@sentry/junior 0.271.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 (75) hide show
  1. package/dist/{adapter-Bqbtr1RV.js → adapter-umap1cth.js} +1 -1
  2. package/dist/{admin-CWBjzyN0.js → admin-0o6E_3lo.js} +1 -1
  3. package/dist/{agent-runner-BiHsH8O_.js → agent-runner-D7Qz-lGO.js} +899 -631
  4. package/dist/api/schema.d.ts +2 -2
  5. package/dist/api/schema.js +5 -5
  6. package/dist/api.d.ts +1 -1
  7. package/dist/api.js +62 -537
  8. package/dist/{app-C81xfpNa.d.ts → app-BcyhJq8x.d.ts} +4 -9
  9. package/dist/app.d.ts +1 -1
  10. package/dist/app.js +673 -134
  11. package/dist/{assistant-reply-DgtX9DaJ.js → assistant-reply-wZ7i4OFa.js} +1 -1
  12. package/dist/{automation-Dwh_xcqM.js → automation-yffXcZt9.js} +1 -1
  13. package/dist/{briefs-0FeSJHQ3.js → briefs-CgMnlpIp.js} +4 -4
  14. package/dist/{catalog-runtime-Bw9kjGVu.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-B0jlQSgO.js → client-DhqbV0Fe.js} +63 -5
  24. package/dist/{reporting-schema-CZREhYc-.js → common-Eu6sx09c.js} +4 -1
  25. package/dist/{config-DrAeD8sr.js → config-C6FTd7PX.js} +7 -15
  26. package/dist/{connections-BzN2K0G9.js → connections-Dq72KajR.js} +11 -11
  27. package/dist/{conversation-AhOVzN_k.js → conversation-T4oabbBi.js} +22 -1
  28. package/dist/{conversation-privacy-D1NhUxyo.js → conversation-privacy-BeRgrsph.js} +1 -1
  29. package/dist/{credential-sync-DpNWl4yy.js → credential-sync-BmCbHAp4.js} +15 -16
  30. package/dist/{db-DSabI7jJ.d.ts → db-DOzBGLn5.d.ts} +11 -1
  31. package/dist/{executor-C2cRw0hZ.js → executor-BafgvQcc.js} +25 -7
  32. package/dist/instrumentation.js +114 -3
  33. package/dist/{job-queue-CEeLYkSj.js → job-queue-C4lphW9Y.js} +3 -3
  34. package/dist/{logging-DuwXUuIY.js → logging-CNs1e04n.js} +6 -1
  35. package/dist/message-CETC5lsl.js +186 -0
  36. package/dist/nitro.d.ts +1 -1
  37. package/dist/nitro.js +3 -3
  38. package/dist/{oauth-callback-server-oM5TJt34.js → oauth-callback-server-DcQZsKMm.js} +2 -2
  39. package/dist/{credentialed-mh7HBqey.js → projection-BOpi891B.js} +4576 -49
  40. package/dist/{prompt-BPSdXYXV.js → prompt-B1ySjUyf.js} +1 -1
  41. package/dist/{registry-BPtbAVFo.js → registry-B-yUrv3h.js} +2 -2
  42. package/dist/{resolve-D44G1jJm.js → resolve-DqVtd4Q9.js} +3 -3
  43. package/dist/{result-CvP8c5bb.js → result-DQwTiLTw.js} +16 -12
  44. package/dist/{runner-FIGIKvk2.js → runner-Dco5qMq6.js} +11 -11
  45. package/dist/{sandbox-egress-signals-C_JzucwP.js → sandbox-egress-signals-DFc_9Mcv.js} +1 -1
  46. package/dist/{schema-DKZgO-xf.js → schema-5xHDGlBW.js} +10 -9
  47. package/dist/{schema-DPCvePop.d.ts → schema-9OGdsaxF.d.ts} +28 -3
  48. package/dist/{service-account-1mJGRAvx.js → service-account-CWfk9Cs-.js} +3 -3
  49. package/dist/{service-account-CHO3YXpt.js → service-account-smdPFWNy.js} +3 -3
  50. package/dist/{session-stop-DuUB1rwQ.js → session-stop-DaT_7tYa.js} +50 -24
  51. package/dist/{skills-DHK7L9Eo.js → skills-BbtJSF2-.js} +2 -2
  52. package/dist/{spawn-CKl_ySIq.js → spawn-Ceikdjkp.js} +2 -2
  53. package/dist/{list-DjJJ5K0x.js → stop-dKUc-x2K.js} +1017 -469
  54. package/dist/{store-Di0E4EvU.js → store-C1xuZcLd.js} +48 -31
  55. package/dist/{store-BF2XEeQz.js → store-DaxUuOmD.js} +3 -3
  56. package/dist/{task-queue-CC7IFtbT.js → task-queue-ymAh_-P1.js} +4 -4
  57. package/dist/{task-runner-DKZtMFwa.js → task-runner-vlH6pEm3.js} +6 -6
  58. package/dist/{thread-state-BQ1T3eeF.js → thread-state-BkUpE9Ae.js} +4 -5
  59. package/dist/{turn-failure-response-B5eSFoHn.js → turn-failure-response-DIsD7ip_.js} +7 -8
  60. package/dist/{turn-lifecycle-CTLEIEOn.js → turn-lifecycle-BdPKdPS8.js} +1 -1
  61. package/dist/turn-session-routing-BdCTDq9m.js +1625 -0
  62. package/dist/{validation-Cjo-3yt3.js → validation-BQjCfh84.js} +1 -1
  63. package/dist/{version-D30MBLLw.js → version-BpiFGZZW.js} +1 -1
  64. package/dist/version.js +1 -1
  65. package/dist/{viewer-D3jhuP66.js → viewer-C6Y1aXjZ.js} +49 -21
  66. package/dist/{work-CL9QwQcl.js → work-ChPh_YEh.js} +27 -39
  67. package/dist/{worker-S2P7IJDI.js → worker-Be_sK_ou.js} +6 -6
  68. package/migrations/0072_conversation_read_positions.sql +10 -0
  69. package/migrations/meta/0072_snapshot.json +4064 -0
  70. package/migrations/meta/_journal.json +7 -0
  71. package/package.json +4 -4
  72. package/dist/message-CRe3WBKc.js +0 -59
  73. package/dist/turn-session-routing-BFVp8TbU.js +0 -6072
  74. package/migrations/AGENTS.md +0 -8
  75. 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-BFVp8TbU.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-DrAeD8sr.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-DuwXUuIY.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-D30MBLLw.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-D3jhuP66.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 { c as juniorReviewItems, d as juniorAutomations, g as juniorConversationBriefs, l as juniorAutomationRuns, y as juniorConversationEvents } from "./executor-C2cRw0hZ.js";
