@vellumai/assistant 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (503) hide show
  1. package/ARCHITECTURE.md +11 -12
  2. package/docker-entrypoint.sh +13 -2
  3. package/docker-init-apt-root.sh +79 -6
  4. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  5. package/openapi.yaml +945 -36
  6. package/package.json +1 -1
  7. package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
  8. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  9. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +272 -0
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  14. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  15. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  16. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  17. package/src/__tests__/compaction-events.test.ts +1 -1
  18. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  19. package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
  20. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  22. package/src/__tests__/config-watcher.test.ts +1 -1
  23. package/src/__tests__/context-token-estimator.test.ts +112 -57
  24. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  27. package/src/__tests__/conversation-agent-loop.test.ts +77 -3
  28. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  29. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  30. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  32. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  33. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  34. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  35. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  36. package/src/__tests__/conversation-pairing.test.ts +2 -2
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  39. package/src/__tests__/conversation-queue.test.ts +1 -1
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
  41. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  42. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  43. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  44. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  45. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  46. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  47. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  48. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  49. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  50. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  51. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  52. package/src/__tests__/date-context.test.ts +45 -0
  53. package/src/__tests__/dm-backfill.test.ts +64 -0
  54. package/src/__tests__/dm-persistence.test.ts +33 -0
  55. package/src/__tests__/document-find-replace.test.ts +501 -0
  56. package/src/__tests__/external-plugin-loader.test.ts +91 -19
  57. package/src/__tests__/first-greeting.test.ts +23 -2
  58. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  59. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  60. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  61. package/src/__tests__/heartbeat-service.test.ts +24 -164
  62. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  63. package/src/__tests__/host-app-control-proxy.test.ts +241 -0
  64. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  66. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  67. package/src/__tests__/host-file-proxy.test.ts +8 -1
  68. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  69. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  70. package/src/__tests__/identity-routes.test.ts +57 -0
  71. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  72. package/src/__tests__/injector-background-turn.test.ts +153 -0
  73. package/src/__tests__/injector-chain.test.ts +7 -0
  74. package/src/__tests__/injector-document-comments.test.ts +378 -0
  75. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  76. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
  77. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  78. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  79. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  80. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  81. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  82. package/src/__tests__/llm-catalog-parity.test.ts +3 -0
  83. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  84. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  85. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  86. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
  87. package/src/__tests__/llm-resolver.test.ts +340 -3
  88. package/src/__tests__/log-export-routes.test.ts +99 -2
  89. package/src/__tests__/managed-profile-guard.test.ts +10 -0
  90. package/src/__tests__/message-queue-steer.test.ts +114 -0
  91. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  92. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  93. package/src/__tests__/notification-deep-link.test.ts +15 -0
  94. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  95. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  96. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  97. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  98. package/src/__tests__/openai-provider.test.ts +323 -3
  99. package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
  100. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  101. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  102. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  103. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  104. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  105. package/src/__tests__/platform-proxy-context.test.ts +6 -1
  106. package/src/__tests__/platform.test.ts +0 -3
  107. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  108. package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
  109. package/src/__tests__/plugin-types.test.ts +2 -2
  110. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  111. package/src/__tests__/process-message-display-content.test.ts +21 -16
  112. package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
  113. package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  115. package/src/__tests__/server-history-render.test.ts +83 -4
  116. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  117. package/src/__tests__/system-prompt.test.ts +57 -101
  118. package/src/__tests__/terminal-tools.test.ts +11 -1
  119. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  120. package/src/__tests__/thread-backfill.test.ts +370 -22
  121. package/src/__tests__/tool-executor.test.ts +90 -1
  122. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  123. package/src/__tests__/twilio-routes.test.ts +1 -1
  124. package/src/__tests__/web-fetch.test.ts +2 -2
  125. package/src/__tests__/workspace-git-service.test.ts +88 -5
  126. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  127. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  128. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  129. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  130. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  131. package/src/a2a/__tests__/task-store.test.ts +246 -0
  132. package/src/a2a/agent-card.ts +58 -0
  133. package/src/a2a/feature-gate.ts +8 -0
  134. package/src/a2a/protocol-constants.ts +21 -0
  135. package/src/a2a/protocol-errors.ts +50 -0
  136. package/src/a2a/protocol-types.ts +162 -0
  137. package/src/a2a/task-store.ts +168 -0
  138. package/src/agent/attachments.ts +1 -0
  139. package/src/agent/loop.ts +208 -22
  140. package/src/background-wake/next-wake.test.ts +289 -0
  141. package/src/background-wake/next-wake.ts +172 -0
  142. package/src/browser/operations.ts +15 -0
  143. package/src/channels/config.ts +9 -0
  144. package/src/channels/types.ts +14 -0
  145. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  146. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  147. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  148. package/src/cli/commands/__tests__/schedules.test.ts +469 -0
  149. package/src/cli/commands/conversations.ts +128 -1
  150. package/src/cli/commands/inference-providers.ts +147 -1
  151. package/src/cli/commands/memory-v2.ts +308 -0
  152. package/src/cli/commands/notifications.ts +89 -37
  153. package/src/cli/commands/plugins.ts +67 -0
  154. package/src/cli/commands/schedules.ts +297 -5
  155. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  156. package/src/cli/lib/install-from-github.ts +8 -9
  157. package/src/cli/lib/search-plugins.ts +163 -0
  158. package/src/cli/program.ts +14 -0
  159. package/src/cli/utils/conversation-id.ts +17 -5
  160. package/src/config/assistant-feature-flags.ts +24 -54
  161. package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
  162. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  163. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  164. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  165. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  166. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  167. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  168. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  169. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  170. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  171. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  172. package/src/config/bundled-tool-registry.ts +22 -12
  173. package/src/config/call-site-defaults.ts +124 -0
  174. package/src/config/feature-flag-registry.json +111 -23
  175. package/src/config/llm-resolver.ts +66 -1
  176. package/src/config/schema.ts +2 -0
  177. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
  178. package/src/config/schemas/call-site-catalog.ts +21 -0
  179. package/src/config/schemas/channels.ts +9 -0
  180. package/src/config/schemas/conversations.ts +10 -0
  181. package/src/config/schemas/heartbeat.ts +14 -0
  182. package/src/config/schemas/llm.ts +4 -3
  183. package/src/config/schemas/memory-retrospective.ts +1 -1
  184. package/src/config/schemas/memory-v2.ts +51 -4
  185. package/src/config/schemas/memory.ts +3 -1
  186. package/src/config/seed-inference-profiles.ts +99 -29
  187. package/src/context/compactor.ts +80 -13
  188. package/src/context/token-estimator.ts +72 -31
  189. package/src/context/window-manager.ts +25 -0
  190. package/src/credential-health/credential-health-service.ts +34 -19
  191. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
  192. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  193. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  194. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  195. package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
  196. package/src/daemon/conversation-agent-loop.ts +252 -56
  197. package/src/daemon/conversation-lifecycle.ts +142 -116
  198. package/src/daemon/conversation-messaging.ts +3 -0
  199. package/src/daemon/conversation-process.ts +273 -0
  200. package/src/daemon/conversation-queue-manager.ts +14 -0
  201. package/src/daemon/conversation-runtime-assembly.ts +144 -75
  202. package/src/daemon/conversation-slash.ts +37 -5
  203. package/src/daemon/conversation-surfaces.ts +45 -2
  204. package/src/daemon/conversation-tool-setup.ts +7 -0
  205. package/src/daemon/conversation.ts +42 -12
  206. package/src/daemon/date-context.ts +40 -0
  207. package/src/daemon/first-greeting.ts +10 -0
  208. package/src/daemon/guardian-action-generators.ts +1 -125
  209. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  210. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  211. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  212. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  213. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  214. package/src/daemon/handlers/config-a2a.ts +449 -0
  215. package/src/daemon/handlers/config-model.test.ts +1 -0
  216. package/src/daemon/handlers/conversations.ts +80 -0
  217. package/src/daemon/handlers/shared.ts +92 -29
  218. package/src/daemon/host-app-control-proxy.ts +69 -18
  219. package/src/daemon/host-bash-proxy.ts +1 -1
  220. package/src/daemon/host-cu-proxy.ts +1 -1
  221. package/src/daemon/host-file-proxy.ts +1 -1
  222. package/src/daemon/host-proxy-preactivation.ts +85 -18
  223. package/src/daemon/host-transfer-proxy.ts +1 -1
  224. package/src/daemon/lifecycle.ts +67 -65
  225. package/src/daemon/memory-v2-startup.ts +49 -13
  226. package/src/daemon/message-protocol.ts +4 -0
  227. package/src/daemon/message-types/conversations.ts +8 -0
  228. package/src/daemon/message-types/document-comments.ts +50 -0
  229. package/src/daemon/message-types/messages.ts +68 -1
  230. package/src/daemon/message-types/notifications.ts +21 -0
  231. package/src/daemon/message-types/surfaces.ts +3 -1
  232. package/src/daemon/message-types/web-activity.ts +57 -0
  233. package/src/daemon/pkb-reminder-builder.test.ts +10 -53
  234. package/src/daemon/pkb-reminder-builder.ts +4 -19
  235. package/src/daemon/plugin-source-watcher.ts +135 -3
  236. package/src/daemon/process-message.ts +72 -12
  237. package/src/daemon/query-complexity-router.ts +75 -0
  238. package/src/daemon/skill-memory-refresh.ts +5 -1
  239. package/src/daemon/trust-context.ts +6 -0
  240. package/src/daemon/wake-target-adapter.ts +2 -0
  241. package/src/documents/document-comments-store.test.ts +338 -0
  242. package/src/documents/document-comments-store.ts +237 -0
  243. package/src/documents/document-store.ts +202 -0
  244. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  245. package/src/export/transcript-formatter.ts +54 -20
  246. package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
  247. package/src/heartbeat/heartbeat-service.ts +35 -191
  248. package/src/home/__tests__/feed-types.test.ts +40 -0
  249. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  250. package/src/home/feed-types.ts +20 -3
  251. package/src/home/home-content-refresh.ts +52 -0
  252. package/src/home/home-greeting-cache.ts +69 -0
  253. package/src/home/home-greeting.ts +94 -0
  254. package/src/home/suggested-prompts.ts +177 -9
  255. package/src/ipc/cli-client.ts +147 -45
  256. package/src/memory/__tests__/conversation-queries.test.ts +220 -0
  257. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  258. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  259. package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
  260. package/src/memory/conversation-crud.ts +133 -43
  261. package/src/memory/conversation-queries.ts +87 -1
  262. package/src/memory/conversation-title-service.ts +26 -4
  263. package/src/memory/db-init.ts +22 -0
  264. package/src/memory/delivery-crud.ts +41 -0
  265. package/src/memory/delivery-status.ts +141 -15
  266. package/src/memory/external-conversation-store.ts +32 -1
  267. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
  268. package/src/memory/graph/conversation-graph-memory.ts +18 -6
  269. package/src/memory/graph/tools.ts +6 -37
  270. package/src/memory/invite-store.ts +53 -0
  271. package/src/memory/jobs-worker.ts +21 -1
  272. package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
  273. package/src/memory/llm-request-log-store.ts +92 -1
  274. package/src/memory/memory-retrospective-constants.ts +28 -0
  275. package/src/memory/memory-retrospective-enqueue.ts +4 -22
  276. package/src/memory/memory-retrospective-job.ts +438 -21
  277. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  278. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  279. package/src/memory/migrations/100-core-tables.ts +1 -0
  280. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  281. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  282. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  283. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  284. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  285. package/src/memory/migrations/253-document-comments.ts +47 -0
  286. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  287. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  288. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  289. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  290. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  291. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  292. package/src/memory/migrations/index.ts +20 -0
  293. package/src/memory/migrations/registry.ts +33 -0
  294. package/src/memory/onboarding-events-store.ts +7 -0
  295. package/src/memory/schema/a2a.ts +15 -0
  296. package/src/memory/schema/calls.ts +1 -0
  297. package/src/memory/schema/conversations.ts +3 -0
  298. package/src/memory/schema/index.ts +1 -0
  299. package/src/memory/schema/inference.ts +2 -0
  300. package/src/memory/schema/infrastructure.ts +2 -0
  301. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  302. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  303. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  304. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  305. package/src/memory/v2/__tests__/injection.test.ts +221 -17
  306. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  307. package/src/memory/v2/__tests__/router.test.ts +489 -1
  308. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  309. package/src/memory/v2/activation-store.ts +14 -16
  310. package/src/memory/v2/cli-command-content.ts +19 -0
  311. package/src/memory/v2/cli-command-store.ts +304 -0
  312. package/src/memory/v2/consolidation-job.ts +14 -0
  313. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  314. package/src/memory/v2/injection-events.ts +101 -0
  315. package/src/memory/v2/injection.ts +69 -29
  316. package/src/memory/v2/page-index.ts +246 -19
  317. package/src/memory/v2/page-store.ts +18 -0
  318. package/src/memory/v2/router.ts +209 -55
  319. package/src/memory/v2/static-context.ts +4 -4
  320. package/src/memory/v2/types.ts +23 -0
  321. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  322. package/src/messaging/providers/a2a/deliver.ts +156 -0
  323. package/src/messaging/providers/gmail/client.ts +9 -2
  324. package/src/messaging/providers/index.ts +18 -3
  325. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  327. package/src/messaging/providers/slack/adapter.ts +178 -25
  328. package/src/messaging/providers/slack/api.test.ts +54 -0
  329. package/src/messaging/providers/slack/api.ts +119 -3
  330. package/src/messaging/providers/slack/client.ts +12 -0
  331. package/src/messaging/providers/slack/deep-link.ts +20 -1
  332. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  333. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  334. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  335. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  336. package/src/messaging/providers/slack/send.test.ts +77 -0
  337. package/src/messaging/providers/slack/send.ts +8 -2
  338. package/src/messaging/providers/slack/types.ts +14 -0
  339. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  340. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  341. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  342. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
  343. package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
  344. package/src/notifications/adapters/macos.ts +12 -2
  345. package/src/notifications/broadcaster.ts +29 -4
  346. package/src/notifications/conversation-seed-composer.ts +14 -2
  347. package/src/notifications/copy-composer.ts +17 -64
  348. package/src/notifications/decision-engine.ts +111 -44
  349. package/src/notifications/deferred-emit.ts +135 -0
  350. package/src/notifications/deterministic-checks.ts +96 -0
  351. package/src/notifications/emit-signal.ts +10 -1
  352. package/src/notifications/home-feed-side-effect.ts +136 -27
  353. package/src/notifications/signal.ts +0 -4
  354. package/src/notifications/types.ts +8 -0
  355. package/src/oauth/connect-orchestrator.ts +3 -0
  356. package/src/oauth/credential-token-resolver.ts +2 -0
  357. package/src/oauth/manual-token-connection.ts +19 -0
  358. package/src/oauth/oauth-store.ts +12 -0
  359. package/src/oauth/platform-connection.test.ts +43 -3
  360. package/src/oauth/platform-connection.ts +13 -4
  361. package/src/oauth/seed-providers.ts +22 -0
  362. package/src/permissions/prompter.ts +5 -2
  363. package/src/permissions/secret-prompter.ts +4 -1
  364. package/src/plugins/defaults/injectors.ts +118 -26
  365. package/src/plugins/external-plugin-loader.ts +82 -10
  366. package/src/plugins/types.ts +16 -7
  367. package/src/prompts/__tests__/system-prompt.test.ts +44 -45
  368. package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
  369. package/src/prompts/normalize-onboarding.ts +40 -0
  370. package/src/prompts/sections.ts +32 -14
  371. package/src/prompts/system-prompt.ts +105 -76
  372. package/src/prompts/template-detection.ts +37 -0
  373. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  374. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  375. package/src/prompts/templates/VOICE.md +3 -0
  376. package/src/prompts/templates/system-sections.ts +51 -10
  377. package/src/providers/__tests__/inference.test.ts +2 -0
  378. package/src/providers/anthropic/client.ts +132 -5
  379. package/src/providers/call-site-routing.ts +24 -6
  380. package/src/providers/connection-resolution.ts +63 -13
  381. package/src/providers/fireworks/client.ts +20 -2
  382. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  383. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  384. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  385. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  386. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  387. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  388. package/src/providers/inference/adapter-factory.ts +24 -21
  389. package/src/providers/inference/auth.ts +15 -3
  390. package/src/providers/inference/backfill.ts +14 -1
  391. package/src/providers/inference/codex-token-refresh.ts +128 -0
  392. package/src/providers/inference/connections.ts +85 -5
  393. package/src/providers/inference/resolve-auth.ts +50 -5
  394. package/src/providers/model-catalog.ts +244 -242
  395. package/src/providers/model-intents.ts +3 -3
  396. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  397. package/src/providers/openai/chat-completions-provider.ts +215 -25
  398. package/src/providers/openai/responses-provider.ts +9 -3
  399. package/src/providers/openrouter/client.ts +46 -4
  400. package/src/providers/platform-proxy/constants.ts +3 -4
  401. package/src/providers/provider-catalog-visibility.ts +3 -1
  402. package/src/providers/provider-send-message.ts +27 -12
  403. package/src/providers/registry.ts +30 -1
  404. package/src/providers/types.ts +25 -0
  405. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  406. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  407. package/src/runtime/agent-wake.ts +212 -57
  408. package/src/runtime/auth/route-policy.ts +20 -3
  409. package/src/runtime/background-job-runner.ts +26 -0
  410. package/src/runtime/channel-reply-delivery.ts +182 -47
  411. package/src/runtime/channel-retry-sweep.ts +141 -16
  412. package/src/runtime/http-server.ts +7 -16
  413. package/src/runtime/http-types.ts +7 -51
  414. package/src/runtime/pending-interactions.ts +51 -8
  415. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  416. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  417. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
  418. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  419. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  420. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  421. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  422. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  423. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  424. package/src/runtime/routes/approval-routes.ts +4 -1
  425. package/src/runtime/routes/channel-availability-routes.ts +5 -0
  426. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  427. package/src/runtime/routes/consolidation-routes.ts +100 -0
  428. package/src/runtime/routes/content-source-routes.ts +78 -0
  429. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  430. package/src/runtime/routes/conversation-query-routes.ts +130 -12
  431. package/src/runtime/routes/conversation-routes.ts +288 -76
  432. package/src/runtime/routes/document-comments-routes.ts +287 -0
  433. package/src/runtime/routes/documents-routes.ts +33 -0
  434. package/src/runtime/routes/home-feed-routes.ts +6 -3
  435. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  436. package/src/runtime/routes/host-browser-routes.ts +8 -1
  437. package/src/runtime/routes/identity-routes.ts +21 -0
  438. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  439. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  440. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  441. package/src/runtime/routes/index.ts +14 -4
  442. package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
  443. package/src/runtime/routes/integrations/a2a.ts +294 -0
  444. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  445. package/src/runtime/routes/log-export-routes.ts +39 -0
  446. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  447. package/src/runtime/routes/notification-routes.ts +19 -2
  448. package/src/runtime/routes/question-routes.ts +4 -1
  449. package/src/runtime/routes/sanity-routes.ts +159 -0
  450. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  451. package/src/runtime/routes/subagents-routes.ts +41 -0
  452. package/src/runtime/services/conversation-serializer.ts +30 -4
  453. package/src/schedule/integration-status.ts +3 -1
  454. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  455. package/src/security/oauth2-device-code.ts +307 -0
  456. package/src/security/oauth2.ts +26 -9
  457. package/src/security/secure-keys.ts +5 -0
  458. package/src/skills/catalog-install.ts +6 -2
  459. package/src/subagent/manager.ts +2 -0
  460. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  461. package/src/tools/browser/browser-execution.ts +93 -0
  462. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  463. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  464. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  465. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  466. package/src/tools/browser/cdp-client/factory.ts +87 -3
  467. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  468. package/src/tools/browser/cdp-client/types.ts +36 -0
  469. package/src/tools/browser/pinned-tabs.ts +90 -0
  470. package/src/tools/document/document-comment-tool.test.ts +379 -0
  471. package/src/tools/document/document-comment-tool.ts +156 -0
  472. package/src/tools/document/document-tool.ts +128 -2
  473. package/src/tools/memory/register.ts +1 -9
  474. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  475. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  476. package/src/tools/network/domain-normalize.ts +17 -0
  477. package/src/tools/network/web-fetch.ts +213 -64
  478. package/src/tools/network/web-search.ts +191 -66
  479. package/src/tools/registry.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +3 -2
  481. package/src/tools/tool-approval-handler.ts +19 -12
  482. package/src/tools/types.ts +41 -2
  483. package/src/tools/ui-surface/definitions.ts +3 -1
  484. package/src/types/onboarding-context.ts +4 -0
  485. package/src/util/__tests__/favicon.test.ts +84 -0
  486. package/src/util/favicon.ts +40 -0
  487. package/src/util/platform.ts +0 -5
  488. package/src/workspace/git-service.ts +75 -4
  489. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  490. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  491. package/src/workspace/migrations/registry.ts +4 -0
  492. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  493. package/src/config/bundled-skills/document/SKILL.md +0 -54
  494. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  495. package/src/daemon/seed-files.ts +0 -18
  496. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  497. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
  498. package/src/runtime/routes/interface-routes.ts +0 -43
  499. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  500. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  501. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  502. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  503. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -51,6 +51,9 @@ let lastCheckArgs:
