@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091853.fbaa2ae

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 (475) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/docs/activation-funnel-telemetry.md +310 -0
  4. package/examples/plugins/echo/README.md +61 -60
  5. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  6. package/examples/plugins/echo/hooks/stop.ts +16 -0
  7. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  8. package/examples/plugins/echo/package.json +1 -2
  9. package/examples/plugins/echo/src/emit.ts +19 -0
  10. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  11. package/openapi.yaml +263 -121
  12. package/package.json +2 -2
  13. package/src/__tests__/activation-early-marking.test.ts +120 -0
  14. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  15. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  16. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  17. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  18. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  19. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  20. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  21. package/src/__tests__/agent-loop.test.ts +441 -96
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  23. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  24. package/src/__tests__/anthropic-provider.test.ts +23 -8
  25. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  26. package/src/__tests__/app-control-flow.test.ts +1 -1
  27. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  28. package/src/__tests__/app-executors.test.ts +132 -0
  29. package/src/__tests__/approval-cascade.test.ts +6 -5
  30. package/src/__tests__/approval-routes-http.test.ts +4 -1
  31. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  32. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  33. package/src/__tests__/channel-approvals.test.ts +1 -1
  34. package/src/__tests__/compaction-circuit.test.ts +258 -0
  35. package/src/__tests__/compaction-direct.test.ts +146 -0
  36. package/src/__tests__/compaction-events.test.ts +7 -7
  37. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  38. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  39. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  40. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  41. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  42. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  43. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  44. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  45. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  46. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  47. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  48. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  49. package/src/__tests__/conversation-error.test.ts +15 -1
  50. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  51. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  52. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  53. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  54. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  55. package/src/__tests__/conversation-queue.test.ts +10 -10
  56. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  57. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  58. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  59. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  60. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  61. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  62. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  64. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  65. package/src/__tests__/conversation-undo.test.ts +2 -2
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  67. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  69. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  70. package/src/__tests__/corrected-target.test.ts +93 -0
  71. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  72. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  73. package/src/__tests__/db-acp-history.test.ts +101 -0
  74. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  75. package/src/__tests__/empty-response-hook.test.ts +1 -1
  76. package/src/__tests__/file-write-tool.test.ts +63 -0
  77. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  78. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  79. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  80. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  81. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  82. package/src/__tests__/heartbeat-service.test.ts +1 -0
  83. package/src/__tests__/history-repair-hook.test.ts +5 -1
  84. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  85. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  86. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  87. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  88. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  89. package/src/__tests__/injector-background-turn.test.ts +13 -23
  90. package/src/__tests__/injector-chain.test.ts +268 -44
  91. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  92. package/src/__tests__/injector-document-comments.test.ts +97 -114
  93. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  94. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  95. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  96. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  97. package/src/__tests__/llm-resolver.test.ts +73 -0
  98. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  99. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  100. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  101. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  102. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  103. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  104. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  105. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  106. package/src/__tests__/plugin-registry.test.ts +3 -49
  107. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  108. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  109. package/src/__tests__/plugin-types.test.ts +0 -70
  110. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  111. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  112. package/src/__tests__/published-app-updater.test.ts +138 -0
  113. package/src/__tests__/reaction-persistence.test.ts +1 -1
  114. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  115. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  116. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  117. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  118. package/src/__tests__/subagent-detail.test.ts +25 -7
  119. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  120. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  121. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  122. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  123. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  124. package/src/__tests__/title-generate-hook.test.ts +5 -1
  125. package/src/__tests__/tool-error-hook.test.ts +1 -1
  126. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  127. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  128. package/src/__tests__/web-fetch.test.ts +45 -0
  129. package/src/acp/__tests__/agent-process.test.ts +161 -0
  130. package/src/acp/__tests__/client-handler.test.ts +40 -0
  131. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  132. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  133. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  134. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  135. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  136. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  137. package/src/acp/agent-process.ts +61 -1
  138. package/src/acp/auto-install.test.ts +280 -0
  139. package/src/acp/auto-install.ts +232 -0
  140. package/src/acp/client-handler.ts +31 -0
  141. package/src/acp/prepare-agent-env.ts +80 -27
  142. package/src/acp/resolve-agent.test.ts +188 -28
  143. package/src/acp/resolve-agent.ts +119 -42
  144. package/src/acp/resume-hint.ts +23 -0
  145. package/src/acp/session-manager.ts +507 -73
  146. package/src/agent/compaction-circuit.ts +60 -102
  147. package/src/agent/loop.ts +403 -251
  148. package/src/api/responses/conversation-message.ts +22 -2
  149. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  150. package/src/approvals/guardian-request-resolvers.ts +27 -1
  151. package/src/background-wake/next-wake.ts +1 -0
  152. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  153. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  154. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  155. package/src/cli/commands/memory-v3.ts +57 -199
  156. package/src/cli/commands/plugins.ts +43 -37
  157. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  158. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  159. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  160. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  161. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  162. package/src/cli/lib/install-from-github.ts +797 -60
  163. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  164. package/src/cli/lib/plugin-details.ts +416 -0
  165. package/src/cli/lib/plugin-marketplace.ts +215 -0
  166. package/src/cli/lib/search-plugins.ts +201 -29
  167. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  168. package/src/config/acp-defaults.test.ts +10 -0
  169. package/src/config/acp-defaults.ts +9 -3
  170. package/src/config/acp-schema.ts +1 -7
  171. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  172. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  173. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  174. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  175. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  176. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  177. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  178. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  180. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  181. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  182. package/src/config/bundled-tool-registry.ts +4 -0
  183. package/src/config/call-site-defaults.ts +0 -3
  184. package/src/config/feature-flag-registry.json +10 -16
  185. package/src/config/llm-resolver.ts +39 -7
  186. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  187. package/src/config/schemas/call-site-catalog.ts +0 -21
  188. package/src/config/schemas/heartbeat.ts +9 -0
  189. package/src/config/schemas/llm.ts +17 -3
  190. package/src/config/schemas/memory-v3.ts +58 -8
  191. package/src/config/seed-inference-profiles.ts +18 -0
  192. package/src/context/compactor.ts +22 -4
  193. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  194. package/src/context/strip-injections.ts +8 -2
  195. package/src/daemon/conversation-agent-loop-handlers.ts +18 -36
  196. package/src/daemon/conversation-agent-loop.ts +308 -1297
  197. package/src/daemon/conversation-error.ts +31 -4
  198. package/src/daemon/conversation-history.ts +1 -1
  199. package/src/daemon/conversation-lifecycle.ts +11 -255
  200. package/src/daemon/conversation-media-retry.ts +19 -6
  201. package/src/daemon/conversation-messaging.ts +17 -0
  202. package/src/daemon/conversation-process.ts +22 -141
  203. package/src/daemon/conversation-queue-manager.ts +8 -0
  204. package/src/daemon/conversation-registry.ts +159 -0
  205. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  206. package/src/daemon/conversation-store.ts +9 -90
  207. package/src/daemon/conversation-surfaces.ts +165 -11
  208. package/src/daemon/conversation-workspace.ts +17 -0
  209. package/src/daemon/conversation.ts +444 -62
  210. package/src/daemon/disk-pressure-policy.ts +0 -1
  211. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  212. package/src/daemon/handlers/config-channels.ts +11 -2
  213. package/src/daemon/handlers/conversations.ts +3 -1
  214. package/src/daemon/handlers/skills.ts +4 -1
  215. package/src/daemon/host-app-control-proxy.ts +72 -57
  216. package/src/daemon/host-proxy-preactivation.ts +1 -3
  217. package/src/daemon/lifecycle.ts +21 -7
  218. package/src/daemon/persist-unsendable-image.ts +62 -25
  219. package/src/daemon/process-message.ts +1 -1
  220. package/src/daemon/server.ts +2 -0
  221. package/src/daemon/tool-side-effects.ts +15 -0
  222. package/src/daemon/wake-conversation-ops.ts +269 -0
  223. package/src/embedded/plugin-api.ts +2 -2
  224. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  225. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  226. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  227. package/src/heartbeat/heartbeat-service.ts +26 -0
  228. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  229. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  230. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  231. package/src/ipc/skill-routes/memory.ts +4 -2
  232. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  233. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  234. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  235. package/src/memory/activation-session-store.ts +43 -0
  236. package/src/memory/conversation-crud.ts +29 -19
  237. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  238. package/src/memory/db-init.ts +6 -0
  239. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  240. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  241. package/src/memory/jobs-worker.ts +25 -1
  242. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  243. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  244. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  245. package/src/memory/migrations/index.ts +3 -0
  246. package/src/memory/onboarding-events-store.ts +66 -18
  247. package/src/memory/schema/acp.ts +4 -0
  248. package/src/memory/schema/infrastructure.ts +13 -0
  249. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  250. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  251. package/src/memory/v2/consolidation-job.ts +2 -63
  252. package/src/memory/v2/types.ts +5 -0
  253. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  254. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  255. package/src/notifications/access-request-copy.ts +298 -0
  256. package/src/notifications/adapters/slack.ts +3 -3
  257. package/src/notifications/adapters/telegram.ts +9 -2
  258. package/src/notifications/copy-composer.ts +49 -267
  259. package/src/notifications/decision-engine.ts +16 -35
  260. package/src/notifications/home-feed-side-effect.ts +1 -6
  261. package/src/plugin-api/constants.ts +4 -0
  262. package/src/plugin-api/index.ts +40 -11
  263. package/src/plugin-api/types.ts +108 -0
  264. package/src/plugins/defaults/compaction/compact.ts +106 -0
  265. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  266. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  267. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  268. package/src/plugins/defaults/compaction/package.json +1 -2
  269. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  270. package/src/plugins/defaults/empty-response/package.json +0 -1
  271. package/src/plugins/defaults/history-repair/package.json +0 -1
  272. package/src/plugins/defaults/index.ts +156 -26
  273. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  274. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  275. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  276. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  277. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  278. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  279. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  280. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  281. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  282. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  283. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  284. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  285. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  286. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  287. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  288. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  289. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  290. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  291. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  293. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  294. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  295. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  296. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  297. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  298. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  299. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  300. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  301. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  302. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  303. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  304. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  306. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  307. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  308. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  309. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  310. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  311. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  312. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  313. package/src/plugins/defaults/title-generate/package.json +0 -1
  314. package/src/plugins/defaults/tool-error/package.json +0 -1
  315. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  316. package/src/plugins/external-api.ts +12 -2
  317. package/src/plugins/pipeline.ts +6 -293
  318. package/src/plugins/registry.ts +9 -37
  319. package/src/plugins/types.ts +76 -381
  320. package/src/plugins/user-loader.ts +30 -127
  321. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  322. package/src/prompts/system-prompt.ts +61 -10
  323. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  324. package/src/providers/anthropic/client.ts +9 -10
  325. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  326. package/src/providers/inference/logit-bias.ts +55 -0
  327. package/src/providers/model-catalog.ts +34 -0
  328. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  329. package/src/providers/openai/chat-completions-provider.ts +44 -1
  330. package/src/providers/retry.ts +22 -0
  331. package/src/providers/types.ts +6 -0
  332. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  333. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  334. package/src/runtime/agent-wake.ts +108 -209
  335. package/src/runtime/assistant-event-hub.ts +1 -1
  336. package/src/runtime/channel-approvals.ts +1 -1
  337. package/src/runtime/interactive-ui.ts +1 -1
  338. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  339. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  340. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  341. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  342. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  343. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  344. package/src/runtime/routes/acp-routes.test.ts +185 -32
  345. package/src/runtime/routes/acp-routes.ts +328 -30
  346. package/src/runtime/routes/app-management-routes.ts +3 -0
  347. package/src/runtime/routes/approval-routes.ts +1 -1
  348. package/src/runtime/routes/browser-routes.ts +1 -1
  349. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  350. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  351. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  352. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  353. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  354. package/src/runtime/routes/conversation-routes.ts +30 -2
  355. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  356. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  357. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  358. package/src/runtime/routes/host-cu-routes.ts +1 -1
  359. package/src/runtime/routes/identity-routes.ts +18 -3
  360. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  361. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  362. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  363. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  364. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  365. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  366. package/src/runtime/routes/playground/helpers.ts +1 -1
  367. package/src/runtime/routes/plugins-routes.ts +337 -35
  368. package/src/runtime/routes/stt-routes.ts +45 -12
  369. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  370. package/src/runtime/routes/work-items-routes.ts +2 -4
  371. package/src/runtime/routes/workspace-routes.ts +50 -15
  372. package/src/runtime/services/conversation-serializer.ts +1 -1
  373. package/src/runtime/verification-outbound-actions.ts +147 -2
  374. package/src/runtime/verification-templates.ts +29 -3
  375. package/src/services/published-app-updater.ts +30 -8
  376. package/src/signals/cancel.ts +2 -4
  377. package/src/subagent/manager.ts +21 -5
  378. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  379. package/src/telemetry/activation-funnel.ts +167 -0
  380. package/src/telemetry/types.ts +13 -0
  381. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  382. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  383. package/src/tools/acp/context.ts +20 -0
  384. package/src/tools/acp/list-agents.test.ts +9 -19
  385. package/src/tools/acp/list-agents.ts +3 -15
  386. package/src/tools/acp/spawn.test.ts +173 -202
  387. package/src/tools/acp/spawn.ts +37 -172
  388. package/src/tools/acp/steer.test.ts +105 -8
  389. package/src/tools/acp/steer.ts +48 -17
  390. package/src/tools/apps/executors.ts +166 -50
  391. package/src/tools/browser/browser-execution.ts +12 -2
  392. package/src/tools/filesystem/write.ts +34 -0
  393. package/src/tools/network/web-fetch.ts +65 -24
  394. package/src/tools/skills/load.ts +1 -1
  395. package/src/tools/subagent/spawn.ts +2 -4
  396. package/src/tools/ui-surface/definitions.ts +32 -5
  397. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  398. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  399. package/docs/plugins.md +0 -832
  400. package/examples/plugins/echo/register.ts +0 -143
  401. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  402. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  403. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  404. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  405. package/src/__tests__/pipeline-runner.test.ts +0 -554
  406. package/src/daemon/wake-target-adapter.ts +0 -253
  407. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  408. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  409. package/src/memory/v3/__tests__/core.test.ts +0 -39
  410. package/src/memory/v3/__tests__/health.test.ts +0 -219
  411. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  412. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  413. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  414. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  415. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  416. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  417. package/src/memory/v3/__tests__/router.test.ts +0 -332
  418. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  419. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  420. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  421. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  422. package/src/memory/v3/assign.ts +0 -268
  423. package/src/memory/v3/capabilities.ts +0 -124
  424. package/src/memory/v3/core.ts +0 -26
  425. package/src/memory/v3/health.ts +0 -0
  426. package/src/memory/v3/maintain-job.ts +0 -314
  427. package/src/memory/v3/needle.ts +0 -115
  428. package/src/memory/v3/orchestrate.ts +0 -126
  429. package/src/memory/v3/page-content.ts +0 -34
  430. package/src/memory/v3/provider-blocks.ts +0 -26
  431. package/src/memory/v3/reconcile.ts +0 -523
  432. package/src/memory/v3/router.ts +0 -190
  433. package/src/memory/v3/selection-log-store.ts +0 -84
  434. package/src/memory/v3/selector.ts +0 -226
  435. package/src/memory/v3/shadow-plugin.ts +0 -411
  436. package/src/memory/v3/snapshot.ts +0 -209
  437. package/src/memory/v3/tree.ts +0 -174
  438. package/src/memory/v3/types.ts +0 -59
  439. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  440. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  441. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  442. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  443. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  444. package/src/plugins/defaults/compaction/register.ts +0 -35
  445. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  446. package/src/plugins/defaults/empty-response/register.ts +0 -23
  447. package/src/plugins/defaults/history-repair/register.ts +0 -24
  448. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  449. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  450. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  451. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  452. package/src/plugins/defaults/title-generate/register.ts +0 -35
  453. package/src/plugins/defaults/tool-error/register.ts +0 -23
  454. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  455. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  456. package/src/proactive-artifact/decision.test.ts +0 -226
  457. package/src/proactive-artifact/decision.ts +0 -165
  458. package/src/proactive-artifact/index.ts +0 -7
  459. package/src/proactive-artifact/job.test.ts +0 -962
  460. package/src/proactive-artifact/job.ts +0 -372
  461. package/src/proactive-artifact/message-copy.ts +0 -58
  462. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  463. package/src/proactive-artifact/trigger-state.ts +0 -123
  464. package/src/util/map-limit.ts +0 -27
  465. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  466. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  467. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  468. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  469. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  470. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  471. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  472. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  473. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  474. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  475. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
