@sentry/junior 0.256.0 → 0.258.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 (106) hide show
  1. package/dist/{actor-BTtLaeWy.js → actor-tlxzqaRe.js} +3 -6
  2. package/dist/{admin-BhucwVfy.js → admin-My1SLGVO.js} +2 -3
  3. package/dist/{agent-hooks-DlSK8PQQ.js → agent-hooks-BJ_5mlRH.js} +71 -111
  4. package/dist/{agent-invocations-BY60iNw-.js → agent-invocations-Qfz7dWgU.js} +8 -13
  5. package/dist/{agent-runner-C6_cd61V.js → agent-runner-DGsFQw72.js} +278 -467
  6. package/dist/api/schema.d.ts +454 -448
  7. package/dist/api/schema.js +5 -5
  8. package/dist/api.d.ts +2 -12
  9. package/dist/api.js +189 -165
  10. package/dist/{app-B0gT1Slp.d.ts → app-PX2H1PxU.d.ts} +20 -37
  11. package/dist/app.d.ts +3 -3
  12. package/dist/app.js +565 -305
  13. package/dist/{assistant-reply-yn5G-YEG.js → assistant-reply-H1M-91oF.js} +3 -5
  14. package/dist/{automation-CHdlHVZP.js → automation-DWBFBenG.js} +6 -11
  15. package/dist/{briefs-U6Bi3IYw.js → briefs-vYoK85rJ.js} +8 -11
  16. package/dist/{cadence-D00SCzT1.js → cadence-R7Em4RjE.js} +1 -2
  17. package/dist/{catalog-runtime-DF5OWqCc.js → catalog-runtime-BN4DVxco.js} +3 -4
  18. package/dist/cli/chat.d.ts +2 -4
  19. package/dist/cli/chat.js +22 -24
  20. package/dist/cli/check.d.ts +1 -3
  21. package/dist/cli/check.js +4 -5
  22. package/dist/cli/env.d.ts +1 -3
  23. package/dist/cli/env.js +1 -2
  24. package/dist/cli/init.d.ts +1 -3
  25. package/dist/cli/init.js +3 -3
  26. package/dist/cli/main.d.ts +1 -3
  27. package/dist/cli/main.js +4 -5
  28. package/dist/cli/plugins.d.ts +2 -4
  29. package/dist/cli/plugins.js +7 -8
  30. package/dist/cli/run.d.ts +1 -2
  31. package/dist/cli/run.js +1 -2
  32. package/dist/cli/snapshot-create.d.ts +1 -3
  33. package/dist/cli/snapshot-create.js +4 -5
  34. package/dist/cli/upgrade.d.ts +3 -6
  35. package/dist/cli/upgrade.js +11 -17
  36. package/dist/{client-BVrAp-pq.js → client-DbyHuMFU.js} +9 -16
  37. package/dist/{config-DdBiDanf.js → config-_WO_AjCB.js} +18 -15
  38. package/dist/{connections-CG04HdpC.js → connections-ag_1JOhB.js} +21 -21
  39. package/dist/{conversation-T18Xlkx9.js → conversation-DjN3mlog.js} +11 -17
  40. package/dist/{conversation-privacy-CpSYOWnP.js → conversation-privacy-DHP_xnVj.js} +2 -3
  41. package/dist/{conversation-work-C6y3T5Hx.js → conversation-work-BaT_T6EB.js} +1 -2
  42. package/dist/{credential-sync-DKWNdcgm.js → credential-sync-B-CPX_ag.js} +89 -52
  43. package/dist/{db-Ca9Nt5ci.d.ts → db-BnRqQ7G5.d.ts} +1 -1
  44. package/dist/{executor-BroK9oKy.js → executor-RvG6kVe1.js} +58 -70
  45. package/dist/{experimental-Qn9rKLgx.js → experimental-BNK1l1jv.js} +2 -3
  46. package/dist/{history-9pO1Zd4Y.js → history-AY8rs-FR.js} +5 -9
  47. package/dist/{history-DvqWZlYX.d.ts → history-BgI33Kie.d.ts} +12 -20
  48. package/dist/{images-D0VZ5nv0.js → images-C2J_bj2g.js} +5 -6
  49. package/dist/instrumentation.d.ts +1 -3
  50. package/dist/instrumentation.js +6 -8
  51. package/dist/{job-queue-BbGg3gvu.js → job-queue-DVe37it2.js} +6 -12
  52. package/dist/{logging-tPQeUe2i.js → logging-DsHONhVm.js} +54 -24
  53. package/dist/{message-DdPGRYQP.js → message-DyitL8no.js} +5 -6
  54. package/dist/{model-profile-BwU8mqwD.js → model-profile-Bg8EoCKD.js} +2 -4
  55. package/dist/nitro.d.ts +3 -5
  56. package/dist/nitro.js +12 -17
  57. package/dist/{oauth-authorization-BA4Dt6a7.js → oauth-authorization-djQn5Nhy.js} +2 -4
  58. package/dist/{oauth-callback-server-B-2errCX.js → oauth-callback-server-CDby-pCn.js} +3 -4
  59. package/dist/{oauth-relay-CdCnu80W.js → oauth-relay-CTygMkNx.js} +2 -3
  60. package/dist/{package-discovery-BVvSnQBB.js → package-discovery-DJBompdd.js} +17 -21
  61. package/dist/{pending-auth-DT3MRVW8.js → pending-auth-D8J1uJUS.js} +37 -28
  62. package/dist/{plugin-module-CVvEUSMH.js → plugin-module-Cf5gVPWP.js} +1 -2
  63. package/dist/{plugins-CjfUP5gJ.d.ts → plugins-B11Mfkmv.d.ts} +0 -4
  64. package/dist/{plugins-egKWjgue.js → plugins-DOm9Y6qI.js} +2 -3
  65. package/dist/{private-trace-result-BLvBjy_e.js → private-trace-result-CE7_lEdq.js} +1 -2
  66. package/dist/{profile-Bb1zCVGP.js → profile-DpYjfKke.js} +16 -28
  67. package/dist/{prompt-C04mP1hu.js → prompt-xgpyMtg7.js} +5 -9
  68. package/dist/{queue-B5SnqRln.d.ts → queue-C37RbDVc.d.ts} +0 -2
  69. package/dist/{registry-CsNavQ_Z.js → registry-Du566MtJ.js} +57 -30
  70. package/dist/{resolve-B6ZZ9duG.js → resolve-ClCIgh8E.js} +7 -12
  71. package/dist/{result-j9nS0se-.js → result-BBbKA61B.js} +13 -17
  72. package/dist/{rolldown-runtime-B4FMCO8f.js → rolldown-runtime-nwUcYXjP.js} +1 -2
  73. package/dist/{runner-Y331DoZ5.js → runner-CCuqFSqx.js} +12 -13
  74. package/dist/{sandbox-egress-signals-BUfjEdHY.js → sandbox-egress-signals-DY1mbKF4.js} +2 -3
  75. package/dist/{schema-BBgv0MNf.js → schema-DrMh3NtR.js} +17 -27
  76. package/dist/{sentry-CgWZM_kV.js → sentry-BBUlHih9.js} +2 -3
  77. package/dist/{session-stop-q-6GNHx6.js → session-stop-CIoFiwwe.js} +57 -41
  78. package/dist/{skills-CyvTsR9M.js → skills-DyQBLIrK.js} +9 -9
  79. package/dist/{spawn-Do5mX3d_.js → spawn-Dqfeo7XP.js} +4 -5
  80. package/dist/{store-DoewL5mG.d.ts → store-CG0VJyye.d.ts} +0 -14
  81. package/dist/{store-D2It0ozF.js → store-DJLwq4x8.js} +13 -15
  82. package/dist/{task-queue-ZMk9bLRT.js → task-queue-BzW4MaWU.js} +5 -7
  83. package/dist/{task-runner-cIGU5VwJ.js → task-runner-D3qJh4fK.js} +22 -26
  84. package/dist/{tasks-CODtRzvX.js → tasks-CEXKTASS.js} +78 -79
  85. package/dist/{thread-state-BPkwVmvK.js → thread-state-W3f6kAtz.js} +13 -19
  86. package/dist/{turn-failure-response-ChmMeKar.js → turn-failure-response-CWx7aNIh.js} +16 -22
  87. package/dist/{turn-lifecycle-BUP7ZOVa.js → turn-lifecycle-C9JFst7-.js} +2 -3
  88. package/dist/{turn-session-routing-BcWM3PLm.js → turn-session-routing-CUxtfpr7.js} +2 -3
  89. package/dist/{validation-DCd69-cP.js → validation-BHRxa5G4.js} +3 -4
  90. package/dist/vercel.d.ts +1 -3
  91. package/dist/vercel.js +1 -2
  92. package/dist/{version-DBc4Qt_P.js → version-C0U6rHg0.js} +3 -5
  93. package/dist/{version-CnXZ614E.d.ts → version-Cc2OFd9G.d.ts} +0 -2
  94. package/dist/version.d.ts +1 -1
  95. package/dist/version.js +1 -2
  96. package/dist/{viewer-C-9nFKYr.js → viewer-CGvvRqKp.js} +61 -44
  97. package/dist/{web-input-Cv42P3ni.js → web-input-Keb5GRWj.js} +139 -198
  98. package/dist/{web-message-id-xPI_EFSC.js → web-message-id-CJSUiksE.js} +3 -6
  99. package/dist/{work-6UmVQq1V.js → work-DRcsJ3hV.js} +14 -16
  100. package/dist/{worker-DAoBZuZn.js → worker-Cjo-QqPq.js} +7 -8
  101. package/migrations/0059_automation_run_version.sql +2 -0
  102. package/migrations/0060_identity_signed_in_at.sql +1 -0
  103. package/migrations/meta/0059_snapshot.json +3619 -0
  104. package/migrations/meta/0060_snapshot.json +3465 -0
  105. package/migrations/meta/_journal.json +15 -1
  106. package/package.json +8 -6
