@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.
- package/dist/{actor-BTtLaeWy.js → actor-tlxzqaRe.js} +3 -6
- package/dist/{admin-BhucwVfy.js → admin-My1SLGVO.js} +2 -3
- package/dist/{agent-hooks-DlSK8PQQ.js → agent-hooks-BJ_5mlRH.js} +71 -111
- package/dist/{agent-invocations-BY60iNw-.js → agent-invocations-Qfz7dWgU.js} +8 -13
- package/dist/{agent-runner-C6_cd61V.js → agent-runner-DGsFQw72.js} +278 -467
- package/dist/api/schema.d.ts +454 -448
- package/dist/api/schema.js +5 -5
- package/dist/api.d.ts +2 -12
- package/dist/api.js +189 -165
- package/dist/{app-B0gT1Slp.d.ts → app-PX2H1PxU.d.ts} +20 -37
- package/dist/app.d.ts +3 -3
- package/dist/app.js +565 -305
- package/dist/{assistant-reply-yn5G-YEG.js → assistant-reply-H1M-91oF.js} +3 -5
- package/dist/{automation-CHdlHVZP.js → automation-DWBFBenG.js} +6 -11
- package/dist/{briefs-U6Bi3IYw.js → briefs-vYoK85rJ.js} +8 -11
- package/dist/{cadence-D00SCzT1.js → cadence-R7Em4RjE.js} +1 -2
- package/dist/{catalog-runtime-DF5OWqCc.js → catalog-runtime-BN4DVxco.js} +3 -4
- package/dist/cli/chat.d.ts +2 -4
- package/dist/cli/chat.js +22 -24
- package/dist/cli/check.d.ts +1 -3
- package/dist/cli/check.js +4 -5
- package/dist/cli/env.d.ts +1 -3
- package/dist/cli/env.js +1 -2
- package/dist/cli/init.d.ts +1 -3
- package/dist/cli/init.js +3 -3
- package/dist/cli/main.d.ts +1 -3
- package/dist/cli/main.js +4 -5
- package/dist/cli/plugins.d.ts +2 -4
- package/dist/cli/plugins.js +7 -8
- package/dist/cli/run.d.ts +1 -2
- package/dist/cli/run.js +1 -2
- package/dist/cli/snapshot-create.d.ts +1 -3
- package/dist/cli/snapshot-create.js +4 -5
- package/dist/cli/upgrade.d.ts +3 -6
- package/dist/cli/upgrade.js +11 -17
- package/dist/{client-BVrAp-pq.js → client-DbyHuMFU.js} +9 -16
- package/dist/{config-DdBiDanf.js → config-_WO_AjCB.js} +18 -15
- package/dist/{connections-CG04HdpC.js → connections-ag_1JOhB.js} +21 -21
- package/dist/{conversation-T18Xlkx9.js → conversation-DjN3mlog.js} +11 -17
- package/dist/{conversation-privacy-CpSYOWnP.js → conversation-privacy-DHP_xnVj.js} +2 -3
- package/dist/{conversation-work-C6y3T5Hx.js → conversation-work-BaT_T6EB.js} +1 -2
- package/dist/{credential-sync-DKWNdcgm.js → credential-sync-B-CPX_ag.js} +89 -52
- package/dist/{db-Ca9Nt5ci.d.ts → db-BnRqQ7G5.d.ts} +1 -1
- package/dist/{executor-BroK9oKy.js → executor-RvG6kVe1.js} +58 -70
- package/dist/{experimental-Qn9rKLgx.js → experimental-BNK1l1jv.js} +2 -3
- package/dist/{history-9pO1Zd4Y.js → history-AY8rs-FR.js} +5 -9
- package/dist/{history-DvqWZlYX.d.ts → history-BgI33Kie.d.ts} +12 -20
- package/dist/{images-D0VZ5nv0.js → images-C2J_bj2g.js} +5 -6
- package/dist/instrumentation.d.ts +1 -3
- package/dist/instrumentation.js +6 -8
- package/dist/{job-queue-BbGg3gvu.js → job-queue-DVe37it2.js} +6 -12
- package/dist/{logging-tPQeUe2i.js → logging-DsHONhVm.js} +54 -24
- package/dist/{message-DdPGRYQP.js → message-DyitL8no.js} +5 -6
- package/dist/{model-profile-BwU8mqwD.js → model-profile-Bg8EoCKD.js} +2 -4
- package/dist/nitro.d.ts +3 -5
- package/dist/nitro.js +12 -17
- package/dist/{oauth-authorization-BA4Dt6a7.js → oauth-authorization-djQn5Nhy.js} +2 -4
- package/dist/{oauth-callback-server-B-2errCX.js → oauth-callback-server-CDby-pCn.js} +3 -4
- package/dist/{oauth-relay-CdCnu80W.js → oauth-relay-CTygMkNx.js} +2 -3
- package/dist/{package-discovery-BVvSnQBB.js → package-discovery-DJBompdd.js} +17 -21
- package/dist/{pending-auth-DT3MRVW8.js → pending-auth-D8J1uJUS.js} +37 -28
- package/dist/{plugin-module-CVvEUSMH.js → plugin-module-Cf5gVPWP.js} +1 -2
- package/dist/{plugins-CjfUP5gJ.d.ts → plugins-B11Mfkmv.d.ts} +0 -4
- package/dist/{plugins-egKWjgue.js → plugins-DOm9Y6qI.js} +2 -3
- package/dist/{private-trace-result-BLvBjy_e.js → private-trace-result-CE7_lEdq.js} +1 -2
- package/dist/{profile-Bb1zCVGP.js → profile-DpYjfKke.js} +16 -28
- package/dist/{prompt-C04mP1hu.js → prompt-xgpyMtg7.js} +5 -9
- package/dist/{queue-B5SnqRln.d.ts → queue-C37RbDVc.d.ts} +0 -2
- package/dist/{registry-CsNavQ_Z.js → registry-Du566MtJ.js} +57 -30
- package/dist/{resolve-B6ZZ9duG.js → resolve-ClCIgh8E.js} +7 -12
- package/dist/{result-j9nS0se-.js → result-BBbKA61B.js} +13 -17
- package/dist/{rolldown-runtime-B4FMCO8f.js → rolldown-runtime-nwUcYXjP.js} +1 -2
- package/dist/{runner-Y331DoZ5.js → runner-CCuqFSqx.js} +12 -13
- package/dist/{sandbox-egress-signals-BUfjEdHY.js → sandbox-egress-signals-DY1mbKF4.js} +2 -3
- package/dist/{schema-BBgv0MNf.js → schema-DrMh3NtR.js} +17 -27
- package/dist/{sentry-CgWZM_kV.js → sentry-BBUlHih9.js} +2 -3
- package/dist/{session-stop-q-6GNHx6.js → session-stop-CIoFiwwe.js} +57 -41
- package/dist/{skills-CyvTsR9M.js → skills-DyQBLIrK.js} +9 -9
- package/dist/{spawn-Do5mX3d_.js → spawn-Dqfeo7XP.js} +4 -5
- package/dist/{store-DoewL5mG.d.ts → store-CG0VJyye.d.ts} +0 -14
- package/dist/{store-D2It0ozF.js → store-DJLwq4x8.js} +13 -15
- package/dist/{task-queue-ZMk9bLRT.js → task-queue-BzW4MaWU.js} +5 -7
- package/dist/{task-runner-cIGU5VwJ.js → task-runner-D3qJh4fK.js} +22 -26
- package/dist/{tasks-CODtRzvX.js → tasks-CEXKTASS.js} +78 -79
- package/dist/{thread-state-BPkwVmvK.js → thread-state-W3f6kAtz.js} +13 -19
- package/dist/{turn-failure-response-ChmMeKar.js → turn-failure-response-CWx7aNIh.js} +16 -22
- package/dist/{turn-lifecycle-BUP7ZOVa.js → turn-lifecycle-C9JFst7-.js} +2 -3
- package/dist/{turn-session-routing-BcWM3PLm.js → turn-session-routing-CUxtfpr7.js} +2 -3
- package/dist/{validation-DCd69-cP.js → validation-BHRxa5G4.js} +3 -4
- package/dist/vercel.d.ts +1 -3
- package/dist/vercel.js +1 -2
- package/dist/{version-DBc4Qt_P.js → version-C0U6rHg0.js} +3 -5
- package/dist/{version-CnXZ614E.d.ts → version-Cc2OFd9G.d.ts} +0 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -2
- package/dist/{viewer-C-9nFKYr.js → viewer-CGvvRqKp.js} +61 -44
- package/dist/{web-input-Cv42P3ni.js → web-input-Keb5GRWj.js} +139 -198
- package/dist/{web-message-id-xPI_EFSC.js → web-message-id-CJSUiksE.js} +3 -6
- package/dist/{work-6UmVQq1V.js → work-DRcsJ3hV.js} +14 -16
- package/dist/{worker-DAoBZuZn.js → worker-Cjo-QqPq.js} +7 -8
- package/migrations/0059_automation_run_version.sql +2 -0
- package/migrations/0060_identity_signed_in_at.sql +1 -0
- package/migrations/meta/0059_snapshot.json +3619 -0
- package/migrations/meta/0060_snapshot.json +3465 -0
- package/migrations/meta/_journal.json +15 -1
- 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-
|
|
2
|
-
import { C as
|
|
3
|
-
import { n as captureMessage, r as continueTrace } from "./sentry-
|
|
4
|
-
import { t as JUNIOR_VERSION } from "./version-
|
|
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-
|
|
6
|
-
import { _ as slackMessageTsSchema, d as parseSlackTeamId, f as parseSlackUserId, h as slackUserIdSchema, m as slackTeamIdSchema, o as parseActorUserId, p as slackChannelIdSchema } from "./actor-
|
|
7
|
-
import { c as getConversationEventStore, d as getDb, f as getSqlExecutor,
|
|
8
|
-
import { a as juniorAgentInvocations, d as juniorDestinations, i as juniorAgentBindings, s as juniorConversations } from "./agent-invocations-
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { n as
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import { M as
|
|
27
|
-
import { o as
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import { a as
|
|
33
|
-
import {
|
|
34
|
-
import { t as
|
|
35
|
-
import { t as
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: {
|
|
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: {
|
|
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
|
-
|
|
346
|
-
//#region src/chat/event-automations/runs.ts
|
|
353
|
+
// src/chat/event-automations/runs.ts
|
|
347
354
|
/**
|
|
348
|
-
* Record a dispatched event automation run
|
|
349
|
-
* exists. The dispatch can finish before
|
|
350
|
-
* retry of the same Event, keeps the row it
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1936
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2170
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4948
|
-
|
|
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
|
-
|
|
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
|
|
5339
|
-
|
|
5340
|
-
|
|
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 };
|