@vellumai/assistant 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (503) hide show
  1. package/ARCHITECTURE.md +11 -12
  2. package/docker-entrypoint.sh +13 -2
  3. package/docker-init-apt-root.sh +79 -6
  4. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  5. package/openapi.yaml +945 -36
  6. package/package.json +1 -1
  7. package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
  8. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  9. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +272 -0
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  14. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  15. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  16. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  17. package/src/__tests__/compaction-events.test.ts +1 -1
  18. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  19. package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
  20. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  22. package/src/__tests__/config-watcher.test.ts +1 -1
  23. package/src/__tests__/context-token-estimator.test.ts +112 -57
  24. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  27. package/src/__tests__/conversation-agent-loop.test.ts +77 -3
  28. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  29. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  30. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  32. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  33. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  34. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  35. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  36. package/src/__tests__/conversation-pairing.test.ts +2 -2
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  39. package/src/__tests__/conversation-queue.test.ts +1 -1
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
  41. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  42. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  43. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  44. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  45. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  46. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  47. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  48. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  49. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  50. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  51. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  52. package/src/__tests__/date-context.test.ts +45 -0
  53. package/src/__tests__/dm-backfill.test.ts +64 -0
  54. package/src/__tests__/dm-persistence.test.ts +33 -0
  55. package/src/__tests__/document-find-replace.test.ts +501 -0
  56. package/src/__tests__/external-plugin-loader.test.ts +91 -19
  57. package/src/__tests__/first-greeting.test.ts +23 -2
  58. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  59. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  60. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  61. package/src/__tests__/heartbeat-service.test.ts +24 -164
  62. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  63. package/src/__tests__/host-app-control-proxy.test.ts +241 -0
  64. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  66. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  67. package/src/__tests__/host-file-proxy.test.ts +8 -1
  68. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  69. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  70. package/src/__tests__/identity-routes.test.ts +57 -0
  71. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  72. package/src/__tests__/injector-background-turn.test.ts +153 -0
  73. package/src/__tests__/injector-chain.test.ts +7 -0
  74. package/src/__tests__/injector-document-comments.test.ts +378 -0
  75. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  76. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
  77. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  78. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  79. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  80. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  81. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  82. package/src/__tests__/llm-catalog-parity.test.ts +3 -0
  83. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  84. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  85. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  86. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
  87. package/src/__tests__/llm-resolver.test.ts +340 -3
  88. package/src/__tests__/log-export-routes.test.ts +99 -2
  89. package/src/__tests__/managed-profile-guard.test.ts +10 -0
  90. package/src/__tests__/message-queue-steer.test.ts +114 -0
  91. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  92. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  93. package/src/__tests__/notification-deep-link.test.ts +15 -0
  94. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  95. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  96. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  97. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  98. package/src/__tests__/openai-provider.test.ts +323 -3
  99. package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
  100. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  101. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  102. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  103. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  104. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  105. package/src/__tests__/platform-proxy-context.test.ts +6 -1
  106. package/src/__tests__/platform.test.ts +0 -3
  107. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  108. package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
  109. package/src/__tests__/plugin-types.test.ts +2 -2
  110. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  111. package/src/__tests__/process-message-display-content.test.ts +21 -16
  112. package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
  113. package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  115. package/src/__tests__/server-history-render.test.ts +83 -4
  116. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  117. package/src/__tests__/system-prompt.test.ts +57 -101
  118. package/src/__tests__/terminal-tools.test.ts +11 -1
  119. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  120. package/src/__tests__/thread-backfill.test.ts +370 -22
  121. package/src/__tests__/tool-executor.test.ts +90 -1
  122. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  123. package/src/__tests__/twilio-routes.test.ts +1 -1
  124. package/src/__tests__/web-fetch.test.ts +2 -2
  125. package/src/__tests__/workspace-git-service.test.ts +88 -5
  126. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  127. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  128. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  129. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  130. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  131. package/src/a2a/__tests__/task-store.test.ts +246 -0
  132. package/src/a2a/agent-card.ts +58 -0
  133. package/src/a2a/feature-gate.ts +8 -0
  134. package/src/a2a/protocol-constants.ts +21 -0
  135. package/src/a2a/protocol-errors.ts +50 -0
  136. package/src/a2a/protocol-types.ts +162 -0
  137. package/src/a2a/task-store.ts +168 -0
  138. package/src/agent/attachments.ts +1 -0
  139. package/src/agent/loop.ts +208 -22
  140. package/src/background-wake/next-wake.test.ts +289 -0
  141. package/src/background-wake/next-wake.ts +172 -0
  142. package/src/browser/operations.ts +15 -0
  143. package/src/channels/config.ts +9 -0
  144. package/src/channels/types.ts +14 -0
  145. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  146. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  147. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  148. package/src/cli/commands/__tests__/schedules.test.ts +469 -0
  149. package/src/cli/commands/conversations.ts +128 -1
  150. package/src/cli/commands/inference-providers.ts +147 -1
  151. package/src/cli/commands/memory-v2.ts +308 -0
  152. package/src/cli/commands/notifications.ts +89 -37
  153. package/src/cli/commands/plugins.ts +67 -0
  154. package/src/cli/commands/schedules.ts +297 -5
  155. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  156. package/src/cli/lib/install-from-github.ts +8 -9
  157. package/src/cli/lib/search-plugins.ts +163 -0
  158. package/src/cli/program.ts +14 -0
  159. package/src/cli/utils/conversation-id.ts +17 -5
  160. package/src/config/assistant-feature-flags.ts +24 -54
  161. package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
  162. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  163. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  164. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  165. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  166. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  167. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  168. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  169. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  170. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  171. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  172. package/src/config/bundled-tool-registry.ts +22 -12
  173. package/src/config/call-site-defaults.ts +124 -0
  174. package/src/config/feature-flag-registry.json +111 -23
  175. package/src/config/llm-resolver.ts +66 -1
  176. package/src/config/schema.ts +2 -0
  177. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
  178. package/src/config/schemas/call-site-catalog.ts +21 -0
  179. package/src/config/schemas/channels.ts +9 -0
  180. package/src/config/schemas/conversations.ts +10 -0
  181. package/src/config/schemas/heartbeat.ts +14 -0
  182. package/src/config/schemas/llm.ts +4 -3
  183. package/src/config/schemas/memory-retrospective.ts +1 -1
  184. package/src/config/schemas/memory-v2.ts +51 -4
  185. package/src/config/schemas/memory.ts +3 -1
  186. package/src/config/seed-inference-profiles.ts +99 -29
  187. package/src/context/compactor.ts +80 -13
  188. package/src/context/token-estimator.ts +72 -31
  189. package/src/context/window-manager.ts +25 -0
  190. package/src/credential-health/credential-health-service.ts +34 -19
  191. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
  192. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  193. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  194. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  195. package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
  196. package/src/daemon/conversation-agent-loop.ts +252 -56
  197. package/src/daemon/conversation-lifecycle.ts +142 -116
  198. package/src/daemon/conversation-messaging.ts +3 -0
  199. package/src/daemon/conversation-process.ts +273 -0
  200. package/src/daemon/conversation-queue-manager.ts +14 -0
  201. package/src/daemon/conversation-runtime-assembly.ts +144 -75
  202. package/src/daemon/conversation-slash.ts +37 -5
  203. package/src/daemon/conversation-surfaces.ts +45 -2
  204. package/src/daemon/conversation-tool-setup.ts +7 -0
  205. package/src/daemon/conversation.ts +42 -12
  206. package/src/daemon/date-context.ts +40 -0
  207. package/src/daemon/first-greeting.ts +10 -0
  208. package/src/daemon/guardian-action-generators.ts +1 -125
  209. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  210. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  211. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  212. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  213. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  214. package/src/daemon/handlers/config-a2a.ts +449 -0
  215. package/src/daemon/handlers/config-model.test.ts +1 -0
  216. package/src/daemon/handlers/conversations.ts +80 -0
  217. package/src/daemon/handlers/shared.ts +92 -29
  218. package/src/daemon/host-app-control-proxy.ts +69 -18
  219. package/src/daemon/host-bash-proxy.ts +1 -1
  220. package/src/daemon/host-cu-proxy.ts +1 -1
  221. package/src/daemon/host-file-proxy.ts +1 -1
  222. package/src/daemon/host-proxy-preactivation.ts +85 -18
  223. package/src/daemon/host-transfer-proxy.ts +1 -1
  224. package/src/daemon/lifecycle.ts +67 -65
  225. package/src/daemon/memory-v2-startup.ts +49 -13
  226. package/src/daemon/message-protocol.ts +4 -0
  227. package/src/daemon/message-types/conversations.ts +8 -0
  228. package/src/daemon/message-types/document-comments.ts +50 -0
  229. package/src/daemon/message-types/messages.ts +68 -1
  230. package/src/daemon/message-types/notifications.ts +21 -0
  231. package/src/daemon/message-types/surfaces.ts +3 -1
  232. package/src/daemon/message-types/web-activity.ts +57 -0
  233. package/src/daemon/pkb-reminder-builder.test.ts +10 -53
  234. package/src/daemon/pkb-reminder-builder.ts +4 -19
  235. package/src/daemon/plugin-source-watcher.ts +135 -3
  236. package/src/daemon/process-message.ts +72 -12
  237. package/src/daemon/query-complexity-router.ts +75 -0
  238. package/src/daemon/skill-memory-refresh.ts +5 -1
  239. package/src/daemon/trust-context.ts +6 -0
  240. package/src/daemon/wake-target-adapter.ts +2 -0
  241. package/src/documents/document-comments-store.test.ts +338 -0
  242. package/src/documents/document-comments-store.ts +237 -0
  243. package/src/documents/document-store.ts +202 -0
  244. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  245. package/src/export/transcript-formatter.ts +54 -20
  246. package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
  247. package/src/heartbeat/heartbeat-service.ts +35 -191
  248. package/src/home/__tests__/feed-types.test.ts +40 -0
  249. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  250. package/src/home/feed-types.ts +20 -3
  251. package/src/home/home-content-refresh.ts +52 -0
  252. package/src/home/home-greeting-cache.ts +69 -0
  253. package/src/home/home-greeting.ts +94 -0
  254. package/src/home/suggested-prompts.ts +177 -9
  255. package/src/ipc/cli-client.ts +147 -45
  256. package/src/memory/__tests__/conversation-queries.test.ts +220 -0
  257. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  258. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  259. package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
  260. package/src/memory/conversation-crud.ts +133 -43
  261. package/src/memory/conversation-queries.ts +87 -1
  262. package/src/memory/conversation-title-service.ts +26 -4
  263. package/src/memory/db-init.ts +22 -0
  264. package/src/memory/delivery-crud.ts +41 -0
  265. package/src/memory/delivery-status.ts +141 -15
  266. package/src/memory/external-conversation-store.ts +32 -1
  267. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
  268. package/src/memory/graph/conversation-graph-memory.ts +18 -6
  269. package/src/memory/graph/tools.ts +6 -37
  270. package/src/memory/invite-store.ts +53 -0
  271. package/src/memory/jobs-worker.ts +21 -1
  272. package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
  273. package/src/memory/llm-request-log-store.ts +92 -1
  274. package/src/memory/memory-retrospective-constants.ts +28 -0
  275. package/src/memory/memory-retrospective-enqueue.ts +4 -22
  276. package/src/memory/memory-retrospective-job.ts +438 -21
  277. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  278. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  279. package/src/memory/migrations/100-core-tables.ts +1 -0
  280. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  281. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  282. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  283. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  284. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  285. package/src/memory/migrations/253-document-comments.ts +47 -0
  286. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  287. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  288. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  289. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  290. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  291. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  292. package/src/memory/migrations/index.ts +20 -0
  293. package/src/memory/migrations/registry.ts +33 -0
  294. package/src/memory/onboarding-events-store.ts +7 -0
  295. package/src/memory/schema/a2a.ts +15 -0
  296. package/src/memory/schema/calls.ts +1 -0
  297. package/src/memory/schema/conversations.ts +3 -0
  298. package/src/memory/schema/index.ts +1 -0
  299. package/src/memory/schema/inference.ts +2 -0
  300. package/src/memory/schema/infrastructure.ts +2 -0
  301. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  302. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  303. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  304. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  305. package/src/memory/v2/__tests__/injection.test.ts +221 -17
  306. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  307. package/src/memory/v2/__tests__/router.test.ts +489 -1
  308. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  309. package/src/memory/v2/activation-store.ts +14 -16
  310. package/src/memory/v2/cli-command-content.ts +19 -0
  311. package/src/memory/v2/cli-command-store.ts +304 -0
  312. package/src/memory/v2/consolidation-job.ts +14 -0
  313. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  314. package/src/memory/v2/injection-events.ts +101 -0
  315. package/src/memory/v2/injection.ts +69 -29
  316. package/src/memory/v2/page-index.ts +246 -19
  317. package/src/memory/v2/page-store.ts +18 -0
  318. package/src/memory/v2/router.ts +209 -55
  319. package/src/memory/v2/static-context.ts +4 -4
  320. package/src/memory/v2/types.ts +23 -0
  321. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  322. package/src/messaging/providers/a2a/deliver.ts +156 -0
  323. package/src/messaging/providers/gmail/client.ts +9 -2
  324. package/src/messaging/providers/index.ts +18 -3
  325. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  327. package/src/messaging/providers/slack/adapter.ts +178 -25
  328. package/src/messaging/providers/slack/api.test.ts +54 -0
  329. package/src/messaging/providers/slack/api.ts +119 -3
  330. package/src/messaging/providers/slack/client.ts +12 -0
  331. package/src/messaging/providers/slack/deep-link.ts +20 -1
  332. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  333. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  334. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  335. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  336. package/src/messaging/providers/slack/send.test.ts +77 -0
  337. package/src/messaging/providers/slack/send.ts +8 -2
  338. package/src/messaging/providers/slack/types.ts +14 -0
  339. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  340. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  341. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  342. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
  343. package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
  344. package/src/notifications/adapters/macos.ts +12 -2
  345. package/src/notifications/broadcaster.ts +29 -4
  346. package/src/notifications/conversation-seed-composer.ts +14 -2
  347. package/src/notifications/copy-composer.ts +17 -64
  348. package/src/notifications/decision-engine.ts +111 -44
  349. package/src/notifications/deferred-emit.ts +135 -0
  350. package/src/notifications/deterministic-checks.ts +96 -0
  351. package/src/notifications/emit-signal.ts +10 -1
  352. package/src/notifications/home-feed-side-effect.ts +136 -27
  353. package/src/notifications/signal.ts +0 -4
  354. package/src/notifications/types.ts +8 -0
  355. package/src/oauth/connect-orchestrator.ts +3 -0
  356. package/src/oauth/credential-token-resolver.ts +2 -0
  357. package/src/oauth/manual-token-connection.ts +19 -0
  358. package/src/oauth/oauth-store.ts +12 -0
  359. package/src/oauth/platform-connection.test.ts +43 -3
  360. package/src/oauth/platform-connection.ts +13 -4
  361. package/src/oauth/seed-providers.ts +22 -0
  362. package/src/permissions/prompter.ts +5 -2
  363. package/src/permissions/secret-prompter.ts +4 -1
  364. package/src/plugins/defaults/injectors.ts +118 -26
  365. package/src/plugins/external-plugin-loader.ts +82 -10
  366. package/src/plugins/types.ts +16 -7
  367. package/src/prompts/__tests__/system-prompt.test.ts +44 -45
  368. package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
  369. package/src/prompts/normalize-onboarding.ts +40 -0
  370. package/src/prompts/sections.ts +32 -14
  371. package/src/prompts/system-prompt.ts +105 -76
  372. package/src/prompts/template-detection.ts +37 -0
  373. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  374. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  375. package/src/prompts/templates/VOICE.md +3 -0
  376. package/src/prompts/templates/system-sections.ts +51 -10
  377. package/src/providers/__tests__/inference.test.ts +2 -0
  378. package/src/providers/anthropic/client.ts +132 -5
  379. package/src/providers/call-site-routing.ts +24 -6
  380. package/src/providers/connection-resolution.ts +63 -13
  381. package/src/providers/fireworks/client.ts +20 -2
  382. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  383. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  384. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  385. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  386. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  387. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  388. package/src/providers/inference/adapter-factory.ts +24 -21
  389. package/src/providers/inference/auth.ts +15 -3
  390. package/src/providers/inference/backfill.ts +14 -1
  391. package/src/providers/inference/codex-token-refresh.ts +128 -0
  392. package/src/providers/inference/connections.ts +85 -5
  393. package/src/providers/inference/resolve-auth.ts +50 -5
  394. package/src/providers/model-catalog.ts +244 -242
  395. package/src/providers/model-intents.ts +3 -3
  396. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  397. package/src/providers/openai/chat-completions-provider.ts +215 -25
  398. package/src/providers/openai/responses-provider.ts +9 -3
  399. package/src/providers/openrouter/client.ts +46 -4
  400. package/src/providers/platform-proxy/constants.ts +3 -4
  401. package/src/providers/provider-catalog-visibility.ts +3 -1
  402. package/src/providers/provider-send-message.ts +27 -12
  403. package/src/providers/registry.ts +30 -1
  404. package/src/providers/types.ts +25 -0
  405. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  406. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  407. package/src/runtime/agent-wake.ts +212 -57
  408. package/src/runtime/auth/route-policy.ts +20 -3
  409. package/src/runtime/background-job-runner.ts +26 -0
  410. package/src/runtime/channel-reply-delivery.ts +182 -47
  411. package/src/runtime/channel-retry-sweep.ts +141 -16
  412. package/src/runtime/http-server.ts +7 -16
  413. package/src/runtime/http-types.ts +7 -51
  414. package/src/runtime/pending-interactions.ts +51 -8
  415. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  416. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  417. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
  418. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  419. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  420. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  421. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  422. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  423. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  424. package/src/runtime/routes/approval-routes.ts +4 -1
  425. package/src/runtime/routes/channel-availability-routes.ts +5 -0
  426. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  427. package/src/runtime/routes/consolidation-routes.ts +100 -0
  428. package/src/runtime/routes/content-source-routes.ts +78 -0
  429. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  430. package/src/runtime/routes/conversation-query-routes.ts +130 -12
  431. package/src/runtime/routes/conversation-routes.ts +288 -76
  432. package/src/runtime/routes/document-comments-routes.ts +287 -0
  433. package/src/runtime/routes/documents-routes.ts +33 -0
  434. package/src/runtime/routes/home-feed-routes.ts +6 -3
  435. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  436. package/src/runtime/routes/host-browser-routes.ts +8 -1
  437. package/src/runtime/routes/identity-routes.ts +21 -0
  438. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  439. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  440. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  441. package/src/runtime/routes/index.ts +14 -4
  442. package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
  443. package/src/runtime/routes/integrations/a2a.ts +294 -0
  444. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  445. package/src/runtime/routes/log-export-routes.ts +39 -0
  446. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  447. package/src/runtime/routes/notification-routes.ts +19 -2
  448. package/src/runtime/routes/question-routes.ts +4 -1
  449. package/src/runtime/routes/sanity-routes.ts +159 -0
  450. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  451. package/src/runtime/routes/subagents-routes.ts +41 -0
  452. package/src/runtime/services/conversation-serializer.ts +30 -4
  453. package/src/schedule/integration-status.ts +3 -1
  454. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  455. package/src/security/oauth2-device-code.ts +307 -0
  456. package/src/security/oauth2.ts +26 -9
  457. package/src/security/secure-keys.ts +5 -0
  458. package/src/skills/catalog-install.ts +6 -2
  459. package/src/subagent/manager.ts +2 -0
  460. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  461. package/src/tools/browser/browser-execution.ts +93 -0
  462. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  463. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  464. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  465. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  466. package/src/tools/browser/cdp-client/factory.ts +87 -3
  467. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  468. package/src/tools/browser/cdp-client/types.ts +36 -0
  469. package/src/tools/browser/pinned-tabs.ts +90 -0
  470. package/src/tools/document/document-comment-tool.test.ts +379 -0
  471. package/src/tools/document/document-comment-tool.ts +156 -0
  472. package/src/tools/document/document-tool.ts +128 -2
  473. package/src/tools/memory/register.ts +1 -9
  474. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  475. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  476. package/src/tools/network/domain-normalize.ts +17 -0
  477. package/src/tools/network/web-fetch.ts +213 -64
  478. package/src/tools/network/web-search.ts +191 -66
  479. package/src/tools/registry.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +3 -2
  481. package/src/tools/tool-approval-handler.ts +19 -12
  482. package/src/tools/types.ts +41 -2
  483. package/src/tools/ui-surface/definitions.ts +3 -1
  484. package/src/types/onboarding-context.ts +4 -0
  485. package/src/util/__tests__/favicon.test.ts +84 -0
  486. package/src/util/favicon.ts +40 -0
  487. package/src/util/platform.ts +0 -5
  488. package/src/workspace/git-service.ts +75 -4
  489. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  490. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  491. package/src/workspace/migrations/registry.ts +4 -0
  492. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  493. package/src/config/bundled-skills/document/SKILL.md +0 -54
  494. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  495. package/src/daemon/seed-files.ts +0 -18
  496. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  497. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
  498. package/src/runtime/routes/interface-routes.ts +0 -43
  499. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  500. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  501. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  502. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  503. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -6,7 +6,7 @@