package/dist/app.js CHANGED
@@ -1,38 +1,39 @@
1
- import { At as consumeSandboxEgressPermissionDeniedSignal, B as effectiveTaskOutcomes, Ct as ingestEvent, Dt as SANDBOX_EGRESS_PROXY_PATH, E as requestAttachmentDeletion, Et as shouldPropagateSandboxEgressTrace, F as logScheduledAutomationRunSkipped, I as scheduledAutomationRunAttributes, It as startOAuthFlow, Kt as getConfigDefaults, Lt as createUserTokenStore, Nt as formatProviderLabel, O as parseSlackThreadId, Ot as clearSandboxEgressSignals, St as processWorkspaceSnapshotJob, Tt as normalizeSandboxEgressTracePropagationDomains, U as stopConversationSession, W as recordSubagentEnded, b as unpublishedArtifactHeaders, i as executeCredentialedEgressRequest, jt as parseSandboxEgressCredentialToken, kt as consumeSandboxEgressAuthRequiredSignal, lt as listPendingAgentInvocationMailboxAppends, n as executeAgentRun, qt as setConfigDefaults, t as createAgentRunner, ut as listPendingAgentInvocationResults, v as publicArtifactHeaders, w as collectAttachmentGarbage, wt as resolveSandboxEgressProviderForHost, x as lookupSlackUserProfile, y as readPublicArtifact } from "./agent-runner-C6_cd61V.js";
2
- import { C as withLogContext, O as JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE, S as withContext, d as logException, f as logInfo, j as JUNIOR_WORKSPACE_SNAPSHOT_JOB_CALLBACK_ROUTE, p as logWarn, r as createRequestContext, v as setSpanAttributes, w as withSpan, y as setSpanStatus } from "./logging-tPQeUe2i.js";
3
- import { n as captureMessage, r as continueTrace } from "./sentry-CgWZM_kV.js";
4
- import { t as JUNIOR_VERSION } from "./version-DBc4Qt_P.js";
5
- 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-DdBiDanf.js";
6
- import { _ as slackMessageTsSchema, d as parseSlackTeamId, f as parseSlackUserId, h as slackUserIdSchema, m as slackTeamIdSchema, o as parseActorUserId, p as slackChannelIdSchema } from "./actor-BTtLaeWy.js";
7
- import { c as getConversationEventStore, d as getDb, f as getSqlExecutor, g as withConversationMutationLock, m as withConversationEventLock, r as readActorIdentity, u as getConversationStore } from "./viewer-C-9nFKYr.js";
8
- import { a as juniorAgentInvocations, d as juniorDestinations, i as juniorAgentBindings, s as juniorConversations } from "./agent-invocations-BY60iNw-.js";
9
- import { f as juniorConversationBriefs, h as juniorConversationEvents, o as juniorAutomationRuns, s as juniorAutomations } from "./executor-BroK9oKy.js";
10
- import { n as pluginCatalogRuntime } from "./catalog-runtime-DF5OWqCc.js";
11
- import { Gt as projectConversationMessages, H as slackEntitySchema, Ht as appendConversationMessages, Jt as settleConversationTitleWork, N as slackMention, U as slackExternalRefIdSchema, b as admitEventAutomationRun, c as withScheduledAutomationLock, i as readScheduledAutomation, l as blockEventAutomation, n as listScheduledAutomationsForTeam, o as saveScheduledAutomationInLock, p as findMatchingEventAutomations, s as scheduledRunId, u as collectEventAutomationMatchKeys, x as eventAutomationRunLimitReason } from "./tasks-CODtRzvX.js";
12
- import { $ as bindSlackDirectCredentialSubject, At as resolveRootVisibility, Ht as getSlackClient, Jt as getWorkspaceTeamId, Nt as listConversationAnnotations, Q as bindScheduledAutomationCredentialSubject, U as collectEventMatchKeys, Wt as isDmChannel, X as takeDueTimerWatches, Yt as runWithWorkspaceTeamId, Z as bindEventAutomationCredentialSubject, a as getPluginRoutes, at as getDashboardConversationLink, ct as setDashboardConversationLinkOptions, d as setPlugins, et as verifyEventAutomationCredentialSubject, f as validatePlugins, ft as createPluginLogger, h as setBriefsConfig, i as getPluginApiRoutes, lt as createPluginState, nt as verifySlackDirectCredentialSubject, pt as readConversationAccessFromSql, q as findMatchingWatches, st as getDashboardTaskLink, tt as verifyScheduledAutomationCredentialSubject, u as getPlugins } from "./agent-hooks-DlSK8PQQ.js";
13
- import { W as annotationCard } from "./conversation-T18Xlkx9.js";
14
- import { O as withLock$1, w as getStateAdapter } from "./profile-Bb1zCVGP.js";
15
- import { c as getThreadStopDecision } from "./pending-auth-DT3MRVW8.js";
16
- import { O as renderTaskInput, f as slackChannelMessageEvent, l as getEventCatalog, p as slackChannelMessageReplyThreadTs, u as SLACK_EVENT_NAMESPACE, y as eventGuidance } from "./session-stop-q-6GNHx6.js";
17
- import { n as getNextRunAtMs } from "./cadence-D00SCzT1.js";
18
- import { f as setSandboxResourceConfig, u as getSandboxResourceConfig } from "./resolve-B6ZZ9duG.js";
19
- import { a as getVercelConversationWorkQueue, 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-BbGg3gvu.js";
20
- import { A as hasRunnableConversationWork$1, M as isInvalidConversationRecordError, O as CONVERSATION_WORK_STALE_ENQUEUE_MS, S as removeActiveConversation, _ as hasRunnableConversationWork, c as clearExpiredConversationLease, g as getConversationWorkState, h as getConversation, m as ensureConversationWake, r as appendAndEnqueueInboundMessage, v as listActiveConversationIds } from "./store-D2It0ozF.js";
21
- import { D as scheduledRunSchema } from "./automation-CHdlHVZP.js";
22
- import { i as setExperimentalFeatures, n as getExperimentalFeatures, r as isExperimentalFeatureEnabled } from "./experimental-Qn9rKLgx.js";
23
- import { n as validatePluginRegistrations, t as validatePluginEgressCredentialHooks } from "./validation-DCd69-cP.js";
24
- import { a as pluginRuntimeRegistrationsFromPluginSet, n as pluginCatalogConfigFromEnv, r as pluginCatalogConfigFromPluginSet, t as defineJuniorPlugins } from "./plugins-egKWjgue.js";
25
- import { A as withNormalizedThreadId, C as buildSlackInboundMessage, D as isSlackMessageStopped, E as verifySlackSignature, F as renderSlackObjectCard, I as createVercelAttachmentStorage, M as THREAD_OPTOUT_ACK, N as startProcessingReaction, O as stopSlackThread, P as parseContent, R as enqueueAgentDispatch, S as recordSkippedConversationMessage, T as runWithSlackInstallation, a as recordWebConversationActivity, c as createProductionSlackWebhookServices, f as createSlackWebApiStatusSender, j as stopConversationTurn, k as normalizeIncomingSlackThreadId, l as getProductionSlackAdapter, n as conversationTurnIdForMessage, o as webActorFromEmail, s as createProductionConversationWorkOptions, t as appendAndEnqueueWebMessage, v as readViewerAutomationSummary, w as clearSlackPendingReactions, z as GET$3 } from "./web-input-Cv42P3ni.js";
26
- import { M as recordAutomationExecution, S as existingConversationId, a as confirmDispatchMailboxAppend, b as notifyAutomationBlocked, f as getPluginDispatchProjection, m as listPendingDispatchMailboxAppends, o as createOrGetDispatch, p as isTerminalDispatchStatus, s as getDispatchConversationId, t as processPluginTask, u as getDispatchRecord, v as markDispatchFailed } from "./task-runner-cIGU5VwJ.js";
27
- import { o as getAuthPausedConversationTurnId, t as enqueueAgentInvocationResult } from "./result-j9nS0se-.js";
28
- import { r as enqueueAgentInvocation } from "./work-6UmVQq1V.js";
29
- import { s as coerceThreadConversationState } from "./thread-state-BPkwVmvK.js";
30
- import { o as unlinkProvider } from "./connections-CG04HdpC.js";
31
- import { a as GET$4, i as publishAppHomeView, r as GET$5, t as receiveLocalOAuthCredential } from "./credential-sync-DKWNdcgm.js";
32
- import { a as registerVercelPluginTaskDevConsumer, n as createVercelPluginTaskCallback, s as queueCallback } from "./task-queue-ZMk9bLRT.js";
33
- import { t as processConversationWork } from "./worker-DAoBZuZn.js";
34
- import { t as bindMessageAgent } from "./message-DdPGRYQP.js";
35
- import { t as bindSpawnAgent } from "./spawn-Do5mX3d_.js";
1
+ import { At as consumeSandboxEgressPermissionDeniedSignal, B as effectiveTaskOutcomes, Ct as ingestEvent, Dt as SANDBOX_EGRESS_PROXY_PATH, E as requestAttachmentDeletion, Et as shouldPropagateSandboxEgressTrace, F as logScheduledAutomationRunSkipped, Ft as resolveBaseUrl, I as scheduledAutomationRunAttributes, It as startOAuthFlow, Kt as getConfigDefaults, Lt as createUserTokenStore, Nt as formatProviderLabel, O as parseSlackThreadId, Ot as clearSandboxEgressSignals, St as processWorkspaceSnapshotJob, Tt as normalizeSandboxEgressTracePropagationDomains, U as stopConversationSession, W as recordSubagentEnded, b as unpublishedArtifactHeaders, i as executeCredentialedEgressRequest, jt as parseSandboxEgressCredentialToken, kt as consumeSandboxEgressAuthRequiredSignal, lt as listPendingAgentInvocationMailboxAppends, n as executeAgentRun, qt as setConfigDefaults, t as createAgentRunner, ut as listPendingAgentInvocationResults, v as publicArtifactHeaders, w as collectAttachmentGarbage, wt as resolveSandboxEgressProviderForHost, x as lookupSlackUserProfile, y as readPublicArtifact } from "./agent-runner-DGsFQw72.js";
2
+ import { C as withContext, M as JUNIOR_WORKSPACE_SNAPSHOT_JOB_CALLBACK_ROUTE, R as normalizeIdentityEmail, T as withSpan, b as setSpanStatus, f as logException, k as JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE, m as logWarn, p as logInfo, r as createRequestContext, v as setSentryUser, w as withLogContext, y as setSpanAttributes } from "./logging-DsHONhVm.js";
3
+ import { n as captureMessage, r as continueTrace } from "./sentry-BBUlHih9.js";
4
+ import { t as JUNIOR_VERSION } from "./version-C0U6rHg0.js";
5
+ 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-_WO_AjCB.js";
6
+ import { _ as slackMessageTsSchema, d as parseSlackTeamId, f as parseSlackUserId, h as slackUserIdSchema, m as slackTeamIdSchema, o as parseActorUserId, p as slackChannelIdSchema } from "./actor-tlxzqaRe.js";
7
+ import { N as recordGoogleSignIn, _ as withConversationMutationLock, c as getConversationEventStore, d as getDb, f as getSqlExecutor, m as withConversationEventLock, r as readActorIdentity, u as getConversationStore } from "./viewer-CGvvRqKp.js";
8
+ import { a as juniorAgentInvocations, d as juniorDestinations, i as juniorAgentBindings, s as juniorConversations } from "./agent-invocations-Qfz7dWgU.js";
9
+ import { a as juniorAutomationRuns, f as juniorConversationBriefs, h as juniorConversationEvents, s as juniorAutomations } from "./executor-RvG6kVe1.js";
10
+ import { m as credentialLogContext } from "./registry-Du566MtJ.js";
11
+ import { n as pluginCatalogRuntime } from "./catalog-runtime-BN4DVxco.js";
12
+ import { C as automationVersionOf, Kt as projectConversationMessages, P as slackMention, U as slackEntitySchema, Ut as appendConversationMessages, W as slackExternalRefIdSchema, Yt as settleConversationTitleWork, b as admitEventAutomationRun, c as withScheduledAutomationLock, i as readScheduledAutomation, l as blockEventAutomation, n as listScheduledAutomationsForTeam, o as saveScheduledAutomationInLock, p as findMatchingEventAutomations, s as scheduledRunId, u as collectEventAutomationMatchKeys, x as eventAutomationRunLimitReason } from "./tasks-CEXKTASS.js";
13
+ import { $ as bindSlackDirectCredentialSubject, At as resolveRootVisibility, Ht as getSlackClient, Jt as getWorkspaceTeamId, Nt as listConversationAnnotations, Q as bindScheduledAutomationCredentialSubject, U as collectEventMatchKeys, Wt as isDmChannel, X as takeDueTimerWatches, Yt as runWithWorkspaceTeamId, Z as bindEventAutomationCredentialSubject, a as getPluginRoutes, at as getDashboardConversationLink, ct as setDashboardConversationLinkOptions, d as setPlugins, et as verifyEventAutomationCredentialSubject, f as validatePlugins, ft as createPluginLogger, h as setBriefsConfig, i as getPluginApiRoutes, lt as createPluginState, nt as verifySlackDirectCredentialSubject, pt as readConversationAccessFromSql, q as findMatchingWatches, st as getDashboardTaskLink, tt as verifyScheduledAutomationCredentialSubject, u as getPlugins } from "./agent-hooks-BJ_5mlRH.js";
14
+ import { W as annotationCard } from "./conversation-DjN3mlog.js";
15
+ import { O as withLock$1, w as getStateAdapter } from "./profile-DpYjfKke.js";
16
+ import { l as getThreadStopDecision, o as SubscribedReplyReason } from "./pending-auth-D8J1uJUS.js";
17
+ import { b as eventGuidance, f as slackChannelMessageEvent, k as renderTaskInput, l as getEventCatalog, m as slackChannelMessageRunsAutomation, p as slackChannelMessageReplyThreadTs, u as SLACK_EVENT_NAMESPACE } from "./session-stop-CIoFiwwe.js";
18
+ import { n as getNextRunAtMs } from "./cadence-R7Em4RjE.js";
19
+ import { f as setSandboxResourceConfig, u as getSandboxResourceConfig } from "./resolve-ClCIgh8E.js";
20
+ import { a as getVercelConversationWorkQueue, 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-DVe37it2.js";
21
+ import { A as hasRunnableConversationWork$1, M as isInvalidConversationRecordError, O as CONVERSATION_WORK_STALE_ENQUEUE_MS, S as removeActiveConversation, _ as hasRunnableConversationWork, c as clearExpiredConversationLease, g as getConversationWorkState, h as getConversation, m as ensureConversationWake, r as appendAndEnqueueInboundMessage, v as listActiveConversationIds } from "./store-DJLwq4x8.js";
22
+ import { D as scheduledRunSchema } from "./automation-DWBFBenG.js";
23
+ import { i as setExperimentalFeatures, n as getExperimentalFeatures, r as isExperimentalFeatureEnabled } from "./experimental-BNK1l1jv.js";
24
+ import { n as validatePluginRegistrations, t as validatePluginEgressCredentialHooks } from "./validation-BHRxa5G4.js";
25
+ import { a as pluginRuntimeRegistrationsFromPluginSet, n as pluginCatalogConfigFromEnv, r as pluginCatalogConfigFromPluginSet, t as defineJuniorPlugins } from "./plugins-DOm9Y6qI.js";
26
+ import { A as THREAD_OPTOUT_ACK, C as buildSlackInboundMessage, D as normalizeIncomingSlackThreadId, E as stopSlackThread, F as renderSlackObjectCard, I as createVercelAttachmentStorage, M as parseContent, N as runWithSlackInstallation, O as withNormalizedThreadId, P as verifySlackSignature, R as enqueueAgentDispatch, S as recordSkippedConversationMessage, T as isSlackMessageStopped, a as recordWebConversationActivity, c as createProductionSlackWebhookServices, f as createSlackWebApiStatusSender, j as startProcessingReaction, k as stopConversationTurn, l as getProductionSlackAdapter, n as conversationTurnIdForMessage, o as webActorFromEmail, s as createProductionConversationWorkOptions, t as appendAndEnqueueWebMessage, v as readViewerAutomationSummary, w as clearSlackPendingReactions, z as GET$3 } from "./web-input-Keb5GRWj.js";
27
+ import { C as existingConversationId, N as recordAutomationExecution, c as getDispatchConversationId, d as getDispatchRecord, h as listPendingDispatchMailboxAppends, i as buildDispatchId, m as isTerminalDispatchStatus, o as confirmDispatchMailboxAppend, p as getPluginDispatchProjection, s as createOrGetDispatch, t as processPluginTask, x as notifyAutomationBlocked, y as markDispatchFailed } from "./task-runner-D3qJh4fK.js";
28
+ import { o as getAuthPausedConversationTurnId, t as enqueueAgentInvocationResult } from "./result-BBbKA61B.js";
29
+ import { r as enqueueAgentInvocation } from "./work-DRcsJ3hV.js";
30
+ import { s as coerceThreadConversationState } from "./thread-state-W3f6kAtz.js";
31
+ import { o as unlinkProvider } from "./connections-ag_1JOhB.js";
32
+ import { a as GET$4, i as publishAppHomeView, r as GET$5, t as receiveLocalOAuthCredential } from "./credential-sync-B-CPX_ag.js";
33
+ import { a as registerVercelPluginTaskDevConsumer, n as createVercelPluginTaskCallback, s as queueCallback } from "./task-queue-BzW4MaWU.js";
34
+ import { t as processConversationWork } from "./worker-Cjo-QqPq.js";
35
+ import { t as bindMessageAgent } from "./message-DyitL8no.js";
36
+ import { t as bindSpawnAgent } from "./spawn-Dqfeo7XP.js";
36
37
  import { createRequire } from "node:module";
37
38
  import { Hono } from "hono";
38
39
  import { z } from "zod";
@@ -44,7 +45,9 @@ import { setTimeout as setTimeout$1 } from "node:timers/promises";
44
45
  import { createRemoteJWKSet, decodeJwt, jwtVerify } from "jose";
45
46
  import { ChannelImpl, ThreadImpl } from "chat";
46
47
  import * as acp from "@agentclientprotocol/sdk";
47
- //#region src/chat/events/timers.ts
48
+ import { createAuthMiddleware } from "better-auth/api";
49
+ import { betterAuth } from "better-auth/minimal";
50
+ // src/chat/events/timers.ts
48
51
  /** Publish a bounded batch of due timers; the mailbox owns agent execution. */
49
52
  async function runTimerWatchHeartbeat(args) {
50
53
  const errors = [];
@@ -63,8 +66,7 @@ async function runTimerWatchHeartbeat(args) {
63
66
  }
64
67
  if (errors.length) throw new AggregateError(errors, "Timer Watch delivery failed");
65
68
  }
66
- //#endregion
67
- //#region src/chat/task-execution/heartbeat.ts
69
+ // src/chat/task-execution/heartbeat.ts
68
70
  const DEFAULT_RECOVERY_LIMIT = 25;
69
71
  function heartbeatIdempotencyKey(reason, conversationId, nowMs) {
70
72
  return `heartbeat:${reason}:${conversationId}:${nowMs}`;
@@ -163,8 +165,7 @@ async function recoverConversationWork(args) {
163
165
  });
164
166
  return result;
165
167
  }
166
- //#endregion
167
- //#region src/chat/agent-dispatch/validation.ts
168
+ // src/chat/agent-dispatch/validation.ts
168
169
  function hasIssueAtPath(issues, path) {
169
170
  return issues.some((issue) => issue.path.length === path.length && issue.path.every((value, index) => value === path[index]));
170
171
  }
@@ -214,8 +215,7 @@ async function verifyDispatchCredentialSubjectAccess(options, plugin) {
214
215
  subject: options.credentialSubject
215
216
  }))) throw new Error("Dispatch credentialSubject is not valid for this action");
