@vellumai/assistant 0.8.10 → 0.8.11-staging.1

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 (400) hide show
  1. package/bun.lock +62 -1
  2. package/docs/workspace-tools.md +196 -0
  3. package/examples/plugins/echo/README.md +3 -3
  4. package/knip.json +1 -0
  5. package/openapi.yaml +460 -128
  6. package/package.json +2 -1
  7. package/scripts/build-plugin-api.ts +299 -0
  8. package/src/__tests__/agent-loop-callsite-precedence.test.ts +7 -0
  9. package/src/__tests__/agent-loop-compaction-events.test.ts +197 -0
  10. package/src/__tests__/agent-loop-exit-reason.test.ts +93 -96
  11. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +2 -0
  12. package/src/__tests__/agent-loop-output-hooks.test.ts +274 -1
  13. package/src/__tests__/agent-loop-override-profile.test.ts +3 -0
  14. package/src/__tests__/agent-loop-provider-error-recording.test.ts +4 -0
  15. package/src/__tests__/agent-loop-thinking.test.ts +4 -0
  16. package/src/__tests__/agent-loop.test.ts +578 -5
  17. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -1
  18. package/src/__tests__/approval-cascade.test.ts +1 -0
  19. package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
  20. package/src/__tests__/btw-routes.test.ts +0 -1
  21. package/src/__tests__/build-persisted-content.test.ts +75 -1
  22. package/src/__tests__/catalog-install-normalize.test.ts +141 -0
  23. package/src/__tests__/ces-startup-timeout.test.ts +60 -0
  24. package/src/__tests__/compaction-events.test.ts +1 -0
  25. package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
  26. package/src/__tests__/context-overflow-reducer.test.ts +264 -124
  27. package/src/__tests__/context-window-manager-overflow-rung.test.ts +351 -0
  28. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  29. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +13 -5
  30. package/src/__tests__/conversation-agent-loop-overflow.test.ts +284 -455
  31. package/src/__tests__/conversation-agent-loop.test.ts +131 -551
  32. package/src/__tests__/conversation-app-control-instantiation.test.ts +13 -0
  33. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  34. package/src/__tests__/conversation-fork-crud.test.ts +259 -0
  35. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  36. package/src/__tests__/conversation-lifecycle.test.ts +257 -1
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +38 -377
  39. package/src/__tests__/conversation-queue.test.ts +1 -39
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +119 -8
  41. package/src/__tests__/conversation-skill-tools.test.ts +491 -5
  42. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  43. package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
  44. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  45. package/src/__tests__/conversation-store.test.ts +74 -0
  46. package/src/__tests__/conversation-surfaces-app-control.test.ts +4 -1
  47. package/src/__tests__/conversation-tool-setup-attribution.test.ts +323 -0
  48. package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +34 -0
  49. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  50. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  51. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -0
  52. package/src/__tests__/corrected-target.test.ts +93 -0
  53. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -5
  54. package/src/__tests__/credential-execution-tools.test.ts +23 -11
  55. package/src/__tests__/credential-security-invariants.test.ts +6 -1
  56. package/src/__tests__/db-schedule-syntax-migration.test.ts +80 -0
  57. package/src/__tests__/device-id.test.ts +70 -1
  58. package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
  59. package/src/__tests__/empty-response-hook.test.ts +242 -66
  60. package/src/__tests__/external-plugin-loader.test.ts +0 -31
  61. package/src/__tests__/get-skill-detail-audit.test.ts +43 -1
  62. package/src/__tests__/guardian-routing-invariants.test.ts +91 -0
  63. package/src/__tests__/history-repair-hook.test.ts +228 -3
  64. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +254 -9
  66. package/src/__tests__/identity-routes.test.ts +1 -0
  67. package/src/__tests__/image-recovery-hook.test.ts +387 -0
  68. package/src/__tests__/injector-chain.test.ts +5 -4
  69. package/src/__tests__/injector-v3-suppression.test.ts +373 -47
  70. package/src/__tests__/intent-routing.test.ts +7 -0
  71. package/src/__tests__/memory-retrieval-hook.test.ts +117 -15
  72. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  73. package/src/__tests__/oauth-store.test.ts +0 -85
  74. package/src/__tests__/{context-overflow-policy.test.ts → overflow-policy.test.ts} +1 -1
  75. package/src/__tests__/parallel-tool.benchmark.test.ts +4 -0
  76. package/src/__tests__/persist-unsendable-image-downscale.test.ts +29 -9
  77. package/src/__tests__/persist-unsendable-image.test.ts +4 -4
  78. package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
  79. package/src/__tests__/plugin-bootstrap.test.ts +82 -73
  80. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  81. package/src/__tests__/plugin-types.test.ts +0 -8
  82. package/src/__tests__/provider-catalog-visibility.test.ts +1 -9
  83. package/src/__tests__/prune-old-conversations-job.test.ts +99 -0
  84. package/src/__tests__/registry.test.ts +240 -1
  85. package/src/__tests__/require-fresh-approval.test.ts +3 -0
  86. package/src/__tests__/schedule-routes.test.ts +116 -1
  87. package/src/__tests__/schedule-store.test.ts +28 -0
  88. package/src/__tests__/schedule-tools.test.ts +94 -1
  89. package/src/__tests__/server-history-render.test.ts +39 -0
  90. package/src/__tests__/skill-projection-feature-flag.test.ts +13 -0
  91. package/src/__tests__/skill-projection.benchmark.test.ts +25 -7
  92. package/src/__tests__/skills.test.ts +202 -0
  93. package/src/__tests__/slim-skill-category.test.ts +195 -0
  94. package/src/__tests__/strip-memory-injections.test.ts +33 -39
  95. package/src/__tests__/test-support/tool-invocation-seed.ts +79 -0
  96. package/src/__tests__/title-generate-hook.test.ts +9 -7
  97. package/src/__tests__/tool-audit-listener.test.ts +264 -1
  98. package/src/__tests__/tool-error-hook.test.ts +4 -3
  99. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
  100. package/src/__tests__/tool-executor-lifecycle-events.test.ts +273 -0
  101. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
  102. package/src/__tests__/tool-start-timestamp.test.ts +218 -0
  103. package/src/__tests__/tools-get-route.test.ts +202 -0
  104. package/src/__tests__/workspace-tool-loader.test.ts +319 -0
  105. package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
  106. package/src/agent/loop.ts +569 -319
  107. package/src/api/events/tool-result.ts +9 -0
  108. package/src/api/events/tool-use-start.ts +7 -0
  109. package/src/api/index.ts +10 -0
  110. package/src/api/responses/conversation-message.ts +135 -27
  111. package/src/api/responses/memory-v3-selection-log.ts +4 -4
  112. package/src/approvals/guardian-request-resolvers.ts +26 -0
  113. package/src/browser-session/backends/host-bridge.ts +29 -0
  114. package/src/browser-session/index.ts +1 -0
  115. package/src/browser-session/types.ts +5 -1
  116. package/src/cli/commands/__tests__/schedules.test.ts +62 -4
  117. package/src/cli/commands/__tests__/skills.test.ts +53 -0
  118. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  119. package/src/cli/commands/inference-providers.ts +0 -8
  120. package/src/cli/commands/plugins.ts +2 -2
  121. package/src/cli/commands/schedules.ts +27 -4
  122. package/src/cli/commands/skills.ts +187 -146
  123. package/src/cli/commands/tools.ts +106 -0
  124. package/src/cli/lib/__tests__/install-from-github.test.ts +256 -328
  125. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +6 -2
  126. package/src/cli/lib/__tests__/plugin-details.test.ts +10 -16
  127. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +2 -2
  128. package/src/cli/lib/__tests__/search-plugins.test.ts +145 -240
  129. package/src/cli/lib/install-from-github.ts +187 -117
  130. package/src/cli/lib/plugin-catalog-cache.ts +9 -9
  131. package/src/cli/lib/plugin-details.ts +38 -68
  132. package/src/cli/lib/plugin-marketplace.ts +42 -14
  133. package/src/cli/lib/search-plugins.ts +29 -129
  134. package/src/cli/program.ts +2 -0
  135. package/src/config/bundled-skills/acp/SKILL.md +1 -0
  136. package/src/config/bundled-skills/app-builder/SKILL.md +1 -0
  137. package/src/config/bundled-skills/app-control/SKILL.md +1 -0
  138. package/src/config/bundled-skills/computer-use/SKILL.md +1 -0
  139. package/src/config/bundled-skills/contacts/SKILL.md +1 -0
  140. package/src/config/bundled-skills/document-editor/SKILL.md +1 -0
  141. package/src/config/bundled-skills/followups/SKILL.md +1 -0
  142. package/src/config/bundled-skills/image-studio/SKILL.md +1 -0
  143. package/src/config/bundled-skills/media-processing/SKILL.md +1 -0
  144. package/src/config/bundled-skills/messaging/SKILL.md +1 -0
  145. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -0
  146. package/src/config/bundled-skills/playbooks/SKILL.md +1 -0
  147. package/src/config/bundled-skills/schedule/SKILL.md +1 -0
  148. package/src/config/bundled-skills/schedule/TOOLS.json +11 -3
  149. package/src/config/bundled-skills/sequences/SKILL.md +1 -0
  150. package/src/config/bundled-skills/settings/SKILL.md +1 -0
  151. package/src/config/bundled-skills/skill-management/SKILL.md +101 -1
  152. package/src/config/bundled-skills/subagent/SKILL.md +1 -0
  153. package/src/config/bundled-skills/transcribe/SKILL.md +1 -0
  154. package/src/config/env-registry.ts +23 -0
  155. package/src/config/feature-flag-registry.json +17 -81
  156. package/src/config/loader.ts +5 -22
  157. package/src/config/schema.ts +2 -0
  158. package/src/config/schemas/__tests__/compaction-logs.test.ts +56 -0
  159. package/src/config/schemas/__tests__/memory-v2.test.ts +0 -1
  160. package/src/config/schemas/__tests__/memory-v3.test.ts +61 -1
  161. package/src/config/schemas/compaction-logs.ts +79 -0
  162. package/src/config/schemas/memory-v2.ts +0 -8
  163. package/src/config/schemas/memory-v3.ts +104 -33
  164. package/src/config/seed-inference-profiles.ts +1 -1
  165. package/src/config/skills.ts +117 -47
  166. package/src/context/compactor.ts +11 -0
  167. package/src/context/strip-injections.ts +38 -4
  168. package/src/credential-execution/feature-gates.ts +0 -21
  169. package/src/credential-execution/startup-timeout.ts +32 -4
  170. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +18 -0
  171. package/src/daemon/conversation-agent-loop-handlers.ts +140 -91
  172. package/src/daemon/conversation-agent-loop.ts +123 -658
  173. package/src/daemon/conversation-error.ts +6 -33
  174. package/src/daemon/conversation-lifecycle.ts +1 -1
  175. package/src/daemon/conversation-runtime-assembly.ts +183 -22
  176. package/src/daemon/conversation-skill-tools.ts +137 -8
  177. package/src/daemon/conversation-slash.ts +0 -14
  178. package/src/daemon/conversation-store.ts +2 -19
  179. package/src/daemon/conversation-tool-setup.ts +87 -1
  180. package/src/daemon/conversation.ts +119 -50
  181. package/src/daemon/external-plugins-bootstrap.ts +36 -95
  182. package/src/daemon/handlers/config-channels.ts +11 -2
  183. package/src/daemon/handlers/shared.ts +9 -1
  184. package/src/daemon/handlers/skills.ts +10 -4
  185. package/src/daemon/host-app-control-proxy.ts +72 -57
  186. package/src/daemon/host-browser-proxy.ts +117 -22
  187. package/src/daemon/lifecycle.ts +34 -5
  188. package/src/daemon/message-protocol.ts +0 -7
  189. package/src/daemon/message-types/schedules.ts +1 -0
  190. package/src/daemon/message-types/skills.ts +17 -0
  191. package/src/daemon/providers-setup.ts +3 -0
  192. package/src/daemon/server.ts +3 -3
  193. package/src/daemon/tool-setup-types.ts +9 -3
  194. package/src/daemon/trust-context.ts +23 -0
  195. package/src/daemon/workspace-tools-watcher.ts +324 -0
  196. package/src/events/tool-audit-listener.ts +78 -16
  197. package/src/events/tool-metrics-listener.ts +2 -5
  198. package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +227 -0
  199. package/src/memory/__tests__/conversation-queries.test.ts +176 -0
  200. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +20 -32
  201. package/src/memory/compaction-log-writer-clickhouse.ts +418 -0
  202. package/src/memory/conversation-crud.ts +134 -3
  203. package/src/memory/conversation-queries.ts +64 -7
  204. package/src/memory/db-init.ts +14 -0
  205. package/src/memory/embedding-backend.test.ts +130 -1
  206. package/src/memory/embedding-backend.ts +79 -106
  207. package/src/memory/embedding-gemini.ts +5 -0
  208. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +12 -0
  209. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +19 -0
  210. package/src/memory/graph/conversation-graph-memory.ts +36 -25
  211. package/src/memory/graph/tool-handlers.ts +3 -0
  212. package/src/memory/job-handlers/cleanup.ts +3 -1
  213. package/src/memory/jobs-store.ts +0 -28
  214. package/src/memory/jobs-worker.ts +10 -22
  215. package/src/memory/memory-marker.ts +29 -0
  216. package/src/memory/memory-retrospective-startup-cleanup.ts +1 -1
  217. package/src/memory/migrations/268-add-memory-v3-selections.ts +6 -0
  218. package/src/memory/migrations/270-schedule-description.ts +36 -0
  219. package/src/memory/migrations/275-tool-invocations-add-skill-id.test.ts +81 -0
  220. package/src/memory/migrations/275-tool-invocations-add-skill-id.ts +20 -0
  221. package/src/memory/migrations/276-tool-invocations-created-at-id-index.test.ts +68 -0
  222. package/src/memory/migrations/276-tool-invocations-created-at-id-index.ts +20 -0
  223. package/src/memory/migrations/277-add-memory-v3-ever-injected.ts +29 -0
  224. package/src/memory/migrations/278-tool-invocations-telemetry-columns.test.ts +96 -0
  225. package/src/memory/migrations/278-tool-invocations-telemetry-columns.ts +39 -0
  226. package/src/memory/migrations/279-create-skill-loaded-events.test.ts +84 -0
  227. package/src/memory/migrations/279-create-skill-loaded-events.ts +26 -0
  228. package/src/memory/migrations/280-conversations-surfaced-at.test.ts +88 -0
  229. package/src/memory/migrations/280-conversations-surfaced-at.ts +24 -0
  230. package/src/memory/migrations/index.ts +10 -0
  231. package/src/memory/migrations/registry.ts +8 -0
  232. package/src/memory/schema/conversations.ts +16 -0
  233. package/src/memory/schema/infrastructure.ts +26 -0
  234. package/src/memory/skill-loaded-events-store.test.ts +160 -0
  235. package/src/memory/skill-loaded-events-store.ts +95 -0
  236. package/src/memory/tool-executed-events-store.test.ts +219 -0
  237. package/src/memory/tool-executed-events-store.ts +102 -0
  238. package/src/memory/tool-usage-store.ts +15 -3
  239. package/src/memory/v2/__tests__/consolidation-job.test.ts +117 -12
  240. package/src/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +189 -0
  241. package/src/memory/v2/__tests__/injected-block-slugs.test.ts +90 -0
  242. package/src/memory/v2/__tests__/page-store.test.ts +33 -0
  243. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +88 -15
  244. package/src/memory/v2/activation-store.ts +50 -1
  245. package/src/memory/v2/consolidation-job.ts +113 -29
  246. package/src/memory/v2/injected-block-slugs.ts +79 -0
  247. package/src/memory/v2/injection.ts +6 -1
  248. package/src/memory/v2/prompts/consolidation.ts +414 -13
  249. package/src/memory/v2/router.ts +2 -28
  250. package/src/memory/v2/static-context.ts +1 -1
  251. package/src/memory/v2/types.ts +16 -0
  252. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  253. package/src/notifications/access-request-copy.ts +298 -0
  254. package/src/notifications/adapters/slack.ts +3 -3
  255. package/src/notifications/adapters/telegram.ts +2 -1
  256. package/src/notifications/copy-composer.ts +49 -267
  257. package/src/notifications/decision-engine.ts +16 -35
  258. package/src/notifications/home-feed-side-effect.ts +1 -6
  259. package/src/oauth/oauth-store.ts +0 -9
  260. package/src/permissions/checker.test.ts +83 -1
  261. package/src/permissions/checker.ts +25 -2
  262. package/src/permissions/gateway-threshold-reader.test.ts +182 -0
  263. package/src/permissions/gateway-threshold-reader.ts +80 -0
  264. package/src/platform/client.ts +1 -3
  265. package/src/platform/feature-gate.ts +3 -12
  266. package/src/plugin-api/constants.ts +4 -2
  267. package/src/plugin-api/index.ts +63 -11
  268. package/src/plugin-api/types.ts +236 -71
  269. package/src/plugins/defaults/compaction/compact.ts +66 -2
  270. package/src/plugins/defaults/compaction/context-overflow-reducer.ts +240 -32
  271. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  272. package/src/{daemon/context-overflow-policy.ts → plugins/defaults/compaction/overflow-policy.ts} +1 -1
  273. package/src/plugins/defaults/compaction/window-manager.ts +303 -1
  274. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +173 -0
  275. package/src/plugins/defaults/empty-response/hooks/stop.ts +11 -115
  276. package/src/plugins/defaults/empty-response/nudge-state-store.ts +46 -0
  277. package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +50 -0
  278. package/src/plugins/defaults/history-repair/hooks/stop.ts +22 -0
  279. package/src/plugins/defaults/history-repair/repair-state-store.ts +51 -0
  280. package/src/plugins/defaults/history-repair/terminal.ts +39 -2
  281. package/src/plugins/defaults/image-recovery/detect.ts +25 -0
  282. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +73 -0
  283. package/src/plugins/defaults/image-recovery/hooks/stop.ts +22 -0
  284. package/src/plugins/defaults/image-recovery/image-recovery-state-store.ts +48 -0
  285. package/src/plugins/defaults/image-recovery/package.json +14 -0
  286. package/src/{daemon/persist-unsendable-image.ts → plugins/defaults/image-recovery/recover.ts} +67 -14
  287. package/src/plugins/defaults/index.ts +71 -5
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +76 -112
  289. package/src/plugins/defaults/memory-retrieval/hooks/{user-prompt-submit-temp.ts → user-prompt-submit.ts} +83 -74
  290. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +14 -8
  291. package/src/plugins/defaults/memory-retrieval/injectors.ts +2 -18
  292. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  293. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1157 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +683 -0
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +161 -140
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +160 -0
  297. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +335 -316
  298. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +145 -53
  299. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +38 -1
  300. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +18 -8
  301. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +112 -71
  302. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +192 -51
  303. package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +4 -16
  304. package/src/plugins/defaults/memory-v3-shadow/card.test.ts +173 -0
  305. package/src/plugins/defaults/memory-v3-shadow/card.ts +116 -0
  306. package/src/plugins/defaults/memory-v3-shadow/core-set.test.ts +104 -0
  307. package/src/plugins/defaults/memory-v3-shadow/core-set.ts +59 -0
  308. package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.test.ts +305 -0
  309. package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.ts +278 -0
  310. package/src/plugins/defaults/memory-v3-shadow/hot-set.test.ts +138 -0
  311. package/src/plugins/defaults/memory-v3-shadow/hot-set.ts +85 -0
  312. package/src/plugins/defaults/memory-v3-shadow/injector.ts +331 -24
  313. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +119 -13
  314. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +169 -114
  315. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +47 -16
  316. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +144 -66
  317. package/src/plugins/defaults/memory-v3-shadow/prune.test.ts +758 -0
  318. package/src/plugins/defaults/memory-v3-shadow/prune.ts +471 -0
  319. package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +68 -16
  320. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +19 -12
  321. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +96 -43
  322. package/src/plugins/defaults/memory-v3-shadow/types.ts +34 -17
  323. package/src/plugins/defaults/title-generate/hooks/stop.ts +9 -11
  324. package/src/plugins/pipeline.ts +8 -5
  325. package/src/plugins/types.ts +5 -51
  326. package/src/providers/cache-control.ts +26 -0
  327. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -2
  328. package/src/providers/model-catalog.ts +13 -1
  329. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +147 -0
  330. package/src/providers/openai/chat-completions-provider.ts +46 -0
  331. package/src/providers/openai/responses-provider.ts +45 -0
  332. package/src/providers/registry.ts +0 -8
  333. package/src/runtime/__tests__/agent-wake.test.ts +0 -1
  334. package/src/runtime/agent-wake.ts +13 -0
  335. package/src/runtime/routes/__tests__/acp-routes.test.ts +151 -0
  336. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +12 -50
  337. package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +322 -0
  338. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +0 -62
  339. package/src/runtime/routes/__tests__/plugins-routes.test.ts +31 -11
  340. package/src/runtime/routes/acp-routes.test.ts +106 -0
  341. package/src/runtime/routes/acp-routes.ts +248 -2
  342. package/src/runtime/routes/browser-tabs-routes.ts +1 -1
  343. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  344. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +0 -14
  345. package/src/runtime/routes/consolidation-routes.ts +6 -82
  346. package/src/runtime/routes/conversation-list-routes.ts +6 -0
  347. package/src/runtime/routes/conversation-management-routes.ts +71 -0
  348. package/src/runtime/routes/identity-routes.ts +8 -0
  349. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  350. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -45
  351. package/src/runtime/routes/plugins-routes.ts +34 -45
  352. package/src/runtime/routes/schedule-routes.ts +43 -5
  353. package/src/runtime/routes/settings-routes.ts +140 -15
  354. package/src/runtime/routes/skills-routes.ts +18 -6
  355. package/src/runtime/services/__tests__/conversation-serializer.test.ts +140 -0
  356. package/src/runtime/services/conversation-serializer.ts +38 -1
  357. package/src/runtime/verification-outbound-actions.ts +147 -2
  358. package/src/runtime/verification-templates.ts +29 -3
  359. package/src/schedule/schedule-store.ts +19 -0
  360. package/src/skills/catalog-install.ts +77 -13
  361. package/src/tasks/task-scheduler.ts +1 -0
  362. package/src/telemetry/types.ts +66 -1
  363. package/src/telemetry/usage-telemetry-reporter.test.ts +542 -13
  364. package/src/telemetry/usage-telemetry-reporter.ts +213 -20
  365. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +49 -2
  366. package/src/tools/browser/__tests__/browser-status.test.ts +29 -5
  367. package/src/tools/browser/browser-execution.ts +27 -9
  368. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +380 -4
  369. package/src/tools/browser/cdp-client/__tests__/host-bridge-cdp-client.test.ts +107 -0
  370. package/src/tools/browser/cdp-client/__tests__/types.test.ts +6 -1
  371. package/src/tools/browser/cdp-client/factory.ts +217 -17
  372. package/src/tools/browser/cdp-client/host-bridge-cdp-client.ts +67 -0
  373. package/src/tools/browser/cdp-client/types.ts +22 -2
  374. package/src/tools/credential-execution/make-authenticated-request.ts +2 -1
  375. package/src/tools/credential-execution/manage-secure-command-tool.ts +169 -164
  376. package/src/tools/credential-execution/run-authenticated-command.ts +2 -1
  377. package/src/tools/executor.ts +39 -7
  378. package/src/tools/registry.ts +387 -5
  379. package/src/tools/schedule/create.ts +16 -0
  380. package/src/tools/schedule/list.ts +12 -4
  381. package/src/tools/schedule/update.ts +12 -0
  382. package/src/tools/skills/load.ts +11 -6
  383. package/src/tools/terminal/safe-env.ts +2 -0
  384. package/src/tools/types.ts +65 -9
  385. package/src/tools/workspace-tools/loader.ts +673 -0
  386. package/src/usage/attribution.ts +28 -0
  387. package/src/util/device-id.ts +17 -3
  388. package/src/util/platform.ts +16 -0
  389. package/tsconfig.plugin-api.json +13 -0
  390. package/src/__tests__/plugin-external-api.test.ts +0 -68
  391. package/src/__tests__/plugin-skill-contribution.test.ts +0 -355
  392. package/src/daemon/message-types/browser.ts +0 -10
  393. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  394. package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +0 -36
  395. package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +0 -37
  396. package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +0 -106
  397. package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +0 -44
  398. package/src/plugins/defaults/memory-v3-shadow/working-set.ts +0 -91
  399. package/src/plugins/external-api.ts +0 -114
  400. package/src/plugins/plugin-skill-contributions.ts +0 -292
