@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
@@ -8,6 +8,7 @@
8
8
  import { createHash } from "node:crypto";
9
9
 
10
10
  import {
11
+ buildSlackChannelLabelMap,
11
12
  buildSlackUserLabelMap,
12
13
  renderSlackTextForModel,
13
14
  } from "@vellumai/slack-text";
@@ -32,7 +33,6 @@ import type {
32
33
  } from "../../provider-types.js";
33
34
  import { resolveSlackAuth } from "./auth.js";
34
35
  import * as slack from "./client.js";
35
- import { SlackApiError } from "./client.js";
36
36
  import {
37
37
  classifyConversationType,
38
38
  isPrivateConversation,
@@ -44,6 +44,7 @@ import type {
44
44
  SlackSearchMatch,
45
45
  SlackUser,
46
46
  } from "./types.js";
47
+ import { SlackApiError } from "./web-api-transport.js";
47
48
 
48
49
  interface NormalizedSlackUserInfo {
49
50
  displayName: string;
@@ -69,6 +70,15 @@ const PERMANENT_USER_INFO_SLACK_ERRORS = new Set([
69
70
  // Cache normalized Slack user facts to avoid repeated API calls within a session.
70
71
  const userInfoCache = new Map<string, Promise<SlackUserInfoLookupResult>>();
71
72
 
73
+ /**
74
+ * Cache resolved channel names for inline `<#C…>` mention rendering, same
75
+ * lifetime and keying discipline as {@link userInfoCache}. Holds `undefined`
76
+ * for channels this auth provably cannot resolve (not found / no permission)
77
+ * so a wall of history mentioning an inaccessible channel doesn't re-fire a
78
+ * doomed lookup per message batch.
79
+ */
80
+ const channelNameCache = new Map<string, Promise<string | undefined>>();
81
+
72
82
  /**
73
83
  * Cached auth resolved during resolveConnection(), split by direction.
74
84
  *
@@ -307,15 +317,54 @@ function isPermanentSlackUserInfoFailure(err: unknown): boolean {
307
317
  );
308
318
  }
309
319
 
320
+ function slackAuthCacheScope(auth: OAuthConnection | string): string {
321
+ return typeof auth === "string"
322
+ ? `token:${createHash("sha256").update(auth).digest("hex")}`
323
+ : `connection:${auth.id}:${auth.accountInfo ?? ""}`;
324
+ }
325
+
310
326
  function slackUserInfoCacheKey(
311
327
  auth: OAuthConnection | string,
312
328
  userId: string,
313
329
  ): string {
314
- const authScope =
315
- typeof auth === "string"
316
- ? `token:${createHash("sha256").update(auth).digest("hex")}`
317
- : `connection:${auth.id}:${auth.accountInfo ?? ""}`;
318
- return `${authScope}:user:${userId}`;
330
+ return `${slackAuthCacheScope(auth)}:user:${userId}`;
331
+ }
332
+
333
+ /**
334
+ * Resolve a channel's display name for inline mention rendering, cached per
335
+ * auth scope. Returns undefined when the channel has no name (DMs) or this
336
+ * auth cannot see it; transient failures are not cached so a later batch
337
+ * retries.
338
+ */
339
+ async function resolveChannelName(
340
+ auth: OAuthConnection | string,
341
+ channelId: string,
342
+ ): Promise<string | undefined> {
343
+ if (!channelId) {
344
+ return undefined;
345
+ }
346
+ const cacheKey = `${slackAuthCacheScope(auth)}:channel:${channelId}`;
347
+ const cached = channelNameCache.get(cacheKey);
348
+ if (cached) {
349
+ return cached;
350
+ }
351
+
352
+ const resolved = slack.conversationInfo(auth, channelId).then(
353
+ (resp) => trimNonEmpty(resp.channel.name),
354
+ (err: unknown) => {
355
+ // Cache the definitive "this auth cannot resolve it" answers; drop
356
+ // everything else so transient failures retry on the next batch.
357
+ const permanent =
358
+ err instanceof SlackApiError &&
359
+ (err.category === "channel_not_found" || err.category === "permission");
360
+ if (!permanent) {
361
+ channelNameCache.delete(cacheKey);
362
+ }
363
+ return undefined;
364
+ },
365
+ );
366
+ channelNameCache.set(cacheKey, resolved);
367
+ return resolved;
319
368
  }
320
369
 
321
370
  function normalizeSlackUserInfo(
@@ -346,8 +395,9 @@ function trimNonEmpty(value: unknown): string | undefined {
346
395
  return trimmed.length > 0 ? trimmed : undefined;
347
396
  }
348
397
 
349
- export function __resetSlackUserInfoCacheForTests(): void {
398
+ export function __resetSlackMentionCachesForTests(): void {
350
399
  userInfoCache.clear();
400
+ channelNameCache.clear();
351
401
  }
352
402
 
353
403
  function slackUserInfoMetadata(
@@ -460,13 +510,13 @@ function mapMessage(
460
510
 
461
511
  function mapSearchMatch(
462
512
  match: SlackSearchMatch,
463
- userLabels: Record<string, string>,
513
+ labels: SlackMentionLabels,
464
514
  ): Message {
465
515
  return {
466
516
  id: match.ts,
467
517
  conversationId: match.channel.id,
468
518
  sender: { id: match.user ?? "unknown", name: match.username ?? "unknown" },
469
- text: renderSlackTextForModel(match.text, { userLabels }),
519
+ text: renderSlackTextForModel(match.text, labels),
470
520
  timestamp: parseFloat(match.ts) * 1000,
471
521
  threadId: match.thread_ts,
472
522
  platform: "slack",
@@ -479,7 +529,7 @@ async function mapSlackMessages(
479
529
  channelId: string,
480
530
  slackMessages: SlackMessage[],
481
531
  ): Promise<Message[]> {
482
- const userLabels = await buildMentionUserLabels(
532
+ const labels = await buildMentionLabels(
483
533
  auth,
484
534
  slackMessages.map((msg) => msg.text),
485
535
  );
@@ -491,31 +541,48 @@ async function mapSlackMessages(
491
541
  msg,
492
542
  channelId,
493
543
  senderInfo,
494
- renderSlackTextForModel(msg.text, { userLabels }),
544
+ renderSlackTextForModel(msg.text, labels),
495
545
  ),
496
546
  );
497
547
  }
498
548
  return messages;
499
549
  }
500
550
 
501
- async function buildMentionUserLabels(
551
+ interface SlackMentionLabels {
552
+ userLabels: Record<string, string>;
553
+ channelLabels: Record<string, string>;
554
+ }
555
+
556
+ /**
557
+ * Resolve display labels for every user and channel mentioned inline in the
558
+ * given texts, so bare `<@U…>` / `<#C…>` tokens render as names instead of
559
+ * falling back to `@unknown-user` / `#unknown-channel`. Pipe-form tokens
560
+ * carry their own label and are skipped by the map builders.
561
+ */
562
+ async function buildMentionLabels(
502
563
  auth: OAuthConnection | string,
503
- textValues: Iterable<string | undefined>,
504
- ): Promise<Record<string, string>> {
505
- return buildSlackUserLabelMap(textValues, (userId) =>
506
- resolveUserName(auth, userId),
507
- );
564
+ textValues: readonly (string | undefined)[],
565
+ ): Promise<SlackMentionLabels> {
566
+ const [userLabels, channelLabels] = await Promise.all([
567
+ buildSlackUserLabelMap(textValues, (userId) =>
568
+ resolveUserName(auth, userId),
569
+ ),
570
+ buildSlackChannelLabelMap(textValues, (channelId) =>
571
+ resolveChannelName(auth, channelId),
572
+ ),
573
+ ]);
574
+ return { userLabels, channelLabels };
508
575
  }
509
576
 
510
577
  async function mapSearchMatches(
511
578
  auth: OAuthConnection | string,
512
579
  matches: SlackSearchMatch[],
513
580
  ): Promise<Message[]> {
514
- const userLabels = await buildMentionUserLabels(
581
+ const labels = await buildMentionLabels(
515
582
  auth,
516
583
  matches.map((match) => match.text),
517
584
  );
518
- return matches.map((match) => mapSearchMatch(match, userLabels));
585
+ return matches.map((match) => mapSearchMatch(match, labels));
519
586
  }
520
587
 
521
588
  export const slackProvider: MessagingProvider = {
@@ -1,9 +1,31 @@
1
1
  import { afterAll, afterEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
3
  const BOT_TOKEN = "xoxb-test";
4
+ const USER_TOKEN = "xoxp-test";
4
5
 
6
+ /** Set false to simulate an install whose Slack setup stored no user token. */
7
+ let userTokenStored = true;
8
+
9
+ function secureKeyFor(account: string): string | undefined {
10
+ if (account.endsWith("/user_token")) {
11
+ return userTokenStored ? USER_TOKEN : undefined;
12
+ }
13
+ return BOT_TOKEN;
14
+ }
15
+
16
+ // `api.ts` resolves identity through `resolveSlackAuth`, whose import graph
17
+ // (oauth/manual-token-connection.ts and siblings) reads several secure-keys
18
+ // exports beyond the two this test overrides. Spread the real module so the
19
+ // mock keeps every export those imports expect; the key accessors are the
20
+ // only behavior under the test's control.
21
+ const realSecureKeys = await import("../../../security/secure-keys.js");
5
22
  mock.module("../../../security/secure-keys.js", () => ({
6
- getSecureKeyAsync: async () => BOT_TOKEN,
23
+ ...realSecureKeys,
24
+ getSecureKeyAsync: async (account: string) => secureKeyFor(account),
25
+ getSecureKeyResultAsync: async (account: string) => ({
26
+ value: secureKeyFor(account),
27
+ unreachable: false,
28
+ }),
7
29
  }));
8
30
 
9
31
  const { appendSlackStream, getSlackConversationInfo, startSlackStream } =
@@ -52,6 +74,68 @@ describe("getSlackConversationInfo", () => {
52
74
  Authorization: `Bearer ${BOT_TOKEN}`,
53
75
  });
54
76
  });
77
+
78
+ test("retries as the user when the bot cannot see the channel", async () => {
79
+ userTokenStored = true;
80
+ const authHeaders: string[] = [];
81
+ globalThis.fetch = mock(async (_input, init) => {
82
+ const auth = (init?.headers as Record<string, string>).Authorization;
83
+ authHeaders.push(auth);
84
+ if (auth === `Bearer ${BOT_TOKEN}`) {
85
+ return new Response(
86
+ JSON.stringify({ ok: false, error: "channel_not_found" }),
87
+ { status: 200, headers: { "content-type": "application/json" } },
88
+ );
89
+ }
90
+ return new Response(
91
+ JSON.stringify({ ok: true, channel: { id: "C9", name: "private" } }),
92
+ { status: 200, headers: { "content-type": "application/json" } },
93
+ );
94
+ }) as unknown as typeof fetch;
95
+
96
+ const info = await getSlackConversationInfo("C9");
97
+
98
+ expect(info).toEqual({ id: "C9", name: "private" });
99
+ expect(authHeaders).toEqual([
100
+ `Bearer ${BOT_TOKEN}`,
101
+ `Bearer ${USER_TOKEN}`,
102
+ ]);
103
+ });
104
+
105
+ test("does not retry as the user for non-visibility errors", async () => {
106
+ userTokenStored = true;
107
+ let calls = 0;
108
+ globalThis.fetch = mock(async () => {
109
+ calls++;
110
+ return new Response(
111
+ JSON.stringify({ ok: false, error: "invalid_auth" }),
112
+ { status: 200, headers: { "content-type": "application/json" } },
113
+ );
114
+ }) as unknown as typeof fetch;
115
+
116
+ await expect(getSlackConversationInfo("C9")).rejects.toThrow(
117
+ "Slack API error: invalid_auth",
118
+ );
119
+ expect(calls).toBe(1);
120
+ });
121
+
122
+ test("does not retry when no distinct user token is stored", async () => {
123
+ userTokenStored = false;
124
+ let calls = 0;
125
+ globalThis.fetch = mock(async () => {
126
+ calls++;
127
+ return new Response(
128
+ JSON.stringify({ ok: false, error: "channel_not_found" }),
129
+ { status: 200, headers: { "content-type": "application/json" } },
130
+ );
131
+ }) as unknown as typeof fetch;
132
+
133
+ await expect(getSlackConversationInfo("C9")).rejects.toThrow(
134
+ "Slack API error: channel_not_found",
135
+ );
136
+ expect(calls).toBe(1);
137
+ userTokenStored = true;
138
+ });
55
139
  });
56
140
 
57
141
  describe("startSlackStream", () => {