@vellumai/assistant 0.11.1 → 0.11.2-staging.2

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 (318) hide show
  1. package/Dockerfile +1 -3
  2. package/README.md +1 -1
  3. package/eslint.config.mjs +28 -8
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
  7. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
  9. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
  12. package/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
  13. package/node_modules/@vellumai/slack-text/src/index.test.ts +48 -0
  14. package/node_modules/@vellumai/slack-text/src/index.ts +25 -6
  15. package/openapi.yaml +292 -5
  16. package/package.json +1 -1
  17. package/src/__tests__/agent-loop-resume-interrupted.test.ts +223 -0
  18. package/src/__tests__/byok-default-profile-ensure.test.ts +43 -0
  19. package/src/__tests__/cli-logger-boundary-guard.test.ts +87 -0
  20. package/src/__tests__/compaction-events.test.ts +139 -2
  21. package/src/__tests__/config-loader-backfill.test.ts +35 -3
  22. package/src/__tests__/config-schema.test.ts +1 -0
  23. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +30 -0
  24. package/src/__tests__/conversation-agent-loop.test.ts +180 -7
  25. package/src/__tests__/conversation-error.test.ts +211 -4
  26. package/src/__tests__/conversation-options-turn-scoped-transport.test.ts +64 -0
  27. package/src/__tests__/conversation-queue.test.ts +126 -0
  28. package/src/__tests__/conversation-retry-route.test.ts +46 -7
  29. package/src/__tests__/conversation-slash.test.ts +4 -2
  30. package/src/__tests__/conversation-summarize-route.test.ts +7 -1
  31. package/src/__tests__/conversation-summarize-up-to.test.ts +71 -5
  32. package/src/__tests__/document-create-dedupe.test.ts +2 -1
  33. package/src/__tests__/document-find-replace.test.ts +2 -1
  34. package/src/__tests__/document-tool-security.test.ts +2 -1
  35. package/src/__tests__/document-update-default-surface.test.ts +2 -1
  36. package/src/__tests__/document-workspace-file.test.ts +467 -0
  37. package/src/__tests__/emit-signal-routing-intent.test.ts +290 -49
  38. package/src/__tests__/guardian-card-withdrawal.test.ts +91 -3
  39. package/src/__tests__/http-user-message-parity.test.ts +66 -0
  40. package/src/__tests__/list-messages-attachments.test.ts +87 -0
  41. package/src/__tests__/list-messages-provider-error.test.ts +143 -0
  42. package/src/__tests__/list-messages-system-card.test.ts +100 -0
  43. package/src/__tests__/llm-resolver.test.ts +19 -9
  44. package/src/__tests__/managed-profile-guard.test.ts +23 -0
  45. package/src/__tests__/notification-platform-adapter.test.ts +130 -2
  46. package/src/__tests__/notification-telegram-adapter.test.ts +6 -0
  47. package/src/__tests__/notification-vellum-adapter.test.ts +45 -0
  48. package/src/__tests__/plugin-api-model-profiles.test.ts +10 -1
  49. package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
  50. package/src/__tests__/provider-error-scenarios.test.ts +140 -0
  51. package/src/__tests__/provider-send-message-override-profile.test.ts +95 -0
  52. package/src/__tests__/run-conversation-turn-persistence.test.ts +66 -3
  53. package/src/__tests__/scripted-turn-metadata-persistence.test.ts +209 -0
  54. package/src/__tests__/skills.test.ts +27 -0
  55. package/src/__tests__/slack-channels-routes.test.ts +0 -2
  56. package/src/__tests__/slack-share-routes.test.ts +0 -3
  57. package/src/__tests__/slack-users-routes.test.ts +0 -2
  58. package/src/__tests__/subagent-tools.test.ts +11 -0
  59. package/src/__tests__/tool-preview-lifecycle.test.ts +58 -0
  60. package/src/__tests__/tool-result-spool.test.ts +5 -4
  61. package/src/__tests__/turn-boundary-resolution.test.ts +53 -0
  62. package/src/__tests__/turn-events-store.test.ts +26 -0
  63. package/src/__tests__/ui-visual-surface.test.ts +695 -0
  64. package/src/__tests__/unified-turn-context-visible-app.test.ts +99 -0
  65. package/src/__tests__/visible-app-context.test.ts +189 -0
  66. package/src/__tests__/workspace-git-service.test.ts +173 -33
  67. package/src/__tests__/workspace-migration-137-repair-retired-fireworks-minimax-model-id.test.ts +157 -0
  68. package/src/__tests__/workspace-migration-138-backfill-home-feed-titles.test.ts +373 -0
  69. package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +137 -0
  70. package/src/agent/loop.ts +36 -7
  71. package/src/api/events/context-window-usage.ts +31 -0
  72. package/src/api/events/notification-intent.ts +8 -0
  73. package/src/api/events/ui-surface-pending.ts +35 -0
  74. package/src/api/index.ts +14 -0
  75. package/src/api/responses/conversation-message.ts +25 -4
  76. package/src/api/surfaces.ts +90 -1
  77. package/src/approvals/guardian-card-withdrawal.ts +66 -31
  78. package/src/approvals/guardian-decision-primitive.ts +4 -0
  79. package/src/calls/__tests__/call-setup-router.test.ts +156 -26
  80. package/src/calls/__tests__/voice-session-bridge.test.ts +201 -12
  81. package/src/calls/call-setup-router.ts +80 -36
  82. package/src/calls/voice-session-bridge.ts +173 -26
  83. package/src/cli/commands/inference.help.ts +3 -3
  84. package/src/cli/commands/notifications.help.ts +3 -2
  85. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +42 -128
  86. package/src/cli/commands/platform/__tests__/credits.test.ts +9 -78
  87. package/src/cli/commands/platform/__tests__/helpers.ts +90 -0
  88. package/src/cli/commands/platform/__tests__/invoices.test.ts +238 -0
  89. package/src/cli/commands/platform/__tests__/plans.test.ts +9 -88
  90. package/src/cli/commands/platform/__tests__/status.test.ts +12 -87
  91. package/src/cli/commands/platform/__tests__/subscription.test.ts +9 -86
  92. package/src/cli/commands/platform/index.help.ts +92 -0
  93. package/src/cli/commands/platform/index.ts +7 -0
  94. package/src/cli/commands/platform/invoices.ts +132 -0
  95. package/src/cli/commands/usage.help.ts +1 -1
  96. package/src/cli/lib/list-installed-plugins.ts +2 -1
  97. package/src/config/__tests__/default-profile-catalog.test.ts +7 -10
  98. package/src/config/__tests__/deployment-context-defaults.test.ts +27 -5
  99. package/src/config/assistant-feature-flags.ts +7 -2
  100. package/src/config/bundled-skills/app-builder/SKILL.md +3 -2
  101. package/src/config/bundled-skills/subagent/SKILL.md +3 -1
  102. package/src/config/bundled-skills/subagent/TOOLS.json +3 -3
  103. package/src/config/bundled-skills/visualize/SKILL.md +163 -0
  104. package/src/config/call-site-defaults.ts +3 -2
  105. package/src/config/default-profile-catalog.ts +75 -70
  106. package/src/config/default-profile-names.ts +13 -28
  107. package/src/config/env-registry.ts +1 -0
  108. package/src/config/feature-flag-registry.json +16 -0
  109. package/src/config/llm-resolver.ts +21 -5
  110. package/src/config/loader.ts +18 -11
  111. package/src/config/schemas/llm.ts +1 -9
  112. package/src/config/schemas/memory-retrospective.ts +9 -0
  113. package/src/config/schemas/monitoring.ts +28 -2
  114. package/src/config/schemas/workspace-git.ts +15 -0
  115. package/src/config/seed-inference-profiles.ts +19 -5
  116. package/src/context/post-turn-tool-result-truncation.ts +2 -2
  117. package/src/conversations/__tests__/message-consolidation.test.ts +54 -0
  118. package/src/conversations/message-consolidation.ts +17 -15
  119. package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +182 -0
  120. package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +181 -0
  121. package/src/daemon/conversation-agent-loop-handlers.ts +90 -7
  122. package/src/daemon/conversation-agent-loop.ts +107 -22
  123. package/src/daemon/conversation-error.ts +169 -48
  124. package/src/daemon/conversation-messaging.ts +87 -4
  125. package/src/daemon/conversation-process.ts +57 -45
  126. package/src/daemon/conversation-runtime-assembly.ts +57 -0
  127. package/src/daemon/conversation-store.ts +36 -1
  128. package/src/daemon/conversation-surfaces.ts +68 -6
  129. package/src/daemon/conversation-turn-finalize.ts +76 -18
  130. package/src/daemon/conversation.ts +122 -27
  131. package/src/daemon/lifecycle.ts +9 -0
  132. package/src/daemon/message-types/conversations.ts +8 -0
  133. package/src/daemon/message-types/surfaces.ts +3 -0
  134. package/src/documents/document-store.ts +247 -10
  135. package/src/home/__tests__/feed-types.test.ts +43 -8
  136. package/src/home/__tests__/feed-writer.test.ts +59 -0
  137. package/src/home/feed-types.ts +34 -7
  138. package/src/home/feed-writer.ts +15 -6
  139. package/src/live-voice/__tests__/activity-label.test.ts +95 -0
  140. package/src/live-voice/__tests__/live-activity-reporter.test.ts +86 -5
  141. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +310 -25
  142. package/src/live-voice/__tests__/live-voice-events.test.ts +4 -4
  143. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +8 -4
  144. package/src/live-voice/__tests__/live-voice-vad.test.ts +9 -1
  145. package/src/live-voice/activity-label.ts +169 -0
  146. package/src/live-voice/live-activity-reporter.ts +35 -6
  147. package/src/live-voice/live-voice-session.ts +282 -41
  148. package/src/live-voice/protocol.ts +35 -0
  149. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +84 -2
  150. package/src/messaging/providers/slack/adapter.ts +86 -19
  151. package/src/messaging/providers/slack/api.test.ts +85 -1
  152. package/src/messaging/providers/slack/api.ts +121 -288
  153. package/src/messaging/providers/slack/client.ts +20 -251
  154. package/src/messaging/providers/slack/send.test.ts +4 -9
  155. package/src/messaging/providers/slack/send.ts +1 -1
  156. package/src/messaging/providers/slack/types.ts +5 -0
  157. package/src/messaging/providers/slack/web-api-transport.test.ts +181 -0
  158. package/src/messaging/providers/slack/web-api-transport.ts +367 -0
  159. package/src/messaging/providers/slack/withdraw.ts +6 -14
  160. package/src/messaging/providers/telegram-bot/send.test.ts +31 -1
  161. package/src/messaging/providers/telegram-bot/send.ts +23 -2
  162. package/src/messaging/providers/telegram-bot/withdraw.test.ts +152 -0
  163. package/src/messaging/providers/telegram-bot/withdraw.ts +166 -0
  164. package/src/monitoring/__tests__/db-integrity-sample.test.ts +18 -4
  165. package/src/monitoring/__tests__/file-descriptors.test.ts +144 -0
  166. package/src/monitoring/file-descriptors.ts +262 -0
  167. package/src/monitoring/process-memory.ts +4 -11
  168. package/src/monitoring/resource-sampler.ts +5 -0
  169. package/src/monitoring/worker.ts +12 -0
  170. package/src/notifications/__tests__/assistant-reply-producer.test.ts +740 -0
  171. package/src/notifications/__tests__/broadcaster.test.ts +347 -7
  172. package/src/notifications/__tests__/copy-composer.test.ts +53 -3
  173. package/src/notifications/__tests__/decision-engine.test.ts +349 -29
  174. package/src/notifications/__tests__/deterministic-checks.test.ts +21 -0
  175. package/src/notifications/__tests__/edit-notification.test.ts +330 -0
  176. package/src/notifications/__tests__/guardian-delivery-recorder.test.ts +71 -0
  177. package/src/notifications/__tests__/home-feed-side-effect.test.ts +170 -12
  178. package/src/notifications/adapters/macos.ts +3 -0
  179. package/src/notifications/adapters/platform.ts +90 -14
  180. package/src/notifications/adapters/telegram.ts +6 -4
  181. package/src/notifications/assistant-reply-producer.ts +219 -0
  182. package/src/notifications/broadcaster.ts +523 -317
  183. package/src/notifications/copy-composer.ts +27 -6
  184. package/src/notifications/decision-engine.ts +154 -77
  185. package/src/notifications/deterministic-checks.ts +6 -7
  186. package/src/notifications/edit-notification.ts +8 -4
  187. package/src/notifications/emit-signal.ts +49 -16
  188. package/src/notifications/guardian-delivery-recorder.ts +8 -2
  189. package/src/notifications/home-feed-side-effect.ts +43 -6
  190. package/src/notifications/notification-utils.ts +39 -4
  191. package/src/notifications/signal.ts +10 -0
  192. package/src/notifications/types.ts +17 -0
  193. package/src/persistence/bookmark-crud.ts +18 -9
  194. package/src/persistence/conversation-attention-store.ts +26 -0
  195. package/src/persistence/conversation-crud.ts +79 -32
  196. package/src/persistence/conversation-queries.ts +30 -15
  197. package/src/persistence/conversation-title-service.ts +5 -170
  198. package/src/persistence/conversation-types.ts +192 -1
  199. package/src/persistence/db-init.ts +14 -1
  200. package/src/persistence/db-maintenance.ts +5 -4
  201. package/src/persistence/embeddings/__tests__/plugin-index-qdrant-init.test.ts +219 -0
  202. package/src/persistence/embeddings/__tests__/plugin-index.test.ts +22 -0
  203. package/src/persistence/embeddings/__tests__/worker-script-version.test.ts +76 -0
  204. package/src/persistence/embeddings/embedding-local.ts +2 -1
  205. package/src/persistence/embeddings/embedding-runtime-manager.ts +29 -5
  206. package/src/persistence/embeddings/plugin-index.ts +84 -12
  207. package/src/persistence/migrations/360-add-document-workspace-path.test.ts +110 -0
  208. package/src/persistence/migrations/360-add-document-workspace-path.ts +39 -0
  209. package/src/persistence/planner-statistics.ts +14 -0
  210. package/src/persistence/schema/documents.ts +26 -11
  211. package/src/persistence/steps.ts +2 -0
  212. package/src/platform/client.test.ts +172 -2
  213. package/src/platform/client.ts +178 -55
  214. package/src/plugin-api/conversation-turn.ts +13 -0
  215. package/src/plugin-api/model-profiles.test.ts +6 -2
  216. package/src/plugins/defaults/memory/__tests__/bookmark-crud.test.ts +34 -0
  217. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +22 -0
  218. package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +11 -0
  219. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +199 -0
  220. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +98 -1
  221. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +105 -1
  222. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +24 -2
  223. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +524 -0
  224. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +1 -1
  225. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +9 -4
  226. package/src/plugins/defaults/memory/host-utils.ts +5 -0
  227. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +105 -1
  228. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +64 -5
  229. package/src/plugins/defaults/memory/memory-retrospective-job.ts +36 -1
  230. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +12 -3
  231. package/src/plugins/defaults/memory/src/__tests__/memory-v2-simulate-route.test.ts +1 -0
  232. package/src/plugins/defaults/memory/substrate/__tests__/skill-store.test.ts +195 -1
  233. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +1 -1
  234. package/src/plugins/defaults/memory/substrate/skill-store.ts +167 -29
  235. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +131 -0
  236. package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +1 -0
  237. package/src/plugins/defaults/memory/v2/activation-log-store.ts +4 -0
  238. package/src/plugins/defaults/memory/v2/injection.ts +48 -3
  239. package/src/plugins/defaults/memory/v2/rerank-local.ts +6 -2
  240. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -1
  241. package/src/plugins/defaults/platform-hosted/routes/reengage.ts +1 -1
  242. package/src/plugins/defaults/turn-context/injectors.ts +1 -0
  243. package/src/plugins/defaults/turn-context/unified-turn-context.ts +26 -0
  244. package/src/plugins/types.ts +17 -0
  245. package/src/prompts/templates/system-sections.ts +2 -2
  246. package/src/providers/__tests__/dispatch-connection-routing.test.ts +43 -2
  247. package/src/providers/__tests__/vellum-mismatch-routing.test.ts +8 -0
  248. package/src/providers/call-site-routing.ts +77 -14
  249. package/src/providers/connection-resolution.ts +42 -2
  250. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +127 -1
  251. package/src/providers/inference/adapter-factory.ts +70 -12
  252. package/src/providers/model-catalog.ts +0 -11
  253. package/src/providers/model-intents.ts +11 -0
  254. package/src/providers/registry.ts +8 -0
  255. package/src/providers/retry.ts +85 -17
  256. package/src/providers/types.ts +8 -1
  257. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  258. package/src/runtime/agent-wake.ts +132 -9
  259. package/src/runtime/channel-approval-types.ts +25 -0
  260. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +2 -2
  261. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +11 -0
  262. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +121 -9
  263. package/src/runtime/routes/__tests__/platform-invoice-routes.test.ts +392 -0
  264. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +7 -0
  265. package/src/runtime/routes/__tests__/workspace-commit-routes.test.ts +63 -0
  266. package/src/runtime/routes/consolidation-routes.ts +1 -1
  267. package/src/runtime/routes/conversation-management-routes.ts +18 -6
  268. package/src/runtime/routes/conversation-query-routes.ts +14 -23
  269. package/src/runtime/routes/conversation-routes.ts +113 -8
  270. package/src/runtime/routes/credential-routes.ts +1 -5
  271. package/src/runtime/routes/documents-routes.ts +207 -12
  272. package/src/runtime/routes/identity-routes.ts +3 -93
  273. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +31 -1
  274. package/src/runtime/routes/inbound-stages/admission-policy.ts +18 -0
  275. package/src/runtime/routes/inference-provider-connection-routes.ts +52 -3
  276. package/src/runtime/routes/notification-routes.ts +4 -1
  277. package/src/runtime/routes/platform-routes.ts +238 -3
  278. package/src/runtime/routes/playground/guard.ts +1 -2
  279. package/src/runtime/routes/slack-channel-routes.ts +2 -4
  280. package/src/runtime/routes/workspace-commit-routes.ts +49 -2
  281. package/src/runtime/routes/workspace-routes.ts +11 -12
  282. package/src/runtime/routes/workspace-utils.ts +49 -2
  283. package/src/runtime/services/conversation-serializer.ts +2 -4
  284. package/src/subagent/__tests__/consult-context-gating.test.ts +98 -0
  285. package/src/subagent/__tests__/consult-context-skills.test.ts +82 -0
  286. package/src/subagent/__tests__/consult-context.test.ts +84 -0
  287. package/src/subagent/__tests__/consult-prompt.test.ts +36 -0
  288. package/src/subagent/consult-context.ts +410 -0
  289. package/src/subagent/consult-prompt.ts +38 -5
  290. package/src/subagent/manager.ts +9 -4
  291. package/src/subagent/types.ts +8 -0
  292. package/src/telemetry/telemetry-event-sources.ts +7 -0
  293. package/src/telemetry/telemetry-wire-source.json +1 -1
  294. package/src/telemetry/telemetry-wire.generated.ts +1 -0
  295. package/src/telemetry/turn-events-store.ts +30 -1
  296. package/src/telemetry/types.ts +24 -0
  297. package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
  298. package/src/tools/browser/pinned-tabs.ts +3 -1
  299. package/src/tools/subagent/spawn.ts +23 -1
  300. package/src/tools/terminal/safe-env.ts +1 -0
  301. package/src/tools/ui-surface/definitions.ts +39 -1
  302. package/src/tools/ui-surface/surface-shape-docs.ts +38 -4
  303. package/src/tools/ui-surface/visual-validation.ts +787 -0
  304. package/src/tools/workflows/run-workflow.test.ts +1 -0
  305. package/src/util/__tests__/short-title.test.ts +229 -0
  306. package/src/util/__tests__/worker-compute.test.ts +65 -0
  307. package/src/util/cgroup-cpu.ts +93 -0
  308. package/src/util/errors.ts +27 -0
  309. package/src/util/process-tree.ts +19 -0
  310. package/src/util/short-title.ts +189 -0
  311. package/src/util/worker-compute.ts +83 -0
  312. package/src/workspace/byok-default-profile-ensure.ts +25 -15
  313. package/src/workspace/git-service.ts +79 -30
  314. package/src/workspace/migrations/137-repair-retired-fireworks-minimax-model-id.ts +131 -0
  315. package/src/workspace/migrations/138-backfill-home-feed-titles.ts +179 -0
  316. package/src/workspace/migrations/139-clear-renamed-cost-profile-label.ts +92 -0
  317. package/src/workspace/migrations/registry.ts +6 -0
  318. package/src/plugins/defaults/memory/substrate/constants.ts +0 -8
