@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
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Asserts `listConsolidationRuns` maps background-conversation rows tagged
3
+ * with `source = MEMORY_V2_CONSOLIDATION_SOURCE` into the heartbeat-runs
4
+ * response shape, derives `status` / `finishedAt` / `durationMs` from
5
+ * **assistant-message presence** (not `lastMessageAt`), and clamps the
6
+ * `limit` query param.
7
+ *
8
+ * Synthetic-field semantics covered here:
9
+ * - `id` and `conversationId` both equal the conversation row's id.
10
+ * - `scheduledFor` and `startedAt` both equal `conversation.createdAt`
11
+ * (no separate schedule timestamp on the row).
12
+ * - `finishedAt` is the `createdAt` of the LATEST assistant message,
13
+ * NOT `conversation.lastMessageAt` — the kickoff user prompt bumps
14
+ * `lastMessageAt` before the agent runs, so it cannot be used as a
15
+ * completion signal.
16
+ * - `durationMs` is `finishedAt − startedAt` when both are present, else
17
+ * null.
18
+ * - `status` is `"ok"` when the conversation has at least one assistant
19
+ * message (positive evidence the agent emitted output) and `"running"`
20
+ * otherwise — including the case where only the kickoff user prompt
21
+ * has been persisted.
22
+ * - `skipReason` and `error` are always null — the conversation row
23
+ * alone cannot distinguish a clean run from a mid-flight crash even
24
+ * once assistant output exists.
25
+ */
26
+
27
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
28
+
29
+ mock.module("../../../util/logger.js", () => ({
30
+ getLogger: () =>
31
+ new Proxy({} as Record<string, unknown>, {
32
+ get: () => () => {},
33
+ }),
34
+ }));
35
+
36
+ import { createConversation } from "../../../memory/conversation-crud.js";
37
+ import { getDb } from "../../../memory/db-connection.js";
38
+ import { initializeDb } from "../../../memory/db-init.js";
39
+ import { rawRun } from "../../../memory/raw-query.js";
40
+ import { ROUTES } from "../consolidation-routes.js";
41
+ import type { RouteDefinition } from "../types.js";
42
+
43
+ initializeDb();
44
+
45
+ function resetTables(): void {
46
+ const db = getDb();
47
+ db.run(`DELETE FROM messages`);
48
+ db.run(`DELETE FROM conversations`);
49
+ }
50
+
51
+ function findHandler(operationId: string): RouteDefinition["handler"] {
52
+ const route = ROUTES.find((r) => r.operationId === operationId);
53
+ if (!route) throw new Error(`Route ${operationId} not found`);
54
+ return route.handler;
55
+ }
56
+
57
+ function insertMessage(
58
+ conversationId: string,
59
+ role: string,
60
+ createdAt: number,
61
+ ): void {
62
+ rawRun(
63
+ "INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES (?, ?, ?, ?, ?)",
64
+ `msg-${conversationId}-${role}-${createdAt}`,
65
+ conversationId,
66
+ role,
67
+ "x",
68
+ createdAt,
69
+ );
70
+ }
71
+
72
+ interface RunRecord {
73
+ id: string;
74
+ scheduledFor: number;
75
+ startedAt: number | null;
76
+ finishedAt: number | null;
77
+ durationMs: number | null;
78
+ status: "ok" | "running";
79
+ skipReason: string | null;
80
+ error: string | null;
81
+ conversationId: string | null;
82
+ createdAt: number;
83
+ }
84
+
85
+ interface ListRunsResponse {
86
+ runs: RunRecord[];
87
+ }
88
+
89
+ describe("listConsolidationRuns handler", () => {
90
+ beforeEach(() => {
91
+ resetTables();
92
+ });
93
+
94
+ test("returns only conversations sourced from memory_v2_consolidation", async () => {
95
+ createConversation({ title: "c1", source: "memory_v2_consolidation" });
96
+ createConversation({ title: "h1", source: "heartbeat" });
97
+ createConversation({ title: "u1", source: "user" });
98
+
99
+ const handler = findHandler("listConsolidationRuns");
100
+ const result = (await handler({})) as ListRunsResponse;
101
+
102
+ expect(result.runs).toHaveLength(1);
103
+ });
104
+
105
+ test("synthesizes status='ok' with finishedAt from latest assistant message", async () => {
106
+ const conv = createConversation({
107
+ title: "c1",
108
+ source: "memory_v2_consolidation",
109
+ });
110
+ rawRun(
111
+ "UPDATE conversations SET created_at = ? WHERE id = ?",
112
+ 1000,
113
+ conv.id,
114
+ );
115
+ // Kickoff user prompt at t=1100 (bumps lastMessageAt — must NOT be
116
+ // mistaken for completion).
117
+ insertMessage(conv.id, "user", 1100);
118
+ // Agent's first assistant turn at t=2000.
119
+ insertMessage(conv.id, "assistant", 2000);
120
+ // Agent's final assistant turn at t=2500.
121
+ insertMessage(conv.id, "assistant", 2500);
122
+
123
+ const handler = findHandler("listConsolidationRuns");
124
+ const result = (await handler({})) as ListRunsResponse;
125
+
126
+ expect(result.runs).toHaveLength(1);
127
+ const run = result.runs[0]!;
128
+ expect(run.id).toBe(conv.id);
129
+ expect(run.conversationId).toBe(conv.id);
130
+ expect(run.status).toBe("ok");
131
+ expect(run.scheduledFor).toBe(1000);
132
+ expect(run.startedAt).toBe(1000);
133
+ // finishedAt = createdAt of LATEST assistant message (2500), NOT
134
+ // the conversation's lastMessageAt (which sqlite triggers may or may
135
+ // not have updated here — irrelevant to this endpoint).
136
+ expect(run.finishedAt).toBe(2500);
137
+ expect(run.durationMs).toBe(1500);
138
+ expect(run.createdAt).toBe(1000);
139
+ });
140
+
141
+ test("synthesizes status='running' when conversation has no assistant message", async () => {
142
+ createConversation({ title: "c1", source: "memory_v2_consolidation" });
143
+
144
+ const handler = findHandler("listConsolidationRuns");
145
+ const result = (await handler({})) as ListRunsResponse;
146
+
147
+ expect(result.runs).toHaveLength(1);
148
+ const run = result.runs[0]!;
149
+ expect(run.status).toBe("running");
150
+ expect(run.finishedAt).toBeNull();
151
+ expect(run.durationMs).toBeNull();
152
+ });
153
+
154
+ test("status stays 'running' when only the kickoff user prompt exists (Codex bug regression guard)", async () => {
155
+ // Regression guard for the original `status from lastMessageAt`
156
+ // heuristic. `processMessage` persists the background kickoff prompt as
157
+ // a user message BEFORE the agent runs, which bumps
158
+ // `conversation.lastMessageAt`. A run that timed out / threw before
159
+ // emitting any assistant turn must still report status='running' (or
160
+ // an explicit failure status once one exists) — never 'ok'.
161
+ const conv = createConversation({
162
+ title: "c1",
163
+ source: "memory_v2_consolidation",
164
+ });
165
+ rawRun(
166
+ "UPDATE conversations SET created_at = ?, last_message_at = ? WHERE id = ?",
167
+ 1000,
168
+ 1100,
169
+ conv.id,
170
+ );
171
+ insertMessage(conv.id, "user", 1100);
172
+
173
+ const handler = findHandler("listConsolidationRuns");
174
+ const result = (await handler({})) as ListRunsResponse;
175
+
176
+ expect(result.runs).toHaveLength(1);
177
+ const run = result.runs[0]!;
178
+ expect(run.status).toBe("running");
179
+ expect(run.finishedAt).toBeNull();
180
+ expect(run.durationMs).toBeNull();
181
+ });
182
+
183
+ test("skipReason and error are always null (not derivable from conversation row)", async () => {
184
+ const conv = createConversation({
185
+ title: "c1",
186
+ source: "memory_v2_consolidation",
187
+ });
188
+ insertMessage(conv.id, "assistant", 2000);
189
+
190
+ const handler = findHandler("listConsolidationRuns");
191
+ const result = (await handler({})) as ListRunsResponse;
192
+
193
+ expect(result.runs[0]!.skipReason).toBeNull();
194
+ expect(result.runs[0]!.error).toBeNull();
195
+ });
196
+
197
+ test("orders runs by createdAt descending", async () => {
198
+ const a = createConversation({
199
+ title: "a",
200
+ source: "memory_v2_consolidation",
201
+ });
202
+ const b = createConversation({
203
+ title: "b",
204
+ source: "memory_v2_consolidation",
205
+ });
206
+ const c = createConversation({
207
+ title: "c",
208
+ source: "memory_v2_consolidation",
209
+ });
210
+ rawRun("UPDATE conversations SET created_at = ? WHERE id = ?", 1000, a.id);
211
+ rawRun("UPDATE conversations SET created_at = ? WHERE id = ?", 3000, b.id);
212
+ rawRun("UPDATE conversations SET created_at = ? WHERE id = ?", 2000, c.id);
213
+
214
+ const handler = findHandler("listConsolidationRuns");
215
+ const result = (await handler({})) as ListRunsResponse;
216
+
217
+ expect(result.runs.map((r) => r.id)).toEqual([b.id, c.id, a.id]);
218
+ });
219
+
220
+ test("limit defaults to 20, clamps to [1, 100], and falls back on non-numeric input", async () => {
221
+ for (let i = 0; i < 5; i++) {
222
+ createConversation({
223
+ title: `c${i}`,
224
+ source: "memory_v2_consolidation",
225
+ });
226
+ }
227
+
228
+ const handler = findHandler("listConsolidationRuns");
229
+
230
+ // Default — all 5 returned (under the 20 default).
231
+ const def = (await handler({})) as ListRunsResponse;
232
+ expect(def.runs).toHaveLength(5);
233
+
234
+ // Explicit limit honored.
235
+ const lim2 = (await handler({
236
+ queryParams: { limit: "2" },
237
+ })) as ListRunsResponse;
238
+ expect(lim2.runs).toHaveLength(2);
239
+
240
+ // Negative clamps to 1.
241
+ const neg = (await handler({
242
+ queryParams: { limit: "-5" },
243
+ })) as ListRunsResponse;
244
+ expect(neg.runs).toHaveLength(1);
245
+
246
+ // Zero clamps to 1.
247
+ const zero = (await handler({
248
+ queryParams: { limit: "0" },
249
+ })) as ListRunsResponse;
250
+ expect(zero.runs).toHaveLength(1);
251
+
252
+ // Non-numeric falls back to the default (20 → all 5 here).
253
+ const bad = (await handler({
254
+ queryParams: { limit: "garbage" },
255
+ })) as ListRunsResponse;
256
+ expect(bad.runs).toHaveLength(5);
257
+ });
258
+ });
@@ -23,6 +23,7 @@ mock.module("../../assistant-event-hub.js", () => ({
23
23
  publish: async () => {},
24
24
  subscribe: () => () => {},
25
25
  },
26
+ broadcastMessage: () => {},
26
27
  }));
