@vellumai/assistant 0.8.5 → 0.8.6

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 (544) hide show
  1. package/AGENTS.md +33 -1
  2. package/ARCHITECTURE.md +1 -1
  3. package/bunfig.toml +6 -1
  4. package/docs/credential-execution-service.md +6 -6
  5. package/docs/plugins.md +4 -3
  6. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
  7. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
  8. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
  9. package/openapi.yaml +1900 -166
  10. package/package.json +1 -1
  11. package/src/__tests__/actor-token-service.test.ts +3 -2
  12. package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
  13. package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
  14. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  15. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  16. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  17. package/src/__tests__/annotate-risk-options.test.ts +1 -0
  18. package/src/__tests__/approval-cascade.test.ts +1 -0
  19. package/src/__tests__/approval-routes-http.test.ts +9 -13
  20. package/src/__tests__/assert-not-live-db.ts +79 -0
  21. package/src/__tests__/assistant-feature-flags-integration.test.ts +9 -25
  22. package/src/__tests__/audit-log-rotation.test.ts +2 -2
  23. package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
  24. package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
  25. package/src/__tests__/browser-skill-endstate.test.ts +3 -3
  26. package/src/__tests__/btw-routes.test.ts +3 -2
  27. package/src/__tests__/call-controller.test.ts +3 -2
  28. package/src/__tests__/channel-approval-routes.test.ts +3 -2
  29. package/src/__tests__/channel-guardian.test.ts +3 -2
  30. package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
  31. package/src/__tests__/channel-reply-delivery.test.ts +35 -0
  32. package/src/__tests__/channel-retry-sweep.test.ts +320 -3
  33. package/src/__tests__/checker.test.ts +12 -12
  34. package/src/__tests__/compaction-events.test.ts +1 -0
  35. package/src/__tests__/compaction-trail-store.test.ts +264 -0
  36. package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
  37. package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
  38. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
  39. package/src/__tests__/computer-use-tools.test.ts +12 -14
  40. package/src/__tests__/config-loader-backfill.test.ts +13 -28
  41. package/src/__tests__/config-loader-corrupt.test.ts +5 -5
  42. package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
  43. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
  44. package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
  45. package/src/__tests__/config-schema.test.ts +10 -10
  46. package/src/__tests__/connection-model-compat.test.ts +83 -0
  47. package/src/__tests__/contacts-tools.test.ts +3 -2
  48. package/src/__tests__/context-token-estimator.test.ts +22 -0
  49. package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
  50. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
  52. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  53. package/src/__tests__/conversation-agent-loop-overflow.test.ts +34 -0
  54. package/src/__tests__/conversation-agent-loop.test.ts +488 -2
  55. package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
  56. package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
  57. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
  58. package/src/__tests__/conversation-attention-store.test.ts +101 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  61. package/src/__tests__/conversation-error.test.ts +30 -0
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -8
  63. package/src/__tests__/conversation-fork-route.test.ts +3 -2
  64. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  65. package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
  66. package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
  67. package/src/__tests__/conversation-lifecycle.test.ts +1 -0
  68. package/src/__tests__/conversation-list-source.test.ts +3 -2
  69. package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
  70. package/src/__tests__/conversation-load-history-stripped.test.ts +1 -0
  71. package/src/__tests__/conversation-pairing.test.ts +53 -0
  72. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
  73. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  74. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -0
  75. package/src/__tests__/conversation-queue.test.ts +333 -291
  76. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -18
  77. package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
  78. package/src/__tests__/conversation-routes-slash-commands.test.ts +33 -2
  79. package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
  80. package/src/__tests__/conversation-skill-tools.test.ts +38 -142
  81. package/src/__tests__/conversation-slash-queue.test.ts +84 -32
  82. package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
  83. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
  85. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
  90. package/src/__tests__/conversation-sync-tags.test.ts +128 -12
  91. package/src/__tests__/conversation-title-service.test.ts +1 -0
  92. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
  93. package/src/__tests__/conversation-usage.test.ts +1 -0
  94. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  95. package/src/__tests__/conversation-workspace-injection.test.ts +5 -0
  96. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -0
  97. package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
  98. package/src/__tests__/credential-broker-server-use.test.ts +5 -5
  99. package/src/__tests__/credential-execution-client.test.ts +72 -1
  100. package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
  101. package/src/__tests__/credential-health-service.test.ts +252 -3
  102. package/src/__tests__/credential-security-invariants.test.ts +5 -5
  103. package/src/__tests__/credential-vault-unit.test.ts +19 -19
  104. package/src/__tests__/credential-vault.test.ts +5 -5
  105. package/src/__tests__/cross-provider-web-search.test.ts +56 -2
  106. package/src/__tests__/db-connection-isolation.test.ts +7 -6
  107. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
  108. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
  109. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
  110. package/src/__tests__/db-test-helpers.ts +58 -0
  111. package/src/__tests__/disk-pressure-guard.test.ts +58 -41
  112. package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
  113. package/src/__tests__/disk-pressure-routes.test.ts +0 -33
  114. package/src/__tests__/disk-pressure-tools.test.ts +0 -4
  115. package/src/__tests__/dm-persistence.test.ts +26 -40
  116. package/src/__tests__/document-create-dedupe.test.ts +189 -0
  117. package/src/__tests__/document-find-replace.test.ts +3 -2
  118. package/src/__tests__/document-tool-security.test.ts +81 -2
  119. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
  120. package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
  121. package/src/__tests__/encrypted-store.test.ts +11 -9
  122. package/src/__tests__/feature-flag-test-helpers.ts +53 -0
  123. package/src/__tests__/filing-service.test.ts +1 -0
  124. package/src/__tests__/first-greeting.test.ts +62 -12
  125. package/src/__tests__/gateway-flag-listener.test.ts +0 -1
  126. package/src/__tests__/gemini-provider.test.ts +26 -0
  127. package/src/__tests__/guardian-action-sweep.test.ts +3 -2
  128. package/src/__tests__/guardian-outbound-http.test.ts +3 -2
  129. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
  130. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -0
  131. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  132. package/src/__tests__/heartbeat-service.test.ts +1 -0
  133. package/src/__tests__/helpers/mock-logger.ts +26 -0
  134. package/src/__tests__/host-bash-routes.test.ts +1 -0
  135. package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
  136. package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
  137. package/src/__tests__/host-shell-tool.test.ts +5 -4
  138. package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
  139. package/src/__tests__/http-conversation-lineage.test.ts +3 -2
  140. package/src/__tests__/http-user-message-parity.test.ts +29 -7
  141. package/src/__tests__/identity-intro-cache.test.ts +133 -22
  142. package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
  143. package/src/__tests__/inference-profile-reaper.test.ts +3 -2
  144. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
  145. package/src/__tests__/injector-disk-pressure.test.ts +3 -17
  146. package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
  147. package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
  148. package/src/__tests__/llm-context-normalization.test.ts +42 -0
  149. package/src/__tests__/llm-resolver.test.ts +331 -0
  150. package/src/__tests__/llm-schema.test.ts +1 -1
  151. package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
  152. package/src/__tests__/mcp-abort-signal.test.ts +14 -0
  153. package/src/__tests__/mcp-client-auth.test.ts +14 -0
  154. package/src/__tests__/messaging-send-tool.test.ts +1 -0
  155. package/src/__tests__/migration-import-from-url.test.ts +3 -3
  156. package/src/__tests__/mock-gateway-ipc.ts +18 -2
  157. package/src/__tests__/model-intents.test.ts +3 -3
  158. package/src/__tests__/native-web-search.test.ts +30 -2
  159. package/src/__tests__/notification-deep-link.test.ts +62 -0
  160. package/src/__tests__/oauth-commands-routes.test.ts +37 -0
  161. package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
  162. package/src/__tests__/oauth-store.test.ts +3 -2
  163. package/src/__tests__/onboarding-template-contract.test.ts +3 -2
  164. package/src/__tests__/openai-provider.test.ts +8 -9
  165. package/src/__tests__/openai-responses-provider.test.ts +70 -10
  166. package/src/__tests__/openrouter-provider-only.test.ts +27 -5
  167. package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
  168. package/src/__tests__/persistence-pipeline.test.ts +139 -1
  169. package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
  170. package/src/__tests__/plugin-bootstrap.test.ts +9 -11
  171. package/src/__tests__/plugin-tool-contribution.test.ts +41 -38
  172. package/src/__tests__/process-message-background-slack.test.ts +21 -16
  173. package/src/__tests__/process-message-display-content.test.ts +19 -22
  174. package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
  175. package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
  176. package/src/__tests__/provider-registry-ollama.test.ts +45 -22
  177. package/src/__tests__/recording-handler.test.ts +1 -0
  178. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  179. package/src/__tests__/registry.test.ts +82 -76
  180. package/src/__tests__/relay-server.test.ts +10 -10
  181. package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
  182. package/src/__tests__/schedule-store.test.ts +16 -1
  183. package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
  184. package/src/__tests__/secret-ingress-http.test.ts +5 -1
  185. package/src/__tests__/secure-keys.test.ts +3 -3
  186. package/src/__tests__/send-endpoint-busy.test.ts +81 -42
  187. package/src/__tests__/server-history-render.test.ts +4 -1
  188. package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
  189. package/src/__tests__/skill-feature-flags.test.ts +14 -16
  190. package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
  191. package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
  192. package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
  193. package/src/__tests__/skill-tool-factory.test.ts +96 -95
  194. package/src/__tests__/slack-channel-config.test.ts +3 -3
  195. package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
  196. package/src/__tests__/subagent-disposal.test.ts +27 -8
  197. package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
  198. package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
  199. package/src/__tests__/subagent-manager-notify.test.ts +20 -8
  200. package/src/__tests__/subagent-notify-parent.test.ts +5 -4
  201. package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
  202. package/src/__tests__/subagent-tools.test.ts +2 -1
  203. package/src/__tests__/suggestion-routes.test.ts +1 -0
  204. package/src/__tests__/system-prompt.test.ts +38 -0
  205. package/src/__tests__/test-preload-verifier.ts +68 -0
  206. package/src/__tests__/test-preload.ts +32 -39
  207. package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
  208. package/src/__tests__/tool-executor.test.ts +55 -10
  209. package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
  210. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  211. package/src/__tests__/twilio-routes.test.ts +3 -2
  212. package/src/__tests__/validate-input.test.ts +381 -0
  213. package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
  214. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
  215. package/src/__tests__/voice-session-bridge.test.ts +37 -28
  216. package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
  217. package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
  218. package/src/acp/session-manager.ts +5 -6
  219. package/src/agent/loop.ts +80 -0
  220. package/src/api/README.md +124 -2
  221. package/src/api/constants/call-sites.ts +27 -0
  222. package/src/api/events/assistant-outbound-attachment.ts +51 -0
  223. package/src/api/events/assistant-text-delta.ts +32 -0
  224. package/src/api/events/assistant-turn-start.ts +33 -0
  225. package/src/api/events/document-comment-created.ts +48 -0
  226. package/src/api/events/document-comment-deleted.ts +24 -0
  227. package/src/api/events/document-comment-reopened.ts +25 -0
  228. package/src/api/events/document-comment-resolved.ts +27 -0
  229. package/src/api/events/generation-cancelled.ts +24 -0
  230. package/src/api/events/generation-handoff.ts +41 -0
  231. package/src/api/events/message-complete.ts +42 -0
  232. package/src/api/events/open-url.ts +30 -0
  233. package/src/{events → api/events}/relationship-state-updated.ts +3 -3
  234. package/src/api/events/tool-use-start.ts +32 -0
  235. package/src/api/index.ts +128 -3
  236. package/src/api/responses/llm-context-response.ts +39 -0
  237. package/src/api/responses/llm-request-log-entry.ts +93 -0
  238. package/src/api/responses/memory-recall-log.ts +65 -0
  239. package/src/api/responses/memory-v2-activation-log.ts +78 -0
  240. package/src/background-wake/background-wake-routes.test.ts +687 -52
  241. package/src/background-wake/platform-client.test.ts +308 -0
  242. package/src/background-wake/platform-client.ts +167 -0
  243. package/src/background-wake/publisher.ts +91 -0
  244. package/src/background-wake/runtime-registry.ts +2 -2
  245. package/src/background-wake/wake-intent-hooks.test.ts +282 -0
  246. package/src/calls/guardian-dispatch.ts +1 -0
  247. package/src/calls/voice-session-bridge.ts +4 -4
  248. package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
  249. package/src/cli/commands/__tests__/notifications.test.ts +184 -40
  250. package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
  251. package/src/cli/commands/channels/index.ts +229 -0
  252. package/src/cli/commands/memory-v3-render.ts +147 -0
  253. package/src/cli/commands/memory-v3.ts +255 -4
  254. package/src/cli/commands/notifications.ts +365 -55
  255. package/src/cli/lib/open-browser.ts +7 -2
  256. package/src/cli/program.ts +2 -0
  257. package/src/config/assistant-feature-flags.ts +23 -42
  258. package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
  259. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  260. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  261. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
  262. package/src/config/call-site-defaults.ts +1 -1
  263. package/src/config/feature-flag-cache.ts +86 -0
  264. package/src/config/feature-flag-registry.json +17 -17
  265. package/src/config/llm-context-resolution.ts +10 -1
  266. package/src/config/llm-resolver.ts +121 -15
  267. package/src/config/loader.ts +4 -5
  268. package/src/config/schemas/__tests__/memory-v2.test.ts +15 -0
  269. package/src/config/schemas/heartbeat.ts +1 -1
  270. package/src/config/schemas/llm.ts +90 -1
  271. package/src/config/schemas/memory-v2.ts +26 -0
  272. package/src/config/schemas/services.ts +6 -2
  273. package/src/config/seed-inference-profiles.ts +36 -16
  274. package/src/context/token-estimator.ts +10 -5
  275. package/src/credential-execution/executable-discovery.ts +40 -0
  276. package/src/credential-execution/process-manager.ts +6 -2
  277. package/src/credential-health/credential-health-service.ts +125 -40
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
  279. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
  280. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
  281. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
  282. package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
  283. package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
  284. package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
  285. package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
  286. package/src/daemon/conversation-agent-loop-handlers.ts +389 -68
  287. package/src/daemon/conversation-agent-loop.ts +132 -28
  288. package/src/daemon/conversation-error.ts +33 -5
  289. package/src/daemon/conversation-messaging.ts +84 -43
  290. package/src/daemon/conversation-process.ts +74 -37
  291. package/src/daemon/conversation-runtime-assembly.ts +29 -9
  292. package/src/daemon/conversation-skill-tools.ts +14 -30
  293. package/src/daemon/conversation-surfaces.ts +69 -34
  294. package/src/daemon/conversation-tool-setup.ts +33 -48
  295. package/src/daemon/conversation.ts +26 -46
  296. package/src/daemon/daemon-control.ts +1 -1
  297. package/src/daemon/daemon-skill-host.ts +9 -2
  298. package/src/daemon/disk-pressure-guard.ts +27 -29
  299. package/src/daemon/first-greeting.ts +31 -13
  300. package/src/daemon/handlers/shared.ts +6 -1
  301. package/src/daemon/lifecycle.ts +12 -12
  302. package/src/daemon/mcp-reload-service.ts +1 -1
  303. package/src/daemon/meet-manifest-loader.ts +10 -17
  304. package/src/daemon/message-types/conversations.ts +20 -22
  305. package/src/daemon/message-types/document-comments.ts +8 -44
  306. package/src/daemon/message-types/home.ts +2 -2
  307. package/src/daemon/message-types/integrations.ts +2 -7
  308. package/src/daemon/message-types/messages.ts +23 -38
  309. package/src/daemon/message-types/subagents.ts +6 -0
  310. package/src/daemon/process-message.ts +9 -9
  311. package/src/daemon/providers-setup.ts +1 -1
  312. package/src/daemon/server.ts +16 -0
  313. package/src/daemon/switch-inference-profile-tool.ts +13 -3
  314. package/src/daemon/tool-setup-types.ts +0 -6
  315. package/src/daemon/wake-target-adapter.ts +10 -0
  316. package/src/documents/document-store.ts +38 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +1 -0
  318. package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -0
  319. package/src/heartbeat/heartbeat-service.ts +63 -0
  320. package/src/home/__tests__/feed-writer.test.ts +161 -0
  321. package/src/home/__tests__/post-connect-feed.test.ts +1 -0
  322. package/src/home/__tests__/suggested-prompts.test.ts +55 -59
  323. package/src/home/feed-writer.ts +146 -7
  324. package/src/home/suggested-prompts.ts +27 -145
  325. package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
  326. package/src/ipc/gateway-client.test.ts +4 -1
  327. package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
  328. package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
  329. package/src/ipc/skill-routes/memory.ts +4 -3
  330. package/src/ipc/skill-routes/registries.ts +28 -29
  331. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
  332. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
  333. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
  334. package/src/memory/__tests__/memory-retrospective-job.test.ts +1 -0
  335. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
  336. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
  337. package/src/memory/conversation-attention-store.ts +17 -3
  338. package/src/memory/conversation-crud.ts +352 -112
  339. package/src/memory/db-connection.ts +29 -19
  340. package/src/memory/db-init.ts +4 -0
  341. package/src/memory/db-singleton.ts +77 -0
  342. package/src/memory/delivery-channels.ts +82 -0
  343. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
  344. package/src/memory/graph/retriever.test.ts +3 -3
  345. package/src/memory/job-handlers/embedding.test.ts +3 -2
  346. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
  347. package/src/memory/jobs-worker.ts +12 -1
  348. package/src/memory/llm-request-log-source-clickhouse.ts +80 -0
  349. package/src/memory/llm-request-log-source-local.ts +24 -0
  350. package/src/memory/llm-request-log-source.ts +31 -0
  351. package/src/memory/llm-request-log-store.ts +188 -3
  352. package/src/memory/memory-v2-activation-log-store.ts +95 -1
  353. package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
  354. package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
  355. package/src/memory/migrations/index.ts +2 -0
  356. package/src/memory/schema/conversations.ts +9 -1
  357. package/src/memory/schema/inference.ts +0 -1
  358. package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
  359. package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
  360. package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
  361. package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
  362. package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
  363. package/src/memory/v2/harness/metrics.ts +5 -1
  364. package/src/memory/v2/harness/replay-input.ts +19 -3
  365. package/src/memory/v2/harness/runner.ts +6 -0
  366. package/src/memory/v2/harness/trace.ts +6 -0
  367. package/src/memory/v3/__tests__/consolidation-job.test.ts +2 -4
  368. package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
  369. package/src/memory/v3/__tests__/edges.test.ts +144 -1
  370. package/src/memory/v3/__tests__/filter.test.ts +48 -0
  371. package/src/memory/v3/__tests__/gate.test.ts +96 -33
  372. package/src/memory/v3/__tests__/index-composition.test.ts +58 -0
  373. package/src/memory/v3/__tests__/loop.test.ts +250 -5
  374. package/src/memory/v3/__tests__/scouts.test.ts +49 -0
  375. package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
  376. package/src/memory/v3/__tests__/shadow-middleware.test.ts +88 -2
  377. package/src/memory/v3/__tests__/traversal.test.ts +39 -0
  378. package/src/memory/v3/__tests__/tree-walk.test.ts +77 -0
  379. package/src/memory/v3/__tests__/validate.test.ts +32 -0
  380. package/src/memory/v3/coretrieval-seed.ts +240 -0
  381. package/src/memory/v3/edges.ts +58 -21
  382. package/src/memory/v3/filter.ts +27 -22
  383. package/src/memory/v3/gate.ts +51 -36
  384. package/src/memory/v3/index-composition.ts +18 -5
  385. package/src/memory/v3/loop.ts +65 -17
  386. package/src/memory/v3/scouts.ts +15 -4
  387. package/src/memory/v3/shadow-diff.ts +287 -0
  388. package/src/memory/v3/shadow-middleware.ts +44 -2
  389. package/src/memory/v3/traversal.ts +6 -1
  390. package/src/memory/v3/tree-walk.ts +6 -1
  391. package/src/memory/v3/validate.ts +56 -33
  392. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  393. package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
  394. package/src/notifications/adapters/slack.ts +45 -11
  395. package/src/notifications/broadcaster.ts +114 -63
  396. package/src/notifications/conversation-pairing.ts +23 -3
  397. package/src/notifications/decisions-store.ts +32 -1
  398. package/src/notifications/deliveries-store.ts +45 -0
  399. package/src/notifications/edit-notification.ts +201 -0
  400. package/src/notifications/emit-signal.ts +11 -1
  401. package/src/notifications/signal.ts +10 -0
  402. package/src/notifications/types.ts +37 -0
  403. package/src/oauth/byo-connection.test.ts +67 -3
  404. package/src/oauth/byo-connection.ts +32 -5
  405. package/src/oauth/connect-orchestrator.ts +9 -0
  406. package/src/oauth/connection-resolver.test.ts +76 -0
  407. package/src/oauth/connection-resolver.ts +49 -10
  408. package/src/oauth/manual-token-connection.ts +51 -3
  409. package/src/oauth/seed-providers.ts +3 -0
  410. package/src/permissions/approval-policy.test.ts +19 -5
  411. package/src/permissions/approval-policy.ts +14 -3
  412. package/src/permissions/checker.ts +21 -8
  413. package/src/platform/client.test.ts +24 -1
  414. package/src/platform/client.ts +8 -0
  415. package/src/platform/feature-gate.ts +15 -0
  416. package/src/plugins/defaults/injectors.ts +2 -8
  417. package/src/plugins/defaults/persistence.ts +25 -6
  418. package/src/plugins/types.ts +57 -13
  419. package/src/proactive-artifact/job.test.ts +1 -0
  420. package/src/prompts/__tests__/system-prompt.test.ts +4 -4
  421. package/src/prompts/system-prompt.ts +38 -40
  422. package/src/prompts/template-detection.ts +10 -4
  423. package/src/prompts/templates/BOOTSTRAP.md +7 -11
  424. package/src/prompts/templates/IDENTITY.md +0 -2
  425. package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
  426. package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
  427. package/src/providers/call-site-routing.ts +33 -9
  428. package/src/providers/connection-model-compat.ts +23 -0
  429. package/src/providers/connection-resolution.ts +39 -20
  430. package/src/providers/fireworks/client.ts +1 -0
  431. package/src/providers/gemini/client.ts +24 -3
  432. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
  433. package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
  434. package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
  435. package/src/providers/inference/auth.ts +0 -8
  436. package/src/providers/inference/connections.ts +3 -66
  437. package/src/providers/inference/resolve-auth.ts +2 -3
  438. package/src/providers/model-catalog.ts +35 -1
  439. package/src/providers/model-intents.ts +3 -3
  440. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  441. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
  442. package/src/providers/openai/chat-completions-provider.ts +110 -12
  443. package/src/providers/openai/codex-models.ts +2 -0
  444. package/src/providers/openai/responses-provider.ts +53 -53
  445. package/src/providers/openrouter/client.ts +13 -8
  446. package/src/providers/provider-send-message.ts +18 -9
  447. package/src/providers/registry.ts +48 -8
  448. package/src/providers/retry.ts +16 -4
  449. package/src/providers/search-provider-catalog.ts +17 -9
  450. package/src/providers/types.ts +9 -0
  451. package/src/runtime/__tests__/agent-wake.test.ts +1 -0
  452. package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
  453. package/src/runtime/access-request-helper.ts +1 -0
  454. package/src/runtime/auth/route-policy.ts +10 -0
  455. package/src/runtime/channel-readiness-service.ts +68 -0
  456. package/src/runtime/channel-reply-delivery.ts +23 -0
  457. package/src/runtime/channel-retry-sweep.ts +47 -14
  458. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  459. package/src/runtime/migrations/vbundle-builder.ts +3 -2
  460. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
  461. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
  462. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  463. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  464. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
  465. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
  466. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +51 -3
  467. package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
  468. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
  469. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
  470. package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
  471. package/src/runtime/routes/acp-routes-list.test.ts +3 -0
  472. package/src/runtime/routes/app-management-routes.ts +111 -4
  473. package/src/runtime/routes/background-wake-routes.ts +188 -20
  474. package/src/runtime/routes/btw-routes.ts +4 -4
  475. package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
  476. package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
  477. package/src/runtime/routes/conversation-list-routes.ts +147 -0
  478. package/src/runtime/routes/conversation-management-routes.ts +39 -14
  479. package/src/runtime/routes/conversation-query-routes.ts +60 -10
  480. package/src/runtime/routes/conversation-routes.ts +186 -140
  481. package/src/runtime/routes/conversations-import-routes.ts +19 -6
  482. package/src/runtime/routes/documents-routes.ts +10 -1
  483. package/src/runtime/routes/group-routes.ts +11 -0
  484. package/src/runtime/routes/home-feed-routes.ts +129 -0
  485. package/src/runtime/routes/identity-intro-cache.ts +61 -16
  486. package/src/runtime/routes/identity-routes.ts +30 -9
  487. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
  488. package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
  489. package/src/runtime/routes/index.ts +2 -0
  490. package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
  491. package/src/runtime/routes/integrations/vercel.ts +15 -0
  492. package/src/runtime/routes/llm-context-normalization.ts +7 -2
  493. package/src/runtime/routes/memory-v3-routes.ts +160 -2
  494. package/src/runtime/routes/migration-routes.ts +20 -13
  495. package/src/runtime/routes/notification-routes.ts +63 -1
  496. package/src/runtime/routes/oauth-commands-routes.ts +6 -1
  497. package/src/runtime/routes/surface-action-routes.ts +1 -38
  498. package/src/runtime/routes/surface-content-routes.ts +12 -5
  499. package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
  500. package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
  501. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
  502. package/src/runtime/slack-dm-text-delivery.ts +177 -0
  503. package/src/runtime/sync/resource-sync-events.ts +1 -1
  504. package/src/runtime/tool-grant-request-helper.ts +1 -0
  505. package/src/schedule/schedule-store.ts +8 -1
  506. package/src/schedule/scheduler.ts +111 -15
  507. package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
  508. package/src/security/encrypted-store.ts +7 -16
  509. package/src/security/store-path-override.ts +61 -0
  510. package/src/signals/user-message.ts +5 -8
  511. package/src/skills/validate-input.ts +177 -0
  512. package/src/subagent/manager.ts +13 -13
  513. package/src/subagent/types.ts +6 -0
  514. package/src/tasks/tool-sanitizer.ts +2 -2
  515. package/src/tools/apps/definitions.ts +35 -21
  516. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
  517. package/src/tools/computer-use/definitions.ts +268 -266
  518. package/src/tools/document/document-tool.ts +131 -8
  519. package/src/tools/execution-target.ts +2 -5
  520. package/src/tools/executor.ts +18 -55
  521. package/src/tools/host-filesystem/edit.test.ts +1 -0
  522. package/src/tools/host-filesystem/read.test.ts +1 -0
  523. package/src/tools/host-filesystem/transfer.test.ts +31 -6
  524. package/src/tools/host-filesystem/write.test.ts +1 -0
  525. package/src/tools/mcp/mcp-tool-factory.ts +0 -2
  526. package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
  527. package/src/tools/network/__tests__/web-search.test.ts +211 -3
  528. package/src/tools/network/managed-search-proxy.ts +183 -0
  529. package/src/tools/network/web-search.ts +199 -44
  530. package/src/tools/policy-context.ts +3 -1
  531. package/src/tools/registry.ts +146 -103
  532. package/src/tools/schedule/create.ts +1 -1
  533. package/src/tools/skills/skill-tool-factory.ts +17 -36
  534. package/src/tools/subagent/spawn.ts +3 -0
  535. package/src/tools/tool-approval-handler.ts +10 -4
  536. package/src/tools/tool-name-aliases.ts +72 -14
  537. package/src/tools/types.ts +17 -15
  538. package/src/tools/ui-surface/definitions.ts +98 -86
  539. package/src/types/onboarding-context.ts +6 -0
  540. package/src/usage/attribution.ts +32 -1
  541. package/src/util/browser.ts +7 -2
  542. package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
  543. package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
  544. package/src/workspace/migrations/registry.ts +4 -0