216
217
  }
217
- //#endregion
218
- //#region src/chat/agent-dispatch/context.ts
218
+ // src/chat/agent-dispatch/context.ts
219
219
  const MAX_DISPATCHES_PER_HEARTBEAT = 25;
220
220
  function bindDispatchCredentialSubject(options, plugin) {
221
221
  const { credentialSubject, ...baseOptions } = options;
@@ -303,7 +303,11 @@ async function dispatchEventAutomation(args) {
303
303
  ...unboundOptions,
304
304
  ...outcomes !== void 0 ? { outcomes } : void 0,
305
305
  ...boundSubject ? { credentialSubject: boundSubject } : void 0,
306
- source: { kind: "event_automation" }
306
+ source: {
307
+ kind: "event_automation",
308
+ automationId: args.automationId,
309
+ automationRunId: buildDispatchId(plugin, unboundOptions.idempotencyKey)
310
+ }
307
311
  };
308
312
  return await dispatch({
309
313
  conversationWorkQueue: args.conversationWorkQueue,
@@ -326,7 +330,11 @@ async function dispatchScheduledAutomation(args) {
326
330
  ...unboundOptions,
327
331
  ...outcomes !== void 0 ? { outcomes } : void 0,
328
332
  ...boundSubject ? { credentialSubject: boundSubject } : void 0,
329
- source: { kind: "scheduled_automation" }
333
+ source: {
334
+ kind: "scheduled_automation",
335
+ automationId: args.automationId,
336
+ automationRunId: unboundOptions.idempotencyKey
337
+ }
330
338
  };
331
339
  return await dispatch({
332
340
  conversationWorkQueue: args.conversationWorkQueue,
@@ -342,17 +350,18 @@ async function getScheduledAutomationDispatch(id) {
342
350
  id
343
351
  });
344
352
  }
345
- //#endregion
346
- //#region src/chat/event-automations/runs.ts
353
+ // src/chat/event-automations/runs.ts
347
354
  /**
348
- * Record a dispatched event automation run and link its Conversation when it
349
- * exists. The dispatch can finish before this write, so a stored run, or a
350
- * retry of the same Event, keeps the row it has.
355
+ * Record a dispatched event automation run with the Automation version it ran,
356
+ * and link its Conversation when it exists. The dispatch can finish before
357
+ * this write, so a stored run, or a retry of the same Event, keeps the row it
358
+ * has.
351
359
  */
352
360
  async function recordEventAutomationRunStarted(db, args) {
353
361
  await db.insert(juniorAutomationRuns).values({
354
362
  id: args.dispatchId,
355
- automationId: args.automationId,
363
+ automationId: args.automation.id,
364
+ automationVersion: automationVersionOf(args.automation),
356
365
  conversationId: existingConversationId(getDispatchConversationId({ id: args.dispatchId })),
357
366
  startedAtMs: args.nowMs,
358
367
  status: "running"
@@ -380,8 +389,7 @@ async function settleEventAutomationRuns(db, nowMs) {
380
389
  });
381
390
  }
382
391
  }
383
- //#endregion
384
- //#region src/chat/automations/reply-attribution.ts
392
+ // src/chat/automations/reply-attribution.ts
385
393
  /** Name the Automation in the footer of the replies that its runs post. */
386
394
  function automationReplyAttribution(title) {
387
395
  const detail = (title ?? "").replace(/\s+/g, " ").trim().slice(0, 128).trim();
@@ -390,8 +398,7 @@ function automationReplyAttribution(title) {
390
398
  detail
391
399
  } : { label: "Automation" };
392
400
  }
393
- //#endregion
394
- //#region src/chat/scheduled-automations/runs.ts
401
+ // src/chat/scheduled-automations/runs.ts
395
402
  const PENDING_CLAIM_STALE_MS = 6e4;
396
403
  const MISSED_RUN_MAX_AGE_MS = 864e5;
397
404
  const INCOMPLETE_RUN_STATUSES = ["pending", "running"];
@@ -419,9 +426,14 @@ async function readRun(db, runId) {
419
426
  const rows = await db.select().from(juniorAutomationRuns).where(eq(juniorAutomationRuns.id, runId)).limit(1);
420
427
  return rows[0] ? parseScheduledRunRow(rows[0]) : void 0;
421
428
  }
422
- async function writeRun(db, run) {
429
+ /**
430
+ * Save a Scheduled automation run. Pass `automationVersion` only when the run
431
+ * is dispatched. Later writes keep the stored version.
432
+ */
433
+ async function writeRun(db, run, automationVersion) {
423
434
  const stored = scheduledRunSchema.parse(run);
424
435
  const columns = {
436
+ ...automationVersion ? { automationVersion } : void 0,
425
437
  attempt: stored.attempt,
426
438
  automationId: stored.taskId,
427
439
  claimedAtMs: stored.claimedAtMs,
@@ -588,14 +600,14 @@ async function claimDueScheduledRun(db, args) {
588
600
  for (const skipped of skippedRuns) logScheduledAutomationRunSkipped(skipped.task, skipped.run, { "app.task.run.error": skipped.errorMessage });
589
601
  return claimed;
590
602
  }
591
- async function updateRun(db, runId, update) {
603
+ async function updateRun(db, runId, update, automationVersion) {
592
604
  return await db.transaction(async (tx) => {
593
605
  await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext(${`junior:scheduler:run:${runId}:lock`}))`);
594
606
  const current = await readRun(tx, runId);
595
607
  if (!current) return void 0;
596
608
  const next = update(current);
597
609
  if (!next) return void 0;
598
- await writeRun(tx, next);
610
+ await writeRun(tx, next, automationVersion);
599
611
  return next;
600
612
  });
601
613
  }
@@ -603,14 +615,17 @@ function canFinishRun(run, startedAtMs) {
603
615
  if (run.status === "pending") return startedAtMs === void 0;
604
616
  return run.status === "running" && run.startedAtMs === startedAtMs;
605
617
  }
606
- /** Mark a pending run dispatched only for the active claim. */
618
+ /**
619
+ * Mark a pending run dispatched only for the active claim, and store the
620
+ * version of `task` that the dispatch ran.
621
+ */
607
622
  async function markScheduledRunDispatched(db, args) {
608
623
  return await updateRun(db, args.runId, (run) => run.status === "pending" && run.claimedAtMs === args.claimedAtMs ? {
609
624
  ...run,
610
625
  dispatchId: args.dispatchId,
611
626
  startedAtMs: args.nowMs,
612
627
  status: "running"
613
- } : void 0);
628
+ } : void 0, automationVersionOf(args.task));
614
629
  }
615
630
  /** Complete a run only for its active start timestamp. */
616
631
  async function markScheduledRunCompleted(db, args) {
@@ -708,8 +723,7 @@ async function advanceScheduledAutomationAfterRun(db, args) {
708
723
  return args.status === "blocked";
709
724
  });
710
725
  }
711
- //#endregion
712
- //#region src/chat/scheduled-automations/heartbeat.ts
726
+ // src/chat/scheduled-automations/heartbeat.ts
713
727
  const SCHEDULED_AUTOMATION_HEARTBEAT_LIMIT = 10;
714
728
  function singleLineMetadataValue(value) {
715
729
  return value.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
@@ -940,6 +954,7 @@ async function runScheduledAutomationHeartbeat(args) {
940
954
  let dispatch;
941
955
  try {
942
956
  dispatch = await dispatchScheduledAutomation({
957
+ automationId: task.id,
943
958
  conversationWorkQueue: args.conversationWorkQueue,
944
959
  nowMs: args.nowMs,
945
960
  options: {
@@ -972,7 +987,8 @@ async function runScheduledAutomationHeartbeat(args) {
972
987
  claimedAtMs: run.claimedAtMs,
973
988
  dispatchId: dispatch.id,
974
989
  nowMs: args.nowMs,
975
- runId: run.id
990
+ runId: run.id,
991
+ task
976
992
  });
977
993
  logInfo("scheduled_automation.run.dispatched", scheduledAutomationRunAttributes(task, dispatched ?? {
978
994
  ...run,
@@ -982,8 +998,7 @@ async function runScheduledAutomationHeartbeat(args) {
982
998
  }
983
999
  return dispatchCount;
984
1000
  }
985
- //#endregion
986
- //#region src/chat/agent-dispatch/heartbeat.ts
1001
+ // src/chat/agent-dispatch/heartbeat.ts
987
1002
  const DEFAULT_PLUGIN_LIMIT = 25;
988
1003
  const PLUGIN_HEARTBEAT_TIMEOUT_MS = 25e3;
989
1004
  const DISPATCH_MAILBOX_APPEND_LIMIT = 100;
@@ -1117,8 +1132,7 @@ async function runHeartbeat(args) {
1117
1132
  nowMs: args.nowMs
1118
1133
  });
1119
1134
  }
1120
- //#endregion
1121
- //#region src/handlers/heartbeat.ts
1135
+ // src/handlers/heartbeat.ts
1122
1136
  function getHeartbeatSecret() {
1123
1137
  return process.env.JUNIOR_SCHEDULER_SECRET?.trim() || process.env.CRON_SECRET?.trim();
1124
1138
  }
@@ -1143,8 +1157,7 @@ async function GET$2(request, waitUntil, options = {}) {
1143
1157
  }));
1144
1158
  return new Response("Accepted", { status: 202 });
1145
1159
  }
1146
- //#endregion
1147
- //#region src/chat/conversations/sql/purge.ts
1160
+ // src/chat/conversations/sql/purge.ts
1148
1161
  /** Discover a root and its current descendants via `parent_conversation_id`. */
1149
1162
  async function discoverConversationTree(executor, root) {
1150
1163
  const all = /* @__PURE__ */ new Map([[root.conversationId, root]]);
@@ -1303,8 +1316,7 @@ async function purgeConversationTree(executor, args) {
1303
1316
  });
1304
1317
  });
1305
1318
  }
1306
- //#endregion
1307
- //#region src/chat/conversations/retention.ts
1319
+ // src/chat/conversations/retention.ts
1308
1320
  /**
1309
1321
  * Content retention policy and the bounded purge job.
1310
1322
  *
@@ -1380,8 +1392,7 @@ async function runRetentionPurge(executor, args) {
1380
1392
  attachments: attachmentResult.deleted
1381
1393
  };
1382
1394
  }
1383
- //#endregion
1384
- //#region src/handlers/retention.ts
1395
+ // src/handlers/retention.ts
1385
1396
  function getRetentionSecret() {
1386
1397
  return process.env.JUNIOR_SCHEDULER_SECRET?.trim() || process.env.CRON_SECRET?.trim();
1387
1398
  }
@@ -1412,8 +1423,7 @@ async function GET$1(request, options = {}) {
1412
1423
  return new Response("Retention purge failed", { status: 500 });
1413
1424
  }
1414
1425
  }
1415
- //#endregion
1416
- //#region src/chat/sandbox/egress/oidc.ts
1426
+ // src/chat/sandbox/egress/oidc.ts
1417
1427
  const OIDC_DISCOVERY_CACHE_TTL_MS = 36e5;
1418
1428
  const OIDC_DISCOVERY_CACHE_MAX_ENTRIES = 8;
1419
1429
  const jwksByIssuer = /* @__PURE__ */ new Map();
@@ -1465,8 +1475,7 @@ async function verifyVercelSandboxOidcToken(token, options = {}) {
1465
1475
  ...options.currentDate ? { currentDate: options.currentDate } : void 0
1466
1476
  })).payload;
1467
1477
  }
1468
- //#endregion
1469
- //#region src/chat/sandbox/egress/proxy.ts
1478
+ // src/chat/sandbox/egress/proxy.ts
1470
1479
  const OIDC_TOKEN_HEADER = "vercel-sandbox-oidc-token";
1471
1480
  const FORWARDED_HOST_HEADER = "vercel-forwarded-host";
1472
1481
  const FORWARDED_SCHEME_HEADER = "vercel-forwarded-scheme";
@@ -1604,11 +1613,26 @@ function buildUpstreamUrl(request) {
1604
1613
  };
1605
1614
  }
1606
1615
  }
1616
+ /**
1617
+ * Name the Conversation and the accountable person from the verified token.
1618
+ * Each proxy request is a new HTTP request with no run log context, so the
1619
+ * proxy binds the same fields that the run binds.
1620
+ */
1621
+ function sandboxEgressLogContext(context) {
1622
+ return {
1623
+ ...context.conversationId ? { conversationId: context.conversationId } : void 0,
1624
+ ...credentialLogContext(context.credentials)
1625
+ };
1626
+ }
1607
1627
  /** Continue inbound trace context only after sandbox egress verification succeeds. */
1608
- function continueSandboxEgressTrace(request, upstreamHost, tracePropagation, callback) {
1628
+ function continueSandboxEgressTrace(request, upstreamHost, tracePropagation, credentialContext, callback) {
1609
1629
  const sentryTrace = request.headers.get("sentry-trace") ?? void 0;
1610
1630
  const baggage = request.headers.get("baggage") ?? void 0;
1611
- const run = () => withSpan("sandbox.egress", "http.server", {}, callback, {
1631
+ const logContext = sandboxEgressLogContext(credentialContext);
1632
+ const run = () => withSpan("sandbox.egress", "http.server", logContext, async () => {
1633
+ setSentryUser(logContext.userId ? { id: logContext.userId } : void 0);
1634
+ return await callback();
1635
+ }, {
1612
1636
  "http.request.method": request.method,
1613
1637
  "url.path": redactedProxyPath(new URL(request.url).pathname)
1614
1638
  });
@@ -1697,7 +1721,7 @@ async function proxySandboxEgressRequest(request, deps = {}) {
1697
1721
  });
1698
1722
  return jsonError("Sandbox egress credential context is not authorized", 403);
1699
1723
  }
1700
- return await continueSandboxEgressTrace(request, upstreamUrl.hostname, deps.tracePropagation ?? {}, async () => await executeCredentialedEgressRequest({
1724
+ return await continueSandboxEgressTrace(request, upstreamUrl.hostname, deps.tracePropagation ?? {}, credentialContext, async () => await executeCredentialedEgressRequest({
1701
1725
  activeEgressId,
1702
1726
  credentialContext,
1703
1727
  deps,
@@ -1706,8 +1730,7 @@ async function proxySandboxEgressRequest(request, deps = {}) {
1706
1730
  upstreamUrl
1707
1731
  }));
1708
1732
  }
1709
- //#endregion
1710
- //#region src/handlers/sandbox-egress-proxy.ts
1733
+ // src/handlers/sandbox-egress-proxy.ts
1711
1734
  /** Handles Vercel Sandbox firewall egress proxy requests. */
1712
1735
  async function ALL(request, options = {}) {
1713
1736
  return await proxySandboxEgressRequest(request, {
@@ -1720,8 +1743,7 @@ async function ALL(request, options = {}) {
1720
1743
  function isSandboxEgressRequest(request) {
1721
1744
  return isSandboxEgressForwardedRequest(request);
1722
1745
  }
1723
- //#endregion
1724
- //#region src/handlers/sandbox-egress-route.ts
1746
+ // src/handlers/sandbox-egress-route.ts
1725
1747
  /**
1726
1748
  * Route authenticated sandbox egress proxy traffic before ordinary app routes.
1727
1749
  */
@@ -1729,8 +1751,7 @@ async function handleSandboxEgressRoute(request, tracePropagationDomains, next)
1729
1751
  if (isSandboxEgressRequest(request)) return await ALL(request, { tracePropagation: { domains: tracePropagationDomains } });
1730
1752
  await next();
1731
1753
  }
1732
- //#endregion
1733
- //#region src/handlers/sandbox-egress-signals.ts
1754
+ // src/handlers/sandbox-egress-signals.ts
1734
1755
  const SANDBOX_EGRESS_TOKEN_HEADER = "x-junior-sandbox-egress-token";
1735
1756
  function isLoopbackDevelopmentRequest(request) {
1736
1757
  const hostname = new URL(request.url).hostname;
@@ -1755,8 +1776,7 @@ async function GET(request) {
1755
1776
  ...permissionDenied ? { permission_denied: permissionDenied } : void 0
1756
1777
  });
1757
1778
  }
1758
- //#endregion
1759
- //#region src/chat/slack/annotation-details.ts
1779
+ // src/chat/slack/annotation-details.ts
1760
1780
  const eventSchema$1 = z.object({
1761
1781
  trigger_id: z.string().min(1),
1762
1782
  user: z.string().min(1),
@@ -1834,8 +1854,7 @@ async function presentSlackAnnotationDetails(event, teamId) {
1834
1854
  metadata: slackEntitySchema.parse(entity)
1835
1855
  });
1836
1856
  }
1837
- //#endregion
1838
- //#region src/chat/ingress/slack-payload.ts
1857
+ // src/chat/ingress/slack-payload.ts
1839
1858
  const eventTimestampSchema = slackMessageTsSchema.refine((value) => Number.isFinite((/* @__PURE__ */ new Date(Number(value) * 1e3)).getTime()));
1840
1859
  const slackSlashCommandSchema = z.object({
1841
1860
  channel_id: z.string().trim().pipe(slackChannelIdSchema),
@@ -1932,8 +1951,173 @@ const slackInteractivePayloadSchema = z.object({
1932
1951
  team_id: z.string().optional()
1933
1952
  }).optional()
1934
1953
  });
1935
- //#endregion
1936
- //#region src/chat/ingress/bot-mention.ts
1954
+ // src/chat/ingress/slash-command.ts
1955
+ async function postEphemeral(event, text) {
1956
+ await event.channel.postEphemeral(event.userId, text, { fallbackToDM: false });
1957
+ }
1958
+ function getCommandName() {
1959
+ return getChatConfig().slack.slashCommand;
1960
+ }
1961
+ async function handleLink(event, provider) {
1962
+ if (!pluginCatalogRuntime.isProvider(provider)) {
1963
+ await postEphemeral(event, `Unknown provider: \`${provider}\``);
1964
+ return;
1965
+ }
1966
+ if (!pluginCatalogRuntime.getOAuthConfig(provider)) {
1967
+ await postEphemeral(event, `${formatProviderLabel(provider)} doesn't support account linking.`);
1968
+ return;
1969
+ }
1970
+ const result = await startOAuthFlow(provider, {
1971
+ actorId: event.userId,
1972
+ actor: {
1973
+ platform: "slack",
1974
+ teamId: event.teamId,
1975
+ userId: event.userId
1976
+ },
1977
+ channelId: event.channelId
1978
+ });
1979
+ if (!result.ok) {
1980
+ await postEphemeral(event, `Failed to start linking: ${result.error}`);
1981
+ return;
1982
+ }
1983
+ if (result.delivery === "fallback_dm") await postEphemeral(event, `Check your DMs for a ${formatProviderLabel(provider)} authorization link.`);
1984
+ else if (result.delivery === false) await postEphemeral(event, "I wasn't able to send you a private authorization link. Please try again in a direct message.");
1985
+ }
1986
+ async function handleUnlink(event, provider) {
1987
+ if (!pluginCatalogRuntime.isProvider(provider)) {
1988
+ await postEphemeral(event, `Unknown provider: \`${provider}\``);
1989
+ return;
1990
+ }
1991
+ if (!(Boolean(pluginCatalogRuntime.getOAuthConfig(provider)) || Boolean(pluginCatalogRuntime.getDefinition(provider)?.manifest.mcp))) {
1992
+ await postEphemeral(event, `${formatProviderLabel(provider)} doesn't support account unlinking.`);
1993
+ return;
1994
+ }
1995
+ const tokenStore = createUserTokenStore();
1996
+ await unlinkProvider(event.userId, provider, tokenStore, event.teamId);
1997
+ logInfo("slash_command.credential.unlinked", { "app.credential.provider": provider });
1998
+ await postEphemeral(event, `Your ${formatProviderLabel(provider)} account has been unlinked.`);
1999
+ }
2000
+ /** Route link and unlink slash commands to the appropriate OAuth flow. */
2001
+ async function handleSlashCommand(event) {
2002
+ const [subcommand, provider, ...rest] = event.text.trim().split(/\s+/);
2003
+ if (!subcommand || !["link", "unlink"].includes(subcommand)) {
2004
+ await postEphemeral(event, `Usage: \`${getCommandName()} link <provider>\` or \`${getCommandName()} unlink <provider>\``);
2005
+ return;
2006
+ }
2007
+ if (!provider || rest.length > 0) {
2008
+ await postEphemeral(event, `Usage: \`${getCommandName()} ${subcommand} <provider>\``);
2009
+ return;
2010
+ }
2011
+ const normalized = provider.toLowerCase();
2012
+ if (subcommand === "link") await handleLink(event, normalized);
2013
+ else await handleUnlink(event, normalized);
2014
+ }
2015
+ // src/chat/ingress/slack-form.ts
2016
+ function parseJson$1(body) {
2017
+ try {
2018
+ return JSON.parse(body);
2019
+ } catch {
2020
+ return;
2021
+ }
2022
+ }
2023
+ function requireSlackPayloadUserId(value, source) {
2024
+ const userId = parseActorUserId(value);
2025
+ if (!userId) throw new Error(`${source} is missing a Slack user id`);
2026
+ return userId;
2027
+ }
2028
+ async function handleSlashCommandForm(args) {
2029
+ const { channel_id: channelId, team_id: teamId, user_id: userId } = args.form;
2030
+ const channel = new ChannelImpl({
2031
+ id: `slack:${channelId}`,
2032
+ adapter: args.adapter,
2033
+ stateAdapter: args.state
2034
+ });
2035
+ await withSpan("chat.slash_command", "chat.slash_command", { userId }, async () => {
2036
+ await handleSlashCommand({
2037
+ channel,
2038
+ channelId,
2039
+ teamId,
2040
+ text: args.form.text,
2041
+ userId
2042
+ });
2043
+ });
2044
+ }
2045
+ async function handleInteractivePayload(args) {
2046
+ if (args.payload.type !== "block_actions") return;
2047
+ const action = args.payload.actions?.find((candidate) => candidate.action_id === "app_home_disconnect");
2048
+ const provider = action?.selected_option?.value ?? action?.value;
2049
+ if (!provider) return;
2050
+ const userId = requireSlackPayloadUserId(args.payload.user?.id, "Slack app home disconnect payload");
2051
+ await withSpan("chat.app_home_disconnect", "chat.app_home_disconnect", { userId }, async () => {
2052
+ try {
2053
+ await unlinkProvider(userId, provider, args.userTokenStore, getWorkspaceTeamId());
2054
+ } catch (error) {
2055
+ logException(error, "app_home.disconnect_unlink.failed", { "app.credential.provider": provider });
2056
+ }
2057
+ try {
2058
+ await publishAppHomeView(getSlackClient(), userId, args.userTokenStore);
2059
+ } catch (error) {
2060
+ logException(error, "app_home.disconnect_publish.failed", { "app.credential.provider": provider });
2061
+ }
2062
+ });
2063
+ }
2064
+ function installationFromInteractive(payload) {
2065
+ return { teamId: payload.team?.id ?? payload.user?.team_id };
2066
+ }
2067
+ /** Handle a Slack slash command or interactive payload form. */
2068
+ async function handleSlackForm(args) {
2069
+ const params = new URLSearchParams(args.body);
2070
+ const { adapter, state } = args;
2071
+ await state.connect();
2072
+ if (params.has("command") && !params.has("payload")) {
2073
+ const result = slackSlashCommandSchema.safeParse(Object.fromEntries(params));
2074
+ if (!result.success) return new Response("Invalid slash command payload", { status: 400 });
2075
+ const form = result.data;
2076
+ const installation = {
2077
+ teamId: form.team_id,
2078
+ enterpriseId: form.enterprise_id,
2079
+ isEnterpriseInstall: form.is_enterprise_install === "true"
2080
+ };
2081
+ args.waitUntil(withLogContext({
2082
+ platform: "slack",
2083
+ userId: form.user_id
2084
+ }, () => runWithWorkspaceTeamId(installation.teamId, () => runWithSlackInstallation({
2085
+ adapter,
2086
+ installation,
2087
+ state,
2088
+ task: () => handleSlashCommandForm({
2089
+ adapter,
2090
+ form,
2091
+ state
2092
+ })
2093
+ })).catch((error) => {
2094
+ logException(error, "slash_command.failed");
2095
+ })));
2096
+ return new Response("", { status: 200 });
2097
+ }
2098
+ const rawPayload = params.get("payload");
2099
+ if (!rawPayload) return new Response("Missing payload", { status: 400 });
2100
+ const result = slackInteractivePayloadSchema.safeParse(parseJson$1(rawPayload));
2101
+ if (!result.success) return new Response("Invalid payload JSON", { status: 400 });
2102
+ const payload = result.data;
2103
+ const installation = installationFromInteractive(payload);
2104
+ args.waitUntil(withLogContext({
2105
+ platform: "slack",
2106
+ userId: payload.user?.id?.trim() || void 0
2107
+ }, () => runWithWorkspaceTeamId(installation.teamId, () => runWithSlackInstallation({
2108
+ adapter,
2109
+ installation,
2110
+ state,
2111
+ task: () => handleInteractivePayload({
2112
+ payload,
2113
+ userTokenStore: args.getUserTokenStore()
2114
+ })
2115
+ })).catch((error) => {
2116
+ logException(error, "slack.interactive.payload.failed");
2117
+ })));
2118
+ return new Response("", { status: 200 });
2119
+ }
2120
+ // src/chat/ingress/bot-mention.ts
1937
2121
  /**
1938
2122
  * Detect Slack bot @mentions that should activate Junior.
1939
2123
  *
@@ -2010,8 +2194,7 @@ function textMentionsBot(text, botUserId) {
2010
2194
  const id = escapeRegExp(botUserId);
2011
2195
  return new RegExp(`<@${id}(?:\\|[^>]+)?>`).test(outside);
2012
2196
  }
2013
- //#endregion
2014
- //#region src/chat/event-automations/ingest.ts
2197
+ // src/chat/event-automations/ingest.ts
2015
2198
  /**
2016
2199
  * Owns event matching and durable event-automation dispatch.
2017
2200
  *
@@ -2043,7 +2226,7 @@ async function ingestEventAutomations(input, options) {
2043
2226
  const event = eventSchema.parse(input);
2044
2227
  const db = getDb();
2045
2228
  const nowMs = options.nowMs ?? Date.now();
2046
- const tasks = await findMatchingEventAutomations(db, event, options.teamId);
2229
+ const tasks = (await findMatchingEventAutomations(db, event, options.teamId)).filter((task) => slackChannelMessageRunsAutomation(event, task));
2047
2230
  let dispatched = 0;
2048
2231
  const errors = [];
2049
2232
  for (const task of tasks) try {
@@ -2068,7 +2251,8 @@ async function ingestEventAutomations(input, options) {
2068
2251
  },
2069
2252
  extra: {
2070
2253
  "app.event.event_type": event.eventType,
2071
- "app.event_automation.max_runs_per_hour": botConfig.maxEventAutomationRunsPerHour,
2254
+ "app.event_automation.max_runs": botConfig.maxEventAutomationRuns,
2255
+ "app.event_automation.run_window_ms": botConfig.eventAutomationRunWindowMs,
2072
2256
  "app.event_automation.resource_type": task.trigger.resourceType,
2073
2257
  "app.task.id": task.id
2074
2258
  }
@@ -2094,6 +2278,7 @@ async function ingestEventAutomations(input, options) {
2094
2278
  taskId: task.id
2095
2279
  } : void 0;
2096
2280
  const dispatch = await dispatchEventAutomation({
2281
+ automationId: task.id,
2097
2282
  conversationWorkQueue: options.queue,
2098
2283
  nowMs,
2099
2284
  options: {
@@ -2108,7 +2293,7 @@ async function ingestEventAutomations(input, options) {
2108
2293
  }
2109
2294
  });
2110
2295
  await recordEventAutomationRunStarted(db, {
2111
- automationId: task.id,
2296
+ automation: task,
2112
2297
  dispatchId: dispatch.id,
2113
2298
  nowMs
2114
2299
  });
@@ -2119,8 +2304,7 @@ async function ingestEventAutomations(input, options) {
2119
2304
  if (errors.length > 0) throw new AggregateError(errors, "Failed to dispatch one or more event automations");
2120
2305
  return { dispatched };
2121
2306
  }
2122
- //#endregion
2123
- //#region src/chat/ingress/workspace-membership.ts
2307
+ // src/chat/ingress/workspace-membership.ts
2124
2308
  const MEMBER_TTL_MS = 3e5;
2125
2309
  const NON_MEMBER_TTL_MS = 3e4;
2126
2310
  const authorTeamSchema = z.object({
@@ -2166,8 +2350,73 @@ async function isSlackWorkspaceMember(raw, state) {
2166
2350
  if (!result.acquired) throw new Error("Could not acquire Slack membership lookup lock");
2167
2351
  return result.value;
2168
2352
  }
2169
- //#endregion
2170
- //#region src/chat/ingress/slack-message-telemetry.ts
2353
+ // src/chat/sign-in/allowlist.ts
2354
+ /**
2355
+ * Domains that may use Junior. Slack authors must have a Slack profile email
2356
+ * on one of them, and dashboard Google sign-ins must come from a Google
2357
+ * Workspace account whose `hd` claim is one of them. Slack and the dashboard
2358
+ * share one list.
2359
+ */
2360
+ /** Read a list env var: a JSON string array or comma-separated values. */
2361
+ function readEnvList(name) {
2362
+ const value = process.env[name]?.trim();
2363
+ if (!value) return void 0;
2364
+ if (!value.startsWith("[")) return value.split(",");
2365
+ let parsed;
2366
+ try {
2367
+ parsed = JSON.parse(value);
2368
+ } catch (error) {
2369
+ throw new Error(`${name} must be a JSON string array`, { cause: error });
2370
+ }
2371
+ const list = z.array(z.string()).safeParse(parsed);
2372
+ if (!list.success) throw new Error(`${name} must be a JSON string array`);
2373
+ return list.data;
2374
+ }
2375
+ /**
2376
+ * Resolve the allowed domains: the `createApp` option when set, else
2377
+ * `JUNIOR_ALLOWED_DOMAINS`. An empty list allows every domain.
2378
+ */
2379
+ function resolveAllowedDomains(allowedDomains) {
2380
+ return [...new Set((allowedDomains ?? readEnvList("JUNIOR_ALLOWED_DOMAINS") ?? []).map((value) => value.trim().toLowerCase()).filter(Boolean))];
2381
+ }
2382
+ /**
2383
+ * Fail startup on the removed Google-specific names. Ignoring them would
2384
+ * leave the list empty, which lets every Slack workspace member in.
2385
+ */
2386
+ function assertNoLegacyAllowedDomains(dashboardOptionKeys) {
2387
+ if (dashboardOptionKeys.includes("allowedGoogleDomains")) throw new Error("dashboard.allowedGoogleDomains was removed; set createApp({ allowedDomains }) instead");
2388
+ if (process.env.JUNIOR_DASHBOARD_GOOGLE_DOMAINS?.trim()) throw new Error("JUNIOR_DASHBOARD_GOOGLE_DOMAINS was removed; set JUNIOR_ALLOWED_DOMAINS instead");
2389
+ }
2390
+ /** Return whether a verified domain may use Junior. An empty list allows all. */
2391
+ function isAllowedDomain(domain, allowedDomains) {
2392
+ if (allowedDomains.length === 0) return true;
2393
+ return Boolean(domain && allowedDomains.includes(domain.toLowerCase()));
2394
+ }
2395
+ // src/chat/ingress/slack-allowed-domain.ts
2396
+ const ALLOWED_TTL_MS = 3e5;
2397
+ const NOT_ALLOWED_TTL_MS = 3e4;
2398
+ /**
2399
+ * Return whether a Slack author may start a Turn. With allowed domains, the
2400
+ * email of the author's Slack profile must be on one of them. Slack verifies
2401
+ * that email, so Junior trusts it. Without allowed domains, every author may.
2402
+ */
2403
+ async function isSlackAuthorDomainAllowed(args) {
2404
+ if (args.allowedDomains.length === 0) return true;
2405
+ const userId = parseSlackUserId(args.user);
2406
+ if (!args.teamId || !userId) return false;
2407
+ const key = `slack:allowed-domain:${args.teamId}:${userId}`;
2408
+ const cached = await args.state.get(key);
2409
+ if (typeof cached === "boolean") return cached;
2410
+ const profile = await lookupSlackUserProfile(userId);
2411
+ const email = profile.email?.toLowerCase();
2412
+ const separator = email?.lastIndexOf("@") ?? -1;
2413
+ const domain = separator > 0 ? email?.slice(separator + 1) : void 0;
2414
+ const allowed = profile.id === userId && isAllowedDomain(domain, args.allowedDomains);
2415
+ if (!allowed) logWarn("slack.allowed_domain.rejected", { "app.reason": email ? "domain_not_allowed" : "missing_email" });
2416
+ await args.state.set(key, allowed, allowed ? ALLOWED_TTL_MS : NOT_ALLOWED_TTL_MS);
2417
+ return allowed;
2418
+ }
2419
+ // src/chat/ingress/slack-message-telemetry.ts
2171
2420
  function field(value, pattern) {
2172
2421
  if (value === void 0) return "missing";
2173
2422
  return typeof value === "string" && value.length <= 64 && pattern.test(value) ? value : "invalid";
@@ -2191,70 +2440,7 @@ function slackMessageAttributes(body) {
2191
2440
  "app.slack.is_ext_shared_channel": typeof body.is_ext_shared_channel === "boolean" ? body.is_ext_shared_channel : body.is_ext_shared_channel === void 0 ? "missing" : "invalid"
2192
2441
  };
2193
2442
  }
2194
- //#endregion
2195
- //#region src/chat/ingress/slash-command.ts
2196
- async function postEphemeral(event, text) {
2197
- await event.channel.postEphemeral(event.userId, text, { fallbackToDM: false });
2198
- }
2199
- function getCommandName() {
2200
- return getChatConfig().slack.slashCommand;
2201
- }
2202
- async function handleLink(event, provider) {
2203
- if (!pluginCatalogRuntime.isProvider(provider)) {
2204
- await postEphemeral(event, `Unknown provider: \`${provider}\``);
2205
- return;
2206
- }
2207
- if (!pluginCatalogRuntime.getOAuthConfig(provider)) {
2208
- await postEphemeral(event, `${formatProviderLabel(provider)} doesn't support account linking.`);
2209
- return;
2210
- }
2211
- const result = await startOAuthFlow(provider, {
2212
- actorId: event.userId,
2213
- actor: {
2214
- platform: "slack",
2215
- teamId: event.teamId,
2216
- userId: event.userId
2217
- },
2218
- channelId: event.channelId
2219
- });
2220
- if (!result.ok) {
2221
- await postEphemeral(event, `Failed to start linking: ${result.error}`);
2222
- return;
2223
- }
2224
- if (result.delivery === "fallback_dm") await postEphemeral(event, `Check your DMs for a ${formatProviderLabel(provider)} authorization link.`);
2225
- else if (result.delivery === false) await postEphemeral(event, "I wasn't able to send you a private authorization link. Please try again in a direct message.");
2226
- }
2227
- async function handleUnlink(event, provider) {
2228
- if (!pluginCatalogRuntime.isProvider(provider)) {
2229
- await postEphemeral(event, `Unknown provider: \`${provider}\``);
2230
- return;
2231
- }
2232
- if (!(Boolean(pluginCatalogRuntime.getOAuthConfig(provider)) || Boolean(pluginCatalogRuntime.getDefinition(provider)?.manifest.mcp))) {
2233
- await postEphemeral(event, `${formatProviderLabel(provider)} doesn't support account unlinking.`);
2234
- return;
2235
- }
2236
- const tokenStore = createUserTokenStore();
2237
- await unlinkProvider(event.userId, provider, tokenStore, event.teamId);
2238
- logInfo("slash_command.credential.unlinked", { "app.credential.provider": provider });
2239
- await postEphemeral(event, `Your ${formatProviderLabel(provider)} account has been unlinked.`);
2240
- }
2241
- /** Route link and unlink slash commands to the appropriate OAuth flow. */
2242
- async function handleSlashCommand(event) {
2243
- const [subcommand, provider, ...rest] = event.text.trim().split(/\s+/);
2244
- if (!subcommand || !["link", "unlink"].includes(subcommand)) {
2245
- await postEphemeral(event, `Usage: \`${getCommandName()} link <provider>\` or \`${getCommandName()} unlink <provider>\``);
2246
- return;
2247
- }
2248
- if (!provider || rest.length > 0) {
2249
- await postEphemeral(event, `Usage: \`${getCommandName()} ${subcommand} <provider>\``);
2250
- return;
2251
- }
2252
- const normalized = provider.toLowerCase();
2253
- if (subcommand === "link") await handleLink(event, normalized);
2254
- else await handleUnlink(event, normalized);
2255
- }
2256
- //#endregion
2257
- //#region src/chat/slack/automation-details.ts
2443
+ // src/chat/slack/automation-details.ts
2258
2444
  function textField(key, label, value) {
2259
2445
  return {
2260
2446
  key,
@@ -2355,8 +2541,7 @@ async function presentSlackAutomationDetails(event, teamId) {
2355
2541
  metadata: slackEntitySchema.parse(entity)
2356
2542
  });
2357
2543
  }
2358
- //#endregion
2359
- //#region src/chat/ingress/slack-webhook.ts
2544
+ // src/chat/ingress/slack-webhook.ts
2360
2545
  function slackEventLogContext(event) {
2361
2546
  const channelId = event?.channel?.trim() || void 0;
2362
2547
  const threadTs = event?.thread_ts?.trim() || event?.ts?.trim() || void 0;
@@ -2550,7 +2735,7 @@ async function handleSlackThreadStop(args) {
2550
2735
  recordSkippedConversationMessage({
2551
2736
  conversation,
2552
2737
  message: args.message,
2553
- skippedReason: `thread_opt_out:${args.stopReason}`,
2738
+ skippedReason: `${SubscribedReplyReason.ThreadOptOut}:${args.stopReason}`,
2554
2739
  text: content.text
2555
2740
  });
2556
2741
  await appendConversationMessages(getConversationEventStore(), {
@@ -2651,13 +2836,19 @@ async function routeParsedMessage(args) {
2651
2836
  });
2652
2837
  return;
2653
2838
  }
2839
+ if (route === "mention" && !await isSlackAuthorDomainAllowed({
2840
+ allowedDomains: args.allowedDomains,
2841
+ state: args.state,
2842
+ teamId: args.installation.teamId,
2843
+ user: args.event.user
2844
+ })) return;
2654
2845
  if (isSubscribed && !isExperimentalFeatureEnabled("passive-routing")) {
2655
2846
  const content = parseContent(message);
2656
2847
  const conversation = coerceThreadConversationState(void 0);
2657
2848
  recordSkippedConversationMessage({
2658
2849
  conversation,
2659
2850
  message,
2660
- skippedReason: `passive_disabled:passive-routing`,
2851
+ skippedReason: `${SubscribedReplyReason.PassiveDisabled}:passive-routing`,
2661
2852
  text: content.text
2662
2853
  });
2663
2854
  await appendConversationMessages(getConversationEventStore(), {
@@ -2767,6 +2958,7 @@ async function handleSlackEvent(args) {
2767
2958
  conversationStore: args.services.conversationStore,
2768
2959
  queue: args.services.queue,
2769
2960
  receivedAtMs,
2961
+ allowedDomains: args.services.allowedDomains ?? [],
2770
2962
  state
2771
2963
  }), {
2772
2964
  keepAlive: true,
@@ -2776,103 +2968,6 @@ async function handleSlackEvent(args) {
2776
2968
  }
2777
2969
  }));
2778
2970
  }
2779
- function requireSlackPayloadUserId(value, source) {
2780
- const userId = parseActorUserId(value);
2781
- if (!userId) throw new Error(`${source} is missing a Slack user id`);
2782
- return userId;
2783
- }
2784
- async function handleSlashCommandForm(args) {
2785
- const { channel_id: channelId, team_id: teamId, user_id: userId } = args.form;
2786
- const channel = new ChannelImpl({
2787
- id: `slack:${channelId}`,
2788
- adapter: args.adapter,
2789
- stateAdapter: args.state
2790
- });
2791
- await withSpan("chat.slash_command", "chat.slash_command", { userId }, async () => {
2792
- await handleSlashCommand({
2793
- channel,
2794
- channelId,
2795
- teamId,
2796
- text: args.form.text,
2797
- userId
2798
- });
2799
- });
2800
- }
2801
- async function handleInteractivePayload(args) {
2802
- if (args.payload.type !== "block_actions") return;
2803
- const action = args.payload.actions?.find((candidate) => candidate.action_id === "app_home_disconnect");
2804
- const provider = action?.selected_option?.value ?? action?.value;
2805
- if (!provider) return;
2806
- const userId = requireSlackPayloadUserId(args.payload.user?.id, "Slack app home disconnect payload");
2807
- await withSpan("chat.app_home_disconnect", "chat.app_home_disconnect", { userId }, async () => {
2808
- try {
2809
- await unlinkProvider(userId, provider, args.userTokenStore, getWorkspaceTeamId());
2810
- } catch (error) {
2811
- logException(error, "app_home.disconnect_unlink.failed", { "app.credential.provider": provider });
2812
- }
2813
- try {
2814
- await publishAppHomeView(getSlackClient(), userId, args.userTokenStore);
2815
- } catch (error) {
2816
- logException(error, "app_home.disconnect_publish.failed", { "app.credential.provider": provider });
2817
- }
2818
- });
2819
- }
2820
- function installationFromInteractive(payload) {
2821
- return { teamId: payload.team?.id ?? payload.user?.team_id };
2822
- }
2823
- async function handleSlackForm(args) {
2824
- const params = new URLSearchParams(args.body);
2825
- const adapter = args.services.getSlackAdapter();
2826
- const state = args.services.state ?? getStateAdapter();
2827
- await state.connect();
2828
- if (params.has("command") && !params.has("payload")) {
2829
- const result = slackSlashCommandSchema.safeParse(Object.fromEntries(params));
2830
- if (!result.success) return new Response("Invalid slash command payload", { status: 400 });
2831
- const form = result.data;
2832
- const installation = {
2833
- teamId: form.team_id,
2834
- enterpriseId: form.enterprise_id,
2835
- isEnterpriseInstall: form.is_enterprise_install === "true"
2836
- };
2837
- enqueue(args.waitUntil, withLogContext({
2838
- platform: "slack",
2839
- userId: form.user_id
2840
- }, () => runWithWorkspaceTeamId(installation.teamId, () => runWithSlackInstallation({
2841
- adapter,
2842
- installation,
2843
- state,
2844
- task: () => handleSlashCommandForm({
2845
- adapter,
2846
- form,
2847
- state
2848
- })
2849
- })).catch((error) => {
2850
- logException(error, "slash_command.failed");
2851
- })));
2852
- return new Response("", { status: 200 });
2853
- }
2854
- const rawPayload = params.get("payload");
2855
- if (!rawPayload) return new Response("Missing payload", { status: 400 });
2856
- const result = slackInteractivePayloadSchema.safeParse(parseJson(rawPayload));
2857
- if (!result.success) return new Response("Invalid payload JSON", { status: 400 });
2858
- const payload = result.data;
2859
- const installation = installationFromInteractive(payload);
2860
- enqueue(args.waitUntil, withLogContext({
2861
- platform: "slack",
2862
- userId: payload.user?.id?.trim() || void 0
2863
- }, () => runWithWorkspaceTeamId(installation.teamId, () => runWithSlackInstallation({
2864
- adapter,
2865
- installation,
2866
- state,
2867
- task: () => handleInteractivePayload({
2868
- payload,
2869
- userTokenStore: getUserTokenStore(args.services)
2870
- })
2871
- })).catch((error) => {
2872
- logException(error, "slack.interactive.payload.failed");
2873
- })));
2874
- return new Response("", { status: 200 });
2875
- }
2876
2971
  /** Handle Slack webhooks by enqueueing durable conversation work. */
2877
2972
  async function handleSlackWebhook(args) {
2878
2973
  const adapter = args.services.getSlackAdapter();
@@ -2883,8 +2978,10 @@ async function handleSlackWebhook(args) {
2883
2978
  request: args.request
2884
2979
  })) return new Response("Invalid signature", { status: 401 });
2885
2980
  if ((args.request.headers.get("content-type") || "").includes("application/x-www-form-urlencoded")) return await handleSlackForm({
2981
+ adapter,
2886
2982
  body,
2887
- services: args.services,
2983
+ getUserTokenStore: () => getUserTokenStore(args.services),
2984
+ state: args.services.state ?? getStateAdapter(),
2888
2985
  waitUntil: args.waitUntil
2889
2986
  });
2890
2987
  const result = slackEventEnvelopeSchema.safeParse(parseJson(body));
@@ -2919,8 +3016,7 @@ async function handleSlackWebhook(args) {
2919
3016
  }
2920
3017
  return new Response("ok", { status: 200 });
2921
3018
  }
2922
- //#endregion
2923
- //#region src/handlers/webhooks.ts
3019
+ // src/handlers/webhooks.ts
2924
3020
  /** Run a platform webhook with shared request tracing and error logging. */
2925
3021
  async function handleWebhookRequest(request, platform, run) {
2926
3022
  const requestContext = createRequestContext(request, { platform });
@@ -2961,8 +3057,7 @@ async function handleWebhookRequest(request, platform, run) {
2961
3057
  }
2962
3058
  });
2963
3059
  }
2964
- //#endregion
2965
- //#region src/handlers/slack-webhook.ts
3060
+ // src/handlers/slack-webhook.ts
2966
3061
  /** Handle the production Slack webhook route. */
2967
3062
  async function POST(request, waitUntil, services) {
2968
3063
  return handleWebhookRequest(request, "slack", () => handleSlackWebhook({
@@ -3060,8 +3155,7 @@ function createVercelConversationWorkCallback(options) {
3060
3155
  function registerVercelConversationWorkDevConsumer(options) {
3061
3156
  return conversationWorkCallback(options).registerDev();
3062
3157
  }
3063
- //#endregion
3064
- //#region src/chat/sandbox/snapshot/job-callback.ts
3158
+ // src/chat/sandbox/snapshot/job-callback.ts
3065
3159
  const WORKSPACE_SNAPSHOT_JOB_DEV_CONSUMER_GROUP = "junior_workspace_snapshots_dev";
3066
3160
  function logWorkspaceSnapshotJobRejected(reason, metadata) {
3067
3161
  logWarn("workspace.snapshot.job.queue_message.rejected", {
@@ -3090,8 +3184,7 @@ function createVercelWorkspaceSnapshotJobCallback() {
3090
3184
  function registerVercelWorkspaceSnapshotJobDevConsumer() {
3091
3185
  return workspaceSnapshotJobCallback().registerDev();
3092
3186
  }
3093
- //#endregion
3094
- //#region src/handlers/artifacts.ts
3187
+ // src/handlers/artifacts.ts
3095
3188
  /** Serve one content-addressed public artifact without authentication. */
3096
3189
  async function publicArtifactGET(args) {
3097
3190
  try {
@@ -3110,8 +3203,7 @@ async function publicArtifactGET(args) {
3110
3203
  return new Response("Internal Server Error", { status: 500 });
3111
3204
  }
3112
3205
  }
3113
- //#endregion
3114
- //#region src/chat/events/workspace.ts
3206
+ // src/chat/events/workspace.ts
3115
3207
  /**
3116
3208
  * Resolve the single-bot Slack team id for event-automation indexing and
3117
3209
  * Event people mentions.
@@ -3135,8 +3227,7 @@ function createEventTeamIdResolver() {
3135
3227
  return await pending;
3136
3228
  };
3137
3229
  }
3138
- //#endregion
3139
- //#region src/chat/events/app-publisher.ts
3230
+ // src/chat/events/app-publisher.ts
3140
3231
  /** Build the core event publisher used by plugin routes. */
3141
3232
  function createEventAppPublisher(args) {
3142
3233
  const resolveEventAutomationTeamId = createEventTeamIdResolver();
@@ -3184,8 +3275,7 @@ function createEventAppPublisher(args) {
3184
3275
  }
3185
3276
  };
3186
3277
  }
3187
- //#endregion
3188
- //#region src/api/acp/conversations.ts
3278
+ // src/api/acp/conversations.ts
3189
3279
  const EVENT_PAGE_SIZE = 50;
3190
3280
  function actorFromUser(user) {
3191
3281
  return webActorFromEmail(user.email, user.displayName ? { fullName: user.displayName } : void 0);
@@ -3321,8 +3411,7 @@ function createAcpConversations(options) {
3321
3411
  }
3322
3412
  };
3323
3413
  }
3324
- //#endregion
3325
- //#region src/api/acp/state.ts
3414
+ // src/api/acp/state.ts
3326
3415
  const MUTATION_LOCK_TTL_MS = 1e4;
3327
3416
  /** Run one ACP state mutation while its shared lock remains owned. */
3328
3417
  async function withLock(state, key, run, options = {}) {
@@ -3351,8 +3440,7 @@ async function withLock(state, key, run, options = {}) {
3351
3440
  async function fenceLock(state, lock, ttlMs = MUTATION_LOCK_TTL_MS) {
3352
3441
  if (!await state.extendLock(lock, ttlMs)) throw new Error(`ACP lock ownership was lost before write for ${lock.threadId}`);
3353
3442
  }
3354
- //#endregion
3355
- //#region src/api/acp/schema.ts
3443
+ // src/api/acp/schema.ts
3356
3444
  /** Validate the ACP wire messages that Junior supports. */
3357
3445
  const ACP_SESSION_ID_PATTERN = /^local:acp:[a-f0-9]{32}$/;
3358
3446
  const metaSchema = z.record(z.string(), z.unknown()).nullable();
@@ -3448,8 +3536,7 @@ const cancelParamsSchema = z.object({
3448
3536
  _meta: metaSchema.optional(),
3449
3537
  sessionId: acpSessionIdSchema
3450
3538
  }).strict();
3451
- //#endregion
3452
- //#region src/api/acp/sleep.ts
3539
+ // src/api/acp/sleep.ts
3453
3540
  /** Wait for a duration and reject when the ACP request stops. */
3454
3541
  function sleep(ms, signal) {
3455
3542
  return new Promise((resolve, reject) => {
@@ -3468,8 +3555,7 @@ function sleep(ms, signal) {
3468
3555
  signal.addEventListener("abort", onAbort, { once: true });
3469
3556
  });
3470
3557
  }
3471
- //#endregion
3472
- //#region src/api/acp/transport.ts
3558
+ // src/api/acp/transport.ts
3473
3559
  /**
3474
3560
  * Short-lived ACP transport state.
3475
3561
  *
@@ -4040,8 +4126,7 @@ function createSseBody(args) {
4040
4126
  }
4041
4127
  });
4042
4128
  }
4043
- //#endregion
4044
- //#region src/api/acp/auth.ts
4129
+ // src/api/acp/auth.ts
4045
4130
  /** Own ACP browser authorization records and connection binding. */
4046
4131
  const ACP_AUTHORIZATION_TTL_MS = 6e5;
4047
4132
  const ACP_CONNECTION_COOKIE = "junior_acp_connection";
@@ -4380,8 +4465,7 @@ async function expireAcpAuthorization(args) {
4380
4465
  if (!result.acquired) return false;
4381
4466
  return result.value;
4382
4467
  }
4383
- //#endregion
4384
- //#region src/api/acp/route.ts
4468
+ // src/api/acp/route.ts
4385
4469
  /**
4386
4470
  * Own the remote ACP v1 Streamable HTTP edge.
4387
4471
  *
@@ -4819,8 +4903,7 @@ function createAcpHttpHandler(options) {
4819
4903
  }
4820
4904
  };
4821
4905
  }
4822
- //#endregion
4823
- //#region src/api/acp/authorization-page.ts
4906
+ // src/api/acp/authorization-page.ts
4824
4907
  const ACP_AUTHORIZATION_PATH = "/_junior/acp/auth/:transactionId";
4825
4908
  const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
4826
4909
  function escapeHtml(value) {
@@ -4944,14 +5027,176 @@ async function handleAcpAuthorizationPage(args) {
4944
5027
  });
4945
5028
  return renderResult(args.agentName, result);
4946
5029
  }
4947
- //#endregion
4948
- //#region src/dashboard-routes.ts
5030
+ // src/chat/sign-in/auth.ts
5031
+ /**
5032
+ * Google sign-in session for the dashboard.
5033
+ *
5034
+ * Better Auth runs the Google OAuth flow and keeps the browser session in an
5035
+ * encrypted cookie. With allowed domains, only Google Workspace accounts on
5036
+ * those domains can sign in. Each sign-in also saves the native Google
5037
+ * identity.
5038
+ */
5039
+ const DEFAULT_AUTH_PATH = "/api/auth";
5040
+ const DEFAULT_SESSION_MAX_AGE_SECONDS = 28800;
5041
+ const googleClaimsSchema = z.object({
5042
+ sub: z.string().min(1),
5043
+ email: z.string().optional(),
5044
+ email_verified: z.boolean().optional(),
5045
+ hd: z.string().optional(),
5046
+ name: z.string().optional(),
5047
+ picture: z.string().optional()
5048
+ });
5049
+ /**
5050
+ * Read Google ID token claims. Returns undefined for a malformed token, so
5051
+ * Better Auth redirects with `unable_to_get_user_info` instead of a 500.
5052
+ */
5053
+ function readGoogleClaims(idToken) {
5054
+ let payload;
5055
+ try {
5056
+ payload = decodeJwt(idToken);
5057
+ } catch {
5058
+ return;
5059
+ }
5060
+ const parsed = googleClaimsSchema.safeParse(payload);
5061
+ return parsed.success ? parsed.data : void 0;
5062
+ }
5063
+ function trimSlashes(value, leading) {
5064
+ let start = 0;
5065
+ let end = value.length;
5066
+ while (leading && start < end && value[start] === "/") start += 1;
5067
+ while (end > start && value[end - 1] === "/") end -= 1;
5068
+ return value.slice(start, end);
5069
+ }
5070
+ function withHttps(host) {
5071
+ return /^https?:\/\//.test(host) ? host : `https://${host}`;
5072
+ }
5073
+ /** Resolve sign-in settings from dashboard options and environment. */
5074
+ function resolveSignInAuthConfig(agentName, allowedDomains, options = {}) {
5075
+ return {
5076
+ agentName,
5077
+ allowedDomains,
5078
+ authPath: `/${trimSlashes(options.authPath?.trim() || DEFAULT_AUTH_PATH, true)}`,
5079
+ baseURL: trimSlashes(withHttps(options.baseURL?.trim() || resolveBaseUrl() || "http://localhost:3000"), false),
5080
+ sessionMaxAgeSeconds: options.sessionMaxAgeSeconds,
5081
+ trustedOrigins: options.trustedOrigins ?? readEnvList("JUNIOR_DASHBOARD_TRUSTED_ORIGINS") ?? []
5082
+ };
5083
+ }
5084
+ function required(value, name) {
5085
+ if (!value?.trim()) throw new Error(`${name} is required for Junior sign-in`);
5086
+ return value.trim();
5087
+ }
5088
+ function createBetterAuth(config) {
5089
+ const secret = required(config.secret ?? process.env.BETTER_AUTH_SECRET ?? process.env.JUNIOR_SECRET, "JUNIOR_SECRET or BETTER_AUTH_SECRET");
5090
+ const sessionMaxAgeSeconds = config.sessionMaxAgeSeconds ?? DEFAULT_SESSION_MAX_AGE_SECONDS;
5091
+ const { allowedDomains } = config;
5092
+ const hostedDomain = allowedDomains.length === 0 ? void 0 : allowedDomains.length === 1 ? allowedDomains[0] : "*";
5093
+ return betterAuth({
5094
+ appName: config.agentName,
5095
+ baseURL: config.baseURL,
5096
+ basePath: config.authPath,
5097
+ secret,
5098
+ trustedOrigins: config.trustedOrigins,
5099
+ socialProviders: { google: {
5100
+ clientId: required(config.googleClientId ?? process.env.GOOGLE_CLIENT_ID, "GOOGLE_CLIENT_ID"),
5101
+ clientSecret: required(config.googleClientSecret ?? process.env.GOOGLE_CLIENT_SECRET, "GOOGLE_CLIENT_SECRET"),
5102
+ hd: hostedDomain,
5103
+ prompt: "select_account",
5104
+ async getUserInfo(token) {
5105
+ if (!token.idToken) return null;
5106
+ const claims = readGoogleClaims(token.idToken);
5107
+ if (!claims) return null;
5108
+ if (!isAllowedDomain(claims.hd, allowedDomains)) return null;
5109
+ return {
5110
+ user: {
5111
+ id: claims.sub,
5112
+ email: claims.email,
5113
+ emailVerified: claims.email_verified === true,
5114
+ image: claims.picture,
5115
+ name: claims.name
5116
+ },
5117
+ data: claims
5118
+ };
5119
+ }
5120
+ } },
5121
+ account: {
5122
+ storeStateStrategy: "cookie",
5123
+ storeAccountCookie: false,
5124
+ updateAccountOnSignIn: false
5125
+ },
5126
+ session: {
5127
+ expiresIn: sessionMaxAgeSeconds,
5128
+ disableSessionRefresh: true,
5129
+ cookieCache: {
5130
+ enabled: true,
5131
+ strategy: "jwe",
5132
+ maxAge: sessionMaxAgeSeconds,
5133
+ refreshCache: false
5134
+ }
5135
+ },
5136
+ hooks: { after: createAuthMiddleware(async (ctx) => {
5137
+ const created = ctx.context.newSession;
5138
+ if (!created || !ctx.path.startsWith("/callback/")) return;
5139
+ const email = normalizeIdentityEmail(created.user.email);
5140
+ if (!created.user.emailVerified || !email) return;
5141
+ const google = (await ctx.context.internalAdapter.findAccounts(created.user.id)).find((account) => account.providerId === "google");
5142
+ if (!google) throw new Error("Google sign-in has no Google account");
5143
+ await recordGoogleSignIn(getSqlExecutor(), {
5144
+ email,
5145
+ subject: google.accountId,
5146
+ displayName: created.user.name || void 0
5147
+ });
5148
+ }) }
5149
+ });
5150
+ }
5151
+ /**
5152
+ * Create the Google sign-in session. Better Auth starts on first use, so apps
5153
+ * that never sign in need no Google credentials.
5154
+ */
5155
+ function createSignInAuth(config) {
5156
+ let auth;
5157
+ const getAuth = () => auth ??= createBetterAuth(config);
5158
+ return {
5159
+ handler(request) {
5160
+ return getAuth().handler(request);
5161
+ },
5162
+ async getSession(request) {
5163
+ const session = await getAuth().api.getSession({ headers: request.headers });
5164
+ if (!session) return null;
5165
+ const { email, emailVerified, name } = session.user;
5166
+ return { user: {
5167
+ email,
5168
+ emailVerified,
5169
+ name
5170
+ } };
5171
+ },
5172
+ async signInWithGoogle(request, callbackURL, errorCallbackURL) {
5173
+ const result = await getAuth().api.signInSocial({
5174
+ body: {
5175
+ provider: "google",
5176
+ callbackURL,
5177
+ errorCallbackURL
5178
+ },
5179
+ headers: request.headers,
5180
+ returnHeaders: true
5181
+ });
5182
+ if (!("url" in result.response) || !result.response.url) throw new Error("Google sign-in did not return a redirect URL");
5183
+ result.headers.set("location", result.response.url);
5184
+ return new Response(null, {
5185
+ status: 302,
5186
+ headers: result.headers
5187
+ });
5188
+ }
5189
+ };
5190
+ }
5191
+ // src/dashboard-routes.ts
4949
5192
  const DASHBOARD_PACKAGE_NAME = "@sentry/junior-dashboard";
4950
5193
  /** Mount the dashboard app on the dashboard-owned host paths. */
4951
5194
  async function createDashboardRouteRegistrations(args) {
4952
5195
  if (!args.dashboard || args.dashboard.disabled) return [];
4953
5196
  const createDashboardApp = args.createDashboardApp ?? await loadDashboardAppFactory();
4954
5197
  return dashboardRouteRegistrations({
5198
+ allowedDomains: args.allowedDomains,
5199
+ auth: args.auth,
4955
5200
  authenticatedRoutes: args.authenticatedRoutes,
4956
5201
  conversationWorkQueue: args.conversationWorkQueue,
4957
5202
  dashboard: args.dashboard,
@@ -5074,7 +5319,7 @@ function routePatternMatchesConcretePath(pattern, concretePath) {
5074
5319
  const patternSegments = routeSegments(pattern);
5075
5320
  const pathSegments = routeSegments(concretePath);
5076
5321
  for (let index = 0; index < patternSegments.length; index += 1) {
5077
- const segment = patternSegments[index];
5322
+ const segment = patternSegments[index] ?? "";
5078
5323
  if (segment === "**" || segment === "*") return true;
5079
5324
  const value = pathSegments[index];
5080
5325
  if (!value || !routeSegmentMatches(segment, value)) return false;
@@ -5100,6 +5345,8 @@ function dashboardRouteRegistrations(args) {
5100
5345
  const dashboardOptions = {
5101
5346
  ...args.dashboard,
5102
5347
  agentName: botConfig.userName,
5348
+ allowedDomains: args.allowedDomains,
5349
+ ...args.auth ? { auth: args.auth } : void 0,
5103
5350
  authenticatedRoutes: args.authenticatedRoutes,
5104
5351
  conversationWorkQueue: args.conversationWorkQueue,
5105
5352
  pluginRoutes: args.pluginRoutes
@@ -5118,8 +5365,7 @@ function validateDashboardRouteOwnership(args) {
5118
5365
  if (!args.dashboard || args.dashboard.disabled) return;
5119
5366
  for (const route of args.routes) if (dashboardOwnedRoutePath(route.path, args.dashboard, args.authenticatedRoutes)) throw new Error(`Plugin "${route.pluginName}" route "${route.path}" conflicts with core dashboard routes`);
5120
5367
  }
5121
- //#endregion
5122
- //#region src/app.ts
5368
+ // src/app.ts
5123
5369
  /** Resolve the public deployment URL used by ACP browser authorization. */
5124
5370
  function resolveAcpBaseURL(dashboard) {
5125
5371
  const configured = dashboard?.baseURL?.trim() || process.env.JUNIOR_BASE_URL?.trim();
@@ -5236,6 +5482,8 @@ async function createApp(options) {
5236
5482
  queue: conversationWorkQueue
5237
5483
  }) });
5238
5484
  let sandboxEgressTracePropagationDomains = [];
5485
+ let signInConfig;
5486
+ let allowedDomains = [];
5239
5487
  try {
5240
5488
  sandboxEgressTracePropagationDomains = normalizeSandboxEgressTracePropagationDomains(options?.sandbox?.egressTracePropagationDomains);
5241
5489
  setSandboxResourceConfig(options?.sandbox);
@@ -5248,6 +5496,10 @@ async function createApp(options) {
5248
5496
  setRuntimeLimits(options?.limits);
5249
5497
  setSlackReactionConfig(options?.slack ?? {});
5250
5498
  if (options?.slack?.crossActorMidRunMode) setCrossActorMidRunMode(options.slack.crossActorMidRunMode);
5499
+ assertNoLegacyAllowedDomains(Object.keys(dashboard ?? {}));
5500
+ allowedDomains = resolveAllowedDomains(options?.allowedDomains);
5501
+ if (allowedDomains.length === 0) logWarn("app.allowed_domains.unset", { "app.reason": "allowedDomains is empty, so any Slack workspace member can use Junior" });
5502
+ if (dashboard && !dashboard.disabled) signInConfig = resolveSignInAuthConfig(botConfig.userName, allowedDomains, dashboard);
5251
5503
  if (shouldValidatePluginCatalog) {
5252
5504
  pluginCatalogRuntime.getSignature();
5253
5505
  validatePluginRegistrations(configuredPlugins?.registrations ?? []);
@@ -5335,10 +5587,14 @@ async function createApp(options) {
5335
5587
  restoreRuntimeConfig$1();
5336
5588
  throw error;
5337
5589
  }
5338
- const slackWebhookServices = createProductionSlackWebhookServices({
5339
- queue: conversationWorkQueue,
5340
- services: runtimeServiceOverrides
5341
- });
5590
+ const signInAuth = signInConfig ? createSignInAuth(signInConfig) : void 0;
5591
+ const slackWebhookServices = {
5592
+ ...createProductionSlackWebhookServices({
5593
+ queue: conversationWorkQueue,
5594
+ services: runtimeServiceOverrides
5595
+ }),
5596
+ allowedDomains
5597
+ };
5342
5598
  const app = new Hono();
5343
5599
  app.onError((err, c) => {
5344
5600
  logException(err, "route.unhandled.exception");
@@ -5354,9 +5610,14 @@ async function createApp(options) {
5354
5610
  ], "/api/acp", (c) => getAcpRuntime().handleRequest(c.req.raw));
5355
5611
  mountRoutes(app, pluginRoutes);
5356
5612
  mountRoutes(app, await createDashboardRouteRegistrations({
5613
+ allowedDomains,
5614
+ auth: signInAuth,
5357
5615
  authenticatedRoutes,
5358
5616
  conversationWorkQueue,
5359
- dashboard,
5617
+ dashboard: dashboard && signInConfig ? {
5618
+ ...dashboard,
5619
+ baseURL: signInConfig.baseURL
5620
+ } : dashboard,
5360
5621
  createDashboardApp: virtualConfig?.createDashboardApp,
5361
5622
  pluginRoutes: pluginApiRoutes
5362
5623
  }));
@@ -5415,5 +5676,4 @@ async function createApp(options) {
5415
5676
  });
5416
5677
  return app;
5417
5678
  }
5418
- //#endregion
5419
5679
  export { JUNIOR_VERSION, createApp, defineJuniorPlugins };