@vellumai/assistant 0.8.1 → 0.8.3

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 (630) hide show
  1. package/ARCHITECTURE.md +13 -19
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +17 -0
  5. package/docker-init-apt-root.sh +167 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +642 -5
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
  21. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  23. package/src/__tests__/anthropic-provider.test.ts +45 -0
  24. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  25. package/src/__tests__/app-executors.test.ts +220 -4
  26. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  27. package/src/__tests__/bundled-asset.test.ts +6 -6
  28. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  29. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  30. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  31. package/src/__tests__/clawhub.test.ts +75 -16
  32. package/src/__tests__/compactor-tail-resolution.test.ts +147 -0
  33. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  34. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  35. package/src/__tests__/config-schema.test.ts +21 -0
  36. package/src/__tests__/config-set-route.test.ts +80 -0
  37. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  38. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  39. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  40. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  41. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  42. package/src/__tests__/context-token-estimator.test.ts +31 -65
  43. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  44. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  45. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  46. package/src/__tests__/conversation-agent-loop.test.ts +59 -1
  47. package/src/__tests__/conversation-error.test.ts +42 -3
  48. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  49. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  50. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  51. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  52. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  53. package/src/__tests__/conversation-pairing.test.ts +54 -0
  54. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  55. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  56. package/src/__tests__/conversation-queue.test.ts +4 -1
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +102 -13
  58. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  59. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  60. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  61. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  64. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  65. package/src/__tests__/date-context.test.ts +45 -0
  66. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  67. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  68. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  69. package/src/__tests__/dm-backfill.test.ts +121 -10
  70. package/src/__tests__/document-tool-security.test.ts +258 -0
  71. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  72. package/src/__tests__/edit-propagation.test.ts +33 -0
  73. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  74. package/src/__tests__/external-plugin-loader.test.ts +151 -55
  75. package/src/__tests__/filing-service.test.ts +140 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  77. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  78. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  79. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  80. package/src/__tests__/heartbeat-service.test.ts +24 -164
  81. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  82. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  83. package/src/__tests__/helpers/wait-for.ts +21 -0
  84. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  85. package/src/__tests__/history-repair.test.ts +73 -0
  86. package/src/__tests__/host-app-control-proxy.test.ts +507 -10
  87. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  88. package/src/__tests__/image-credentials.test.ts +1 -1
  89. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  90. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  91. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  92. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  93. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  94. package/src/__tests__/injector-background-turn.test.ts +153 -0
  95. package/src/__tests__/injector-chain.test.ts +15 -8
  96. package/src/__tests__/install-skill-routing.test.ts +155 -37
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -3
  98. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  99. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  100. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  101. package/src/__tests__/llm-catalog-parity.test.ts +58 -13
  102. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  103. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  104. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
  105. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  106. package/src/__tests__/llm-resolver.test.ts +255 -2
  107. package/src/__tests__/llm-usage-store.test.ts +114 -0
  108. package/src/__tests__/managed-profile-guard.test.ts +41 -29
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  110. package/src/__tests__/managed-store.test.ts +84 -192
  111. package/src/__tests__/media-generate-image.test.ts +1 -1
  112. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  113. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  114. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  115. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  116. package/src/__tests__/notification-deep-link.test.ts +15 -0
  117. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  118. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  119. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  120. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  121. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  122. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  123. package/src/__tests__/openai-provider.test.ts +242 -3
  124. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  125. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  126. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  127. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  128. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  129. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +7 -2
  130. package/src/__tests__/platform.test.ts +2 -0
  131. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  132. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  133. package/src/__tests__/plugin-external-api.test.ts +68 -0
  134. package/src/__tests__/plugin-registry.test.ts +0 -77
  135. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  136. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  137. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  138. package/src/__tests__/plugin-types.test.ts +3 -13
  139. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  140. package/src/__tests__/process-message-display-content.test.ts +421 -0
  141. package/src/__tests__/provider-catalog-visibility.test.ts +158 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  143. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +33 -31
  144. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  145. package/src/__tests__/schedule-routes.test.ts +50 -3
  146. package/src/__tests__/schedule-store.test.ts +94 -0
  147. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  148. package/src/__tests__/schema-transforms.test.ts +20 -0
  149. package/src/__tests__/search-skills-unified.test.ts +0 -5
  150. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
  151. package/src/__tests__/server-history-render.test.ts +43 -0
  152. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  153. package/src/__tests__/skill-load-tool.test.ts +27 -89
  154. package/src/__tests__/skill-memory.test.ts +23 -3
  155. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  156. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  157. package/src/__tests__/skills-install-extract.test.ts +49 -38
  158. package/src/__tests__/skills-install-staging.test.ts +159 -0
  159. package/src/__tests__/skills-uninstall.test.ts +9 -41
  160. package/src/__tests__/skills.test.ts +51 -58
  161. package/src/__tests__/slack-channel-config.test.ts +9 -0
  162. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  163. package/src/__tests__/system-prompt.test.ts +670 -63
  164. package/src/__tests__/terminal-tools.test.ts +28 -1
  165. package/src/__tests__/thread-backfill.test.ts +557 -27
  166. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  167. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  168. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  169. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  170. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  171. package/src/__tests__/tool-executor.test.ts +16 -4
  172. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  173. package/src/__tests__/turn-events-store.test.ts +256 -0
  174. package/src/__tests__/twilio-routes.test.ts +4 -0
  175. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  176. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  177. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  178. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  179. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  180. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  181. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  182. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  183. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  184. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  185. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  186. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  187. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  188. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  189. package/src/a2a/__tests__/task-store.test.ts +246 -0
  190. package/src/a2a/agent-card.ts +58 -0
  191. package/src/a2a/feature-gate.ts +8 -0
  192. package/src/a2a/protocol-constants.ts +21 -0
  193. package/src/a2a/protocol-errors.ts +50 -0
  194. package/src/a2a/protocol-types.ts +162 -0
  195. package/src/a2a/task-store.ts +168 -0
  196. package/src/acp/resolve-agent.ts +1 -1
  197. package/src/agent/image-optimize.ts +13 -5
  198. package/src/agent/loop.ts +167 -18
  199. package/src/calls/voice-session-bridge.ts +61 -42
  200. package/src/channels/config.ts +9 -0
  201. package/src/channels/types.ts +122 -0
  202. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  203. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  204. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  205. package/src/cli/commands/__tests__/schedules.test.ts +960 -0
  206. package/src/cli/commands/changelog.ts +106 -42
  207. package/src/cli/commands/conversations.ts +102 -17
  208. package/src/cli/commands/default-action.ts +10 -53
  209. package/src/cli/commands/notifications.ts +388 -346
  210. package/src/cli/commands/plugins.ts +252 -0
  211. package/src/cli/commands/schedules.ts +683 -0
  212. package/src/cli/commands/telemetry.ts +40 -0
  213. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  214. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  215. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  216. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  217. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  218. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  219. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  220. package/src/cli/lib/cli-colors.ts +12 -0
  221. package/src/cli/lib/confirm-prompt.ts +79 -0
  222. package/src/cli/lib/install-from-github.ts +303 -0
  223. package/src/cli/lib/list-installed-plugins.ts +137 -0
  224. package/src/cli/lib/search-plugins.ts +163 -0
  225. package/src/cli/lib/uninstall-plugin.ts +82 -0
  226. package/src/cli/lib/unknown-command.ts +111 -0
  227. package/src/cli/program.ts +52 -2
  228. package/src/config/assistant-feature-flags.ts +24 -54
  229. package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
  230. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  231. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  232. package/src/config/bundled-skills/document/SKILL.md +23 -3
  233. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  234. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  235. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  236. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  237. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  238. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  239. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  240. package/src/config/bundled-tool-registry.ts +6 -0
  241. package/src/config/call-site-defaults.ts +105 -0
  242. package/src/config/feature-flag-registry.json +41 -9
  243. package/src/config/llm-resolver.ts +52 -1
  244. package/src/config/loader.ts +64 -38
  245. package/src/config/schema.ts +9 -10
  246. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  247. package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
  248. package/src/config/schemas/channels.ts +17 -0
  249. package/src/config/schemas/compaction.ts +28 -0
  250. package/src/config/schemas/conversations.ts +10 -0
  251. package/src/config/schemas/heartbeat.ts +23 -0
  252. package/src/config/schemas/llm-request-logs.ts +31 -7
  253. package/src/config/schemas/llm.ts +1 -0
  254. package/src/config/schemas/memory-retrieval.ts +18 -0
  255. package/src/config/schemas/memory-retrospective.ts +1 -1
  256. package/src/config/schemas/memory-v2.ts +4 -4
  257. package/src/config/schemas/memory.ts +3 -1
  258. package/src/config/schemas/tools.ts +14 -0
  259. package/src/config/seed-inference-profiles.ts +99 -29
  260. package/src/config/skills.ts +3 -96
  261. package/src/context/compactor.ts +1107 -0
  262. package/src/context/token-estimator.ts +34 -36
  263. package/src/context/window-manager.ts +197 -1520
  264. package/src/credential-execution/managed-catalog.ts +37 -0
  265. package/src/credential-health/credential-health-service.ts +280 -19
  266. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +33 -18
  267. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  268. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  269. package/src/daemon/approval-generators.ts +8 -6
  270. package/src/daemon/config-watcher.ts +94 -31
  271. package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
  272. package/src/daemon/conversation-agent-loop.ts +198 -11
  273. package/src/daemon/conversation-error.ts +171 -37
  274. package/src/daemon/conversation-lifecycle.ts +53 -40
  275. package/src/daemon/conversation-messaging.ts +25 -6
  276. package/src/daemon/conversation-process.ts +49 -12
  277. package/src/daemon/conversation-runtime-assembly.ts +25 -1
  278. package/src/daemon/conversation-slash.ts +12 -5
  279. package/src/daemon/conversation-store.ts +11 -4
  280. package/src/daemon/conversation-tool-setup.ts +39 -7
  281. package/src/daemon/conversation.ts +33 -8
  282. package/src/daemon/date-context.ts +40 -0
  283. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  284. package/src/daemon/first-greeting.ts +22 -2
  285. package/src/daemon/guardian-action-generators.ts +1 -125
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  287. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  288. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  289. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  290. package/src/daemon/handlers/config-a2a.ts +289 -0
  291. package/src/daemon/handlers/config-model.ts +6 -5
  292. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  293. package/src/daemon/handlers/conversations.ts +1 -0
  294. package/src/daemon/handlers/shared.ts +14 -5
  295. package/src/daemon/handlers/skills.ts +111 -108
  296. package/src/daemon/history-repair.ts +28 -1
  297. package/src/daemon/host-app-control-proxy.ts +153 -27
  298. package/src/daemon/host-proxy-preactivation.ts +85 -18
  299. package/src/daemon/lifecycle.ts +89 -91
  300. package/src/daemon/meet-host-supervisor.ts +5 -4
  301. package/src/daemon/memory-v2-startup.ts +85 -0
  302. package/src/daemon/message-protocol.ts +1 -0
  303. package/src/daemon/message-types/conversations.ts +25 -0
  304. package/src/daemon/message-types/messages.ts +61 -0
  305. package/src/daemon/message-types/notifications.ts +21 -0
  306. package/src/daemon/message-types/subagents.ts +1 -0
  307. package/src/daemon/message-types/sync.ts +1 -0
  308. package/src/daemon/pkb-reminder-builder.test.ts +11 -54
  309. package/src/daemon/pkb-reminder-builder.ts +5 -20
  310. package/src/daemon/plugin-source-watcher.ts +146 -0
  311. package/src/daemon/process-message.ts +24 -3
  312. package/src/daemon/server.ts +11 -2
  313. package/src/daemon/skill-memory-refresh.ts +33 -0
  314. package/src/daemon/wake-target-adapter.ts +2 -0
  315. package/src/documents/document-store.ts +221 -3
  316. package/src/embedded/plugin-api.ts +40 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  318. package/src/export/transcript-formatter.ts +54 -20
  319. package/src/filing/filing-service.ts +39 -0
  320. package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
  321. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  322. package/src/heartbeat/heartbeat-service.ts +73 -189
  323. package/src/home/__tests__/feed-types.test.ts +80 -0
  324. package/src/home/feed-types.ts +36 -2
  325. package/src/home/post-connect-feed.ts +1 -0
  326. package/src/index.ts +18 -1
  327. package/src/ipc/cli-client.ts +147 -45
  328. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  329. package/src/mcp/client.ts +20 -4
  330. package/src/media/image-credentials.ts +3 -3
  331. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  332. package/src/memory/__tests__/conversation-queries.test.ts +483 -0
  333. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  334. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  335. package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
  336. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  337. package/src/memory/__tests__/message-content.test.ts +35 -0
  338. package/src/memory/bookmark-crud.ts +42 -10
  339. package/src/memory/context-search/sources/conversations.ts +62 -2
  340. package/src/memory/context-search/sources/workspace.ts +4 -0
  341. package/src/memory/conversation-crud.ts +63 -19
  342. package/src/memory/conversation-queries.ts +197 -11
  343. package/src/memory/conversation-title-service.ts +26 -4
  344. package/src/memory/db-init.ts +12 -0
  345. package/src/memory/delivery-crud.ts +152 -5
  346. package/src/memory/embedding-backend.ts +4 -4
  347. package/src/memory/external-conversation-store.ts +66 -5
  348. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +150 -12
  349. package/src/memory/graph/conversation-graph-memory.ts +49 -21
  350. package/src/memory/graph/tools.ts +9 -40
  351. package/src/memory/indexer.ts +34 -29
  352. package/src/memory/invite-store.ts +53 -0
  353. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  354. package/src/memory/jobs/embed-concept-page.ts +20 -11
  355. package/src/memory/jobs-worker.ts +6 -1
  356. package/src/memory/llm-request-log-source-clickhouse.ts +24 -12
  357. package/src/memory/llm-request-log-source.ts +19 -52
  358. package/src/memory/llm-request-log-store.ts +92 -1
  359. package/src/memory/llm-usage-store.ts +125 -5
  360. package/src/memory/memory-retrospective-enqueue.ts +1 -20
  361. package/src/memory/memory-retrospective-job.ts +33 -6
  362. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  363. package/src/memory/message-content.ts +1 -1
  364. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  365. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  366. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  367. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  368. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  369. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  370. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  371. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  372. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  373. package/src/memory/migrations/index.ts +9 -0
  374. package/src/memory/migrations/registry.ts +16 -0
  375. package/src/memory/onboarding-events-store.ts +106 -0
  376. package/src/memory/schema/a2a.ts +15 -0
  377. package/src/memory/schema/bookmarks.ts +0 -2
  378. package/src/memory/schema/calls.ts +1 -0
  379. package/src/memory/schema/index.ts +1 -0
  380. package/src/memory/schema/inference.ts +3 -3
  381. package/src/memory/schema/infrastructure.ts +13 -0
  382. package/src/memory/turn-events-store.ts +127 -2
  383. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  384. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  385. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  386. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  387. package/src/memory/v2/__tests__/injection.test.ts +288 -11
  388. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  389. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  390. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  391. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  392. package/src/memory/v2/__tests__/router.test.ts +15 -0
  393. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  394. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  395. package/src/memory/v2/activation-store.ts +14 -16
  396. package/src/memory/v2/cli-command-content.ts +19 -0
  397. package/src/memory/v2/cli-command-store.ts +304 -0
  398. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  399. package/src/memory/v2/injection.ts +81 -26
  400. package/src/memory/v2/migration.ts +49 -19
  401. package/src/memory/v2/page-index.ts +63 -8
  402. package/src/memory/v2/prompts/router.ts +11 -8
  403. package/src/memory/v2/prompts/sweep.ts +2 -2
  404. package/src/memory/v2/qdrant.ts +135 -7
  405. package/src/memory/v2/router.ts +9 -8
  406. package/src/memory/v2/skill-store.ts +120 -35
  407. package/src/memory/v2/static-context.ts +4 -4
  408. package/src/memory/v2/types.ts +23 -0
  409. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  410. package/src/messaging/providers/a2a/deliver.ts +156 -0
  411. package/src/messaging/providers/gmail/client.ts +9 -2
  412. package/src/messaging/providers/index.ts +11 -2
  413. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  414. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  415. package/src/messaging/providers/slack/adapter.ts +43 -5
  416. package/src/messaging/providers/slack/client.ts +27 -0
  417. package/src/messaging/providers/slack/deep-link.ts +65 -0
  418. package/src/messaging/providers/slack/download.ts +104 -0
  419. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  420. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  421. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  422. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  423. package/src/messaging/providers/slack/types.ts +20 -1
  424. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  425. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  426. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  427. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  428. package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
  429. package/src/notifications/adapters/macos.ts +12 -2
  430. package/src/notifications/broadcaster.ts +29 -4
  431. package/src/notifications/conversation-pairing.ts +2 -1
  432. package/src/notifications/copy-composer.ts +17 -64
  433. package/src/notifications/decision-engine.ts +113 -45
  434. package/src/notifications/deterministic-checks.ts +96 -0
  435. package/src/notifications/emit-signal.ts +21 -1
  436. package/src/notifications/home-feed-side-effect.ts +138 -5
  437. package/src/notifications/signal.ts +3 -5
  438. package/src/notifications/types.ts +8 -0
  439. package/src/oauth/connection-resolver.ts +8 -4
  440. package/src/oauth/platform-connection.test.ts +43 -3
  441. package/src/oauth/platform-connection.ts +19 -6
  442. package/src/oauth/seed-providers.ts +10 -1
  443. package/src/permissions/checker.ts +2 -0
  444. package/src/permissions/ipc-risk-types.ts +1 -0
  445. package/src/permissions/question-prompter.test.ts +416 -0
  446. package/src/permissions/question-prompter.ts +294 -0
  447. package/src/platform/client.test.ts +1 -1
  448. package/src/platform/client.ts +1 -1
  449. package/src/plugin-api/constants.ts +26 -0
  450. package/src/plugin-api/index.ts +34 -1
  451. package/src/plugin-api/types.ts +104 -22
  452. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  453. package/src/plugins/defaults/compaction.ts +0 -4
  454. package/src/plugins/defaults/empty-response.ts +0 -2
  455. package/src/plugins/defaults/history-repair.ts +0 -2
  456. package/src/plugins/defaults/injectors.ts +74 -22
  457. package/src/plugins/defaults/llm-call.ts +0 -2
  458. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  459. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  460. package/src/plugins/defaults/persistence.ts +0 -2
  461. package/src/plugins/defaults/title-generate.ts +0 -5
  462. package/src/plugins/defaults/token-estimate.ts +0 -2
  463. package/src/plugins/defaults/tool-error.ts +0 -7
  464. package/src/plugins/defaults/tool-execute.ts +0 -2
  465. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  466. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  467. package/src/plugins/external-api.ts +104 -0
  468. package/src/plugins/external-plugin-loader.ts +187 -42
  469. package/src/plugins/feature-gate.ts +22 -0
  470. package/src/plugins/pipeline.ts +37 -0
  471. package/src/plugins/registry.ts +48 -80
  472. package/src/plugins/types.ts +40 -26
  473. package/src/plugins/user-loader.ts +21 -2
  474. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  475. package/src/proactive-artifact/job.test.ts +37 -5
  476. package/src/prompts/__tests__/system-prompt.test.ts +10 -43
  477. package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
  478. package/src/prompts/normalize-onboarding.ts +27 -0
  479. package/src/prompts/sections.ts +302 -0
  480. package/src/prompts/system-prompt.ts +63 -174
  481. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  482. package/src/prompts/templates/system-sections.ts +164 -0
  483. package/src/providers/__tests__/inference.test.ts +24 -7
  484. package/src/providers/anthropic/client.ts +28 -28
  485. package/src/providers/call-site-routing.ts +24 -6
  486. package/src/providers/connection-resolution.ts +68 -11
  487. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  488. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  489. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  490. package/src/providers/inference/adapter-factory.ts +32 -6
  491. package/src/providers/inference/auth.ts +12 -0
  492. package/src/providers/inference/backfill.ts +14 -1
  493. package/src/providers/inference/connections.ts +159 -34
  494. package/src/providers/inference/resolve-auth.ts +14 -4
  495. package/src/providers/model-catalog.ts +249 -12
  496. package/src/providers/model-intents.ts +3 -3
  497. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  498. package/src/providers/openai/chat-completions-provider.ts +169 -8
  499. package/src/providers/openrouter/client.ts +49 -4
  500. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
  501. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  502. package/src/providers/provider-availability.ts +17 -2
  503. package/src/providers/provider-catalog-visibility.ts +38 -0
  504. package/src/providers/provider-send-message.ts +27 -12
  505. package/src/providers/registry.ts +52 -15
  506. package/src/providers/retry.ts +47 -1
  507. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  508. package/src/runtime/agent-wake.ts +103 -15
  509. package/src/runtime/auth/route-policy.ts +21 -1
  510. package/src/runtime/btw-sidechain.ts +2 -0
  511. package/src/runtime/http-server.ts +7 -16
  512. package/src/runtime/http-types.ts +19 -47
  513. package/src/runtime/migrations/origin-mode.ts +1 -1
  514. package/src/runtime/pending-interactions.ts +1 -0
  515. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  516. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  517. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  518. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
  519. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  520. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  521. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  522. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  523. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  524. package/src/runtime/routes/acp-routes.ts +5 -3
  525. package/src/runtime/routes/auth-routes.ts +1 -1
  526. package/src/runtime/routes/bookmark-routes.ts +5 -3
  527. package/src/runtime/routes/btw-routes.ts +5 -1
  528. package/src/runtime/routes/channel-availability-routes.ts +126 -0
  529. package/src/runtime/routes/consolidation-routes.ts +100 -0
  530. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  531. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  532. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  533. package/src/runtime/routes/conversation-query-routes.ts +99 -35
  534. package/src/runtime/routes/conversation-routes.ts +97 -11
  535. package/src/runtime/routes/documents-routes.ts +25 -86
  536. package/src/runtime/routes/group-routes.ts +5 -0
  537. package/src/runtime/routes/inbound-conversation.ts +28 -8
  538. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  539. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  540. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  541. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  542. package/src/runtime/routes/index.ts +8 -0
  543. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  544. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  545. package/src/runtime/routes/inference-provider-connection-routes.ts +199 -22
  546. package/src/runtime/routes/integrations/a2a.ts +235 -0
  547. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  548. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  549. package/src/runtime/routes/integrations/twilio.ts +6 -13
  550. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  551. package/src/runtime/routes/notification-routes.ts +1 -1
  552. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  553. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  554. package/src/runtime/routes/question-routes.ts +259 -0
  555. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  556. package/src/runtime/routes/schedule-routes.ts +4 -7
  557. package/src/runtime/routes/subagents-routes.ts +98 -18
  558. package/src/runtime/routes/telemetry-routes.ts +27 -0
  559. package/src/runtime/routes/tts-routes.ts +27 -2
  560. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  561. package/src/runtime/routes/workspace-routes.ts +28 -0
  562. package/src/runtime/services/conversation-serializer.ts +39 -7
  563. package/src/runtime/sync/resource-sync-events.ts +93 -1
  564. package/src/schedule/schedule-store.ts +27 -2
  565. package/src/schedule/scheduler.ts +9 -1
  566. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  567. package/src/security/untrusted-content.ts +93 -8
  568. package/src/skills/catalog-files.ts +1 -1
  569. package/src/skills/catalog-install.ts +233 -116
  570. package/src/skills/clawhub.ts +70 -13
  571. package/src/skills/managed-store.ts +4 -119
  572. package/src/skills/skillssh-registry.ts +27 -48
  573. package/src/subagent/manager.ts +17 -7
  574. package/src/telemetry/types.ts +113 -1
  575. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  576. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  577. package/src/tools/apps/executors.ts +58 -7
  578. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  579. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  580. package/src/tools/browser/browser-execution.ts +15 -11
  581. package/src/tools/computer-use/definitions.ts +3 -3
  582. package/src/tools/credentials/vault.ts +1 -1
  583. package/src/tools/document/document-tool.ts +124 -1
  584. package/src/tools/filesystem/edit.ts +1 -1
  585. package/src/tools/filesystem/list.ts +1 -1
  586. package/src/tools/filesystem/read.ts +1 -1
  587. package/src/tools/filesystem/write.ts +5 -2
  588. package/src/tools/host-filesystem/transfer.ts +1 -1
  589. package/src/tools/host-terminal/host-shell.ts +1 -1
  590. package/src/tools/memory/register.ts +1 -9
  591. package/src/tools/permission-checker.ts +1 -1
  592. package/src/tools/registry.ts +17 -7
  593. package/src/tools/schedule/create.ts +2 -2
  594. package/src/tools/schema-transforms.ts +7 -2
  595. package/src/tools/side-effects.ts +1 -0
  596. package/src/tools/skills/delete-managed.ts +4 -4
  597. package/src/tools/skills/execute.ts +1 -1
  598. package/src/tools/skills/scaffold-managed.ts +3 -2
  599. package/src/tools/subagent/notify-parent.ts +1 -1
  600. package/src/tools/system/request-permission.ts +2 -2
  601. package/src/tools/terminal/safe-env.ts +60 -1
  602. package/src/tools/tool-manifest.ts +2 -0
  603. package/src/tools/types.ts +107 -21
  604. package/src/tools/ui-surface/definitions.ts +6 -5
  605. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  606. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  607. package/src/types/onboarding-context.ts +2 -0
  608. package/src/util/errors.ts +17 -0
  609. package/src/util/platform.ts +10 -0
  610. package/src/watcher/__tests__/engine.test.ts +22 -0
  611. package/src/watcher/engine.ts +6 -2
  612. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  613. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  614. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  615. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  616. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  617. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  618. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  619. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  620. package/src/workspace/migrations/registry.ts +10 -0
  621. package/src/workspace/migrations/runner.ts +39 -9
  622. package/src/workspace/migrations/types.ts +4 -0
  623. package/examples/plugins/echo/bun.lock +0 -25
  624. package/src/__tests__/context-window-manager.test.ts +0 -2481
  625. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  626. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  627. package/src/context/prompts/compact.md +0 -26
  628. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  629. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  630. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
