@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,195 @@
1
+ /**
2
+ * Integration tests for the agent loop's `provider_error` recording path.
3
+ *
4
+ * When the `llmCall` pipeline throws (provider rejected the request before
5
+ * returning a usable response), the loop must emit a `provider_error` event
6
+ * carrying the loop-level raw request and the thrown error so downstream
7
+ * consumers can persist an `llm_request_logs` row. Without this, rejected
8
+ * calls leave nothing in the LLM inspector — only a pino log line.
9
+ *
10
+ * Coverage:
11
+ * - Emits `provider_error` with `rawRequest`, `error`, and `actualProvider`
12
+ * when the provider throws a `ProviderError`.
13
+ * - `rawRequest` carries the message history, tools, and system prompt the
14
+ * loop attempted to send — so the row replays/debugs cleanly.
15
+ * - `actualProvider` echoes `ProviderError.provider` when available, falling
16
+ * back to `provider.name` for non-ProviderError throws.
17
+ * - The error is still re-thrown internally (the existing `error` event
18
+ * still fires after the new `provider_error` event), preserving the
19
+ * outer-catch behavior (abort/Sentry/break).
20
+ * - Skips emission on user-aborted runs — there is no provider rejection
21
+ * worth recording when the user cancelled.
22
+ */
23
+
24
+ import { describe, expect, test } from "bun:test";
25
+
26
+ import type { AgentEvent } from "../agent/loop.js";
27
+ import { AgentLoop } from "../agent/loop.js";
28
+ import type {
29
+ Message,
30
+ Provider,
31
+ ProviderResponse,
32
+ SendMessageOptions,
33
+ ToolDefinition,
34
+ } from "../providers/types.js";
35
+ import { ProviderError } from "../util/errors.js";
36
+
37
+ /**
38
+ * Build a provider that throws on every `sendMessage` call. Records what
39
+ * the loop attempted to send so the test can assert `rawRequest` carries
40
+ * the right payload.
41
+ */
42
+ function makeThrowingProvider(
43
+ name: string,
44
+ throwFn: () => Error,
45
+ ): {
46
+ provider: Provider;
47
+ calls: Array<{
48
+ messages: Message[];
49
+ tools?: ToolDefinition[];
50
+ systemPrompt?: string;
51
+ }>;
52
+ } {
53
+ const calls: Array<{
54
+ messages: Message[];
55
+ tools?: ToolDefinition[];
56
+ systemPrompt?: string;
57
+ }> = [];
58
+ const provider: Provider = {
59
+ name,
60
+ async sendMessage(
61
+ messages: Message[],
62
+ tools?: ToolDefinition[],
63
+ systemPrompt?: string,
64
+ _options?: SendMessageOptions,
65
+ ): Promise<ProviderResponse> {
66
+ calls.push({ messages: [...messages], tools, systemPrompt });
67
+ throw throwFn();
68
+ },
69
+ };
70
+ return { provider, calls };
71
+ }
72
+
73
+ describe("AgentLoop provider_error event emission", () => {
74
+ test("emits provider_error with loop-level rawRequest when provider throws ProviderError", async () => {
75
+ const thrown = new ProviderError(
76
+ "Anthropic API error (429): rate limited",
77
+ "anthropic",
78
+ 429,
79
+ { retryAfterMs: 1500 },
80
+ );
81
+ const { provider, calls } = makeThrowingProvider("anthropic", () => thrown);
82
+
83
+ const events: AgentEvent[] = [];
84
+ const loop = new AgentLoop(provider, "you are a helpful assistant");
85
+
86
+ await loop.run(
87
+ [{ role: "user", content: [{ type: "text", text: "hi" }] }],
88
+ (e) => {
89
+ events.push(e);
90
+ },
91
+ );
92
+
93
+ expect(calls).toHaveLength(1);
94
+
95
+ const providerErrorEvent = events.find((e) => e.type === "provider_error");
96
+ expect(providerErrorEvent).toBeDefined();
97
+ if (providerErrorEvent?.type !== "provider_error") {
98
+ throw new Error("type narrowing");
99
+ }
100
+ expect(providerErrorEvent.error).toBe(thrown);
101
+ expect(providerErrorEvent.actualProvider).toBe("anthropic");
102
+
103
+ // rawRequest should carry the loop-level abstract shape: messages,
104
+ // tools, systemPrompt, and the provider name we tried to dispatch
105
+ // through. The provider-specific shape (e.g. Gemini's `contents`) is
106
+ // never built because the provider threw before returning it.
107
+ const raw = providerErrorEvent.rawRequest as Record<string, unknown>;
108
+ expect(raw.provider).toBe("anthropic");
109
+ expect(raw.systemPrompt).toBe("you are a helpful assistant");
110
+ expect(Array.isArray(raw.messages)).toBe(true);
111
+ expect((raw.messages as Message[])[0].role).toBe("user");
112
+ });
113
+
114
+ test("error event still fires after provider_error (outer catch behavior unchanged)", async () => {
115
+ const thrown = new ProviderError(
116
+ "Gemini API error (500): internal",
117
+ "gemini",
118
+ 500,
119
+ );
120
+ const { provider } = makeThrowingProvider("gemini", () => thrown);
121
+
122
+ const events: AgentEvent[] = [];
123
+ const loop = new AgentLoop(provider, "system");
124
+
125
+ await loop.run(
126
+ [{ role: "user", content: [{ type: "text", text: "hi" }] }],
127
+ (e) => {
128
+ events.push(e);
129
+ },
130
+ );
131
+
132
+ const providerErrorIdx = events.findIndex(
133
+ (e) => e.type === "provider_error",
134
+ );
135
+ const errorIdx = events.findIndex((e) => e.type === "error");
136
+ expect(providerErrorIdx).toBeGreaterThanOrEqual(0);
137
+ expect(errorIdx).toBeGreaterThanOrEqual(0);
138
+ // Recording-first ordering is load-bearing: a consumer that sees the
139
+ // generic `error` event and shuts the stream down must have already
140
+ // received the `provider_error` row for the rejected call.
141
+ expect(providerErrorIdx).toBeLessThan(errorIdx);
142
+ });
143
+
144
+ test("falls back to provider.name when a non-ProviderError is thrown", async () => {
145
+ const thrown = new Error("unexpected SDK boom");
146
+ const { provider } = makeThrowingProvider("openai", () => thrown);
147
+
148
+ const events: AgentEvent[] = [];
149
+ const loop = new AgentLoop(provider, "system");
150
+
151
+ await loop.run(
152
+ [{ role: "user", content: [{ type: "text", text: "hi" }] }],
153
+ (e) => {
154
+ events.push(e);
155
+ },
156
+ );
157
+
158
+ const providerErrorEvent = events.find((e) => e.type === "provider_error");
159
+ expect(providerErrorEvent).toBeDefined();
160
+ if (providerErrorEvent?.type !== "provider_error") {
161
+ throw new Error("type narrowing");
162
+ }
163
+ // The thrown Error has no `.provider` field, so the event falls back to
164
+ // the dispatching provider's `name` — keeps the persisted log row's
165
+ // `provider` column populated even for surprise errors.
166
+ expect(providerErrorEvent.actualProvider).toBe("openai");
167
+ expect(providerErrorEvent.error).toBe(thrown);
168
+ });
169
+
170
+ test("does NOT emit provider_error on user-aborted runs", async () => {
171
+ const controller = new AbortController();
172
+ const thrown = new Error("aborted");
173
+ const { provider } = makeThrowingProvider("anthropic", () => {
174
+ // Pre-abort then throw so the loop's catch sees `signal.aborted === true`.
175
+ controller.abort();
176
+ return thrown;
177
+ });
178
+
179
+ const events: AgentEvent[] = [];
180
+ const loop = new AgentLoop(provider, "system");
181
+
182
+ await loop.run(
183
+ [{ role: "user", content: [{ type: "text", text: "hi" }] }],
184
+ (e) => {
185
+ events.push(e);
186
+ },
187
+ controller.signal,
188
+ );
189
+
190
+ const providerErrorEvent = events.find((e) => e.type === "provider_error");
191
+ // Cancellation should never produce a recording row — there's no
192
+ // provider rejection worth logging when the user pulled the plug.
193
+ expect(providerErrorEvent).toBeUndefined();
194
+ });
195
+ });
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Regression test: `classifyDiskPressureTurnPolicy` must receive the
3
+ * caller-supplied `callSite` from `wakeAgentForOpportunity`, not a
4
+ * hardcoded `"mainAgent"`.
5
+ *
6
+ * Today the disk-pressure classifier's `isBackgroundTurn` branches on
7
+ * `isDirectWake` before it consults `callSite`, so the hardcoded value
8
+ * did not produce a runtime regression. But the metadata recorded the
9
+ * wrong call site for any wake initiated by a background job (e.g.
10
+ * memory-v2 consolidation), and the inconsistency would bite the moment
11
+ * policy ever branches on `callSite` for wake turns. This test pins the
12
+ * forwarded value so the contract stays honest.
13
+ */
14
+
15
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
16
+
17
+ import type {
18
+ DiskPressureTurnMetadata,
19
+ DiskPressureTurnPolicyDecision,
20
+ } from "../daemon/disk-pressure-policy.js";
21
+ import type { Message } from "../providers/types.js";
22
+
23
+ mock.module("../memory/conversation-crud.js", () => ({
24
+ getConversationOverrideProfile: () => undefined,
25
+ }));
26
+
27
+ mock.module("../config/loader.js", () => ({
28
+ getConfig: () => ({ llm: {} }),
29
+ }));
30
+
31
+ mock.module("../config/llm-context-resolution.js", () => ({
32
+ resolveEffectiveContextWindow: () => ({ maxInputTokens: 200_000 }),
33
+ }));
34
+
35
+ const classifyCalls: DiskPressureTurnMetadata[] = [];
36
+ mock.module("../daemon/disk-pressure-policy.js", () => ({
37
+ classifyDiskPressureTurnPolicy: (
38
+ _status: unknown,
39
+ metadata: DiskPressureTurnMetadata,
40
+ ): DiskPressureTurnPolicyDecision => {
41
+ classifyCalls.push(metadata);
42
+ return { action: "allow-normal" };
43
+ },
44
+ }));
45
+
46
+ mock.module("../daemon/disk-pressure-guard.js", () => ({
47
+ getDiskPressureStatus: () => ({
48
+ enabled: false,
49
+ state: "disabled",
50
+ locked: false,
51
+ acknowledged: false,
52
+ overrideActive: false,
53
+ effectivelyLocked: false,
54
+ lockId: null,
55
+ usagePercent: null,
56
+ thresholdPercent: 95,
57
+ path: null,
58
+ lastCheckedAt: null,
59
+ blockedCapabilities: [],
60
+ error: null,
61
+ }),
62
+ }));
63
+
64
+ import {
65
+ __resetWakeChainForTests,
66
+ wakeAgentForOpportunity,
67
+ type WakeTarget,
68
+ } from "../runtime/agent-wake.js";
69
+
70
+ function makeTarget(): WakeTarget {
71
+ const history: Message[] = [];
72
+ let processing = false;
73
+ return {
74
+ conversationId: "conv-wake-callsite",
75
+ agentLoop: {
76
+ run: (async (messages: Message[]) =>
77
+ messages) as WakeTarget["agentLoop"]["run"],
78
+ },
79
+ getMessages: () => history,
80
+ pushMessage: (msg) => {
81
+ history.push(msg);
82
+ },
83
+ emitAgentEvent: () => {},
84
+ isProcessing: () => processing,
85
+ markProcessing: (on) => {
86
+ processing = on;
87
+ },
88
+ persistTailMessage: async () => {},
89
+ };
90
+ }
91
+
92
+ beforeEach(() => {
93
+ __resetWakeChainForTests();
94
+ classifyCalls.length = 0;
95
+ });
96
+
97
+ describe("wakeAgentForOpportunity — disk-pressure callSite forwarding", () => {
98
+ test("forwards opts.callSite to classifyDiskPressureTurnPolicy", async () => {
99
+ const target = makeTarget();
100
+
101
+ await wakeAgentForOpportunity(
102
+ {
103
+ conversationId: target.conversationId,
104
+ hint: "consolidate buffer",
105
+ source: "memory_v2_consolidation",
106
+ callSite: "memoryV2Consolidation",
107
+ },
108
+ { resolveTarget: async () => target },
109
+ );
110
+
111
+ expect(classifyCalls).toHaveLength(1);
112
+ expect(classifyCalls[0]!.callSite).toBe("memoryV2Consolidation");
113
+ expect(classifyCalls[0]!.isDirectWake).toBe(true);
114
+ });
115
+
116
+ test("defaults to mainAgent when opts.callSite is omitted", async () => {
117
+ const target = makeTarget();
118
+
119
+ await wakeAgentForOpportunity(
120
+ {
121
+ conversationId: target.conversationId,
122
+ hint: "resume",
123
+ source: "scheduler",
124
+ },
125
+ { resolveTarget: async () => target },
126
+ );
127
+
128
+ expect(classifyCalls).toHaveLength(1);
129
+ expect(classifyCalls[0]!.callSite).toBe("mainAgent");
130
+ });
131
+ });
@@ -223,6 +223,22 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
223
223
  expect(system[1].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
224
224
  });
