@vellumai/assistant 0.8.0 → 0.8.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 (692) hide show
  1. package/AGENTS.md +11 -0
  2. package/Dockerfile +5 -4
  3. package/README.md +2 -2
  4. package/docker-entrypoint.sh +16 -0
  5. package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +420 -0
  6. package/eslint-rules/cli-no-daemon-internals.js +283 -0
  7. package/eslint.config.mjs +12 -0
  8. package/knip.json +2 -1
  9. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -1
  10. package/openapi.yaml +4847 -1698
  11. package/package.json +3 -1
  12. package/scripts/generate-openapi.ts +52 -4
  13. package/scripts/sync-llm-catalog.ts +165 -0
  14. package/scripts/sync-web-search-catalog.ts +107 -0
  15. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +169 -0
  16. package/src/__tests__/agent-loop-override-profile.test.ts +26 -1
  17. package/src/__tests__/anthropic-provider.test.ts +92 -2
  18. package/src/__tests__/app-control-flow.test.ts +7 -0
  19. package/src/__tests__/assistant-events-sse-shed.test.ts +232 -0
  20. package/src/__tests__/avatar-identity-sync.test.ts +87 -0
  21. package/src/__tests__/background-workers-disk-pressure.test.ts +11 -22
  22. package/src/__tests__/btw-routes.test.ts +1 -0
  23. package/src/__tests__/call-site-routing-provider.test.ts +172 -45
  24. package/src/__tests__/cancel-resolves-conversation-key.test.ts +44 -3
  25. package/src/__tests__/channel-policy.test.ts +12 -0
  26. package/src/__tests__/checker.test.ts +89 -0
  27. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +35 -7
  28. package/src/__tests__/compact-event-conversation-id-guard.test.ts +33 -5
  29. package/src/__tests__/compaction-strip-metadata-clear.test.ts +26 -1
  30. package/src/__tests__/config-loader-backfill.test.ts +526 -102
  31. package/src/__tests__/config-loader-corrupt.test.ts +68 -0
  32. package/src/__tests__/config-loader-platform-defaults.test.ts +77 -23
  33. package/src/__tests__/config-schema-cmd.test.ts +63 -29
  34. package/src/__tests__/config-schema.test.ts +14 -3
  35. package/src/__tests__/config-set-platform-guard.test.ts +75 -152
  36. package/src/__tests__/config-set-route.test.ts +198 -0
  37. package/src/__tests__/config-watcher.test.ts +6 -0
  38. package/src/__tests__/contacts-tools.test.ts +51 -199
  39. package/src/__tests__/context-search-agent-protocol.test.ts +21 -2
  40. package/src/__tests__/context-search-agent-runner.test.ts +22 -138
  41. package/src/__tests__/context-search-conversations-source.test.ts +42 -16
  42. package/src/__tests__/context-search-fanout.test.ts +20 -157
  43. package/src/__tests__/context-search-memory-v2-source.test.ts +3 -3
  44. package/src/__tests__/context-search-types.test.ts +7 -2
  45. package/src/__tests__/context-window-manager.test.ts +389 -1
  46. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  47. package/src/__tests__/conversation-crud-inference-profile.test.ts +100 -0
  48. package/src/__tests__/conversation-error.test.ts +38 -0
  49. package/src/__tests__/conversation-fork-crud.test.ts +241 -1
  50. package/src/__tests__/conversation-inference-profile-route.test.ts +14 -14
  51. package/src/__tests__/conversation-init.benchmark.test.ts +1 -0
  52. package/src/__tests__/conversation-lifecycle.test.ts +124 -0
  53. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +100 -1
  54. package/src/__tests__/conversation-process-callsite.test.ts +21 -1
  55. package/src/__tests__/conversation-runtime-assembly.test.ts +4 -4
  56. package/src/__tests__/conversation-slash-commands.test.ts +194 -2
  57. package/src/__tests__/conversation-surfaces-app-control.test.ts +323 -3
  58. package/src/__tests__/credential-security-invariants.test.ts +5 -6
  59. package/src/__tests__/daemon-credential-client.test.ts +56 -1
  60. package/src/__tests__/db-activation-state-fk-cascade.test.ts +132 -0
  61. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +37 -0
  62. package/src/__tests__/db-memory-graph-event-date-repair.test.ts +43 -20
  63. package/src/__tests__/db-proxy-transaction.test.ts +206 -0
  64. package/src/__tests__/external-plugin-loader.test.ts +458 -0
  65. package/src/__tests__/filing-service.test.ts +23 -3
  66. package/src/__tests__/fixtures/mock-chrome-extension.ts +5 -0
  67. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  68. package/src/__tests__/graph-extraction-event-date.test.ts +34 -0
  69. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -8
  70. package/src/__tests__/heartbeat-disk-pressure.test.ts +21 -8
  71. package/src/__tests__/heartbeat-service.test.ts +50 -233
  72. package/src/__tests__/history-repair.test.ts +89 -0
  73. package/src/__tests__/host-app-control-proxy.test.ts +109 -1
  74. package/src/__tests__/host-app-control-routes.test.ts +247 -1
  75. package/src/__tests__/host-browser-proxy.test.ts +416 -20
  76. package/src/__tests__/host-browser-routes.test.ts +325 -33
  77. package/src/__tests__/host-proxy-preactivation.test.ts +211 -0
  78. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +246 -0
  79. package/src/__tests__/inference-profile-reaper.test.ts +154 -0
  80. package/src/__tests__/inference-profile-session-handler.test.ts +398 -0
  81. package/src/__tests__/inference-profile-session-ipc.test.ts +236 -0
  82. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -1
  83. package/src/__tests__/install-skill-routing.test.ts +2 -2
  84. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +15 -0
  85. package/src/__tests__/llm-callsite-catalog.test.ts +20 -1
  86. package/src/__tests__/llm-catalog-parity.test.ts +146 -0
  87. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +188 -0
  88. package/src/__tests__/llm-request-log-source-factory.test.ts +124 -0
  89. package/src/__tests__/llm-resolver.test.ts +46 -0
  90. package/src/__tests__/managed-profile-guard.test.ts +131 -2
  91. package/src/__tests__/mcp-auth-routes.test.ts +1 -0
  92. package/src/__tests__/mcp-cli.test.ts +182 -220
  93. package/src/__tests__/mcp-health-check.test.ts +56 -27
  94. package/src/__tests__/memory-jobs-worker-lanes.test.ts +18 -11
  95. package/src/__tests__/message-complete-display-id.test.ts +175 -0
  96. package/src/__tests__/notification-platform-adapter.test.ts +229 -0
  97. package/src/__tests__/oauth-cli.test.ts +38 -2009
  98. package/src/__tests__/oauth-commands-routes.test.ts +711 -0
  99. package/src/__tests__/oauth-connect-routes.test.ts +174 -11
  100. package/src/__tests__/oauth-providers-routes.test.ts +14 -10
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +33 -12
  102. package/src/__tests__/openai-responses-provider.test.ts +17 -0
  103. package/src/__tests__/plugin-bootstrap.test.ts +31 -2
  104. package/src/__tests__/plugin-route-contribution.test.ts +31 -3
  105. package/src/__tests__/plugin-tool-contribution.test.ts +31 -3
  106. package/src/__tests__/plugin-types.test.ts +13 -11
  107. package/src/__tests__/process-message-background-slack.test.ts +46 -0
  108. package/src/__tests__/profile-entry-status.test.ts +43 -0
  109. package/src/__tests__/provider-managed-proxy-integration.test.ts +12 -4
  110. package/src/__tests__/provider-registry-ollama.test.ts +12 -4
  111. package/src/__tests__/provider-send-message-override-profile.test.ts +10 -4
  112. package/src/__tests__/relay-server.test.ts +118 -0
  113. package/src/__tests__/retry-thinking-tool-choice.test.ts +15 -0
  114. package/src/__tests__/schedule-retry.test.ts +56 -4
  115. package/src/__tests__/schedule-routes.test.ts +104 -0
  116. package/src/__tests__/scheduler-disk-pressure.test.ts +0 -4
  117. package/src/__tests__/scheduler-recurrence.test.ts +87 -34
  118. package/src/__tests__/scheduler-reuse-conversation.test.ts +161 -5
  119. package/src/__tests__/scheduler-wake.test.ts +0 -63
  120. package/src/__tests__/secret-allowlist.test.ts +1 -0
  121. package/src/__tests__/secret-routes-managed-proxy.test.ts +12 -4
  122. package/src/__tests__/shell-credential-ref.test.ts +95 -3
  123. package/src/__tests__/shell-tool-proxy-mode.test.ts +14 -0
  124. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  125. package/src/__tests__/skill-load-tool.test.ts +2 -4
  126. package/src/__tests__/subagent-call-site-routing.test.ts +78 -16
  127. package/src/__tests__/suggestion-routes.test.ts +3 -3
  128. package/src/__tests__/sync-message-contract.test.ts +63 -0
  129. package/src/__tests__/task-scheduler.test.ts +88 -23
  130. package/src/__tests__/update-bulletin-job.test.ts +96 -193
  131. package/src/__tests__/usage-cli.test.ts +11 -73
  132. package/src/__tests__/user-plugin-loader.test.ts +145 -0
  133. package/src/__tests__/vercel-config.test.ts +168 -0
  134. package/src/__tests__/web-search-catalog-parity.test.ts +86 -0
  135. package/src/__tests__/web-search.test.ts +303 -2
  136. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +1 -21
  137. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +58 -0
  138. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +53 -20
  139. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +191 -0
  140. package/src/__tests__/workspace-migration-076-drop-services-inference-mode.test.ts +211 -0
  141. package/src/__tests__/workspace-migration-077-seed-memory-router-callsite.test.ts +174 -0
  142. package/src/__tests__/workspace-migration-079-home-feed-notification-only.test.ts +323 -0
  143. package/src/__tests__/workspace-migration-080-restrict-vercel-api-token-metadata.test.ts +299 -0
  144. package/src/__tests__/workspace-migration-081-backfill-bash-allowed-tools.test.ts +410 -0
  145. package/src/__tests__/workspace-migration-082-backfill-managed-profile-labels.test.ts +268 -0
  146. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +3 -3
  147. package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +115 -0
  148. package/src/acp/__tests__/helpers/which-stub.ts +4 -2
  149. package/src/acp/resolve-agent.test.ts +25 -0
  150. package/src/acp/resolve-agent.ts +13 -2
  151. package/src/acp/session-manager.ts +14 -0
  152. package/src/approvals/guardian-request-resolvers.ts +32 -87
  153. package/src/calls/relay-server.ts +35 -0
  154. package/src/calls/relay-setup-router.ts +36 -0
  155. package/src/calls/types.ts +1 -0
  156. package/src/calls/voice-session-bridge.ts +23 -4
  157. package/src/channels/config.ts +14 -1
  158. package/src/channels/types.ts +1 -0
  159. package/src/cli/AGENTS.md +164 -4
  160. package/src/cli/__tests__/notifications.test.ts +54 -0
  161. package/src/cli/commands/__tests__/avatar.test.ts +540 -0
  162. package/src/cli/commands/__tests__/backup.test.ts +236 -776
  163. package/src/cli/commands/__tests__/cache.test.ts +1 -1
  164. package/src/cli/commands/__tests__/changelog.test.ts +593 -0
  165. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +503 -0
  166. package/src/cli/commands/__tests__/conversations-import.test.ts +515 -0
  167. package/src/cli/commands/__tests__/domain-register.test.ts +140 -167
  168. package/src/cli/commands/__tests__/domain-status.test.ts +137 -76
  169. package/src/cli/commands/__tests__/email-attachment.test.ts +314 -337
  170. package/src/cli/commands/__tests__/email-core.test.ts +579 -0
  171. package/src/cli/commands/__tests__/image-generation.test.ts +87 -824
  172. package/src/cli/commands/__tests__/inference-send.test.ts +30 -266
  173. package/src/cli/commands/__tests__/inference-session.test.ts +423 -0
  174. package/src/cli/commands/__tests__/memory-v2.test.ts +81 -110
  175. package/src/cli/commands/__tests__/skills.test.ts +563 -0
  176. package/src/cli/commands/__tests__/status.test.ts +249 -0
  177. package/src/cli/commands/__tests__/stt.test.ts +320 -0
  178. package/src/cli/commands/__tests__/tts-synthesize.test.ts +4 -603
  179. package/src/cli/commands/__tests__/tts.test.ts +321 -0
  180. package/src/cli/commands/__tests__/webhooks.test.ts +86 -511
  181. package/src/cli/commands/attachment.ts +8 -3
  182. package/src/cli/commands/audit.ts +95 -64
  183. package/src/cli/commands/auth.ts +61 -58
  184. package/src/cli/commands/avatar.ts +276 -390
  185. package/src/cli/commands/backup.ts +409 -505
  186. package/src/cli/commands/bash.ts +9 -5
  187. package/src/cli/commands/browser.ts +28 -9
  188. package/src/cli/commands/cache.ts +9 -4
  189. package/src/cli/commands/changelog.ts +414 -0
  190. package/src/cli/commands/channel-verification-sessions.ts +238 -317
  191. package/src/cli/commands/clients.ts +8 -3
  192. package/src/cli/commands/completions.ts +9 -9
  193. package/src/cli/commands/config.ts +102 -72
  194. package/src/cli/commands/contacts.ts +575 -696
  195. package/src/cli/commands/conversations-defer.ts +17 -69
  196. package/src/cli/commands/conversations-import.ts +90 -253
  197. package/src/cli/commands/conversations.ts +346 -436
  198. package/src/cli/commands/credential-execution.ts +9 -6
  199. package/src/cli/commands/credentials.ts +456 -736
  200. package/src/cli/commands/domain.ts +128 -206
  201. package/src/cli/commands/email.ts +606 -794
  202. package/src/cli/commands/gateway.ts +8 -1
  203. package/src/cli/commands/image-generation.ts +157 -205
  204. package/src/cli/commands/inference-providers.ts +352 -0
  205. package/src/cli/commands/inference-session.ts +415 -0
  206. package/src/cli/commands/inference.ts +87 -65
  207. package/src/cli/commands/keys.ts +8 -3
  208. package/src/cli/commands/mcp.ts +103 -287
  209. package/src/cli/commands/memory-v2.ts +162 -516
  210. package/src/cli/commands/notifications.ts +33 -7
  211. package/src/cli/commands/oauth/apps.ts +292 -261
  212. package/src/cli/commands/oauth/connect.ts +176 -297
  213. package/src/cli/commands/oauth/disconnect.ts +16 -215
  214. package/src/cli/commands/oauth/index.ts +49 -45
  215. package/src/cli/commands/oauth/mode.ts +43 -199
  216. package/src/cli/commands/oauth/ping.ts +17 -125
  217. package/src/cli/commands/oauth/providers.ts +732 -921
  218. package/src/cli/commands/oauth/request.ts +60 -350
  219. package/src/cli/commands/oauth/shared.ts +11 -121
  220. package/src/cli/commands/oauth/status.ts +31 -121
  221. package/src/cli/commands/oauth/token.ts +13 -55
  222. package/src/cli/commands/pending.ts +19 -10
  223. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +133 -183
  224. package/src/cli/commands/platform/__tests__/connect.test.ts +66 -181
  225. package/src/cli/commands/platform/__tests__/disconnect.test.ts +71 -227
  226. package/src/cli/commands/platform/__tests__/status.test.ts +169 -287
  227. package/src/cli/commands/platform/connect.ts +16 -80
  228. package/src/cli/commands/platform/disconnect.ts +14 -112
  229. package/src/cli/commands/platform/index.ts +177 -246
  230. package/src/cli/commands/routes.ts +153 -336
  231. package/src/cli/commands/sequence.ts +316 -360
  232. package/src/cli/commands/skills.ts +449 -671
  233. package/src/cli/commands/status.ts +58 -37
  234. package/src/cli/commands/stt.ts +94 -262
  235. package/src/cli/commands/task.ts +14 -40
  236. package/src/cli/commands/trust.ts +8 -3
  237. package/src/cli/commands/tts.ts +162 -167
  238. package/src/cli/commands/ui.ts +35 -42
  239. package/src/cli/commands/usage.ts +188 -126
  240. package/src/cli/commands/watchers.ts +8 -3
  241. package/src/cli/commands/webhooks.ts +99 -193
  242. package/src/cli/lib/__tests__/register-command.test.ts +85 -0
  243. package/src/cli/lib/daemon-credential-client.ts +4 -5
  244. package/src/cli/lib/nested-value.ts +44 -0
  245. package/src/cli/lib/open-browser.ts +36 -0
  246. package/src/cli/lib/register-command.ts +19 -0
  247. package/src/cli/lib/time-ago.ts +34 -0
  248. package/src/cli/program.ts +2 -4
  249. package/src/cli/utils/__tests__/conversation-id.test.ts +66 -0
  250. package/src/cli/utils/__tests__/parse-duration.test.ts +49 -0
  251. package/src/cli/utils/conversation-id.ts +30 -0
  252. package/src/cli/utils/parse-duration.ts +41 -0
  253. package/src/config/acp-defaults.test.ts +5 -1
  254. package/src/config/acp-defaults.ts +11 -4
  255. package/src/config/bundled-skills/acp/TOOLS.json +2 -2
  256. package/src/config/bundled-skills/app-control/TOOLS.json +32 -0
  257. package/src/config/bundled-skills/contacts/SKILL.md +12 -45
  258. package/src/config/bundled-skills/contacts/TOOLS.json +0 -57
  259. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +0 -12
  260. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +0 -58
  261. package/src/config/bundled-tool-registry.ts +0 -2
  262. package/src/config/feature-flag-registry.json +16 -0
  263. package/src/config/llm-resolver.ts +16 -1
  264. package/src/config/loader.ts +76 -14
  265. package/src/config/raw-config-utils.ts +2 -30
  266. package/src/config/schema.ts +4 -0
  267. package/src/config/schemas/__tests__/memory-v2.test.ts +49 -0
  268. package/src/config/schemas/call-site-catalog.ts +29 -7
  269. package/src/config/schemas/llm-request-logs.ts +57 -0
  270. package/src/config/schemas/llm.ts +52 -2
  271. package/src/config/schemas/memory-retrospective.ts +48 -0
  272. package/src/config/schemas/memory-v2.ts +32 -1
  273. package/src/config/schemas/memory.ts +4 -0
  274. package/src/config/schemas/services.ts +15 -12
  275. package/src/config/seed-inference-profiles.ts +195 -134
  276. package/src/contacts/contact-store.ts +0 -61
  277. package/src/context/window-manager.ts +191 -5
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +79 -0
  279. package/src/daemon/__tests__/conversation-tool-setup.test.ts +109 -4
  280. package/src/daemon/__tests__/daemon-skill-host.test.ts +10 -4
  281. package/src/daemon/approval-generators.ts +23 -29
  282. package/src/daemon/config-watcher.ts +2 -0
  283. package/src/daemon/conversation-agent-loop-handlers.ts +24 -0
  284. package/src/daemon/conversation-agent-loop.ts +127 -97
  285. package/src/daemon/conversation-error.ts +21 -0
  286. package/src/daemon/conversation-lifecycle.ts +46 -5
  287. package/src/daemon/conversation-process.ts +36 -19
  288. package/src/daemon/conversation-runtime-assembly.ts +14 -5
  289. package/src/daemon/conversation-slash.ts +175 -23
  290. package/src/daemon/conversation-store.ts +17 -10
  291. package/src/daemon/conversation-surfaces.ts +76 -12
  292. package/src/daemon/conversation-tool-setup.ts +24 -14
  293. package/src/daemon/conversation.ts +48 -9
  294. package/src/daemon/external-plugins-bootstrap.ts +18 -8
  295. package/src/daemon/guardian-action-generators.ts +7 -22
  296. package/src/daemon/handlers/config-model.ts +8 -126
  297. package/src/daemon/handlers/config-slack-channel.ts +10 -7
  298. package/src/daemon/handlers/config-vercel.ts +3 -1
  299. package/src/daemon/handlers/skills.ts +84 -5
  300. package/src/daemon/history-repair.ts +33 -6
  301. package/src/daemon/host-app-control-proxy.ts +44 -19
  302. package/src/daemon/host-bash-proxy.ts +85 -158
  303. package/src/daemon/host-browser-proxy.ts +96 -35
  304. package/src/daemon/host-proxy-base.ts +13 -1
  305. package/src/daemon/host-proxy-preactivation.ts +25 -1
  306. package/src/daemon/identity-helpers.ts +19 -0
  307. package/src/daemon/lifecycle.ts +42 -43
  308. package/src/daemon/meet-host-supervisor.ts +15 -15
  309. package/src/daemon/memory-v2-startup.ts +9 -2
  310. package/src/daemon/message-protocol.ts +6 -0
  311. package/src/daemon/message-types/bookmarks.ts +18 -0
  312. package/src/daemon/message-types/conversations.ts +12 -9
  313. package/src/daemon/message-types/messages.ts +9 -1
  314. package/src/daemon/message-types/sync.ts +60 -0
  315. package/src/daemon/pkb-reminder-builder.test.ts +54 -13
  316. package/src/daemon/pkb-reminder-builder.ts +21 -7
  317. package/src/daemon/process-message.ts +56 -23
  318. package/src/daemon/server.ts +23 -18
  319. package/src/daemon/shutdown-handlers.ts +0 -2
  320. package/src/daemon/tool-setup-types.ts +9 -0
  321. package/src/daemon/tool-side-effects.ts +6 -4
  322. package/src/daemon/wake-target-adapter.ts +11 -0
  323. package/src/export/transcript-formatter.ts +61 -2
  324. package/src/filing/filing-service.ts +40 -53
  325. package/src/heartbeat/__tests__/heartbeat-service.test.ts +359 -0
  326. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  327. package/src/heartbeat/heartbeat-service.ts +148 -127
  328. package/src/home/__tests__/feed-types.test.ts +63 -131
  329. package/src/home/__tests__/feed-writer.test.ts +77 -278
  330. package/src/home/__tests__/post-connect-feed.test.ts +9 -12
  331. package/src/home/feed-types.ts +19 -73
  332. package/src/home/feed-writer.ts +25 -156
  333. package/src/home/post-connect-feed.ts +1 -3
  334. package/src/ipc/__tests__/cli-ipc.test.ts +2 -0
  335. package/src/ipc/__tests__/email-ipc.test.ts +506 -0
  336. package/src/ipc/__tests__/exit-helper.test.ts +104 -0
  337. package/src/ipc/__tests__/streaming-client.test.ts +237 -0
  338. package/src/ipc/__tests__/streaming-framing.test.ts +142 -0
  339. package/src/ipc/assistant-server.ts +55 -6
  340. package/src/ipc/cli-client.ts +370 -50
  341. package/src/ipc/routes/db-proxy-transaction.ts +151 -0
  342. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +60 -0
  343. package/src/ipc/skill-routes/events.ts +30 -3
  344. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +46 -0
  345. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +1 -0
  346. package/src/live-voice/live-voice-session-manager.ts +11 -4
  347. package/src/live-voice/live-voice-session.ts +14 -6
  348. package/src/memory/__tests__/bookmark-crud.test.ts +258 -0
  349. package/src/memory/__tests__/bookmark-schema.test.ts +181 -0
  350. package/src/memory/__tests__/conversation-types.test.ts +36 -0
  351. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +130 -0
  352. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +177 -0
  353. package/src/memory/__tests__/memory-retrospective-job.test.ts +328 -0
  354. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +213 -0
  355. package/src/memory/__tests__/memory-retrospective-trigger-check.test.ts +90 -0
  356. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +69 -0
  357. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +3 -0
  358. package/src/memory/bookmark-crud.ts +179 -0
  359. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +31 -9
  360. package/src/memory/context-search/agent-protocol.ts +5 -1
  361. package/src/memory/context-search/agent-runner.ts +60 -85
  362. package/src/memory/context-search/limits.ts +1 -4
  363. package/src/memory/context-search/search.ts +23 -113
  364. package/src/memory/context-search/sources/conversations.ts +18 -6
  365. package/src/memory/context-search/sources/memory-v2.ts +39 -14
  366. package/src/memory/context-search/sources/memory.ts +7 -0
  367. package/src/memory/context-search/sources/workspace.ts +13 -10
  368. package/src/memory/context-search/types.ts +1 -1
  369. package/src/memory/conversation-bootstrap.ts +11 -0
  370. package/src/memory/conversation-crud.ts +312 -10
  371. package/src/memory/conversation-queries.ts +9 -5
  372. package/src/memory/conversation-title-service.ts +1 -0
  373. package/src/memory/conversation-types.ts +16 -0
  374. package/src/memory/db-init.ts +14 -0
  375. package/src/memory/embedding-backend.ts +2 -1
  376. package/src/memory/embedding-runtime-manager.ts +1 -2
  377. package/src/memory/graph/__tests__/remember-description.test.ts +55 -0
  378. package/src/memory/graph/conversation-graph-memory.ts +76 -5
  379. package/src/memory/graph/extraction.ts +4 -0
  380. package/src/memory/graph/graph-memory-state-store.ts +16 -3
  381. package/src/memory/graph/tool-handlers.ts +17 -7
  382. package/src/memory/graph/tools.ts +44 -5
  383. package/src/memory/indexer.ts +17 -0
  384. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +13 -15
  385. package/src/memory/jobs/embed-concept-page.ts +45 -9
  386. package/src/memory/jobs-store.ts +51 -1
  387. package/src/memory/jobs-worker.ts +52 -3
  388. package/src/memory/llm-request-log-source-clickhouse.ts +317 -0
  389. package/src/memory/llm-request-log-source-local.ts +26 -0
  390. package/src/memory/llm-request-log-source.ts +97 -0
  391. package/src/memory/llm-request-log-store.ts +1 -1
  392. package/src/memory/memory-retrospective-constants.ts +13 -0
  393. package/src/memory/memory-retrospective-enqueue.ts +114 -0
  394. package/src/memory/memory-retrospective-job.ts +351 -0
  395. package/src/memory/memory-retrospective-startup-cleanup.ts +108 -0
  396. package/src/memory/memory-retrospective-state.ts +162 -0
  397. package/src/memory/memory-retrospective-trigger-check.ts +91 -0
  398. package/src/memory/memory-v2-activation-log-store.ts +49 -5
  399. package/src/memory/memory-v2-concept-frequency.ts +4 -0
  400. package/src/memory/message-content.ts +38 -1
  401. package/src/memory/migrations/227-add-conversation-inference-profile.ts +6 -1
  402. package/src/memory/migrations/228-rename-inference-profile-snake-case.ts +20 -7
  403. package/src/memory/migrations/229-delete-private-conversations.test.ts +70 -1
  404. package/src/memory/migrations/229-delete-private-conversations.ts +12 -0
  405. package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +16 -2
  406. package/src/memory/migrations/240-conversation-inference-profile-session.ts +25 -0
  407. package/src/memory/migrations/241-activation-state-fk-cascade.ts +50 -0
  408. package/src/memory/migrations/242-message-bookmarks.ts +38 -0
  409. package/src/memory/migrations/243-provider-connections.ts +68 -0
  410. package/src/memory/migrations/244-provider-connection-status-label.ts +23 -0
  411. package/src/memory/migrations/245-memory-retrospective-state.ts +36 -0
  412. package/src/memory/migrations/246-backfill-provider-connection-label.ts +81 -0
  413. package/src/memory/migrations/__tests__/244-provider-connection-status-label.test.ts +84 -0
  414. package/src/memory/migrations/__tests__/245-memory-retrospective-state.test.ts +125 -0
  415. package/src/memory/migrations/__tests__/246-backfill-provider-connection-label.test.ts +192 -0
  416. package/src/memory/migrations/index.ts +7 -0
  417. package/src/memory/published-pages-store.ts +16 -0
  418. package/src/memory/schema/bookmarks.ts +38 -0
  419. package/src/memory/schema/conversations.ts +2 -0
  420. package/src/memory/schema/index.ts +2 -0
  421. package/src/memory/schema/inference.ts +29 -0
  422. package/src/memory/schema/memory-core.ts +9 -0
  423. package/src/memory/search/semantic.ts +1 -4
  424. package/src/memory/v2/__tests__/__snapshots__/prompts-router.test.ts.snap +27 -0
  425. package/src/memory/v2/__tests__/activation-store.test.ts +5 -5
  426. package/src/memory/v2/__tests__/activation.test.ts +11 -4
  427. package/src/memory/v2/__tests__/backfill-jobs.test.ts +38 -21
  428. package/src/memory/v2/__tests__/consolidation-job.test.ts +123 -135
  429. package/src/memory/v2/__tests__/edge-index.test.ts +1 -1
  430. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +111 -0
  431. package/src/memory/v2/__tests__/injection.test.ts +628 -10
  432. package/src/memory/v2/__tests__/migration.test.ts +7 -3
  433. package/src/memory/v2/__tests__/page-index.test.ts +277 -0
  434. package/src/memory/v2/__tests__/page-store.test.ts +14 -1
  435. package/src/memory/v2/__tests__/prompts-router.test.ts +257 -0
  436. package/src/memory/v2/__tests__/qdrant.test.ts +72 -0
  437. package/src/memory/v2/__tests__/reranker.test.ts +4 -4
  438. package/src/memory/v2/__tests__/router.test.ts +516 -0
  439. package/src/memory/v2/__tests__/sim.test.ts +45 -1
  440. package/src/memory/v2/__tests__/skill-store.test.ts +58 -3
  441. package/src/memory/v2/__tests__/static-context.test.ts +7 -22
  442. package/src/memory/v2/__tests__/sweep-job.test.ts +95 -0
  443. package/src/memory/v2/activation-store.ts +34 -5
  444. package/src/memory/v2/activation.ts +40 -27
  445. package/src/memory/v2/backfill-jobs.ts +17 -84
  446. package/src/memory/v2/consolidation-job.ts +85 -78
  447. package/src/memory/v2/frontmatter-sweep.ts +91 -0
  448. package/src/memory/v2/injection.ts +440 -109
  449. package/src/memory/v2/migration.ts +117 -20
  450. package/src/memory/v2/page-index.ts +191 -0
  451. package/src/memory/v2/page-store.ts +3 -0
  452. package/src/memory/v2/prompts/consolidation.ts +9 -7
  453. package/src/memory/v2/prompts/router.ts +192 -0
  454. package/src/memory/v2/qdrant.ts +100 -87
  455. package/src/memory/v2/reranker.ts +14 -7
  456. package/src/memory/v2/router.ts +322 -0
  457. package/src/memory/v2/sim.ts +25 -12
  458. package/src/memory/v2/skill-store.ts +118 -29
  459. package/src/memory/v2/static-context.ts +16 -9
  460. package/src/memory/v2/sweep-job.ts +122 -96
  461. package/src/memory/v2/types.ts +10 -6
  462. package/src/memory/validation.ts +13 -0
  463. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +182 -0
  464. package/src/notifications/__tests__/home-feed-side-effect.test.ts +199 -0
  465. package/src/notifications/__tests__/signal-registry.test.ts +17 -0
  466. package/src/notifications/adapters/platform.ts +171 -0
  467. package/src/notifications/conversation-pairing.ts +2 -2
  468. package/src/notifications/copy-composer.ts +15 -0
  469. package/src/notifications/destination-resolver.ts +21 -0
  470. package/src/notifications/emit-signal.ts +28 -1
  471. package/src/notifications/home-feed-side-effect.ts +111 -0
  472. package/src/notifications/signal.ts +5 -0
  473. package/src/permissions/checker.ts +12 -0
  474. package/src/permissions/ipc-risk-types.ts +2 -0
  475. package/src/plugin-api/index.ts +13 -0
  476. package/src/plugin-api/package.json +12 -0
  477. package/src/plugin-api/types.ts +62 -0
  478. package/src/plugins/defaults/injectors.ts +19 -3
  479. package/src/plugins/external-plugin-loader.ts +294 -0
  480. package/src/plugins/types.ts +46 -30
  481. package/src/plugins/user-loader.ts +64 -41
  482. package/src/proactive-artifact/job.test.ts +12 -4
  483. package/src/proactive-artifact/job.ts +4 -0
  484. package/src/proactive-artifact/trigger-state.test.ts +9 -0
  485. package/src/proactive-artifact/trigger-state.ts +4 -0
  486. package/src/prompts/__tests__/system-prompt.test.ts +105 -0
  487. package/src/prompts/system-prompt.ts +22 -1
  488. package/src/prompts/update-bulletin-job.ts +61 -73
  489. package/src/providers/__tests__/dispatch-connection-routing.test.ts +279 -0
  490. package/src/providers/__tests__/inference.test.ts +288 -0
  491. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  492. package/src/providers/__tests__/provider-secret-catalog.test.ts +6 -0
  493. package/src/providers/__tests__/retry-callsite.test.ts +14 -32
  494. package/src/providers/__tests__/satellite-connection-routing.test.ts +510 -0
  495. package/src/providers/__tests__/search-provider-catalog.test.ts +80 -0
  496. package/src/providers/anthropic/client.ts +95 -26
  497. package/src/providers/call-site-routing.ts +94 -16
  498. package/src/providers/connection-resolution.ts +163 -0
  499. package/src/providers/inference/__tests__/connections-status-label.test.ts +250 -0
  500. package/src/providers/inference/adapter-factory.ts +173 -0
  501. package/src/providers/inference/auth.ts +112 -0
  502. package/src/providers/inference/backfill.ts +196 -0
  503. package/src/providers/inference/connections.ts +356 -0
  504. package/src/providers/inference/resolve-auth.ts +65 -0
  505. package/src/providers/model-catalog.ts +104 -6
  506. package/src/providers/openai/responses-provider.ts +4 -2
  507. package/src/providers/provider-env-vars.ts +17 -7
  508. package/src/providers/provider-secret-catalog.ts +49 -30
  509. package/src/providers/provider-send-message.ts +41 -20
  510. package/src/providers/registry.ts +143 -159
  511. package/src/providers/retry.ts +18 -10
  512. package/src/providers/search-provider-catalog.ts +121 -0
  513. package/src/runtime/AGENTS.md +18 -5
  514. package/src/runtime/__tests__/background-job-runner.test.ts +357 -0
  515. package/src/runtime/__tests__/pre-first-message-gate.test.ts +82 -0
  516. package/src/runtime/actor-trust-resolver.ts +32 -10
  517. package/src/runtime/agent-wake.ts +35 -6
  518. package/src/runtime/assistant-event-hub.ts +3 -85
  519. package/src/runtime/auth/route-policy.ts +303 -8
  520. package/src/runtime/auth/same-actor.ts +2 -0
  521. package/src/runtime/background-job-runner.ts +339 -0
  522. package/src/runtime/btw-sidechain.ts +1 -0
  523. package/src/runtime/http-router.ts +36 -1
  524. package/src/runtime/http-server.ts +31 -5
  525. package/src/runtime/http-types.ts +2 -0
  526. package/src/runtime/middleware/__tests__/request-logger.test.ts +162 -0
  527. package/src/runtime/middleware/request-logger.ts +62 -1
  528. package/src/runtime/pre-first-message-gate.ts +83 -0
  529. package/src/runtime/routes/__tests__/backup-routes.test.ts +8 -1
  530. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +251 -0
  531. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +142 -0
  532. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +315 -0
  533. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +189 -0
  534. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +15 -136
  535. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +736 -0
  536. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +4 -4
  537. package/src/runtime/routes/__tests__/stt-routes.test.ts +5 -1
  538. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +384 -0
  539. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  540. package/src/runtime/routes/acp-routes.ts +10 -8
  541. package/src/runtime/routes/app-management-routes.ts +228 -3
  542. package/src/runtime/routes/approval-routes.ts +0 -18
  543. package/src/runtime/routes/audit-routes.ts +43 -0
  544. package/src/runtime/routes/auth-routes.ts +72 -0
  545. package/src/runtime/routes/avatar-routes.ts +273 -20
  546. package/src/runtime/routes/backup-routes.ts +406 -2
  547. package/src/runtime/routes/bookmark-routes.ts +154 -0
  548. package/src/runtime/routes/channel-verification-routes.ts +2 -1
  549. package/src/runtime/routes/contact-routes.ts +0 -160
  550. package/src/runtime/routes/conversation-cli-routes.ts +192 -0
  551. package/src/runtime/routes/conversation-management-routes.ts +30 -43
  552. package/src/runtime/routes/conversation-query-routes.ts +334 -86
  553. package/src/runtime/routes/conversation-routes.ts +31 -10
  554. package/src/runtime/routes/conversations-import-routes.ts +229 -0
  555. package/src/runtime/routes/credential-routes.ts +540 -0
  556. package/src/runtime/routes/debug-routes.ts +2 -2
  557. package/src/runtime/routes/document-pdf-renderer.ts +5 -1
  558. package/src/runtime/routes/domain-routes.ts +167 -0
  559. package/src/runtime/routes/email-routes.ts +603 -0
  560. package/src/runtime/routes/errors.ts +2 -2
  561. package/src/runtime/routes/events-routes.ts +192 -0
  562. package/src/runtime/routes/home-feed-routes.ts +6 -78
  563. package/src/runtime/routes/host-app-control-routes.ts +44 -2
  564. package/src/runtime/routes/host-browser-routes.ts +103 -22
  565. package/src/runtime/routes/http-adapter.ts +2 -0
  566. package/src/runtime/routes/identity-routes.ts +5 -0
  567. package/src/runtime/routes/image-generation-routes.ts +99 -0
  568. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +137 -1
  569. package/src/runtime/routes/inbound-stages/background-dispatch.ts +87 -7
  570. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +156 -0
  571. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +22 -4
  572. package/src/runtime/routes/index.ts +36 -0
  573. package/src/runtime/routes/inference-profile-session-handler.ts +312 -0
  574. package/src/runtime/routes/inference-profile-session-reaper.ts +98 -0
  575. package/src/runtime/routes/inference-profile-session-routes.ts +146 -0
  576. package/src/runtime/routes/inference-provider-connection-routes.ts +317 -0
  577. package/src/runtime/routes/inference-send-routes.ts +115 -0
  578. package/src/runtime/routes/integrations/twilio.ts +1 -0
  579. package/src/runtime/routes/mcp-auth-routes.ts +283 -9
  580. package/src/runtime/routes/memory-v2-routes.ts +13 -398
  581. package/src/runtime/routes/notification-routes.ts +2 -0
  582. package/src/runtime/routes/oauth-apps.ts +112 -7
  583. package/src/runtime/routes/oauth-commands-routes.ts +1007 -0
  584. package/src/runtime/routes/oauth-connect-routes.ts +67 -5
  585. package/src/runtime/routes/oauth-providers.ts +298 -8
  586. package/src/runtime/routes/platform-routes.ts +336 -0
  587. package/src/runtime/routes/playground/inject-failures.ts +2 -1
  588. package/src/runtime/routes/playground/reset-circuit.ts +2 -1
  589. package/src/runtime/routes/playground/state.ts +2 -1
  590. package/src/runtime/routes/publish-routes.ts +221 -0
  591. package/src/runtime/routes/schedule-routes.ts +82 -0
  592. package/src/runtime/routes/sequence-routes.ts +291 -0
  593. package/src/runtime/routes/settings-routes.ts +2 -10
  594. package/src/runtime/routes/skills-routes.ts +31 -1
  595. package/src/runtime/routes/stt-routes.ts +240 -3
  596. package/src/runtime/routes/surface-action-routes.ts +43 -7
  597. package/src/runtime/routes/tts-routes.ts +67 -0
  598. package/src/runtime/routes/types.ts +32 -0
  599. package/src/runtime/routes/user-routes-cli.ts +243 -0
  600. package/src/runtime/routes/webhook-routes.ts +165 -0
  601. package/src/runtime/sync/resource-sync-events.ts +25 -0
  602. package/src/runtime/sync/sync-publisher.test.ts +105 -0
  603. package/src/runtime/sync/sync-publisher.ts +21 -0
  604. package/src/schedule/scheduler.ts +200 -123
  605. package/src/security/__tests__/provider-key-env-fallback.test.ts +12 -6
  606. package/src/security/secret-patterns.ts +3 -0
  607. package/src/sequence/engine.ts +38 -40
  608. package/src/subagent/manager.ts +20 -15
  609. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +206 -0
  610. package/src/tools/browser/browser-execution.ts +15 -4
  611. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +174 -0
  612. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +16 -13
  613. package/src/tools/browser/cdp-client/extension-cdp-client.ts +24 -1
  614. package/src/tools/browser/cdp-client/factory.ts +66 -5
  615. package/src/tools/browser/runtime-check.ts +77 -0
  616. package/src/tools/memory/register.test.ts +3 -3
  617. package/src/tools/memory/register.ts +9 -1
  618. package/src/tools/network/__tests__/web-search.test.ts +156 -0
  619. package/src/tools/network/web-search.ts +280 -37
  620. package/src/tools/permission-checker.ts +13 -5
  621. package/src/tools/subagent/spawn.ts +3 -3
  622. package/src/tools/terminal/shell.ts +44 -0
  623. package/src/usage/attribution.ts +3 -2
  624. package/src/util/pricing.ts +86 -160
  625. package/src/watcher/__tests__/engine.test.ts +301 -0
  626. package/src/watcher/constants.ts +7 -0
  627. package/src/watcher/engine.ts +90 -90
  628. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +6 -9
  629. package/src/workspace/migrations/054-seed-recall-callsite.ts +10 -1
  630. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +28 -4
  631. package/src/workspace/migrations/069-seed-onboarding-threads.ts +8 -2
  632. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +104 -0
  633. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +93 -0
  634. package/src/workspace/migrations/074-drop-deprecated-secret-detection-keys.ts +117 -0
  635. package/src/workspace/migrations/075-memory-v2-bm25-b-default-reembed.ts +61 -0
  636. package/src/workspace/migrations/076-drop-services-inference-mode.ts +62 -0
  637. package/src/workspace/migrations/077-seed-memory-router-callsite.ts +89 -0
  638. package/src/workspace/migrations/078-release-notes-tavily-web-search.ts +66 -0
  639. package/src/workspace/migrations/079-home-feed-notification-only.ts +197 -0
  640. package/src/workspace/migrations/080-restrict-vercel-api-token-metadata.ts +182 -0
  641. package/src/workspace/migrations/081-backfill-bash-allowed-tools-for-injection-credentials.ts +160 -0
  642. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +154 -0
  643. package/src/workspace/migrations/registry.ts +22 -0
  644. package/src/workspace/migrations/runner.ts +13 -2
  645. package/src/workspace/migrations/types.ts +13 -3
  646. package/src/workspace/provider-commit-message-generator.ts +3 -2
  647. package/src/__tests__/context-search-pkb-source.test.ts +0 -498
  648. package/src/__tests__/credentials-cli.test.ts +0 -1225
  649. package/src/__tests__/memory-admin-recall.test.ts +0 -213
  650. package/src/approvals/__tests__/guardian-feed-event.test.ts +0 -303
  651. package/src/cli/commands/__tests__/email-download.test.ts +0 -260
  652. package/src/cli/commands/__tests__/email-list.test.ts +0 -216
  653. package/src/cli/commands/__tests__/email-register.test.ts +0 -186
  654. package/src/cli/commands/__tests__/email-send.test.ts +0 -416
  655. package/src/cli/commands/__tests__/email-status.test.ts +0 -185
  656. package/src/cli/commands/__tests__/email-unregister.test.ts +0 -168
  657. package/src/cli/commands/__tests__/routes.test.ts +0 -562
  658. package/src/cli/commands/__tests__/stt-transcribe.test.ts +0 -454
  659. package/src/cli/commands/autonomy.ts +0 -365
  660. package/src/cli/commands/memory.ts +0 -424
  661. package/src/cli/commands/oauth/__tests__/connect.test.ts +0 -947
  662. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +0 -686
  663. package/src/cli/commands/oauth/__tests__/mode.test.ts +0 -632
  664. package/src/cli/commands/oauth/__tests__/ping.test.ts +0 -631
  665. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +0 -573
  666. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +0 -330
  667. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +0 -521
  668. package/src/cli/commands/oauth/__tests__/status.test.ts +0 -551
  669. package/src/cli/commands/oauth/__tests__/token.test.ts +0 -420
  670. package/src/cli/lib/daemon-avatar-client.ts +0 -37
  671. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -87
  672. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +0 -207
  673. package/src/daemon/__tests__/conversation-feed-event.test.ts +0 -304
  674. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +0 -233
  675. package/src/home/__tests__/assistant-feed-authoring.test.ts +0 -156
  676. package/src/home/__tests__/emit-feed-event.test.ts +0 -169
  677. package/src/home/__tests__/feed-population-integration.test.ts +0 -312
  678. package/src/home/__tests__/feed-scheduler.test.ts +0 -222
  679. package/src/home/__tests__/phase5-exit-criteria.test.ts +0 -229
  680. package/src/home/__tests__/platform-gmail-digest.test.ts +0 -222
  681. package/src/home/__tests__/rollup-producer.test.ts +0 -507
  682. package/src/home/assistant-feed-authoring.ts +0 -135
  683. package/src/home/emit-feed-event.ts +0 -169
  684. package/src/home/feed-scheduler.ts +0 -281
  685. package/src/home/platform-gmail-digest.ts +0 -163
  686. package/src/home/rewrite-command-preview.ts +0 -66
  687. package/src/home/rewrite-feed-title.ts +0 -58
  688. package/src/home/rollup-producer.ts +0 -426
  689. package/src/memory/admin.ts +0 -326
  690. package/src/memory/context-search/sources/pkb.ts +0 -476
  691. package/src/memory/graph/compaction.ts +0 -299
  692. /package/src/cli/{commands → lib}/cache-fs.ts +0 -0
