@vellumai/assistant 0.8.1 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (630) hide show
  1. package/ARCHITECTURE.md +13 -19
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +17 -0
  5. package/docker-init-apt-root.sh +167 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +642 -5
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
  21. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  23. package/src/__tests__/anthropic-provider.test.ts +45 -0
  24. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  25. package/src/__tests__/app-executors.test.ts +220 -4
  26. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  27. package/src/__tests__/bundled-asset.test.ts +6 -6
  28. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  29. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  30. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  31. package/src/__tests__/clawhub.test.ts +75 -16
  32. package/src/__tests__/compactor-tail-resolution.test.ts +147 -0
  33. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  34. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  35. package/src/__tests__/config-schema.test.ts +21 -0
  36. package/src/__tests__/config-set-route.test.ts +80 -0
  37. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  38. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  39. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  40. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  41. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  42. package/src/__tests__/context-token-estimator.test.ts +31 -65
  43. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  44. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  45. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  46. package/src/__tests__/conversation-agent-loop.test.ts +59 -1
  47. package/src/__tests__/conversation-error.test.ts +42 -3
  48. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  49. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  50. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  51. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  52. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  53. package/src/__tests__/conversation-pairing.test.ts +54 -0
  54. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  55. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  56. package/src/__tests__/conversation-queue.test.ts +4 -1
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +102 -13
  58. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  59. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  60. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  61. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  64. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  65. package/src/__tests__/date-context.test.ts +45 -0
  66. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  67. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  68. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  69. package/src/__tests__/dm-backfill.test.ts +121 -10
  70. package/src/__tests__/document-tool-security.test.ts +258 -0
  71. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  72. package/src/__tests__/edit-propagation.test.ts +33 -0
  73. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  74. package/src/__tests__/external-plugin-loader.test.ts +151 -55
  75. package/src/__tests__/filing-service.test.ts +140 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  77. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  78. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  79. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  80. package/src/__tests__/heartbeat-service.test.ts +24 -164
  81. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  82. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  83. package/src/__tests__/helpers/wait-for.ts +21 -0
  84. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  85. package/src/__tests__/history-repair.test.ts +73 -0
  86. package/src/__tests__/host-app-control-proxy.test.ts +507 -10
  87. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  88. package/src/__tests__/image-credentials.test.ts +1 -1
  89. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  90. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  91. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  92. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  93. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  94. package/src/__tests__/injector-background-turn.test.ts +153 -0
  95. package/src/__tests__/injector-chain.test.ts +15 -8
  96. package/src/__tests__/install-skill-routing.test.ts +155 -37
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -3
  98. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  99. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  100. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  101. package/src/__tests__/llm-catalog-parity.test.ts +58 -13
  102. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  103. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  104. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
  105. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  106. package/src/__tests__/llm-resolver.test.ts +255 -2
  107. package/src/__tests__/llm-usage-store.test.ts +114 -0
  108. package/src/__tests__/managed-profile-guard.test.ts +41 -29
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  110. package/src/__tests__/managed-store.test.ts +84 -192
  111. package/src/__tests__/media-generate-image.test.ts +1 -1
  112. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  113. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  114. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  115. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  116. package/src/__tests__/notification-deep-link.test.ts +15 -0
  117. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  118. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  119. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  120. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  121. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  122. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  123. package/src/__tests__/openai-provider.test.ts +242 -3
  124. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  125. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  126. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  127. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  128. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  129. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +7 -2
  130. package/src/__tests__/platform.test.ts +2 -0
  131. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  132. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  133. package/src/__tests__/plugin-external-api.test.ts +68 -0
  134. package/src/__tests__/plugin-registry.test.ts +0 -77
  135. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  136. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  137. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  138. package/src/__tests__/plugin-types.test.ts +3 -13
  139. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  140. package/src/__tests__/process-message-display-content.test.ts +421 -0
  141. package/src/__tests__/provider-catalog-visibility.test.ts +158 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  143. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +33 -31
  144. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  145. package/src/__tests__/schedule-routes.test.ts +50 -3
  146. package/src/__tests__/schedule-store.test.ts +94 -0
  147. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  148. package/src/__tests__/schema-transforms.test.ts +20 -0
  149. package/src/__tests__/search-skills-unified.test.ts +0 -5
  150. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
  151. package/src/__tests__/server-history-render.test.ts +43 -0
  152. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  153. package/src/__tests__/skill-load-tool.test.ts +27 -89
  154. package/src/__tests__/skill-memory.test.ts +23 -3
  155. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  156. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  157. package/src/__tests__/skills-install-extract.test.ts +49 -38
  158. package/src/__tests__/skills-install-staging.test.ts +159 -0
  159. package/src/__tests__/skills-uninstall.test.ts +9 -41
  160. package/src/__tests__/skills.test.ts +51 -58
  161. package/src/__tests__/slack-channel-config.test.ts +9 -0
  162. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  163. package/src/__tests__/system-prompt.test.ts +670 -63
  164. package/src/__tests__/terminal-tools.test.ts +28 -1
  165. package/src/__tests__/thread-backfill.test.ts +557 -27
  166. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  167. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  168. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  169. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  170. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  171. package/src/__tests__/tool-executor.test.ts +16 -4
  172. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  173. package/src/__tests__/turn-events-store.test.ts +256 -0
  174. package/src/__tests__/twilio-routes.test.ts +4 -0
  175. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  176. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  177. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  178. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  179. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  180. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  181. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  182. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  183. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  184. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  185. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  186. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  187. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  188. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  189. package/src/a2a/__tests__/task-store.test.ts +246 -0
  190. package/src/a2a/agent-card.ts +58 -0
  191. package/src/a2a/feature-gate.ts +8 -0
  192. package/src/a2a/protocol-constants.ts +21 -0
  193. package/src/a2a/protocol-errors.ts +50 -0
  194. package/src/a2a/protocol-types.ts +162 -0
  195. package/src/a2a/task-store.ts +168 -0
  196. package/src/acp/resolve-agent.ts +1 -1
  197. package/src/agent/image-optimize.ts +13 -5
  198. package/src/agent/loop.ts +167 -18
  199. package/src/calls/voice-session-bridge.ts +61 -42
  200. package/src/channels/config.ts +9 -0
  201. package/src/channels/types.ts +122 -0
  202. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  203. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  204. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  205. package/src/cli/commands/__tests__/schedules.test.ts +960 -0
  206. package/src/cli/commands/changelog.ts +106 -42
  207. package/src/cli/commands/conversations.ts +102 -17
  208. package/src/cli/commands/default-action.ts +10 -53
  209. package/src/cli/commands/notifications.ts +388 -346
  210. package/src/cli/commands/plugins.ts +252 -0
  211. package/src/cli/commands/schedules.ts +683 -0
  212. package/src/cli/commands/telemetry.ts +40 -0
  213. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  214. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  215. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  216. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  217. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  218. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  219. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  220. package/src/cli/lib/cli-colors.ts +12 -0
  221. package/src/cli/lib/confirm-prompt.ts +79 -0
  222. package/src/cli/lib/install-from-github.ts +303 -0
  223. package/src/cli/lib/list-installed-plugins.ts +137 -0
  224. package/src/cli/lib/search-plugins.ts +163 -0
  225. package/src/cli/lib/uninstall-plugin.ts +82 -0
  226. package/src/cli/lib/unknown-command.ts +111 -0
  227. package/src/cli/program.ts +52 -2
  228. package/src/config/assistant-feature-flags.ts +24 -54
  229. package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
  230. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  231. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  232. package/src/config/bundled-skills/document/SKILL.md +23 -3
  233. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  234. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  235. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  236. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  237. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  238. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  239. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  240. package/src/config/bundled-tool-registry.ts +6 -0
  241. package/src/config/call-site-defaults.ts +105 -0
  242. package/src/config/feature-flag-registry.json +41 -9
  243. package/src/config/llm-resolver.ts +52 -1
  244. package/src/config/loader.ts +64 -38
  245. package/src/config/schema.ts +9 -10
  246. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  247. package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
  248. package/src/config/schemas/channels.ts +17 -0
  249. package/src/config/schemas/compaction.ts +28 -0
  250. package/src/config/schemas/conversations.ts +10 -0
  251. package/src/config/schemas/heartbeat.ts +23 -0
  252. package/src/config/schemas/llm-request-logs.ts +31 -7
  253. package/src/config/schemas/llm.ts +1 -0
  254. package/src/config/schemas/memory-retrieval.ts +18 -0
  255. package/src/config/schemas/memory-retrospective.ts +1 -1
  256. package/src/config/schemas/memory-v2.ts +4 -4
  257. package/src/config/schemas/memory.ts +3 -1
  258. package/src/config/schemas/tools.ts +14 -0
  259. package/src/config/seed-inference-profiles.ts +99 -29
  260. package/src/config/skills.ts +3 -96
  261. package/src/context/compactor.ts +1107 -0
  262. package/src/context/token-estimator.ts +34 -36
  263. package/src/context/window-manager.ts +197 -1520
  264. package/src/credential-execution/managed-catalog.ts +37 -0
  265. package/src/credential-health/credential-health-service.ts +280 -19
  266. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +33 -18
  267. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  268. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  269. package/src/daemon/approval-generators.ts +8 -6
  270. package/src/daemon/config-watcher.ts +94 -31
  271. package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
  272. package/src/daemon/conversation-agent-loop.ts +198 -11
  273. package/src/daemon/conversation-error.ts +171 -37
  274. package/src/daemon/conversation-lifecycle.ts +53 -40
  275. package/src/daemon/conversation-messaging.ts +25 -6
  276. package/src/daemon/conversation-process.ts +49 -12
  277. package/src/daemon/conversation-runtime-assembly.ts +25 -1
  278. package/src/daemon/conversation-slash.ts +12 -5
  279. package/src/daemon/conversation-store.ts +11 -4
  280. package/src/daemon/conversation-tool-setup.ts +39 -7
  281. package/src/daemon/conversation.ts +33 -8
  282. package/src/daemon/date-context.ts +40 -0
  283. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  284. package/src/daemon/first-greeting.ts +22 -2
  285. package/src/daemon/guardian-action-generators.ts +1 -125
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  287. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  288. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  289. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  290. package/src/daemon/handlers/config-a2a.ts +289 -0
  291. package/src/daemon/handlers/config-model.ts +6 -5
  292. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  293. package/src/daemon/handlers/conversations.ts +1 -0
  294. package/src/daemon/handlers/shared.ts +14 -5
  295. package/src/daemon/handlers/skills.ts +111 -108
  296. package/src/daemon/history-repair.ts +28 -1
  297. package/src/daemon/host-app-control-proxy.ts +153 -27
  298. package/src/daemon/host-proxy-preactivation.ts +85 -18
  299. package/src/daemon/lifecycle.ts +89 -91
  300. package/src/daemon/meet-host-supervisor.ts +5 -4
  301. package/src/daemon/memory-v2-startup.ts +85 -0
  302. package/src/daemon/message-protocol.ts +1 -0
  303. package/src/daemon/message-types/conversations.ts +25 -0
  304. package/src/daemon/message-types/messages.ts +61 -0
  305. package/src/daemon/message-types/notifications.ts +21 -0
  306. package/src/daemon/message-types/subagents.ts +1 -0
  307. package/src/daemon/message-types/sync.ts +1 -0
  308. package/src/daemon/pkb-reminder-builder.test.ts +11 -54
  309. package/src/daemon/pkb-reminder-builder.ts +5 -20
  310. package/src/daemon/plugin-source-watcher.ts +146 -0
  311. package/src/daemon/process-message.ts +24 -3
  312. package/src/daemon/server.ts +11 -2
  313. package/src/daemon/skill-memory-refresh.ts +33 -0
  314. package/src/daemon/wake-target-adapter.ts +2 -0
  315. package/src/documents/document-store.ts +221 -3
  316. package/src/embedded/plugin-api.ts +40 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  318. package/src/export/transcript-formatter.ts +54 -20
  319. package/src/filing/filing-service.ts +39 -0
  320. package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
  321. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  322. package/src/heartbeat/heartbeat-service.ts +73 -189
  323. package/src/home/__tests__/feed-types.test.ts +80 -0
  324. package/src/home/feed-types.ts +36 -2
  325. package/src/home/post-connect-feed.ts +1 -0
  326. package/src/index.ts +18 -1
  327. package/src/ipc/cli-client.ts +147 -45
  328. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  329. package/src/mcp/client.ts +20 -4
  330. package/src/media/image-credentials.ts +3 -3
  331. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  332. package/src/memory/__tests__/conversation-queries.test.ts +483 -0
  333. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  334. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  335. package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
  336. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  337. package/src/memory/__tests__/message-content.test.ts +35 -0
  338. package/src/memory/bookmark-crud.ts +42 -10
  339. package/src/memory/context-search/sources/conversations.ts +62 -2
  340. package/src/memory/context-search/sources/workspace.ts +4 -0
  341. package/src/memory/conversation-crud.ts +63 -19
  342. package/src/memory/conversation-queries.ts +197 -11
  343. package/src/memory/conversation-title-service.ts +26 -4
  344. package/src/memory/db-init.ts +12 -0
  345. package/src/memory/delivery-crud.ts +152 -5
  346. package/src/memory/embedding-backend.ts +4 -4
  347. package/src/memory/external-conversation-store.ts +66 -5
  348. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +150 -12
  349. package/src/memory/graph/conversation-graph-memory.ts +49 -21
  350. package/src/memory/graph/tools.ts +9 -40
  351. package/src/memory/indexer.ts +34 -29
  352. package/src/memory/invite-store.ts +53 -0
  353. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  354. package/src/memory/jobs/embed-concept-page.ts +20 -11
  355. package/src/memory/jobs-worker.ts +6 -1
  356. package/src/memory/llm-request-log-source-clickhouse.ts +24 -12
  357. package/src/memory/llm-request-log-source.ts +19 -52
  358. package/src/memory/llm-request-log-store.ts +92 -1
  359. package/src/memory/llm-usage-store.ts +125 -5
  360. package/src/memory/memory-retrospective-enqueue.ts +1 -20
  361. package/src/memory/memory-retrospective-job.ts +33 -6
  362. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  363. package/src/memory/message-content.ts +1 -1
  364. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  365. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  366. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  367. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  368. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  369. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  370. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  371. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  372. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  373. package/src/memory/migrations/index.ts +9 -0
  374. package/src/memory/migrations/registry.ts +16 -0
  375. package/src/memory/onboarding-events-store.ts +106 -0
  376. package/src/memory/schema/a2a.ts +15 -0
  377. package/src/memory/schema/bookmarks.ts +0 -2
  378. package/src/memory/schema/calls.ts +1 -0
  379. package/src/memory/schema/index.ts +1 -0
  380. package/src/memory/schema/inference.ts +3 -3
  381. package/src/memory/schema/infrastructure.ts +13 -0
  382. package/src/memory/turn-events-store.ts +127 -2
  383. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  384. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  385. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  386. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  387. package/src/memory/v2/__tests__/injection.test.ts +288 -11
  388. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  389. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  390. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  391. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  392. package/src/memory/v2/__tests__/router.test.ts +15 -0
  393. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  394. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  395. package/src/memory/v2/activation-store.ts +14 -16
  396. package/src/memory/v2/cli-command-content.ts +19 -0
  397. package/src/memory/v2/cli-command-store.ts +304 -0
  398. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  399. package/src/memory/v2/injection.ts +81 -26
  400. package/src/memory/v2/migration.ts +49 -19
  401. package/src/memory/v2/page-index.ts +63 -8
  402. package/src/memory/v2/prompts/router.ts +11 -8
  403. package/src/memory/v2/prompts/sweep.ts +2 -2
  404. package/src/memory/v2/qdrant.ts +135 -7
  405. package/src/memory/v2/router.ts +9 -8
  406. package/src/memory/v2/skill-store.ts +120 -35
  407. package/src/memory/v2/static-context.ts +4 -4
  408. package/src/memory/v2/types.ts +23 -0
  409. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  410. package/src/messaging/providers/a2a/deliver.ts +156 -0
  411. package/src/messaging/providers/gmail/client.ts +9 -2
  412. package/src/messaging/providers/index.ts +11 -2
  413. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  414. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  415. package/src/messaging/providers/slack/adapter.ts +43 -5
  416. package/src/messaging/providers/slack/client.ts +27 -0
  417. package/src/messaging/providers/slack/deep-link.ts +65 -0
  418. package/src/messaging/providers/slack/download.ts +104 -0
  419. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  420. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  421. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  422. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  423. package/src/messaging/providers/slack/types.ts +20 -1
  424. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  425. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  426. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  427. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  428. package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
  429. package/src/notifications/adapters/macos.ts +12 -2
  430. package/src/notifications/broadcaster.ts +29 -4
  431. package/src/notifications/conversation-pairing.ts +2 -1
  432. package/src/notifications/copy-composer.ts +17 -64
  433. package/src/notifications/decision-engine.ts +113 -45
  434. package/src/notifications/deterministic-checks.ts +96 -0
  435. package/src/notifications/emit-signal.ts +21 -1
  436. package/src/notifications/home-feed-side-effect.ts +138 -5
  437. package/src/notifications/signal.ts +3 -5
  438. package/src/notifications/types.ts +8 -0
  439. package/src/oauth/connection-resolver.ts +8 -4
  440. package/src/oauth/platform-connection.test.ts +43 -3
  441. package/src/oauth/platform-connection.ts +19 -6
  442. package/src/oauth/seed-providers.ts +10 -1
  443. package/src/permissions/checker.ts +2 -0
  444. package/src/permissions/ipc-risk-types.ts +1 -0
  445. package/src/permissions/question-prompter.test.ts +416 -0
  446. package/src/permissions/question-prompter.ts +294 -0
  447. package/src/platform/client.test.ts +1 -1
  448. package/src/platform/client.ts +1 -1
  449. package/src/plugin-api/constants.ts +26 -0
  450. package/src/plugin-api/index.ts +34 -1
  451. package/src/plugin-api/types.ts +104 -22
  452. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  453. package/src/plugins/defaults/compaction.ts +0 -4
  454. package/src/plugins/defaults/empty-response.ts +0 -2
  455. package/src/plugins/defaults/history-repair.ts +0 -2
  456. package/src/plugins/defaults/injectors.ts +74 -22
  457. package/src/plugins/defaults/llm-call.ts +0 -2
  458. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  459. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  460. package/src/plugins/defaults/persistence.ts +0 -2
  461. package/src/plugins/defaults/title-generate.ts +0 -5
  462. package/src/plugins/defaults/token-estimate.ts +0 -2
  463. package/src/plugins/defaults/tool-error.ts +0 -7
  464. package/src/plugins/defaults/tool-execute.ts +0 -2
  465. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  466. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  467. package/src/plugins/external-api.ts +104 -0
  468. package/src/plugins/external-plugin-loader.ts +187 -42
  469. package/src/plugins/feature-gate.ts +22 -0
  470. package/src/plugins/pipeline.ts +37 -0
  471. package/src/plugins/registry.ts +48 -80
  472. package/src/plugins/types.ts +40 -26
  473. package/src/plugins/user-loader.ts +21 -2
  474. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  475. package/src/proactive-artifact/job.test.ts +37 -5
  476. package/src/prompts/__tests__/system-prompt.test.ts +10 -43
  477. package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
  478. package/src/prompts/normalize-onboarding.ts +27 -0
  479. package/src/prompts/sections.ts +302 -0
  480. package/src/prompts/system-prompt.ts +63 -174
  481. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  482. package/src/prompts/templates/system-sections.ts +164 -0
  483. package/src/providers/__tests__/inference.test.ts +24 -7
  484. package/src/providers/anthropic/client.ts +28 -28
  485. package/src/providers/call-site-routing.ts +24 -6
  486. package/src/providers/connection-resolution.ts +68 -11
  487. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  488. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  489. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  490. package/src/providers/inference/adapter-factory.ts +32 -6
  491. package/src/providers/inference/auth.ts +12 -0
  492. package/src/providers/inference/backfill.ts +14 -1
  493. package/src/providers/inference/connections.ts +159 -34
  494. package/src/providers/inference/resolve-auth.ts +14 -4
  495. package/src/providers/model-catalog.ts +249 -12
  496. package/src/providers/model-intents.ts +3 -3
  497. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  498. package/src/providers/openai/chat-completions-provider.ts +169 -8
  499. package/src/providers/openrouter/client.ts +49 -4
  500. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
  501. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  502. package/src/providers/provider-availability.ts +17 -2
  503. package/src/providers/provider-catalog-visibility.ts +38 -0
  504. package/src/providers/provider-send-message.ts +27 -12
  505. package/src/providers/registry.ts +52 -15
  506. package/src/providers/retry.ts +47 -1
  507. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  508. package/src/runtime/agent-wake.ts +103 -15
  509. package/src/runtime/auth/route-policy.ts +21 -1
  510. package/src/runtime/btw-sidechain.ts +2 -0
  511. package/src/runtime/http-server.ts +7 -16
  512. package/src/runtime/http-types.ts +19 -47
  513. package/src/runtime/migrations/origin-mode.ts +1 -1
  514. package/src/runtime/pending-interactions.ts +1 -0
  515. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  516. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  517. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  518. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
  519. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  520. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  521. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  522. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  523. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  524. package/src/runtime/routes/acp-routes.ts +5 -3
  525. package/src/runtime/routes/auth-routes.ts +1 -1
  526. package/src/runtime/routes/bookmark-routes.ts +5 -3
  527. package/src/runtime/routes/btw-routes.ts +5 -1
  528. package/src/runtime/routes/channel-availability-routes.ts +126 -0
  529. package/src/runtime/routes/consolidation-routes.ts +100 -0
  530. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  531. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  532. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  533. package/src/runtime/routes/conversation-query-routes.ts +99 -35
  534. package/src/runtime/routes/conversation-routes.ts +97 -11
  535. package/src/runtime/routes/documents-routes.ts +25 -86
  536. package/src/runtime/routes/group-routes.ts +5 -0
  537. package/src/runtime/routes/inbound-conversation.ts +28 -8
  538. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  539. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  540. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  541. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  542. package/src/runtime/routes/index.ts +8 -0
  543. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  544. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  545. package/src/runtime/routes/inference-provider-connection-routes.ts +199 -22
  546. package/src/runtime/routes/integrations/a2a.ts +235 -0
  547. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  548. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  549. package/src/runtime/routes/integrations/twilio.ts +6 -13
  550. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  551. package/src/runtime/routes/notification-routes.ts +1 -1
  552. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  553. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  554. package/src/runtime/routes/question-routes.ts +259 -0
  555. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  556. package/src/runtime/routes/schedule-routes.ts +4 -7
  557. package/src/runtime/routes/subagents-routes.ts +98 -18
  558. package/src/runtime/routes/telemetry-routes.ts +27 -0
  559. package/src/runtime/routes/tts-routes.ts +27 -2
  560. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  561. package/src/runtime/routes/workspace-routes.ts +28 -0
  562. package/src/runtime/services/conversation-serializer.ts +39 -7
  563. package/src/runtime/sync/resource-sync-events.ts +93 -1
  564. package/src/schedule/schedule-store.ts +27 -2
  565. package/src/schedule/scheduler.ts +9 -1
  566. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  567. package/src/security/untrusted-content.ts +93 -8
  568. package/src/skills/catalog-files.ts +1 -1
  569. package/src/skills/catalog-install.ts +233 -116
  570. package/src/skills/clawhub.ts +70 -13
  571. package/src/skills/managed-store.ts +4 -119
  572. package/src/skills/skillssh-registry.ts +27 -48
  573. package/src/subagent/manager.ts +17 -7
  574. package/src/telemetry/types.ts +113 -1
  575. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  576. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  577. package/src/tools/apps/executors.ts +58 -7
  578. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  579. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  580. package/src/tools/browser/browser-execution.ts +15 -11
  581. package/src/tools/computer-use/definitions.ts +3 -3
  582. package/src/tools/credentials/vault.ts +1 -1
  583. package/src/tools/document/document-tool.ts +124 -1
  584. package/src/tools/filesystem/edit.ts +1 -1
  585. package/src/tools/filesystem/list.ts +1 -1
  586. package/src/tools/filesystem/read.ts +1 -1
  587. package/src/tools/filesystem/write.ts +5 -2
  588. package/src/tools/host-filesystem/transfer.ts +1 -1
  589. package/src/tools/host-terminal/host-shell.ts +1 -1
  590. package/src/tools/memory/register.ts +1 -9
  591. package/src/tools/permission-checker.ts +1 -1
  592. package/src/tools/registry.ts +17 -7
  593. package/src/tools/schedule/create.ts +2 -2
  594. package/src/tools/schema-transforms.ts +7 -2
  595. package/src/tools/side-effects.ts +1 -0
  596. package/src/tools/skills/delete-managed.ts +4 -4
  597. package/src/tools/skills/execute.ts +1 -1
  598. package/src/tools/skills/scaffold-managed.ts +3 -2
  599. package/src/tools/subagent/notify-parent.ts +1 -1
  600. package/src/tools/system/request-permission.ts +2 -2
  601. package/src/tools/terminal/safe-env.ts +60 -1
  602. package/src/tools/tool-manifest.ts +2 -0
  603. package/src/tools/types.ts +107 -21
  604. package/src/tools/ui-surface/definitions.ts +6 -5
  605. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  606. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  607. package/src/types/onboarding-context.ts +2 -0
  608. package/src/util/errors.ts +17 -0
  609. package/src/util/platform.ts +10 -0
  610. package/src/watcher/__tests__/engine.test.ts +22 -0
  611. package/src/watcher/engine.ts +6 -2
  612. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  613. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  614. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  615. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  616. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  617. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  618. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  619. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  620. package/src/workspace/migrations/registry.ts +10 -0
  621. package/src/workspace/migrations/runner.ts +39 -9
  622. package/src/workspace/migrations/types.ts +4 -0
  623. package/examples/plugins/echo/bun.lock +0 -25
  624. package/src/__tests__/context-window-manager.test.ts +0 -2481
  625. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  626. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  627. package/src/context/prompts/compact.md +0 -26
  628. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  629. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  630. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