@@ -1,114 +1,42 @@
1
1
  /**
2
- * Slack Web API client for direct outbound messaging.
2
+ * Slack Web API feature surface for streaming replies, uploads, and channel
3
+ * metadata.
3
4
  *
4
- * Calls the Slack Web API directly using bot_token from the secure store,
5
- * eliminating the gateway HTTP proxy hop. Rate-limit retries, error
6
- * classification, and payload shapes follow Slack Web API conventions.
5
+ * Transport (retries, envelope checking, the unified `SlackApiError`) lives in
6
+ * `web-api-transport.ts`; identity selection lives in `auth.ts`
7
+ * (`resolveSlackAuth`). Each call here names the identity it acts as, per the
8
+ * rules in `auth.ts`:
9
+ * - "bot" for everything the assistant does as itself: streaming replies,
10
+ * uploads, reading its own message blocks for edit-in-place.
11
+ * - "user" only as a bounded FALLBACK: `conversations.info` channel-name
12
+ * resolution acts as the bot first and retries with the stored user token
13
+ * only when the bot cannot see the channel. See
14
+ * {@link getSlackConversationInfo} for why that stays within the
15
+ * neutral-bot rule for route-reachable calls.
7
16
  */
8
17
 
9
18
  import type { KnownBlock } from "@slack/types";
