@vellumai/assistant 0.8.3 → 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 (342) hide show
  1. package/docker-entrypoint.sh +0 -1
  2. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  3. package/openapi.yaml +610 -16
  4. package/package.json +1 -1
  5. package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
  6. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  7. package/src/__tests__/agent-loop.test.ts +88 -3
  8. package/src/__tests__/anthropic-provider.test.ts +272 -0
  9. package/src/__tests__/approval-cascade.test.ts +1 -1
  10. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  11. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  12. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  13. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  14. package/src/__tests__/compaction-events.test.ts +1 -1
  15. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  16. package/src/__tests__/config-watcher.test.ts +1 -1
  17. package/src/__tests__/context-token-estimator.test.ts +91 -1
  18. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  19. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  20. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  21. package/src/__tests__/conversation-agent-loop.test.ts +25 -7
  22. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  23. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  24. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  25. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  26. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  27. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  28. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  29. package/src/__tests__/conversation-pairing.test.ts +2 -2
  30. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  31. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  32. package/src/__tests__/conversation-queue.test.ts +1 -1
  33. package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
  34. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  35. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  36. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  37. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  38. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  39. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  40. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  41. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  42. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  43. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  44. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  45. package/src/__tests__/dm-backfill.test.ts +64 -0
  46. package/src/__tests__/dm-persistence.test.ts +33 -0
  47. package/src/__tests__/document-find-replace.test.ts +501 -0
  48. package/src/__tests__/first-greeting.test.ts +23 -2
  49. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  50. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  51. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  52. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  53. package/src/__tests__/host-file-proxy.test.ts +8 -1
  54. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  55. package/src/__tests__/identity-routes.test.ts +57 -0
  56. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  57. package/src/__tests__/injector-chain.test.ts +2 -0
  58. package/src/__tests__/injector-document-comments.test.ts +378 -0
  59. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  60. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  61. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  62. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  63. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  64. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  65. package/src/__tests__/llm-resolver.test.ts +85 -1
  66. package/src/__tests__/log-export-routes.test.ts +99 -2
  67. package/src/__tests__/message-queue-steer.test.ts +114 -0
  68. package/src/__tests__/openai-provider.test.ts +105 -0
  69. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  70. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  71. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  72. package/src/__tests__/platform.test.ts +0 -3
  73. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  74. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  75. package/src/__tests__/process-message-display-content.test.ts +21 -16
  76. package/src/__tests__/server-history-render.test.ts +83 -4
  77. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  78. package/src/__tests__/system-prompt.test.ts +51 -28
  79. package/src/__tests__/terminal-tools.test.ts +11 -1
  80. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  81. package/src/__tests__/thread-backfill.test.ts +370 -22
  82. package/src/__tests__/tool-executor.test.ts +90 -1
  83. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  84. package/src/__tests__/twilio-routes.test.ts +1 -1
  85. package/src/__tests__/web-fetch.test.ts +2 -2
  86. package/src/__tests__/workspace-git-service.test.ts +88 -5
  87. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  88. package/src/agent/attachments.ts +1 -0
  89. package/src/agent/loop.ts +57 -20
  90. package/src/background-wake/next-wake.test.ts +289 -0
  91. package/src/background-wake/next-wake.ts +172 -0
  92. package/src/browser/operations.ts +15 -0
  93. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  94. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  95. package/src/cli/commands/conversations.ts +128 -1
  96. package/src/cli/commands/inference-providers.ts +147 -1
  97. package/src/cli/commands/memory-v2.ts +308 -0
  98. package/src/cli/commands/notifications.ts +24 -2
  99. package/src/cli/utils/conversation-id.ts +17 -5
  100. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  101. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  102. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  103. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  104. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  105. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  106. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  107. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  108. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  109. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  110. package/src/config/bundled-tool-registry.ts +22 -12
  111. package/src/config/call-site-defaults.ts +19 -0
  112. package/src/config/feature-flag-registry.json +99 -3
  113. package/src/config/llm-resolver.ts +16 -2
  114. package/src/config/schemas/__tests__/memory-v2.test.ts +4 -0
  115. package/src/config/schemas/call-site-catalog.ts +21 -0
  116. package/src/config/schemas/llm.ts +3 -0
  117. package/src/config/schemas/memory-v2.ts +48 -1
  118. package/src/context/compactor.ts +8 -1
  119. package/src/context/token-estimator.ts +47 -4
  120. package/src/context/window-manager.ts +25 -0
  121. package/src/credential-health/credential-health-service.ts +34 -19
  122. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  123. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  124. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  125. package/src/daemon/conversation-agent-loop-handlers.ts +153 -23
  126. package/src/daemon/conversation-agent-loop.ts +223 -54
  127. package/src/daemon/conversation-lifecycle.ts +142 -116
  128. package/src/daemon/conversation-messaging.ts +3 -0
  129. package/src/daemon/conversation-process.ts +273 -0
  130. package/src/daemon/conversation-queue-manager.ts +14 -0
  131. package/src/daemon/conversation-runtime-assembly.ts +135 -75
  132. package/src/daemon/conversation-slash.ts +37 -5
  133. package/src/daemon/conversation-surfaces.ts +45 -2
  134. package/src/daemon/conversation-tool-setup.ts +7 -0
  135. package/src/daemon/conversation.ts +42 -5
  136. package/src/daemon/first-greeting.ts +10 -0
  137. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  138. package/src/daemon/handlers/config-a2a.ts +160 -0
  139. package/src/daemon/handlers/config-model.test.ts +1 -0
  140. package/src/daemon/handlers/conversations.ts +79 -0
  141. package/src/daemon/handlers/shared.ts +92 -29
  142. package/src/daemon/host-bash-proxy.ts +1 -1
  143. package/src/daemon/host-cu-proxy.ts +1 -1
  144. package/src/daemon/host-file-proxy.ts +1 -1
  145. package/src/daemon/host-transfer-proxy.ts +1 -1
  146. package/src/daemon/lifecycle.ts +18 -4
  147. package/src/daemon/message-protocol.ts +4 -0
  148. package/src/daemon/message-types/conversations.ts +8 -0
  149. package/src/daemon/message-types/document-comments.ts +50 -0
  150. package/src/daemon/message-types/messages.ts +68 -1
  151. package/src/daemon/message-types/surfaces.ts +3 -1
  152. package/src/daemon/message-types/web-activity.ts +57 -0
  153. package/src/daemon/plugin-source-watcher.ts +135 -3
  154. package/src/daemon/process-message.ts +69 -12
  155. package/src/daemon/query-complexity-router.ts +75 -0
  156. package/src/daemon/trust-context.ts +6 -0
  157. package/src/documents/document-comments-store.test.ts +338 -0
  158. package/src/documents/document-comments-store.ts +237 -0
  159. package/src/documents/document-store.ts +202 -0
  160. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  161. package/src/heartbeat/heartbeat-service.ts +1 -0
  162. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  163. package/src/home/feed-types.ts +6 -1
  164. package/src/home/home-content-refresh.ts +52 -0
  165. package/src/home/home-greeting-cache.ts +69 -0
  166. package/src/home/home-greeting.ts +94 -0
  167. package/src/home/suggested-prompts.ts +177 -9
  168. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  169. package/src/memory/__tests__/memory-retrospective-job.test.ts +320 -6
  170. package/src/memory/conversation-crud.ts +133 -43
  171. package/src/memory/db-init.ts +16 -0
  172. package/src/memory/delivery-crud.ts +41 -0
  173. package/src/memory/delivery-status.ts +141 -15
  174. package/src/memory/external-conversation-store.ts +32 -1
  175. package/src/memory/jobs-worker.ts +21 -1
  176. package/src/memory/memory-retrospective-constants.ts +28 -0
  177. package/src/memory/memory-retrospective-enqueue.ts +3 -2
  178. package/src/memory/memory-retrospective-job.ts +408 -18
  179. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  180. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  181. package/src/memory/migrations/100-core-tables.ts +1 -0
  182. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  183. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  184. package/src/memory/migrations/253-document-comments.ts +47 -0
  185. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  186. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  187. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  188. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  189. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  190. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  191. package/src/memory/migrations/index.ts +17 -0
  192. package/src/memory/migrations/registry.ts +25 -0
  193. package/src/memory/onboarding-events-store.ts +7 -0
  194. package/src/memory/schema/calls.ts +1 -0
  195. package/src/memory/schema/conversations.ts +3 -0
  196. package/src/memory/schema/infrastructure.ts +1 -0
  197. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  198. package/src/memory/v2/__tests__/injection.test.ts +31 -14
  199. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  200. package/src/memory/v2/__tests__/router.test.ts +489 -1
  201. package/src/memory/v2/consolidation-job.ts +14 -0
  202. package/src/memory/v2/injection-events.ts +101 -0
  203. package/src/memory/v2/injection.ts +21 -10
  204. package/src/memory/v2/page-index.ts +209 -7
  205. package/src/memory/v2/page-store.ts +18 -0
  206. package/src/memory/v2/router.ts +209 -55
  207. package/src/messaging/providers/index.ts +7 -1
  208. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  209. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  210. package/src/messaging/providers/slack/adapter.ts +178 -25
  211. package/src/messaging/providers/slack/api.test.ts +54 -0
  212. package/src/messaging/providers/slack/api.ts +119 -3
  213. package/src/messaging/providers/slack/client.ts +12 -0
  214. package/src/messaging/providers/slack/deep-link.ts +20 -1
  215. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  216. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  217. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  218. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  219. package/src/messaging/providers/slack/send.test.ts +77 -0
  220. package/src/messaging/providers/slack/send.ts +8 -2
  221. package/src/messaging/providers/slack/types.ts +14 -0
  222. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
  223. package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
  224. package/src/notifications/conversation-seed-composer.ts +14 -2
  225. package/src/notifications/deferred-emit.ts +135 -0
  226. package/src/notifications/emit-signal.ts +9 -1
  227. package/src/notifications/home-feed-side-effect.ts +60 -30
  228. package/src/oauth/connect-orchestrator.ts +3 -0
  229. package/src/oauth/credential-token-resolver.ts +2 -0
  230. package/src/oauth/manual-token-connection.ts +19 -0
  231. package/src/oauth/oauth-store.ts +12 -0
  232. package/src/oauth/seed-providers.ts +22 -0
  233. package/src/permissions/prompter.ts +5 -2
  234. package/src/permissions/secret-prompter.ts +4 -1
  235. package/src/plugins/defaults/injectors.ts +82 -9
  236. package/src/prompts/__tests__/system-prompt.test.ts +46 -2
  237. package/src/prompts/normalize-onboarding.ts +40 -0
  238. package/src/prompts/sections.ts +32 -14
  239. package/src/prompts/system-prompt.ts +105 -68
  240. package/src/prompts/template-detection.ts +37 -0
  241. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  242. package/src/prompts/templates/BOOTSTRAP.md +8 -0
  243. package/src/prompts/templates/VOICE.md +3 -0
  244. package/src/prompts/templates/system-sections.ts +53 -3
  245. package/src/providers/anthropic/client.ts +132 -5
  246. package/src/providers/fireworks/client.ts +20 -2
  247. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  248. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  249. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  250. package/src/providers/inference/adapter-factory.ts +15 -1
  251. package/src/providers/inference/auth.ts +3 -3
  252. package/src/providers/inference/codex-token-refresh.ts +128 -0
  253. package/src/providers/inference/resolve-auth.ts +49 -6
  254. package/src/providers/model-catalog.ts +48 -1
  255. package/src/providers/openai/chat-completions-provider.ts +57 -20
  256. package/src/providers/openai/responses-provider.ts +9 -3
  257. package/src/providers/openrouter/client.ts +5 -1
  258. package/src/providers/types.ts +25 -0
  259. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  260. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  261. package/src/runtime/agent-wake.ts +151 -56
  262. package/src/runtime/auth/route-policy.ts +7 -3
  263. package/src/runtime/background-job-runner.ts +26 -0
  264. package/src/runtime/channel-reply-delivery.ts +182 -47
  265. package/src/runtime/channel-retry-sweep.ts +141 -16
  266. package/src/runtime/http-types.ts +7 -4
  267. package/src/runtime/pending-interactions.ts +51 -8
  268. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  269. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +55 -1
  270. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  271. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  272. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  273. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  274. package/src/runtime/routes/approval-routes.ts +4 -1
  275. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  276. package/src/runtime/routes/content-source-routes.ts +78 -0
  277. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  278. package/src/runtime/routes/conversation-query-routes.ts +60 -1
  279. package/src/runtime/routes/conversation-routes.ts +281 -76
  280. package/src/runtime/routes/document-comments-routes.ts +287 -0
  281. package/src/runtime/routes/documents-routes.ts +33 -0
  282. package/src/runtime/routes/home-feed-routes.ts +6 -3
  283. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  284. package/src/runtime/routes/host-browser-routes.ts +8 -1
  285. package/src/runtime/routes/identity-routes.ts +21 -0
  286. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  287. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  288. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  289. package/src/runtime/routes/index.ts +12 -4
  290. package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
  291. package/src/runtime/routes/integrations/a2a.ts +60 -1
  292. package/src/runtime/routes/log-export-routes.ts +39 -0
  293. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  294. package/src/runtime/routes/notification-routes.ts +19 -2
  295. package/src/runtime/routes/question-routes.ts +4 -1
  296. package/src/runtime/routes/sanity-routes.ts +159 -0
  297. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  298. package/src/runtime/services/conversation-serializer.ts +30 -4
  299. package/src/schedule/integration-status.ts +3 -1
  300. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  301. package/src/security/oauth2-device-code.ts +307 -0
  302. package/src/security/oauth2.ts +26 -9
  303. package/src/security/secure-keys.ts +5 -0
  304. package/src/skills/catalog-install.ts +6 -2
  305. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  306. package/src/tools/browser/browser-execution.ts +93 -0
  307. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  308. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  309. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  310. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  311. package/src/tools/browser/cdp-client/factory.ts +87 -3
  312. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  313. package/src/tools/browser/cdp-client/types.ts +36 -0
  314. package/src/tools/browser/pinned-tabs.ts +90 -0
  315. package/src/tools/document/document-comment-tool.test.ts +379 -0
  316. package/src/tools/document/document-comment-tool.ts +156 -0
  317. package/src/tools/document/document-tool.ts +128 -2
  318. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  319. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  320. package/src/tools/network/domain-normalize.ts +17 -0
  321. package/src/tools/network/web-fetch.ts +213 -64
  322. package/src/tools/network/web-search.ts +191 -66
  323. package/src/tools/terminal/safe-env.ts +3 -2
  324. package/src/tools/tool-approval-handler.ts +19 -12
  325. package/src/tools/types.ts +4 -0
  326. package/src/tools/ui-surface/definitions.ts +3 -1
  327. package/src/types/onboarding-context.ts +4 -0
  328. package/src/util/__tests__/favicon.test.ts +84 -0
  329. package/src/util/favicon.ts +40 -0
  330. package/src/util/platform.ts +0 -5
  331. package/src/workspace/git-service.ts +75 -4
  332. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  333. package/src/workspace/migrations/registry.ts +2 -0
  334. package/src/config/bundled-skills/document/SKILL.md +0 -54
  335. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  336. package/src/daemon/seed-files.ts +0 -18
  337. package/src/runtime/routes/interface-routes.ts +0 -43
  338. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  339. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  340. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  341. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  342. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -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,158 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ mkdtempSync,