@@ -0,0 +1,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
+ }
@@ -73,7 +73,7 @@ function installHintFor(command: string): string {
73
73
  */
74
74
  function findAgentBinary(agent: AcpAgentConfig): string | null {
75
75
  const PATH = agent.env?.PATH ?? process.env.PATH;
76
- return Bun.which(agent.command, PATH ? { PATH } : undefined);
76
+ return Bun.which(agent.command, PATH != null ? { PATH } : undefined);
77
77
  }
78
78
 
79
79
  /**
@@ -21,6 +21,11 @@ const MAX_DIMENSION = 1568;
21
21
  // Threshold below which we skip optimization — small images don't need it.
22
22
  const OPTIMIZE_THRESHOLD_BYTES = 300 * 1024; // 300 KB
23
23
 
24
+ // Anthropic rejects any single image whose source payload exceeds 5 MB,
25
+ // regardless of pixel dimensions. Cap at ~3.5 MB raw so the base64-encoded
26
+ // form (raw * 4/3) stays comfortably under 5 MB even after re-encoding.
27
+ const MAX_TRANSPORT_BYTES = Math.floor(3.5 * 1024 * 1024); // ~3.5 MB raw
28
+
24
29
  const JPEG_QUALITY = 80;
25
30
 
26
31
  // Content-addressed disk cache to avoid re-running sips on the same image.
@@ -130,10 +135,13 @@ function runSips(inputBytes: Buffer): Buffer | null {
130
135
  /**
131
136
  * Decide whether an image needs to be rescaled before sending.
132
137
  *
133
- * Anthropic rejects many-image requests when any image exceeds 2000 px on a
134
- * side, so dimensions — not file size are the authoritative gate. A sparse
135
- * screenshot can be under 300 KB while still being 3000+ px wide, which the
136
- * byte-size heuristic alone would let slip through.
138
+ * Two independent gates apply:
139
+ * 1. Pixel dimensions — Anthropic rejects many-image requests when any
140
+ * image exceeds 2000 px on a side. A sparse screenshot can be under
141
+ * 300 KB while still being 3000+ px wide.
142
+ * 2. Byte size — Anthropic rejects any image whose source payload
143
+ * exceeds 5 MB. A 1500×1500 high-color screenshot can produce a >5 MB
144
+ * payload while staying well under the dimension cap.
137
145
  *
138
146
  * Exported for unit testing.
139
147
  */
@@ -141,8 +149,8 @@ export function shouldRescaleImage(
141
149
  dims: { width: number; height: number } | null,
142
150
  byteLength: number,
143
151
  ): boolean {
152
+ if (byteLength > MAX_TRANSPORT_BYTES) return true;
144
153
  if (dims) {
145
- // Dimensions known — they are the authoritative check.
146
154
  return dims.width > MAX_DIMENSION || dims.height > MAX_DIMENSION;
147
155
  }
148
156
  // Dimensions unparseable — fall back to file size as a rough proxy.
package/src/agent/loop.ts CHANGED
@@ -67,6 +67,43 @@ export interface CheckpointInfo {
67
67
 
68
68
  export type CheckpointDecision = "continue" | "yield";
69
69
 
70
+ /**
71
+ * Why an {@link AgentLoop.run} invocation exited its `while (true)` body.
72
+ *
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".
78
+ *
79
+ * Values are stable wire/DB strings — they are written to SQLite and
80
+ * surfaced over the inspector wire format, so renaming any of them is a
81
+ * breaking change.
82
+ *
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}.
86
+ */
87
+ export type AgentLoopExitReason =
88
+ /** `if (signal?.aborted) break;` at the top of the loop. */
89
+ | "aborted_pre_call"
90
+ /** Empty assistant response after the configured retry budget. */
91
+ | "empty_response_exhausted"
92
+ /** Assistant message has no tool-use blocks (or no tool executor). */
93
+ | "no_tool_calls"
94
+ /** Signal aborted while building the user-side tool-results message. */
95
+ | "aborted_post_response"
96
+ /** Signal aborted mid-tool-execution; completed results were pushed. */
97
+ | "aborted_during_tools"
98
+ /** A tool result requested handing back to the user. */
99
+ | "yield_to_user"
100
+ /** The orchestrator's `onCheckpoint` callback returned `"yield"`. */
101
+ | "checkpoint_yield"
102
+ /** Signal aborted while the catch handler was synthesizing an error turn. */
103
+ | "aborted_via_error"
104
+ /** Catch-block fallback: an unhandled error broke the loop. */
105
+ | "error";
106
+
70
107
  export type AgentEvent =
71
108
  | { type: "text_delta"; text: string }
72
109
  | { type: "thinking_delta"; thinking: string }
@@ -126,6 +163,32 @@ export type AgentEvent =
126
163
  content?: unknown[];
127
164
  }
128
165
  | { type: "error"; error: Error }
166
+ | {
167
+ /**
168
+ * Emitted when the `llmCall` pipeline throws — i.e. the provider
169
+ * rejected the request before returning a usable response. Carries
170
+ * the loop-level raw request we attempted to send (messages, tools,
171
+ * system prompt, provider-agnostic config) plus the thrown error.
172
+ * Consumers (`handleProviderError` in the daemon handlers, the
173
+ * `onEvent` in `agent-wake`) persist these as `llm_request_logs`
174
+ * rows so failed calls are queryable in the LLM inspector instead
175
+ * of only surfacing in pino logs.
176
+ *
177
+ * `rawRequest` is the loop-level abstract shape rather than the
178
+ * provider-specific payload (which the provider builds internally
179
+ * and never returns when it throws). `actualProvider` echoes the
180
+ * `ProviderError.provider` tag when available so the persisted row
181
+ * has the same `provider` column value as a successful `usage` row.
182
+ *
183
+ * Re-thrown by the inner LLM-call try/catch after emission so the
184
+ * outer agent-loop catch still handles abort, Sentry capture, the
185
+ * existing `error` event, and the loop break.
186
+ */
187
+ type: "provider_error";
188
+ rawRequest: unknown;
189
+ error: Error;
190
+ actualProvider?: string;
191
+ }
129
192
  | {
130
193
  type: "usage";
131
194
  inputTokens: number;
@@ -144,6 +207,18 @@ export type AgentEvent =
144
207
  * for this call (e.g. legacy/stubbed code paths).
145
208
  */
146
209
  estimatedInputTokens?: number;
210
+ }
211
+ | {
212
+ /**
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;
217
+ * intermediate rows keep `agent_loop_exit_reason = NULL`, which is the
218
+ * canonical "loop kept going" signal.
219
+ */
220
+ type: "agent_loop_exit";
221
+ reason: AgentLoopExitReason;
147
222
  };
148
223
 
149
224
  const DEFAULT_CONFIG: AgentLoopConfig = {
@@ -398,8 +473,24 @@ export class AgentLoop {
398
473
  const substitutionMap = new Map<string, string>();
399
474
  let streamingPending = "";
400
475
 
476
+ // Idempotency guard for `emitExit`. Used so the throw path in the
477
+ // empty-response branch can stamp its reason ("empty_response_exhausted")
478
+ // before throwing — the catch handler that observes the rethrow will
479
+ // then attempt to stamp "error" and harmlessly no-op, preserving the
480
+ // more specific reason. Also defends against accidental future
481
+ // double-emits if a new break site is added without checking this.
482
+ let exitReasonEmitted = false;
483
+ const emitExit = async (reason: AgentLoopExitReason): Promise<void> => {
484
+ if (exitReasonEmitted) return;
485
+ exitReasonEmitted = true;
486
+ await onEvent({ type: "agent_loop_exit", reason });
487
+ };
488
+
401
489
  while (true) {
402
- if (signal?.aborted) break;
490
+ if (signal?.aborted) {
491
+ await emitExit("aborted_pre_call");
492
+ break;
493
+ }
403
494
 
404
495
  rlog.info(
405
496
  { turn: toolUseTurns, messageCount: history.length },
@@ -618,23 +709,64 @@ export class AgentLoop {
618
709
  toolUseTurns,
619
710
  );
620
711
 
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
- );
712
+ // Inner try/catch narrows error-recording scope to the provider
713
+ // call itself. The outer agent-loop catch (below) wraps the entire
714
+ // turn body (tool execution, plugin pipelines, checkpoints), so
715
+ // recording there would risk mis-attributing tool/plugin throws as
716
+ // provider rejections. On provider failure we emit `provider_error`
717
+ // with the loop-level raw request so consumers can persist it as an
718
+ // `llm_request_logs` row, then re-throw so the existing outer catch
719
+ // continues to handle abort sync, Sentry capture, the `error` event,
720
+ // and the loop break unchanged.
721
+ let response: LLMCallResult;
722
+ try {
723
+ response = await runPipeline<LLMCallArgs, LLMCallResult>(
724
+ "llmCall",
725
+ getMiddlewaresFor("llmCall"),
726
+ (args) =>
727
+ args.provider.sendMessage(
728
+ args.messages,
729
+ args.tools,
730
+ args.systemPrompt,
731
+ args.options,
732
+ ),
733
+ llmCallArgs,
734
+ turnCtx,
735
+ DEFAULT_TIMEOUTS.llmCall,
736
+ );
737
+ } catch (llmCallError) {
738
+ // Skip recording on abort — the user cancelled the request and
739
+ // there's no provider rejection worth a log row. The outer catch
740
+ // still synthesizes cancellation tool_results.
741
+ if (!signal?.aborted) {
742
+ const errInstance =
743
+ llmCallError instanceof Error
744
+ ? llmCallError
745
+ : new Error(String(llmCallError));
746
+ // Strip non-serializable / runtime-only fields from `options`
747
+ // before snapshotting. `onEvent` is a closure with side effects
748
+ // and `signal` is an AbortSignal — neither is meaningful in a
749
+ // persisted log row, and `JSON.stringify` would silently drop or
750
+ // misrepresent both.
751
+ const rawRequest = {
752
+ provider: this.provider.name,
753
+ messages: llmCallArgs.messages,
754
+ tools: llmCallArgs.tools,
755
+ systemPrompt: llmCallArgs.systemPrompt,
756
+ config: llmCallArgs.options?.config,
757
+ };
758
+ onEvent({
759
+ type: "provider_error",
760
+ rawRequest,
761
+ error: errInstance,
762
+ actualProvider:
763
+ errInstance instanceof ProviderError
764
+ ? errInstance.provider
765
+ : this.provider.name,
766
+ });
767
+ }
768
+ throw llmCallError;
769
+ }
638
770
 
639
771
  const providerDurationMs = Date.now() - providerStart;
640
772
 
@@ -785,6 +917,11 @@ export class AgentLoop {
785
917
  { turn: toolUseTurns, retries: emptyResponseRetries },
786
918
  "emptyResponse pipeline requested error surface",
787
919
  );
920
+ // Stamp the specific exit reason *before* throwing. The catch
921
+ // handler below will see the rethrown error and attempt to stamp
922
+ // "error" — guarded by `exitReasonEmitted`, that becomes a no-op
923
+ // and the more specific reason wins.
924
+ await emitExit("empty_response_exhausted");
788
925
  throw new AssistantError(
789
926
  "Model returned empty response after tool results",
790
927
  ErrorCode.INTERNAL_ERROR,
@@ -811,6 +948,7 @@ export class AgentLoop {
811
948
  await onEvent({ type: "message_complete", message: assistantMessage });
812
949
 
813
950
  if (toolUseBlocks.length === 0 || !this.toolExecutor) {
951
+ await emitExit("no_tool_calls");
814
952
  break;
815
953
  }
816
954
 
@@ -835,6 +973,7 @@ export class AgentLoop {
835
973
  }),
836
974
  );
837
975
  history.push({ role: "user", content: cancelledBlocks });
976
+ await emitExit("aborted_post_response");
838
977
  break;
839
978
  }
840
979
 
@@ -1022,6 +1161,7 @@ export class AgentLoop {
1022
1161
  // If cancelled during execution, push completed results and stop
1023
1162
  if (signal?.aborted) {
1024
1163
  history.push({ role: "user", content: resultBlocks });
1164
+ await emitExit("aborted_during_tools");
1025
1165
  break;
1026
1166
  }
1027
1167
 
@@ -1029,6 +1169,7 @@ export class AgentLoop {
1029
1169
  // surface awaiting a button click), push results and stop the loop.
1030
1170
  if (toolResults.some(({ result }) => result.yieldToUser)) {
1031
1171
  history.push({ role: "user", content: resultBlocks });
1172
+ await emitExit("yield_to_user");
1032
1173
  break;
1033
1174
  }
1034
1175
 
@@ -1095,6 +1236,7 @@ export class AgentLoop {
1095
1236
  history,
1096
1237
  });
1097
1238
  if (decision === "yield") {
1239
+ await emitExit("checkpoint_yield");
1098
1240
  break;
1099
1241
  }
1100
1242
  }
@@ -1114,6 +1256,7 @@ export class AgentLoop {
1114
1256
  );
1115
1257
  history.push({ role: "user", content: cancelledBlocks });
1116
1258
  }
1259
+ await emitExit("aborted_via_error");
1117
1260
  break;
1118
1261
  }
1119
1262
  const err = error instanceof Error ? error : new Error(String(error));
@@ -1125,6 +1268,12 @@ export class AgentLoop {
1125
1268
  Sentry.captureException(err);
1126
1269
  }
1127
1270
  onEvent({ type: "error", error: err });
1271
+ // Catch-block fallback. If the rethrow came from the
1272
+ // empty-response throw path above, `emitExit("error")` no-ops
1273
+ // because `emitExit("empty_response_exhausted")` already ran
1274
+ // before the throw. Otherwise, this is the genuine
1275
+ // unhandled-error exit.
1276
+ await emitExit("error");
1128
1277
  break;
1129
1278
  }
1130
1279
  }