6
6
  * retryable and dead-lettered events, and replaying dead letters.
7
7
  */
8
8
 
9
- import { and, eq, lte } from "drizzle-orm";
9
+ import { and, eq, lte, or } from "drizzle-orm";
10
10
 
11
11
  import { getDb } from "./db-connection.js";
12
12
  import {
@@ -44,6 +44,7 @@ export function acknowledgeDelivery(
44
44
  db.update(channelInboundEvents)
45
45
  .set({
46
46
  deliveryStatus: "delivered",
47
+ retryAfter: null,
47
48
  updatedAt: now,
48
49
  })
49
50
  .where(eq(channelInboundEvents.id, existing.id))
@@ -61,6 +62,23 @@ export function markProcessed(eventId: string): void {
61
62
  .run();
62
63
  }
63
64
 
65
+ /** Mark an event's outbound callback delivery as complete. */
66
+ export function markDeliveryDelivered(eventId: string): void {
67
+ const db = getDb();
68
+ db.update(channelInboundEvents)
69
+ .set({
70
+ deliveryStatus: "delivered",
71
+ retryAfter: null,
72
+ updatedAt: Date.now(),
73
+ })
74
+ .where(eq(channelInboundEvents.id, eventId))
75
+ .run();
76
+ }
77
+
78
+ function errorMessage(err: unknown): string {
79
+ return err instanceof Error ? err.message : String(err);
80
+ }
81
+
64
82
  /**
65
83
  * Record a processing failure. Classifies the error to decide whether
66
84
  * the event should be retried (status='failed') or dead-lettered
@@ -79,7 +97,7 @@ export function recordProcessingFailure(eventId: string, err: unknown): void {
79
97
 
80
98
  const attempts = (row?.attempts ?? 0) + 1;
81
99
  const category = classifyError(err);
82
- const errorMsg = err instanceof Error ? err.message : String(err);
100
+ const errorMsg = errorMessage(err);
83
101
 
84
102
  if (category === "fatal" || attempts >= RETRY_MAX_ATTEMPTS) {
85
103
  db.update(channelInboundEvents)
@@ -107,6 +125,51 @@ export function recordProcessingFailure(eventId: string, err: unknown): void {
107
125
  }
108
126
  }
109
127
 
128
+ /**
129
+ * Record an outbound callback delivery failure without changing the processing
130
+ * status. Delivery uses its own retry budget so a turn that needed processing
131
+ * retries still gets a full delivery retry window.
132
+ */
133
+ export function recordDeliveryFailure(eventId: string, err: unknown): void {
134
+ const db = getDb();
135
+ const now = Date.now();
136
+
137
+ const row = db
138
+ .select({ attempts: channelInboundEvents.deliveryAttempts })
139
+ .from(channelInboundEvents)
140
+ .where(eq(channelInboundEvents.id, eventId))
141
+ .get();
142
+
143
+ const attempts = (row?.attempts ?? 0) + 1;
144
+ const category = classifyError(err);
145
+ const errorMsg = errorMessage(err);
146
+
147
+ if (category === "fatal" || attempts >= RETRY_MAX_ATTEMPTS) {
148
+ db.update(channelInboundEvents)
149
+ .set({
150
+ deliveryStatus: "dead_letter",
151
+ deliveryAttempts: attempts,
152
+ lastProcessingError: errorMsg,
153
+ retryAfter: null,
154
+ updatedAt: now,
155
+ })
156
+ .where(eq(channelInboundEvents.id, eventId))
157
+ .run();
158
+ } else {
159
+ const delay = retryDelayForAttempt(attempts);
160
+ db.update(channelInboundEvents)
161
+ .set({
162
+ deliveryStatus: "failed",
163
+ deliveryAttempts: attempts,
164
+ lastProcessingError: errorMsg,
165
+ retryAfter: now + delay,
166
+ updatedAt: now,
167
+ })
168
+ .where(eq(channelInboundEvents.id, eventId))
169
+ .run();
170
+ }
171
+ }
172
+
110
173
  /**
111
174
  * Mark an event as failed with a specific error message, bypassing error
112
175
  * classification. Use this when the failure reason is known and the event
@@ -180,6 +243,38 @@ export function getRetryableEvents(limit = 20): Array<{
180
243
  .all();
181
244
  }
182
245
 
246
+ /** Fetch callback deliveries eligible for retry without rerunning processing. */
247
+ export function getRetryableDeliveryEvents(limit = 20): Array<{
248
+ id: string;
249
+ conversationId: string;
250
+ messageId: string | null;
251
+ processingAttempts: number;
252
+ rawPayload: string | null;
253
+ deliveredSegmentCount: number;
254
+ }> {
255
+ const db = getDb();
256
+ const now = Date.now();
257
+ return db
258
+ .select({
259
+ id: channelInboundEvents.id,
260
+ conversationId: channelInboundEvents.conversationId,
261
+ messageId: channelInboundEvents.messageId,
262
+ processingAttempts: channelInboundEvents.processingAttempts,
263
+ rawPayload: channelInboundEvents.rawPayload,
264
+ deliveredSegmentCount: channelInboundEvents.deliveredSegmentCount,
265
+ })
266
+ .from(channelInboundEvents)
267
+ .where(
268
+ and(
269
+ eq(channelInboundEvents.processingStatus, "processed"),
270
+ eq(channelInboundEvents.deliveryStatus, "failed"),
271
+ lte(channelInboundEvents.retryAfter, now),
272
+ ),
273
+ )
274
+ .limit(limit)
275
+ .all();
276
+ }
277
+
183
278
  /** Fetch dead-lettered events. */
184
279
  export function getDeadLetterEvents(): Array<{
185
280
  id: string;
@@ -204,7 +299,15 @@ export function getDeadLetterEvents(): Array<{
204
299
  createdAt: channelInboundEvents.createdAt,
205
300
  })
206
301
  .from(channelInboundEvents)
207
- .where(eq(channelInboundEvents.processingStatus, "dead_letter"))
302
+ .where(
303
+ or(
304
+ eq(channelInboundEvents.processingStatus, "dead_letter"),
305
+ and(
306
+ eq(channelInboundEvents.processingStatus, "processed"),
307
+ eq(channelInboundEvents.deliveryStatus, "dead_letter"),
308
+ ),
309
+ ),
310
+ )
208
311
  .all();
209
312
  }
210
313
 
@@ -218,27 +321,50 @@ export function replayDeadLetters(eventIds: string[]): number {
218
321
  let count = 0;
219
322
  for (const id of eventIds) {
220
323
  const existing = db
221
- .select({ id: channelInboundEvents.id })
324
+ .select({
325
+ id: channelInboundEvents.id,
326
+ processingStatus: channelInboundEvents.processingStatus,
327
+ deliveryStatus: channelInboundEvents.deliveryStatus,
328
+ })
222
329
  .from(channelInboundEvents)
223
330
  .where(
224
331
  and(
225
332
  eq(channelInboundEvents.id, id),
226
- eq(channelInboundEvents.processingStatus, "dead_letter"),
333
+ or(
334
+ eq(channelInboundEvents.processingStatus, "dead_letter"),
335
+ and(
336
+ eq(channelInboundEvents.processingStatus, "processed"),
337
+ eq(channelInboundEvents.deliveryStatus, "dead_letter"),
338
+ ),
339
+ ),
227
340
  ),
228
341
  )
229
342
  .get();
230
343
  if (!existing) continue;
231
344
 
232
- db.update(channelInboundEvents)
233
- .set({
234
- processingStatus: "failed",
235
- processingAttempts: 0,
236
- lastProcessingError: null,
237
- retryAfter: now,
238
- updatedAt: now,
239
- })
240
- .where(eq(channelInboundEvents.id, id))
241
- .run();
345
+ if (existing.processingStatus === "dead_letter") {
346
+ db.update(channelInboundEvents)
347
+ .set({
348
+ processingStatus: "failed",
349
+ processingAttempts: 0,
350
+ lastProcessingError: null,
351
+ retryAfter: now,
352
+ updatedAt: now,
353
+ })
354
+ .where(eq(channelInboundEvents.id, id))
355
+ .run();
356
+ } else {
357
+ db.update(channelInboundEvents)
358
+ .set({
359
+ deliveryStatus: "failed",
360
+ deliveryAttempts: 0,
361
+ lastProcessingError: null,
362
+ retryAfter: now,
363
+ updatedAt: now,
364
+ })
365
+ .where(eq(channelInboundEvents.id, id))
366
+ .run();
367
+ }
242
368
  count++;
243
369
  }
244
370
  return count;
@@ -7,7 +7,7 @@
7
7
  * list APIs.
8
8
  */
9
9
 
10
- import { and, eq, inArray, isNull } from "drizzle-orm";
10
+ import { and, eq, inArray, isNull, sql } from "drizzle-orm";
11
11
 
12
12
  import { getDb } from "./db-connection.js";
13
13
  import { externalConversationBindings } from "./schema.js";
@@ -16,6 +16,7 @@ export interface ExternalConversationBinding {
16
16
  conversationId: string;
17
17
  sourceChannel: string;
18
18
  externalChatId: string;
19
+ externalChatName?: string | null;
19
20
  externalThreadId?: string | null;
20
21
  externalUserId?: string | null;
21
22
  displayName?: string | null;
@@ -30,6 +31,7 @@ export interface UpsertBindingInput {
30
31
  conversationId: string;
31
32
  sourceChannel: string;
32
33
  externalChatId: string;
34
+ externalChatName?: string | null;
33
35
  externalThreadId?: string | null;
34
36
  externalUserId?: string | null;
35
37
  displayName?: string | null;
@@ -43,6 +45,13 @@ function normalizeExternalThreadId(
43
45
  return trimmed ? trimmed : null;
44
46
  }
45
47
 
48
+ function normalizeExternalChatName(
49
+ externalChatName?: string | null,
50
+ ): string | null {
51
+ const trimmed = externalChatName?.trim();
52
+ return trimmed ? trimmed : null;
53
+ }
54
+
46
55
  /**
47
56
  * Insert or update an external conversation binding on conflict (conversationId).
48
57
  * On conflict, updates channel metadata and timestamps.
@@ -51,6 +60,7 @@ export function upsertBinding(input: UpsertBindingInput): void {
51
60
  const db = getDb();
52
61
  const now = Date.now();
53
62
  const externalThreadId = normalizeExternalThreadId(input.externalThreadId);
63
+ const externalChatName = normalizeExternalChatName(input.externalChatName);
54
64
 
55
65
  // If a stale binding exists for this channel/chat/thread tuple under a
56
66
  // different conversationId, remove it first so the unique index is not violated.
@@ -75,6 +85,7 @@ export function upsertBinding(input: UpsertBindingInput): void {
75
85
  conversationId: input.conversationId,
76
86
  sourceChannel: input.sourceChannel,
77
87
  externalChatId: input.externalChatId,
88
+ externalChatName,
78
89
  externalThreadId,
79
90
  externalUserId: input.externalUserId ?? null,
80
91
  displayName: input.displayName ?? null,
@@ -88,6 +99,9 @@ export function upsertBinding(input: UpsertBindingInput): void {
88
99
  set: {
89
100
  sourceChannel: input.sourceChannel,
90
101
  externalChatId: input.externalChatId,
102
+ externalChatName:
103
+ externalChatName ??
104
+ sql`${externalConversationBindings.externalChatName}`,
91
105
  externalThreadId,
92
106
  externalUserId: input.externalUserId ?? null,
93
107
  displayName: input.displayName ?? null,
@@ -158,6 +172,23 @@ export function upsertOutboundBinding(input: {
158
172
  .run();
159
173
  }
160
174
 
175
+ export function updateExternalChatName(
176
+ conversationId: string,
177
+ externalChatName: string,
178
+ ): void {
179
+ const db = getDb();
180
+ const trimmedName = externalChatName.trim();
181
+ if (!trimmedName) return;
182
+
183
+ db.update(externalConversationBindings)
184
+ .set({
185
+ externalChatName: trimmedName,
186
+ updatedAt: Date.now(),
187
+ })
188
+ .where(eq(externalConversationBindings.conversationId, conversationId))
189
+ .run();
190
+ }
191
+
161
192
  /**
162
193
  * Look up an external binding by conversation ID.
163
194
  */
@@ -177,7 +177,7 @@ const { migrateActivationState } =
177
177
  await import("../../migrations/232-activation-state.js");
178
178
  const schema = await import("../../schema.js");
179
179
  const { _resetMemoryV2QdrantForTests } = await import("../../v2/qdrant.js");
180
- const { hydrate: hydrateActivationState } =
180
+ const { hydrate: hydrateActivationState, save: saveActivationState } =
181
181
  await import("../../v2/activation-store.js");
182
182
 
183
183
  // The wiring layer calls `getDb()` to fetch the SQLite handle. We mock
@@ -215,9 +215,14 @@ function createTestDb(): DrizzleDb {
215
215
  return db;
216
216
  }
217
217
 
218
- function makeConfig(v2Enabled: boolean): AssistantConfig {
218
+ function makeConfig(v2Enabled: boolean, memoryEnabled = true): AssistantConfig {
219
+ // Pin `router.enabled: false` so these tests exercise the activation
220
+ // pipeline. Router-mode coverage lives in `memory/v2/__tests__/injection.test.ts`.
219
221
  return applyNestedDefaults({
220
- memory: { v2: { enabled: v2Enabled } },
222
+ memory: {
223
+ enabled: memoryEnabled,
224
+ v2: { enabled: v2Enabled, router: { enabled: false } },
225
+ },
221
226
  }) as AssistantConfig;
222
227
  }
223
228
 
@@ -518,6 +523,50 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (context-load pat
518
523
  });
519
524
  });
520
525
 
526
+ describe("ConversationGraphMemory.prepareMemory — memory.enabled gate", () => {
527
+ test("memory.enabled=false short-circuits per-turn path: mode=none, no injection, v2/v1 not called", async () => {
528
+ stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
529
+
530
+ const memory = makeMemory();
531
+ const config = makeConfig(true, false);
532
+ const messages = makeMessages();
533
+
534
+ const result = await memory.prepareMemory(
535
+ messages,
536
+ config,
537
+ new AbortController().signal,
538
+ noopEvent,
539
+ );
540
+
541
+ expect(result.mode).toBe("none");
542
+ expect(result.injectedBlockText).toBeNull();
543
+ expect(result.runMessages).toEqual(messages);
544
+ expect(retrieveForTurnMock).not.toHaveBeenCalled();
545
+ expect(loadContextMemoryMock).not.toHaveBeenCalled();
546
+ });
547
+
548
+ test("memory.enabled=false short-circuits context-load path: mode=none, no injection, v2/v1 not called", async () => {
549
+ stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
550
+
551
+ const memory = new ConversationGraphMemory("conv-test-master-off");
552
+ const config = makeConfig(true, false);
553
+ const messages = makeMessages("first message of the conversation here");
554
+
555
+ const result = await memory.prepareMemory(
556
+ messages,
557
+ config,
558
+ new AbortController().signal,
559
+ noopEvent,
560
+ );
561
+
562
+ expect(result.mode).toBe("none");
563
+ expect(result.injectedBlockText).toBeNull();
564
+ expect(result.runMessages).toEqual(messages);
565
+ expect(loadContextMemoryMock).not.toHaveBeenCalled();
566
+ expect(retrieveForTurnMock).not.toHaveBeenCalled();
567
+ });
568
+ });
569
+
521
570
  describe("ConversationGraphMemory.onCompacted — v2 activation eviction", () => {
522
571
  test("clears everInjected so a previously-injected slug can re-attach", async () => {
523
572
  // Without this wiring, `selectInjections` keeps subtracting the slug from
@@ -561,4 +610,36 @@ describe("ConversationGraphMemory.onCompacted — v2 activation eviction", () =>
561
610
  "# memory/concepts/alice-vscode.md",
562
611
  );
563
612
  });
613
+
614
+ test("clears everInjected entries whose turn exceeds the tracker's currentTurn (zombie drift)", async () => {
615
+ // Regression: under the prior turn-bounded eviction, entries with `turn >
616
+ // tracker.currentTurn` survived `onCompacted` forever. This can happen
617
+ // after a non-graceful shutdown: `everInjected` is persisted every turn
618
+ // while the tracker snapshot is only persisted on graceful dispose, so a
619
+ // SIGKILL'd session followed by a reload restores the tracker from an
620
+ // older snapshot with a lower currentTurn while keeping the high-turn
621
+ // entries on disk.
622
+ const conversationId = "conv-test-zombie-drift";
623
+ const memory = new ConversationGraphMemory(conversationId);
624
+
625
+ // Seed the simulated post-crash state directly: tracker stays at
626
+ // currentTurn=0 (default for a fresh ConversationGraphMemory), while the
627
+ // persisted row carries everInjected entries from turns 10 and 20 (left
628
+ // over from a prior session that never disposed cleanly).
629
+ await saveActivationState(testDbHandle!, conversationId, {
630
+ messageId: "msg-zombie",
631
+ state: {},
632
+ everInjected: [
633
+ { slug: "alice-vscode", turn: 10 },
634
+ { slug: "bob-pkg-mgr", turn: 20 },
635
+ ],
636
+ currentTurn: 0,
637
+ updatedAt: 1,
638
+ });
639
+
640
+ await memory.onCompacted(0);
641
+
642
+ const after = await hydrateActivationState(testDbHandle!, conversationId);
643
+ expect(after?.everInjected).toEqual([]);
644
+ });
564
645
  });
@@ -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];
@@ -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,
@@ -56,37 +54,14 @@ 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 =
62
+ const REMEMBER_DESCRIPTION =
75
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.";
76
64
 
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
- }
89
-
90
65
  /**
91
66
  * Save a fact to the assistant's knowledge base. The fact is appended to
92
67
  * `buffer.md` (immediately available in the next conversation) and the daily
@@ -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: {
@@ -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
  // ---------------------------------------------------------------------------
@@ -1,3 +1,5 @@
1
+ import { join } from "node:path";
2
+
1
3
  import { getConfig } from "../config/loader.js";
2
4
  import type { AssistantConfig } from "../config/types.js";
3
5
  import {
@@ -6,6 +8,7 @@ import {
6
8
  shouldLogDiskPressureBackgroundSkip,
7
9
  } from "../daemon/disk-pressure-background-gate.js";
8
10
  import { getLogger } from "../util/logger.js";
11
+ import { getWorkspaceDir } from "../util/platform.js";
9
12
  import { getMemoryCheckpoint, setMemoryCheckpoint } from "./checkpoints.js";
10
13
  import {
11
14
  getLastScheduledCleanupEnqueueMs,
@@ -75,7 +78,10 @@ import {
75
78
  memoryV2MigrateJob,
76
79
  memoryV2ReembedJob,
77
80
  } from "./v2/backfill-jobs.js";
78
- import { memoryV2ConsolidateJob } from "./v2/consolidation-job.js";
81
+ import {
82
+ countBufferLines,
83
+ memoryV2ConsolidateJob,
84
+ } from "./v2/consolidation-job.js";
79
85
  import { memoryV2SweepJob } from "./v2/sweep-job.js";
80
86
 
81
87
  const log = getLogger("memory-jobs-worker");
@@ -739,11 +745,25 @@ export function maybeEnqueueGraphMaintenanceJobs(
739
745
  },
740
746
  ];
741
747
 
748
+ let enqueuedV2 = false;
742
749
  for (const { key, intervalMs, jobType } of schedule) {
743
750
  const lastRun = parseInt(getMemoryCheckpoint(key) ?? "0", 10);
744
751
  if (nowMs - lastRun >= intervalMs) {
745
752
  enqueueMemoryJob(jobType, {});
746
753
  setMemoryCheckpoint(key, String(nowMs));
754
+ if (jobType === "memory_v2_consolidate") enqueuedV2 = true;
755
+ }
756
+ }
757
+
758
+ const maxLines = config.memory.v2.consolidation_max_buffer_lines;
759
+ if (v2Active && !enqueuedV2 && maxLines !== null) {
760
+ const bufferPath = join(getWorkspaceDir(), "memory", "buffer.md");
761
+ if (countBufferLines(bufferPath) >= maxLines) {
762
+ enqueueMemoryJob("memory_v2_consolidate", {});
763
+ setMemoryCheckpoint(
764
+ GRAPH_MAINTENANCE_CHECKPOINTS.memoryV2Consolidate,
765
+ String(nowMs),
766
+ );
747
767
  }
748
768
  }
749
769
  }
@@ -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}
@@ -194,7 +196,8 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
194
196
  provider,
195
197
  request_payload,
196
198
  response_payload,
197
- toUnixTimestamp64Milli(created_at) AS created_at
199
+ toUnixTimestamp64Milli(created_at) AS created_at,
200
+ agent_loop_exit_reason
198
201
  FROM ${this.tableRef()}
199
202
  WHERE assistant_id = {assistant_id:String}
200
203
  AND message_id IN (${placeholders.join(",")})
@@ -283,6 +286,8 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
283
286
  requestPayload: row.request_payload,
284
287
  responsePayload: row.response_payload,
285
288
  createdAt: Number(row.created_at),
289
+ agentLoopExitReason:
290
+ row.agent_loop_exit_reason === "" ? null : row.agent_loop_exit_reason,
286
291
  };
287
292
  }
288
293