@@ -11,6 +11,9 @@
11
11
  * - Skips (re)injection when the `<info>` block is already present in the
12
12
  * turn's working messages (presence detection — the block persists in
13
13
  * history between compactions).
14
+ * - Still injects when only a dynamic `<memory>` activation block is present
15
+ * (that wrapper must not be mistaken for the static block) — both on a
16
+ * normal turn and after compaction strips the prior `<info>` block.
14
17
  *
15
18
  * The injector sources its content itself via `readMemoryV2StaticContent()`
16
19
  * behind the personal-memory trust gate, so each test seeds the workspace
@@ -35,7 +38,7 @@ mock.module("../config/loader.js", () => ({
35
38
  }));
36
39
 
37
40
  const { defaultInjectors } =
38
- await import("../plugins/defaults/injectors/register.js");
41
+ await import("../plugins/defaults/memory-retrieval/injectors.js");
39
42
  import type { Injector, TurnContext } from "../plugins/types.js";
40
43
  import type { Message } from "../providers/types.js";
41
44
  import { getWorkspacePromptPath } from "../util/platform.js";
@@ -54,7 +57,6 @@ function makeContext(overrides: Partial<TurnContext> = {}): TurnContext {
54
57
  conversationId: "conv-test",
55
58
  turnIndex: 0,
56
59
  trust: { sourceChannel: "vellum", trustClass: "guardian" },
57
- injectionInputs: {},
58
60
  ...overrides,
59
61
  };
