@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
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Edit an already-sent notification.
3
+ *
4
+ * Patches the home-feed entry the user actually sees, then attempts a
5
+ * best-effort update of any per-channel deliveries that support
6
+ * in-place edits (Slack via chat.update today). Feed-only fields
7
+ * (`urgency`, `status`) skip the channel hop — channel messages don't
8
+ * carry that metadata, only body/title.
9
+ */
10
+
11
+ import {
12
+ type FeedItem,
13
+ type FeedItemStatus,
14
+ type FeedItemUrgency,
15
+ } from "../home/feed-types.js";
16
+ import { patchFeedItemContent } from "../home/feed-writer.js";
17
+ import { getLogger } from "../util/logger.js";
18
+ import { findLatestDecisionByEventId } from "./decisions-store.js";
19
+ import {
20
+ findDeliveriesByDecisionId,
21
+ type NotificationDeliveryRow,
22
+ updateDeliveryRenderedCopy,
23
+ } from "./deliveries-store.js";
24
+ import { getBroadcaster } from "./emit-signal.js";
25
+ import type { NotificationChannel } from "./types.js";
26
+
27
+ const log = getLogger("edit-notification");
28
+
29
+ /** Prefix used by `home-feed-side-effect` when minting feed item ids. */
30
+ export const FEED_ITEM_ID_PREFIX = "notif:";
31
+
32
+ export interface EditNotificationParams {
33
+ /** Feed item id (`notif:<uuid>`) or bare signal uuid. */
34
+ id: string;
35
+ title?: string;
36
+ body?: string;
37
+ urgency?: FeedItemUrgency;
38
+ status?: FeedItemStatus;
39
+ }
40
+
41
+ export type ChannelEditOutcome =
42
+ | "updated"
43
+ | "unsupported"
44
+ | "skipped"
45
+ | "failed";
46
+
47
+ export interface ChannelEditResult {
48
+ channel: NotificationChannel;
49
+ deliveryId: string;
50
+ outcome: ChannelEditOutcome;
51
+ /** Reason for skip/failure when `outcome` is not `"updated"`. */
52
+ reason?: string;
53
+ }
54
+
55
+ export interface EditNotificationResult {
56
+ feedItem: FeedItem;
57
+ channels: ChannelEditResult[];
58
+ }
59
+
60
+ /**
61
+ * Normalize a user-supplied id into the canonical feed-item form
62
+ * (`notif:<uuid>`). Accepts either the full prefixed id or a bare uuid.
63
+ */
64
+ export function normalizeFeedItemId(id: string): string {
65
+ const trimmed = id.trim();
66
+ if (trimmed.startsWith(FEED_ITEM_ID_PREFIX)) return trimmed;
67
+ return `${FEED_ITEM_ID_PREFIX}${trimmed}`;
68
+ }
69
+
70
+ /** Strip the `notif:` prefix to recover the original signal/event id. */
71
+ export function feedItemIdToSignalId(feedItemId: string): string {
72
+ return feedItemId.startsWith(FEED_ITEM_ID_PREFIX)
73
+ ? feedItemId.slice(FEED_ITEM_ID_PREFIX.length)
74
+ : feedItemId;
75
+ }
76
+
77
+ /**
78
+ * Apply an edit to a previously-sent notification.
79
+ *
80
+ * Returns the updated feed item plus per-channel update outcomes.
81
+ * Resolves to `null` when the feed item id isn't on disk so callers
82
+ * can surface a clear "not found" to the user.
83
+ */
84
+ export async function editNotification(
85
+ params: EditNotificationParams,
86
+ ): Promise<EditNotificationResult | null> {
87
+ const feedItemId = normalizeFeedItemId(params.id);
88
+
89
+ const feedItem = await patchFeedItemContent(feedItemId, {
90
+ title: params.title,
91
+ summary: params.body,
92
+ urgency: params.urgency,
93
+ status: params.status,
94
+ });
95
+ if (!feedItem) {
96
+ log.warn({ feedItemId }, "Edit requested for unknown feed item");
97
+ return null;
98
+ }
99
+
100
+ // Only edit channel messages when the user-visible text changed.
101
+ // Urgency/status are feed-only — pushing a channel update for those
102
+ // alone would re-deliver the same body and confuse the recipient.
103
+ const shouldUpdateChannels =
104
+ params.title !== undefined || params.body !== undefined;
105
+ if (!shouldUpdateChannels) {
106
+ return { feedItem, channels: [] };
107
+ }
108
+
109
+ const signalId = feedItemIdToSignalId(feedItemId);
110
+ const decision = findLatestDecisionByEventId(signalId);
111
+ if (!decision) {
112
+ log.info(
113
+ { feedItemId, signalId },
114
+ "Feed item has no persisted decision — skipping channel updates",
115
+ );
116
+ return { feedItem, channels: [] };
117
+ }
118
+
119
+ const deliveries = findDeliveriesByDecisionId(decision.id);
120
+ const channels = await updateChannelDeliveries(deliveries, {
121
+ title: params.title,
122
+ body: params.body,
123
+ });
124
+
125
+ return { feedItem, channels };
126
+ }
127
+
128
+ async function updateChannelDeliveries(
129
+ deliveries: NotificationDeliveryRow[],
130
+ patch: { title?: string; body?: string },
131
+ ): Promise<ChannelEditResult[]> {
132
+ const broadcaster = getBroadcaster();
133
+ const results: ChannelEditResult[] = [];
134
+
135
+ for (const delivery of deliveries) {
136
+ const channel = delivery.channel as NotificationChannel;
137
+ if (delivery.status !== "sent") {
138
+ results.push({
139
+ channel,
140
+ deliveryId: delivery.id,
141
+ outcome: "skipped",
142
+ reason: `delivery status is ${delivery.status}`,
143
+ });
144
+ continue;
145
+ }
146
+
147
+ const adapter = broadcaster.getAdapter(channel);
148
+ if (!adapter?.update) {
149
+ results.push({
150
+ channel,
151
+ deliveryId: delivery.id,
152
+ outcome: "unsupported",
153
+ reason: `${channel} adapter does not support in-place edits`,
154
+ });
155
+ continue;
156
+ }
157
+
158
+ try {
159
+ const result = await adapter.update(
160
+ {
161
+ deliveryId: delivery.id,
162
+ destination: delivery.destination,
163
+ messageId: delivery.messageId,
164
+ },
165
+ patch,
166
+ );
167
+ if (!result.success) {
168
+ results.push({
169
+ channel,
170
+ deliveryId: delivery.id,
171
+ outcome: "failed",
172
+ reason: result.error ?? "unknown error",
173
+ });
174
+ continue;
175
+ }
176
+ updateDeliveryRenderedCopy(delivery.id, {
177
+ renderedTitle: patch.title,
178
+ renderedBody: patch.body,
179
+ });
180
+ results.push({
181
+ channel,
182
+ deliveryId: delivery.id,
183
+ outcome: "updated",
184
+ });
185
+ } catch (err) {
186
+ const message = err instanceof Error ? err.message : String(err);
187
+ log.error(
188
+ { err, channel, deliveryId: delivery.id },
189
+ "Channel adapter update threw",
190
+ );
191
+ results.push({
192
+ channel,
193
+ deliveryId: delivery.id,
194
+ outcome: "failed",
195
+ reason: message,
196
+ });
197
+ }
198
+ }
199
+
200
+ return results;
201
+ }
@@ -51,7 +51,7 @@ const log = getLogger("emit-signal");
51
51
 