@@ -97,6 +97,16 @@ function recordReengagementTimestamp(): void {
97
97
  }
98
98
  }
99
99
 
100
+ function refreshBackgroundWakeIntentSoon(reason: string): void {
101
+ void import("../background-wake/publisher.js")
102
+ .then(({ refreshBackgroundWakeIntent }) =>
103
+ refreshBackgroundWakeIntent(reason),
104
+ )
105
+ .catch((err) =>
106
+ log.warn({ err, reason }, "Failed to queue background wake refresh"),
107
+ );
108
+ }
109
+
100
110
  export interface HeartbeatDeps {
101
111
  alerter: (alert: HeartbeatAlert) => void;
102
112
  onConversationCreated?: (info: {
@@ -107,6 +117,19 @@ export interface HeartbeatDeps {
107
117
  getCurrentHour?: () => number;
108
118
  }
109
119
 
120
+ export interface ManagedWakeHeartbeatRunOptions {
121
+ now?: number;
122
+ toleranceMs?: number;
123
+ assumeDue?: boolean;
124
+ scheduledFor?: number;
125
+ }
126
+
127
+ export interface ManagedWakeHeartbeatRunResult {
128
+ due: boolean;
129
+ completed: number;
130
+ skipped: number;
131
+ }
132
+
110
133
  export class HeartbeatService {
111
134
  private static instance?: HeartbeatService;
112
135
 
@@ -153,12 +176,47 @@ export class HeartbeatService {
153
176
  return this._nextRunAt;
154
177
  }
155
178
 
179
+ async runManagedWakeIfDue(
180
+ options: ManagedWakeHeartbeatRunOptions = {},
181
+ ): Promise<ManagedWakeHeartbeatRunResult> {
182
+ const now = options.now ?? Date.now();
183
+ const toleranceMs = options.toleranceMs ?? 0;
184
+ const dueByLocalTimer =
185
+ this._nextRunAt != null && this._nextRunAt <= now + toleranceMs;
186
+
187
+ if (!dueByLocalTimer && options.assumeDue !== true) {
188
+ return { due: false, completed: 0, skipped: 0 };
189
+ }
190
+
191
+ if (!dueByLocalTimer) {
192
+ if (this._pendingRunId) {
193
+ supersedePendingRun(this._pendingRunId);
194
+ this._pendingRunId = null;
195
+ }
196
+ this._nextRunAt = options.scheduledFor ?? now;
197
+ this._pendingRunId = insertPendingHeartbeatRun(this._nextRunAt);
198
+ }
199
+
200
+ const completed = await this.runOnce({ force: false });
201
+
202
+ if (this.cronMode && !this.stopped) {
203
+ this.scheduleNextCronRun(getConfig().heartbeat);
204
+ }
205
+
206
+ return {
207
+ due: true,
208
+ completed: completed ? 1 : 0,
209
+ skipped: completed ? 0 : 1,
210
+ };
211
+ }
212
+
156
213
  start(): void {
157
214
  this.stopped = false;
158
215
  const config = getConfig().heartbeat;
159
216
  if (!config.enabled) {
160
217
  log.info("Heartbeat disabled by config");
161
218
  this._nextRunAt = null;
219
+ refreshBackgroundWakeIntentSoon("heartbeat-disabled");
162
220
  return;
163
221
  }
164
222
  if (this.timer) return;
@@ -283,6 +341,7 @@ export class HeartbeatService {
283
341
  { nextRunAt: new Date(nextRunAt).toISOString(), delayMs },
284
342
  "Heartbeat cron run scheduled",
285
343
  );
344
+ refreshBackgroundWakeIntentSoon("heartbeat-cron-scheduled");
286
345
  } catch (err) {
287
346
  log.warn(
288
347
  { err },
@@ -309,6 +368,7 @@ export class HeartbeatService {
309
368
  this._nextRunAt = null;
310
369
  this.cronMode = false;
311
370
  this.start();
371
+ refreshBackgroundWakeIntentSoon("heartbeat-reconfigured");
312
372
  }
313
373
 
314
374
  /**
@@ -386,6 +446,7 @@ export class HeartbeatService {
386
446
 
387
447
  if (!force && !config.enabled) {
388
448
  if (runId) skipHeartbeatRun(runId, "disabled");
449
+ refreshBackgroundWakeIntentSoon("heartbeat-disabled");
389
450
  return false;
390
451
  }
391
452
 
@@ -510,6 +571,7 @@ export class HeartbeatService {
510
571
  if (!this.cronMode) {
511
572
  this.scheduleNextRun(getConfig().heartbeat.intervalMs);
512
573
  }
574
+ refreshBackgroundWakeIntentSoon("heartbeat-run-complete");
513
575
  }
514
576
  return true;
515
577
  }
@@ -520,6 +582,7 @@ export class HeartbeatService {
520
582
  }
521
583
  this._nextRunAt = Date.now() + intervalMs;
522
584
  this._pendingRunId = insertPendingHeartbeatRun(this._nextRunAt);
585
+ refreshBackgroundWakeIntentSoon("heartbeat-interval-scheduled");
523
586
  }
524
587
 
525
588
  /**
@@ -35,6 +35,7 @@ mock.module("../../runtime/assistant-event-hub.js", () => ({
35
35
  // hub's real shape has more fields. Tests never call them.
36
36
  subscribe: () => () => {},
37
37
  },
38
+ broadcastMessage: async () => {},
38
39
  }));
39
40
 
40
41
  // Dynamic import so the module resolves after the mock above is in
@@ -44,9 +45,11 @@ const {
44
45
  HOME_FEED_FILENAME,
45
46
  HOME_FEED_VERSION,
46
47
  appendFeedItem,
48
+ clearAllConversationIds,
47
49
  getHomeFeedPath,
48
50
  patchFeedItemStatus,
49
51
  readHomeFeed,
52
+ stripConversationIds,
50
53
  } = await import("../feed-writer.js");
51
54
 
52
55
  type FeedItemStatus = "new" | "seen" | "acted_on";
@@ -60,6 +63,7 @@ interface TestFeedItem {
60
63
  timestamp: string;
61
64
  status: FeedItemStatus;
62
65
  expiresAt?: string;
66
+ conversationId?: string;
63
67
  createdAt: string;
64
68
  }
65
69
 
@@ -422,6 +426,163 @@ describe("feed-writer", () => {
422
426
  });
423
427
  });
424
428
 
429
+ describe("stripConversationIds", () => {
430
+ test("removes conversationId from matching items and leaves others untouched", async () => {
431
+ await appendFeedItem(
432
+ makeItem({
433
+ id: "item-a",
434
+ title: "Linked",
435
+ conversationId: "conv-123",
436
+ }),
437
+ );
438
+ await appendFeedItem(
439
+ makeItem({
440
+ id: "item-b",
441
+ title: "Other conv",
442
+ conversationId: "conv-456",
443
+ createdAt: "2026-04-14T12:00:01.000Z",
444
+ }),
445
+ );
446
+ await appendFeedItem(
447
+ makeItem({
448
+ id: "item-c",
449
+ title: "No conv",
450
+ createdAt: "2026-04-14T12:00:02.000Z",
451
+ }),
452
+ );
453
+
454
+ await stripConversationIds("conv-123");
455
+
456
+ const decoded = readFileJson();
457
+ const itemA = decoded.items.find((i) => i.id === "item-a")!;
458
+ const itemB = decoded.items.find((i) => i.id === "item-b")!;
459
+ const itemC = decoded.items.find((i) => i.id === "item-c")!;
460
+
461
+ expect(itemA.conversationId).toBeUndefined();
462
+ expect(itemB.conversationId).toBe("conv-456");
463
+ expect(itemC.conversationId).toBeUndefined();
464
+ });
465
+
466
+ test("returns the count of items modified", async () => {
467
+ await appendFeedItem(
468
+ makeItem({
469
+ id: "m1",
470
+ conversationId: "conv-abc",
471
+ }),
472
+ );
473
+ await appendFeedItem(
474
+ makeItem({
475
+ id: "m2",
476
+ conversationId: "conv-abc",
477
+ createdAt: "2026-04-14T12:00:01.000Z",
478
+ }),
479
+ );
480
+ await appendFeedItem(
481
+ makeItem({
482
+ id: "m3",
483
+ conversationId: "conv-other",
484
+ createdAt: "2026-04-14T12:00:02.000Z",
485
+ }),
486
+ );
487
+
488
+ const count = await stripConversationIds("conv-abc");
489
+ expect(count).toBe(2);
490
+ });
491
+
492
+ test("returns 0 when no items match", async () => {
493
+ await appendFeedItem(
494
+ makeItem({
495
+ id: "no-match",
496
+ conversationId: "conv-xyz",
497
+ }),
498
+ );
499
+
500
+ const count = await stripConversationIds("conv-nonexistent");
501
+ expect(count).toBe(0);
502
+ });
503
+
504
+ test("coalesces with pending appends correctly", async () => {
505
+ // Fire an append and a strip concurrently — both should land in
506
+ // the same coalesced write cycle.
507
+ const appendPromise = appendFeedItem(
508
+ makeItem({
509
+ id: "coalesce-item",
510
+ conversationId: "conv-coalesce",
511
+ }),
512
+ );
513
+ const stripPromise = stripConversationIds("conv-coalesce");
514
+
515
+ await Promise.all([appendPromise, stripPromise]);
516
+
517
+ const decoded = readFileJson();
518
+ const item = decoded.items.find((i) => i.id === "coalesce-item")!;
519
+ // The append lands first, then the strip runs — conversationId
520
+ // should be gone.
521
+ expect(item).toBeDefined();
522
+ expect(item.conversationId).toBeUndefined();
523
+ });
524
+
525
+ test("items retain all other fields after strip", async () => {
526
+ await appendFeedItem(
527
+ makeItem({
528
+ id: "retain-fields",
529
+ title: "Keep me",
530
+ summary: "Important summary",
531
+ conversationId: "conv-strip",
532
+ priority: 75,
533
+ status: "seen",
534
+ }),
535
+ );
536
+
537
+ await stripConversationIds("conv-strip");
538
+
539
+ const decoded = readFileJson();
540
+ const item = decoded.items.find((i) => i.id === "retain-fields")!;
541
+ expect(item.conversationId).toBeUndefined();
542
+ expect(item.id).toBe("retain-fields");
543
+ expect(item.title).toBe("Keep me");
544
+ expect(item.summary).toBe("Important summary");
545
+ expect(item.priority).toBe(75);
546
+ expect(item.status).toBe("seen");
547
+ expect(item.type).toBe("notification");
548
+ });
549
+ });
550
+
551
+ describe("clearAllConversationIds", () => {
552
+ test("strips conversationId from all items that have one", async () => {
553
+ await appendFeedItem(
554
+ makeItem({
555
+ id: "all-1",
556
+ conversationId: "conv-aaa",
557
+ }),
558
+ );
559
+ await appendFeedItem(
560
+ makeItem({
561
+ id: "all-2",
562
+ conversationId: "conv-bbb",
563
+ createdAt: "2026-04-14T12:00:01.000Z",
564
+ }),
565
+ );
566
+ await appendFeedItem(
567
+ makeItem({
568
+ id: "all-3",
569
+ title: "No conv link",
570
+ createdAt: "2026-04-14T12:00:02.000Z",
571
+ }),
572
+ );
573
+
574
+ const count = await clearAllConversationIds();
575
+ expect(count).toBe(2);
576
+
577
+ const decoded = readFileJson();
578
+ for (const item of decoded.items) {
579
+ expect(item.conversationId).toBeUndefined();
580
+ }
581
+ // All three items still exist.
582
+ expect(decoded.items).toHaveLength(3);
583
+ });
584
+ });
585
+
425
586
  describe("SSE publish", () => {
426
587
  test("publishes home_feed_updated with correct newItemCount", async () => {
427
588
  await appendFeedItem(
@@ -11,6 +11,7 @@ mock.module("../../runtime/assistant-event-hub.js", () => ({
11
11
  publish: publishSpy,
12
12
  subscribe: () => () => {},
13
13
  },
14
+ broadcastMessage: async () => {},
14
15
  }));
15
16
 
16
17
  const { emitPostConnectNudge } = await import("../post-connect-feed.js");
@@ -1,22 +1,12 @@
1
- import { describe, expect, mock, test } from "bun:test";
1
+ import { afterEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
3
  // ─── Mocks ─────────────────────────────────────────────────────────────
4
4
 
5
- let mockConnectedProviders = new Set<string>();
6
-
7
- mock.module("../../oauth/oauth-store.js", () => ({
8
- listProviders: () => [
9
- { provider: "google" },
10
- { provider: "slack" },
11
- { provider: "notion" },
12
- { provider: "linear" },
13
- { provider: "github" },
14
- ],
15
- }));
5
+ const mockIntegrationSummary = "Gmail ✓ | Slack ✓ | Twilio ✗ | Telegram ✗";
6
+ let mockSidechainText = "";
16
7
 
17
8
  mock.module("../../schedule/integration-status.js", () => ({
18
- isOAuthProviderConnected: async (provider: string) =>
19
- mockConnectedProviders.has(provider),
9
+ formatIntegrationSummary: async () => mockIntegrationSummary,
20
10
  }));
21
11
 
22
12
  mock.module("../../util/logger.js", () => ({
@@ -31,7 +21,7 @@ mock.module("../../config/loader.js", () => ({
31
21
  }));
32
22
 
33
23
  mock.module("../../config/llm-resolver.js", () => ({
34
- resolveCallSiteConfig: () => ({ provider: "mock" }),
24
+ resolveCallSiteConfig: () => ({ provider: "mock", maxTokens: 256 }),
35
25
  }));
36
26
 
37
27
  mock.module("../../providers/provider-send-message.js", () => ({
@@ -51,70 +41,76 @@ mock.module("../../prompts/system-prompt.js", () => ({
51
41
  }));
52
42
 
53
43
  mock.module("../../runtime/btw-sidechain.js", () => ({
54
- runBtwSidechain: async () => ({ text: "" }),
44
+ runBtwSidechain: async () => ({ text: mockSidechainText }),
55
45
  }));
56
46
 
57
- const { getSuggestedPrompts } = await import("../suggested-prompts.js");
47
+ mock.module("../../runtime/assistant-event.js", () => ({
48
+ buildAssistantEvent: (e: unknown) => e,
49
+ }));
50
+
51
+ mock.module("../../runtime/assistant-event-hub.js", () => ({
52
+ assistantEventHub: { publish: async () => {} },
53
+ }));
54
+
55
+ const {
56
+ getSuggestedPrompts,
57
+ refreshAssistantSuggestedPrompts,
58
+ invalidateAssistantSuggestedPromptsCache,
59
+ } = await import("../suggested-prompts.js");
58
60
 
59
61
  // ─── Tests ─────────────────────────────────────────────────────────────
60
62
 
61
63
  describe("getSuggestedPrompts", () => {
62
- test("shows 'Connect X' prompts when providers are disconnected", async () => {
63
- mockConnectedProviders = new Set();
64
+ afterEach(() => {
65
+ invalidateAssistantSuggestedPromptsCache();
66
+ mockSidechainText = "";
67
+ });
64
68
 
69
+ test("returns empty array before cache is populated", async () => {
65
70
  const prompts = await getSuggestedPrompts();
66
- const ids = prompts.map((p) => p.id);
67
-
68
- expect(ids).toContain("connect-google");
69
- expect(ids).toContain("connect-slack");
70
- expect(prompts.find((p) => p.id === "connect-google")!.label).toBe(
71
- "Connect Gmail",
72
- );
71
+ expect(prompts).toEqual([]);
73
72
  });
74
73
 
75
- test("shows email management prompts when Google is connected", async () => {
76
- mockConnectedProviders = new Set(["google"]);
74
+ test("returns LLM-generated prompts after refresh", async () => {
75
+ mockSidechainText = JSON.stringify([
76
+ { label: "Triage my inbox", prompt: "Help me triage my inbox" },
77
+ { label: "Check meetings", prompt: "What meetings do I have today?" },
78
+ ]);
77
79
 
80
+ await refreshAssistantSuggestedPrompts();
78
81
  const prompts = await getSuggestedPrompts();
79
- const ids = prompts.map((p) => p.id);
80
-
81
- // Should NOT show "Connect Gmail"
82
- expect(ids).not.toContain("connect-google");
83
-
84
- // Should show management prompts
85
- expect(ids).toContain("manage-google-triage-my-inbox");
86
- expect(ids).toContain("manage-google-summarize-today's-emails");
87
-
88
- const triage = prompts.find(
89
- (p) => p.id === "manage-google-triage-my-inbox",
90
- );
91
- expect(triage).toBeDefined();
92
- expect(triage!.label).toBe("Triage my inbox");
93
- expect(triage!.icon).toBe("mail");
94
- expect(triage!.source).toBe("deterministic");
82
+
83
+ expect(prompts).toHaveLength(2);
84
+ expect(prompts[0]!.label).toBe("Triage my inbox");
85
+ expect(prompts[0]!.source).toBe("assistant");
86
+ expect(prompts[1]!.label).toBe("Check meetings");
95
87
  });
96
88
 
97
- test("still shows Connect prompts for disconnected providers alongside management prompts", async () => {
98
- mockConnectedProviders = new Set(["google"]);
89
+ test("invalidation clears the cache", async () => {
90
+ mockSidechainText = JSON.stringify([
91
+ { label: "Do stuff", prompt: "Do stuff for me" },
92
+ ]);
99
93
 
100
- const prompts = await getSuggestedPrompts();
101
- const ids = prompts.map((p) => p.id);
94
+ await refreshAssistantSuggestedPrompts();
95
+ expect(await getSuggestedPrompts()).toHaveLength(1);
102
96
 
103
- // Gmail management prompts
104
- expect(ids).toContain("manage-google-triage-my-inbox");
105
- // Slack still disconnected
106
- expect(ids).toContain("connect-slack");
97
+ invalidateAssistantSuggestedPromptsCache();
98
+ expect(await getSuggestedPrompts()).toEqual([]);
107
99
  });
108
100
 
109
- test("providers without connectedPrompts show nothing when connected", async () => {
110
- mockConnectedProviders = new Set(["slack"]);
101
+ test("handles empty LLM response gracefully", async () => {
102
+ mockSidechainText = "";
111
103
 
104
+ await refreshAssistantSuggestedPrompts();
112
105
  const prompts = await getSuggestedPrompts();
113
- const ids = prompts.map((p) => p.id);
106
+ expect(prompts).toEqual([]);
107
+ });
108
+
109
+ test("handles malformed LLM response gracefully", async () => {
110
+ mockSidechainText = "not valid json at all";
114
111
 
115
- // No connect prompt since connected
116
- expect(ids).not.toContain("connect-slack");
117
- // No management prompts since Slack doesn't define any
118
- expect(ids.filter((id) => id.startsWith("manage-slack"))).toHaveLength(0);
112
+ await refreshAssistantSuggestedPrompts();
113
+ const prompts = await getSuggestedPrompts();
114
+ expect(prompts).toEqual([]);
119
115
  });
120
116
  });
@@ -44,6 +44,7 @@ import { getDataDir } from "../util/platform.js";
44
44
  import {
45
45
  type FeedItem,
46
46
  type FeedItemStatus,
47
+ type FeedItemUrgency,
47
48
  type HomeFeedFile,
48
49
  parseFeedFile,
49
50
  } from "./feed-types.js";
@@ -155,6 +156,72 @@ export async function patchFeedItemStatus(
155
156
  return resultPromise;
156
157
  }
157
158
 
159
+ /**
160
+ * Patch the user-editable copy fields on a single feed item by id.
161
+ *
162
+ * Returns the updated `FeedItem`, or `null` if no item with the given
163
+ * id exists. Used by the `assistant notifications edit` flow. Patches
164
+ * are applied inside the same coalescing queue as appends and status
165
+ * patches so overlapping writes don't race.
166
+ *
167
+ * Only fields explicitly present on `patch` are touched. Pass an empty
168
+ * object and the call is a no-op that returns the existing item (or
169
+ * `null` if the id isn't on disk).
170
+ */
171
+ export interface FeedItemContentPatch {
172
+ title?: string;
173
+ summary?: string;
174
+ urgency?: FeedItemUrgency;
175
+ status?: FeedItemStatus;
176
+ }
177
+
178
+ export async function patchFeedItemContent(
179
+ id: string,
180
+ patch: FeedItemContentPatch,
181
+ ): Promise<FeedItem | null> {
182
+ let resolveResult!: (value: FeedItem | null) => void;
183
+ const resultPromise = new Promise<FeedItem | null>((resolve) => {
184
+ resolveResult = resolve;
185
+ });
186
+ pendingContentPatches.push({ id, patch, resolve: resolveResult });
187
+ void scheduleWrite();
188
+ return resultPromise;
189
+ }
190
+
191
+ /**
192
+ * Remove the `conversationId` field from all feed items that reference
193
+ * the given conversation. Returns the number of items modified.
194
+ *
195
+ * This is the daemon-side cleanup path invoked when a conversation is
196
+ * deleted — it prevents "Go to Thread" buttons from linking to a
197
+ * conversation that no longer exists. Goes through the same coalescing
198
+ * queue as appends and patches so there are no file-I/O races.
199
+ */
200
+ export async function stripConversationIds(
201
+ conversationId: string,
202
+ ): Promise<number> {
203
+ let resolveResult!: (count: number) => void;
204
+ const resultPromise = new Promise<number>((resolve) => {
205
+ resolveResult = resolve;
206
+ });
207
+ pendingStrips.push({ conversationId, resolve: resolveResult });
208
+ void scheduleWrite();
209
+ return resultPromise;
210
+ }
211
+
212
+ /**
213
+ * Remove the `conversationId` field from ALL feed items that have one.
214
+ * Returns the number of items modified.
215
+ *
216
+ * Used by the clear-all-conversations flow to bulk-invalidate every
217
+ * "Go to Thread" link at once. Uses the same coalescing queue with a
218
+ * `"*"` sentinel that the strip loop in `runWrite` treats as "match
219
+ * all items with a conversationId".
220
+ */
221
+ export async function clearAllConversationIds(): Promise<number> {
222
+ return stripConversationIds("*");
223
+ }
224
+
158
225
  // ─── Internal: coalescing queue ────────────────────────────────────────
159
226
 
160
227
  /**
@@ -168,6 +235,15 @@ const pendingPatches: Array<{
168
235
  status: FeedItemStatus;
169
236
  resolve: (value: FeedItem | null) => void;
170
237
  }> = [];
238
+ const pendingContentPatches: Array<{
239
+ id: string;
240
+ patch: FeedItemContentPatch;
241
+ resolve: (value: FeedItem | null) => void;
242
+ }> = [];
243
+ const pendingStrips: Array<{
244
+ conversationId: string;
245
+ resolve: (count: number) => void;
246
+ }> = [];
171
247
 
172
248
  let writeInFlight: Promise<void> | null = null;
173
249
  let writeDirty = false;
@@ -206,6 +282,11 @@ function scheduleWrite(): Promise<void> {
206
282
  async function runWrite(): Promise<void> {
207
283
  const appendsToApply = pendingAppends.splice(0, pendingAppends.length);
208
284
  const patchesToApply = pendingPatches.splice(0, pendingPatches.length);
285
+ const contentPatchesToApply = pendingContentPatches.splice(
286
+ 0,
287
+ pendingContentPatches.length,
288
+ );
289
+ const stripsToApply = pendingStrips.splice(0, pendingStrips.length);
209
290
 
210
291
  const current = readHomeFeed();
211
292
  let items = current.items.slice();
@@ -233,6 +314,57 @@ async function runWrite(): Promise<void> {
233
314
  patchResults.push({ resolve: patch.resolve, value: updated });
234
315
  }
235
316
 
317
+ const contentPatchResults: Array<{
318
+ resolve: (v: FeedItem | null) => void;
319
+ value: FeedItem | null;
320
+ }> = [];
321
+ for (const { id, patch, resolve } of contentPatchesToApply) {
322
+ const idx = items.findIndex((i) => i.id === id);
323
+ if (idx === -1) {
324
+ contentPatchResults.push({ resolve, value: null });
325
+ continue;
326
+ }
327
+ const existing = items[idx]!;
328
+ const updated: FeedItem = { ...existing };
329
+ if (patch.title !== undefined) {
330
+ const trimmed = patch.title.trim();
331
+ if (trimmed.length === 0) {
332
+ delete updated.title;
333
+ } else {
334
+ updated.title = trimmed;
335
+ }
336
+ }
337
+ if (patch.summary !== undefined) {
338
+ updated.summary = patch.summary;
339
+ }
340
+ if (patch.urgency !== undefined) {
341
+ updated.urgency = patch.urgency;
342
+ }
343
+ if (patch.status !== undefined) {
344
+ updated.status = patch.status;
345
+ }
346
+ items[idx] = updated;
347
+ contentPatchResults.push({ resolve, value: updated });
348
+ }
349
+
350
+ // Strip conversationId from matching items.
351
+ const stripResults: Array<{
352
+ resolve: (count: number) => void;
353
+ count: number;
354
+ }> = [];
355
+ for (const strip of stripsToApply) {
356
+ let count = 0;
357
+ for (let i = 0; i < items.length; i++) {
358
+ const matchAll = strip.conversationId === "*";
359
+ const matchOne = items[i]!.conversationId === strip.conversationId;
360
+ if (matchAll ? items[i]!.conversationId != null : matchOne) {
361
+ items[i] = { ...items[i]!, conversationId: undefined };
362
+ count++;
363
+ }
364
+ }
365
+ stripResults.push({ resolve: strip.resolve, count });
366
+ }
367
+
236
368
  items.sort(compareFeedItems);
237
369
 
238
370
  const updatedAt = new Date().toISOString();
@@ -258,17 +390,24 @@ async function runWrite(): Promise<void> {
258
390
  publishHomeFeedUpdated(updatedAt, newItemCount);
259
391
  }
260
392
 
261
- // Resolve pending patch promises AFTER we've emitted the SSE event
262
- // so callers awaiting `patchFeedItemStatus` observe a fully
263
- // consistent world: the on-disk file, the SSE event, and the
264
- // returned `FeedItem` all reflect the same write.
393
+ // Resolve pending patch and strip promises AFTER we've emitted the
394
+ // SSE event so callers awaiting `patchFeedItemStatus` or
395
+ // `stripConversationIds` observe a fully consistent world: the
396
+ // on-disk file, the SSE event, and the returned value all reflect
397
+ // the same write.
265
398
  //
266
- // If the write failed, resolve all patch promises with `null` the
267
- // state was not persisted, and callers (e.g. HTTP route handlers)
268
- // must not report success when the underlying write failed.
399
+ // If the write failed, resolve patch promises with `null` and strip
400
+ // promises with `0` — the state was not persisted, and callers must
401
+ // not report success when the underlying write failed.
269
402
  for (const { resolve, value } of patchResults) {
270
403
  resolve(wrote ? value : null);
271
404
  }
405
+ for (const { resolve, value } of contentPatchResults) {
406
+ resolve(wrote ? value : null);
407
+ }
408
+ for (const { resolve, count } of stripResults) {
409
+ resolve(wrote ? count : 0);
410
+ }
272
411
  }
273
412
 
274
413
  /**