@vellumai/assistant 0.8.1 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (630) hide show
  1. package/ARCHITECTURE.md +13 -19
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +17 -0
  5. package/docker-init-apt-root.sh +167 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +642 -5
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
  21. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  23. package/src/__tests__/anthropic-provider.test.ts +45 -0
  24. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  25. package/src/__tests__/app-executors.test.ts +220 -4
  26. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  27. package/src/__tests__/bundled-asset.test.ts +6 -6
  28. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  29. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  30. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  31. package/src/__tests__/clawhub.test.ts +75 -16
  32. package/src/__tests__/compactor-tail-resolution.test.ts +147 -0
  33. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  34. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  35. package/src/__tests__/config-schema.test.ts +21 -0
  36. package/src/__tests__/config-set-route.test.ts +80 -0
  37. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  38. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  39. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  40. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  41. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  42. package/src/__tests__/context-token-estimator.test.ts +31 -65
  43. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  44. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  45. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  46. package/src/__tests__/conversation-agent-loop.test.ts +59 -1
  47. package/src/__tests__/conversation-error.test.ts +42 -3
  48. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  49. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  50. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  51. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  52. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  53. package/src/__tests__/conversation-pairing.test.ts +54 -0
  54. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  55. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  56. package/src/__tests__/conversation-queue.test.ts +4 -1
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +102 -13
  58. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  59. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  60. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  61. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  64. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  65. package/src/__tests__/date-context.test.ts +45 -0
  66. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  67. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  68. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  69. package/src/__tests__/dm-backfill.test.ts +121 -10
  70. package/src/__tests__/document-tool-security.test.ts +258 -0
  71. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  72. package/src/__tests__/edit-propagation.test.ts +33 -0
  73. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  74. package/src/__tests__/external-plugin-loader.test.ts +151 -55
  75. package/src/__tests__/filing-service.test.ts +140 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  77. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  78. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  79. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  80. package/src/__tests__/heartbeat-service.test.ts +24 -164
  81. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  82. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  83. package/src/__tests__/helpers/wait-for.ts +21 -0
  84. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  85. package/src/__tests__/history-repair.test.ts +73 -0
  86. package/src/__tests__/host-app-control-proxy.test.ts +507 -10
  87. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  88. package/src/__tests__/image-credentials.test.ts +1 -1
  89. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  90. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  91. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  92. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  93. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  94. package/src/__tests__/injector-background-turn.test.ts +153 -0
  95. package/src/__tests__/injector-chain.test.ts +15 -8
  96. package/src/__tests__/install-skill-routing.test.ts +155 -37
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -3
  98. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  99. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  100. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  101. package/src/__tests__/llm-catalog-parity.test.ts +58 -13
  102. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  103. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  104. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
  105. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  106. package/src/__tests__/llm-resolver.test.ts +255 -2
  107. package/src/__tests__/llm-usage-store.test.ts +114 -0
  108. package/src/__tests__/managed-profile-guard.test.ts +41 -29
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  110. package/src/__tests__/managed-store.test.ts +84 -192
  111. package/src/__tests__/media-generate-image.test.ts +1 -1
  112. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  113. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  114. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  115. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  116. package/src/__tests__/notification-deep-link.test.ts +15 -0
  117. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  118. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  119. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  120. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  121. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  122. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  123. package/src/__tests__/openai-provider.test.ts +242 -3
  124. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  125. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  126. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  127. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  128. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  129. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +7 -2
  130. package/src/__tests__/platform.test.ts +2 -0
  131. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  132. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  133. package/src/__tests__/plugin-external-api.test.ts +68 -0
  134. package/src/__tests__/plugin-registry.test.ts +0 -77
  135. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  136. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  137. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  138. package/src/__tests__/plugin-types.test.ts +3 -13
  139. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  140. package/src/__tests__/process-message-display-content.test.ts +421 -0
  141. package/src/__tests__/provider-catalog-visibility.test.ts +158 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  143. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +33 -31
  144. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  145. package/src/__tests__/schedule-routes.test.ts +50 -3
  146. package/src/__tests__/schedule-store.test.ts +94 -0
  147. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  148. package/src/__tests__/schema-transforms.test.ts +20 -0
  149. package/src/__tests__/search-skills-unified.test.ts +0 -5
  150. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
  151. package/src/__tests__/server-history-render.test.ts +43 -0
  152. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  153. package/src/__tests__/skill-load-tool.test.ts +27 -89
  154. package/src/__tests__/skill-memory.test.ts +23 -3
  155. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  156. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  157. package/src/__tests__/skills-install-extract.test.ts +49 -38
  158. package/src/__tests__/skills-install-staging.test.ts +159 -0
  159. package/src/__tests__/skills-uninstall.test.ts +9 -41
  160. package/src/__tests__/skills.test.ts +51 -58
  161. package/src/__tests__/slack-channel-config.test.ts +9 -0
  162. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  163. package/src/__tests__/system-prompt.test.ts +670 -63
  164. package/src/__tests__/terminal-tools.test.ts +28 -1
  165. package/src/__tests__/thread-backfill.test.ts +557 -27
  166. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  167. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  168. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  169. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  170. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  171. package/src/__tests__/tool-executor.test.ts +16 -4
  172. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  173. package/src/__tests__/turn-events-store.test.ts +256 -0
  174. package/src/__tests__/twilio-routes.test.ts +4 -0
  175. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  176. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  177. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  178. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  179. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  180. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  181. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  182. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  183. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  184. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  185. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  186. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  187. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  188. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  189. package/src/a2a/__tests__/task-store.test.ts +246 -0
  190. package/src/a2a/agent-card.ts +58 -0
  191. package/src/a2a/feature-gate.ts +8 -0
  192. package/src/a2a/protocol-constants.ts +21 -0
  193. package/src/a2a/protocol-errors.ts +50 -0
  194. package/src/a2a/protocol-types.ts +162 -0
  195. package/src/a2a/task-store.ts +168 -0
  196. package/src/acp/resolve-agent.ts +1 -1
  197. package/src/agent/image-optimize.ts +13 -5
  198. package/src/agent/loop.ts +167 -18
  199. package/src/calls/voice-session-bridge.ts +61 -42
  200. package/src/channels/config.ts +9 -0
  201. package/src/channels/types.ts +122 -0
  202. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  203. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  204. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  205. package/src/cli/commands/__tests__/schedules.test.ts +960 -0
  206. package/src/cli/commands/changelog.ts +106 -42
  207. package/src/cli/commands/conversations.ts +102 -17
  208. package/src/cli/commands/default-action.ts +10 -53
  209. package/src/cli/commands/notifications.ts +388 -346
  210. package/src/cli/commands/plugins.ts +252 -0
  211. package/src/cli/commands/schedules.ts +683 -0
  212. package/src/cli/commands/telemetry.ts +40 -0
  213. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  214. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  215. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  216. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  217. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  218. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  219. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  220. package/src/cli/lib/cli-colors.ts +12 -0
  221. package/src/cli/lib/confirm-prompt.ts +79 -0
  222. package/src/cli/lib/install-from-github.ts +303 -0
  223. package/src/cli/lib/list-installed-plugins.ts +137 -0
  224. package/src/cli/lib/search-plugins.ts +163 -0
  225. package/src/cli/lib/uninstall-plugin.ts +82 -0
  226. package/src/cli/lib/unknown-command.ts +111 -0
  227. package/src/cli/program.ts +52 -2
  228. package/src/config/assistant-feature-flags.ts +24 -54
  229. package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
  230. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  231. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  232. package/src/config/bundled-skills/document/SKILL.md +23 -3
  233. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  234. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  235. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  236. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  237. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  238. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  239. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  240. package/src/config/bundled-tool-registry.ts +6 -0
  241. package/src/config/call-site-defaults.ts +105 -0
  242. package/src/config/feature-flag-registry.json +41 -9
  243. package/src/config/llm-resolver.ts +52 -1
  244. package/src/config/loader.ts +64 -38
  245. package/src/config/schema.ts +9 -10
  246. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  247. package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
  248. package/src/config/schemas/channels.ts +17 -0
  249. package/src/config/schemas/compaction.ts +28 -0
  250. package/src/config/schemas/conversations.ts +10 -0
  251. package/src/config/schemas/heartbeat.ts +23 -0
  252. package/src/config/schemas/llm-request-logs.ts +31 -7
  253. package/src/config/schemas/llm.ts +1 -0
  254. package/src/config/schemas/memory-retrieval.ts +18 -0
  255. package/src/config/schemas/memory-retrospective.ts +1 -1
  256. package/src/config/schemas/memory-v2.ts +4 -4
  257. package/src/config/schemas/memory.ts +3 -1
  258. package/src/config/schemas/tools.ts +14 -0
  259. package/src/config/seed-inference-profiles.ts +99 -29
  260. package/src/config/skills.ts +3 -96
  261. package/src/context/compactor.ts +1107 -0
  262. package/src/context/token-estimator.ts +34 -36
  263. package/src/context/window-manager.ts +197 -1520
  264. package/src/credential-execution/managed-catalog.ts +37 -0
  265. package/src/credential-health/credential-health-service.ts +280 -19
  266. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +33 -18
  267. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  268. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  269. package/src/daemon/approval-generators.ts +8 -6
  270. package/src/daemon/config-watcher.ts +94 -31
  271. package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
  272. package/src/daemon/conversation-agent-loop.ts +198 -11
  273. package/src/daemon/conversation-error.ts +171 -37
  274. package/src/daemon/conversation-lifecycle.ts +53 -40
  275. package/src/daemon/conversation-messaging.ts +25 -6
  276. package/src/daemon/conversation-process.ts +49 -12
  277. package/src/daemon/conversation-runtime-assembly.ts +25 -1
  278. package/src/daemon/conversation-slash.ts +12 -5
  279. package/src/daemon/conversation-store.ts +11 -4
  280. package/src/daemon/conversation-tool-setup.ts +39 -7
  281. package/src/daemon/conversation.ts +33 -8
  282. package/src/daemon/date-context.ts +40 -0
  283. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  284. package/src/daemon/first-greeting.ts +22 -2
  285. package/src/daemon/guardian-action-generators.ts +1 -125
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  287. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  288. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  289. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  290. package/src/daemon/handlers/config-a2a.ts +289 -0
  291. package/src/daemon/handlers/config-model.ts +6 -5
  292. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  293. package/src/daemon/handlers/conversations.ts +1 -0
  294. package/src/daemon/handlers/shared.ts +14 -5
  295. package/src/daemon/handlers/skills.ts +111 -108
  296. package/src/daemon/history-repair.ts +28 -1
  297. package/src/daemon/host-app-control-proxy.ts +153 -27
  298. package/src/daemon/host-proxy-preactivation.ts +85 -18
  299. package/src/daemon/lifecycle.ts +89 -91
  300. package/src/daemon/meet-host-supervisor.ts +5 -4
  301. package/src/daemon/memory-v2-startup.ts +85 -0
  302. package/src/daemon/message-protocol.ts +1 -0
  303. package/src/daemon/message-types/conversations.ts +25 -0
  304. package/src/daemon/message-types/messages.ts +61 -0
  305. package/src/daemon/message-types/notifications.ts +21 -0
  306. package/src/daemon/message-types/subagents.ts +1 -0
  307. package/src/daemon/message-types/sync.ts +1 -0
  308. package/src/daemon/pkb-reminder-builder.test.ts +11 -54
  309. package/src/daemon/pkb-reminder-builder.ts +5 -20
  310. package/src/daemon/plugin-source-watcher.ts +146 -0
  311. package/src/daemon/process-message.ts +24 -3
  312. package/src/daemon/server.ts +11 -2
  313. package/src/daemon/skill-memory-refresh.ts +33 -0
  314. package/src/daemon/wake-target-adapter.ts +2 -0
  315. package/src/documents/document-store.ts +221 -3
  316. package/src/embedded/plugin-api.ts +40 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  318. package/src/export/transcript-formatter.ts +54 -20
  319. package/src/filing/filing-service.ts +39 -0
  320. package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
  321. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  322. package/src/heartbeat/heartbeat-service.ts +73 -189
  323. package/src/home/__tests__/feed-types.test.ts +80 -0
  324. package/src/home/feed-types.ts +36 -2
  325. package/src/home/post-connect-feed.ts +1 -0
  326. package/src/index.ts +18 -1
  327. package/src/ipc/cli-client.ts +147 -45
  328. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  329. package/src/mcp/client.ts +20 -4
  330. package/src/media/image-credentials.ts +3 -3
  331. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  332. package/src/memory/__tests__/conversation-queries.test.ts +483 -0
  333. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  334. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  335. package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
  336. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  337. package/src/memory/__tests__/message-content.test.ts +35 -0
  338. package/src/memory/bookmark-crud.ts +42 -10
  339. package/src/memory/context-search/sources/conversations.ts +62 -2
  340. package/src/memory/context-search/sources/workspace.ts +4 -0
  341. package/src/memory/conversation-crud.ts +63 -19
  342. package/src/memory/conversation-queries.ts +197 -11
  343. package/src/memory/conversation-title-service.ts +26 -4
  344. package/src/memory/db-init.ts +12 -0
  345. package/src/memory/delivery-crud.ts +152 -5
  346. package/src/memory/embedding-backend.ts +4 -4
  347. package/src/memory/external-conversation-store.ts +66 -5
  348. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +150 -12
  349. package/src/memory/graph/conversation-graph-memory.ts +49 -21
  350. package/src/memory/graph/tools.ts +9 -40
  351. package/src/memory/indexer.ts +34 -29
  352. package/src/memory/invite-store.ts +53 -0
  353. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  354. package/src/memory/jobs/embed-concept-page.ts +20 -11
  355. package/src/memory/jobs-worker.ts +6 -1
  356. package/src/memory/llm-request-log-source-clickhouse.ts +24 -12
  357. package/src/memory/llm-request-log-source.ts +19 -52
  358. package/src/memory/llm-request-log-store.ts +92 -1
  359. package/src/memory/llm-usage-store.ts +125 -5
  360. package/src/memory/memory-retrospective-enqueue.ts +1 -20
  361. package/src/memory/memory-retrospective-job.ts +33 -6
  362. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  363. package/src/memory/message-content.ts +1 -1
  364. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  365. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  366. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  367. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  368. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  369. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  370. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  371. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  372. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  373. package/src/memory/migrations/index.ts +9 -0
  374. package/src/memory/migrations/registry.ts +16 -0
  375. package/src/memory/onboarding-events-store.ts +106 -0
  376. package/src/memory/schema/a2a.ts +15 -0
  377. package/src/memory/schema/bookmarks.ts +0 -2
  378. package/src/memory/schema/calls.ts +1 -0
  379. package/src/memory/schema/index.ts +1 -0
  380. package/src/memory/schema/inference.ts +3 -3
  381. package/src/memory/schema/infrastructure.ts +13 -0
  382. package/src/memory/turn-events-store.ts +127 -2
  383. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  384. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  385. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  386. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  387. package/src/memory/v2/__tests__/injection.test.ts +288 -11
  388. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  389. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  390. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  391. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  392. package/src/memory/v2/__tests__/router.test.ts +15 -0
  393. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  394. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  395. package/src/memory/v2/activation-store.ts +14 -16
  396. package/src/memory/v2/cli-command-content.ts +19 -0
  397. package/src/memory/v2/cli-command-store.ts +304 -0
  398. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  399. package/src/memory/v2/injection.ts +81 -26
  400. package/src/memory/v2/migration.ts +49 -19
  401. package/src/memory/v2/page-index.ts +63 -8
  402. package/src/memory/v2/prompts/router.ts +11 -8
  403. package/src/memory/v2/prompts/sweep.ts +2 -2
  404. package/src/memory/v2/qdrant.ts +135 -7
  405. package/src/memory/v2/router.ts +9 -8
  406. package/src/memory/v2/skill-store.ts +120 -35
  407. package/src/memory/v2/static-context.ts +4 -4
  408. package/src/memory/v2/types.ts +23 -0
  409. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  410. package/src/messaging/providers/a2a/deliver.ts +156 -0
  411. package/src/messaging/providers/gmail/client.ts +9 -2
  412. package/src/messaging/providers/index.ts +11 -2
  413. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  414. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  415. package/src/messaging/providers/slack/adapter.ts +43 -5
  416. package/src/messaging/providers/slack/client.ts +27 -0
  417. package/src/messaging/providers/slack/deep-link.ts +65 -0
  418. package/src/messaging/providers/slack/download.ts +104 -0
  419. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  420. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  421. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  422. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  423. package/src/messaging/providers/slack/types.ts +20 -1
  424. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  425. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  426. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  427. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  428. package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
  429. package/src/notifications/adapters/macos.ts +12 -2
  430. package/src/notifications/broadcaster.ts +29 -4
  431. package/src/notifications/conversation-pairing.ts +2 -1
  432. package/src/notifications/copy-composer.ts +17 -64
  433. package/src/notifications/decision-engine.ts +113 -45
  434. package/src/notifications/deterministic-checks.ts +96 -0
  435. package/src/notifications/emit-signal.ts +21 -1
  436. package/src/notifications/home-feed-side-effect.ts +138 -5
  437. package/src/notifications/signal.ts +3 -5
  438. package/src/notifications/types.ts +8 -0
  439. package/src/oauth/connection-resolver.ts +8 -4
  440. package/src/oauth/platform-connection.test.ts +43 -3
  441. package/src/oauth/platform-connection.ts +19 -6
  442. package/src/oauth/seed-providers.ts +10 -1
  443. package/src/permissions/checker.ts +2 -0
  444. package/src/permissions/ipc-risk-types.ts +1 -0
  445. package/src/permissions/question-prompter.test.ts +416 -0
  446. package/src/permissions/question-prompter.ts +294 -0
  447. package/src/platform/client.test.ts +1 -1
  448. package/src/platform/client.ts +1 -1
  449. package/src/plugin-api/constants.ts +26 -0
  450. package/src/plugin-api/index.ts +34 -1
  451. package/src/plugin-api/types.ts +104 -22
  452. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  453. package/src/plugins/defaults/compaction.ts +0 -4
  454. package/src/plugins/defaults/empty-response.ts +0 -2
  455. package/src/plugins/defaults/history-repair.ts +0 -2
  456. package/src/plugins/defaults/injectors.ts +74 -22
  457. package/src/plugins/defaults/llm-call.ts +0 -2
  458. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  459. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  460. package/src/plugins/defaults/persistence.ts +0 -2
  461. package/src/plugins/defaults/title-generate.ts +0 -5
  462. package/src/plugins/defaults/token-estimate.ts +0 -2
  463. package/src/plugins/defaults/tool-error.ts +0 -7
  464. package/src/plugins/defaults/tool-execute.ts +0 -2
  465. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  466. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  467. package/src/plugins/external-api.ts +104 -0
  468. package/src/plugins/external-plugin-loader.ts +187 -42
  469. package/src/plugins/feature-gate.ts +22 -0
  470. package/src/plugins/pipeline.ts +37 -0
  471. package/src/plugins/registry.ts +48 -80
  472. package/src/plugins/types.ts +40 -26
  473. package/src/plugins/user-loader.ts +21 -2
  474. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  475. package/src/proactive-artifact/job.test.ts +37 -5
  476. package/src/prompts/__tests__/system-prompt.test.ts +10 -43
  477. package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
  478. package/src/prompts/normalize-onboarding.ts +27 -0
  479. package/src/prompts/sections.ts +302 -0
  480. package/src/prompts/system-prompt.ts +63 -174
  481. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  482. package/src/prompts/templates/system-sections.ts +164 -0
  483. package/src/providers/__tests__/inference.test.ts +24 -7
  484. package/src/providers/anthropic/client.ts +28 -28
  485. package/src/providers/call-site-routing.ts +24 -6
  486. package/src/providers/connection-resolution.ts +68 -11
  487. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  488. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  489. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  490. package/src/providers/inference/adapter-factory.ts +32 -6
  491. package/src/providers/inference/auth.ts +12 -0
  492. package/src/providers/inference/backfill.ts +14 -1
  493. package/src/providers/inference/connections.ts +159 -34
  494. package/src/providers/inference/resolve-auth.ts +14 -4
  495. package/src/providers/model-catalog.ts +249 -12
  496. package/src/providers/model-intents.ts +3 -3
  497. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  498. package/src/providers/openai/chat-completions-provider.ts +169 -8
  499. package/src/providers/openrouter/client.ts +49 -4
  500. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
  501. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  502. package/src/providers/provider-availability.ts +17 -2
  503. package/src/providers/provider-catalog-visibility.ts +38 -0
  504. package/src/providers/provider-send-message.ts +27 -12
  505. package/src/providers/registry.ts +52 -15
  506. package/src/providers/retry.ts +47 -1
  507. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  508. package/src/runtime/agent-wake.ts +103 -15
  509. package/src/runtime/auth/route-policy.ts +21 -1
  510. package/src/runtime/btw-sidechain.ts +2 -0
  511. package/src/runtime/http-server.ts +7 -16
  512. package/src/runtime/http-types.ts +19 -47
  513. package/src/runtime/migrations/origin-mode.ts +1 -1
  514. package/src/runtime/pending-interactions.ts +1 -0
  515. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  516. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  517. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  518. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
  519. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  520. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  521. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  522. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  523. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  524. package/src/runtime/routes/acp-routes.ts +5 -3
  525. package/src/runtime/routes/auth-routes.ts +1 -1
  526. package/src/runtime/routes/bookmark-routes.ts +5 -3
  527. package/src/runtime/routes/btw-routes.ts +5 -1
  528. package/src/runtime/routes/channel-availability-routes.ts +126 -0
  529. package/src/runtime/routes/consolidation-routes.ts +100 -0
  530. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  531. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  532. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  533. package/src/runtime/routes/conversation-query-routes.ts +99 -35
  534. package/src/runtime/routes/conversation-routes.ts +97 -11
  535. package/src/runtime/routes/documents-routes.ts +25 -86
  536. package/src/runtime/routes/group-routes.ts +5 -0
  537. package/src/runtime/routes/inbound-conversation.ts +28 -8
  538. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  539. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  540. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  541. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  542. package/src/runtime/routes/index.ts +8 -0
  543. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  544. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  545. package/src/runtime/routes/inference-provider-connection-routes.ts +199 -22
  546. package/src/runtime/routes/integrations/a2a.ts +235 -0
  547. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  548. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  549. package/src/runtime/routes/integrations/twilio.ts +6 -13
  550. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  551. package/src/runtime/routes/notification-routes.ts +1 -1
  552. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  553. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  554. package/src/runtime/routes/question-routes.ts +259 -0
  555. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  556. package/src/runtime/routes/schedule-routes.ts +4 -7
  557. package/src/runtime/routes/subagents-routes.ts +98 -18
  558. package/src/runtime/routes/telemetry-routes.ts +27 -0
  559. package/src/runtime/routes/tts-routes.ts +27 -2
  560. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  561. package/src/runtime/routes/workspace-routes.ts +28 -0
  562. package/src/runtime/services/conversation-serializer.ts +39 -7
  563. package/src/runtime/sync/resource-sync-events.ts +93 -1
  564. package/src/schedule/schedule-store.ts +27 -2
  565. package/src/schedule/scheduler.ts +9 -1
  566. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  567. package/src/security/untrusted-content.ts +93 -8
  568. package/src/skills/catalog-files.ts +1 -1
  569. package/src/skills/catalog-install.ts +233 -116
  570. package/src/skills/clawhub.ts +70 -13
  571. package/src/skills/managed-store.ts +4 -119
  572. package/src/skills/skillssh-registry.ts +27 -48
  573. package/src/subagent/manager.ts +17 -7
  574. package/src/telemetry/types.ts +113 -1
  575. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  576. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  577. package/src/tools/apps/executors.ts +58 -7
  578. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  579. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  580. package/src/tools/browser/browser-execution.ts +15 -11
  581. package/src/tools/computer-use/definitions.ts +3 -3
  582. package/src/tools/credentials/vault.ts +1 -1
  583. package/src/tools/document/document-tool.ts +124 -1
  584. package/src/tools/filesystem/edit.ts +1 -1
  585. package/src/tools/filesystem/list.ts +1 -1
  586. package/src/tools/filesystem/read.ts +1 -1
  587. package/src/tools/filesystem/write.ts +5 -2
  588. package/src/tools/host-filesystem/transfer.ts +1 -1
  589. package/src/tools/host-terminal/host-shell.ts +1 -1
  590. package/src/tools/memory/register.ts +1 -9
  591. package/src/tools/permission-checker.ts +1 -1
  592. package/src/tools/registry.ts +17 -7
  593. package/src/tools/schedule/create.ts +2 -2
  594. package/src/tools/schema-transforms.ts +7 -2
  595. package/src/tools/side-effects.ts +1 -0
  596. package/src/tools/skills/delete-managed.ts +4 -4
  597. package/src/tools/skills/execute.ts +1 -1
  598. package/src/tools/skills/scaffold-managed.ts +3 -2
  599. package/src/tools/subagent/notify-parent.ts +1 -1
  600. package/src/tools/system/request-permission.ts +2 -2
  601. package/src/tools/terminal/safe-env.ts +60 -1
  602. package/src/tools/tool-manifest.ts +2 -0
  603. package/src/tools/types.ts +107 -21
  604. package/src/tools/ui-surface/definitions.ts +6 -5
  605. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  606. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  607. package/src/types/onboarding-context.ts +2 -0
  608. package/src/util/errors.ts +17 -0
  609. package/src/util/platform.ts +10 -0
  610. package/src/watcher/__tests__/engine.test.ts +22 -0
  611. package/src/watcher/engine.ts +6 -2
  612. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  613. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  614. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  615. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  616. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  617. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  618. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  619. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  620. package/src/workspace/migrations/registry.ts +10 -0
  621. package/src/workspace/migrations/runner.ts +39 -9
  622. package/src/workspace/migrations/types.ts +4 -0
  623. package/examples/plugins/echo/bun.lock +0 -25
  624. package/src/__tests__/context-window-manager.test.ts +0 -2481
  625. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  626. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  627. package/src/context/prompts/compact.md +0 -26
  628. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  629. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  630. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
