@vellumai/assistant 0.8.5 → 0.8.7-dev.202606052118.34cd356

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 (1323) hide show
  1. package/AGENTS.md +33 -1
  2. package/ARCHITECTURE.md +1 -1
  3. package/Dockerfile +21 -4
  4. package/bun.lock +13 -4
  5. package/bunfig.toml +6 -1
  6. package/docker-entrypoint.sh +12 -8
  7. package/docker-init-apt-root.sh +3 -1
  8. package/docker-kata-apt-env.sh +3 -1
  9. package/docker-kata-runtime-family.sh +12 -0
  10. package/docs/architecture/memory.md +1 -1
  11. package/docs/credential-execution-service.md +6 -6
  12. package/docs/plugins.md +114 -86
  13. package/examples/plugins/echo/README.md +13 -12
  14. package/examples/plugins/echo/register.ts +0 -54
  15. package/knip.json +1 -0
  16. package/node_modules/@vellumai/environments/bun.lock +24 -0
  17. package/node_modules/@vellumai/environments/package.json +18 -0
  18. package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +95 -0
  19. package/node_modules/@vellumai/environments/src/index.ts +11 -0
  20. package/node_modules/@vellumai/environments/src/seeds.ts +73 -0
  21. package/node_modules/@vellumai/environments/src/types.ts +70 -0
  22. package/node_modules/@vellumai/environments/tsconfig.json +20 -0
  23. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +11 -0
  24. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +15 -17
  25. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
  26. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +15 -7
  27. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
  28. package/openapi.yaml +8748 -589
  29. package/package.json +8 -4
  30. package/scripts/generate-openapi.ts +88 -54
  31. package/src/__tests__/actor-token-service.test.ts +3 -2
  32. package/src/__tests__/agent-loop-callsite-precedence.test.ts +42 -80
  33. package/src/__tests__/agent-loop-exit-reason.test.ts +284 -48
  34. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +141 -0
  35. package/src/__tests__/agent-loop-override-profile.test.ts +21 -33
  36. package/src/__tests__/agent-loop-provider-error-recording.test.ts +7 -5
  37. package/src/__tests__/agent-loop-thinking.test.ts +17 -12
  38. package/src/__tests__/agent-loop.test.ts +238 -422
  39. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +7 -2
  40. package/src/__tests__/agent-wake-override-profile.test.ts +23 -40
  41. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  42. package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
  43. package/src/__tests__/annotate-risk-options.test.ts +3 -3
  44. package/src/__tests__/anthropic-provider.test.ts +296 -57
  45. package/src/__tests__/app-builder-skill-instructions.test.ts +22 -0
  46. package/src/__tests__/app-control-flow.test.ts +6 -1
  47. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  48. package/src/__tests__/approval-cascade.test.ts +5 -11
  49. package/src/__tests__/approval-routes-http.test.ts +17 -16
  50. package/src/__tests__/assert-not-live-db.ts +79 -0
  51. package/src/__tests__/assistant-event-hub.test.ts +25 -0
  52. package/src/__tests__/assistant-event.test.ts +15 -0
  53. package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
  54. package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -27
  55. package/src/__tests__/assistant-stream-state.test.ts +645 -0
  56. package/src/__tests__/audit-log-rotation.test.ts +2 -2
  57. package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
  58. package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
  59. package/src/__tests__/avatar-e2e.test.ts +7 -37
  60. package/src/__tests__/avatar-generator.test.ts +12 -42
  61. package/src/__tests__/avatar-identity-sync.test.ts +28 -3
  62. package/src/__tests__/background-shell-bash.test.ts +3 -7
  63. package/src/__tests__/background-workers-disk-pressure.test.ts +11 -8
  64. package/src/__tests__/browser-skill-endstate.test.ts +3 -3
  65. package/src/__tests__/btw-routes.test.ts +72 -17
  66. package/src/__tests__/build-persisted-content.test.ts +184 -0
  67. package/src/__tests__/call-controller.test.ts +3 -2
  68. package/src/__tests__/call-pointer-messages.test.ts +5 -3
  69. package/src/__tests__/call-site-routing-provider.test.ts +22 -40
  70. package/src/__tests__/catalog-files.test.ts +1 -0
  71. package/src/__tests__/channel-approval-routes.test.ts +52 -23
  72. package/src/__tests__/channel-approvals.test.ts +4 -2
  73. package/src/__tests__/channel-guardian.test.ts +3 -2
  74. package/src/__tests__/channel-invite-transport.test.ts +1 -5
  75. package/src/__tests__/channel-readiness-routes.test.ts +0 -4
  76. package/src/__tests__/channel-readiness-slack-remote.test.ts +170 -0
  77. package/src/__tests__/channel-reply-delivery.test.ts +35 -0
  78. package/src/__tests__/channel-retry-sweep.test.ts +388 -79
  79. package/src/__tests__/checker.test.ts +12 -12
  80. package/src/__tests__/clawhub-files.test.ts +1 -0
  81. package/src/__tests__/compaction-circuit.test.ts +258 -0
  82. package/src/__tests__/compaction-direct.test.ts +132 -0
  83. package/src/__tests__/compaction-events.test.ts +6 -17
  84. package/src/__tests__/compaction-trail-store.test.ts +186 -0
  85. package/src/__tests__/compaction.benchmark.test.ts +0 -30
  86. package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
  87. package/src/__tests__/compactor-image-manifest-trust.test.ts +112 -0
  88. package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
  89. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
  90. package/src/__tests__/computer-use-tools.test.ts +14 -16
  91. package/src/__tests__/config-loader-backfill.test.ts +13 -28
  92. package/src/__tests__/config-loader-corrupt.test.ts +5 -5
  93. package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
  94. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
  95. package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
  96. package/src/__tests__/config-schema.test.ts +10 -10
  97. package/src/__tests__/config-watcher.test.ts +28 -0
  98. package/src/__tests__/connection-model-compat.test.ts +83 -0
  99. package/src/__tests__/contacts-tools.test.ts +3 -2
  100. package/src/__tests__/context-search-agent-runner.test.ts +6 -3
  101. package/src/__tests__/context-token-estimator.test.ts +56 -0
  102. package/src/__tests__/context-window-manager-compact-retry.test.ts +291 -0
  103. package/src/__tests__/conversation-abort-tool-results.test.ts +73 -23
  104. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +10 -7
  105. package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
  106. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +23 -34
  107. package/src/__tests__/conversation-agent-loop-overflow.test.ts +477 -930
  108. package/src/__tests__/conversation-agent-loop.test.ts +1056 -1068
  109. package/src/__tests__/conversation-analysis-routes.test.ts +8 -3
  110. package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
  111. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -1
  112. package/src/__tests__/conversation-attention-store.test.ts +101 -0
  113. package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
  114. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  115. package/src/__tests__/conversation-clear-safety.test.ts +20 -10
  116. package/src/__tests__/conversation-confirmation-signals.test.ts +16 -45
  117. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
  118. package/src/__tests__/conversation-disk-view.test.ts +10 -17
  119. package/src/__tests__/conversation-error.test.ts +30 -0
  120. package/src/__tests__/conversation-fork-crud.test.ts +132 -157
  121. package/src/__tests__/conversation-fork-route.test.ts +19 -16
  122. package/src/__tests__/conversation-history-web-search.test.ts +12 -1
  123. package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
  124. package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
  125. package/src/__tests__/conversation-init.benchmark.test.ts +6 -6
  126. package/src/__tests__/conversation-lifecycle.test.ts +4 -2
  127. package/src/__tests__/conversation-list-source.test.ts +3 -2
  128. package/src/__tests__/conversation-load-history-repair.test.ts +5 -3
  129. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -1
  130. package/src/__tests__/conversation-message-sync-tags.test.ts +3 -4
  131. package/src/__tests__/conversation-pairing.test.ts +60 -4
  132. package/src/__tests__/conversation-pre-run-repair.test.ts +1 -1
  133. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +36 -7
  134. package/src/__tests__/conversation-process-callsite.test.ts +28 -30
  135. package/src/__tests__/conversation-provider-retry-repair.test.ts +85 -51
  136. package/src/__tests__/conversation-queue.test.ts +605 -455
  137. package/src/__tests__/conversation-routes-disk-view.test.ts +9 -20
  138. package/src/__tests__/conversation-routes-guardian-reply.test.ts +35 -10
  139. package/src/__tests__/conversation-routes-slash-commands.test.ts +40 -8
  140. package/src/__tests__/conversation-runtime-assembly.test.ts +395 -313
  141. package/src/__tests__/conversation-runtime-workspace.test.ts +114 -36
  142. package/src/__tests__/conversation-skill-tools.test.ts +38 -142
  143. package/src/__tests__/conversation-slash-commands.test.ts +8 -42
  144. package/src/__tests__/conversation-slash-queue.test.ts +125 -62
  145. package/src/__tests__/conversation-slash-unknown.test.ts +18 -15
  146. package/src/__tests__/conversation-speed-override.test.ts +9 -22
  147. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  148. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +136 -15
  149. package/src/__tests__/conversation-surfaces-app-control.test.ts +32 -4
  150. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
  151. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
  152. package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
  153. package/src/__tests__/conversation-surfaces-state-update.test.ts +8 -5
  154. package/src/__tests__/conversation-surfaces-table-action.test.ts +13 -32
  155. package/src/__tests__/conversation-sync-tags.test.ts +140 -12
  156. package/src/__tests__/conversation-title-service.test.ts +136 -2
  157. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +53 -11
  158. package/src/__tests__/conversation-unread-route.test.ts +14 -2
  159. package/src/__tests__/conversation-usage.test.ts +1 -2
  160. package/src/__tests__/conversation-wipe.test.ts +1 -1
  161. package/src/__tests__/conversation-workspace-cache-state.test.ts +21 -17
  162. package/src/__tests__/conversation-workspace-injection.test.ts +119 -23
  163. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +39 -13
  164. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  165. package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
  166. package/src/__tests__/credential-broker-server-use.test.ts +5 -5
  167. package/src/__tests__/credential-execution-client.test.ts +72 -1
  168. package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
  169. package/src/__tests__/credential-execution-tools.test.ts +1 -2
  170. package/src/__tests__/credential-health-service.test.ts +252 -3
  171. package/src/__tests__/credential-security-invariants.test.ts +5 -6
  172. package/src/__tests__/credential-vault-unit.test.ts +19 -19
  173. package/src/__tests__/credential-vault.test.ts +5 -5
  174. package/src/__tests__/cross-provider-web-search.test.ts +275 -4
  175. package/src/__tests__/cu-unified-flow.test.ts +26 -1
  176. package/src/__tests__/db-acp-history.test.ts +101 -0
  177. package/src/__tests__/db-connection-isolation.test.ts +7 -6
  178. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
  179. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
  180. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
  181. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -0
  182. package/src/__tests__/db-test-helpers.ts +58 -0
  183. package/src/__tests__/disk-pressure-guard.test.ts +119 -36
  184. package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
  185. package/src/__tests__/disk-pressure-routes.test.ts +9 -35
  186. package/src/__tests__/disk-pressure-tools.test.ts +0 -4
  187. package/src/__tests__/dm-persistence.test.ts +38 -43
  188. package/src/__tests__/document-create-dedupe.test.ts +189 -0
  189. package/src/__tests__/document-find-replace.test.ts +3 -2
  190. package/src/__tests__/document-tool-security.test.ts +81 -2
  191. package/src/__tests__/dynamic-page-surface.test.ts +99 -0
  192. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
  193. package/src/__tests__/edit-propagation.test.ts +1 -2
  194. package/src/__tests__/empty-response-hook.test.ts +304 -0
  195. package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
  196. package/src/__tests__/encrypted-store.test.ts +11 -9
  197. package/src/__tests__/feature-flag-test-helpers.ts +53 -0
  198. package/src/__tests__/file-write-tool.test.ts +63 -0
  199. package/src/__tests__/filing-service.test.ts +3 -2
  200. package/src/__tests__/first-greeting.test.ts +103 -12
  201. package/src/__tests__/gateway-flag-listener.test.ts +0 -1
  202. package/src/__tests__/gemini-image-service.test.ts +13 -0
  203. package/src/__tests__/gemini-inline-media.test.ts +78 -0
  204. package/src/__tests__/gemini-provider.test.ts +375 -26
  205. package/src/__tests__/guardian-action-sweep.test.ts +3 -2
  206. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  207. package/src/__tests__/guardian-outbound-http.test.ts +3 -2
  208. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  209. package/src/__tests__/guardian-routing-state.test.ts +60 -71
  210. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
  211. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +11 -8
  212. package/src/__tests__/heartbeat-disk-pressure.test.ts +3 -0
  213. package/src/__tests__/heartbeat-service.test.ts +4 -1
  214. package/src/__tests__/helpers/mock-logger.ts +26 -0
  215. package/src/__tests__/helpers/mock-provider.ts +110 -0
  216. package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
  217. package/src/__tests__/history-repair-hook.test.ts +162 -0
  218. package/src/__tests__/history-repair-observability.test.ts +1 -1
  219. package/src/__tests__/history-repair.test.ts +2 -1
  220. package/src/__tests__/host-app-control-proxy.test.ts +2 -0
  221. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  222. package/src/__tests__/host-bash-routes.test.ts +1 -0
  223. package/src/__tests__/host-cu-proxy.test.ts +2 -0
  224. package/src/__tests__/host-cu-routes-targeted.test.ts +4 -3
  225. package/src/__tests__/host-file-edit-tool.test.ts +4 -2
  226. package/src/__tests__/host-file-proxy.test.ts +31 -0
  227. package/src/__tests__/host-file-read-tool.test.ts +4 -2
  228. package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
  229. package/src/__tests__/host-file-write-tool.test.ts +9 -3
  230. package/src/__tests__/host-proxy-preactivation.test.ts +53 -14
  231. package/src/__tests__/host-shell-tool.test.ts +11 -5
  232. package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
  233. package/src/__tests__/http-conversation-lineage.test.ts +3 -2
  234. package/src/__tests__/http-user-message-parity.test.ts +31 -9
  235. package/src/__tests__/identity-intro-cache.test.ts +140 -96
  236. package/src/__tests__/identity-routes.test.ts +248 -7
  237. package/src/__tests__/inbound-slack-persistence.test.ts +56 -75
  238. package/src/__tests__/inference-profile-reaper.test.ts +3 -2
  239. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
  240. package/src/__tests__/injector-background-turn.test.ts +3 -9
  241. package/src/__tests__/injector-chain.test.ts +139 -275
  242. package/src/__tests__/injector-disk-pressure.test.ts +78 -58
  243. package/src/__tests__/injector-document-comments.test.ts +3 -3
  244. package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
  245. package/src/__tests__/injector-v3-suppression.test.ts +214 -0
  246. package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
  247. package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
  248. package/src/__tests__/list-messages-attachments.test.ts +7 -8
  249. package/src/__tests__/list-messages-hidden-metadata.test.ts +131 -11
  250. package/src/__tests__/list-messages-page-latest.test.ts +60 -1
  251. package/src/__tests__/list-messages-tool-merge.test.ts +56 -6
  252. package/src/__tests__/llm-context-normalization.test.ts +42 -0
  253. package/src/__tests__/llm-request-log-turn-query.test.ts +42 -86
  254. package/src/__tests__/llm-resolver.test.ts +346 -39
  255. package/src/__tests__/llm-schema.test.ts +1 -1
  256. package/src/__tests__/llm-usage-store.test.ts +268 -1
  257. package/src/__tests__/log-export-routes.test.ts +59 -0
  258. package/src/__tests__/managed-skill-lifecycle.test.ts +1 -8
  259. package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
  260. package/src/__tests__/mcp-abort-signal.test.ts +14 -0
  261. package/src/__tests__/mcp-auth-routes.test.ts +15 -10
  262. package/src/__tests__/mcp-client-auth.test.ts +14 -0
  263. package/src/__tests__/mcp-health-check.test.ts +18 -13
  264. package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
  265. package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
  266. package/src/__tests__/messaging-send-tool.test.ts +9 -4
  267. package/src/__tests__/migration-export-http.test.ts +12 -12
  268. package/src/__tests__/migration-import-commit-http.test.ts +8 -8
  269. package/src/__tests__/migration-import-from-url.test.ts +3 -3
  270. package/src/__tests__/migration-import-preflight-http.test.ts +7 -7
  271. package/src/__tests__/migration-validate-http.test.ts +3 -3
  272. package/src/__tests__/mock-gateway-ipc.ts +18 -2
  273. package/src/__tests__/model-intents.test.ts +3 -3
  274. package/src/__tests__/native-web-search.test.ts +235 -22
  275. package/src/__tests__/notification-decision-identity.test.ts +9 -18
  276. package/src/__tests__/notification-decision-recipient-context.test.ts +3 -6
  277. package/src/__tests__/notification-deep-link.test.ts +62 -0
  278. package/src/__tests__/oauth-commands-routes.test.ts +38 -1
  279. package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
  280. package/src/__tests__/oauth-store.test.ts +3 -2
  281. package/src/__tests__/onboarding-template-contract.test.ts +15 -2
  282. package/src/__tests__/openai-image-service.test.ts +17 -0
  283. package/src/__tests__/openai-provider.test.ts +105 -80
  284. package/src/__tests__/openai-responses-provider.test.ts +90 -86
  285. package/src/__tests__/openrouter-provider-only.test.ts +27 -5
  286. package/src/__tests__/outbound-slack-persistence.test.ts +48 -2
  287. package/src/__tests__/{overflow-reduce-pipeline.test.ts → overflow-reduction-loop.test.ts} +64 -286
  288. package/src/__tests__/parallel-tool.benchmark.test.ts +24 -36
  289. package/src/__tests__/persist-unsendable-image.test.ts +215 -0
  290. package/src/__tests__/persistence-secret-redaction.test.ts +86 -13
  291. package/src/__tests__/pipeline-runner.test.ts +31 -43
  292. package/src/__tests__/pkb-autoinject.test.ts +2 -5
  293. package/src/__tests__/plugin-bootstrap.test.ts +72 -63
  294. package/src/__tests__/plugin-registry.test.ts +0 -27
  295. package/src/__tests__/plugin-route-contribution.test.ts +6 -16
  296. package/src/__tests__/plugin-skill-contribution.test.ts +7 -17
  297. package/src/__tests__/plugin-tool-contribution.test.ts +51 -64
  298. package/src/__tests__/plugin-types.test.ts +8 -173
  299. package/src/__tests__/prechat-onboarding-contract.test.ts +23 -0
  300. package/src/__tests__/process-message-background-slack.test.ts +38 -32
  301. package/src/__tests__/process-message-display-content.test.ts +55 -66
  302. package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
  303. package/src/__tests__/provider-commit-message-generator.test.ts +19 -14
  304. package/src/__tests__/provider-error-scenarios.test.ts +7 -6
  305. package/src/__tests__/provider-platform-proxy-integration.test.ts +215 -8
  306. package/src/__tests__/provider-registry-ollama.test.ts +45 -22
  307. package/src/__tests__/provider-send-message-override-profile.test.ts +9 -25
  308. package/src/__tests__/provider-streaming.benchmark.test.ts +12 -22
  309. package/src/__tests__/provider-usage-tracking.test.ts +0 -6
  310. package/src/__tests__/ratelimit.test.ts +9 -4
  311. package/src/__tests__/reaction-persistence.test.ts +1 -1
  312. package/src/__tests__/recording-handler.test.ts +1 -0
  313. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +6 -1
  314. package/src/__tests__/registry.test.ts +82 -76
  315. package/src/__tests__/relay-server.test.ts +30 -23
  316. package/src/__tests__/resolve-trust-class.test.ts +4 -4
  317. package/src/__tests__/retry-openrouter-only-normalization.test.ts +5 -8
  318. package/src/__tests__/retry-thinking-tool-choice.test.ts +10 -13
  319. package/src/__tests__/retry-verbosity-normalization.test.ts +5 -8
  320. package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
  321. package/src/__tests__/runtime-events-sse-reconnect.test.ts +390 -0
  322. package/src/__tests__/schedule-routes.test.ts +683 -12
  323. package/src/__tests__/schedule-store.test.ts +124 -1
  324. package/src/__tests__/schedule-tools.test.ts +160 -0
  325. package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
  326. package/src/__tests__/secret-ingress-http.test.ts +7 -3
  327. package/src/__tests__/secret-prompt-log-hygiene.test.ts +11 -7
  328. package/src/__tests__/secret-prompter-channel-fallback.test.ts +11 -9
  329. package/src/__tests__/secret-response-routing.test.ts +13 -11
  330. package/src/__tests__/secure-keys.test.ts +3 -3
  331. package/src/__tests__/send-endpoint-busy.test.ts +87 -44
  332. package/src/__tests__/server-history-render.test.ts +318 -2
  333. package/src/__tests__/shell-observability.test.ts +249 -0
  334. package/src/__tests__/skill-feature-flags-integration.test.ts +52 -21
  335. package/src/__tests__/skill-feature-flags.test.ts +20 -22
  336. package/src/__tests__/skill-load-feature-flag.test.ts +15 -15
  337. package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
  338. package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
  339. package/src/__tests__/skill-tool-factory.test.ts +96 -95
  340. package/src/__tests__/skills-files-catalog-fallback.test.ts +10 -0
  341. package/src/__tests__/skillssh-files.test.ts +1 -0
  342. package/src/__tests__/slack-channel-config.test.ts +3 -3
  343. package/src/__tests__/starter-task-flow.test.ts +6 -6
  344. package/src/__tests__/strip-memory-injections.test.ts +102 -14
  345. package/src/__tests__/subagent-call-site-routing.test.ts +14 -6
  346. package/src/__tests__/subagent-disposal.test.ts +27 -8
  347. package/src/__tests__/subagent-fork-notifications.test.ts +25 -12
  348. package/src/__tests__/subagent-fork-spawn.test.ts +14 -5
  349. package/src/__tests__/subagent-manager-notify.test.ts +21 -11
  350. package/src/__tests__/subagent-notify-parent.test.ts +6 -7
  351. package/src/__tests__/subagent-spawn-tool-fork.test.ts +59 -1
  352. package/src/__tests__/subagent-tools.test.ts +2 -1
  353. package/src/__tests__/suggestion-routes.test.ts +4 -3
  354. package/src/__tests__/sync-message-contract.test.ts +19 -16
  355. package/src/__tests__/system-prompt.test.ts +112 -0
  356. package/src/__tests__/task-scheduler.test.ts +162 -1
  357. package/src/__tests__/terminal-tools.test.ts +9 -25
  358. package/src/__tests__/test-preload-verifier.ts +68 -0
  359. package/src/__tests__/test-preload.ts +32 -39
  360. package/src/__tests__/thread-backfill.test.ts +4 -9
  361. package/src/__tests__/title-generate-hook.test.ts +319 -0
  362. package/src/__tests__/tool-error-hook.test.ts +278 -0
  363. package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
  364. package/src/__tests__/tool-executor.test.ts +55 -10
  365. package/src/__tests__/tool-preview-lifecycle.test.ts +481 -15
  366. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  367. package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
  368. package/src/__tests__/tool-result-truncation.test.ts +1 -1
  369. package/src/__tests__/tools-audio-read.test.ts +113 -0
  370. package/src/__tests__/turn-boundary-resolution.test.ts +44 -84
  371. package/src/__tests__/turn-events-store.test.ts +11 -7
  372. package/src/__tests__/twilio-routes.test.ts +3 -2
  373. package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
  374. package/src/__tests__/ui-work-result-surface.test.ts +159 -0
  375. package/src/__tests__/usage-routes.test.ts +285 -1
  376. package/src/__tests__/user-plugin-loader.test.ts +2 -2
  377. package/src/__tests__/validate-input.test.ts +381 -0
  378. package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
  379. package/src/__tests__/voice-scoped-grant-consumer.test.ts +10 -7
  380. package/src/__tests__/voice-session-bridge.test.ts +56 -38
  381. package/src/__tests__/web-search-backend-failure.test.ts +166 -0
  382. package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
  383. package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
  384. package/src/acp/__tests__/agent-process.test.ts +161 -0
  385. package/src/acp/__tests__/client-handler.test.ts +40 -0
  386. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  387. package/src/acp/__tests__/helpers/exec-file-stub.ts +101 -0
  388. package/src/acp/__tests__/prepare-agent-env.test.ts +143 -31
  389. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  390. package/src/acp/__tests__/session-manager-resume.test.ts +695 -0
  391. package/src/acp/agent-process.ts +61 -1
  392. package/src/acp/auto-install.test.ts +125 -0
  393. package/src/acp/auto-install.ts +174 -0
  394. package/src/acp/client-handler.ts +31 -0
  395. package/src/acp/feature-gate.test.ts +48 -0
  396. package/src/acp/feature-gate.ts +34 -0
  397. package/src/acp/prepare-agent-env.ts +52 -11
  398. package/src/acp/resolve-agent.test.ts +147 -6
  399. package/src/acp/resolve-agent.ts +81 -7
  400. package/src/acp/resume-hint.ts +22 -0
  401. package/src/acp/session-manager.ts +492 -77
  402. package/src/agent/compaction-circuit.ts +98 -0
  403. package/src/agent/loop.ts +730 -449
  404. package/src/api/README.md +126 -2
  405. package/src/api/constants/call-sites.ts +27 -0
  406. package/src/api/constants/tool-execution.ts +21 -0
  407. package/src/api/events/assistant-activity-state.ts +75 -0
  408. package/src/api/events/assistant-outbound-attachment.ts +49 -0
  409. package/src/api/events/assistant-text-delta.ts +30 -0
  410. package/src/api/events/assistant-thinking-delta.ts +33 -0
  411. package/src/api/events/assistant-turn-start.ts +31 -0
  412. package/src/api/events/avatar-updated.ts +24 -0
  413. package/src/api/events/compaction-circuit-closed.ts +26 -0
  414. package/src/api/events/compaction-circuit-open.ts +28 -0
  415. package/src/api/events/confirmation-request.ts +114 -0
  416. package/src/api/events/contact-request.ts +33 -0
  417. package/src/api/events/conversation-error.ts +77 -0
  418. package/src/api/events/conversation-list-invalidated.ts +38 -0
  419. package/src/api/events/conversation-title-updated.ts +24 -0
  420. package/src/api/events/disk-pressure-status-changed.ts +61 -0
  421. package/src/api/events/document-comment-created.ts +44 -0
  422. package/src/api/events/document-comment-deleted.ts +22 -0
  423. package/src/api/events/document-comment-reopened.ts +23 -0
  424. package/src/api/events/document-comment-resolved.ts +25 -0
  425. package/src/api/events/document-editor-update.ts +27 -0
  426. package/src/api/events/error.ts +32 -0
  427. package/src/api/events/generation-cancelled.ts +22 -0
  428. package/src/api/events/generation-handoff.ts +39 -0
  429. package/src/api/events/home-feed-updated.ts +26 -0
  430. package/src/api/events/identity-changed.ts +32 -0
  431. package/src/api/events/interaction-resolved.ts +50 -0
  432. package/src/api/events/message-complete.ts +40 -0
  433. package/src/api/events/message-dequeued.ts +21 -0
  434. package/src/api/events/message-queued-deleted.ts +23 -0
  435. package/src/api/events/message-queued.ts +22 -0
  436. package/src/api/events/message-request-complete.ts +29 -0
  437. package/src/api/events/navigate-settings.ts +20 -0
  438. package/src/api/events/notification-intent.ts +33 -0
  439. package/src/api/events/open-url.ts +28 -0
  440. package/src/api/events/question-request.ts +67 -0
  441. package/src/{events → api/events}/relationship-state-updated.ts +6 -8
  442. package/src/api/events/secret-request.ts +42 -0
  443. package/src/api/events/subagent-event.ts +79 -0
  444. package/src/api/events/subagent-spawned.ts +40 -0
  445. package/src/api/events/subagent-status-changed.ts +65 -0
  446. package/src/api/events/sync-changed.ts +29 -0
  447. package/src/api/events/tool-output-chunk.ts +45 -0
  448. package/src/api/events/tool-result.ts +129 -0
  449. package/src/api/events/tool-use-preview-start.ts +32 -0
  450. package/src/api/events/tool-use-start.ts +30 -0
  451. package/src/api/events/trace-event.ts +69 -0
  452. package/src/api/events/turn-profile-auto-routed.ts +28 -0
  453. package/src/api/events/ui-surface-complete.ts +30 -0
  454. package/src/api/events/ui-surface-dismiss.ts +22 -0
  455. package/src/api/events/ui-surface-show.ts +67 -0
  456. package/src/api/events/ui-surface-update.ts +26 -0
  457. package/src/api/events/usage-update.ts +34 -0
  458. package/src/api/events/user-message-echo.ts +35 -0
  459. package/src/api/index.ts +517 -3
  460. package/src/api/requests/dictation.ts +45 -0
  461. package/src/api/responses/conversation-message.ts +374 -0
  462. package/src/api/responses/disk-pressure-status.ts +26 -0
  463. package/src/api/responses/home.ts +217 -0
  464. package/src/api/responses/llm-context-response.ts +41 -0
  465. package/src/api/responses/llm-request-log-entry.ts +93 -0
  466. package/src/api/responses/memory-recall-log.ts +65 -0
  467. package/src/api/responses/memory-v2-activation-log.ts +78 -0
  468. package/src/api/responses/memory-v3-selection-log.ts +50 -0
  469. package/src/api/responses/subagent-detail.ts +48 -0
  470. package/src/approvals/guardian-decision-primitive.ts +7 -15
  471. package/src/approvals/guardian-request-resolvers.ts +7 -10
  472. package/src/avatar/__tests__/avatar-manifest.test.ts +236 -0
  473. package/src/avatar/__tests__/avatar-store.test.ts +198 -0
  474. package/src/avatar/avatar-manifest.ts +195 -0
  475. package/src/avatar/avatar-store.ts +113 -0
  476. package/src/avatar/traits-png-sync.ts +8 -2
  477. package/src/background-wake/background-wake-routes.test.ts +687 -52
  478. package/src/background-wake/next-wake.test.ts +31 -1
  479. package/src/background-wake/next-wake.ts +5 -1
  480. package/src/background-wake/platform-client.test.ts +308 -0
  481. package/src/background-wake/platform-client.ts +167 -0
  482. package/src/background-wake/publisher.ts +91 -0
  483. package/src/background-wake/runtime-registry.ts +2 -2
  484. package/src/background-wake/wake-intent-hooks.test.ts +282 -0
  485. package/src/calls/call-conversation-messages.ts +6 -4
  486. package/src/calls/guardian-action-sweep.ts +6 -4
  487. package/src/calls/guardian-dispatch.ts +1 -0
  488. package/src/calls/relay-server.ts +12 -8
  489. package/src/calls/voice-session-bridge.ts +17 -31
  490. package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
  491. package/src/cli/commands/__tests__/memory-v3.test.ts +245 -0
  492. package/src/cli/commands/__tests__/notifications.test.ts +242 -54
  493. package/src/cli/commands/avatar.ts +17 -11
  494. package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
  495. package/src/cli/commands/channels/index.ts +229 -0
  496. package/src/cli/commands/conversations.ts +15 -1
  497. package/src/cli/commands/db/__tests__/repair.test.ts +540 -0
  498. package/src/cli/commands/db/__tests__/status.test.ts +253 -0
  499. package/src/cli/commands/db/format.ts +48 -0
  500. package/src/cli/commands/db/index.ts +29 -0
  501. package/src/cli/commands/db/repair-step-conversation-backfill.ts +345 -0
  502. package/src/cli/commands/db/repair-step-integrity.ts +146 -0
  503. package/src/cli/commands/db/repair-steps.ts +164 -0
  504. package/src/cli/commands/db/repair.ts +141 -0
  505. package/src/cli/commands/db/status.ts +366 -0
  506. package/src/cli/commands/memory-v3.ts +168 -203
  507. package/src/cli/commands/notifications.ts +427 -65
  508. package/src/cli/lib/cli-colors.ts +24 -6
  509. package/src/cli/lib/open-browser.ts +7 -2
  510. package/src/cli/program.ts +6 -5
  511. package/src/config/__tests__/feature-flag-registry-guard.test.ts +4 -4
  512. package/src/config/acp-defaults.test.ts +10 -0
  513. package/src/config/acp-defaults.ts +6 -0
  514. package/src/config/assistant-feature-flags.ts +47 -55
  515. package/src/config/bundled-skills/acp/SKILL.md +64 -30
  516. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  517. package/src/config/bundled-skills/app-builder/SKILL.md +224 -387
  518. package/src/config/bundled-skills/app-builder/TOOLS.json +29 -0
  519. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  520. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  521. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  522. package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
  523. package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
  524. package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
  525. package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
  526. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  527. package/src/config/bundled-skills/document-editor/SKILL.md +33 -24
  528. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  529. package/src/config/bundled-skills/media-processing/services/reduce.ts +6 -9
  530. package/src/config/bundled-skills/messaging/SKILL.md +0 -7
  531. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +7 -2
  532. package/src/config/bundled-skills/schedule/SKILL.md +2 -2
  533. package/src/config/bundled-skills/schedule/TOOLS.json +10 -2
  534. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
  535. package/src/config/bundled-tool-registry.ts +2 -0
  536. package/src/config/call-site-defaults.ts +3 -8
  537. package/src/config/feature-flag-cache.ts +86 -0
  538. package/src/config/feature-flag-registry.json +81 -25
  539. package/src/config/llm-context-resolution.ts +10 -1
  540. package/src/config/llm-resolver.ts +121 -15
  541. package/src/config/loader.ts +4 -5
  542. package/src/config/schemas/__tests__/memory-v2.test.ts +2 -211
  543. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
  544. package/src/config/schemas/call-site-catalog.ts +8 -15
  545. package/src/config/schemas/heartbeat.ts +10 -1
  546. package/src/config/schemas/llm.ts +93 -4
  547. package/src/config/schemas/memory-lifecycle.ts +24 -0
  548. package/src/config/schemas/memory-v2.ts +8 -227
  549. package/src/config/schemas/memory-v3.ts +47 -0
  550. package/src/config/schemas/memory.ts +6 -1
  551. package/src/config/schemas/platform.ts +8 -0
  552. package/src/config/schemas/services.ts +6 -2
  553. package/src/config/schemas/timeouts.ts +3 -1
  554. package/src/config/seed-inference-profiles.ts +38 -18
  555. package/src/config/skills.ts +13 -0
  556. package/src/context/compactor.ts +55 -32
  557. package/src/context/strip-injections.ts +128 -0
  558. package/src/context/token-estimator.ts +52 -5
  559. package/src/context/tool-result-truncation.ts +1 -66
  560. package/src/context/window-manager.ts +141 -26
  561. package/src/credential-execution/executable-discovery.ts +56 -0
  562. package/src/credential-execution/process-manager.ts +6 -2
  563. package/src/credential-health/credential-health-service.ts +125 -40
  564. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +9 -6
  565. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +15 -17
  566. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
  567. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
  568. package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
  569. package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
  570. package/src/daemon/__tests__/native-web-search-metadata.test.ts +11 -8
  571. package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
  572. package/src/daemon/__tests__/web-search-status-text.test.ts +11 -6
  573. package/src/daemon/approval-generators.ts +4 -4
  574. package/src/daemon/assistant-attachments.ts +1 -1
  575. package/src/daemon/config-watcher.ts +7 -1
  576. package/src/daemon/context-overflow-reducer.ts +0 -1
  577. package/src/daemon/conversation-agent-loop-handlers.ts +1146 -247
  578. package/src/daemon/conversation-agent-loop.ts +570 -1457
  579. package/src/daemon/conversation-error.ts +40 -12
  580. package/src/daemon/conversation-history.ts +27 -10
  581. package/src/daemon/conversation-launch.ts +4 -8
  582. package/src/daemon/conversation-lifecycle.ts +13 -42
  583. package/src/daemon/conversation-messaging.ts +89 -49
  584. package/src/daemon/conversation-notifiers.ts +7 -5
  585. package/src/daemon/conversation-process.ts +183 -130
  586. package/src/daemon/conversation-registry.ts +159 -0
  587. package/src/daemon/conversation-runtime-assembly.ts +209 -362
  588. package/src/daemon/conversation-skill-tools.ts +14 -30
  589. package/src/daemon/conversation-slash.ts +6 -25
  590. package/src/daemon/conversation-store.ts +15 -95
  591. package/src/daemon/conversation-surfaces.ts +346 -107
  592. package/src/daemon/conversation-tool-setup.ts +38 -77
  593. package/src/daemon/conversation-workspace.ts +17 -0
  594. package/src/daemon/conversation.ts +143 -186
  595. package/src/daemon/daemon-control.ts +1 -1
  596. package/src/daemon/daemon-skill-host.ts +7 -4
  597. package/src/daemon/disk-pressure-guard.ts +54 -50
  598. package/src/daemon/external-plugins-bootstrap.ts +53 -32
  599. package/src/daemon/first-greeting.ts +53 -13
  600. package/src/daemon/guardian-action-generators.ts +2 -2
  601. package/src/daemon/handlers/config-a2a.ts +51 -36
  602. package/src/daemon/handlers/config-slack-channel.ts +20 -14
  603. package/src/daemon/handlers/config-telegram.ts +16 -2
  604. package/src/daemon/handlers/conversations.ts +9 -23
  605. package/src/daemon/handlers/shared.ts +158 -77
  606. package/src/daemon/handlers/skills.ts +53 -20
  607. package/src/daemon/host-app-control-proxy.ts +54 -1
  608. package/src/daemon/host-cu-proxy.ts +46 -22
  609. package/src/daemon/host-file-proxy.ts +25 -1
  610. package/src/daemon/host-proxy-preactivation.ts +25 -6
  611. package/src/daemon/lifecycle.ts +65 -67
  612. package/src/daemon/mcp-reload-service.ts +1 -1
  613. package/src/daemon/meet-manifest-loader.ts +10 -17
  614. package/src/daemon/message-protocol.ts +2 -3
  615. package/src/daemon/message-provenance.ts +49 -0
  616. package/src/daemon/message-types/apps.ts +1 -29
  617. package/src/daemon/message-types/contacts.ts +3 -20
  618. package/src/daemon/message-types/conversations.ts +25 -125
  619. package/src/daemon/message-types/document-comments.ts +8 -44
  620. package/src/daemon/message-types/documents.ts +3 -9
  621. package/src/daemon/message-types/home.ts +5 -18
  622. package/src/daemon/message-types/integrations.ts +4 -13
  623. package/src/daemon/message-types/messages.ts +50 -428
  624. package/src/daemon/message-types/notifications.ts +2 -32
  625. package/src/daemon/message-types/settings.ts +3 -8
  626. package/src/daemon/message-types/skills.ts +4 -0
  627. package/src/daemon/message-types/subagents.ts +6 -0
  628. package/src/daemon/message-types/surfaces.ts +138 -3
  629. package/src/daemon/message-types/sync.ts +12 -25
  630. package/src/daemon/message-types/workspace.ts +3 -11
  631. package/src/daemon/now-scratchpad.ts +21 -0
  632. package/src/daemon/orphan-reaper.test.ts +210 -0
  633. package/src/daemon/orphan-reaper.ts +240 -0
  634. package/src/daemon/overflow-reduction-loop.ts +230 -0
  635. package/src/daemon/persist-unsendable-image.ts +117 -0
  636. package/src/daemon/process-message.ts +59 -58
  637. package/src/daemon/providers-setup.ts +1 -1
  638. package/src/daemon/server.ts +30 -0
  639. package/src/daemon/switch-inference-profile-tool.ts +13 -3
  640. package/src/daemon/tool-setup-types.ts +0 -6
  641. package/src/daemon/tool-side-effects.ts +10 -7
  642. package/src/daemon/trace-emitter.ts +6 -4
  643. package/src/daemon/trust-context.ts +32 -0
  644. package/src/daemon/wake-target-adapter.ts +24 -2
  645. package/src/documents/document-store.ts +38 -0
  646. package/src/export/__tests__/transcript-formatter.test.ts +1 -0
  647. package/src/heartbeat/__tests__/heartbeat-service.test.ts +34 -0
  648. package/src/heartbeat/heartbeat-run-store.ts +54 -1
  649. package/src/heartbeat/heartbeat-service.ts +105 -0
  650. package/src/home/__tests__/feed-writer.test.ts +161 -0
  651. package/src/home/__tests__/post-connect-feed.test.ts +1 -0
  652. package/src/home/__tests__/suggested-prompts.test.ts +55 -59
  653. package/src/home/feed-types.ts +36 -221
  654. package/src/home/feed-writer.ts +146 -7
  655. package/src/home/home-greeting-cache.ts +24 -1
  656. package/src/home/suggested-prompts.ts +27 -145
  657. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  658. package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
  659. package/src/ipc/__tests__/email-ipc.test.ts +0 -9
  660. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  661. package/src/ipc/gateway-client.test.ts +6 -3
  662. package/src/ipc/gateway-client.ts +3 -3
  663. package/src/ipc/routes/__tests__/route-adapter.test.ts +244 -0
  664. package/src/ipc/routes/route-adapter.ts +45 -6
  665. package/src/ipc/skill-routes/__tests__/memory.test.ts +34 -9
  666. package/src/ipc/skill-routes/__tests__/providers.test.ts +10 -10
  667. package/src/ipc/skill-routes/__tests__/registries.test.ts +59 -20
  668. package/src/ipc/skill-routes/memory.ts +29 -13
  669. package/src/ipc/skill-routes/providers.ts +5 -6
  670. package/src/ipc/skill-routes/registries.ts +39 -88
  671. package/src/live-voice/__tests__/live-voice-archive.test.ts +24 -11
  672. package/src/media/gemini-image-service.ts +15 -0
  673. package/src/media/openai-image-service.ts +14 -0
  674. package/src/media/types.ts +34 -0
  675. package/src/memory/__tests__/conversation-queries.test.ts +192 -8
  676. package/src/memory/__tests__/db-maintenance.test.ts +128 -0
  677. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
  678. package/src/memory/__tests__/jobs-store-job-classes.test.ts +5 -4
  679. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +82 -5
  680. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
  681. package/src/memory/__tests__/memory-retrospective-job.test.ts +11 -6
  682. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
  683. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
  684. package/src/memory/__tests__/memory-v3-selections-migration.test.ts +103 -0
  685. package/src/memory/auth-fallback-events-store.ts +94 -0
  686. package/src/memory/context-search/agent-runner.ts +2 -4
  687. package/src/memory/conversation-attention-store.ts +17 -3
  688. package/src/memory/conversation-crud.ts +386 -115
  689. package/src/memory/conversation-queries.ts +78 -22
  690. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  691. package/src/memory/conversation-title-service.ts +65 -41
  692. package/src/memory/db-connection.ts +29 -19
  693. package/src/memory/db-init.ts +18 -0
  694. package/src/memory/db-maintenance.ts +18 -2
  695. package/src/memory/db-singleton.ts +77 -0
  696. package/src/memory/delivery-channels.ts +82 -0
  697. package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
  698. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
  699. package/src/memory/graph/consolidation.ts +8 -11
  700. package/src/memory/graph/conversation-graph-memory.ts +106 -8
  701. package/src/memory/graph/extraction.ts +6 -9
  702. package/src/memory/graph/narrative.ts +2 -2
  703. package/src/memory/graph/pattern-scan.ts +2 -2
  704. package/src/memory/graph/retriever.test.ts +3 -3
  705. package/src/memory/graph/retriever.ts +20 -26
  706. package/src/memory/graph/tools.ts +4 -4
  707. package/src/memory/job-handlers/conversation-starters.ts +45 -34
  708. package/src/memory/job-handlers/embedding.test.ts +3 -2
  709. package/src/memory/job-handlers/summarization.ts +1 -2
  710. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
  711. package/src/memory/jobs-store.ts +36 -1
  712. package/src/memory/jobs-worker.ts +93 -43
  713. package/src/memory/llm-request-log-source-clickhouse.ts +55 -1
  714. package/src/memory/llm-request-log-source-local.ts +13 -0
  715. package/src/memory/llm-request-log-source.ts +21 -6
  716. package/src/memory/llm-request-log-store.ts +147 -3
  717. package/src/memory/llm-usage-store.ts +234 -50
  718. package/src/memory/memory-marker.ts +17 -0
  719. package/src/memory/memory-retrospective-job.ts +6 -2
  720. package/src/memory/memory-v2-activation-log-store.ts +13 -1
  721. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
  722. package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
  723. package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
  724. package/src/memory/migrations/267-llm-usage-events-add-assistant-version.ts +46 -0
  725. package/src/memory/migrations/268-add-memory-v3-selections.ts +28 -0
  726. package/src/memory/migrations/269-schedule-script-timeout.ts +11 -0
  727. package/src/memory/migrations/270-messages-role-created-at-index.ts +18 -0
  728. package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
  729. package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
  730. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  731. package/src/memory/migrations/__tests__/267-llm-usage-events-add-assistant-version.test.ts +117 -0
  732. package/src/memory/migrations/index.ts +9 -0
  733. package/src/memory/pkb/autoinject.ts +61 -0
  734. package/src/memory/pkb/context.ts +50 -0
  735. package/src/memory/pkb/types.ts +14 -0
  736. package/src/memory/schedule-attribution-sql.ts +104 -0
  737. package/src/memory/schema/acp.ts +4 -0
  738. package/src/memory/schema/conversations.ts +9 -1
  739. package/src/memory/schema/inference.ts +0 -1
  740. package/src/memory/schema/infrastructure.ts +27 -0
  741. package/src/memory/usage-grouped-buckets.ts +6 -1
  742. package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
  743. package/src/memory/v2/__tests__/consolidation-job.test.ts +125 -1
  744. package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
  745. package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
  746. package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
  747. package/src/memory/v2/__tests__/migration.test.ts +11 -3
  748. package/src/memory/v2/__tests__/page-index.test.ts +37 -1
  749. package/src/memory/v2/__tests__/router.test.ts +14 -4
  750. package/src/memory/v2/__tests__/sweep-job.test.ts +9 -5
  751. package/src/memory/v2/backfill-jobs.ts +6 -0
  752. package/src/memory/v2/consolidation-job.ts +99 -10
  753. package/src/memory/v2/harness/metrics.ts +5 -1
  754. package/src/memory/v2/harness/replay-input.ts +19 -3
  755. package/src/memory/v2/harness/runner.ts +6 -0
  756. package/src/memory/v2/harness/trace.ts +6 -0
  757. package/src/memory/v2/migration.ts +5 -3
  758. package/src/memory/v2/page-index.ts +11 -0
  759. package/src/memory/v2/router.ts +8 -11
  760. package/src/memory/v2/sweep-job.ts +8 -11
  761. package/src/memory/v2/types.ts +1 -0
  762. package/src/messaging/providers/slack/render-transcript.test.ts +1 -1
  763. package/src/messaging/providers/slack/render-transcript.ts +2 -2
  764. package/src/messaging/style-analyzer.ts +8 -11
  765. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  766. package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
  767. package/src/notifications/adapters/slack.ts +45 -11
  768. package/src/notifications/broadcaster.ts +114 -63
  769. package/src/notifications/conversation-pairing.ts +23 -8
  770. package/src/notifications/decision-engine.ts +16 -16
  771. package/src/notifications/decisions-store.ts +32 -1
  772. package/src/notifications/deliveries-store.ts +45 -0
  773. package/src/notifications/edit-notification.ts +201 -0
  774. package/src/notifications/emit-signal.ts +11 -1
  775. package/src/notifications/home-feed-side-effect.ts +12 -1
  776. package/src/notifications/preference-extractor.ts +11 -14
  777. package/src/notifications/signal.ts +10 -0
  778. package/src/notifications/types.ts +37 -0
  779. package/src/oauth/byo-connection.test.ts +67 -3
  780. package/src/oauth/byo-connection.ts +32 -5
  781. package/src/oauth/connect-orchestrator.ts +9 -0
  782. package/src/oauth/connection-resolver.test.ts +76 -0
  783. package/src/oauth/connection-resolver.ts +49 -10
  784. package/src/oauth/manual-token-connection.ts +51 -3
  785. package/src/oauth/seed-providers.ts +3 -0
  786. package/src/permissions/approval-policy.test.ts +19 -5
  787. package/src/permissions/approval-policy.ts +14 -3
  788. package/src/permissions/checker.ts +21 -8
  789. package/src/permissions/prompter.ts +46 -36
  790. package/src/permissions/question-prompter.test.ts +35 -26
  791. package/src/permissions/question-prompter.ts +6 -10
  792. package/src/platform/client.test.ts +24 -1
  793. package/src/platform/client.ts +8 -0
  794. package/src/platform/feature-gate.ts +15 -0
  795. package/src/plugin-api/constants.ts +4 -0
  796. package/src/plugin-api/index.ts +10 -1
  797. package/src/plugin-api/types.ts +176 -4
  798. package/src/plugins/defaults/compaction/compact.ts +59 -0
  799. package/src/plugins/defaults/compaction/package.json +15 -0
  800. package/src/plugins/defaults/compaction/register.ts +24 -0
  801. package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
  802. package/src/plugins/defaults/empty-response/package.json +15 -0
  803. package/src/plugins/defaults/empty-response/register.ts +23 -0
  804. package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +35 -0
  805. package/src/plugins/defaults/history-repair/package.json +15 -0
  806. package/src/plugins/defaults/history-repair/register.ts +24 -0
  807. package/src/{daemon/history-repair.ts → plugins/defaults/history-repair/terminal.ts} +48 -35
  808. package/src/plugins/defaults/index.ts +22 -49
  809. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +95 -0
  810. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
  811. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
  812. package/src/plugins/defaults/{injectors.ts → memory-retrieval/injectors.ts} +297 -120
  813. package/src/plugins/defaults/memory-v3-shadow/__tests__/assign.test.ts +242 -0
  814. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +118 -0
  815. package/src/plugins/defaults/memory-v3-shadow/__tests__/core.test.ts +39 -0
  816. package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +36 -0
  817. package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +37 -0
  818. package/src/plugins/defaults/memory-v3-shadow/__tests__/health.test.ts +219 -0
  819. package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +330 -0
  820. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +288 -0
  821. package/src/plugins/defaults/memory-v3-shadow/__tests__/needle.test.ts +107 -0
  822. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +436 -0
  823. package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +13 -0
  824. package/src/plugins/defaults/memory-v3-shadow/__tests__/reconcile.test.ts +274 -0
  825. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +61 -0
  826. package/src/plugins/defaults/memory-v3-shadow/__tests__/router.test.ts +332 -0
  827. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +179 -0
  828. package/src/plugins/defaults/memory-v3-shadow/__tests__/selector.test.ts +470 -0
  829. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +432 -0
  830. package/src/plugins/defaults/memory-v3-shadow/__tests__/snapshot.test.ts +168 -0
  831. package/src/plugins/defaults/memory-v3-shadow/__tests__/tree.test.ts +192 -0
  832. package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +54 -0
  833. package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +106 -0
  834. package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +44 -0
  835. package/src/plugins/defaults/memory-v3-shadow/assign.ts +268 -0
  836. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +124 -0
  837. package/src/plugins/defaults/memory-v3-shadow/core.ts +26 -0
  838. package/src/plugins/defaults/memory-v3-shadow/data/README.md +84 -0
  839. package/src/plugins/defaults/memory-v3-shadow/data/assignments.json +5 -0
  840. package/src/plugins/defaults/memory-v3-shadow/data/core.json +1 -0
  841. package/src/plugins/defaults/memory-v3-shadow/data/leaves/domain-a/topic-x.md +9 -0
  842. package/src/plugins/defaults/memory-v3-shadow/data/leaves/domain-a/topic-y.md +9 -0
  843. package/src/plugins/defaults/memory-v3-shadow/data/leaves/domain-b/topic-z.md +9 -0
  844. package/src/plugins/defaults/memory-v3-shadow/health.ts +0 -0
  845. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  846. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  847. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  848. package/src/plugins/defaults/memory-v3-shadow/llm-retry.ts +32 -0
  849. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +314 -0
  850. package/src/plugins/defaults/memory-v3-shadow/needle.ts +115 -0
  851. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +126 -0
  852. package/src/plugins/defaults/memory-v3-shadow/package.json +15 -0
  853. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +34 -0
  854. package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +26 -0
  855. package/src/plugins/defaults/memory-v3-shadow/reconcile.ts +523 -0
  856. package/src/plugins/defaults/memory-v3-shadow/register.ts +26 -0
  857. package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +32 -0
  858. package/src/plugins/defaults/memory-v3-shadow/router.ts +190 -0
  859. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +84 -0
  860. package/src/plugins/defaults/memory-v3-shadow/selector.ts +226 -0
  861. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +349 -0
  862. package/src/plugins/defaults/memory-v3-shadow/snapshot.ts +209 -0
  863. package/src/plugins/defaults/memory-v3-shadow/tree.ts +174 -0
  864. package/src/plugins/defaults/memory-v3-shadow/types.ts +59 -0
  865. package/src/plugins/defaults/memory-v3-shadow/working-set.ts +88 -0
  866. package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
  867. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
  868. package/src/plugins/defaults/title-generate/package.json +15 -0
  869. package/src/plugins/defaults/title-generate/register.ts +35 -0
  870. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
  871. package/src/plugins/defaults/tool-error/package.json +15 -0
  872. package/src/plugins/defaults/tool-error/register.ts +23 -0
  873. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
  874. package/src/plugins/defaults/tool-result-truncate/package.json +15 -0
  875. package/src/plugins/defaults/tool-result-truncate/register.ts +24 -0
  876. package/src/plugins/defaults/tool-result-truncate/terminal.ts +132 -0
  877. package/src/plugins/external-plugin-loader.ts +2 -2
  878. package/src/plugins/pipeline.ts +8 -35
  879. package/src/plugins/registry.ts +8 -25
  880. package/src/plugins/types.ts +62 -677
  881. package/src/plugins/user-loader.ts +4 -3
  882. package/src/proactive-artifact/aux-message-injector.ts +4 -5
  883. package/src/proactive-artifact/job.test.ts +29 -21
  884. package/src/proactive-artifact/job.ts +3 -1
  885. package/src/prompts/__tests__/system-prompt.test.ts +46 -4
  886. package/src/prompts/sections.ts +20 -7
  887. package/src/prompts/system-prompt.ts +38 -40
  888. package/src/prompts/template-detection.ts +10 -4
  889. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +64 -0
  890. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +2 -2
  891. package/src/prompts/templates/BOOTSTRAP.md +12 -12
  892. package/src/prompts/templates/IDENTITY.md +0 -2
  893. package/src/prompts/templates/system-sections.ts +21 -0
  894. package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
  895. package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
  896. package/src/providers/__tests__/retry-callsite.test.ts +25 -25
  897. package/src/providers/__tests__/satellite-connection-routing.test.ts +7 -21
  898. package/src/providers/anthropic/client.ts +61 -34
  899. package/src/providers/call-site-routing.ts +34 -18
  900. package/src/providers/connection-model-compat.ts +23 -0
  901. package/src/providers/connection-resolution.ts +39 -20
  902. package/src/providers/fireworks/client.ts +1 -0
  903. package/src/providers/gemini/client.ts +176 -37
  904. package/src/providers/gemini/inline-media.ts +74 -0
  905. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
  906. package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
  907. package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
  908. package/src/providers/inference/auth.ts +0 -8
  909. package/src/providers/inference/connections.ts +3 -66
  910. package/src/providers/inference/resolve-auth.ts +2 -3
  911. package/src/providers/model-catalog.ts +35 -1
  912. package/src/providers/model-intents.ts +3 -3
  913. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  914. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +269 -7
  915. package/src/providers/openai/chat-completions-provider.ts +155 -16
  916. package/src/providers/openai/codex-models.ts +2 -0
  917. package/src/providers/openai/responses-provider.ts +54 -57
  918. package/src/providers/openrouter/client.ts +15 -14
  919. package/src/providers/placeholder-sentinels.ts +35 -0
  920. package/src/providers/provider-send-message.ts +23 -14
  921. package/src/providers/ratelimit.ts +1 -9
  922. package/src/providers/registry.ts +48 -8
  923. package/src/providers/retry.ts +16 -9
  924. package/src/providers/search-provider-catalog.ts +17 -9
  925. package/src/providers/types.ts +20 -2
  926. package/src/providers/usage-tracking.ts +1 -9
  927. package/src/runtime/__tests__/agent-wake.test.ts +142 -32
  928. package/src/runtime/__tests__/background-job-runner.test.ts +2 -3
  929. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  930. package/src/runtime/access-request-helper.ts +1 -0
  931. package/src/runtime/agent-wake.ts +95 -23
  932. package/src/runtime/assistant-event-hub.ts +38 -8
  933. package/src/runtime/assistant-stream-state.ts +368 -0
  934. package/src/runtime/auth/__tests__/guard-tests.test.ts +75 -109
  935. package/src/runtime/auth/__tests__/route-policy.test.ts +153 -170
  936. package/src/runtime/auth/route-policy.ts +42 -1069
  937. package/src/runtime/background-job-runner.ts +1 -4
  938. package/src/runtime/btw-sidechain.ts +3 -1
  939. package/src/runtime/channel-approvals.ts +4 -15
  940. package/src/runtime/channel-invite-transport.ts +5 -6
  941. package/src/runtime/channel-readiness-service.ts +70 -5
  942. package/src/runtime/channel-reply-delivery.ts +23 -0
  943. package/src/runtime/channel-retry-sweep.ts +59 -30
  944. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  945. package/src/runtime/http-router.ts +35 -43
  946. package/src/runtime/http-types.ts +23 -71
  947. package/src/runtime/interactive-ui.ts +1 -1
  948. package/src/runtime/invite-instruction-generator.ts +3 -3
  949. package/src/runtime/migrations/vbundle-builder.ts +3 -2
  950. package/src/runtime/pending-interactions.ts +3 -2
  951. package/src/runtime/routes/__tests__/acp-routes.test.ts +253 -55
  952. package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +565 -0
  953. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
  954. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
  955. package/src/runtime/routes/__tests__/content-source-routes.test.ts +4 -4
  956. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +436 -0
  957. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +237 -0
  958. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +129 -1
  959. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  960. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
  961. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +26 -72
  962. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
  963. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +58 -5
  964. package/src/runtime/routes/__tests__/sanity-routes.test.ts +6 -6
  965. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
  966. package/src/runtime/routes/__tests__/stt-routes.test.ts +3 -3
  967. package/src/runtime/routes/__tests__/suggest-trust-rule-routes.test.ts +5 -2
  968. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  969. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +297 -0
  970. package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
  971. package/src/runtime/routes/__tests__/tts-routes.test.ts +9 -5
  972. package/src/runtime/routes/acp-routes-list.test.ts +3 -0
  973. package/src/runtime/routes/acp-routes.test.ts +186 -100
  974. package/src/runtime/routes/acp-routes.ts +110 -35
  975. package/src/runtime/routes/app-management-routes.ts +175 -106
  976. package/src/runtime/routes/app-routes.ts +38 -20
  977. package/src/runtime/routes/approval-routes.ts +17 -5
  978. package/src/runtime/routes/attachment-routes.ts +51 -16
  979. package/src/runtime/routes/audio-routes.ts +1 -0
  980. package/src/runtime/routes/audit-routes.ts +5 -0
  981. package/src/runtime/routes/auth-routes.ts +5 -0
  982. package/src/runtime/routes/avatar-routes.ts +264 -59
  983. package/src/runtime/routes/background-tool-routes.ts +9 -0
  984. package/src/runtime/routes/background-wake-routes.ts +201 -23
  985. package/src/runtime/routes/backup-routes.ts +45 -0
  986. package/src/runtime/routes/bookmark-routes.ts +13 -0
  987. package/src/runtime/routes/brain-graph-routes.ts +9 -0
  988. package/src/runtime/routes/browser-routes.ts +6 -1
  989. package/src/runtime/routes/browser-tabs-routes.ts +11 -10
  990. package/src/runtime/routes/btw-routes.ts +37 -27
  991. package/src/runtime/routes/cache-routes.ts +13 -0
  992. package/src/runtime/routes/call-routes.ts +21 -10
  993. package/src/runtime/routes/channel-availability-routes.ts +5 -1
  994. package/src/runtime/routes/channel-readiness-routes.ts +37 -4
  995. package/src/runtime/routes/channel-route-definitions.ts +21 -0
  996. package/src/runtime/routes/channel-verification-routes.ts +21 -0
  997. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +9 -2
  998. package/src/runtime/routes/client-routes.ts +9 -0
  999. package/src/runtime/routes/consolidation-routes.ts +133 -25
  1000. package/src/runtime/routes/contact-prompt-routes.ts +9 -0
  1001. package/src/runtime/routes/contact-routes.ts +90 -23
  1002. package/src/runtime/routes/content-source-routes.ts +5 -1
  1003. package/src/runtime/routes/conversation-analysis-routes.ts +11 -1
  1004. package/src/runtime/routes/conversation-attention-routes.ts +5 -0
  1005. package/src/runtime/routes/conversation-cli-routes.ts +54 -7
  1006. package/src/runtime/routes/conversation-compaction-routes.ts +292 -0
  1007. package/src/runtime/routes/conversation-list-routes.ts +225 -9
  1008. package/src/runtime/routes/conversation-management-routes.ts +96 -28
  1009. package/src/runtime/routes/conversation-query-routes.ts +150 -51
  1010. package/src/runtime/routes/conversation-routes.ts +615 -327
  1011. package/src/runtime/routes/conversation-starter-routes.ts +35 -20
  1012. package/src/runtime/routes/conversations-import-routes.ts +48 -13
  1013. package/src/runtime/routes/credential-prompt-routes.ts +5 -0
  1014. package/src/runtime/routes/credential-routes.ts +25 -6
  1015. package/src/runtime/routes/debug-bash-routes.ts +5 -0
  1016. package/src/runtime/routes/debug-routes.ts +11 -2
  1017. package/src/runtime/routes/defer-routes.ts +13 -0
  1018. package/src/runtime/routes/diagnostics-routes.ts +37 -46
  1019. package/src/runtime/routes/disk-pressure-routes.ts +17 -31
  1020. package/src/runtime/routes/document-comments-routes.ts +46 -27
  1021. package/src/runtime/routes/documents-routes.ts +35 -11
  1022. package/src/runtime/routes/domain-routes.ts +98 -51
  1023. package/src/runtime/routes/email-routes.ts +33 -0
  1024. package/src/runtime/routes/epoch-millis-range.ts +34 -0
  1025. package/src/runtime/routes/events-routes.ts +107 -8
  1026. package/src/runtime/routes/filing-routes.ts +9 -4
  1027. package/src/runtime/routes/gateway-log-routes.ts +31 -4
  1028. package/src/runtime/routes/global-search-routes.ts +53 -50
  1029. package/src/runtime/routes/group-routes.ts +32 -5
  1030. package/src/runtime/routes/guardian-action-routes.ts +9 -0
  1031. package/src/runtime/routes/guardian-approval-interception.ts +0 -31
  1032. package/src/runtime/routes/heartbeat-routes.ts +57 -21
  1033. package/src/runtime/routes/home-feed-routes.ts +149 -16
  1034. package/src/runtime/routes/home-state-routes.ts +8 -40
  1035. package/src/runtime/routes/host-app-control-routes.ts +6 -1
  1036. package/src/runtime/routes/host-bash-routes.ts +5 -0
  1037. package/src/runtime/routes/host-browser-routes.ts +13 -0
  1038. package/src/runtime/routes/host-cu-routes.ts +6 -1
  1039. package/src/runtime/routes/host-file-routes.ts +26 -6
  1040. package/src/runtime/routes/host-transfer-routes.ts +13 -2
  1041. package/src/runtime/routes/http-adapter.ts +1 -2
  1042. package/src/runtime/routes/identity-intro-cache.ts +72 -39
  1043. package/src/runtime/routes/identity-routes.ts +257 -20
  1044. package/src/runtime/routes/image-generation-routes.ts +45 -2
  1045. package/src/runtime/routes/inbound-message-handler.ts +16 -12
  1046. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +524 -12
  1047. package/src/runtime/routes/inbound-stages/background-dispatch.ts +72 -27
  1048. package/src/runtime/routes/index.ts +4 -0
  1049. package/src/runtime/routes/inference-profile-session-routes.ts +13 -3
  1050. package/src/runtime/routes/inference-provider-connection-routes.ts +26 -31
  1051. package/src/runtime/routes/inference-send-routes.ts +11 -11
  1052. package/src/runtime/routes/integrations/a2a.ts +32 -7
  1053. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
  1054. package/src/runtime/routes/integrations/slack/channel.ts +23 -3
  1055. package/src/runtime/routes/integrations/slack/share.ts +36 -8
  1056. package/src/runtime/routes/integrations/telegram.ts +34 -9
  1057. package/src/runtime/routes/integrations/twilio.ts +77 -7
  1058. package/src/runtime/routes/integrations/vercel.ts +18 -3
  1059. package/src/runtime/routes/internal-oauth-routes.ts +5 -0
  1060. package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
  1061. package/src/runtime/routes/internal-twilio-routes.ts +13 -0
  1062. package/src/runtime/routes/llm-call-sites-routes.ts +39 -4
  1063. package/src/runtime/routes/llm-context-normalization.ts +7 -2
  1064. package/src/runtime/routes/log-export-routes.ts +36 -10
  1065. package/src/runtime/routes/mcp-auth-routes.ts +25 -0
  1066. package/src/runtime/routes/memory-item-routes.ts +21 -10
  1067. package/src/runtime/routes/memory-v2-routes.ts +105 -44
  1068. package/src/runtime/routes/memory-v3-routes.ts +313 -257
  1069. package/src/runtime/routes/migration-rollback-routes.ts +5 -1
  1070. package/src/runtime/routes/migration-routes.ts +49 -13
  1071. package/src/runtime/routes/notification-routes.ts +80 -2
  1072. package/src/runtime/routes/oauth-apps.ts +99 -23
  1073. package/src/runtime/routes/oauth-commands-routes.ts +43 -15
  1074. package/src/runtime/routes/oauth-connect-routes.ts +9 -0
  1075. package/src/runtime/routes/oauth-lifecycle-routes.ts +5 -1
  1076. package/src/runtime/routes/oauth-providers.ts +79 -15
  1077. package/src/runtime/routes/platform-routes.ts +102 -5
  1078. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +9 -6
  1079. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +37 -16
  1080. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +7 -3
  1081. package/src/runtime/routes/playground/__tests__/state.test.ts +10 -3
  1082. package/src/runtime/routes/playground/force-compact.ts +2 -2
  1083. package/src/runtime/routes/playground/helpers.ts +1 -2
  1084. package/src/runtime/routes/playground/inject-failures.ts +13 -8
  1085. package/src/runtime/routes/playground/reset-circuit.ts +14 -9
  1086. package/src/runtime/routes/playground/seed-conversation.ts +1 -1
  1087. package/src/runtime/routes/playground/seeded-conversations.ts +3 -3
  1088. package/src/runtime/routes/playground/state.ts +4 -3
  1089. package/src/runtime/routes/plugins-routes.ts +22 -19
  1090. package/src/runtime/routes/profiler-routes.ts +17 -4
  1091. package/src/runtime/routes/ps-routes.ts +5 -0
  1092. package/src/runtime/routes/publish-routes.ts +13 -3
  1093. package/src/runtime/routes/question-routes.ts +5 -0
  1094. package/src/runtime/routes/recording-routes.ts +25 -12
  1095. package/src/runtime/routes/rename-conversation-routes.ts +10 -0
  1096. package/src/runtime/routes/sanity-routes.ts +9 -2
  1097. package/src/runtime/routes/schedule-routes.ts +288 -88
  1098. package/src/runtime/routes/secret-routes.ts +31 -6
  1099. package/src/runtime/routes/sequence-routes.ts +33 -0
  1100. package/src/runtime/routes/settings-routes.ts +65 -19
  1101. package/src/runtime/routes/skills-routes.ts +166 -73
  1102. package/src/runtime/routes/slack-channel-routes.ts +5 -0
  1103. package/src/runtime/routes/stt-routes.ts +13 -6
  1104. package/src/runtime/routes/subagents-routes.ts +24 -18
  1105. package/src/runtime/routes/suggest-trust-rule-routes.ts +7 -2
  1106. package/src/runtime/routes/surface-action-routes.ts +10 -38
  1107. package/src/runtime/routes/surface-content-routes.ts +21 -6
  1108. package/src/runtime/routes/surface-conversation-resolver.ts +66 -0
  1109. package/src/runtime/routes/task-routes.ts +37 -0
  1110. package/src/runtime/routes/telemetry-routes.ts +9 -0
  1111. package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
  1112. package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
  1113. package/src/runtime/routes/trace-event-routes.ts +42 -1
  1114. package/src/runtime/routes/trust-rules-routes.ts +31 -2
  1115. package/src/runtime/routes/tts-routes.ts +48 -6
  1116. package/src/runtime/routes/types.ts +83 -16
  1117. package/src/runtime/routes/ui-request-routes.ts +5 -0
  1118. package/src/runtime/routes/upgrade-broadcast-routes.ts +5 -0
  1119. package/src/runtime/routes/usage-routes.ts +118 -42
  1120. package/src/runtime/routes/user-routes-cli.ts +9 -0
  1121. package/src/runtime/routes/user-routes.ts +5 -1
  1122. package/src/runtime/routes/wake-conversation-routes.ts +5 -0
  1123. package/src/runtime/routes/watcher-routes.ts +21 -0
  1124. package/src/runtime/routes/webhook-routes.ts +50 -2
  1125. package/src/runtime/routes/wipe-conversation-routes.ts +8 -0
  1126. package/src/runtime/routes/work-items-routes.ts +49 -23
  1127. package/src/runtime/routes/workspace-commit-routes.ts +5 -0
  1128. package/src/runtime/routes/workspace-routes.test.ts +42 -0
  1129. package/src/runtime/routes/workspace-routes.ts +124 -9
  1130. package/src/runtime/services/__tests__/analyze-conversation.test.ts +10 -4
  1131. package/src/runtime/services/analyze-conversation.ts +5 -8
  1132. package/src/runtime/services/conversation-serializer.ts +24 -2
  1133. package/src/runtime/slack-dm-text-delivery.ts +177 -0
  1134. package/src/runtime/sync/resource-sync-events.ts +17 -3
  1135. package/src/runtime/sync/sync-publisher.ts +2 -2
  1136. package/src/runtime/tool-grant-request-helper.ts +1 -0
  1137. package/src/schedule/run-script.ts +28 -3
  1138. package/src/schedule/schedule-store.ts +36 -2
  1139. package/src/schedule/schedule-usage-store.ts +83 -0
  1140. package/src/schedule/scheduler.ts +126 -21
  1141. package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
  1142. package/src/security/encrypted-store.ts +7 -16
  1143. package/src/security/store-path-override.ts +61 -0
  1144. package/src/signals/cancel.ts +2 -4
  1145. package/src/signals/user-message.ts +10 -16
  1146. package/src/skills/catalog-files.ts +4 -1
  1147. package/src/skills/catalog-install.ts +3 -0
  1148. package/src/skills/categories-cache.ts +118 -0
  1149. package/src/skills/clawhub-files.ts +1 -0
  1150. package/src/skills/skillssh-files.ts +1 -0
  1151. package/src/skills/validate-input.ts +177 -0
  1152. package/src/subagent/manager.ts +33 -24
  1153. package/src/subagent/types.ts +6 -0
  1154. package/src/tasks/tool-sanitizer.ts +2 -2
  1155. package/src/telemetry/types.ts +55 -1
  1156. package/src/telemetry/usage-telemetry-reporter.test.ts +250 -4
  1157. package/src/telemetry/usage-telemetry-reporter.ts +88 -2
  1158. package/src/tools/acp/context.ts +20 -0
  1159. package/src/tools/acp/list-agents.test.ts +7 -1
  1160. package/src/tools/acp/spawn.test.ts +198 -93
  1161. package/src/tools/acp/spawn.ts +32 -70
  1162. package/src/tools/acp/steer.test.ts +105 -8
  1163. package/src/tools/acp/steer.ts +48 -17
  1164. package/src/tools/apps/definitions.ts +42 -24
  1165. package/src/tools/apps/executors.ts +13 -8
  1166. package/src/tools/ask-question/ask-question-tool.test.ts +120 -105
  1167. package/src/tools/ask-question/ask-question-tool.ts +85 -90
  1168. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
  1169. package/src/tools/computer-use/definitions.ts +295 -289
  1170. package/src/tools/credential-execution/make-authenticated-request.ts +56 -51
  1171. package/src/tools/credential-execution/manage-secure-command-tool.ts +2 -2
  1172. package/src/tools/credential-execution/run-authenticated-command.ts +82 -77
  1173. package/src/tools/credentials/vault.ts +112 -111
  1174. package/src/tools/document/document-tool.ts +131 -8
  1175. package/src/tools/execution-target.ts +3 -6
  1176. package/src/tools/execution-timeout.ts +3 -4
  1177. package/src/tools/executor.ts +18 -107
  1178. package/src/tools/filesystem/edit.ts +45 -42
  1179. package/src/tools/filesystem/list.ts +33 -30
  1180. package/src/tools/filesystem/read.ts +54 -35
  1181. package/src/tools/filesystem/write.ts +69 -32
  1182. package/src/tools/host-filesystem/edit.test.ts +1 -0
  1183. package/src/tools/host-filesystem/edit.ts +44 -42
  1184. package/src/tools/host-filesystem/read.test.ts +1 -0
  1185. package/src/tools/host-filesystem/read.ts +49 -35
  1186. package/src/tools/host-filesystem/transfer.test.ts +31 -6
  1187. package/src/tools/host-filesystem/transfer.ts +121 -108
  1188. package/src/tools/host-filesystem/write.test.ts +1 -0
  1189. package/src/tools/host-filesystem/write.ts +33 -31
  1190. package/src/tools/host-terminal/host-shell.ts +50 -48
  1191. package/src/tools/mcp/mcp-tool-factory.ts +0 -2
  1192. package/src/tools/memory/register.ts +23 -24
  1193. package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
  1194. package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
  1195. package/src/tools/network/__tests__/web-search.test.ts +222 -6
  1196. package/src/tools/network/managed-search-proxy.ts +183 -0
  1197. package/src/tools/network/web-fetch.ts +49 -46
  1198. package/src/tools/network/web-search-error.test.ts +248 -0
  1199. package/src/tools/network/web-search-error.ts +267 -0
  1200. package/src/tools/network/web-search.ts +414 -97
  1201. package/src/tools/policy-context.ts +3 -1
  1202. package/src/tools/registry.ts +184 -118
  1203. package/src/tools/schedule/create.ts +14 -1
  1204. package/src/tools/schedule/update.ts +16 -0
  1205. package/src/tools/shared/filesystem/audio-read.ts +122 -0
  1206. package/src/tools/shared/filesystem/image-read.ts +1 -1
  1207. package/src/tools/skills/execute.ts +34 -31
  1208. package/src/tools/skills/load.ts +29 -23
  1209. package/src/tools/skills/skill-tool-factory.ts +17 -36
  1210. package/src/tools/subagent/notify-parent.ts +35 -32
  1211. package/src/tools/subagent/spawn.ts +2 -1
  1212. package/src/tools/system/avatar-generator.ts +13 -22
  1213. package/src/tools/system/request-permission.ts +30 -27
  1214. package/src/tools/terminal/safe-env.ts +10 -1
  1215. package/src/tools/terminal/shell.ts +190 -61
  1216. package/src/tools/tool-approval-handler.ts +10 -4
  1217. package/src/tools/tool-defaults.ts +20 -9
  1218. package/src/tools/tool-manifest.ts +4 -4
  1219. package/src/tools/tool-name-aliases.ts +72 -14
  1220. package/src/tools/types.ts +86 -33
  1221. package/src/tools/ui-surface/definitions.ts +196 -95
  1222. package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
  1223. package/src/tts/provider-catalog.ts +76 -1
  1224. package/src/types/onboarding-context.ts +6 -0
  1225. package/src/usage/attribution.ts +32 -1
  1226. package/src/usage/types.ts +10 -0
  1227. package/src/util/browser.ts +7 -2
  1228. package/src/util/errors.ts +2 -2
  1229. package/src/util/map-limit.ts +27 -0
  1230. package/src/util/mutex.ts +47 -0
  1231. package/src/util/platform.ts +15 -12
  1232. package/src/work-items/work-item-runner.ts +7 -2
  1233. package/src/workspace/git-service.ts +1 -42
  1234. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +7 -20
  1235. package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
  1236. package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
  1237. package/src/workspace/migrations/092-backfill-v3-leaves.ts +169 -0
  1238. package/src/workspace/migrations/093-backfill-leaf-ids.ts +144 -0
  1239. package/src/workspace/migrations/094-seed-avatar-manifest.ts +155 -0
  1240. package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
  1241. package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
  1242. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +117 -0
  1243. package/src/workspace/migrations/__tests__/094-seed-avatar-manifest.test.ts +136 -0
  1244. package/src/workspace/migrations/__tests__/backfill-leaf-ids.test.ts +175 -0
  1245. package/src/workspace/migrations/__tests__/backfill-v3-leaves.test.ts +124 -0
  1246. package/src/workspace/migrations/registry.ts +16 -0
  1247. package/src/workspace/provider-commit-message-generator.ts +15 -17
  1248. package/tsconfig.json +4 -1
  1249. package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
  1250. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  1251. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  1252. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -262
  1253. package/src/__tests__/empty-response-pipeline.test.ts +0 -301
  1254. package/src/__tests__/history-repair-pipeline.test.ts +0 -396
  1255. package/src/__tests__/llm-call-pipeline.test.ts +0 -281
  1256. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
  1257. package/src/__tests__/persistence-pipeline.test.ts +0 -376
  1258. package/src/__tests__/title-generate-pipeline.test.ts +0 -211
  1259. package/src/__tests__/token-estimate-pipeline.test.ts +0 -481
  1260. package/src/__tests__/tool-error-pipeline.test.ts +0 -241
  1261. package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
  1262. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -344
  1263. package/src/cli/commands/__tests__/memory-v3-render.test.ts +0 -340
  1264. package/src/cli/commands/memory-v3-render.ts +0 -344
  1265. package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
  1266. package/src/daemon/message-types/disk-pressure.ts +0 -9
  1267. package/src/email/feature-gate.ts +0 -23
  1268. package/src/gallery/default-gallery.ts +0 -1359
  1269. package/src/gallery/gallery-manifest.ts +0 -28
  1270. package/src/memory/v3/__tests__/coactivation-store.test.ts +0 -422
  1271. package/src/memory/v3/__tests__/consolidation-job.test.ts +0 -468
  1272. package/src/memory/v3/__tests__/edge-learning-job.test.ts +0 -324
  1273. package/src/memory/v3/__tests__/edges.test.ts +0 -563
  1274. package/src/memory/v3/__tests__/filter.test.ts +0 -512
  1275. package/src/memory/v3/__tests__/gate.test.ts +0 -574
  1276. package/src/memory/v3/__tests__/index-composition.test.ts +0 -233
  1277. package/src/memory/v3/__tests__/loop.test.ts +0 -530
  1278. package/src/memory/v3/__tests__/retriever.test.ts +0 -226
  1279. package/src/memory/v3/__tests__/scouts.test.ts +0 -440
  1280. package/src/memory/v3/__tests__/shadow-middleware.test.ts +0 -312
  1281. package/src/memory/v3/__tests__/system-prompts.test.ts +0 -154
  1282. package/src/memory/v3/__tests__/traversal.test.ts +0 -469
  1283. package/src/memory/v3/__tests__/tree-index.test.ts +0 -280
  1284. package/src/memory/v3/__tests__/tree-store.test.ts +0 -529
  1285. package/src/memory/v3/__tests__/tree-walk.test.ts +0 -707
  1286. package/src/memory/v3/__tests__/validate.test.ts +0 -245
  1287. package/src/memory/v3/auto-edges.ts +0 -223
  1288. package/src/memory/v3/coactivation-store.ts +0 -124
  1289. package/src/memory/v3/consolidation-job.ts +0 -323
  1290. package/src/memory/v3/edge-learning-job.ts +0 -160
  1291. package/src/memory/v3/edges.ts +0 -249
  1292. package/src/memory/v3/filter.ts +0 -281
  1293. package/src/memory/v3/gate.ts +0 -334
  1294. package/src/memory/v3/index-composition.ts +0 -113
  1295. package/src/memory/v3/llm-capture.ts +0 -46
  1296. package/src/memory/v3/loop.ts +0 -382
  1297. package/src/memory/v3/maintenance.ts +0 -144
  1298. package/src/memory/v3/prompt-context.ts +0 -33
  1299. package/src/memory/v3/prompts/consolidation.ts +0 -458
  1300. package/src/memory/v3/prompts/system-prompts.ts +0 -196
  1301. package/src/memory/v3/retriever.ts +0 -33
  1302. package/src/memory/v3/scouts.ts +0 -420
  1303. package/src/memory/v3/shadow-middleware.ts +0 -305
  1304. package/src/memory/v3/traversal.ts +0 -206
  1305. package/src/memory/v3/tree-index.ts +0 -237
  1306. package/src/memory/v3/tree-store.ts +0 -394
  1307. package/src/memory/v3/tree-walk.ts +0 -351
  1308. package/src/memory/v3/types.ts +0 -65
  1309. package/src/memory/v3/validate.ts +0 -300
  1310. package/src/plugins/defaults/circuit-breaker.ts +0 -141
  1311. package/src/plugins/defaults/compaction.ts +0 -141
  1312. package/src/plugins/defaults/empty-response.ts +0 -124
  1313. package/src/plugins/defaults/history-repair.ts +0 -83
  1314. package/src/plugins/defaults/llm-call.ts +0 -77
  1315. package/src/plugins/defaults/memory-retrieval.ts +0 -219
  1316. package/src/plugins/defaults/overflow-reduce.ts +0 -185
  1317. package/src/plugins/defaults/persistence.ts +0 -127
  1318. package/src/plugins/defaults/title-generate.ts +0 -90
  1319. package/src/plugins/defaults/token-estimate.ts +0 -101
  1320. package/src/plugins/defaults/tool-error.ts +0 -119
  1321. package/src/plugins/defaults/tool-execute.ts +0 -87
  1322. package/src/plugins/defaults/tool-result-truncate.ts +0 -84
  1323. package/src/skills/category-inference.ts +0 -111
