@sentry/junior 0.272.0 → 0.273.0

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 (70) hide show
  1. package/dist/{adapter-BDYDi1F9.js → adapter-umap1cth.js} +1 -1
  2. package/dist/{admin-Dj7EFycG.js → admin-0o6E_3lo.js} +1 -1
  3. package/dist/{agent-runner-Cl6Usx2Y.js → agent-runner-D7Qz-lGO.js} +517 -298
  4. package/dist/api/schema.d.ts +1 -1
  5. package/dist/api/schema.js +4 -4
  6. package/dist/api.d.ts +1 -1
  7. package/dist/api.js +56 -573
  8. package/dist/{app-DjZblE2J.d.ts → app-BcyhJq8x.d.ts} +4 -9
  9. package/dist/app.d.ts +1 -1
  10. package/dist/app.js +673 -127
  11. package/dist/{assistant-reply-DN2w6JL_.js → assistant-reply-wZ7i4OFa.js} +1 -1
  12. package/dist/{automation-CV7g5h3A.js → automation-yffXcZt9.js} +1 -1
  13. package/dist/{briefs-PysAqnjV.js → briefs-CgMnlpIp.js} +4 -4
  14. package/dist/{catalog-runtime-BNeCWIaF.js → catalog-runtime-TygDsPOU.js} +1 -1
  15. package/dist/cli/chat.js +15 -15
  16. package/dist/cli/check.js +3 -3
  17. package/dist/cli/init.js +1 -1
  18. package/dist/cli/main.js +3 -3
  19. package/dist/cli/plugins.js +4 -4
  20. package/dist/cli/snapshot-create.js +4 -4
  21. package/dist/cli/upgrade.d.ts +1 -1
  22. package/dist/cli/upgrade.js +3 -3
  23. package/dist/{client-FRphcDWW.js → client-DhqbV0Fe.js} +63 -5
  24. package/dist/{reporting-schema-CZREhYc-.js → common-Eu6sx09c.js} +4 -1
  25. package/dist/{config-BUP9wjiR.js → config-C6FTd7PX.js} +6 -14
  26. package/dist/{connections-CesHau9u.js → connections-Dq72KajR.js} +11 -11
  27. package/dist/{conversation-BtQzLoK8.js → conversation-T4oabbBi.js} +10 -0
  28. package/dist/{conversation-privacy-DiGvA9a3.js → conversation-privacy-BeRgrsph.js} +1 -1
  29. package/dist/{credential-sync-D2odJC30.js → credential-sync-BmCbHAp4.js} +15 -16
  30. package/dist/{db-BPLmOwXf.d.ts → db-DOzBGLn5.d.ts} +1 -1
  31. package/dist/{executor-DGDpyegI.js → executor-BafgvQcc.js} +2 -2
  32. package/dist/instrumentation.js +114 -3
  33. package/dist/{job-queue-CjB97sii.js → job-queue-C4lphW9Y.js} +3 -3
  34. package/dist/{logging-CjkDr-jP.js → logging-CNs1e04n.js} +6 -1
  35. package/dist/{message-D9y8PKA1.js → message-CETC5lsl.js} +9 -9
  36. package/dist/nitro.d.ts +1 -1
  37. package/dist/nitro.js +3 -3
  38. package/dist/{oauth-callback-server-02Pej6hj.js → oauth-callback-server-DcQZsKMm.js} +2 -2
  39. package/dist/{credentialed-CgTulRDH.js → projection-BOpi891B.js} +4576 -49
  40. package/dist/{prompt-vfceH0c-.js → prompt-B1ySjUyf.js} +1 -1
  41. package/dist/{registry-7XgwNgQe.js → registry-B-yUrv3h.js} +2 -2
  42. package/dist/{resolve-CuuxNfWG.js → resolve-DqVtd4Q9.js} +3 -3
  43. package/dist/{result-BRhb6iN1.js → result-DQwTiLTw.js} +9 -9
  44. package/dist/{runner-DaWCofuS.js → runner-Dco5qMq6.js} +11 -11
  45. package/dist/{sandbox-egress-signals-Bn6FA1UC.js → sandbox-egress-signals-DFc_9Mcv.js} +1 -1
  46. package/dist/{schema-D5VLF4NT.js → schema-5xHDGlBW.js} +9 -7
  47. package/dist/{schema-CNjAzYOw.d.ts → schema-9OGdsaxF.d.ts} +13 -0
  48. package/dist/{service-account-D9vrsi8d.js → service-account-CWfk9Cs-.js} +3 -3
  49. package/dist/{service-account-H45LIAG2.js → service-account-smdPFWNy.js} +3 -3
  50. package/dist/{session-stop-HKl0ECX0.js → session-stop-DaT_7tYa.js} +11 -11
  51. package/dist/{skills-CsiKu3-g.js → skills-BbtJSF2-.js} +2 -2
  52. package/dist/{spawn-DqjBxqWh.js → spawn-Ceikdjkp.js} +2 -2
  53. package/dist/{list-JZUP1yog.js → stop-dKUc-x2K.js} +996 -432
  54. package/dist/{store-DyVKH0QB.js → store-C1xuZcLd.js} +4 -4
  55. package/dist/{store-CPF2kzbu.js → store-DaxUuOmD.js} +3 -3
  56. package/dist/{task-queue-BbQXCcNX.js → task-queue-ymAh_-P1.js} +4 -4
  57. package/dist/{task-runner-ChzW5E7R.js → task-runner-vlH6pEm3.js} +6 -6
  58. package/dist/{thread-state-Ds8JTsX6.js → thread-state-BkUpE9Ae.js} +4 -5
  59. package/dist/{turn-failure-response-DCMvjsA0.js → turn-failure-response-DIsD7ip_.js} +7 -8
  60. package/dist/{turn-lifecycle-DM_77q33.js → turn-lifecycle-BdPKdPS8.js} +1 -1
  61. package/dist/turn-session-routing-BdCTDq9m.js +1625 -0
  62. package/dist/{validation-DcGelbBi.js → validation-BQjCfh84.js} +1 -1
  63. package/dist/{version-C48azPKq.js → version-BpiFGZZW.js} +1 -1
  64. package/dist/version.js +1 -1
  65. package/dist/{viewer-BCRuO8Wg.js → viewer-C6Y1aXjZ.js} +27 -15
  66. package/dist/{work-zxkmbJAp.js → work-ChPh_YEh.js} +10 -38
  67. package/dist/{worker-CNNxHF1V.js → worker-Be_sK_ou.js} +6 -6
  68. package/package.json +4 -4
  69. package/dist/turn-session-routing-BV_gX7n3.js +0 -6072
  70. package/dist/{oauth-relay-CTygMkNx.js → oauth-relay-C6ZAyJgf.js} +1 -1
package/dist/app.js CHANGED
@@ -1,43 +1,43 @@
1
- import { B as readScheduledAutomation, G as blockEventAutomation, Gr as setExperimentalFeatures, H as saveScheduledAutomationInLock, Ir as SubscribedReplyReason, Jn as hasRunnableConversationWork, K as collectEventAutomationMatchKeys, M as recordAutomationExecution, Mn as CONVERSATION_WORK_STALE_ENQUEUE_MS, R as listScheduledAutomationsForTeam, S as existingConversationId, U as scheduledRunId, Ur as getExperimentalFeatures, W as withScheduledAutomationLock, Wr as isExperimentalFeatureEnabled, Wt as appendConversationMessages, Xn as isInvalidConversationRecordError, Xr as getConfigDefaults, Xt as settleConversationTitleWork, Y as findMatchingEventAutomations, Zr as setConfigDefaults, a as confirmDispatchMailboxAppend, b as notifyAutomationBlocked, f as getPluginDispatchProjection, ht as eventGuidance, it as automationVersionOf, m as listPendingDispatchMailboxAppends, nt as eventAutomationRunLimitReason, o as createOrGetDispatch, p as isTerminalDispatchStatus, qt as projectConversationMessages, r as buildDispatchId, s as getDispatchConversationId, tt as admitEventAutomationRun, u as getDispatchRecord, v as markDispatchFailed, zr as getThreadStopDecision } from "./turn-session-routing-BV_gX7n3.js";
2
- import { _ as snapshotRuntimeConfig, d as restoreRuntimeConfig, f as setBotModelConfig, g as setSlackReactionConfig, h as setRuntimeLimits, i as getChatConfig, m as setProfiles, n as botConfig, o as getSlackBotToken, p as setCrossActorMidRunMode, u as resetRuntimeConfig } from "./config-BUP9wjiR.js";
3
- import { A as JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE, E as withSpan, L as GOOGLE_IDENTITY_PROVIDER, N as JUNIOR_WORKSPACE_SNAPSHOT_JOB_CALLBACK_ROUTE, T as withLogContext, b as setSpanAttributes, h as logWarn, m as logInfo, p as logException, r as createRequestContext, w as withContext, z as normalizeIdentityEmail } from "./logging-CjkDr-jP.js";
1
+ import { $i as runWithWorkspaceTeamId, Ai as findMatchingWatches, C as projectConversationMessages, Ca as createPluginLogger, Dn as getPluginApiRoutes, Dr as isVercelSandboxEgressRequest, E as settleConversationTitleWork, Ei as collectEventMatchKeys, Et as isInvalidConversationRecordError, Fa as readConversationEventPage, Fi as bindScheduledAutomationCredentialSubject, Fn as validatePlugins, Gi as getSlackClient, Gr as postSlackEphemeralMessage, Hn as dismissOpenReviewItems, Ii as bindSlackDirectCredentialSubject, Jn as searchReviewItems, Ka as listConversationAnnotations, Li as verifyEventAutomationCredentialSubject, Mr as resolveBaseUrl, Ni as takeDueTimerWatches, Nn as getPlugins, On as getPluginRoutes, Or as proxyVercelSandboxEgressRequest, Pi as bindEventAutomationCredentialSubject, Pn as setPlugins, Qi as getWorkspaceTeamId, Qn as canActOnReviewItem, Ri as verifyScheduledAutomationCredentialSubject, Rn as setBriefsConfig, Rr as slackMention, Sn as createUserTokenStore, Ua as resolveRootVisibility, Wr as openSlackModal, Xr as slackEntitySchema, Za as createPluginCache, Zr as slackExternalRefIdSchema, aa as setDashboardConversationLinkOptions, ao as setConfigDefaults, ar as reviewItemReaderForUser, b as appendConversationMessages, ba as createPluginState, bn as isExperimentalFeatureEnabled, ci as consumeSandboxEgressPermissionDeniedSignal, ct as CONVERSATION_WORK_STALE_ENQUEUE_MS, fn as SubscribedReplyReason, hn as getThreadStopDecision, ia as getDashboardTaskLink, io as getConfigDefaults, mi as normalizeSandboxEgressTracePropagationDomains, oi as clearSandboxEgressSignals, qi as isDmChannel, ra as getDashboardReviewItemLink, si as consumeSandboxEgressAuthRequiredSignal, ta as getDashboardConversationLink, ui as parseSandboxEgressCredentialToken, wa as readConversationAccessFromSql, wt as hasRunnableConversationWork, xa as createPluginEmbedder, xn as setExperimentalFeatures, yn as getExperimentalFeatures, zi as verifySlackDirectCredentialSubject } from "./projection-BOpi891B.js";
2
+ import { d as restoreRuntimeConfig, f as setBotModelConfig, g as snapshotRuntimeConfig, h as setSlackReactionConfig, i as getChatConfig, m as setRuntimeLimits, n as botConfig, o as getSlackBotToken, p as setProfiles, u as resetRuntimeConfig } from "./config-C6FTd7PX.js";
3
+ import { A as JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE, E as withSpan, L as GOOGLE_IDENTITY_PROVIDER, N as JUNIOR_WORKSPACE_SNAPSHOT_JOB_CALLBACK_ROUTE, T as withLogContext, b as setSpanAttributes, h as logWarn, m as logInfo, p as logException, r as createRequestContext, w as withContext, z as normalizeIdentityEmail } from "./logging-CNs1e04n.js";
4
4
  import { n as captureMessage } from "./sentry-MDkYAVPV.js";
