@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
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Tests for `assistant/src/memory/v2/injection-events.ts` and its sibling
3
+ * migration `256-memory-v2-injection-events.ts`.
4
+ *
5
+ * Coverage matrix:
6
+ * - Migration creates the table + both indexes; safe to re-run.
7
+ * - Backfill replays router-sourced concepts from memory_v2_activation_logs
8
+ * and is idempotent on a forced re-run with cleared checkpoint.
9
+ * - Backfill is a no-op when the activation-logs table doesn't exist
10
+ * (pre-234 DB).
11
+ * - recordInjectionEvents appends one row per slug per call; empty list
12
+ * is a no-op.
13
+ * - computeInjectionScore matches the closed-form decay at known deltas
14
+ * (0d ≈ 1, 3d ≈ 0.5, 6d ≈ 0.25) and sums multiple events linearly.
15
+ * - computeInjectionScores returns the same per-slug values in batch and
16
+ * omits slugs with no events.
17
+ *
18
+ * Uses an in-memory bun:sqlite database — no real workspace DB.
19
+ */
20
+
21
+ import { Database } from "bun:sqlite";
22
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
23
+
24
+ import { drizzle } from "drizzle-orm/bun-sqlite";
25
+
26
+ import { makeMockLogger } from "../../../__tests__/helpers/mock-logger.js";
27
+
28
+ mock.module("../../../util/logger.js", () => ({
29
+ getLogger: () => makeMockLogger(),
30
+ }));
31
+
32
+ import type { DrizzleDb } from "../../db-connection.js";
33
+ import { getSqliteFrom } from "../../db-connection.js";
34
+ import { migrateMemoryV2ActivationLogs } from "../../migrations/234-memory-v2-activation-logs.js";
35
+ import {
36
+ downMemoryV2InjectionEvents,
37
+ migrateMemoryV2InjectionEvents,
38
+ } from "../../migrations/256-memory-v2-injection-events.js";
39
+ import * as schema from "../../schema.js";
40
+ import {
41
+ computeInjectionScore,
42
+ computeInjectionScores,
43
+ INJECTION_SCORE_HALF_LIFE_MS,
44
+ recordInjectionEvents,
45
+ } from "../injection-events.js";
46
+
47
+ // memory_checkpoints is required by withCrashRecovery and is normally
48
+ // created by an early core migration. Stand it up by hand so we can run
49
+ // the v2 migrations in isolation against a fresh in-memory DB.
50
+ const CHECKPOINTS_DDL = /*sql*/ `
51
+ CREATE TABLE memory_checkpoints (
52
+ key TEXT PRIMARY KEY,
53
+ value TEXT NOT NULL,
54
+ updated_at INTEGER NOT NULL
55
+ )
56
+ `;
57
+
58
+ let sqlite: Database;
59
+ let database: DrizzleDb;
60
+
61
+ beforeEach(() => {
62
+ sqlite = new Database(":memory:");
63
+ database = drizzle(sqlite, { schema });
64
+ getSqliteFrom(database).exec(CHECKPOINTS_DDL);
65
+ });
66
+
67
+ afterEach(() => {
68
+ sqlite.close();
69
+ });
70
+
71
+ function insertActivationLog(
72
+ rawDb: Database,
73
+ args: {
74
+ id: string;
75
+ concepts: Array<{ slug: string; source: string; status?: string }>;
76
+ createdAt: number;
77
+ },
78
+ ): void {
79
+ rawDb
80
+ .prepare(
81
+ `INSERT INTO memory_v2_activation_logs (
82
+ id, conversation_id, message_id, turn, mode,
83
+ concepts_json, skills_json, config_json, created_at
84
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
85
+ )
86
+ .run(
87
+ args.id,
88
+ "conv-1",
89
+ `msg-${args.id}`,
90
+ 1,
91
+ "router",
92
+ JSON.stringify(args.concepts),
93
+ "[]",
94
+ "{}",
95
+ args.createdAt,
96
+ );
97
+ }
98
+
99
+ describe("migrateMemoryV2InjectionEvents", () => {
100
+ test("creates table and both indexes; safe to re-run", () => {
101
+ migrateMemoryV2InjectionEvents(database);
102
+ migrateMemoryV2InjectionEvents(database);
103
+
104
+ const raw = getSqliteFrom(database);
105
+ const table = raw
106
+ .query(
107
+ `SELECT name FROM sqlite_master WHERE type='table' AND name='memory_v2_injection_events'`,
108
+ )
109
+ .get();
110
+ expect(table).toBeTruthy();
111
+
112
+ const indexNames = new Set(
113
+ (
114
+ raw
115
+ .query(
116
+ `SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='memory_v2_injection_events'`,
117
+ )
118
+ .all() as Array<{ name: string }>
119
+ ).map((r) => r.name),
120
+ );
121
+ expect(indexNames.has("idx_memory_v2_injection_events_slug_time")).toBe(
122
+ true,
123
+ );
124
+ expect(indexNames.has("idx_memory_v2_injection_events_time")).toBe(true);
125
+ });
126
+
127
+ test("backfill replays router-sourced concepts and ignores carry_over", () => {
128
+ migrateMemoryV2ActivationLogs(database);
129
+ const raw = getSqliteFrom(database);
130
+ insertActivationLog(raw, {
131
+ id: "log-1",
132
+ concepts: [
133
+ { slug: "alice", source: "router", status: "injected" },
134
+ { slug: "bob", source: "router", status: "in_context" },
135
+ { slug: "ghost", source: "carry_over", status: "not_injected" },
136
+ ],
137
+ createdAt: 1_000_000,
138
+ });
139
+ insertActivationLog(raw, {
140
+ id: "log-2",
141
+ concepts: [{ slug: "alice", source: "router", status: "injected" }],
142
+ createdAt: 2_000_000,
143
+ });
144
+
145
+ migrateMemoryV2InjectionEvents(database);
146
+
147
+ const rows = raw
148
+ .query(
149
+ `SELECT slug, injected_at FROM memory_v2_injection_events ORDER BY injected_at, slug`,
150
+ )
151
+ .all() as Array<{ slug: string; injected_at: number }>;
152
+ expect(rows).toEqual([
153
+ { slug: "alice", injected_at: 1_000_000 },
154
+ { slug: "bob", injected_at: 1_000_000 },
155
+ { slug: "alice", injected_at: 2_000_000 },
156
+ ]);
157
+ });
158
+
159
+ test("backfill is a no-op when memory_v2_activation_logs is absent", () => {
160
+ // No activation-logs migration applied first.
161
+ expect(() => migrateMemoryV2InjectionEvents(database)).not.toThrow();
162
+ const { n } = getSqliteFrom(database)
163
+ .query(`SELECT COUNT(*) as n FROM memory_v2_injection_events`)
164
+ .get() as { n: number };
165
+ expect(n).toBe(0);
166
+ });
167
+
168
+ test("forced re-run does not double-insert existing events", () => {
169
+ migrateMemoryV2ActivationLogs(database);
170
+ const raw = getSqliteFrom(database);
171
+ insertActivationLog(raw, {
172
+ id: "log-1",
173
+ concepts: [{ slug: "alice", source: "router", status: "injected" }],
174
+ createdAt: 1_000_000,
175
+ });
176
+
177
+ migrateMemoryV2InjectionEvents(database);
178
+ // Simulate someone manually clearing the checkpoint — the in-table
179
+ // guard should still prevent re-backfill.
180
+ raw
181
+ .prepare(
182
+ `DELETE FROM memory_checkpoints WHERE key = 'migration_memory_v2_injection_events_v1'`,
183
+ )
184
+ .run();
185
+ migrateMemoryV2InjectionEvents(database);
186
+
187
+ const { n } = raw
188
+ .query(`SELECT COUNT(*) as n FROM memory_v2_injection_events`)
189
+ .get() as { n: number };
190
+ expect(n).toBe(1);
191
+ });
192
+
193
+ test("downMemoryV2InjectionEvents drops the table", () => {
194
+ migrateMemoryV2InjectionEvents(database);
195
+ downMemoryV2InjectionEvents(database);
196
+ const table = getSqliteFrom(database)
197
+ .query(
198
+ `SELECT name FROM sqlite_master WHERE type='table' AND name='memory_v2_injection_events'`,
199
+ )
200
+ .get();
201
+ expect(table).toBeFalsy();
202
+ });
203
+ });
204
+
205
+ describe("recordInjectionEvents", () => {
206
+ beforeEach(() => {
207
+ migrateMemoryV2InjectionEvents(database);
208
+ });
209
+
210
+ test("appends one row per slug at the same timestamp", () => {
211
+ const t = 1_000_000;
212
+ recordInjectionEvents(database, ["alice", "bob", "alice"], t);
213
+ const rows = getSqliteFrom(database)
214
+ .query(
215
+ `SELECT slug, injected_at FROM memory_v2_injection_events ORDER BY id`,
216
+ )
217
+ .all();
218
+ expect(rows).toEqual([
219
+ { slug: "alice", injected_at: t },
220
+ { slug: "bob", injected_at: t },
221
+ { slug: "alice", injected_at: t },
222
+ ]);
223
+ });
224
+
225
+ test("empty list is a no-op", () => {
226
+ recordInjectionEvents(database, [], 1_000_000);
227
+ const { n } = getSqliteFrom(database)
228
+ .query(`SELECT COUNT(*) as n FROM memory_v2_injection_events`)
229
+ .get() as { n: number };
230
+ expect(n).toBe(0);
231
+ });
232
+ });
233
+
234
+ describe("computeInjectionScore", () => {
235
+ beforeEach(() => {
236
+ migrateMemoryV2InjectionEvents(database);
237
+ });
238
+
239
+ test("returns 0 for a slug with no events", () => {
240
+ expect(computeInjectionScore(database, "missing", Date.now())).toBe(0);
241
+ });
242
+
243
+ test("single event 0 days ago → score ≈ 1", () => {
244
+ const now = 10_000_000_000;
245
+ recordInjectionEvents(database, ["alice"], now);
246
+ expect(computeInjectionScore(database, "alice", now)).toBeCloseTo(1, 5);
247
+ });
248
+
249
+ test("single event 3 days (one half-life) ago → score ≈ 0.5", () => {
250
+ const now = 10_000_000_000;
251
+ recordInjectionEvents(
252
+ database,
253
+ ["alice"],
254
+ now - INJECTION_SCORE_HALF_LIFE_MS,
255
+ );
256
+ expect(computeInjectionScore(database, "alice", now)).toBeCloseTo(0.5, 5);
257
+ });
258
+
259
+ test("single event 6 days (two half-lives) ago → score ≈ 0.25", () => {
260
+ const now = 10_000_000_000;
261
+ recordInjectionEvents(
262
+ database,
263
+ ["alice"],
264
+ now - 2 * INJECTION_SCORE_HALF_LIFE_MS,
265
+ );
266
+ expect(computeInjectionScore(database, "alice", now)).toBeCloseTo(0.25, 5);
267
+ });
268
+
269
+ test("multiple events sum independently", () => {
270
+ const now = 10_000_000_000;
271
+ recordInjectionEvents(database, ["alice"], now);
272
+ recordInjectionEvents(
273
+ database,
274
+ ["alice"],
275
+ now - INJECTION_SCORE_HALF_LIFE_MS,
276
+ );
277
+ recordInjectionEvents(
278
+ database,
279
+ ["alice"],
280
+ now - 2 * INJECTION_SCORE_HALF_LIFE_MS,
281
+ );
282
+ expect(computeInjectionScore(database, "alice", now)).toBeCloseTo(1.75, 5);
283
+ });
284
+ });
285
+
286
+ describe("computeInjectionScores", () => {
287
+ beforeEach(() => {
288
+ migrateMemoryV2InjectionEvents(database);
289
+ });
290
+
291
+ test("returns the same per-slug values as the single-slug helper", () => {
292
+ const now = 10_000_000_000;
293
+ recordInjectionEvents(database, ["alice", "bob"], now);
294
+ recordInjectionEvents(
295
+ database,
296
+ ["alice"],
297
+ now - INJECTION_SCORE_HALF_LIFE_MS,
298
+ );
299
+
300
+ const scores = computeInjectionScores(
301
+ database,
302
+ ["alice", "bob", "ghost"],
303
+ now,
304
+ );
305
+ expect(scores.get("alice")).toBeCloseTo(1.5, 5);
306
+ expect(scores.get("bob")).toBeCloseTo(1, 5);
307
+ // ghost has no events — omitted from the result, not present as 0.
308
+ expect(scores.has("ghost")).toBe(false);
309
+ expect(scores.get("alice")).toBeCloseTo(
310
+ computeInjectionScore(database, "alice", now),
311
+ 5,
312
+ );
313
+ });
314
+
315
+ test("empty slug list returns empty map", () => {
316
+ expect(computeInjectionScores(database, [], Date.now()).size).toBe(0);
317
+ });
318
+ });
@@ -161,6 +161,38 @@ mock.module("../skill-store.js", () => ({
161
161
  listSkillEntries: () => Array.from(skillState.entries.values()),
162
162
  }));
163
163
 
164
+ // ---------------------------------------------------------------------------
165
+ // CLI-command-store mock
166
+ // ---------------------------------------------------------------------------
167
+ //
168
+ // Mirrors the skill-store mock. CLI subcommand synthetic entries flow through
169
+ // the unified pipeline under the `cli-commands/<name>` slug prefix and render
170
+ // under `### CLI Commands You Can Use`. Tests stage `cliCommandState.entries`
171
+ // and rely on `stageTurn` plumbing to land slugs in the candidate set.
172
+
173
+ interface CliCommandEntryStub {
174
+ id: string;
175
+ description: string;
176
+ content: string;
177
+ }
178
+
179
+ const cliCommandState = {
180
+ entries: new Map<string, CliCommandEntryStub>(),
181
+ };
182
+
183
+ mock.module("../cli-command-store.js", () => ({
184
+ getCliCommandCapability: (idOrSlug: string) => {
185
+ const id = idOrSlug.startsWith("cli-commands/")
186
+ ? idOrSlug.slice("cli-commands/".length)
187
+ : idOrSlug;
188
+ return cliCommandState.entries.get(id) ?? null;
189
+ },
190
+ isCliCommandSlug: (slug: string) => slug.startsWith("cli-commands/"),
191
+ CLI_COMMAND_SLUG_PREFIX: "cli-commands/",
192
+ cliCommandSlugFor: (name: string) => `cli-commands/${name}`,
193
+ listCliCommandEntries: () => Array.from(cliCommandState.entries.values()),
194
+ }));
195
+
164
196
  // ---------------------------------------------------------------------------
165
197
  // Activation-log store mock
166
198
  // ---------------------------------------------------------------------------
@@ -228,6 +260,8 @@ mock.module("../page-store.js", () => ({
228
260
  interface RouterResultStub {
229
261
  selectedSlugs: string[];
230
262
  failureReason: string | null;
263
+ /** Tier provenance per slug. Defaults to `tier3:0` for any selected slug. */
264
+ sourceBySlug?: Map<string, string>;
231
265
  }
232
266
 
233
267
  const routerState = {
@@ -238,12 +272,20 @@ const routerState = {
238
272
  mock.module("../router.js", () => ({
239
273
  runRouter: async () => {
240
274
  routerState.callCount++;
241
- return (
242
- routerState.nextResult ?? {
243
- selectedSlugs: [],
244
- failureReason: null,
245
- }
246
- );
275
+ const result = routerState.nextResult ?? {
276
+ selectedSlugs: [],
277
+ failureReason: null,
278
+ };
279
+ // Synthesize a default sourceBySlug for stubs that don't set one — pre-
280
+ // tier-provenance tests stage `selectedSlugs` only and expect every pick
281
+ // to flow through as a router selection. Treating them as `tier3:0` is
282
+ // the closest equivalent under the new model.
283
+ if (!result.sourceBySlug) {
284
+ const map = new Map<string, string>();
285
+ for (const slug of result.selectedSlugs) map.set(slug, "tier3:0");
286
+ result.sourceBySlug = map;
287
+ }
288
+ return result;
247
289
  },
248
290
  }));
249
291
 
@@ -356,8 +398,10 @@ import type { SkillEntry } from "../types.js";
356
398
  const { getSqliteFrom } = await import("../../db-connection.js");
357
399
  const { migrateActivationState } =
358
400
  await import("../../migrations/232-activation-state.js");
401
+ const { migrateMemoryV2InjectionEvents } =
402
+ await import("../../migrations/256-memory-v2-injection-events.js");
359
403
  const schema = await import("../../schema.js");
360
- const { evictCompactedTurns, hydrate, save } =
404
+ const { clearEverInjected, hydrate, save } =
361
405
  await import("../activation-store.js");
362
406
  const { injectMemoryV2Block } = await import("../injection.js");
363
407
  const { _resetMemoryV2QdrantForTests } = await import("../qdrant.js");
@@ -377,6 +421,7 @@ function createTestDb(): DrizzleDb {
377
421
  )
378
422
  `);
379
423
  migrateActivationState(db);
424
+ migrateMemoryV2InjectionEvents(db);
380
425
  return db;
381
426
  }
382
427
 
@@ -484,6 +529,7 @@ function resetState(): void {
484
529
  state.queryResponses.dense.length = 0;
485
530
  state.queryResponses.sparse.length = 0;
486
531
  skillState.entries.clear();
532
+ cliCommandState.entries.clear();
487
533
  telemetryState.recordCalls.length = 0;
488
534
  telemetryState.recordShouldThrow = false;
489
535
  pageStoreState.failingSlugs.clear();
@@ -503,6 +549,13 @@ function stageSkills(entries: SkillEntry[]): void {
503
549
  }
504
550
  }
505
551
 
552
+ /** Stage cli-command-store cache entries for the upcoming render. */
553
+ function stageCliCommands(entries: CliCommandEntryStub[]): void {
554
+ for (const entry of entries) {
555
+ cliCommandState.entries.set(entry.id, entry);
556
+ }
557
+ }
558
+
506
559
  let db: DrizzleDb;
507
560
  beforeEach(() => {
508
561
  db = createTestDb();
@@ -653,10 +706,10 @@ describe("injectMemoryV2Block", () => {
653
706
  config: makeConfig(),
654
707
  });
655
708
 
656
- // Simulate compaction: drop all everInjected entries with turn <= 1.
709
+ // Simulate compaction: clear the entire everInjected list.
657
710
  const beforeEvict = await hydrate(db, "conv-1");
658
711
  expect(beforeEvict).not.toBeNull();
659
- const afterEvict = evictCompactedTurns(beforeEvict!, 1);
712
+ const afterEvict = clearEverInjected(beforeEvict!);
660
713
  expect(afterEvict.everInjected).toEqual([]);
661
714
  await save(db, "conv-1", afterEvict);
662
715
 
@@ -703,7 +756,7 @@ describe("injectMemoryV2Block", () => {
703
756
 
704
757
  expect(result.block).not.toBeNull();
705
758
  expect(result.block).toContain(
706
- "**CRITICAL:** These are page summaries. Read the page file if it looks relevant.",
759
+ 'Use `file_read("memory/concepts/path/to/file.md")` to read the full pages for any of the injected memory summaries you want more information on.',
707
760
  );
708
761
  expect(result.block).toContain(
709
762
  "# memory/concepts/summarized-page.md\nA short prose description",
@@ -736,11 +789,13 @@ describe("injectMemoryV2Block", () => {
736
789
  });
737
790
 
738
791
  expect(result.block).not.toBeNull();
739
- // CRITICAL header appears exactly once.
740
- const criticalCount = (
741
- result.block!.match(/\*\*CRITICAL:\*\* These are page summaries\./g) ?? []
792
+ // Header appears exactly once.
793
+ const headerCount = (
794
+ result.block!.match(
795
+ /Use `file_read\("memory\/concepts\/path\/to\/file\.md"\)` to read/g,
796
+ ) ?? []
742
797
  ).length;
743
- expect(criticalCount).toBe(1);
798
+ expect(headerCount).toBe(1);
744
799
  // summarized-page → short form (path + summary, no body, no frontmatter).
745
800
  expect(result.block).toContain("# memory/concepts/summarized-page.md\nA");
746
801
  expect(result.block).not.toContain("Long-form body content");
@@ -1060,6 +1115,153 @@ describe("injectMemoryV2Block", () => {
1060
1115
  expect(result.block).toBeNull();
1061
1116
  });
1062
1117
 
1118
+ // ---------------------------------------------------------------------------
1119
+ // CLI-command synthetic entries — same unified-pool plumbing as skills.
1120
+ // ---------------------------------------------------------------------------
1121
+
1122
+ test("renders a retrieved cli-commands/<name> slug under CLI Commands You Can Use", async () => {
1123
+ stageTurn([{ slug: "cli-commands/attachment", denseScore: 0.9 }]);
1124
+ stageCliCommands([
1125
+ {
1126
+ id: "attachment",
1127
+ description: "Manage file attachments for conversations",
1128
+ content: 'The "assistant attachment" CLI command is available...',
1129
+ },
1130
+ ]);
1131
+
1132
+ const result = await injectMemoryV2Block({
1133
+ database: db,
1134
+ conversationId: "conv-1",
1135
+ currentTurn: 1,
1136
+ userMessage: "How do I register a video?",
1137
+ assistantMessage: "",
1138
+ nowText: "Now",
1139
+ messageId: "msg-1",
1140
+ config: makeConfig(),
1141
+ });
1142
+
1143
+ expect(result.toInject).toEqual(["cli-commands/attachment"]);
1144
+ expect(result.block).not.toBeNull();
1145
+ const headerIdx = result.block!.indexOf("### CLI Commands You Can Use");
1146
+ const lineIdx = result.block!.indexOf(
1147
+ "- `assistant attachment`: Manage file attachments for conversations",
1148
+ );
1149
+ expect(headerIdx).toBeGreaterThan(-1);
1150
+ expect(lineIdx).toBeGreaterThan(headerIdx);
1151
+ });
1152
+
1153
+ test("renders concepts, skills, then cli-commands in that order in mixed blocks", async () => {
1154
+ stageTurn([
1155
+ { slug: "alice-vscode", denseScore: 0.95 },
1156
+ { slug: "skills/example-skill-a", denseScore: 0.85 },
1157
+ { slug: "cli-commands/config", denseScore: 0.75 },
1158
+ ]);
1159
+ stageSkills([
1160
+ {
1161
+ id: "example-skill-a",
1162
+ content:
1163
+ 'The "Example Skill A" skill (example-skill-a) is available. Helps with examples.',
1164
+ },
1165
+ ]);
1166
+ stageCliCommands([
1167
+ {
1168
+ id: "config",
1169
+ description: "Manage configuration",
1170
+ content: 'The "assistant config" CLI command is available...',
1171
+ },
1172
+ ]);
1173
+
1174
+ const result = await injectMemoryV2Block({
1175
+ database: db,
1176
+ conversationId: "conv-1",
1177
+ currentTurn: 1,
1178
+ userMessage: "Help me",
1179
+ assistantMessage: "",
1180
+ nowText: "Now",
1181
+ messageId: "msg-1",
1182
+ config: makeConfig(),
1183
+ });
1184
+
1185
+ expect(new Set(result.toInject)).toEqual(
1186
+ new Set([
1187
+ "alice-vscode",
1188
+ "skills/example-skill-a",
1189
+ "cli-commands/config",
1190
+ ]),
1191
+ );
1192
+ const conceptIdx = result.block!.indexOf(
1193
+ "# memory/concepts/alice-vscode.md",
1194
+ );
1195
+ const skillsIdx = result.block!.indexOf("### Skills You Can Use");
1196
+ const cliIdx = result.block!.indexOf("### CLI Commands You Can Use");
1197
+ expect(conceptIdx).toBeGreaterThan(-1);
1198
+ expect(skillsIdx).toBeGreaterThan(conceptIdx);
1199
+ expect(cliIdx).toBeGreaterThan(skillsIdx);
1200
+ });
1201
+
1202
+ test("cli-command slugs whose entry is missing from the cache are dropped silently", async () => {
1203
+ stageTurn([{ slug: "cli-commands/missing-command", denseScore: 0.9 }]);
1204
+
1205
+ const result = await injectMemoryV2Block({
1206
+ database: db,
1207
+ conversationId: "conv-1",
1208
+ currentTurn: 1,
1209
+ userMessage: "anything",
1210
+ assistantMessage: "",
1211
+ nowText: "Now",
1212
+ messageId: "msg-1",
1213
+ config: makeConfig(),
1214
+ });
1215
+
1216
+ expect(result.toInject).toEqual([]);
1217
+ expect(result.block).toBeNull();
1218
+
1219
+ const persisted = await hydrate(db, "conv-1");
1220
+ expect(persisted!.everInjected).toEqual([]);
1221
+ });
1222
+
1223
+ test("cli-commands participate in everInjected so they dedupe across turns", async () => {
1224
+ const entry = {
1225
+ id: "config",
1226
+ description: "Manage configuration",
1227
+ content: 'The "assistant config" CLI command is available...',
1228
+ };
1229
+ stageTurn([{ slug: "cli-commands/config", denseScore: 0.9 }]);
1230
+ stageCliCommands([entry]);
1231
+ const result1 = await injectMemoryV2Block({
1232
+ database: db,
1233
+ conversationId: "conv-1",
1234
+ currentTurn: 1,
1235
+ userMessage: "config",
1236
+ assistantMessage: "",
1237
+ nowText: "Now",
1238
+ messageId: "msg-1",
1239
+ config: makeConfig(),
1240
+ });
1241
+ expect(result1.toInject).toEqual(["cli-commands/config"]);
1242
+ expect(result1.block).toContain("### CLI Commands You Can Use");
1243
+
1244
+ stageTurn([{ slug: "cli-commands/config", denseScore: 0.9 }]);
1245
+ stageCliCommands([entry]);
1246
+ const result2 = await injectMemoryV2Block({
1247
+ database: db,
1248
+ conversationId: "conv-1",
1249
+ currentTurn: 2,
1250
+ userMessage: "more config",
1251
+ assistantMessage: "ok",
1252
+ nowText: "Now",
1253
+ messageId: "msg-2",
1254
+ config: makeConfig(),
1255
+ });
1256
+ expect(result2.toInject).toEqual([]);
1257
+ expect(result2.block).toBeNull();
1258
+
1259
+ const persisted = await hydrate(db, "conv-1");
1260
+ expect(persisted!.everInjected).toEqual([
1261
+ { slug: "cli-commands/config", turn: 1 },
1262
+ ]);
1263
+ });
1264
+
1063
1265
  test("context-load mode renders topNow even when every slug was previously injected", async () => {
1064
1266
  // Turn 1 (per-turn): seed alice as injected.
1065
1267
  stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
@@ -1633,7 +1835,9 @@ describe("injectMemoryV2Block", () => {
1633
1835
  expect(row.mode).toBe("router");
1634
1836
  const aliceRow = row.concepts.find((c) => c.slug === "alice-vscode");
1635
1837
  expect(aliceRow).toBeDefined();
1636
- expect(aliceRow!.source).toBe("router");
1838
+ // Default-stub provenance is `tier3:0` (single-batch path); see the
1839
+ // runRouter mock for the synthesis rule.
1840
+ expect(aliceRow!.source).toBe("tier3:0");
1637
1841
  expect(aliceRow!.status).toBe("injected");
1638
1842
  expect(aliceRow!.finalActivation).toBe(0);
1639
1843
  expect(aliceRow!.ownActivation).toBe(0);
@@ -1815,7 +2019,7 @@ describe("injectMemoryV2Block", () => {
1815
2019
  );
1816
2020
  expect(phantom).toBeDefined();
1817
2021
  expect(phantom!.status).toBe("page_missing");
1818
- expect(phantom!.source).toBe("router");
2022
+ expect(phantom!.source).toBe("tier3:0");
1819
2023
  });
1820
2024
 
1821
2025
  test("flag-on: router re-picking a prior-everInjected slug does NOT re-render it; non-overlapping picks render and append to everInjected", async () => {
@@ -1920,7 +2124,7 @@ describe("injectMemoryV2Block", () => {
1920
2124
  expect(bobRow).toBeDefined();
1921
2125
  expect(aliceRow!.source).toBe("carry_over");
1922
2126
  expect(aliceRow!.status).toBe("in_context");
1923
- expect(bobRow!.source).toBe("router");
2127
+ expect(bobRow!.source).toBe("tier3:0");
1924
2128
  expect(bobRow!.status).toBe("injected");
1925
2129
  });
1926
2130