5
+ readFileSync,
6
+ rmSync,
7
+ writeFileSync,
8
+ } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
12
+
13
+ import { deprecateBackgroundConversationOverrideMigration } from "../workspace/migrations/088-deprecate-background-conversation-override.js";
14
+
15
+ let workspaceDir: string;
16
+
17
+ beforeEach(() => {
18
+ workspaceDir = mkdtempSync(join(tmpdir(), "vellum-migration-088-test-"));
19
+ });
20
+
21
+ afterEach(() => {
22
+ if (existsSync(workspaceDir)) {
23
+ rmSync(workspaceDir, { recursive: true, force: true });
24
+ }
25
+ });
26
+
27
+ function promptSystemDir(): string {
28
+ return join(workspaceDir, "prompts", "system");
29
+ }
30
+
31
+ function writeOverride(body: string): string {
32
+ const dir = promptSystemDir();
33
+ mkdirSync(dir, { recursive: true });
34
+ const filePath = join(dir, "08-background-conversation.md");
35
+ writeFileSync(filePath, body, "utf-8");
36
+ return filePath;
37
+ }
38
+
39
+ describe("088-deprecate-background-conversation-override migration", () => {
40
+ test("has expected id and description", () => {
41
+ expect(deprecateBackgroundConversationOverrideMigration.id).toBe(
42
+ "088-deprecate-background-conversation-override",
43
+ );
44
+ expect(
45
+ deprecateBackgroundConversationOverrideMigration.description,
46
+ ).toContain("08-background-conversation");
47
+ });
48
+
49
+ test("renames the override to .deprecated, preserving body", () => {
50
+ const body = "## Custom\n\nUser-customized background note.\n";
51
+ writeOverride(body);
52
+
53
+ deprecateBackgroundConversationOverrideMigration.run(workspaceDir);
54
+
55
+ const originalPath = join(
56
+ promptSystemDir(),
57
+ "08-background-conversation.md",
58
+ );
59
+ const deprecatedPath = join(
60
+ promptSystemDir(),
61
+ "08-background-conversation.md.deprecated",
62
+ );
63
+
64
+ expect(existsSync(originalPath)).toBe(false);
65
+ expect(existsSync(deprecatedPath)).toBe(true);
66
+ expect(readFileSync(deprecatedPath, "utf-8")).toBe(body);
67
+ });
68
+
69
+ test("no-ops when the override is absent", () => {
70
+ expect(() =>
71
+ deprecateBackgroundConversationOverrideMigration.run(workspaceDir),
72
+ ).not.toThrow();
73
+
74
+ const deprecatedPath = join(
75
+ promptSystemDir(),
76
+ "08-background-conversation.md.deprecated",
77
+ );
78
+ expect(existsSync(deprecatedPath)).toBe(false);
79
+ });
80
+
81
+ test("does not touch unrelated section overrides", () => {
82
+ const dir = promptSystemDir();
83
+ mkdirSync(dir, { recursive: true });
84
+ const otherPath = join(dir, "07-external-content.md");
85
+ writeFileSync(otherPath, "## External Content\n\nUntouched.\n", "utf-8");
86
+ writeOverride("body");
87
+
88
+ deprecateBackgroundConversationOverrideMigration.run(workspaceDir);
89
+
90
+ expect(readFileSync(otherPath, "utf-8")).toContain("Untouched");
91
+ });
92
+
93
+ test("is safe to re-run after the rename has happened", () => {
94
+ writeOverride("body");
95
+
96
+ deprecateBackgroundConversationOverrideMigration.run(workspaceDir);
97
+ expect(() =>
98
+ deprecateBackgroundConversationOverrideMigration.run(workspaceDir),
99
+ ).not.toThrow();
100
+
101
+ const deprecatedPath = join(
102
+ promptSystemDir(),
103
+ "08-background-conversation.md.deprecated",
104
+ );
105
+ expect(readFileSync(deprecatedPath, "utf-8")).toBe("body");
106
+ });
107
+
108
+ test("drops the .md and keeps the .deprecated copy when both exist", () => {
109
+ // A user re-created the override after a prior partial run. The bundled
110
+ // section is gone, so the .md would render unconditionally — drop it.
111
+ const dir = promptSystemDir();
112
+ mkdirSync(dir, { recursive: true });
113
+ const deprecatedPath = join(
114
+ dir,
115
+ "08-background-conversation.md.deprecated",
116
+ );
117
+ writeFileSync(deprecatedPath, "preserved.\n", "utf-8");
118
+ const recreatedPath = writeOverride("re-created body\n");
119
+
120
+ deprecateBackgroundConversationOverrideMigration.run(workspaceDir);
121
+
122
+ expect(existsSync(recreatedPath)).toBe(false);
123
+ expect(readFileSync(deprecatedPath, "utf-8")).toBe("preserved.\n");
124
+ });
125
+
126
+ test("down() restores the override when only .deprecated exists", () => {
127
+ writeOverride("body");
128
+ deprecateBackgroundConversationOverrideMigration.run(workspaceDir);
129
+
130
+ deprecateBackgroundConversationOverrideMigration.down(workspaceDir);
131
+
132
+ const overridePath = join(
133
+ promptSystemDir(),
134
+ "08-background-conversation.md",
135
+ );
136
+ const deprecatedPath = join(
137
+ promptSystemDir(),
138
+ "08-background-conversation.md.deprecated",
139
+ );
140
+ expect(existsSync(overridePath)).toBe(true);
141
+ expect(existsSync(deprecatedPath)).toBe(false);
142
+ expect(readFileSync(overridePath, "utf-8")).toBe("body");
143
+ });
144
+
145
+ test("down() is a no-op when only the .md exists", () => {
146
+ writeOverride("body");
147
+
148
+ expect(() =>
149
+ deprecateBackgroundConversationOverrideMigration.down(workspaceDir),
150
+ ).not.toThrow();
151
+
152
+ const overridePath = join(
153
+ promptSystemDir(),
154
+ "08-background-conversation.md",
155
+ );
156
+ expect(readFileSync(overridePath, "utf-8")).toBe("body");
157
+ });
158
+ });
@@ -38,6 +38,7 @@ export function attachmentsToContentBlocks(
38
38
  filename: attachment.filename,
39
39
  },