51
51
  /** Optional override for getTool — lets tests supply skill-origin tools. */
52
52
  let getToolOverride: ((name: string) => Tool | undefined) | undefined;
53
53
 
54
+ /** Optional override for getAllTools — lets tests supply a registry snapshot. */
55
+ let getAllToolsOverride: (() => Tool[]) | undefined;
56
+
54
57
  /** Override the check() result for tests that need to trigger prompting. */
55
58
  let checkResultOverride: { decision: string; reason: string } | undefined;
56
59
 
@@ -144,7 +147,7 @@ mock.module("../tools/registry.js", () => ({
144
147
  execute: async () => fakeToolResult,
145
148
  };
146
149
  },
147
- getAllTools: () => [],
150
+ getAllTools: () => (getAllToolsOverride ? getAllToolsOverride() : []),
148
151
  }));
149
152
 
150
153
  mock.module("../tools/shared/filesystem/path-policy.js", () => ({
@@ -179,6 +182,7 @@ describe("ToolExecutor allowedToolNames gating", () => {
179
182
  fakeToolResult = { content: "ok", isError: false };
180
183
  lastCheckArgs = undefined;
181
184
  getToolOverride = undefined;
185
+ getAllToolsOverride = undefined;
182
186
  checkResultOverride = undefined;
183
187
  checkFnOverride = undefined;
184
188
  cachedAssessmentOverride = undefined;
@@ -271,6 +275,89 @@ describe("ToolExecutor allowedToolNames gating", () => {
271
275
  expect(result.content).toContain("file_read");
272
276
  expect(result.content).toContain("not currently active");
273
277
  });
278
+
279
+ test("unknown tool suggestion list is scoped to allowedToolNames", async () => {
280
+ // Surfacing every globally registered tool would leak tools active in
281
+ // other sessions and misdirect the model to tools it cannot invoke.
282
+ const makeTool = (name: string): Tool =>
283
+ ({
284
+ name,
285
+ description: "test tool",
286
+ category: "test",
287
+ defaultRiskLevel: RiskLevel.Low,
288
+ getDefinition: () => ({
289
+ name,
290
+ description: "test tool",
291
+ input_schema: { type: "object" as const, properties: {} },
292
+ }),
293
+ execute: async () => fakeToolResult,
294
+ }) as unknown as Tool;
295
+ getAllToolsOverride = () => [
296
+ makeTool("file_read"),
297
+ makeTool("file_write"),
298
+ makeTool("secret_skill_tool"),
299
+ ];
300
+ const executor = new ToolExecutor(makePrompter());
301
+ const allowed = new Set(["file_read", "file_write"]);
302
+ const result = await executor.execute(
303
+ "unknown_tool",
304
+ { foo: "bar" },
305
+ makeContext({ allowedToolNames: allowed }),
306
+ );
307
+ expect(result.isError).toBe(true);
308
+ expect(result.content).toBe(
309
+ "Unknown tool: unknown_tool. Available tools: file_read, file_write",
310
+ );
311
+ expect(result.content).not.toContain("secret_skill_tool");
312
+ });
313
+
314
+ test("unknown tool name reports 'Unknown tool' even when allowedToolNames is set", async () => {
315
+ // Regression: a hallucinated tool name with allowedToolNames set used to
316
+ // hit the "not currently active. Load the skill that provides this tool
317
+ // first." gate, which sent the model chasing a nonexistent skill. The
318
+ // registry-lookup gate runs first now so the model sees the real list.
319
+ const executor = new ToolExecutor(makePrompter());
320
+ const allowed = new Set(["file_read"]);
321
+ const result = await executor.execute(
322
+ "unknown_tool",
323
+ { foo: "bar" },
324
+ makeContext({ allowedToolNames: allowed }),
325
+ );
326
+ expect(result.isError).toBe(true);
327
+ expect(result.content).toContain("Unknown tool: unknown_tool");
328
+ expect(result.content).not.toContain("not currently active");
329
+ });
330
+
331
+ test("inactive skill tool names the owning skill in the load hint", async () => {
332
+ const executor = new ToolExecutor(makePrompter());
333
+ getToolOverride = (name: string) => {
334
+ if (name !== "skill_tool_x") return undefined;
335
+ return {
336
+ name,
337
+ description: "tool from a skill",
338
+ category: "skill",
339
+ defaultRiskLevel: RiskLevel.Low,
340
+ origin: "skill" as const,
341
+ ownerSkillId: "my-skill",
342
+ getDefinition: () => ({
343
+ name,
344
+ description: "tool from a skill",
345
+ input_schema: { type: "object" as const, properties: {} },
346
+ }),
347
+ execute: async () => fakeToolResult,
348
+ };
349
+ };
350
+ const allowed = new Set(["file_read"]);
351
+ const result = await executor.execute(
352
+ "skill_tool_x",
353
+ {},
354
+ makeContext({ allowedToolNames: allowed }),
355
+ );
356
+ expect(result.isError).toBe(true);
357
+ expect(result.content).toBe(
358
+ 'Tool "skill_tool_x" is not currently active. Load the "my-skill" skill that provides this tool first.',
359
+ );
360
+ });
274
361
  });
275
362
 
276
363
  describe("ToolExecutor policy context plumbing", () => {
@@ -846,6 +933,7 @@ describe("ToolExecutor forcePromptSideEffects enforcement", () => {
846
933
  // Import the real buildSanitizedEnv (not mocked) for baseline credential tests
847
934
  const {
848
935
  buildSanitizedEnv,
936
+ KATA_INJECTED_ENV_VARS,
849
937
  KATA_SAFE_ENV_VARS,
850
938
  SAFE_ENV_VARS,
851
939
  ALWAYS_INJECTED_ENV_VARS,
@@ -910,6 +998,7 @@ describe("buildSanitizedEnv — baseline: credential exclusion", () => {
910
998
  const allowed: string[] = [
911
999
  ...SAFE_ENV_VARS,
912
1000
  ...KATA_SAFE_ENV_VARS,
1001
+ ...KATA_INJECTED_ENV_VARS,
913
1002
  ...ALWAYS_INJECTED_ENV_VARS,
914
1003
  ];
915
1004
  const env = buildSanitizedEnv();
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Tests that `ToolExecutionResult.activityMetadata` plumbs through to the
3
+ * emitted `tool_result` server event via `handleToolResult`. The forward path
4
+ * is: ToolExecutionResult (set by the tool executor) → AgentEvent tool_result
5
+ * (emitted by the agent loop) → tool_result server message (emitted by
6
+ * handleToolResult to the SSE sink).
7
+ *
8
+ * Mirrors the mocked-dependency pattern used in tool-preview-lifecycle.test.ts
9
+ * and annotate-risk-options.test.ts.
10
+ */
11
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
12
+
13
+ // ── Mock platform (must precede imports that read it) ─────────────────────────
14
+ mock.module("../util/logger.js", () => ({
15
+ getLogger: () =>
16
+ new Proxy({} as Record<string, unknown>, {
17
+ get: () => () => {},
18
+ }),
19
+ }));
20
+
21
+ mock.module("../config/loader.js", () => ({
22
+ getConfig: () => ({
23
+ skills: {
24
+ entries: {},
25
+ load: { extraDirs: [], watch: false, watchDebounceMs: 0 },
26
+ install: { nodeManager: "npm" },
27
+ allowBundled: null,
28
+ remoteProviders: {
29
+ skillssh: { enabled: true },
30
+ clawhub: { enabled: true },
31
+ },
32
+ remotePolicy: {
33
+ blockSuspicious: true,
34
+ blockMalware: true,
35
+ maxSkillsShRisk: "medium",
36
+ },
37
+ },
38
+ }),
39
+ loadConfig: () => ({}),
40
+ }));
41
+
42
+ mock.module("../memory/conversation-crud.js", () => ({
43
+ addMessage: () => ({ id: "mock-msg-id" }),
44
+ getMessageById: () => null,
45
+ updateMessageContent: () => {},
46
+ provenanceFromTrustContext: () => ({}),
47
+ }));
48
+
49
+ mock.module("../memory/llm-request-log-store.js", () => ({
50
+ recordRequestLog: () => {},
51
+ backfillMessageIdOnLogs: () => {},
52
+ }));
53
+
54
+ // ── Imports (after mocks) ─────────────────────────────────────────────────────
55
+ import type {
56
+ EventHandlerDeps,
57
+ EventHandlerState,
58
+ } from "../daemon/conversation-agent-loop-handlers.js";
59
+ import {
60
+ createEventHandlerState,
61
+ handleToolResult,
62
+ } from "../daemon/conversation-agent-loop-handlers.js";
63
+ import type { ServerMessage } from "../daemon/message-protocol.js";
64
+ import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
65
+
66
+ type ToolResultEvent = Extract<ServerMessage, { type: "tool_result" }>;
67
+
68
+ // ── Helpers ───────────────────────────────────────────────────────────────────
69
+
70
+ function createCollectorDeps(): {
71
+ deps: EventHandlerDeps;
72
+ events: ServerMessage[];
73
+ } {
74
+ const events: ServerMessage[] = [];
75
+ const deps = {
76
+ ctx: {
77
+ conversationId: "conv-meta",
78
+ provider: { name: "anthropic" },
79
+ traceEmitter: { emit: () => {} },
80
+ streamThinking: false,
81
+ emitActivityState: () => {},
82
+ markWorkspaceTopLevelDirty: () => {},
83
+ currentTurnSurfaces: [],
84
+ } as unknown as EventHandlerDeps["ctx"],
85
+ onEvent: (msg: ServerMessage) => events.push(msg),
86
+ reqId: "req-meta",
87
+ isFirstMessage: false,
88
+ shouldGenerateTitle: false,
89
+ rlog: new Proxy({} as Record<string, unknown>, {
90
+ get: () => () => {},
91
+ }) as unknown as EventHandlerDeps["rlog"],
92
+ turnChannelContext: {
93
+ userMessageChannel: "vellum",
94
+ assistantMessageChannel: "vellum",
95
+ } as EventHandlerDeps["turnChannelContext"],
96
+ turnInterfaceContext: {
97
+ userMessageInterface: "macos",
98
+ assistantMessageInterface: "macos",
99
+ } as EventHandlerDeps["turnInterfaceContext"],
100
+ } as EventHandlerDeps;
101
+ return { deps, events };
102
+ }
103
+
104
+ function primeState(state: EventHandlerState, toolUseId: string): void {
105
+ state.toolUseIdToName.set(toolUseId, "web_search");
106
+ state.toolCallTimestamps.set(toolUseId, { startedAt: Date.now() });
107
+ state.currentTurnToolUseIds.push(toolUseId);
108
+ }
109
+
110
+ // ── Tests ─────────────────────────────────────────────────────────────────────
111
+
112
+ describe("tool_result activityMetadata plumbing", () => {
113
+ let state: EventHandlerState;
114
+
115
+ beforeEach(() => {
116
+ state = createEventHandlerState();
117
+ });
118
+
119
+ test("forwards activityMetadata to the emitted tool_result event", () => {
120
+ const { deps, events } = createCollectorDeps();
121
+ const toolUseId = "toolu_meta_present";
122
+ primeState(state, toolUseId);
123
+
124
+ const activityMetadata: ToolActivityMetadata = {
125
+ webSearch: {
126
+ query: "x",
127
+ provider: "tavily",
128
+ resultCount: 0,
129
+ durationMs: 1,
130
+ results: [],
131
+ },
132
+ };
133
+
134
+ handleToolResult(state, deps, {
135
+ type: "tool_result",
136
+ toolUseId,
137
+ content: "",
138
+ isError: false,
139
+ activityMetadata,
140
+ });
141
+
142
+ const toolResultEvent = events.find(
143
+ (e): e is ToolResultEvent => e.type === "tool_result",
144
+ );
145
+ expect(toolResultEvent).toBeDefined();
146
+ expect(toolResultEvent?.activityMetadata).toEqual(activityMetadata);
147
+ });
148
+
149
+ test("omits activityMetadata when the executor did not populate it", () => {
150
+ const { deps, events } = createCollectorDeps();
151
+ const toolUseId = "toolu_meta_absent";
152
+ primeState(state, toolUseId);
153
+
154
+ handleToolResult(state, deps, {
155
+ type: "tool_result",
156
+ toolUseId,
157
+ content: "ok",
158
+ isError: false,
159
+ });
160
+
161
+ const toolResultEvent = events.find(
162
+ (e): e is ToolResultEvent => e.type === "tool_result",
163
+ );
164
+ expect(toolResultEvent).toBeDefined();
165
+ expect(toolResultEvent?.activityMetadata).toBeUndefined();
166
+ });
167
+ });
@@ -311,7 +311,7 @@ mock.module("../tools/credentials/metadata-store.js", () => ({
311
311
  }));
312
312
 
313
313
  mock.module("../providers/registry.js", () => ({
314
- initializeProviders: () => {},
314
+ initializeProviders: async () => {},
315
315
  }));
316
316
 
317
317
  import {
@@ -1622,8 +1622,8 @@ describe("web_fetch tool", () => {
1622
1622
  );
1623
1623
 
1624
1624
  expect(result.isError).toBe(false);
1625
- expect(result.content).toContain("Extracted text content is very short");
1626
- expect(result.content).toContain("JavaScript rendering");
1625
+ expect(result.content).toContain("Extracted only");
1626
+ expect(result.content).toContain("JavaScript-rendered");
1627
1627
  });
1628
1628
 
1629
1629
  test("does not suggest browser skill when HTML page has substantial content", async () => {
@@ -5,6 +5,7 @@ import {
5
5
  mkdirSync,
6
6
  readFileSync,
7
7
  rmSync,
8
+ statSync,
8
9
  writeFileSync,
9
10
  } from "node:fs";
10
11
  import { tmpdir } from "node:os";
@@ -87,6 +88,58 @@ describe("WorkspaceGitService", () => {
87
88
  expect(userEmail).toBe("assistant@vellum.ai");
88
89
  });
89
90
 
91
+ test("installs branch guard hook that blocks non-main branches", async () => {
92
+ const service = new WorkspaceGitService(testDir);
93
+ await service.ensureInitialized();
94
+
95
+ const hooksPath = execFileSync("git", ["config", "core.hooksPath"], {
96
+ cwd: testDir,
97
+ encoding: "utf-8",
98
+ }).trim();
99
+ expect(hooksPath).toBe(".githooks");
100
+
101
+ const hookPath = join(testDir, ".githooks", "reference-transaction");
102
+ expect(existsSync(hookPath)).toBe(true);
103
+ expect(statSync(hookPath).mode & 0o111).not.toBe(0);
104
+
105
+ const hookContent = readFileSync(hookPath, "utf-8");
106
+ expect(hookContent).toContain(
107
+ "assistant workspace git branches are disabled",
108
+ );
109
+
110
+ expect(() =>
111
+ execFileSync("git", ["branch", "apollo/test-branch"], {
112
+ cwd: testDir,
113
+ encoding: "utf-8",
114
+ }),
115
+ ).toThrow(/assistant workspace git branches are disabled/);
116
+ });
117
+
118
+ test("branch guard allows deleting old non-main branches", async () => {
119
+ const service = new WorkspaceGitService(testDir);
120
+ await service.ensureInitialized();
121
+
122
+ execFileSync(
123
+ "git",
124
+ ["-c", "core.hooksPath=/dev/null", "branch", "old-branch"],
125
+ {
126
+ cwd: testDir,
127
+ },
128
+ );
129
+
130
+ execFileSync("git", ["branch", "-D", "old-branch"], { cwd: testDir });
131
+
132
+ const branches = execFileSync(
133
+ "git",
134
+ ["branch", "--format=%(refname:short)"],
135
+ {
136
+ cwd: testDir,
137
+ encoding: "utf-8",
138
+ },
139
+ );
140
+ expect(branches).not.toContain("old-branch");
141
+ });
142
+
90
143
  test("multiple ensureInitialized calls are idempotent", async () => {
91
144
  const service = new WorkspaceGitService(testDir);
92
145
 
@@ -487,7 +540,7 @@ describe("WorkspaceGitService", () => {
487
540
  // Set up a pre-existing git repo on a feature branch
488
541
  execFileSync("git", ["init", "-b", "main"], { cwd: testDir });
489
542
  execFileSync("git", ["config", "user.name", "Test"], { cwd: testDir });
490
- execFileSync("git", ["config", "user.email", "test@test.com"], {
543
+ execFileSync("git", ["config", "user.email", "user@example.com"], {
491
544
  cwd: testDir,
492
545
  });
493
546
  writeFileSync(join(testDir, "file.txt"), "content");
@@ -527,7 +580,7 @@ describe("WorkspaceGitService", () => {
527
580
  // Set up a pre-existing git repo then detach HEAD
528
581
  execFileSync("git", ["init", "-b", "main"], { cwd: testDir });
529
582
  execFileSync("git", ["config", "user.name", "Test"], { cwd: testDir });
530
- execFileSync("git", ["config", "user.email", "test@test.com"], {
583
+ execFileSync("git", ["config", "user.email", "user@example.com"], {
531
584
  cwd: testDir,
532
585
  });
533
586
  writeFileSync(join(testDir, "file.txt"), "content");
@@ -571,7 +624,7 @@ describe("WorkspaceGitService", () => {
571
624
  // This exercises the --discard-changes fallback in ensureOnMainLocked().
572
625
  execFileSync("git", ["init", "-b", "main"], { cwd: testDir });
573
626
  execFileSync("git", ["config", "user.name", "Test"], { cwd: testDir });
574
- execFileSync("git", ["config", "user.email", "test@test.com"], {
627
+ execFileSync("git", ["config", "user.email", "user@example.com"], {
575
628
  cwd: testDir,
576
629
  });
577
630
  writeFileSync(join(testDir, "file.txt"), "original content");
@@ -619,7 +672,7 @@ describe("WorkspaceGitService", () => {
619
672
  // Set up a pre-existing git repo without our gitignore rules
620
673
  execFileSync("git", ["init", "-b", "main"], { cwd: testDir });
621
674
  execFileSync("git", ["config", "user.name", "Test"], { cwd: testDir });
622
- execFileSync("git", ["config", "user.email", "test@test.com"], {
675
+ execFileSync("git", ["config", "user.email", "user@example.com"], {
623
676
  cwd: testDir,
624
677
  });
625
678
  writeFileSync(join(testDir, ".gitignore"), "node_modules/\n");
@@ -648,7 +701,7 @@ describe("WorkspaceGitService", () => {
648
701
  // Set up a pre-existing git repo with the OLD broad data/ rule
649
702
  execFileSync("git", ["init", "-b", "main"], { cwd: testDir });
650
703
  execFileSync("git", ["config", "user.name", "Test"], { cwd: testDir });
651
- execFileSync("git", ["config", "user.email", "test@test.com"], {
704
+ execFileSync("git", ["config", "user.email", "user@example.com"], {
652
705
  cwd: testDir,
653
706
  });
654
707
  const oldGitignore =
@@ -711,6 +764,36 @@ describe("WorkspaceGitService", () => {
711
764
  expect(userEmail).toBe("assistant@vellum.ai");
712
765
  });
713
766
 
767
+ test("existing repo gets branch guard installed on init", async () => {
768
+ execFileSync("git", ["init", "-b", "main"], { cwd: testDir });
769
+ execFileSync("git", ["config", "user.name", "Test"], { cwd: testDir });
770
+ execFileSync("git", ["config", "user.email", "user@example.com"], {
771
+ cwd: testDir,
772
+ });
773
+ writeFileSync(join(testDir, "file.txt"), "content");
774
+ execFileSync("git", ["add", "-A"], { cwd: testDir });
775
+ execFileSync("git", ["commit", "-m", "init"], { cwd: testDir });
776
+
777
+ const service = new WorkspaceGitService(testDir);
778
+ await service.ensureInitialized();
779
+
780
+ const hooksPath = execFileSync("git", ["config", "core.hooksPath"], {
781
+ cwd: testDir,
782
+ encoding: "utf-8",
783
+ }).trim();
784
+ expect(hooksPath).toBe(".githooks");
785
+ expect(
786
+ existsSync(join(testDir, ".githooks", "reference-transaction")),
787
+ ).toBe(true);
788
+
789
+ expect(() =>
790
+ execFileSync("git", ["branch", "feature-after-init"], {
791
+ cwd: testDir,
792
+ encoding: "utf-8",
793
+ }),
794
+ ).toThrow(/assistant workspace git branches are disabled/);
795
+ });
796
+
714
797
  test("existing repo with correct config is idempotent", async () => {
715
798
  // Set up a repo that already has everything configured correctly
716
799
  execFileSync("git", ["init", "-b", "main"], { cwd: testDir });
@@ -0,0 +1,228 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
11
+
12
+ import { memoryRouterBalancedProfileMigration } from "../workspace/migrations/087-memory-router-balanced-profile.js";
13
+
14
+ let workspaceDir: string;
15
+
16
+ function freshWorkspace(): void {
17
+ workspaceDir = join(
18
+ tmpdir(),
19
+ `vellum-migration-087-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
20
+ );
21
+ mkdirSync(workspaceDir, { recursive: true });
22
+ }
23
+
24
+ function writeConfig(data: Record<string, unknown>): void {
25
+ writeFileSync(
26
+ join(workspaceDir, "config.json"),
27
+ JSON.stringify(data, null, 2) + "\n",
28
+ );
29
+ }
30
+
31
+ function readConfig(): Record<string, unknown> {
32
+ return JSON.parse(readFileSync(join(workspaceDir, "config.json"), "utf-8"));
33
+ }
34
+
35
+ function configPath(): string {
36
+ return join(workspaceDir, "config.json");
37
+ }
38
+
39
+ beforeEach(() => {
40
+ freshWorkspace();
41
+ delete process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH;
42
+ });
43
+
44
+ afterEach(() => {
45
+ delete process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH;
46
+ if (existsSync(workspaceDir)) {
47
+ rmSync(workspaceDir, { recursive: true, force: true });
48
+ }
49
+ });
50
+
51
+ describe("087-memory-router-balanced-profile migration", () => {
52
+ test("has correct migration id", () => {
53
+ expect(memoryRouterBalancedProfileMigration.id).toBe(
54
+ "087-memory-router-balanced-profile",
55
+ );
56
+ });
57
+
58
+ test("replaces seeded Sonnet 4.6 + 1M context with balanced profile", () => {
59
+ writeConfig({
60
+ llm: {
61
+ default: { provider: "anthropic" },
62
+ callSites: {
63
+ memoryRouter: {
64
+ model: "claude-sonnet-4-6",
65
+ contextWindow: { maxInputTokens: 1_000_000 },
66
+ },
67
+ },
68
+ },
69
+ });
70
+
71
+ memoryRouterBalancedProfileMigration.run(workspaceDir);
72
+
73
+ const config = readConfig() as {
74
+ llm: { callSites: Record<string, Record<string, unknown>> };
75
+ };
76
+ expect(config.llm.callSites.memoryRouter).toEqual({ profile: "balanced" });
77
+ });
78
+
79
+ test("creates the call site when missing", () => {
80
+ writeConfig({
81
+ llm: { default: { provider: "anthropic" } },
82
+ });
83
+
84
+ memoryRouterBalancedProfileMigration.run(workspaceDir);
85
+
86
+ const config = readConfig() as {
87
+ llm: { callSites: Record<string, Record<string, unknown>> };
88
+ };
89
+ expect(config.llm.callSites.memoryRouter).toEqual({ profile: "balanced" });
90
+ });
91
+
92
+ test("writes a fresh starter config when config.json is absent", () => {
93
+ memoryRouterBalancedProfileMigration.run(workspaceDir);
94
+
95
+ expect(existsSync(configPath())).toBe(true);
96
+ const config = readConfig() as {
97
+ llm: { callSites: Record<string, Record<string, unknown>> };
98
+ };
99
+ expect(config.llm.callSites.memoryRouter).toEqual({ profile: "balanced" });
100
+ });
101
+
102
+ test("skips BYOK / non-Anthropic workspaces to avoid breaking memoryRouter", () => {
103
+ // `balanced` resolves to the managed Anthropic connection, which BYOK
104
+ // installs disable. Rewriting to `balanced` there would silently disable
105
+ // memory injection (getConfiguredProvider returns null). Preserve whatever
106
+ // memoryRouter config the workspace already has — including absence.
107
+ writeConfig({
108
+ llm: {
109
+ default: { provider: "openai", model: "gpt-5.4" },
110
+ callSites: {
111
+ memoryRouter: { model: "gpt-5.4" },
112
+ },
113
+ },
114
+ });
115
+
116
+ memoryRouterBalancedProfileMigration.run(workspaceDir);
117
+
118
+ const config = readConfig() as {
119
+ llm: { callSites: Record<string, Record<string, unknown>> };
120
+ };
121
+ expect(config.llm.callSites.memoryRouter).toEqual({ model: "gpt-5.4" });
122
+ });
123
+
124
+ test("skips BYOK workspaces with no existing memoryRouter entry", () => {
125
+ writeConfig({
126
+ llm: { default: { provider: "gemini" } },
127
+ });
128
+
129
+ memoryRouterBalancedProfileMigration.run(workspaceDir);
130
+
131
+ const config = readConfig() as {
132
+ llm: { callSites?: Record<string, Record<string, unknown>> };
133
+ };
134
+ expect(config.llm.callSites?.memoryRouter).toBeUndefined();
135
+ });
136
+
137
+ test("preserves user customizations on memoryRouter (non-077-seeded shape)", () => {
138
+ writeConfig({
139
+ llm: {
140
+ default: { provider: "anthropic" },
141
+ callSites: {
142
+ memoryRouter: {
143
+ model: "claude-haiku-4-5-20251001",
144
+ effort: "low",
145
+ },
146
+ },
147
+ },
148
+ });
149
+
150
+ memoryRouterBalancedProfileMigration.run(workspaceDir);
151
+
152
+ const config = readConfig() as {
153
+ llm: { callSites: Record<string, Record<string, unknown>> };
154
+ };
155
+ expect(config.llm.callSites.memoryRouter).toEqual({
156
+ model: "claude-haiku-4-5-20251001",
157
+ effort: "low",
158
+ });
159
+ });
160
+
161
+ test("is idempotent — second run does not change the call site", () => {
162
+ writeConfig({
163
+ llm: {
164
+ default: { provider: "anthropic" },
165
+ callSites: {
166
+ memoryRouter: {
167
+ model: "claude-sonnet-4-6",
168
+ contextWindow: { maxInputTokens: 1_000_000 },
169
+ },
170
+ },
171
+ },
172
+ });
173
+
174
+ memoryRouterBalancedProfileMigration.run(workspaceDir);
175
+ const afterFirst = readFileSync(configPath(), "utf-8");
176
+ memoryRouterBalancedProfileMigration.run(workspaceDir);
177
+ const afterSecond = readFileSync(configPath(), "utf-8");
178
+
179
+ expect(afterSecond).toBe(afterFirst);
180
+ });
181
+
182
+ test("skips when VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH is set", () => {
183
+ process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH = "/tmp/overlay.json";
184
+ writeConfig({
185
+ llm: {
186
+ default: { provider: "anthropic" },
187
+ callSites: {
188
+ memoryRouter: { model: "claude-sonnet-4-6" },
189
+ },
190
+ },
191
+ });
192
+
193
+ memoryRouterBalancedProfileMigration.run(workspaceDir);
194
+
195
+ const config = readConfig() as {
196
+ llm: { callSites: Record<string, Record<string, unknown>> };
197
+ };
198
+ expect(config.llm.callSites.memoryRouter).toEqual({
199
+ model: "claude-sonnet-4-6",
200
+ });
201
+ });
202
+
203
+ test("preserves sibling call-site entries", () => {
204
+ writeConfig({
205
+ llm: {
206
+ default: { provider: "anthropic" },
207
+ callSites: {
208
+ mainAgent: { model: "claude-opus-4-7", maxTokens: 32000 },
209
+ memoryRouter: {
210
+ model: "claude-sonnet-4-6",
211
+ contextWindow: { maxInputTokens: 1_000_000 },
212
+ },
213
+ },
214
+ },
215
+ });
216
+
217
+ memoryRouterBalancedProfileMigration.run(workspaceDir);
218
+
219
+ const config = readConfig() as {
220
+ llm: { callSites: Record<string, Record<string, unknown>> };
221
+ };
222
+ expect(config.llm.callSites.mainAgent).toEqual({
223
+ model: "claude-opus-4-7",
224
+ maxTokens: 32000,
225
+ });
226
+ expect(config.llm.callSites.memoryRouter).toEqual({ profile: "balanced" });
227
+ });
228
+ });