@vellumai/assistant 0.8.2 → 0.8.4

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 (503) hide show
  1. package/ARCHITECTURE.md +11 -12
  2. package/docker-entrypoint.sh +13 -2
  3. package/docker-init-apt-root.sh +79 -6
  4. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  5. package/openapi.yaml +945 -36
  6. package/package.json +1 -1
  7. package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
  8. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  9. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +272 -0
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  14. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  15. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  16. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  17. package/src/__tests__/compaction-events.test.ts +1 -1
  18. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  19. package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
  20. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  22. package/src/__tests__/config-watcher.test.ts +1 -1
  23. package/src/__tests__/context-token-estimator.test.ts +112 -57
  24. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  27. package/src/__tests__/conversation-agent-loop.test.ts +77 -3
  28. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  29. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  30. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  32. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  33. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  34. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  35. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  36. package/src/__tests__/conversation-pairing.test.ts +2 -2
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  39. package/src/__tests__/conversation-queue.test.ts +1 -1
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
  41. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  42. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  43. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  44. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  45. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  46. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  47. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  48. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  49. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  50. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  51. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  52. package/src/__tests__/date-context.test.ts +45 -0
  53. package/src/__tests__/dm-backfill.test.ts +64 -0
  54. package/src/__tests__/dm-persistence.test.ts +33 -0
  55. package/src/__tests__/document-find-replace.test.ts +501 -0
  56. package/src/__tests__/external-plugin-loader.test.ts +91 -19
  57. package/src/__tests__/first-greeting.test.ts +23 -2
  58. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  59. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  60. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  61. package/src/__tests__/heartbeat-service.test.ts +24 -164
  62. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  63. package/src/__tests__/host-app-control-proxy.test.ts +241 -0
  64. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  66. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  67. package/src/__tests__/host-file-proxy.test.ts +8 -1
  68. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  69. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  70. package/src/__tests__/identity-routes.test.ts +57 -0
  71. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  72. package/src/__tests__/injector-background-turn.test.ts +153 -0
  73. package/src/__tests__/injector-chain.test.ts +7 -0
  74. package/src/__tests__/injector-document-comments.test.ts +378 -0
  75. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  76. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
  77. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  78. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  79. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  80. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  81. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  82. package/src/__tests__/llm-catalog-parity.test.ts +3 -0
  83. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  84. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  85. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  86. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
  87. package/src/__tests__/llm-resolver.test.ts +340 -3
  88. package/src/__tests__/log-export-routes.test.ts +99 -2
  89. package/src/__tests__/managed-profile-guard.test.ts +10 -0
  90. package/src/__tests__/message-queue-steer.test.ts +114 -0
  91. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  92. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  93. package/src/__tests__/notification-deep-link.test.ts +15 -0
  94. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  95. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  96. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  97. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  98. package/src/__tests__/openai-provider.test.ts +323 -3
  99. package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
  100. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  101. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  102. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  103. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  104. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  105. package/src/__tests__/platform-proxy-context.test.ts +6 -1
  106. package/src/__tests__/platform.test.ts +0 -3
  107. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  108. package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
  109. package/src/__tests__/plugin-types.test.ts +2 -2
  110. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  111. package/src/__tests__/process-message-display-content.test.ts +21 -16
  112. package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
  113. package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  115. package/src/__tests__/server-history-render.test.ts +83 -4
  116. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  117. package/src/__tests__/system-prompt.test.ts +57 -101
  118. package/src/__tests__/terminal-tools.test.ts +11 -1
  119. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  120. package/src/__tests__/thread-backfill.test.ts +370 -22
  121. package/src/__tests__/tool-executor.test.ts +90 -1
  122. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  123. package/src/__tests__/twilio-routes.test.ts +1 -1
  124. package/src/__tests__/web-fetch.test.ts +2 -2
  125. package/src/__tests__/workspace-git-service.test.ts +88 -5
  126. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  127. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  128. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  129. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  130. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  131. package/src/a2a/__tests__/task-store.test.ts +246 -0
  132. package/src/a2a/agent-card.ts +58 -0
  133. package/src/a2a/feature-gate.ts +8 -0
  134. package/src/a2a/protocol-constants.ts +21 -0
  135. package/src/a2a/protocol-errors.ts +50 -0
  136. package/src/a2a/protocol-types.ts +162 -0
  137. package/src/a2a/task-store.ts +168 -0
  138. package/src/agent/attachments.ts +1 -0
  139. package/src/agent/loop.ts +208 -22
  140. package/src/background-wake/next-wake.test.ts +289 -0
  141. package/src/background-wake/next-wake.ts +172 -0
  142. package/src/browser/operations.ts +15 -0
  143. package/src/channels/config.ts +9 -0
  144. package/src/channels/types.ts +14 -0
  145. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  146. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  147. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  148. package/src/cli/commands/__tests__/schedules.test.ts +469 -0
  149. package/src/cli/commands/conversations.ts +128 -1
  150. package/src/cli/commands/inference-providers.ts +147 -1
  151. package/src/cli/commands/memory-v2.ts +308 -0
  152. package/src/cli/commands/notifications.ts +89 -37
  153. package/src/cli/commands/plugins.ts +67 -0
  154. package/src/cli/commands/schedules.ts +297 -5
  155. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  156. package/src/cli/lib/install-from-github.ts +8 -9
  157. package/src/cli/lib/search-plugins.ts +163 -0
  158. package/src/cli/program.ts +14 -0
  159. package/src/cli/utils/conversation-id.ts +17 -5
  160. package/src/config/assistant-feature-flags.ts +24 -54
  161. package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
  162. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  163. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  164. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  165. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  166. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  167. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  168. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  169. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  170. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  171. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  172. package/src/config/bundled-tool-registry.ts +22 -12
  173. package/src/config/call-site-defaults.ts +124 -0
  174. package/src/config/feature-flag-registry.json +111 -23
  175. package/src/config/llm-resolver.ts +66 -1
  176. package/src/config/schema.ts +2 -0
  177. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
  178. package/src/config/schemas/call-site-catalog.ts +21 -0
  179. package/src/config/schemas/channels.ts +9 -0
  180. package/src/config/schemas/conversations.ts +10 -0
  181. package/src/config/schemas/heartbeat.ts +14 -0
  182. package/src/config/schemas/llm.ts +4 -3
  183. package/src/config/schemas/memory-retrospective.ts +1 -1
  184. package/src/config/schemas/memory-v2.ts +51 -4
  185. package/src/config/schemas/memory.ts +3 -1
  186. package/src/config/seed-inference-profiles.ts +99 -29
  187. package/src/context/compactor.ts +80 -13
  188. package/src/context/token-estimator.ts +72 -31
  189. package/src/context/window-manager.ts +25 -0
  190. package/src/credential-health/credential-health-service.ts +34 -19
  191. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
  192. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  193. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  194. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  195. package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
  196. package/src/daemon/conversation-agent-loop.ts +252 -56
  197. package/src/daemon/conversation-lifecycle.ts +142 -116
  198. package/src/daemon/conversation-messaging.ts +3 -0
  199. package/src/daemon/conversation-process.ts +273 -0
  200. package/src/daemon/conversation-queue-manager.ts +14 -0
  201. package/src/daemon/conversation-runtime-assembly.ts +144 -75
  202. package/src/daemon/conversation-slash.ts +37 -5
  203. package/src/daemon/conversation-surfaces.ts +45 -2
  204. package/src/daemon/conversation-tool-setup.ts +7 -0
  205. package/src/daemon/conversation.ts +42 -12
  206. package/src/daemon/date-context.ts +40 -0
  207. package/src/daemon/first-greeting.ts +10 -0
  208. package/src/daemon/guardian-action-generators.ts +1 -125
  209. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  210. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  211. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  212. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  213. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  214. package/src/daemon/handlers/config-a2a.ts +449 -0
  215. package/src/daemon/handlers/config-model.test.ts +1 -0
  216. package/src/daemon/handlers/conversations.ts +80 -0
  217. package/src/daemon/handlers/shared.ts +92 -29
  218. package/src/daemon/host-app-control-proxy.ts +69 -18
  219. package/src/daemon/host-bash-proxy.ts +1 -1
  220. package/src/daemon/host-cu-proxy.ts +1 -1
  221. package/src/daemon/host-file-proxy.ts +1 -1
  222. package/src/daemon/host-proxy-preactivation.ts +85 -18
  223. package/src/daemon/host-transfer-proxy.ts +1 -1
  224. package/src/daemon/lifecycle.ts +67 -65
  225. package/src/daemon/memory-v2-startup.ts +49 -13
  226. package/src/daemon/message-protocol.ts +4 -0
  227. package/src/daemon/message-types/conversations.ts +8 -0
  228. package/src/daemon/message-types/document-comments.ts +50 -0
  229. package/src/daemon/message-types/messages.ts +68 -1
  230. package/src/daemon/message-types/notifications.ts +21 -0
  231. package/src/daemon/message-types/surfaces.ts +3 -1
  232. package/src/daemon/message-types/web-activity.ts +57 -0
  233. package/src/daemon/pkb-reminder-builder.test.ts +10 -53
  234. package/src/daemon/pkb-reminder-builder.ts +4 -19
  235. package/src/daemon/plugin-source-watcher.ts +135 -3
  236. package/src/daemon/process-message.ts +72 -12
  237. package/src/daemon/query-complexity-router.ts +75 -0
  238. package/src/daemon/skill-memory-refresh.ts +5 -1
  239. package/src/daemon/trust-context.ts +6 -0
  240. package/src/daemon/wake-target-adapter.ts +2 -0
  241. package/src/documents/document-comments-store.test.ts +338 -0
  242. package/src/documents/document-comments-store.ts +237 -0
  243. package/src/documents/document-store.ts +202 -0
  244. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  245. package/src/export/transcript-formatter.ts +54 -20
  246. package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
  247. package/src/heartbeat/heartbeat-service.ts +35 -191
  248. package/src/home/__tests__/feed-types.test.ts +40 -0
  249. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  250. package/src/home/feed-types.ts +20 -3
  251. package/src/home/home-content-refresh.ts +52 -0
  252. package/src/home/home-greeting-cache.ts +69 -0
  253. package/src/home/home-greeting.ts +94 -0
  254. package/src/home/suggested-prompts.ts +177 -9
  255. package/src/ipc/cli-client.ts +147 -45
  256. package/src/memory/__tests__/conversation-queries.test.ts +220 -0
  257. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  258. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  259. package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
  260. package/src/memory/conversation-crud.ts +133 -43
  261. package/src/memory/conversation-queries.ts +87 -1
  262. package/src/memory/conversation-title-service.ts +26 -4
  263. package/src/memory/db-init.ts +22 -0
  264. package/src/memory/delivery-crud.ts +41 -0
  265. package/src/memory/delivery-status.ts +141 -15
  266. package/src/memory/external-conversation-store.ts +32 -1
  267. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
  268. package/src/memory/graph/conversation-graph-memory.ts +18 -6
  269. package/src/memory/graph/tools.ts +6 -37
  270. package/src/memory/invite-store.ts +53 -0
  271. package/src/memory/jobs-worker.ts +21 -1
  272. package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
  273. package/src/memory/llm-request-log-store.ts +92 -1
  274. package/src/memory/memory-retrospective-constants.ts +28 -0
  275. package/src/memory/memory-retrospective-enqueue.ts +4 -22
  276. package/src/memory/memory-retrospective-job.ts +438 -21
  277. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  278. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  279. package/src/memory/migrations/100-core-tables.ts +1 -0
  280. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  281. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  282. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  283. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  284. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  285. package/src/memory/migrations/253-document-comments.ts +47 -0
  286. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  287. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  288. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  289. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  290. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  291. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  292. package/src/memory/migrations/index.ts +20 -0
  293. package/src/memory/migrations/registry.ts +33 -0
  294. package/src/memory/onboarding-events-store.ts +7 -0
  295. package/src/memory/schema/a2a.ts +15 -0
  296. package/src/memory/schema/calls.ts +1 -0
  297. package/src/memory/schema/conversations.ts +3 -0
  298. package/src/memory/schema/index.ts +1 -0
  299. package/src/memory/schema/inference.ts +2 -0
  300. package/src/memory/schema/infrastructure.ts +2 -0
  301. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  302. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  303. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  304. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  305. package/src/memory/v2/__tests__/injection.test.ts +221 -17
  306. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  307. package/src/memory/v2/__tests__/router.test.ts +489 -1
  308. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  309. package/src/memory/v2/activation-store.ts +14 -16
  310. package/src/memory/v2/cli-command-content.ts +19 -0
  311. package/src/memory/v2/cli-command-store.ts +304 -0
  312. package/src/memory/v2/consolidation-job.ts +14 -0
  313. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  314. package/src/memory/v2/injection-events.ts +101 -0
  315. package/src/memory/v2/injection.ts +69 -29
  316. package/src/memory/v2/page-index.ts +246 -19
  317. package/src/memory/v2/page-store.ts +18 -0
  318. package/src/memory/v2/router.ts +209 -55
  319. package/src/memory/v2/static-context.ts +4 -4
  320. package/src/memory/v2/types.ts +23 -0
  321. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  322. package/src/messaging/providers/a2a/deliver.ts +156 -0
  323. package/src/messaging/providers/gmail/client.ts +9 -2
  324. package/src/messaging/providers/index.ts +18 -3
  325. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  327. package/src/messaging/providers/slack/adapter.ts +178 -25
  328. package/src/messaging/providers/slack/api.test.ts +54 -0
  329. package/src/messaging/providers/slack/api.ts +119 -3
  330. package/src/messaging/providers/slack/client.ts +12 -0
  331. package/src/messaging/providers/slack/deep-link.ts +20 -1
  332. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  333. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  334. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  335. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  336. package/src/messaging/providers/slack/send.test.ts +77 -0
  337. package/src/messaging/providers/slack/send.ts +8 -2
  338. package/src/messaging/providers/slack/types.ts +14 -0
  339. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  340. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  341. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  342. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
  343. package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
  344. package/src/notifications/adapters/macos.ts +12 -2
  345. package/src/notifications/broadcaster.ts +29 -4
  346. package/src/notifications/conversation-seed-composer.ts +14 -2
  347. package/src/notifications/copy-composer.ts +17 -64
  348. package/src/notifications/decision-engine.ts +111 -44
  349. package/src/notifications/deferred-emit.ts +135 -0
  350. package/src/notifications/deterministic-checks.ts +96 -0
  351. package/src/notifications/emit-signal.ts +10 -1
  352. package/src/notifications/home-feed-side-effect.ts +136 -27
  353. package/src/notifications/signal.ts +0 -4
  354. package/src/notifications/types.ts +8 -0
  355. package/src/oauth/connect-orchestrator.ts +3 -0
  356. package/src/oauth/credential-token-resolver.ts +2 -0
  357. package/src/oauth/manual-token-connection.ts +19 -0
  358. package/src/oauth/oauth-store.ts +12 -0
  359. package/src/oauth/platform-connection.test.ts +43 -3
  360. package/src/oauth/platform-connection.ts +13 -4
  361. package/src/oauth/seed-providers.ts +22 -0
  362. package/src/permissions/prompter.ts +5 -2
  363. package/src/permissions/secret-prompter.ts +4 -1
  364. package/src/plugins/defaults/injectors.ts +118 -26
  365. package/src/plugins/external-plugin-loader.ts +82 -10
  366. package/src/plugins/types.ts +16 -7
  367. package/src/prompts/__tests__/system-prompt.test.ts +44 -45
  368. package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
  369. package/src/prompts/normalize-onboarding.ts +40 -0
  370. package/src/prompts/sections.ts +32 -14
  371. package/src/prompts/system-prompt.ts +105 -76
  372. package/src/prompts/template-detection.ts +37 -0
  373. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  374. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  375. package/src/prompts/templates/VOICE.md +3 -0
  376. package/src/prompts/templates/system-sections.ts +51 -10
  377. package/src/providers/__tests__/inference.test.ts +2 -0
  378. package/src/providers/anthropic/client.ts +132 -5
  379. package/src/providers/call-site-routing.ts +24 -6
  380. package/src/providers/connection-resolution.ts +63 -13
  381. package/src/providers/fireworks/client.ts +20 -2
  382. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  383. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  384. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  385. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  386. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  387. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  388. package/src/providers/inference/adapter-factory.ts +24 -21
  389. package/src/providers/inference/auth.ts +15 -3
  390. package/src/providers/inference/backfill.ts +14 -1
  391. package/src/providers/inference/codex-token-refresh.ts +128 -0
  392. package/src/providers/inference/connections.ts +85 -5
  393. package/src/providers/inference/resolve-auth.ts +50 -5
  394. package/src/providers/model-catalog.ts +244 -242
  395. package/src/providers/model-intents.ts +3 -3
  396. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  397. package/src/providers/openai/chat-completions-provider.ts +215 -25
  398. package/src/providers/openai/responses-provider.ts +9 -3
  399. package/src/providers/openrouter/client.ts +46 -4
  400. package/src/providers/platform-proxy/constants.ts +3 -4
  401. package/src/providers/provider-catalog-visibility.ts +3 -1
  402. package/src/providers/provider-send-message.ts +27 -12
  403. package/src/providers/registry.ts +30 -1
  404. package/src/providers/types.ts +25 -0
  405. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  406. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  407. package/src/runtime/agent-wake.ts +212 -57
  408. package/src/runtime/auth/route-policy.ts +20 -3
  409. package/src/runtime/background-job-runner.ts +26 -0
  410. package/src/runtime/channel-reply-delivery.ts +182 -47
  411. package/src/runtime/channel-retry-sweep.ts +141 -16
  412. package/src/runtime/http-server.ts +7 -16
  413. package/src/runtime/http-types.ts +7 -51
  414. package/src/runtime/pending-interactions.ts +51 -8
  415. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  416. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  417. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
  418. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  419. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  420. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  421. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  422. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  423. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  424. package/src/runtime/routes/approval-routes.ts +4 -1
  425. package/src/runtime/routes/channel-availability-routes.ts +5 -0
  426. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  427. package/src/runtime/routes/consolidation-routes.ts +100 -0
  428. package/src/runtime/routes/content-source-routes.ts +78 -0
  429. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  430. package/src/runtime/routes/conversation-query-routes.ts +130 -12
  431. package/src/runtime/routes/conversation-routes.ts +288 -76
  432. package/src/runtime/routes/document-comments-routes.ts +287 -0
  433. package/src/runtime/routes/documents-routes.ts +33 -0
  434. package/src/runtime/routes/home-feed-routes.ts +6 -3
  435. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  436. package/src/runtime/routes/host-browser-routes.ts +8 -1
  437. package/src/runtime/routes/identity-routes.ts +21 -0
  438. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  439. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  440. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  441. package/src/runtime/routes/index.ts +14 -4
  442. package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
  443. package/src/runtime/routes/integrations/a2a.ts +294 -0
  444. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  445. package/src/runtime/routes/log-export-routes.ts +39 -0
  446. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  447. package/src/runtime/routes/notification-routes.ts +19 -2
  448. package/src/runtime/routes/question-routes.ts +4 -1
  449. package/src/runtime/routes/sanity-routes.ts +159 -0
  450. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  451. package/src/runtime/routes/subagents-routes.ts +41 -0
  452. package/src/runtime/services/conversation-serializer.ts +30 -4
  453. package/src/schedule/integration-status.ts +3 -1
  454. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  455. package/src/security/oauth2-device-code.ts +307 -0
  456. package/src/security/oauth2.ts +26 -9
  457. package/src/security/secure-keys.ts +5 -0
  458. package/src/skills/catalog-install.ts +6 -2
  459. package/src/subagent/manager.ts +2 -0
  460. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  461. package/src/tools/browser/browser-execution.ts +93 -0
  462. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  463. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  464. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  465. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  466. package/src/tools/browser/cdp-client/factory.ts +87 -3
  467. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  468. package/src/tools/browser/cdp-client/types.ts +36 -0
  469. package/src/tools/browser/pinned-tabs.ts +90 -0
  470. package/src/tools/document/document-comment-tool.test.ts +379 -0
  471. package/src/tools/document/document-comment-tool.ts +156 -0
  472. package/src/tools/document/document-tool.ts +128 -2
  473. package/src/tools/memory/register.ts +1 -9
  474. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  475. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  476. package/src/tools/network/domain-normalize.ts +17 -0
  477. package/src/tools/network/web-fetch.ts +213 -64
  478. package/src/tools/network/web-search.ts +191 -66
  479. package/src/tools/registry.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +3 -2
  481. package/src/tools/tool-approval-handler.ts +19 -12
  482. package/src/tools/types.ts +41 -2
  483. package/src/tools/ui-surface/definitions.ts +3 -1
  484. package/src/types/onboarding-context.ts +4 -0
  485. package/src/util/__tests__/favicon.test.ts +84 -0
  486. package/src/util/favicon.ts +40 -0
  487. package/src/util/platform.ts +0 -5
  488. package/src/workspace/git-service.ts +75 -4
  489. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  490. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  491. package/src/workspace/migrations/registry.ts +4 -0
  492. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  493. package/src/config/bundled-skills/document/SKILL.md +0 -54
  494. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  495. package/src/daemon/seed-files.ts +0 -18
  496. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  497. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
  498. package/src/runtime/routes/interface-routes.ts +0 -43
  499. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  500. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  501. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  502. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  503. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -7,7 +7,7 @@ import { type DrizzleDb, getSqliteFrom } from "../../db-connection.js";