40
40
  extracted_text: attachment.extractedText,
41
+ ...(attachment.id ? { _attachmentId: attachment.id } : {}),
41
42
  } as ContentBlock;
42
43
  });
43
44
  }
package/src/agent/loop.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  getCalibrationProviderKey,
8
8
  } from "../context/token-estimator.js";
9
9
  import { calculateMaxToolResultChars } from "../context/tool-result-truncation.js";
10
+ import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
10
11
  import { defaultEmptyResponseTerminal } from "../plugins/defaults/empty-response.js";
11
12
  import { defaultToolErrorTerminal } from "../plugins/defaults/tool-error.js";
12
13
  import { defaultToolResultTruncateTerminal } from "../plugins/defaults/tool-result-truncate.js";
@@ -68,21 +69,22 @@ export interface CheckpointInfo {
68
69
  export type CheckpointDecision = "continue" | "yield";
69
70
 
70
71
  /**
71
- * Why an {@link AgentLoop.run} invocation exited its `while (true)` body.
72
+ * Why an agent turn reached a terminal state.
72
73
  *
73
- * Emitted exactly once per run as part of an {@link AgentEvent} of type
74
- * `agent_loop_exit`, then persisted onto the **final** `llm_request_logs`
75
- * row of the run. Rows from intermediate turns keep a NULL
76
- * `agent_loop_exit_reason`, which is how downstream tooling (and the LLM
77
- * Context Inspector) distinguishes "loop kept going" from "loop is done".
74
+ * Emitted as part of an {@link AgentEvent} of type `agent_loop_exit`, then
75
+ * persisted onto the **final** `llm_request_logs` row of the turn. Rows from
76
+ * intermediate turns keep a NULL `agent_loop_exit_reason`, which is how
77
+ * downstream tooling (and the LLM Context Inspector) distinguishes "loop kept
78
+ * going" from "loop is done".
78
79
  *
79
80
  * Values are stable wire/DB strings — they are written to SQLite and
80
81
  * surfaced over the inspector wire format, so renaming any of them is a
81
82
  * breaking change.
82
83
  *
83
- * Cardinality matches the nine `break;`/`throw` sites currently inside the
84
- * loop body. Keep in sync with `emitExit` call sites in
85
- * {@link AgentLoop.run}.
84
+ * Keep in sync with `emitExit` call sites in {@link AgentLoop.run} and the
85
+ * outer conversation orchestrator paths that terminate after a checkpoint
86
+ * yield. A checkpoint yield used for budget compaction is intentionally not
87
+ * a terminal reason — it is a control transfer before re-entering the loop.
86
88
  */
87
89
  export type AgentLoopExitReason =
88
90
  /** `if (signal?.aborted) break;` at the top of the loop. */
@@ -97,8 +99,12 @@ export type AgentLoopExitReason =
97
99
  | "aborted_during_tools"
98
100
  /** A tool result requested handing back to the user. */
99
101
  | "yield_to_user"
100
- /** The orchestrator's `onCheckpoint` callback returned `"yield"`. */
101
- | "checkpoint_yield"
102
+ /** The orchestrator yielded at checkpoint to process a queued message. */
103
+ | "checkpoint_handoff"
104
+ /** Context-window recovery exhausted and the turn ended with an error. */
105
+ | "context_too_large"
106
+ /** User cancellation landed after a non-terminal checkpoint yield. */
107
+ | "aborted_after_checkpoint"
102
108
  /** Signal aborted while the catch handler was synthesizing an error turn. */
103
109
  | "aborted_via_error"
104
110
  /** Catch-block fallback: an unhandled error broke the loop. */
@@ -142,6 +148,7 @@ export type AgentEvent =
142
148
  approvalMode?: string;
143
149
  approvalReason?: string;
144
150
  riskThreshold?: string;
151
+ activityMetadata?: ToolActivityMetadata;
145
152
  }
