@vellumai/assistant 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (503) hide show
  1. package/ARCHITECTURE.md +11 -12
  2. package/docker-entrypoint.sh +13 -2
  3. package/docker-init-apt-root.sh +79 -6
  4. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  5. package/openapi.yaml +945 -36
  6. package/package.json +1 -1
  7. package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
  8. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  9. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +272 -0
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  14. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  15. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  16. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  17. package/src/__tests__/compaction-events.test.ts +1 -1
  18. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  19. package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
  20. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  22. package/src/__tests__/config-watcher.test.ts +1 -1
  23. package/src/__tests__/context-token-estimator.test.ts +112 -57
  24. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  27. package/src/__tests__/conversation-agent-loop.test.ts +77 -3
  28. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  29. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  30. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  32. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  33. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  34. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  35. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  36. package/src/__tests__/conversation-pairing.test.ts +2 -2
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  39. package/src/__tests__/conversation-queue.test.ts +1 -1
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
  41. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  42. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  43. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  44. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  45. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  46. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  47. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  48. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  49. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  50. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  51. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  52. package/src/__tests__/date-context.test.ts +45 -0
  53. package/src/__tests__/dm-backfill.test.ts +64 -0
  54. package/src/__tests__/dm-persistence.test.ts +33 -0
  55. package/src/__tests__/document-find-replace.test.ts +501 -0
  56. package/src/__tests__/external-plugin-loader.test.ts +91 -19
  57. package/src/__tests__/first-greeting.test.ts +23 -2
  58. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  59. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  60. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  61. package/src/__tests__/heartbeat-service.test.ts +24 -164
  62. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  63. package/src/__tests__/host-app-control-proxy.test.ts +241 -0
  64. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  66. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  67. package/src/__tests__/host-file-proxy.test.ts +8 -1
  68. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  69. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  70. package/src/__tests__/identity-routes.test.ts +57 -0
  71. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  72. package/src/__tests__/injector-background-turn.test.ts +153 -0
  73. package/src/__tests__/injector-chain.test.ts +7 -0
  74. package/src/__tests__/injector-document-comments.test.ts +378 -0
  75. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  76. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
  77. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  78. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  79. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  80. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  81. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  82. package/src/__tests__/llm-catalog-parity.test.ts +3 -0
  83. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  84. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  85. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  86. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
  87. package/src/__tests__/llm-resolver.test.ts +340 -3
  88. package/src/__tests__/log-export-routes.test.ts +99 -2
  89. package/src/__tests__/managed-profile-guard.test.ts +10 -0
  90. package/src/__tests__/message-queue-steer.test.ts +114 -0
  91. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  92. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  93. package/src/__tests__/notification-deep-link.test.ts +15 -0
  94. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  95. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  96. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  97. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  98. package/src/__tests__/openai-provider.test.ts +323 -3
  99. package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
  100. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  101. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  102. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  103. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  104. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  105. package/src/__tests__/platform-proxy-context.test.ts +6 -1
  106. package/src/__tests__/platform.test.ts +0 -3
  107. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  108. package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
  109. package/src/__tests__/plugin-types.test.ts +2 -2
  110. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  111. package/src/__tests__/process-message-display-content.test.ts +21 -16
  112. package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
  113. package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  115. package/src/__tests__/server-history-render.test.ts +83 -4
  116. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  117. package/src/__tests__/system-prompt.test.ts +57 -101
  118. package/src/__tests__/terminal-tools.test.ts +11 -1
  119. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  120. package/src/__tests__/thread-backfill.test.ts +370 -22
  121. package/src/__tests__/tool-executor.test.ts +90 -1
  122. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  123. package/src/__tests__/twilio-routes.test.ts +1 -1
  124. package/src/__tests__/web-fetch.test.ts +2 -2
  125. package/src/__tests__/workspace-git-service.test.ts +88 -5
  126. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  127. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  128. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  129. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  130. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  131. package/src/a2a/__tests__/task-store.test.ts +246 -0
  132. package/src/a2a/agent-card.ts +58 -0
  133. package/src/a2a/feature-gate.ts +8 -0
  134. package/src/a2a/protocol-constants.ts +21 -0
  135. package/src/a2a/protocol-errors.ts +50 -0
  136. package/src/a2a/protocol-types.ts +162 -0
  137. package/src/a2a/task-store.ts +168 -0
  138. package/src/agent/attachments.ts +1 -0
  139. package/src/agent/loop.ts +208 -22
  140. package/src/background-wake/next-wake.test.ts +289 -0
  141. package/src/background-wake/next-wake.ts +172 -0
  142. package/src/browser/operations.ts +15 -0
  143. package/src/channels/config.ts +9 -0
  144. package/src/channels/types.ts +14 -0
  145. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  146. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  147. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  148. package/src/cli/commands/__tests__/schedules.test.ts +469 -0
  149. package/src/cli/commands/conversations.ts +128 -1
  150. package/src/cli/commands/inference-providers.ts +147 -1
  151. package/src/cli/commands/memory-v2.ts +308 -0
  152. package/src/cli/commands/notifications.ts +89 -37
  153. package/src/cli/commands/plugins.ts +67 -0
  154. package/src/cli/commands/schedules.ts +297 -5
  155. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  156. package/src/cli/lib/install-from-github.ts +8 -9
  157. package/src/cli/lib/search-plugins.ts +163 -0
  158. package/src/cli/program.ts +14 -0
  159. package/src/cli/utils/conversation-id.ts +17 -5
  160. package/src/config/assistant-feature-flags.ts +24 -54
  161. package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
  162. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  163. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  164. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  165. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  166. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  167. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  168. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  169. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  170. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  171. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  172. package/src/config/bundled-tool-registry.ts +22 -12
  173. package/src/config/call-site-defaults.ts +124 -0
  174. package/src/config/feature-flag-registry.json +111 -23
  175. package/src/config/llm-resolver.ts +66 -1
  176. package/src/config/schema.ts +2 -0
  177. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
  178. package/src/config/schemas/call-site-catalog.ts +21 -0
  179. package/src/config/schemas/channels.ts +9 -0
  180. package/src/config/schemas/conversations.ts +10 -0
  181. package/src/config/schemas/heartbeat.ts +14 -0
  182. package/src/config/schemas/llm.ts +4 -3
  183. package/src/config/schemas/memory-retrospective.ts +1 -1
  184. package/src/config/schemas/memory-v2.ts +51 -4
  185. package/src/config/schemas/memory.ts +3 -1
  186. package/src/config/seed-inference-profiles.ts +99 -29
  187. package/src/context/compactor.ts +80 -13
  188. package/src/context/token-estimator.ts +72 -31
  189. package/src/context/window-manager.ts +25 -0
  190. package/src/credential-health/credential-health-service.ts +34 -19
  191. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
  192. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  193. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  194. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  195. package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
  196. package/src/daemon/conversation-agent-loop.ts +252 -56
  197. package/src/daemon/conversation-lifecycle.ts +142 -116
  198. package/src/daemon/conversation-messaging.ts +3 -0
  199. package/src/daemon/conversation-process.ts +273 -0
  200. package/src/daemon/conversation-queue-manager.ts +14 -0
  201. package/src/daemon/conversation-runtime-assembly.ts +144 -75
  202. package/src/daemon/conversation-slash.ts +37 -5
  203. package/src/daemon/conversation-surfaces.ts +45 -2
  204. package/src/daemon/conversation-tool-setup.ts +7 -0
  205. package/src/daemon/conversation.ts +42 -12
  206. package/src/daemon/date-context.ts +40 -0
  207. package/src/daemon/first-greeting.ts +10 -0
  208. package/src/daemon/guardian-action-generators.ts +1 -125
  209. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  210. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  211. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  212. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  213. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  214. package/src/daemon/handlers/config-a2a.ts +449 -0
  215. package/src/daemon/handlers/config-model.test.ts +1 -0
  216. package/src/daemon/handlers/conversations.ts +80 -0
  217. package/src/daemon/handlers/shared.ts +92 -29
  218. package/src/daemon/host-app-control-proxy.ts +69 -18
  219. package/src/daemon/host-bash-proxy.ts +1 -1
  220. package/src/daemon/host-cu-proxy.ts +1 -1
  221. package/src/daemon/host-file-proxy.ts +1 -1
  222. package/src/daemon/host-proxy-preactivation.ts +85 -18
  223. package/src/daemon/host-transfer-proxy.ts +1 -1
  224. package/src/daemon/lifecycle.ts +67 -65
  225. package/src/daemon/memory-v2-startup.ts +49 -13
  226. package/src/daemon/message-protocol.ts +4 -0
  227. package/src/daemon/message-types/conversations.ts +8 -0
  228. package/src/daemon/message-types/document-comments.ts +50 -0
  229. package/src/daemon/message-types/messages.ts +68 -1
  230. package/src/daemon/message-types/notifications.ts +21 -0
  231. package/src/daemon/message-types/surfaces.ts +3 -1
  232. package/src/daemon/message-types/web-activity.ts +57 -0
  233. package/src/daemon/pkb-reminder-builder.test.ts +10 -53
  234. package/src/daemon/pkb-reminder-builder.ts +4 -19
  235. package/src/daemon/plugin-source-watcher.ts +135 -3
  236. package/src/daemon/process-message.ts +72 -12
  237. package/src/daemon/query-complexity-router.ts +75 -0
  238. package/src/daemon/skill-memory-refresh.ts +5 -1
  239. package/src/daemon/trust-context.ts +6 -0
  240. package/src/daemon/wake-target-adapter.ts +2 -0
  241. package/src/documents/document-comments-store.test.ts +338 -0
  242. package/src/documents/document-comments-store.ts +237 -0
  243. package/src/documents/document-store.ts +202 -0
  244. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  245. package/src/export/transcript-formatter.ts +54 -20
  246. package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
  247. package/src/heartbeat/heartbeat-service.ts +35 -191
  248. package/src/home/__tests__/feed-types.test.ts +40 -0
  249. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  250. package/src/home/feed-types.ts +20 -3
  251. package/src/home/home-content-refresh.ts +52 -0
  252. package/src/home/home-greeting-cache.ts +69 -0
  253. package/src/home/home-greeting.ts +94 -0
  254. package/src/home/suggested-prompts.ts +177 -9
  255. package/src/ipc/cli-client.ts +147 -45
  256. package/src/memory/__tests__/conversation-queries.test.ts +220 -0
  257. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  258. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  259. package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
  260. package/src/memory/conversation-crud.ts +133 -43
  261. package/src/memory/conversation-queries.ts +87 -1
  262. package/src/memory/conversation-title-service.ts +26 -4
  263. package/src/memory/db-init.ts +22 -0
  264. package/src/memory/delivery-crud.ts +41 -0
  265. package/src/memory/delivery-status.ts +141 -15
  266. package/src/memory/external-conversation-store.ts +32 -1
  267. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
  268. package/src/memory/graph/conversation-graph-memory.ts +18 -6
  269. package/src/memory/graph/tools.ts +6 -37
  270. package/src/memory/invite-store.ts +53 -0
  271. package/src/memory/jobs-worker.ts +21 -1
  272. package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
  273. package/src/memory/llm-request-log-store.ts +92 -1
  274. package/src/memory/memory-retrospective-constants.ts +28 -0
  275. package/src/memory/memory-retrospective-enqueue.ts +4 -22
  276. package/src/memory/memory-retrospective-job.ts +438 -21
  277. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  278. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  279. package/src/memory/migrations/100-core-tables.ts +1 -0
  280. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  281. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  282. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  283. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  284. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  285. package/src/memory/migrations/253-document-comments.ts +47 -0
  286. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  287. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  288. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  289. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  290. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  291. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  292. package/src/memory/migrations/index.ts +20 -0
  293. package/src/memory/migrations/registry.ts +33 -0
  294. package/src/memory/onboarding-events-store.ts +7 -0
  295. package/src/memory/schema/a2a.ts +15 -0
  296. package/src/memory/schema/calls.ts +1 -0
  297. package/src/memory/schema/conversations.ts +3 -0
  298. package/src/memory/schema/index.ts +1 -0
  299. package/src/memory/schema/inference.ts +2 -0
  300. package/src/memory/schema/infrastructure.ts +2 -0
  301. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  302. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  303. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  304. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  305. package/src/memory/v2/__tests__/injection.test.ts +221 -17
  306. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  307. package/src/memory/v2/__tests__/router.test.ts +489 -1
  308. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  309. package/src/memory/v2/activation-store.ts +14 -16
  310. package/src/memory/v2/cli-command-content.ts +19 -0
  311. package/src/memory/v2/cli-command-store.ts +304 -0
  312. package/src/memory/v2/consolidation-job.ts +14 -0
  313. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  314. package/src/memory/v2/injection-events.ts +101 -0
  315. package/src/memory/v2/injection.ts +69 -29
  316. package/src/memory/v2/page-index.ts +246 -19
  317. package/src/memory/v2/page-store.ts +18 -0
  318. package/src/memory/v2/router.ts +209 -55
  319. package/src/memory/v2/static-context.ts +4 -4
  320. package/src/memory/v2/types.ts +23 -0
  321. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  322. package/src/messaging/providers/a2a/deliver.ts +156 -0
  323. package/src/messaging/providers/gmail/client.ts +9 -2
  324. package/src/messaging/providers/index.ts +18 -3
  325. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  327. package/src/messaging/providers/slack/adapter.ts +178 -25
  328. package/src/messaging/providers/slack/api.test.ts +54 -0
  329. package/src/messaging/providers/slack/api.ts +119 -3
  330. package/src/messaging/providers/slack/client.ts +12 -0
  331. package/src/messaging/providers/slack/deep-link.ts +20 -1
  332. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  333. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  334. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  335. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  336. package/src/messaging/providers/slack/send.test.ts +77 -0
  337. package/src/messaging/providers/slack/send.ts +8 -2
  338. package/src/messaging/providers/slack/types.ts +14 -0
  339. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  340. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  341. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  342. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
  343. package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
  344. package/src/notifications/adapters/macos.ts +12 -2
  345. package/src/notifications/broadcaster.ts +29 -4
  346. package/src/notifications/conversation-seed-composer.ts +14 -2
  347. package/src/notifications/copy-composer.ts +17 -64
  348. package/src/notifications/decision-engine.ts +111 -44
  349. package/src/notifications/deferred-emit.ts +135 -0
  350. package/src/notifications/deterministic-checks.ts +96 -0
  351. package/src/notifications/emit-signal.ts +10 -1
  352. package/src/notifications/home-feed-side-effect.ts +136 -27
  353. package/src/notifications/signal.ts +0 -4
  354. package/src/notifications/types.ts +8 -0
  355. package/src/oauth/connect-orchestrator.ts +3 -0
  356. package/src/oauth/credential-token-resolver.ts +2 -0
  357. package/src/oauth/manual-token-connection.ts +19 -0
  358. package/src/oauth/oauth-store.ts +12 -0
  359. package/src/oauth/platform-connection.test.ts +43 -3
  360. package/src/oauth/platform-connection.ts +13 -4
  361. package/src/oauth/seed-providers.ts +22 -0
  362. package/src/permissions/prompter.ts +5 -2
  363. package/src/permissions/secret-prompter.ts +4 -1
  364. package/src/plugins/defaults/injectors.ts +118 -26
  365. package/src/plugins/external-plugin-loader.ts +82 -10
  366. package/src/plugins/types.ts +16 -7
  367. package/src/prompts/__tests__/system-prompt.test.ts +44 -45
  368. package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
  369. package/src/prompts/normalize-onboarding.ts +40 -0
  370. package/src/prompts/sections.ts +32 -14
  371. package/src/prompts/system-prompt.ts +105 -76
  372. package/src/prompts/template-detection.ts +37 -0
  373. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  374. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  375. package/src/prompts/templates/VOICE.md +3 -0
  376. package/src/prompts/templates/system-sections.ts +51 -10
  377. package/src/providers/__tests__/inference.test.ts +2 -0
  378. package/src/providers/anthropic/client.ts +132 -5
  379. package/src/providers/call-site-routing.ts +24 -6
  380. package/src/providers/connection-resolution.ts +63 -13
  381. package/src/providers/fireworks/client.ts +20 -2
  382. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  383. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  384. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  385. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  386. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  387. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  388. package/src/providers/inference/adapter-factory.ts +24 -21
  389. package/src/providers/inference/auth.ts +15 -3
  390. package/src/providers/inference/backfill.ts +14 -1
  391. package/src/providers/inference/codex-token-refresh.ts +128 -0
  392. package/src/providers/inference/connections.ts +85 -5
  393. package/src/providers/inference/resolve-auth.ts +50 -5
  394. package/src/providers/model-catalog.ts +244 -242
  395. package/src/providers/model-intents.ts +3 -3
  396. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  397. package/src/providers/openai/chat-completions-provider.ts +215 -25
  398. package/src/providers/openai/responses-provider.ts +9 -3
  399. package/src/providers/openrouter/client.ts +46 -4
  400. package/src/providers/platform-proxy/constants.ts +3 -4
  401. package/src/providers/provider-catalog-visibility.ts +3 -1
  402. package/src/providers/provider-send-message.ts +27 -12
  403. package/src/providers/registry.ts +30 -1
  404. package/src/providers/types.ts +25 -0
  405. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  406. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  407. package/src/runtime/agent-wake.ts +212 -57
  408. package/src/runtime/auth/route-policy.ts +20 -3
  409. package/src/runtime/background-job-runner.ts +26 -0
  410. package/src/runtime/channel-reply-delivery.ts +182 -47
  411. package/src/runtime/channel-retry-sweep.ts +141 -16
  412. package/src/runtime/http-server.ts +7 -16
  413. package/src/runtime/http-types.ts +7 -51
  414. package/src/runtime/pending-interactions.ts +51 -8
  415. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  416. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  417. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
  418. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  419. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  420. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  421. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  422. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  423. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  424. package/src/runtime/routes/approval-routes.ts +4 -1
  425. package/src/runtime/routes/channel-availability-routes.ts +5 -0
  426. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  427. package/src/runtime/routes/consolidation-routes.ts +100 -0
  428. package/src/runtime/routes/content-source-routes.ts +78 -0
  429. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  430. package/src/runtime/routes/conversation-query-routes.ts +130 -12
  431. package/src/runtime/routes/conversation-routes.ts +288 -76
  432. package/src/runtime/routes/document-comments-routes.ts +287 -0
  433. package/src/runtime/routes/documents-routes.ts +33 -0
  434. package/src/runtime/routes/home-feed-routes.ts +6 -3
  435. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  436. package/src/runtime/routes/host-browser-routes.ts +8 -1
  437. package/src/runtime/routes/identity-routes.ts +21 -0
  438. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  439. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  440. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  441. package/src/runtime/routes/index.ts +14 -4
  442. package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
  443. package/src/runtime/routes/integrations/a2a.ts +294 -0
  444. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  445. package/src/runtime/routes/log-export-routes.ts +39 -0
  446. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  447. package/src/runtime/routes/notification-routes.ts +19 -2
  448. package/src/runtime/routes/question-routes.ts +4 -1
  449. package/src/runtime/routes/sanity-routes.ts +159 -0
  450. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  451. package/src/runtime/routes/subagents-routes.ts +41 -0
  452. package/src/runtime/services/conversation-serializer.ts +30 -4
  453. package/src/schedule/integration-status.ts +3 -1
  454. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  455. package/src/security/oauth2-device-code.ts +307 -0
  456. package/src/security/oauth2.ts +26 -9
  457. package/src/security/secure-keys.ts +5 -0
  458. package/src/skills/catalog-install.ts +6 -2
  459. package/src/subagent/manager.ts +2 -0
  460. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  461. package/src/tools/browser/browser-execution.ts +93 -0
  462. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  463. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  464. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  465. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  466. package/src/tools/browser/cdp-client/factory.ts +87 -3
  467. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  468. package/src/tools/browser/cdp-client/types.ts +36 -0
  469. package/src/tools/browser/pinned-tabs.ts +90 -0
  470. package/src/tools/document/document-comment-tool.test.ts +379 -0
  471. package/src/tools/document/document-comment-tool.ts +156 -0
  472. package/src/tools/document/document-tool.ts +128 -2
  473. package/src/tools/memory/register.ts +1 -9
  474. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  475. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  476. package/src/tools/network/domain-normalize.ts +17 -0
  477. package/src/tools/network/web-fetch.ts +213 -64
  478. package/src/tools/network/web-search.ts +191 -66
  479. package/src/tools/registry.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +3 -2
  481. package/src/tools/tool-approval-handler.ts +19 -12
  482. package/src/tools/types.ts +41 -2
  483. package/src/tools/ui-surface/definitions.ts +3 -1
  484. package/src/types/onboarding-context.ts +4 -0
  485. package/src/util/__tests__/favicon.test.ts +84 -0
  486. package/src/util/favicon.ts +40 -0
  487. package/src/util/platform.ts +0 -5
  488. package/src/workspace/git-service.ts +75 -4
  489. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  490. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  491. package/src/workspace/migrations/registry.ts +4 -0
  492. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  493. package/src/config/bundled-skills/document/SKILL.md +0 -54
  494. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  495. package/src/daemon/seed-files.ts +0 -18
  496. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  497. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
  498. package/src/runtime/routes/interface-routes.ts +0 -43
  499. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  500. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  501. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  502. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  503. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -0,0 +1,266 @@