@@ -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
  // ---------------------------------------------------------------------------
@@ -357,7 +389,7 @@ const { getSqliteFrom } = await import("../../db-connection.js");
357
389
  const { migrateActivationState } =
358
390
  await import("../../migrations/232-activation-state.js");
359
391
  const schema = await import("../../schema.js");
360
- const { evictCompactedTurns, hydrate, save } =
392
+ const { clearEverInjected, hydrate, save } =
361
393
  await import("../activation-store.js");
362
394
  const { injectMemoryV2Block } = await import("../injection.js");
363
395
  const { _resetMemoryV2QdrantForTests } = await import("../qdrant.js");
@@ -484,6 +516,7 @@ function resetState(): void {
484
516
  state.queryResponses.dense.length = 0;
485
517
  state.queryResponses.sparse.length = 0;
486
518
  skillState.entries.clear();
519
+ cliCommandState.entries.clear();
487
520
  telemetryState.recordCalls.length = 0;
488
521
  telemetryState.recordShouldThrow = false;
489
522
  pageStoreState.failingSlugs.clear();
@@ -503,6 +536,13 @@ function stageSkills(entries: SkillEntry[]): void {
503
536
  }
504
537
  }
505
538
 
539
+ /** Stage cli-command-store cache entries for the upcoming render. */
540
+ function stageCliCommands(entries: CliCommandEntryStub[]): void {
541
+ for (const entry of entries) {
542
+ cliCommandState.entries.set(entry.id, entry);
543
+ }
544
+ }
545
+
506
546
  let db: DrizzleDb;