7
7
  import { migrateActivationState } from "../../migrations/232-activation-state.js";
8
8
  import * as schema from "../../schema.js";
9
9
  import {
10
- evictCompactedTurns,
10
+ clearEverInjected,
11
11
  forkActivationState,
12
12
  hydrate,
13
13
  save,
@@ -146,23 +146,37 @@ describe("activation-store", () => {
146
146
  });
147
147
  });
148
148
 
149
- describe("evictCompactedTurns", () => {
150
- test("drops entries with turn <= upToTurn and preserves the rest", () => {
149
+ describe("clearEverInjected", () => {
150
+ test("empties the everInjected list", () => {
151
151
  const state = buildState({
152
152
  everInjected: [
153
153
  { slug: "slug-a", turn: 1 },
154
154
  { slug: "slug-b", turn: 2 },
155
155
  { slug: "slug-c", turn: 3 },
156
- { slug: "slug-d", turn: 5 },
157
156
  ],
158
157
  });
159
158
 
160
- const result = evictCompactedTurns(state, 2);
159
+ const result = clearEverInjected(state);
161
160
 
162
- expect(result.everInjected).toEqual([
163
- { slug: "slug-c", turn: 3 },
164
- { slug: "slug-d", turn: 5 },
165
- ]);
161
+ expect(result.everInjected).toEqual([]);
162
+ });
163
+
164
+ test("clears entries even when their turn exceeds currentTurn — the SIGKILL drift case", () => {
165
+ // Regression: under turn-bounded eviction, entries with turn >
166
+ // currentTurn survived forever. A non-graceful shutdown can persist
167
+ // everInjected entries with high turn values, then a restart restores
168
+ // the tracker from an older snapshot with a lower currentTurn.
169
+ const state = buildState({
170
+ currentTurn: 5,
171
+ everInjected: [
172
+ { slug: "slug-a", turn: 10 },
173
+ { slug: "slug-b", turn: 20 },
174
+ ],
175
+ });
176
+
177
+ const result = clearEverInjected(state);
178
+
179
+ expect(result.everInjected).toEqual([]);
166
180
  });
167
181
 
168
182
  test("returns a new object — does not mutate the input", () => {
@@ -170,7 +184,7 @@ describe("activation-store", () => {
170
184
  everInjected: [{ slug: "slug-a", turn: 1 }],
171
185
  });
172
186
 
173
- const result = evictCompactedTurns(state, 1);
187
+ const result = clearEverInjected(state);
174
188
 
175
189
  expect(result.everInjected).toEqual([]);
176
190
  expect(state.everInjected).toEqual([{ slug: "slug-a", turn: 1 }]);
@@ -179,24 +193,12 @@ describe("activation-store", () => {
179
193
 
180
194
  test("preserves every other field on the state", () => {
181
195
  const state = buildState();
182
- const result = evictCompactedTurns(state, 0);
196
+ const result = clearEverInjected(state);
183
197
 
184
198
  expect(result.messageId).toBe(state.messageId);
185
199
  expect(result.state).toEqual(state.state);
186
200
  expect(result.currentTurn).toBe(state.currentTurn);
187
201
  expect(result.updatedAt).toBe(state.updatedAt);
188
202
  });
189
-
190
- test("evicts everything when upToTurn covers the entire list", () => {
191
- const state = buildState({
192
- everInjected: [
193
- { slug: "slug-a", turn: 1 },
194
- { slug: "slug-b", turn: 2 },
195
- ],
196
- });
197
-
198
- const result = evictCompactedTurns(state, 5);
199
- expect(result.everInjected).toEqual([]);
200
- });
201
203
  });
202
204
  });
@@ -0,0 +1,404 @@
1
+ /**
2
+ * Tests for `assistant/src/memory/v2/cli-command-store.ts`.
3
+ *
4
+ * Coverage matrix:
5
+ * - `seedV2CliCommandEntries` enumerates the program tree and upserts one
6
+ * `cli-commands/<name>` point per top-level subcommand.
7
+ * - It skips the auto-injected `help` builtin.
8
+ * - It calls `pruneSlugsWithPrefixExcept("cli-commands/", ...)` with the
9
+ * current id list under the `cli-command` kind so stale rows clear.
10
+ * - The legacy `kind` backfill runs once per process before pruning.
11
+ * - It populates the `entries` cache so `getCliCommandCapability` resolves
12
+ * both bare names and unified-collection slugs.
13
+ * - It swallows embedding-backend errors and leaves prior cache intact.
14
+ * - Stale in-flight results yield to the latest requested generation.
15
+ *
16
+ * Hermetic by design: the embedding backend, Qdrant module, and CLI program
17
+ * tree are module-mocked so the suite never touches a real backend or the
18
+ * full Commander wire-up.
19
+ */
20
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
21
+
22
+ import { Command } from "commander";
23
+
24
+ import { makeMockLogger } from "../../../__tests__/helpers/mock-logger.js";
25
+
26
+ mock.module("../../../util/logger.js", () => ({
27
+ getLogger: () => makeMockLogger(),
28
+ }));
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Programmable test state
32
+ // ---------------------------------------------------------------------------
33
+
34
+ interface UpsertCall {
35
+ slug: string;
36
+ dense: number[];
37
+ sparse: { indices: number[]; values: number[] };
38
+ updatedAt: number;
39
+ kind?: string;
40
+ }
41
+
42
+ interface PruneCall {
43
+ prefix: string;
44
+ activeSuffixes: readonly string[];
45
+ options?: { kind?: string };
46
+ }
47
+
48
+ interface BackfillCall {
49
+ prefix: string;
50
+ kind: string;
51
+ allowedSuffixes: ReadonlySet<string>;
52
+ }
53
+
54
+ interface CommandSpec {
55
+ name: string;
56
+ description: string;
57
+ helpText: string;
58
+ }
59
+
60
+ interface TestState {
61
+ commands: CommandSpec[];
62
+ embedThrows: Error | null;
63
+ embedReturn: number[][];
64
+ sparseReturn: { indices: number[]; values: number[] };
65
+ upsertCalls: UpsertCall[];
66
+ pruneCalls: PruneCall[];
67
+ upsertThrows: Error | null;
68
+ backfillCalls: BackfillCall[];
69
+ backfillThrows: Error | null;
70
+ callSequence: Array<"upsert" | "prune" | "backfill">;
71
+ }
72
+
73
+ const state: TestState = {
74
+ commands: [],
75
+ embedThrows: null,
76
+ embedReturn: [],
77
+ sparseReturn: { indices: [1], values: [1] },
78
+ upsertCalls: [],
79
+ pruneCalls: [],
80
+ upsertThrows: null,
81
+ backfillCalls: [],
82
+ backfillThrows: null,
83
+ callSequence: [],
84
+ };
85
+
86
+ mock.module("../../../config/loader.js", () => ({
87
+ getConfig: () => ({
88
+ memory: {
89
+ qdrant: { url: "http://127.0.0.1:6333", vectorSize: 3, onDisk: false },
90
+ v2: { bm25_k1: 1.2, bm25_b: 0.75 },
91
+ },
92
+ }),
93
+ }));
94
+
95
+ // Stub the CLI program tree so tests don't need to wire the entire Commander
96
+ // registration graph. Each test stages `state.commands`; the mock returns a
97
+ // fresh `Command` tree whose children carry the staged help text.
98
+ mock.module("../../../cli/program.js", () => ({
99
+ buildCliProgramTree: () => {
100
+ const program = new Command();
101
+ program.name("assistant");
102
+ for (const spec of state.commands) {
103
+ const child = program.command(spec.name).description(spec.description);
104
+ // helpInformation() is built from Commander state — stub it directly so
105
+ // the seeded content matches the test fixture verbatim.
106
+ child.helpInformation = () => spec.helpText;
107
+ }
108
+ return program;
109
+ },
110
+ }));
111
+
112
+ mock.module("../../embedding-backend.js", () => ({
113
+ embedWithBackend: async (_config: unknown, inputs: unknown[]) => {
114
+ if (state.embedThrows) throw state.embedThrows;
115
+ const vectors = state.embedReturn.length
116
+ ? state.embedReturn
117
+ : inputs.map(() => [0.1, 0.2, 0.3]);
118
+ return { provider: "local", model: "test-model", vectors };
119
+ },
120
+ generateSparseEmbedding: () => state.sparseReturn,
121
+ }));
122
+
123
+ mock.module("../sparse-bm25.js", () => ({
124
+ generateBm25DocEmbedding: () => state.sparseReturn,
125
+ getConceptPageCorpusStats: () => null,
126
+ }));
127
+
128
+ mock.module("../anisotropy.js", () => ({
129
+ applyCorrectionIfCalibrated: async (v: number[]) => v,
130
+ }));
131
+
132
+ mock.module("../page-index.js", () => ({
133
+ invalidatePageIndex: () => {},
134
+ }));
135
+
136
+ mock.module("../qdrant.js", () => ({
137
+ upsertConceptPageEmbedding: async (params: UpsertCall) => {
138
+ if (state.upsertThrows) throw state.upsertThrows;
139
+ state.callSequence.push("upsert");
140
+ state.upsertCalls.push(params);
141
+ },
142
+ pruneSlugsWithPrefixExcept: async (
143
+ prefix: string,
144
+ activeSuffixes: readonly string[],
145
+ options?: { kind?: string },
146
+ ) => {
147
+ state.callSequence.push("prune");
148
+ state.pruneCalls.push({ prefix, activeSuffixes, options });
149
+ },
150
+ backfillKindOnPointsWithPrefix: async (
151
+ prefix: string,
152
+ kind: string,
153
+ allowedSuffixes: ReadonlySet<string>,
154
+ ) => {
155
+ if (state.backfillThrows) throw state.backfillThrows;
156
+ state.callSequence.push("backfill");
157
+ state.backfillCalls.push({ prefix, kind, allowedSuffixes });
158
+ return 0;
159
+ },
160
+ }));
161
+
162
+ const {
163
+ seedV2CliCommandEntries,
164
+ getCliCommandCapability,
165
+ listCliCommandEntries,
166
+ isCliCommandSlug,
167
+ _resetCliCommandStoreForTests,
168
+ } = await import("../cli-command-store.js");
169
+
170
+ function resetState(): void {
171
+ state.commands = [];
172
+ state.embedThrows = null;
173
+ state.embedReturn = [];
174
+ state.sparseReturn = { indices: [1], values: [1] };
175
+ state.upsertCalls.length = 0;
176
+ state.pruneCalls.length = 0;
177
+ state.upsertThrows = null;
178
+ state.backfillCalls.length = 0;
179
+ state.backfillThrows = null;
180
+ state.callSequence.length = 0;
181
+ _resetCliCommandStoreForTests();
182
+ }
183
+
184
+ beforeEach(resetState);
185
+ afterEach(resetState);
186
+
187
+ describe("seedV2CliCommandEntries", () => {
188
+ test("upserts each top-level command under cli-commands/<name>", async () => {
189
+ state.commands = [
190
+ {
191
+ name: "attachment",
192
+ description: "Manage file attachments for conversations",
193
+ helpText: "Usage: assistant attachment ...",
194
+ },
195
+ {
196
+ name: "browser",
197
+ description: "Control the browser via the running assistant.",
198
+ helpText: "Usage: assistant browser ...",
199
+ },
200
+ ];
201
+ state.embedReturn = [
202
+ [0.1, 0.2, 0.3],
203
+ [0.4, 0.5, 0.6],
204
+ ];
205
+
206
+ await seedV2CliCommandEntries();
207
+
208
+ expect(state.upsertCalls).toHaveLength(2);
209
+ const slugs = state.upsertCalls.map((c) => c.slug).sort();
210
+ expect(slugs).toEqual(["cli-commands/attachment", "cli-commands/browser"]);
211
+ expect(state.upsertCalls.every((c) => c.kind === "cli-command")).toBe(true);
212
+ });
213
+
214
+ test("skips Commander's auto-injected `help` builtin", async () => {
215
+ state.commands = [
216
+ {
217
+ name: "attachment",
218
+ description: "Manage file attachments",
219
+ helpText: "Usage: assistant attachment ...",
220
+ },
221
+ {
222
+ name: "help",
223
+ description: "display help for command",
224
+ helpText: "Usage: assistant help ...",
225
+ },
226
+ ];
227
+ state.embedReturn = [[0.1, 0.2, 0.3]];
228
+
229
+ await seedV2CliCommandEntries();
230
+
231
+ expect(state.upsertCalls.map((c) => c.slug)).toEqual([
232
+ "cli-commands/attachment",
233
+ ]);
234
+ });
235
+
236
+ test("calls pruneSlugsWithPrefixExcept with kind: 'cli-command'", async () => {
237
+ state.commands = [
238
+ {
239
+ name: "config",
240
+ description: "Manage configuration",
241
+ helpText: "...",
242
+ },
243
+ ];
244
+ state.embedReturn = [[0.1, 0.2, 0.3]];
245
+
246
+ await seedV2CliCommandEntries();
247
+
248
+ expect(state.pruneCalls).toHaveLength(1);
249
+ expect(state.pruneCalls[0].prefix).toBe("cli-commands/");
250
+ expect([...state.pruneCalls[0].activeSuffixes]).toEqual(["config"]);
251
+ expect(state.pruneCalls[0].options).toEqual({ kind: "cli-command" });
252
+ });
253
+
254
+ test("runs backfill before prune so legacy kindless points are reachable", async () => {
255
+ state.commands = [
256
+ {
257
+ name: "config",
258
+ description: "Manage configuration",
259
+ helpText: "...",
260
+ },
261
+ ];
262
+ state.embedReturn = [[0.1, 0.2, 0.3]];
263
+
264
+ await seedV2CliCommandEntries();
265
+
266
+ expect(state.backfillCalls).toHaveLength(1);
267
+ expect(state.backfillCalls[0].prefix).toBe("cli-commands/");
268
+ expect(state.backfillCalls[0].kind).toBe("cli-command");
269
+ expect([...state.backfillCalls[0].allowedSuffixes]).toEqual(["config"]);
270
+ expect(state.callSequence.filter((s) => s !== "upsert")).toEqual([
271
+ "backfill",
272
+ "prune",
273
+ ]);
274
+ });
275
+
276
+ test("backfill only runs once across repeated seeds in the same process", async () => {
277
+ state.commands = [
278
+ { name: "config", description: "Manage configuration", helpText: "..." },
279
+ ];
280
+ state.embedReturn = [[0.1, 0.2, 0.3]];
281
+
282
+ await seedV2CliCommandEntries();
283
+ state.embedReturn = [[0.4, 0.5, 0.6]];
284
+ await seedV2CliCommandEntries();
285
+
286
+ expect(state.backfillCalls).toHaveLength(1);
287
+ expect(state.pruneCalls).toHaveLength(2);
288
+ });
289
+
290
+ test("populates the entries cache so getCliCommandCapability resolves both forms", async () => {
291
+ state.commands = [
292
+ {
293
+ name: "attachment",
294
+ description: "Manage file attachments",
295
+ helpText: "Usage: assistant attachment ...",
296
+ },
297
+ ];
298
+ state.embedReturn = [[0.1, 0.2, 0.3]];
299
+
300
+ expect(getCliCommandCapability("attachment")).toBeNull();
301
+
302
+ await seedV2CliCommandEntries();
303
+
304
+ const byId = getCliCommandCapability("attachment");
305
+ const bySlug = getCliCommandCapability("cli-commands/attachment");
306
+ expect(byId).not.toBeNull();
307
+ expect(byId?.id).toBe("attachment");
308
+ expect(byId?.description).toBe("Manage file attachments");
309
+ expect(byId?.content).toContain('"assistant attachment"');
310
+ expect(byId?.content).toContain("Manage file attachments");
311
+ expect(byId?.content).toContain("Usage: assistant attachment ...");
312
+ expect(bySlug).toEqual(byId);
313
+
314
+ expect(getCliCommandCapability("unknown-command")).toBeNull();
315
+ expect(getCliCommandCapability("cli-commands/unknown")).toBeNull();
316
+ });
317
+
318
+ test("isCliCommandSlug recognises the prefix", () => {
319
+ expect(isCliCommandSlug("cli-commands/attachment")).toBe(true);
320
+ expect(isCliCommandSlug("skills/example")).toBe(false);
321
+ expect(isCliCommandSlug("alice")).toBe(false);
322
+ });
323
+
324
+ test("listCliCommandEntries returns frozen sorted snapshots", async () => {
325
+ state.commands = [
326
+ { name: "browser", description: "Control browser", helpText: "..." },
327
+ { name: "attachment", description: "Manage attachments", helpText: "." },
328
+ ];
329
+ state.embedReturn = [
330
+ [0.1, 0.2, 0.3],
331
+ [0.4, 0.5, 0.6],
332
+ ];
333
+
334
+ await seedV2CliCommandEntries();
335
+
336
+ const snapshot = listCliCommandEntries();
337
+ expect(snapshot.map((e) => e.id)).toEqual(["attachment", "browser"]);
338
+ expect(Object.isFrozen(snapshot[0])).toBe(true);
339
+ });
340
+
341
+ test("swallows embedWithBackend errors and leaves prior cache intact", async () => {
342
+ state.commands = [
343
+ { name: "config", description: "Manage configuration", helpText: "..." },
344
+ ];
345
+ state.embedReturn = [[0.1, 0.2, 0.3]];
346
+
347
+ await seedV2CliCommandEntries();
348
+ const before = getCliCommandCapability("config");
349
+ expect(before).not.toBeNull();
350
+
351
+ state.upsertCalls.length = 0;
352
+ state.pruneCalls.length = 0;
353
+ state.embedThrows = new Error("backend exploded");
354
+
355
+ await expect(seedV2CliCommandEntries()).resolves.toBeUndefined();
356
+
357
+ expect(state.upsertCalls).toHaveLength(0);
358
+ expect(state.pruneCalls).toHaveLength(0);
359
+ const after = getCliCommandCapability("config");
360
+ expect(after).toEqual(before);
361
+ });
362
+
363
+ test("propagates embedding errors when throwOnError is set", async () => {
364
+ state.commands = [
365
+ { name: "config", description: "Manage configuration", helpText: "..." },
366
+ ];
367
+ state.embedThrows = new Error("backend exploded");
368
+
369
+ await expect(
370
+ seedV2CliCommandEntries({ throwOnError: true }),
371
+ ).rejects.toThrow("backend exploded");
372
+ });
373
+
374
+ test("skips stale in-flight seed results when a newer refresh is requested", async () => {
375
+ state.commands = [
376
+ { name: "config", description: "Manage configuration", helpText: "..." },
377
+ ];
378
+ state.embedReturn = [[0.1, 0.2, 0.3]];
379
+
380
+ const firstSeed = seedV2CliCommandEntries();
381
+ state.commands = [
382
+ { name: "browser", description: "Control browser", helpText: "..." },
383
+ ];
384
+ const secondSeed = seedV2CliCommandEntries();
385
+
386
+ await Promise.all([firstSeed, secondSeed]);
387
+
388
+ expect(state.upsertCalls.map((c) => c.slug)).toEqual([
389
+ "cli-commands/browser",
390
+ ]);
391
+ expect(getCliCommandCapability("config")).toBeNull();
392
+ expect(getCliCommandCapability("browser")).not.toBeNull();
393
+ });
394
+
395
+ test("empty command set still calls prune to clear stale rows", async () => {
396
+ state.commands = [];
397
+
398
+ await seedV2CliCommandEntries();
399
+
400
+ expect(state.upsertCalls).toHaveLength(0);
401
+ expect(state.pruneCalls).toHaveLength(1);
402
+ expect(state.pruneCalls[0].activeSuffixes).toEqual([]);
403
+ });
404
+ });
@@ -2,6 +2,7 @@
2
2
  * Tests for `assistant/src/memory/v2/frontmatter-sweep.ts`.
3
3
  *
4
4
  * Coverage:
5
+ * - v2 disabled → returns early, no warns, no throw.
5
6
  * - Empty workspace → no warns, no throw.
6
7
  * - One bad page (unknown frontmatter key) → exactly one warn carrying
7
8
  * `errCode: "unrecognized_keys"` and the offending slug.
@@ -13,6 +14,8 @@ import { tmpdir } from "node:os";
13
14
  import { join } from "node:path";
14
15
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
15
16
 
17
+ import type { AssistantConfig } from "../../../config/schema.js";
18
+
16
19
  const warnCalls: Array<{ data: Record<string, unknown>; msg: string }> = [];
17
20
  const recordingLogger = {
18
21
  warn: (data: Record<string, unknown>, msg: string) => {
@@ -32,6 +35,17 @@ mock.module("../../../util/logger.js", () => ({
32
35
 
33
36
  const { sweepConceptPageFrontmatter } = await import("../frontmatter-sweep.js");
34
37
 
38
+ /** Minimal config shape the sweep touches; cast to AssistantConfig at the boundary. */
39
+ function makeConfig(v2Enabled: boolean): AssistantConfig {
40
+ return {
41
+ memory: {
42
+ v2: { enabled: v2Enabled },
43
+ },
44
+ } as unknown as AssistantConfig;
45
+ }
46
+
47
+ const v2On = makeConfig(true);
48
+
35
49
  function makeWorkspace(pages: Record<string, string>): string {
36
50
  const dir = mkdtempSync(join(tmpdir(), "frontmatter-sweep-"));
37
51
  const conceptsDir = join(dir, "memory", "concepts");
@@ -55,10 +69,17 @@ describe("sweepConceptPageFrontmatter", () => {
55
69
  warnCalls.length = 0;
56
70
  });
57
71
 
72
+ test("does nothing when memory.v2.enabled is false", async () => {
73
+ // Pass a non-existent dir to prove the gate short-circuits BEFORE any I/O:
74
+ // if the body ran, listPages would surface a warn for the unreadable path.
75
+ await sweepConceptPageFrontmatter(makeConfig(false), "/nonexistent/path");
76
+ expect(warnCalls).toHaveLength(0);
77
+ });
78
+
58
79
  test("empty workspace: no warns, no throw", async () => {
59
80
  const dir = makeWorkspace({});
60
81
  try {
61
- await sweepConceptPageFrontmatter(dir);
82
+ await sweepConceptPageFrontmatter(v2On, dir);
62
83
  expect(warnCalls).toHaveLength(0);
63
84
  } finally {
64
85
  rmSync(dir, { recursive: true, force: true });
@@ -68,7 +89,7 @@ describe("sweepConceptPageFrontmatter", () => {
68
89
  test("one bad page emits exactly one unrecognized_keys warn", async () => {
69
90
  const dir = makeWorkspace({ "bad-one": badPage });
70
91
  try {
71
- await sweepConceptPageFrontmatter(dir);
92
+ await sweepConceptPageFrontmatter(v2On, dir);
72
93
  expect(warnCalls).toHaveLength(1);
73
94
  expect(warnCalls[0].data.slug).toBe("bad-one");
74
95
  expect(warnCalls[0].data.errCode).toBe("unrecognized_keys");
@@ -85,7 +106,7 @@ describe("sweepConceptPageFrontmatter", () => {
85
106
  "bad-b": badPage,
86
107
  });
87
108
  try {
88
- await sweepConceptPageFrontmatter(dir);
109
+ await sweepConceptPageFrontmatter(v2On, dir);
89
110
  const slugs = warnCalls.map((c) => c.data.slug).sort();
90
111
  expect(slugs).toEqual(["bad-a", "bad-b"]);
91
112
  for (const call of warnCalls) {
@@ -101,7 +122,7 @@ describe("sweepConceptPageFrontmatter", () => {
101
122
  mangled: `---\nedges: [unterminated\n---\nbody\n`,
102
123
  });
103
124
  try {
104
- await sweepConceptPageFrontmatter(dir);
125
+ await sweepConceptPageFrontmatter(v2On, dir);
105
126
  expect(warnCalls.length).toBeGreaterThanOrEqual(1);
106
127
  expect(warnCalls.some((c) => c.data.slug === "mangled")).toBe(true);
107
128
  } finally {