10
- import { n as pluginCatalogRuntime } from "./catalog-runtime-Bw9kjGVu.js";
11
- import { $ as effectiveTaskOutcomes, A as verifySlackSignature, C as syncReviewItemSlackCards, D as parseSlackReviewItemFormTarget, E as parseSlackReviewItemClick, I as joinPublicChannel, O as parseSlackUserInputSubmission, R as logScheduledAutomationRunSkipped, S as readSlackReviewItemCardConversationId, T as buildSlackReviewItemModal, Tt as ingestEvent, Y as readViewerAutomationSummary, _ as unpublishedArtifactHeaders, _t as stopConversationSession, at as conversationTurnIdForMessage, ct as recordWebConversationActivity, ft as collectAttachmentGarbage, g as readPublicArtifact, h as publicArtifactHeaders, it as appendAndEnqueueWebMessage, j as parseSlackThreadId, k as runWithSlackInstallation, lt as webActorFromEmail, mt as requestAttachmentDeletion, n as executeAgentRun, nt as renderReviewItemMessage, t as createAgentRunner, tt as deliverReviewItemChange, v as lookupSlackUserProfile, vt as recordSubagentEnded, wt as processWorkspaceSnapshotJob, x as createSlackReviewItemShow, z as scheduledAutomationRunAttributes } from "./agent-runner-BiHsH8O_.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-mh7HBqey.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-Bqbtr1RV.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, I as annotationCard, T as conversationReportStatusSchema, h as conversationFeedQuerySchema } from "./conversation-AhOVzN_k.js";
17
- import { b as listPendingAgentInvocationMailboxAppends, x as listPendingAgentInvocationResults } from "./store-Di0E4EvU.js";
18
- import { E as renderSlackObjectCard, S as renderTaskInput, d as getEventCatalog, f as SLACK_EVENT_NAMESPACE, g as slackChannelMessageRunsAutomation, h as slackChannelMessageReplyThreadTs, m as slackChannelMessageEvent } from "./session-stop-DuUB1rwQ.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-D44G1jJm.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-CEeLYkSj.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-BF2XEeQz.js";
23
- import { M as scheduledRunSchema } from "./automation-Dwh_xcqM.js";
24
- import { n as validatePluginRegistrations, t as validatePluginEgressCredentialHooks } from "./validation-Cjo-3yt3.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-DjJJ5K0x.js";
27
- import { n as enqueueAgentInvocationResult, o as getAuthPausedConversationTurnId } from "./result-CvP8c5bb.js";
28
- import { r as enqueueAgentInvocation } from "./work-CL9QwQcl.js";
29
- import { o as coerceThreadConversationState } from "./thread-state-BQ1T3eeF.js";
30
- import { o as unlinkProvider } from "./connections-BzN2K0G9.js";
31
- import { a as GET$4, i as publishAppHomeView, r as GET$5, t as receiveLocalOAuthCredential } from "./credential-sync-DpNWl4yy.js";
32
- import { a as pluginTaskQueueConsumer, c as queueCallback, n as createVercelPluginTaskCallback, o as registerVercelPluginTaskDevConsumer } from "./task-queue-CC7IFtbT.js";
33
- import { t as processConversationWork } from "./worker-S2P7IJDI.js";
34
- import { t as bindMessageAgent } from "./message-CRe3WBKc.js";
35
- import { t as bindSpawnAgent } from "./spawn-CKl_ySIq.js";
36
- import { t as processPluginTask } from "./task-runner-DKZtMFwa.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";
@@ -1924,13 +1925,6 @@ async function handleSlackReviewItemClick(args) {
1924
1925
  services,
1925
1926
  target
1926
1927
  };