225
225
 
226
+ test("omits empty dynamic system block after cache boundary", async () => {
227
+ const staticBlock = "You are a helpful assistant.";
228
+ const prompt = staticBlock + SYSTEM_PROMPT_CACHE_BOUNDARY;
229
+
230
+ await provider.sendMessage([userMsg("Hi")], undefined, prompt);
231
+
232
+ const system = lastStreamParams!.system as Array<{
233
+ type: string;
234
+ text: string;
235
+ cache_control?: { type: string; ttl?: string };
236
+ }>;
237
+ expect(system).toHaveLength(1);
238
+ expect(system[0].text).toBe(staticBlock);
239
+ expect(system[0].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
240
+ });
241
+
226
242
  test("drops static system block cache_control when total would exceed 4", async () => {
227
243
  const staticBlock = "You are a helpful assistant.";
228
244
  const dynamicBlock = "User workspace files here.";
@@ -2346,6 +2362,35 @@ describe("OpenRouterProvider — Anthropic dispatch", () => {
2346
2362
  expect(lastStreamParams!.reasoning).toBeUndefined();
2347
2363
  });
2348
2364
 
2365
+ test("sends OpenRouter app-attribution headers on Anthropic-compatible requests", async () => {
2366
+ const { OpenRouterProvider } =
2367
+ await import("../providers/openrouter/client.js");
2368
+ const provider = new OpenRouterProvider(
2369
+ "or-key",
2370
+ "anthropic/claude-sonnet-4.6",
2371
+ );
2372
+ await provider.sendMessage([userMsg("hi")], undefined, undefined, {
2373
+ config: {
2374
+ usageAttributionHeaders: {
2375
+ "Vellum-Organization-Id": "org-123",
2376
+ },
2377
+ },
2378
+ });
2379
+
2380
+ expect(_lastStreamOptions?.headers).toEqual(
2381
+ expect.objectContaining({
2382
+ "HTTP-Referer": "https://www.vellum.ai",
2383
+ "X-OpenRouter-Title": "Vellum Assistant",
2384
+ "X-OpenRouter-Categories": "personal-agent,cli-agent",
2385
+ "Vellum-Organization-Id": "org-123",
2386
+ }),
2387
+ );
2388
+ expect(lastStreamParams).not.toHaveProperty("HTTP-Referer");
2389
+ expect(lastStreamParams).not.toHaveProperty("X-OpenRouter-Title");
2390
+ expect(lastStreamParams).not.toHaveProperty("X-OpenRouter-Categories");
2391
+ expect(lastStreamParams).not.toHaveProperty("usageAttributionHeaders");
2392
+ });
2393
+
2349
2394
  test("per-request model override routes based on the overridden model", async () => {
2350
2395
  const { OpenRouterProvider } =
2351
2396
  await import("../providers/openrouter/client.js");
@@ -22,15 +22,18 @@ function makeApp(overrides: Partial<AppDefinition> = {}): AppDefinition {
22
22
  }
23
23
 
24
24
  function makeMockStore(overrides: Partial<AppStore> = {}): AppStore {
25
+ const files = new Set<string>();
25
26
  return {
26
27
  getApp: () => makeApp(),
27
28
  listApps: () => [makeApp()],
28
- appFileExists: () => false,
29
+ appFileExists: (_appId: string, path: string) => files.has(path),
29
30
  createApp: (params) =>
30
31
  makeApp({ name: params.name, description: params.description }),
31
32
  updateApp: (id, updates) => makeApp({ id, ...updates }),
32
33
  deleteApp: () => {},
33
- writeAppFile: () => {},
34
+ writeAppFile: (_appId: string, path: string) => {
35
+ files.add(path);
36
+ },
34
37
  ...overrides,
35
38
  };
36
39
  }
@@ -107,7 +110,10 @@ describe("app-builder skill tool scripts", () => {
107
110
  isError: false,
108
111
  });
109
112
  const result = await appCreateScript.run(
110
- { name: "Auto App" },
113
+ {
114
+ name: "Auto App",
115
+ source_files: { "src/main.tsx": "// real code" },
116
+ },
111
117
  makeContext({ proxyToolResolver: proxy }),
112
118
  );
113
119
  expect(result.isError).toBe(false);
@@ -1,13 +1,31 @@
1
- import { describe, expect, mock, test } from "bun:test";
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ type CompileResult = {
4
+ ok: boolean;
5
+ errors: string[];
6
+ warnings: string[];
7
+ durationMs: number;
8
+ };
9
+
10
+ let compileResultOverride: CompileResult = {
11
+ ok: true,
12
+ errors: [],
13
+ warnings: [],
14
+ durationMs: 0,
15
+ };
2
16
 
3
17
  mock.module("../bundler/app-compiler.js", () => ({
4
- compileApp: async () => ({
18
+ compileApp: async () => compileResultOverride,
19
+ }));
20
+
21
+ beforeEach(() => {
22
+ compileResultOverride = {
5
23
  ok: true,
6
24
  errors: [],
7
25
  warnings: [],
8
26
  durationMs: 0,
9
- }),
10
- }));
27
+ };
28
+ });
11
29
 
12
30
  import type { AppDefinition } from "../memory/app-store.js";
13
31
  import type { AppStore } from "../tools/apps/executors.js";
@@ -102,6 +120,204 @@ describe("executeAppCreate", () => {
102
120
  expect(files["src/main.tsx"]).toBeDefined();
103
121
  expect(files["src/main.tsx"]).toContain("import { render } from 'preact'");
104
122
  expect(files["src/main.tsx"]).toContain('{"Hello, New App!"}');
123
+ // next_steps directive must be present so the model keeps writing
124
+ // real source files instead of treating the scaffold as done.
125
+ const parsed = JSON.parse(result.content);
126
+ expect(parsed.next_steps).toContain("placeholder src/main.tsx");
127
+ expect(parsed.next_steps).toContain("app_refresh");
128
+ });
129
+
130
+ test("skips auto_open on scaffold even when proxy resolver is available", async () => {
131
+ const files: Record<string, string> = {};
132
+ const app = makeMultifileApp({ name: "New App" });
133
+ const store: AppStore = {
134
+ ...mockStore(app, files),
135
+ createApp: () => app,
136
+ };
137
+ let proxyCalled = false;
138
+ const proxyResolver = async () => {
139
+ proxyCalled = true;
140
+ return { content: JSON.stringify({ opened: true }), isError: false };
141
+ };
142
+
143
+ const result = await executeAppCreate(
144
+ { name: "New App" },
145
+ store,
146
+ proxyResolver,
147
+ );
148
+
149
+ expect(result.isError).toBe(false);
150
+ expect(proxyCalled).toBe(false);
151
+ const parsed = JSON.parse(result.content);
152
+ expect(parsed.auto_opened).toBeUndefined();
153
+ expect(parsed.next_steps).toContain("placeholder src/main.tsx");
154
+ expect(parsed.next_steps).toContain("app_refresh");
155
+ });
156
+
157
+ test("omits next_steps when main.tsx was pre-written before app_create", async () => {
158
+ // The agent's supported workflow is to write the real source files first
159
+ // and then call app_create. In that case the placeholder directive would
160
+ // be false and risk triggering a destructive rewrite of correct code.
161
+ const files: Record<string, string> = {
162
+ "src/main.tsx": "// real code from the agent",
163
+ };
164
+ const app = makeMultifileApp({ name: "Pre-written App" });
165
+ const store: AppStore = {
166
+ ...mockStore(app, files),
167
+ createApp: () => app,
168
+ };
169
+
170
+ const result = await executeAppCreate({ name: "Pre-written App" }, store);
171
+
172
+ expect(result.isError).toBe(false);
173
+ // The pre-written file must be preserved
174
+ expect(files["src/main.tsx"]).toBe("// real code from the agent");
175
+ const parsed = JSON.parse(result.content);
176
+ expect(parsed.next_steps).toBeUndefined();
177
+ });
178
+
179
+ test("includes next_steps when compile fails on the scaffold", async () => {
180
+ compileResultOverride = {
181
+ ok: false,
182
+ errors: ["unexpected token"],
183
+ warnings: [],
184
+ durationMs: 3,
185
+ };
186
+ const files: Record<string, string> = {};
187
+ const app = makeMultifileApp({ name: "Broken App" });
188
+ const store: AppStore = {
189
+ ...mockStore(app, files),
190
+ createApp: () => app,
191
+ };
192
+
193
+ const result = await executeAppCreate({ name: "Broken App" }, store);
194
+
195
+ expect(result.isError).toBe(false);
196
+ const parsed = JSON.parse(result.content);
197
+ expect(parsed.compile_errors).toEqual(["unexpected token"]);
198
+ expect(parsed.next_steps).toContain("placeholder src/main.tsx");
199
+ expect(parsed.next_steps).toContain("app_refresh");
200
+ });
201
+
202
+ test("omits next_steps when compile fails on pre-written files", async () => {
203
+ compileResultOverride = {
204
+ ok: false,
205
+ errors: ["unexpected token"],
206
+ warnings: [],
207
+ durationMs: 3,
208
+ };
209
+ const files: Record<string, string> = {
210
+ "src/main.tsx": "// agent's real (but broken) code",
211
+ };
212
+ const app = makeMultifileApp({ name: "Broken Pre-written App" });
213
+ const store: AppStore = {
214
+ ...mockStore(app, files),
215
+ createApp: () => app,
216
+ };
217
+
218
+ const result = await executeAppCreate(
219
+ { name: "Broken Pre-written App" },
220
+ store,
221
+ );
222
+
223
+ expect(result.isError).toBe(false);
224
+ const parsed = JSON.parse(result.content);
225
+ expect(parsed.compile_errors).toEqual(["unexpected token"]);
226
+ expect(parsed.next_steps).toBeUndefined();
227
+ });
228
+
229
+ test("suppresses auto_open when only scaffold exists", async () => {
230
+ const files: Record<string, string> = {};
231
+ const app = makeMultifileApp({ name: "New App" });
232
+ const store: AppStore = {
233
+ ...mockStore(app, files),
234
+ createApp: () => app,
235
+ };
236
+ let proxyCalledWith: Record<string, unknown> | undefined;
237
+ const proxyResolver = async (
238
+ toolName: string,
239
+ input: Record<string, unknown>,
240
+ ) => {
241
+ proxyCalledWith = { toolName, input };
242
+ return { content: JSON.stringify({ opened: true }), isError: false };
243
+ };
244
+
245
+ const result = await executeAppCreate(
246
+ { name: "New App" },
247
+ store,
248
+ proxyResolver,
249
+ );
250
+
251
+ expect(result.isError).toBe(false);
252
+ const parsed = JSON.parse(result.content);
253
+ expect(proxyCalledWith).toBeUndefined();
254
+ expect(parsed.auto_opened).toBeUndefined();
255
+ expect(parsed.next_steps).toContain("placeholder src/main.tsx");
256
+ });
257
+
258
+ test("fires auto_open when source_files includes main.tsx", async () => {
259
+ const files: Record<string, string> = {};
260
+ const app = makeMultifileApp({ name: "Real App" });
261
+ const store: AppStore = {
262
+ ...mockStore(app, files),
263
+ createApp: () => app,
264
+ };
265
+ let proxyCalledWith: Record<string, unknown> | undefined;
266
+ const proxyResolver = async (
267
+ toolName: string,
268
+ input: Record<string, unknown>,
269
+ ) => {
270
+ proxyCalledWith = { toolName, input };
271
+ return { content: JSON.stringify({ opened: true }), isError: false };
272
+ };
273
+
274
+ const result = await executeAppCreate(
275
+ {
276
+ name: "Real App",
277
+ source_files: {
278
+ "src/main.tsx":
279
+ "import { render } from 'preact';\nrender(<div>Real</div>, document.getElementById('app')!);",
280
+ "src/styles.css": "body { margin: 0; }",
281
+ },
282
+ },
283
+ store,
284
+ proxyResolver,
285
+ );
286
+
287
+ expect(result.isError).toBe(false);
288
+ const parsed = JSON.parse(result.content);
289
+ expect(proxyCalledWith).toBeDefined();
290
+ expect(parsed.auto_opened).toBe(true);
291
+ expect(parsed.next_steps).toBeUndefined();
292
+ expect(files["src/main.tsx"]).toContain("Real");
293
+ expect(files["src/styles.css"]).toContain("margin");
294
+ });
295
+
296
+ test("source_files are written and prevent scaffold", async () => {
297
+ const files: Record<string, string> = {};
298
+ const app = makeMultifileApp({ name: "Inline App" });
299
+ const store: AppStore = {
300
+ ...mockStore(app, files),
301
+ createApp: () => app,
302
+ };
303
+
304
+ const result = await executeAppCreate(
305
+ {
306
+ name: "Inline App",
307
+ source_files: {
308
+ "src/main.tsx": "// custom app code",
309
+ "src/components/App.tsx": "// App component",
310
+ },
311
+ },
312
+ store,
313
+ );
314
+
315
+ expect(result.isError).toBe(false);
316
+ expect(files["src/main.tsx"]).toBe("// custom app code");
317
+ expect(files["src/components/App.tsx"]).toBe("// App component");
318
+ expect(files["src/index.html"]).toBeDefined();
319
+ const parsed = JSON.parse(result.content);
320
+ expect(parsed.next_steps).toBeUndefined();
105
321
  });
106
322
 
107
323
  test("rejects retired html shortcut", async () => {
@@ -585,3 +585,38 @@ describe("indexer v1/v2 mutual exclusion for graph_extract", () => {
585
585
  );
586
586
  });
587
587
  });
588
+
589
+ // ─────────────────────────────────────────────────────────────────
590
+ // Indexer v1/v2 mutual exclusion: build_conversation_summary feeds
591
+ // v1-only readers (fetchRecentSummaries, semantic search). When
592
+ // memory.v2.enabled is on, the summary writes are unread, so the
593
+ // indexer must not enqueue them.
594
+ // ─────────────────────────────────────────────────────────────────
595
+
596
+ describe("indexer v1/v2 mutual exclusion for build_conversation_summary", () => {
597
+ const originalV2Enabled = TEST_CONFIG.memory.v2.enabled;
598
+
599
+ afterEach(() => {
600
+ TEST_CONFIG.memory.v2.enabled = originalV2Enabled;
601
+ });
602
+
603
+ test("v2 active (config on) → build_conversation_summary not enqueued", async () => {
604
+ TEST_CONFIG.memory.v2.enabled = true;
605
+
606
+ const source = createConversation("summary-v2-active");
607
+ await indexMessages(source.id, 2);
608
+
609
+ expect(countJobsOfType("build_conversation_summary", source.id)).toBe(0);
610
+ });
611
+
612
+ test("config gate off → build_conversation_summary enqueued", async () => {
613
+ TEST_CONFIG.memory.v2.enabled = false;
614
+
615
+ const source = createConversation("summary-v2-config-off");
616
+ await indexMessages(source.id, 2);
617
+
618
+ expect(
619
+ countJobsOfType("build_conversation_summary", source.id),
620
+ ).toBeGreaterThanOrEqual(1);
621
+ });
622
+ });