@sentry/junior 0.255.0 → 0.257.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 (68) hide show
  1. package/dist/{admin-CeJh4SuR.js → admin-DgIkm6Vy.js} +1 -1
  2. package/dist/{agent-hooks-CDVx26Uu.js → agent-hooks-BjtTzvru.js} +13 -13
  3. package/dist/{agent-invocations-BY60iNw-.js → agent-invocations-Bdu3ROfo.js} +2 -1
  4. package/dist/{agent-runner-Caiof09f.js → agent-runner-D7OboVb3.js} +41 -33
  5. package/dist/api/schema.d.ts +41 -1
  6. package/dist/api/schema.js +2 -2
  7. package/dist/api.js +121 -33
  8. package/dist/{app-B0gT1Slp.d.ts → app-BsU5QNBd.d.ts} +7 -1
  9. package/dist/app.d.ts +1 -1
  10. package/dist/app.js +587 -210
  11. package/dist/{assistant-reply-BddmM66R.js → assistant-reply-CbTPebzL.js} +1 -1
  12. package/dist/{briefs-iVq_8-ft.js → briefs-oLOldnDM.js} +4 -4
  13. package/dist/{catalog-runtime-hKmI-iZa.js → catalog-runtime-CZzhGQ7F.js} +1 -1
  14. package/dist/cli/chat.js +16 -16
  15. package/dist/cli/check.js +3 -3
  16. package/dist/cli/main.js +2 -2
  17. package/dist/cli/plugins.js +4 -4
  18. package/dist/cli/snapshot-create.js +3 -3
  19. package/dist/cli/upgrade.js +3 -3
  20. package/dist/{client-Clj7onnT.js → client-D8HnkOQX.js} +2 -2
  21. package/dist/{config-BDAfLTI9.js → config-CJRnWK9H.js} +2 -2
  22. package/dist/{connections-BC0RTejT.js → connections-ClYrKzpX.js} +16 -11
  23. package/dist/{conversation-T18Xlkx9.js → conversation-CKNLYBJt.js} +3 -2
  24. package/dist/{conversation-privacy-1ERtieuM.js → conversation-privacy-C8b1nphZ.js} +1 -1
  25. package/dist/{credential-sync-BZuqqIHk.js → credential-sync-BXfohNmJ.js} +82 -40
  26. package/dist/{executor-Dh4Ofsnx.js → executor-Do0THDRR.js} +33 -23
  27. package/dist/{experimental-Qn9rKLgx.js → experimental-CQIvMUO7.js} +1 -0
  28. package/dist/{images-C-WOnTc_.js → images-BN_BiBXF.js} +3 -3
  29. package/dist/instrumentation.js +2 -2
  30. package/dist/{job-queue-BaCL36FY.js → job-queue-B6SV9uVa.js} +1 -1
  31. package/dist/{logging-D3aYP0aG.js → logging-CWxiIuCF.js} +50 -15
  32. package/dist/{message-B9xqD2BP.js → message-TUOdbSBW.js} +3 -3
  33. package/dist/nitro.d.ts +1 -1
  34. package/dist/nitro.js +3 -3
  35. package/dist/{oauth-callback-server-i5qdAou6.js → oauth-callback-server-Dj5UJODo.js} +2 -2
  36. package/dist/{pending-auth-D2t1Moxf.js → pending-auth-s_QFribU.js} +3 -3
  37. package/dist/{profile-DnkX3e2S.js → profile-h-72wM78.js} +4 -4
  38. package/dist/{prompt-C04mP1hu.js → prompt-DyoEj9c2.js} +1 -1
  39. package/dist/{registry-CmKmKHpp.js → registry-sLTr-sV9.js} +43 -3
  40. package/dist/{resolve-jfNHtEBx.js → resolve-ncQPZROQ.js} +2 -2
  41. package/dist/{result-Df5nEDX2.js → result-C883vngT.js} +7 -7
  42. package/dist/{runner-CmzoXEI4.js → runner-CzYImdOF.js} +10 -10
  43. package/dist/{sandbox-egress-signals-DjMNKLZz.js → sandbox-egress-signals-CZbMINHI.js} +1 -1
  44. package/dist/{schema-BBgv0MNf.js → schema-Dy69D4Ge.js} +4 -3
  45. package/dist/{session-stop-BEBfE97J.js → session-stop-CQjKd4a2.js} +46 -20
  46. package/dist/{skills-Dc-AMYi4.js → skills-C22SLruK.js} +2 -2
  47. package/dist/{spawn-OgTwBUj0.js → spawn-DY9CLXDL.js} +2 -2
  48. package/dist/{store-BashBewg.js → store-DXFIQy-G.js} +6 -6
  49. package/dist/{task-queue-VvIKqlQ4.js → task-queue-B17vV0La.js} +3 -3
  50. package/dist/{task-runner-D2CpD6GN.js → task-runner-DUjvzYXj.js} +11 -11
  51. package/dist/{tasks-dJooqiip.js → tasks-D_WIJtvO.js} +25 -10
  52. package/dist/{thread-state-CsMC-ob0.js → thread-state-Ch4yuCqG.js} +7 -7
  53. package/dist/{turn-failure-response-zB364t7_.js → turn-failure-response-C5nBKC4S.js} +8 -8
  54. package/dist/{turn-lifecycle-BfodfmiY.js → turn-lifecycle-CO-ylUmV.js} +1 -1
  55. package/dist/{turn-session-routing-BAh_qsBJ.js → turn-session-routing-B3LWooK5.js} +1 -1
  56. package/dist/{validation-LzIilOb6.js → validation-CXYxaNYx.js} +1 -1
  57. package/dist/{version-CDM8Rq6E.js → version-BcdZy2zg.js} +1 -1
  58. package/dist/version.js +1 -1
  59. package/dist/{viewer-Rnim7alE.js → viewer-DSZXygMU.js} +53 -5
  60. package/dist/{web-input-Dc1VwcT4.js → web-input-B9Bv0_lR.js} +73 -75
  61. package/dist/{work-BAn43SsN.js → work-nAfDSUFP.js} +9 -9
  62. package/dist/{worker-mBXq4_zg.js → worker-vGXZ6XFT.js} +5 -5
  63. package/migrations/0059_automation_run_version.sql +2 -0
  64. package/migrations/0060_identity_signed_in_at.sql +1 -0
  65. package/migrations/meta/0059_snapshot.json +3619 -0
  66. package/migrations/meta/0060_snapshot.json +3465 -0
  67. package/migrations/meta/_journal.json +15 -1
  68. package/package.json +6 -5