package/src/agent/loop.ts CHANGED
@@ -1,34 +1,32 @@
1
1
  import * as Sentry from "@sentry/node";
2
2
 
3
3
  import type { LLMCallSite } from "../config/schemas/llm.js";
4
+ import { stripInjectionsForCompaction } from "../context/strip-injections.js";
4
5
  import {
5
6
  estimatePromptTokensRaw,
7
+ estimatePromptTokensWithTools,
6
8
  estimateToolsTokens,
7
9
  getCalibrationProviderKey,
8
10
  } from "../context/token-estimator.js";
9
- import { calculateMaxToolResultChars } from "../context/tool-result-truncation.js";
11
+ import type { ContextWindowResult } from "../context/window-manager.js";
10
12
  import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
11
- import { defaultEmptyResponseTerminal } from "../plugins/defaults/empty-response.js";
12
- import { defaultToolErrorTerminal } from "../plugins/defaults/tool-error.js";
13
- import { defaultToolResultTruncateTerminal } from "../plugins/defaults/tool-result-truncate.js";
14
- import { DEFAULT_TIMEOUTS, runPipeline } from "../plugins/pipeline.js";
15
- import { getMiddlewaresFor } from "../plugins/registry.js";
16
- import type {
17
- EmptyResponseArgs,
18
- EmptyResponseDecision,
19
- LLMCallArgs,
20
- LLMCallResult,
21
- ToolErrorArgs,
22
- ToolErrorDecision,
23
- ToolResultTruncateArgs,
24
- ToolResultTruncateResult,
25
- TurnContext,
26
- } from "../plugins/types.js";
13
+ import { HOOKS } from "../plugin-api/constants.js";
14
+ import type { PostToolUseContext, StopContext } from "../plugin-api/types.js";
15
+ import {
16
+ DEFAULT_COMPACTION_PLUGIN_NAME,
17
+ defaultCompact,
18
+ } from "../plugins/defaults/compaction/compact.js";
19
+ import type { PostCompactionHookInput } from "../plugins/defaults/memory-retrieval/hooks/post-compact.js";
20
+ import { runHook } from "../plugins/pipeline.js";
21
+ import type { CompactionCircuitEvent, TurnContext } from "../plugins/types.js";
22
+ import { PluginExecutionError } from "../plugins/types.js";
27
23
  import { normalizeThinkingConfigForWire } from "../providers/thinking-config.js";