@@ -318,8 +318,9 @@ export async function startVoiceTurn(
318
318
 
319
319
  // Phone voice has no interactive permission/secret UI, so apply explicit
320
320
  // per-role policies by default. Local live voice opts into the normal
321
- // client approval path instead. Side-effect double-defense is wired
322
- // below at the conversation-configure point.
321
+ // client approval path instead. Side-effect double-defense
322
+ // (forcePromptSideEffects) is wired inside the agent-loop IIFE so it
323
+ // is always paired with cleanup() in the IIFE's finally.
323
324
  const trustClass = opts.trustContext?.trustClass;
324
325
  const isGuardian = trustClass === "guardian";
325
326
  const approvalMode = opts.approvalMode ?? "phone-call";
@@ -391,34 +392,57 @@ export async function startVoiceTurn(
391
392
  }
392
393
  }
393
394
 
394
- // Non-guardian phone voice forces side-effect tools to prompt so the
395
- // auto-deny handler below reliably sees a confirmation_request. Without
396
- // this, a broad allow trust rule (e.g. wildcard bash) would let
397
- // side-effect tools execute without ever emitting an event for the
398
- // auto-deny / scoped-grant handler to intercept.
399
- conversation.forcePromptSideEffects =
400
- !isGuardian && !usesLocalInteractiveApprovals;
401
- conversation.setAssistantId(opts.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID);
402
- conversation.callSessionId = voiceSessionId;
403
- conversation.setTrustContext(opts.trustContext ?? null);
404
- conversation.setCommandIntent(null);
405
- conversation.setTurnChannelContext(turnChannelContext);
406
- conversation.setTurnInterfaceContext?.(turnInterfaceContext);
407
- conversation.setChannelCapabilities(
408
- resolveChannelCapabilities(
409
- turnChannelContext.userMessageChannel,
410
- turnInterfaceContext.userMessageInterface,
411
- ),
412
- );
413
- conversation.setVoiceCallControlPrompt(voiceCallControlPrompt);
395
+ // Hoisted so the catch below can clear partially-applied turn state
396
+ // when a setter or `persistUserMessage` throws otherwise `trustContext`,
397
+ // `callSessionId`, etc. leak into subsequent non-voice turns on the same
398
+ // conversation. The client callback is only reset when this turn actually
399
+ // installed it (tracked via `clientCallbackInstalled`); otherwise cleanup
400
+ // would detach an active sender installed by a prior turn.
401
+ let clientCallbackInstalled = false;
402
+ const cleanup = () => {
403
+ conversation.setChannelCapabilities(null);
404
+ conversation.setTrustContext(null);
405
+ conversation.setCommandIntent(null);
406
+ conversation.setAssistantId("self");
407
+ conversation.setVoiceCallControlPrompt(null);
408
+ conversation.callSessionId = undefined;
409
+ conversation.forcePromptSideEffects = false;
410
+ if (clientCallbackInstalled) {
411
+ // Reset the client callback to a no-op so the stale closure doesn't
412
+ // intercept events from future turns on the same conversation.
413
+ conversation.updateClient(() => {}, true);
414
+ }
415
+ };
414
416
 