27
28
 
28
29
  import { getDb } from "../../../memory/db-connection.js";
@@ -114,7 +115,10 @@ function seedConversation(id: string): void {
114
115
  describe("PUT /v1/conversations/:id/inference-profile", () => {
115
116
  beforeEach(() => {
116
117
  clearConversations();
117
- configLlmProfiles = { fast: { model: "model-a" }, slow: { model: "model-b" } };
118
+ configLlmProfiles = {
119
+ fast: { model: "model-a" },
120
+ slow: { model: "model-b" },
121
+ };
118
122
  });
119
123
 
120
124
  test("PUT with ttlSeconds=600 → response includes sessionId (UUID), expiresAt, ttlSeconds=600", async () => {
@@ -14,6 +14,12 @@ import {
14
14
 
15
15
  let rawConfigFixture: Record<string, unknown> = {};
16
16
  let savedRawConfig: Record<string, unknown> | null = null;
17
+ // Counters / spies so tests can assert that `commitConfigWrite` ran its
18
+ // post-write side effects. Each `replaceProfileRoute.handler` call that
19
+ // hits `commitConfigWrite` should bump these once.
20
+ let invalidateConfigCacheCalls = 0;
21
+ let initializeProvidersCalls = 0;
22
+ let clearEmbeddingBackendCacheCalls = 0;
17
23
 
18
24
  mock.module("../../../config/loader.js", () => ({
19
25
  loadRawConfig: () => structuredClone(rawConfigFixture),
@@ -26,6 +32,28 @@ mock.module("../../../config/loader.js", () => ({
26
32
  ) => {
27
33
  Object.assign(target, overrides);
28
34
  },
35
+ // `commitConfigWrite` (used by `handleReplaceInferenceProfile`) pulls
36
+ // in `getConfig` for the provider reinit's config arg and
37
+ // `invalidateConfigCache` so the next caller sees the fresh write.
38
+ // Stub both: getConfig returns whatever was last saved (or the fixture
39
+ // if nothing has been saved yet) and the cache-invalidation function
40
+ // is a counter so we can assert it fired.
41
+ getConfig: () => structuredClone(savedRawConfig ?? rawConfigFixture),
42
+ invalidateConfigCache: () => {
43
+ invalidateConfigCacheCalls += 1;
44
+ },
45
+ }));
46
+
47
+ mock.module("../../../providers/registry.js", () => ({
48
+ initializeProviders: async () => {
49
+ initializeProvidersCalls += 1;
50
+ },
51
+ }));
52
+
53
+ mock.module("../../../memory/embedding-backend.js", () => ({
54
+ clearEmbeddingBackendCache: () => {
55
+ clearEmbeddingBackendCacheCalls += 1;
56
+ },
29
57
  }));
30
58
 
31
59
  import type { ConversationCreateType } from "../../../memory/conversation-crud.js";
@@ -43,6 +71,10 @@ import {
43
71
  memoryV2ActivationLogs,
44
72
  messages,
45
73
  } from "../../../memory/schema.js";
74
+ import {
75
+ createConnection,
76
+ getConnection,
77
+ } from "../../../providers/inference/connections.js";
46
78
  import { ROUTES } from "../conversation-query-routes.js";
47
79
 
48
80
  // Local subset: this test only exercises a single concept row.
@@ -288,6 +320,9 @@ describe("GET /v1/messages/:id/llm-context — conversationTotalEstimatedCostUsd
288
320
  describe("PUT /v1/config/llm/profiles/:name", () => {
289
321
  beforeEach(() => {
290
322
  savedRawConfig = null;
323
+ invalidateConfigCacheCalls = 0;
324
+ initializeProvidersCalls = 0;
325
+ clearEmbeddingBackendCacheCalls = 0;
291
326
  rawConfigFixture = {
292
327
  llm: {
293
328
  profiles: {
@@ -313,8 +348,8 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
313
348
  };
314
349
  });
315
350
 
316
- test("owns contextWindow maxInputTokens while preserving non-UI profile leaves", () => {
317
- const result = replaceProfileRoute.handler({
351
+ test("owns contextWindow maxInputTokens while preserving non-UI profile leaves", async () => {
352
+ const result = await replaceProfileRoute.handler({
318
353
  pathParams: { name: "custom" },
319
354
  body: {
320
355
  provider: "openai",
@@ -343,8 +378,8 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
343
378
  expect(savedProfile.openrouter).toEqual({ only: ["anthropic"] });
344
379
  });
345
380
 
346
- test("writes only the replacement contextWindow maxInputTokens override", () => {
347
- const result = replaceProfileRoute.handler({
381
+ test("writes only the replacement contextWindow maxInputTokens override", async () => {
382
+ const result = await replaceProfileRoute.handler({
348
383
  pathParams: { name: "custom" },
349
384
  body: {
350
385
  provider: "openai",
@@ -375,8 +410,8 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
375
410
  expect(savedProfile.openrouter).toEqual({ only: ["anthropic"] });
376
411
  });
377
412
 
378
- test("writes provider_connection when present in body", () => {
379
- const result = replaceProfileRoute.handler({
413
+ test("writes provider_connection when present in body", async () => {
414
+ const result = await replaceProfileRoute.handler({
380
415
  pathParams: { name: "custom" },
381
416
  body: {
382
417
  provider: "openai",
@@ -396,7 +431,7 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
396
431
  expect(savedProfile.provider_connection).toBe("personal-openai");
397
432
  });
398
433
 
399
- test("clears provider_connection when omitted from body (UI-owned key)", () => {
434
+ test("auto-derives provider_connection when omitted from body (Any active)", async () => {
400
435
  // Seed an existing binding so the test starts from a non-empty state.
401
436
  (
402
437
  rawConfigFixture.llm as {
@@ -404,14 +439,63 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
404
439
  }
405
440
  ).profiles.custom.provider_connection = "stale-openai";
406
441
 
407
- const result = replaceProfileRoute.handler({
442
+ const result = await replaceProfileRoute.handler({
408
443
  pathParams: { name: "custom" },
409
444
  body: {
410
445
  provider: "openai",
411
446
  model: "gpt-5.5",
412
447
  // provider_connection deliberately omitted — the UI cleared the
413
- // picker back to "Any active" and the route must wipe the saved
414
- // binding, not silently round-trip it.
448
+ // picker back to "Any active". The route auto-derives an active
449
+ // connection for the provider to prevent stale inheritance during
450
+ // config deep-merge.
451
+ },
452
+ });
453
+
454
+ expect(result).toEqual({ ok: true });
455
+ const savedProfile = (
456
+ savedRawConfig?.llm as {
457
+ profiles: Record<string, Record<string, unknown>>;
458
+ }
459
+ ).profiles.custom;
460
+
461
+ // The canonical "openai-managed" connection exists in the test DB;
462
+ // the route auto-derives it when the UI omits provider_connection.
463
+ expect(savedProfile.provider_connection).toBe("openai-managed");
464
+ });
465
+
466
+ test("auto-derives provider_connection for BYOK provider (Any active)", async () => {
467
+ // Seed a fireworks connection in the DB.
468
+ createConnection(getDb(), {
469
+ name: "fireworks",
470
+ provider: "fireworks",
471
+ auth: { type: "api_key", credential: "fireworks:api_key" },
472
+ });
473
+
474
+ const result = await replaceProfileRoute.handler({
475
+ pathParams: { name: "custom" },
476
+ body: {
477
+ provider: "fireworks",
478
+ model: "accounts/fireworks/models/llama-v3p1-8b-instruct",
479
+ },
480
+ });
481
+
482
+ expect(result).toEqual({ ok: true });
483
+ const savedProfile = (
484
+ savedRawConfig?.llm as {
485
+ profiles: Record<string, Record<string, unknown>>;
486
+ }
487
+ ).profiles.custom;
488
+
489
+ expect(savedProfile.provider).toBe("fireworks");
490
+ expect(savedProfile.provider_connection).toBe("fireworks");
491
+ });
492
+
493
+ test("auto-creates provider_connection when no connection exists for provider", async () => {
494
+ const result = await replaceProfileRoute.handler({
495
+ pathParams: { name: "custom" },
496
+ body: {
497
+ provider: "openrouter",
498
+ model: "anthropic/claude-sonnet-4-6",
415
499
  },
416
500
  });
417
501
 
@@ -422,7 +506,16 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
422
506
  }
423
507
  ).profiles.custom;
424
508
 
425
- expect(savedProfile.provider_connection).toBeUndefined();
509
+ expect(savedProfile.provider).toBe("openrouter");
510
+ expect(savedProfile.provider_connection).toBe("openrouter-personal");
511
+
512
+ const conn = getConnection(getDb(), "openrouter-personal");
513
+ expect(conn).not.toBeNull();
514
+ expect(conn!.provider).toBe("openrouter");
515
+ expect(conn!.auth).toEqual({
516
+ type: "api_key",
517
+ credential: "credential/openrouter/api_key",
518
+ });
426
519
  });
427
520
 
428
521
  describe("managed profile guard", () => {
@@ -439,8 +532,8 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
439
532
  };
440
533
  });
441
534
 
442
- test("allows label edit on managed profile, preserving seed fields", () => {
443
- const result = replaceProfileRoute.handler({
535
+ test("allows label edit on managed profile, preserving seed fields", async () => {
536
+ const result = await replaceProfileRoute.handler({
444
537
  pathParams: { name: "balanced" },
445
538
  body: { label: "My Balanced" },
446
539
  });
@@ -459,8 +552,8 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
459
552
  expect(savedProfile.source).toBe("managed");
460
553
  });
461
554
 
462
- test("allows status edit on managed profile", () => {
463
- const result = replaceProfileRoute.handler({
555
+ test("allows status edit on managed profile", async () => {
556
+ const result = await replaceProfileRoute.handler({
464
557
  pathParams: { name: "balanced" },
465
558
  body: { status: "disabled" },
466
559
  });
@@ -476,8 +569,8 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
476
569
  expect(savedProfile.provider).toBe("anthropic");
477
570
  });
478
571
 
479
- test("allows label+status edit together", () => {
480
- const result = replaceProfileRoute.handler({
572
+ test("allows label+status edit together", async () => {
573
+ const result = await replaceProfileRoute.handler({
481
574
  pathParams: { name: "balanced" },
482
575
  body: { label: "Renamed", status: "disabled" },
483
576
  });
@@ -493,25 +586,81 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
493
586
  expect(savedProfile.status).toBe("disabled");
494
587
  });
495
588
 
496
- test("rejects provider edit on managed profile with disallowed-keys error", () => {
497
- expect(() =>
589
+ test("rejects provider edit on managed profile with disallowed-keys error", async () => {
590
+ // The handler is `async`, so synchronous BadRequest throws still
591
+ // surface as a rejected promise; assert via `.rejects.toThrow`.
592
+ await expect(
498
593
  replaceProfileRoute.handler({
499
594
  pathParams: { name: "balanced" },
500
595
  body: { provider: "openai", model: "gpt-5" },
501
596
  }),
502
- ).toThrow(/Cannot edit managed profile "balanced" fields \[provider, model\]/);
597
+ ).rejects.toThrow(
598
+ /Cannot edit managed profile "balanced" fields \[provider, model\]/,
599
+ );
503
600
  });
504
601
 
505
- test("rejects mixed allowed+disallowed fields", () => {
602
+ test("rejects mixed allowed+disallowed fields", async () => {
506
603
  // label is allowed but maxTokens is not — must reject without partially
507
604
  // applying label, so saver should never be invoked.
508
- expect(() =>
605
+ await expect(
509
606
  replaceProfileRoute.handler({
510
607
  pathParams: { name: "balanced" },
511
608
  body: { label: "Try", maxTokens: 999 },
512
609
  }),
513
- ).toThrow(/Cannot edit managed profile "balanced" fields \[maxTokens\]/);
610
+ ).rejects.toThrow(
611
+ /Cannot edit managed profile "balanced" fields \[maxTokens\]/,
612
+ );
514
613
  expect(savedRawConfig).toBeNull();
614
+ // Reject path skips commitConfigWrite entirely — no provider reinit
615
+ // or cache invalidation should fire on a guard rejection.
616
+ expect(initializeProvidersCalls).toBe(0);
617
+ expect(invalidateConfigCacheCalls).toBe(0);
618
+ expect(clearEmbeddingBackendCacheCalls).toBe(0);
619
+ });
620
+ });
621
+
622
+ describe("commitConfigWrite side effects", () => {
623
+ test("status flip on managed profile triggers provider reinit + cache invalidation", async () => {
624
+ // Seed a managed profile that the user will disable. commitConfigWrite
625
+ // must reinit the provider registry so the status change is reflected
626
+ // in the running daemon immediately, not at the next watcher tick.
627
+ (rawConfigFixture.llm as { profiles: Record<string, unknown> }).profiles[
628
+ "balanced"
629
+ ] = {
630
+ source: "managed",
631
+ provider: "anthropic",
632
+ model: "claude-sonnet-4-6",
633
+ label: "Balanced",
634
+ status: "active",
635
+ };
636
+
637
+ const result = await replaceProfileRoute.handler({
638
+ pathParams: { name: "balanced" },
639
+ body: { status: "disabled" },
640
+ });
641
+
642
+ expect(result).toEqual({ ok: true });
643
+ expect(initializeProvidersCalls).toBe(1);
644
+ expect(invalidateConfigCacheCalls).toBe(1);
645
+ expect(clearEmbeddingBackendCacheCalls).toBe(1);
646
+ });
647
+
648
+ test("custom profile provider swap triggers provider reinit + cache invalidation", async () => {
649
+ // Custom profile path: provider/model swap on a user-owned profile.
650
+ // Same side-effect contract — registry must reinit so the new
651
+ // provider is wired into the running daemon without restart.
652
+ const result = await replaceProfileRoute.handler({
653
+ pathParams: { name: "custom" },
654
+ body: {
655
+ provider: "openai",
656
+ model: "gpt-5.5",
657
+ },
658
+ });
659
+
660
+ expect(result).toEqual({ ok: true });
661
+ expect(initializeProvidersCalls).toBe(1);
662
+ expect(invalidateConfigCacheCalls).toBe(1);
663
+ expect(clearEmbeddingBackendCacheCalls).toBe(1);
515
664
  });
516
665
  });
517
666
  });