@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
@@ -3,7 +3,7 @@ import { describe, expect, test } from "bun:test";
3
3
  import { z } from "zod";
4
4
 
5
5
  import { resolveCallSiteConfig } from "../config/llm-resolver.js";
6
- import { LLMSchema } from "../config/schemas/llm.js";
6
+ import { type LLMCallSite, LLMSchema } from "../config/schemas/llm.js";
7
7
 
8
8
  const fullDefault = {
9
9
  provider: "anthropic" as const,
@@ -369,7 +369,8 @@ describe("resolveCallSiteConfig", () => {
369
369
  const resolved = resolveCallSiteConfig("mainAgent", llm, {
370
370
  overrideProfile: "nonexistent",
371
371
  });
372
- // Falls through to default the missing override contributes nothing.
372
+ // overrideProfile is set so the shipped default's profile is stripped.
373
+ // The nonexistent overrideProfile also adds nothing. Falls through to default.
373
374
  expect(resolved.effort).toBe("max");
374
375
  expect(resolved.model).toBe("claude-opus-4-7");
375
376
  });
@@ -536,4 +537,256 @@ describe("resolveCallSiteConfig", () => {
536
537
  expect(resolved.maxTokens).toBe(65536);
537
538
  expect(resolved.contextWindow.maxInputTokens).toBe(1048576);
538
539
  });
540
+
541
+ test("call site with no explicit config falls back to CALL_SITE_DEFAULTS", () => {
542
+ const llm = LLMSchema.parse({
543
+ default: fullDefault,
544
+ profiles: {
545
+ "cost-optimized": {
546
+ model: "claude-haiku-4-5-20251001",
547
+ effort: "low",
548
+ },
549
+ },
550
+ });
551
+ const resolved = resolveCallSiteConfig("memoryExtraction", llm);
552
+ expect(resolved.model).toBe("claude-haiku-4-5-20251001");
553
+ expect(resolved.effort).toBe("low");
554
+ });
555
+
556
+ test("explicit callSites config overrides CALL_SITE_DEFAULTS", () => {
557
+ const llm = LLMSchema.parse({
558
+ default: fullDefault,
559
+ profiles: {
560
+ "cost-optimized": {
561
+ model: "claude-haiku-4-5-20251001",
562
+ effort: "low",
563
+ },
564
+ "quality-optimized": { model: "claude-opus-4-7", effort: "max" },
565
+ },
566
+ callSites: {
567
+ memoryExtraction: { profile: "quality-optimized" },
568
+ },
569
+ });
570
+ const resolved = resolveCallSiteConfig("memoryExtraction", llm);
571
+ expect(resolved.model).toBe("claude-opus-4-7");
572
+ expect(resolved.effort).toBe("max");
573
+ });
574
+
575
+ test("BYOK: disabled managed profile falls back to custom-* user profile", () => {
576
+ const llm = LLMSchema.parse({
577
+ default: {
578
+ ...fullDefault,
579
+ provider: "openai",
580
+ model: "gpt-5.5",
581
+ provider_connection: "openai-personal",
582
+ },
583
+ profiles: {
584
+ "cost-optimized": {
585
+ status: "disabled",
586
+ model: "claude-haiku-4-5-20251001",
587
+ provider: "anthropic",
588
+ provider_connection: "anthropic-managed",
589
+ },
590
+ "custom-cost-optimized": {
591
+ source: "user",
592
+ model: "gpt-5.4-nano",
593
+ provider: "openai",
594
+ provider_connection: "openai-personal",
595
+ },
596
+ "custom-balanced": {
597
+ source: "user",
598
+ model: "gpt-5.5",
599
+ provider: "openai",
600
+ provider_connection: "openai-personal",
601
+ },
602
+ },
603
+ activeProfile: "custom-balanced",
604
+ });
605
+ const resolved = resolveCallSiteConfig("memoryExtraction", llm);
606
+ expect(resolved.provider).toBe("openai");
607
+ expect(resolved.model).toBe("gpt-5.4-nano");
608
+ expect(resolved.provider_connection).toBe("openai-personal");
609
+ });
610
+
611
+ test("BYOK: strips profile when neither managed nor custom-* is available", () => {
612
+ const llm = LLMSchema.parse({
613
+ default: {
614
+ ...fullDefault,
615
+ provider: "openai",
616
+ model: "gpt-5.5",
617
+ provider_connection: "openai-personal",
618
+ },
619
+ profiles: {
620
+ "cost-optimized": {
621
+ status: "disabled",
622
+ model: "claude-haiku-4-5-20251001",
623
+ provider: "anthropic",
624
+ provider_connection: "anthropic-managed",
625
+ },
626
+ "custom-balanced": {
627
+ model: "gpt-5.5",
628
+ provider: "openai",
629
+ provider_connection: "openai-personal",
630
+ },
631
+ },
632
+ activeProfile: "custom-balanced",
633
+ });
634
+ const resolved = resolveCallSiteConfig("memoryExtraction", llm);
635
+ expect(resolved.provider).toBe("openai");
636
+ expect(resolved.model).toBe("gpt-5.5");
637
+ expect(resolved.provider_connection).toBe("openai-personal");
638
+ });
639
+
640
+ test("BYOK full-workspace: cost-optimized call sites use custom-cost-optimized, balanced use custom-balanced", () => {
641
+ const byokConfig = LLMSchema.parse({
642
+ default: {
643
+ ...fullDefault,
644
+ provider: "openai",
645
+ model: "gpt-5.5",
646
+ provider_connection: "openai-personal",
647
+ },
648
+ profiles: {
649
+ balanced: {
650
+ status: "disabled",
651
+ source: "managed",
652
+ provider: "anthropic",
653
+ model: "claude-sonnet-4-6",
654
+ provider_connection: "anthropic-managed",
655
+ },
656
+ "cost-optimized": {
657
+ status: "disabled",
658
+ source: "managed",
659
+ provider: "anthropic",
660
+ model: "claude-haiku-4-5-20251001",
661
+ provider_connection: "anthropic-managed",
662
+ },
663
+ "quality-optimized": {
664
+ status: "disabled",
665
+ source: "managed",
666
+ provider: "anthropic",
667
+ model: "claude-opus-4-7",
668
+ provider_connection: "anthropic-managed",
669
+ },
670
+ "custom-balanced": {
671
+ source: "user",
672
+ provider: "openai",
673
+ model: "gpt-5.5",
674
+ provider_connection: "openai-personal",
675
+ },
676
+ "custom-cost-optimized": {
677
+ source: "user",
678
+ provider: "openai",
679
+ model: "gpt-5.4-nano",
680
+ provider_connection: "openai-personal",
681
+ },
682
+ "custom-quality-optimized": {
683
+ source: "user",
684
+ provider: "openai",
685
+ model: "gpt-5.5-pro",
686
+ provider_connection: "openai-personal",
687
+ },
688
+ },
689
+ activeProfile: "custom-balanced",
690
+ });
691
+
692
+ const callSites: LLMCallSite[] = [
693
+ "mainAgent", "subagentSpawn", "heartbeatAgent", "filingAgent",
694
+ "compactionAgent", "analyzeConversation", "callAgent",
695
+ "memoryExtraction", "memoryConsolidation", "memoryRetrieval",
696
+ "memoryRouter", "recall", "conversationSummarization",
697
+ "commitMessage", "conversationStarters", "replySuggestion",
698
+ "conversationTitle", "identityIntro", "emptyStateGreeting",
699
+ "notificationDecision", "interactionClassifier", "inference",
700
+ ];
701
+
702
+ for (const cs of callSites) {
703
+ const resolved = resolveCallSiteConfig(cs, byokConfig);
704
+ expect(resolved.provider_connection).not.toBe("anthropic-managed");
705
+ expect(resolved.provider).toBe("openai");
706
+ }
707
+
708
+ // Cost-optimized call sites should use the user's nano model
709
+ const costSite = resolveCallSiteConfig("heartbeatAgent", byokConfig);
710
+ expect(costSite.model).toBe("gpt-5.4-nano");
711
+
712
+ // Balanced call sites should use the user's balanced model
713
+ const balancedSite = resolveCallSiteConfig("mainAgent", byokConfig);
714
+ expect(balancedSite.model).toBe("gpt-5.5");
715
+ });
716
+
717
+ test("BYOK: tuning overrides from defaults apply on top of custom-* fallback profile", () => {
718
+ const byokConfig = LLMSchema.parse({
719
+ default: {
720
+ ...fullDefault,
721
+ provider: "openai",
722
+ model: "gpt-5.5",
723
+ provider_connection: "openai-personal",
724
+ },
725
+ profiles: {
726
+ "cost-optimized": {
727
+ status: "disabled",
728
+ provider: "anthropic",
729
+ model: "claude-haiku-4-5-20251001",
730
+ provider_connection: "anthropic-managed",
731
+ },
732
+ "custom-cost-optimized": {
733
+ source: "user",
734
+ provider: "openai",
735
+ model: "gpt-5.4-nano",
736
+ provider_connection: "openai-personal",
737
+ },
738
+ "custom-balanced": {
739
+ provider: "openai",
740
+ model: "gpt-5.5",
741
+ provider_connection: "openai-personal",
742
+ },
743
+ },
744
+ activeProfile: "custom-balanced",
745
+ });
746
+
747
+ const resolved = resolveCallSiteConfig("commitMessage", byokConfig);
748
+ expect(resolved.provider).toBe("openai");
749
+ expect(resolved.model).toBe("gpt-5.4-nano");
750
+ expect(resolved.maxTokens).toBe(120);
751
+ expect(resolved.effort).toBe("low");
752
+ expect(resolved.thinking.enabled).toBe(false);
753
+ });
754
+
755
+ test("overrideProfile wins over CALL_SITE_DEFAULTS profile for non-main call sites", () => {
756
+ const llm = LLMSchema.parse({
757
+ default: fullDefault,
758
+ profiles: {
759
+ "cost-optimized": { model: "claude-haiku-4-5-20251001", effort: "low" },
760
+ "quality-optimized": { model: "claude-opus-4-7", effort: "max" },
761
+ },
762
+ });
763
+ const resolved = resolveCallSiteConfig("inference", llm, {
764
+ overrideProfile: "quality-optimized",
765
+ });
766
+ expect(resolved.model).toBe("claude-opus-4-7");
767
+ expect(resolved.effort).toBe("max");
768
+ });
769
+
770
+ test("profile with provider but no provider_connection inherits stale default connection (JARVIS-861)", () => {
771
+ // This test documents the merge behavior that causes JARVIS-861: a profile
772
+ // overrides `provider` but not `provider_connection`, so the deep merge
773
+ // inherits a stale connection from the default layer. The fix is in the
774
+ // dispatch layer (connection-resolution auto-resolves the mismatch).
775
+ const llm = LLMSchema.parse({
776
+ default: {
777
+ ...fullDefault,
778
+ provider_connection: "anthropic-managed",
779
+ },
780
+ profiles: {
781
+ fireworks: { provider: "fireworks", model: "accounts/fireworks/models/kimi-k2p5" },
782
+ },
783
+ activeProfile: "fireworks",
784
+ });
785
+
786
+ const resolved = resolveCallSiteConfig("mainAgent", llm);
787
+
788
+ expect(resolved.provider).toBe("fireworks");
789
+ // The merge inherits the stale connection — the dispatch layer handles this.
790
+ expect(resolved.provider_connection).toBe("anthropic-managed");
791
+ });
539
792
  });