1927
- if (click.kind === "done") {
1928
- await act({
1929
- ...common,
1930
- action: "complete"
1931
- });
1932
- return;
1933
- }
1934
1928
  if (click.kind === "dismiss") {
1935
1929
  await act({
1936
1930
  ...common,
@@ -5294,59 +5288,6 @@ function redirectUriMatches(registered, requested) {
5294
5288
  return candidate !== null && isLoopbackHttp(candidate) && candidate.hostname === url.hostname && candidate.pathname === url.pathname && candidate.search === url.search;
5295
5289
  });
5296
5290
  }
5297
- // src/api/mcp/oauth/metadata.ts
5298
- /**
5299
- * Own the MCP OAuth paths, scope, and discovery documents.
5300
- *
5301
- * Every URL comes from the trusted public origin of the app. The documents
5302
- * advertise Client ID Metadata Documents and no client registration endpoint.
5303
- */
5304
- const MCP_PATH = "/api/mcp";
5305
- const MCP_SCOPE = "conversations:read";
5306
- const MCP_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource/api/mcp";
5307
- const MCP_AUTHORIZATION_SERVER_METADATA_PATH = "/.well-known/oauth-authorization-server";
5308
- const MCP_AUTHORIZE_PATH = "/api/mcp/oauth/authorize";
5309
- const MCP_TOKEN_PATH = "/api/mcp/oauth/token";
5310
- /** Dashboard-authenticated consent page for one pending authorization. */
5311
- const MCP_CONSENT_PATH = "/_junior/mcp/authorize/:authorizationId";
5312
- /** Resolve the MCP URLs for this request from the trusted public origin. */
5313
- function resolveMcpUrls(request, baseURL) {
5314
- const origin = resolvePublicOrigin(request, baseURL);
5315
- if (!origin) throw new Error("Junior MCP requires a configured public base URL");
5316
- const url = (path) => new URL(path, origin).toString();
5317
- return {
5318
- authorize: url(MCP_AUTHORIZE_PATH),
5319
- issuer: origin.origin,
5320
- resource: url(MCP_PATH),
5321
- resourceMetadata: url(MCP_RESOURCE_METADATA_PATH),
5322
- token: url(MCP_TOKEN_PATH),
5323
- consent: (authorizationId) => url(MCP_CONSENT_PATH.replace(":authorizationId", encodeURIComponent(authorizationId)))
5324
- };
5325
- }
5326
- /** RFC 9728 protected resource metadata for `/api/mcp`. */
5327
- function protectedResourceMetadata(urls) {
5328
- return {
5329
- resource: urls.resource,
5330
- authorization_servers: [urls.issuer],
5331
- bearer_methods_supported: ["header"],
5332
- scopes_supported: [MCP_SCOPE]
5333
- };
5334
- }
5335
- /** RFC 8414 authorization server metadata. CIMD is the only client model. */
5336
- function authorizationServerMetadata(urls) {
5337
- return {
5338
- issuer: urls.issuer,
5339
- authorization_endpoint: urls.authorize,
5340
- token_endpoint: urls.token,
5341
- scopes_supported: [MCP_SCOPE],
5342
- response_types_supported: ["code"],
5343
- grant_types_supported: ["authorization_code", "refresh_token"],
5344
- code_challenge_methods_supported: ["S256"],
5345
- token_endpoint_auth_methods_supported: ["none"],
5346
- authorization_response_iss_parameter_supported: true,
5347
- client_id_metadata_document_supported: true
5348
- };
5349
- }
5350
5291
  // src/api/mcp/oauth/page.ts
5351
5292
  /** Render one MCP sign-in page. It sends no referrer and loads no resources. */
5352
5293
  function renderMcpPage(page) {
@@ -5362,6 +5303,42 @@ function renderMcpPage(page) {
5362
5303
  title: page.title
5363
5304
  });
5364
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);
5365
5342
  // src/api/mcp/oauth/authorization.ts
