@sentry/junior 0.257.0 → 0.259.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/adapter-Bn0gFVSF.js +270 -0
- package/dist/{admin-DgIkm6Vy.js → admin-C8xz2f10.js} +2 -3
- package/dist/{agent-hooks-BjtTzvru.js → agent-hooks-B00FaKdP.js} +70 -109
- package/dist/{agent-invocations-Bdu3ROfo.js → agent-invocations-Qfz7dWgU.js} +6 -12
- package/dist/{agent-runner-D7OboVb3.js → agent-runner-CPSd7rKm.js} +324 -742
- package/dist/api/schema.d.ts +442 -447
- package/dist/api/schema.js +6 -6
- package/dist/api.d.ts +2 -12
- package/dist/api.js +165 -158
- package/dist/{app-BsU5QNBd.d.ts → app-Bbxrehjf.d.ts} +25 -45
- package/dist/app.d.ts +3 -3
- package/dist/app.js +206 -321
- package/dist/{assistant-reply-CbTPebzL.js → assistant-reply-CqthTerd.js} +3 -5
- package/dist/{automation-CHdlHVZP.js → automation-DWBFBenG.js} +6 -11
- package/dist/{briefs-oLOldnDM.js → briefs-DNYbQ9cD.js} +8 -11
- package/dist/{cadence-D00SCzT1.js → cadence-R7Em4RjE.js} +1 -2
- package/dist/{catalog-runtime-CZzhGQ7F.js → catalog-runtime-B8SuFPGb.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 +8 -5
- package/dist/cli/plugins.d.ts +2 -4
- package/dist/cli/plugins.js +7 -8
- package/dist/cli/run.d.ts +3 -3
- package/dist/cli/run.js +3 -3
- package/dist/cli/snapshot-create.d.ts +1 -3
- package/dist/cli/snapshot-create.js +5 -5
- package/dist/cli/upgrade.d.ts +3 -6
- package/dist/cli/upgrade.js +11 -17
- package/dist/{client-D8HnkOQX.js → client-BZdxgKyL.js} +64 -18
- package/dist/{config-CJRnWK9H.js → config-Deqpfpg9.js} +21 -15
- package/dist/{connections-ClYrKzpX.js → connections-9gJkfS85.js} +179 -20
- package/dist/{conversation-CKNLYBJt.js → conversation-DjN3mlog.js} +8 -15
- package/dist/{conversation-privacy-C8b1nphZ.js → conversation-privacy-CFhNKxp-.js} +2 -3
- package/dist/{conversation-work-C6y3T5Hx.js → conversation-work-BaT_T6EB.js} +1 -2
- package/dist/{credential-sync-BXfohNmJ.js → credential-sync-BABGymK3.js} +23 -190
- package/dist/{db-Ca9Nt5ci.d.ts → db-BnRqQ7G5.d.ts} +1 -1
- package/dist/{executor-Do0THDRR.js → executor-Bb3eQb8q.js} +30 -52
- package/dist/{experimental-CQIvMUO7.js → experimental-BNK1l1jv.js} +2 -4
- 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-BN_BiBXF.js → images-BbSdVHC6.js} +5 -6
- package/dist/instrumentation.d.ts +1 -3
- package/dist/instrumentation.js +6 -8
- package/dist/{job-queue-B6SV9uVa.js → job-queue-vdGU-5ge.js} +6 -12
- package/dist/{logging-CWxiIuCF.js → logging-BStXN56b.js} +7 -12
- package/dist/{message-TUOdbSBW.js → message-DRf6m4uO.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-Dj5UJODo.js → oauth-callback-server-JmOgLcGl.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-s_QFribU.js → pending-auth-rG5U_QS1.js} +92 -83
- package/dist/{plugin-module-CVvEUSMH.js → plugin-module-Cf5gVPWP.js} +1 -2
- package/dist/{plugins-CjfUP5gJ.d.ts → plugins-B_0Cr0MO.d.ts} +5 -5
- 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-h-72wM78.js → profile-qBM6B_-V.js} +14 -295
- package/dist/{prompt-DyoEj9c2.js → prompt-xgpyMtg7.js} +5 -9
- package/dist/{queue-B5SnqRln.d.ts → queue-C37RbDVc.d.ts} +0 -2
- package/dist/{registry-sLTr-sV9.js → registry-B_M_x_iS.js} +56 -30
- package/dist/{resolve-ncQPZROQ.js → resolve-BzpuLh8c.js} +8 -12
- package/dist/{result-C883vngT.js → result-DEZH_AnZ.js} +13 -17
- package/dist/{rolldown-runtime-B4FMCO8f.js → rolldown-runtime-nwUcYXjP.js} +1 -2
- package/dist/{runner-CzYImdOF.js → runner-BqQlG10t.js} +12 -13
- package/dist/{sandbox-egress-signals-CZbMINHI.js → sandbox-egress-signals-DCq3WLs5.js} +2 -3
- package/dist/{schema-Dy69D4Ge.js → schema-CF9HqNRY.js} +30 -27
- package/dist/{sentry-CgWZM_kV.js → sentry-BBUlHih9.js} +2 -3
- package/dist/service-account-BqTBkyD7.js +120 -0
- package/dist/service-account-BzhW7kXu.js +661 -0
- package/dist/{session-stop-CQjKd4a2.js → session-stop-CV65ZlU3.js} +23 -33
- package/dist/{skills-C22SLruK.js → skills-CiWS5_Ub.js} +9 -9
- package/dist/{spawn-DY9CLXDL.js → spawn-BSAQ7UQZ.js} +4 -5
- package/dist/{store-DXFIQy-G.js → store-B1GES4gC.js} +13 -15
- package/dist/{store-DoewL5mG.d.ts → store-CG0VJyye.d.ts} +0 -14
- package/dist/{task-queue-B17vV0La.js → task-queue-BAeGEMjJ.js} +5 -7
- package/dist/{task-runner-DUjvzYXj.js → task-runner-BlGhcQPi.js} +22 -26
- package/dist/{tasks-D_WIJtvO.js → tasks-DieKX3d4.js} +63 -78
- package/dist/{thread-state-Ch4yuCqG.js → thread-state-DpSWS4Rr.js} +13 -19
- package/dist/{turn-failure-response-C5nBKC4S.js → turn-failure-response-DQljogGJ.js} +16 -22
- package/dist/{turn-lifecycle-CO-ylUmV.js → turn-lifecycle-BLgrkV_L.js} +2 -3
- package/dist/{turn-session-routing-B3LWooK5.js → turn-session-routing-BlajheOU.js} +2 -3
- package/dist/{validation-CXYxaNYx.js → validation-23d7K9tX.js} +3 -4
- package/dist/vercel.d.ts +1 -3
- package/dist/vercel.js +1 -2
- package/dist/{version-BcdZy2zg.js → version-B_XumRx-.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-DSZXygMU.js → viewer-B2_ABsYi.js} +26 -57
- package/dist/{web-input-B9Bv0_lR.js → web-input-BZDyxpzR.js} +101 -158
- package/dist/{web-message-id-xPI_EFSC.js → web-message-id-CJSUiksE.js} +3 -6
- package/dist/{work-nAfDSUFP.js → work-D3QKiV0O.js} +14 -16
- package/dist/{worker-vGXZ6XFT.js → worker-C8mUjYf9.js} +7 -8
- package/package.json +10 -9
package/dist/app.js
CHANGED
|
@@ -1,40 +1,39 @@
|
|
|
1
|
-
import { At as
|
|
2
|
-
import { f as
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { _ as
|
|
7
|
-
import { _ as
|
|
8
|
-
import {
|
|
9
|
-
import { a as
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { n as
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import { n as
|
|
26
|
-
import { a as
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import { a as
|
|
34
|
-
import {
|
|
35
|
-
import { t as
|
|
36
|
-
import { t as
|
|
37
|
-
import { t as bindSpawnAgent } from "./spawn-DY9CLXDL.js";
|
|
1
|
+
import { At as parseSandboxEgressCredentialToken, B as effectiveTaskOutcomes, Ct as resolveSandboxEgressProviderForHost, Dt as clearSandboxEgressSignals, E as requestAttachmentDeletion, Et as SANDBOX_EGRESS_PROXY_PATH, F as logScheduledAutomationRunSkipped, Ft as startOAuthFlow, Gt as getConfigDefaults, I as scheduledAutomationRunAttributes, It as createUserTokenStore, Kt as setConfigDefaults, Mt as formatProviderLabel, O as parseSlackThreadId, Ot as consumeSandboxEgressAuthRequiredSignal, Pt as resolveBaseUrl, St as ingestEvent, Tt as shouldPropagateSandboxEgressTrace, U as stopConversationSession, W as recordSubagentEnded, b as unpublishedArtifactHeaders, i as executeCredentialedEgressRequest, kt as consumeSandboxEgressPermissionDeniedSignal, lt as listPendingAgentInvocationMailboxAppends, n as executeAgentRun, t as createAgentRunner, ut as listPendingAgentInvocationResults, v as publicArtifactHeaders, w as collectAttachmentGarbage, wt as normalizeSandboxEgressTracePropagationDomains, x as lookupSlackUserProfile, xt as processWorkspaceSnapshotJob, y as readPublicArtifact } from "./agent-runner-CPSd7rKm.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-BStXN56b.js";
|
|
3
|
+
import { n as captureMessage, r as continueTrace } from "./sentry-BBUlHih9.js";
|
|
4
|
+
import { t as JUNIOR_VERSION } from "./version-B_XumRx-.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-Deqpfpg9.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-B2_ABsYi.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-Bb3eQb8q.js";
|
|
10
|
+
import { m as credentialLogContext } from "./registry-B_M_x_iS.js";
|
|
11
|
+
import { n as pluginCatalogRuntime } from "./catalog-runtime-B8SuFPGb.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-DieKX3d4.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-B00FaKdP.js";
|
|
14
|
+
import { W as annotationCard } from "./conversation-DjN3mlog.js";
|
|
15
|
+
import { l as getThreadStopDecision, o as SubscribedReplyReason } from "./pending-auth-rG5U_QS1.js";
|
|
16
|
+
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-CV65ZlU3.js";
|
|
17
|
+
import { r as getStateAdapter, s as withLock$1 } from "./adapter-Bn0gFVSF.js";
|
|
18
|
+
import { n as getNextRunAtMs } from "./cadence-R7Em4RjE.js";
|
|
19
|
+
import { f as setSandboxResourceConfig, u as getSandboxResourceConfig } from "./resolve-BzpuLh8c.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-vdGU-5ge.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-B1GES4gC.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-23d7K9tX.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-BZDyxpzR.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-BlGhcQPi.js";
|
|
28
|
+
import { o as getAuthPausedConversationTurnId, t as enqueueAgentInvocationResult } from "./result-DEZH_AnZ.js";
|
|
29
|
+
import { r as enqueueAgentInvocation } from "./work-D3QKiV0O.js";
|
|
30
|
+
import { s as coerceThreadConversationState } from "./thread-state-DpSWS4Rr.js";
|
|
31
|
+
import { o as unlinkProvider } from "./connections-9gJkfS85.js";
|
|
32
|
+
import { a as GET$4, i as publishAppHomeView, r as GET$5, t as receiveLocalOAuthCredential } from "./credential-sync-BABGymK3.js";
|
|
33
|
+
import { a as registerVercelPluginTaskDevConsumer, n as createVercelPluginTaskCallback, s as queueCallback } from "./task-queue-BAeGEMjJ.js";
|
|
34
|
+
import { t as processConversationWork } from "./worker-C8mUjYf9.js";
|
|
35
|
+
import { t as bindMessageAgent } from "./message-DRf6m4uO.js";
|
|
36
|
+
import { t as bindSpawnAgent } from "./spawn-BSAQ7UQZ.js";
|
|
38
37
|
import { createRequire } from "node:module";
|
|
39
38
|
import { Hono } from "hono";
|
|
40
39
|
import { z } from "zod";
|
|
@@ -48,7 +47,7 @@ import { ChannelImpl, ThreadImpl } from "chat";
|
|
|
48
47
|
import * as acp from "@agentclientprotocol/sdk";
|
|
49
48
|
import { createAuthMiddleware } from "better-auth/api";
|
|
50
49
|
import { betterAuth } from "better-auth/minimal";
|
|
51
|
-
|
|
50
|
+
// src/chat/events/timers.ts
|
|
52
51
|
/** Publish a bounded batch of due timers; the mailbox owns agent execution. */
|
|
53
52
|
async function runTimerWatchHeartbeat(args) {
|
|
54
53
|
const errors = [];
|
|
@@ -67,8 +66,7 @@ async function runTimerWatchHeartbeat(args) {
|
|
|
67
66
|
}
|
|
68
67
|
if (errors.length) throw new AggregateError(errors, "Timer Watch delivery failed");
|
|
69
68
|
}
|
|
70
|
-
|
|
71
|
-
//#region src/chat/task-execution/heartbeat.ts
|
|
69
|
+
// src/chat/task-execution/heartbeat.ts
|
|
72
70
|
const DEFAULT_RECOVERY_LIMIT = 25;
|
|
73
71
|
function heartbeatIdempotencyKey(reason, conversationId, nowMs) {
|
|
74
72
|
return `heartbeat:${reason}:${conversationId}:${nowMs}`;
|
|
@@ -167,8 +165,7 @@ async function recoverConversationWork(args) {
|
|
|
167
165
|
});
|
|
168
166
|
return result;
|
|
169
167
|
}
|
|
170
|
-
|
|
171
|
-
//#region src/chat/agent-dispatch/validation.ts
|
|
168
|
+
// src/chat/agent-dispatch/validation.ts
|
|
172
169
|
function hasIssueAtPath(issues, path) {
|
|
173
170
|
return issues.some((issue) => issue.path.length === path.length && issue.path.every((value, index) => value === path[index]));
|
|
174
171
|
}
|
|
@@ -218,8 +215,7 @@ async function verifyDispatchCredentialSubjectAccess(options, plugin) {
|
|
|
218
215
|
subject: options.credentialSubject
|
|
219
216
|
}))) throw new Error("Dispatch credentialSubject is not valid for this action");
|
|
220
217
|
}
|
|
221
|
-
|
|
222
|
-
//#region src/chat/agent-dispatch/context.ts
|
|
218
|
+
// src/chat/agent-dispatch/context.ts
|
|
223
219
|
const MAX_DISPATCHES_PER_HEARTBEAT = 25;
|
|
224
220
|
function bindDispatchCredentialSubject(options, plugin) {
|
|
225
221
|
const { credentialSubject, ...baseOptions } = options;
|
|
@@ -307,7 +303,11 @@ async function dispatchEventAutomation(args) {
|
|
|
307
303
|
...unboundOptions,
|
|
308
304
|
...outcomes !== void 0 ? { outcomes } : void 0,
|
|
309
305
|
...boundSubject ? { credentialSubject: boundSubject } : void 0,
|
|
310
|
-
source: {
|
|
306
|
+
source: {
|
|
307
|
+
kind: "event_automation",
|
|
308
|
+
automationId: args.automationId,
|
|
309
|
+
automationRunId: buildDispatchId(plugin, unboundOptions.idempotencyKey)
|
|
310
|
+
}
|
|
311
311
|
};
|
|
312
312
|
return await dispatch({
|
|
313
313
|
conversationWorkQueue: args.conversationWorkQueue,
|
|
@@ -330,7 +330,11 @@ async function dispatchScheduledAutomation(args) {
|
|
|
330
330
|
...unboundOptions,
|
|
331
331
|
...outcomes !== void 0 ? { outcomes } : void 0,
|
|
332
332
|
...boundSubject ? { credentialSubject: boundSubject } : void 0,
|
|
333
|
-
source: {
|
|
333
|
+
source: {
|
|
334
|
+
kind: "scheduled_automation",
|
|
335
|
+
automationId: args.automationId,
|
|
336
|
+
automationRunId: unboundOptions.idempotencyKey
|
|
337
|
+
}
|
|
334
338
|
};
|
|
335
339
|
return await dispatch({
|
|
336
340
|
conversationWorkQueue: args.conversationWorkQueue,
|
|
@@ -346,8 +350,7 @@ async function getScheduledAutomationDispatch(id) {
|
|
|
346
350
|
id
|
|
347
351
|
});
|
|
348
352
|
}
|
|
349
|
-
|
|
350
|
-
//#region src/chat/event-automations/runs.ts
|
|
353
|
+
// src/chat/event-automations/runs.ts
|
|
351
354
|
/**
|
|
352
355
|
* Record a dispatched event automation run with the Automation version it ran,
|
|
353
356
|
* and link its Conversation when it exists. The dispatch can finish before
|
|
@@ -386,8 +389,7 @@ async function settleEventAutomationRuns(db, nowMs) {
|
|
|
386
389
|
});
|
|
387
390
|
}
|
|
388
391
|
}
|
|
389
|
-
|
|
390
|
-
//#region src/chat/automations/reply-attribution.ts
|
|
392
|
+
// src/chat/automations/reply-attribution.ts
|
|
391
393
|
/** Name the Automation in the footer of the replies that its runs post. */
|
|
392
394
|
function automationReplyAttribution(title) {
|
|
393
395
|
const detail = (title ?? "").replace(/\s+/g, " ").trim().slice(0, 128).trim();
|
|
@@ -396,8 +398,7 @@ function automationReplyAttribution(title) {
|
|
|
396
398
|
detail
|
|
397
399
|
} : { label: "Automation" };
|
|
398
400
|
}
|
|
399
|
-
|
|
400
|
-
//#region src/chat/scheduled-automations/runs.ts
|
|
401
|
+
// src/chat/scheduled-automations/runs.ts
|
|
401
402
|
const PENDING_CLAIM_STALE_MS = 6e4;
|
|
402
403
|
const MISSED_RUN_MAX_AGE_MS = 864e5;
|
|
403
404
|
const INCOMPLETE_RUN_STATUSES = ["pending", "running"];
|
|
@@ -722,8 +723,7 @@ async function advanceScheduledAutomationAfterRun(db, args) {
|
|
|
722
723
|
return args.status === "blocked";
|
|
723
724
|
});
|
|
724
725
|
}
|
|
725
|
-
|
|
726
|
-
//#region src/chat/scheduled-automations/heartbeat.ts
|
|
726
|
+
// src/chat/scheduled-automations/heartbeat.ts
|
|
727
727
|
const SCHEDULED_AUTOMATION_HEARTBEAT_LIMIT = 10;
|
|
728
728
|
function singleLineMetadataValue(value) {
|
|
729
729
|
return value.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
|
|
@@ -954,6 +954,7 @@ async function runScheduledAutomationHeartbeat(args) {
|
|
|
954
954
|
let dispatch;
|
|
955
955
|
try {
|
|
956
956
|
dispatch = await dispatchScheduledAutomation({
|
|
957
|
+
automationId: task.id,
|
|
957
958
|
conversationWorkQueue: args.conversationWorkQueue,
|
|
958
959
|
nowMs: args.nowMs,
|
|
959
960
|
options: {
|
|
@@ -997,8 +998,7 @@ async function runScheduledAutomationHeartbeat(args) {
|
|
|
997
998
|
}
|
|
998
999
|
return dispatchCount;
|
|
999
1000
|
}
|
|
1000
|
-
|
|
1001
|
-
//#region src/chat/agent-dispatch/heartbeat.ts
|
|
1001
|
+
// src/chat/agent-dispatch/heartbeat.ts
|
|
1002
1002
|
const DEFAULT_PLUGIN_LIMIT = 25;
|
|
1003
1003
|
const PLUGIN_HEARTBEAT_TIMEOUT_MS = 25e3;
|
|
1004
1004
|
const DISPATCH_MAILBOX_APPEND_LIMIT = 100;
|
|
@@ -1132,8 +1132,7 @@ async function runHeartbeat(args) {
|
|
|
1132
1132
|
nowMs: args.nowMs
|
|
1133
1133
|
});
|
|
1134
1134
|
}
|
|
1135
|
-
|
|
1136
|
-
//#region src/handlers/heartbeat.ts
|
|
1135
|
+
// src/handlers/heartbeat.ts
|
|
1137
1136
|
function getHeartbeatSecret() {
|
|
1138
1137
|
return process.env.JUNIOR_SCHEDULER_SECRET?.trim() || process.env.CRON_SECRET?.trim();
|
|
1139
1138
|
}
|
|
@@ -1158,8 +1157,7 @@ async function GET$2(request, waitUntil, options = {}) {
|
|
|
1158
1157
|
}));
|
|
1159
1158
|
return new Response("Accepted", { status: 202 });
|
|
1160
1159
|
}
|
|
1161
|
-
|
|
1162
|
-
//#region src/chat/conversations/sql/purge.ts
|
|
1160
|
+
// src/chat/conversations/sql/purge.ts
|
|
1163
1161
|
/** Discover a root and its current descendants via `parent_conversation_id`. */
|
|
1164
1162
|
async function discoverConversationTree(executor, root) {
|
|
1165
1163
|
const all = /* @__PURE__ */ new Map([[root.conversationId, root]]);
|
|
@@ -1318,8 +1316,7 @@ async function purgeConversationTree(executor, args) {
|
|
|
1318
1316
|
});
|
|
1319
1317
|
});
|
|
1320
1318
|
}
|
|
1321
|
-
|
|
1322
|
-
//#region src/chat/conversations/retention.ts
|
|
1319
|
+
// src/chat/conversations/retention.ts
|
|
1323
1320
|
/**
|
|
1324
1321
|
* Content retention policy and the bounded purge job.
|
|
1325
1322
|
*
|
|
@@ -1395,8 +1392,7 @@ async function runRetentionPurge(executor, args) {
|
|
|
1395
1392
|
attachments: attachmentResult.deleted
|
|
1396
1393
|
};
|
|
1397
1394
|
}
|
|
1398
|
-
|
|
1399
|
-
//#region src/handlers/retention.ts
|
|
1395
|
+
// src/handlers/retention.ts
|
|
1400
1396
|
function getRetentionSecret() {
|
|
1401
1397
|
return process.env.JUNIOR_SCHEDULER_SECRET?.trim() || process.env.CRON_SECRET?.trim();
|
|
1402
1398
|
}
|
|
@@ -1427,8 +1423,7 @@ async function GET$1(request, options = {}) {
|
|
|
1427
1423
|
return new Response("Retention purge failed", { status: 500 });
|
|
1428
1424
|
}
|
|
1429
1425
|
}
|
|
1430
|
-
|
|
1431
|
-
//#region src/chat/sandbox/egress/oidc.ts
|
|
1426
|
+
// src/chat/sandbox/egress/oidc.ts
|
|
1432
1427
|
const OIDC_DISCOVERY_CACHE_TTL_MS = 36e5;
|
|
1433
1428
|
const OIDC_DISCOVERY_CACHE_MAX_ENTRIES = 8;
|
|
1434
1429
|
const jwksByIssuer = /* @__PURE__ */ new Map();
|
|
@@ -1480,8 +1475,7 @@ async function verifyVercelSandboxOidcToken(token, options = {}) {
|
|
|
1480
1475
|
...options.currentDate ? { currentDate: options.currentDate } : void 0
|
|
1481
1476
|
})).payload;
|
|
1482
1477
|
}
|
|
1483
|
-
|
|
1484
|
-
//#region src/chat/sandbox/egress/proxy.ts
|
|
1478
|
+
// src/chat/sandbox/egress/proxy.ts
|
|
1485
1479
|
const OIDC_TOKEN_HEADER = "vercel-sandbox-oidc-token";
|
|
1486
1480
|
const FORWARDED_HOST_HEADER = "vercel-forwarded-host";
|
|
1487
1481
|
const FORWARDED_SCHEME_HEADER = "vercel-forwarded-scheme";
|
|
@@ -1736,8 +1730,7 @@ async function proxySandboxEgressRequest(request, deps = {}) {
|
|
|
1736
1730
|
upstreamUrl
|
|
1737
1731
|
}));
|
|
1738
1732
|
}
|
|
1739
|
-
|
|
1740
|
-
//#region src/handlers/sandbox-egress-proxy.ts
|
|
1733
|
+
// src/handlers/sandbox-egress-proxy.ts
|
|
1741
1734
|
/** Handles Vercel Sandbox firewall egress proxy requests. */
|
|
1742
1735
|
async function ALL(request, options = {}) {
|
|
1743
1736
|
return await proxySandboxEgressRequest(request, {
|
|
@@ -1750,8 +1743,7 @@ async function ALL(request, options = {}) {
|
|
|
1750
1743
|
function isSandboxEgressRequest(request) {
|
|
1751
1744
|
return isSandboxEgressForwardedRequest(request);
|
|
1752
1745
|
}
|
|
1753
|
-
|
|
1754
|
-
//#region src/handlers/sandbox-egress-route.ts
|
|
1746
|
+
// src/handlers/sandbox-egress-route.ts
|
|
1755
1747
|
/**
|
|
1756
1748
|
* Route authenticated sandbox egress proxy traffic before ordinary app routes.
|
|
1757
1749
|
*/
|
|
@@ -1759,8 +1751,7 @@ async function handleSandboxEgressRoute(request, tracePropagationDomains, next)
|
|
|
1759
1751
|
if (isSandboxEgressRequest(request)) return await ALL(request, { tracePropagation: { domains: tracePropagationDomains } });
|
|
1760
1752
|
await next();
|
|
1761
1753
|
}
|
|
1762
|
-
|
|
1763
|
-
//#region src/handlers/sandbox-egress-signals.ts
|
|
1754
|
+
// src/handlers/sandbox-egress-signals.ts
|
|
1764
1755
|
const SANDBOX_EGRESS_TOKEN_HEADER = "x-junior-sandbox-egress-token";
|
|
1765
1756
|
function isLoopbackDevelopmentRequest(request) {
|
|
1766
1757
|
const hostname = new URL(request.url).hostname;
|
|
@@ -1785,8 +1776,7 @@ async function GET(request) {
|
|
|
1785
1776
|
...permissionDenied ? { permission_denied: permissionDenied } : void 0
|
|
1786
1777
|
});
|
|
1787
1778
|
}
|
|
1788
|
-
|
|
1789
|
-
//#region src/chat/slack/annotation-details.ts
|
|
1779
|
+
// src/chat/slack/annotation-details.ts
|
|
1790
1780
|
const eventSchema$1 = z.object({
|
|
1791
1781
|
trigger_id: z.string().min(1),
|
|
1792
1782
|
user: z.string().min(1),
|
|
@@ -1864,8 +1854,7 @@ async function presentSlackAnnotationDetails(event, teamId) {
|
|
|
1864
1854
|
metadata: slackEntitySchema.parse(entity)
|
|
1865
1855
|
});
|
|
1866
1856
|
}
|
|
1867
|
-
|
|
1868
|
-
//#region src/chat/ingress/slack-payload.ts
|
|
1857
|
+
// src/chat/ingress/slack-payload.ts
|
|
1869
1858
|
const eventTimestampSchema = slackMessageTsSchema.refine((value) => Number.isFinite((/* @__PURE__ */ new Date(Number(value) * 1e3)).getTime()));
|
|
1870
1859
|
const slackSlashCommandSchema = z.object({
|
|
1871
1860
|
channel_id: z.string().trim().pipe(slackChannelIdSchema),
|
|
@@ -1962,8 +1951,7 @@ const slackInteractivePayloadSchema = z.object({
|
|
|
1962
1951
|
team_id: z.string().optional()
|
|
1963
1952
|
}).optional()
|
|
1964
1953
|
});
|
|
1965
|
-
|
|
1966
|
-
//#region src/chat/ingress/slash-command.ts
|
|
1954
|
+
// src/chat/ingress/slash-command.ts
|
|
1967
1955
|
async function postEphemeral(event, text) {
|
|
1968
1956
|
await event.channel.postEphemeral(event.userId, text, { fallbackToDM: false });
|
|
1969
1957
|
}
|
|
@@ -2024,8 +2012,7 @@ async function handleSlashCommand(event) {
|
|
|
2024
2012
|
if (subcommand === "link") await handleLink(event, normalized);
|
|
2025
2013
|
else await handleUnlink(event, normalized);
|
|
2026
2014
|
}
|
|
2027
|
-
|
|
2028
|
-
//#region src/chat/ingress/slack-form.ts
|
|
2015
|
+
// src/chat/ingress/slack-form.ts
|
|
2029
2016
|
function parseJson$1(body) {
|
|
2030
2017
|
try {
|
|
2031
2018
|
return JSON.parse(body);
|
|
@@ -2130,8 +2117,7 @@ async function handleSlackForm(args) {
|
|
|
2130
2117
|
})));
|
|
2131
2118
|
return new Response("", { status: 200 });
|
|
2132
2119
|
}
|
|
2133
|
-
|
|
2134
|
-
//#region src/chat/ingress/bot-mention.ts
|
|
2120
|
+
// src/chat/ingress/bot-mention.ts
|
|
2135
2121
|
/**
|
|
2136
2122
|
* Detect Slack bot @mentions that should activate Junior.
|
|
2137
2123
|
*
|
|
@@ -2208,8 +2194,7 @@ function textMentionsBot(text, botUserId) {
|
|
|
2208
2194
|
const id = escapeRegExp(botUserId);
|
|
2209
2195
|
return new RegExp(`<@${id}(?:\\|[^>]+)?>`).test(outside);
|
|
2210
2196
|
}
|
|
2211
|
-
|
|
2212
|
-
//#region src/chat/event-automations/ingest.ts
|
|
2197
|
+
// src/chat/event-automations/ingest.ts
|
|
2213
2198
|
/**
|
|
2214
2199
|
* Owns event matching and durable event-automation dispatch.
|
|
2215
2200
|
*
|
|
@@ -2266,7 +2251,8 @@ async function ingestEventAutomations(input, options) {
|
|
|
2266
2251
|
},
|
|
2267
2252
|
extra: {
|
|
2268
2253
|
"app.event.event_type": event.eventType,
|
|
2269
|
-
"app.event_automation.
|
|
2254
|
+
"app.event_automation.max_runs": botConfig.maxEventAutomationRuns,
|
|
2255
|
+
"app.event_automation.run_window_ms": botConfig.eventAutomationRunWindowMs,
|
|
2270
2256
|
"app.event_automation.resource_type": task.trigger.resourceType,
|
|
2271
2257
|
"app.task.id": task.id
|
|
2272
2258
|
}
|
|
@@ -2292,6 +2278,7 @@ async function ingestEventAutomations(input, options) {
|
|
|
2292
2278
|
taskId: task.id
|
|
2293
2279
|
} : void 0;
|
|
2294
2280
|
const dispatch = await dispatchEventAutomation({
|
|
2281
|
+
automationId: task.id,
|
|
2295
2282
|
conversationWorkQueue: options.queue,
|
|
2296
2283
|
nowMs,
|
|
2297
2284
|
options: {
|
|
@@ -2317,8 +2304,7 @@ async function ingestEventAutomations(input, options) {
|
|
|
2317
2304
|
if (errors.length > 0) throw new AggregateError(errors, "Failed to dispatch one or more event automations");
|
|
2318
2305
|
return { dispatched };
|
|
2319
2306
|
}
|
|
2320
|
-
|
|
2321
|
-
//#region src/chat/ingress/workspace-membership.ts
|
|
2307
|
+
// src/chat/ingress/workspace-membership.ts
|
|
2322
2308
|
const MEMBER_TTL_MS = 3e5;
|
|
2323
2309
|
const NON_MEMBER_TTL_MS = 3e4;
|
|
2324
2310
|
const authorTeamSchema = z.object({
|
|
@@ -2364,13 +2350,14 @@ async function isSlackWorkspaceMember(raw, state) {
|
|
|
2364
2350
|
if (!result.acquired) throw new Error("Could not acquire Slack membership lookup lock");
|
|
2365
2351
|
return result.value;
|
|
2366
2352
|
}
|
|
2367
|
-
|
|
2368
|
-
//#region src/chat/sign-in/allowlist.ts
|
|
2369
|
-
/** Google accounts that may sign in. The dashboard uses the same list. */
|
|
2353
|
+
// src/chat/sign-in/allowlist.ts
|
|
2370
2354
|
/**
|
|
2371
|
-
*
|
|
2372
|
-
*
|
|
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.
|
|
2373
2359
|
*/
|
|
2360
|
+
/** Read a list env var: a JSON string array or comma-separated values. */
|
|
2374
2361
|
function readEnvList(name) {
|
|
2375
2362
|
const value = process.env[name]?.trim();
|
|
2376
2363
|
if (!value) return void 0;
|
|
@@ -2386,150 +2373,50 @@ function readEnvList(name) {
|
|
|
2386
2373
|
return list.data;
|
|
2387
2374
|
}
|
|
2388
2375
|
/**
|
|
2389
|
-
* Resolve the
|
|
2390
|
-
*
|
|
2376
|
+
* Resolve the allowed domains: the `createApp` option when set, else
|
|
2377
|
+
* `JUNIOR_ALLOWED_DOMAINS`. An empty list allows every domain.
|
|
2391
2378
|
*/
|
|
2392
|
-
function
|
|
2393
|
-
|
|
2394
|
-
return {
|
|
2395
|
-
domains: list(options.allowedGoogleDomains, "JUNIOR_DASHBOARD_GOOGLE_DOMAINS"),
|
|
2396
|
-
emails: list(options.allowedEmails, "JUNIOR_DASHBOARD_ALLOWED_EMAILS")
|
|
2397
|
-
};
|
|
2379
|
+
function resolveAllowedDomains(allowedDomains) {
|
|
2380
|
+
return [...new Set((allowedDomains ?? readEnvList("JUNIOR_ALLOWED_DOMAINS") ?? []).map((value) => value.trim().toLowerCase()).filter(Boolean))];
|
|
2398
2381
|
}
|
|
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
2382
|
/**
|
|
2407
|
-
*
|
|
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.
|
|
2383
|
+
* Fail startup on the removed Google-specific names. Ignoring them would
|
|
2384
|
+
* leave the list empty, which lets every Slack workspace member in.
|
|
2414
2385
|
*/
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
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
|
-
}
|
|
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;
|
|
2440
2398
|
/**
|
|
2441
|
-
*
|
|
2442
|
-
*
|
|
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.
|
|
2443
2402
|
*/
|
|
2444
|
-
async function
|
|
2445
|
-
|
|
2446
|
-
const
|
|
2447
|
-
if (!
|
|
2448
|
-
const
|
|
2449
|
-
const
|
|
2450
|
-
|
|
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;
|
|
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;
|
|
2502
2410
|
const profile = await lookupSlackUserProfile(userId);
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
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
|
|
2532
|
-
//#region src/chat/ingress/slack-message-telemetry.ts
|
|
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
|
|
2533
2420
|
function field(value, pattern) {
|
|
2534
2421
|
if (value === void 0) return "missing";
|
|
2535
2422
|
return typeof value === "string" && value.length <= 64 && pattern.test(value) ? value : "invalid";
|
|
@@ -2553,8 +2440,7 @@ function slackMessageAttributes(body) {
|
|
|
2553
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"
|
|
2554
2441
|
};
|
|
2555
2442
|
}
|
|
2556
|
-
|
|
2557
|
-
//#region src/chat/slack/automation-details.ts
|
|
2443
|
+
// src/chat/slack/automation-details.ts
|
|
2558
2444
|
function textField(key, label, value) {
|
|
2559
2445
|
return {
|
|
2560
2446
|
key,
|
|
@@ -2655,8 +2541,7 @@ async function presentSlackAutomationDetails(event, teamId) {
|
|
|
2655
2541
|
metadata: slackEntitySchema.parse(entity)
|
|
2656
2542
|
});
|
|
2657
2543
|
}
|
|
2658
|
-
|
|
2659
|
-
//#region src/chat/ingress/slack-webhook.ts
|
|
2544
|
+
// src/chat/ingress/slack-webhook.ts
|
|
2660
2545
|
function slackEventLogContext(event) {
|
|
2661
2546
|
const channelId = event?.channel?.trim() || void 0;
|
|
2662
2547
|
const threadTs = event?.thread_ts?.trim() || event?.ts?.trim() || void 0;
|
|
@@ -2850,7 +2735,7 @@ async function handleSlackThreadStop(args) {
|
|
|
2850
2735
|
recordSkippedConversationMessage({
|
|
2851
2736
|
conversation,
|
|
2852
2737
|
message: args.message,
|
|
2853
|
-
skippedReason:
|
|
2738
|
+
skippedReason: `${SubscribedReplyReason.ThreadOptOut}:${args.stopReason}`,
|
|
2854
2739
|
text: content.text
|
|
2855
2740
|
});
|
|
2856
2741
|
await appendConversationMessages(getConversationEventStore(), {
|
|
@@ -2951,14 +2836,19 @@ async function routeParsedMessage(args) {
|
|
|
2951
2836
|
});
|
|
2952
2837
|
return;
|
|
2953
2838
|
}
|
|
2954
|
-
if (route === "mention" && !await
|
|
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;
|
|
2955
2845
|
if (isSubscribed && !isExperimentalFeatureEnabled("passive-routing")) {
|
|
2956
2846
|
const content = parseContent(message);
|
|
2957
2847
|
const conversation = coerceThreadConversationState(void 0);
|
|
2958
2848
|
recordSkippedConversationMessage({
|
|
2959
2849
|
conversation,
|
|
2960
2850
|
message,
|
|
2961
|
-
skippedReason:
|
|
2851
|
+
skippedReason: `${SubscribedReplyReason.PassiveDisabled}:passive-routing`,
|
|
2962
2852
|
text: content.text
|
|
2963
2853
|
});
|
|
2964
2854
|
await appendConversationMessages(getConversationEventStore(), {
|
|
@@ -3068,7 +2958,7 @@ async function handleSlackEvent(args) {
|
|
|
3068
2958
|
conversationStore: args.services.conversationStore,
|
|
3069
2959
|
queue: args.services.queue,
|
|
3070
2960
|
receivedAtMs,
|
|
3071
|
-
|
|
2961
|
+
allowedDomains: args.services.allowedDomains ?? [],
|
|
3072
2962
|
state
|
|
3073
2963
|
}), {
|
|
3074
2964
|
keepAlive: true,
|
|
@@ -3126,8 +3016,7 @@ async function handleSlackWebhook(args) {
|
|
|
3126
3016
|
}
|
|
3127
3017
|
return new Response("ok", { status: 200 });
|
|
3128
3018
|
}
|
|
3129
|
-
|
|
3130
|
-
//#region src/handlers/webhooks.ts
|
|
3019
|
+
// src/handlers/webhooks.ts
|
|
3131
3020
|
/** Run a platform webhook with shared request tracing and error logging. */
|
|
3132
3021
|
async function handleWebhookRequest(request, platform, run) {
|
|
3133
3022
|
const requestContext = createRequestContext(request, { platform });
|
|
@@ -3168,8 +3057,7 @@ async function handleWebhookRequest(request, platform, run) {
|
|
|
3168
3057
|
}
|
|
3169
3058
|
});
|
|
3170
3059
|
}
|
|
3171
|
-
|
|
3172
|
-
//#region src/handlers/slack-webhook.ts
|
|
3060
|
+
// src/handlers/slack-webhook.ts
|
|
3173
3061
|
/** Handle the production Slack webhook route. */
|
|
3174
3062
|
async function POST(request, waitUntil, services) {
|
|
3175
3063
|
return handleWebhookRequest(request, "slack", () => handleSlackWebhook({
|
|
@@ -3267,8 +3155,7 @@ function createVercelConversationWorkCallback(options) {
|
|
|
3267
3155
|
function registerVercelConversationWorkDevConsumer(options) {
|
|
3268
3156
|
return conversationWorkCallback(options).registerDev();
|
|
3269
3157
|
}
|
|
3270
|
-
|
|
3271
|
-
//#region src/chat/sandbox/snapshot/job-callback.ts
|
|
3158
|
+
// src/chat/sandbox/snapshot/job-callback.ts
|
|
3272
3159
|
const WORKSPACE_SNAPSHOT_JOB_DEV_CONSUMER_GROUP = "junior_workspace_snapshots_dev";
|
|
3273
3160
|
function logWorkspaceSnapshotJobRejected(reason, metadata) {
|
|
3274
3161
|
logWarn("workspace.snapshot.job.queue_message.rejected", {
|
|
@@ -3297,8 +3184,7 @@ function createVercelWorkspaceSnapshotJobCallback() {
|
|
|
3297
3184
|
function registerVercelWorkspaceSnapshotJobDevConsumer() {
|
|
3298
3185
|
return workspaceSnapshotJobCallback().registerDev();
|
|
3299
3186
|
}
|
|
3300
|
-
|
|
3301
|
-
//#region src/handlers/artifacts.ts
|
|
3187
|
+
// src/handlers/artifacts.ts
|
|
3302
3188
|
/** Serve one content-addressed public artifact without authentication. */
|
|
3303
3189
|
async function publicArtifactGET(args) {
|
|
3304
3190
|
try {
|
|
@@ -3317,8 +3203,7 @@ async function publicArtifactGET(args) {
|
|
|
3317
3203
|
return new Response("Internal Server Error", { status: 500 });
|
|
3318
3204
|
}
|
|
3319
3205
|
}
|
|
3320
|
-
|
|
3321
|
-
//#region src/chat/events/workspace.ts
|
|
3206
|
+
// src/chat/events/workspace.ts
|
|
3322
3207
|
/**
|
|
3323
3208
|
* Resolve the single-bot Slack team id for event-automation indexing and
|
|
3324
3209
|
* Event people mentions.
|
|
@@ -3342,8 +3227,7 @@ function createEventTeamIdResolver() {
|
|
|
3342
3227
|
return await pending;
|
|
3343
3228
|
};
|
|
3344
3229
|
}
|
|
3345
|
-
|
|
3346
|
-
//#region src/chat/events/app-publisher.ts
|
|
3230
|
+
// src/chat/events/app-publisher.ts
|
|
3347
3231
|
/** Build the core event publisher used by plugin routes. */
|
|
3348
3232
|
function createEventAppPublisher(args) {
|
|
3349
3233
|
const resolveEventAutomationTeamId = createEventTeamIdResolver();
|
|
@@ -3391,8 +3275,7 @@ function createEventAppPublisher(args) {
|
|
|
3391
3275
|
}
|
|
3392
3276
|
};
|
|
3393
3277
|
}
|
|
3394
|
-
|
|
3395
|
-
//#region src/api/acp/conversations.ts
|
|
3278
|
+
// src/api/acp/conversations.ts
|
|
3396
3279
|
const EVENT_PAGE_SIZE = 50;
|
|
3397
3280
|
function actorFromUser(user) {
|
|
3398
3281
|
return webActorFromEmail(user.email, user.displayName ? { fullName: user.displayName } : void 0);
|
|
@@ -3528,8 +3411,7 @@ function createAcpConversations(options) {
|
|
|
3528
3411
|
}
|
|
3529
3412
|
};
|
|
3530
3413
|
}
|
|
3531
|
-
|
|
3532
|
-
//#region src/api/acp/state.ts
|
|
3414
|
+
// src/api/acp/state.ts
|
|
3533
3415
|
const MUTATION_LOCK_TTL_MS = 1e4;
|
|
3534
3416
|
/** Run one ACP state mutation while its shared lock remains owned. */
|
|
3535
3417
|
async function withLock(state, key, run, options = {}) {
|
|
@@ -3558,8 +3440,7 @@ async function withLock(state, key, run, options = {}) {
|
|
|
3558
3440
|
async function fenceLock(state, lock, ttlMs = MUTATION_LOCK_TTL_MS) {
|
|
3559
3441
|
if (!await state.extendLock(lock, ttlMs)) throw new Error(`ACP lock ownership was lost before write for ${lock.threadId}`);
|
|
3560
3442
|
}
|
|
3561
|
-
|
|
3562
|
-
//#region src/api/acp/schema.ts
|
|
3443
|
+
// src/api/acp/schema.ts
|
|
3563
3444
|
/** Validate the ACP wire messages that Junior supports. */
|
|
3564
3445
|
const ACP_SESSION_ID_PATTERN = /^local:acp:[a-f0-9]{32}$/;
|
|
3565
3446
|
const metaSchema = z.record(z.string(), z.unknown()).nullable();
|
|
@@ -3655,8 +3536,7 @@ const cancelParamsSchema = z.object({
|
|
|
3655
3536
|
_meta: metaSchema.optional(),
|
|
3656
3537
|
sessionId: acpSessionIdSchema
|
|
3657
3538
|
}).strict();
|
|
3658
|
-
|
|
3659
|
-
//#region src/api/acp/sleep.ts
|
|
3539
|
+
// src/api/acp/sleep.ts
|
|
3660
3540
|
/** Wait for a duration and reject when the ACP request stops. */
|
|
3661
3541
|
function sleep(ms, signal) {
|
|
3662
3542
|
return new Promise((resolve, reject) => {
|
|
@@ -3675,8 +3555,7 @@ function sleep(ms, signal) {
|
|
|
3675
3555
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
3676
3556
|
});
|
|
3677
3557
|
}
|
|
3678
|
-
|
|
3679
|
-
//#region src/api/acp/transport.ts
|
|
3558
|
+
// src/api/acp/transport.ts
|
|
3680
3559
|
/**
|
|
3681
3560
|
* Short-lived ACP transport state.
|
|
3682
3561
|
*
|
|
@@ -4247,8 +4126,7 @@ function createSseBody(args) {
|
|
|
4247
4126
|
}
|
|
4248
4127
|
});
|
|
4249
4128
|
}
|
|
4250
|
-
|
|
4251
|
-
//#region src/api/acp/auth.ts
|
|
4129
|
+
// src/api/acp/auth.ts
|
|
4252
4130
|
/** Own ACP browser authorization records and connection binding. */
|
|
4253
4131
|
const ACP_AUTHORIZATION_TTL_MS = 6e5;
|
|
4254
4132
|
const ACP_CONNECTION_COOKIE = "junior_acp_connection";
|
|
@@ -4587,8 +4465,7 @@ async function expireAcpAuthorization(args) {
|
|
|
4587
4465
|
if (!result.acquired) return false;
|
|
4588
4466
|
return result.value;
|
|
4589
4467
|
}
|
|
4590
|
-
|
|
4591
|
-
//#region src/api/acp/route.ts
|
|
4468
|
+
// src/api/acp/route.ts
|
|
4592
4469
|
/**
|
|
4593
4470
|
* Own the remote ACP v1 Streamable HTTP edge.
|
|
4594
4471
|
*
|
|
@@ -5026,8 +4903,7 @@ function createAcpHttpHandler(options) {
|
|
|
5026
4903
|
}
|
|
5027
4904
|
};
|
|
5028
4905
|
}
|
|
5029
|
-
|
|
5030
|
-
//#region src/api/acp/authorization-page.ts
|
|
4906
|
+
// src/api/acp/authorization-page.ts
|
|
5031
4907
|
const ACP_AUTHORIZATION_PATH = "/_junior/acp/auth/:transactionId";
|
|
5032
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;
|
|
5033
4909
|
function escapeHtml(value) {
|
|
@@ -5151,17 +5027,39 @@ async function handleAcpAuthorizationPage(args) {
|
|
|
5151
5027
|
});
|
|
5152
5028
|
return renderResult(args.agentName, result);
|
|
5153
5029
|
}
|
|
5154
|
-
|
|
5155
|
-
//#region src/chat/sign-in/auth.ts
|
|
5030
|
+
// src/chat/sign-in/auth.ts
|
|
5156
5031
|
/**
|
|
5157
|
-
* Google sign-in session
|
|
5032
|
+
* Google sign-in session for the dashboard.
|
|
5158
5033
|
*
|
|
5159
5034
|
* Better Auth runs the Google OAuth flow and keeps the browser session in an
|
|
5160
|
-
* encrypted cookie.
|
|
5161
|
-
*
|
|
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.
|
|
5162
5038
|
*/
|
|
5163
5039
|
const DEFAULT_AUTH_PATH = "/api/auth";
|
|
5164
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
|
+
}
|
|
5165
5063
|
function trimSlashes(value, leading) {
|
|
5166
5064
|
let start = 0;
|
|
5167
5065
|
let end = value.length;
|
|
@@ -5173,14 +5071,12 @@ function withHttps(host) {
|
|
|
5173
5071
|
return /^https?:\/\//.test(host) ? host : `https://${host}`;
|
|
5174
5072
|
}
|
|
5175
5073
|
/** 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);
|
|
5074
|
+
function resolveSignInAuthConfig(agentName, allowedDomains, options = {}) {
|
|
5179
5075
|
return {
|
|
5180
5076
|
agentName,
|
|
5181
|
-
|
|
5182
|
-
authPath,
|
|
5183
|
-
baseURL,
|
|
5077
|
+
allowedDomains,
|
|
5078
|
+
authPath: `/${trimSlashes(options.authPath?.trim() || DEFAULT_AUTH_PATH, true)}`,
|
|
5079
|
+
baseURL: trimSlashes(withHttps(options.baseURL?.trim() || resolveBaseUrl() || "http://localhost:3000"), false),
|
|
5184
5080
|
sessionMaxAgeSeconds: options.sessionMaxAgeSeconds,
|
|
5185
5081
|
trustedOrigins: options.trustedOrigins ?? readEnvList("JUNIOR_DASHBOARD_TRUSTED_ORIGINS") ?? []
|
|
5186
5082
|
};
|
|
@@ -5192,8 +5088,8 @@ function required(value, name) {
|
|
|
5192
5088
|
function createBetterAuth(config) {
|
|
5193
5089
|
const secret = required(config.secret ?? process.env.BETTER_AUTH_SECRET ?? process.env.JUNIOR_SECRET, "JUNIOR_SECRET or BETTER_AUTH_SECRET");
|
|
5194
5090
|
const sessionMaxAgeSeconds = config.sessionMaxAgeSeconds ?? DEFAULT_SESSION_MAX_AGE_SECONDS;
|
|
5195
|
-
const {
|
|
5196
|
-
const hostedDomain =
|
|
5091
|
+
const { allowedDomains } = config;
|
|
5092
|
+
const hostedDomain = allowedDomains.length === 0 ? void 0 : allowedDomains.length === 1 ? allowedDomains[0] : "*";
|
|
5197
5093
|
return betterAuth({
|
|
5198
5094
|
appName: config.agentName,
|
|
5199
5095
|
baseURL: config.baseURL,
|
|
@@ -5205,12 +5101,20 @@ function createBetterAuth(config) {
|
|
|
5205
5101
|
clientSecret: required(config.googleClientSecret ?? process.env.GOOGLE_CLIENT_SECRET, "GOOGLE_CLIENT_SECRET"),
|
|
5206
5102
|
hd: hostedDomain,
|
|
5207
5103
|
prompt: "select_account",
|
|
5208
|
-
|
|
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;
|
|
5209
5109
|
return {
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
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
|
|
5214
5118
|
};
|
|
5215
5119
|
}
|
|
5216
5120
|
} },
|
|
@@ -5233,7 +5137,7 @@ function createBetterAuth(config) {
|
|
|
5233
5137
|
const created = ctx.context.newSession;
|
|
5234
5138
|
if (!created || !ctx.path.startsWith("/callback/")) return;
|
|
5235
5139
|
const email = normalizeIdentityEmail(created.user.email);
|
|
5236
|
-
if (!created.user.emailVerified || !email
|
|
5140
|
+
if (!created.user.emailVerified || !email) return;
|
|
5237
5141
|
const google = (await ctx.context.internalAdapter.findAccounts(created.user.id)).find((account) => account.providerId === "google");
|
|
5238
5142
|
if (!google) throw new Error("Google sign-in has no Google account");
|
|
5239
5143
|
await recordGoogleSignIn(getSqlExecutor(), {
|
|
@@ -5245,8 +5149,8 @@ function createBetterAuth(config) {
|
|
|
5245
5149
|
});
|
|
5246
5150
|
}
|
|
5247
5151
|
/**
|
|
5248
|
-
* Create the
|
|
5249
|
-
*
|
|
5152
|
+
* Create the Google sign-in session. Better Auth starts on first use, so apps
|
|
5153
|
+
* that never sign in need no Google credentials.
|
|
5250
5154
|
*/
|
|
5251
5155
|
function createSignInAuth(config) {
|
|
5252
5156
|
let auth;
|
|
@@ -5284,14 +5188,14 @@ function createSignInAuth(config) {
|
|
|
5284
5188
|
}
|
|
5285
5189
|
};
|
|
5286
5190
|
}
|
|
5287
|
-
|
|
5288
|
-
//#region src/dashboard-routes.ts
|
|
5191
|
+
// src/dashboard-routes.ts
|
|
5289
5192
|
const DASHBOARD_PACKAGE_NAME = "@sentry/junior-dashboard";
|
|
5290
5193
|
/** Mount the dashboard app on the dashboard-owned host paths. */
|
|
5291
5194
|
async function createDashboardRouteRegistrations(args) {
|
|
5292
5195
|
if (!args.dashboard || args.dashboard.disabled) return [];
|
|
5293
5196
|
const createDashboardApp = args.createDashboardApp ?? await loadDashboardAppFactory();
|
|
5294
5197
|
return dashboardRouteRegistrations({
|
|
5198
|
+
allowedDomains: args.allowedDomains,
|
|
5295
5199
|
auth: args.auth,
|
|
5296
5200
|
authenticatedRoutes: args.authenticatedRoutes,
|
|
5297
5201
|
conversationWorkQueue: args.conversationWorkQueue,
|
|
@@ -5393,6 +5297,8 @@ function dashboardHostRoutePaths(dashboard, authenticatedRoutes = []) {
|
|
|
5393
5297
|
"/api/people/*",
|
|
5394
5298
|
"/api/personal-tokens",
|
|
5395
5299
|
"/api/personal-tokens/*",
|
|
5300
|
+
"/api/service-accounts",
|
|
5301
|
+
"/api/service-accounts/*",
|
|
5396
5302
|
"/api/workspaces",
|
|
5397
5303
|
"/api/workspaces/*",
|
|
5398
5304
|
"/api/config",
|
|
@@ -5415,7 +5321,7 @@ function routePatternMatchesConcretePath(pattern, concretePath) {
|
|
|
5415
5321
|
const patternSegments = routeSegments(pattern);
|
|
5416
5322
|
const pathSegments = routeSegments(concretePath);
|
|
5417
5323
|
for (let index = 0; index < patternSegments.length; index += 1) {
|
|
5418
|
-
const segment = patternSegments[index];
|
|
5324
|
+
const segment = patternSegments[index] ?? "";
|
|
5419
5325
|
if (segment === "**" || segment === "*") return true;
|
|
5420
5326
|
const value = pathSegments[index];
|
|
5421
5327
|
if (!value || !routeSegmentMatches(segment, value)) return false;
|
|
@@ -5441,6 +5347,7 @@ function dashboardRouteRegistrations(args) {
|
|
|
5441
5347
|
const dashboardOptions = {
|
|
5442
5348
|
...args.dashboard,
|
|
5443
5349
|
agentName: botConfig.userName,
|
|
5350
|
+
allowedDomains: args.allowedDomains,
|
|
5444
5351
|
...args.auth ? { auth: args.auth } : void 0,
|
|
5445
5352
|
authenticatedRoutes: args.authenticatedRoutes,
|
|
5446
5353
|
conversationWorkQueue: args.conversationWorkQueue,
|
|
@@ -5460,8 +5367,7 @@ function validateDashboardRouteOwnership(args) {
|
|
|
5460
5367
|
if (!args.dashboard || args.dashboard.disabled) return;
|
|
5461
5368
|
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`);
|
|
5462
5369
|
}
|
|
5463
|
-
|
|
5464
|
-
//#region src/app.ts
|
|
5370
|
+
// src/app.ts
|
|
5465
5371
|
/** Resolve the public deployment URL used by ACP browser authorization. */
|
|
5466
5372
|
function resolveAcpBaseURL(dashboard) {
|
|
5467
5373
|
const configured = dashboard?.baseURL?.trim() || process.env.JUNIOR_BASE_URL?.trim();
|
|
@@ -5579,7 +5485,7 @@ async function createApp(options) {
|
|
|
5579
5485
|
}) });
|
|
5580
5486
|
let sandboxEgressTracePropagationDomains = [];
|
|
5581
5487
|
let signInConfig;
|
|
5582
|
-
let
|
|
5488
|
+
let allowedDomains = [];
|
|
5583
5489
|
try {
|
|
5584
5490
|
sandboxEgressTracePropagationDomains = normalizeSandboxEgressTracePropagationDomains(options?.sandbox?.egressTracePropagationDomains);
|
|
5585
5491
|
setSandboxResourceConfig(options?.sandbox);
|
|
@@ -5592,18 +5498,10 @@ async function createApp(options) {
|
|
|
5592
5498
|
setRuntimeLimits(options?.limits);
|
|
5593
5499
|
setSlackReactionConfig(options?.slack ?? {});
|
|
5594
5500
|
if (options?.slack?.crossActorMidRunMode) setCrossActorMidRunMode(options.slack.crossActorMidRunMode);
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
if (
|
|
5598
|
-
|
|
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
|
-
}
|
|
5501
|
+
assertNoLegacyAllowedDomains(Object.keys(dashboard ?? {}));
|
|
5502
|
+
allowedDomains = resolveAllowedDomains(options?.allowedDomains);
|
|
5503
|
+
if (allowedDomains.length === 0) logWarn("app.allowed_domains.unset", { "app.reason": "allowedDomains is empty, so any Slack workspace member can use Junior" });
|
|
5504
|
+
if (dashboard && !dashboard.disabled) signInConfig = resolveSignInAuthConfig(botConfig.userName, allowedDomains, dashboard);
|
|
5607
5505
|
if (shouldValidatePluginCatalog) {
|
|
5608
5506
|
pluginCatalogRuntime.getSignature();
|
|
5609
5507
|
validatePluginRegistrations(configuredPlugins?.registrations ?? []);
|
|
@@ -5697,7 +5595,7 @@ async function createApp(options) {
|
|
|
5697
5595
|
queue: conversationWorkQueue,
|
|
5698
5596
|
services: runtimeServiceOverrides
|
|
5699
5597
|
}),
|
|
5700
|
-
|
|
5598
|
+
allowedDomains
|
|
5701
5599
|
};
|
|
5702
5600
|
const app = new Hono();
|
|
5703
5601
|
app.onError((err, c) => {
|
|
@@ -5714,6 +5612,7 @@ async function createApp(options) {
|
|
|
5714
5612
|
], "/api/acp", (c) => getAcpRuntime().handleRequest(c.req.raw));
|
|
5715
5613
|
mountRoutes(app, pluginRoutes);
|
|
5716
5614
|
mountRoutes(app, await createDashboardRouteRegistrations({
|
|
5615
|
+
allowedDomains,
|
|
5717
5616
|
auth: signInAuth,
|
|
5718
5617
|
authenticatedRoutes,
|
|
5719
5618
|
conversationWorkQueue,
|
|
@@ -5736,19 +5635,6 @@ async function createApp(options) {
|
|
|
5736
5635
|
app.get("/api/oauth/callback/:provider", (c) => {
|
|
5737
5636
|
return GET$5(c.req.raw, c.req.param("provider"), waitUntil, { conversationWorkQueue });
|
|
5738
5637
|
});
|
|
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
|
-
}
|
|
5752
5638
|
app.get("/api/internal/sandbox-egress/signals", (c) => {
|
|
5753
5639
|
return GET(c.req.raw);
|
|
5754
5640
|
});
|
|
@@ -5792,5 +5678,4 @@ async function createApp(options) {
|
|
|
5792
5678
|
});
|
|
5793
5679
|
return app;
|
|
5794
5680
|
}
|
|
5795
|
-
//#endregion
|
|
5796
5681
|
export { JUNIOR_VERSION, createApp, defineJuniorPlugins };
|