package/dist/app.js CHANGED
@@ -1,38 +1,40 @@
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-Caiof09f.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-D3aYP0aG.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, I as scheduledAutomationRunAttributes, It as resolveBaseUrl, Jt as setConfigDefaults, Lt as startOAuthFlow, Nt as deliverPrivateMessage, O as parseSlackThreadId, Ot as clearSandboxEgressSignals, Pt as formatProviderLabel, Rt as createUserTokenStore, 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 getConfigDefaults, t as createAgentRunner, ut as listPendingAgentInvocationResults, v as publicArtifactHeaders, w as collectAttachmentGarbage, wt as resolveSandboxEgressProviderForHost, x as lookupSlackUserProfile, y as readPublicArtifact } from "./agent-runner-D7OboVb3.js";
2
+ import { f as escapeXml } from "./client-D8HnkOQX.js";
3
+ 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-CWxiIuCF.js";
3
4
  import { n as captureMessage, r as continueTrace } from "./sentry-CgWZM_kV.js";
4
- import { t as JUNIOR_VERSION } from "./version-CDM8Rq6E.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-BDAfLTI9.js";
5
+ import { t as JUNIOR_VERSION } from "./version-BcdZy2zg.js";
6
+ 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-CJRnWK9H.js";
6
7
  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-Rnim7alE.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-Dh4Ofsnx.js";
10
- import { n as pluginCatalogRuntime } from "./catalog-runtime-hKmI-iZa.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-dJooqiip.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-CDVx26Uu.js";
13
- import { W as annotationCard } from "./conversation-T18Xlkx9.js";
14
- import { O as withLock$1, w as getStateAdapter } from "./profile-DnkX3e2S.js";
15
- import { c as getThreadStopDecision } from "./pending-auth-D2t1Moxf.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-BEBfE97J.js";
8
+ import { F as recordGoogleSignIn, L as slackUserHasGoogleSignIn, M as findSlackIdentityUserId, R as upsertIdentity, d as getConversationStore, f as getDb, h as withConversationEventLock, i as readActorIdentity, l as getConversationEventStore, n as findUserByEmailFromSql, p as getSqlExecutor, v as withConversationMutationLock } from "./viewer-DSZXygMU.js";
9
+ import { a as juniorAgentInvocations, d as juniorDestinations, i as juniorAgentBindings, s as juniorConversations } from "./agent-invocations-Bdu3ROfo.js";
10
+ import { a as juniorAutomationRuns, f as juniorConversationBriefs, h as juniorConversationEvents, s as juniorAutomations } from "./executor-Do0THDRR.js";
11
+ import { m as credentialLogContext } from "./registry-sLTr-sV9.js";
12
+ import { n as pluginCatalogRuntime } from "./catalog-runtime-CZzhGQ7F.js";
13
+ 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, j as formatSlackLink, l as blockEventAutomation, n as listScheduledAutomationsForTeam, o as saveScheduledAutomationInLock, p as findMatchingEventAutomations, s as scheduledRunId, u as collectEventAutomationMatchKeys, x as eventAutomationRunLimitReason } from "./tasks-D_WIJtvO.js";
14
+ 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-BjtTzvru.js";
15
+ import { W as annotationCard } from "./conversation-CKNLYBJt.js";
16
+ import { O as withLock$1, w as getStateAdapter } from "./profile-h-72wM78.js";
17
+ import { c as getThreadStopDecision } from "./pending-auth-s_QFribU.js";
18
+ 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-CQjKd4a2.js";
17
19
  import { n as getNextRunAtMs } from "./cadence-D00SCzT1.js";
18
- import { f as setSandboxResourceConfig, u as getSandboxResourceConfig } from "./resolve-jfNHtEBx.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-BaCL36FY.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-BashBewg.js";
20
+ import { f as setSandboxResourceConfig, u as getSandboxResourceConfig } from "./resolve-ncQPZROQ.js";
21
+ 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-B6SV9uVa.js";
22
+ 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-DXFIQy-G.js";
21
23
  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-LzIilOb6.js";
24
+ import { i as setExperimentalFeatures, n as getExperimentalFeatures, r as isExperimentalFeatureEnabled } from "./experimental-CQIvMUO7.js";
25
+ import { n as validatePluginRegistrations, t as validatePluginEgressCredentialHooks } from "./validation-CXYxaNYx.js";
24
26
  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-Dc1VwcT4.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-D2CpD6GN.js";
27
- import { o as getAuthPausedConversationTurnId, t as enqueueAgentInvocationResult } from "./result-Df5nEDX2.js";
28
- import { r as enqueueAgentInvocation } from "./work-BAn43SsN.js";
29
- import { s as coerceThreadConversationState } from "./thread-state-CsMC-ob0.js";
30
- import { o as unlinkProvider } from "./connections-BC0RTejT.js";
31
- import { a as GET$4, i as publishAppHomeView, r as GET$5, t as receiveLocalOAuthCredential } from "./credential-sync-BZuqqIHk.js";
32
- import { a as registerVercelPluginTaskDevConsumer, n as createVercelPluginTaskCallback, s as queueCallback } from "./task-queue-VvIKqlQ4.js";
33
- import { t as processConversationWork } from "./worker-mBXq4_zg.js";
34
- import { t as bindMessageAgent } from "./message-B9xqD2BP.js";
35
- import { t as bindSpawnAgent } from "./spawn-OgTwBUj0.js";
27
+ 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-B9Bv0_lR.js";
28
+ 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-DUjvzYXj.js";
29
+ import { o as getAuthPausedConversationTurnId, t as enqueueAgentInvocationResult } from "./result-C883vngT.js";
30
+ import { r as enqueueAgentInvocation } from "./work-nAfDSUFP.js";
31
+ import { s as coerceThreadConversationState } from "./thread-state-Ch4yuCqG.js";
32
+ import { o as unlinkProvider } from "./connections-ClYrKzpX.js";
33
+ import { a as GET$4, i as publishAppHomeView, o as htmlCallbackResponse, r as GET$5, t as receiveLocalOAuthCredential } from "./credential-sync-BXfohNmJ.js";
34
+ import { a as registerVercelPluginTaskDevConsumer, n as createVercelPluginTaskCallback, s as queueCallback } from "./task-queue-B17vV0La.js";
35
+ import { t as processConversationWork } from "./worker-vGXZ6XFT.js";
36
+ import { t as bindMessageAgent } from "./message-TUOdbSBW.js";
37
+ import { t as bindSpawnAgent } from "./spawn-DY9CLXDL.js";
36
38
  import { createRequire } from "node:module";
37
39
  import { Hono } from "hono";
38
40
  import { z } from "zod";
@@ -44,6 +46,8 @@ import { setTimeout as setTimeout$1 } from "node:timers/promises";
44
46
  import { createRemoteJWKSet, decodeJwt, jwtVerify } from "jose";
45
47
  import { ChannelImpl, ThreadImpl } from "chat";
46
48
  import * as acp from "@agentclientprotocol/sdk";