@@ -13,6 +13,7 @@ import type { LoopToolExecutor } from "../agent/loop.js";
13
13
  import type { ServerMessage } from "../daemon/message-protocol.js";
14
14
  import { resetPluginRegistryAndRegisterDefaults } from "../plugins/defaults/index.js";
15
15
  import type { Message, Provider, ToolDefinition } from "../providers/types.js";
16
+ import { ContextOverflowError } from "../providers/types.js";
16
17
 
17
18
  const conversationCrudRealSnapshot = {
18
19
  ...(createRequire(import.meta.url)(
@@ -94,7 +95,7 @@ mock.module("../config/loader.js", () => ({
94
95
  // Token estimator returns a small value by default (well within budget)
95
96
  // so preflight does not trigger unless the test overrides it. Both the
96
97
  // calibrated entry point (`estimatePromptTokens`, which backs the preflight
97
- // overflow gate and the convergence path) and the raw entry point
98
+ // overflow gate and reactive overflow recovery) and the raw entry point
98
99
  // (`estimatePromptTokensRaw`, used by the pre-send calibration capture) are
99
100
  // stubbed so either call site can drive the test.
100
101
  let mockEstimateTokens = 1000;
@@ -114,42 +115,106 @@ mock.module("../context/token-estimator.js", () => ({
114
115
  let mockReducerStepFn:
115
116
  | ((msgs: Message[], cfg: unknown, state: unknown) => unknown)
116
117
  | null = null;
118
+ const makeInitialReducerState = () => ({
119
+ appliedTiers: [] as string[],
120
+ injectionMode: "full" as const,
121
+ exhausted: false,
122
+ });
123
+ const runMockReducer = async (
124
+ msgs: Message[],
125
+ cfg: unknown,
126
+ state: unknown,
127
+ ) => {
128
+ if (mockReducerStepFn) return mockReducerStepFn(msgs, cfg, state);
129
+ return {
130
+ messages: msgs,
131
+ tier: "forced_compaction",
132
+ state: {
133
+ appliedTiers: [
134
+ "forced_compaction",
135
+ "tool_result_truncation",
136
+ "media_stubbing",
137
+ "injection_downgrade",
138
+ ],
139
+ injectionMode: "full",
140
+ exhausted: true,
141
+ },
142
+ estimatedTokens: 1000,
143
+ };
144
+ };
117
145
  mock.module(
118
146
  "../plugins/defaults/compaction/context-overflow-reducer.js",
119
147
  () => ({
120
- createInitialReducerState: () => ({
121
- appliedTiers: [],
122
- injectionMode: "full" as const,
123
- exhausted: false,
124
- }),
125
- reduceContextOverflow: async (
126
- msgs: Message[],
127
- cfg: unknown,
128
- state: unknown,
129
- ) => {
130
- if (mockReducerStepFn) return mockReducerStepFn(msgs, cfg, state);
131
- return {
132
- messages: msgs,
133
- tier: "forced_compaction",
148
+ createInitialReducerState: makeInitialReducerState,
149
+ reduceContextOverflow: runMockReducer,
150
+ }),
151
+ );
152
+
153
+ // Stand-in for `ContextWindowManager`'s turn-scoped overflow ladder. Threads
154
+ // reducer state across a turn's rungs and delegates each rung to the mocked
155
+ // reducer, mirroring `reduceOverflowOneRung` / `resetOverflowRecovery`.
156
+ // `recoverContextOverflow` adapts a rung into the `ContextWindowResult` the
157
+ // agent loop's compaction path consumes, mirroring the real manager's
158
+ // `overflowStepToResult` so the loop sees the rung's reduced history, injection
159
+ // mode, terminal auto-compress flag, and exhaustion.
160
+ function makeOverflowLadderStub(): {
161
+ resetOverflowRecovery: () => void;
162
+ reduceOverflowOneRung: (
163
+ msgs: Message[],
164
+ opts: unknown,
165
+ signal?: AbortSignal,
166
+ ) => Promise<unknown>;
167
+ recoverContextOverflow: (
168
+ msgs: Message[],
169
+ opts: unknown,
170
+ signal?: AbortSignal,
171
+ ) => Promise<unknown>;
172
+ } {
173
+ let state: unknown;
174
+ const reduceOverflowOneRung = async (msgs: Message[], opts: unknown) => {
175
+ if (!state) state = makeInitialReducerState();
176
+ const step = (await runMockReducer(msgs, opts, state)) as {
177
+ state: unknown;
178
+ };
179
+ state = step.state;
180
+ return step;
181
+ };
182
+ return {
183
+ resetOverflowRecovery: () => {
184
+ state = undefined;
185
+ },
186
+ reduceOverflowOneRung,
187
+ recoverContextOverflow: async (msgs: Message[], opts: unknown) => {
188
+ const step = (await reduceOverflowOneRung(msgs, opts)) as {
189
+ messages: Message[];
190
+ estimatedTokens?: number;
134
191
  state: {
135
- appliedTiers: [
136
- "forced_compaction",
137
- "tool_result_truncation",
138
- "media_stubbing",
139
- "injection_downgrade",
140
- ],
141
- injectionMode: "full",
142
- exhausted: true,
143
- },
144
- estimatedTokens: 1000,
192
+ appliedTiers: string[];
193
+ injectionMode: string;
194
+ exhausted: boolean;
195
+ };
196
+ compactionResult?: Record<string, unknown>;
197
+ };
198
+ const base = step.compactionResult ?? {
199
+ compacted: false,
200
+ messages: step.messages,
201
+ };
202
+ return {
203
+ ...base,
204
+ messages: step.messages,
205
+ injectionMode: step.state.injectionMode,
206
+ autoCompressApplied: step.state.appliedTiers.includes(
207
+ "auto_compress_latest_turn",
208
+ ),
209
+ exhausted: step.state.exhausted,
145
210
  };
146
211
  },
147
- }),
148
- );
212
+ };
213
+ }
149
214
 
150
215
  // Policy: default to fail_gracefully
151
216
  let mockOverflowAction: string = "fail_gracefully";
152
- mock.module("../daemon/context-overflow-policy.js", () => ({
217
+ mock.module("../plugins/defaults/compaction/overflow-policy.js", () => ({
153
218
  resolveOverflowAction: () => mockOverflowAction,
154
219
  }));
155
220
 
@@ -431,6 +496,7 @@ mock.module("../plugins/defaults/history-repair/terminal.js", () => ({
431
496
  },
432
497
  }),
433
498
  deepRepairHistory: (msgs: Message[]) => ({ messages: msgs, stats: {} }),
499
+ isRepairableOrderingError: () => false,
434
500
  }));
435
501
 
436
502
  const recordUsageMock = mock(() => {});
@@ -508,11 +574,6 @@ mock.module("../daemon/conversation-error.js", () => ({
508
574
  isContextTooLarge: (msg: string) => /context.?length.?exceeded/i.test(msg),
509
575
  }));
510
576
 
511
- mock.module("../daemon/conversation-slash.js", () => ({
512
- isProviderOrderingError: (msg: string) =>
513
- /ordering|before.*after|messages.*order/i.test(msg),
514
- }));
515
-
516
577
  mock.module("../util/truncate.js", () => ({
517
578
  truncate: (s: string) => s,
518
579
  }));
@@ -716,6 +777,16 @@ function makeCtx(
716
777
 
717
778
  ...ctxOverrides,
718
779
  } as unknown as Conversation;
780
+ // Reactive overflow recovery resolves the turn-scoped reduction ladder off
781
+ // the manager; give every fake manager the ladder methods unless a test
782
+ // supplied its own.
783
+ const manager = ctx.contextWindowManager as unknown as Record<
784
+ string,
785
+ unknown
786
+ >;
787
+ if (typeof manager.reduceOverflowOneRung !== "function") {
788
+ Object.assign(manager, makeOverflowLadderStub());
789
+ }
719
790
  fakeContextWindowManagers.set(conversationId, ctx.contextWindowManager);
720
791
  return ctx;
721
792
  }
@@ -1470,509 +1541,6 @@ describe("session-agent-loop", () => {
1470
1541
  });
1471
1542
  });
1472
1543
 
1473
- describe("context window exhaustion (context-too-large recovery)", () => {
1474
- test("forwards cache-aware compaction usage to recordUsage", async () => {
1475
- const events: ServerMessage[] = [];
1476
- mockEstimateTokens = 120_000;
1477
-
1478
- mockReducerStepFn = (msgs: Message[]) => ({
1479
- messages: msgs,
1480
- tier: "forced_compaction",
1481
- state: {
1482
- appliedTiers: ["forced_compaction"],
1483
- injectionMode: "full",
1484
- exhausted: false,
1485
- },
1486
- estimatedTokens: 5_000,
1487
- compactionResult: {
1488
- compacted: true,
1489
- messages: msgs,
1490
- compactedPersistedMessages: 5,
1491
- summaryText: "Summary of prior conversation",
1492
- previousEstimatedInputTokens: 90_000,
1493
- estimatedInputTokens: 30_000,
1494
- maxInputTokens: 100_000,
1495
- thresholdTokens: 80_000,
1496
- compactedMessages: 10,
1497
- summaryCalls: 2,
1498
- summaryInputTokens: 500,
1499
- summaryOutputTokens: 200,
1500
- summaryModel: "claude-opus-4-6",
1501
- summaryCacheCreationInputTokens: 120,
1502
- summaryCacheReadInputTokens: 340,
1503
- summaryRawResponses: [
1504
- {
1505
- usage: {
1506
- cache_creation: { ephemeral_5m_input_tokens: 120 },
1507
- cache_read_input_tokens: 340,
1508
- },
1509
- },
1510
- ],
1511
- },
1512
- });
1513
-
1514
- // The provider rejects the first call as too large; the convergence
1515
- // reducer compacts and the rerun completes the turn, forwarding the
1516
- // compaction's cache-aware usage to recordUsage.
1517
- const { provider } = createMockProvider([
1518
- new Error("context_length_exceeded"),
1519
- textResponse("recovered"),
1520
- ]);
1521
- const ctx = makeCtx({
1522
- loopProvider: provider,
1523
- contextWindowManager: {
1524
- updateConfig: () => {},
1525
- shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
1526
- maybeCompact: async () => ({ compacted: false }),
1527
- } as unknown as Conversation["contextWindowManager"],
1528
- });
1529
- await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
1530
-
1531
- const compactorCall = recordUsageMock.mock.calls.find(
1532
- (call) => (call as unknown[])[5] === "context_compactor",
1533
- ) as unknown[] | undefined;
1534
- expect(compactorCall).toBeDefined();
1535
-
1536
- const [
1537
- usageCtx,
1538
- inputTokens,
1539
- outputTokens,
1540
- model,
1541
- _onEvent,
1542
- actor,
1543
- reqId,
1544
- cacheCreationInputTokens,
1545
- cacheReadInputTokens,
1546
- rawResponse,
1547
- ] = compactorCall ?? [];
1548
-
1549
- expect(usageCtx).toMatchObject({ conversationId: "test-conv" });
1550
- expect(inputTokens).toBe(500);
1551
- expect(outputTokens).toBe(200);
1552
- expect(model).toBe("claude-opus-4-6");
1553
- expect(actor).toBe("context_compactor");
1554
- expect(reqId).toBe("test-req");
1555
- expect(cacheCreationInputTokens).toBe(120);
1556
- expect(cacheReadInputTokens).toBe(340);
1557
- expect(rawResponse).toEqual({
1558
- usage: {
1559
- cache_creation: { ephemeral_5m_input_tokens: 120 },
1560
- cache_read_input_tokens: 340,
1561
- },
1562
- });
1563
- });
1564
-
1565
- test("convergence loop applies reducer and retries when context-too-large is detected", async () => {
1566
- const events: ServerMessage[] = [];
1567
- let reducerCalled = false;
1568
-
1569
- // Configure reducer to succeed on first call — return reduced messages
1570
- // with a compaction result to trigger the context_compacted event.
1571
- mockReducerStepFn = (msgs: Message[]) => {
1572
- reducerCalled = true;
1573
- return {
1574
- messages: msgs,
1575
- tier: "forced_compaction",
1576
- state: {
1577
- appliedTiers: ["forced_compaction"],
1578
- injectionMode: "full",
1579
- exhausted: false,
1580
- },
1581
- estimatedTokens: 30000,
1582
- compactionResult: {
1583
- compacted: true,
1584
- messages: msgs,
1585
- compactedPersistedMessages: 5,
1586
- summaryText: "Summary of prior conversation",
1587
- previousEstimatedInputTokens: 90000,
1588
- estimatedInputTokens: 30000,
1589
- maxInputTokens: 100000,
1590
- thresholdTokens: 80000,
1591
- compactedMessages: 10,
1592
- summaryCalls: 1,
1593
- summaryInputTokens: 500,
1594
- summaryOutputTokens: 200,
1595
- summaryModel: "mock-model",
1596
- },
1597
- };
1598
- };
1599
-
1600
- // The provider rejects the first call with a context-too-large error,
1601
- // then succeeds once the orchestrator has reduced the context.
1602
- const { provider, calls } = createMockProvider([
1603
- new Error("context_length_exceeded"),
1604
- textResponse("recovered"),
1605
- ]);
1606
-
1607
- const ctx = makeCtx({
1608
- loopProvider: provider,
1609
- contextWindowManager: {
1610
- updateConfig: () => {},
1611
- shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
1612
- maybeCompact: async () => ({ compacted: false }),
1613
- } as unknown as Conversation["contextWindowManager"],
1614
- });
1615
-
1616
- await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
1617
-
1618
- expect(reducerCalled).toBe(true);
1619
- expect(calls.length).toBe(2);
1620
- const compactEvent = events.find((e) => e.type === "context_compacted");
1621
- expect(compactEvent).toBeDefined();
1622
- });
1623
-
1624
- test("emits conversation_error when context stays too large after all recovery attempts", async () => {
1625
- const events: ServerMessage[] = [];
1626
-
1627
- // The provider rejects every call with a context-too-large error, so the
1628
- // orchestrator exhausts its recovery attempts.
1629
- const ctx = makeCtx({
1630
- providerResponses: [new Error("context_length_exceeded")],
1631
- contextWindowManager: {
1632
- updateConfig: () => {},
1633
- shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
1634
- // Compaction succeeds but context is still too large
1635
- maybeCompact: async () => ({
1636
- compacted: true,
1637
- messages: [
1638
- { role: "user", content: [{ type: "text", text: "Hello" }] },
1639
- ] as Message[],
1640
- compactedPersistedMessages: 5,
1641
- summaryText: "Summary",
1642
- previousEstimatedInputTokens: 90000,
1643
- estimatedInputTokens: 85000,
1644
- maxInputTokens: 100000,
1645
- thresholdTokens: 80000,
1646
- compactedMessages: 2,
1647
- summaryCalls: 1,
1648
- summaryInputTokens: 500,
1649
- summaryOutputTokens: 200,
1650
- summaryModel: "mock-model",
1651
- }),
1652
- } as unknown as Conversation["contextWindowManager"],
1653
- });
1654
-
1655
- await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
1656
-
1657
- const conversationError = events.find(
1658
- (e) => e.type === "conversation_error",
1659
- );
1660
- expect(conversationError).toBeDefined();
1661
- });
1662
-
1663
- test("bounded convergence loop applies reducer tiers and recovers", async () => {
1664
- const events: ServerMessage[] = [];
1665
- let reducerCalls = 0;
1666
-
1667
- // Reducer: succeed on first call, returning reduced messages
1668
- mockReducerStepFn = (msgs: Message[]) => {
1669
- reducerCalls++;
1670
- return {
1671
- messages: msgs,
1672
- tier: "forced_compaction",
1673
- state: {
1674
- appliedTiers: ["forced_compaction"],
1675
- injectionMode: "full",
1676
- exhausted: false,
1677
- },
1678
- estimatedTokens: 5000,
1679
- };
1680
- };
1681
-
1682
- // The provider rejects the first call with a context-too-large error,
1683
- // then succeeds once the orchestrator has reduced the context.
1684
- const { provider, calls } = createMockProvider([
1685
- new Error("context_length_exceeded"),
1686
- textResponse("recovered via convergence"),
1687
- ]);
1688
-
1689
- const ctx = makeCtx({
1690
- loopProvider: provider,
1691
- contextWindowManager: {
1692
- updateConfig: () => {},
1693
- shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
1694
- maybeCompact: async () => ({ compacted: false }),
1695
- } as unknown as Conversation["contextWindowManager"],
1696
- });
1697
-
1698
- await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
1699
-
1700
- expect(reducerCalls).toBeGreaterThanOrEqual(1);
1701
- expect(calls.length).toBe(2);
1702
- const conversationError = events.find(
1703
- (e) => e.type === "conversation_error",
1704
- );
1705
- expect(conversationError).toBeUndefined();
1706
- const complete = events.find((e) => e.type === "message_complete");
1707
- expect(complete).toBeDefined();
1708
- });
1709
-
1710
- test("non-interactive auto-compress continues without approval prompt", async () => {
1711
- const events: ServerMessage[] = [];
1712
-
1713
- // Reducer exhausts all tiers
1714
- mockReducerStepFn = (msgs: Message[]) => ({
1715
- messages: msgs,
1716
- tier: "injection_downgrade",
1717
- state: {
1718
- appliedTiers: [
1719
- "forced_compaction",
1720
- "tool_result_truncation",
1721
- "media_stubbing",
1722
- "injection_downgrade",
1723
- ],
1724
- injectionMode: "minimal",
1725
- exhausted: true,
1726
- },
1727
- estimatedTokens: 120000,
1728
- });
1729
-
1730
- mockOverflowAction = "auto_compress_latest_turn";
1731
-
1732
- // The provider rejects the first two calls with context-too-large errors,
1733
- // then succeeds after the emergency auto-compress runs.
1734
- const ctx = makeCtx({
1735
- providerResponses: [
1736
- new Error("context_length_exceeded"),
1737
- new Error("context_length_exceeded"),
1738
- textResponse("auto-recovered"),
1739
- ],
1740
- hasNoClient: true,
1741
- contextWindowManager: {
1742
- updateConfig: () => {},
1743
- shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
1744
- maybeCompact: async () => ({
1745
- compacted: true,
1746
- messages: [
1747
- { role: "user", content: [{ type: "text", text: "Hello" }] },
1748
- ] as Message[],
1749
- compactedPersistedMessages: 3,
1750
- summaryText: "Compressed summary",
1751
- previousEstimatedInputTokens: 120000,
1752
- estimatedInputTokens: 30000,
1753
- maxInputTokens: 100000,
1754
- thresholdTokens: 80000,
1755
- compactedMessages: 5,
1756
- summaryCalls: 1,
1757
- summaryInputTokens: 300,
1758
- summaryOutputTokens: 100,
1759
- summaryModel: "mock-model",
1760
- }),
1761
- } as unknown as Conversation["contextWindowManager"],
1762
- });
1763
-
1764
- await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
1765
-
1766
- // Should not produce conversation_error since auto-compress recovered
1767
- const conversationError = events.find(
1768
- (e) => e.type === "conversation_error",
1769
- );
1770
- expect(conversationError).toBeUndefined();
1771
- const complete = events.find((e) => e.type === "message_complete");
1772
- expect(complete).toBeDefined();
1773
- });
1774
-
1775
- test("emits budget_yield_unrecovered when auto_compress rerun yields at mid-loop budget", async () => {
1776
- // Regression test for the silent-stall failure mode:
1777
- // when every recovery layer has been applied (tier reducer
1778
- // exhausted + auto_compress_latest_turn emergency compaction)
1779
- // and the final `agentLoop.run` STILL yields at the mid-loop
1780
- // budget checkpoint, the orchestrator used to fall through to
1781
- // post-turn cleanup with NO `agent_loop_exit_reason` emitted —
1782
- // the turn just stopped mid-action and `llm_request_logs` showed
1783
- // a NULL exit reason on the final row. We now emit
1784
- // `budget_yield_unrecovered` so the inspector and dashboards can
1785
- // attribute the silent stall.
1786
- const events: ServerMessage[] = [];
1787
-
1788
- // Reducer exhausts all 4 tiers on first call so the convergence
1789
- // loop runs exactly one iteration before falling through to
1790
- // the auto_compress_latest_turn branch.
1791
- mockReducerStepFn = (msgs: Message[]) => ({
1792
- messages: msgs,
1793
- tier: "injection_downgrade",
1794
- state: {
1795
- appliedTiers: [
1796
- "forced_compaction",
1797
- "tool_result_truncation",
1798
- "media_stubbing",
1799
- "injection_downgrade",
1800
- ],
1801
- injectionMode: "minimal",
1802
- exhausted: true,
1803
- },
1804
- estimatedTokens: 120_000,
1805
- });
1806
-
1807
- mockOverflowAction = "auto_compress_latest_turn";
1808
-
1809
- // Sits between the preflight budget (preflightBudget =
1810
- // 100k * 0.95 = 95k — anything above triggers preflight reducer
1811
- // *before* we get to the convergence/auto_compress path under
1812
- // test) and the mid-loop threshold (preflightBudget * 0.85 =
1813
- // ≈80.75k — anything above flips yieldedForBudget on a checkpoint
1814
- // call). 90k satisfies both so the path reaches call 3.
1815
- mockEstimateTokens = 90_000;
1816
-
1817
- // Calls 1 (initial) and 2 (convergence rerun) reject with
1818
- // context-too-large so `contextTooLargeDetected` stays true through the
1819
- // convergence exit and the orchestrator enters the auto_compress branch.
1820
- // Call 3 (the auto_compress rerun) is a tool turn: the loop runs it
1821
- // without a compaction hook, so when its mid-loop budget gate trips on
1822
- // the still-oversized estimate it yields `exitReason = "budget"` rather
1823
- // than recovering — the silent-stall path under test.
1824
- const ctx = makeCtx({
1825
- providerResponses: [
1826
- new Error("context_length_exceeded"),
1827
- new Error("context_length_exceeded"),
1828
- toolUseResponse("t1", "read_file", { path: "/a.txt" }),
1829
- ],
1830
- loopTools: [
1831
- {
1832
- name: "read_file",
1833
- description: "Read a file",
1834
- input_schema: {
1835
- type: "object",
1836
- properties: { path: { type: "string" } },
1837
- },
1838
- },
1839
- ],
1840
- toolExecutor: async () => ({ content: "data", isError: false }),
1841
- hasNoClient: true,
1842
- contextWindowManager: {
1843
- updateConfig: () => {},
1844
- shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
1845
- maybeCompact: async () => ({
1846
- compacted: true,
1847
- messages: [
1848
- { role: "user", content: [{ type: "text", text: "Hello" }] },
1849
- ] as Message[],
1850
- compactedPersistedMessages: 3,
1851
- summaryText: "Compressed summary",
1852
- previousEstimatedInputTokens: 120000,
1853
- estimatedInputTokens: 30000,
1854
- maxInputTokens: 100000,
1855
- thresholdTokens: 80000,
1856
- compactedMessages: 5,
1857
- summaryCalls: 1,
1858
- summaryInputTokens: 300,
1859
- summaryOutputTokens: 100,
1860
- summaryModel: "mock-model",
1861
- }),
1862
- } as unknown as Conversation["contextWindowManager"],
1863
- });
1864
-
1865
- await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
1866
-
1867
- // Observability emit: exit reason was stamped onto the latest
1868
- // llm_request_logs row.
1869
- expect(setAgentLoopExitReasonOnLatestLogMock).toHaveBeenCalledWith(
1870
- "test-conv",
1871
- "budget_yield_unrecovered",
1872
- );
1873
-
1874
- // We did NOT also emit context_too_large — the auto_compress
1875
- // branch resets `contextTooLargeDetected` before its rerun and
1876
- // the rerun's yield-for-budget keeps it false, so the error
1877
- // branch above stays skipped.
1878
- expect(setAgentLoopExitReasonOnLatestLogMock).not.toHaveBeenCalledWith(
1879
- "test-conv",
1880
- "context_too_large",
1881
- );
1882
-
1883
- // User-facing emit: the classified BUDGET_YIELD_UNRECOVERED
1884
- // error is sent to the client so the UI can render a notice
1885
- // instead of leaving the turn looking like a silent ghost. The
1886
- // assistant-side notice persistence is exercised in the overflow
1887
- // suite (`conversation-agent-loop-overflow.test.ts`); this test
1888
- // owns the observability + emit contract.
1889
- const conversationError = events.find(
1890
- (e) => e.type === "conversation_error",
1891
- );
1892
- expect(conversationError).toBeDefined();
1893
- if (conversationError && "code" in conversationError) {
1894
- expect(conversationError.code).toBe("BUDGET_YIELD_UNRECOVERED");
1895
- expect(conversationError.retryable).toBe(true);
1896
- expect(conversationError.errorCategory).toBe(
1897
- "budget_yield_unrecovered",
1898
- );
1899
- } else {
1900
- throw new Error("conversation_error missing `code` field");
1901
- }
1902
- });
1903
-
1904
- test("recovery loop is bounded by maxAttempts", async () => {
1905
- const events: ServerMessage[] = [];
1906
- let reducerCalls = 0;
1907
-
1908
- // Reducer never exhausts — always returns non-exhausted state
1909
- // but context always stays too large
1910
- mockReducerStepFn = (msgs: Message[]) => {
1911
- reducerCalls++;
1912
- return {
1913
- messages: msgs,
1914
- tier: "forced_compaction",
1915
- state: {
1916
- appliedTiers: ["forced_compaction"],
1917
- injectionMode: "full",
1918
- exhausted: false,
1919
- },
1920
- estimatedTokens: 120000,
1921
- };
1922
- };
1923
-
1924
- // The provider rejects every call with a context-too-large error, so the
1925
- // orchestrator keeps retrying until it hits the attempt ceiling.
1926
- const ctx = makeCtx({
1927
- providerResponses: [new Error("context_length_exceeded")],
1928
- contextWindowManager: {
1929
- updateConfig: () => {},
1930
- shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
1931
- maybeCompact: async () => ({ compacted: false }),
1932
- } as unknown as Conversation["contextWindowManager"],
1933
- });
1934
-
1935
- await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
1936
-
1937
- // maxAttempts is 3 — reducer should be called at most 3 times
1938
- expect(reducerCalls).toBeLessThanOrEqual(3);
1939
- });
1940
- });
1941
-
1942
- describe("provider ordering error retry", () => {
1943
- test("retries with deep repair when ordering error is detected", async () => {
1944
- const events: ServerMessage[] = [];
1945
-
1946
- // The provider rejects the first call with an ordering error, then
1947
- // succeeds once the orchestrator's deep repair re-sends the turn.
1948
- const { provider, calls } = createMockProvider([
1949
- new Error("messages ordering error"),
1950
- textResponse("fixed"),
1951
- ]);
1952
-
1953
- const ctx = makeCtx({ loopProvider: provider });
1954
- await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
1955
-
1956
- expect(calls.length).toBe(2);
1957
- });
1958
-
1959
- test("emits deferred ordering error when retry also fails", async () => {
1960
- const events: ServerMessage[] = [];
1961
-
1962
- // The provider rejects every call with an ordering error, so even the
1963
- // deep-repair retry fails and the orchestrator surfaces the error.
1964
- const ctx = makeCtx({
1965
- providerResponses: [new Error("messages ordering error")],
1966
- });
1967
- await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
1968
-
1969
- const conversationError = events.find(
1970
- (e) => e.type === "conversation_error",
1971
- );
1972
- expect(conversationError).toBeDefined();
1973
- });
1974
- });
1975
-
1976
1544
  describe("checkpoint handoff (infinite loop prevention)", () => {
1977
1545
  test("yields at checkpoint when canHandoffAtCheckpoint returns true", async () => {
1978
1546
  const events: ServerMessage[] = [];
@@ -2591,8 +2159,8 @@ describe("session-agent-loop", () => {
2591
2159
  // Indexer/projector mocks default to no-op; no finalized row in this
2592
2160
  // test, so `mockMessageById` stays null.
2593
2161
 
2594
- // A single reducer tier converges the oversized context so the
2595
- // orchestrator re-enters the loop after the first call fails.
2162
+ // A single reducer rung reduces the oversized context so the loop
2163
+ // re-enters after the first call's overflow rejection.
2596
2164
  mockReducerStepFn = (msgs: Message[]) => ({
2597
2165
  messages: msgs,
2598
2166
  tier: "forced_compaction",
@@ -2606,11 +2174,16 @@ describe("session-agent-loop", () => {
2606
2174
 
2607
2175
  // GIVEN a real loop whose first call rejects with context-too-large
2608
2176
  // (reserving msg-strand-A but never finalizing it), then recovers via
2609
- // convergence on re-entry. The re-entry's `llm_call_started` must
2610
- // delete the stranded msg-strand-A before reserving msg-strand-B.
2177
+ // the reactive overflow ladder on re-entry. The re-entry's
2178
+ // `llm_call_started` must delete the stranded msg-strand-A before
2179
+ // reserving msg-strand-B.
2611
2180
  const ctx = makeCtx({
2612
2181
  providerResponses: [
2613
- new Error("context_length_exceeded"),
2182
+ new ContextOverflowError(
2183
+ "context_length_exceeded: 250000 tokens > 200000 maximum",
2184
+ "mock-provider",
2185
+ { actualTokens: 250_000, maxTokens: 200_000 },
2186
+ ),
2614
2187
  textResponse("retry succeeded"),
2615
2188
  ],
2616
2189
  contextWindowManager: {
@@ -3570,7 +3143,7 @@ describe("session-agent-loop", () => {
3570
3143
  });
3571
3144
 
3572
3145
  describe("compaction-strip marker persistence", () => {
3573
- test("records historyStrippedAt when convergence strip runs", async () => {
3146
+ test("records historyStrippedAt when overflow-recovery strip runs", async () => {
3574
3147
  // Reducer: succeed on first call, returning reduced messages.
3575
3148
  mockReducerStepFn = (msgs: Message[]) => ({
3576
3149
  messages: msgs,
@@ -3583,15 +3156,18 @@ describe("session-agent-loop", () => {
3583
3156
  estimatedTokens: 5000,
3584
3157
  });
3585
3158
 
3586
- // GIVEN a real loop that appends a tool turn (so the run reports
3587
- // `appendedNewMessages`) and then rejects with a context-too-large
3588
- // error on the following call — the orchestrator strips that appended
3589
- // history during its bounded convergence path before a final call
3590
- // recovers.
3159
+ // GIVEN a real loop that appends a tool turn and then rejects with a
3160
+ // context-too-large error on the following call — reactive overflow
3161
+ // recovery strips that appended history when it compacts before a final
3162
+ // call recovers.
3591
3163
  const ctx = makeCtx({
3592
3164
  providerResponses: [
3593
3165
  toolUseResponse("t1", "file_read", {}),
3594
- new Error("context_length_exceeded"),
3166
+ new ContextOverflowError(
3167
+ "context_length_exceeded: 250000 tokens > 200000 maximum",
3168
+ "mock-provider",
3169
+ { actualTokens: 250_000, maxTokens: 200_000 },
3170
+ ),
3595
3171
  textResponse("recovered"),
3596
3172
  ],
3597
3173
  loopTools: [
@@ -3609,7 +3185,7 @@ describe("session-agent-loop", () => {
3609
3185
  } as unknown as Conversation["contextWindowManager"],
3610
3186
  });
3611
3187
 
3612
- // WHEN the orchestrator runs the turn to completion
3188
+ // WHEN the loop runs the turn to completion
3613
3189
  await runAgentLoopImpl(ctx, "hello", "msg-1", () => {});
3614
3190
 
3615
3191
  const stripCalls = setConversationHistoryStrippedAtMock.mock.calls.filter(
@@ -3636,12 +3212,16 @@ describe("session-agent-loop", () => {
3636
3212
 
3637
3213
  // GIVEN a real loop that appends a tool turn and then rejects with a
3638
3214
  // context-too-large error on the following call, driving the
3639
- // convergence strip whose marker-write helper is stubbed to throw,
3215
+ // overflow-recovery strip whose marker-write helper is stubbed to throw,
3640
3216
  // before a final call recovers.
3641
3217
  const ctx = makeCtx({
3642
3218
  providerResponses: [
3643
3219
  toolUseResponse("t1", "file_read", {}),
3644
- new Error("context_length_exceeded"),
3220
+ new ContextOverflowError(
3221
+ "context_length_exceeded: 250000 tokens > 200000 maximum",
3222
+ "mock-provider",
3223
+ { actualTokens: 250_000, maxTokens: 200_000 },
3224
+ ),
3645
3225
  textResponse("recovered"),
3646
3226
  ],
3647
3227
  loopTools: [