52
52
  let broadcasterInstance: NotificationBroadcaster | null = null;
53
53
 
54
- function getBroadcaster(): NotificationBroadcaster {
54
+ export function getBroadcaster(): NotificationBroadcaster {
55
55
  if (!broadcasterInstance) {
56
56
  broadcasterInstance = new NotificationBroadcaster([
57
57
  new VellumAdapter(broadcastMessage),
@@ -188,6 +188,15 @@ export interface EmitSignalParams<TEventName extends string = string> {
188
188
  source?: string;
189
189
  conversationType?: ConversationCreateType;
190
190
  };
191
+ /**
192
+ * When true, the vellum-channel delivery materializes a fresh conversation
193
+ * to host the notification (and any follow-up interaction). Set this only
194
+ * for flows where the conversation IS the interaction surface — e.g.
195
+ * guardian.question, tool grant requests, ingress access requests. Passive
196
+ * notifications leave this unset; they surface via the home feed and link
197
+ * back to their originating conversation via `sourceContextId`.
198
+ */
199
+ requiresConversation?: boolean;
191
200
  }
192
201
 
193
202
  export interface EmitSignalResult {
@@ -223,6 +232,7 @@ export async function emitNotificationSignal<TEventName extends string>(
223
232
  routingHints: params.routingHints,
224
233
  conversationAffinityHint: params.conversationAffinityHint,
225
234
  conversationMetadata: params.conversationMetadata,
235
+ requiresConversation: params.requiresConversation,
226
236
  };
227
237
 
228
238
  try {
@@ -213,4 +213,14 @@ export interface NotificationSignal<TEventName extends string = string> {
213
213
  source?: string;
214
214
  conversationType?: ConversationCreateType;
215
215
  };
216
+ /**
217
+ * When true, the vellum-channel delivery materializes a fresh conversation
218
+ * to host the notification (and any follow-up interaction). Set this only
219
+ * for flows where the conversation IS the interaction surface — e.g.
220
+ * guardian.question, tool grant requests, ingress access requests. Passive
221
+ * notifications (scheduler fired, watcher alert, activity complete, etc.)
222
+ * should leave this unset; they surface via the home feed and link back
223
+ * to their originating conversation via `sourceContextId`.
224
+ */
225
+ requiresConversation?: boolean;
216
226
  }
@@ -45,6 +45,12 @@ export interface NotificationDeliveryResult {
45
45
  export interface DeliveryResult {
46
46
  success: boolean;
47
47
  error?: string;
48
+ /**
49
+ * Channel-native message identifier captured at send time (e.g. Slack `ts`).
50
+ * Persisted onto `notification_deliveries.messageId` so later edits can
51
+ * target the same message via `ChannelAdapter.update()`.
52
+ */
53
+ messageId?: string;
48
54
  }
49
55
 
50
56
  /** Resolved destination for a specific channel. */
@@ -91,6 +97,16 @@ export interface ChannelDeliveryPayload {
91
97
  urgency: AttentionHints["urgency"];
92
98
  }
93
99
 
100
+ /**
101
+ * Patch supplied when an already-delivered notification is edited.
102
+ * Adapters only need to act on `title` and `body` — feed-only fields
103
+ * like `urgency` and `status` are handled by the home-feed patch.
104
+ */
105
+ export interface ChannelUpdatePayload {
106
+ title?: string;
107
+ body?: string;
108
+ }
109
+
94
110
  /** Interface that each channel adapter must implement. */
95
111
  export interface ChannelAdapter {
96
112
  channel: NotificationChannel;
@@ -98,6 +114,27 @@ export interface ChannelAdapter {
98
114
  payload: ChannelDeliveryPayload,
99
115
  destination: ChannelDestination,
100
116
  ): Promise<DeliveryResult>;
117
+ /**
118
+ * Optional: edit a previously-sent message in place. Channels that
119
+ * cannot edit (push, email, SMS) omit this and the router treats
120
+ * them as `"unsupported"`. Adapters that implement it use
121
+ * `delivery.messageId` (captured at send time) as the channel-native
122
+ * handle for the in-place update.
123
+ */
124
+ update?(
125
+ delivery: ChannelUpdateContext,
126
+ patch: ChannelUpdatePayload,
127
+ ): Promise<DeliveryResult>;
128
+ }
129
+
130
+ /** Per-delivery state an adapter needs to update a previously-sent message. */
131
+ export interface ChannelUpdateContext {
132
+ /** notification_deliveries.id */
133
+ deliveryId: string;
134
+ /** Channel-native destination (e.g. Slack chat id). */
135
+ destination: string;
136
+ /** Channel-native message identifier captured at send time. */
137
+ messageId: string | null;
101
138
  }
102
139
 
103
140
  // -- Decision engine output ---------------------------------------------------
@@ -28,7 +28,7 @@ mock.module("../util/logger.js", () => ({
28
28
  // Use encrypted backend with a temp store path
29
29
  // ---------------------------------------------------------------------------
30
30
 
31
- import { _setStorePath } from "../security/encrypted-store.js";
31
+ import { setStorePathForTesting } from "../__tests__/encrypted-store-test-helpers.js";
32
32
  import { _resetBackend } from "../security/secure-keys.js";
33
33
 
34
34
  const TEST_DIR = join(
@@ -130,6 +130,7 @@ mock.module("./oauth-store.js", () => ({
130
130
  // Imports (after mocks)
131
131
  // ---------------------------------------------------------------------------
132
132
 
133
+ import { credentialKey } from "../security/credential-key.js";
133
134
  import { setSecureKeyAsync } from "../security/secure-keys.js";
134
135
  import {
135
136
  _resetInflightRefreshes,
@@ -163,7 +164,7 @@ afterAll(() => {
163
164
  });
164
165
 
165
166
  beforeEach(() => {
166
- _setStorePath(STORE_PATH);
167
+ setStorePathForTesting(STORE_PATH);
167
168
  _setMetadataPath(join(TEST_DIR, "metadata.json"));
168
169
  _resetBackend();
169
170
  _resetRefreshBreakers();
@@ -251,11 +252,22 @@ function createConnection(service = "google"): BYOOAuthConnection {
251
252
  return new BYOOAuthConnection({
252
253
  id: `conn-${service}`,
253
254
  provider: service,
254
- baseUrl: "https://gmail.googleapis.com/gmail/v1/users/me",
255
+ baseUrl:
256
+ service === "telegram"
257
+ ? "https://api.telegram.org"
258
+ : "https://gmail.googleapis.com/gmail/v1/users/me",
255
259
  accountInfo: null,
256
260
  });
257
261
  }
258
262
 
263
+ async function setupTelegramCredential() {
264
+ await setupCredential("telegram");
265
+ await setSecureKeyAsync(
266
+ credentialKey("telegram", "bot_token"),
267
+ "telegram-test-token",
268
+ );
269
+ }
270
+
259
271
  // ---------------------------------------------------------------------------
260
272
  // Tests
261
273
  // ---------------------------------------------------------------------------
@@ -437,6 +449,58 @@ describe("BYOOAuthConnection", () => {
437
449
  expect(headers.get("X-Custom-Header")).toBe("custom-value");
438
450
  expect(headers.get("Authorization")).toBe("Bearer test-access-token");
439
451
  });
452
+
453
+ test("uses Telegram Bot API token URL format without Bearer auth", async () => {
454
+ await setupTelegramCredential();
455
+ const conn = createConnection("telegram");
456
+
457
+ const result = await conn.request({
458
+ method: "GET",
459
+ path: "/getMe",
460
+ });
461
+
462
+ expect(result.status).toBe(200);
463
+ expect(mockFetch).toHaveBeenCalledTimes(1);
464
+
465
+ const [url, init] = mockFetch.mock.calls[0];
466
+ expect(url).toBe("https://api.telegram.org/bottelegram-test-token/getMe");
467
+ const headers = (init as RequestInit).headers as Headers;
468
+ expect(headers.has("Authorization")).toBe(false);
469
+ });
470
+
471
+ test("does not double-prefix Telegram paths that already include bot token", async () => {
472
+ await setupTelegramCredential();
473
+ const conn = createConnection("telegram");
474
+
475
+ await conn.request({
476
+ method: "GET",
477
+ path: "/bottelegram-test-token/getMe",
478
+ });
479
+
480
+ const [url, init] = mockFetch.mock.calls[0];
481
+ expect(url).toBe("https://api.telegram.org/bottelegram-test-token/getMe");
482
+ const headers = (init as RequestInit).headers as Headers;
483
+ expect(headers.has("Authorization")).toBe(false);
484
+ });
485
+
486
+ test("returns Telegram 401 responses without OAuth refresh", async () => {
487
+ await setupTelegramCredential();
488
+ const conn = createConnection("telegram");
489
+ const refreshCallsBefore = mockRefreshOAuth2Token.mock.calls.length;
490
+
491
+ globalThis.fetch = mock(() =>
492
+ Promise.resolve(new Response("Unauthorized", { status: 401 })),
493
+ ) as unknown as typeof fetch;
494
+
495
+ const result = await conn.request({
496
+ method: "GET",
497
+ path: "/getMe",
498
+ });
499
+
500
+ expect(result.status).toBe(401);
501
+ expect(result.body).toBe("Unauthorized");
502
+ expect(mockRefreshOAuth2Token.mock.calls.length).toBe(refreshCallsBefore);
503
+ });
440
504
  });
441
505
 
442
506
  describe("proactive token refresh", () => {
@@ -46,7 +46,11 @@ export class BYOOAuthConnection implements OAuthConnection {
46
46
  this.provider,
47
47
  async (token) => {
48
48
  const effectiveBaseUrl = req.baseUrl ?? this.baseUrl;
49
- let fullUrl = `${effectiveBaseUrl}${req.path}`;
49
+ const isTelegram = this.provider === "telegram";
50
+ const requestPath = isTelegram
51
+ ? buildTelegramBotApiPath(req.path, token)
52
+ : req.path;
53
+ let fullUrl = `${effectiveBaseUrl}${requestPath}`;
50
54
 
51
55
  if (req.query && Object.keys(req.query).length > 0) {
52
56
  const params = new URLSearchParams();
@@ -60,8 +64,12 @@ export class BYOOAuthConnection implements OAuthConnection {
60
64
  fullUrl += `?${params.toString()}`;
61
65
  }
62
66
 
67
+ const logUrl = isTelegram
68
+ ? redactTelegramBotTokenFromUrl(fullUrl, token)
69
+ : fullUrl;
70
+
63
71
  log.debug(
64
- { method: req.method, url: fullUrl, provider: this.provider },
72
+ { method: req.method, url: logUrl, provider: this.provider },
65
73
  "Making authenticated request",
66
74
  );
67
75
 
@@ -76,18 +84,23 @@ export class BYOOAuthConnection implements OAuthConnection {
76
84
  headers.set(key, value);
77
85
  }
78
86
  }
79
- headers.set("Authorization", `Bearer ${token}`);
87
+ if (!isTelegram) {
88
+ headers.set("Authorization", `Bearer ${token}`);
89
+ }
80
90
 
81
91
  const resp = await fetch(fullUrl, {
82
92
  method: req.method,
83
93
  headers,
84
94
  body: req.body ? JSON.stringify(req.body) : undefined,
85
95
  signal: req.signal
86
- ? AbortSignal.any([req.signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)])
96
+ ? AbortSignal.any([
97
+ req.signal,
98
+ AbortSignal.timeout(REQUEST_TIMEOUT_MS),
99
+ ])
87
100
  : AbortSignal.timeout(REQUEST_TIMEOUT_MS),
88
101
  });
89
102
 
90
- if (resp.status === 401) {
103
+ if (resp.status === 401 && !isTelegram) {
91
104
  // Throw with a status property so withValidToken detects the 401
92
105
  // and triggers its refresh-and-retry logic.
93
106
  const err = new Error(`HTTP 401 from ${this.provider}`);
@@ -108,6 +121,20 @@ export class BYOOAuthConnection implements OAuthConnection {
108
121
  }
109
122
  }
110
123
 
124
+ function buildTelegramBotApiPath(path: string, token: string): string {
125
+ if (path.startsWith("/bot")) return path;
126
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
127
+ return `/bot${token}${normalizedPath}`;
128
+ }
129
+
130
+ function redactTelegramBotTokenFromUrl(url: string, token: string): string {
131
+ const redactedStoredToken = url.split(token).join("[REDACTED]");
132
+ return redactedStoredToken.replace(
133
+ /\/bot[^/?#]+(?=\/|[?#]|$)/g,
134
+ "/bot[REDACTED]",
135
+ );
136
+ }
137
+
111
138
  async function buildResponse(resp: Response): Promise<OAuthConnectionResponse> {
112
139
  const headers: Record<string, string> = {};
113
140
  resp.headers.forEach((value, key) => {
@@ -64,6 +64,12 @@ export interface OAuthConnectOptions {
64
64
  openUrl?: (url: string) => void;
65
65
  /** Send a message to the client (e.g. open_url). */
66
66
  sendToClient?: (msg: { type: string; [key: string]: unknown }) => void;
67
+ /**
68
+ * Conversation this connect flow belongs to. When set and `sendToClient`
69
+ * is used, the emitted `open_url` event carries it on the inner message
70
+ * so downstream consumers can route per-conversation.
71
+ */
72
+ conversationId?: string;
67
73
 
68
74
  /**
69
75
  * Callback transport to use for the OAuth redirect.
@@ -331,6 +337,9 @@ export async function orchestrateOAuthConnect(
331
337
  type: "open_url",
332
338
  url,
333
339
  title: `Connect ${options.service}`,
340
+ ...(options.conversationId
341
+ ? { conversationId: options.conversationId }
342
+ : {}),
334
343
  });
335
344
  } else {
336
345
  log.warn(
@@ -9,6 +9,7 @@ let mockConnection: Record<string, unknown> | undefined;
9
9
  let mockAccessToken: string | undefined;
10
10
  let mockConfig: Record<string, unknown> = {};
11
11
  let mockPlatformClient: Record<string, unknown> | null = null;
12
+ let syncManualTokenCalls: string[] = [];
12
13
 
13
14
  // ---------------------------------------------------------------------------
14
15
  // Module mocks (must precede imports of the module under test)
@@ -47,6 +48,15 @@ mock.module("./credential-token-resolver.js", () => ({
47
48
  }),
48
49
  }));
49
50
 
51
+ mock.module("./manual-token-connection.js", () => ({
52
+ syncManualTokenConnection: async (provider: string) => {
53
+ syncManualTokenCalls.push(provider);
54
+ if (provider === "telegram" && mockConnection?.provider === "telegram") {
55
+ mockConnection.accountInfo = "@example_bot";
56
+ }
57
+ },
58
+ }));
59
+
50
60
  mock.module("../config/loader.js", () => ({
51
61
  getConfig: () => mockConfig,
52
62
  }));
@@ -92,6 +102,7 @@ function setupDefaults(): void {
92
102
  mockProvider = {
93
103
  provider: "google",
94
104
  baseUrl: "https://gmail.googleapis.com/gmail/v1/users/me",
105
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
95
106
  managedServiceConfigKey: null,
96
107
  };
97
108
  mockConnection = {
@@ -121,6 +132,7 @@ function setupDefaults(): void {
121
132
  },
122
133
  };
123
134
  mockPlatformClient = makeMockClient();
135
+ syncManualTokenCalls = [];
124
136
  }
125
137
 
126
138
  // ---------------------------------------------------------------------------
@@ -159,6 +171,42 @@ describe("resolveOAuthConnection", () => {
159
171
  expect(result.accountInfo).toBe("user@example.com");
160
172
  });
161
173
 
174
+ test("managed path falls back to displayed account label when account_identifier does not match", async () => {
175
+ mockProvider!.managedServiceConfigKey = "google-oauth";
176
+ const fetchPaths: string[] = [];
177
+ mockPlatformClient = {
178
+ ...makeMockClient(),
179
+ fetch: mock(async (path: string) => {
180
+ fetchPaths.push(path);
181
+ if (path.includes("account_identifier=alice%40example.com")) {
182
+ return new Response(JSON.stringify({ results: [] }), { status: 200 });
183
+ }
184
+ return new Response(
185
+ JSON.stringify({
186
+ results: [
187
+ {
188
+ id: "platform-conn-1",
189
+ account_label: "alice@example.com",
190
+ },
191
+ ],
192
+ }),
193
+ { status: 200 },
194
+ );
195
+ }),
196
+ };
197
+
198
+ const result = await resolveOAuthConnection("google", {
199
+ account: "alice@example.com",
200
+ });
201
+
202
+ expect(result).toBeInstanceOf(PlatformOAuthConnection);
203
+ expect(result.accountInfo).toBe("alice@example.com");
204
+ expect(fetchPaths).toEqual([
205
+ "/v1/assistants/asst-123/oauth/connections/?provider=google&status=ACTIVE&account_identifier=alice%40example.com",
206
+ "/v1/assistants/asst-123/oauth/connections/?provider=google&status=ACTIVE",
207
+ ]);
208
+ });
209
+
162
210
  test("returns PlatformOAuthConnection when GitHub is in managed mode", async () => {
163
211
  mockProvider!.provider = "github";
164
212
  mockProvider!.managedServiceConfigKey = "github-oauth";
@@ -216,6 +264,34 @@ describe("resolveOAuthConnection", () => {
216
264
  }),
217
265
  ).rejects.toThrow(/No active OAuth connection found/);
218
266
  });
267
+
268
+ test("BYO path reconciles manual-token providers before exact account lookup", async () => {
269
+ mockProvider = {
270
+ provider: "telegram",
271
+ baseUrl: "https://api.telegram.org",
272
+ authorizeUrl: "urn:manual-token",
273
+ managedServiceConfigKey: null,
274
+ };
275
+ mockConnection = {
276
+ id: "conn-telegram",
277
+ provider: "telegram",
278
+ oauthAppId: "app-telegram",
279
+ accountInfo: null,
280
+ grantedScopes: JSON.stringify([]),
281
+ status: "active",
282
+ clientId: "manual-config",
283
+ };
284
+ mockAccessToken = "telegram-test-token";
285
+
286
+ const result = await resolveOAuthConnection("telegram", {
287
+ account: "@example_bot",
288
+ });
289
+
290
+ expect(syncManualTokenCalls).toEqual(["telegram"]);
291
+ expect(mockConnection.accountInfo).toBe("@example_bot");
292
+ expect(result).toBeInstanceOf(BYOOAuthConnection);
293
+ expect(result.id).toBe("conn-telegram");
294
+ });
219
295
  });
220
296
 
221
297
  describe("resolveEffectiveBaseUrl", () => {