5
- import { t as JUNIOR_VERSION } from "./version-C48azPKq.js";
5
+ import { t as JUNIOR_VERSION } from "./version-BpiFGZZW.js";
6
6
  import { _ as slackMessageTsSchema, f as parseSlackTeamId, g as slackMessageTsFromMs, h as parseSlackMessageTs, m as slackChannelIdSchema, n as createSlackActor, p as parseSlackUserId, s as parseActorUserId, u as parseSlackChannelId } from "./actor-B4DYygOB.js";
7
- import { C as getSqlExecutor, M as resolveActorIdentityId, O as withConversationMutationLock, R as recordGoogleSignIn, S as getDb, T as withConversationEventLock, _ as closeDb, g as createCache, i as readActorIdentity, l as resolveViewerUser, p as parseAvatarUrl, x as getConversationStore, y as getConversationEventStore } from "./viewer-BCRuO8Wg.js";
7
+ import { C as getSqlExecutor, E as withConversationEventLock, N as resolveActorIdentityId, S as getDb, _ as closeDb, g as createCache, i as readActorIdentity, k as withConversationMutationLock, l as resolveViewerUser, p as parseAvatarUrl, x as getConversationStore, y as getConversationEventStore, z as recordGoogleSignIn } from "./viewer-C6Y1aXjZ.js";
8
8
  import { a as juniorAgentInvocations, d as juniorDestinations, i as juniorAgentBindings, s as juniorConversations } from "./agent-invocations-C1RNOCs8.js";
9
- import { _ as juniorConversationBriefs, b as juniorConversationEvents, c as juniorReviewItems, d as juniorAutomations, l as juniorAutomationRuns } from "./executor-DGDpyegI.js";
10
- import { n as pluginCatalogRuntime } from "./catalog-runtime-BNeCWIaF.js";
11
- import { A as verifySlackSignature, B as scheduledAutomationRunAttributes, C as syncReviewItemSlackCards, D as parseSlackReviewItemFormTarget, E as parseSlackReviewItemClick, Et as ingestEvent, I as joinPublicChannel, O as parseSlackUserInputSubmission, S as readSlackReviewItemCardConversationId, T as buildSlackReviewItemModal, Tt as processWorkspaceSnapshotJob, X as readViewerAutomationSummary, _ as unpublishedArtifactHeaders, at as appendAndEnqueueWebMessage, et as effectiveTaskOutcomes, g as readPublicArtifact, h as publicArtifactHeaders, ht as requestAttachmentDeletion, j as parseSlackThreadId, k as runWithSlackInstallation, lt as recordWebConversationActivity, n as executeAgentRun, nt as deliverReviewItemChange, ot as conversationTurnIdForMessage, pt as collectAttachmentGarbage, rt as renderReviewItemMessage, t as createAgentRunner, ut as webActorFromEmail, v as lookupSlackUserProfile, vt as stopConversationSession, x as createSlackReviewItemShow, yt as recordSubagentEnded, z as logScheduledAutomationRunSkipped } from "./agent-runner-Cl6Usx2Y.js";
12
- import { At as slackEntitySchema, Bn as getDashboardTaskLink, Bt as clearSandboxEgressSignals, Cr as resolveRootVisibility, Dn as getSlackClient, Er as listConversationAnnotations, Fn as runWithWorkspaceTeamId, Ht as consumeSandboxEgressPermissionDeniedSignal, Ln as getDashboardConversationLink, M as canActOnReviewItem, Pn as getWorkspaceTeamId, S as dismissOpenReviewItems, Tt as postSlackEphemeralMessage, Vn as setDashboardConversationLinkOptions, Vt as consumeSandboxEgressAuthRequiredSignal, Wt as parseSandboxEgressCredentialToken, Xt as normalizeSandboxEgressTracePropagationDomains, _n as bindScheduledAutomationCredentialSubject, ar as createPluginLogger, bn as verifyScheduledAutomationCredentialSubject, c as getPluginRoutes, cn as collectEventMatchKeys, ct as proxyVercelSandboxEgressRequest, fn as findMatchingWatches, ft as resolveBaseUrl, gn as bindEventAutomationCredentialSubject, h as validatePlugins, hn as takeDueTimerWatches, jr as createPluginCache, jt as slackExternalRefIdSchema, kn as isDmChannel, m as setPlugins, n as createUserTokenStore, nr as createPluginState, or as readConversationAccessFromSql, p as getPlugins, rr as createPluginEmbedder, s as getPluginApiRoutes, st as isVercelSandboxEgressRequest, v as setBriefsConfig, vn as bindSlackDirectCredentialSubject, vt as slackMention, wt as openSlackModal, xn as verifySlackDirectCredentialSubject, yn as verifyEventAutomationCredentialSubject } from "./credentialed-CgTulRDH.js";
9
+ import { _ as juniorConversationBriefs, b as juniorConversationEvents, c as juniorReviewItems, d as juniorAutomations, l as juniorAutomationRuns } from "./executor-BafgvQcc.js";
10
+ import { n as pluginCatalogRuntime } from "./catalog-runtime-TygDsPOU.js";
11
+ import { A as verifySlackSignature, B as scheduledAutomationRunAttributes, C as syncReviewItemSlackCards, D as parseSlackReviewItemFormTarget, E as parseSlackReviewItemClick, Et as ingestEvent, I as joinPublicChannel, J as readViewerAutomationExecutions, K as ViewerTaskNotFoundError, O as parseSlackUserInputSubmission, Q as readViewerAutomations, S as readSlackReviewItemCardConversationId, T as buildSlackReviewItemModal, Tt as processWorkspaceSnapshotJob, X as readViewerAutomationSummary, _ as unpublishedArtifactHeaders, at as appendAndEnqueueWebMessage, et as effectiveTaskOutcomes, g as readPublicArtifact, h as publicArtifactHeaders, ht as requestAttachmentDeletion, j as parseSlackThreadId, k as runWithSlackInstallation, lt as recordWebConversationActivity, n as executeAgentRun, nt as deliverReviewItemChange, ot as conversationTurnIdForMessage, pt as collectAttachmentGarbage, rt as renderReviewItemMessage, t as createAgentRunner, ut as webActorFromEmail, v as lookupSlackUserProfile, vt as stopConversationSession, x as createSlackReviewItemShow, yt as recordSubagentEnded, z as logScheduledAutomationRunSkipped } from "./agent-runner-D7Qz-lGO.js";
13
12
  import { a as withLock, i as fenceLock, n as MUTATION_LOCK_TTL_MS } from "./locks-D-0h111u.js";
14
- import { r as getStateAdapter, t as disconnectStateAdapter } from "./adapter-BDYDi1F9.js";
13
+ import { r as getStateAdapter, t as disconnectStateAdapter } from "./adapter-umap1cth.js";
15
14
  import { n as getNextRunAtMs } from "./cadence-R7Em4RjE.js";
