@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
@@ -0,0 +1,168 @@
1
+ import { eq } from "drizzle-orm";
2
+
3
+ import type { DrizzleDb } from "../memory/db-connection.js";
4
+ import { getDb } from "../memory/db-connection.js";
5
+ import { rawChanges } from "../memory/raw-query.js";
6
+ import { a2aTasks } from "../memory/schema.js";
7
+ import { TERMINAL_TASK_STATES } from "./protocol-constants.js";
8
+ import type {
9
+ A2AMessage,
10
+ A2ATask,
11
+ Artifact,
12
+ TaskState,
13
+ } from "./protocol-types.js";
14
+
15
+ // ── Internal types ──────────────────────────────────────────────────
16
+
17
+ /** Raw database row shape for a2a_tasks. */
18
+ type A2ATaskRow = typeof a2aTasks.$inferSelect;
19
+
20
+ // ── Helpers ─────────────────────────────────────────────────────────
21
+
22
+ /** Throw if the task doesn't exist or is in a terminal state. */
23
+ function assertNonTerminal(
24
+ db: DrizzleDb,
25
+ taskId: string,
26
+ targetState: TaskState,
27
+ ): void {
28
+ const current = db
29
+ .select({ state: a2aTasks.state })
30
+ .from(a2aTasks)
31
+ .where(eq(a2aTasks.id, taskId))
32
+ .get();
33
+
34
+ if (!current) {
35
+ throw new Error(`A2A task not found: ${taskId}`);
36
+ }
37
+
38
+ if (TERMINAL_TASK_STATES.has(current.state as TaskState)) {
39
+ throw new Error(
40
+ `Cannot transition task ${taskId} from terminal state "${current.state}" to "${targetState}"`,
41
+ );
42
+ }
43
+ }
44
+
45
+ function rowToTask(row: A2ATaskRow): A2ATask {
46
+ return {
47
+ id: row.id,
48
+ context_id: row.contextId ?? undefined,
49
+ status: {
50
+ state: row.state as TaskState,
51
+ message: row.statusMessage
52
+ ? {
53
+ message_id: crypto.randomUUID(),
54
+ role: "agent",
55
+ parts: [{ kind: "text", text: row.statusMessage }],
56
+ }
57
+ : undefined,
58
+ timestamp: new Date(row.updatedAt).toISOString(),
59
+ },
60
+ artifacts: row.artifactsJson
61
+ ? (JSON.parse(row.artifactsJson) as Artifact[])
62
+ : undefined,
63
+ };
64
+ }
65
+
66
+ // ── Store functions ─────────────────────────────────────────────────
67
+
68
+ export function createTask(params: {
69
+ contextId?: string;
70
+ senderAssistantId: string;
71
+ requestMessage: A2AMessage;
72
+ pushUrl?: string;
73
+ }): A2ATask {
74
+ const db = getDb();
75
+ const now = Date.now();
76
+
77
+ const row: A2ATaskRow = {
78
+ id: crypto.randomUUID(),
79
+ contextId: params.contextId ?? null,
80
+ conversationId: null,
81
+ state: "submitted",
82
+ statusMessage: null,
83
+ requestMessageJson: JSON.stringify(params.requestMessage),
84
+ artifactsJson: null,
85
+ pushUrl: params.pushUrl ?? null,
86
+ senderAssistantId: params.senderAssistantId,
87
+ createdAt: now,
88
+ updatedAt: now,
89
+ };
90
+
91
+ db.insert(a2aTasks).values(row).run();
92
+
93
+ return rowToTask(row);
94
+ }
95
+
96
+ export function getTask(taskId: string): A2ATask | null {
97
+ const db = getDb();
98
+ const row = db.select().from(a2aTasks).where(eq(a2aTasks.id, taskId)).get();
99
+ return row ? rowToTask(row) : null;
100
+ }
101
+
102
+ export function updateState(
103
+ taskId: string,
104
+ state: TaskState,
105
+ statusMessage?: string,
106
+ ): A2ATask {
107
+ const db = getDb();
108
+ assertNonTerminal(db, taskId, state);
109
+
110
+ db.update(a2aTasks)
111
+ .set({
112
+ state,
113
+ statusMessage: statusMessage ?? null,
114
+ updatedAt: Date.now(),
115
+ })
116
+ .where(eq(a2aTasks.id, taskId))
117
+ .run();
118
+
119
+ return rowToTask(
120
+ db.select().from(a2aTasks).where(eq(a2aTasks.id, taskId)).get()!,
121
+ );
122
+ }
123
+
124
+ export function completeWithArtifacts(
125
+ taskId: string,
126
+ artifacts: Artifact[],
127
+ ): A2ATask {
128
+ const db = getDb();
129
+ assertNonTerminal(db, taskId, "completed");
130
+
131
+ db.update(a2aTasks)
132
+ .set({
133
+ state: "completed",
134
+ statusMessage: null,
135
+ artifactsJson: JSON.stringify(artifacts),
136
+ updatedAt: Date.now(),
137
+ })
138
+ .where(eq(a2aTasks.id, taskId))
139
+ .run();
140
+
141
+ return rowToTask(
142
+ db.select().from(a2aTasks).where(eq(a2aTasks.id, taskId)).get()!,
143
+ );
144
+ }
145
+
146
+ export function linkConversation(taskId: string, conversationId: string): void {
147
+ const db = getDb();
148
+ const now = Date.now();
149
+
150
+ db.update(a2aTasks)
151
+ .set({ conversationId, updatedAt: now })
152
+ .where(eq(a2aTasks.id, taskId))
153
+ .run();
154
+
155
+ if (rawChanges() === 0) {
156
+ throw new Error(`A2A task not found: ${taskId}`);
157
+ }
158
+ }
159
+
160
+ export function getPushUrl(taskId: string): string | null {
161
+ const db = getDb();
162
+ const row = db
163
+ .select({ pushUrl: a2aTasks.pushUrl })
164
+ .from(a2aTasks)
165
+ .where(eq(a2aTasks.id, taskId))
166
+ .get();
167
+ return row?.pushUrl ?? null;
168
+ }
@@ -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";
@@ -67,6 +68,48 @@ export interface CheckpointInfo {
67
68
 
68
69
  export type CheckpointDecision = "continue" | "yield";
69
70
 
71
+ /**
72
+ * Why an agent turn reached a terminal state.
73
+ *
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".
79
+ *
80
+ * Values are stable wire/DB strings — they are written to SQLite and
81
+ * surfaced over the inspector wire format, so renaming any of them is a
82
+ * breaking change.
83
+ *
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.
88
+ */
89
+ export type AgentLoopExitReason =
90
+ /** `if (signal?.aborted) break;` at the top of the loop. */
91
+ | "aborted_pre_call"
92
+ /** Empty assistant response after the configured retry budget. */
93
+ | "empty_response_exhausted"
94
+ /** Assistant message has no tool-use blocks (or no tool executor). */
95
+ | "no_tool_calls"
96
+ /** Signal aborted while building the user-side tool-results message. */
97
+ | "aborted_post_response"
98
+ /** Signal aborted mid-tool-execution; completed results were pushed. */
99
+ | "aborted_during_tools"
100
+ /** A tool result requested handing back to the user. */
101
+ | "yield_to_user"
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"
108
+ /** Signal aborted while the catch handler was synthesizing an error turn. */
109
+ | "aborted_via_error"
110
+ /** Catch-block fallback: an unhandled error broke the loop. */
111
+ | "error";
112
+
70
113
  export type AgentEvent =
71
114
  | { type: "text_delta"; text: string }
72
115
  | { type: "thinking_delta"; thinking: string }
@@ -105,6 +148,7 @@ export type AgentEvent =
105
148
  approvalMode?: string;
106
149
  approvalReason?: string;
107
150
  riskThreshold?: string;
151
+ activityMetadata?: ToolActivityMetadata;
108
152
  }