60
62
  }
@@ -96,7 +98,7 @@ describe("memory-v2-static injector", () => {
96
98
 
97
99
  test("returns null in minimal mode even with content", async () => {
98
100
  seedEssentials("Alice prefers VS Code.");
99
- const ctx = makeContext({ injectionInputs: { mode: "minimal" } });
101
+ const ctx = makeContext({ mode: "minimal" });
100
102
  expect(await memoryV2StaticInjector.produce(ctx)).toBeNull();
101
103
  });
102
104
 
@@ -140,10 +142,12 @@ describe("memory-v2-static injector", () => {
140
142
  expect(await memoryV2StaticInjector.produce(ctx, runMessages)).toBeNull();
141
143
  });
142
144
 
143
- test("skips (re)injection when a legacy <memory>-wrapped static block is present", async () => {
144
- // Rows persisted before the `<info>` switch rehydrate the static block as
145
- // `<memory>…</memory>`. Re-injecting a fresh `<info>` copy alongside it
146
- // would duplicate the content until the next compaction.
145
+ test("injects the <info> block even when a dynamic <memory> activation block is present", async () => {
146
+ // Regression (#33612): the v2 *dynamic* activation block uses the
147
+ // `<memory>…</memory>` wrapper and `prepareMemory` prepends it to the tail
148
+ // user message every turn, before this injector runs. The static injector
149
+ // must NOT treat that as its own `<info>` block — doing so suppressed the
150
+ // static block on essentially every turn.
147
151
  seedEssentials("Alice prefers VS Code.");
148
152
  const ctx = makeContext();
149
153
  const runMessages: Message[] = [
@@ -152,12 +156,86 @@ describe("memory-v2-static injector", () => {
152
156
  content: [
153
157
  {
154
158
  type: "text",
155
- text: "<memory>\n## Essentials\n\nAlice prefers VS Code.\n</memory>",
159
+ text: "<memory>\n# memory/concepts/foo.md\nactivated page summary\n</memory>",
156
160
  },
157
161
  { type: "text", text: "What next?" },
158
162
  ],
159
163
  },
160
164
  ];
165
+ const block = await memoryV2StaticInjector.produce(ctx, runMessages);
166
+ expect(block).not.toBeNull();
167
+ expect(block!.id).toBe("memory-v2-static");
168
+ expect(block!.text).toBe(
169
+ "<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
170
+ );
171
+ });
172
+
173
+ test("reinjects the <info> block after compaction strips it, alongside a fresh <memory> block", async () => {
174
+ // Post-compaction state: `stripInjectionsForCompaction` removed the prior
175
+ // `<info>` block, and the next turn's `prepareMemory` re-added a fresh
176
+ // dynamic `<memory>` block ahead of the chain. With no `<info>` present, the
177
+ // injector must reinject it rather than skip on the `<memory>` block.
178
+ seedEssentials("Alice prefers VS Code.");
179
+ const ctx = makeContext();
180
+ const postCompactMessages: Message[] = [
181
+ {
182
+ role: "user",
183
+ content: [
184
+ { type: "text", text: "<memory>\nactivated page summary\n</memory>" },
185
+ { type: "text", text: "Continue please." },
186
+ ],
187
+ },
188
+ ];
189
+ const block = await memoryV2StaticInjector.produce(
190
+ ctx,
191
+ postCompactMessages,
192
+ );
193
+ expect(block).not.toBeNull();
194
+ expect(block!.id).toBe("memory-v2-static");
195
+ expect(block!.text).toContain("## Essentials");
196
+ });
197
+
198
+ test("skips (re)injection when the <info> block is carried in a flattened history block", async () => {
199
+ // Regression: when a historical user message's separate injection content
200
+ // blocks get collapsed into one text block (observed after a daemon restart
201
+ // / plugin hot-reload rebuilt the rendered history mid-conversation — the
202
+ // blocks are joined with "\n"), the `<info>` wrapper is no longer its own
203
+ // block but a newline-delimited section inside a larger block. Presence
204
+ // detection must still recognize it; otherwise the injector re-injects a
205
+ // duplicate `<info>` onto the new tail (the double-injection bug).
206
+ seedEssentials("Alice prefers VS Code.");
207
+ const ctx = makeContext();
208
+ const flattenedFirstUser = [
209
+ "<workspace>\nRoot: /ws\nFiles: a.md\n</workspace>",
210
+ "<turn_context>\ncurrent_time: now\n</turn_context>",
211
+ "<memory>\nactivated page summary\n</memory>",
212
+ "<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
213
+ "what should we do next?",
214
+ ].join("\n");
215
+ const runMessages: Message[] = [
216
+ { role: "user", content: [{ type: "text", text: flattenedFirstUser }] },
217
+ { role: "assistant", content: [{ type: "text", text: "On it." }] },
218
+ { role: "user", content: [{ type: "text", text: "great, continue" }] },
219
+ ];
161
220
  expect(await memoryV2StaticInjector.produce(ctx, runMessages)).toBeNull();
162
221
  });
222
+
223
+ test("still injects when a user message opens an <info> tag without closing it", async () => {
224
+ // False-positive guard: the wrapper matcher requires BOTH the opening and
225
+ // closing tags, so user prose that merely opens an injection-like tag must
226
+ // not be mistaken for a carried block and suppress injection.
227
+ seedEssentials("Alice prefers VS Code.");
228
+ const ctx = makeContext();
229
+ const runMessages: Message[] = [
230
+ {
231
+ role: "user",
232
+ content: [
233
+ { type: "text", text: "<info>\n is this how the tag works?" },
234
+ ],
235
+ },
236
+ ];
237
+ const block = await memoryV2StaticInjector.produce(ctx, runMessages);
238
+ expect(block).not.toBeNull();
239
+ expect(block!.id).toBe("memory-v2-static");
240
+ });
163
241
  });
@@ -9,17 +9,17 @@
9
9
 
10
10
  import { describe, expect, test } from "bun:test";
11
11
 
12
- import type { ConversationCandidateSet } from "../notifications/conversation-candidates.js";
13
12
  import {
14
13
  buildAccessRequestContractText,
15
14
  buildAccessRequestIdentityLine,
16
- composeFallbackCopy,
17
15
  hasAccessRequestInstructions,
18
16
  hasInviteFlowDirective,
19
17
  normalizeForDirectiveMatching,
20
18
  sanitizeIdentityField,
21
19
  sanitizeMessagePreview,
22
- } from "../notifications/copy-composer.js";
20
+ } from "../notifications/access-request-copy.js";
21
+ import type { ConversationCandidateSet } from "../notifications/conversation-candidates.js";
22
+ import { composeFallbackCopy } from "../notifications/copy-composer.js";
23
23
  import {
24
24
  enforceGuardianCallConversationAffinity,
25
25
  validateConversationActions,
@@ -128,12 +128,19 @@ describe("Parallel tool execution benchmarks", () => {
128
128
  return { content: "ok", isError: false };
129
129
  };
130
130
 
131
- const loop = new AgentLoop(provider, "system", {
131
+ const loop = new AgentLoop({
132
+ provider: provider,
133
+ systemPrompt: "system",
134
+ conversationId: "test-conversation",
132
135
  tools: dummyTools,
133
136
  toolExecutor: toolExecutor,
134
137
  });
135
138
  const start = Date.now();
136
- await loop.run([userMessage], () => {});
139
+ await loop.run({
140
+ messages: [userMessage],
141
+ onEvent: () => {},
142
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
143
+ });
137
144
  const elapsed = Date.now() - start;
138
145
 
139
146
  // Parallel: ~50ms + overhead. Sequential would be ~250ms.
@@ -169,12 +176,19 @@ describe("Parallel tool execution benchmarks", () => {
169
176
  return { content: "ok", isError: false };
170
177
  };
171
178
 
172
- const loop = new AgentLoop(provider, "system", {
179
+ const loop = new AgentLoop({
180
+ provider: provider,
181
+ systemPrompt: "system",
182
+ conversationId: "test-conversation",
173
183
  tools: dummyTools,
174
184
  toolExecutor: toolExecutor,
175
185
  });
176
186
  const start = Date.now();
177
- await loop.run([userMessage], () => {});
187
+ await loop.run({
188
+ messages: [userMessage],
189
+ onEvent: () => {},
190
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
191
+ });
178
192
  const elapsed = Date.now() - start;
179
193
 
180
194
  // All 10 tools should have executed
@@ -222,13 +236,20 @@ describe("Parallel tool execution benchmarks", () => {
222
236
  return { content: "ok", isError: false };
223
237
  };
224
238
 
225
- const loop = new AgentLoop(provider, "system", {
239
+ const loop = new AgentLoop({
240
+ provider: provider,
241
+ systemPrompt: "system",
242
+ conversationId: "test-conversation",
226
243
  tools: dummyTools,
227
244
  toolExecutor: toolExecutor,
228
245
  });
229
246
  const events: AgentEvent[] = [];
230
247
  const start = Date.now();
231
- await loop.run([userMessage], collectEvents(events));
248
+ await loop.run({
249
+ messages: [userMessage],
250
+ onEvent: collectEvents(events),
251
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
252
+ });
232
253
  const elapsed = Date.now() - start;
233
254
 
234
255
  // Parallel: ~2000ms (dominated by slow tool). Sequential: ~2400ms (2000 + 4*100).
@@ -289,12 +310,18 @@ describe("Parallel tool execution benchmarks", () => {
289
310
  return { content: "should not return", isError: false };
290
311
  };
291
312
 
292
- const loop = new AgentLoop(provider, "system", {
313
+ const loop = new AgentLoop({
314
+ provider: provider,
315
+ systemPrompt: "system",
316
+ conversationId: "test-conversation",
293
317
  tools: dummyTools,
294
318
  toolExecutor: toolExecutor,
295
319
  });
296
320
  const start = Date.now();
297
- const { history } = await loop.run([userMessage], () => {}, {
321
+ const { history } = await loop.run({
322
+ messages: [userMessage],
323
+ onEvent: () => {},
324
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
298
325
  signal: controller.signal,
299
326
  });
300
327
  const elapsed = Date.now() - start;
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Regression test for the durable-downscale path of the image-too-large
3
+ * recovery (JARVIS-1041 review follow-up).
4
+ *
5
+ * When an oversized stored image *can* be shrunk on this host (the common macOS
6
+ * path where `sips` is available), `persistUnsendableImageDowngrades` must write
7
+ * the downscaled bytes back to the DB — not leave the original in place. The
8
+ * latest tool-result media is intentionally kept in context, so leaving the
9
+ * full-size block would rehydrate and re-reject on every later turn instead of
10
+ * durably self-healing the conversation.
11
+ *
12
+ * `optimizeImageForTransport` needs `sips` and a decodable image to actually
13
+ * downscale, which is not portable to CI, so it is mocked here to simulate a
14
+ * successful shrink. The mock is process-global, so this case lives in its own
15
+ * file (the test runner isolates each file in its own process) to avoid
16
+ * disturbing the no-op-resize cases in persist-unsendable-image.test.ts.
17
+ */
18
+
19
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
20
+
21
+ // ── Module mock (must precede the import of the module under test) ────
22
+ // Simulate a host where resizing succeeds: any oversized image is shrunk to a
23
+ // small, distinct JPEG payload. durableImageReplacement checks the provider
24
+ // caps before calling this, so in-limit images never reach the mock.
25
+ const SHRUNK_DATA = "c2hydW5r"; // base64 for "shrunk"
26
+ mock.module("../agent/image-optimize.js", () => ({
27
+ optimizeImageForTransport: () => ({
28
+ data: SHRUNK_DATA,
29
+ mediaType: "image/jpeg",
30
+ }),
31
+ }));
32
+
33
+ import { persistUnsendableImageDowngrades } from "../daemon/persist-unsendable-image.js";
34
+ import {
35
+ addMessage,
36
+ createConversation,
37
+ getMessages,
38
+ } from "../memory/conversation-crud.js";
39
+ import { getDb } from "../memory/db-connection.js";
40
+ import { initializeDb } from "../memory/db-init.js";
41
+ import type { ContentBlock } from "../providers/types.js";
42
+
43
+ initializeDb();
44
+
45
+ function resetTables(): void {
46
+ const db = getDb();
47
+ db.run("DELETE FROM message_attachments");
48
+ db.run("DELETE FROM attachments");
49
+ db.run("DELETE FROM memory_segments");
50
+ db.run("DELETE FROM memory_embeddings");
51
+ db.run("DELETE FROM messages");
52
+ db.run("DELETE FROM conversations");
53
+ }
54
+
55
+ /** Minimal PNG whose IHDR declares dimensions past the 8000px provider cap. */
56
+ function oversizedPngBase64(): string {
57
+ const width = 12000;
58
+ const height = 9000;
59
+ return Buffer.from(
60
+ Uint8Array.from([
61
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, // PNG signature
62
+ 0x00, 0x00, 0x00, 0x0d, // IHDR length (13)
63
+ 0x49, 0x48, 0x44, 0x52, // "IHDR"
64
+ (width >>> 24) & 0xff,
65
+ (width >>> 16) & 0xff,
66
+ (width >>> 8) & 0xff,
67
+ width & 0xff,
68
+ (height >>> 24) & 0xff,
69
+ (height >>> 16) & 0xff,
70
+ (height >>> 8) & 0xff,
71
+ height & 0xff,
72
+ 0x08, 0x06, 0x00, 0x00, 0x00,
73
+ ]),
74
+ ).toString("base64");
75
+ }
76
+
77
+ function toolResultWithImage(data: string): ContentBlock {
78
+ return {
79
+ type: "tool_result",
80
+ tool_use_id: "toolu_123",
81
+ content: "Screenshot captured",
82
+ contentBlocks: [
83
+ { type: "image", source: { type: "base64", media_type: "image/png", data } },
84
+ ],
85
+ };
86
+ }
87
+
88
+ function storedContent(conversationId: string): ContentBlock[][] {
89
+ return getMessages(conversationId).map(
90
+ (row) => JSON.parse(row.content) as ContentBlock[],
91
+ );
92
+ }
93
+
94
+ describe("persistUnsendableImageDowngrades (downscalable host)", () => {
95
+ beforeEach(() => {
96
+ resetTables();
97
+ });
98
+
99
+ /** JARVIS-1041: an oversized screenshot that CAN be shrunk must persist the
100
+ * downscaled bytes, not the note and not the original. */
101
+ test("persists the downscaled image for a shrinkable tool_result screenshot", async () => {
102
+ // GIVEN a tool_result holding an oversized but shrinkable screenshot
103
+ const conv = createConversation();
104
+ await addMessage(
105
+ conv.id,
106
+ "user",
107
+ JSON.stringify([toolResultWithImage(oversizedPngBase64())]),
108
+ { skipIndexing: true },
109
+ );
110
+
111
+ // WHEN the downgrade is persisted
112
+ const rewritten = persistUnsendableImageDowngrades(conv.id);
113
+
114
+ // THEN the nested block stays an image, rewritten to the downscaled payload
115
+ expect(rewritten).toBe(1);
116
+ const [content] = storedContent(conv.id);
117
+ const toolResult = content.find((b) => b.type === "tool_result") as {
118
+ contentBlocks?: ContentBlock[];
119
+ };
120
+ const nested = toolResult.contentBlocks?.[0];
121
+ expect(nested?.type).toBe("image");
122
+ expect((nested as Extract<ContentBlock, { type: "image" }>).source.data).toBe(
123
+ SHRUNK_DATA,
124
+ );
125
+ });
126
+
127
+ /** Re-running is a no-op: the downscaled payload is within limits. */
128
+ test("is idempotent after a downscale rewrite", async () => {
129
+ // GIVEN a conversation whose oversized screenshot was already downscaled
130
+ const conv = createConversation();
131
+ await addMessage(
132
+ conv.id,
133
+ "user",
134
+ JSON.stringify([toolResultWithImage(oversizedPngBase64())]),
135
+ { skipIndexing: true },
136
+ );
137
+ expect(persistUnsendableImageDowngrades(conv.id)).toBe(1);
138
+
139
+ // WHEN the downgrade runs again
140
+ const secondRun = persistUnsendableImageDowngrades(conv.id);
141
+
142
+ // THEN nothing further is rewritten
143
+ expect(secondRun).toBe(0);
144
+ });
145
+ });
@@ -14,7 +14,10 @@
14
14
 
15
15
  import { beforeEach, describe, expect, test } from "bun:test";
16
16
 
17
- import { persistUnsendableImageDowngrades } from "../daemon/persist-unsendable-image.js";
17
+ import {
18
+ oversizedImageReplacement,
19
+ persistUnsendableImageDowngrades,
20
+ } from "../daemon/persist-unsendable-image.js";
18
21
  import {
19
22
  addMessage,
20
23
  createConversation,
@@ -87,6 +90,20 @@ function imageBlock(data: string): ContentBlock {
87
90
  };
88
91
  }
89
92
 
93
+ /**
94
+ * A tool_result carrying a nested image in its contentBlocks, mirroring what a
95
+ * browser screenshot produces. This is the JARVIS-1041 shape: the oversized
96
+ * image lives at tool_result.contentBlocks, never as a top-level block.
97
+ */
98
+ function toolResultWithImage(data: string): ContentBlock {
99
+ return {
100
+ type: "tool_result",
101
+ tool_use_id: "toolu_123",
102
+ content: "Screenshot captured",
103
+ contentBlocks: [imageBlock(data)],
104
+ };
105
+ }
106
+
90
107
  function storedContent(conversationId: string): ContentBlock[][] {
91
108
  return getMessages(conversationId).map(
92
109
  (row) => JSON.parse(row.content) as ContentBlock[],
@@ -194,6 +211,61 @@ describe("persistUnsendableImageDowngrades", () => {
194
211
  expect(content.some((b) => b.type === "image")).toBe(false);
195
212
  });
196
213
 
214
+ /** JARVIS-1041: the oversized image is nested inside a tool_result (e.g. a
215
+ * browser screenshot), not a top-level block. The downgrade must descend
216
+ * into tool_result.contentBlocks and swap the nested image for a note, while
217
+ * keeping the tool_result itself intact so tool_use/tool_result pairing
218
+ * survives. */
219
+ test("downgrades an oversized image nested in tool_result.contentBlocks", async () => {
220
+ // GIVEN an assistant turn whose tool_result holds an oversized screenshot
221
+ const conv = createConversation();
222
+ await addMessage(
223
+ conv.id,
224
+ "user",
225
+ JSON.stringify([toolResultWithImage(makePngBase64(12000, 9000))]),
226
+ { skipIndexing: true },
227
+ );
228
+
229
+ // WHEN the downgrade is persisted
230
+ const rewritten = persistUnsendableImageDowngrades(conv.id);
231
+
232
+ // THEN the message is rewritten with the nested image swapped for a note
233
+ expect(rewritten).toBe(1);
234
+ const [content] = storedContent(conv.id);
235
+ const toolResult = content.find((b) => b.type === "tool_result");
236
+ expect(toolResult).toBeDefined();
237
+ // AND the tool_result is preserved (pairing intact) with no image left
238
+ const nested = (toolResult as { contentBlocks?: ContentBlock[] })
239
+ .contentBlocks;
240
+ expect(nested?.some((b) => b.type === "image")).toBe(false);
241
+ expect(nested?.some((b) => b.type === "text")).toBe(true);
242
+ });
243
+
244
+ /** A sendable nested screenshot is never disturbed. */
245
+ test("leaves a normally-sized tool_result image untouched", async () => {
246
+ // GIVEN a tool_result with an image well within provider limits
247
+ const conv = createConversation();
248
+ await addMessage(
249
+ conv.id,
250
+ "user",
251
+ JSON.stringify([toolResultWithImage(makePngBase64(1024, 768))]),
252
+ { skipIndexing: true },
253
+ );
254
+
255
+ // WHEN the downgrade is persisted
256
+ const rewritten = persistUnsendableImageDowngrades(conv.id);
257
+
258
+ // THEN nothing is rewritten and the nested image remains
259
+ expect(rewritten).toBe(0);
260
+ const [content] = storedContent(conv.id);
261
+ const toolResult = content.find((b) => b.type === "tool_result") as {
262
+ contentBlocks?: ContentBlock[];
263
+ };
264
+ expect(toolResult.contentBlocks?.some((b) => b.type === "image")).toBe(
265
+ true,
266
+ );
267
+ });
268
+
197
269
  /** Re-running after a rewrite is a safe no-op (no image blocks remain). */
198
270
  test("is idempotent — a second run rewrites nothing", async () => {
199
271
  // GIVEN a conversation whose oversized image has already been downgraded
@@ -213,3 +285,27 @@ describe("persistUnsendableImageDowngrades", () => {
213
285
  expect(secondRun).toBe(0);
214
286
  });
215
287
  });
288
+
289
+ describe("oversizedImageReplacement", () => {
290
+ /** A still-sendable image must be left alone — never replaced with a note.
291
+ * This is the gate that keeps the in-memory recovery from discarding valid
292
+ * screenshots when only one image in the turn was actually oversized. */
293
+ test("returns null for an image within the provider caps", () => {
294
+ const sendable = imageBlock(makePngBase64(1024, 768)) as Extract<
295
+ ContentBlock,
296
+ { type: "image" }
297
+ >;
298
+ expect(oversizedImageReplacement(sendable)).toBeNull();
299
+ });
300
+
301
+ /** An image past the provider caps that cannot be shrunk on this host (fake
302
+ * PNG that sips cannot decode) collapses to the unsendable note. */
303
+ test("returns the unsendable note when an oversized image cannot be shrunk", () => {
304
+ const oversized = imageBlock(makePngBase64(12000, 9000)) as Extract<
305
+ ContentBlock,
306
+ { type: "image" }
307
+ >;
308
+ const replacement = oversizedImageReplacement(oversized);
309
+ expect(replacement?.type).toBe("text");
310
+ });
311
+ });
@@ -34,22 +34,19 @@ describe("buildShimSource", () => {
34
34
  test("emits a globalThis trampoline + one binding per export", () => {
35
35
  const source = buildShimSource(
36
36
  ["foo", "bar"],
37
- Symbol.for("vellum.plugin-api.v1"),
37
+ Symbol.for("vellum.plugin-api"),
38
38
  );
39
39
  expect(source).toBe(
40
- `const api = globalThis[Symbol.for("vellum.plugin-api.v1")];\n` +
40
+ `const api = globalThis[Symbol.for("vellum.plugin-api")];\n` +
41
41
  `export const foo = api.foo;\n` +
42
42
  `export const bar = api.bar;\n`,
43
43
  );
44
44
  });
45
45
 
46
46
  test("handles an empty export list (today's types-only surface)", () => {
47
- const source = buildShimSource(
48
- [],
49
- Symbol.for("vellum.plugin-api.v1"),
50
- );
47
+ const source = buildShimSource([], Symbol.for("vellum.plugin-api"));
51
48
  expect(source).toBe(
52
- `const api = globalThis[Symbol.for("vellum.plugin-api.v1")];\n`,
49
+ `const api = globalThis[Symbol.for("vellum.plugin-api")];\n`,
53
50
  );
54
51
  });
55
52
  });
@@ -109,7 +106,7 @@ describe("ensurePluginApiShim", () => {
109
106
  const pluginDir = join(workspaceDir, "plugins", "fake-plugin");
110
107
  await mkdir(pluginDir, { recursive: true });
111
108
  await writeFile(
112
- join(pluginDir, "register.js"),
109
+ join(pluginDir, "probe.js"),
113
110
  `import * as api from "@vellumai/plugin-api";\nexport { api };\n`,
114
111
  );
115
112
 
@@ -118,7 +115,7 @@ describe("ensurePluginApiShim", () => {
118
115
  // → plugin-api namespace. If any link in that chain is broken, this
119
116
  // import throws.
120
117
  const mod: { api: Record<string, unknown> } = await import(
121
- join(pluginDir, "register.js")
118
+ join(pluginDir, "probe.js")
122
119
  );
123
120
  expect(mod.api).toBeDefined();
124
121
  });