@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091926.ebb2d62

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 (490) 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 +273 -121
  12. package/package.json +2 -2
  13. package/scripts/sync-llm-catalog.ts +2 -0
  14. package/src/__tests__/activation-early-marking.test.ts +120 -0
  15. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  16. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  17. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  18. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  19. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  20. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  21. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  22. package/src/__tests__/agent-loop.test.ts +441 -96
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  24. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  25. package/src/__tests__/anthropic-provider.test.ts +23 -8
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  27. package/src/__tests__/app-control-flow.test.ts +1 -1
  28. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  29. package/src/__tests__/app-executors.test.ts +132 -0
  30. package/src/__tests__/approval-cascade.test.ts +6 -5
  31. package/src/__tests__/approval-routes-http.test.ts +4 -1
  32. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  33. package/src/__tests__/build-persisted-content.test.ts +75 -1
  34. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  35. package/src/__tests__/channel-approvals.test.ts +1 -1
  36. package/src/__tests__/compaction-circuit.test.ts +258 -0
  37. package/src/__tests__/compaction-direct.test.ts +146 -0
  38. package/src/__tests__/compaction-events.test.ts +7 -7
  39. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  40. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  41. package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
  42. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  43. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  44. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  45. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  46. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  47. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  48. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  49. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  50. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  51. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  52. package/src/__tests__/conversation-error.test.ts +15 -1
  53. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  54. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  55. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  56. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  57. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  58. package/src/__tests__/conversation-queue.test.ts +10 -10
  59. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  60. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  61. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  62. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  63. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  64. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  65. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  66. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  67. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  68. package/src/__tests__/conversation-undo.test.ts +2 -2
  69. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  70. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  71. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  72. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  73. package/src/__tests__/corrected-target.test.ts +93 -0
  74. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -4
  75. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  76. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  77. package/src/__tests__/db-acp-history.test.ts +101 -0
  78. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  79. package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
  80. package/src/__tests__/empty-response-hook.test.ts +1 -1
  81. package/src/__tests__/file-write-tool.test.ts +63 -0
  82. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  83. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  84. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  85. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  86. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  87. package/src/__tests__/heartbeat-service.test.ts +1 -0
  88. package/src/__tests__/history-repair-hook.test.ts +5 -1
  89. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  90. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  91. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  92. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  93. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  94. package/src/__tests__/injector-background-turn.test.ts +13 -23
  95. package/src/__tests__/injector-chain.test.ts +268 -44
  96. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  97. package/src/__tests__/injector-document-comments.test.ts +97 -114
  98. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  99. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  100. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  101. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  102. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  103. package/src/__tests__/llm-resolver.test.ts +73 -0
  104. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  105. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  106. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  107. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  108. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  109. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  110. package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
  111. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  112. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  113. package/src/__tests__/plugin-registry.test.ts +3 -49
  114. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  115. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  116. package/src/__tests__/plugin-types.test.ts +0 -70
  117. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  118. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  119. package/src/__tests__/published-app-updater.test.ts +138 -0
  120. package/src/__tests__/reaction-persistence.test.ts +1 -1
  121. package/src/__tests__/retry-thinking-adaptive-only.test.ts +245 -0
  122. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  123. package/src/__tests__/server-history-render.test.ts +39 -0
  124. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  125. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  126. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  127. package/src/__tests__/subagent-detail.test.ts +25 -7
  128. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  129. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  130. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  131. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  132. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  133. package/src/__tests__/title-generate-hook.test.ts +5 -1
  134. package/src/__tests__/tool-error-hook.test.ts +1 -1
  135. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  136. package/src/__tests__/tool-start-timestamp.test.ts +218 -0
  137. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  138. package/src/__tests__/web-fetch.test.ts +45 -0
  139. package/src/acp/__tests__/agent-process.test.ts +161 -0
  140. package/src/acp/__tests__/client-handler.test.ts +40 -0
  141. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  142. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  143. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  144. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  145. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  146. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  147. package/src/acp/agent-process.ts +61 -1
  148. package/src/acp/auto-install.test.ts +280 -0
  149. package/src/acp/auto-install.ts +232 -0
  150. package/src/acp/client-handler.ts +31 -0
  151. package/src/acp/prepare-agent-env.ts +80 -27
  152. package/src/acp/resolve-agent.test.ts +188 -28
  153. package/src/acp/resolve-agent.ts +119 -42
  154. package/src/acp/resume-hint.ts +23 -0
  155. package/src/acp/session-manager.ts +507 -73
  156. package/src/agent/compaction-circuit.ts +60 -102
  157. package/src/agent/loop.ts +403 -251
  158. package/src/api/events/tool-result.ts +9 -0
  159. package/src/api/events/tool-use-start.ts +7 -0
  160. package/src/api/responses/conversation-message.ts +72 -13
  161. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  162. package/src/approvals/guardian-request-resolvers.ts +27 -1
  163. package/src/background-wake/next-wake.ts +1 -0
  164. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  165. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  166. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  167. package/src/cli/commands/memory-v3.ts +57 -199
  168. package/src/cli/commands/plugins.ts +43 -37
  169. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  170. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  171. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  172. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  173. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  174. package/src/cli/lib/install-from-github.ts +797 -60
  175. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  176. package/src/cli/lib/plugin-details.ts +416 -0
  177. package/src/cli/lib/plugin-marketplace.ts +215 -0
  178. package/src/cli/lib/search-plugins.ts +201 -29
  179. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  180. package/src/config/acp-defaults.test.ts +10 -0
  181. package/src/config/acp-defaults.ts +9 -3
  182. package/src/config/acp-schema.ts +1 -7
  183. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  184. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  185. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  186. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  187. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  188. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  189. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  190. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  191. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  192. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  193. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  194. package/src/config/bundled-tool-registry.ts +4 -0
  195. package/src/config/call-site-defaults.ts +0 -3
  196. package/src/config/feature-flag-registry.json +10 -56
  197. package/src/config/llm-resolver.ts +39 -7
  198. package/src/config/loader.ts +5 -22
  199. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  200. package/src/config/schemas/call-site-catalog.ts +0 -21
  201. package/src/config/schemas/heartbeat.ts +9 -0
  202. package/src/config/schemas/llm.ts +17 -3
  203. package/src/config/schemas/memory-v3.ts +58 -8
  204. package/src/config/seed-inference-profiles.ts +18 -0
  205. package/src/context/compactor.ts +22 -4
  206. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  207. package/src/context/strip-injections.ts +8 -2
  208. package/src/daemon/conversation-agent-loop-handlers.ts +120 -42
  209. package/src/daemon/conversation-agent-loop.ts +308 -1297
  210. package/src/daemon/conversation-error.ts +31 -4
  211. package/src/daemon/conversation-history.ts +1 -1
  212. package/src/daemon/conversation-lifecycle.ts +11 -255
  213. package/src/daemon/conversation-media-retry.ts +19 -6
  214. package/src/daemon/conversation-messaging.ts +17 -0
  215. package/src/daemon/conversation-process.ts +22 -141
  216. package/src/daemon/conversation-queue-manager.ts +8 -0
  217. package/src/daemon/conversation-registry.ts +159 -0
  218. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  219. package/src/daemon/conversation-store.ts +9 -90
  220. package/src/daemon/conversation-surfaces.ts +165 -11
  221. package/src/daemon/conversation-workspace.ts +17 -0
  222. package/src/daemon/conversation.ts +444 -62
  223. package/src/daemon/disk-pressure-policy.ts +0 -1
  224. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  225. package/src/daemon/handlers/config-channels.ts +11 -2
  226. package/src/daemon/handlers/conversations.ts +3 -1
  227. package/src/daemon/handlers/shared.ts +9 -1
  228. package/src/daemon/handlers/skills.ts +4 -1
  229. package/src/daemon/host-app-control-proxy.ts +72 -57
  230. package/src/daemon/host-proxy-preactivation.ts +1 -3
  231. package/src/daemon/lifecycle.ts +21 -7
  232. package/src/daemon/persist-unsendable-image.ts +62 -25
  233. package/src/daemon/process-message.ts +1 -1
  234. package/src/daemon/server.ts +2 -0
  235. package/src/daemon/tool-side-effects.ts +15 -0
  236. package/src/daemon/wake-conversation-ops.ts +269 -0
  237. package/src/embedded/plugin-api.ts +2 -2
  238. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  239. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  240. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  241. package/src/heartbeat/heartbeat-service.ts +26 -0
  242. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  243. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  244. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  245. package/src/ipc/skill-routes/memory.ts +4 -2
  246. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  247. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  248. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  249. package/src/memory/activation-session-store.ts +43 -0
  250. package/src/memory/conversation-crud.ts +29 -19
  251. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  252. package/src/memory/db-init.ts +6 -0
  253. package/src/memory/embedding-backend.ts +5 -22
  254. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  255. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  256. package/src/memory/jobs-worker.ts +25 -1
  257. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  258. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  259. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  260. package/src/memory/migrations/index.ts +3 -0
  261. package/src/memory/onboarding-events-store.ts +66 -18
  262. package/src/memory/schema/acp.ts +4 -0
  263. package/src/memory/schema/infrastructure.ts +13 -0
  264. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  265. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  266. package/src/memory/v2/consolidation-job.ts +2 -63
  267. package/src/memory/v2/types.ts +5 -0
  268. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  269. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  270. package/src/notifications/access-request-copy.ts +298 -0
  271. package/src/notifications/adapters/slack.ts +3 -3
  272. package/src/notifications/adapters/telegram.ts +9 -2
  273. package/src/notifications/copy-composer.ts +49 -267
  274. package/src/notifications/decision-engine.ts +16 -35
  275. package/src/notifications/home-feed-side-effect.ts +1 -6
  276. package/src/plugin-api/constants.ts +4 -0
  277. package/src/plugin-api/index.ts +40 -11
  278. package/src/plugin-api/types.ts +108 -0
  279. package/src/plugins/defaults/compaction/compact.ts +106 -0
  280. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  281. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  282. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  283. package/src/plugins/defaults/compaction/package.json +1 -2
  284. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  285. package/src/plugins/defaults/empty-response/package.json +0 -1
  286. package/src/plugins/defaults/history-repair/package.json +0 -1
  287. package/src/plugins/defaults/index.ts +156 -26
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  290. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  291. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  292. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  293. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  297. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  298. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  299. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  300. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  301. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  302. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  303. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  304. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  306. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  307. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  308. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  309. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  310. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  311. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  312. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  313. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  314. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  315. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  316. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  317. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  318. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  319. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  320. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  321. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  322. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  323. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  324. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  325. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  326. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  327. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  328. package/src/plugins/defaults/title-generate/package.json +0 -1
  329. package/src/plugins/defaults/tool-error/package.json +0 -1
  330. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  331. package/src/plugins/external-api.ts +12 -2
  332. package/src/plugins/pipeline.ts +6 -293
  333. package/src/plugins/registry.ts +9 -37
  334. package/src/plugins/types.ts +76 -381
  335. package/src/plugins/user-loader.ts +30 -127
  336. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  337. package/src/prompts/system-prompt.ts +61 -10
  338. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  339. package/src/providers/anthropic/client.ts +9 -10
  340. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  341. package/src/providers/inference/logit-bias.ts +55 -0
  342. package/src/providers/model-catalog.ts +57 -0
  343. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  344. package/src/providers/openai/chat-completions-provider.ts +44 -1
  345. package/src/providers/retry.ts +57 -7
  346. package/src/providers/types.ts +6 -0
  347. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  348. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  349. package/src/runtime/agent-wake.ts +108 -209
  350. package/src/runtime/assistant-event-hub.ts +1 -1
  351. package/src/runtime/channel-approvals.ts +1 -1
  352. package/src/runtime/interactive-ui.ts +1 -1
  353. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  354. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  355. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  356. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  357. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  358. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  359. package/src/runtime/routes/acp-routes.test.ts +185 -32
  360. package/src/runtime/routes/acp-routes.ts +328 -30
  361. package/src/runtime/routes/app-management-routes.ts +3 -0
  362. package/src/runtime/routes/approval-routes.ts +1 -1
  363. package/src/runtime/routes/browser-routes.ts +1 -1
  364. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  365. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  366. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  367. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  368. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  369. package/src/runtime/routes/conversation-routes.ts +30 -2
  370. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  371. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  372. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  373. package/src/runtime/routes/host-cu-routes.ts +1 -1
  374. package/src/runtime/routes/identity-routes.ts +18 -3
  375. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  376. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  377. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  378. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  379. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  380. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  381. package/src/runtime/routes/playground/helpers.ts +1 -1
  382. package/src/runtime/routes/plugins-routes.ts +337 -35
  383. package/src/runtime/routes/stt-routes.ts +45 -12
  384. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  385. package/src/runtime/routes/work-items-routes.ts +2 -4
  386. package/src/runtime/routes/workspace-routes.ts +50 -15
  387. package/src/runtime/services/conversation-serializer.ts +1 -1
  388. package/src/runtime/verification-outbound-actions.ts +147 -2
  389. package/src/runtime/verification-templates.ts +29 -3
  390. package/src/services/published-app-updater.ts +30 -8
  391. package/src/signals/cancel.ts +2 -4
  392. package/src/subagent/manager.ts +21 -5
  393. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  394. package/src/telemetry/activation-funnel.ts +167 -0
  395. package/src/telemetry/types.ts +13 -0
  396. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  397. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  398. package/src/tools/acp/context.ts +20 -0
  399. package/src/tools/acp/list-agents.test.ts +9 -19
  400. package/src/tools/acp/list-agents.ts +3 -15
  401. package/src/tools/acp/spawn.test.ts +173 -202
  402. package/src/tools/acp/spawn.ts +37 -172
  403. package/src/tools/acp/steer.test.ts +105 -8
  404. package/src/tools/acp/steer.ts +48 -17
  405. package/src/tools/apps/executors.ts +166 -50
  406. package/src/tools/browser/browser-execution.ts +12 -2
  407. package/src/tools/filesystem/write.ts +34 -0
  408. package/src/tools/network/web-fetch.ts +65 -24
  409. package/src/tools/skills/load.ts +1 -1
  410. package/src/tools/subagent/spawn.ts +2 -4
  411. package/src/tools/ui-surface/definitions.ts +32 -5
  412. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  413. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  414. package/docs/plugins.md +0 -832
  415. package/examples/plugins/echo/register.ts +0 -143
  416. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  417. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  418. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  419. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  420. package/src/__tests__/pipeline-runner.test.ts +0 -554
  421. package/src/daemon/wake-target-adapter.ts +0 -253
  422. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  423. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  424. package/src/memory/v3/__tests__/core.test.ts +0 -39
  425. package/src/memory/v3/__tests__/health.test.ts +0 -219
  426. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  427. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  428. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  429. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  430. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  431. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  432. package/src/memory/v3/__tests__/router.test.ts +0 -332
  433. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  434. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  435. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  436. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  437. package/src/memory/v3/assign.ts +0 -268
  438. package/src/memory/v3/capabilities.ts +0 -124
  439. package/src/memory/v3/core.ts +0 -26
  440. package/src/memory/v3/health.ts +0 -0
  441. package/src/memory/v3/maintain-job.ts +0 -314
  442. package/src/memory/v3/needle.ts +0 -115
  443. package/src/memory/v3/orchestrate.ts +0 -126
  444. package/src/memory/v3/page-content.ts +0 -34
  445. package/src/memory/v3/provider-blocks.ts +0 -26
  446. package/src/memory/v3/reconcile.ts +0 -523
  447. package/src/memory/v3/router.ts +0 -190
  448. package/src/memory/v3/selection-log-store.ts +0 -84
  449. package/src/memory/v3/selector.ts +0 -226
  450. package/src/memory/v3/shadow-plugin.ts +0 -411
  451. package/src/memory/v3/snapshot.ts +0 -209
  452. package/src/memory/v3/tree.ts +0 -174
  453. package/src/memory/v3/types.ts +0 -59
  454. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  455. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  456. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  457. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  458. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  459. package/src/plugins/defaults/compaction/register.ts +0 -35
  460. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  461. package/src/plugins/defaults/empty-response/register.ts +0 -23
  462. package/src/plugins/defaults/history-repair/register.ts +0 -24
  463. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  464. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  465. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  466. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  467. package/src/plugins/defaults/title-generate/register.ts +0 -35
  468. package/src/plugins/defaults/tool-error/register.ts +0 -23
  469. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  470. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  471. package/src/proactive-artifact/decision.test.ts +0 -226
  472. package/src/proactive-artifact/decision.ts +0 -165
  473. package/src/proactive-artifact/index.ts +0 -7
  474. package/src/proactive-artifact/job.test.ts +0 -962
  475. package/src/proactive-artifact/job.ts +0 -372
  476. package/src/proactive-artifact/message-copy.ts +0 -58
  477. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  478. package/src/proactive-artifact/trigger-state.ts +0 -123
  479. package/src/util/map-limit.ts +0 -27
  480. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  481. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  482. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  483. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  484. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  485. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  486. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  487. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  488. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  489. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  490. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