16
- import { D as conversationSurfaceSchema, L as annotationCard, T as conversationReportStatusSchema, h as conversationFeedQuerySchema } from "./conversation-BtQzLoK8.js";
17
- import { b as listPendingAgentInvocationMailboxAppends, x as listPendingAgentInvocationResults } from "./store-DyVKH0QB.js";
18
- import { C as renderTaskInput, O as renderSlackObjectCard, d as getEventCatalog, f as SLACK_EVENT_NAMESPACE, g as slackChannelMessageRunsAutomation, h as slackChannelMessageReplyThreadTs, m as slackChannelMessageEvent } from "./session-stop-HKl0ECX0.js";
15
+ import { D as conversationSurfaceSchema, L as annotationCard, T as conversationReportStatusSchema, Y as conversationBriefSchema, h as conversationFeedQuerySchema } from "./conversation-T4oabbBi.js";
16
+ import { b as listPendingAgentInvocationMailboxAppends, x as listPendingAgentInvocationResults } from "./store-C1xuZcLd.js";
17
+ import { C as renderTaskInput, O as renderSlackObjectCard, d as getEventCatalog, f as SLACK_EVENT_NAMESPACE, g as slackChannelMessageRunsAutomation, h as slackChannelMessageReplyThreadTs, m as slackChannelMessageEvent } from "./session-stop-DaT_7tYa.js";
19
18
  import { h as buttonUserInputField } from "./reporting-window-DgHgwQB3.js";
20
- import { c as setSandboxResourceConfig, o as getSandboxResourceConfig } from "./resolve-CuuxNfWG.js";
21
- import { a as getVercelConversationWorkQueue, d as getQueueProvider, g as isConversationQueueMessageRejectedError, h as conversationQueueMessageSchema, i as verifyWorkspaceSnapshotJobMessage, o as resolveConversationWorkQueueTopic, s as verifyConversationQueueMessage, t as WORKSPACE_SNAPSHOT_JOB_QUEUE_TOPIC } from "./job-queue-CjB97sii.js";
22
- import { S as removeActiveConversation, _ as hasRunnableConversationWork$1, c as clearExpiredConversationLease, g as getConversationWorkState, h as getConversation, m as ensureConversationWake, r as appendAndEnqueueInboundMessage, v as listActiveConversationIds } from "./store-CPF2kzbu.js";
23
- import { M as scheduledRunSchema } from "./automation-CV7g5h3A.js";
24
- import { n as validatePluginRegistrations, t as validatePluginEgressCredentialHooks } from "./validation-DcGelbBi.js";
19
+ import { c as setSandboxResourceConfig, o as getSandboxResourceConfig } from "./resolve-DqVtd4Q9.js";
20
+ import { a as getVercelConversationWorkQueue, d as getQueueProvider, g as isConversationQueueMessageRejectedError, h as conversationQueueMessageSchema, i as verifyWorkspaceSnapshotJobMessage, o as resolveConversationWorkQueueTopic, s as verifyConversationQueueMessage, t as WORKSPACE_SNAPSHOT_JOB_QUEUE_TOPIC } from "./job-queue-C4lphW9Y.js";
21
+ import { S as removeActiveConversation, _ as hasRunnableConversationWork$1, c as clearExpiredConversationLease, g as getConversationWorkState, h as getConversation, m as ensureConversationWake, r as appendAndEnqueueInboundMessage, v as listActiveConversationIds } from "./store-DaxUuOmD.js";
22
+ import { B as readScheduledAutomation, G as blockEventAutomation, H as saveScheduledAutomationInLock, K as collectEventAutomationMatchKeys, M as recordAutomationExecution, R as listScheduledAutomationsForTeam, S as existingConversationId, U as scheduledRunId, W as withScheduledAutomationLock, Y as findMatchingEventAutomations, a as confirmDispatchMailboxAppend, b as notifyAutomationBlocked, f as getPluginDispatchProjection, ht as eventGuidance, it as automationVersionOf, m as listPendingDispatchMailboxAppends, nt as eventAutomationRunLimitReason, o as createOrGetDispatch, p as isTerminalDispatchStatus, r as buildDispatchId, s as getDispatchConversationId, tt as admitEventAutomationRun, u as getDispatchRecord, v as markDispatchFailed } from "./turn-session-routing-BdCTDq9m.js";
23
+ import { M as scheduledRunSchema, l as automationListQuerySchema, s as automationExecutionStatusSchema } from "./automation-yffXcZt9.js";
24
+ import { n as validatePluginRegistrations, t as validatePluginEgressCredentialHooks } from "./validation-BQjCfh84.js";
25
25
  import { a as pluginRuntimeRegistrationsFromPluginSet, n as pluginCatalogConfigFromEnv, r as pluginCatalogConfigFromPluginSet, t as defineJuniorPlugins } from "./plugins-DOm9Y6qI.js";
26
- import { A as stopSlackThread, B as actOnReviewItem, C as isAllowedSignInEmail, D as buildSlackInboundMessage, E as recordSkippedConversationMessage, F as bindMessageAuthorIdentityId, H as readReviewItemForUser, I as startProcessingReaction, K as enqueueAgentDispatch, L as stripLeadingBotMention, M as withNormalizedThreadId, N as stopConversationTurn, O as clearSlackPendingReactions, P as THREAD_OPTOUT_ACK, R as parseContent, S as isAllowedDomain, T as resolveAllowedDomains, U as reviewItemChange, V as closeReviewItemsAnsweredByReply, W as getAttachmentStorage, _ as createProductionConversationWorkOptions, b as createSlackWebApiStatusSender, j as normalizeIncomingSlackThreadId, k as isSlackMessageStopped, q as GET$3, t as readConversationFeed, v as createProductionSlackWebhookServices, w as readEnvList, x as assertNoLegacyAllowedDomains, y as getProductionSlackAdapter, z as ReviewItemActionError } from "./list-JZUP1yog.js";
27
- import { n as enqueueAgentInvocationResult, o as getAuthPausedConversationTurnId } from "./result-BRhb6iN1.js";
28
- import { r as enqueueAgentInvocation } from "./work-zxkmbJAp.js";
29
- import { o as coerceThreadConversationState } from "./thread-state-Ds8JTsX6.js";
30
- import { o as unlinkProvider } from "./connections-CesHau9u.js";
31
- import { a as GET$4, i as publishAppHomeView, r as GET$5, t as receiveLocalOAuthCredential } from "./credential-sync-D2odJC30.js";
32
- import { a as pluginTaskQueueConsumer, c as queueCallback, n as createVercelPluginTaskCallback, o as registerVercelPluginTaskDevConsumer } from "./task-queue-BbQXCcNX.js";
33
- import { t as processConversationWork } from "./worker-CNNxHF1V.js";
34
- import { t as bindMessageAgent } from "./message-D9y8PKA1.js";
35
- import { t as bindSpawnAgent } from "./spawn-DqjBxqWh.js";
36
- import { t as processPluginTask } from "./task-runner-ChzW5E7R.js";
26
+ import { A as recordSkippedConversationMessage, B as startProcessingReaction, C as getProductionSlackAdapter, D as isAllowedSignInEmail, E as isAllowedDomain, F as normalizeIncomingSlackThreadId, G as closeReviewItemsAnsweredByReply, H as parseContent, I as withNormalizedThreadId, J as getAttachmentStorage, K as readReviewItemForUser, L as stopConversationTurn, M as clearSlackPendingReactions, N as isSlackMessageStopped, O as readEnvList, P as stopSlackThread, R as THREAD_OPTOUT_ACK, S as createProductionSlackWebhookServices, T as assertNoLegacyAllowedDomains, U as ReviewItemActionError, V as stripLeadingBotMention, W as actOnReviewItem, X as enqueueAgentDispatch, Z as GET$3, _ as archiveConversation, a as readConversationFeed, g as encodeConversationCursor, h as decodeConversationCursor, j as buildSlackInboundMessage, k as resolveAllowedDomains, n as readConversationDetail, o as readConversationRecordFromSql, q as reviewItemChange, t as stopConversationTurnForViewer, w as createSlackWebApiStatusSender, x as createProductionConversationWorkOptions, z as bindMessageAuthorIdentityId } from "./stop-dKUc-x2K.js";
27
+ import { n as enqueueAgentInvocationResult, o as getAuthPausedConversationTurnId } from "./result-DQwTiLTw.js";
28
+ import { r as enqueueAgentInvocation } from "./work-ChPh_YEh.js";
29
+ import { o as coerceThreadConversationState } from "./thread-state-BkUpE9Ae.js";
30
+ import { o as unlinkProvider } from "./connections-Dq72KajR.js";
31
+ import { a as GET$4, i as publishAppHomeView, r as GET$5, t as receiveLocalOAuthCredential } from "./credential-sync-BmCbHAp4.js";
32
+ import { a as pluginTaskQueueConsumer, c as queueCallback, n as createVercelPluginTaskCallback, o as registerVercelPluginTaskDevConsumer } from "./task-queue-ymAh_-P1.js";
33
+ import { t as processConversationWork } from "./worker-Be_sK_ou.js";
34
+ import { t as bindMessageAgent } from "./message-CETC5lsl.js";
35
+ import { t as bindSpawnAgent } from "./spawn-Ceikdjkp.js";
36
+ import { t as processPluginTask } from "./task-runner-vlH6pEm3.js";
37
37
  import { createRequire } from "node:module";
38
38
  import { Hono } from "hono";
39
- import { z } from "zod";
40
39
  import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
40
+ import { z } from "zod";
41
41
  import { and, asc, eq, inArray, isNotNull, isNull, lte, ne, or, sql } from "drizzle-orm";
42
42
  import { EVENT_SUMMARY_MAX_LENGTH, EVENT_TEXT_MAX_LENGTH, dispatchOptionsSchema, eventSchema, eventTriggerFiltersText, isSlackDestination, userSchema } from "@sentry/junior-plugin-api";
43
43
  import { pathToFileURL } from "node:url";
@@ -49,6 +49,7 @@ import * as acp from "@agentclientprotocol/sdk";
49
49
  import dns from "node:dns";