5366
5343
  /**
5367
5344
  * Own the OAuth authorization endpoint and its pending requests.
@@ -5383,7 +5360,7 @@ const mcpAuthorizationSchema = z.object({
5383
5360
  expiresAtMs: z.number().int().positive(),
5384
5361
  redirectUri: z.string().min(1),
5385
5362
  resource: z.string().min(1),
5386
- scope: z.literal(MCP_SCOPE),
5363
+ scope: mcpScopeValueSchema,
5387
5364
  state: z.string().max(STATE_MAX_LENGTH).optional()
5388
5365
  }).strict();
5389
5366
  /** The user who owns one consent page, and the hash of its form token. */
@@ -5490,8 +5467,8 @@ async function handleMcpAuthorize(args) {
5490
5467
  const codeChallenge = params.get("code_challenge") ?? "";
5491
5468
  if (params.get("code_challenge_method") !== "S256" || !CODE_CHALLENGE_PATTERN.test(codeChallenge)) return fail("invalid_request", "PKCE with S256 is required.");
5492
5469
  if (oauthState !== void 0 && oauthState.length > STATE_MAX_LENGTH) return fail("invalid_request", "The state parameter is too long.");
5493
- const scope = params.get("scope")?.trim();
5494
- 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}.`);
5495
5472
  if (params.get("resource") !== args.urls.resource) return fail("invalid_target", `The resource must be ${args.urls.resource}.`);
5496
5473
  const authorizationId = randomBytes(32).toString("base64url");
5497
5474
  const authorization = {
@@ -5501,7 +5478,7 @@ async function handleMcpAuthorize(args) {
5501
5478
  expiresAtMs: Date.now() + AUTHORIZATION_TTL_MS,
5502
5479
  redirectUri,
5503
5480
  resource: args.urls.resource,
5504
- scope: MCP_SCOPE,
5481
+ scope: scopes.join(" "),
5505
5482
  state: oauthState
5506
5483
  };
5507
5484
  await args.state.set(authorizationKey(authorizationId), authorization, AUTHORIZATION_TTL_MS);
@@ -5536,7 +5513,7 @@ const authorizationCodeSchema = z.object({
5536
5513
  grantId: z.string().uuid(),
5537
5514
  redirectUri: z.string().min(1),
5538
5515
  resource: z.string().min(1),
5539
- scope: z.literal(MCP_SCOPE)
5516
+ scope: mcpScopeValueSchema
5540
5517
  }).strict();
5541
5518
  const grantSchema = z.object({
5542
5519
  clientId: z.string().min(1),
@@ -5544,7 +5521,7 @@ const grantSchema = z.object({
5544
5521
  /** Absolute end of the grant. Refresh never moves it. */
5545
5522
  expiresAtMs: z.number().int().positive(),
5546
5523
  resource: z.string().min(1),
5547
- scope: z.literal(MCP_SCOPE)
5524
+ scope: mcpScopeValueSchema
5548
5525
  }).strict();
5549
5526
  const tokenSchema = z.object({ grantId: z.string().uuid() }).strict();
5550
5527
  function codeKey(code) {
@@ -5579,7 +5556,7 @@ async function issueAuthorizationCode(args) {
5579
5556
  grantId: randomUUID(),
5580
5557
  redirectUri: args.redirectUri,
5581
5558
  resource: args.resource,
5582
- scope: MCP_SCOPE
5559
+ scope: args.scope
5583
5560
  };
5584
5561
  await args.state.set(codeKey(code), record, CODE_TTL_MS);
5585
5562
  return code;
@@ -5669,8 +5646,8 @@ async function refreshGrant(args) {
5669
5646
  const token = await readRecord(args.state, key, tokenSchema);
5670
5647
  const grant = token ? await readGrant(args.state, token.grantId) : void 0;
5671
5648
  if (!token || !grant || grant.clientId !== args.clientId || args.resource !== void 0 && args.resource !== grant.resource) return invalidGrant("The refresh token is not valid.");
5672
- if (args.scope !== void 0 && args.scope !== grant.scope) return {
5673
- 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}.`,
5674
5651
  error: "invalid_scope",