@@ -36,6 +36,7 @@ import {
36
36
  } from "../embedding-backend.js";
37
37
  import { invalidatePageIndex } from "./page-index.js";
38
38
  import {
39
+ backfillKindOnPointsWithPrefix,
39
40
  pruneSlugsWithPrefixExcept,
40
41
  upsertConceptPageEmbedding,
41
42
  } from "./qdrant.js";
@@ -78,6 +79,11 @@ export function skillSlugFor(id: string): string {
78
79
  * successful re-seed so callers always see a consistent snapshot.
79
80
  */
80
81
  let entries: Map<string, SkillEntry> | null = null;
82
+ let requestedSeedGeneration = 0;
83
+ let processedSeedGeneration = 0;
84
+ let activeSeedDrain: Promise<void> | null = null;
85
+ let lastSeedError: unknown = null;
86
+ const seedWaiters: Array<{ generation: number; resolve: () => void }> = [];
81
87
 
82
88
  /**
83
89
  * Seed (or re-seed) skill embeddings into the unified concept-page collection.
@@ -85,32 +91,19 @@ let entries: Map<string, SkillEntry> | null = null;
85
91
  * background callers like daemon startup; pass `{ throwOnError: true }` from
86
92
  * synchronous CLI-driven paths that need to surface failures to the operator.
87
93
  *
88
- * Single-flight + coalesced: at most one seed runs at a time, and concurrent
89
- * callers are coalesced into one follow-up re-snapshot that runs after the
90
- * in-flight seed completes. Without this, an older snapshot can finish after
91
- * a newer one and overwrite the newer skill state. Strict callers observe
92
- * the most recent run's outcome via `lastSeedError`.
94
+ * Single-flight + coalesced: at most one seed runs at a time. Requests made
95
+ * while a seed is in flight advance the requested generation; stale in-flight
96
+ * snapshots are skipped before they write embeddings or replace the cache,
97
+ * then the drain loop immediately processes the latest generation. Strict
98
+ * callers observe the awaited generation's latest outcome via `lastSeedError`.
93
99
  */
94
- let seedTail: Promise<void> = Promise.resolve();
95
- let seedPending: Promise<void> | null = null;
96
- let lastSeedError: unknown = null;
97
100
 
98
- export async function seedV2SkillEntries(
99
- opts: { throwOnError?: boolean } = {},
100
- ): Promise<void> {
101
- if (!seedPending) {
102
- const next = seedTail.then(async () => {
103
- seedPending = null;
104
- await runSeedOnce();
105
- });
106
- seedTail = next.catch(() => {});
107
- seedPending = next;
108
- }
109
- await seedPending;
110
- if (opts.throwOnError && lastSeedError) {
111
- throw lastSeedError;
112
- }
113
- }
101
+ /**
102
+ * In-process latch for the legacy `kind` backfill (see
103
+ * {@link backfillKindOnPointsWithPrefix}). New upserts always write `kind`,
104
+ * so once the latch is set there is no follow-up work to do this process.
105
+ */
106
+ let legacyKindBackfillDone = false;
114
107
 
115
108
  /**
116
109
  * Steps (per run):
@@ -132,7 +125,49 @@ export async function seedV2SkillEntries(
132
125
  * stale points from prior catalog state (e.g. uninstalled skills).
133
126
  * 7. Replace the module-level `entries` cache with the freshly built map.
134
127
  */
135
- async function runSeedOnce(): Promise<void> {
128
+ export async function seedV2SkillEntries(
129
+ opts: { throwOnError?: boolean } = {},
130
+ ): Promise<void> {
131
+ const generation = ++requestedSeedGeneration;
132
+ const waiter = new Promise<void>((resolve) => {
133
+ seedWaiters.push({ generation, resolve });
134
+ });
135
+ startSeedDrainIfNeeded();
136
+ await waiter;
137
+ if (opts.throwOnError && lastSeedError) {
138
+ throw lastSeedError;
139
+ }
140
+ }
141
+
142
+ function startSeedDrainIfNeeded(): void {
143
+ if (activeSeedDrain) return;
144
+ if (processedSeedGeneration >= requestedSeedGeneration) return;
145
+
146
+ activeSeedDrain = drainSeedQueue().finally(() => {
147
+ activeSeedDrain = null;
148
+ startSeedDrainIfNeeded();
149
+ });
150
+ }
151
+
152
+ async function drainSeedQueue(): Promise<void> {
153
+ while (processedSeedGeneration < requestedSeedGeneration) {
154
+ const generationToProcess = requestedSeedGeneration;
155
+ await runSeedV2SkillEntries(generationToProcess);
156
+ processedSeedGeneration = generationToProcess;
157
+ resolveSeedWaiters();
158
+ }
159
+ }
160
+
161
+ function resolveSeedWaiters(): void {
162
+ for (let i = seedWaiters.length - 1; i >= 0; i -= 1) {
163
+ const waiter = seedWaiters[i]!;
164
+ if (waiter.generation > processedSeedGeneration) continue;
165
+ seedWaiters.splice(i, 1);
166
+ waiter.resolve();
167
+ }
168
+ }
169
+
170
+ async function runSeedV2SkillEntries(generation: number): Promise<void> {
136
171
  try {
137
172
  const config = getConfig();
138
173
  const catalog = loadSkillCatalog();
@@ -160,11 +195,19 @@ async function runSeedOnce(): Promise<void> {
160
195
  // Seed uninstalled catalog skills so their activation hints are
161
196
  // discoverable by intent. Track whether the catalog was available so we
162
197
  // can guard pruning below.
198
+ //
199
+ // Build the legacy-backfill allowlist in parallel: every locally
200
+ // installed skill id (regardless of enabled state) plus every remote
201
+ // catalog id. Restricting the backfill to this set keeps user-authored
202
+ // concept pages that happen to live under `skills/<slug>` from being
203
+ // mis-tagged and then pruned. See `backfillKindOnPointsWithPrefix`.
204
+ const knownSkillIds = new Set<string>(installedIds);
163
205
  let catalogAvailable = false;
164
206
  try {
165
207
  const fullCatalog = await getCatalog();
166
208
  catalogAvailable = fullCatalog.length > 0;
167
209
  for (const entry of fullCatalog) {
210
+ knownSkillIds.add(entry.id);
168
211
  if (installedIds.has(entry.id)) continue;
169
212
  const flagKey = entry.metadata?.vellum?.["feature-flag"];
170
213
  if (flagKey && !isAssistantFeatureFlagEnabled(flagKey, config))
@@ -184,12 +227,16 @@ async function runSeedOnce(): Promise<void> {
184
227
  // unavailable embedding backend in the all-disabled case, so pruning and
185
228
  // cache replacement still run and clear stale state.
186
229
  const nextEntries = new Map<string, SkillEntry>();
230
+ let denseVectors: number[][] = [];
231
+ let encodeSparse: (
232
+ input: string,
233
+ ) => ReturnType<typeof generateSparseEmbedding> = generateSparseEmbedding;
187
234
  if (seeds.length > 0) {
188
235
  const embedded = await embedWithBackend(
189
236
  config,
190
237
  seeds.map((s) => s.content),
191
238
  );
192
- const denseVectors = await Promise.all(
239
+ denseVectors = await Promise.all(
193
240
  embedded.vectors.map((v) =>
194
241
  applyCorrectionIfCalibrated(v, embedded.provider, embedded.model),
195
242
  ),
@@ -203,14 +250,25 @@ async function runSeedOnce(): Promise<void> {
203
250
  // entirely. Fall back to the legacy TF encoder only during the cold-start
204
251
  // window before corpus stats finish building.
205
252
  const corpusStats = getConceptPageCorpusStats();
206
- const encodeSparse = (input: string) =>
253
+ encodeSparse = (input: string) =>
207
254
  corpusStats
208
255
  ? generateBm25DocEmbedding(input, corpusStats, {
209
256
  k1: config.memory.v2.bm25_k1,
210
257
  b: config.memory.v2.bm25_b,
211
258
  })
212
259
  : generateSparseEmbedding(input);
260
+ }
213
261
 
262
+ if (generation !== requestedSeedGeneration) {
263
+ log.info(
264
+ { generation, latestGeneration: requestedSeedGeneration },
265
+ "Skipping stale v2 skill seed result",
266
+ );
267
+ lastSeedError = null;
268
+ return;
269
+ }
270
+
271
+ if (seeds.length > 0) {
214
272
  const now = Date.now();
215
273
  await Promise.all(
216
274
  seeds.map((seed, i) =>
@@ -233,6 +291,25 @@ async function runSeedOnce(): Promise<void> {
233
291
  // uninstalled catalog skills should exist, so skip pruning entirely to
234
292
  // avoid aggressively removing previously-seeded catalog skill embeddings.
235
293
  if (catalogAvailable) {
294
+ // Tag legacy skill points missing `payload.kind` before pruning so the
295
+ // kind-scoped prune can see them. Once-per-process; the backfill is
296
+ // idempotent (server-side `is_empty` filter), so a partial failure
297
+ // converges on retry.
298
+ if (!legacyKindBackfillDone) {
299
+ try {
300
+ await backfillKindOnPointsWithPrefix(
301
+ SKILL_SLUG_PREFIX,
302
+ SKILL_PAYLOAD_KIND,
303
+ knownSkillIds,
304
+ );
305
+ legacyKindBackfillDone = true;
306
+ } catch (err) {
307
+ log.warn(
308
+ { err },
309
+ "Failed to backfill kind on legacy skill points — pruning may leave orphans this run",
310
+ );
311
+ }
312
+ }
236
313
  await pruneSlugsWithPrefixExcept(
237
314
  SKILL_SLUG_PREFIX,
238
315
  seeds.map((s) => s.id),
@@ -265,12 +342,16 @@ async function runSeedOnce(): Promise<void> {
265
342
  * Accepts either a bare skill id (`example-skill`) or its unified-collection
266
343
  * slug (`skills/example-skill`) so render-side callers can pass through what
267
344
  * they have without a manual prefix strip.
345
+ *
346
+ * Returns a frozen copy so callers cannot mutate the underlying cache entry
347
+ * — matches the defensive-copy contract of `listSkillEntries`.
268
348
  */
269
349
  export function getSkillCapability(idOrSlug: string): SkillEntry | null {
270
350
  const id = idOrSlug.startsWith(SKILL_SLUG_PREFIX)
271
351
  ? idOrSlug.slice(SKILL_SLUG_PREFIX.length)
272
352
  : idOrSlug;
273
- return entries?.get(id) ?? null;
353
+ const entry = entries?.get(id);
354
+ return entry ? Object.freeze({ ...entry }) : null;
274
355
  }
275
356
 
276
357
  /** True iff the slug refers to a skill entry in the unified collection. */
@@ -280,8 +361,9 @@ export function isSkillSlug(slug: string): boolean {
280
361
 
281
362
  /**
282
363
  * Snapshot of the in-process skill cache, sorted by skill id (ASCII order)
283
- * for determinism. Returns a freshly allocated array on each call so callers
284
- * cannot mutate the underlying cache.
364
+ * for determinism. Returns a freshly allocated array of frozen entry copies
365
+ * on each call, so callers cannot mutate the underlying cache — neither by
366
+ * reassigning the array nor by writing through entry fields.
285
367
  *
286
368
  * The cache is replaced atomically by `seedV2SkillEntries`, so a snapshot
287
369
  * may be stale once a subsequent seed run completes. Callers that need
@@ -289,15 +371,18 @@ export function isSkillSlug(slug: string): boolean {
289
371
  */
290
372
  export function listSkillEntries(): SkillEntry[] {
291
373
  if (!entries) return [];
292
- return [...entries.values()].sort((a, b) =>
293
- a.id < b.id ? -1 : a.id > b.id ? 1 : 0,
294
- );
374
+ return [...entries.values()]
375
+ .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
376
+ .map((entry) => Object.freeze({ ...entry }));
295
377
  }
296
378
 
297
379
  /** @internal Test-only: clear the module-level cache. */
298
380
  export function _resetSkillStoreForTests(): void {
299
381
  entries = null;
300
- seedTail = Promise.resolve();
301
- seedPending = null;
382
+ requestedSeedGeneration = 0;
383
+ processedSeedGeneration = 0;
384
+ activeSeedDrain = null;
385
+ seedWaiters.splice(0, seedWaiters.length);
302
386
  lastSeedError = null;
387
+ legacyKindBackfillDone = false;
303
388
  }
@@ -35,9 +35,9 @@ const MEMORY_V2_STATIC_BLOCKS: readonly MemoryV2StaticBlock[] = [
35
35
  ];
36
36
 
37
37
  /**
38
- * Build the v2 static memory block, gated on `config.memory.v2.enabled`.
39
- * Empty/missing files are skipped; returns `null` when the gate is off or
40
- * every file is empty.
38
+ * Build the v2 static memory block, gated on `config.memory.enabled` and
39
+ * `config.memory.v2.enabled`. Empty/missing files are skipped; returns `null`
40
+ * when either gate is off or every file is empty.
41
41
  */
42
42
  export function readMemoryV2StaticContent(): string | null {
43
43
  let config;
@@ -46,7 +46,7 @@ export function readMemoryV2StaticContent(): string | null {
46
46
  } catch {
47
47
  return null;
48
48
  }
49
- if (!config.memory.v2.enabled) {
49
+ if (!config.memory.enabled || !config.memory.v2.enabled) {
50
50
  return null;
51
51
  }
52
52
 
@@ -115,3 +115,26 @@ export interface SkillEntry {
115
115
  id: string;
116
116
  content: string;
117
117
  }
118
+
119
+ // ---------------------------------------------------------------------------
120
+ // CLI-command entries (synthetic concept-collection rows, not on-disk pages)
121
+ // ---------------------------------------------------------------------------
122
+
123
+ /**
124
+ * Per-CLI-subcommand capability snapshot held in-process and embedded into the
125
+ * unified `memory_v2_concept_pages` Qdrant collection under the slug
126
+ * `cli-commands/<name>`. `content` is the full `helpInformation()` output for
127
+ * the top-level subcommand — the embedding target, intentionally uncapped so
128
+ * activation hints in flag descriptions and examples carry semantic weight.
129
+ * `description` is the one-line Commander description, rendered terse in
130
+ * `### CLI Commands You Can Use` so the injection block stays compact even
131
+ * for verbose `--help` outputs.
132
+ *
133
+ * Plain interface (no Zod) — same in-process-only justification as
134
+ * `SkillEntry`.
135
+ */
136
+ export interface CliCommandEntry {
137
+ id: string;
138
+ description: string;
139
+ content: string;
140
+ }
@@ -0,0 +1,274 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import type { ChannelReplyPayload } from "@vellumai/gateway-client";
4
+
5
+ import type { A2ATask, Artifact } from "../../../../a2a/protocol-types.js";
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Mock state
9
+ // ---------------------------------------------------------------------------
10
+
11
+ let completedTask: A2ATask | null = null;
12
+ let completeWithArtifactsCalls: Array<{
13
+ taskId: string;
14
+ artifacts: Artifact[];
15
+ }> = [];
16
+ let pushUrlByTaskId: Record<string, string | null> = {};
17
+ let completeError: Error | null = null;
18
+
19
+ const fetchCalls: Array<{
20
+ url: string;
21
+ init: RequestInit;
22
+ }> = [];
23
+ let fetchResponses: Array<{ ok: boolean; status: number; body: string }> = [];
24
+ let fetchCallIndex = 0;
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Mocks
28
+ // ---------------------------------------------------------------------------
29
+
30
+ const defaultTask: A2ATask = {
31
+ id: "task-123",
32
+ status: { state: "completed", timestamp: new Date().toISOString() },
33
+ artifacts: [
34
+ {
35
+ artifact_id: "art-1",
36
+ parts: [{ kind: "text", text: "Hello from assistant" }],
37
+ },
38
+ ],
39
+ };
40
+
41
+ mock.module("../../../../a2a/task-store.js", () => ({
42
+ completeWithArtifacts: (taskId: string, artifacts: Artifact[]): A2ATask => {
43
+ completeWithArtifactsCalls.push({ taskId, artifacts });
44
+ if (completeError) throw completeError;
45
+ return completedTask ?? defaultTask;
46
+ },
47
+ getPushUrl: (taskId: string): string | null => {
48
+ return pushUrlByTaskId[taskId] ?? null;
49
+ },
50
+ }));
51
+
52
+ mock.module("../../../../util/logger.js", () => ({
53
+ getLogger: () => ({
54
+ debug: () => {},
55
+ info: () => {},
56
+ warn: () => {},
57
+ error: () => {},
58
+ }),
59
+ }));
60
+
61
+ // Intercept global fetch for push notification testing
62
+ const originalFetch = globalThis.fetch;
63
+
64
+ // Import the module under test AFTER mocks are set up
65
+ const { deliverA2AReply } = await import("../deliver.js");
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Setup / teardown
69
+ // ---------------------------------------------------------------------------
70
+
71
+ beforeEach(() => {
72
+ completedTask = null;
73
+ completeWithArtifactsCalls = [];
74
+ pushUrlByTaskId = {};
75
+ completeError = null;
76
+ fetchCalls.length = 0;
77
+ fetchResponses = [];
78
+ fetchCallIndex = 0;
79
+
80
+ globalThis.fetch = (async (
81
+ input: string | URL | Request,
82
+ init?: RequestInit,
83
+ ) => {
84
+ const url = typeof input === "string" ? input : input.toString();
85
+ fetchCalls.push({ url, init: init ?? {} });
86
+ const responseSpec = fetchResponses[fetchCallIndex++] ?? {
87
+ ok: true,
88
+ status: 200,
89
+ body: "{}",
90
+ };
91
+ return new Response(responseSpec.body, {
92
+ status: responseSpec.status,
93
+ statusText: responseSpec.ok ? "OK" : "Error",
94
+ });
95
+ }) as typeof fetch;
96
+ });
97
+
98
+ afterEach(() => {
99
+ globalThis.fetch = originalFetch;
100
+ });
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // Tests
104
+ // ---------------------------------------------------------------------------
105
+
106
+ describe("deliverA2AReply", () => {
107
+ const baseCallbackUrl = "https://example.com/deliver/a2a?taskId=task-123";
108
+
109
+ test("completes task with text artifact", async () => {
110
+ const payload: ChannelReplyPayload = {
111
+ chatId: "chat-1",
112
+ text: "Hello from the assistant",
113
+ };
114
+
115
+ const result = await deliverA2AReply(baseCallbackUrl, payload);
116
+
117
+ expect(result.ok).toBe(true);
118
+ expect(completeWithArtifactsCalls).toHaveLength(1);
119
+ expect(completeWithArtifactsCalls[0].taskId).toBe("task-123");
120
+ expect(completeWithArtifactsCalls[0].artifacts).toHaveLength(1);
121
+ expect(completeWithArtifactsCalls[0].artifacts[0].parts).toEqual([
122
+ { kind: "text", text: "Hello from the assistant" },
123
+ ]);
124
+ });
125
+
126
+ test("completes task with file attachments", async () => {
127
+ const payload: ChannelReplyPayload = {
128
+ chatId: "chat-1",
129
+ text: "Here is a file",
130
+ attachments: [
131
+ {
132
+ id: "att-1",
133
+ filename: "report.pdf",
134
+ mimeType: "application/pdf",
135
+ sizeBytes: 1024,
136
+ kind: "file",
137
+ data: "data:application/pdf;base64,abc123",
138
+ },
139
+ ],
140
+ };
141
+
142
+ const result = await deliverA2AReply(baseCallbackUrl, payload);
143
+
144
+ expect(result.ok).toBe(true);
145
+ expect(completeWithArtifactsCalls).toHaveLength(1);
146
+ const parts = completeWithArtifactsCalls[0].artifacts[0].parts;
147
+ expect(parts).toHaveLength(2);
148
+ expect(parts[0]).toEqual({
149
+ kind: "text",
150
+ text: "Here is a file",
151
+ });
152
+ expect(parts[1]).toEqual({
153
+ kind: "file",
154
+ filename: "report.pdf",
155
+ media_type: "application/pdf",
156
+ url: "data:application/pdf;base64,abc123",
157
+ });
158
+ });
159
+
160
+ test("returns ok: false when taskId is missing from URL", async () => {
161
+ const result = await deliverA2AReply("https://example.com/deliver/a2a", {
162
+ chatId: "chat-1",
163
+ text: "Hello",
164
+ });
165
+
166
+ expect(result.ok).toBe(false);
167
+ expect(completeWithArtifactsCalls).toHaveLength(0);
168
+ });
169
+
170
+ test("returns ok: true when payload has no content", async () => {
171
+ const result = await deliverA2AReply(baseCallbackUrl, {
172
+ chatId: "chat-1",
173
+ });
174
+
175
+ expect(result.ok).toBe(true);
176
+ expect(completeWithArtifactsCalls).toHaveLength(0);
177
+ });
178
+
179
+ test("returns ok: false when task completion throws", async () => {
180
+ completeError = new Error("A2A task not found: task-123");
181
+
182
+ const result = await deliverA2AReply(baseCallbackUrl, {
183
+ chatId: "chat-1",
184
+ text: "Hello",
185
+ });
186
+
187
+ expect(result.ok).toBe(false);
188
+ });
189
+
190
+ test("returns ok: false when task is already terminal", async () => {
191
+ completeError = new Error(
192
+ 'Cannot transition task task-123 from terminal state "completed" to "completed"',
193
+ );
194
+
195
+ const result = await deliverA2AReply(baseCallbackUrl, {
196
+ chatId: "chat-1",
197
+ text: "Hello",
198
+ });
199
+
200
+ expect(result.ok).toBe(false);
201
+ });
202
+
203
+ describe("push notifications", () => {
204
+ test("POSTs completed task to push URL", async () => {
205
+ pushUrlByTaskId["task-123"] = "https://requester.example.com/push";
206
+ fetchResponses = [{ ok: true, status: 200, body: "{}" }];
207
+
208
+ const result = await deliverA2AReply(baseCallbackUrl, {
209
+ chatId: "chat-1",
210
+ text: "Done",
211
+ });
212
+
213
+ expect(result.ok).toBe(true);
214
+
215
+ // Wait for the fire-and-forget push to complete
216
+ await new Promise((r) => setTimeout(r, 50));
217
+
218
+ expect(fetchCalls).toHaveLength(1);
219
+ expect(fetchCalls[0].url).toBe("https://requester.example.com/push");
220
+ expect(fetchCalls[0].init.method).toBe("POST");
221
+
222
+ const headers = fetchCalls[0].init.headers as Record<string, string>;
223
+ expect(headers["Content-Type"]).toBe("application/a2a+json");
224
+ expect(headers["A2A-Version"]).toBe("1.0");
225
+ });
226
+
227
+ test("does not push when no push URL configured", async () => {
228
+ const result = await deliverA2AReply(baseCallbackUrl, {
229
+ chatId: "chat-1",
230
+ text: "Done",
231
+ });
232
+
233
+ expect(result.ok).toBe(true);
234
+
235
+ await new Promise((r) => setTimeout(r, 50));
236
+
237
+ expect(fetchCalls).toHaveLength(0);
238
+ });
239
+
240
+ test("push failure does not affect delivery result", async () => {
241
+ pushUrlByTaskId["task-123"] = "https://requester.example.com/push";
242
+ // All retries fail with 500
243
+ fetchResponses = Array(4).fill({
244
+ ok: false,
245
+ status: 500,
246
+ body: "Internal Server Error",
247
+ });
248
+
249
+ const result = await deliverA2AReply(baseCallbackUrl, {
250
+ chatId: "chat-1",
251
+ text: "Done",
252
+ });
253
+
254
+ // Delivery still succeeds even though push will fail
255
+ expect(result.ok).toBe(true);
256
+ });
257
+
258
+ test("stops retrying on non-retryable client error", async () => {
259
+ pushUrlByTaskId["task-123"] = "https://requester.example.com/push";
260
+ fetchResponses = [{ ok: false, status: 404, body: "Not Found" }];
261
+
262
+ await deliverA2AReply(baseCallbackUrl, {
263
+ chatId: "chat-1",
264
+ text: "Done",
265
+ });
266
+
267
+ // Wait for the fire-and-forget push to settle
268
+ await new Promise((r) => setTimeout(r, 50));
269
+
270
+ // Should only attempt once on a 4xx (non-429) error
271
+ expect(fetchCalls).toHaveLength(1);
272
+ });
273
+ });
274
+ });