@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
@@ -26,7 +26,7 @@ import type { QdrantSparseVector } from "../qdrant-client.js";
26
26
  import { memorySummaries } from "../schema.js";
27
27
  import { conversations } from "../schema/conversations.js";
28
28
  import {
29
- evictCompactedTurns as evictCompactedTurnsV2,
29
+ clearEverInjected as clearV2EverInjected,
30
30
  hydrate as hydrateV2State,
31
31
  save as saveV2State,
32
32
  } from "../v2/activation-store.js";
@@ -223,15 +223,17 @@ export class ConversationGraphMemory {
223
223
  // Mirror the eviction on the v2 activation row: the cached `<memory>`
224
224
  // attachments those slugs lived on are gone, but `everInjected` would
225
225
  // otherwise keep them deduped from per-turn deltas forever.
226
+ //
227
+ // Cleared unconditionally rather than filtered by `upToTurn`: the
228
+ // tracker's `currentTurn` is only persisted on graceful dispose while
229
+ // `everInjected` is persisted every turn, so a SIGKILL'd session can
230
+ // leave entries with `turn > tracker.currentTurn` that a turn-bounded
231
+ // filter would skip.
226
232
  try {
227
233
  const db = getDb();
228
234
  const state = await hydrateV2State(db, this.conversationId);
229
235
  if (state) {
230
- await saveV2State(
231
- db,
232
- this.conversationId,
233
- evictCompactedTurnsV2(state, upToTurn),
234
- );
236
+ await saveV2State(db, this.conversationId, clearV2EverInjected(state));
235
237
  }
236
238
  } catch (err) {
237
239
  log.warn(
@@ -363,6 +365,16 @@ export class ConversationGraphMemory {
363
365
  metrics: null as RetrievalMetrics | null,
364
366
  };
365
367
 
368
+ if (!config.memory.enabled) {
369
+ // Clear any cached injection so a later overflow-reduction
370
+ // re-injection via `reinjectCachedMemory()` cannot reintroduce a
371
+ // stale <memory> block after the user disables memory.
372
+ this.lastInjectedBlock = null;
373
+ this.lastInjectedNodeIds = [];
374
+ this.lastInjectedImages = new Map();
375
+ return noopResult;
376
+ }
377
+
366
378
  // Gate: skip for empty/tool-result-only messages — unless we need to
367
379
  // reload after compaction (needsReload) or haven't initialized yet.
368
380
  const lastMessage = messages[messages.length - 1];
@@ -438,8 +450,10 @@ export class ConversationGraphMemory {
438
450
  // turns. v1's `loadContextMemory` produced these as a side effect of
439
451
  // hybrid retrieval; the v2 path skips that retrieval, so embed
440
452
  // explicitly here.
453
+ const userQueryText = rawUserText ?? userQuery ?? "";
441
454
  const userQueryEmbed = await this.computeQueryVectors(
442
- rawUserText ?? userQuery ?? "",
455
+ userQueryText,
456
+ userQueryText,
443
457
  config,
444
458
  signal,
445
459
  );
@@ -598,8 +612,16 @@ export class ConversationGraphMemory {
598
612
  if (v2.routed) {
599
613
  // Surface a per-turn query embedding so PKB hint search still runs
600
614
  // on v2 turns. v1's `retrieveForTurn` produced these as a side effect;
601
- // the v2 path skips that retrieval, so embed explicitly here.
615
+ // the v2 path skips that retrieval, so embed explicitly here. Match
616
+ // v1's split: dense embeds the combined assistant+user text (short
617
+ // referential follow-ups like "do that one" need the assistant turn
618
+ // for semantic grounding), while sparse uses the user message alone
619
+ // to keep lexical signal focused on what the user actually said.
620
+ const denseQueryText = [assistantLast, userLast]
621
+ .filter((m) => m.length > 0)
622
+ .join("\n\n");
602
623
  const perTurnEmbed = await this.computeQueryVectors(
624
+ denseQueryText,
603
625
  userLast,
604
626
  config,
605
627
  signal,
@@ -697,24 +719,30 @@ export class ConversationGraphMemory {
697
719
  * static fallback rather than blocking the turn.
698
720
  */
699
721
  private async computeQueryVectors(
700
- text: string,
722
+ denseText: string,
723
+ sparseText: string,
701
724
  config: AssistantConfig,
702
725
  signal: AbortSignal,
703
726
  ): Promise<{ dense?: number[]; sparse?: QdrantSparseVector }> {
704
- const trimmed = text.trim();
705
- if (trimmed.length === 0) return {};
727
+ const trimmedDense = denseText.trim();
728
+ const trimmedSparse = sparseText.trim();
706
729
  let dense: number[] | undefined;
707
- try {
708
- const result = await embedWithRetry(config, [trimmed], { signal });
709
- dense = result.vectors[0];
710
- } catch (err) {
711
- log.warn(
712
- { err: err instanceof Error ? err.message : String(err) },
713
- "Failed to embed query for PKB hints on v2 path",
714
- );
730
+ if (trimmedDense.length > 0) {
731
+ try {
732
+ const result = await embedWithRetry(config, [trimmedDense], { signal });
733
+ dense = result.vectors[0];
734
+ } catch (err) {
735
+ log.warn(
736
+ { err: err instanceof Error ? err.message : String(err) },
737
+ "Failed to embed query for PKB hints on v2 path",
738
+ );
739
+ }
740
+ }
741
+ let sparse: QdrantSparseVector | undefined;
742
+ if (trimmedSparse.length > 0) {
743
+ const sparseRaw = generateSparseEmbedding(trimmedSparse);
744
+ sparse = sparseRaw.indices.length > 0 ? sparseRaw : undefined;
715
745
  }
716
- const sparseRaw = generateSparseEmbedding(trimmed);
717
- const sparse = sparseRaw.indices.length > 0 ? sparseRaw : undefined;
718
746
  return { dense, sparse };
719
747
  }
720
748
 
@@ -2,8 +2,6 @@
2
2
  // Memory Tool definitions for agentic recall and remember.
3
3
  // ---------------------------------------------------------------------------
4
4
 
5
- import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
6
- import type { AssistantConfig } from "../../config/types.js";
7
5
  import type { ToolDefinition } from "../../providers/types.js";
8
6
  import {
9
7
  ALL_RECALL_SOURCES,
@@ -20,7 +18,7 @@ const RECALL_DEPTHS = ["fast", "standard", "deep"] as const;
20
18
  export const graphRecallDefinition: ToolDefinition = {
21
19
  name: "recall",
22
20
  description:
23
- 'Search local information the moment you feel uncertain. Use recall for memory, past conversations, and workspace files — before you guess, before you ask, before you hedge. Auto-injection is incomplete by design; it surfaces patterns, not the specifics you need to answer well. If you catch yourself reaching for "I think", "I believe", "if I remember", "didn\'t we", "last time" — that\'s the signal. Recall. If the user references someone, a place, a decision, a document, or prior work you should be able to find locally — recall. Call it multiple times per conversation if the turn warrants it. Be specific in your query for best results.',
21
+ 'Search local information the moment you feel uncertain. Use recall for memory, past conversations, and workspace files — before you guess, before you ask, before you hedge. Auto-injection is incomplete by design; it surfaces patterns, not the specifics you need to answer well. If you catch yourself reaching for "I think", "I believe", "if I remember", "didn\'t we", "last time" — that\'s the signal. Recall. If a turn references someone, a place, a decision, a document, or prior work you should be able to find locally — recall. Call it multiple times per conversation if the turn warrants it. Be specific in your query for best results.',
24
22
  input_schema: {
25
23
  type: "object",
26
24
  properties: {
@@ -56,36 +54,13 @@ export const graphRecallDefinition: ToolDefinition = {
56
54
  };
57
55
 
58
56
  /**
59
- * Default (high-pressure) `remember` tool description. Used when the
60
- * `memory-retrospective` feature flag is OFF. The volume-shaming language
61
- * ("almost every turn", "most frequently used tool") drives aggressive
62
- * in-conversation capture for users who don't have the retrospective
63
- * backstop enabled.
57
+ * `remember` tool description. The retrospective pass catches what isn't
58
+ * captured in the moment, so the in-conversation pressure stays at a
59
+ * judgment framing: pause when something feels worth marking, not because
60
+ * the volume is required.
64
61
  */
65
- const REMEMBER_DESCRIPTION_DEFAULT =
66
- "Remember anything concrete: facts, preferences, corrections, plans, felt moments, names, dates, decisions. Default to remembering. Never wait until end of conversation. Corrections are highest priority — call remember the same turn the correction lands. **CRITICAL:** You should be calling remember on almost every turn. This should be your most frequently used tool.";
67
-
68
- /**
69
- * Relaxed `remember` tool description used when `memory-retrospective` is
70
- * ON. The retrospective pass catches what isn't captured in the moment, so
71
- * the in-conversation pressure eases to a judgment framing: pause when
72
- * something feels worth marking, not because the volume is required.
73
- */
74
- const REMEMBER_DESCRIPTION_RELAXED =
75
- "Remember anything concrete the user shared: corrections, plans, decisions, felt moments, names, dates, commitments, preferences. Corrections are the highest priority — call `remember` the same turn the correction lands. You don't have to call this on every turn; a retrospective pass reviews the conversation after each message-count / time interval and saves what you didn't capture. Use judgment: pause and remember when something feels worth marking, not because the volume is required.";
76
-
77
- /**
78
- * Return the description that should appear in the `remember` tool
79
- * registration for the current config. The variant is selected by the
80
- * `memory-retrospective` assistant feature flag. Exposed as a function so
81
- * the tool registrar can compute the value at registration time without
82
- * importing config layers into the static definition.
83
- */
84
- export function getRememberDescription(config: AssistantConfig): string {
85
- return isAssistantFeatureFlagEnabled("memory-retrospective", config)
86
- ? REMEMBER_DESCRIPTION_RELAXED
87
- : REMEMBER_DESCRIPTION_DEFAULT;
88
- }
62
+ const REMEMBER_DESCRIPTION =
63
+ "Remember anything concrete shared in conversation: corrections, plans, decisions, felt moments, names, dates, commitments, preferences. Corrections are the highest priority — call `remember` the same turn the correction lands. You don't have to call this on every turn; a retrospective pass reviews the conversation after each message-count / time interval and saves what you didn't capture. Use judgment: pause and remember when something feels worth marking, not because the volume is required.";
89
64
 
90
65
  /**
91
66
  * Save a fact to the assistant's knowledge base. The fact is appended to
@@ -94,16 +69,10 @@ export function getRememberDescription(config: AssistantConfig): string {
94
69
  * writes go under `memory/`; otherwise they go under `pkb/`. Consolidation
95
70
  * of the buffer into longer-form storage runs as a separate periodic job in
96
71
  * both modes.
97
- *
98
- * The static `description` field carries the default (high-pressure) text
99
- * so any direct importer that doesn't go through `getRememberDescription`
100
- * still gets a valid tool definition. The registered `RememberTool` in
101
- * `tools/memory/register.ts` overrides this at registration time with the
102
- * flag-aware variant.
103
72
  */
104
73
  export const graphRememberDefinition: ToolDefinition = {
105
74
  name: "remember",
106
- description: REMEMBER_DESCRIPTION_DEFAULT,
75
+ description: REMEMBER_DESCRIPTION,
107
76
  input_schema: {
108
77
  type: "object",
109
78
  properties: {
@@ -115,7 +84,7 @@ export const graphRememberDefinition: ToolDefinition = {
115
84
  finish_turn: {
116
85
  type: "boolean",
117
86
  description:
118
- "When you have nothing else to say and want to hand control back to the user you MUST set this to true. When true, your turn ends after this tool call. It's critical that you do this in order to avoid unnecessary LLM calls.",
87
+ "When you have nothing else to say and want to yield the turn you MUST set this to true. When true, your turn ends after this tool call. It's critical that you do this in order to avoid unnecessary LLM calls.",
119
88
  },
120
89
  },
121
90
  required: ["content"],
@@ -169,32 +169,30 @@ export async function indexMessageNow(
169
169
  const batchSize = config.extraction.batchSize ?? 10;
170
170
  const idleTimeoutMs = config.extraction.idleTimeoutMs ?? 300_000;
171
171
 
172
+ // Reading config here is best-effort: when it fails we treat v2 as
173
+ // inactive (failing-open to v1) so a config error never silently
174
+ // drops the extraction or summarization paths.
175
+ let triggerConfig: ReturnType<typeof getConfig> | null = null;
176
+ try {
177
+ triggerConfig = getConfig();
178
+ } catch (err) {
179
+ log.debug(
180
+ { err, conversationId: input.conversationId },
181
+ "Skipping feature-gated extraction triggers: failed to load config",
182
+ );
183
+ }
184
+
185
+ const v2Config =
186
+ triggerConfig != null && triggerConfig.memory.v2.enabled
187
+ ? triggerConfig
188
+ : null;
189
+
172
190
  // Recursion guard: skip graph extraction + auto-analysis enqueues
173
191
  // when the source conversation is itself an auto-analysis
174
192
  // conversation. The analysis agent writes memory directly via tools,
175
193
  // so extracting from its reflective musings would double-count and
176
194
  // analyzing its own output would loop indefinitely.
177
- // Summaries still run — they feed the graph retrieval pipeline and
178
- // are not recursion-prone.
179
195
  if (!isAutoAnalysisSource) {
180
- // Reading config here is best-effort: when it fails we treat v2 as
181
- // inactive (failing-open to v1) so a config error never silently
182
- // drops both extraction paths.
183
- let triggerConfig: ReturnType<typeof getConfig> | null = null;
184
- try {
185
- triggerConfig = getConfig();
186
- } catch (err) {
187
- log.debug(
188
- { err, conversationId: input.conversationId },
189
- "Skipping feature-gated extraction triggers: failed to load config",
190
- );
191
- }
192
-
193
- const v2Config =
194
- triggerConfig != null && triggerConfig.memory.v2.enabled
195
- ? triggerConfig
196
- : null;
197
-
198
196
  // ── Graph extraction (v1) ───────────────────────────────────────
199
197
  // Suppressed when v2 is active — v2 reads memory from buffer.md
200
198
  // and concept pages, so the v1 graph would be stale data nobody
@@ -302,15 +300,22 @@ export async function indexMessageNow(
302
300
  }
303
301
  }
304
302
 
305
- // ── Conversation summarization (independent of extraction) ────────
306
- // Summaries feed the graph retrieval pipeline via fetchRecentSummaries().
307
- // Debounced on the same idle timeoutno threshold trigger needed since
308
- // summaries compress the whole conversation, not incremental batches.
309
- upsertDebouncedJob(
310
- "build_conversation_summary",
311
- { conversationId: input.conversationId },
312
- Date.now() + idleTimeoutMs,
313
- );
303
+ // ── Conversation summarization (v1) ───────────────────────────────
304
+ // Summaries feed the v1 graph retrieval pipeline (fetchRecentSummaries,
305
+ // semantic search). Suppressed when v2 is active v2 readers (concept
306
+ // pages, activation pipeline) do not consume `memorySummaries`, so the
307
+ // summarization LLM call would produce rows nothing reads. Stale rows
308
+ // from before v2 was enabled are short-circuited at dispatch in
309
+ // jobs-worker.ts. Debounced on the same idle timeout — no threshold
310
+ // trigger needed since summaries compress the whole conversation, not
311
+ // incremental batches.
312
+ if (v2Config == null) {
313
+ upsertDebouncedJob(
314
+ "build_conversation_summary",
315
+ { conversationId: input.conversationId },
316
+ Date.now() + idleTimeoutMs,
317
+ );
318
+ }
314
319
  }
315
320
 
316
321
  if (skippedShortSegments > 0) {
@@ -363,6 +363,59 @@ export function findActiveVoiceInvites(params: {
363
363
  return rows.map(rowToInvite);
364
364
  }
365
365
 
366
+ // ---------------------------------------------------------------------------
367
+ // claimA2AInvite — validate + consume an A2A invite token
368
+ // ---------------------------------------------------------------------------
369
+
370
+ export function claimA2AInvite(params: {
371
+ tokenHash: string;
372
+ redeemedByExternalUserId: string;
373
+ }): { claimed: boolean; invite: IngressInvite | null; error?: string } {
374
+ const invite = findByTokenHash(params.tokenHash);
375
+
376
+ if (!invite) {
377
+ return { claimed: false, invite: null, error: "not_found" };
378
+ }
379
+
380
+ if (invite.sourceChannel !== "a2a") {
381
+ return { claimed: false, invite, error: "wrong_channel" };
382
+ }
383
+
384
+ // Idempotency: if already redeemed by the same acceptor, return success
385
+ if (invite.status === "redeemed") {
386
+ if (invite.redeemedByExternalUserId === params.redeemedByExternalUserId) {
387
+ return { claimed: true, invite };
388
+ }
389
+ return { claimed: false, invite, error: "already_redeemed_by_other" };
390
+ }
391
+
392
+ if (invite.status !== "active") {
393
+ return { claimed: false, invite, error: "not_found" };
394
+ }
395
+
396
+ if (Date.now() >= invite.expiresAt) {
397
+ markInviteExpired(invite.id);
398
+ return { claimed: false, invite, error: "expired" };
399
+ }
400
+
401
+ if (invite.useCount >= invite.maxUses) {
402
+ return { claimed: false, invite, error: "already_redeemed" };
403
+ }
404
+
405
+ const recorded = recordInviteUse({
406
+ inviteId: invite.id,
407
+ externalUserId: params.redeemedByExternalUserId,
408
+ });
409
+
410
+ if (!recorded) {
411
+ return { claimed: false, invite, error: "not_found" };
412
+ }
413
+
414
+ // Re-read to get updated state
415
+ const updated = findByTokenHash(params.tokenHash);
416
+ return { claimed: true, invite: updated };
417
+ }
418
+
366
419
  // ---------------------------------------------------------------------------
367
420
  // findByInviteCodeHash
368
421
  // ---------------------------------------------------------------------------
@@ -151,6 +151,8 @@ type MemoryJob = ReturnType<MemoryJobMod["claimMemoryJobs"]>[number];
151
151
  const { embedConceptPageJob, enqueueEmbedConceptPageJob } =
152
152
  await import("../embed-concept-page.js");
153
153
  const { writePage } = await import("../../v2/page-store.js");
154
+ const { _resetQdrantBreaker, isQdrantBreakerOpen, withQdrantBreaker } =
155
+ await import("../../qdrant-circuit-breaker.js");
154
156
 
155
157
  // Use a tiny vectorSize so the cache-dim check matches our stub vector.
156
158
  const TEST_CONFIG = {
@@ -186,6 +188,7 @@ beforeEach(() => {
186
188
  embedWithBackendCalls.length = 0;
187
189
  upsertCalls.length = 0;
188
190
  deleteCalls.length = 0;
191
+ _resetQdrantBreaker();
189
192
  });
190
193
 
191
194
  afterEach(() => {
@@ -436,6 +439,76 @@ describe("embedConceptPageJob — defensive", () => {
436
439
  });
437
440
  });
438
441
 
442
+ describe("embedConceptPageJob — Qdrant breaker integration", () => {
443
+ test("half-open probe success closes the breaker so embed catch-up unthrottles", async () => {
444
+ // Trip the breaker by recording 5 consecutive Qdrant failures. Without
445
+ // this fix, `embed_concept_page` bypassed the breaker entirely — winning
446
+ // the half-open probe slot did not transition state back to closed and
447
+ // the embed lane stayed throttled at one job per tick indefinitely.
448
+ for (let i = 0; i < 5; i++) {
449
+ try {
450
+ await withQdrantBreaker(async () => {
451
+ throw new Error("simulated qdrant failure");
452
+ });
453
+ } catch {
454
+ // expected
455
+ }
456
+ }
457
+ expect(isQdrantBreakerOpen()).toBe(true);
458
+
459
+ // Advance time past the 30s cooldown so the next breaker call transitions
460
+ // open → half-open and allows the probe through.
461
+ const originalNow = Date.now;
462
+ Date.now = () => originalNow() + 60_000;
463
+ try {
464
+ await writePage(tmpWorkspace, {
465
+ slug: "probe-success",
466
+ frontmatter: { edges: [], ref_files: [], ref_urls: [] },
467
+ body: "Probe body.\n",
468
+ });
469
+
470
+ await embedConceptPageJob(
471
+ makeJob({ slug: "probe-success" }),
472
+ TEST_CONFIG,
473
+ );
474
+ } finally {
475
+ Date.now = originalNow;
476
+ }
477
+
478
+ expect(upsertCalls).toHaveLength(1);
479
+ // Probe succeeded → breaker should now be closed (not open, not
480
+ // half-open), restoring full embed-lane concurrency.
481
+ expect(isQdrantBreakerOpen()).toBe(false);
482
+ });
483
+
484
+ test("half-open probe success on the delete path also closes the breaker", async () => {
485
+ // Same flow as above but exercising the missing-page branch — both v2
486
+ // Qdrant calls (`upsert` and `delete`) must close the breaker on success.
487
+ for (let i = 0; i < 5; i++) {
488
+ try {
489
+ await withQdrantBreaker(async () => {
490
+ throw new Error("simulated qdrant failure");
491
+ });
492
+ } catch {
493
+ // expected
494
+ }
495
+ }
496
+ expect(isQdrantBreakerOpen()).toBe(true);
497
+
498
+ const originalNow = Date.now;
499
+ Date.now = () => originalNow() + 60_000;
500
+ try {
501
+ // No `writePage` — the handler takes the delete branch.
502
+ await embedConceptPageJob(makeJob({ slug: "missing-slug" }), TEST_CONFIG);
503
+ } finally {
504
+ Date.now = originalNow;
505
+ }
506
+
507
+ expect(deleteCalls).toEqual(["missing-slug"]);
508
+ expect(isQdrantBreakerOpen()).toBe(false);
509
+ });
510
+ });
511
+
439
512
  describe("enqueueEmbedConceptPageJob", () => {
440
513
  test("enqueues a pending embed_concept_page job with the slug payload", () => {
441
514
  const id = enqueueEmbedConceptPageJob({ slug: "alice-prefers-vs-code" });
@@ -34,6 +34,7 @@ import {
34
34
  import { embeddingInputContentHash } from "../embedding-types.js";
35
35
  import { asString, blobToVector, vectorToBlob } from "../job-utils.js";
36
36
  import { enqueueMemoryJob, type MemoryJob } from "../jobs-store.js";
37
+ import { withQdrantBreaker } from "../qdrant-circuit-breaker.js";
37
38
  import { memoryEmbeddings } from "../schema.js";
38
39
  import { readPage } from "../v2/page-store.js";
39
40
  import {
@@ -81,7 +82,10 @@ export async function embedConceptPageJob(
81
82
  if (!page) {
82
83
  // Page was deleted out from under us — clean up the prior embedding so
83
84
  // retrieval no longer surfaces a slug whose disk-side prose is gone.
84
- await deleteConceptPageEmbedding(slug);
85
+ // Route through the Qdrant breaker so success on the half-open probe
86
+ // slot transitions the breaker back to closed and unthrottles embed
87
+ // catch-up.
88
+ await withQdrantBreaker(() => deleteConceptPageEmbedding(slug));
85
89
  return;
86
90
  }
87
91
 
@@ -280,16 +284,21 @@ export async function embedConceptPageJob(
280
284
  ? await applyCorrectionIfCalibrated(summaryDense, writeProvider, writeModel)
281
285
  : undefined;
282
286
 
283
- await upsertConceptPageEmbedding({
284
- slug,
285
- dense: correctedDense,
286
- sparse,
287
- summary:
288
- correctedSummaryDense && summarySparse
289
- ? { dense: correctedSummaryDense, sparse: summarySparse }
290
- : undefined,
291
- updatedAt: now,
292
- });
287
+ // Route through the Qdrant breaker so a probe-slot success transitions the
288
+ // breaker back to closed; without this wrapper the embed lane stays
289
+ // throttled at one job per tick indefinitely after a half-open success.
290
+ await withQdrantBreaker(() =>
291
+ upsertConceptPageEmbedding({
292
+ slug,
293
+ dense: correctedDense,
294
+ sparse,
295
+ summary:
296
+ correctedSummaryDense && summarySparse
297
+ ? { dense: correctedSummaryDense, sparse: summarySparse }
298
+ : undefined,
299
+ updatedAt: now,
300
+ }),
301
+ );
293
302
  }
294
303
 
295
304
  /** SQLite cache row shape returned by `readEmbeddingCache`. */
@@ -95,7 +95,6 @@ const V1_QDRANT_JOB_TYPES = new Set<MemoryJobType>([
95
95
  "embed_attachment",
96
96
  "embed_graph_node",
97
97
  "embed_pkb_file",
98
- "graph_trigger_embed",
99
98
  "rebuild_index",
100
99
  "delete_qdrant_vectors",
101
100
  ]);
@@ -525,6 +524,12 @@ async function processJob(
525
524
  pruneOldTraceEventsJob(job, config);
526
525
  return;
527
526
  case "build_conversation_summary":
527
+ // Stale rows enqueued before v2 was enabled must not consume the
528
+ // `conversationSummarization` LLM budget — v2 readers do not consume
529
+ // `memorySummaries`, mirroring the `graph_extract` gate below.
530
+ if (config.memory.v2.enabled) {
531
+ return;
532
+ }
528
533
  await buildConversationSummaryJob(job, config);
529
534
  return;
530
535
  case "backfill":
@@ -57,6 +57,7 @@ interface ClickHouseRow {
57
57
  request_payload: string;
58
58
  response_payload: string;
59
59
  created_at: string;
60
+ agent_loop_exit_reason: string;
60
61
  }
61
62
 
62
63
  /** Injectable fetch override for tests. Defaults to globalThis.fetch. */
@@ -123,7 +124,8 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
123
124
  provider,
124
125
  request_payload,
125
126
  response_payload,
126
- toUnixTimestamp64Milli(created_at) AS created_at
127
+ toUnixTimestamp64Milli(created_at) AS created_at,
128
+ agent_loop_exit_reason
127
129
  FROM ${this.tableRef()}
128
130
  WHERE assistant_id = {assistant_id:String}
129
131
  AND id = {log_id:String}
@@ -172,14 +174,21 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
172
174
  private async selectByMessageIds(ids: string[]): Promise<LogRow[]> {
173
175
  if (ids.length === 0) return [];
174
176
  const aid = await this.assistantId();
175
- // ClickHouse Array(String) URL encoding is fiddly; the message IDs are
176
- // server-generated UUIDs (or safe internal strings), so inline the
177
- // literal after escaping single quotes. No SQL-injection surfacethe
178
- // values originate from our own SQLite messages table.
179
- const idLiteral =
180
- "[" +
181
- ids.map((id) => `'${id.replace(/'/g, "''")}'`).join(",") +
182
- "]";
177
+ // Bind each id as its own {id_N:String} placeholder. The IDs ultimately
178
+ // come from a caller-supplied path parameter `getAssistantMessageIdsInTurn`
179
+ // passes the input straight through when the message lookup misses so
180
+ // inline literal building (even with quote-doubling) is unsafe: ClickHouse
181
+ // honors `\'` as an escaped quote inside string literals, letting a
182
+ // backslash-suffixed id break out of the IN clause and bypass the
183
+ // `assistant_id` scope filter. Type-bound parameters carry value, not
184
+ // syntax, regardless of content.
185
+ const params: Record<string, string> = { assistant_id: aid };
186
+ const placeholders: string[] = [];
187
+ for (let i = 0; i < ids.length; i++) {
188
+ const key = `id_${i}`;
189
+ params[key] = ids[i]!;
190
+ placeholders.push(`{${key}:String}`);
191
+ }
183
192
  const sql = `SELECT
184
193
  id,
185
194
  conversation_id,
@@ -187,14 +196,15 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
187
196
  provider,
188
197
  request_payload,
189
198
  response_payload,
190
- toUnixTimestamp64Milli(created_at) AS created_at
199
+ toUnixTimestamp64Milli(created_at) AS created_at,
200
+ agent_loop_exit_reason
191
201
  FROM ${this.tableRef()}
192
202
  WHERE assistant_id = {assistant_id:String}
193
- AND message_id IN ${idLiteral}
203
+ AND message_id IN (${placeholders.join(",")})
194
204
  ORDER BY created_at ASC, id ASC
195
205
  LIMIT 1 BY id
196
206
  FORMAT JSONEachRow`;
197
- const rows = await this.exec(sql, { assistant_id: aid });
207
+ const rows = await this.exec(sql, params);
198
208
  return rows.map((r) => this.toLogRow(r));
199
209
  }
200
210
 
@@ -276,6 +286,8 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
276
286
  requestPayload: row.request_payload,
277
287
  responsePayload: row.response_payload,
278
288
  createdAt: Number(row.created_at),
289
+ agentLoopExitReason:
290
+ row.agent_loop_exit_reason === "" ? null : row.agent_loop_exit_reason,
279
291
  };
280
292
  }
281
293