49
+ import { createAuthMiddleware } from "better-auth/api";
50
+ import { betterAuth } from "better-auth/minimal";
47
51
  //#region src/chat/events/timers.ts
48
52
  /** Publish a bounded batch of due timers; the mailbox owns agent execution. */
49
53
  async function runTimerWatchHeartbeat(args) {
@@ -345,14 +349,16 @@ async function getScheduledAutomationDispatch(id) {
345
349
  //#endregion
346
350
  //#region src/chat/event-automations/runs.ts
347
351
  /**
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.
352
+ * Record a dispatched event automation run with the Automation version it ran,
353
+ * and link its Conversation when it exists. The dispatch can finish before
354
+ * this write, so a stored run, or a retry of the same Event, keeps the row it
355
+ * has.
351
356
  */
352
357
  async function recordEventAutomationRunStarted(db, args) {
353
358
  await db.insert(juniorAutomationRuns).values({
354
359
  id: args.dispatchId,
355
- automationId: args.automationId,
360
+ automationId: args.automation.id,
361
+ automationVersion: automationVersionOf(args.automation),
356
362
  conversationId: existingConversationId(getDispatchConversationId({ id: args.dispatchId })),
357
363
  startedAtMs: args.nowMs,
358
364
  status: "running"
@@ -419,9 +425,14 @@ async function readRun(db, runId) {
419
425
  const rows = await db.select().from(juniorAutomationRuns).where(eq(juniorAutomationRuns.id, runId)).limit(1);
420
426
  return rows[0] ? parseScheduledRunRow(rows[0]) : void 0;
421
427
  }
422
- async function writeRun(db, run) {
428
+ /**
429
+ * Save a Scheduled automation run. Pass `automationVersion` only when the run
430
+ * is dispatched. Later writes keep the stored version.
431
+ */
432
+ async function writeRun(db, run, automationVersion) {
423
433
  const stored = scheduledRunSchema.parse(run);
424
434
  const columns = {
435
+ ...automationVersion ? { automationVersion } : void 0,
425
436
  attempt: stored.attempt,
426
437
  automationId: stored.taskId,
427
438
  claimedAtMs: stored.claimedAtMs,
@@ -588,14 +599,14 @@ async function claimDueScheduledRun(db, args) {
588
599
  for (const skipped of skippedRuns) logScheduledAutomationRunSkipped(skipped.task, skipped.run, { "app.task.run.error": skipped.errorMessage });
589
600
  return claimed;
590
601
  }
591
- async function updateRun(db, runId, update) {
602
+ async function updateRun(db, runId, update, automationVersion) {
592
603
  return await db.transaction(async (tx) => {
593
604
  await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext(${`junior:scheduler:run:${runId}:lock`}))`);
594
605
  const current = await readRun(tx, runId);
595
606
  if (!current) return void 0;
596
607
  const next = update(current);
597
608
  if (!next) return void 0;
598
- await writeRun(tx, next);
609
+ await writeRun(tx, next, automationVersion);
599
610
  return next;
600
611
  });
601
612
  }
@@ -603,14 +614,17 @@ function canFinishRun(run, startedAtMs) {
603
614
  if (run.status === "pending") return startedAtMs === void 0;
604
615
  return run.status === "running" && run.startedAtMs === startedAtMs;
605
616
  }
606
- /** Mark a pending run dispatched only for the active claim. */
617
+ /**
618
+ * Mark a pending run dispatched only for the active claim, and store the
619
+ * version of `task` that the dispatch ran.
620
+ */
607
621
  async function markScheduledRunDispatched(db, args) {
608
622
  return await updateRun(db, args.runId, (run) => run.status === "pending" && run.claimedAtMs === args.claimedAtMs ? {
609
623
  ...run,
610
624
  dispatchId: args.dispatchId,
611
625
  startedAtMs: args.nowMs,
612
626
  status: "running"
613
- } : void 0);
627
+ } : void 0, automationVersionOf(args.task));
614
628
  }
615
629
  /** Complete a run only for its active start timestamp. */
616
630
  async function markScheduledRunCompleted(db, args) {
@@ -972,7 +986,8 @@ async function runScheduledAutomationHeartbeat(args) {
972
986
  claimedAtMs: run.claimedAtMs,
973
987
  dispatchId: dispatch.id,
974
988
  nowMs: args.nowMs,
975
- runId: run.id
989
+ runId: run.id,
990
+ task
976
991
  });
977
992
  logInfo("scheduled_automation.run.dispatched", scheduledAutomationRunAttributes(task, dispatched ?? {
978
993
  ...run,
@@ -1604,11 +1619,26 @@ function buildUpstreamUrl(request) {
1604
1619
  };
1605
1620
  }
1606
1621
  }
1622
+ /**
1623
+ * Name the Conversation and the accountable person from the verified token.
1624
+ * Each proxy request is a new HTTP request with no run log context, so the
1625
+ * proxy binds the same fields that the run binds.
1626
+ */
1627
+ function sandboxEgressLogContext(context) {
1628
+ return {
1629
+ ...context.conversationId ? { conversationId: context.conversationId } : void 0,
1630
+ ...credentialLogContext(context.credentials)
1631
+ };
1632
+ }
1607
1633
  /** Continue inbound trace context only after sandbox egress verification succeeds. */
1608
- function continueSandboxEgressTrace(request, upstreamHost, tracePropagation, callback) {
1634
+ function continueSandboxEgressTrace(request, upstreamHost, tracePropagation, credentialContext, callback) {
1609
1635
  const sentryTrace = request.headers.get("sentry-trace") ?? void 0;
1610
1636
  const baggage = request.headers.get("baggage") ?? void 0;
1611
- const run = () => withSpan("sandbox.egress", "http.server", {}, callback, {
1637
+ const logContext = sandboxEgressLogContext(credentialContext);
1638
+ const run = () => withSpan("sandbox.egress", "http.server", logContext, async () => {
1639
+ setSentryUser(logContext.userId ? { id: logContext.userId } : void 0);
1640
+ return await callback();
1641
+ }, {
1612
1642
  "http.request.method": request.method,
1613
1643
  "url.path": redactedProxyPath(new URL(request.url).pathname)
1614
1644
  });
@@ -1697,7 +1727,7 @@ async function proxySandboxEgressRequest(request, deps = {}) {
1697
1727
  });
1698
1728
  return jsonError("Sandbox egress credential context is not authorized", 403);
1699
1729
  }
1700
- return await continueSandboxEgressTrace(request, upstreamUrl.hostname, deps.tracePropagation ?? {}, async () => await executeCredentialedEgressRequest({
1730
+ return await continueSandboxEgressTrace(request, upstreamUrl.hostname, deps.tracePropagation ?? {}, credentialContext, async () => await executeCredentialedEgressRequest({
1701
1731
  activeEgressId,
1702
1732
  credentialContext,
1703
1733
  deps,
@@ -1933,6 +1963,174 @@ const slackInteractivePayloadSchema = z.object({
1933
1963
  }).optional()
1934
1964
  });
1935
1965
  //#endregion
1966
+ //#region src/chat/ingress/slash-command.ts
1967
+ async function postEphemeral(event, text) {
1968
+ await event.channel.postEphemeral(event.userId, text, { fallbackToDM: false });
1969
+ }
1970
+ function getCommandName() {
1971
+ return getChatConfig().slack.slashCommand;
1972
+ }
1973
+ async function handleLink(event, provider) {
1974
+ if (!pluginCatalogRuntime.isProvider(provider)) {
1975
+ await postEphemeral(event, `Unknown provider: \`${provider}\``);
1976
+ return;
1977
+ }
1978
+ if (!pluginCatalogRuntime.getOAuthConfig(provider)) {
1979
+ await postEphemeral(event, `${formatProviderLabel(provider)} doesn't support account linking.`);
1980
+ return;
1981
+ }
1982
+ const result = await startOAuthFlow(provider, {
1983
+ actorId: event.userId,
1984
+ actor: {
1985
+ platform: "slack",
1986
+ teamId: event.teamId,
1987
+ userId: event.userId
1988
+ },
1989
+ channelId: event.channelId
1990
+ });
1991
+ if (!result.ok) {
1992
+ await postEphemeral(event, `Failed to start linking: ${result.error}`);
1993
+ return;
1994
+ }
1995
+ if (result.delivery === "fallback_dm") await postEphemeral(event, `Check your DMs for a ${formatProviderLabel(provider)} authorization link.`);
1996
+ 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.");
1997
+ }
1998
+ async function handleUnlink(event, provider) {
1999
+ if (!pluginCatalogRuntime.isProvider(provider)) {
2000
+ await postEphemeral(event, `Unknown provider: \`${provider}\``);
2001
+ return;
2002
+ }
2003
+ if (!(Boolean(pluginCatalogRuntime.getOAuthConfig(provider)) || Boolean(pluginCatalogRuntime.getDefinition(provider)?.manifest.mcp))) {
2004
+ await postEphemeral(event, `${formatProviderLabel(provider)} doesn't support account unlinking.`);
2005
+ return;
2006
+ }
2007
+ const tokenStore = createUserTokenStore();
2008
+ await unlinkProvider(event.userId, provider, tokenStore, event.teamId);
2009
+ logInfo("slash_command.credential.unlinked", { "app.credential.provider": provider });
2010
+ await postEphemeral(event, `Your ${formatProviderLabel(provider)} account has been unlinked.`);
2011
+ }
2012
+ /** Route link and unlink slash commands to the appropriate OAuth flow. */
2013
+ async function handleSlashCommand(event) {
2014
+ const [subcommand, provider, ...rest] = event.text.trim().split(/\s+/);
2015
+ if (!subcommand || !["link", "unlink"].includes(subcommand)) {
2016
+ await postEphemeral(event, `Usage: \`${getCommandName()} link <provider>\` or \`${getCommandName()} unlink <provider>\``);
2017
+ return;
2018
+ }
2019
+ if (!provider || rest.length > 0) {
2020
+ await postEphemeral(event, `Usage: \`${getCommandName()} ${subcommand} <provider>\``);
2021
+ return;
2022
+ }
2023
+ const normalized = provider.toLowerCase();
2024
+ if (subcommand === "link") await handleLink(event, normalized);
2025
+ else await handleUnlink(event, normalized);
2026
+ }
2027
+ //#endregion
2028
+ //#region src/chat/ingress/slack-form.ts
2029
+ function parseJson$1(body) {
2030
+ try {
2031
+ return JSON.parse(body);
2032
+ } catch {
2033
+ return;
2034
+ }
2035
+ }
2036
+ function requireSlackPayloadUserId(value, source) {
2037
+ const userId = parseActorUserId(value);
2038
+ if (!userId) throw new Error(`${source} is missing a Slack user id`);
2039
+ return userId;
2040
+ }
2041
+ async function handleSlashCommandForm(args) {
2042
+ const { channel_id: channelId, team_id: teamId, user_id: userId } = args.form;
2043
+ const channel = new ChannelImpl({
2044
+ id: `slack:${channelId}`,
2045
+ adapter: args.adapter,
2046
+ stateAdapter: args.state
2047
+ });
2048
+ await withSpan("chat.slash_command", "chat.slash_command", { userId }, async () => {
2049
+ await handleSlashCommand({
2050
+ channel,
2051
+ channelId,
2052
+ teamId,
2053
+ text: args.form.text,
2054
+ userId
2055
+ });
2056
+ });
2057
+ }
2058
+ async function handleInteractivePayload(args) {
2059
+ if (args.payload.type !== "block_actions") return;
2060
+ const action = args.payload.actions?.find((candidate) => candidate.action_id === "app_home_disconnect");
2061
+ const provider = action?.selected_option?.value ?? action?.value;
2062
+ if (!provider) return;
2063
+ const userId = requireSlackPayloadUserId(args.payload.user?.id, "Slack app home disconnect payload");
2064
+ await withSpan("chat.app_home_disconnect", "chat.app_home_disconnect", { userId }, async () => {
2065
+ try {
2066
+ await unlinkProvider(userId, provider, args.userTokenStore, getWorkspaceTeamId());
2067
+ } catch (error) {
2068
+ logException(error, "app_home.disconnect_unlink.failed", { "app.credential.provider": provider });
2069
+ }
2070
+ try {
2071
+ await publishAppHomeView(getSlackClient(), userId, args.userTokenStore);
2072
+ } catch (error) {
2073
+ logException(error, "app_home.disconnect_publish.failed", { "app.credential.provider": provider });
2074
+ }
2075
+ });
2076
+ }
2077
+ function installationFromInteractive(payload) {
2078
+ return { teamId: payload.team?.id ?? payload.user?.team_id };
2079
+ }
2080
+ /** Handle a Slack slash command or interactive payload form. */
2081
+ async function handleSlackForm(args) {
2082
+ const params = new URLSearchParams(args.body);
2083
+ const { adapter, state } = args;
2084
+ await state.connect();
2085
+ if (params.has("command") && !params.has("payload")) {
2086
+ const result = slackSlashCommandSchema.safeParse(Object.fromEntries(params));
2087
+ if (!result.success) return new Response("Invalid slash command payload", { status: 400 });
2088
+ const form = result.data;
2089
+ const installation = {
2090
+ teamId: form.team_id,
2091
+ enterpriseId: form.enterprise_id,
2092
+ isEnterpriseInstall: form.is_enterprise_install === "true"
2093
+ };
2094
+ args.waitUntil(withLogContext({
2095
+ platform: "slack",
2096
+ userId: form.user_id
2097
+ }, () => runWithWorkspaceTeamId(installation.teamId, () => runWithSlackInstallation({
2098
+ adapter,
2099
+ installation,
2100
+ state,
2101
+ task: () => handleSlashCommandForm({
2102
+ adapter,
2103
+ form,
2104
+ state
2105
+ })
2106
+ })).catch((error) => {
2107
+ logException(error, "slash_command.failed");
2108
+ })));
2109
+ return new Response("", { status: 200 });
2110
+ }
2111
+ const rawPayload = params.get("payload");
2112
+ if (!rawPayload) return new Response("Missing payload", { status: 400 });
2113
+ const result = slackInteractivePayloadSchema.safeParse(parseJson$1(rawPayload));
2114
+ if (!result.success) return new Response("Invalid payload JSON", { status: 400 });
2115
+ const payload = result.data;
2116
+ const installation = installationFromInteractive(payload);
2117
+ args.waitUntil(withLogContext({
2118
+ platform: "slack",
2119
+ userId: payload.user?.id?.trim() || void 0
2120
+ }, () => runWithWorkspaceTeamId(installation.teamId, () => runWithSlackInstallation({
2121
+ adapter,
2122
+ installation,
2123
+ state,
2124
+ task: () => handleInteractivePayload({
2125
+ payload,
2126
+ userTokenStore: args.getUserTokenStore()
2127
+ })
2128
+ })).catch((error) => {
2129
+ logException(error, "slack.interactive.payload.failed");
2130
+ })));
2131
+ return new Response("", { status: 200 });
2132
+ }
2133
+ //#endregion
1936
2134
  //#region src/chat/ingress/bot-mention.ts
1937
2135
  /**
1938
2136
  * Detect Slack bot @mentions that should activate Junior.
@@ -2043,7 +2241,7 @@ async function ingestEventAutomations(input, options) {
2043
2241
  const event = eventSchema.parse(input);
2044
2242
  const db = getDb();
2045
2243
  const nowMs = options.nowMs ?? Date.now();
2046
- const tasks = await findMatchingEventAutomations(db, event, options.teamId);
2244
+ const tasks = (await findMatchingEventAutomations(db, event, options.teamId)).filter((task) => slackChannelMessageRunsAutomation(event, task));
2047
2245
  let dispatched = 0;
2048
2246
  const errors = [];
2049
2247
  for (const task of tasks) try {
@@ -2108,7 +2306,7 @@ async function ingestEventAutomations(input, options) {
2108
2306
  }
2109
2307
  });
2110
2308
  await recordEventAutomationRunStarted(db, {
2111
- automationId: task.id,
2309
+ automation: task,
2112
2310
  dispatchId: dispatch.id,
2113
2311
  nowMs
2114
2312
  });
@@ -2167,6 +2365,170 @@ async function isSlackWorkspaceMember(raw, state) {
2167
2365
  return result.value;
2168
2366
  }
2169
2367
  //#endregion
2368
+ //#region src/chat/sign-in/allowlist.ts
2369
+ /** Google accounts that may sign in. The dashboard uses the same list. */
2370
+ /**
2371
+ * Read a list env var the same way as the dashboard: a JSON string array or
2372
+ * comma-separated values.
2373
+ */
2374
+ function readEnvList(name) {
2375
+ const value = process.env[name]?.trim();
2376
+ if (!value) return void 0;
2377
+ if (!value.startsWith("[")) return value.split(",");
2378
+ let parsed;
2379
+ try {
2380
+ parsed = JSON.parse(value);
2381
+ } catch (error) {
2382
+ throw new Error(`${name} must be a JSON string array`, { cause: error });
2383
+ }
2384
+ const list = z.array(z.string()).safeParse(parsed);
2385
+ if (!list.success) throw new Error(`${name} must be a JSON string array`);
2386
+ return list.data;
2387
+ }
2388
+ /**
2389
+ * Resolve the sign-in allowlist. Use the same values as the dashboard: its
2390
+ * options when set, else its environment variables.
2391
+ */
2392
+ function resolveSignInAllowlist(options = {}) {
2393
+ const list = (values, name) => (values ?? readEnvList(name) ?? []).map((value) => value.trim().toLowerCase()).filter(Boolean);
2394
+ return {
2395
+ domains: list(options.allowedGoogleDomains, "JUNIOR_DASHBOARD_GOOGLE_DOMAINS"),
2396
+ emails: list(options.allowedEmails, "JUNIOR_DASHBOARD_ALLOWED_EMAILS")
2397
+ };
2398
+ }
2399
+ /** Return whether a normalized Google email is on the allowlist. */
2400
+ function isSignInAllowed(email, allowlist) {
2401
+ const domain = email.slice(email.lastIndexOf("@") + 1);
2402
+ return allowlist.emails.includes(email) || allowlist.domains.includes(domain);
2403
+ }
2404
+ //#endregion
2405
+ //#region src/chat/sign-in/slack.ts
2406
+ /**
2407
+ * Slack sign-in through the shared Google sign-in session.
2408
+ *
2409
+ * Slack sends a person here when they have no recent Google sign-in. The page
2410
+ * uses the browser session that the dashboard also uses. When the person has
2411
+ * no session, or an old one, it starts a Google sign-in and comes back here.
2412
+ * The sign-in counts only when its Google email belongs to the user of the
2413
+ * Slack identity that got the link.
2414
+ */
2415
+ const SLACK_SIGN_IN_PATH = "/api/sign-in/slack";
2416
+ const SIGN_IN_STATE_TTL_MS = 6e5;
2417
+ const signInStateSchema = z.object({
2418
+ redirected: z.boolean(),
2419
+ slackTeamId: z.string().min(1),
2420
+ slackUserId: z.string().min(1)
2421
+ });
2422
+ function stateKey(state) {
2423
+ return `sign-in:v2:${state}`;
2424
+ }
2425
+ function page(title, message, status) {
2426
+ return htmlCallbackResponse(escapeXml(title), escapeXml(message), status);
2427
+ }
2428
+ /** Store one Slack sign-in request and return its link. */
2429
+ async function createSlackSignInUrl(args) {
2430
+ const state = randomBytes(24).toString("base64url");
2431
+ await args.state.set(stateKey(state), {
2432
+ redirected: false,
2433
+ slackTeamId: args.slackTeamId,
2434
+ slackUserId: args.slackUserId
2435
+ }, SIGN_IN_STATE_TTL_MS);
2436
+ const url = new URL(SLACK_SIGN_IN_PATH, args.baseURL);
2437
+ url.searchParams.set("state", state);
2438
+ return url.toString();
2439
+ }
2440
+ /**
2441
+ * Finish a Slack sign-in when the browser has a recent Google sign-in for the
2442
+ * user of the Slack identity. Otherwise start one Google sign-in.
2443
+ */
2444
+ async function handleSlackSignIn(args) {
2445
+ const token = new URL(args.request.url).searchParams.get("state");
2446
+ const parsed = token ? signInStateSchema.safeParse(await args.state.get(stateKey(token))) : void 0;
2447
+ if (!token || !parsed?.success) return page("Sign-in link expired", `Mention ${botConfig.userName} in Slack again to get a new link.`, 400);
2448
+ const stored = parsed.data;
2449
+ const session = await args.auth.getSession(args.request);
2450
+ const email = session?.user.emailVerified ? normalizeIdentityEmail(session.user.email) : void 0;
2451
+ let failure;
2452
+ if (email && !isSignInAllowed(email, args.allowlist)) failure = page("Account not allowed", `${email} cannot use ${botConfig.userName}. Sign in with your work account.`, 403);
2453
+ else if (email) {
2454
+ const executor = getSqlExecutor();
2455
+ const userId = await findSlackIdentityUserId(executor, stored.slackTeamId, stored.slackUserId);
2456
+ const user = await findUserByEmailFromSql(executor.db(), email);
2457
+ if (!userId || user?.id !== userId) failure = page("Slack account does not match", `Your Slack email is not ${email}. Sign in with the account that matches your Slack email.`, 403);
2458
+ else if (await slackUserHasGoogleSignIn(executor, {
2459
+ signedInSince: new Date(Date.now() - args.signIn.maxAgeMs),
2460
+ slackTeamId: stored.slackTeamId,
2461
+ slackUserId: stored.slackUserId
2462
+ })) {
2463
+ await args.state.delete(stateKey(token));
2464
+ return page("You are signed in", "Return to Slack and send your message again.", 200);
2465
+ }
2466
+ }
2467
+ if (stored.redirected) return failure ?? page("Sign-in failed", "Google sign-in did not finish. Sign in with your work account.", 400);
2468
+ await args.state.set(stateKey(token), {
2469
+ ...stored,
2470
+ redirected: true
2471
+ }, SIGN_IN_STATE_TTL_MS);
2472
+ const returnUrl = new URL(SLACK_SIGN_IN_PATH, args.signIn.baseURL);
2473
+ returnUrl.searchParams.set("state", token);
2474
+ return await args.auth.signInWithGoogle(args.request, returnUrl.toString(), returnUrl.toString());
2475
+ }
2476
+ //#endregion
2477
+ //#region src/chat/ingress/slack-sign-in.ts
2478
+ /** Require a recent Google sign-in before a Slack user can start a Turn. */
2479
+ /**
2480
+ * Return whether this Slack user may start work.
2481
+ *
2482
+ * The user of the Slack identity must have signed in with Google, in Slack or
2483
+ * in the dashboard, within `signIn.maxAgeMs`. When it has not, save the Slack
2484
+ * identity with the email from the Slack profile, so the sign-in page can match
2485
+ * it, and check again. Then send the user a private sign-in link and drop the
2486
+ * message.
2487
+ */
2488
+ async function hasSlackSignIn(args) {
2489
+ if (!args.signIn) return true;
2490
+ const teamId = args.installation.teamId;
2491
+ const channelId = args.event.channel;
2492
+ const userId = parseSlackUserId(args.event.user);
2493
+ if (!teamId || !channelId || !userId) return false;
2494
+ const executor = getSqlExecutor();
2495
+ const signedInSince = new Date(Date.now() - args.signIn.maxAgeMs);
2496
+ const hasSignIn = () => slackUserHasGoogleSignIn(executor, {
2497
+ signedInSince,
2498
+ slackTeamId: teamId,
2499
+ slackUserId: userId
2500
+ });
2501
+ if (await hasSignIn()) return true;
2502
+ const profile = await lookupSlackUserProfile(userId);
2503
+ await upsertIdentity(executor, {
2504
+ kind: "user",
2505
+ provider: "slack",
2506
+ providerTenantId: teamId,
2507
+ providerSubjectId: userId,
2508
+ ...profile.real_name ? { displayName: profile.real_name } : void 0,
2509
+ ...profile.name ? { handle: profile.name } : void 0,
2510
+ ...profile.email ? {
2511
+ email: profile.email,
2512
+ emailVerified: true
2513
+ } : void 0,
2514
+ metadata: { platform: "slack" }
2515
+ });
2516
+ if (await hasSignIn()) return true;
2517
+ const link = await createSlackSignInUrl({
2518
+ baseURL: args.signIn.baseURL,
2519
+ slackTeamId: teamId,
2520
+ slackUserId: userId,
2521
+ state: args.state
2522
+ });
2523
+ await deliverPrivateMessage({
2524
+ channelId,
2525
+ ...args.event.thread_ts ? { threadTs: args.event.thread_ts } : void 0,
2526
+ userId,
2527
+ text: `${formatSlackLink(link, `Sign in to use ${botConfig.userName}`)}. Sign in with your work Google account, then send your message again.`
2528
+ });
2529
+ return false;
2530
+ }
2531
+ //#endregion
2170
2532
  //#region src/chat/ingress/slack-message-telemetry.ts
2171
2533
  function field(value, pattern) {
2172
2534
  if (value === void 0) return "missing";
@@ -2192,68 +2554,6 @@ function slackMessageAttributes(body) {
2192
2554
  };
2193
2555
  }
2194
2556
  //#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
2557
  //#region src/chat/slack/automation-details.ts
2258
2558
  function textField(key, label, value) {
2259
2559
  return {
@@ -2651,6 +2951,7 @@ async function routeParsedMessage(args) {
2651
2951
  });
2652
2952
  return;
2653
2953
  }
2954
+ if (route === "mention" && !await hasSlackSignIn(args)) return;
2654
2955
  if (isSubscribed && !isExperimentalFeatureEnabled("passive-routing")) {
2655
2956
  const content = parseContent(message);
2656
2957
  const conversation = coerceThreadConversationState(void 0);
@@ -2767,6 +3068,7 @@ async function handleSlackEvent(args) {
2767
3068
  conversationStore: args.services.conversationStore,
2768
3069
  queue: args.services.queue,
2769
3070
  receivedAtMs,
3071
+ signIn: args.services.signIn,
2770
3072
  state
2771
3073
  }), {
2772
3074
  keepAlive: true,
@@ -2776,103 +3078,6 @@ async function handleSlackEvent(args) {
2776
3078
  }
2777
3079
  }));
2778
3080
  }
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
3081
  /** Handle Slack webhooks by enqueueing durable conversation work. */
2877
3082
  async function handleSlackWebhook(args) {
2878
3083
  const adapter = args.services.getSlackAdapter();
@@ -2883,8 +3088,10 @@ async function handleSlackWebhook(args) {
2883
3088
  request: args.request
2884
3089
  })) return new Response("Invalid signature", { status: 401 });
2885
3090
  if ((args.request.headers.get("content-type") || "").includes("application/x-www-form-urlencoded")) return await handleSlackForm({
3091
+ adapter,
2886
3092
  body,
2887
- services: args.services,
3093
+ getUserTokenStore: () => getUserTokenStore(args.services),
3094
+ state: args.services.state ?? getStateAdapter(),
2888
3095
  waitUntil: args.waitUntil
2889
3096
  });
2890
3097
  const result = slackEventEnvelopeSchema.safeParse(parseJson(body));
@@ -4945,6 +5152,139 @@ async function handleAcpAuthorizationPage(args) {
4945
5152
  return renderResult(args.agentName, result);
4946
5153
  }
4947
5154
  //#endregion
5155
+ //#region src/chat/sign-in/auth.ts
5156
+ /**
5157
+ * Google sign-in session shared by the dashboard and Slack.
5158
+ *
5159
+ * Better Auth runs the Google OAuth flow and keeps the browser session in an
5160
+ * encrypted cookie. Each sign-in also saves the native Google identity with its
5161
+ * sign-in time, so Slack can check a recent sign-in without the browser.
5162
+ */
5163
+ const DEFAULT_AUTH_PATH = "/api/auth";
5164
+ const DEFAULT_SESSION_MAX_AGE_SECONDS = 28800;
5165
+ function trimSlashes(value, leading) {
5166
+ let start = 0;
5167
+ let end = value.length;
5168
+ while (leading && start < end && value[start] === "/") start += 1;
5169
+ while (end > start && value[end - 1] === "/") end -= 1;
5170
+ return value.slice(start, end);
5171
+ }
5172
+ function withHttps(host) {
5173
+ return /^https?:\/\//.test(host) ? host : `https://${host}`;
5174
+ }
5175
+ /** Resolve sign-in settings from dashboard options and environment. */
5176
+ function resolveSignInAuthConfig(agentName, options = {}) {
5177
+ const authPath = `/${trimSlashes(options.authPath?.trim() || DEFAULT_AUTH_PATH, true)}`;
5178
+ const baseURL = trimSlashes(withHttps(options.baseURL?.trim() || resolveBaseUrl() || "http://localhost:3000"), false);
5179
+ return {
5180
+ agentName,
5181
+ allowlist: resolveSignInAllowlist(options),
5182
+ authPath,
5183
+ baseURL,
5184
+ sessionMaxAgeSeconds: options.sessionMaxAgeSeconds,
5185
+ trustedOrigins: options.trustedOrigins ?? readEnvList("JUNIOR_DASHBOARD_TRUSTED_ORIGINS") ?? []
5186
+ };
5187
+ }
5188
+ function required(value, name) {
5189
+ if (!value?.trim()) throw new Error(`${name} is required for Junior sign-in`);
5190
+ return value.trim();
5191
+ }
5192
+ function createBetterAuth(config) {
5193
+ const secret = required(config.secret ?? process.env.BETTER_AUTH_SECRET ?? process.env.JUNIOR_SECRET, "JUNIOR_SECRET or BETTER_AUTH_SECRET");
5194
+ const sessionMaxAgeSeconds = config.sessionMaxAgeSeconds ?? DEFAULT_SESSION_MAX_AGE_SECONDS;
5195
+ const { domains, emails } = config.allowlist;
5196
+ const hostedDomain = domains.length === 1 && emails.length === 0 ? domains[0] : void 0;
5197
+ return betterAuth({
5198
+ appName: config.agentName,
5199
+ baseURL: config.baseURL,
5200
+ basePath: config.authPath,
5201
+ secret,
5202
+ trustedOrigins: config.trustedOrigins,
5203
+ socialProviders: { google: {
5204
+ clientId: required(config.googleClientId ?? process.env.GOOGLE_CLIENT_ID, "GOOGLE_CLIENT_ID"),
5205
+ clientSecret: required(config.googleClientSecret ?? process.env.GOOGLE_CLIENT_SECRET, "GOOGLE_CLIENT_SECRET"),
5206
+ hd: hostedDomain,
5207
+ prompt: "select_account",
5208
+ mapProfileToUser(profile) {
5209
+ return {
5210
+ email: profile.email,
5211
+ emailVerified: profile.email_verified,
5212
+ image: profile.picture,
5213
+ name: profile.name
5214
+ };
5215
+ }
5216
+ } },
5217
+ account: {
5218
+ storeStateStrategy: "cookie",
5219
+ storeAccountCookie: false,
5220
+ updateAccountOnSignIn: false
5221
+ },
5222
+ session: {
5223
+ expiresIn: sessionMaxAgeSeconds,
5224
+ disableSessionRefresh: true,
5225
+ cookieCache: {
5226
+ enabled: true,
5227
+ strategy: "jwe",
5228
+ maxAge: sessionMaxAgeSeconds,
5229
+ refreshCache: false
5230
+ }
5231
+ },
5232
+ hooks: { after: createAuthMiddleware(async (ctx) => {
5233
+ const created = ctx.context.newSession;
5234
+ if (!created || !ctx.path.startsWith("/callback/")) return;
5235
+ const email = normalizeIdentityEmail(created.user.email);
5236
+ if (!created.user.emailVerified || !email || !isSignInAllowed(email, config.allowlist)) return;
5237
+ const google = (await ctx.context.internalAdapter.findAccounts(created.user.id)).find((account) => account.providerId === "google");
5238
+ if (!google) throw new Error("Google sign-in has no Google account");
5239
+ await recordGoogleSignIn(getSqlExecutor(), {
5240
+ email,
5241
+ subject: google.accountId,
5242
+ displayName: created.user.name || void 0
5243
+ });
5244
+ }) }
5245
+ });
5246
+ }
5247
+ /**
5248
+ * Create the shared Google sign-in session. Better Auth starts on first use,
5249
+ * so apps that never sign in need no Google credentials.
5250
+ */
5251
+ function createSignInAuth(config) {
5252
+ let auth;
5253
+ const getAuth = () => auth ??= createBetterAuth(config);
5254
+ return {
5255
+ handler(request) {
5256
+ return getAuth().handler(request);
5257
+ },
5258
+ async getSession(request) {
5259
+ const session = await getAuth().api.getSession({ headers: request.headers });
5260
+ if (!session) return null;
5261
+ const { email, emailVerified, name } = session.user;
5262
+ return { user: {
5263
+ email,
5264
+ emailVerified,
5265
+ name
5266
+ } };
5267
+ },
5268
+ async signInWithGoogle(request, callbackURL, errorCallbackURL) {
5269
+ const result = await getAuth().api.signInSocial({
5270
+ body: {
5271
+ provider: "google",
5272
+ callbackURL,
5273
+ errorCallbackURL
5274
+ },
5275
+ headers: request.headers,
5276
+ returnHeaders: true
5277
+ });
5278
+ if (!("url" in result.response) || !result.response.url) throw new Error("Google sign-in did not return a redirect URL");
5279
+ result.headers.set("location", result.response.url);
5280
+ return new Response(null, {
5281
+ status: 302,
5282
+ headers: result.headers
5283
+ });
5284
+ }
5285
+ };
5286
+ }
5287
+ //#endregion
4948
5288
  //#region src/dashboard-routes.ts
4949
5289
  const DASHBOARD_PACKAGE_NAME = "@sentry/junior-dashboard";
4950
5290
  /** Mount the dashboard app on the dashboard-owned host paths. */
@@ -4952,6 +5292,7 @@ async function createDashboardRouteRegistrations(args) {
4952
5292
  if (!args.dashboard || args.dashboard.disabled) return [];
4953
5293
  const createDashboardApp = args.createDashboardApp ?? await loadDashboardAppFactory();
4954
5294
  return dashboardRouteRegistrations({
5295
+ auth: args.auth,
4955
5296
  authenticatedRoutes: args.authenticatedRoutes,
4956
5297
  conversationWorkQueue: args.conversationWorkQueue,
4957
5298
  dashboard: args.dashboard,
@@ -5100,6 +5441,7 @@ function dashboardRouteRegistrations(args) {
5100
5441
  const dashboardOptions = {
5101
5442
  ...args.dashboard,
5102
5443
  agentName: botConfig.userName,
5444
+ ...args.auth ? { auth: args.auth } : void 0,
5103
5445
  authenticatedRoutes: args.authenticatedRoutes,
5104
5446
  conversationWorkQueue: args.conversationWorkQueue,
5105
5447
  pluginRoutes: args.pluginRoutes
@@ -5236,6 +5578,8 @@ async function createApp(options) {
5236
5578
  queue: conversationWorkQueue
5237
5579
  }) });
5238
5580
  let sandboxEgressTracePropagationDomains = [];
5581
+ let signInConfig;
5582
+ let slackSignIn;
5239
5583
  try {
5240
5584
  sandboxEgressTracePropagationDomains = normalizeSandboxEgressTracePropagationDomains(options?.sandbox?.egressTracePropagationDomains);
5241
5585
  setSandboxResourceConfig(options?.sandbox);
@@ -5248,6 +5592,18 @@ async function createApp(options) {
5248
5592
  setRuntimeLimits(options?.limits);
5249
5593
  setSlackReactionConfig(options?.slack ?? {});
5250
5594
  if (options?.slack?.crossActorMidRunMode) setCrossActorMidRunMode(options.slack.crossActorMidRunMode);
5595
+ const dashboardMounted = Boolean(dashboard && !dashboard.disabled);
5596
+ if (isExperimentalFeatureEnabled("slack-sign-in") || dashboardMounted) signInConfig = resolveSignInAuthConfig(botConfig.userName, dashboard);
5597
+ if (signInConfig && isExperimentalFeatureEnabled("slack-sign-in")) {
5598
+ const { allowlist } = signInConfig;
5599
+ if (!allowlist.domains.length && !allowlist.emails.length) throw new Error("experimental.slack-sign-in requires allowed Google domains or emails");
5600
+ const maxAgeDays = options?.slack?.signInMaxAgeDays ?? 7;
5601
+ if (!(maxAgeDays > 0)) throw new Error("slack.signInMaxAgeDays must be greater than 0");
5602
+ slackSignIn = {
5603
+ baseURL: signInConfig.baseURL,
5604
+ maxAgeMs: maxAgeDays * 24 * 60 * 60 * 1e3
5605
+ };
5606
+ }
5251
5607
  if (shouldValidatePluginCatalog) {
5252
5608
  pluginCatalogRuntime.getSignature();
5253
5609
  validatePluginRegistrations(configuredPlugins?.registrations ?? []);
@@ -5335,10 +5691,14 @@ async function createApp(options) {
5335
5691
  restoreRuntimeConfig$1();
5336
5692
  throw error;
5337
5693
  }
5338
- const slackWebhookServices = createProductionSlackWebhookServices({
5339
- queue: conversationWorkQueue,
5340
- services: runtimeServiceOverrides
5341
- });
5694
+ const signInAuth = signInConfig ? createSignInAuth(signInConfig) : void 0;
5695
+ const slackWebhookServices = {
5696
+ ...createProductionSlackWebhookServices({
5697
+ queue: conversationWorkQueue,
5698
+ services: runtimeServiceOverrides
5699
+ }),
5700
+ ...slackSignIn ? { signIn: slackSignIn } : void 0
5701
+ };
5342
5702
  const app = new Hono();
5343
5703
  app.onError((err, c) => {
5344
5704
  logException(err, "route.unhandled.exception");
@@ -5354,9 +5714,13 @@ async function createApp(options) {
5354
5714
  ], "/api/acp", (c) => getAcpRuntime().handleRequest(c.req.raw));
5355
5715
  mountRoutes(app, pluginRoutes);
5356
5716
  mountRoutes(app, await createDashboardRouteRegistrations({
5717
+ auth: signInAuth,
5357
5718
  authenticatedRoutes,
5358
5719
  conversationWorkQueue,
5359
- dashboard,
5720
+ dashboard: dashboard && signInConfig ? {
5721
+ ...dashboard,
5722
+ baseURL: signInConfig.baseURL
5723
+ } : dashboard,
5360
5724
  createDashboardApp: virtualConfig?.createDashboardApp,
5361
5725
  pluginRoutes: pluginApiRoutes
5362
5726
  }));
@@ -5372,6 +5736,19 @@ async function createApp(options) {
5372
5736
  app.get("/api/oauth/callback/:provider", (c) => {
5373
5737
  return GET$5(c.req.raw, c.req.param("provider"), waitUntil, { conversationWorkQueue });
5374
5738
  });
5739
+ if (signInAuth && signInConfig && slackSignIn) {
5740
+ const auth = signInAuth;
5741
+ if (!dashboard || dashboard.disabled) app.on(["GET", "POST"], `${signInConfig.authPath}/*`, (c) => auth.handler(c.req.raw));
5742
+ const { allowlist } = signInConfig;
5743
+ const signIn = slackSignIn;
5744
+ app.get(SLACK_SIGN_IN_PATH, (c) => handleSlackSignIn({
5745
+ allowlist,
5746
+ auth,
5747
+ signIn,
5748
+ request: c.req.raw,
5749
+ state: getStateAdapter()
5750
+ }));
5751
+ }
5375
5752
  app.get("/api/internal/sandbox-egress/signals", (c) => {
5376
5753
  return GET(c.req.raw);
5377
5754
  });