415
417
  const requestId = crypto.randomUUID();
416
418
  const turnId = crypto.randomUUID();
417
- const messageId = await conversation.persistUserMessage(
418
- persistedContent,
419
- [],
420
- requestId,
421
- );
419
+ let messageId: string;
420
+ try {
421
+ conversation.setAssistantId(
422
+ opts.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
423
+ );
424
+ conversation.callSessionId = voiceSessionId;
425
+ conversation.setTrustContext(opts.trustContext ?? null);
426
+ conversation.setCommandIntent(null);
427
+ conversation.setTurnChannelContext(turnChannelContext);
428
+ conversation.setTurnInterfaceContext?.(turnInterfaceContext);
429
+ conversation.setChannelCapabilities(
430
+ resolveChannelCapabilities(
431
+ turnChannelContext.userMessageChannel,
432
+ turnInterfaceContext.userMessageInterface,
433
+ ),
434
+ );
435
+ conversation.setVoiceCallControlPrompt(voiceCallControlPrompt);
436
+
437
+ messageId = await conversation.persistUserMessage(
438
+ persistedContent,
439
+ [],
440
+ requestId,
441
+ );
442
+ } catch (err) {
443
+ cleanup();
444
+ throw err;
445
+ }
422
446
  try {
423
447
  opts.callbacks?.persisted_user_message_id?.(messageId);
424
448
  } catch (err) {
@@ -556,25 +580,20 @@ export async function startVoiceTurn(
556
580
  }
557
581
  broadcastMessage(msg);
558
582
  });