28
24
  import type {
29
25
  ContentBlock,
30
26
  Message,
31
27
  Provider,
28
+ ProviderResponse,
29
+ SendMessageOptions,
32
30
  ToolDefinition,
33
31
  ToolResultContent,
34
32
  } from "../providers/types.js";
@@ -37,12 +35,20 @@ import {
37
35
  applyStreamingSubstitution,
38
36
  applySubstitutions,
39
37
  } from "../tools/sensitive-output-placeholders.js";
40
- import { AssistantError, ErrorCode, ProviderError } from "../util/errors.js";
38
+ import { ProviderError } from "../util/errors.js";
41
39
  import { getLogger } from "../util/logger.js";
42
40
  import { isRetryableNetworkError } from "../util/retry.js";
41
+ import { CompactionCircuit } from "./compaction-circuit.js";
43
42
 
44
43
  const log = getLogger("agent-loop");
45
44
 
45
+ /** Fraction of the preflight budget at which a checkpoint triggers mid-loop compaction. */
46
+ const MID_LOOP_YIELD_THRESHOLD_RATIO = 0.85;
47
+
48
+ /** In-context message count above which the budget gate raises the safety-margin floor. */
49
+ const LONG_HISTORY_MESSAGE_THRESHOLD = 50;
50
+ const LONG_HISTORY_SAFETY_MARGIN_FLOOR = 0.15;
51
+
46
52
  export interface AgentLoopConfig {
47
53
  maxTokens: number;
48
54
  maxInputTokens?: number; // context window size for tool result truncation
@@ -66,7 +72,39 @@ export interface CheckpointInfo {
66
72
  history: Message[]; // current history snapshot for token estimation
67
73
  }
68
74
 
69
- export type CheckpointDecision = "continue" | "yield";
75
+ /**
76
+ * Why a checkpoint paused the loop. Surfaced back to the caller via
77
+ * {@link AgentLoopRunResult.exitReason} so the orchestrator reacts to
78
+ * the loop's own signal (hand off to a queued message vs. compact and
79
+ * re-enter) instead of the checkpoint callback mutating orchestrator state.
80
+ */
81
+ export type ExitReason = "handoff" | "budget";
82
+
83
+ export type CheckpointDecision = "continue" | ExitReason;
84
+
85
+ /** Result of {@link AgentLoop.run}. */
86
+ export interface AgentLoopRunResult {
87
+ /** Full conversation history after the run, including everything appended this run. */
88
+ history: Message[];
89
+ /**
90
+ * Reason the loop paused at a checkpoint, or `null` on a terminal stop
91
+ * (completion, error, abort, or a tool-requested yield-to-user).
92
+ */
93
+ exitReason: ExitReason | null;
94
+ /**
95
+ * Whether the loop produced at least one new assistant message this run —
96
+ * the forward-progress signal for the ordering-error retry gate and the
97
+ * overflow convergence fold (immune to in-loop compaction shrinking history
98
+ * below a pre-run length).
99
+ */
100
+ appendedNewMessages: boolean;
101
+ /**
102
+ * Slice of `history` appended this run, measured from the loop's input or
103
+ * from the compacted base when it compacts in place. The loop owns this
104
+ * boundary, so it cannot desync the way an externally-held index can.
105
+ */
106
+ newMessages: Message[];
107
+ }
70
108
 
71
109
  /**
72
110
  * Why an agent turn reached a terminal state.
@@ -89,8 +127,6 @@ export type CheckpointDecision = "continue" | "yield";
89
127
  export type AgentLoopExitReason =
90
128
  /** `if (signal?.aborted) break;` at the top of the loop. */
91
129
  | "aborted_pre_call"
92
- /** Empty assistant response after the configured retry budget. */
93
- | "empty_response_exhausted"
94
130
  /** Assistant message has no tool-use blocks (or no tool executor). */
95
131
  | "no_tool_calls"
96
132
  /** Signal aborted while building the user-side tool-results message. */
@@ -111,6 +147,8 @@ export type AgentLoopExitReason =
111
147
  * leaving `agent_loop_exit_reason` NULL.
112
148
  */
113
149
  | "budget_yield_unrecovered"
150
+ /** Provider stopped because the configured output-token limit was reached. */
151
+ | "max_tokens_reached"
114
152
  /** User cancellation landed after a non-terminal checkpoint yield. */
115
153
  | "aborted_after_checkpoint"
116
154
  /** Signal aborted while the catch handler was synthesizing an error turn. */
@@ -119,9 +157,19 @@ export type AgentLoopExitReason =
119
157
  | "error";
120
158
 
121
159
  export type AgentEvent =
160
+ /**
161
+ * Emitted once per LLM call inside the loop, immediately before the
162
+ * `provider.sendMessage` invocation. Carries the optional `callSite` tag so
163
+ * downstream handlers (the daemon's persistence pipeline) can decide
164
+ * whether to reserve a row for this call. One `llm_call_started` precedes
165
+ * every `message_complete` for the same call; multi-call agent turns emit
166
+ * one pair per call.
167
+ */
168
+ | { type: "llm_call_started"; callSite?: LLMCallSite }
122
169
  | { type: "text_delta"; text: string }
123
170
  | { type: "thinking_delta"; thinking: string }
124
171
  | { type: "message_complete"; message: Message }
172
+ | { type: "max_tokens_reached"; stopReason: string }
125
173
  | {
126
174
  type: "tool_use";
127
175
  id: string;
@@ -157,6 +205,14 @@ export type AgentEvent =
157
205
  approvalReason?: string;
158
206
  riskThreshold?: string;
159
207
  activityMetadata?: ToolActivityMetadata;
208
+ /**
209
+ * Set when the loop synthesizes this result for a tool_use that never
210
+ * executed (a "Cancelled by user" block on abort). The daemon still
211
+ * captures it into `pendingToolResults` and forwards it to the client,
212
+ * but skips the side effects that assume the tool ran — marking the
213
+ * workspace dirty and emitting a post-tool "thinking" activity state.
214
+ */
215
+ cancelled?: boolean;
160
216
  }
161
217
  | { type: "tool_use_preview_start"; toolUseId: string; toolName: string }
162
218
  | {
@@ -191,7 +247,7 @@ export type AgentEvent =
191
247
  | { type: "error"; error: Error }
192
248
  | {
193
249
  /**
194
- * Emitted when the `llmCall` pipeline throws — i.e. the provider
250
+ * Emitted when the provider call throws — i.e. the provider
195
251
  * rejected the request before returning a usable response. Carries
196
252
  * the loop-level raw request we attempted to send (messages, tools,
197
253
  * system prompt, provider-agnostic config) plus the thrown error.
@@ -234,6 +290,60 @@ export type AgentEvent =
234
290
  */
235
291
  estimatedInputTokens?: number;
236
292
  }
293
+ | {
294
+ /**
295
+ * Emitted when the loop begins compacting the running history because
296
+ * the mid-loop budget gate tripped. The daemon's event dispatcher
297
+ * translates it into a "compacting context" activity state so clients
298
+ * surface that the turn paused to summarize context.
299
+ */
300
+ type: "context_compacting";
301
+ }
302
+ | {
303
+ /**
304
+ * Emitted after the loop's inline mid-loop compaction pipeline runs,
305
+ * immediately before re-injection — whether or not the pipeline actually
306
+ * compacted. The daemon's event dispatcher always commits `basis` (the
307
+ * stripped pre-compaction history) as the conversation's durable message
308
+ * state, so re-injection ({@link MidLoopCompaction.reinject}) re-applies
309
+ * injections onto the stripped base rather than stacking on top of the
310
+ * still-injected messages. When `result.compacted` is set it
311
+ * additionally commits the durable compaction result (DB-record fields,
312
+ * graph-memory side effects, SSE) and flips the per-turn re-injection
313
+ * guards on the handler state.
314
+ *
315
+ * Treated as a critical event: a failed durable commit re-throws so the
316
+ * turn aborts rather than re-injecting against half-applied state.
317
+ *
318
+ * `basis` is the stripped pre-compaction history the summary was built
319
+ * from; the dispatcher uses it to project Slack provenance onto the
320
+ * compacted result.
321
+ */
322
+ type: "compaction_completed";
323
+ result: ContextWindowResult;
324
+ basis: Message[];
325
+ }
326
+ | {
327
+ /**
328
+ * Emitted right after the loop strips runtime injections from the
329
+ * running history, before the compaction pipeline runs. The daemon's
330
+ * event dispatcher records the history-stripped marker — a Conversation
331
+ * DB-record field read back at load time to strip embedded injection
332
+ * prefixes from pre-strip messages. Best-effort: a transient marker
333
+ * write must not abort the turn, so unlike `compaction_completed` this
334
+ * event is not treated as critical.
335
+ */
336
+ type: "history_stripped";
337
+ }
338
+ /**
339
+ * Circuit-breaker transitions emitted when auto-compaction is paused
340
+ * (`compaction_circuit_open`, after three consecutive summary-LLM
341
+ * failures) or resumed (`compaction_circuit_closed`). These are already
342
+ * in wire-contract shape; the daemon's event dispatcher forwards them to
343
+ * the client unchanged so the "auto-compaction paused" banner shows and
344
+ * dismisses.
345
+ */
346
+ | CompactionCircuitEvent
237
347
  | {
238
348
  /**
239
349
  * Emitted when an agent turn reaches a terminal state. Checkpoint
@@ -254,8 +364,19 @@ const DEFAULT_CONFIG: AgentLoopConfig = {
254
364
  minTurnIntervalMs: 150,
255
365
  };
256
366
 
257
- const MAX_CONSECUTIVE_ERROR_NUDGES = 3;
258
- const MAX_EMPTY_RESPONSE_RETRIES = 1;
367
+ const MAX_STOP_CONTINUE_RETRIES = 1;
368
+ const MAX_TOKENS_STOP_REASONS = new Set([
369
+ "length",
370
+ "max_output_tokens",
371
+ "max_tokens",
372
+ ]);
373
+
374
+ export function isMaxTokensStopReason(
375
+ stopReason: string | null | undefined,
376
+ ): boolean {
377
+ if (!stopReason) return false;
378
+ return MAX_TOKENS_STOP_REASONS.has(stopReason.trim().toLowerCase());
379
+ }
259
380
 
260
381
  /**
261
382
  * Build a minimal {@link TurnContext} for pipeline invocations inside the
@@ -264,12 +385,11 @@ const MAX_EMPTY_RESPONSE_RETRIES = 1;
264
385
  * {@link AgentLoop.run}); this helper is the fallback used only by unit
265
386
  * tests that construct `AgentLoop` directly without an orchestrator.
266
387
  *
267
- * When the orchestrator-supplied context is present, {@link resolveLoopTurnContext}
268
- * is used instead of this helper so the pipeline sees the real
269
- * `conversationId`, trust, and `contextWindowManager`. In the fallback path
270
- * the returned context is still useful for pipeline logging: `requestId`
271
- * surfaces in every structured record, and `turnIndex` reflects the
272
- * current tool-use iteration.
388
+ * When the orchestrator-supplied context is present it is used directly so the
389
+ * pipeline sees the real `conversationId`, trust, and `contextWindowManager`.
390
+ * In the fallback path the returned context is still useful for pipeline
391
+ * logging: `requestId` surfaces in every structured record, and `turnIndex`
392
+ * reflects the current tool-use iteration.
273
393
  */
274
394
  function buildLoopTurnContext(
275
395
  requestId: string | undefined,
@@ -289,29 +409,6 @@ function buildLoopTurnContext(
289
409
  };
290
410
  }
291
411
 
292
- /**
293
- * Produce a `TurnContext` for a pipeline call inside {@link AgentLoop.run}.
294
- *
295
- * When the orchestrator supplied a `turnContext`, clone it and overwrite
296
- * `requestId` + `turnIndex` with the loop-scoped values so plugin log
297
- * records correctly attribute the call to the current tool-use iteration
298
- * while preserving the real `conversationId`, trust context, and
299
- * `contextWindowManager` the orchestrator assembled for the turn. Without
300
- * an orchestrator context (unit tests that instantiate `AgentLoop` with no
301
- * `turnContext`), fall back to {@link buildLoopTurnContext}'s synthesized
302
- * placeholder.
303
- */
304
- function resolveLoopTurnContext(
305
- base: TurnContext | undefined,
306
- requestId: string | undefined,
307
- turnIndex: number,
308
- ): TurnContext {
309
- if (base) {
310
- return { ...base, requestId: requestId ?? base.requestId, turnIndex };
311
- }
312
- return buildLoopTurnContext(requestId, turnIndex);
313
- }
314
-
315
412
  /**
316
413
  * User-config HTTP status codes that should never page the on-call: billing
317
414
  * exhaustion (402), invalid credentials (401), and forbidden/plan-gated (403).
@@ -354,23 +451,91 @@ export interface ResolvedSystemPrompt {
354
451
  model?: string;
355
452
  }
356
453
 
454
+ /**
455
+ * Orchestrator-supplied hook the loop invokes when the mid-loop budget gate
456
+ * trips and inline compaction runs. The loop owns the trigger, the
457
+ * compaction call, the result interpretation (circuit-breaker
458
+ * bookkeeping + the exhaustion decision), and the inline continue; this hook
459
+ * bridges the injection state the loop is intentionally blind to. Durable
460
+ * persistence is signalled out-of-band via the `history_stripped` (marker)
461
+ * and `compaction_completed` (basis commit + successful summary) {@link
462
+ * AgentEvent}s; the {@link MidLoopCompaction.postCompactionHook} is
463
+ * orchestrator-supplied, and its inputs migrate loop-ward as the loop
464
+ * subsumes the re-injection ceremony.
465
+ */
466
+ export interface MidLoopCompaction {
467
+ /**
468
+ * Re-apply runtime injections onto the post-compaction history and return
469
+ * the history to continue from. The loop supplies its own working state via
470
+ * {@link PostCompactionHookInput} so the hook re-injects from that rather
471
+ * than reading it back from orchestrator state.
472
+ */
473
+ postCompactionHook: (input: PostCompactionHookInput) => Promise<Message[]>;
474
+ }
475
+
476
+ export interface AgentLoopRunOptions {
477
+ signal?: AbortSignal;
478
+ requestId?: string;
479
+ onCheckpoint?: (
480
+ checkpoint: CheckpointInfo,
481
+ ) => CheckpointDecision | Promise<CheckpointDecision>;
482
+ callSite?: LLMCallSite;
483
+ /**
484
+ * Per-turn context supplied by the orchestrator. Every pipeline
485
+ * invocation inside the loop clones from this value (overwriting only
486
+ * `turnIndex`/`requestId`) so middleware sees the real conversation
487
+ * identity, trust class, and `contextWindowManager` rather than the
488
+ * `"agent-loop"` sentinel used when the loop is instantiated standalone
489
+ * in unit tests.
490
+ */
491
+ turnContext?: TurnContext;
492
+ /**
493
+ * Ad-hoc inference-profile override applied to every LLM call the loop
494
+ * issues. When set, each `SendMessageOptions.config` carries
495
+ * `overrideProfile = <name>` so the provider's resolver layers
496
+ * `llm.profiles[<name>]` between the workspace `activeProfile` and any
497
+ * call-site named profile. Missing profile names silently fall through.
498
+ */
499
+ overrideProfile?: string;
500
+ resolveOverrideProfile?: () => string | undefined;
501
+ /**
502
+ * Resolves the orchestrator's effective context window for this turn: the
503
+ * provider max-input-token ceiling (read by tool-result truncation) plus the
504
+ * `overflowRecovery` config that drives the mid-loop budget gate. Resolved
505
+ * fresh per checkpoint so a mid-turn profile change is reflected. Absent →
506
+ * truncation falls back to `this.config.maxInputTokens` and the budget gate
507
+ * is skipped (agent wakes pass `overflowRecovery.enabled = false`).
508
+ */
509
+ resolveContextWindow?: () => {
510
+ maxInputTokens: number;
511
+ overflowRecovery: { enabled: boolean; safetyMarginRatio: number };
512
+ };
513
+ /**
514
+ * Hooks for inline mid-loop compaction. When supplied and the budget gate
515
+ * trips, the loop compacts in place and continues instead of yielding
516
+ * `exitReason = "budget"`. Callers without a compaction path (agent wakes,
517
+ * convergence/auto-compress reruns) omit this and keep yielding for budget.
518
+ */
519
+ compaction?: MidLoopCompaction;
520
+ /**
521
+ * When true, the latest user message carries a volatile per-turn block
522
+ * (e.g. a memory-v3 `<memory>` injection) that varies across otherwise
523
+ * identical turns. Forwarded to each `SendMessageOptions.config` so the
524
+ * provider anchors its long-TTL cache breakpoint on the most recent STABLE
525
+ * user message instead of the volatile latest one, keeping the cached
526
+ * prefix reusable. Default unset → existing behavior.
527
+ */
528
+ mutableLatestUserMessage?: boolean;
529
+ }
530
+
357
531
  /**
358
532
  * Callback shape the loop uses to execute a tool invocation.
359
- *
360
- * The trailing `turnContext` is optional so in-process tests that wire the
361
- * callback without an orchestrator keep working. Production sites (the
362
- * `Conversation`'s `createToolExecutor`) forward the supplied context into
363
- * `ToolExecutor.execute` so the `toolExecute` pipeline sees the orchestrator's
364
- * real conversation identity/trust/contextWindowManager instead of the
365
- * synthesized placeholder `ToolExecutor` would otherwise build from the
366
- * `ToolContext` alone.
367
533
  */
368
534
  export type LoopToolExecutor = (
369
535
  name: string,
370
536
  input: Record<string, unknown>,
371
537
  onOutput?: (chunk: string) => void,
372
538
  toolUseId?: string,
373
- turnContext?: TurnContext,
374
539
  ) => Promise<{
375
540
  content: string;
376
541
  isError: boolean;
@@ -401,6 +566,20 @@ export type LoopToolExecutor = (
401
566
  activityMetadata?: ToolActivityMetadata;
402
567
  }>;
403
568
 
569
+ export interface AgentLoopConstructorOptions {
570
+ config?: Partial<AgentLoopConfig>;
571
+ tools?: ToolDefinition[];
572
+ toolExecutor?: LoopToolExecutor;
573
+ resolveTools?: (history: Message[]) => ToolDefinition[];
574
+ resolveSystemPrompt?: (history: Message[]) => ResolvedSystemPrompt;
575
+ /**
576
+ * Conversation this loop drives. Used to scope the loop-held compaction
577
+ * circuit breaker; defaults to an empty key for test loops that never
578
+ * exercise compaction.
579
+ */
580
+ conversationId?: string;
581
+ }
582
+
404
583
  export class AgentLoop {
405
584
  private provider: Provider;
406
585
  private systemPrompt: string;
@@ -412,15 +591,28 @@ export class AgentLoop {
412
591
  | null;
413
592
  private toolExecutor: LoopToolExecutor | null;
414
593
 
594
+ /**
595
+ * Loop-held compaction circuit breaker. The loop has a 1:1 lifetime with its
596
+ * conversation, so it is the source of truth for the cross-turn failure
597
+ * counter and cooldown deadline. Non-loop callers (the orchestrator's
598
+ * compaction paths, `Conversation.forceCompact`, and the dev-only playground
599
+ * routes) reach it via `agentLoop.compactionCircuit`.
600
+ */
601
+ readonly compactionCircuit: CompactionCircuit;
602
+
415
603
  constructor(
416
604
  provider: Provider,
417
605
  systemPrompt: string,
418
- config?: Partial<AgentLoopConfig>,
419
- tools?: ToolDefinition[],
420
- toolExecutor?: LoopToolExecutor,
421
- resolveTools?: (history: Message[]) => ToolDefinition[],
422
- resolveSystemPrompt?: (history: Message[]) => ResolvedSystemPrompt,
606
+ options?: AgentLoopConstructorOptions,
423
607
  ) {
608
+ const {
609
+ config,
610
+ tools,
611
+ toolExecutor,
612
+ resolveTools,
613
+ resolveSystemPrompt,
614
+ conversationId,
615
+ } = options ?? {};
424
616
  this.provider = provider;
425
617
  this.systemPrompt = systemPrompt;
426
618
  this.config = { ...DEFAULT_CONFIG, ...config };
@@ -428,16 +620,16 @@ export class AgentLoop {
428
620
  this.resolveTools = resolveTools ?? null;
429
621
  this.resolveSystemPrompt = resolveSystemPrompt ?? null;
430
622
  this.toolExecutor = toolExecutor ?? null;
623
+ this.compactionCircuit = new CompactionCircuit(conversationId ?? "");
431
624
  }
432
625
 
433
626
  /**
434
627
  * Resolve the tool definitions sent to the provider for the given turn.
435
628
  *
436
629
  * Mirrors the logic of {@link getToolTokenBudget} but returns the tool
437
- * array itself — callers that need to thread the tool set into a plugin
438
- * pipeline (e.g. `tokenEstimate`, where the pipeline's args include
439
- * `tools`) use this rather than re-implementing the dynamic-vs-static
440
- * resolver fork.
630
+ * array itself — callers that need to thread the tool set into the token
631
+ * estimate (`estimatePromptTokensWithTools`, whose args include `tools`)
632
+ * use this rather than re-implementing the dynamic-vs-static resolver fork.
441
633
  */
442
634
  getResolvedTools(history?: Message[]): ToolDefinition[] {
443
635
  return history && this.resolveTools
@@ -457,58 +649,167 @@ export class AgentLoop {
457
649
  return estimateToolsTokens(this.getResolvedTools(history));
458
650
  }
459
651
 
652
+ /**
653
+ * Calibrated prompt-token estimate for `history`, including the
654
+ * resolved-tool budget for the turn.
655
+ */
656
+ private estimateTokens(history: Message[]): number {
657
+ return estimatePromptTokensWithTools(
658
+ history,
659
+ this.systemPrompt,
660
+ this.getResolvedTools(history),
661
+ getCalibrationProviderKey(this.provider),
662
+ );
663
+ }
664
+
665
+ /**
666
+ * Record a compaction outcome against the loop's circuit breaker. Three
667
+ * consecutive failures trip a cooldown that suspends auto-compaction; a
668
+ * success resets the counter. Any open/closed transition is emitted on the
669
+ * loop's own event channel via `onEvent`.
670
+ *
671
+ * Bookkeeping is best-effort — a failure here must not turn a recoverable
672
+ * compaction outcome into a user-visible turn failure.
673
+ */
674
+ private async recordCompactionOutcome(
675
+ turnContext: TurnContext,
676
+ summaryFailed: boolean,
677
+ onEvent: (event: AgentEvent) => void | Promise<void>,
678
+ ): Promise<void> {
679
+ try {
680
+ await this.compactionCircuit.recordOutcome(summaryFailed, onEvent);
681
+ } catch (recordError) {
682
+ log.error(
683
+ { err: recordError, requestId: turnContext.requestId },
684
+ "Recording a compaction outcome against the circuit breaker failed; suppressing to keep the agent loop alive",
685
+ );
686
+ }
687
+ }
688
+
689
+ /**
690
+ * Compact the running history in place when the mid-loop budget gate trips.
691
+ *
692
+ * Calls the default compaction plugin on the stripped history, then
693
+ * re-applies injections via the supplied hooks. Returns the history to
694
+ * continue from, or `null` when the compactor exhausted its retry budget so
695
+ * the caller yields `exitReason = "budget"` and the orchestrator escalates.
696
+ */
697
+ private async compact(
698
+ history: Message[],
699
+ turnContext: TurnContext,
700
+ compaction: MidLoopCompaction,
701
+ signal: AbortSignal | undefined,
702
+ onEvent: (event: AgentEvent) => void | Promise<void>,
703
+ overrideProfile: string | null,
704
+ ): Promise<Message[] | null> {
705
+ await onEvent({ type: "context_compacting" });
706
+ // Strip runtime injections so the compactor summarizes the raw persistent
707
+ // messages.
708
+ const rawHistory = stripInjectionsForCompaction(history);
709
+ // Record the history-stripped marker right after stripping, before the
710
+ // pipeline runs.
711
+ await onEvent({ type: "history_stripped" });
712
+ const manager = turnContext.contextWindowManager;
713
+ if (manager == null) {
714
+ throw new PluginExecutionError(
715
+ "default-compaction: turnContext.contextWindowManager is missing — orchestrator must attach it before invoking compaction",
716
+ DEFAULT_COMPACTION_PLUGIN_NAME,
717
+ );
718
+ }
719
+ // The mid-loop budget gate is reached only when this turn decides to
720
+ // compact in place, so `force` past the auto-threshold check.
721
+ // `actorTrustClass` comes from the turn context (the actor whose turn
722
+ // triggered compaction) so the compactor's image manifest excludes
723
+ // guardian-only attachments for untrusted actors. `overrideProfile` is the
724
+ // turn's resolved inference-profile override for the summary call.
725
+ const compactResult = await defaultCompact({
726
+ manager,
727
+ messages: rawHistory,
728
+ signal,
729
+ force: true,
730
+ actorTrustClass: turnContext.trust.trustClass,
731
+ overrideProfile,
732
+ });
733
+ // `force: true` bypasses the auto-threshold gate, but early returns
734
+ // for "no eligible messages" / "insufficient messages" still leave
735
+ // `summaryFailed` undefined. Only record an outcome when the summary LLM
736
+ // actually ran.
737
+ if (compactResult.summaryFailed !== undefined) {
738
+ await this.recordCompactionOutcome(
739
+ turnContext,
740
+ compactResult.summaryFailed,
741
+ onEvent,
742
+ );
743
+ }
744
+ // Emit unconditionally: the dispatcher commits the stripped `basis` as the
745
+ // durable message base whether or not the pipeline compacted (re-injection
746
+ // reads it), and runs the durable compaction commit only when
747
+ // `result.compacted`.
748
+ await onEvent({
749
+ type: "compaction_completed",
750
+ result: compactResult,
751
+ basis: rawHistory,
752
+ });
753
+ if (compactResult.exhausted ?? false) {
754
+ return null;
755
+ }
756
+ // Re-inject onto the same base the `compaction_completed` dispatch commits:
757
+ // the compacted messages when the pipeline compacted, the stripped
758
+ // pre-compaction history otherwise.
759
+ return compaction.postCompactionHook({
760
+ history: compactResult.compacted ? compactResult.messages : rawHistory,
761
+ turnContext,
762
+ });
763
+ }
764
+
460
765
  async run(
461
766
  messages: Message[],
462
767
  onEvent: (event: AgentEvent) => void | Promise<void>,
463
- signal?: AbortSignal,
464
- requestId?: string,
465
- onCheckpoint?: (
466
- checkpoint: CheckpointInfo,
467
- ) => CheckpointDecision | Promise<CheckpointDecision>,
468
- callSite?: LLMCallSite,
469
- /**
470
- * Optional per-turn context supplied by the orchestrator. Every pipeline
471
- * invocation inside the loop clones from this value (overwriting only
472
- * `turnIndex`/`requestId`) so middleware sees the real conversation
473
- * identity, trust class, and `contextWindowManager` rather than the
474
- * `"agent-loop"` sentinel used when the loop is instantiated standalone
475
- * in unit tests.
476
- */
477
- turnContext?: TurnContext,
478
- /**
479
- * Optional ad-hoc inference-profile override applied to every LLM call
480
- * the loop issues. When set, each `SendMessageOptions.config` carries
481
- * `overrideProfile = <name>` so the provider's resolver layers
482
- * `llm.profiles[<name>]` between the workspace `activeProfile` and any
483
- * call-site named profile. Missing profile names silently fall through.
484
- * Used by per-conversation pinned profiles to override the workspace
485
- * default for the lifetime of an agent loop run.
486
- */
487
- overrideProfile?: string,
488
- effectiveMaxInputTokens?: number,
489
- resolveOverrideProfile?: () => string | undefined,
490
- resolveEffectiveMaxInputTokens?: () => number | undefined,
491
- ): Promise<Message[]> {
492
- const history = [...messages];
493
- const initialHistoryLength = messages.length;
768
+ options?: AgentLoopRunOptions,
769
+ ): Promise<AgentLoopRunResult> {
770
+ const {
771
+ signal,
772
+ requestId,
773
+ onCheckpoint,
774
+ callSite,
775
+ turnContext,
776
+ overrideProfile,
777
+ resolveOverrideProfile,
778
+ resolveContextWindow,
779
+ compaction,
780
+ mutableLatestUserMessage,
781
+ } = options ?? {};
782
+ let history = [...messages];
783
+ // Index into `history` where this run's appended output begins. It starts
784
+ // after the input and resets to the compacted base whenever the loop
785
+ // compacts in place, so `history.slice(newMessagesStart)` is always exactly
786
+ // what the loop produced since the last (re-injected) base.
787
+ let newMessagesStart = history.length;
788
+ let producedVisibleTextThisRun = false;
494
789
  let toolUseTurns = 0;
495
- let consecutiveErrorTurns = 0;
496
- let emptyResponseRetries = 0;
790
+ let stopContinueRetries = 0;
497
791
  let lastLlmCallTime = 0;
792
+ let exitReason: ExitReason | null = null;
793
+ let appendedNewMessages = false;
498
794
  const rlog = requestId ? log.child({ requestId }) : log;
499
795
 
796
+ // Resolve the inference-profile override that applies right now. The
797
+ // optional resolver lets a turn observe a confirmed mid-turn profile switch
798
+ // before the next model call; absent a resolver the turn-start value holds.
799
+ const resolveEffectiveOverrideProfile = (): string | undefined =>
800
+ resolveOverrideProfile ? resolveOverrideProfile() : overrideProfile;
801
+
500
802
  // Per-run substitution map for sensitive output placeholders.
501
803
  // Bindings are accumulated from tool results; placeholders are
502
804
  // resolved in streamed deltas and final assistant message text.
503
805
  const substitutionMap = new Map<string, string>();
504
806
  let streamingPending = "";
505
807
 
506
- // Idempotency guard for `emitExit`. Used so the throw path in the
507
- // empty-response branch can stamp its reason ("empty_response_exhausted")
508
- // before throwing the catch handler that observes the rethrow will
509
- // then attempt to stamp "error" and harmlessly no-op, preserving the
510
- // more specific reason. Also defends against accidental future
511
- // double-emits if a new break site is added without checking this.
808
+ // Idempotency guard for `emitExit`: the first reason stamped wins. A break
809
+ // site that stamps a specific reason before unwinding into the catch
810
+ // handler keeps that reason instead of the generic "error", and the guard
811
+ // also defends against accidental double-emits if a new break site is
812
+ // added without checking this.
512
813
  let exitReasonEmitted = false;
513
814
  const emitExit = async (reason: AgentLoopExitReason): Promise<void> => {
514
815
  if (exitReasonEmitted) return;
@@ -593,6 +894,15 @@ export class AgentLoop {
593
894
  providerConfig.cacheTtl = this.config.cacheTtl;
594
895
  }
595
896
 
897
+ // Cache-anchor signal for volatile latest-user-message turns (e.g.
898
+ // memory-v3 injects its `<memory>` block into the latest user
899
+ // message). Not part of the call-site schema, so it is always sourced
900
+ // from the per-run option regardless of `callSite`. Only set when true
901
+ // so the wire/config stays byte-identical when off.
902
+ if (mutableLatestUserMessage) {
903
+ providerConfig.mutableLatestUserMessage = true;
904
+ }
905
+
596
906
  // Per-call LLM call-site identifier. Surfaces on the per-call
597
907
  // `config.callSite` so `RetryProvider.normalizeSendMessageOptions`
598
908
  // can route through `resolveCallSiteConfig` against
@@ -603,6 +913,16 @@ export class AgentLoop {
603
913
  if (callSite) {
604
914
  providerConfig.callSite = callSite;
605
915
  providerConfig.usageTracking = "manual";
916
+ // Per-conversation seed for deterministic `mix`-profile expansion.
917
+ // Sourced from the orchestrator-supplied turn context's
918
+ // conversationId so every LLM call in a conversation resolves the
919
+ // same mix arm (stable across turns and retries, and across daemon
920
+ // restarts since the seed is the durable conversation id). Absent
921
+ // for standalone `AgentLoop` instances (unit tests / no turnContext)
922
+ // — those fall back to per-call random mix selection.
923
+ if (turnContext?.conversationId) {
924
+ providerConfig.selectionSeed = turnContext.conversationId;
925
+ }
606
926
  }
607
927
 
608
928
  // Per-call inference-profile override. The resolver layers
@@ -610,12 +930,8 @@ export class AgentLoop {
610
930
  // `activeProfile` and any call-site named profile. Threading it on
611
931
  // every send (rather than once at construction) keeps subagents that
612
932
  // share an `AgentLoop` instance but ought to inherit a different
613
- // profile correct — and matches how `callSite` is plumbed. The
614
- // optional resolver lets a turn observe an explicitly confirmed
615
- // profile-session switch before the next model call.
616
- const effectiveOverrideProfile = resolveOverrideProfile
617
- ? resolveOverrideProfile()
618
- : overrideProfile;
933
+ // profile correct — and matches how `callSite` is plumbed.
934
+ const effectiveOverrideProfile = resolveEffectiveOverrideProfile();
619
935
  if (effectiveOverrideProfile) {
620
936
  providerConfig.overrideProfile = effectiveOverrideProfile;
621
937
  }
@@ -658,98 +974,91 @@ export class AgentLoop {
658
974
  // Also strip old AX tree snapshots to keep TTFT from growing
659
975
  // linearly with step count in computer-use sessions.
660
976
  const providerHistory = compactAxTreeHistory(
661
- stripOldImageBlocks(history),
977
+ stripOldMediaBlocks(history),
662
978
  );
663
979
 
664
- // Wrap the provider call in the `llmCall` pipeline so middleware
665
- // contributed by plugins may observe, rewrite, short-circuit, or
666
- // post-process every LLM request. The terminal below is the real
667
- // `provider.sendMessage(...)` call; middleware reach it by calling
668
- // `next(args)`. The default `defaultLlmCallPlugin` contributes a
669
- // passthrough middleware that forwards to `next(args)` — it
670
- // registers at module load and sits at the outermost onion layer,
671
- // so it must yield to keep user-registered `llmCall` middleware
672
- // reachable. Timeout is `null` (`DEFAULT_TIMEOUTS.llmCall`) — the
673
- // provider layer already enforces its own HTTP-level budgets.
674
- //
675
- // The `onEvent` wrapping is kept inside `args.options` so substitution
676
- // and streaming behavior exactly match the pre-pipeline call site.
677
- const llmCallArgs: LLMCallArgs = {
678
- provider: this.provider,
679
- messages: providerHistory,
980
+ // The `onEvent` wrapping below applies sensitive-output placeholder
981
+ // substitution to streamed text while forwarding every other event
982
+ // type through unchanged.
983
+ const providerOptions: SendMessageOptions = {
680
984
  tools: currentTools.length > 0 ? currentTools : undefined,
681
985
  systemPrompt: turnSystemPrompt,
682
- options: {
683
- config: providerConfig,
684
- onEvent: (event) => {
685
- if (event.type === "text_delta") {
686
- // Apply sensitive-output placeholder substitution (chunk-safe)
687
- if (substitutionMap.size > 0) {
688
- const combined = streamingPending + event.text;
689
- const { emit, pending } = applyStreamingSubstitution(
690
- combined,
691
- substitutionMap,
692
- );
693
- streamingPending = pending;
694
- if (emit.length > 0) {
695
- onEvent({ type: "text_delta", text: emit });
696
- }
697
- } else {
698
- onEvent({ type: "text_delta", text: event.text });
986
+ config: providerConfig,
987
+ onEvent: (event) => {
988
+ if (event.type === "text_delta") {
989
+ // Apply sensitive-output placeholder substitution (chunk-safe)
990
+ if (substitutionMap.size > 0) {
991
+ const combined = streamingPending + event.text;
992
+ const { emit, pending } = applyStreamingSubstitution(
993
+ combined,
994
+ substitutionMap,
995
+ );
996
+ streamingPending = pending;
997
+ if (emit.length > 0) {
998
+ onEvent({ type: "text_delta", text: emit });
699
999
  }
700
- } else if (event.type === "thinking_delta") {
701
- onEvent({ type: "thinking_delta", thinking: event.thinking });
702
- } else if (event.type === "tool_use_preview_start") {
703
- onEvent({
704
- type: "tool_use_preview_start",
705
- toolUseId: event.toolUseId,
706
- toolName: event.toolName,
707
- });
708
- } else if (event.type === "input_json_delta") {
709
- onEvent({
710
- type: "input_json_delta",
711
- toolName: event.toolName,
712
- toolUseId: event.toolUseId,
713
- accumulatedJson: event.accumulatedJson,
714
- });
715
- } else if (event.type === "server_tool_start") {
716
- onEvent({
717
- type: "server_tool_start",
718
- name: event.name,
719
- toolUseId: event.toolUseId,
720
- input: event.input,
721
- });
722
- } else if (event.type === "server_tool_complete") {
723
- onEvent({
724
- type: "server_tool_complete",
725
- toolUseId: event.toolUseId,
726
- isError: event.isError,
727
- ...(event.content ? { content: event.content } : {}),
728
- ...(event.resolvedInput
729
- ? { resolvedInput: event.resolvedInput }
730
- : {}),
731
- ...(event.errorCode ? { errorCode: event.errorCode } : {}),
732
- ...(event.errorMessage
733
- ? { errorMessage: event.errorMessage }
734
- : {}),
735
- });
1000
+ } else {
1001
+ onEvent({ type: "text_delta", text: event.text });
736
1002
  }
737
- },
738
- signal,
1003
+ } else if (event.type === "thinking_delta") {
1004
+ onEvent({ type: "thinking_delta", thinking: event.thinking });
1005
+ } else if (event.type === "tool_use_preview_start") {
1006
+ onEvent({
1007
+ type: "tool_use_preview_start",
1008
+ toolUseId: event.toolUseId,
1009
+ toolName: event.toolName,
1010
+ });
1011
+ } else if (event.type === "input_json_delta") {
1012
+ onEvent({
1013
+ type: "input_json_delta",
1014
+ toolName: event.toolName,
1015
+ toolUseId: event.toolUseId,
1016
+ accumulatedJson: event.accumulatedJson,
1017
+ });
1018
+ } else if (event.type === "server_tool_start") {
1019
+ onEvent({
1020
+ type: "server_tool_start",
1021
+ name: event.name,
1022
+ toolUseId: event.toolUseId,
1023
+ input: event.input,
1024
+ });
1025
+ } else if (event.type === "server_tool_complete") {
1026
+ onEvent({
1027
+ type: "server_tool_complete",
1028
+ toolUseId: event.toolUseId,
1029
+ isError: event.isError,
1030
+ ...(event.content ? { content: event.content } : {}),
1031
+ ...(event.resolvedInput
1032
+ ? { resolvedInput: event.resolvedInput }
1033
+ : {}),
1034
+ ...(event.errorCode ? { errorCode: event.errorCode } : {}),
1035
+ ...(event.errorMessage
1036
+ ? { errorMessage: event.errorMessage }
1037
+ : {}),
1038
+ });
1039
+ }
739
1040
  },
1041
+ signal,
740
1042
  };
741
1043
 
742
- // Per-turn pipeline context. When the orchestrator threaded a full
743
- // `turnContext` into `run()`, use it (overwriting `turnIndex` with
744
- // the loop-scoped tool-use iteration) so middleware sees the real
745
- // conversation identity, trust, and `contextWindowManager`. The
746
- // synthesized fallback is only reached by standalone unit-test
747
- // instantiations that never plumb a context through.
748
- const turnCtx = resolveLoopTurnContext(
749
- turnContext,
750
- requestId,
751
- toolUseTurns,
752
- );
1044
+ // Per-turn pipeline context. Real call sites thread a full
1045
+ // `turnContext` into `run()` and it is used directly; standalone
1046
+ // unit-test instantiations that never plumb a context through fall
1047
+ // back to a synthesized placeholder scoped to the tool-use iteration.
1048
+ const turnCtx =
1049
+ turnContext ?? buildLoopTurnContext(requestId, toolUseTurns);
1050
+
1051
+ // Announce the LLM-call boundary so downstream handlers (the
1052
+ // daemon's persistence pipeline) can reserve an empty assistant row
1053
+ // and stamp the resulting `messageId` onto every streaming event the
1054
+ // call emits. Emit as late as possible — after history stripping,
1055
+ // arg construction, and turn-context resolution — so the gap
1056
+ // between "we said the call started" and the actual provider HTTP
1057
+ // call is minimized. Awaited so the row is created and the
1058
+ // `assistant_turn_start` wire event reaches the client BEFORE the
1059
+ // provider starts streaming deltas — the deltas downstream will
1060
+ // carry the freshly-reserved id.
1061
+ await onEvent({ type: "llm_call_started", callSite });
753
1062
 
754
1063
  // Inner try/catch narrows error-recording scope to the provider
755
1064
  // call itself. The outer agent-loop catch (below) wraps the entire
@@ -760,21 +1069,11 @@ export class AgentLoop {
760
1069
  // `llm_request_logs` row, then re-throw so the existing outer catch
761
1070
  // continues to handle abort sync, Sentry capture, the `error` event,
762
1071
  // and the loop break unchanged.
763
- let response: LLMCallResult;
1072
+ let response: ProviderResponse;
764
1073
  try {
765
- response = await runPipeline<LLMCallArgs, LLMCallResult>(
766
- "llmCall",
767
- getMiddlewaresFor("llmCall"),
768
- (args) =>
769
- args.provider.sendMessage(
770
- args.messages,
771
- args.tools,
772
- args.systemPrompt,
773
- args.options,
774
- ),
775
- llmCallArgs,
776
- turnCtx,
777
- DEFAULT_TIMEOUTS.llmCall,
1074
+ response = await this.provider.sendMessage(
1075
+ providerHistory,
1076
+ providerOptions,
778
1077
  );
779
1078
  } catch (llmCallError) {
780
1079
  // Skip recording on abort — the user cancelled the request and
@@ -792,10 +1091,10 @@ export class AgentLoop {
792
1091
  // misrepresent both.
793
1092
  const rawRequest = {
794
1093
  provider: this.provider.name,
795
- messages: llmCallArgs.messages,
796
- tools: llmCallArgs.tools,
797
- systemPrompt: llmCallArgs.systemPrompt,
798
- config: llmCallArgs.options?.config,
1094
+ messages: providerHistory,
1095
+ tools: providerOptions.tools,
1096
+ systemPrompt: providerOptions.systemPrompt,
1097
+ config: providerOptions.config,
799
1098
  };
800
1099
  onEvent({
801
1100
  type: "provider_error",
@@ -862,130 +1161,100 @@ export class AgentLoop {
862
1161
  "LLM call complete",
863
1162
  );
864
1163
 
865
- // Detect empty responses: no user-visible text and no tool calls.
866
- // This can happen when the model fails to produce output after
867
- // receiving a large tool result. Retry once with a nudge before
868
- // the message is persisted.
869
- //
870
- // Only nudge when the model hasn't already delivered text to the user
871
- // earlier in this tool-use chain. If a prior assistant turn in history
872
- // contained visible text (e.g. the model said its piece before calling
873
- // a side-effect tool like `remember`), an empty follow-up is the model
874
- // correctly ending its turn — nudging would mislead it into thinking
875
- // its earlier text didn't land and cause a verbatim re-send.
876
- //
877
- // Note: we check ANY prior assistant turn from this run()
878
- // invocation, not just the most recent one. In multi-step tool-use
879
- // chains (say-something → call-tool → call-another-tool → end),
880
- // the "say-something" text lives on an earlier assistant turn while
881
- // the most recent assistant turn is a pure tool_use with no text.
882
- // Restricting the check to the most recent assistant turn would
883
- // falsely nudge in that case and trigger a duplicate re-send of
884
- // text the user already saw.
885
- //
886
- // Scope the scan to messages appended during this run() call only.
887
- // Assistant text from prior conversation turns (earlier run()
888
- // invocations passed in via `messages`) must NOT suppress the
889
- // nudge — those turns completed long ago and have no bearing on
890
- // whether the current tool-use chain has delivered text yet.
891
- //
892
- // The actual decision (nudge vs. accept vs. error) is delegated to
893
- // the `emptyResponse` plugin pipeline. The pipeline returns a
894
- // decision; the loop carries out the side-effect (pushing the nudge
895
- // or surfacing the error). See `plugins/defaults/empty-response.ts`
896
- // for the default decision logic.
897
- const hasVisibleText = response.content.some(
898
- (block) => block.type === "text" && block.text.trim().length > 0,
899
- );
900
- const priorAssistantHadVisibleText = (() => {
901
- for (let i = history.length - 1; i >= initialHistoryLength; i--) {
902
- const msg = history[i];
903
- if (msg.role !== "assistant") continue;
904
- const hasText = msg.content.some(
905
- (block) =>
906
- block.type === "text" &&
907
- typeof (block as { text?: unknown }).text === "string" &&
908
- (block as { text: string }).text.trim().length > 0,
909
- );
910
- if (hasText) return true;
911
- }
912
- return false;
913
- })();
914
-
915
- const emptyResponseArgs: EmptyResponseArgs = {
916
- responseContent: response.content,
917
- toolUseBlocksLength: toolUseBlocks.length,
918
- toolUseTurns,
919
- emptyResponseRetries,
920
- maxEmptyResponseRetries: MAX_EMPTY_RESPONSE_RETRIES,
921
- priorAssistantHadVisibleText,
922
- };
923
- const emptyResponseCtx = resolveLoopTurnContext(
924
- turnContext,
925
- requestId,
926
- toolUseTurns,
927
- );
928
- const emptyResponseDecision: EmptyResponseDecision = await runPipeline(
929
- "emptyResponse",
930
- getMiddlewaresFor("emptyResponse"),
931
- async (args) => defaultEmptyResponseTerminal(args),
932
- emptyResponseArgs,
933
- emptyResponseCtx,
934
- DEFAULT_TIMEOUTS.emptyResponse,
935
- );
936
-
937
- if (emptyResponseDecision.action === "nudge") {
938
- // Fall back to the canonical nudge text if the plugin returned
939
- // `action: "nudge"` but forgot `nudgeText`. Keeps a misbehaving
940
- // plugin from silently breaking the loop invariant that the
941
- // model sees a coherent prompt.
942
- const nudgeText =
943
- emptyResponseDecision.nudgeText ??
944
- "<system_notice>Your previous response was empty. You must respond to the user with a summary of what you found or did. Do not use any tools — just respond with text.</system_notice>";
945
- emptyResponseRetries++;
1164
+ if (isMaxTokensStopReason(response.stopReason)) {
1165
+ const safeContent = response.content.filter(
1166
+ (block) =>
1167
+ block.type !== "tool_use" &&
1168
+ block.type !== "server_tool_use" &&
1169
+ block.type !== "web_search_tool_result",
1170
+ );
1171
+ const safeAssistantMessage: Message = {
1172
+ role: "assistant",
1173
+ content: safeContent,
1174
+ };
946
1175
  rlog.warn(
947
- { turn: toolUseTurns, retry: emptyResponseRetries },
948
- "Model returned empty response after tool results — retrying",
1176
+ {
1177
+ turn: toolUseTurns,
1178
+ stopReason: response.stopReason,
1179
+ contentBlocks: response.content.length,
1180
+ safeContentBlocks: safeContent.length,
1181
+ toolUseCount: toolUseBlocks.length,
1182
+ },
1183
+ "LLM response reached output token limit",
949
1184
  );
950
- history.push({
951
- role: "user",
952
- content: [{ type: "text", text: nudgeText }],
1185
+ history.push(safeAssistantMessage);
1186
+ appendedNewMessages = true;
1187
+ await onEvent({
1188
+ type: "max_tokens_reached",
1189
+ stopReason: response.stopReason,
1190
+ });
1191
+ await onEvent({
1192
+ type: "message_complete",
1193
+ message: safeAssistantMessage,
953
1194
  });
954
- continue;
1195
+ await emitExit("max_tokens_reached");
1196
+ break;
955
1197
  }
956
1198
 
957
- if (emptyResponseDecision.action === "error") {
958
- rlog.error(
959
- { turn: toolUseTurns, retries: emptyResponseRetries },
960
- "emptyResponse pipeline requested error surface",
961
- );
962
- // Stamp the specific exit reason *before* throwing. The catch
963
- // handler below will see the rethrown error and attempt to stamp
964
- // "error" guarded by `exitReasonEmitted`, that becomes a no-op
965
- // and the more specific reason wins.
966
- await emitExit("empty_response_exhausted");
967
- throw new AssistantError(
968
- "Model returned empty response after tool results",
969
- ErrorCode.INTERNAL_ERROR,
970
- );
1199
+ // The model's "stop" moment: a response with no tool calls is about to
1200
+ // yield to the user. The `stop` hook (below) decides whether to accept
1201
+ // the turn or re-query with a follow-up; `priorAssistantHadVisibleText`
1202
+ // gates the ops log for the post-tool empty case.
1203
+ const hasVisibleText = response.content.some(
1204
+ (block) => block.type === "text" && block.text.trim().length > 0,
1205
+ );
1206
+ const priorAssistantHadVisibleText = producedVisibleTextThisRun;
1207
+ if (hasVisibleText) {
1208
+ producedVisibleTextThisRun = true;
971
1209
  }
972
1210
 
973
- // action === "accept" — fall through. Emit a dedicated log line for
974
- // the specific "empty turn after tool results, retries exhausted"
975
- // case so ops dashboards that grep on this line keep working.
976
- if (
977
- !hasVisibleText &&
978
- toolUseBlocks.length === 0 &&
979
- toolUseTurns > 0 &&
980
- !priorAssistantHadVisibleText
981
- ) {
982
- rlog.error(
983
- { turn: toolUseTurns, retries: emptyResponseRetries },
984
- "Model returned empty response after tool results — retries exhausted",
985
- );
1211
+ if (toolUseBlocks.length === 0) {
1212
+ // The model stopped requesting tools the run's stop boundary. The
1213
+ // `stop` hook decides whether to let the turn end or re-query with a
1214
+ // follow-up turn. It receives the full history and, when it asks to
1215
+ // continue, appends the follow-up turn itself.
1216
+ const stopCtx: StopContext = {
1217
+ conversationId: turnCtx.conversationId,
1218
+ messages: [...history],
1219
+ responseContent: response.content,
1220
+ stopReason: response.stopReason,
1221
+ decision: "stop",
1222
+ logger: rlog,
1223
+ };
1224
+ const finalStopCtx = await runHook(HOOKS.STOP, stopCtx);
1225
+
1226
+ if (finalStopCtx.decision === "continue") {
1227
+ // The loop owns the retry budget: a hook always asks to continue
1228
+ // when a nudge is warranted, and the loop stops anyway once the
1229
+ // budget is spent. This bounds the hook-driven re-query loop.
1230
+ if (stopContinueRetries < MAX_STOP_CONTINUE_RETRIES) {
1231
+ stopContinueRetries++;
1232
+ rlog.warn(
1233
+ { turn: toolUseTurns, retry: stopContinueRetries },
1234
+ "Model returned empty response after tool results — retrying",
1235
+ );
1236
+ history = finalStopCtx.messages;
1237
+ continue;
1238
+ }
1239
+
1240
+ // Budget spent — accept the empty turn. Emit a dedicated log line
1241
+ // for the post-tool empty case so ops dashboards that grep on it
1242
+ // keep working.
1243
+ if (
1244
+ !hasVisibleText &&
1245
+ toolUseTurns > 0 &&
1246
+ !priorAssistantHadVisibleText
1247
+ ) {
1248
+ rlog.error(
1249
+ { turn: toolUseTurns, retries: stopContinueRetries },
1250
+ "Model returned empty response after tool results — retries exhausted",
1251
+ );
1252
+ }
1253
+ }
986
1254
  }
987
1255
 
988
1256
  history.push(assistantMessage);
1257
+ appendedNewMessages = true;
989
1258
 
990
1259
  await onEvent({ type: "message_complete", message: assistantMessage });
991
1260
 
@@ -1015,6 +1284,15 @@ export class AgentLoop {
1015
1284
  }),
1016
1285
  );
1017
1286
  history.push({ role: "user", content: cancelledBlocks });
1287
+ for (const toolUse of toolUseBlocks) {
1288
+ await onEvent({
1289
+ type: "tool_result",
1290
+ toolUseId: toolUse.id,
1291
+ content: "Cancelled by user",
1292
+ isError: true,
1293
+ cancelled: true,
1294
+ });
1295
+ }
1018
1296
  await emitExit("aborted_post_response");
1019
1297
  break;
1020
1298
  }
@@ -1044,14 +1322,6 @@ export class AgentLoop {
1044
1322
  });
1045
1323
  },
1046
1324
  toolUse.id,
1047
- // Forward the loop's resolved `TurnContext` through the
1048
- // executor callback so `ToolExecutor.execute` can thread the
1049
- // real orchestrator context into the `toolExecute` pipeline.
1050
- // Standalone tests that don't wire a `turnContext` into
1051
- // `AgentLoop.run()` pass `undefined` here and the executor
1052
- // falls back to the synthesized placeholder — preserving the
1053
- // existing unit-test behavior.
1054
- turnCtx,
1055
1325
  );
1056
1326
 
1057
1327
  return { toolUse, result };
@@ -1115,61 +1385,39 @@ export class AgentLoop {
1115
1385
  }),
1116
1386
  );
1117
1387
 
1118
- // Pre-emptively truncate oversized tool results to prevent context
1119
- // overflow. The work is delegated to the `toolResultTruncate`
1120
- // plugin pipeline so downstream plugins can swap in a smarter
1121
- // truncation strategy (e.g. a summariser) while the default
1122
- // middleware preserves the historical tail-drop behaviour.
1388
+ // Run the `post-tool-use` hook once per tool result, after the tool
1389
+ // returns and before the result joins the provider-bound history.
1390
+ // The default tool-result-truncate plugin tail-drops oversized output
1391
+ // to fit the context window; user hooks can swap in a smarter strategy
1392
+ // (e.g. a summariser) or observe results for side effects.
1123
1393
  const contextWindowTokens =
1124
- resolveEffectiveMaxInputTokens?.() ??
1125
- effectiveMaxInputTokens ??
1394
+ resolveContextWindow?.().maxInputTokens ??
1126
1395
  this.config.maxInputTokens ??
1127
1396
  180_000;
1128
- const maxChars = calculateMaxToolResultChars(contextWindowTokens);
1129
- const truncateMiddlewares = getMiddlewaresFor("toolResultTruncate");
1130
1397
 
1131
- let truncatedCount = 0;
1132
- const truncatedBlocks: ContentBlock[] = [];
1398
+ const resultBlocks: ContentBlock[] = [];
1399
+ const additionalContextBlocks: ContentBlock[] = [];
1133
1400
  for (const block of rawResultBlocks) {
1134
1401
  if (block.type !== "tool_result") {
1135
- truncatedBlocks.push(block);
1402
+ resultBlocks.push(block);
1136
1403
  continue;
1137
1404
  }
1138
- const toolBlock = block as ToolResultContent;
1139
- if (
1140
- typeof toolBlock.content !== "string" ||
1141
- toolBlock.content.length <= maxChars
1142
- ) {
1143
- truncatedBlocks.push(block);
1144
- continue;
1145
- }
1146
- const pipelineResult = await runPipeline<
1147
- ToolResultTruncateArgs,
1148
- ToolResultTruncateResult
1149
- >(
1150
- "toolResultTruncate",
1151
- truncateMiddlewares,
1152
- async (args) => defaultToolResultTruncateTerminal(args),
1153
- { content: toolBlock.content, maxChars },
1154
- turnCtx,
1155
- DEFAULT_TIMEOUTS.toolResultTruncate,
1156
- );
1157
- if (pipelineResult.truncated) {
1158
- truncatedCount++;
1159
- truncatedBlocks.push({
1160
- ...toolBlock,
1161
- content: pipelineResult.content,
1405
+ const postToolUseCtx: PostToolUseContext = {
1406
+ conversationId: turnCtx.conversationId,
1407
+ toolResponse: block as ToolResultContent,
1408
+ messages: history,
1409
+ maxInputTokens: contextWindowTokens,
1410
+ logger: rlog,
1411
+ };
1412
+ const finalCtx = await runHook(HOOKS.POST_TOOL_USE, postToolUseCtx);
1413
+ resultBlocks.push(finalCtx.toolResponse);
1414
+ if (finalCtx.additionalContext !== undefined) {
1415
+ additionalContextBlocks.push({
1416
+ type: "text",
1417
+ text: finalCtx.additionalContext,
1162
1418
  });
1163
- } else {
1164
- truncatedBlocks.push(block);
1165
1419
  }
1166
1420
  }
1167
- const resultBlocks = truncatedBlocks;
1168
- if (truncatedCount > 0) {
1169
- log.warn(
1170
- `Truncated ${truncatedCount} oversized tool result(s) to prevent context overflow`,
1171
- );
1172
- }
1173
1421
 
1174
1422
  // Emit tool_result events AFTER truncation so downstream consumers
1175
1423
  // (e.g. session persistence) receive the truncated content.
@@ -1221,59 +1469,20 @@ export class AgentLoop {
1221
1469
 
1222
1470
  toolUseTurns++;
1223
1471
 
1224
- // When any tool returned an error, nudge the LLM to retry with
1225
- // corrected parameters instead of ending its turn. Skip the nudge
1226
- // after MAX_CONSECUTIVE_ERROR_NUDGES consecutive error turns
1227
- // (the error is likely unrecoverable at that point). The nudge
1228
- // decision is delegated to the `toolError` plugin pipeline so user
1229
- // plugins can change the text, observe the event, or suppress it.
1230
- const hasToolError = toolResults.some(({ result }) => result.isError);
1231
- if (hasToolError) {
1232
- consecutiveErrorTurns++;
1233
- } else {
1234
- consecutiveErrorTurns = 0;
1235
- }
1236
- const toolErrorArgs: ToolErrorArgs = {
1237
- hasToolError,
1238
- consecutiveErrorTurns,
1239
- maxConsecutiveErrorNudges: MAX_CONSECUTIVE_ERROR_NUDGES,
1240
- };
1241
- const toolErrorCtx: TurnContext = resolveLoopTurnContext(
1242
- turnContext,
1243
- requestId,
1244
- toolUseTurns - 1,
1245
- );
1246
- const toolErrorDecision = await runPipeline<
1247
- ToolErrorArgs,
1248
- ToolErrorDecision
1249
- >(
1250
- "toolError",
1251
- getMiddlewaresFor("toolError"),
1252
- // Terminal: the canonical nudge decision. The default plugin's
1253
- // middleware is a passthrough (so later-registered user plugins
1254
- // aren't shadowed), so this terminal is what actually produces
1255
- // the decision when no user plugin overrides it. Wiring the
1256
- // decision here also ensures the nudge fires for direct
1257
- // AgentLoop callers (tests, benchmarks) that skip
1258
- // `bootstrapPlugins()` and therefore never register the default.
1259
- async (args) => defaultToolErrorTerminal(args),
1260
- toolErrorArgs,
1261
- toolErrorCtx,
1262
- DEFAULT_TIMEOUTS.toolError,
1263
- );
1264
- if (toolErrorDecision.action === "nudge") {
1265
- resultBlocks.push({
1266
- type: "text",
1267
- text: toolErrorDecision.nudgeText,
1268
- });
1269
- }
1472
+ // Append any guidance a post-tool-use hook surfaced via
1473
+ // `additionalContext` (e.g. tool-error retry coaching) as separate
1474
+ // blocks. They join the provider-bound history below but were not part
1475
+ // of the tool_result events emitted above, so the model sees the
1476
+ // guidance while the client-facing and persisted tool output stay the
1477
+ // tool's actual result.
1478
+ resultBlocks.push(...additionalContextBlocks);
1270
1479
 
1271
- // Add tool results as a user message and continue the loop
1480
+ // Add tool results as a user message and continue the loop.
1272
1481
  history.push({ role: "user", content: resultBlocks });
1273
1482
 
1274
1483
  // Invoke checkpoint callback after tool results are in history.
1275
- // The callback may be async the mid-loop budget check delegates
1276
- // to the `tokenEstimate` plugin pipeline, which is asynchronous.
1484
+ // Handoff is offered first so a queued message takes precedence over
1485
+ // the mid-loop budget yield below.
1277
1486
  if (onCheckpoint) {
1278
1487
  const decision = await onCheckpoint({
1279
1488
  turnIndex: toolUseTurns - 1, // 0-based (toolUseTurns was already incremented)
@@ -1281,7 +1490,64 @@ export class AgentLoop {
1281
1490
  hasToolUse: true,
1282
1491
  history,
1283
1492
  });
1284
- if (decision === "yield") {
1493
+ if (decision !== "continue") {
1494
+ exitReason = decision;
1495
+ break;
1496
+ }
1497
+ }
1498
+
1499
+ // Mid-loop budget gate: when overflow recovery is enabled, estimate
1500
+ // the running context size as it approaches the preflight budget.
1501
+ // With a `compaction` hook the loop compacts in place and continues;
1502
+ // without one it yields (`exitReason = "budget"`) so the orchestrator
1503
+ // can recover before the next provider call risks a hard
1504
+ // context-too-large rejection. Keyed off the loop's own
1505
+ // `history.length` (the messages actually in context this turn,
1506
+ // including tool iterations) rather than the durable conversation
1507
+ // count.
1508
+ const contextWindow = resolveContextWindow?.();
1509
+ if (contextWindow?.overflowRecovery.enabled) {
1510
+ const { maxInputTokens, overflowRecovery } = contextWindow;
1511
+ const safetyMargin =
1512
+ history.length > LONG_HISTORY_MESSAGE_THRESHOLD
1513
+ ? Math.max(
1514
+ overflowRecovery.safetyMarginRatio,
1515
+ LONG_HISTORY_SAFETY_MARGIN_FLOOR,
1516
+ )
1517
+ : overflowRecovery.safetyMarginRatio;
1518
+ const preflightBudget = Math.floor(
1519
+ maxInputTokens * (1 - safetyMargin),
1520
+ );
1521
+ const midLoopThreshold =
1522
+ preflightBudget * MID_LOOP_YIELD_THRESHOLD_RATIO;
1523
+ const estimated = this.estimateTokens(history);
1524
+ if (estimated > midLoopThreshold) {
1525
+ if (compaction) {
1526
+ rlog.info(
1527
+ { phase: "mid-loop", estimated, threshold: midLoopThreshold },
1528
+ "Token estimate approaching budget — compacting in place",
1529
+ );
1530
+ const compacted = await this.compact(
1531
+ history,
1532
+ turnCtx,
1533
+ compaction,
1534
+ signal,
1535
+ onEvent,
1536
+ resolveEffectiveOverrideProfile() ?? null,
1537
+ );
1538
+ if (compacted) {
1539
+ history = compacted;
1540
+ // The compacted, re-injected array is the new base; output
1541
+ // produced after this point is what the orchestrator persists.
1542
+ newMessagesStart = history.length;
1543
+ continue;
1544
+ }
1545
+ }
1546
+ rlog.warn(
1547
+ { phase: "mid-loop", estimated, threshold: midLoopThreshold },
1548
+ "Token estimate approaching budget — yielding for compaction",
1549
+ );
1550
+ exitReason = "budget";
1285
1551
  break;
1286
1552
  }
1287
1553
  }
@@ -1300,6 +1566,15 @@ export class AgentLoop {
1300
1566
  }),
1301
1567
  );
1302
1568
  history.push({ role: "user", content: cancelledBlocks });
1569
+ for (const toolUse of toolUseBlocks) {
1570
+ await onEvent({
1571
+ type: "tool_result",
1572
+ toolUseId: toolUse.id,
1573
+ content: "Cancelled by user",
1574
+ isError: true,
1575
+ cancelled: true,
1576
+ });
1577
+ }
1303
1578
  }
1304
1579
  await emitExit("aborted_via_error");
1305
1580
  break;
@@ -1313,11 +1588,9 @@ export class AgentLoop {
1313
1588
  Sentry.captureException(err);
1314
1589
  }
1315
1590
  onEvent({ type: "error", error: err });
1316
- // Catch-block fallback. If the rethrow came from the
1317
- // empty-response throw path above, `emitExit("error")` no-ops
1318
- // because `emitExit("empty_response_exhausted")` already ran
1319
- // before the throw. Otherwise, this is the genuine
1320
- // unhandled-error exit.
1591
+ // Catch-block fallback. A break site that stamped a more specific
1592
+ // reason before unwinding here keeps it; the guard makes this a no-op.
1593
+ // Otherwise this is the genuine unhandled-error exit.
1321
1594
  await emitExit("error");
1322
1595
  break;
1323
1596
  }
@@ -1332,7 +1605,12 @@ export class AgentLoop {
1332
1605
  "Agent loop exited",
1333
1606
  );
1334
1607
 
1335
- return history;
1608
+ return {
1609
+ history,
1610
+ exitReason,
1611
+ appendedNewMessages,
1612
+ newMessages: history.slice(newMessagesStart),
1613
+ };
1336
1614
  }
1337
1615
  }
1338
1616
 
@@ -1433,7 +1711,7 @@ export function compactAxTreeHistory(messages: Message[]): Message[] {
1433
1711
  * turn. Using the last user message unconditionally would leave the most
1434
1712
  * recent tool screenshots unprotected from stripping.
1435
1713
  */
1436
- function stripOldImageBlocks(history: Message[]): Message[] {
1714
+ function stripOldMediaBlocks(history: Message[]): Message[] {
1437
1715
  // Find the last user message that contains tool_result blocks.
1438
1716
  let lastToolResultUserIdx = -1;
1439
1717
  for (let i = history.length - 1; i >= 0; i--) {
@@ -1450,29 +1728,32 @@ function stripOldImageBlocks(history: Message[]): Message[] {
1450
1728
  // Keep the most recent tool-result user message intact (current turn)
1451
1729
  if (idx === lastToolResultUserIdx || msg.role !== "user") return msg;
1452
1730
 
1453
- // Check if any tool_result blocks have image contentBlocks
1454
- const hasImages = msg.content.some(
1731
+ // Check if any tool_result blocks carry embedded media (image or audio).
1732
+ const isMedia = (cb: ContentBlock) =>
1733
+ cb.type === "image" || cb.type === "file";
1734
+ const hasMedia = msg.content.some(
1455
1735
  (b) =>
1456
1736
  b.type === "tool_result" &&
1457
- (b as ToolResultContent).contentBlocks?.some(
1458
- (cb) => cb.type === "image",
1459
- ),
1737
+ (b as ToolResultContent).contentBlocks?.some(isMedia),
1460
1738
  );
1461
- if (!hasImages) return msg;
1739
+ if (!hasMedia) return msg;
1462
1740
 
1463
- // Strip images from tool_result blocks, replacing with text marker
1741
+ // Strip media from tool_result blocks, replacing with a text marker. The
1742
+ // model already saw/heard the media in the turn it was captured; resending
1743
+ // the bytes every turn (a 12 MB audio clip isn't optimized like images)
1744
+ // bloats the request until compaction.
1464
1745
  return {
1465
1746
  ...msg,
1466
1747
  content: msg.content.map((b) => {
1467
1748
  if (b.type !== "tool_result") return b;
1468
1749
  const tr = b as ToolResultContent;
1469
- if (!tr.contentBlocks?.some((cb) => cb.type === "image")) return b;
1750
+ if (!tr.contentBlocks?.some(isMedia)) return b;
1470
1751
  return {
1471
1752
  ...tr,
1472
1753
  contentBlocks: undefined,
1473
1754
  content:
1474
1755
  (tr.content || "") +
1475
- "\n[Screenshot was captured and shown previously — image data removed to save context.]",
1756
+ "\n[Media (image/audio) was captured and shown previously — binary data removed to save context.]",
1476
1757
  };
1477
1758
  }),
1478
1759
  };