@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
@@ -1,274 +0,0 @@
1
- import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
5
-
6
- import type { Provider } from "../../../providers/types.js";
7
- import { readPage, writePage } from "../../v2/page-store.js";
8
- import { type LeafRef, reconcileTree } from "../reconcile.js";
9
-
10
- /**
11
- * A canned provider whose `assign_leaves` tool-use response is computed from
12
- * the user message (the page content) via `idsForMessage`. This lets a test
13
- * deterministically re-home each page onto specific surviving leaves without a
14
- * real LLM. The leaf index in the system prompt is sorted by path; tests pass
15
- * the 1-based id of the target leaf in that sorted order.
16
- */
17
- function stubProvider(
18
- idsForMessage: (message: string, systemPrompt: string) => number[],
19
- ): Provider {
20
- return {
21
- name: "stub",
22
- async sendMessage(messages: unknown, opts: unknown) {
23
- const msg = messages as Array<{
24
- content: Array<{ type: string; text?: string }>;
25
- }>;
26
- const text = msg[0]?.content?.map((c) => c.text ?? "").join("") ?? "";
27
- const systemPrompt =
28
- (opts as { systemPrompt?: string }).systemPrompt ?? "";
29
- const ids = idsForMessage(text, systemPrompt);
30
- return {
31
- id: "msg_stub",
32
- role: "assistant",
33
- model: "stub-model",
34
- stopReason: "tool_use",
35
- content: [
36
- {
37
- type: "tool_use",
38
- id: "tool_1",
39
- name: "assign_leaves",
40
- input: { ids },
41
- },
42
- ],
43
- };
44
- },
45
- } as unknown as Provider;
46
- }
47
-
48
- let workspaceDir: string;
49
- let dataDir: string;
50
-
51
- beforeEach(async () => {
52
- workspaceDir = await mkdtemp(join(tmpdir(), "reconcile-ws-"));
53
- dataDir = await mkdtemp(join(tmpdir(), "reconcile-data-"));
54
- });
55
-
56
- afterEach(async () => {
57
- await rm(workspaceDir, { recursive: true, force: true });
58
- await rm(dataDir, { recursive: true, force: true });
59
- await rm(join(dataDir, "..", "v3-snapshots"), {
60
- recursive: true,
61
- force: true,
62
- }).catch(() => {});
63
- });
64
-
65
- /** Write a leaf `.md` with `{path, in_core, id}` frontmatter. */
66
- async function writeLeaf(
67
- path: string,
68
- opts: { id?: string; inCore?: boolean; body?: string } = {},
69
- ): Promise<void> {
70
- const full = join(dataDir, "leaves", `${path}.md`);
71
- await mkdir(join(full, ".."), { recursive: true });
72
- const lines = [`path: ${path}`, `in_core: ${opts.inCore ?? false}`];
73
- if (opts.id) lines.push(`id: ${opts.id}`);
74
- await writeFile(
75
- full,
76
- `---\n${lines.join("\n")}\n---\n${opts.body ?? `Leaf ${path}.`}\n`,
77
- );
78
- }
79
-
80
- async function writeCore(alwaysOn: string[]): Promise<void> {
81
- await writeFile(join(dataDir, "core.json"), JSON.stringify({ alwaysOn }));
82
- }
83
-
84
- /**
85
- * `loadLeafTree` (used by the re-homing path) reads `assignments.json`
86
- * unconditionally, so seed an empty one. Page→leaf membership comes from page
87
- * frontmatter, not this file.
88
- */
89
- async function seedAssignments(): Promise<void> {
90
- await writeFile(join(dataDir, "assignments.json"), "{}");
91
- }
92
-
93
- async function writePageWithLeaves(
94
- slug: string,
95
- leaves: string[],
96
- ): Promise<void> {
97
- await writePage(workspaceDir, {
98
- slug,
99
- frontmatter: { edges: [], ref_files: [], ref_urls: [], leaves },
100
- body: `Body of ${slug}.`,
101
- });
102
- }
103
-
104
- async function pageLeaves(slug: string): Promise<string[]> {
105
- const page = await readPage(workspaceDir, slug);
106
- return page?.frontmatter.leaves ?? [];
107
- }
108
-
109
- async function coreOf(): Promise<string[]> {
110
- const raw = await readFile(join(dataDir, "core.json"), "utf8");
111
- return (JSON.parse(raw) as { alwaysOn: string[] }).alwaysOn;
112
- }
113
-
114
- describe("reconcileTree", () => {
115
- test("rename preserves assignments (refs rewritten old → new)", async () => {
116
- // Current tree: the leaf moved from domain-a/topic-x → domain-b/topic-x,
117
- // keeping its stable id.
118
- await writeLeaf("domain-b/topic-x", { id: "leaf-x" });
119
- await writeCore(["domain-a/topic-x"]);
120
- await writePageWithLeaves("page-a", ["domain-a/topic-x"]);
121
-
122
- const prev: LeafRef[] = [{ id: "leaf-x", path: "domain-a/topic-x" }];
123
- const result = await reconcileTree({
124
- prevLeaves: prev,
125
- dataDir,
126
- workspaceDir,
127
- });
128
-
129
- expect(await pageLeaves("page-a")).toEqual(["domain-b/topic-x"]);
130
- expect(await coreOf()).toEqual(["domain-b/topic-x"]);
131
- expect(result.renames).toEqual([
132
- {
133
- id: "leaf-x",
134
- oldPath: "domain-a/topic-x",
135
- newPath: "domain-b/topic-x",
136
- },
137
- ]);
138
- expect(result.idToNewPath.get("leaf-x")).toBe("domain-b/topic-x");
139
- });
140
-
141
- test("delete re-homes members with no orphan", async () => {
142
- // leaf-x is deleted; leaf-y survives. page-a was only on leaf-x.
143
- await writeLeaf("domain-a/topic-y", { id: "leaf-y" });
144
- await writeCore([]);
145
- await seedAssignments();
146
- await writePageWithLeaves("page-a", ["domain-a/topic-x"]);
147
-
148
- // Surviving leaf is sorted index 1 (only leaf in tree).
149
- const provider = stubProvider(() => [1]);
150
-
151
- const prev: LeafRef[] = [
152
- { id: "leaf-x", path: "domain-a/topic-x" },
153
- { id: "leaf-y", path: "domain-a/topic-y" },
154
- ];
155
- const result = await reconcileTree({
156
- prevLeaves: prev,
157
- dataDir,
158
- workspaceDir,
159
- provider,
160
- });
161
-
162
- const after = await pageLeaves("page-a");
163
- // No orphan: at least one surviving leaf, and the dead path is gone.
164
- expect(after.length).toBeGreaterThanOrEqual(1);
165
- expect(after).not.toContain("domain-a/topic-x");
166
- expect(after).toContain("domain-a/topic-y");
167
- expect(result.deleted).toEqual(["domain-a/topic-x"]);
168
- });
169
-
170
- test("split distributes members across toPaths", async () => {
171
- // leaf-x splits into leaf-x1 (domain-a/x1) and leaf-x2 (domain-a/x2).
172
- await writeLeaf("domain-a/x1", { id: "leaf-x1" });
173
- await writeLeaf("domain-a/x2", { id: "leaf-x2" });
174
- // A third surviving leaf NOT in the split target set — members must not
175
- // land here.
176
- await writeLeaf("domain-b/other", { id: "leaf-other" });
177
- await writeCore([]);
178
- await seedAssignments();
179
- await writePageWithLeaves("page-a", ["domain-a/topic-x"]);
180
- await writePageWithLeaves("page-b", ["domain-a/topic-x"]);
181
-
182
- // Restricted tree (split.toPaths) sorts as [domain-a/x1, domain-a/x2].
183
- // page-a → x1 (id 1), page-b → x2 (id 2).
184
- const provider = stubProvider((message) =>
185
- message.includes("page-a") ? [1] : [2],
186
- );
187
-
188
- const prev: LeafRef[] = [{ id: "leaf-x", path: "domain-a/topic-x" }];
189
- await reconcileTree({
190
- prevLeaves: prev,
191
- dataDir,
192
- workspaceDir,
193
- provider,
194
- splits: [{ fromId: "leaf-x", toPaths: ["domain-a/x1", "domain-a/x2"] }],
195
- });
196
-
197
- expect(await pageLeaves("page-a")).toEqual(["domain-a/x1"]);
198
- expect(await pageLeaves("page-b")).toEqual(["domain-a/x2"]);
199
- // Neither page landed on the off-target leaf.
200
- expect(await pageLeaves("page-a")).not.toContain("domain-b/other");
201
- expect(await pageLeaves("page-b")).not.toContain("domain-b/other");
202
- });
203
-
204
- test("dangling core entry is pruned", async () => {
205
- await writeLeaf("domain-a/topic-x", { id: "leaf-x" });
206
- // core.json points at a leaf that no longer exists in the tree.
207
- await writeCore(["domain-a/topic-x", "domain-a/gone"]);
208
- await writePageWithLeaves("page-a", ["domain-a/topic-x"]);
209
-
210
- // No prev entry for the gone leaf and no page references it, so reconcile
211
- // succeeds and prunes the stale core entry.
212
- const prev: LeafRef[] = [{ id: "leaf-x", path: "domain-a/topic-x" }];
213
- const result = await reconcileTree({
214
- prevLeaves: prev,
215
- dataDir,
216
- workspaceDir,
217
- });
218
-
219
- expect(await coreOf()).toEqual(["domain-a/topic-x"]);
220
- expect(result.prunedCore).toEqual(["domain-a/gone"]);
221
- });
222
-
223
- test("out-of-band dangling page ref converges (prune pass)", async () => {
224
- // The current tree has only leaf-x. page-a references leaf-x (fine) PLUS a
225
- // residual leaf path that exists in NEITHER prev nor current — an out-of-band
226
- // ref a maintainer left when deleting a leaf file without a `prevLeaves`
227
- // entry. v1 reconcile is a convergence/prune pass: it drops the dangling ref
228
- // (the page keeps its surviving leaf, so no re-home is needed) instead of
229
- // failing closed.
230
- await writeLeaf("domain-a/topic-x", { id: "leaf-x" });
231
- await writeCore(["domain-a/topic-x"]);
232
- await seedAssignments();
233
- await writePageWithLeaves("page-a", [
234
- "domain-a/topic-x",
235
- "domain-a/residual",
236
- ]);
237
-
238
- const prev: LeafRef[] = [{ id: "leaf-x", path: "domain-a/topic-x" }];
239
- const result = await reconcileTree({
240
- prevLeaves: prev,
241
- dataDir,
242
- workspaceDir,
243
- });
244
-
245
- // Converged: the dangling residual ref is gone; the surviving leaf remains.
246
- expect(await pageLeaves("page-a")).toEqual(["domain-a/topic-x"]);
247
- expect(await coreOf()).toEqual(["domain-a/topic-x"]);
248
- // The out-of-band drop is not a reconciler-tracked delete (no prev entry),
249
- // so it is not reported in `deleted`.
250
- expect(result.deleted).toEqual([]);
251
- });
252
-
253
- test("page whose ONLY leaf dangles out-of-band is re-homed, not orphaned", async () => {
254
- // page-a's sole leaf was deleted on disk with no `prevLeaves` entry. The
255
- // convergence pass must re-home it onto a surviving leaf before dropping the
256
- // dead ref, so the page is never left with zero leaves.
257
- await writeLeaf("domain-a/topic-x", { id: "leaf-x" });
258
- await writeCore([]);
259
- await seedAssignments();
260
- // page-a points only at a leaf absent from the tree (out-of-band delete).
261
- await writePageWithLeaves("page-a", ["domain-a/gone"]);
262
-
263
- // Only surviving leaf is sorted index 1 → re-home target.
264
- const provider = stubProvider(() => [1]);
265
-
266
- const prev: LeafRef[] = [{ id: "leaf-x", path: "domain-a/topic-x" }];
267
- await reconcileTree({ prevLeaves: prev, dataDir, workspaceDir, provider });
268
-
269
- const after = await pageLeaves("page-a");
270
- expect(after).not.toContain("domain-a/gone"); // dangling ref dropped
271
- expect(after).toEqual(["domain-a/topic-x"]); // re-homed onto the survivor
272
- expect(after.length).toBeGreaterThanOrEqual(1); // never orphaned
273
- });
274
- });
@@ -1,61 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import { wrapMemoryBlock } from "../../memory-marker.js";
4
- import { renderMemoryBlock } from "../render-injection.js";
5
- import { Slug } from "../types.js";
6
-
7
- const resolver =
8
- (pages: Record<Slug, string>) =>
9
- async (slug: Slug): Promise<string> =>
10
- pages[slug] ?? "";
11
-
12
- describe("renderMemoryBlock", () => {
13
- test("wraps the selection in a single <memory> block, in order", async () => {
14
- const pages: Record<Slug, string> = {
15
- "page-a": "alpha body",
16
- "page-b": "beta body",
17
- };
18
-
19
- const block = await renderMemoryBlock(
20
- ["page-a", "page-b"],
21
- resolver(pages),
22
- );
23
-
24
- expect(block).toBe("<memory>\nalpha body\nbeta body\n</memory>");
25
- });
26
-
27
- test("empty selection renders the empty string", async () => {
28
- const block = await renderMemoryBlock([], async () => "unused");
29
- expect(block).toBe("");
30
- });
31
-
32
- test("preserves input order deterministically", async () => {
33
- const pages: Record<Slug, string> = {
34
- "page-a": "A",
35
- "page-b": "B",
36
- "page-c": "C",
37
- };
38
-
39
- const forward = await renderMemoryBlock(
40
- ["page-a", "page-b", "page-c"],
41
- resolver(pages),
42
- );
43
- const reversed = await renderMemoryBlock(
44
- ["page-c", "page-b", "page-a"],
45
- resolver(pages),
46
- );
47
-
48
- expect(forward).toBe("<memory>\nA\nB\nC\n</memory>");
49
- expect(reversed).toBe("<memory>\nC\nB\nA\n</memory>");
50
- });
51
-
52
- test("emits the shared wrapMemoryBlock marker the v2 stripper recognizes", async () => {
53
- const block = await renderMemoryBlock(
54
- ["page-a"],
55
- resolver({ "page-a": "x" }),
56
- );
57
- // Guard the v2/v3 marker contract: the rendered block must be byte-identical
58
- // to wrapping the joined content with the shared helper.
59
- expect(block).toBe(wrapMemoryBlock("x"));
60
- });
61
- });
@@ -1,332 +0,0 @@
1
- /**
2
- * Tests for `assistant/src/memory/v3/router.ts`.
3
- *
4
- * Coverage matrix:
5
- * - Returned IDs map to the right leaves by 1-based index, in model order.
6
- * - Omitted `ids` → no leaves (the router must name leaves explicitly,
7
- * never open the whole tree).
8
- * - Explicit `ids: []` → no leaves (deliberate abstention).
9
- * - Out-of-range / duplicate IDs ignored, no throw.
10
- * - No provider / missing tool_use / schema mismatch / throw → no leaves
11
- * (degrade to the deterministic lanes), the last three after a re-prompt
12
- * retry; a malformed response that recovers on retry returns its IDs.
13
- * - The rendered leaf block is byte-identical across two calls with
14
- * different queries (the cache invariant).
15
- * - The system prompt mentions "register" (locks the routing commitment).
16
- *
17
- * The provider is stubbed so no network calls fire.
18
- */
19
-
20
- import { beforeEach, describe, expect, mock, test } from "bun:test";
21
-
22
- import type {
23
- Message,
24
- Provider,
25
- ProviderResponse,
26
- SendMessageOptions,
27
- ToolUseContent,
28
- } from "../../../providers/types.js";
29
- import type { LeafNode, LeafPath, LeafTree, TurnContext } from "../types.js";
30
-
31
- // ---------------------------------------------------------------------------
32
- // Mocks installed BEFORE the router import so the module observes them at
33
- // load time.
34
- // ---------------------------------------------------------------------------
35
-
36
- let providerStub: Provider | null = null;
37
-
38
- interface ProviderCall {
39
- messages: Message[];
40
- options: SendMessageOptions | undefined;
41
- }
42
- const providerCalls: ProviderCall[] = [];
43
-
44
- mock.module("../../../providers/provider-send-message.js", () => ({
45
- getConfiguredProvider: async () => providerStub,
46
- extractToolUse: (response: ProviderResponse) =>
47
- response.content.find((b): b is ToolUseContent => b.type === "tool_use"),
48
- }));
49
-
50
- mock.module("../../../util/logger.js", () => ({
51
- getLogger: () =>
52
- new Proxy({} as Record<string, unknown>, {
53
- get: (_t, prop) => (prop === "child" ? () => ({}) : () => {}),
54
- }),
55
- }));
56
-
57
- const { routeL1, renderLeafBlock } = await import("../router.js");
58
-
59
- // ---------------------------------------------------------------------------
60
- // Helpers.
61
- // ---------------------------------------------------------------------------
62
-
63
- function makeProvider(response: ProviderResponse): Provider {
64
- return {
65
- name: "stub",
66
- sendMessage: async (messages, options) => {
67
- providerCalls.push({ messages, options });
68
- return response;
69
- },
70
- };
71
- }
72
-
73
- function toolUseResponse(input: Record<string, unknown>): ProviderResponse {
74
- return {
75
- model: "stub-model",
76
- stopReason: "tool_use",
77
- usage: { inputTokens: 0, outputTokens: 0 },
78
- content: [{ type: "tool_use", id: "tu-1", name: "open_leaves", input }],
79
- };
80
- }
81
-
82
- /** A 200 response that carries no tool_use — the malformed-but-successful case
83
- * the re-prompt retry exists to recover from. */
84
- function noToolResponse(): ProviderResponse {
85
- return {
86
- model: "stub-model",
87
- stopReason: "end_turn",
88
- usage: { inputTokens: 0, outputTokens: 0 },
89
- content: [{ type: "text", text: "no tool call" }],
90
- };
91
- }
92
-
93
- /** Provider returning a different response per call (the i-th call returns
94
- * responses[i], or the last entry once exhausted), recording each call so a
95
- * test can assert how many attempts were made. */
96
- function makeSequenceProvider(responses: ProviderResponse[]): Provider {
97
- let i = 0;
98
- return {
99
- name: "sequence",
100
- sendMessage: async (messages, options) => {
101
- providerCalls.push({ messages, options });
102
- const response = responses[Math.min(i, responses.length - 1)];
103
- i += 1;
104
- return response;
105
- },
106
- };
107
- }
108
-
109
- /** Provider that records each call and then throws — for the throw-after-retries
110
- * path (the provider's own RetryProvider has already exhausted its backoff). */
111
- function makeThrowingProvider(): Provider {
112
- return {
113
- name: "throwing",
114
- sendMessage: async (messages, options) => {
115
- providerCalls.push({ messages, options });
116
- throw new Error("boom");
117
- },
118
- };
119
- }
120
-
121
- function makeLeaf(path: LeafPath, description: string): LeafNode {
122
- return {
123
- path,
124
- frontmatter: { path, in_core: false },
125
- description,
126
- members: [],
127
- domain: path.split("/")[0],
128
- };
129
- }
130
-
131
- /** Tree whose leaves are intentionally inserted out of sorted order so the
132
- * tests verify the router sorts by path (not insertion order) before
133
- * assigning 1-based IDs. Sorted → [people/alice, people/bob, projects/atlas]. */
134
- function makeTree(): LeafTree {
135
- const entries: LeafNode[] = [
136
- makeLeaf("projects/atlas", "The Atlas project roadmap and status."),
137
- makeLeaf("people/bob", "Bob — colleague, prefers async."),
138
- makeLeaf("people/alice", "Alice — manager, weekly 1:1s."),
139
- ];
140
- return {
141
- leaves: new Map(entries.map((n) => [n.path, n])),
142
- byPage: new Map(),
143
- };
144
- }
145
-
146
- function makeTurn(currentMessage: string): TurnContext {
147
- return {
148
- conversationId: "conv-xyz",
149
- turnNumber: 1,
150
- currentMessage,
151
- recentContext: "earlier we talked about the timeline",
152
- };
153
- }
154
-
155
- beforeEach(() => {
156
- providerStub = null;
157
- providerCalls.length = 0;
158
- });
159
-
160
- // ---------------------------------------------------------------------------
161
- // Tests.
162
- // ---------------------------------------------------------------------------
163
-
164
- describe("routeL1 — id mapping", () => {
165
- test("returned IDs map to leaves by 1-based index, in model order", async () => {
166
- providerStub = makeProvider(toolUseResponse({ ids: [3, 1] }));
167
- const result = await routeL1(makeTurn("how's atlas?"), makeTree());
168
- expect(result).toEqual(["projects/atlas", "people/alice"]);
169
- });
170
-
171
- test("omitted ids opens no leaves (must name leaves explicitly)", async () => {
172
- providerStub = makeProvider(toolUseResponse({}));
173
- const result = await routeL1(makeTurn("anything"), makeTree());
174
- expect(result).toEqual([]);
175
- });
176
-
177
- test("explicit empty ids opens no leaves (abstention)", async () => {
178
- providerStub = makeProvider(toolUseResponse({ ids: [] }));
179
- const result = await routeL1(makeTurn("nothing relevant"), makeTree());
180
- expect(result).toEqual([]);
181
- });
182
-
183
- test("out-of-range and duplicate IDs are ignored without throwing", async () => {
184
- providerStub = makeProvider(toolUseResponse({ ids: [2, 99, 0, -1, 2] }));
185
- const result = await routeL1(makeTurn("bob?"), makeTree());
186
- expect(result).toEqual(["people/bob"]);
187
- });
188
-
189
- test("empty tree returns no leaves and never calls the provider", async () => {
190
- providerStub = makeProvider(toolUseResponse({ ids: [1] }));
191
- const result = await routeL1(makeTurn("hi"), {
192
- leaves: new Map(),
193
- byPage: new Map(),
194
- });
195
- expect(result).toEqual([]);
196
- expect(providerCalls).toHaveLength(0);
197
- });
198
- });
199
-
200
- describe("routeL1 — degradation on failure", () => {
201
- test("no provider → no leaves, without calling the provider", async () => {
202
- providerStub = null;
203
- const result = await routeL1(makeTurn("x"), makeTree());
204
- expect(result).toEqual([]);
205
- expect(providerCalls).toHaveLength(0);
206
- });
207
-
208
- test("missing tool_use → no leaves after retrying", async () => {
209
- providerStub = makeProvider(noToolResponse());
210
- const result = await routeL1(makeTurn("x"), makeTree());
211
- expect(result).toEqual([]);
212
- expect(providerCalls).toHaveLength(3);
213
- });
214
-
215
- test("schema mismatch → no leaves after retrying", async () => {
216
- providerStub = makeProvider(toolUseResponse({ ids: "not-an-array" }));
217
- const result = await routeL1(makeTurn("x"), makeTree());
218
- expect(result).toEqual([]);
219
- expect(providerCalls).toHaveLength(3);
220
- });
221
-
222
- test("provider throw → no leaves after retrying", async () => {
223
- providerStub = makeThrowingProvider();
224
- const result = await routeL1(makeTurn("x"), makeTree());
225
- expect(result).toEqual([]);
226
- expect(providerCalls).toHaveLength(3);
227
- });
228
-
229
- test("a malformed response that recovers on retry returns its IDs", async () => {
230
- providerStub = makeSequenceProvider([
231
- noToolResponse(),
232
- toolUseResponse({ ids: [2] }),
233
- ]);
234
- const result = await routeL1(makeTurn("bob?"), makeTree());
235
- expect(result).toEqual(["people/bob"]);
236
- expect(providerCalls).toHaveLength(2);
237
- });
238
- });
239
-
240
- describe("routeL1 — request shape", () => {
241
- test("forces tool_choice to open_leaves with the v3 call site", async () => {
242
- providerStub = makeProvider(toolUseResponse({ ids: [1] }));
243
- await routeL1(makeTurn("alice?"), makeTree());
244
-
245
- expect(providerCalls).toHaveLength(1);
246
- const [call] = providerCalls;
247
- const cfg = call.options?.config as Record<string, unknown>;
248
- expect(cfg?.callSite).toBe("memoryV3RouteL1");
249
- expect(cfg?.tool_choice).toEqual({ type: "tool", name: "open_leaves" });
250
- expect(call.options?.tools?.[0]?.name).toBe("open_leaves");
251
- });
252
-
253
- test("leaf block is the first content block with an ephemeral cache breakpoint", async () => {
254
- providerStub = makeProvider(toolUseResponse({ ids: [1] }));
255
- await routeL1(makeTurn("alice?"), makeTree());
256
-
257
- const [blockA, blockB] = providerCalls[0].messages[0].content as Array<{
258
- type: string;
259
- text: string;
260
- cache_control?: { type: string; ttl?: string };
261
- }>;
262
- expect(blockA.type).toBe("text");
263
- expect(blockA.text).toContain("<leaves>");
264
- expect(blockA.cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
265
-
266
- expect(blockB.type).toBe("text");
267
- expect(blockB.text).toContain("<current_message>alice?</current_message>");
268
- expect(blockB.text).toContain("<recent_context>");
269
- expect(blockB.cache_control).toBeUndefined();
270
- });
271
-
272
- test("situational context renders in the per-turn block when present", async () => {
273
- providerStub = makeProvider(toolUseResponse({ ids: [1] }));
274
- await routeL1(
275
- {
276
- ...makeTurn("x"),
277
- situationalContext: "Today is Saturday. Alice's anniversary is today.",
278
- },
279
- makeTree(),
280
- );
281
- const blockB = providerCalls[0].messages[0].content[1] as { text: string };
282
- expect(blockB.text).toContain(
283
- "<situation>Today is Saturday. Alice's anniversary is today.</situation>",
284
- );
285
- });
286
-
287
- test("situational context is omitted when the turn has none", async () => {
288
- providerStub = makeProvider(toolUseResponse({ ids: [1] }));
289
- await routeL1(makeTurn("x"), makeTree());
290
- const blockB = providerCalls[0].messages[0].content[1] as { text: string };
291
- expect(blockB.text).not.toContain("<situation>");
292
- });
293
-
294
- test("system prompt mentions situation (locks the routing commitment)", async () => {
295
- providerStub = makeProvider(toolUseResponse({ ids: [1] }));
296
- await routeL1(makeTurn("x"), makeTree());
297
- expect(providerCalls[0].options?.systemPrompt).toMatch(/[Ss]ituation/);
298
- });
299
-
300
- test("system prompt mentions register (locks the routing commitment)", async () => {
301
- providerStub = makeProvider(toolUseResponse({ ids: [1] }));
302
- await routeL1(makeTurn("x"), makeTree());
303
- expect(providerCalls[0].options?.systemPrompt).toMatch(/register/);
304
- });
305
- });
306
-
307
- describe("renderLeafBlock — cache invariant", () => {
308
- test("is byte-identical across two calls with different queries", async () => {
309
- const tree = makeTree();
310
- providerStub = makeProvider(toolUseResponse({ ids: [1] }));
311
-
312
- await routeL1(makeTurn("first query about alice"), tree);
313
- await routeL1(makeTurn("totally different query about atlas"), tree);
314
-
315
- const prefixA = (
316
- providerCalls[0].messages[0].content[0] as { text: string }
317
- ).text;
318
- const prefixB = (
319
- providerCalls[1].messages[0].content[0] as { text: string }
320
- ).text;
321
- expect(prefixA).toBe(prefixB);
322
- // And it equals the pure renderer's output.
323
- expect(prefixA).toBe(renderLeafBlock(tree));
324
- });
325
-
326
- test("renders leaves sorted by path with 1-based numbering", () => {
327
- const block = renderLeafBlock(makeTree());
328
- expect(block).toContain("[1] people/alice — ");
329
- expect(block).toContain("[2] people/bob — ");
330
- expect(block).toContain("[3] projects/atlas — ");
331
- });
332
- });