583
+ clientCallbackInstalled = true;
559
584
 
560
585
  // Fire-and-forget the agent loop
561
- const cleanup = () => {
562
- // Reset channel capabilities so a subsequent desktop session on the
563
- // same conversation is not incorrectly treated as a voice client.
564
- conversation.setChannelCapabilities(null);
565
- conversation.setTrustContext(null);
566
- conversation.setCommandIntent(null);
567
- conversation.setAssistantId("self");
568
- conversation.setVoiceCallControlPrompt(null);
569
- conversation.callSessionId = undefined;
570
- conversation.forcePromptSideEffects = false;
571
- // Reset the conversation's client callback to a no-op so the stale
572
- // closure doesn't intercept events from future turns on the same conversation.
573
- conversation.updateClient(() => {}, true);
574
- };
575
-
576
586
  void (async () => {
577
587
  try {
588
+ // Non-guardian phone voice forces side-effect tools to prompt so the
589
+ // auto-deny handler above reliably sees a confirmation_request. Without
590
+ // this, a broad allow trust rule (e.g. wildcard bash) would let
591
+ // side-effect tools execute without ever emitting an event for the
592
+ // auto-deny / scoped-grant handler to intercept. Set inside the
593
+ // try/finally so a failed setup before this point cannot leak the
594
+ // flag into subsequent non-voice turns on the same conversation.
595
+ conversation.forcePromptSideEffects =
596
+ !isGuardian && !usesLocalInteractiveApprovals;
578
597
  await conversation.runAgentLoop(
579
598
  persistedContent,
580
599
  messageId,
@@ -94,6 +94,15 @@ const CHANNEL_POLICIES = {
94
94
  codeRedemptionEnabled: false,
95
95
  },
96
96
  },
97
+ a2a: {
98
+ notification: {
99
+ deliveryEnabled: false,
100
+ conversationStrategy: "continue_existing_conversation",
101
+ },
102
+ invite: {
103
+ codeRedemptionEnabled: false,
104
+ },
105
+ },
97
106
  } as const satisfies Record<ChannelId, ChannelNotificationPolicy>;
98
107
 
99
108
  export type ChannelPolicies = typeof CHANNEL_POLICIES;