5675
5652
  ok: false
5676
5653
  };
@@ -5689,9 +5666,9 @@ async function refreshGrant(args) {
5689
5666
  return await issueTokens(args.state, token.grantId, grant);
5690
5667
  }
5691
5668
  /**
5692
- * Return the user email of one MCP access token for one resource. Personal
5693
- * tokens and other bearer values never match, because only this module
5694
- * 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.
5695
5672
  */
5696
5673
  async function authenticateMcpAccessToken(args) {
5697
5674
  if (!args.token.startsWith(ACCESS_TOKEN_PREFIX)) return void 0;
@@ -5699,7 +5676,62 @@ async function authenticateMcpAccessToken(args) {
5699
5676
  if (!token) return void 0;
5700
5677
  const grant = await readGrant(args.state, token.grantId);
5701
5678
  if (!grant || grant.resource !== args.resource) return void 0;
5702
- 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
+ };
5703
5735
  }
5704
5736
  // src/api/mcp/oauth/consent-page.ts
5705
5737
  /**
@@ -5727,8 +5759,10 @@ function consentPage(args) {
5727
5759
  const clientHost = new URL(args.authorization.clientId).host;
5728
5760
  const clientName = args.authorization.clientName ?? clientHost;
5729
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("");
5730
5763
  return renderMcpPage({
5731
- 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>
5732
5766
  <form method="post">
5733
5767
  <input type="hidden" name="csrf" value="${escapeHtml(args.csrfToken)}">
5734
5768
  ${button("approve", "Allow", true)}${button("deny", "Deny", false)}
@@ -5798,13 +5832,15 @@ async function handleMcpConsentPage(args) {
5798
5832
  email: args.user.email,
5799
5833
  redirectUri: authorization.redirectUri,
5800
5834
  resource: authorization.resource,
5835
+ scope: authorization.scope,
5801
5836
  state: args.state
5802
5837
  }) },
5803
5838
  redirectUri: authorization.redirectUri,
5804
5839
  state: authorization.state
5805
5840
  });
5806
5841
  }
5807
- // 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. */
5808
5844
  /** MCP view of one Conversation. It omits dashboard-only fields. */