@@ -0,0 +1,71 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ capabilityOrDiskBody,
5
+ type CapabilityResolvers,
6
+ isCapabilitySlug,
7
+ renderCapabilityContent,
8
+ } from "../capabilities.js";
9
+
10
+ describe("isCapabilitySlug", () => {
11
+ test("true for skill and CLI-command slugs, false otherwise", () => {
12
+ expect(isCapabilitySlug("skills/meet-join")).toBe(true);
13
+ expect(isCapabilitySlug("cli-commands/schedules")).toBe(true);
14
+ expect(isCapabilitySlug("relationship/vows")).toBe(false);
15
+ expect(isCapabilitySlug("some-page")).toBe(false);
16
+ });
17
+ });
18
+
19
+ describe("renderCapabilityContent", () => {
20
+ const resolvers: CapabilityResolvers = {
21
+ skill: (slug) =>
22
+ slug === "skills/foo" ? { id: "foo", content: "foo capability" } : null,
23
+ cli: (slug) =>
24
+ slug === "cli-commands/bar"
25
+ ? { id: "bar", content: "bar capability" }
26
+ : null,
27
+ };
28
+
29
+ test("renders a skill slug with a Skill header", () => {
30
+ expect(renderCapabilityContent("skills/foo", resolvers)).toBe(
31
+ "# Skill: foo\nfoo capability",
32
+ );
33
+ });
34
+
35
+ test("renders a CLI-command slug with a CLI header", () => {
36
+ expect(renderCapabilityContent("cli-commands/bar", resolvers)).toBe(
37
+ "# CLI command: bar\nbar capability",
38
+ );
39
+ });
40
+
41
+ test("degrades to '' for a capability slug the cache cannot resolve", () => {
42
+ expect(renderCapabilityContent("skills/missing", resolvers)).toBe("");
43
+ });
44
+
45
+ test("returns null for a non-capability slug so the caller reads the on-disk page", () => {
46
+ expect(renderCapabilityContent("relationship/vows", resolvers)).toBeNull();
47
+ });
48
+ });
49
+
50
+ describe("capabilityOrDiskBody", () => {
51
+ test("a capability slug routes through renderCapabilityContent and never reads disk", async () => {
52
+ let diskReads = 0;
53
+ const readDiskBody = async (): Promise<string> => {
54
+ diskReads += 1;
55
+ return "disk body — should not be read";
56
+ };
57
+ // A capability slug whose production cache has no entry degrades to "" (the
58
+ // `renderCapabilityContent` contract) rather than falling through to the
59
+ // injected disk reader, which must stay untouched.
60
+ expect(await capabilityOrDiskBody("skills/example", readDiskBody)).toBe("");
61
+ expect(diskReads).toBe(0);
62
+ });
63
+
64
+ test("a non-capability slug returns the injected disk reader's result", async () => {
65
+ const readDiskBody = async (slug: string): Promise<string> =>
66
+ `disk body for ${slug}`;
67
+ expect(await capabilityOrDiskBody("relationship/vows", readDiskBody)).toBe(
68
+ "disk body for relationship/vows",
69
+ );
70
+ });
71
+ });
@@ -0,0 +1,181 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import { makeMockLogger } from "../../../../__tests__/helpers/mock-logger.js";
4
+ import type { AssistantConfig } from "../../../../config/types.js";
5
+
6
+ mock.module("../../../../util/logger.js", () => ({
7
+ getLogger: () => makeMockLogger(),
8
+ }));
9
+
10
+ mock.module("../../../../memory/qdrant-client.js", () => ({
11
+ resolveQdrantUrl: () => "http://127.0.0.1:6333",
12
+ }));
13
+
14
+ // Stub the shared embedding backend. Records the queries it was asked to embed
15
+ // and returns one deterministic vector so `denseLane` can issue the search.
16
+ const embedState = {
17
+ calls: [] as string[][],
18
+ throws: null as Error | null,
19
+ };
20
+ mock.module("../../../../memory/embedding-backend.js", () => ({
21
+ embedWithBackend: async (_config: unknown, inputs: string[]) => {
22
+ embedState.calls.push(inputs);
23
+ if (embedState.throws) throw embedState.throws;
24
+ return {
25
+ provider: "local",
26
+ model: "test-model",
27
+ vectors: inputs.map(() => [0.1, 0.2, 0.3, 0.4]),
28
+ };
29
+ },
30
+ }));
31
+
32
+ // Mock the @qdrant/js-client-rest client. The mock records the query params and
33
+ // returns whatever section points the test programmed (in descending score
34
+ // order, matching Qdrant's behavior).
35
+ type MockPoint = {
36
+ payload: { article: string; ordinal: number };
37
+ score: number;
38
+ };
39
+
40
+ const state = {
41
+ points: [] as MockPoint[],
42
+ queryThrows: null as Error | null,
43
+ queryCalls: [] as Array<{
44
+ collection: string;
45
+ query: unknown;
46
+ limit: number;
47
+ }>,
48
+ };
49
+
50
+ class MockQdrantClient {
51
+ constructor(_opts: unknown) {}
52
+ async query(name: string, params: { query: unknown; limit: number }) {
53
+ state.queryCalls.push({
54
+ collection: name,
55
+ query: params.query,
56
+ limit: params.limit,
57
+ });
58
+ if (state.queryThrows) throw state.queryThrows;
59
+ return { points: state.points };
60
+ }
61
+ }
62
+
63
+ mock.module("@qdrant/js-client-rest", () => ({
64
+ QdrantClient: MockQdrantClient,
65
+ }));
66
+
67
+ const { denseLane, OVERSAMPLE } = await import("../dense.js");
68
+ const { SECTION_COLLECTION, _resetSectionDenseStoreForTests } =
69
+ await import("../section-dense-store.js");
70
+
71
+ const CONFIG = {
72
+ memory: { qdrant: { vectorSize: 4, onDisk: true } },
73
+ } as unknown as AssistantConfig;
74
+
75
+ function point(article: string, ordinal: number, score: number): MockPoint {
76
+ return { payload: { article, ordinal }, score };
77
+ }
78
+
79
+ function resetState(): void {
80
+ embedState.calls.length = 0;
81
+ embedState.throws = null;
82
+ state.points = [];
83
+ state.queryThrows = null;
84
+ state.queryCalls.length = 0;
85
+ _resetSectionDenseStoreForTests();
86
+ }
87
+
88
+ describe("memory v3 dense lane", () => {
89
+ beforeEach(resetState);
90
+ afterEach(resetState);
91
+
92
+ test("targets the section collection at k * OVERSAMPLE depth", async () => {
93
+ state.points = [point("page-a", 0, 0.9)];
94
+
95
+ await denseLane(CONFIG, "some query", 5);
96
+
97
+ // Embedded the query once.
98
+ expect(embedState.calls).toEqual([["some query"]]);
99
+ // Oversampled the section search so dedupe still yields k articles.
100
+ expect(state.queryCalls).toHaveLength(1);
101
+ expect(state.queryCalls[0]!.collection).toBe(SECTION_COLLECTION);
102
+ expect(state.queryCalls[0]!.limit).toBe(5 * OVERSAMPLE);
103
+ expect(state.queryCalls[0]!.query).toEqual([0.1, 0.2, 0.3, 0.4]);
104
+ });
105
+
106
+ test("dedupes section points to distinct articles with their best section", async () => {
107
+ // page-a's best section is ordinal 2 (highest score); the later page-a
108
+ // section is ignored once the article is already represented.
109
+ state.points = [
110
+ point("page-a", 2, 0.95),
111
+ point("topic-x", 0, 0.9),
112
+ point("page-a", 1, 0.5),
113
+ ];
114
+
115
+ const hits = await denseLane(CONFIG, "query", 5);
116
+
117
+ expect(hits).toEqual([
118
+ { article: "page-a", section: 2 },
119
+ { article: "topic-x", section: 0 },
120
+ ]);
121
+ });
122
+
123
+ test("oversamples then truncates to k distinct articles", async () => {
124
+ state.points = [
125
+ point("page-a", 0, 0.99),
126
+ point("page-b", 0, 0.98),
127
+ point("page-c", 0, 0.97),
128
+ point("page-d", 0, 0.96),
129
+ ];
130
+
131
+ const hits = await denseLane(CONFIG, "query", 2);
132
+
133
+ expect(hits).toEqual([
134
+ { article: "page-a", section: 0 },
135
+ { article: "page-b", section: 0 },
136
+ ]);
137
+ });
138
+
139
+ test("ignores malformed payloads", async () => {
140
+ state.points = [
141
+ {
142
+ payload: {
143
+ article: 123,
144
+ ordinal: 0,
145
+ } as unknown as MockPoint["payload"],
146
+ score: 0.9,
147
+ },
148
+ point("topic-x", 1, 0.8),
149
+ ];
150
+
151
+ const hits = await denseLane(CONFIG, "query", 5);
152
+
153
+ expect(hits).toEqual([{ article: "topic-x", section: 1 }]);
154
+ });
155
+
156
+ test("a thrown Qdrant search degrades to []", async () => {
157
+ state.queryThrows = new Error("qdrant unreachable");
158
+
159
+ const hits = await denseLane(CONFIG, "query", 5);
160
+
161
+ expect(hits).toEqual([]);
162
+ });
163
+
164
+ test("a failed embedding degrades to []", async () => {
165
+ embedState.throws = new Error("embed backend down");
166
+
167
+ const hits = await denseLane(CONFIG, "query", 5);
168
+
169
+ expect(hits).toEqual([]);
170
+ // No Qdrant round-trip when embedding fails.
171
+ expect(state.queryCalls).toHaveLength(0);
172
+ });
173
+
174
+ test("non-positive k short-circuits without a search", async () => {
175
+ const hits = await denseLane(CONFIG, "query", 0);
176
+
177
+ expect(hits).toEqual([]);
178
+ expect(embedState.calls).toEqual([]);
179
+ expect(state.queryCalls).toHaveLength(0);
180
+ });
181
+ });
@@ -0,0 +1,247 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import type { PageIndexEntry } from "../../../../memory/v2/page-index.js";
4
+ import { buildEdgeGraph, edgeExpand } from "../edge.js";
5
+ import type { Slug } from "../types.js";
6
+
7
+ /** Minimal page-index entry factory — only the fields the edge lane reads. */
8
+ function entry(id: number, slug: Slug, edges: number[] = []): PageIndexEntry {
9
+ return { id, slug, summary: "", edges, leaves: [], modifiedAt: 0 };
10
+ }
11
+
12
+ /** Build a raw-page reader from a fixture map; missing slugs read as "". */
13
+ function rawReader(pages: Record<string, string>) {
14
+ return async (slug: Slug): Promise<string> => pages[slug] ?? "";
15
+ }
16
+
17
+ /** Frontmatter helper: wrap a `links:` list around an optional body. */
18
+ function withLinks(links: string[], body = "body"): string {
19
+ const block = links.map((l) => ` - "${l}"`).join("\n");
20
+ return `---\ntitle: x\nlinks:\n${block}\n---\n\n${body}`;
21
+ }
22
+
23
+ describe("buildEdgeGraph — links: frontmatter", () => {
24
+ test("parses target + description split on the first ' — '", async () => {
25
+ const articles = [entry(1, "page-a"), entry(2, "page-b")];
26
+ const graph = await buildEdgeGraph(
27
+ articles,
28
+ rawReader({
29
+ "page-a": withLinks(["page-b — the b page — with an extra dash"]),
30
+ "page-b": "no frontmatter",
31
+ }),
32
+ );
33
+ const out = graph.adjacency.get("page-a")!;
34
+ // Split on the FIRST ' — ' only: description keeps the trailing dash text.
35
+ expect(out.get("page-b")).toBe("the b page — with an extra dash");
36
+ });
37
+
38
+ test("drops unknown/dangling link targets", async () => {
39
+ const articles = [entry(1, "page-a"), entry(2, "page-b")];
40
+ const graph = await buildEdgeGraph(
41
+ articles,
42
+ rawReader({
43
+ "page-a": withLinks([
44
+ "page-b — known target",
45
+ "page-ghost — dangling target not in corpus",
46
+ ]),
47
+ }),
48
+ );
49
+ const out = graph.adjacency.get("page-a")!;
50
+ expect(out.has("page-b")).toBe(true);
51
+ expect(out.has("page-ghost")).toBe(false);
52
+ expect([...out.keys()]).toEqual(["page-b"]);
53
+ });
54
+
55
+ test("a page with no links: falls back to wikilinks and numeric edges", async () => {
56
+ const articles = [
57
+ entry(1, "page-a", [2]), // numeric edge → page-b
58
+ entry(2, "page-b"),
59
+ entry(3, "topic-x"),
60
+ ];
61
+ const graph = await buildEdgeGraph(
62
+ articles,
63
+ rawReader({
64
+ // No links: frontmatter; body has an inline wikilink to topic-x.
65
+ "page-a": "plain body referencing [[topic-x]] inline",
66
+ }),
67
+ );
68
+ const out = graph.adjacency.get("page-a")!;
69
+ expect(out.has("page-b")).toBe(true); // from numeric edge
70
+ expect(out.has("topic-x")).toBe(true); // from wikilink
71
+ // No curated descriptions on wikilink/numeric edges.
72
+ expect(out.get("page-b")).toBeUndefined();
73
+ expect(out.get("topic-x")).toBeUndefined();
74
+ });
75
+ });
76
+
77
+ describe("buildEdgeGraph — three-source union", () => {
78
+ test("links ∪ wikilinks ∪ numeric edges merge into one adjacency map", async () => {
79
+ const articles = [
80
+ entry(1, "page-a", [4]), // numeric edge → topic-y
81
+ entry(2, "page-b"),
82
+ entry(3, "topic-x"),
83
+ entry(4, "topic-y"),
84
+ ];
85
+ const graph = await buildEdgeGraph(
86
+ articles,
87
+ rawReader({
88
+ "page-a": withLinks(
89
+ ["page-b — authored neighbour"],
90
+ "and a [[topic-x]] wikilink in the body",
91
+ ),
92
+ }),
93
+ );
94
+ const out = graph.adjacency.get("page-a")!;
95
+ expect(new Set(out.keys())).toEqual(
96
+ new Set(["page-b", "topic-x", "topic-y"]),
97
+ );
98
+ expect(out.get("page-b")).toBe("authored neighbour"); // from links
99
+ expect(out.get("topic-x")).toBeUndefined(); // from wikilink
100
+ expect(out.get("topic-y")).toBeUndefined(); // from numeric edge
101
+ });
102
+
103
+ test("read failure still keeps numeric-edge fallback", async () => {
104
+ const articles = [entry(1, "page-a", [2]), entry(2, "page-b")];
105
+ const graph = await buildEdgeGraph(articles, async () => {
106
+ throw new Error("read failed");
107
+ });
108
+ const out = graph.adjacency.get("page-a")!;
109
+ expect(out.has("page-b")).toBe(true);
110
+ });
111
+ });
112
+
113
+ describe("buildEdgeGraph — hubs", () => {
114
+ test("marks articles with in-degree > hubDegree as hubs", async () => {
115
+ // hub receives 3 inbound edges; with hubDegree=2 it is a hub.
116
+ const articles = [
117
+ entry(1, "page-a"),
118
+ entry(2, "page-b"),
119
+ entry(3, "page-c"),
120
+ entry(4, "hub"),
121
+ ];
122
+ const graph = await buildEdgeGraph(
123
+ articles,
124
+ rawReader({
125
+ "page-a": withLinks(["hub — to hub"]),
126
+ "page-b": withLinks(["hub — to hub"]),
127
+ "page-c": withLinks(["hub — to hub"]),
128
+ }),
129
+ { hubDegree: 2 },
130
+ );
131
+ expect(graph.hubs.has("hub")).toBe(true);
132
+ expect(graph.hubs.has("page-a")).toBe(false);
133
+ });
134
+ });
135
+
136
+ describe("edgeExpand", () => {
137
+ /** Build a star/chain graph: page-a → b,c,d,e,f,g ; topic-x → b. */
138
+ async function fixtureGraph(hubDegree = 30) {
139
+ const articles = [
140
+ entry(1, "page-a"),
141
+ entry(2, "page-b"),
142
+ entry(3, "page-c"),
143
+ entry(4, "page-d"),
144
+ entry(5, "page-e"),
145
+ entry(6, "page-f"),
146
+ entry(7, "page-g"),
147
+ entry(8, "topic-x"),
148
+ ];
149
+ return buildEdgeGraph(
150
+ articles,
151
+ rawReader({
152
+ "page-a": withLinks([
153
+ "page-b — neighbour b",
154
+ "page-c — neighbour c",
155
+ "page-d — neighbour d",
156
+ "page-e — neighbour e",
157
+ "page-f — neighbour f",
158
+ "page-g — neighbour g",
159
+ ]),
160
+ "topic-x": withLinks(["page-b — also to b"]),
161
+ }),
162
+ { hubDegree },
163
+ );
164
+ }
165
+
166
+ test("carries the curated links description through expansion", async () => {
167
+ const graph = await fixtureGraph();
168
+ const out = edgeExpand(graph, ["page-a"]);
169
+ const b = out.find((n) => n.article === "page-b");
170
+ expect(b?.description).toBe("neighbour b");
171
+ });
172
+
173
+ test("respects perSeed", async () => {
174
+ const graph = await fixtureGraph();
175
+ const out = edgeExpand(graph, ["page-a"], { perSeed: 2 });
176
+ expect(out).toHaveLength(2);
177
+ expect(out.map((n) => n.article)).toEqual(["page-b", "page-c"]);
178
+ });
179
+
180
+ test("respects cap across seeds", async () => {
181
+ const graph = await fixtureGraph();
182
+ // Two seeds each could add many; cap=3 truncates the total.
183
+ const out = edgeExpand(graph, ["page-a", "topic-x"], {
184
+ perSeed: 6,
185
+ cap: 3,
186
+ });
187
+ expect(out).toHaveLength(3);
188
+ });
189
+
190
+ test("respects seedCount (only top-N seeds expanded)", async () => {
191
+ // seedCount=1: only the first seed expands; the second seed's unique
192
+ // neighbour is never reached.
193
+ const articles = [
194
+ entry(1, "seed-1"),
195
+ entry(2, "seed-2"),
196
+ entry(3, "only-via-2"),
197
+ entry(4, "via-1"),
198
+ ];
199
+ const g = await buildEdgeGraph(
200
+ articles,
201
+ rawReader({
202
+ "seed-1": withLinks(["via-1 — x"]),
203
+ "seed-2": withLinks(["only-via-2 — y"]),
204
+ }),
205
+ );
206
+ const out = edgeExpand(g, ["seed-1", "seed-2"], { seedCount: 1 });
207
+ expect(out.map((n) => n.article)).toEqual(["via-1"]);
208
+ expect(out.find((n) => n.article === "only-via-2")).toBeUndefined();
209
+ });
210
+
211
+ test("excludes hub neighbours from expansion", async () => {
212
+ // page-b has in-degree 2 (page-a + topic-x). At the default threshold it
213
+ // is not a hub; lowering hubDegree below 2 makes it one.
214
+ const nonHubGraph = await fixtureGraph();
215
+ expect(nonHubGraph.hubs.has("page-b")).toBe(false);
216
+
217
+ const hubGraph = await fixtureGraph(1); // page-b in-degree 2 > 1 → hub
218
+ expect(hubGraph.hubs.has("page-b")).toBe(true);
219
+ const out = edgeExpand(hubGraph, ["page-a"]);
220
+ expect(out.find((n) => n.article === "page-b")).toBeUndefined();
221
+ // non-hub neighbours still surface
222
+ expect(out.find((n) => n.article === "page-c")).toBeDefined();
223
+ });
224
+
225
+ test("respects the alive predicate", async () => {
226
+ const graph = await fixtureGraph();
227
+ const out = edgeExpand(graph, ["page-a"], {
228
+ alive: (slug) => slug !== "page-c",
229
+ });
230
+ expect(out.find((n) => n.article === "page-c")).toBeUndefined();
231
+ expect(out.find((n) => n.article === "page-b")).toBeDefined();
232
+ });
233
+
234
+ test("does not surface the seeds themselves", async () => {
235
+ const articles = [entry(1, "page-a"), entry(2, "page-b")];
236
+ const graph = await buildEdgeGraph(
237
+ articles,
238
+ rawReader({
239
+ "page-a": withLinks(["page-b — to b"]),
240
+ "page-b": withLinks(["page-a — back to a"]),
241
+ }),
242
+ );
243
+ const out = edgeExpand(graph, ["page-a", "page-b"]);
244
+ // page-a and page-b are both seeds; neither should be surfaced.
245
+ expect(out).toHaveLength(0);
246
+ });
247
+ });