50
50
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
51
51
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
52
+ import { HTTPException } from "hono/http-exception";
52
53
  import { bodyLimit } from "hono/body-limit";
53
54
  import { createAuthMiddleware } from "better-auth/api";
54
55
  import { betterAuth } from "better-auth/minimal";
@@ -5287,59 +5288,6 @@ function redirectUriMatches(registered, requested) {
5287
5288
  return candidate !== null && isLoopbackHttp(candidate) && candidate.hostname === url.hostname && candidate.pathname === url.pathname && candidate.search === url.search;
5288
5289
  });
5289
5290
  }
5290
- // src/api/mcp/oauth/metadata.ts
5291
- /**
5292
- * Own the MCP OAuth paths, scope, and discovery documents.
5293
- *
5294
- * Every URL comes from the trusted public origin of the app. The documents
5295
- * advertise Client ID Metadata Documents and no client registration endpoint.
5296
- */
5297
- const MCP_PATH = "/api/mcp";
5298
- const MCP_SCOPE = "conversations:read";
5299
- const MCP_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource/api/mcp";
5300
- const MCP_AUTHORIZATION_SERVER_METADATA_PATH = "/.well-known/oauth-authorization-server";
5301
- const MCP_AUTHORIZE_PATH = "/api/mcp/oauth/authorize";
5302
- const MCP_TOKEN_PATH = "/api/mcp/oauth/token";
5303
- /** Dashboard-authenticated consent page for one pending authorization. */
5304
- const MCP_CONSENT_PATH = "/_junior/mcp/authorize/:authorizationId";
5305
- /** Resolve the MCP URLs for this request from the trusted public origin. */
5306
- function resolveMcpUrls(request, baseURL) {
5307
- const origin = resolvePublicOrigin(request, baseURL);
5308
- if (!origin) throw new Error("Junior MCP requires a configured public base URL");
5309
- const url = (path) => new URL(path, origin).toString();
5310
- return {
5311
- authorize: url(MCP_AUTHORIZE_PATH),
5312
- issuer: origin.origin,
5313
- resource: url(MCP_PATH),
5314
- resourceMetadata: url(MCP_RESOURCE_METADATA_PATH),
5315
- token: url(MCP_TOKEN_PATH),
5316
- consent: (authorizationId) => url(MCP_CONSENT_PATH.replace(":authorizationId", encodeURIComponent(authorizationId)))
5317
- };
5318
- }
5319
- /** RFC 9728 protected resource metadata for `/api/mcp`. */
5320
- function protectedResourceMetadata(urls) {
5321
- return {
5322
- resource: urls.resource,
5323
- authorization_servers: [urls.issuer],
5324
- bearer_methods_supported: ["header"],
5325
- scopes_supported: [MCP_SCOPE]
5326
- };
5327
- }
5328
- /** RFC 8414 authorization server metadata. CIMD is the only client model. */
5329
- function authorizationServerMetadata(urls) {
5330
- return {
5331
- issuer: urls.issuer,
5332
- authorization_endpoint: urls.authorize,
5333
- token_endpoint: urls.token,
5334
- scopes_supported: [MCP_SCOPE],
5335
- response_types_supported: ["code"],
5336
- grant_types_supported: ["authorization_code", "refresh_token"],
5337
- code_challenge_methods_supported: ["S256"],
5338
- token_endpoint_auth_methods_supported: ["none"],
5339
- authorization_response_iss_parameter_supported: true,
5340
- client_id_metadata_document_supported: true
5341
- };
5342
- }
5343
5291
  // src/api/mcp/oauth/page.ts
5344
5292
  /** Render one MCP sign-in page. It sends no referrer and loads no resources. */
5345
5293
  function renderMcpPage(page) {
@@ -5355,6 +5303,42 @@ function renderMcpPage(page) {
5355
5303
  title: page.title
5356
5304
  });
5357
5305
  }
5306
+ // src/api/mcp/oauth/scopes.ts
5307
+ /**
5308
+ * Own the MCP OAuth scopes. Each scope unlocks a fixed set of tools. No
5309
+ * scope lets a client send a message or start a Turn.
5310
+ *
5311
+ * A grant stores its scopes as one space-separated string (RFC 6749 section
5312
+ * 3.3), so grants from before the scope split still parse.
5313
+ */
5314
+ /** Every scope, in the order that the consent page shows it. */
5315
+ const MCP_SCOPES = [
5316
+ "conversations:read",
5317
+ "conversations:manage",
5318
+ "review_items:read",
5319
+ "automations:read"
5320
+ ];
5321
+ /** What each scope lets the client do, as the consent page says it. */
5322
+ const MCP_SCOPE_DESCRIPTIONS = {
5323
+ "conversations:read": "List and read the conversations that you started or take part in",
5324
+ "conversations:manage": "Archive your conversations and stop a running Turn",
5325
+ "review_items:read": "List and read your review items",
5326
+ "automations:read": "List and read the automations that you can see"
5327
+ };
5328
+ /** The `scope` value that names every scope. */
5329
+ const MCP_ALL_SCOPES = MCP_SCOPES.join(" ");
5330
+ /**
5331
+ * Parse a space-separated scope value into known scopes in canonical order.
5332
+ * Return `undefined` when it is empty or names an unknown scope.
5333
+ */
5334
+ function parseMcpScopes(value) {
5335
+ const names = value.split(" ").filter(Boolean);
5336
+ if (names.length === 0) return void 0;
5337
+ if (!names.every((name) => MCP_SCOPES.includes(name))) return void 0;
5338
+ return MCP_SCOPES.filter((scope) => names.includes(scope));
5339
+ }
5340
+ /** A stored scope value: known scopes, space-separated, in canonical order. */
5341
+ const mcpScopeValueSchema = z.string().refine((value) => parseMcpScopes(value)?.join(" ") === value);
5358
5342
  // src/api/mcp/oauth/authorization.ts
5359
5343
  /**
5360
5344
  * Own the OAuth authorization endpoint and its pending requests.
@@ -5376,7 +5360,7 @@ const mcpAuthorizationSchema = z.object({
5376
5360
  expiresAtMs: z.number().int().positive(),
5377
5361
  redirectUri: z.string().min(1),
5378
5362
  resource: z.string().min(1),
5379
- scope: z.literal(MCP_SCOPE),
5363
+ scope: mcpScopeValueSchema,
5380
5364
  state: z.string().max(STATE_MAX_LENGTH).optional()
5381
5365
  }).strict();
5382
5366
  /** The user who owns one consent page, and the hash of its form token. */