5809
5845
  const mcpConversationSchema = z.object({
5810
5846
  conversationId: z.string(),
@@ -5818,6 +5854,7 @@ const mcpConversationSchema = z.object({
5818
5854
  archivedAt: z.string().optional(),
5819
5855
  url: z.string().optional()
5820
5856
  }).strict();
5857
+ /** Map a Conversation summary to its MCP view. */
5821
5858
  function mcpConversation(summary) {
5822
5859
  return {
5823
5860
  conversationId: summary.conversationId,
@@ -5832,34 +5869,519 @@ function mcpConversation(summary) {
5832
5869
  url: getDashboardConversationLink(summary.conversationId)
5833
5870
  };
5834
5871
  }
5835
- /** Register `listConversations`, which lists the Conversations of `user`. */
5836
- 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 }) {
5837
6300
  const feedQuery = conversationFeedQuerySchema.shape;
5838
- server.registerTool("listConversations", {
5839
- title: "List conversations",
5840
- 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.",
5841
6304
  inputSchema: {
5842
6305
  q: feedQuery.q.describe("Text to search for in titles and Briefs."),
5843
- 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.")
5844
6307
  },
5845
6308
  outputSchema: { conversations: z.array(mcpConversationSchema) },
5846
6309
  annotations: {
5847
- readOnlyHint: true,
5848
- openWorldHint: false
6310
+ destructiveHint: false,
6311
+ idempotentHint: true,
6312
+ openWorldHint: false,
6313
+ readOnlyHint: true
5849
6314
  }
5850
6315
  }, async ({ q, status }) => {
5851
- const result = { conversations: (await readConversationFeed({
6316
+ return mcpToolResult({ conversations: (await readConversationFeed({
5852
6317
  q,
5853
6318
  status,
5854
6319
  viewer: user
5855
- })).conversations.map(mcpConversation) };
5856
- return {
5857
- content: [{
5858
- type: "text",
5859
- text: JSON.stringify(result)
5860
- }],
5861
- structuredContent: result
5862
- };
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
+ }
5863
6385
  });
5864
6386
  }
5865
6387
  // src/api/mcp/server.ts
@@ -5871,18 +6393,31 @@ function registerListConversationsTool(server, user) {
5871
6393
  * JSON. No MCP session or SSE stream outlives the request, so the route works
5872
6394
  * on any app instance and is not limited by the live-stream duration.
5873
6395
  */
5874
- /** Build the MCP server for one authenticated user. */
5875
- 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) {
5876
6410
  const server = new McpServer({
5877
6411
  name: "junior",
5878
6412
  version: JUNIOR_VERSION
5879
- });
5880
- registerListConversationsTool(server, user);
6413
+ }, { instructions: INSTRUCTIONS });
6414
+ for (const scope of scopes) for (const register of TOOLS_BY_SCOPE[scope]) register(server, context);
5881
6415
  return server;
5882
6416
  }
5883
6417
  /** Answer one Streamable HTTP POST for an authenticated user. */
5884
- async function handleMcpServerRequest(request, user) {
5885
- const server = createMcpServer(user);
6418
+ async function handleMcpServerRequest(request, args) {
6419
+ const { scopes, ...context } = args;
6420
+ const server = createMcpServer(context, scopes);
5886
6421
  const transport = new WebStandardStreamableHTTPServerTransport({
5887
6422
  enableJsonResponse: true,
5888
6423
  sessionIdGenerator: void 0
@@ -5979,7 +6514,7 @@ function unauthorized(urls, sentToken) {
5979
6514
  status: 401,
5980
6515
  headers: {
5981
6516
  "cache-control": "no-store",
5982
- "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}"`
5983
6518
  }
5984
6519
  });
5985
6520
  }
@@ -6008,18 +6543,22 @@ function createMcpHttpHandler(options) {
6008
6543
  ], MCP_PATH, async (c) => {
6009
6544
  const requestUrls = urls(c.req.raw);
6010
6545
  const token = bearerToken(c.req.raw);
6011
- const email = token ? await authenticateMcpAccessToken({
6546
+ const grant = token ? await authenticateMcpAccessToken({
6012
6547
  resource: requestUrls.resource,
6013
6548
  state: options.state,
6014
6549
  token
6015
6550
  }) : void 0;
6016
- const user = email && isAllowedSignInEmail(email, options.allowedDomains) ? await resolveViewerUser(email) : void 0;
6017
- 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);
6018
6553
  if (c.req.method !== "POST") return new Response(null, {
6019
6554
  status: 405,
6020
6555
  headers: { allow: "POST" }
6021
6556
  });
6022
- 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
+ });
6023
6562
  });
6024
6563
  return app.fetch;
6025
6564
  }
@@ -6609,7 +7148,6 @@ async function createApp(options) {
6609
7148
  setBotModelConfig(options ?? {});
6610
7149
  setRuntimeLimits(options?.limits);
6611
7150
  setSlackReactionConfig(options?.slack ?? {});
6612
- if (options?.slack?.crossActorMidRunMode) setCrossActorMidRunMode(options.slack.crossActorMidRunMode);
6613
7151
  assertNoLegacyAllowedDomains(Object.keys(dashboard ?? {}));
6614
7152
  allowedDomains = resolveAllowedDomains(options?.allowedDomains);
6615
7153
  if (allowedDomains.length === 0) logWarn("app.allowed_domains.unset", { "app.reason": "allowedDomains is empty, so any Slack workspace member can use Junior" });
@@ -6757,6 +7295,7 @@ async function createApp(options) {
6757
7295
  handleMcpRequest ??= createMcpHttpHandler({
6758
7296
  allowedDomains,
6759
7297
  baseURL: resolvePublicBaseURL(dashboard),
7298
+ queue: conversationWorkQueue,
6760
7299
  state: getMcpState()
6761
7300
  });
6762
7301
  return handleMcpRequest(c.req.raw);