@@ -3,10 +3,12 @@ import { describe, expect, test } from "bun:test";
3
3
  import type { ContextWindowConfig } from "../config/types.js";
4
4
  import { estimateTextTokens } from "../context/token-estimator.js";
5
5
  import {
6
+ appendTailAnchorToSummary,
6
7
  clampSummaryAtSectionBoundary,
7
8
  CONTEXT_SUMMARY_MARKER,
8
9
  ContextWindowManager,
9
10
  createContextSummaryMessage,
11
+ extractTailAssistantText,
10
12
  getSummaryFromContextMessage,
11
13
  stripCompactionOnlyInjections,
12
14
  } from "../context/window-manager.js";
@@ -72,7 +74,7 @@ describe("ContextWindowManager", () => {
72
74
  expect(result.reason).toBe("below compaction threshold");
73
75
  });
74
76
 
75
- test("explains forced compaction skip when conversation already fits target", async () => {
77
+ test("explains forced compaction skip when only one user turn exists", async () => {
76
78
  const provider = createProvider(() => {
77
79
  throw new Error("summarizer should not be called");
78
80
  });
@@ -84,6 +86,10 @@ describe("ContextWindowManager", () => {
84
86
  targetBudgetRatio: 0.5,
85
87
  }),
86
88
  });
89
+ // Only one user turn — there is nothing earlier to summarize, so
90
+ // forced compaction must still skip but report a clear reason
91
+ // instead of "conversation already fits within the compaction
92
+ // target". `force=true` is honored everywhere else.
87
93
  const history = [message("user", "hello"), message("assistant", "hi")];
88
94
 
89
95
  const result = await manager.maybeCompact(history, undefined, {
@@ -93,8 +99,151 @@ describe("ContextWindowManager", () => {
93
99
  expect(result.compacted).toBe(false);
94
100
  expect(result.messages).toEqual(history);
95
101
  expect(result.reason).toBe(
102
+ "only one user turn — nothing earlier to compact",
103
+ );
104
+ });
105
+
106
+ test("forced compaction summarizes when adjustForToolPairs would walk boundary to summary", async () => {
107
+ let summaryCalls = 0;
108
+ const provider = createProvider(() => {
109
+ summaryCalls += 1;
110
+ return {
111
+ content: [{ type: "text", text: "## Summary\n- rescue path ran" }],
112
+ model: "mock-model",
113
+ usage: { inputTokens: 100, outputTokens: 25 },
114
+ stopReason: "end_turn",
115
+ };
116
+ });
117
+ const manager = new ContextWindowManager({
118
+ provider,
119
+ systemPrompt: "system prompt",
120
+ config: makeConfig({
121
+ maxInputTokens: 10_000,
122
+ targetBudgetRatio: 0.5,
123
+ }),
124
+ });
125
+ // Conversation starts with consecutive `assistant(tool_use)` →
126
+ // `user(tool_result + text)` pairs. `collectUserTurnStartIndexes`
127
+ // includes the mixed user messages (not tool_result-only), so the
128
+ // earliest `userTurnStarts` entry is the message containing
129
+ // `tool_result(tool-1)`. Once the projection-optimism clamp
130
+ // decrements the keep boundary to that user turn,
131
+ // `adjustForToolPairs` walks the boundary back through the
132
+ // tool_use/tool_result chain to index 0 — under the old code that
133
+ // routed `/compact` through the "already fits" skip path. With the
134
+ // rescue, summarization runs and orphan `tool_result` blocks are
135
+ // stripped from the kept region.
136
+ const history: Message[] = [
137
+ {
138
+ role: "assistant",
139
+ content: [{ type: "tool_use", id: "tool-1", name: "x", input: {} }],
140
+ },
141
+ {
142
+ role: "user",
143
+ content: [
144
+ { type: "tool_result", tool_use_id: "tool-1", content: "result1" },
145
+ { type: "text", text: "u1" },
146
+ ],
147
+ },
148
+ {
149
+ role: "assistant",
150
+ content: [{ type: "tool_use", id: "tool-2", name: "x", input: {} }],
151
+ },
152
+ {
153
+ role: "user",
154
+ content: [
155
+ { type: "tool_result", tool_use_id: "tool-2", content: "result2" },
156
+ { type: "text", text: "u2" },
157
+ ],
158
+ },
159
+ message("assistant", "a3"),
160
+ message("user", "u3"),
161
+ message("assistant", "a4"),
162
+ ];
163
+
164
+ const result = await manager.maybeCompact(history, undefined, {
165
+ force: true,
166
+ precomputedEstimate: 50_000,
167
+ });
168
+
169
+ expect(result.compacted).toBe(true);
170
+ expect(summaryCalls).toBe(1);
171
+ expect(result.reason).not.toBe(
96
172
  "conversation already fits within the compaction target",
97
173
  );
174
+ expect(result.reason).not.toBe(
175
+ "truncated tool results without summarization",
176
+ );
177
+ expect(result.compactedMessages).toBeGreaterThan(0);
178
+
179
+ // The kept region must not contain orphan tool_result blocks whose
180
+ // tool_use lives in the compacted region — the LLM API would reject
181
+ // such messages on the next agent turn.
182
+ const keptToolUseIds = new Set<string>();
183
+ for (const msg of result.messages) {
184
+ if (msg.role !== "assistant") continue;
185
+ for (const block of msg.content) {
186
+ if (
187
+ (block.type === "tool_use" || block.type === "server_tool_use") &&
188
+ "id" in block
189
+ ) {
190
+ keptToolUseIds.add((block as { id: string }).id);
191
+ }
192
+ }
193
+ }
194
+ for (const msg of result.messages) {
195
+ if (msg.role !== "user") continue;
196
+ for (const block of msg.content) {
197
+ if (
198
+ (block.type === "tool_result" ||
199
+ block.type === "web_search_tool_result") &&
200
+ "tool_use_id" in block
201
+ ) {
202
+ expect(keptToolUseIds.has(block.tool_use_id as string)).toBe(true);
203
+ }
204
+ }
205
+ }
206
+ });
207
+
208
+ test("forced compaction summarizes when compactable count is below MIN guard", async () => {
209
+ let summaryCalls = 0;
210
+ const provider = createProvider(() => {
211
+ summaryCalls += 1;
212
+ return {
213
+ content: [{ type: "text", text: "## Summary\n- min-bypass ran" }],
214
+ model: "mock-model",
215
+ usage: { inputTokens: 100, outputTokens: 25 },
216
+ stopReason: "end_turn",
217
+ };
218
+ });
219
+ const manager = new ContextWindowManager({
220
+ provider,
221
+ systemPrompt: "system prompt",
222
+ config: makeConfig({
223
+ maxInputTokens: 10_000,
224
+ targetBudgetRatio: 0.5,
225
+ }),
226
+ });
227
+ // Two user turns separated by a single assistant message — the
228
+ // smallest realistic conversation where a forced compaction has
229
+ // anything to summarize. After the projection clamp + rescue, the
230
+ // compactable region is at most one user turn (the first one),
231
+ // which can fall below `MIN_COMPACTABLE_PERSISTED_MESSAGES`. The
232
+ // bypass must let summarization run instead of returning
233
+ // "insufficient compactable persisted messages".
234
+ const history: Message[] = [message("user", "u1"), message("user", "u2")];
235
+
236
+ const result = await manager.maybeCompact(history, undefined, {
237
+ force: true,
238
+ precomputedEstimate: 50_000,
239
+ });
240
+
241
+ expect(result.compacted).toBe(true);
242
+ expect(summaryCalls).toBe(1);
243
+ expect(result.reason).not.toBe(
244
+ "insufficient compactable persisted messages",
245
+ );
246
+ expect(result.compactedMessages).toBeGreaterThan(0);
98
247
  });
99
248
 
100
249
  test("forced compaction summarizes when projection fits but real usage exceeds target", async () => {
@@ -2091,3 +2240,242 @@ describe("clampSummaryAtSectionBoundary", () => {
2091
2240
  expect(clamped.length).toBeLessThanOrEqual(100);
2092
2241
  });
2093
2242
  });
2243
+
2244
+ describe("extractTailAssistantText", () => {
2245
+ test("returns the most recent assistant text block", () => {
2246
+ const messages: Message[] = [
2247
+ message("user", "u1"),
2248
+ message("assistant", "a1 first"),
2249
+ message("user", "u2"),
2250
+ message("assistant", "a2 last"),
2251
+ ];
2252
+ expect(extractTailAssistantText(messages)).toBe("a2 last");
2253
+ });
2254
+
2255
+ test("returns null when no assistant text is present", () => {
2256
+ const messages: Message[] = [message("user", "u1"), message("user", "u2")];
2257
+ expect(extractTailAssistantText(messages)).toBeNull();
2258
+ });
2259
+
2260
+ test("skips assistant messages with only tool_use blocks and finds the prior text", () => {
2261
+ const messages: Message[] = [
2262
+ message("assistant", "a1 narration before tool use"),
2263
+ message("user", "u1"),
2264
+ {
2265
+ role: "assistant",
2266
+ content: [
2267
+ {
2268
+ type: "tool_use",
2269
+ id: "tool-1",
2270
+ name: "bash",
2271
+ input: { command: "ls" },
2272
+ } as ContentBlock,
2273
+ ],
2274
+ },
2275
+ ];
2276
+ expect(extractTailAssistantText(messages)).toBe(
2277
+ "a1 narration before tool use",
2278
+ );
2279
+ });
2280
+
2281
+ test("clamps long text from the start so the END is preserved", () => {
2282
+ const longText = "early prefix " + "x".repeat(2000) + " FINAL NEXT STEP";
2283
+ const messages: Message[] = [message("assistant", longText)];
2284
+ const result = extractTailAssistantText(messages, 200);
2285
+ expect(result).not.toBeNull();
2286
+ expect(result!.startsWith("[...truncated]")).toBe(true);
2287
+ expect(result!.endsWith("FINAL NEXT STEP")).toBe(true);
2288
+ // Stripped block size ≈ maxChars; "[...truncated] " adds a fixed prefix.
2289
+ expect(result!.length).toBeLessThanOrEqual(200 + "[...truncated] ".length);
2290
+ });
2291
+
2292
+ test("ignores empty/whitespace-only assistant text", () => {
2293
+ const messages: Message[] = [
2294
+ message("assistant", "real content"),
2295
+ message("assistant", " \n "),
2296
+ ];
2297
+ expect(extractTailAssistantText(messages)).toBe("real content");
2298
+ });
2299
+
2300
+ test("returns null for an empty messages array", () => {
2301
+ expect(extractTailAssistantText([])).toBeNull();
2302
+ });
2303
+ });
2304
+
2305
+ describe("appendTailAnchorToSummary", () => {
2306
+ test("appends a tag-wrapped block after the summary", () => {
2307
+ const out = appendTailAnchorToSummary(
2308
+ "## Goals\n- item",
2309
+ "Next step: file the SSE followup.",
2310
+ );
2311
+ expect(out).toContain("## Goals\n- item");
2312
+ expect(out).toContain(
2313
+ "<verbatim_tail>\nNext step: file the SSE followup.\n</verbatim_tail>",
2314
+ );
2315
+ expect(out.endsWith("</verbatim_tail>")).toBe(true);
2316
+ });
2317
+
2318
+ test("is idempotent: re-applying with new text replaces the prior tail", () => {
2319
+ const first = appendTailAnchorToSummary("body", "tail-1");
2320
+ const second = appendTailAnchorToSummary(first, "tail-2");
2321
+ expect(second).toContain("body");
2322
+ expect(second).toContain("tail-2");
2323
+ expect(second).not.toContain("tail-1");
2324
+ // Exactly one open-tag occurrence — no stacking.
2325
+ expect(second.match(/<verbatim_tail>/g)?.length).toBe(1);
2326
+ });
2327
+ });
2328
+
2329
+ describe("compaction tail-anchor", () => {
2330
+ test("splices the last assistant text block verbatim into the summary message", async () => {
2331
+ const provider = createProvider(() => ({
2332
+ content: [{ type: "text", text: "## Goals\n- LLM summary" }],
2333
+ model: "mock-model",
2334
+ usage: { inputTokens: 100, outputTokens: 25 },
2335
+ stopReason: "end_turn",
2336
+ }));
2337
+ const manager = new ContextWindowManager({
2338
+ provider,
2339
+ systemPrompt: "system prompt",
2340
+ config: makeConfig({ maxInputTokens: 600 }),
2341
+ });
2342
+ const long = "x".repeat(240);
2343
+ const distinctiveTail =
2344
+ "Pushed 8fe70d63a0 — next step: file the SSE followup as promised.";
2345
+ // Place `distinctiveTail` as the assistant response for u1 so it lands
2346
+ // at the end of the compactable region. With the same 600-token budget
2347
+ // and 6-message shape as the existing 600-token compaction test above,
2348
+ // the binary search settles on keepTurns=2 (kept = [u2, a2, u3, a3];
2349
+ // compactable = [u1, distinctiveTail]) — exercising the real-world
2350
+ // drift scenario where the model's last narration in a long work span
2351
+ // gets summarized away.
2352
+ const history: Message[] = [
2353
+ message("user", `u1 ${long}`),
2354
+ message("assistant", distinctiveTail),
2355
+ message("user", `u2 ${long}`),
2356
+ message("assistant", `a2 ${long}`),
2357
+ message("user", `u3 ${long}`),
2358
+ message("assistant", `a3 ${long}`),
2359
+ ];
2360
+
2361
+ const result = await manager.maybeCompact(history);
2362
+
2363
+ expect(result.compacted).toBe(true);
2364
+ const summaryInner = getSummaryFromContextMessage(result.messages[0]);
2365
+ expect(summaryInner).not.toBeNull();
2366
+ // LLM summary still present.
2367
+ expect(summaryInner).toContain("LLM summary");
2368
+ // Verbatim tail spliced in: distinctive text from the LAST assistant
2369
+ // message in the compactable region (here, `distinctiveTail`).
2370
+ expect(summaryInner).toContain("<verbatim_tail>");
2371
+ expect(summaryInner).toContain(distinctiveTail);
2372
+ expect(summaryInner).toContain("</verbatim_tail>");
2373
+ // summaryText reflects what's persisted in messages[0] for consistency
2374
+ // with downstream consumers (DB, context_compacted event).
2375
+ expect(result.summaryText).toContain(distinctiveTail);
2376
+ });
2377
+
2378
+ test("omits the tail-anchor block when no assistant text exists in compactable region", async () => {
2379
+ // Construct a scenario where the compactable region has assistant
2380
+ // messages with ONLY tool_use blocks (no text) plus user turns. The
2381
+ // anchor should be omitted gracefully.
2382
+ const provider = createProvider(() => ({
2383
+ content: [{ type: "text", text: "## Goals\n- summary" }],
2384
+ model: "mock-model",
2385
+ usage: { inputTokens: 100, outputTokens: 25 },
2386
+ stopReason: "end_turn",
2387
+ }));
2388
+ const manager = new ContextWindowManager({
2389
+ provider,
2390
+ systemPrompt: "system prompt",
2391
+ config: makeConfig({ maxInputTokens: 600 }),
2392
+ });
2393
+ const long = "x".repeat(240);
2394
+ const history: Message[] = [
2395
+ message("user", `u1 ${long}`),
2396
+ {
2397
+ role: "assistant",
2398
+ content: [
2399
+ {
2400
+ type: "tool_use",
2401
+ id: "tool-1",
2402
+ name: "bash",
2403
+ input: { command: "ls" },
2404
+ } as ContentBlock,
2405
+ ],
2406
+ },
2407
+ {
2408
+ role: "user",
2409
+ content: [
2410
+ {
2411
+ type: "tool_result",
2412
+ tool_use_id: "tool-1",
2413
+ content: "ls output",
2414
+ } as ContentBlock,
2415
+ ],
2416
+ },
2417
+ message("user", `u2 ${long}`),
2418
+ message("assistant", `a2 ${long}`),
2419
+ message("user", `u3 ${long}`),
2420
+ message("assistant", `a3 ${long}`),
2421
+ ];
2422
+
2423
+ const result = await manager.maybeCompact(history);
2424
+
2425
+ expect(result.compacted).toBe(true);
2426
+ const summaryInner = getSummaryFromContextMessage(result.messages[0]);
2427
+ expect(summaryInner).not.toBeNull();
2428
+ // No tail anchor when the only compactable assistant message has no text.
2429
+ // (a2 / a3 are kept verbatim post-compaction since they're recent enough,
2430
+ // so the compactable-region's only assistant message is the tool_use one.)
2431
+ if (summaryInner!.includes("<verbatim_tail>")) {
2432
+ // If a2 ended up in the compactable region after binary search, the
2433
+ // anchor would surface a2's text — which is fine; the assertion that
2434
+ // matters is that the spliced content (when present) is verbatim
2435
+ // content from the compactable region, not noise. Validate the
2436
+ // ordering: anchor must follow LLM summary text.
2437
+ expect(summaryInner!.indexOf("summary")).toBeLessThan(
2438
+ summaryInner!.indexOf("<verbatim_tail>"),
2439
+ );
2440
+ }
2441
+ });
2442
+
2443
+ test("clamps tail-anchor when the last assistant text is longer than the cap", async () => {
2444
+ const provider = createProvider(() => ({
2445
+ content: [{ type: "text", text: "## Goals\n- summary" }],
2446
+ model: "mock-model",
2447
+ usage: { inputTokens: 100, outputTokens: 25 },
2448
+ stopReason: "end_turn",
2449
+ }));
2450
+ const manager = new ContextWindowManager({
2451
+ provider,
2452
+ systemPrompt: "system prompt",
2453
+ config: makeConfig({ maxInputTokens: 600 }),
2454
+ });
2455
+ const long = "x".repeat(240);
2456
+ const tailEnd = "FINAL DISTINCTIVE END MARKER";
2457
+ // Long enough to trip TAIL_ANCHOR_MAX_CHARS (=1500) clamping.
2458
+ const longTail = "early body " + "y".repeat(2000) + " " + tailEnd;
2459
+ const history: Message[] = [
2460
+ message("user", `u1 ${long}`),
2461
+ message("assistant", longTail),
2462
+ message("user", `u2 ${long}`),
2463
+ message("assistant", `a2 ${long}`),
2464
+ message("user", `u3 ${long}`),
2465
+ message("assistant", `a3 ${long}`),
2466
+ ];
2467
+
2468
+ const result = await manager.maybeCompact(history);
2469
+
2470
+ expect(result.compacted).toBe(true);
2471
+ const summaryInner = getSummaryFromContextMessage(result.messages[0]);
2472
+ expect(summaryInner).not.toBeNull();
2473
+ if (summaryInner!.includes("<verbatim_tail>")) {
2474
+ // When clamped, the END is preserved (most recent narration).
2475
+ expect(summaryInner).toContain(tailEnd);
2476
+ // And the early prefix is dropped.
2477
+ expect(summaryInner).toContain("[...truncated]");
2478
+ expect(summaryInner).not.toContain("early body");
2479
+ }
2480
+ });
2481
+ });
@@ -76,6 +76,7 @@ const defaultLlmConfig: LLMConfig = {
76
76
  profiles: {},
77
77
  profileOrder: [],
78
78
  callSites: {},
79
+ profileSession: { defaultTtlSeconds: 1800, maxTtlSeconds: 43200 },
79
80
  pricingOverrides: [],
80
81
  };
81
82
 
@@ -9,7 +9,9 @@ mock.module("../util/logger.js", () => ({
9
9
  import {
10
10
  createConversation,
11
11
  getConversation,
12
+ getConversationOverrideProfileFromRow,
12
13
  setConversationInferenceProfile,
14
+ setConversationInferenceProfileSession,
13
15
  } from "../memory/conversation-crud.js";
14
16
  import { getDb } from "../memory/db-connection.js";
15
17
  import { initializeDb } from "../memory/db-init.js";
@@ -52,3 +54,101 @@ describe("setConversationInferenceProfile", () => {
52
54
  expect(updated).toHaveProperty("inferenceProfile", "cost-optimized");
53
55
  });
54
56
  });
57
+
58
+ describe("getConversationOverrideProfileFromRow — lazy expiry check", () => {
59
+ beforeEach(() => {
60
+ const db = getDb();
61
+ db.run(`DELETE FROM messages`);
62
+ db.run(`DELETE FROM conversations`);
63
+ });
64
+
65
+ test("returns undefined when inferenceProfileExpiresAt is in the past", () => {
66
+ const conv = createConversation("inference-profile-expired");
67
+ // Set a session-backed profile with an already-expired timestamp.
68
+ setConversationInferenceProfileSession(
69
+ conv.id,
70
+ "balanced",
71
+ "session-uuid-1",
72
+ Date.now() - 1,
73
+ );
74
+ const row = getConversation(conv.id);
75
+ expect(row).not.toBeNull();
76
+ expect(getConversationOverrideProfileFromRow(row)).toBeUndefined();
77
+ });
78
+
79
+ test("returns the profile when inferenceProfileExpiresAt is in the future", () => {
80
+ const conv = createConversation("inference-profile-active-session");
81
+ setConversationInferenceProfileSession(
82
+ conv.id,
83
+ "balanced",
84
+ "session-uuid-2",
85
+ Date.now() + 60_000,
86
+ );
87
+ const row = getConversation(conv.id);
88
+ expect(row).not.toBeNull();
89
+ expect(getConversationOverrideProfileFromRow(row)).toBe("balanced");
90
+ });
91
+
92
+ test("returns undefined at the exact-expiry boundary (expiresAt === now)", () => {
93
+ // Boundary consistency with the reaper SQL (`expires_at <= now`) and
94
+ // the active-session queries (`expiresAt > now`): the lazy check must
95
+ // treat `expiresAt === now` as expired, not active. Otherwise a
96
+ // just-expired session would be served for one extra turn while the
97
+ // reaper is racing to clear it.
98
+ const conv = createConversation("inference-profile-boundary");
99
+ const now = Date.now();
100
+ setConversationInferenceProfileSession(
101
+ conv.id,
102
+ "balanced",
103
+ "session-uuid-boundary",
104
+ now,
105
+ );
106
+ const row = getConversation(conv.id);
107
+ expect(row).not.toBeNull();
108
+ expect(row?.inferenceProfileExpiresAt).toBe(now);
109
+ // Freeze Date.now to the exact stored expiry so this is deterministic.
110
+ const realNow = Date.now;
111
+ Date.now = () => now;
112
+ try {
113
+ expect(getConversationOverrideProfileFromRow(row)).toBeUndefined();
114
+ } finally {
115
+ Date.now = realNow;
116
+ }
117
+ });
118
+
119
+ test("returns the profile when no expiry is set (non-session override)", () => {
120
+ const conv = createConversation("inference-profile-no-expiry");
121
+ setConversationInferenceProfileSession(
122
+ conv.id,
123
+ "quality-optimized",
124
+ null,
125
+ null,
126
+ );
127
+ const row = getConversation(conv.id);
128
+ expect(row).not.toBeNull();
129
+ expect(getConversationOverrideProfileFromRow(row)).toBe(
130
+ "quality-optimized",
131
+ );
132
+ });
133
+
134
+ test.each<"background" | "scheduled">(["background", "scheduled"])(
135
+ "returns undefined for %s conversations even when a profile is pinned",
136
+ (conversationType) => {
137
+ const conv = createConversation({
138
+ title: `inference-profile-${conversationType}`,
139
+ conversationType,
140
+ });
141
+ setConversationInferenceProfileSession(
142
+ conv.id,
143
+ "quality-optimized",
144
+ null,
145
+ null,
146
+ );
147
+ const row = getConversation(conv.id);
148
+ expect(row).not.toBeNull();
149
+ expect(row?.conversationType).toBe(conversationType);
150
+ expect(row?.inferenceProfile).toBe("quality-optimized");
151
+ expect(getConversationOverrideProfileFromRow(row)).toBeUndefined();
152
+ },
153
+ );
154
+ });
@@ -305,6 +305,44 @@ describe("classifyConversationError", () => {
305
305
  });
306
306
  });
307
307
 
308
+ describe("image-input dimension errors via ProviderError (400)", () => {
309
+ it("classifies Anthropic 400 with image-dimension overflow as image_dimensions_too_large (non-retryable)", () => {
310
+ const err = new ProviderError(
311
+ 'Anthropic API error (400): 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.8.content.3.image.source.base64.data: At least one of the image dimensions exceed max allowed size: 8000 pixels"},"request_id":"req_011CaoaGzPXNs2dxAWegSg9D"}',
312
+ "anthropic",
313
+ 400,
314
+ );
315
+ const result = classifyConversationError(err, baseCtx);
316
+ expect(result.code).toBe("IMAGE_TOO_LARGE");
317
+ expect(result.errorCategory).toBe("image_dimensions_too_large");
318
+ expect(result.retryable).toBe(false);
319
+ expect(result.userMessage).toContain("image");
320
+ expect(result.userMessage).toContain("8000");
321
+ });
322
+
323
+ it("matches the singular 'image dimension exceeds' phrasing as well", () => {
324
+ const err = new ProviderError(
325
+ "image dimension exceeds max allowed size: 8000 pixels",
326
+ "anthropic",
327
+ 400,
328
+ );
329
+ const result = classifyConversationError(err, baseCtx);
330
+ expect(result.errorCategory).toBe("image_dimensions_too_large");
331
+ expect(result.retryable).toBe(false);
332
+ });
333
+
334
+ it("does not steal generic 400s that happen to mention 'image'", () => {
335
+ const err = new ProviderError(
336
+ "invalid request: image source is missing",
337
+ "anthropic",
338
+ 400,
339
+ );
340
+ const result = classifyConversationError(err, baseCtx);
341
+ expect(result.errorCategory).toBe("provider_api_error");
342
+ expect(result.retryable).toBe(true);
343
+ });
344
+ });
345
+
308
346
  describe("ordering errors (tool_use/tool_result mismatches)", () => {
309
347
  const cases = [
310
348
  "tool_result block not immediately after tool_use block",