@@ -5483,8 +5467,8 @@ async function handleMcpAuthorize(args) {
5483
5467
  const codeChallenge = params.get("code_challenge") ?? "";
5484
5468
  if (params.get("code_challenge_method") !== "S256" || !CODE_CHALLENGE_PATTERN.test(codeChallenge)) return fail("invalid_request", "PKCE with S256 is required.");
5485
5469
  if (oauthState !== void 0 && oauthState.length > STATE_MAX_LENGTH) return fail("invalid_request", "The state parameter is too long.");
5486
- const scope = params.get("scope")?.trim();
5487
- if (scope && scope !== "conversations:read") return fail("invalid_scope", `The only scope is ${MCP_SCOPE}.`);
5470
+ const scopes = parseMcpScopes(params.get("scope")?.trim() || MCP_ALL_SCOPES);
5471
+ if (!scopes) return fail("invalid_scope", `Use only these scopes: ${MCP_ALL_SCOPES}.`);
5488
5472
  if (params.get("resource") !== args.urls.resource) return fail("invalid_target", `The resource must be ${args.urls.resource}.`);
5489
5473
  const authorizationId = randomBytes(32).toString("base64url");
5490
5474
  const authorization = {
@@ -5494,7 +5478,7 @@ async function handleMcpAuthorize(args) {
5494
5478
  expiresAtMs: Date.now() + AUTHORIZATION_TTL_MS,
5495
5479
  redirectUri,
5496
5480
  resource: args.urls.resource,
5497
- scope: MCP_SCOPE,
5481
+ scope: scopes.join(" "),
5498
5482
  state: oauthState
5499
5483
  };
5500
5484
  await args.state.set(authorizationKey(authorizationId), authorization, AUTHORIZATION_TTL_MS);
@@ -5529,7 +5513,7 @@ const authorizationCodeSchema = z.object({
5529
5513
  grantId: z.string().uuid(),
5530
5514
  redirectUri: z.string().min(1),
5531
5515
  resource: z.string().min(1),
5532
- scope: z.literal(MCP_SCOPE)
5516
+ scope: mcpScopeValueSchema
5533
5517
  }).strict();
5534
5518
  const grantSchema = z.object({
5535
5519
  clientId: z.string().min(1),
@@ -5537,7 +5521,7 @@ const grantSchema = z.object({
5537
5521
  /** Absolute end of the grant. Refresh never moves it. */
5538
5522
  expiresAtMs: z.number().int().positive(),
5539
5523
  resource: z.string().min(1),
5540
- scope: z.literal(MCP_SCOPE)
5524
+ scope: mcpScopeValueSchema
5541
5525
  }).strict();
5542
5526
  const tokenSchema = z.object({ grantId: z.string().uuid() }).strict();
5543
5527
  function codeKey(code) {
@@ -5572,7 +5556,7 @@ async function issueAuthorizationCode(args) {
5572
5556
  grantId: randomUUID(),
5573
5557
  redirectUri: args.redirectUri,
5574
5558
  resource: args.resource,
5575
- scope: MCP_SCOPE
5559
+ scope: args.scope
5576
5560
  };
5577
5561
  await args.state.set(codeKey(code), record, CODE_TTL_MS);
5578
5562
  return code;
@@ -5662,8 +5646,8 @@ async function refreshGrant(args) {
5662
5646
  const token = await readRecord(args.state, key, tokenSchema);
5663
5647
  const grant = token ? await readGrant(args.state, token.grantId) : void 0;
5664
5648
  if (!token || !grant || grant.clientId !== args.clientId || args.resource !== void 0 && args.resource !== grant.resource) return invalidGrant("The refresh token is not valid.");
5665
- if (args.scope !== void 0 && args.scope !== grant.scope) return {
5666
- description: `The only scope is ${MCP_SCOPE}.`,
5649
+ if (args.scope !== void 0 && parseMcpScopes(args.scope)?.join(" ") !== grant.scope) return {
5650
+ description: `Refresh keeps the granted scope: ${grant.scope}.`,
5667
5651
  error: "invalid_scope",
5668
5652
  ok: false
5669
5653
  };
@@ -5682,9 +5666,9 @@ async function refreshGrant(args) {
5682
5666
  return await issueTokens(args.state, token.grantId, grant);
5683
5667
  }
5684
5668
  /**
5685
- * Return the user email of one MCP access token for one resource. Personal
5686
- * tokens and other bearer values never match, because only this module
5687
- * stores MCP access tokens.
5669
+ * Return the user email and scopes of one MCP access token for one
5670
+ * resource. Personal tokens and other bearer values never match, because
5671
+ * only this module stores MCP access tokens.
5688
5672
  */
5689
5673
  async function authenticateMcpAccessToken(args) {
5690
5674
  if (!args.token.startsWith(ACCESS_TOKEN_PREFIX)) return void 0;
@@ -5692,7 +5676,62 @@ async function authenticateMcpAccessToken(args) {
5692
5676
  if (!token) return void 0;
5693
5677
  const grant = await readGrant(args.state, token.grantId);
5694
5678
  if (!grant || grant.resource !== args.resource) return void 0;
5695
- return grant.email;
5679
+ return {
5680
+ email: grant.email,
5681
+ scopes: parseMcpScopes(grant.scope) ?? []
5682
+ };
5683
+ }
5684
+ // src/api/mcp/oauth/metadata.ts
5685
+ /**
5686
+ * Own the MCP OAuth paths and discovery documents.
5687
+ *
5688
+ * Every URL comes from the trusted public origin of the app. The documents
5689
+ * advertise Client ID Metadata Documents and no client registration endpoint.
5690
+ */
5691
+ const MCP_PATH = "/api/mcp";
5692
+ const MCP_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource/api/mcp";
5693
+ const MCP_AUTHORIZATION_SERVER_METADATA_PATH = "/.well-known/oauth-authorization-server";
5694
+ const MCP_AUTHORIZE_PATH = "/api/mcp/oauth/authorize";
5695
+ const MCP_TOKEN_PATH = "/api/mcp/oauth/token";
5696
+ /** Dashboard-authenticated consent page for one pending authorization. */
5697
+ const MCP_CONSENT_PATH = "/_junior/mcp/authorize/:authorizationId";
5698
+ /** Resolve the MCP URLs for this request from the trusted public origin. */
5699
+ function resolveMcpUrls(request, baseURL) {
5700
+ const origin = resolvePublicOrigin(request, baseURL);
5701
+ if (!origin) throw new Error("Junior MCP requires a configured public base URL");
5702
+ const url = (path) => new URL(path, origin).toString();
5703
+ return {
5704
+ authorize: url(MCP_AUTHORIZE_PATH),
5705
+ issuer: origin.origin,
5706
+ resource: url(MCP_PATH),
5707
+ resourceMetadata: url(MCP_RESOURCE_METADATA_PATH),
5708
+ token: url(MCP_TOKEN_PATH),
5709
+ consent: (authorizationId) => url(MCP_CONSENT_PATH.replace(":authorizationId", encodeURIComponent(authorizationId)))
5710
+ };
5711
+ }
5712
+ /** RFC 9728 protected resource metadata for `/api/mcp`. */
5713
+ function protectedResourceMetadata(urls) {
5714
+ return {
5715
+ resource: urls.resource,
5716
+ authorization_servers: [urls.issuer],
5717
+ bearer_methods_supported: ["header"],
5718
+ scopes_supported: [...MCP_SCOPES]
5719
+ };
5720
+ }
5721
+ /** RFC 8414 authorization server metadata. CIMD is the only client model. */
5722
+ function authorizationServerMetadata(urls) {
5723
+ return {
5724
+ issuer: urls.issuer,
5725
+ authorization_endpoint: urls.authorize,
5726
+ token_endpoint: urls.token,
5727
+ scopes_supported: [...MCP_SCOPES],
5728
+ response_types_supported: ["code"],
5729
+ grant_types_supported: ["authorization_code", "refresh_token"],
5730
+ code_challenge_methods_supported: ["S256"],
5731
+ token_endpoint_auth_methods_supported: ["none"],
5732
+ authorization_response_iss_parameter_supported: true,
5733
+ client_id_metadata_document_supported: true
5734
+ };
5696
5735
  }
5697
5736
  // src/api/mcp/oauth/consent-page.ts
5698
5737
  /**
@@ -5720,8 +5759,10 @@ function consentPage(args) {
5720
5759
  const clientHost = new URL(args.authorization.clientId).host;
5721
5760
  const clientName = args.authorization.clientName ?? clientHost;
5722
5761
  const button = (action, label, primary) => `<button type="submit" name="action" value="${action}" style="border:0;border-radius:.375rem;cursor:pointer;font:inherit;font-weight:700;padding:.625rem 1rem;margin-right:.5rem;${primary ? "background:#a78bfa;color:#000" : "background:#333;color:#fff"}">${label}</button>`;
5762
+ const scopeItems = (parseMcpScopes(args.authorization.scope) ?? []).map((scope) => `<li style="margin:0 0 .25rem">${escapeHtml(MCP_SCOPE_DESCRIPTIONS[scope])} <code style="color:#8a8a8a">${escapeHtml(scope)}</code></li>`).join("");
5723
5763
  return renderMcpPage({
5724
- bodyHtml: ` <p style="margin:0 0 1rem;color:#b8b8b8;line-height:1.5">The app is published at <strong style="color:#fff">${escapeHtml(clientHost)}</strong>. It can list your ${escapeHtml(args.agentName)} conversations.</p>
5764
+ bodyHtml: ` <p style="margin:0 0 .5rem;color:#b8b8b8;line-height:1.5">The app is published at <strong style="color:#fff">${escapeHtml(clientHost)}</strong>. In ${escapeHtml(args.agentName)}, it can:</p>
5765
+ <ul style="margin:0 0 1rem;padding-left:1.25rem;color:#b8b8b8;line-height:1.5">${scopeItems}</ul>
5725
5766
  <form method="post">
5726
5767
  <input type="hidden" name="csrf" value="${escapeHtml(args.csrfToken)}">
5727
5768
  ${button("approve", "Allow", true)}${button("deny", "Deny", false)}
@@ -5791,13 +5832,15 @@ async function handleMcpConsentPage(args) {
5791
5832
  email: args.user.email,
5792
5833
  redirectUri: authorization.redirectUri,
5793
5834
  resource: authorization.resource,
5835
+ scope: authorization.scope,
5794
5836
  state: args.state
5795
5837
  }) },
5796
5838
  redirectUri: authorization.redirectUri,
5797
5839
  state: authorization.state
5798
5840
  });
5799
5841
  }
5800
- // src/api/mcp/tools/list-conversations.ts
5842
+ // src/api/mcp/conversation.ts
5843
+ /** Own the MCP view of one Conversation, shared by the conversation tools. */
5801
5844
  /** MCP view of one Conversation. It omits dashboard-only fields. */
5802
5845
  const mcpConversationSchema = z.object({
5803
5846
  conversationId: z.string(),
@@ -5811,6 +5854,7 @@ const mcpConversationSchema = z.object({
5811
5854
  archivedAt: z.string().optional(),
5812
5855
  url: z.string().optional()
5813
5856
  }).strict();
5857
+ /** Map a Conversation summary to its MCP view. */
5814
5858
  function mcpConversation(summary) {
5815
5859
  return {
5816
5860
  conversationId: summary.conversationId,
@@ -5825,34 +5869,519 @@ function mcpConversation(summary) {
5825
5869
  url: getDashboardConversationLink(summary.conversationId)
5826
5870
  };
5827
5871
  }
5828
- /** Register `listConversations`, which lists the Conversations of `user`. */
5829
- function registerListConversationsTool(server, user) {
5872
+ /** Conversation id param shared by the conversation tools. */
5873
+ const mcpConversationIdSchema = z.string().min(1).describe("Conversation id from searchConversations.");
5874
+ /** MCP view of one user or assistant Message. */
5875
+ const mcpMessageSchema = z.object({
5876
+ seq: z.number().int(),
5877
+ createdAt: z.string(),
5878
+ role: z.enum(["assistant", "user"]),
5879
+ author: z.string().optional(),
5880
+ text: z.string(),
5881
+ attachments: z.array(z.object({
5882
+ filename: z.string(),
5883
+ contentType: z.string(),
5884
+ bytes: z.number().int()
5885
+ }).strict()).optional()
5886
+ }).strict();
5887
+ /**
5888
+ * Keep the user and assistant Messages of a report event page, in order.
5889
+ * System Messages and Messages with no text or attachment are left out.
5890
+ */
5891
+ function mcpMessages(events) {
5892
+ return events.flatMap((event) => {
5893
+ const data = event.data;
5894
+ if (data.type !== "message" || data.role === "system") return [];
5895
+ const text = data.text ?? "";
5896
+ const attachments = data.attachments?.map(({ bytes, contentType, filename }) => ({
5897
+ bytes,
5898
+ contentType,
5899
+ filename
5900
+ }));
5901
+ if (!text && !attachments?.length) return [];
5902
+ const author = data.actorIdentity?.fullName ?? data.actorIdentity?.slackUserName;
5903
+ return [{
5904
+ seq: event.seq,
5905
+ createdAt: event.createdAt,
5906
+ role: data.role,
5907
+ ...author ? { author } : void 0,
5908
+ text,
5909
+ ...attachments?.length ? { attachments } : void 0
5910
+ }];
5911
+ });
5912
+ }
5913
+ // src/api/mcp/tool-context.ts
5914
+ /**
5915
+ * Return one structured tool result. The text block holds the same JSON, as
5916
+ * the MCP spec recommends for clients that do not read `structuredContent`.
5917
+ */
5918
+ function mcpToolResult(result) {
5919
+ return {
5920
+ content: [{
5921
+ type: "text",
5922
+ text: JSON.stringify(result)
5923
+ }],
5924
+ structuredContent: result
5925
+ };
5926
+ }
5927
+ /** Return a tool error that the client model can read and repair. */
5928
+ function mcpToolError(message) {
5929
+ return {
5930
+ content: [{
5931
+ type: "text",
5932
+ text: message
5933
+ }],
5934
+ isError: true
5935
+ };
5936
+ }
5937
+ /**
5938
+ * Turn a client error from a shared API function, such as a `404` or a
5939
+ * `409`, into a tool error. Rethrow every other failure.
5940
+ */
5941
+ function apiToolError(error) {
5942
+ if (error instanceof HTTPException && error.status < 500) return mcpToolError(error.message);
5943
+ throw error;
5944
+ }
5945
+ /**
5946
+ * Return whether `user` started or takes part in a Conversation. MCP tools
5947
+ * read and change only these Conversations, the same set that
5948
+ * `searchConversations` shows.
5949
+ */
5950
+ async function isConversationParticipant(user, conversationId) {
5951
+ return (await readConversationAccessFromSql(getDb(), [conversationId], user)).get(conversationId)?.isParticipant ?? false;
5952
+ }
5953
+ // src/api/mcp/tools/archive-conversation.ts
5954
+ /** Register `archiveConversation`, which archives or restores for the user. */
5955
+ function registerArchiveConversationTool(server, { user }) {
5956
+ server.registerTool("archiveConversation", {
5957
+ title: "Archive conversation",
5958
+ description: "Archive one of your conversations, or restore it. Archiving hides it only for you, and new activity restores it.",
5959
+ inputSchema: {
5960
+ conversationId: mcpConversationIdSchema,
5961
+ archived: z.boolean().default(true).describe("False restores an archived conversation."),
5962
+ lastSeenAt: z.string().datetime().describe("`lastSeenAt` from searchConversations or getConversation. The archive fails when the conversation has newer activity.")
5963
+ },
5964
+ outputSchema: { archivedAt: z.string().nullable() },
5965
+ annotations: {
5966
+ destructiveHint: false,
5967
+ idempotentHint: true,
5968
+ openWorldHint: false,
5969
+ readOnlyHint: false
5970
+ }
5971
+ }, async ({ archived, conversationId, lastSeenAt }) => {
5972
+ try {
5973
+ return mcpToolResult(await archiveConversation(user, conversationId, {
5974
+ archived,
5975
+ lastSeenAt
5976
+ }));
5977
+ } catch (error) {
5978
+ return apiToolError(error);
5979
+ }
5980
+ });
5981
+ }
5982
+ // src/api/mcp/automation.ts
5983
+ /** Own the MCP view of one Automation, shared by the automation tools. */
5984
+ /** MCP view of one Automation. It omits cost and run-count charts. */
5985
+ const mcpAutomationSchema = z.object({
5986
+ id: z.string(),
5987
+ kind: z.enum(["scheduled", "event"]),
5988
+ title: z.string(),
5989
+ instruction: z.string(),
5990
+ status: z.enum([
5991
+ "active",
5992
+ "blocked",
5993
+ "paused",
5994
+ "completed"
5995
+ ]),
5996
+ statusReason: z.string().optional(),
5997
+ trigger: z.string().describe("The schedule, or the events and filters that start a run."),
5998
+ nextRunAt: z.string().optional(),
5999
+ destination: z.string().describe("Slack channel that gets the results."),
6000
+ createdBy: z.string(),
6001
+ ownedByYou: z.boolean(),
6002
+ lastRunAt: z.string().optional(),
6003
+ lastRunStatus: z.enum([
6004
+ "blocked",
6005
+ "completed",
6006
+ "failed"
6007
+ ]).optional(),
6008
+ lastConversationId: z.string().optional(),
6009
+ url: z.string().optional()
6010
+ }).strict();
6011
+ function triggerText(automation) {
6012
+ if (automation.kind === "scheduled") return `${automation.schedule} (${automation.timezone})`;
6013
+ const { events, filters } = automation.trigger;
6014
+ return [`${events.join(", ")} on ${automation.resource}`, ...filters].join("; ");
6015
+ }
6016
+ /** Map an Automation summary to its MCP view. */
6017
+ function mcpAutomation(automation) {
6018
+ const url = getDashboardTaskLink(automation.id);
6019
+ return {
6020
+ id: automation.id,
6021
+ kind: automation.kind,
6022
+ title: automation.title,
6023
+ instruction: automation.instruction,
6024
+ status: automation.status,
6025
+ ...automation.statusReason ? { statusReason: automation.statusReason } : void 0,
6026
+ trigger: triggerText(automation),
6027
+ ...automation.kind === "scheduled" && automation.nextRunAt ? { nextRunAt: automation.nextRunAt } : void 0,
6028
+ destination: automation.destination.label,
6029
+ createdBy: automation.createdBy,
6030
+ ownedByYou: automation.ownedByViewer,
6031
+ ...automation.lastRunAt ? { lastRunAt: automation.lastRunAt } : void 0,
6032
+ ...automation.lastRunStatus ? { lastRunStatus: automation.lastRunStatus } : void 0,
6033
+ ...automation.lastConversationId ? { lastConversationId: automation.lastConversationId } : void 0,
6034
+ ...url ? { url } : void 0
6035
+ };
6036
+ }
6037
+ // src/api/mcp/tools/get-automation.ts
6038
+ const RECENT_RUN_LIMIT = 10;
6039
+ /** Register `getAutomation`, which reads one Automation and its recent runs. */
6040
+ function registerGetAutomationTool(server, { user }) {
6041
+ server.registerTool("getAutomation", {
6042
+ title: "Get automation",
6043
+ description: `Get one automation and its ${RECENT_RUN_LIMIT} newest runs.`,
6044
+ inputSchema: { id: z.string().min(1).describe("Automation id from searchAutomations.") },
6045
+ outputSchema: {
6046
+ automation: mcpAutomationSchema,
6047
+ runs: z.array(z.object({
6048
+ executedAt: z.string(),
6049
+ status: automationExecutionStatusSchema,
6050
+ error: z.string().optional(),
6051
+ conversationId: z.string().optional()
6052
+ }).strict())
6053
+ },
6054
+ annotations: {
6055
+ destructiveHint: false,
6056
+ idempotentHint: true,
6057
+ openWorldHint: false,
6058
+ readOnlyHint: true
6059
+ }
6060
+ }, async ({ id }) => {
6061
+ try {
6062
+ const list = await readViewerAutomationExecutions(user, id);
6063
+ return mcpToolResult({
6064
+ automation: mcpAutomation(list.automation),
6065
+ runs: list.executions.slice(0, RECENT_RUN_LIMIT).map(({ conversationId, error, executedAt, status }) => ({
6066
+ executedAt,
6067
+ status,
6068
+ ...error ? { error } : void 0,
6069
+ ...conversationId ? { conversationId } : void 0
6070
+ }))
6071
+ });
6072
+ } catch (error) {
6073
+ if (error instanceof ViewerTaskNotFoundError) return mcpToolError("Automation not found.");
6074
+ throw error;
6075
+ }
6076
+ });
6077
+ }
6078
+ // src/api/mcp/tools/get-conversation.ts
6079
+ const mcpBriefSchema = conversationBriefSchema.omit({
6080
+ keywords: true,
6081
+ record: true,
6082
+ schemaVersion: true
6083
+ }).extend({ updatedAt: z.string() });
6084
+ function mcpBrief(brief, updatedAt) {
6085
+ return {
6086
+ summary: brief.summary,
6087
+ intent: brief.intent,
6088
+ outcome: brief.outcome,
6089
+ decisions: brief.decisions,
6090
+ openDecisions: brief.openDecisions,
6091
+ facts: brief.facts,
6092
+ links: brief.links,
6093
+ updatedAt
6094
+ };
6095
+ }
6096
+ const RECENT_EVENT_LIMIT = 100;
6097
+ /**
6098
+ * Register `getConversation`. It returns metadata, the Brief, and the last
6099
+ * Reply, not the whole transcript.
6100
+ */
6101
+ function registerGetConversationTool(server, { user }) {
6102
+ server.registerTool("getConversation", {
6103
+ title: "Get conversation",
6104
+ description: "Get one conversation: its metadata, its Brief (a summary of intent, outcome, and state), and the last assistant reply. Use listConversationMessages for the full transcript.",
6105
+ inputSchema: { conversationId: mcpConversationIdSchema },
6106
+ outputSchema: {
6107
+ conversation: mcpConversationSchema,
6108
+ brief: mcpBriefSchema.optional(),
6109
+ lastReply: mcpMessageSchema.optional()
6110
+ },
6111
+ annotations: {
6112
+ destructiveHint: false,
6113
+ idempotentHint: true,
6114
+ openWorldHint: false,
6115
+ readOnlyHint: true
6116
+ }
6117
+ }, async ({ conversationId }) => {
6118
+ const detail = await isConversationParticipant(user, conversationId) ? await readConversationDetail(conversationId, {
6119
+ limit: RECENT_EVENT_LIMIT,
6120
+ viewer: user
6121
+ }) : void 0;
6122
+ if (!detail) return mcpToolError("Conversation not found.");
6123
+ const lastReply = mcpMessages(detail.events).filter((message) => message.role === "assistant").at(-1);
6124
+ return mcpToolResult({
6125
+ conversation: mcpConversation(detail),
6126
+ ...detail.brief ? { brief: mcpBrief(detail.brief.content, detail.brief.updatedAt) } : void 0,
6127
+ ...lastReply ? { lastReply } : void 0
6128
+ });
6129
+ });
6130
+ }
6131
+ // src/api/mcp/review-item.ts
6132
+ /**
6133
+ * Own the MCP view of one Review item, shared by the review item tools.
6134
+ *
6135
+ * MCP Review items stay read-only. An answer or dismissal sends a message to
6136
+ * the linked Conversations, which can start a Turn.
6137
+ */
6138
+ /** MCP view of one Review item. Answers happen in Slack or the dashboard. */
6139
+ const mcpReviewItemSchema = z.object({
6140
+ id: z.string(),
6141
+ kind: z.enum(["notice", "action"]),
6142
+ status: z.enum([
6143
+ "open",
6144
+ "done",
6145
+ "dismissed"
6146
+ ]),
6147
+ title: z.string(),
6148
+ body: z.string(),
6149
+ question: z.string().optional(),
6150
+ assignees: z.array(z.string()).describe("Names or emails. Empty when anyone can act."),
6151
+ canAct: z.boolean().describe("True when you can answer or dismiss it."),
6152
+ conversationIds: z.array(z.string()),
6153
+ subject: z.object({
6154
+ id: z.string(),
6155
+ title: z.string().optional(),
6156
+ url: z.string().optional()
6157
+ }).strict().optional(),
6158
+ createdAt: z.string(),
6159
+ updatedAt: z.string(),
6160
+ url: z.string().optional().describe("Dashboard link to answer it.")
6161
+ }).strict();
6162
+ /** Map a Review item to its MCP view for one user. */
6163
+ function mcpReviewItem(item, userId) {
6164
+ const url = getDashboardReviewItemLink(item.id);
6165
+ return {
6166
+ id: item.id,
6167
+ kind: item.kind,
6168
+ status: item.status,
6169
+ title: item.title,
6170
+ body: item.body,
6171
+ ...item.request ? { question: item.request.question } : void 0,
6172
+ assignees: item.assignedToEveryone ? [] : item.assignees.map((person) => person.displayName ?? person.email),
6173
+ canAct: canActOnReviewItem(item, userId),
6174
+ conversationIds: item.conversationIds,
6175
+ ...item.subject ? { subject: {
6176
+ id: item.subject.id,
6177
+ ...item.subject.title ? { title: item.subject.title } : void 0,
6178
+ ...item.subject.url ? { url: item.subject.url } : void 0
6179
+ } } : void 0,
6180
+ createdAt: item.createdAt.toISOString(),
6181
+ updatedAt: item.updatedAt.toISOString(),
6182
+ ...url ? { url } : void 0
6183
+ };
6184
+ }
6185
+ // src/api/mcp/tools/get-review-item.ts
6186
+ /** Register `getReviewItem`, which reads one Review item in any status. */
6187
+ function registerGetReviewItemTool(server, { user }) {
6188
+ server.registerTool("getReviewItem", {
6189
+ title: "Get review item",
6190
+ description: "Get one review item, open or closed.",
6191
+ inputSchema: { id: z.string().min(1).describe("Review item id from searchReviewItems.") },
6192
+ outputSchema: { item: mcpReviewItemSchema },
6193
+ annotations: {
6194
+ destructiveHint: false,
6195
+ idempotentHint: true,
6196
+ openWorldHint: false,
6197
+ readOnlyHint: true
6198
+ }
6199
+ }, async ({ id }) => {
6200
+ try {
6201
+ return mcpToolResult({ item: mcpReviewItem(await readReviewItemForUser(user, id), user.id) });
6202
+ } catch (error) {
6203
+ if (error instanceof ReviewItemActionError) return mcpToolError(error.message);
6204
+ throw error;
6205
+ }
6206
+ });
6207
+ }
6208
+ // src/api/mcp/tools/search-automations.ts
6209
+ /** Register `searchAutomations`, which searches the Automations the user can see. */
6210
+ function registerSearchAutomationsTool(server, { user }) {
6211
+ const listQuery = automationListQuerySchema.shape;
6212
+ server.registerTool("searchAutomations", {
6213
+ title: "Search automations",
6214
+ description: "Search the automations that you can see, newest first. An automation is a saved instruction that runs on a schedule or on events.",
6215
+ inputSchema: {
6216
+ q: listQuery.q.describe("Text to search for in titles and instructions."),
6217
+ scope: z.enum([
6218
+ "all",
6219
+ "mine",
6220
+ "attention"
6221
+ ]).default("all").describe("`mine` lists the ones you created. `attention` lists the ones that are blocked, cannot run, or had a problem in their last run."),
6222
+ state: listQuery.state.describe("Filter by state."),
6223
+ page: listQuery.page.describe("Page number, from 1.")
6224
+ },
6225
+ outputSchema: {
6226
+ automations: z.array(mcpAutomationSchema),
6227
+ total: z.number().int().describe("Matches across every page.")
6228
+ },
6229
+ annotations: {
6230
+ destructiveHint: false,
6231
+ idempotentHint: true,
6232
+ openWorldHint: false,
6233
+ readOnlyHint: true
6234
+ }
6235
+ }, async ({ page, q, scope, state }) => {
6236
+ const list = await readViewerAutomations(user, automationListQuerySchema.parse({
6237
+ page,
6238
+ q,
6239
+ scope,
6240
+ state
6241
+ }));
6242
+ return mcpToolResult({
6243
+ automations: list.automations.map(mcpAutomation),
6244
+ total: list.total
6245
+ });
6246
+ });
6247
+ }
6248
+ // src/api/mcp/tools/list-conversation-messages.ts
6249
+ const EVENT_SCAN_LIMIT = 200;
6250
+ /** Register `listConversationMessages`, which pages a transcript backward. */
6251
+ function registerListConversationMessagesTool(server, { user }) {
6252
+ server.registerTool("listConversationMessages", {
6253
+ title: "List conversation messages",
6254
+ description: "List the user and assistant messages of one conversation, oldest first, ending with the newest. Tool calls are not included.",
6255
+ inputSchema: {
6256
+ conversationId: mcpConversationIdSchema,
6257
+ before: z.string().min(1).max(1e3).optional().describe("`nextCursor` from an earlier call. Lists the messages before it."),
6258
+ limit: z.number().int().min(1).max(50).default(10).describe("Maximum number of messages.")
6259
+ },
6260
+ outputSchema: {
6261
+ messages: z.array(mcpMessageSchema),
6262
+ nextCursor: z.string().optional().describe("Set when older messages can exist.")
6263
+ },
6264
+ annotations: {
6265
+ destructiveHint: false,
6266
+ idempotentHint: true,
6267
+ openWorldHint: false,
6268
+ readOnlyHint: true
6269
+ }
6270
+ }, async ({ before, conversationId, limit }) => {
6271
+ const record = await isConversationParticipant(user, conversationId) ? await readConversationRecordFromSql(conversationId) : void 0;
6272
+ if (!record) return mcpToolError("Conversation not found.");
6273
+ const beforeSeq = before ? decodeConversationCursor({
6274
+ conversationId,
6275
+ cursor: before
6276
+ })?.seq : void 0;
6277
+ if (before && beforeSeq === void 0) return mcpToolError("The cursor is not valid. Omit `before` to read the newest messages.");
6278
+ if (record.conversation.transcriptPurgedAtMs !== void 0) return mcpToolResult({ messages: [] });
6279
+ const page = await readConversationEventPage(getSqlExecutor(), {
6280
+ beforeSeq,
6281
+ canExposePayload: true,
6282
+ conversationId,
6283
+ limit: EVENT_SCAN_LIMIT
6284
+ });
6285
+ const scanned = mcpMessages(page.events);
6286
+ const messages = scanned.slice(-limit);
6287
+ const olderSeq = scanned.length > messages.length ? messages[0]?.seq : page.previousSeq;
6288
+ return mcpToolResult({
6289
+ messages,
6290
+ ...olderSeq === void 0 ? void 0 : { nextCursor: encodeConversationCursor({
6291
+ conversationId,
6292
+ seq: olderSeq
6293
+ }) }
6294
+ });
6295
+ });
6296
+ }
6297
+ // src/api/mcp/tools/search-conversations.ts
6298
+ /** Register `searchConversations`, which searches the Conversations of the user. */
6299
+ function registerSearchConversationsTool(server, { user }) {
5830
6300
  const feedQuery = conversationFeedQuerySchema.shape;
5831
- server.registerTool("listConversations", {
5832
- title: "List conversations",
5833
- description: "List up to 50 of your conversations, newest activity first. Only conversations that you started or take part in are listed.",
6301
+ server.registerTool("searchConversations", {
6302
+ title: "Search conversations",
6303
+ description: "Search your conversations and return up to 50, newest activity first. Only conversations that you started or take part in match.",
5834
6304
  inputSchema: {
5835
6305
  q: feedQuery.q.describe("Text to search for in titles and Briefs."),
5836
- status: feedQuery.status.describe("List conversations that you have not archived (default) or that you archived. A search ignores this filter.")
6306
+ status: feedQuery.status.describe("Return conversations that you have not archived (default) or that you archived. A search ignores this filter.")
5837
6307
  },
5838
6308
  outputSchema: { conversations: z.array(mcpConversationSchema) },
5839
6309
  annotations: {
5840
- readOnlyHint: true,
5841
- openWorldHint: false
6310
+ destructiveHint: false,
6311
+ idempotentHint: true,
6312
+ openWorldHint: false,
6313
+ readOnlyHint: true
5842
6314
  }
5843
6315
  }, async ({ q, status }) => {
5844
- const result = { conversations: (await readConversationFeed({
6316
+ return mcpToolResult({ conversations: (await readConversationFeed({
5845
6317
  q,
5846
6318
  status,
5847
6319
  viewer: user
5848
- })).conversations.map(mcpConversation) };
5849
- return {
5850
- content: [{
5851
- type: "text",
5852
- text: JSON.stringify(result)
5853
- }],
5854
- structuredContent: result
5855
- };
6320
+ })).conversations.map(mcpConversation) });
6321
+ });
6322
+ }
6323
+ // src/api/mcp/tools/search-review-items.ts
6324
+ /** Register `searchReviewItems`, which searches open Review items for the user. */
6325
+ function registerSearchReviewItemsTool(server, { user }) {
6326
+ server.registerTool("searchReviewItems", {
6327
+ title: "Search review items",
6328
+ description: "Search the open review items that you can see, newest change first. A review item is a question or notice that waits for a person.",
6329
+ inputSchema: {
6330
+ q: z.string().trim().min(1).max(200).optional().describe("Words to find in the title or body."),
6331
+ conversationId: z.string().min(1).optional().describe("Return only the items linked to this conversation."),
6332
+ limit: z.number().int().min(1).max(50).default(20).describe("Maximum number of items.")
6333
+ },
6334
+ outputSchema: {
6335
+ items: z.array(mcpReviewItemSchema),
6336
+ truncated: z.boolean().describe("True when more items match.")
6337
+ },
6338
+ annotations: {
6339
+ destructiveHint: false,
6340
+ idempotentHint: true,
6341
+ openWorldHint: false,
6342
+ readOnlyHint: true
6343
+ }
6344
+ }, async ({ conversationId, limit, q }) => {
6345
+ const items = await searchReviewItems(getDb(), reviewItemReaderForUser(user), {
6346
+ ...conversationId ? { conversationId } : void 0,
6347
+ limit: limit + 1,
6348
+ ...q ? { query: q } : void 0,
6349
+ statuses: ["open"]
6350
+ });
6351
+ return mcpToolResult({
6352
+ items: items.slice(0, limit).map((item) => mcpReviewItem(item, user.id)),
6353
+ truncated: items.length > limit
6354
+ });
6355
+ });
6356
+ }
6357
+ // src/api/mcp/tools/stop-conversation.ts
6358
+ /** Register `stopConversation`, which stops the active Turn. */
6359
+ function registerStopConversationTool(server, { queue, user }) {
6360
+ server.registerTool("stopConversation", {
6361
+ title: "Stop conversation",
6362
+ description: "Stop the running turn of one of your conversations. Messages that wait for the turn are dropped. It starts no new work.",
6363
+ inputSchema: { conversationId: mcpConversationIdSchema },
6364
+ outputSchema: {
6365
+ status: z.enum(["no_work", "requested"]).describe("`no_work` when nothing was running."),
6366
+ droppedMessages: z.number().int()
6367
+ },
6368
+ annotations: {
6369
+ destructiveHint: true,
6370
+ idempotentHint: true,
6371
+ openWorldHint: false,
6372
+ readOnlyHint: false
6373
+ }
6374
+ }, async ({ conversationId }) => {
6375
+ if (!await isConversationParticipant(user, conversationId)) return mcpToolError("Conversation not found.");
6376
+ try {
6377
+ const result = await stopConversationTurnForViewer(user, conversationId, { queue });
6378
+ return mcpToolResult({
6379
+ status: result.status,
6380
+ droppedMessages: result.stoppedInboundMessageIds.length
6381
+ });
6382
+ } catch (error) {
6383
+ return apiToolError(error);
6384
+ }
5856
6385
  });
5857
6386
  }
5858
6387
  // src/api/mcp/server.ts
@@ -5864,18 +6393,31 @@ function registerListConversationsTool(server, user) {
5864
6393
  * JSON. No MCP session or SSE stream outlives the request, so the route works
5865
6394
  * on any app instance and is not limited by the live-stream duration.
5866
6395
  */
5867
- /** Build the MCP server for one authenticated user. */
5868
- function createMcpServer(user) {
6396
+ /** The tools that each scope unlocks. A client sees only its granted tools. */
6397
+ const TOOLS_BY_SCOPE = {
6398
+ "conversations:read": [
6399
+ registerSearchConversationsTool,
6400
+ registerGetConversationTool,
6401
+ registerListConversationMessagesTool
6402
+ ],
6403
+ "conversations:manage": [registerArchiveConversationTool, registerStopConversationTool],
6404
+ "review_items:read": [registerSearchReviewItemsTool, registerGetReviewItemTool],
6405
+ "automations:read": [registerSearchAutomationsTool, registerGetAutomationTool]
6406
+ };
6407
+ const INSTRUCTIONS = "Junior is a Slack agent. These tools read your Junior conversations, review items, and automations. They cannot send a message or start agent work. To ask Junior for work or to answer a review item, use the Slack or dashboard link in a result.";
6408
+ /** Build the MCP server for one authenticated user and granted scopes. */
6409
+ function createMcpServer(context, scopes) {
5869
6410
  const server = new McpServer({
5870
6411
  name: "junior",
5871
6412
  version: JUNIOR_VERSION
5872
- });
5873
- registerListConversationsTool(server, user);
6413
+ }, { instructions: INSTRUCTIONS });
6414
+ for (const scope of scopes) for (const register of TOOLS_BY_SCOPE[scope]) register(server, context);
5874
6415
  return server;
5875
6416
  }
5876
6417
  /** Answer one Streamable HTTP POST for an authenticated user. */
5877
- async function handleMcpServerRequest(request, user) {
5878
- const server = createMcpServer(user);
6418
+ async function handleMcpServerRequest(request, args) {
6419
+ const { scopes, ...context } = args;
6420
+ const server = createMcpServer(context, scopes);
5879
6421
  const transport = new WebStandardStreamableHTTPServerTransport({
5880
6422
  enableJsonResponse: true,
5881
6423
  sessionIdGenerator: void 0
@@ -5972,7 +6514,7 @@ function unauthorized(urls, sentToken) {
5972
6514
  status: 401,
5973
6515
  headers: {
5974
6516
  "cache-control": "no-store",
5975
- "www-authenticate": `Bearer ${sentToken ? "error=\"invalid_token\", " : ""}resource_metadata="${urls.resourceMetadata}", scope="${MCP_SCOPE}"`
6517
+ "www-authenticate": `Bearer ${sentToken ? "error=\"invalid_token\", " : ""}resource_metadata="${urls.resourceMetadata}", scope="${MCP_ALL_SCOPES}"`
5976
6518
  }
5977
6519
  });
5978
6520
  }
@@ -6001,18 +6543,22 @@ function createMcpHttpHandler(options) {
6001
6543
  ], MCP_PATH, async (c) => {
6002
6544
  const requestUrls = urls(c.req.raw);
6003
6545
  const token = bearerToken(c.req.raw);
6004
- const email = token ? await authenticateMcpAccessToken({
6546
+ const grant = token ? await authenticateMcpAccessToken({
6005
6547
  resource: requestUrls.resource,
6006
6548
  state: options.state,
6007
6549
  token
6008
6550
  }) : void 0;
6009
- const user = email && isAllowedSignInEmail(email, options.allowedDomains) ? await resolveViewerUser(email) : void 0;
6010
- if (!user) return unauthorized(requestUrls, token !== void 0);
6551
+ const user = grant && isAllowedSignInEmail(grant.email, options.allowedDomains) ? await resolveViewerUser(grant.email) : void 0;
6552
+ if (!grant || !user) return unauthorized(requestUrls, token !== void 0);
6011
6553
  if (c.req.method !== "POST") return new Response(null, {
6012
6554
  status: 405,
6013
6555
  headers: { allow: "POST" }
6014
6556
  });
6015
- return await handleMcpServerRequest(c.req.raw, user);
6557
+ return await handleMcpServerRequest(c.req.raw, {
6558
+ queue: options.queue,
6559
+ scopes: grant.scopes,
6560
+ user
6561
+ });
6016
6562
  });
6017
6563
  return app.fetch;
6018
6564
  }
@@ -6602,7 +7148,6 @@ async function createApp(options) {
6602
7148
  setBotModelConfig(options ?? {});
6603
7149
  setRuntimeLimits(options?.limits);
6604
7150
  setSlackReactionConfig(options?.slack ?? {});
6605
- if (options?.slack?.crossActorMidRunMode) setCrossActorMidRunMode(options.slack.crossActorMidRunMode);
6606
7151
  assertNoLegacyAllowedDomains(Object.keys(dashboard ?? {}));
6607
7152
  allowedDomains = resolveAllowedDomains(options?.allowedDomains);
6608
7153
  if (allowedDomains.length === 0) logWarn("app.allowed_domains.unset", { "app.reason": "allowedDomains is empty, so any Slack workspace member can use Junior" });
@@ -6750,6 +7295,7 @@ async function createApp(options) {
6750
7295
  handleMcpRequest ??= createMcpHttpHandler({
6751
7296
  allowedDomains,
6752
7297
  baseURL: resolvePublicBaseURL(dashboard),
7298
+ queue: conversationWorkQueue,
6753
7299
  state: getMcpState()
6754
7300
  });
6755
7301
  return handleMcpRequest(c.req.raw);