146
153
  | { type: "tool_use_preview_start"; toolUseId: string; toolName: string }
147
154
  | {
@@ -161,6 +168,17 @@ export type AgentEvent =
161
168
  toolUseId: string;
162
169
  isError: boolean;
163
170
  content?: unknown[];
171
+ /**
172
+ * Finalized input for the server tool (e.g. the actual web-search
173
+ * query). Carried through so the daemon can populate accurate activity
174
+ * metadata; Anthropic streams server-tool input via deltas that aren't
175
+ * resolved at `server_tool_start` time.
176
+ */
177
+ resolvedInput?: Record<string, unknown>;
178
+ /** Provider-specific error code (e.g. `max_uses_exceeded`). */
179
+ errorCode?: string;
180
+ /** Optional human-readable error message from the provider. */
181
+ errorMessage?: string;
164
182
  }
165
183
  | { type: "error"; error: Error }
166
184
  | {
@@ -210,10 +228,11 @@ export type AgentEvent =
210
228
  }
211
229
  | {
212
230
  /**
213
- * Emitted exactly once at the end of {@link AgentLoop.run}, after the
214
- * loop body has exited (whether via `break;`, an unhandled error in
215
- * the catch block, or the empty-response throw path). Consumers
216
- * persist `reason` onto the final `llm_request_logs` row for the run;
231
+ * Emitted when an agent turn reaches a terminal state. Checkpoint
232
+ * yields used for orchestration (handoff or budget compaction) are not
233
+ * emitted by {@link AgentLoop.run}; the outer orchestrator emits a
234
+ * terminal reason only if that control transfer truly ends the turn.
235
+ * Consumers persist `reason` onto the final `llm_request_logs` row;
217
236
  * intermediate rows keep `agent_loop_exit_reason = NULL`, which is the
218
237
  * canonical "loop kept going" signal.
219
238
  */
@@ -371,6 +390,7 @@ export type LoopToolExecutor = (
371
390
  approvalMode?: string;
372
391
  approvalReason?: string;
373
392
  riskThreshold?: string;
393
+ activityMetadata?: ToolActivityMetadata;
374
394
  }>;
375
395
 
376
396
  export class AgentLoop {
@@ -458,6 +478,8 @@ export class AgentLoop {
458
478
  */
459
479
  overrideProfile?: string,
460
480
  effectiveMaxInputTokens?: number,
481
+ resolveOverrideProfile?: () => string | undefined,
482
+ resolveEffectiveMaxInputTokens?: () => number | undefined,
461
483
  ): Promise<Message[]> {
462
484
  const history = [...messages];
463
485
  const initialHistoryLength = messages.length;
@@ -580,9 +602,14 @@ export class AgentLoop {
580
602
  // `activeProfile` and any call-site named profile. Threading it on
581
603
  // every send (rather than once at construction) keeps subagents that
582
604
  // share an `AgentLoop` instance but ought to inherit a different
583
- // profile correct — and matches how `callSite` is plumbed.
584
- if (overrideProfile) {
585
- providerConfig.overrideProfile = overrideProfile;
605
+ // profile correct — and matches how `callSite` is plumbed. The
606
+ // optional resolver lets a turn observe an explicitly confirmed
607
+ // profile-session switch before the next model call.
608
+ const effectiveOverrideProfile = resolveOverrideProfile
609
+ ? resolveOverrideProfile()
610
+ : overrideProfile;
611
+ if (effectiveOverrideProfile) {
612
+ providerConfig.overrideProfile = effectiveOverrideProfile;
586
613
  }
587
614
 
588
615
  // Rate-limit consecutive LLM calls to prevent spin when tools return instantly
@@ -690,6 +717,13 @@ export class AgentLoop {
690
717
  toolUseId: event.toolUseId,
691
718
  isError: event.isError,
692
719
  ...(event.content ? { content: event.content } : {}),
720
+ ...(event.resolvedInput
721
+ ? { resolvedInput: event.resolvedInput }
722
+ : {}),
723
+ ...(event.errorCode ? { errorCode: event.errorCode } : {}),
724
+ ...(event.errorMessage
725
+ ? { errorMessage: event.errorMessage }
726
+ : {}),
693
727
  });
694
728
  }
695
729
  },
@@ -1079,7 +1113,10 @@ export class AgentLoop {
1079
1113
  // truncation strategy (e.g. a summariser) while the default
1080
1114
  // middleware preserves the historical tail-drop behaviour.
1081
1115
  const contextWindowTokens =
1082
- effectiveMaxInputTokens ?? this.config.maxInputTokens ?? 180_000;
1116
+ resolveEffectiveMaxInputTokens?.() ??
1117
+ effectiveMaxInputTokens ??
1118
+ this.config.maxInputTokens ??
1119
+ 180_000;
1083
1120
  const maxChars = calculateMaxToolResultChars(contextWindowTokens);
1084
1121
  const truncateMiddlewares = getMiddlewaresFor("toolResultTruncate");
1085
1122
 
@@ -1155,6 +1192,7 @@ export class AgentLoop {
1155
1192
  approvalMode: result.approvalMode,
1156
1193
  approvalReason: result.approvalReason,
1157
1194
  riskThreshold: result.riskThreshold,
1195
+ activityMetadata: result.activityMetadata,
1158
1196
  });
1159
1197
  }
1160
1198
 
@@ -1236,7 +1274,6 @@ export class AgentLoop {
1236
1274
  history,
1237
1275
  });
1238
1276
  if (decision === "yield") {
1239
- await emitExit("checkpoint_yield");
1240
1277
  break;
1241
1278
  }
1242
1279
  }