1
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ type MockSlackInfo = {
4
+ id: string;
5
+ name?: string;
6
+ nameNormalized?: string;
7
+ } | null;
8
+
9
+ type MockSlackResult = MockSlackInfo | { slackError: string };
10
+
11
+ const slackInfoCalls: string[] = [];
12
+ let slackInfoImpl: (channelId: string) => Promise<MockSlackResult> = async () =>
13
+ null;
14
+ const syncInvalidations: string[][] = [];
15
+ const originalFetch = globalThis.fetch;
16
+
17
+ mock.module("../../../security/secure-keys.js", () => ({
18
+ getSecureKeyAsync: async () => "xoxb-test",
19
+ }));
20
+
21
+ mock.module("../../sync/sync-publisher.js", () => ({
22
+ publishSyncInvalidation: async (tags: string[]) => {
23
+ syncInvalidations.push([...tags]);
24
+ },
25
+ }));
26
+
27
+ import {
28
+ conversationMetadataSyncTag,
29
+ SYNC_TAGS,
30
+ } from "../../../daemon/message-types/sync.js";
31
+ import { createConversation } from "../../../memory/conversation-crud.js";
32
+ import { getDb, resetDb } from "../../../memory/db-connection.js";
33
+ import { initializeDb } from "../../../memory/db-init.js";
34
+ import {
35
+ getBindingByConversation,
36
+ upsertBinding,
37
+ } from "../../../memory/external-conversation-store.js";
38
+ import { ROUTES } from "../slack-channel-routes.js";
39
+ import type { RouteDefinition } from "../types.js";
40
+
41
+ initializeDb();
42
+
43
+ interface ResolveResponse {
44
+ channelId: string;
45
+ channelName?: string;
46
+ cached: boolean;
47
+ resolved: boolean;
48
+ reason?: string;
49
+ }
50
+
51
+ function resolveHandler(): RouteDefinition["handler"] {
52
+ const route = ROUTES.find(
53
+ (r) => r.operationId === "slack_channel_name_resolve",
54
+ );
55
+ if (!route) throw new Error("slack_channel_name_resolve route not found");
56
+ return route.handler;
57
+ }
58
+
59
+ const handler = resolveHandler();
60
+
61
+ function clearTables(): void {
62
+ const db = getDb();
63
+ db.run("DELETE FROM external_conversation_bindings");
64
+ db.run("DELETE FROM conversations");
65
+ }
66
+
67
+ function createBoundConversation(input: {
68
+ sourceChannel?: string;
69
+ externalChatId: string;
70
+ externalChatName?: string | null;
71
+ externalThreadId?: string | null;
72
+ externalUserId?: string | null;
73
+ displayName?: string | null;
74
+ username?: string | null;
75
+ }): string {
76
+ const conversation = createConversation("Slack route test");
77
+ upsertBinding({
78
+ conversationId: conversation.id,
79
+ sourceChannel: input.sourceChannel ?? "slack",
80
+ externalChatId: input.externalChatId,
81
+ externalChatName: input.externalChatName,
82
+ externalThreadId: input.externalThreadId,
83
+ externalUserId: input.externalUserId,
84
+ displayName: input.displayName,
85
+ username: input.username,
86
+ });
87
+ return conversation.id;
88
+ }
89
+
90
+ async function resolve(conversationId: string): Promise<ResolveResponse> {
91
+ return (await handler({
92
+ pathParams: { conversationId },
93
+ })) as ResolveResponse;
94
+ }
95
+
96
+ function mockSlackFetch(): void {
97
+ globalThis.fetch = mock(async (input) => {
98
+ const url = new URL(String(input));
99
+ if (url.pathname !== "/api/conversations.info") {
100
+ throw new Error(`Unexpected Slack API request: ${url.pathname}`);
101
+ }
102
+
103
+ const channelId = url.searchParams.get("channel") ?? "";
104
+ slackInfoCalls.push(channelId);
105
+ const result = await slackInfoImpl(channelId);
106
+ if (result && "slackError" in result) {
107
+ return Response.json({ ok: false, error: result.slackError });
108
+ }
109
+
110
+ return Response.json({
111
+ ok: true,
112
+ channel: result
113
+ ? {
114
+ id: result.id,
115
+ name: result.name,
116
+ name_normalized: result.nameNormalized,
117
+ }
118
+ : undefined,
119
+ });
120
+ }) as unknown as typeof fetch;
121
+ }
122
+
123
+ beforeEach(() => {
124
+ clearTables();
125
+ slackInfoCalls.length = 0;
126
+ syncInvalidations.length = 0;
127
+ slackInfoImpl = async () => null;
128
+ mockSlackFetch();
129
+ });
130
+
131
+ afterAll(() => {
132
+ globalThis.fetch = originalFetch;
133
+ resetDb();
134
+ mock.restore();
135
+ });
136
+
137
+ describe("slack_channel_name_resolve", () => {
138
+ test("returns cached friendly name without calling Slack", async () => {
139
+ const conversationId = createBoundConversation({
140
+ externalChatId: "CACHED123",
141
+ externalChatName: "team-updates",
142
+ });
143
+
144
+ const result = await resolve(conversationId);
145
+
146
+ expect(result).toEqual({
147
+ channelId: "CACHED123",
148
+ channelName: "team-updates",
149
+ cached: true,
150
+ resolved: true,
151
+ });
152
+ expect(slackInfoCalls).toEqual([]);
153
+ });
154
+
155
+ test("resolves an unresolved channel ID and persists the returned name", async () => {
156
+ const conversationId = createBoundConversation({
157
+ externalChatId: "CRESOLVE123",
158
+ externalChatName: "CRESOLVE123",
159
+ externalThreadId: "1710000000.000100",
160
+ externalUserId: "U123",
161
+ displayName: "Alice",
162
+ username: "alice",
163
+ });
164
+ slackInfoImpl = async (channelId) => ({
165
+ id: channelId,
166
+ name: "engineering",
167
+ });
168
+
169
+ const result = await resolve(conversationId);
170
+
171
+ expect(slackInfoCalls).toEqual(["CRESOLVE123"]);
172
+ expect(result).toEqual({
173
+ channelId: "CRESOLVE123",
174
+ channelName: "engineering",
175
+ cached: false,
176
+ resolved: true,
177
+ });
178
+
179
+ const updated = getBindingByConversation(conversationId);
180
+ expect(updated?.externalChatName).toBe("engineering");
181
+ expect(updated?.externalThreadId).toBe("1710000000.000100");
182
+ expect(updated?.externalUserId).toBe("U123");
183
+ expect(updated?.displayName).toBe("Alice");
184
+ expect(updated?.username).toBe("alice");
185
+ });
186
+
187
+ test("publishes conversation sync tags after persisting a returned name", async () => {
188
+ const conversationId = createBoundConversation({
189
+ externalChatId: "CSYNC123",
190
+ externalChatName: "CSYNC123",
191
+ });
192
+ slackInfoImpl = async (channelId) => ({
193
+ id: channelId,
194
+ nameNormalized: "team-sync",
195
+ });
196
+
197
+ const result = await resolve(conversationId);
198
+
199
+ expect(result).toEqual({
200
+ channelId: "CSYNC123",
201
+ channelName: "team-sync",
202
+ cached: false,
203
+ resolved: true,
204
+ });
205
+ expect(syncInvalidations).toContainEqual([
206
+ SYNC_TAGS.conversationsList,
207
+ conversationMetadataSyncTag(conversationId),
208
+ ]);
209
+ });
210
+
211
+ test("does not call Slack for DM bindings", async () => {
212
+ const conversationId = createBoundConversation({
213
+ externalChatId: "D123",
214
+ externalChatName: null,
215
+ });
216
+
217
+ const result = await resolve(conversationId);
218
+
219
+ expect(result).toEqual({
220
+ channelId: "D123",
221
+ cached: false,
222
+ resolved: false,
223
+ reason: "dm",
224
+ });
225
+ expect(slackInfoCalls).toEqual([]);
226
+ expect(getBindingByConversation(conversationId)?.externalChatName).toBe(
227
+ null,
228
+ );
229
+ });
230
+
231
+ test("rejects non-Slack bindings", async () => {
232
+ const conversationId = createBoundConversation({
233
+ sourceChannel: "telegram",
234
+ externalChatId: "chat-123",
235
+ });
236
+
237
+ await expect(resolve(conversationId)).rejects.toMatchObject({
238
+ code: "NOT_FOUND",
239
+ statusCode: 404,
240
+ });
241
+ expect(slackInfoCalls).toEqual([]);
242
+ });
243
+
244
+ test("returns unresolved on Slack API failure without mutating the binding", async () => {
245
+ const conversationId = createBoundConversation({
246
+ externalChatId: "CFAIL123",
247
+ externalChatName: "CFAIL123",
248
+ });
249
+ const before = getBindingByConversation(conversationId);
250
+ slackInfoImpl = async () => ({ slackError: "missing_scope" });
251
+
252
+ const result = await resolve(conversationId);
253
+
254
+ expect(result).toEqual({
255
+ channelId: "CFAIL123",
256
+ cached: false,
257
+ resolved: false,
258
+ reason: "permission",
259
+ });
260
+ expect(slackInfoCalls).toEqual(["CFAIL123"]);
261
+
262
+ const after = getBindingByConversation(conversationId);
263
+ expect(after?.externalChatName).toBe("CFAIL123");
264
+ expect(after?.updatedAt).toBe(before?.updatedAt);
265
+ });
266
+ });
@@ -78,7 +78,10 @@ function handleConfirm({ body }: RouteHandlerArgs) {
78
78
  // ACP permissions: resolve directly without a Conversation object.
79
79
  // No PermissionPrompter involved, so the route owns deregistration.
80
80
  if (interaction.directResolve) {
81
- pendingInteractions.resolve(requestId);
81
+ pendingInteractions.resolve(
82
+ requestId,
83
+ effectiveDecision === "allow" ? "approved" : "rejected",
84
+ );
82
85
  interaction.directResolve(effectiveDecision as UserDecision);
83
86
  return { accepted: true };
84
87
  }
@@ -17,12 +17,14 @@
17
17
 
18
18
  import { z } from "zod";
19
19
 
20
+ import { isA2AEnabled } from "../../a2a/feature-gate.js";
20
21
  import {
21
22
  CHANNEL_IDS,
22
23
  CHANNEL_METADATA,
23
24
  type ChannelId,
24
25
  type ChannelInfo,
25
26
  } from "../../channels/types.js";
27
+ import { getConfig } from "../../config/loader.js";
26
28
  import { VellumPlatformClient } from "../../platform/client.js";
27
29
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
28
30
 
@@ -75,6 +77,9 @@ async function handleGetChannelAvailability(
75
77
  if (await hasRegisteredInbox()) {
76
78
  ids.push("email");
77
79
  }
80
+ if (isA2AEnabled(getConfig())) {
81
+ ids.push("a2a");
82
+ }
78
83
  // CHANNEL_METADATA is `Partial<Record<ChannelId, ChannelInfo>>` because
79
84
  // unsurfaced channels deliberately have no metadata. `ids` only ever
80
85
  // contains channels that BASE_AVAILABLE_CHANNELS / the email branch
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Route definitions for ChatGPT subscription OAuth authentication.
3
+ *
4
+ * POST /v1/inference/chatgpt-subscription/auth — generate a PKCE authorize
5
+ * URL for the user to visit. Returns `{ authorize_url, state }`.
6
+ *
7
+ * POST /v1/inference/chatgpt-subscription/auth/exchange — accept the
8
+ * authorization code + state from the redirect, exchange for tokens,
9
+ * store in CES, and upsert the provider connection.
10
+ */
11
+
12
+ import { z } from "zod";
13
+
14
+ import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
15
+ import { getConfigReadOnly } from "../../config/loader.js";
16
+ import { getDb } from "../../memory/db-connection.js";
17
+ import {
18
+ createConnection,
19
+ getConnection,
20
+ updateConnection,
21
+ } from "../../providers/inference/connections.js";
22
+ import type { OAuth2Config } from "../../security/oauth2.js";
23
+ import {
24
+ exchangeCodeForTokens,
25
+ generateCodeChallenge,
26
+ generateCodeVerifier,
27
+ generateState,
28
+ } from "../../security/oauth2.js";
29
+ import { setSecureKeyAsync } from "../../security/secure-keys.js";
30
+ import { getLogger } from "../../util/logger.js";
31
+ import { BadRequestError } from "./errors.js";
32
+ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
33
+
34
+ function requireFeatureFlag() {
35
+ const config = getConfigReadOnly();
36
+ if (!isAssistantFeatureFlagEnabled("chatgpt-subscription-auth", config)) {
37
+ throw new BadRequestError(
38
+ "ChatGPT subscription auth is not enabled for this assistant.",
39
+ );
40
+ }
41
+ }
42
+
43
+ const log = getLogger("chatgpt-subscription-auth");
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // OAuth config
47
+ // ---------------------------------------------------------------------------
48
+
49
+ const OPENAI_OAUTH_CONFIG: OAuth2Config = {
50
+ authorizeUrl: "https://auth.openai.com/oauth/authorize",
51
+ tokenExchangeUrl: "https://auth.openai.com/oauth/token",
52
+ clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
53
+ scopes: ["openid", "profile", "email", "offline_access"],
54
+ scopeSeparator: " ",
55
+ authorizeParams: { id_token_add_organizations: "true" },
56
+ };
57
+
58
+ const REDIRECT_URI = "http://localhost:1455/auth/callback";
59
+ const CONNECTION_NAME = "chatgpt-subscription";
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Module-level PKCE state storage
63
+ // ---------------------------------------------------------------------------
64
+
65
+ interface PendingAuth {
66
+ codeVerifier: string;
67
+ createdAt: number;
68
+ }
69
+
70
+ const pendingAuths = new Map<string, PendingAuth>();
71
+
72
+ const PENDING_AUTH_TTL_MS = 10 * 60 * 1000; // 10 minutes
73
+
74
+ /** Remove entries older than 10 minutes. */
75
+ function cleanupExpiredEntries(): void {
76
+ const cutoff = Date.now() - PENDING_AUTH_TTL_MS;
77
+ for (const [key, entry] of pendingAuths) {
78
+ if (entry.createdAt < cutoff) {
79
+ pendingAuths.delete(key);
80
+ }
81
+ }
82
+ }
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Handlers
86
+ // ---------------------------------------------------------------------------
87
+
88
+ async function handleStartAuth(_args: RouteHandlerArgs) {
89
+ requireFeatureFlag();
90
+ cleanupExpiredEntries();
91
+
92
+ const codeVerifier = generateCodeVerifier();
93
+ const codeChallenge = generateCodeChallenge(codeVerifier);
94
+ const state = generateState();
95
+
96
+ pendingAuths.set(state, { codeVerifier, createdAt: Date.now() });
97
+
98
+ const params = new URLSearchParams({
99
+ response_type: "code",
100
+ client_id: OPENAI_OAUTH_CONFIG.clientId,
101
+ redirect_uri: REDIRECT_URI,
102
+ scope: OPENAI_OAUTH_CONFIG.scopes.join(
103
+ OPENAI_OAUTH_CONFIG.scopeSeparator,
104
+ ),
105
+ state,
106
+ code_challenge: codeChallenge,
107
+ code_challenge_method: "S256",
108
+ ...OPENAI_OAUTH_CONFIG.authorizeParams,
109
+ });
110
+
111
+ const authorizeUrl = `${OPENAI_OAUTH_CONFIG.authorizeUrl}?${params.toString()}`;
112
+
113
+ return { authorize_url: authorizeUrl, state };
114
+ }
115
+
116
+ async function handleExchange(args: RouteHandlerArgs) {
117
+ requireFeatureFlag();
118
+ const { code, state } = args.body as { code: string; state: string };
119
+
120
+ const pending = pendingAuths.get(state);
121
+ if (!pending) {
122
+ throw new Error(
123
+ "Invalid or expired state parameter. Please restart the auth flow.",
124
+ );
125
+ }
126
+
127
+ pendingAuths.delete(state);
128
+
129
+ // Check TTL
130
+ if (Date.now() - pending.createdAt > PENDING_AUTH_TTL_MS) {
131
+ throw new Error("Auth flow expired. Please restart the auth flow.");
132
+ }
133
+
134
+ const { tokens } = await exchangeCodeForTokens(
135
+ OPENAI_OAUTH_CONFIG,
136
+ code,
137
+ REDIRECT_URI,
138
+ pending.codeVerifier,
139
+ );
140
+
141
+ // Store tokens in CES
142
+ const accessStored = await setSecureKeyAsync(
143
+ "credential/chatgpt/access_token",
144
+ tokens.accessToken,
145
+ );
146
+ if (!accessStored) {
147
+ log.error("Failed to store ChatGPT access token in CES");
148
+ throw new Error("Failed to store access token");
149
+ }
150
+
151
+ if (tokens.refreshToken) {
152
+ const refreshStored = await setSecureKeyAsync(
153
+ "credential/chatgpt/refresh_token",
154
+ tokens.refreshToken,
155
+ );
156
+ if (!refreshStored) {
157
+ log.error("Failed to store ChatGPT refresh token in CES");
158
+ throw new Error("Failed to store refresh token");
159
+ }
160
+ }
161
+
162
+ if (tokens.expiresIn) {
163
+ const expiresAt = Math.floor(Date.now() / 1000 + tokens.expiresIn);
164
+ await setSecureKeyAsync(
165
+ "credential/chatgpt/expires_at",
166
+ String(expiresAt),
167
+ );
168
+ }
169
+
170
+ // Upsert provider connection
171
+ const db = getDb();
172
+ const authInput = {
173
+ type: "oauth_subscription" as const,
174
+ credential: "credential/chatgpt/access_token",
175
+ };
176
+
177
+ const existing = getConnection(db, CONNECTION_NAME);
178
+ if (existing) {
179
+ const updateResult = updateConnection(db, CONNECTION_NAME, {
180
+ auth: authInput,
181
+ });
182
+ if (!updateResult.ok) {
183
+ log.error(
184
+ { error: updateResult.error },
185
+ "Failed to update chatgpt-subscription connection",
186
+ );
187
+ throw new Error("Failed to update connection");
188
+ }
189
+ } else {
190
+ const createResult = createConnection(db, {
191
+ name: CONNECTION_NAME,
192
+ provider: "openai",
193
+ auth: authInput,
194
+ });
195
+ if (!createResult.ok) {
196
+ log.error(
197
+ { error: createResult.error },
198
+ "Failed to create chatgpt-subscription connection",
199
+ );
200
+ throw new Error("Failed to create connection");
201
+ }
202
+ }
203
+
204
+ log.info("ChatGPT subscription auth flow completed successfully");
205
+ return { ok: true };
206
+ }
207
+
208
+ // ---------------------------------------------------------------------------
209
+ // Route definitions
210
+ // ---------------------------------------------------------------------------
211
+
212
+ export const ROUTES: RouteDefinition[] = [
213
+ {
214
+ operationId: "inference_chatgpt_subscription_auth",
215
+ endpoint: "inference/chatgpt-subscription/auth",
216
+ method: "POST",
217
+ policyKey: "inference/provider-connections",
218
+ summary: "Start ChatGPT subscription OAuth PKCE flow",
219
+ description:
220
+ "Generate a PKCE authorize URL for ChatGPT subscription auth. Returns the URL and state for the client to open in a browser.",
221
+ tags: ["inference"],
222
+ responseBody: z.object({
223
+ authorize_url: z.string(),
224
+ state: z.string(),
225
+ }),
226
+ handler: handleStartAuth,
227
+ },
228
+ {
229
+ operationId: "inference_chatgpt_subscription_auth_exchange",
230
+ endpoint: "inference/chatgpt-subscription/auth/exchange",
231
+ method: "POST",
232
+ policyKey: "inference/provider-connections",
233
+ summary: "Exchange ChatGPT subscription OAuth authorization code",
234
+ description:
235
+ "Accept an authorization code and state from the OAuth redirect, exchange it for tokens, store them in CES, and upsert the provider connection.",
236
+ tags: ["inference"],
237
+ requestBody: z.object({
238
+ code: z.string(),
239
+ state: z.string(),
240
+ }),
241
+ responseBody: z.object({
242
+ ok: z.boolean(),
243
+ }),
244
+ handler: handleExchange,
245
+ },
246
+ ];
@@ -17,11 +17,16 @@ import { z } from "zod";
17
17
 
18
18
  import { getConfig } from "../../config/loader.js";
19
19
  import { getMemoryCheckpoint } from "../../memory/checkpoints.js";
20
+ import {
21
+ getMessageRoleStatsByConversation,
22
+ listConversationsBySource,
23
+ } from "../../memory/conversation-queries.js";
20
24
  import {
21
25
  enqueueMemoryJob,
22
26
  hasActiveJobOfType,
23
27
  } from "../../memory/jobs-store.js";
24
28
  import { GRAPH_MAINTENANCE_CHECKPOINTS } from "../../memory/jobs-worker.js";
29
+ import { MEMORY_V2_CONSOLIDATION_SOURCE } from "../../memory/v2/constants.js";
25
30
  import { BadRequestError } from "./errors.js";
26
31
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
27
32
 
@@ -111,4 +116,99 @@ export const ROUTES: RouteDefinition[] = [
111
116
  return { success: true, ran: true, jobId };
112
117
  },
113
118
  },
119
+ {
120
+ operationId: "listConsolidationRuns",
121
+ endpoint: "consolidation/runs",
122
+ method: "GET",
123
+ policyKey: "consolidation",
124
+ summary: "List consolidation runs",
125
+ description:
126
+ "Return recent memory v2 consolidation conversations as run records. " +
127
+ "Each consolidation dispatch creates exactly one background conversation " +
128
+ "tagged with `source = memory_v2_consolidation`; that conversation IS " +
129
+ "the run. Synthetic fields: `id` mirrors `conversationId` (no separate " +
130
+ "run row exists), `scheduledFor` and `startedAt` both equal " +
131
+ "`conversation.createdAt` (no separate schedule timestamp), " +
132
+ "`finishedAt` is the `createdAt` of the latest assistant message in " +
133
+ "the conversation (NOT `conversation.lastMessageAt`, which the kickoff " +
134
+ "user prompt bumps before the agent runs). `status` is `'ok'` when " +
135
+ "the conversation has at least one assistant message — i.e. positive " +
136
+ "evidence the agent emitted output — otherwise `'running'`. This is a " +
137
+ "weaker signal than heartbeat's `'ok'`: without a dedicated runs " +
138
+ "table we cannot distinguish 'ran cleanly' from 'crashed after " +
139
+ "emitting at least one assistant message'. `skipReason` and `error` " +
140
+ "are always null — skipped runs (lock held, disabled, empty buffer) " +
141
+ "never create a conversation, and run failure detail is not stored " +
142
+ "on the conversation row. Shape mirrors `heartbeat/runs` so the " +
143
+ "schedules settings UI can reuse its run-row component.",
144
+ tags: ["consolidation"],
145
+ queryParams: [
146
+ {
147
+ name: "limit",
148
+ schema: { type: "integer" },
149
+ description: "Max runs to return (default 20, max 100)",
150
+ },
151
+ ],
152
+ responseBody: z.object({
153
+ runs: z
154
+ .array(
155
+ z.object({
156
+ id: z.string(),
157
+ scheduledFor: z.number(),
158
+ startedAt: z.number().nullable(),
159
+ finishedAt: z.number().nullable(),
160
+ durationMs: z.number().nullable(),
161
+ status: z.enum(["ok", "running"]),
162
+ skipReason: z.string().nullable(),
163
+ error: z.string().nullable(),
164
+ conversationId: z.string().nullable(),
165
+ createdAt: z.number(),
166
+ }),
167
+ )
168
+ .describe("Consolidation run records"),
169
+ }),
170
+ handler: async ({ queryParams }: RouteHandlerArgs) => {
171
+ const params = queryParams ?? {};
172
+ const rawLimit = Number(params.limit ?? 20);
173
+ const limit = Number.isFinite(rawLimit)
174
+ ? Math.min(Math.max(Math.floor(rawLimit), 1), 100)
175
+ : 20;
176
+ const rows = listConversationsBySource(
177
+ MEMORY_V2_CONSOLIDATION_SOURCE,
178
+ limit,
179
+ );
180
+ // Aggregate assistant-message stats in one batched query: presence of
181
+ // an assistant message is the strongest "agent emitted output" signal
182
+ // available without a dedicated consolidation runs table. The kickoff
183
+ // user prompt is persisted via `addMessage` before the agent run,
184
+ // which bumps `conversations.lastMessageAt` — so that field cannot
185
+ // be used to infer completion.
186
+ const assistantStats = getMessageRoleStatsByConversation(
187
+ rows.map((r) => r.id),
188
+ "assistant",
189
+ );
190
+ return {
191
+ runs: rows.map((c) => {
192
+ const stat = assistantStats.get(c.id);
193
+ const hasAssistantOutput = (stat?.count ?? 0) > 0;
194
+ const finishedAt = hasAssistantOutput ? stat!.lastAt : null;
195
+ return {
196
+ id: c.id,
197
+ scheduledFor: c.createdAt,
198
+ startedAt: c.createdAt,
199
+ finishedAt,
200
+ durationMs:
201
+ finishedAt != null ? finishedAt - c.createdAt : null,
202
+ status: (hasAssistantOutput ? "ok" : "running") as
203
+ | "ok"
204
+ | "running",
205
+ skipReason: null,
206
+ error: null,
207
+ conversationId: c.id,
208
+ createdAt: c.createdAt,
209
+ };
210
+ }),
211
+ };
212
+ },
213
+ },
114
214
  ];