@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
@@ -17,6 +17,7 @@ import {
17
17
  addMessage,
18
18
  clearAll,
19
19
  createConversation,
20
+ deleteConversation,
20
21
  deleteLastExchange,
21
22
  getConversation,
22
23
  getMessages,
@@ -24,6 +25,7 @@ import {
24
25
  import { isLastUserMessageToolResult } from "../memory/conversation-queries.js";
25
26
  import { getDb } from "../memory/db-connection.js";
26
27
  import { initializeDb } from "../memory/db-init.js";
28
+ import { skillLoadedEvents } from "../memory/schema.js";
27
29
  // Initialize db once before all tests
28
30
  initializeDb();
29
31
 
@@ -351,6 +353,78 @@ describe("attachment orphan cleanup", () => {
351
353
  expect(linkCount.c).toBe(0);
352
354
  });
353
355
 
356
+ test("clearAll removes skill_loaded_events", async () => {
357
+ // Privacy posture: Clear All wins over unflushed telemetry — the
358
+ // skill_loaded_events rows (conversation id, skill name, model
359
+ // attribution) must not survive the wipe and ship later.
360
+ getDb().delete(skillLoadedEvents).run();
361
+ const conv = createConversation("test");
362
+ getDb()
363
+ .insert(skillLoadedEvents)
364
+ .values([
365
+ {
366
+ id: "sle-clear-1",
367
+ createdAt: 1000,
368
+ conversationId: conv.id,
369
+ skillName: "web-research",
370
+ },
371
+ // Rows without a conversation are wiped too.
372
+ { id: "sle-clear-2", createdAt: 2000, skillName: "tasks" },
373
+ ])
374
+ .run();
375
+
376
+ await clearAll();
377
+
378
+ expect(getDb().select().from(skillLoadedEvents).all()).toHaveLength(0);
379
+ });
380
+
381
+ test("deleteConversation removes that conversation's skill_loaded_events", async () => {
382
+ getDb().delete(skillLoadedEvents).run();
383
+ const conv = createConversation("doomed");
384
+ await addMessage(conv.id, "user", "hello");
385
+ const other = createConversation("kept");
386
+ getDb()
387
+ .insert(skillLoadedEvents)
388
+ .values([
389
+ {
390
+ id: "sle-del-1",
391
+ createdAt: 1000,
392
+ conversationId: conv.id,
393
+ skillName: "web-research",
394
+ },
395
+ {
396
+ id: "sle-del-2",
397
+ createdAt: 2000,
398
+ conversationId: other.id,
399
+ skillName: "tasks",
400
+ },
401
+ ])
402
+ .run();
403
+
404
+ deleteConversation(conv.id);
405
+
406
+ const remaining = getDb().select().from(skillLoadedEvents).all();
407
+ expect(remaining.map((r) => r.id)).toEqual(["sle-del-2"]);
408
+ });
409
+
410
+ test("deleteConversation removes skill_loaded_events when the conversation has no messages", () => {
411
+ getDb().delete(skillLoadedEvents).run();
412
+ const conv = createConversation("empty");
413
+ getDb()
414
+ .insert(skillLoadedEvents)
415
+ .values({
416
+ id: "sle-del-empty",
417
+ createdAt: 1000,
418
+ conversationId: conv.id,
419
+ skillName: "web-research",
420
+ })
421
+ .run();
422
+
423
+ deleteConversation(conv.id);
424
+
425
+ expect(getDb().select().from(skillLoadedEvents).all()).toHaveLength(0);
426
+ });
427
+
354
428
  test("deleteLastExchange does not delete unlinked user uploads", async () => {
355
429
  const conv = createConversation("test");
356
430
  await addMessage(conv.id, "user", "hello");
@@ -511,6 +511,7 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
511
511
  _setActiveAppControlSession({
512
512
  conversationId: "conv-1",
513
513
  app: "com.example.editor",
514
+ targetClientId: "client-b",
514
515
  });
515
516
 
516
517
  const resultPromise = surfaceProxyResolver(ctx, "app_control_observe", {
@@ -595,6 +596,7 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
595
596
  _setActiveAppControlSession({
596
597
  conversationId: "conv-1",
597
598
  app: "com.example.editor",
599
+ targetClientId: "client-mine",
598
600
  });
599
601
 
600
602
  const resultPromise = surfaceProxyResolver(ctx, "app_control_observe", {
@@ -617,7 +619,7 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
617
619
  proxy.dispose();
618
620
  });
619
621
 
620
- test("single same-user client with no target proceeds without forcing targetClientId", async () => {
622
+ test("single same-user client with no target resolves to that client", async () => {
621
623
  mockHubClients = [
622
624
  {
623
625
  clientId: "only-client",
@@ -630,6 +632,7 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
630
632
  _setActiveAppControlSession({
631
633
  conversationId: "conv-1",
632
634
  app: "com.example.editor",
635
+ targetClientId: "only-client",
633
636
  });
634
637
 
635
638
  const resultPromise = surfaceProxyResolver(ctx, "app_control_observe", {
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Tests for conversation model attribution threading in
3
+ * conversation-tool-setup.ts: `resolveConversationAttribution` must mirror
4
+ * the agent-loop usage path (the current turn's call site — defaulting to
5
+ * mainAgent — plus the per-turn override profile), `createToolExecutor`
6
+ * must stamp the snapshot onto the ToolContext handed to the executor, and
7
+ * attribution resolution failures must never break tool execution.
8
+ */
9
+
10
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
11
+
12
+ import type { ToolSetupContext } from "../daemon/conversation-tool-setup.js";
13
+ import type { SurfaceData, SurfaceType } from "../daemon/message-protocol.js";
14
+ import type { PermissionPrompter } from "../permissions/prompter.js";
15
+ import type { SecretPrompter } from "../permissions/secret-prompter.js";
16
+ import type { ToolExecutor } from "../tools/executor.js";
17
+ import type { ToolContext, ToolExecutionResult } from "../tools/types.js";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Module mocks (must precede the import of the module under test)
21
+ // ---------------------------------------------------------------------------
22
+
23
+ let mockLlmConfig: Record<string, unknown> = {};
24
+ let configThrows = false;
25
+
26
+ // Non-llm fields used by other conversation-tool-setup consumers (e.g.
27
+ // createResolveToolsCallback reads `tools.exclude`), so test files sharing
28
+ // this process keep working against the mocked loader.
29
+ const baseConfig = {
30
+ tools: { exclude: [] as string[] },
31
+ timeouts: {
32
+ shellDefaultTimeoutSec: 120,
33
+ shellMaxTimeoutSec: 600,
34
+ permissionTimeoutSec: 300,
35
+ toolExecutionTimeoutSec: 600,
36
+ },
37
+ services: {},
38
+ };
39
+
40
+ function mockGetConfig() {
41
+ if (configThrows) throw new Error("config unavailable");
42
+ return { ...baseConfig, llm: mockLlmConfig };
43
+ }
44
+
45
+ mock.module("../config/loader.js", () => ({
46
+ getConfig: mockGetConfig,
47
+ loadConfig: mockGetConfig,
48
+ invalidateConfigCache: () => {},
49
+ loadRawConfig: () => ({}),
50
+ saveRawConfig: () => {},
51
+ getNestedValue: () => undefined,
52
+ setNestedValue: () => {},
53
+ }));
54
+
55
+ mock.module("../runtime/assistant-event-hub.js", () => ({
56
+ broadcastMessage: mock(() => {}),
57
+ }));
58
+
59
+ mock.module("../daemon/conversation-surfaces.js", () => ({
60
+ refreshSurfacesForApp: mock(() => {}),
61
+ surfaceProxyResolver: mock(() =>
62
+ Promise.resolve({ content: "", isError: false }),
63
+ ),
64
+ }));
65
+
66
+ mock.module("../services/published-app-updater.js", () => ({
67
+ updatePublishedAppDeployment: mock(() => Promise.resolve()),
68
+ }));
69
+
70
+ mock.module("../tools/browser/browser-screencast.js", () => ({
71
+ registerConversationSender: mock(() => {}),
72
+ }));
73
+
74
+ mock.module("../memory/app-store.js", () => ({
75
+ getApp: mock(() => null),
76
+ getAppDirPath: mock(() => "/tmp/test-apps/dummy"),
77
+ isMultifileApp: mock(() => false),
78
+ getAppsDir: mock(() => "/tmp/test-apps"),
79
+ resolveAppIdByDirName: mock(() => null),
80
+ resolveAppIdFromPath: mock(() => null),
81
+ }));
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Imports after mocks are in place
85
+ // ---------------------------------------------------------------------------
86
+
87
+ import { LLMSchema } from "../config/schemas/llm.js";
88
+ import {
89
+ createToolExecutor,
90
+ resolveConversationAttribution,
91
+ } from "../daemon/conversation-tool-setup.js";
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Helpers
95
+ // ---------------------------------------------------------------------------
96
+
97
+ function setLlmConfig(raw: unknown): void {
98
+ mockLlmConfig = LLMSchema.parse(raw) as Record<string, unknown>;
99
+ }
100
+
101
+ /** Build a minimal ToolSetupContext stub. */
102
+ function makeCtx(overrides: Partial<ToolSetupContext> = {}): ToolSetupContext {
103
+ return {
104
+ conversationId: "conv-test",
105
+ currentRequestId: "req-1",
106
+ workingDir: "/tmp/test",
107
+ abortController: null,
108
+ traceEmitter: { emit: () => {} },
109
+ sendToClient: mock(() => {}),
110
+ pendingSurfaceActions: new Map(),
111
+ lastSurfaceAction: new Map(),
112
+ surfaceState: new Map<
113
+ string,
114
+ { surfaceType: SurfaceType; data: SurfaceData; title?: string }
115
+ >(),
116
+ surfaceUndoStacks: new Map(),
117
+ accumulatedSurfaceState: new Map(),
118
+ surfaceActionRequestIds: new Set<string>(),
119
+ currentTurnSurfaces: [],
120
+ isProcessing: () => false,
121
+ enqueueMessage: () => ({ queued: false, requestId: "r" }),
122
+ getQueueDepth: () => 0,
123
+ processMessage: async () => "",
124
+ withSurface: async <T>(_id: string, fn: () => T | Promise<T>) => fn(),
125
+ ...overrides,
126
+ };
127
+ }
128
+
129
+ /** Fake ToolExecutor that captures the context of each execute() call. */
130
+ function makeCapturingExecutor() {
131
+ const calls: Array<{ name: string; context: ToolContext }> = [];
132
+ const executor = {
133
+ execute: async (
134
+ name: string,
135
+ _input: Record<string, unknown>,
136
+ context: ToolContext,
137
+ ): Promise<ToolExecutionResult> => {
138
+ calls.push({ name, context });
139
+ return { content: "ok", isError: false };
140
+ },
141
+ };
142
+ return { executor: executor as unknown as ToolExecutor, calls };
143
+ }
144
+
145
+ const noopPrompter = {
146
+ prompt: mock(async () => ({ decision: "allow" as const })),
147
+ } as unknown as PermissionPrompter;
148
+ const noopSecretPrompter = {
149
+ prompt: mock(async () => ({ cancelled: true })),
150
+ } as unknown as SecretPrompter;
151
+
152
+ function makeToolFn(executor: ToolExecutor, ctx: ToolSetupContext) {
153
+ return createToolExecutor(
154
+ executor,
155
+ noopPrompter,
156
+ noopSecretPrompter,
157
+ ctx,
158
+ () => {},
159
+ );
160
+ }
161
+
162
+ // The module mock outlives this file when multiple test files share a
163
+ // process, so leave the config in a working (non-throwing) state.
164
+ afterEach(() => {
165
+ configThrows = false;
166
+ });
167
+
168
+ beforeEach(() => {
169
+ configThrows = false;
170
+ setLlmConfig({
171
+ default: { provider: "anthropic", model: "model-default" },
172
+ profiles: {
173
+ active: { provider: "openai", model: "model-active" },
174
+ pinned: { provider: "gemini", model: "model-pinned" },
175
+ },
176
+ activeProfile: "active",
177
+ });
178
+ });
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // Tests
182
+ // ---------------------------------------------------------------------------
183
+
184
+ // Precedence semantics (override/active/call-site/default) are owned by
185
+ // resolveUsageAttribution and covered in usage-attribution.test.ts; the
186
+ // tests here only exercise what the wrapper adds on top.
187
+ describe("resolveConversationAttribution", () => {
188
+ test("falls back to the workspace active profile when no override is set", () => {
189
+ const snapshot = resolveConversationAttribution({
190
+ conversationId: "conv-test",
191
+ });
192
+
193
+ expect(snapshot).toMatchObject({
194
+ callSite: "mainAgent",
195
+ overrideProfile: null,
196
+ appliedProfile: "active",
197
+ profileSource: "active",
198
+ resolvedProvider: "openai",
199
+ resolvedModel: "model-active",
200
+ });
201
+ });
202
+
203
+ test("attributes non-main turns to the turn call site like the usage path (voice callAgent)", () => {
204
+ setLlmConfig({
205
+ default: { provider: "anthropic", model: "model-default" },
206
+ profiles: {
207
+ active: { provider: "openai", model: "model-active" },
208
+ pinned: { provider: "gemini", model: "model-pinned" },
209
+ },
210
+ activeProfile: "active",
211
+ callSites: { callAgent: { profile: "pinned" } },
212
+ });
213
+
214
+ const snapshot = resolveConversationAttribution({
215
+ conversationId: "conv-test",
216
+ currentCallSite: "callAgent",
217
+ });
218
+
219
+ // Matches resolveUsageAttribution semantics for non-main call sites:
220
+ // the call-site profile wins (profileSource "call_site"), not the
221
+ // workspace active profile the mainAgent path would report.
222
+ expect(snapshot).toMatchObject({
223
+ callSite: "callAgent",
224
+ activeProfile: "active",
225
+ callSiteProfile: "pinned",
226
+ appliedProfile: "pinned",
227
+ profileSource: "call_site",
228
+ resolvedProvider: "gemini",
229
+ resolvedModel: "model-pinned",
230
+ });
231
+ });
232
+
233
+ test("returns null instead of throwing when resolution fails", () => {
234
+ configThrows = true;
235
+
236
+ expect(
237
+ resolveConversationAttribution({ conversationId: "conv-test" }),
238
+ ).toBeNull();
239
+ });
240
+ });
241
+
242
+ describe("createToolExecutor attribution threading", () => {
243
+ test("stamps the attribution snapshot onto the ToolContext for direct tool calls", async () => {
244
+ const { executor, calls } = makeCapturingExecutor();
245
+ const toolFn = makeToolFn(
246
+ executor,
247
+ makeCtx({ currentTurnOverrideProfile: "pinned" }),
248
+ );
249
+
250
+ const result = await toolFn("file_read", { path: "/tmp/a" });
251
+
252
+ expect(result).toMatchObject({ content: "ok", isError: false });
253
+ expect(calls).toHaveLength(1);
254
+ expect(calls[0].context.attribution).toMatchObject({
255
+ callSite: "mainAgent",
256
+ appliedProfile: "pinned",
257
+ profileSource: "conversation",
258
+ resolvedProvider: "gemini",
259
+ resolvedModel: "model-pinned",
260
+ });
261
+ });
262
+
263
+ test("threads a non-main turn call site (voice callAgent) into the snapshot", async () => {
264
+ setLlmConfig({
265
+ default: { provider: "anthropic", model: "model-default" },
266
+ profiles: {
267
+ active: { provider: "openai", model: "model-active" },
268
+ pinned: { provider: "gemini", model: "model-pinned" },
269
+ },
270
+ activeProfile: "active",
271
+ callSites: { callAgent: { profile: "pinned" } },
272
+ });
273
+
274
+ const { executor, calls } = makeCapturingExecutor();
275
+ const toolFn = makeToolFn(
276
+ executor,
277
+ makeCtx({ currentCallSite: "callAgent" }),
278
+ );
279
+
280
+ await toolFn("file_read", { path: "/tmp/a" });
281
+
282
+ expect(calls).toHaveLength(1);
283
+ expect(calls[0].context.attribution).toMatchObject({
284
+ callSite: "callAgent",
285
+ appliedProfile: "pinned",
286
+ profileSource: "call_site",
287
+ resolvedProvider: "gemini",
288
+ resolvedModel: "model-pinned",
289
+ });
290
+ });
291
+
292
+ test("stamps the attribution snapshot onto skill_execute dispatches", async () => {
293
+ const { executor, calls } = makeCapturingExecutor();
294
+ const toolFn = makeToolFn(executor, makeCtx());
295
+
296
+ await toolFn("skill_execute", {
297
+ tool: "file_read",
298
+ input: { path: "/tmp/a" },
299
+ activity: "testing",
300
+ });
301
+
302
+ expect(calls).toHaveLength(1);
303
+ expect(calls[0].name).toBe("file_read");
304
+ expect(calls[0].context.attribution).toMatchObject({
305
+ callSite: "mainAgent",
306
+ appliedProfile: "active",
307
+ profileSource: "active",
308
+ });
309
+ });
310
+
311
+ test("attribution resolution failure yields null and does not break tool execution", async () => {
312
+ configThrows = true;
313
+
314
+ const { executor, calls } = makeCapturingExecutor();
315
+ const toolFn = makeToolFn(executor, makeCtx());
316
+
317
+ const result = await toolFn("file_read", { path: "/tmp/a" });
318
+
319
+ expect(result).toMatchObject({ content: "ok", isError: false });
320
+ expect(calls).toHaveLength(1);
321
+ expect(calls[0].context.attribution).toBeNull();
322
+ });
323
+ });
@@ -154,4 +154,38 @@ describe("createResolveToolsCallback — toolsDisabledDepth", () => {
154
154
  resolve(EMPTY_HISTORY);
155
155
  expect(ctx.allowedToolNames).toEqual(new Set());
156
156
  });
157
+
158
+ test("records the resolved set on lastResolvedToolNames for inventory queries", () => {
159
+ // GIVEN a normal resolver call
160
+ const toolDefs = [makeToolDef("tool_a"), makeToolDef("tool_b")];
161
+ const ctx = makeCtx();
162
+ const resolve = createResolveToolsCallback(toolDefs, ctx)!;
163
+
164
+ // WHEN tools resolve normally
165
+ resolve(EMPTY_HISTORY);
166
+
167
+ // THEN the durable snapshot mirrors the per-turn gate
168
+ expect(ctx.lastResolvedToolNames).toEqual(ctx.allowedToolNames!);
169
+ expect(ctx.lastResolvedToolNames?.has("tool_a")).toBe(true);
170
+ expect(ctx.lastResolvedToolNames?.has("tool_b")).toBe(true);
171
+ });
172
+
173
+ test("preserves lastResolvedToolNames when a later call disables tools", () => {
174
+ // GIVEN a conversation that resolved tools on a normal turn
175
+ const toolDefs = [makeToolDef("tool_a"), makeToolDef("tool_b")];
176
+ const ctx = makeCtx();
177
+ const resolve = createResolveToolsCallback(toolDefs, ctx)!;
178
+ resolve(EMPTY_HISTORY);
179
+ const snapshot = ctx.lastResolvedToolNames;
180
+
181
+ // WHEN a subsequent call disables tools (e.g. pointer generation, or the
182
+ // turn-teardown that clears the per-turn gate)
183
+ ctx.toolsDisabledDepth = 1;
184
+ resolve(EMPTY_HISTORY);
185
+
186
+ // THEN the per-turn gate is empty but the inventory snapshot survives
187
+ expect(ctx.allowedToolNames).toEqual(new Set());
188
+ expect(ctx.lastResolvedToolNames).toBe(snapshot);
189
+ expect(ctx.lastResolvedToolNames?.has("tool_a")).toBe(true);
190
+ });
157
191
  });
@@ -147,6 +147,7 @@ mock.module("../plugins/defaults/compaction/window-manager.js", () => ({
147
147
  async maybeCompact() {
148
148
  return { compacted: false };
149
149
  }
150
+ resetOverflowRecovery() {}
150
151
  },
151
152
  createContextSummaryMessage: () => ({
152
153
  role: "user",
@@ -190,6 +190,7 @@ mock.module("../plugins/defaults/compaction/window-manager.js", () => ({
190
190
  async maybeCompact() {
191
191
  return { compacted: false };
192
192
  }
193
+ resetOverflowRecovery() {}
193
194
  },
194
195
  createContextSummaryMessage: () => ({
195
196
  role: "user",
@@ -270,7 +271,6 @@ mock.module("../agent/loop.js", () => ({
270
271
  return {
271
272
  history: [...messages, assistantMessage],
272
273
  exitReason: null,
273
- appendedNewMessages: true,
274
274
  newMessages: [assistantMessage],
275
275
  };
276
276
  }
@@ -187,6 +187,7 @@ mock.module("../plugins/defaults/compaction/window-manager.js", () => ({
187
187
  async maybeCompact() {
188
188
  return { compacted: false };
189
189
  }
190
+ resetOverflowRecovery() {}
190
191
  },
191
192
  createContextSummaryMessage: () => ({
192
193
  role: "user",
@@ -0,0 +1,93 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { computeCorrectedOverflowTarget } from "../plugins/defaults/compaction/corrected-target.js";
4
+
5
+ describe("computeCorrectedOverflowTarget", () => {
6
+ test("lowers the target proportionally when the estimator under-counted", () => {
7
+ /**
8
+ * When the provider's actual token count exceeds the estimator's count,
9
+ * the target is scaled down by the estimation-error ratio so a re-estimate
10
+ * of the reduced history lands under the provider's real ceiling.
11
+ */
12
+ // GIVEN a preflight budget and an estimator that under-counted (185k vs 242k actual)
13
+ const preflightBudget = 162_000;
14
+ const actualTokens = 242_000;
15
+ const estimatedTokens = 185_000;
16
+
17
+ // WHEN computing the corrected target
18
+ const result = computeCorrectedOverflowTarget({
19
+ preflightBudget,
20
+ actualTokens,
21
+ estimatedTokens,
22
+ });
23
+
24
+ // THEN the target is the budget divided by the error ratio
25
+ const expectedRatio = actualTokens / estimatedTokens;
26
+ expect(result.targetTokens).toBe(
27
+ Math.floor(preflightBudget / expectedRatio),
28
+ );
29
+ // AND the ratio is surfaced for logging
30
+ expect(result.estimationErrorRatio).toBeCloseTo(expectedRatio, 10);
31
+ // AND the corrected target is below the nominal budget
32
+ expect(result.targetTokens).toBeLessThan(preflightBudget);
33
+ });
34
+
35
+ test("returns the preflight budget unchanged when the estimator did not under-count", () => {
36
+ /**
37
+ * If the estimator matched or over-counted the provider, no correction is
38
+ * applied and the nominal preflight budget is the target.
39
+ */
40
+ // GIVEN an actual count at or below the estimate
41
+ const preflightBudget = 162_000;
42
+
43
+ // WHEN computing the corrected target
44
+ const result = computeCorrectedOverflowTarget({
45
+ preflightBudget,
46
+ actualTokens: 180_000,
47
+ estimatedTokens: 185_000,
48
+ });
49
+
50
+ // THEN the target is the unchanged preflight budget and no ratio is reported
51
+ expect(result.targetTokens).toBe(preflightBudget);
52
+ expect(result.estimationErrorRatio).toBeNull();
53
+ });
54
+
55
+ test("falls back to the preflight budget when the actual count is unavailable", () => {
56
+ /**
57
+ * Proxy-rewrapped or unparseable rejections yield a null actual count; the
58
+ * correction is skipped rather than guessed.
59
+ */
60
+ // GIVEN a null actual token count
61
+ const preflightBudget = 162_000;
62
+
63
+ // WHEN computing the corrected target
64
+ const result = computeCorrectedOverflowTarget({
65
+ preflightBudget,
66
+ actualTokens: null,
67
+ estimatedTokens: 185_000,
68
+ });
69
+
70
+ // THEN the nominal budget is used with no ratio
71
+ expect(result.targetTokens).toBe(preflightBudget);
72
+ expect(result.estimationErrorRatio).toBeNull();
73
+ });
74
+
75
+ test("falls back to the preflight budget when the estimate is zero", () => {
76
+ /**
77
+ * A zero estimate would make the ratio undefined; guard against it.
78
+ */
79
+ // GIVEN a zero estimate
80
+ const preflightBudget = 162_000;
81
+
82
+ // WHEN computing the corrected target
83
+ const result = computeCorrectedOverflowTarget({
84
+ preflightBudget,
85
+ actualTokens: 242_000,
86
+ estimatedTokens: 0,
87
+ });
88
+
89
+ // THEN the nominal budget is used with no ratio
90
+ expect(result.targetTokens).toBe(preflightBudget);
91
+ expect(result.estimationErrorRatio).toBeNull();
92
+ });
93
+ });
@@ -157,7 +157,7 @@ describe("CES flags respect explicit false overrides", () => {
157
157
  // ---------------------------------------------------------------------------
158
158
 
159
159
  describe("CES flags do not affect unrelated flags", () => {
160
- test("enabling all CES flags does not change declared default-enabled flags", () => {
160
+ test("enabling all CES flags does not change other declared flags", () => {
161
161
  const overrides: Record<string, boolean> = {};
162
162
  for (const key of ALL_CES_FLAG_KEYS) {
163
163
  overrides[key] = true;
@@ -165,10 +165,8 @@ describe("CES flags do not affect unrelated flags", () => {
165
165
  setOverridesForTesting(overrides);
166
166
  const config = makeConfig();
167
167
 
168
- // account-deletion defaults to true in the registry and should stay true.
169
- expect(isAssistantFeatureFlagEnabled("account-deletion", config)).toBe(
170
- true,
171
- );
168
+ // auto-analyze defaults to false and should stay false.
169
+ expect(isAssistantFeatureFlagEnabled("auto-analyze", config)).toBe(false);
172
170
  });
173
171
 
174
172
  test("enabling all CES flags does not change unrelated fail-closed flags", () => {