109
153
  | { type: "tool_use_preview_start"; toolUseId: string; toolName: string }
110
154
  | {
@@ -124,8 +168,45 @@ export type AgentEvent =
124
168
  toolUseId: string;
125
169
  isError: boolean;
126
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;
127
182
  }
128
183
  | { type: "error"; error: Error }
184
+ | {
185
+ /**
186
+ * Emitted when the `llmCall` pipeline throws — i.e. the provider
187
+ * rejected the request before returning a usable response. Carries
188
+ * the loop-level raw request we attempted to send (messages, tools,
189
+ * system prompt, provider-agnostic config) plus the thrown error.
190
+ * Consumers (`handleProviderError` in the daemon handlers, the
191
+ * `onEvent` in `agent-wake`) persist these as `llm_request_logs`
192
+ * rows so failed calls are queryable in the LLM inspector instead
193
+ * of only surfacing in pino logs.
194
+ *
195
+ * `rawRequest` is the loop-level abstract shape rather than the
196
+ * provider-specific payload (which the provider builds internally
197
+ * and never returns when it throws). `actualProvider` echoes the
198
+ * `ProviderError.provider` tag when available so the persisted row
199
+ * has the same `provider` column value as a successful `usage` row.
200
+ *
201
+ * Re-thrown by the inner LLM-call try/catch after emission so the
202
+ * outer agent-loop catch still handles abort, Sentry capture, the
203
+ * existing `error` event, and the loop break.
204
+ */
205
+ type: "provider_error";
206
+ rawRequest: unknown;
207
+ error: Error;
208
+ actualProvider?: string;
209
+ }
129
210
  | {
130
211
  type: "usage";
131
212
  inputTokens: number;
@@ -144,6 +225,19 @@ export type AgentEvent =
144
225
  * for this call (e.g. legacy/stubbed code paths).
145
226
  */
146
227
  estimatedInputTokens?: number;
228
+ }
229
+ | {
230
+ /**
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;
236
+ * intermediate rows keep `agent_loop_exit_reason = NULL`, which is the
237
+ * canonical "loop kept going" signal.
238
+ */
239
+ type: "agent_loop_exit";
240
+ reason: AgentLoopExitReason;
147
241
  };