507
547
  beforeEach(() => {
508
548
  db = createTestDb();
@@ -653,10 +693,10 @@ describe("injectMemoryV2Block", () => {
653
693
  config: makeConfig(),
654
694
  });
655
695
 
656
- // Simulate compaction: drop all everInjected entries with turn <= 1.
696
+ // Simulate compaction: clear the entire everInjected list.
657
697
  const beforeEvict = await hydrate(db, "conv-1");
658
698
  expect(beforeEvict).not.toBeNull();
659
- const afterEvict = evictCompactedTurns(beforeEvict!, 1);
699
+ const afterEvict = clearEverInjected(beforeEvict!);
660
700
  expect(afterEvict.everInjected).toEqual([]);
661
701
  await save(db, "conv-1", afterEvict);
662
702
 
@@ -879,17 +919,17 @@ describe("injectMemoryV2Block", () => {
879
919
  // Unified pool — skills as `skills/<id>` slugs
880
920
  // ---------------------------------------------------------------------------
881
921
 
882
- test("renders a skill-only block via the skills/ slug prefix", async () => {
922
+ test("renders a retrieved skills/<id> slug under Skills You Can Use", async () => {
883
923
  // No concept-page candidates this turn — the only ANN hit is a skill
884
924
  // slug. The render path branches on `skills/` prefix: it pulls the
885
925
  // entry from the skill-store cache (mocked) and emits the bullet under
886
926
  // the `### Skills You Can Use` subsection.
887
- stageTurn([{ slug: "skills/example-skill-a", denseScore: 0.9 }]);
927
+ stageTurn([{ slug: "skills/retrieved-skill", denseScore: 0.9 }]);
888
928
  stageSkills([
889
929
  {
890
- id: "example-skill-a",
930
+ id: "retrieved-skill",
891
931
  content:
892
- 'The "Example Skill A" skill (example-skill-a) is available. Helps with examples.',
932
+ 'The "Retrieved Skill" skill (retrieved-skill) is available. Helps with retrieved skills.',
893
933
  },
894
934
  ]);
895
935
 
@@ -904,16 +944,18 @@ describe("injectMemoryV2Block", () => {
904
944
  config: makeConfig(),
905
945
  });
906
946
 
907
- expect(result.toInject).toEqual(["skills/example-skill-a"]);
947
+ expect(result.toInject).toEqual(["skills/retrieved-skill"]);
908
948
  expect(result.block).not.toBeNull();
909
949
  expect(result.block).not.toContain("<memory>");
910
950
  expect(result.block).not.toContain("</memory>");
911
951
  expect(result.block).not.toContain("## What I Remember Right Now");
912
952
  expect(result.block).not.toContain("# memory/concepts/alice-vscode.md");
913
- expect(result.block).toContain("### Skills You Can Use");
914
- expect(result.block).toContain(
915
- '- The "Example Skill A" skill (example-skill-a) is available. Helps with examples. → use skill_load to activate',
953
+ const headerIdx = result.block!.indexOf("### Skills You Can Use");
954
+ const skillIdx = result.block!.indexOf(
955
+ '- The "Retrieved Skill" skill (retrieved-skill) is available. Helps with retrieved skills. → use skill_load to activate',
916
956
  );
957
+ expect(headerIdx).toBeGreaterThan(-1);
958
+ expect(skillIdx).toBeGreaterThan(headerIdx);
917
959
  });
918
960
 
919
961
  test("renders concept-page sections before the skills subsection in mixed blocks", async () => {
@@ -1058,6 +1100,153 @@ describe("injectMemoryV2Block", () => {
1058
1100
  expect(result.block).toBeNull();
1059
1101
  });
1060
1102
 
1103
+ // ---------------------------------------------------------------------------
1104
+ // CLI-command synthetic entries — same unified-pool plumbing as skills.
1105
+ // ---------------------------------------------------------------------------
1106
+
1107
+ test("renders a retrieved cli-commands/<name> slug under CLI Commands You Can Use", async () => {
1108
+ stageTurn([{ slug: "cli-commands/attachment", denseScore: 0.9 }]);
1109
+ stageCliCommands([
1110
+ {
1111
+ id: "attachment",
1112
+ description: "Manage file attachments for conversations",
1113
+ content: 'The "assistant attachment" CLI command is available...',
1114
+ },
1115
+ ]);
1116
+
1117
+ const result = await injectMemoryV2Block({
1118
+ database: db,
1119
+ conversationId: "conv-1",
1120
+ currentTurn: 1,
1121
+ userMessage: "How do I register a video?",
1122
+ assistantMessage: "",
1123
+ nowText: "Now",
1124
+ messageId: "msg-1",
1125
+ config: makeConfig(),
1126
+ });
1127
+
1128
+ expect(result.toInject).toEqual(["cli-commands/attachment"]);
1129
+ expect(result.block).not.toBeNull();
1130
+ const headerIdx = result.block!.indexOf("### CLI Commands You Can Use");
1131
+ const lineIdx = result.block!.indexOf(
1132
+ "- `assistant attachment`: Manage file attachments for conversations",
1133
+ );
1134
+ expect(headerIdx).toBeGreaterThan(-1);
1135
+ expect(lineIdx).toBeGreaterThan(headerIdx);
1136
+ });
1137
+
1138
+ test("renders concepts, skills, then cli-commands in that order in mixed blocks", async () => {
1139
+ stageTurn([
1140
+ { slug: "alice-vscode", denseScore: 0.95 },
1141
+ { slug: "skills/example-skill-a", denseScore: 0.85 },
1142
+ { slug: "cli-commands/config", denseScore: 0.75 },
1143
+ ]);
1144
+ stageSkills([
1145
+ {
1146
+ id: "example-skill-a",
1147
+ content:
1148
+ 'The "Example Skill A" skill (example-skill-a) is available. Helps with examples.',
1149
+ },
1150
+ ]);
1151
+ stageCliCommands([
1152
+ {
1153
+ id: "config",
1154
+ description: "Manage configuration",
1155
+ content: 'The "assistant config" CLI command is available...',
1156
+ },
1157
+ ]);
1158
+
1159
+ const result = await injectMemoryV2Block({
1160
+ database: db,
1161
+ conversationId: "conv-1",
1162
+ currentTurn: 1,
1163
+ userMessage: "Help me",
1164
+ assistantMessage: "",
1165
+ nowText: "Now",
1166
+ messageId: "msg-1",
1167
+ config: makeConfig(),
1168
+ });
1169
+
1170
+ expect(new Set(result.toInject)).toEqual(
1171
+ new Set([
1172
+ "alice-vscode",
1173
+ "skills/example-skill-a",
1174
+ "cli-commands/config",
1175
+ ]),
1176
+ );
1177
+ const conceptIdx = result.block!.indexOf(
1178
+ "# memory/concepts/alice-vscode.md",
1179
+ );
1180
+ const skillsIdx = result.block!.indexOf("### Skills You Can Use");
1181
+ const cliIdx = result.block!.indexOf("### CLI Commands You Can Use");
1182
+ expect(conceptIdx).toBeGreaterThan(-1);
1183
+ expect(skillsIdx).toBeGreaterThan(conceptIdx);
1184
+ expect(cliIdx).toBeGreaterThan(skillsIdx);
1185
+ });
1186
+
1187
+ test("cli-command slugs whose entry is missing from the cache are dropped silently", async () => {
1188
+ stageTurn([{ slug: "cli-commands/missing-command", denseScore: 0.9 }]);
1189
+
1190
+ const result = await injectMemoryV2Block({
1191
+ database: db,
1192
+ conversationId: "conv-1",
1193
+ currentTurn: 1,
1194
+ userMessage: "anything",
1195
+ assistantMessage: "",
1196
+ nowText: "Now",
1197
+ messageId: "msg-1",
1198
+ config: makeConfig(),
1199
+ });
1200
+
1201
+ expect(result.toInject).toEqual([]);
1202
+ expect(result.block).toBeNull();
1203
+
1204
+ const persisted = await hydrate(db, "conv-1");
1205
+ expect(persisted!.everInjected).toEqual([]);
1206
+ });
1207
+
1208
+ test("cli-commands participate in everInjected so they dedupe across turns", async () => {
1209
+ const entry = {
1210
+ id: "config",
1211
+ description: "Manage configuration",
1212
+ content: 'The "assistant config" CLI command is available...',
1213
+ };
1214
+ stageTurn([{ slug: "cli-commands/config", denseScore: 0.9 }]);
1215
+ stageCliCommands([entry]);
1216
+ const result1 = await injectMemoryV2Block({
1217
+ database: db,
1218
+ conversationId: "conv-1",
1219
+ currentTurn: 1,
1220
+ userMessage: "config",
1221
+ assistantMessage: "",
1222
+ nowText: "Now",
1223
+ messageId: "msg-1",
1224
+ config: makeConfig(),
1225
+ });
1226
+ expect(result1.toInject).toEqual(["cli-commands/config"]);
1227
+ expect(result1.block).toContain("### CLI Commands You Can Use");
1228
+
1229
+ stageTurn([{ slug: "cli-commands/config", denseScore: 0.9 }]);
1230
+ stageCliCommands([entry]);
1231
+ const result2 = await injectMemoryV2Block({
1232
+ database: db,
1233
+ conversationId: "conv-1",
1234
+ currentTurn: 2,
1235
+ userMessage: "more config",
1236
+ assistantMessage: "ok",
1237
+ nowText: "Now",
1238
+ messageId: "msg-2",
1239
+ config: makeConfig(),
1240
+ });
1241
+ expect(result2.toInject).toEqual([]);
1242
+ expect(result2.block).toBeNull();
1243
+
1244
+ const persisted = await hydrate(db, "conv-1");
1245
+ expect(persisted!.everInjected).toEqual([
1246
+ { slug: "cli-commands/config", turn: 1 },
1247
+ ]);
1248
+ });
1249
+
1061
1250
  test("context-load mode renders topNow even when every slug was previously injected", async () => {
1062
1251
  // Turn 1 (per-turn): seed alice as injected.
1063
1252
  stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
@@ -1679,6 +1868,56 @@ describe("injectMemoryV2Block", () => {
1679
1868
  expect(row.concepts).toEqual([]);
1680
1869
  });
1681
1870
 
1871
+ test("flag-on: router-failure path swallows a save() error and returns block:null instead of throwing", async () => {
1872
+ // PR 30176 refactored router-failure handling to delegate to
1873
+ // `finalizeInjection`. That regressed the prior inline log-and-continue
1874
+ // semantics on the router-failure path: a transient SQLite write
1875
+ // throwing during the stub-state save now aborted the whole turn
1876
+ // because `finalizeInjection`'s try/catch re-threw caughtErr at the end.
1877
+ //
1878
+ // This test stages exactly that scenario — router returns
1879
+ // `failureReason: api_error` AND `save()` throws — and asserts the
1880
+ // turn completes with `{ block: null, toInject: [] }` rather than
1881
+ // propagating the SQLite error to `prepareMemory`.
1882
+ routerState.nextResult = {
1883
+ selectedSlugs: [],
1884
+ failureReason: "api_error",
1885
+ };
1886
+ activationStoreState.saveShouldThrow = true;
1887
+
1888
+ let threw: unknown = undefined;
1889
+ let result: Awaited<ReturnType<typeof injectMemoryV2Block>> | undefined;
1890
+ try {
1891
+ result = await injectMemoryV2Block({
1892
+ database: db,
1893
+ conversationId: "conv-router-fail-save-throws",
1894
+ currentTurn: 5,
1895
+ userMessage: "anything",
1896
+ assistantMessage: "ok",
1897
+ nowText: "Now",
1898
+ messageId: "msg-fail-save",
1899
+ config: makeConfig({ router: { enabled: true } }),
1900
+ });
1901
+ } catch (err) {
1902
+ threw = err;
1903
+ }
1904
+
1905
+ expect(threw).toBeUndefined();
1906
+ expect(result).toBeDefined();
1907
+ expect(result!.block).toBeNull();
1908
+ expect(result!.toInject).toEqual([]);
1909
+
1910
+ // Telemetry still flushes with `mode: "errored"` so the failure stays
1911
+ // observable — the same row the inline pre-refactor path emitted.
1912
+ expect(telemetryState.recordCalls.length).toBe(1);
1913
+ const row = telemetryState.recordCalls[0] as {
1914
+ mode: string;
1915
+ concepts: unknown[];
1916
+ };
1917
+ expect(row.mode).toBe("errored");
1918
+ expect(row.concepts).toEqual([]);
1919
+ });
1920
+
1682
1921
  test("flag-on: router abstention (empty selectedSlugs, no failure) writes mode:`router` row with no injected pages", async () => {
1683
1922
  routerState.nextResult = {
1684
1923
  selectedSlugs: [],
@@ -1904,5 +2143,43 @@ describe("injectMemoryV2Block", () => {
1904
2143
  const row = telemetryState.recordCalls[0] as { mode: string };
1905
2144
  expect(row.mode).toBe("per-turn");
1906
2145
  });
2146
+
2147
+ test("flag-on + mode='context-load': router runs (everInjected was cleared by onCompacted so dedupe is a no-op; abstention is accepted as the trade-off)", async () => {
2148
+ // Context-load is the full top-K bootstrap fired after compaction or
2149
+ // a fresh conversation reload. The earlier worry about the router's
2150
+ // `everInjected` dedupe filtering out post-compaction restorations
2151
+ // doesn't apply: `ConversationGraphMemory.onCompacted` calls
2152
+ // `evictCompactedTurnsV2` which empties the list before this code
2153
+ // runs. Router abstention here means no v2 pages this turn — that's
2154
+ // preferable to letting the activation graph pick something arbitrary.
2155
+ routerState.nextResult = {
2156
+ selectedSlugs: ["alice-vscode"],
2157
+ failureReason: null,
2158
+ };
2159
+
2160
+ const result = await injectMemoryV2Block({
2161
+ database: db,
2162
+ conversationId: "conv-context-load-router-on",
2163
+ currentTurn: 1,
2164
+ userMessage: "Tell me about Alice",
2165
+ assistantMessage: "",
2166
+ nowText: "Now",
2167
+ messageId: "msg-1",
2168
+ mode: "context-load",
2169
+ config: makeConfig({ router: { enabled: true } }),
2170
+ });
2171
+
2172
+ // Router was called on context-load too.
2173
+ expect(routerState.callCount).toBe(1);
2174
+
2175
+ // Router's picks were rendered.
2176
+ expect(result.toInject).toEqual(["alice-vscode"]);
2177
+ expect(result.block).toContain("# memory/concepts/alice-vscode.md");
2178
+
2179
+ // Telemetry row reflects the router mode, not the activation mode.
2180
+ expect(telemetryState.recordCalls.length).toBe(1);
2181
+ const row = telemetryState.recordCalls[0] as { mode: string };
2182
+ expect(row.mode).toBe("router");
2183
+ });
1907
2184
  });
1908
2185
  });
@@ -754,6 +754,93 @@ describe("runMemoryV2Migration", () => {
754
754
  expect(body).toContain("second body");
755
755
  });
756
756
 
757
+ test("force=true cleanly strips the prior migration block from essentials.md", async () => {
758
+ insertNode(database, {
759
+ content: "Alice prefers VS Code.",
760
+ significance: 0.95,
761
+ });
762
+ await runMemoryV2Migration({
763
+ workspaceDir,
764
+ database,
765
+ provider: buildStubProvider("body"),
766
+ });
767
+
768
+ const essentialsPath = join(workspaceDir, "memory", "essentials.md");
769
+ const afterFirst = readFileSync(essentialsPath, "utf-8");
770
+ expect(afterFirst).toContain("<!-- migration:v1-to-v2 -->");
771
+ expect(afterFirst).toContain("<!-- /migration:v1-to-v2 -->");
772
+
773
+ await runMemoryV2Migration({
774
+ workspaceDir,
775
+ database,
776
+ provider: buildStubProvider("body"),
777
+ force: true,
778
+ });
779
+
780
+ const afterRerun = readFileSync(essentialsPath, "utf-8");
781
+ // Exactly one migration block — no leftover/duplicated markers.
782
+ expect(afterRerun.match(/<!-- migration:v1-to-v2 -->/g)?.length ?? 0).toBe(
783
+ 1,
784
+ );
785
+ expect(
786
+ afterRerun.match(/<!-- \/migration:v1-to-v2 -->/g)?.length ?? 0,
787
+ ).toBe(1);
788
+ });
789
+
790
+ test("force=true preserves user-appended content after the migration block", async () => {
791
+ insertNode(database, {
792
+ content: "Alice prefers VS Code.",
793
+ significance: 0.95,
794
+ });
795
+ await runMemoryV2Migration({
796
+ workspaceDir,
797
+ database,
798
+ provider: buildStubProvider("body"),
799
+ });
800
+
801
+ const essentialsPath = join(workspaceDir, "memory", "essentials.md");
802
+ const beforeAppend = readFileSync(essentialsPath, "utf-8");
803
+ const userAppended = "\nUser added this after the migration ran.\n";
804
+ writeFileSync(essentialsPath, beforeAppend + userAppended, "utf-8");
805
+
806
+ await runMemoryV2Migration({
807
+ workspaceDir,
808
+ database,
809
+ provider: buildStubProvider("body"),
810
+ force: true,
811
+ });
812
+
813
+ const after = readFileSync(essentialsPath, "utf-8");
814
+ expect(after).toContain("User added this after the migration ran.");
815
+ // And exactly one migration envelope remains.
816
+ expect(after.match(/<!-- migration:v1-to-v2 -->/g)?.length ?? 0).toBe(1);
817
+ expect(after.match(/<!-- \/migration:v1-to-v2 -->/g)?.length ?? 0).toBe(1);
818
+ });
819
+
820
+ test("force=true on legacy (close-marker-less) file strips only up to the next blank line", async () => {
821
+ // Simulate a file written by the prior migration format: opening marker
822
+ // with no closing sentinel, with user-appended content separated by a
823
+ // blank line. The strip should preserve the user content.
824
+ insertNode(database, { content: "Alice prefers VS Code." });
825
+ const essentialsPath = join(workspaceDir, "memory", "essentials.md");
826
+ writeFileSync(
827
+ essentialsPath,
828
+ "<!-- migration:v1-to-v2 -->\nlegacy migrated line\n\nUser-appended legacy note.\n",
829
+ "utf-8",
830
+ );
831
+
832
+ await runMemoryV2Migration({
833
+ workspaceDir,
834
+ database,
835
+ provider: buildStubProvider("body"),
836
+ force: true,
837
+ });
838
+
839
+ const after = readFileSync(essentialsPath, "utf-8");
840
+ expect(after).toContain("User-appended legacy note.");
841
+ expect(after).not.toContain("legacy migrated line");
842
+ });
843
+
757
844
  test("clearing the sentinel allows a non-force re-run", async () => {
758
845
  insertNode(database, { content: "fact" });
759
846
  const provider = buildStubProvider("body");
@@ -245,6 +245,89 @@ describe("getPageIndex", () => {
245
245
  const idx = await getPageIndex(workspaceDir);
246
246
  expect(idx.bySlug.get("alice")?.summary.length).toBe(200);
247
247
  });
248
+
249
+ test("collapses embedded newlines in frontmatter.summary to single spaces", async () => {
250
+ await writePage(
251
+ workspaceDir,
252
+ makePage("alice", { summary: "First line.\nSecond line.\nThird line." }),
253
+ );
254
+ const idx = await getPageIndex(workspaceDir);
255
+ expect(idx.bySlug.get("alice")?.summary).toBe(
256
+ "First line. Second line. Third line.",
257
+ );
258
+ });
259
+
260
+ test("collapses embedded newlines and runs of whitespace in body fallback", async () => {
261
+ await writePage(
262
+ workspaceDir,
263
+ makePage("alice", {
264
+ body: " Body with\n\nmultiple\tlines\n and spaces. ",
265
+ }),
266
+ );
267
+ const idx = await getPageIndex(workspaceDir);
268
+ expect(idx.bySlug.get("alice")?.summary).toBe(
269
+ "Body with multiple lines and spaces.",
270
+ );
271
+ });
272
+
273
+ test("normalizes skill-entry content with embedded newlines", async () => {
274
+ skillState.entries = [
275
+ { id: "browser", content: "Drive a browser.\nSupports multiple tabs." },
276
+ ];
277
+ const idx = await getPageIndex(workspaceDir);
278
+ expect(idx.bySlug.get("skills/browser")?.summary).toBe(
279
+ "Drive a browser. Supports multiple tabs.",
280
+ );
281
+ });
282
+
283
+ test("renders a single line per entry even when summaries contain newlines", async () => {
284
+ await writePage(
285
+ workspaceDir,
286
+ makePage("alice", { summary: "line one\nline two" }),
287
+ );
288
+ const idx = await getPageIndex(workspaceDir);
289
+ // Exactly one trailing newline — the entry itself must not split.
290
+ expect(idx.rendered.split("\n").filter(Boolean).length).toBe(1);
291
+ });
292
+
293
+ test("drops a user concept page whose slug collides with a seeded skill entry", async () => {
294
+ await writePage(
295
+ workspaceDir,
296
+ makePage("skills/browser", {
297
+ summary: "User-authored page that shadows the skill.",
298
+ }),
299
+ );
300
+ skillState.entries = [{ id: "browser", content: "Seeded skill content." }];
301
+
302
+ const idx = await getPageIndex(workspaceDir);
303
+ // Only the skill entry survives under skills/browser.
304
+ expect(idx.entries.filter((e) => e.slug === "skills/browser").length).toBe(
305
+ 1,
306
+ );
307
+ expect(idx.bySlug.get("skills/browser")?.summary).toBe(
308
+ "Seeded skill content.",
309
+ );
310
+ });
311
+
312
+ test("collision dedupe leaves non-colliding pages and skills intact", async () => {
313
+ await writePage(workspaceDir, makePage("alice", { summary: "Alice" }));
314
+ await writePage(
315
+ workspaceDir,
316
+ makePage("skills/browser", { summary: "Shadow page." }),
317
+ );
318
+ skillState.entries = [
319
+ { id: "browser", content: "Seeded browser." },
320
+ { id: "calendar", content: "Seeded calendar." },
321
+ ];
322
+
323
+ const idx = await getPageIndex(workspaceDir);
324
+ expect(idx.entries.map((e) => e.slug)).toEqual([
325
+ "alice",
326
+ "skills/browser",
327
+ "skills/calendar",
328
+ ]);
329
+ expect(idx.bySlug.get("skills/browser")?.summary).toBe("Seeded browser.");
330
+ });
248
331
  });
249
332
 
250
333
  // ---------------------------------------------------------------------------
@@ -123,6 +123,39 @@ describe("renderRouterPrompt — page index handling", () => {
123
123
  });
124
124
  });
125
125
 
126
+ describe("renderRouterPrompt — replacement-pattern specials", () => {
127
+ // String.prototype.replaceAll interprets `$&`, `$'`, `` $` ``, `$$`, and
128
+ // `$n` in the replacement string as backreferences. LLM-generated page
129
+ // index content can contain literal `$` runs, so the substituter must
130
+ // pass values through unchanged.
131
+ const SPECIALS = "$& and $' and $` and $$ and $1";
132
+
133
+ test.each([
134
+ [
135
+ "pageIndexBlock",
136
+ { assistantName: "Aria", userName: "Alice", pageIndexBlock: SPECIALS },
137
+ ],
138
+ [
139
+ "assistantName",
140
+ {
141
+ assistantName: SPECIALS,
142
+ userName: "Alice",
143
+ pageIndexBlock: SAMPLE_INDEX,
144
+ },
145
+ ],
146
+ [
147
+ "userName",
148
+ {
149
+ assistantName: "Aria",
150
+ userName: SPECIALS,
151
+ pageIndexBlock: SAMPLE_INDEX,
152
+ },
153
+ ],
154
+ ])("renders %s with $ specials verbatim", (_, opts) => {
155
+ expect(renderRouterPrompt(opts)).toContain(SPECIALS);
156
+ });
157
+ });
158
+
126
159
  describe("renderRouterPrompt — determinism & snapshot stability", () => {
127
160
  test("returns the same string for the same inputs", () => {
128
161
  const opts = {
@@ -196,7 +229,7 @@ describe("resolveRouterPrompt — override path", () => {
196
229
  };
197
230
 
198
231
  test("null overridePath returns the bundled prompt verbatim", () => {
199
- expect(resolveRouterPrompt(null, STD_OPTS)).toEqual(
232
+ expect(resolveRouterPrompt(null, tmpDir, STD_OPTS)).toEqual(
200
233
  renderRouterPrompt(STD_OPTS),
201
234
  );
202
235
  });
@@ -209,16 +242,35 @@ describe("resolveRouterPrompt — override path", () => {
209
242
  "utf-8",
210
243
  );
211
244
 
212
- const out = resolveRouterPrompt(overridePath, STD_OPTS);
245
+ const out = resolveRouterPrompt(overridePath, tmpDir, STD_OPTS);
213
246
  expect(out).toContain("Hi Aria, you are routing for Alice.");
214
247
  expect(out).toContain(SAMPLE_INDEX);
215
248
  expect(out).not.toContain("{{ASSISTANT_NAME}}");
216
249
  expect(out).not.toContain("{{PAGE_INDEX}}");
217
250
  });
218
251
 
252
+ test("relative override path is resolved under the passed workspaceDir, not the default workspace", () => {
253
+ // Write the override into the per-test temp dir, which acts as a
254
+ // non-default workspace. The configured path is purely relative so the
255
+ // loader must resolve it against the supplied workspaceDir — if it
256
+ // resolved against the process-wide default workspace instead, the file
257
+ // wouldn't be found and the bundled prompt would be returned.
258
+ const relativeName = "router-override.md";
259
+ writeFileSync(
260
+ join(tmpDir, relativeName),
261
+ "Routed via {{ASSISTANT_NAME}} for {{USER_NAME}} :: {{PAGE_INDEX}}",
262
+ "utf-8",
263
+ );
264
+
265
+ const out = resolveRouterPrompt(relativeName, tmpDir, STD_OPTS);
266
+ expect(out).toContain("Routed via Aria for Alice");
267
+ expect(out).toContain(SAMPLE_INDEX);
268
+ expect(out).not.toEqual(renderRouterPrompt(STD_OPTS));
269
+ });
270
+
219
271
  test("missing override file falls back to bundled prompt", () => {
220
272
  const overridePath = join(tmpDir, "does-not-exist.md");
221
- expect(resolveRouterPrompt(overridePath, STD_OPTS)).toEqual(
273
+ expect(resolveRouterPrompt(overridePath, tmpDir, STD_OPTS)).toEqual(
222
274
  renderRouterPrompt(STD_OPTS),
223
275
  );
224
276
  });
@@ -226,7 +278,7 @@ describe("resolveRouterPrompt — override path", () => {
226
278
  test("empty override file falls back to bundled prompt", () => {
227
279
  const overridePath = join(tmpDir, "empty.md");
228
280
  writeFileSync(overridePath, " \n\t\n", "utf-8");
229
- expect(resolveRouterPrompt(overridePath, STD_OPTS)).toEqual(
281
+ expect(resolveRouterPrompt(overridePath, tmpDir, STD_OPTS)).toEqual(
230
282
  renderRouterPrompt(STD_OPTS),
231
283
  );
232
284
  });
@@ -234,7 +286,7 @@ describe("resolveRouterPrompt — override path", () => {
234
286
  test("override that is a directory falls back to bundled prompt", () => {
235
287
  // Pass the temp directory itself as the override path — lstat sees a
236
288
  // directory, not a regular file, so the loader bails to bundled.
237
- expect(resolveRouterPrompt(tmpDir, STD_OPTS)).toEqual(
289
+ expect(resolveRouterPrompt(tmpDir, tmpDir, STD_OPTS)).toEqual(
238
290
  renderRouterPrompt(STD_OPTS),
239
291
  );
240
292
  });
@@ -247,7 +299,7 @@ describe("resolveRouterPrompt — override path", () => {
247
299
  "utf-8",
248
300
  );
249
301
 
250
- const out = resolveRouterPrompt(overridePath, {
302
+ const out = resolveRouterPrompt(overridePath, tmpDir, {
251
303
  assistantName: null,
252
304
  userName: null,
253
305
  pageIndexBlock: "",