@@ -1223,7 +1223,14 @@ describe("getUsageGroupedSeries", () => {
1223
1223
  describe("queryUnreportedUsageEvents", () => {
1224
1224
  beforeEach(() => {
1225
1225
  const db = getDb();
1226
+ // Order matters: clear `llm_usage_events` (no FK), then `messages`
1227
+ // (FK to conversations cascades, but be explicit), then
1228
+ // `conversations`. The conversation-level metadata tests below
1229
+ // depend on a clean conversations + messages slate so JOINs are
1230
+ // deterministic.
1226
1231
  db.run(`DELETE FROM llm_usage_events`);
1232
+ db.run(`DELETE FROM messages`);
1233
+ db.run(`DELETE FROM conversations`);
1227
1234
  });
1228
1235
 
1229
1236
  test("returns events with createdAt strictly greater than afterCreatedAt in ascending order", () => {
@@ -1277,4 +1284,111 @@ describe("queryUnreportedUsageEvents", () => {
1277
1284
  const events = queryUnreportedUsageEvents(0, undefined, 100);
1278
1285
  expect(events).toHaveLength(0);
1279
1286
  });
1287
+
1288
+ // -------------------------------------------------------------------------
1289
+ // Conversation-level metadata (conversationType + turnIndex). These are
1290
+ // JOIN-computed at telemetry-query time so the reporter can emit them on
1291
+ // the wire without persisting extra columns on `llm_usage_events`.
1292
+ // -------------------------------------------------------------------------
1293
+
1294
+ test("conversationType is JOINed from the conversations table", () => {
1295
+ const db = getDb();
1296
+ const now = Date.now();
1297
+ db.run(
1298
+ `INSERT INTO conversations (id, conversation_type, created_at, updated_at) VALUES ('conv-std', 'standard', ${now}, ${now})`,
1299
+ );
1300
+ db.run(
1301
+ `INSERT INTO conversations (id, conversation_type, created_at, updated_at) VALUES ('conv-bg', 'background', ${now}, ${now})`,
1302
+ );
1303
+
1304
+ insertEventAt(1000, { conversationId: "conv-std" });
1305
+ insertEventAt(2000, { conversationId: "conv-bg" });
1306
+ insertEventAt(3000, { conversationId: null });
1307
+
1308
+ const events = queryUnreportedUsageEvents(0, undefined, 100);
1309
+ expect(events).toHaveLength(3);
1310
+ expect(events[0].conversationType).toBe("standard");
1311
+ expect(events[1].conversationType).toBe("background");
1312
+ // LLM calls without a parent conversation get null — LEFT JOIN, not INNER.
1313
+ expect(events[2].conversationType).toBeNull();
1314
+ });
1315
+
1316
+ test("turnIndex counts real user turns up to the LLM call's createdAt", () => {
1317
+ const db = getDb();
1318
+ const now = Date.now();
1319
+ db.run(
1320
+ `INSERT INTO conversations (id, conversation_type, created_at, updated_at) VALUES ('conv-1', 'standard', ${now}, ${now})`,
1321
+ );
1322
+ // Two user messages in the conversation.
1323
+ db.run(
1324
+ `INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES ('m1', 'conv-1', 'user', 'hello', 1000)`,
1325
+ );
1326
+ db.run(
1327
+ `INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES ('m2', 'conv-1', 'user', 'follow up', 3000)`,
1328
+ );
1329
+
1330
+ // Three LLM calls across the timeline:
1331
+ // - mid-turn-1 (between m1 and m2)
1332
+ // - exactly at m2's createdAt (still counts m2: `created_at <= e.created_at`)
1333
+ // - after m2
1334
+ insertEventAt(2000, { conversationId: "conv-1" });
1335
+ insertEventAt(3000, { conversationId: "conv-1" });
1336
+ insertEventAt(4000, { conversationId: "conv-1" });
1337
+
1338
+ const events = queryUnreportedUsageEvents(0, undefined, 100);
1339
+ expect(events).toHaveLength(3);
1340
+ expect(events[0].turnIndex).toBe(1);
1341
+ expect(events[1].turnIndex).toBe(2);
1342
+ expect(events[2].turnIndex).toBe(2);
1343
+ });
1344
+
1345
+ test("turnIndex is null when the LLM call has no conversationId", () => {
1346
+ insertEventAt(1000, { conversationId: null });
1347
+
1348
+ const events = queryUnreportedUsageEvents(0, undefined, 100);
1349
+ expect(events).toHaveLength(1);
1350
+ expect(events[0].conversationId).toBeNull();
1351
+ expect(events[0].turnIndex).toBeNull();
1352
+ });
1353
+
1354
+ test("turnIndex skips tool_result rows when counting", () => {
1355
+ const db = getDb();
1356
+ const now = Date.now();
1357
+ db.run(
1358
+ `INSERT INTO conversations (id, conversation_type, created_at, updated_at) VALUES ('conv-tr', 'standard', ${now}, ${now})`,
1359
+ );
1360
+ // One real user turn, then a tool_result row (which should be
1361
+ // ignored), then the LLM call. Expected turn_index = 1.
1362
+ db.run(
1363
+ `INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES ('real-1', 'conv-tr', 'user', 'real text', 1000)`,
1364
+ );
1365
+ db.run(
1366
+ `INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES ('tool-1', 'conv-tr', 'user', '[{"type":"tool_result","tool_use_id":"x","content":""}]', 1500)`,
1367
+ );
1368
+ insertEventAt(2000, { conversationId: "conv-tr" });
1369
+
1370
+ const events = queryUnreportedUsageEvents(0, undefined, 100);
1371
+ expect(events).toHaveLength(1);
1372
+ expect(events[0].turnIndex).toBe(1);
1373
+ });
1374
+
1375
+ test("turnIndex is 0 when the LLM call fires before any user message", () => {
1376
+ const db = getDb();
1377
+ const now = Date.now();
1378
+ db.run(
1379
+ `INSERT INTO conversations (id, conversation_type, created_at, updated_at) VALUES ('conv-early', 'standard', ${now}, ${now})`,
1380
+ );
1381
+ // LLM call fires at t=1000; first user message is at t=2000.
1382
+ insertEventAt(1000, { conversationId: "conv-early" });
1383
+ db.run(
1384
+ `INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES ('later', 'conv-early', 'user', 'hi', 2000)`,
1385
+ );
1386
+
1387
+ const events = queryUnreportedUsageEvents(0, undefined, 100);
1388
+ expect(events).toHaveLength(1);
1389
+ // Conversation exists but no user turn has fired yet. The CASE
1390
+ // short-circuits only on null conversationId, so we get a real 0.
1391
+ // Analytics can treat 0 as "pre-first-turn" if needed.
1392
+ expect(events[0].turnIndex).toBe(0);
1393
+ });
1280
1394
  });
@@ -67,6 +67,8 @@ mock.module("../config/loader.js", () => ({
67
67
  },
68
68
  getConfig: () => rawConfig,
69
69
  invalidateConfigCache: () => {},
70
+ withSuppressedConfigDiskWrites: async (fn: () => unknown) => fn(),
71
+ withSuppressedConfigDiskWritesSync: (fn: () => unknown) => fn(),
70
72
  }));
71
73
 
72
74
  mock.module("../providers/registry.js", () => ({
@@ -77,6 +79,16 @@ mock.module("../memory/embedding-backend.js", () => ({
77
79
  clearEmbeddingBackendCache: () => {},
78
80
  }));
79
81
 
82
+ // The replace-profile handler auto-derives `provider_connection` from the
83
+ // first active connection matching the requested provider when the body
84
+ // omits it. That path queries the `provider_connections` table, which the
85
+ // test doesn't migrate — stub it out so the guard logic stays the focus.
86
+ mock.module("../providers/inference/connections.js", () => ({
87
+ listConnections: () => [],
88
+ createConnection: () => ({ ok: false, error: { code: "already_exists" } }),
89
+ PROVIDERS_REQUIRING_BASE_URL_AND_MODELS: new Set(["openai-compatible"]),
90
+ }));
91
+
80
92
  import { ROUTES } from "../runtime/routes/conversation-query-routes.js";
81
93
  import { BadRequestError } from "../runtime/routes/errors.js";
82
94
 
@@ -96,39 +108,39 @@ beforeEach(() => {
96
108
  // ---------------------------------------------------------------------------
97
109
 
98
110
  describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
99
- test("rejects edits to quality-optimized that touch non-label/status fields", () => {
100
- expect(() =>
111
+ test("rejects edits to quality-optimized that touch non-label/status fields", async () => {
112
+ await expect(
101
113
  replaceRoute.handler({
102
114
  pathParams: { name: "quality-optimized" },
103
115
  body: { provider: "openai", model: "gpt-4o" },
104
116
  }),
105
- ).toThrow(
117
+ ).rejects.toThrow(
106
118
  'Cannot edit managed profile "quality-optimized" fields [provider, model]. ' +
107
119
  "Only label and status may be edited; duplicate to a custom profile to change other fields.",
108
120
  );
109
121
  });
110
122
 
111
- test("rejects edits to balanced", () => {
112
- expect(() =>
123
+ test("rejects edits to balanced", async () => {
124
+ await expect(
113
125
  replaceRoute.handler({
114
126
  pathParams: { name: "balanced" },
115
127
  body: { provider: "openai", model: "gpt-4o" },
116
128
  }),
117
- ).toThrow(BadRequestError);
129
+ ).rejects.toThrow(BadRequestError);
118
130
  });
119
131
 
120
- test("rejects edits to cost-optimized", () => {
121
- expect(() =>
132
+ test("rejects edits to cost-optimized", async () => {
133
+ await expect(
122
134
  replaceRoute.handler({
123
135
  pathParams: { name: "cost-optimized" },
124
136
  body: { provider: "openai", model: "gpt-4o" },
125
137
  }),
126
- ).toThrow(BadRequestError);
138
+ ).rejects.toThrow(BadRequestError);
127
139
  });
128
140
 
129
- test("allows edits to custom-balanced (user-owned)", () => {
141
+ test("allows edits to custom-balanced (user-owned)", async () => {
130
142
  savedRaw = null;
131
- const result = replaceRoute.handler({
143
+ const result = await replaceRoute.handler({
132
144
  pathParams: { name: "custom-balanced" },
133
145
  body: { provider: "openai", model: "gpt-4o" },
134
146
  });
@@ -136,9 +148,9 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
136
148
  expect(savedRaw).not.toBeNull();
137
149
  });
138
150
 
139
- test("allows edits to a user-defined profile", () => {
151
+ test("allows edits to a user-defined profile", async () => {
140
152
  savedRaw = null;
141
- const result = replaceRoute.handler({
153
+ const result = await replaceRoute.handler({
142
154
  pathParams: { name: "my-custom" },
143
155
  body: { provider: "openai", model: "gpt-4o" },
144
156
  });
@@ -147,14 +159,14 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
147
159
  });
148
160
 
149
161
  // -------------------------------------------------------------------------
150
- // Null-as-clear sentinel: `patchManagedProfileFields` has handled `null`
151
- // for label and status since #30362, but the Zod `ProfileEntry` schema
152
- // had them as `.optional()` (not `.nullable()`), which rejected null
153
- // payloads at parse time before the route handler ever saw them.
154
- // These tests lock the round-trip now that the schema accepts null.
162
+ // Null-as-clear sentinel: clients send `{ label: null }` or
163
+ // `{ status: null }` to clear a managed profile's overrides back to the
164
+ // seed defaults. The Zod `ProfileEntry` schema accepts null for both
165
+ // fields, and the managed-profile guard / `patchManagedProfileFields`
166
+ // propagate the clear through to disk. These tests lock the round-trip.
155
167
  // -------------------------------------------------------------------------
156
168
 
157
- test("PUT { label: null } on managed profile clears the label on disk", () => {
169
+ test("PUT { label: null } on managed profile clears the label on disk", async () => {
158
170
  savedRaw = null;
159
171
  rawConfig = {
160
172
  llm: {
@@ -168,7 +180,7 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
168
180
  },
169
181
  },
170
182
  };
171
- const result = replaceRoute.handler({
183
+ const result = await replaceRoute.handler({
172
184
  pathParams: { name: "balanced" },
173
185
  body: { label: null },
174
186
  });
@@ -182,7 +194,7 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
182
194
  expect(profile.source).toBe("managed");
183
195
  });
184
196
 
185
- test("PUT { status: null } on managed profile clears status (back to active-by-absence)", () => {
197
+ test("PUT { status: null } on managed profile clears status (back to active-by-absence)", async () => {
186
198
  savedRaw = null;
187
199
  rawConfig = {
188
200
  llm: {
@@ -196,7 +208,7 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
196
208
  },
197
209
  },
198
210
  };
199
- const result = replaceRoute.handler({
211
+ const result = await replaceRoute.handler({
200
212
  pathParams: { name: "quality-optimized" },
201
213
  body: { status: null },
202
214
  });
@@ -208,7 +220,7 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
208
220
  expect(profile.model).toBe("claude-opus");
209
221
  });
210
222
 
211
- test("PUT { label: null, status: null } clears both in a single request", () => {
223
+ test("PUT { label: null, status: null } clears both in a single request", async () => {
212
224
  savedRaw = null;
213
225
  rawConfig = {
214
226
  llm: {
@@ -223,7 +235,7 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
223
235
  },
224
236
  },
225
237
  };
226
- const result = replaceRoute.handler({
238
+ const result = await replaceRoute.handler({
227
239
  pathParams: { name: "cost-optimized" },
228
240
  body: { label: null, status: null },
229
241
  });
@@ -236,7 +248,7 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
236
248
  expect(profile.model).toBe("claude-haiku");
237
249
  });
238
250
 
239
- test("PUT { label: null, status: 'disabled' } mixes clear + set in one call", () => {
251
+ test("PUT { label: null, status: 'disabled' } mixes clear + set in one call", async () => {
240
252
  savedRaw = null;
241
253
  rawConfig = {
242
254
  llm: {
@@ -250,7 +262,7 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
250
262
  },
251
263
  },
252
264
  };
253
- const result = replaceRoute.handler({
265
+ const result = await replaceRoute.handler({
254
266
  pathParams: { name: "balanced" },
255
267
  body: { label: null, status: "disabled" },
256
268
  });
@@ -261,17 +273,17 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
261
273
  expect(profile.status).toBe("disabled");
262
274
  });
263
275
 
264
- test("PUT { label: '' } on managed profile still rejected by `.min(1)`", () => {
276
+ test("PUT { label: '' } on managed profile still rejected by `.min(1)`", async () => {
265
277
  // `.nullable()` only widens the type to accept null — empty strings
266
278
  // still fail the min-length check, which is correct: an empty string
267
279
  // would persist as a literal "" override, not the clear-to-seed
268
280
  // intent. Clients must send `null` to clear.
269
- expect(() =>
281
+ await expect(
270
282
  replaceRoute.handler({
271
283
  pathParams: { name: "balanced" },
272
284
  body: { label: "" },
273
285
  }),
274
- ).toThrow(BadRequestError);
286
+ ).rejects.toThrow(BadRequestError);
275
287
  });
276
288
  });
277
289