148
242
 
149
243
  const DEFAULT_CONFIG: AgentLoopConfig = {
@@ -296,6 +390,7 @@ export type LoopToolExecutor = (
296
390
  approvalMode?: string;
297
391
  approvalReason?: string;
298
392
  riskThreshold?: string;
393
+ activityMetadata?: ToolActivityMetadata;
299
394
  }>;
300
395
 
301
396
  export class AgentLoop {
@@ -383,6 +478,8 @@ export class AgentLoop {
383
478
  */
384
479
  overrideProfile?: string,
385
480
  effectiveMaxInputTokens?: number,
481
+ resolveOverrideProfile?: () => string | undefined,
482
+ resolveEffectiveMaxInputTokens?: () => number | undefined,
386
483
  ): Promise<Message[]> {
387
484
  const history = [...messages];
388
485
  const initialHistoryLength = messages.length;
@@ -398,8 +495,24 @@ export class AgentLoop {
398
495
  const substitutionMap = new Map<string, string>();
399
496
  let streamingPending = "";
400
497
 
498
+ // Idempotency guard for `emitExit`. Used so the throw path in the
499
+ // empty-response branch can stamp its reason ("empty_response_exhausted")
500
+ // before throwing — the catch handler that observes the rethrow will
501
+ // then attempt to stamp "error" and harmlessly no-op, preserving the
502
+ // more specific reason. Also defends against accidental future
503
+ // double-emits if a new break site is added without checking this.
504
+ let exitReasonEmitted = false;
505
+ const emitExit = async (reason: AgentLoopExitReason): Promise<void> => {
506
+ if (exitReasonEmitted) return;
507
+ exitReasonEmitted = true;
508
+ await onEvent({ type: "agent_loop_exit", reason });
509
+ };
510
+
401
511
  while (true) {
402
- if (signal?.aborted) break;
512
+ if (signal?.aborted) {
513
+ await emitExit("aborted_pre_call");
514
+ break;
515
+ }
403
516
 
404
517
  rlog.info(
405
518
  { turn: toolUseTurns, messageCount: history.length },
@@ -489,9 +602,14 @@ export class AgentLoop {
489
602
  // `activeProfile` and any call-site named profile. Threading it on
490
603
  // every send (rather than once at construction) keeps subagents that
491
604
  // share an `AgentLoop` instance but ought to inherit a different
492
- // profile correct — and matches how `callSite` is plumbed.
493
- if (overrideProfile) {
494
- 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;
495
613
  }
496
614
 
497
615
  // Rate-limit consecutive LLM calls to prevent spin when tools return instantly
@@ -599,6 +717,13 @@ export class AgentLoop {
599
717
  toolUseId: event.toolUseId,
600
718
  isError: event.isError,
601
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
+ : {}),
602
727
  });
603
728
  }
604
729
  },
@@ -618,23 +743,64 @@ export class AgentLoop {
618
743
  toolUseTurns,
619
744
  );
620
745
 
621
- const response: LLMCallResult = await runPipeline<
622
- LLMCallArgs,
623
- LLMCallResult
624
- >(
625
- "llmCall",
626
- getMiddlewaresFor("llmCall"),
627
- (args) =>
628
- args.provider.sendMessage(
629
- args.messages,
630
- args.tools,
631
- args.systemPrompt,
632
- args.options,
633
- ),
634
- llmCallArgs,
635
- turnCtx,
636
- DEFAULT_TIMEOUTS.llmCall,
637
- );
746
+ // Inner try/catch narrows error-recording scope to the provider
747
+ // call itself. The outer agent-loop catch (below) wraps the entire
748
+ // turn body (tool execution, plugin pipelines, checkpoints), so
749
+ // recording there would risk mis-attributing tool/plugin throws as
750
+ // provider rejections. On provider failure we emit `provider_error`
751
+ // with the loop-level raw request so consumers can persist it as an
752
+ // `llm_request_logs` row, then re-throw so the existing outer catch
753
+ // continues to handle abort sync, Sentry capture, the `error` event,
754
+ // and the loop break unchanged.
755
+ let response: LLMCallResult;
756
+ try {
757
+ response = await runPipeline<LLMCallArgs, LLMCallResult>(
758
+ "llmCall",
759
+ getMiddlewaresFor("llmCall"),
760
+ (args) =>
761
+ args.provider.sendMessage(
762
+ args.messages,
763
+ args.tools,
764
+ args.systemPrompt,
765
+ args.options,
766
+ ),
767
+ llmCallArgs,
768
+ turnCtx,
769
+ DEFAULT_TIMEOUTS.llmCall,
770
+ );
771
+ } catch (llmCallError) {
772
+ // Skip recording on abort — the user cancelled the request and
773
+ // there's no provider rejection worth a log row. The outer catch
774
+ // still synthesizes cancellation tool_results.
775
+ if (!signal?.aborted) {
776
+ const errInstance =
777
+ llmCallError instanceof Error
778
+ ? llmCallError
779
+ : new Error(String(llmCallError));
780
+ // Strip non-serializable / runtime-only fields from `options`
781
+ // before snapshotting. `onEvent` is a closure with side effects
782
+ // and `signal` is an AbortSignal — neither is meaningful in a
783
+ // persisted log row, and `JSON.stringify` would silently drop or
784
+ // misrepresent both.
785
+ const rawRequest = {
786
+ provider: this.provider.name,
787
+ messages: llmCallArgs.messages,
788
+ tools: llmCallArgs.tools,
789
+ systemPrompt: llmCallArgs.systemPrompt,
790
+ config: llmCallArgs.options?.config,
791
+ };
792
+ onEvent({
793
+ type: "provider_error",
794
+ rawRequest,
795
+ error: errInstance,
796
+ actualProvider:
797
+ errInstance instanceof ProviderError
798
+ ? errInstance.provider
799
+ : this.provider.name,
800
+ });
801
+ }
802
+ throw llmCallError;
803
+ }
638
804
 
639
805
  const providerDurationMs = Date.now() - providerStart;
640
806
 
@@ -785,6 +951,11 @@ export class AgentLoop {
785
951
  { turn: toolUseTurns, retries: emptyResponseRetries },
786
952
  "emptyResponse pipeline requested error surface",
787
953
  );
954
+ // Stamp the specific exit reason *before* throwing. The catch
955
+ // handler below will see the rethrown error and attempt to stamp
956
+ // "error" — guarded by `exitReasonEmitted`, that becomes a no-op
957
+ // and the more specific reason wins.
958
+ await emitExit("empty_response_exhausted");
788
959
  throw new AssistantError(
789
960
  "Model returned empty response after tool results",
790
961
  ErrorCode.INTERNAL_ERROR,
@@ -811,6 +982,7 @@ export class AgentLoop {
811
982
  await onEvent({ type: "message_complete", message: assistantMessage });
812
983
 
813
984
  if (toolUseBlocks.length === 0 || !this.toolExecutor) {
985
+ await emitExit("no_tool_calls");
814
986
  break;
815
987
  }
816
988
 
@@ -835,6 +1007,7 @@ export class AgentLoop {
835
1007
  }),
836
1008
  );
837
1009
  history.push({ role: "user", content: cancelledBlocks });
1010
+ await emitExit("aborted_post_response");
838
1011
  break;
839
1012
  }
840
1013
 
@@ -940,7 +1113,10 @@ export class AgentLoop {
940
1113
  // truncation strategy (e.g. a summariser) while the default
941
1114
  // middleware preserves the historical tail-drop behaviour.
942
1115
  const contextWindowTokens =
943
- effectiveMaxInputTokens ?? this.config.maxInputTokens ?? 180_000;
1116
+ resolveEffectiveMaxInputTokens?.() ??
1117
+ effectiveMaxInputTokens ??
1118
+ this.config.maxInputTokens ??
1119
+ 180_000;
944
1120
  const maxChars = calculateMaxToolResultChars(contextWindowTokens);
945
1121
  const truncateMiddlewares = getMiddlewaresFor("toolResultTruncate");
946
1122
 
@@ -1016,12 +1192,14 @@ export class AgentLoop {
1016
1192
  approvalMode: result.approvalMode,
1017
1193
  approvalReason: result.approvalReason,
1018
1194
  riskThreshold: result.riskThreshold,
1195
+ activityMetadata: result.activityMetadata,
1019
1196
  });
1020
1197
  }
1021
1198
 
1022
1199
  // If cancelled during execution, push completed results and stop
1023
1200
  if (signal?.aborted) {
1024
1201
  history.push({ role: "user", content: resultBlocks });
1202
+ await emitExit("aborted_during_tools");
1025
1203
  break;
1026
1204
  }
1027
1205
 
@@ -1029,6 +1207,7 @@ export class AgentLoop {
1029
1207
  // surface awaiting a button click), push results and stop the loop.
1030
1208
  if (toolResults.some(({ result }) => result.yieldToUser)) {
1031
1209
  history.push({ role: "user", content: resultBlocks });
1210
+ await emitExit("yield_to_user");
1032
1211
  break;
1033
1212
  }
1034
1213
 
@@ -1114,6 +1293,7 @@ export class AgentLoop {
1114
1293
  );
1115
1294
  history.push({ role: "user", content: cancelledBlocks });
1116
1295
  }
1296
+ await emitExit("aborted_via_error");
1117
1297
  break;
1118
1298
  }
1119
1299
  const err = error instanceof Error ? error : new Error(String(error));
@@ -1125,6 +1305,12 @@ export class AgentLoop {
1125
1305
  Sentry.captureException(err);
1126
1306
  }
1127
1307
  onEvent({ type: "error", error: err });
1308
+ // Catch-block fallback. If the rethrow came from the
1309
+ // empty-response throw path above, `emitExit("error")` no-ops
1310
+ // because `emitExit("empty_response_exhausted")` already ran
1311
+ // before the throw. Otherwise, this is the genuine
1312
+ // unhandled-error exit.
1313
+ await emitExit("error");
1128
1314
  break;
1129
1315
  }
1130
1316
  }