10
19
  import type { SlackStreamTask } from "@vellumai/gateway-client";
11
20
 
12
- import { credentialKey } from "../../../security/credential-key.js";
13
- import { getSecureKeyAsync } from "../../../security/secure-keys.js";
14
- import { getLogger } from "../../../util/logger.js";
15
-
16
- const log = getLogger("slack-api");
17
-
18
- const SLACK_API_BASE = "https://slack.com/api";
19
- const SLACK_MAX_RATE_LIMIT_RETRIES = 3;
20
- const SLACK_DEFAULT_RETRY_AFTER_S = 1;
21
-
22
- // ---------------------------------------------------------------------------
23
- // Error types
24
- // ---------------------------------------------------------------------------
25
-
26
- export type SlackErrorCategory =
27
- | "auth"
28
- | "rate_limit"
29
- | "not_found"
30
- | "permission"
31
- | "channel_not_found"
32
- | "client_error"
33
- | "transient"
34
- | "unknown";
35
-
36
- const SLACK_ERROR_CODE_MAP: Record<string, SlackErrorCategory> = {
37
- invalid_auth: "auth",
38
- token_expired: "auth",
39
- token_revoked: "auth",
40
- not_authed: "auth",
41
- account_inactive: "auth",
42
- org_login_required: "auth",
43
- rate_limited: "rate_limit",
44
- ratelimited: "rate_limit",
45
- channel_not_found: "channel_not_found",
46
- is_archived: "channel_not_found",
47
- not_in_channel: "permission",
48
- missing_scope: "permission",
49
- ekm_access_denied: "permission",
50
- not_allowed_token_type: "permission",
51
- restricted_action: "permission",
52
- cannot_dm_bot: "permission",
53
- user_not_found: "not_found",
54
- message_not_found: "not_found",
55
- thread_not_found: "not_found",
56
- invalid_blocks: "client_error",
57
- };
58
-
59
- function classifySlackError(errorCode: string | undefined): SlackErrorCategory {
60
- if (!errorCode) {
61
- return "unknown";
62
- }
63
- return SLACK_ERROR_CODE_MAP[errorCode] ?? "unknown";
64
- }
65
-
66
- export class SlackApiError extends Error {
67
- readonly slackError: string | undefined;
68
- readonly category: SlackErrorCategory;
69
-
70
- constructor(slackError: string | undefined) {
71
- super(`Slack API error: ${slackError ?? "unknown"}`);
72
- this.name = "SlackApiError";
73
- this.slackError = slackError;
74
- this.category = classifySlackError(slackError);
75
- }
76
- }
77
-
78
- // ---------------------------------------------------------------------------
79
- // Credential resolution
80
- // ---------------------------------------------------------------------------
81
-
82
- async function resolveBotToken(): Promise<string> {
83
- const botToken = await getSecureKeyAsync(
84
- credentialKey("slack_channel", "bot_token"),
85
- );
86
- if (!botToken) {
87
- throw new Error("Slack bot token not configured");
88
- }
89
- return botToken;
90
- }
91
-
92
- // ---------------------------------------------------------------------------
93
- // Core API caller with rate-limit retries
94
- // ---------------------------------------------------------------------------
95
-
96
- interface SlackApiResponse {
97
- ok: boolean;
98
- error?: string;
21
+ import { resolveSlackAuth, type SlackAuthIdentity } from "./auth.js";
22
+ import { conversationInfo } from "./client.js";
23
+ import type {
24
+ SlackApiResponse,
25
+ SlackConversationInfoResponse,
26
+ } from "./types.js";
27
+ import {
28
+ SlackApiError,
29
+ slackRequest,
30
+ type SlackRequestOptions,
31
+ } from "./web-api-transport.js";
32
+
33
+ /** Envelope fields the outbound surfaces read off successful responses. */
34
+ interface SlackOutboundApiResponse extends SlackApiResponse {
99
35
  ts?: string;
100
36
  upload_url?: string;
101
37
  file_id?: string;
102
38
  }
103
39
 
104
- interface SlackConversationsInfoResponse extends SlackApiResponse {
105
- channel?: {
106
- id?: unknown;
107
- name?: unknown;
108
- name_normalized?: unknown;
109
- };
110
- }
111
-
112
40
  export interface SlackConversationInfo {
113
41
  id: string;
114
42
  name?: string;
@@ -116,160 +44,45 @@ export interface SlackConversationInfo {
116
44
  }
117
45
 
118
46
  /**
119
- * Call a Slack Web API method with rate-limit retries.
47
+ * Resolve the named identity and dispatch through the shared transport.
48
+ * Throws when no Slack credentials are configured at all; `resolveSlackAuth`
49
+ * itself falls back user -> bot -> legacy OAuth connection before that.
50
+ */
51
+ async function slackApiRequest<T extends SlackApiResponse>(
52
+ identity: SlackAuthIdentity,
53
+ method: string,
54
+ opts: SlackRequestOptions,
55
+ ): Promise<T> {
56
+ const auth = await resolveSlackAuth(identity);
57
+ if (!auth) {
58
+ throw new Error("Slack bot token not configured");
59
+ }
60
+ return slackRequest<T>(auth, method, opts);
61
+ }
62
+
63
+ /**
64
+ * Call a Slack Web API write method as the bot, with rate-limit retries.
120
65
  *
121
- * Throws SlackApiError for non-retryable Slack-level errors.
122
- * Throws Error for transport-level failures after exhausting retries.
66
+ * Throws SlackApiError for non-retryable Slack-level errors and for
67
+ * transport-level failures after exhausting retries.
123
68
  */
124
69
  export async function callSlackApi(
125
70
  method: string,
126
71
  body: Record<string, unknown>,
127
- ): Promise<SlackApiResponse> {
128
- const botToken = await resolveBotToken();
129
-
130
- let lastError: string | undefined;
131
-
132
- for (let attempt = 0; attempt <= SLACK_MAX_RATE_LIMIT_RETRIES; attempt++) {
133
- const response = await fetch(`${SLACK_API_BASE}/${method}`, {
134
- method: "POST",
135
- headers: {
136
- Authorization: `Bearer ${botToken}`,
137
- "Content-Type": "application/json",
138
- },
139
- body: JSON.stringify(body),
140
- });
141
-
142
- if (response.status === 429) {
143
- if (attempt >= SLACK_MAX_RATE_LIMIT_RETRIES) {
144
- throw new Error("Slack rate limit exceeded after retries");
145
- }
146
- const retryAfter =
147
- parseInt(response.headers.get("Retry-After") ?? "", 10) ||
148
- SLACK_DEFAULT_RETRY_AFTER_S;
149
- log.warn({ method, retryAfter, attempt }, "Slack rate limited, retrying");
150
- await new Promise((r) => setTimeout(r, retryAfter * 1000));
151
- continue;
152
- }
153
-
154
- if (response.status >= 500) {
155
- if (attempt >= SLACK_MAX_RATE_LIMIT_RETRIES) {
156
- throw new Error(
157
- `Slack ${method} failed with status ${response.status} after retries`,
158
- );
159
- }
160
- log.warn(
161
- { method, status: response.status, attempt },
162
- "Slack 5xx error, retrying",
163
- );
164
- await new Promise((r) =>
165
- setTimeout(r, SLACK_DEFAULT_RETRY_AFTER_S * 1000),
166
- );
167
- continue;
168
- }
169
-
170
- const data = (await response.json()) as SlackApiResponse;
171
-
172
- if (!data.ok) {
173
- lastError = data.error;
174
- const category = classifySlackError(data.error);
175
-
176
- if (category === "rate_limit" && attempt < SLACK_MAX_RATE_LIMIT_RETRIES) {
177
- log.warn(
178
- { method, slackError: data.error, attempt },
179
- "Slack rate limited (body), retrying",
180
- );
181
- await new Promise((r) =>
182
- setTimeout(r, SLACK_DEFAULT_RETRY_AFTER_S * 1000),
183
- );
184
- continue;
185
- }
186
-
187
- throw new SlackApiError(data.error);
188
- }
189
-
190
- return data;
191
- }
192
-
193
- throw new Error(
194
- `Slack ${method} failed after retries: ${lastError ?? "unknown"}`,
195
- );
72
+ ): Promise<SlackOutboundApiResponse> {
73
+ return slackApiRequest<SlackOutboundApiResponse>("bot", method, { body });
196
74
  }
197
75
 
198
76
  /**
199
- * Call a Slack Web API read method with query parameters.
77
+ * Call a Slack Web API method as the bot with a form-urlencoded body.
200
78
  */
201
- async function callSlackApiGet(
79
+ export async function callSlackApiForm(
202
80
  method: string,
203
81
  params: URLSearchParams,
204
- ): Promise<SlackApiResponse> {
205
- const botToken = await resolveBotToken();
206
- const query = params.toString();
207
- const url = `${SLACK_API_BASE}/${method}${query ? `?${query}` : ""}`;
208
-
209
- let lastError: string | undefined;
210
-
211
- for (let attempt = 0; attempt <= SLACK_MAX_RATE_LIMIT_RETRIES; attempt++) {
212
- const response = await fetch(url, {
213
- method: "GET",
214
- headers: {
215
- Authorization: `Bearer ${botToken}`,
216
- },
217
- });
218
-
219
- if (response.status === 429) {
220
- if (attempt >= SLACK_MAX_RATE_LIMIT_RETRIES) {
221
- throw new Error("Slack rate limit exceeded after retries");
222
- }
223
- const retryAfter =
224
- parseInt(response.headers.get("Retry-After") ?? "", 10) ||
225
- SLACK_DEFAULT_RETRY_AFTER_S;
226
- log.warn({ method, retryAfter, attempt }, "Slack rate limited, retrying");
227
- await new Promise((r) => setTimeout(r, retryAfter * 1000));
228
- continue;
229
- }
230
-
231
- if (response.status >= 500) {
232
- if (attempt >= SLACK_MAX_RATE_LIMIT_RETRIES) {
233
- throw new Error(
234
- `Slack ${method} failed with status ${response.status} after retries`,
235
- );
236
- }
237
- log.warn(
238
- { method, status: response.status, attempt },
239
- "Slack 5xx error, retrying",
240
- );
241
- await new Promise((r) =>
242
- setTimeout(r, SLACK_DEFAULT_RETRY_AFTER_S * 1000),
243
- );
244
- continue;
245
- }
246
-
247
- const data = (await response.json()) as SlackApiResponse;
248
-
249
- if (!data.ok) {
250
- lastError = data.error;
251
- const category = classifySlackError(data.error);
252
-
253
- if (category === "rate_limit" && attempt < SLACK_MAX_RATE_LIMIT_RETRIES) {
254
- log.warn(
255
- { method, slackError: data.error, attempt },
256
- "Slack rate limited (body), retrying",
257
- );
258
- await new Promise((r) =>
259
- setTimeout(r, SLACK_DEFAULT_RETRY_AFTER_S * 1000),
260
- );
261
- continue;
262
- }
263
-
264
- throw new SlackApiError(data.error);
265
- }
266
-
267
- return data;
268
- }
269
-
270
- throw new Error(
271
- `Slack ${method} failed after retries: ${lastError ?? "unknown"}`,
272
- );
82
+ ): Promise<SlackOutboundApiResponse> {
83
+ return slackApiRequest<SlackOutboundApiResponse>("bot", method, {
84
+ form: params,
85
+ });
273
86
  }
274
87
 
275
88
  // ---------------------------------------------------------------------------
@@ -428,14 +241,9 @@ function normalizeSlackString(value: unknown): string | undefined {
428
241
  return trimmed || undefined;
429
242
  }
430
243
 
431
- export async function getSlackConversationInfo(
432
- channelId: string,
433
- ): Promise<SlackConversationInfo | null> {
434
- const data = (await callSlackApiGet(
435
- "conversations.info",
436
- new URLSearchParams({ channel: channelId }),
437
- )) as SlackConversationsInfoResponse;
438
-
244
+ function parseSlackConversationInfo(
245
+ data: SlackConversationInfoResponse,
246
+ ): SlackConversationInfo | null {
439
247
  const id = normalizeSlackString(data.channel?.id);
440
248
  if (!id) {
441
249
  return null;
@@ -451,6 +259,52 @@ export async function getSlackConversationInfo(
451
259
  };
452
260
  }
453
261
 
262
+ /**
263
+ * Resolve a channel's identity and display names via `conversations.info`.
264
+ *
265
+ * Acts as the bot first, honoring the neutral-bot rule for route-reachable
266
+ * calls (`auth.ts`): this function backs the `slack_channel_name_resolve`
267
+ * route. When the bot cannot see the channel (`channel_not_found` /
268
+ * permission errors) and a DISTINCT user token is stored, it retries once as
269
+ * the owner, so channels the owner is in but the bot is not still resolve.
270
+ * The wider identity is used only for the exact case the bot cannot answer,
271
+ * and the response is scoped to a channel the calling conversation is
272
+ * already bound to. Legacy OAuth installs have no separate user identity, so
273
+ * the fallback never fires there.
274
+ */
275
+ export async function getSlackConversationInfo(
276
+ channelId: string,
277
+ ): Promise<SlackConversationInfo | null> {
278
+ const botAuth = await resolveSlackAuth("bot");
279
+ if (!botAuth) {
280
+ throw new Error("Slack bot token not configured");
281
+ }
282
+
283
+ try {
284
+ return parseSlackConversationInfo(
285
+ await conversationInfo(botAuth, channelId),
286
+ );
287
+ } catch (err) {
288
+ if (
289
+ !(err instanceof SlackApiError) ||
290
+ (err.category !== "channel_not_found" && err.category !== "permission")
291
+ ) {
292
+ throw err;
293
+ }
294
+ const userAuth = await resolveSlackAuth("user");
295
+ if (
296
+ typeof botAuth !== "string" ||
297
+ typeof userAuth !== "string" ||
298
+ userAuth === botAuth
299
+ ) {
300
+ throw err;
301
+ }
302
+ return parseSlackConversationInfo(
303
+ await conversationInfo(userAuth, channelId),
304
+ );
305
+ }
306
+ }
307
+
454
308
  interface SlackHistoryResponse extends SlackApiResponse {
455
309
  messages?: Array<{ ts?: string; blocks?: unknown[] }>;
456
310
  }
@@ -458,55 +312,34 @@ interface SlackHistoryResponse extends SlackApiResponse {
458
312
  /**
459
313
  * Fetch the Block Kit blocks of a single channel message by timestamp.
460
314
  *
461
- * Used to edit a message in place while preserving its existing content e.g.
315
+ * Used to edit a message in place while preserving its existing content, e.g.
462
316
  * withdrawing an approval card's buttons without discarding the card body.
463
- * Returns null when the message can't be read (missing `*:history` scope, a
464
- * threaded reply not present in channel history, or a deleted message) so
465
- * callers can degrade gracefully instead of failing the edit.
317
+ * Acts as the bot: the messages being edited are the bot's own. Returns null
318
+ * when the message can't be read (missing `*:history` scope, a threaded reply
319
+ * not present in channel history, or a deleted message) so callers can degrade
320
+ * gracefully instead of failing the edit.
466
321
  */
467
322
  export async function getSlackMessageBlocks(
468
323
  channelId: string,
469
324
  ts: string,
470
325
  ): Promise<unknown[] | null> {
471
- const data = (await callSlackApiGet(
326
+ const data = await slackApiRequest<SlackHistoryResponse>(
327
+ "bot",
472
328
  "conversations.history",
473
- new URLSearchParams({
474
- channel: channelId,
475
- latest: ts,
476
- oldest: ts,
477
- inclusive: "true",
478
- limit: "1",
479
- }),
480
- )) as SlackHistoryResponse;
329
+ {
330
+ query: {
331
+ channel: channelId,
332
+ latest: ts,
333
+ oldest: ts,
334
+ inclusive: "true",
335
+ limit: "1",
336
+ },
337
+ },
338
+ );
481
339
  const message = data.messages?.find((m) => m.ts === ts) ?? data.messages?.[0];
482
340
  return Array.isArray(message?.blocks) ? message.blocks : null;
483
341
  }
484
342
 
485
- /**
486
- * Call a Slack Web API method with form-urlencoded body.
487
- */
488
- export async function callSlackApiForm(
489
- method: string,
490
- params: URLSearchParams,
491
- ): Promise<SlackApiResponse> {
492
- const botToken = await resolveBotToken();
493
-
494
- const response = await fetch(`${SLACK_API_BASE}/${method}`, {
495
- method: "POST",
496
- headers: {
497
- Authorization: `Bearer ${botToken}`,
498
- "Content-Type": "application/x-www-form-urlencoded",
499
- },
500
- body: params,
501
- });
502
-
503
- const data = (await response.json()) as SlackApiResponse;
504
- if (!data.ok) {
505
- throw new SlackApiError(data.error);
506
- }
507
- return data;
508
- }
509
-
510
343
  /**
511
344
  * Upload raw bytes to a Slack-provided upload URL.
512
345
  */