@sentry/junior 0.267.0 → 0.269.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/README.md +2 -9
- package/dist/{actor-DZSokpwj.js → actor-CP9K2Idu.js} +2 -1
- package/dist/{adapter-BybMEezj.js → adapter-2-CG25be.js} +3 -51
- package/dist/{admin-DOs1_EiW.js → admin-DSs8S5Uy.js} +1 -1
- package/dist/{agent-invocations-CxKldAeB.js → agent-invocations-C1RNOCs8.js} +2 -0
- package/dist/{agent-runner-DgNATN4D.js → agent-runner-CrDrPUtY.js} +3411 -1640
- package/dist/api/schema.d.ts +2 -5630
- package/dist/api/schema.js +7 -7
- package/dist/api.d.ts +3 -1
- package/dist/api.js +268 -287
- package/dist/{app-DAB3fzyp.d.ts → app-BrMMzjYm.d.ts} +10 -3
- package/dist/app.d.ts +1 -1
- package/dist/app.js +371 -223
- package/dist/{automation-CmeFBSEh.js → automation-p87hg_AV.js} +74 -74
- package/dist/{briefs-CWKjka7v.js → briefs-PtODdLNu.js} +4 -4
- package/dist/{catalog-runtime-cMlAI5LY.js → catalog-runtime-BsoSmTM0.js} +1 -1
- package/dist/cli/chat.js +16 -26
- package/dist/cli/check.js +3 -3
- package/dist/cli/main.js +5 -3
- package/dist/cli/plugins.js +4 -4
- package/dist/cli/snapshot-create.js +4 -4
- package/dist/cli/upgrade.d.ts +1 -1
- package/dist/cli/upgrade.js +3 -3
- package/dist/{client-BjgbMawj.js → client-Cqf4TboL.js} +20 -15
- package/dist/{config-CdB6yJj5.js → config-CPsE4T5L.js} +2 -2
- package/dist/{connections-RE_65jzK.js → connections-upFXLmhG.js} +10 -11
- package/dist/{conversation-DSo310Ps.js → conversation-CTpD_PGk.js} +7 -84
- package/dist/{conversation-privacy-BzgJC5k0.js → conversation-privacy-D6zJc8ff.js} +1 -1
- package/dist/{credential-sync-DhEGEpgq.js → credential-sync-CS_6IC-f.js} +30 -34
- package/dist/{credentialed-CIGwoLU2.js → credentialed-BU-OW7HP.js} +577 -74
- package/dist/{store-B8CABbmR.d.ts → db-BzOCzVb1.d.ts} +17 -7
- package/dist/{executor-VYrx_K4w.js → executor-CNS7ONt-.js} +78 -4
- package/dist/instrumentation.js +2 -2
- package/dist/{assistant-reply-CT7VcXQe.js → interruption-marker-g6Xup3Fk.js} +8 -2
- package/dist/{job-queue-C0BZSa7r.js → job-queue-DI-k-Bbk.js} +2 -2
- package/dist/{list-1XQP-o6K.js → list-CGoQdXTp.js} +429 -1401
- package/dist/locks-D-0h111u.js +48 -0
- package/dist/{logging-DuZ1A4ab.js → logging-n7knse-O.js} +1 -1
- package/dist/{history-C84mlAwO.js → media-85PDJxO_.js} +83 -1
- package/dist/{message-NKSbbnA6.js → message-BpaWd8TT.js} +4 -4
- package/dist/nitro.d.ts +1 -1
- package/dist/nitro.js +3 -3
- package/dist/{oauth-callback-server-qAHLPtml.js → oauth-callback-server-CpGmEw_i.js} +2 -2
- package/dist/{prompt-BW9jFsqZ.js → prompt-BlQB0QQt.js} +1 -1
- package/dist/{registry-FQutO926.js → registry-By36_xsm.js} +2 -2
- package/dist/reporting-schema-CZREhYc-.js +104 -0
- package/dist/reporting-window-CVarfId9.js +255 -0
- package/dist/{resolve-Bk4yeuHy.js → resolve-CUIBcdPd.js} +3 -3
- package/dist/{result-B9e9ATOU.js → result-BemsOMOO.js} +31 -12
- package/dist/{runner-C3S2ObH-.js → runner-BVok7PAA.js} +12 -11
- package/dist/{sandbox-egress-signals-DqpGzzMB.js → sandbox-egress-signals-BSUWQI95.js} +1 -1
- package/dist/{schema-X7Fj4fv1.js → schema-ChnFo5f2.js} +98 -6
- package/dist/schema-jK4-1u_H.d.ts +5927 -0
- package/dist/{service-account-Do30HUNv.js → service-account-DHvlLnZw.js} +4 -3
- package/dist/{service-account-BQasl-qq.js → service-account-hMmESDcr.js} +3 -3
- package/dist/{session-stop-DkOjt46_.js → session-stop-BQW_iFcd.js} +499 -23
- package/dist/{skills-CQHrwfOa.js → skills-DmJJIuLF.js} +2 -2
- package/dist/{spawn-BHuqN14f.js → spawn-ChCm46i-.js} +2 -2
- package/dist/{store-6EetVP-m.js → store-1DVhoKOL.js} +4 -4
- package/dist/{store-DvNZIsCr.js → store-DCo2n5iJ.js} +3 -3
- package/dist/{task-queue-B0DLgd2P.js → task-queue-HMC4QjFm.js} +3 -3
- package/dist/task-runner-cA6yjn_E.js +269 -0
- package/dist/{thread-state-DRmdJ2QN.js → thread-state-CHy8LS0C.js} +8 -18
- package/dist/{turn-failure-response-G11cs2gE.js → turn-failure-response-Dr7erLWs.js} +11 -16
- package/dist/{turn-lifecycle-OzLIwvi1.js → turn-lifecycle-bf2fIYi_.js} +1 -1
- package/dist/{tasks-0ilmuCux.js → turn-session-routing-BqO0qXLk.js} +959 -44
- package/dist/{validation-CxEmt1id.js → validation-DHR1ivfh.js} +1 -1
- package/dist/{version-Bapedn4A.js → version-CXCHLgWm.js} +1 -1
- package/dist/version.js +1 -1
- package/dist/{viewer-DrN3oC2X.js → viewer-DSbD6DF6.js} +503 -241
- package/dist/{work-DUQoNcqP.js → work-BE_qTYGi.js} +17 -36
- package/dist/{worker-D1l4Ugk0.js → worker-Cm9_sWXg.js} +15 -7
- package/migrations/0065_user_customization.sql +1 -0
- package/migrations/0066_review_items.sql +53 -0
- package/migrations/0067_review_item_dedupe_tenant.sql +2 -0
- package/migrations/0068_review_item_slack_cards.sql +10 -0
- package/migrations/0069_system_identity.sql +25 -0
- package/migrations/0070_review_item_foreign_keys.sql +5 -0
- package/migrations/AGENTS.md +17 -0
- package/migrations/meta/0065_snapshot.json +3511 -0
- package/migrations/meta/0066_snapshot.json +4099 -0
- package/migrations/meta/0067_snapshot.json +3921 -0
- package/migrations/meta/0068_snapshot.json +4176 -0
- package/migrations/meta/0069_snapshot.json +3979 -0
- package/migrations/meta/0070_snapshot.json +3997 -0
- package/migrations/meta/_journal.json +42 -0
- package/package.json +5 -5
- package/dist/db-Bg-zC3r5.d.ts +0 -5
- package/dist/history-BgI33Kie.d.ts +0 -78
- package/dist/images-CecjXAAz.js +0 -64
- package/dist/task-runner-B-PD_JOj.js +0 -1074
- package/dist/turn-session-routing-Dyut9l7Y.js +0 -76
- package/dist/user-input-request-BDFBdF8r.js +0 -116
- package/dist/web-message-id-CJSUiksE.js +0 -236
- package/migrations/README.md +0 -30
package/dist/app.js
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { _ as snapshotRuntimeConfig, d as restoreRuntimeConfig, f as setBotModelConfig, g as setSlackReactionConfig, h as setRuntimeLimits, i as getChatConfig, m as setProfiles, n as botConfig, o as getSlackBotToken, p as setCrossActorMidRunMode, u as resetRuntimeConfig } from "./config-
|
|
3
|
-
import { A as JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE, E as withSpan, N as JUNIOR_WORKSPACE_SNAPSHOT_JOB_CALLBACK_ROUTE, T as withLogContext, b as setSpanAttributes, h as logWarn, m as logInfo, p as logException, r as createRequestContext, w as withContext, z as normalizeIdentityEmail } from "./logging-
|
|
1
|
+
import { B as readScheduledAutomation, Fr as SubscribedReplyReason, G as blockEventAutomation, H as saveScheduledAutomationInLock, Hr as getExperimentalFeatures, Jn as hasRunnableConversationWork, K as collectEventAutomationMatchKeys, M as recordAutomationExecution, Mn as CONVERSATION_WORK_STALE_ENQUEUE_MS, R as listScheduledAutomationsForTeam, Rr as getThreadStopDecision, S as existingConversationId, U as scheduledRunId, Ur as isExperimentalFeatureEnabled, W as withScheduledAutomationLock, Wr as setExperimentalFeatures, Wt as appendConversationMessages, Xn as isInvalidConversationRecordError, Xr as setConfigDefaults, Xt as settleConversationTitleWork, Y as findMatchingEventAutomations, Yr as getConfigDefaults, a as confirmDispatchMailboxAppend, b as notifyAutomationBlocked, f as getPluginDispatchProjection, ht as eventGuidance, it as automationVersionOf, m as listPendingDispatchMailboxAppends, nt as eventAutomationRunLimitReason, o as createOrGetDispatch, p as isTerminalDispatchStatus, qt as projectConversationMessages, r as buildDispatchId, s as getDispatchConversationId, tt as admitEventAutomationRun, u as getDispatchRecord, v as markDispatchFailed } from "./turn-session-routing-BqO0qXLk.js";
|
|
2
|
+
import { _ as snapshotRuntimeConfig, d as restoreRuntimeConfig, f as setBotModelConfig, g as setSlackReactionConfig, h as setRuntimeLimits, i as getChatConfig, m as setProfiles, n as botConfig, o as getSlackBotToken, p as setCrossActorMidRunMode, u as resetRuntimeConfig } from "./config-CPsE4T5L.js";
|
|
3
|
+
import { A as JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE, E as withSpan, L as GOOGLE_IDENTITY_PROVIDER, N as JUNIOR_WORKSPACE_SNAPSHOT_JOB_CALLBACK_ROUTE, T as withLogContext, b as setSpanAttributes, h as logWarn, m as logInfo, p as logException, r as createRequestContext, w as withContext, z as normalizeIdentityEmail } from "./logging-n7knse-O.js";
|
|
4
4
|
import { n as captureMessage } from "./sentry-MDkYAVPV.js";
|
|
5
|
-
import { t as JUNIOR_VERSION } from "./version-
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { a as juniorAgentInvocations, d as juniorDestinations, i as juniorAgentBindings, s as juniorConversations } from "./agent-invocations-
|
|
9
|
-
import {
|
|
10
|
-
import { n as pluginCatalogRuntime } from "./catalog-runtime-
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
5
|
+
import { t as JUNIOR_VERSION } from "./version-CXCHLgWm.js";
|
|
6
|
+
import { f as parseSlackTeamId, g as slackMessageTsSchema, h as slackMessageTsFromMs, m as parseSlackMessageTs, n as createSlackActor, p as parseSlackUserId, s as parseActorUserId, u as parseSlackChannelId } from "./actor-CP9K2Idu.js";
|
|
7
|
+
import { C as getSqlExecutor, M as resolveActorIdentityId, O as withConversationMutationLock, R as recordGoogleSignIn, S as getDb, T as withConversationEventLock, _ as closeDb, g as createCache, i as readActorIdentity, l as resolveViewerUser, p as parseAvatarUrl, x as getConversationStore, y as getConversationEventStore } from "./viewer-DSbD6DF6.js";
|
|
8
|
+
import { a as juniorAgentInvocations, d as juniorDestinations, i as juniorAgentBindings, s as juniorConversations } from "./agent-invocations-C1RNOCs8.js";
|
|
9
|
+
import { c as juniorReviewItems, d as juniorAutomations, g as juniorConversationBriefs, l as juniorAutomationRuns, y as juniorConversationEvents } from "./executor-CNS7ONt-.js";
|
|
10
|
+
import { n as pluginCatalogRuntime } from "./catalog-runtime-BsoSmTM0.js";
|
|
11
|
+
import { A as verifySlackSignature, C as syncReviewItemSlackCards, Ct as processWorkspaceSnapshotJob, D as parseSlackReviewItemFormTarget, E as parseSlackReviewItemClick, J as readViewerAutomationSummary, L as logScheduledAutomationRunSkipped, O as parseSlackUserInputSubmission, Q as effectiveTaskOutcomes, R as scheduledAutomationRunAttributes, S as readSlackReviewItemCardConversationId, T as buildSlackReviewItemModal, _ as unpublishedArtifactHeaders, _t as recordSubagentEnded, ct as webActorFromEmail, dt as collectAttachmentGarbage, et as deliverReviewItemChange, g as readPublicArtifact, gt as stopConversationSession, h as publicArtifactHeaders, it as conversationTurnIdForMessage, j as parseSlackThreadId, k as runWithSlackInstallation, n as executeAgentRun, pt as requestAttachmentDeletion, rt as appendAndEnqueueWebMessage, st as recordWebConversationActivity, t as createAgentRunner, tt as renderReviewItemMessage, v as lookupSlackUserProfile, wt as ingestEvent, x as createSlackReviewItemShow } from "./agent-runner-CrDrPUtY.js";
|
|
12
|
+
import { At as slackEntitySchema, Bn as getDashboardTaskLink, Bt as clearSandboxEgressSignals, Cr as resolveRootVisibility, Dn as getSlackClient, Er as listConversationAnnotations, Fn as runWithWorkspaceTeamId, Ht as consumeSandboxEgressPermissionDeniedSignal, Ln as getDashboardConversationLink, M as canActOnReviewItem, Pn as getWorkspaceTeamId, S as dismissOpenReviewItems, Tt as postSlackEphemeralMessage, Vn as setDashboardConversationLinkOptions, Vt as consumeSandboxEgressAuthRequiredSignal, Wt as parseSandboxEgressCredentialToken, Xt as normalizeSandboxEgressTracePropagationDomains, _n as bindScheduledAutomationCredentialSubject, ar as createPluginLogger, bn as verifyScheduledAutomationCredentialSubject, c as getPluginRoutes, cn as collectEventMatchKeys, ct as proxyVercelSandboxEgressRequest, fn as findMatchingWatches, ft as resolveBaseUrl, gn as bindEventAutomationCredentialSubject, h as validatePlugins, hn as takeDueTimerWatches, jr as createPluginCache, jt as slackExternalRefIdSchema, kn as isDmChannel, m as setPlugins, n as createUserTokenStore, nr as createPluginState, or as readConversationAccessFromSql, p as getPlugins, s as getPluginApiRoutes, st as isVercelSandboxEgressRequest, v as setBriefsConfig, vn as bindSlackDirectCredentialSubject, vt as slackMention, wt as openSlackModal, xn as verifySlackDirectCredentialSubject, yn as verifyEventAutomationCredentialSubject } from "./credentialed-BU-OW7HP.js";
|
|
13
|
+
import { a as withLock, i as fenceLock, n as MUTATION_LOCK_TTL_MS } from "./locks-D-0h111u.js";
|
|
14
|
+
import { r as getStateAdapter, t as disconnectStateAdapter } from "./adapter-2-CG25be.js";
|
|
14
15
|
import { n as getNextRunAtMs } from "./cadence-R7Em4RjE.js";
|
|
15
|
-
import { D as conversationSurfaceSchema, I as annotationCard, T as conversationReportStatusSchema, h as conversationFeedQuerySchema } from "./conversation-
|
|
16
|
-
import { b as listPendingAgentInvocationMailboxAppends, x as listPendingAgentInvocationResults } from "./store-
|
|
17
|
-
import { S as renderTaskInput, d as getEventCatalog, f as SLACK_EVENT_NAMESPACE, g as slackChannelMessageRunsAutomation, h as slackChannelMessageReplyThreadTs, m as slackChannelMessageEvent } from "./session-stop-
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { n as validatePluginRegistrations, t as validatePluginEgressCredentialHooks } from "./validation-
|
|
16
|
+
import { D as conversationSurfaceSchema, I as annotationCard, T as conversationReportStatusSchema, h as conversationFeedQuerySchema } from "./conversation-CTpD_PGk.js";
|
|
17
|
+
import { b as listPendingAgentInvocationMailboxAppends, x as listPendingAgentInvocationResults } from "./store-1DVhoKOL.js";
|
|
18
|
+
import { E as renderSlackObjectCard, S as renderTaskInput, d as getEventCatalog, f as SLACK_EVENT_NAMESPACE, g as slackChannelMessageRunsAutomation, h as slackChannelMessageReplyThreadTs, m as slackChannelMessageEvent } from "./session-stop-BQW_iFcd.js";
|
|
19
|
+
import { m as buttonUserInputField } from "./reporting-window-CVarfId9.js";
|
|
20
|
+
import { c as setSandboxResourceConfig, o as getSandboxResourceConfig } from "./resolve-CUIBcdPd.js";
|
|
21
|
+
import { a as getVercelConversationWorkQueue, d as getQueueProvider, g as isConversationQueueMessageRejectedError, h as conversationQueueMessageSchema, i as verifyWorkspaceSnapshotJobMessage, o as resolveConversationWorkQueueTopic, s as verifyConversationQueueMessage, t as WORKSPACE_SNAPSHOT_JOB_QUEUE_TOPIC } from "./job-queue-DI-k-Bbk.js";
|
|
22
|
+
import { S as removeActiveConversation, _ as hasRunnableConversationWork$1, c as clearExpiredConversationLease, g as getConversationWorkState, h as getConversation, m as ensureConversationWake, r as appendAndEnqueueInboundMessage, v as listActiveConversationIds } from "./store-DCo2n5iJ.js";
|
|
23
|
+
import { M as scheduledRunSchema } from "./automation-p87hg_AV.js";
|
|
24
|
+
import { n as validatePluginRegistrations, t as validatePluginEgressCredentialHooks } from "./validation-DHR1ivfh.js";
|
|
24
25
|
import { a as pluginRuntimeRegistrationsFromPluginSet, n as pluginCatalogConfigFromEnv, r as pluginCatalogConfigFromPluginSet, t as defineJuniorPlugins } from "./plugins-DOm9Y6qI.js";
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import { a as
|
|
32
|
-
import {
|
|
33
|
-
import { t as
|
|
34
|
-
import { t as
|
|
35
|
-
import { t as
|
|
26
|
+
import { A as stopSlackThread, B as actOnReviewItem, C as isAllowedSignInEmail, D as buildSlackInboundMessage, E as recordSkippedConversationMessage, F as bindMessageAuthorIdentityId, H as readReviewItemForUser, I as startProcessingReaction, K as enqueueAgentDispatch, L as stripLeadingBotMention, M as withNormalizedThreadId, N as stopConversationTurn, O as clearSlackPendingReactions, P as THREAD_OPTOUT_ACK, R as parseContent, S as isAllowedDomain, T as resolveAllowedDomains, U as reviewItemChange, V as closeReviewItemsAnsweredByReply, W as getAttachmentStorage, _ as createProductionConversationWorkOptions, b as createSlackWebApiStatusSender, j as normalizeIncomingSlackThreadId, k as isSlackMessageStopped, q as GET$3, t as readConversationFeed, v as createProductionSlackWebhookServices, w as readEnvList, x as assertNoLegacyAllowedDomains, y as getProductionSlackAdapter, z as ReviewItemActionError } from "./list-CGoQdXTp.js";
|
|
27
|
+
import { n as enqueueAgentInvocationResult, o as getAuthPausedConversationTurnId } from "./result-BemsOMOO.js";
|
|
28
|
+
import { r as enqueueAgentInvocation } from "./work-BE_qTYGi.js";
|
|
29
|
+
import { o as coerceThreadConversationState } from "./thread-state-CHy8LS0C.js";
|
|
30
|
+
import { o as unlinkProvider } from "./connections-upFXLmhG.js";
|
|
31
|
+
import { a as GET$4, i as publishAppHomeView, r as GET$5, t as receiveLocalOAuthCredential } from "./credential-sync-CS_6IC-f.js";
|
|
32
|
+
import { a as pluginTaskQueueConsumer, c as queueCallback, n as createVercelPluginTaskCallback, o as registerVercelPluginTaskDevConsumer } from "./task-queue-HMC4QjFm.js";
|
|
33
|
+
import { t as processConversationWork } from "./worker-Cm9_sWXg.js";
|
|
34
|
+
import { t as bindMessageAgent } from "./message-BpaWd8TT.js";
|
|
35
|
+
import { t as bindSpawnAgent } from "./spawn-ChCm46i-.js";
|
|
36
|
+
import { t as processPluginTask } from "./task-runner-cA6yjn_E.js";
|
|
36
37
|
import { createRequire } from "node:module";
|
|
37
38
|
import { Hono } from "hono";
|
|
38
39
|
import { z } from "zod";
|
|
39
40
|
import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
40
|
-
import { and, asc, eq, inArray, isNotNull, isNull, lte, or, sql } from "drizzle-orm";
|
|
41
|
+
import { and, asc, eq, inArray, isNotNull, isNull, lte, ne, or, sql } from "drizzle-orm";
|
|
41
42
|
import { EVENT_SUMMARY_MAX_LENGTH, EVENT_TEXT_MAX_LENGTH, dispatchOptionsSchema, eventSchema, eventTriggerFiltersText, isSlackDestination, userSchema } from "@sentry/junior-plugin-api";
|
|
42
43
|
import { pathToFileURL } from "node:url";
|
|
43
|
-
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
44
44
|
import net from "node:net";
|
|
45
45
|
import { decodeJwt } from "jose";
|
|
46
46
|
import https from "node:https";
|
|
@@ -1227,6 +1227,12 @@ async function selectExpiredRoots(executor, args) {
|
|
|
1227
1227
|
where bindings.parent_conversation_id = tree.conversation_id
|
|
1228
1228
|
or bindings.child_conversation_id = tree.conversation_id
|
|
1229
1229
|
)
|
|
1230
|
+
or exists (
|
|
1231
|
+
select 1 from junior_review_items review_items
|
|
1232
|
+
where review_items.added_by_conversation_id = tree.conversation_id
|
|
1233
|
+
and review_items.status = 'open'
|
|
1234
|
+
and review_items.automation_id is null
|
|
1235
|
+
)
|
|
1230
1236
|
or (
|
|
1231
1237
|
${juniorDestinations.visibility} is distinct from 'public'
|
|
1232
1238
|
and (
|
|
@@ -1234,6 +1240,15 @@ async function selectExpiredRoots(executor, args) {
|
|
|
1234
1240
|
select 1 from junior_conversation_briefs briefs
|
|
1235
1241
|
where briefs.conversation_id = tree.conversation_id
|
|
1236
1242
|
)
|
|
1243
|
+
or exists (
|
|
1244
|
+
select 1 from junior_review_items review_items
|
|
1245
|
+
where review_items.added_by_conversation_id = tree.conversation_id
|
|
1246
|
+
and review_items.visibility = 'private'
|
|
1247
|
+
and (
|
|
1248
|
+
review_items.status <> 'open'
|
|
1249
|
+
or review_items.automation_id is null
|
|
1250
|
+
)
|
|
1251
|
+
)
|
|
1237
1252
|
or exists (
|
|
1238
1253
|
select 1 from junior_conversations metadata
|
|
1239
1254
|
where metadata.conversation_id = tree.conversation_id
|
|
@@ -1259,7 +1274,9 @@ async function selectExpiredRoots(executor, args) {
|
|
|
1259
1274
|
* event rows for the given conversation and every descendant, stamp
|
|
1260
1275
|
* `transcript_purged_at`, and — for non-public content — null the raw-payload
|
|
1261
1276
|
* metadata (`title`, `channel_name`, legacy actor JSON) so purged private
|
|
1262
|
-
* conversations keep only safe metadata.
|
|
1277
|
+
* conversations keep only safe metadata. Non-public content also loses its
|
|
1278
|
+
* Briefs and the Review items that it added. The metadata rows themselves
|
|
1279
|
+
* survive.
|
|
1263
1280
|
*/
|
|
1264
1281
|
async function purgeConversationTree(executor, args) {
|
|
1265
1282
|
return await withConversationMutationLock(executor, args.rootConversationId, async () => {
|
|
@@ -1304,9 +1321,17 @@ async function purgeConversationTree(executor, args) {
|
|
|
1304
1321
|
await executor.db().delete(juniorConversationEvents).where(inArray(juniorConversationEvents.conversationId, ids));
|
|
1305
1322
|
await executor.db().delete(juniorAgentInvocations).where(or(inArray(juniorAgentInvocations.parentConversationId, ids), inArray(juniorAgentInvocations.childConversationId, ids)));
|
|
1306
1323
|
await executor.db().delete(juniorAgentBindings).where(or(inArray(juniorAgentBindings.parentConversationId, ids), inArray(juniorAgentBindings.childConversationId, ids)));
|
|
1324
|
+
await dismissOpenReviewItems(executor.db(), {
|
|
1325
|
+
addedByConversationIds: ids,
|
|
1326
|
+
nowMs: args.nowMs,
|
|
1327
|
+
reason: "Conversation purged"
|
|
1328
|
+
});
|
|
1307
1329
|
await requestAttachmentDeletion(executor, ids, args.nowMs);
|
|
1308
1330
|
const scrubMetadata = args.retention ? !isPublic : resolvedScrubMetadata;
|
|
1309
|
-
if (scrubMetadata)
|
|
1331
|
+
if (scrubMetadata) {
|
|
1332
|
+
await executor.db().delete(juniorConversationBriefs).where(inArray(juniorConversationBriefs.conversationId, ids));
|
|
1333
|
+
await executor.db().delete(juniorReviewItems).where(and(inArray(juniorReviewItems.addedByConversationId, ids), eq(juniorReviewItems.visibility, "private"), or(ne(juniorReviewItems.status, "open"), isNull(juniorReviewItems.automationId))));
|
|
1334
|
+
}
|
|
1310
1335
|
await executor.db().update(juniorConversations).set({
|
|
1311
1336
|
transcriptPurgedAt: new Date(args.nowMs),
|
|
1312
1337
|
...scrubMetadata ? {
|
|
@@ -1697,66 +1722,28 @@ async function isSlackWorkspaceMember(raw, state) {
|
|
|
1697
1722
|
};
|
|
1698
1723
|
});
|
|
1699
1724
|
}
|
|
1700
|
-
// src/chat/ingress/slack-
|
|
1701
|
-
function requireSlackPayloadUserId(value, source) {
|
|
1702
|
-
const userId = parseActorUserId(value);
|
|
1703
|
-
if (!userId) throw new Error(`${source} is missing a Slack user id`);
|
|
1704
|
-
return userId;
|
|
1705
|
-
}
|
|
1706
|
-
async function handleAppHomeDisconnect(args) {
|
|
1707
|
-
await withSpan("chat.app_home_disconnect", "chat.app_home_disconnect", { userId: args.userId }, async () => {
|
|
1708
|
-
try {
|
|
1709
|
-
await unlinkProvider(args.userId, args.provider, args.userTokenStore, getWorkspaceTeamId());
|
|
1710
|
-
} catch (error) {
|
|
1711
|
-
logException(error, "app_home.disconnect_unlink.failed", { "app.credential.provider": args.provider });
|
|
1712
|
-
}
|
|
1713
|
-
try {
|
|
1714
|
-
await publishAppHomeView(getSlackClient(), args.userId, args.userTokenStore);
|
|
1715
|
-
} catch (error) {
|
|
1716
|
-
logException(error, "app_home.disconnect_publish.failed", { "app.credential.provider": args.provider });
|
|
1717
|
-
}
|
|
1718
|
-
});
|
|
1719
|
-
}
|
|
1725
|
+
// src/chat/ingress/slack-interaction-input.ts
|
|
1720
1726
|
/**
|
|
1721
|
-
*
|
|
1722
|
-
* from the person who
|
|
1723
|
-
*
|
|
1724
|
-
*
|
|
1725
|
-
*
|
|
1727
|
+
* Build the Slack input that a click or form makes in a thread, as a
|
|
1728
|
+
* mention from the person who acted. The input has no Slack message of its
|
|
1729
|
+
* own, so `messageId` is not a timestamp. Every Slack timestamp reader, such
|
|
1730
|
+
* as reactions, the actor `slack_ts`, and attachment lookup, then resolves
|
|
1731
|
+
* `raw.ts`: the message that the person clicked. `inputTs` sets the time of
|
|
1732
|
+
* the input.
|
|
1726
1733
|
*/
|
|
1727
|
-
|
|
1728
|
-
const userId = requireSlackPayloadUserId(args.payload.user?.id, "Slack user input payload");
|
|
1729
|
-
const { channelId, messageTs } = args;
|
|
1730
|
-
const open = await getOpenSlackUserInput(args.state, {
|
|
1731
|
-
channelId,
|
|
1732
|
-
threadTs: args.threadTs
|
|
1733
|
-
});
|
|
1734
|
-
if (!open || open.messageTs !== messageTs) return;
|
|
1735
|
-
const answer = args.answer(open);
|
|
1736
|
-
if (isEmptyUserInputAnswer(answer)) return;
|
|
1737
|
-
const userTeam = args.payload.user?.team_id;
|
|
1738
|
-
if (!await isSlackWorkspaceMember({
|
|
1739
|
-
user: userId,
|
|
1740
|
-
user_team: userTeam
|
|
1741
|
-
}, args.state)) return;
|
|
1742
|
-
if (!await args.isAuthorAllowed(userId)) return;
|
|
1743
|
-
if (!await claimSlackUserInput(args.state, {
|
|
1744
|
-
channelId,
|
|
1745
|
-
claimedBy: userId,
|
|
1746
|
-
messageTs
|
|
1747
|
-
})) return;
|
|
1734
|
+
function buildSlackInteractionInput(args) {
|
|
1748
1735
|
const event = {
|
|
1749
1736
|
type: "message",
|
|
1750
|
-
channel: channelId,
|
|
1737
|
+
channel: args.channelId,
|
|
1751
1738
|
team: args.payload.team?.id,
|
|
1752
|
-
text:
|
|
1739
|
+
text: args.text,
|
|
1753
1740
|
thread_ts: args.threadTs,
|
|
1754
1741
|
ts: args.inputTs,
|
|
1755
|
-
user: userId,
|
|
1756
|
-
user_team:
|
|
1742
|
+
user: args.userId,
|
|
1743
|
+
user_team: args.payload.user?.team_id
|
|
1757
1744
|
};
|
|
1758
1745
|
const parsed = args.adapter.parseMessage(event);
|
|
1759
|
-
|
|
1746
|
+
return new Message({
|
|
1760
1747
|
attachments: parsed.attachments,
|
|
1761
1748
|
author: parsed.author,
|
|
1762
1749
|
formatted: parsed.formatted,
|
|
@@ -1766,127 +1753,278 @@ async function answerSlackUserInput(args) {
|
|
|
1766
1753
|
metadata: parsed.metadata,
|
|
1767
1754
|
raw: {
|
|
1768
1755
|
...event,
|
|
1769
|
-
ts: messageTs
|
|
1756
|
+
ts: args.messageTs
|
|
1770
1757
|
},
|
|
1771
1758
|
text: parsed.text,
|
|
1772
1759
|
threadId: parsed.threadId
|
|
1773
1760
|
});
|
|
1761
|
+
}
|
|
1762
|
+
// src/chat/ingress/slack-review-item.ts
|
|
1763
|
+
async function tellPerson(target, userId, text) {
|
|
1764
|
+
await postSlackEphemeralMessage({
|
|
1765
|
+
channelId: target.channelId,
|
|
1766
|
+
text,
|
|
1767
|
+
threadTs: target.threadTs,
|
|
1768
|
+
userId
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
/**
|
|
1772
|
+
* Resolve the Junior User of the person who acted. An action starts a Turn
|
|
1773
|
+
* as that person, so it needs the same workspace and allowed domain checks
|
|
1774
|
+
* as a Slack mention.
|
|
1775
|
+
*/
|
|
1776
|
+
async function resolveActingUser(services, payload, target) {
|
|
1777
|
+
const slackUserId = parseActorUserId(payload.user?.id);
|
|
1778
|
+
const teamId = payload.team?.id ?? payload.user?.team_id;
|
|
1779
|
+
if (!slackUserId || !teamId) throw new Error("Slack review item payload is missing a user or team");
|
|
1780
|
+
if (!await isSlackWorkspaceMember({
|
|
1781
|
+
user: slackUserId,
|
|
1782
|
+
user_team: payload.user?.team_id
|
|
1783
|
+
}, services.state) || !await services.isAuthorAllowed(slackUserId)) return;
|
|
1784
|
+
const user = (await readActorIdentity(createSlackActor(teamId, slackUserId, void 0)))?.user;
|
|
1785
|
+
if (!user) {
|
|
1786
|
+
await tellPerson(target, slackUserId, "Junior does not know your account yet. Sign in to the Junior dashboard, then try again.");
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
return {
|
|
1790
|
+
slackUserId,
|
|
1791
|
+
user
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
/** Show the current state of an item, such as after a click on a stale card. */
|
|
1795
|
+
async function refreshCards(services, user, itemId) {
|
|
1796
|
+
const item = await readReviewItemForUser(user, itemId).catch(() => void 0);
|
|
1797
|
+
if (!item) return;
|
|
1798
|
+
await syncReviewItemSlackCards({
|
|
1799
|
+
getSlackAdapter: () => services.adapter,
|
|
1800
|
+
itemId: item.id
|
|
1801
|
+
});
|
|
1802
|
+
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Do one person action from a Slack card. The thread of the card gets the
|
|
1805
|
+
* action as Slack input from the person, like a typed reply. The other
|
|
1806
|
+
* linked Conversations get messages from
|
|
1807
|
+
* `actOnReviewItem`. An expected failure, such as an item that is already
|
|
1808
|
+
* closed, goes to the person as an ephemeral message.
|
|
1809
|
+
*/
|
|
1810
|
+
async function act(args) {
|
|
1811
|
+
const { services, target } = args;
|
|
1812
|
+
const actor = await resolveActingUser(services, args.payload, target);
|
|
1813
|
+
if (!actor) return;
|
|
1814
|
+
const conversationId = await readSlackReviewItemCardConversationId({
|
|
1815
|
+
...target,
|
|
1816
|
+
itemId: args.itemId
|
|
1817
|
+
}) ?? `slack:${target.channelId}:${target.threadTs}`;
|
|
1818
|
+
const delivery = {
|
|
1819
|
+
queue: services.queue,
|
|
1820
|
+
show: createSlackReviewItemShow(() => services.adapter)
|
|
1821
|
+
};
|
|
1822
|
+
let item;
|
|
1774
1823
|
try {
|
|
1775
|
-
await args.
|
|
1824
|
+
const current = await readReviewItemForUser(actor.user, args.itemId);
|
|
1825
|
+
item = await actOnReviewItem({
|
|
1826
|
+
action: args.action,
|
|
1827
|
+
exceptConversationId: conversationId,
|
|
1828
|
+
firstCloseOnly: true,
|
|
1829
|
+
delivery,
|
|
1830
|
+
id: current.id,
|
|
1831
|
+
user: actor.user,
|
|
1832
|
+
...args.answer ? { answer: args.answer(current) } : void 0
|
|
1833
|
+
});
|
|
1776
1834
|
} catch (error) {
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1835
|
+
if (!(error instanceof ReviewItemActionError)) throw error;
|
|
1836
|
+
await tellPerson(target, actor.slackUserId, error.message);
|
|
1837
|
+
await refreshCards(services, actor.user, args.itemId);
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
if (!item.conversationIds.includes(conversationId)) return;
|
|
1841
|
+
const change = reviewItemChange(args.action, actor.user);
|
|
1842
|
+
try {
|
|
1843
|
+
await services.persistMessage(buildSlackInteractionInput({
|
|
1844
|
+
adapter: services.adapter,
|
|
1845
|
+
channelId: target.channelId,
|
|
1846
|
+
inputTs: args.inputTs,
|
|
1847
|
+
messageId: args.messageId,
|
|
1848
|
+
messageTs: target.messageTs,
|
|
1849
|
+
payload: args.payload,
|
|
1850
|
+
text: renderReviewItemMessage(item, change),
|
|
1851
|
+
threadTs: target.threadTs,
|
|
1852
|
+
userId: actor.slackUserId
|
|
1853
|
+
}));
|
|
1854
|
+
} catch (error) {
|
|
1855
|
+
logException(error, "review_item.slack_input.persist.failed", { "app.review_item.id": item.id });
|
|
1856
|
+
await deliverReviewItemChange({
|
|
1857
|
+
change,
|
|
1858
|
+
delivery,
|
|
1859
|
+
item,
|
|
1860
|
+
nowMs: Date.now()
|
|
1780
1861
|
});
|
|
1781
|
-
throw error;
|
|
1782
1862
|
}
|
|
1783
|
-
await closeSlackUserInput({
|
|
1784
|
-
note: slackUserInputAnswerNote({
|
|
1785
|
-
answer,
|
|
1786
|
-
request: open.request,
|
|
1787
|
-
userId
|
|
1788
|
-
}),
|
|
1789
|
-
open
|
|
1790
|
-
});
|
|
1791
1863
|
}
|
|
1792
|
-
|
|
1793
|
-
function blockActionTarget(payload) {
|
|
1864
|
+
function cardTarget(payload) {
|
|
1794
1865
|
const channelId = parseSlackChannelId(payload.channel?.id);
|
|
1795
1866
|
const messageTs = parseSlackMessageTs(payload.message?.ts);
|
|
1796
|
-
if (!channelId || !messageTs) throw new Error("Slack
|
|
1867
|
+
if (!channelId || !messageTs) throw new Error("Slack review item payload is incomplete");
|
|
1797
1868
|
return {
|
|
1798
1869
|
channelId,
|
|
1799
1870
|
messageTs,
|
|
1800
1871
|
threadTs: parseSlackMessageTs(payload.message?.thread_ts) ?? messageTs
|
|
1801
1872
|
};
|
|
1802
1873
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
const
|
|
1806
|
-
|
|
1807
|
-
const
|
|
1808
|
-
if (!
|
|
1809
|
-
|
|
1874
|
+
async function openAnswerForm(args) {
|
|
1875
|
+
const { target } = args;
|
|
1876
|
+
const triggerId = args.payload.trigger_id;
|
|
1877
|
+
if (!triggerId) throw new Error("Slack review item form payload is missing a trigger id");
|
|
1878
|
+
const actor = await resolveActingUser(args.services, args.payload, target);
|
|
1879
|
+
if (!actor) return;
|
|
1880
|
+
let item;
|
|
1881
|
+
try {
|
|
1882
|
+
item = await readReviewItemForUser(actor.user, args.itemId);
|
|
1883
|
+
} catch (error) {
|
|
1884
|
+
if (!(error instanceof ReviewItemActionError)) throw error;
|
|
1885
|
+
await tellPerson(target, actor.slackUserId, error.message);
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
if (!canActOnReviewItem(item, actor.user.id)) {
|
|
1889
|
+
await tellPerson(target, actor.slackUserId, item.status === "open" ? "Only an assignee can act on this item." : "This item is already closed.");
|
|
1890
|
+
await refreshCards(args.services, actor.user, item.id);
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
const view = buildSlackReviewItemModal(item, {
|
|
1810
1894
|
...target,
|
|
1811
|
-
|
|
1812
|
-
answer: (open) => {
|
|
1813
|
-
const field = buttonUserInputField(open.request);
|
|
1814
|
-
return normalizeUserInputAnswer(open.request, {
|
|
1815
|
-
requestId: open.requestId,
|
|
1816
|
-
values: field ? { [field.id]: [choice] } : {}
|
|
1817
|
-
});
|
|
1818
|
-
},
|
|
1819
|
-
inputTs: actionTs,
|
|
1820
|
-
messageId: `slack-user-input-answer:${actionTs}`,
|
|
1821
|
-
payload: args.payload,
|
|
1822
|
-
isAuthorAllowed: args.isAuthorAllowed,
|
|
1823
|
-
persistMessage: args.persistMessage,
|
|
1824
|
-
state: args.state
|
|
1895
|
+
itemId: item.id
|
|
1825
1896
|
});
|
|
1826
|
-
|
|
1827
|
-
/** Open the answer form of the open request for the person who clicked. */
|
|
1828
|
-
async function handleUserInputOpenForm(args) {
|
|
1829
|
-
const target = blockActionTarget(args.payload);
|
|
1830
|
-
const triggerId = args.payload.trigger_id;
|
|
1831
|
-
if (!triggerId) throw new Error("Slack user input form payload is missing a trigger id");
|
|
1832
|
-
const open = await getOpenSlackUserInput(args.state, target);
|
|
1833
|
-
if (!open || open.messageTs !== target.messageTs) return;
|
|
1897
|
+
if (!view) return;
|
|
1834
1898
|
await openSlackModal({
|
|
1835
1899
|
triggerId,
|
|
1836
|
-
view
|
|
1900
|
+
view
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
/** Handle one click on a Review item card. */
|
|
1904
|
+
async function handleSlackReviewItemClick(args) {
|
|
1905
|
+
const { click, payload, services } = args;
|
|
1906
|
+
const target = cardTarget(payload);
|
|
1907
|
+
if (click.kind === "answer") {
|
|
1908
|
+
await openAnswerForm({
|
|
1909
|
+
itemId: click.itemId,
|
|
1910
|
+
payload,
|
|
1911
|
+
services,
|
|
1912
|
+
target
|
|
1913
|
+
});
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
const actionTs = parseSlackMessageTs(args.action.action_ts);
|
|
1917
|
+
if (!actionTs) throw new Error("Slack review item click payload is incomplete");
|
|
1918
|
+
const common = {
|
|
1919
|
+
inputTs: actionTs,
|
|
1920
|
+
itemId: click.itemId,
|
|
1921
|
+
messageId: `slack-review-item:${actionTs}`,
|
|
1922
|
+
payload,
|
|
1923
|
+
services,
|
|
1924
|
+
target
|
|
1925
|
+
};
|
|
1926
|
+
if (click.kind === "done") {
|
|
1927
|
+
await act({
|
|
1928
|
+
...common,
|
|
1929
|
+
action: "complete"
|
|
1930
|
+
});
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
if (click.kind === "dismiss") {
|
|
1934
|
+
await act({
|
|
1935
|
+
...common,
|
|
1936
|
+
action: "dismiss"
|
|
1937
|
+
});
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
await act({
|
|
1941
|
+
...common,
|
|
1942
|
+
action: "submit",
|
|
1943
|
+
answer: (item) => {
|
|
1944
|
+
const field = item.request && buttonUserInputField(item.request);
|
|
1945
|
+
if (!field?.options.some((option) => option.label === click.label)) throw new ReviewItemActionError("conflict", "This card changed. Use the updated card.");
|
|
1946
|
+
return {
|
|
1947
|
+
requestId: item.id,
|
|
1948
|
+
values: { [field.id]: [click.label] }
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1837
1951
|
});
|
|
1838
1952
|
}
|
|
1839
|
-
/**
|
|
1840
|
-
async function
|
|
1953
|
+
/** Submit the answer of a Review item form. */
|
|
1954
|
+
async function handleSlackReviewItemSubmission(args) {
|
|
1841
1955
|
const view = args.payload.view;
|
|
1842
|
-
const formTarget =
|
|
1956
|
+
const formTarget = parseSlackReviewItemFormTarget(view?.private_metadata);
|
|
1843
1957
|
const channelId = parseSlackChannelId(formTarget?.channelId);
|
|
1844
1958
|
const messageTs = parseSlackMessageTs(formTarget?.messageTs);
|
|
1845
1959
|
const threadTs = parseSlackMessageTs(formTarget?.threadTs);
|
|
1846
|
-
if (!view?.id || !channelId || !messageTs || !threadTs) throw new Error("Slack
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
requestId: open.requestId,
|
|
1960
|
+
if (!view?.id || !formTarget || !channelId || !messageTs || !threadTs) throw new Error("Slack review item form submission is incomplete");
|
|
1961
|
+
await act({
|
|
1962
|
+
action: "submit",
|
|
1963
|
+
answer: (item) => item.request ? parseSlackUserInputSubmission({
|
|
1964
|
+
request: item.request,
|
|
1965
|
+
requestId: item.id,
|
|
1853
1966
|
values: view.state?.values ?? {}
|
|
1854
|
-
})
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1967
|
+
}) : {
|
|
1968
|
+
requestId: item.id,
|
|
1969
|
+
values: {}
|
|
1970
|
+
},
|
|
1971
|
+
inputTs: slackMessageTsFromMs(Date.now()),
|
|
1972
|
+
itemId: formTarget.itemId,
|
|
1973
|
+
messageId: `slack-review-item:${view.id}`,
|
|
1859
1974
|
payload: args.payload,
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1975
|
+
services: args.services,
|
|
1976
|
+
target: {
|
|
1977
|
+
channelId,
|
|
1978
|
+
messageTs,
|
|
1979
|
+
threadTs
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
// src/chat/ingress/slack-interactive.ts
|
|
1984
|
+
function requireSlackPayloadUserId(value, source) {
|
|
1985
|
+
const userId = parseActorUserId(value);
|
|
1986
|
+
if (!userId) throw new Error(`${source} is missing a Slack user id`);
|
|
1987
|
+
return userId;
|
|
1988
|
+
}
|
|
1989
|
+
async function handleAppHomeDisconnect(args) {
|
|
1990
|
+
await withSpan("chat.app_home_disconnect", "chat.app_home_disconnect", { userId: args.userId }, async () => {
|
|
1991
|
+
try {
|
|
1992
|
+
await unlinkProvider(args.userId, args.provider, args.userTokenStore, getWorkspaceTeamId());
|
|
1993
|
+
} catch (error) {
|
|
1994
|
+
logException(error, "app_home.disconnect_unlink.failed", { "app.credential.provider": args.provider });
|
|
1995
|
+
}
|
|
1996
|
+
try {
|
|
1997
|
+
await publishAppHomeView(getSlackClient(), args.userId, args.userTokenStore);
|
|
1998
|
+
} catch (error) {
|
|
1999
|
+
logException(error, "app_home.disconnect_publish.failed", { "app.credential.provider": args.provider });
|
|
2000
|
+
}
|
|
1864
2001
|
});
|
|
1865
2002
|
}
|
|
1866
2003
|
/**
|
|
1867
|
-
* Handle Slack interactions from App Home and
|
|
1868
|
-
*
|
|
2004
|
+
* Handle Slack interactions from App Home and Review item cards: block
|
|
2005
|
+
* actions and submitted forms.
|
|
1869
2006
|
*/
|
|
1870
2007
|
async function handleSlackInteractivePayload(args) {
|
|
1871
2008
|
const spanAttributes = { userId: args.payload.user?.id?.trim() || void 0 };
|
|
1872
|
-
if (args.payload.type === "view_submission" && args.payload.view?.callback_id === "
|
|
1873
|
-
await withSpan("chat.
|
|
2009
|
+
if (args.payload.type === "view_submission" && args.payload.view?.callback_id === "junior_review_item") {
|
|
2010
|
+
await withSpan("chat.slack_review_item_submit", "chat.slack_review_item_submit", spanAttributes, () => handleSlackReviewItemSubmission({
|
|
2011
|
+
payload: args.payload,
|
|
2012
|
+
services: args
|
|
2013
|
+
}));
|
|
1874
2014
|
return;
|
|
1875
2015
|
}
|
|
1876
2016
|
if (args.payload.type !== "block_actions") return;
|
|
1877
2017
|
for (const action of args.payload.actions ?? []) {
|
|
1878
|
-
const
|
|
1879
|
-
if (
|
|
1880
|
-
await withSpan("chat.
|
|
1881
|
-
|
|
1882
|
-
|
|
2018
|
+
const click = parseSlackReviewItemClick(action);
|
|
2019
|
+
if (click) {
|
|
2020
|
+
await withSpan("chat.slack_review_item_click", "chat.slack_review_item_click", spanAttributes, () => handleSlackReviewItemClick({
|
|
2021
|
+
action,
|
|
2022
|
+
click,
|
|
2023
|
+
payload: args.payload,
|
|
2024
|
+
services: args
|
|
1883
2025
|
}));
|
|
1884
2026
|
return;
|
|
1885
2027
|
}
|
|
1886
|
-
if (kind === "open_form") {
|
|
1887
|
-
await withSpan("chat.slack_user_input_open_form", "chat.slack_user_input_open_form", spanAttributes, () => handleUserInputOpenForm(args));
|
|
1888
|
-
return;
|
|
1889
|
-
}
|
|
1890
2028
|
}
|
|
1891
2029
|
const disconnect = args.payload.actions?.find((candidate) => candidate.action_id === "app_home_disconnect");
|
|
1892
2030
|
const provider = disconnect?.selected_option?.value ?? disconnect?.value;
|
|
@@ -1931,6 +2069,7 @@ async function handleSlackForm(args) {
|
|
|
1931
2069
|
payload,
|
|
1932
2070
|
isAuthorAllowed: (userId) => args.isAuthorAllowed(userId, installation),
|
|
1933
2071
|
persistMessage: (message) => args.persistMessage(message, installation),
|
|
2072
|
+
queue: args.queue,
|
|
1934
2073
|
state,
|
|
1935
2074
|
userTokenStore: args.getUserTokenStore()
|
|
1936
2075
|
})
|
|
@@ -2387,6 +2526,17 @@ async function buildThread(args) {
|
|
|
2387
2526
|
function shouldIgnoreMessage(message) {
|
|
2388
2527
|
return message.author.isMe === true || !parseActorUserId(message.author.userId);
|
|
2389
2528
|
}
|
|
2529
|
+
/**
|
|
2530
|
+
* Resolve the Slack author Identity at ingress and bind its id to the message.
|
|
2531
|
+
* Ingress does no profile lookup. The worker adds the profile names later.
|
|
2532
|
+
* Bot authors wait for their own bot Identity, so they get no id here.
|
|
2533
|
+
*/
|
|
2534
|
+
async function bindSlackAuthorIdentity(message, installation) {
|
|
2535
|
+
const teamId = installation.teamId;
|
|
2536
|
+
const userId = parseActorUserId(message.author.userId);
|
|
2537
|
+
if (!teamId || !userId || message.author.isBot === true) return;
|
|
2538
|
+
bindMessageAuthorIdentityId(message, await resolveActorIdentityId(getSqlExecutor(), createSlackActor(teamId, userId, null)));
|
|
2539
|
+
}
|
|
2390
2540
|
function shouldPersistBeforeAck(body) {
|
|
2391
2541
|
return body.event?.type === "app_mention" || body.event?.type === "message";
|
|
2392
2542
|
}
|
|
@@ -2485,6 +2635,7 @@ async function handleSlackThreadStop(args) {
|
|
|
2485
2635
|
conversationId,
|
|
2486
2636
|
state: args.state
|
|
2487
2637
|
});
|
|
2638
|
+
await bindSlackAuthorIdentity(args.message, args.installation);
|
|
2488
2639
|
const content = parseContent(args.message);
|
|
2489
2640
|
const conversation = coerceThreadConversationState(void 0);
|
|
2490
2641
|
recordSkippedConversationMessage({
|
|
@@ -2495,7 +2646,7 @@ async function handleSlackThreadStop(args) {
|
|
|
2495
2646
|
});
|
|
2496
2647
|
await appendConversationMessages(getConversationEventStore(), {
|
|
2497
2648
|
conversation,
|
|
2498
|
-
conversationId
|
|
2649
|
+
conversationId
|
|
2499
2650
|
});
|
|
2500
2651
|
await thread.post(THREAD_OPTOUT_ACK);
|
|
2501
2652
|
}
|
|
@@ -2591,13 +2742,20 @@ async function routeParsedMessage(args) {
|
|
|
2591
2742
|
});
|
|
2592
2743
|
return;
|
|
2593
2744
|
}
|
|
2594
|
-
|
|
2745
|
+
const isAuthorDomainAllowed = () => isSlackAuthorDomainAllowed({
|
|
2595
2746
|
allowedBotUserIds: args.allowedBotUserIds,
|
|
2596
2747
|
allowedDomains: args.allowedDomains,
|
|
2597
2748
|
state: args.state,
|
|
2598
2749
|
teamId: args.installation.teamId,
|
|
2599
2750
|
user: args.event.user
|
|
2600
|
-
})
|
|
2751
|
+
});
|
|
2752
|
+
if (route === "mention" && !await isAuthorDomainAllowed()) return;
|
|
2753
|
+
await bindSlackAuthorIdentity(message, args.installation);
|
|
2754
|
+
const conversationId = await resolveSlackConversationId({
|
|
2755
|
+
canonicalThreadId,
|
|
2756
|
+
conversationStore: args.conversationStore,
|
|
2757
|
+
installation: args.installation
|
|
2758
|
+
});
|
|
2601
2759
|
if (isSubscribed && !isExperimentalFeatureEnabled("passive-routing")) {
|
|
2602
2760
|
const content = parseContent(message);
|
|
2603
2761
|
const conversation = coerceThreadConversationState(void 0);
|
|
@@ -2609,15 +2767,10 @@ async function routeParsedMessage(args) {
|
|
|
2609
2767
|
});
|
|
2610
2768
|
await appendConversationMessages(getConversationEventStore(), {
|
|
2611
2769
|
conversation,
|
|
2612
|
-
conversationId
|
|
2770
|
+
conversationId
|
|
2613
2771
|
});
|
|
2614
2772
|
return;
|
|
2615
2773
|
}
|
|
2616
|
-
const conversationId = await resolveSlackConversationId({
|
|
2617
|
-
canonicalThreadId,
|
|
2618
|
-
conversationStore: args.conversationStore,
|
|
2619
|
-
installation: args.installation
|
|
2620
|
-
});
|
|
2621
2774
|
const persist = () => persistSlackMessage({
|
|
2622
2775
|
adapter: args.adapter,
|
|
2623
2776
|
installation: args.installation,
|
|
@@ -2629,22 +2782,34 @@ async function routeParsedMessage(args) {
|
|
|
2629
2782
|
route,
|
|
2630
2783
|
state: args.state
|
|
2631
2784
|
});
|
|
2632
|
-
|
|
2785
|
+
const closeAnswered = async () => {
|
|
2786
|
+
if (message.author.isBot === true) return;
|
|
2787
|
+
await closeReviewItemsAnsweredByReply({
|
|
2788
|
+
...route === "subscribed" ? { canAnswer: isAuthorDomainAllowed } : void 0,
|
|
2789
|
+
conversationId,
|
|
2790
|
+
delivery: {
|
|
2791
|
+
queue: args.queue,
|
|
2792
|
+
show: createSlackReviewItemShow(() => args.adapter)
|
|
2793
|
+
},
|
|
2794
|
+
resolveUser: async () => {
|
|
2795
|
+
const teamId = args.installation.teamId;
|
|
2796
|
+
const userId = parseActorUserId(message.author.userId);
|
|
2797
|
+
if (!teamId || !userId) return void 0;
|
|
2798
|
+
return (await readActorIdentity(createSlackActor(teamId, userId, null)))?.user;
|
|
2799
|
+
},
|
|
2800
|
+
text: stripLeadingBotMention(parseContent(message).topLevelText, {
|
|
2801
|
+
botUserId: args.adapter.botUserId,
|
|
2802
|
+
stripLeadingSlackMentionToken: true
|
|
2803
|
+
})
|
|
2804
|
+
});
|
|
2805
|
+
};
|
|
2806
|
+
try {
|
|
2633
2807
|
await persist();
|
|
2634
|
-
|
|
2808
|
+
} catch (error) {
|
|
2809
|
+
if (error instanceof SlackEventPersistenceError && error.saved) await closeAnswered();
|
|
2810
|
+
throw error;
|
|
2635
2811
|
}
|
|
2636
|
-
await
|
|
2637
|
-
channelId: args.event.channel,
|
|
2638
|
-
claimedBy: message.author.userId,
|
|
2639
|
-
isSaved: (error) => error instanceof SlackEventPersistenceError && error.saved,
|
|
2640
|
-
note: (request) => slackUserInputAnswerNote({
|
|
2641
|
-
request,
|
|
2642
|
-
userId: message.author.userId
|
|
2643
|
-
}),
|
|
2644
|
-
persist,
|
|
2645
|
-
state: args.state,
|
|
2646
|
-
threadTs: args.event.thread_ts
|
|
2647
|
-
});
|
|
2812
|
+
await closeAnswered();
|
|
2648
2813
|
}
|
|
2649
2814
|
async function handleSlackEvent(args) {
|
|
2650
2815
|
const event = args.body.event;
|
|
@@ -2703,7 +2868,7 @@ async function handleSlackEvent(args) {
|
|
|
2703
2868
|
if (!await isSlackWorkspaceMember(event, state)) return;
|
|
2704
2869
|
const channelId = event.channel;
|
|
2705
2870
|
const threadTs = event.thread_ts;
|
|
2706
|
-
if (!(await withLock
|
|
2871
|
+
if (!(await withLock(state, `slack:ingress:slack:${channelId}:${threadTs}`, () => handleSlackSessionStop({
|
|
2707
2872
|
adapter,
|
|
2708
2873
|
channelId,
|
|
2709
2874
|
conversationStore: args.services.conversationStore,
|
|
@@ -2722,7 +2887,7 @@ async function handleSlackEvent(args) {
|
|
|
2722
2887
|
setSpanAttributes({ "app.slack.membership": member ? "verified" : "unverified" });
|
|
2723
2888
|
if (!member) return;
|
|
2724
2889
|
const message = adapter.parseMessage(event);
|
|
2725
|
-
if (!(await withLock
|
|
2890
|
+
if (!(await withLock(state, `slack:ingress:${normalizeMessageThreadId(message).threadId}`, () => routeParsedMessage({
|
|
2726
2891
|
adapter,
|
|
2727
2892
|
event,
|
|
2728
2893
|
installation,
|
|
@@ -2748,8 +2913,9 @@ async function handleSlackEvent(args) {
|
|
|
2748
2913
|
*/
|
|
2749
2914
|
async function persistSlackMentionWithLock(args) {
|
|
2750
2915
|
const { message, threadId } = normalizeMessageThreadId(args.message);
|
|
2916
|
+
await bindSlackAuthorIdentity(message, args.installation);
|
|
2751
2917
|
try {
|
|
2752
|
-
if (!(await withLock
|
|
2918
|
+
if (!(await withLock(args.state, `slack:ingress:${threadId}`, () => persistSlackMessage({
|
|
2753
2919
|
adapter: args.adapter,
|
|
2754
2920
|
installation: args.installation,
|
|
2755
2921
|
message,
|
|
@@ -2799,6 +2965,7 @@ async function handleSlackWebhook(args) {
|
|
|
2799
2965
|
services: args.services,
|
|
2800
2966
|
state
|
|
2801
2967
|
}),
|
|
2968
|
+
queue: args.services.queue,
|
|
2802
2969
|
state,
|
|
2803
2970
|
waitUntil: args.waitUntil
|
|
2804
2971
|
});
|
|
@@ -3280,35 +3447,6 @@ function resolvePublicOrigin(request, configured) {
|
|
|
3280
3447
|
url.hash = "";
|
|
3281
3448
|
return url;
|
|
3282
3449
|
}
|
|
3283
|
-
// src/api/acp/state.ts
|
|
3284
|
-
const MUTATION_LOCK_TTL_MS = 1e4;
|
|
3285
|
-
/** Run one ACP state mutation while its shared lock remains owned. */
|
|
3286
|
-
async function withLock(state, key, run, options = {}) {
|
|
3287
|
-
const ttlMs = options.ttlMs ?? 1e4;
|
|
3288
|
-
const startedAtMs = Date.now();
|
|
3289
|
-
let lock;
|
|
3290
|
-
while (true) {
|
|
3291
|
-
lock = await state.acquireLock(key, ttlMs);
|
|
3292
|
-
if (lock) break;
|
|
3293
|
-
if (Date.now() - startedAtMs >= (options.waitMs ?? 0)) return { acquired: false };
|
|
3294
|
-
await setTimeout$1(options.retryMs ?? 25, void 0, { ref: false });
|
|
3295
|
-
}
|
|
3296
|
-
const heartbeat = options.keepAlive ? setInterval(() => void state.extendLock(lock, ttlMs).catch(() => void 0), Math.floor(ttlMs / 3)) : void 0;
|
|
3297
|
-
heartbeat?.unref();
|
|
3298
|
-
try {
|
|
3299
|
-
return {
|
|
3300
|
-
acquired: true,
|
|
3301
|
-
value: await run(lock)
|
|
3302
|
-
};
|
|
3303
|
-
} finally {
|
|
3304
|
-
if (heartbeat) clearInterval(heartbeat);
|
|
3305
|
-
await state.releaseLock(lock);
|
|
3306
|
-
}
|
|
3307
|
-
}
|
|
3308
|
-
/** Prove ACP lock ownership immediately before a blind state write. */
|
|
3309
|
-
async function fenceLock(state, lock, ttlMs = MUTATION_LOCK_TTL_MS) {
|
|
3310
|
-
if (!await state.extendLock(lock, ttlMs)) throw new Error(`ACP lock ownership was lost before write for ${lock.threadId}`);
|
|
3311
|
-
}
|
|
3312
3450
|
// src/api/acp/schema.ts
|
|
3313
3451
|
/** Validate the ACP wire messages that Junior supports. */
|
|
3314
3452
|
const ACP_SESSION_ID_PATTERN = /^local:acp:[a-f0-9]{32}$/;
|
|
@@ -6035,7 +6173,8 @@ function createBetterAuth(config) {
|
|
|
6035
6173
|
await recordGoogleSignIn(getSqlExecutor(), {
|
|
6036
6174
|
email,
|
|
6037
6175
|
subject: google.accountId,
|
|
6038
|
-
displayName: created.user.name || void 0
|
|
6176
|
+
displayName: created.user.name || void 0,
|
|
6177
|
+
avatarUrl: parseAvatarUrl(created.user.image, GOOGLE_IDENTITY_PROVIDER)
|
|
6039
6178
|
});
|
|
6040
6179
|
}) }
|
|
6041
6180
|
});
|
|
@@ -6149,6 +6288,7 @@ function dashboardHostRoutePaths(dashboard, authenticatedRoutes = []) {
|
|
|
6149
6288
|
`${conversationsPath}/*`,
|
|
6150
6289
|
pagePath("locations"),
|
|
6151
6290
|
`${pagePath("locations")}/*`,
|
|
6291
|
+
pagePath("review"),
|
|
6152
6292
|
peoplePath,
|
|
6153
6293
|
`${peoplePath}/*`,
|
|
6154
6294
|
pagePath("automations"),
|
|
@@ -6182,6 +6322,8 @@ function dashboardHostRoutePaths(dashboard, authenticatedRoutes = []) {
|
|
|
6182
6322
|
"/api/plugin-reports",
|
|
6183
6323
|
"/api/user-pages",
|
|
6184
6324
|
"/api/user-pages/*",
|
|
6325
|
+
"/api/user-customization",
|
|
6326
|
+
"/api/user-customization/*",
|
|
6185
6327
|
"/api/automations",
|
|
6186
6328
|
"/api/automations/*",
|
|
6187
6329
|
"/api/skills",
|
|
@@ -6196,6 +6338,8 @@ function dashboardHostRoutePaths(dashboard, authenticatedRoutes = []) {
|
|
|
6196
6338
|
"/api/locations/*",
|
|
6197
6339
|
"/api/people",
|
|
6198
6340
|
"/api/people/*",
|
|
6341
|
+
"/api/review-items",
|
|
6342
|
+
"/api/review-items/*",
|
|
6199
6343
|
"/api/personal-tokens",
|
|
6200
6344
|
"/api/personal-tokens/*",
|
|
6201
6345
|
"/api/service-accounts",
|
|
@@ -6429,7 +6573,10 @@ async function createApp(options) {
|
|
|
6429
6573
|
validatePluginRegistrations(configuredPlugins?.registrations ?? []);
|
|
6430
6574
|
validatePluginEgressCredentialHooks(configuredPlugins?.registrations ?? []);
|
|
6431
6575
|
}
|
|
6432
|
-
pluginRoutes = getPluginRoutes({
|
|
6576
|
+
pluginRoutes = getPluginRoutes({
|
|
6577
|
+
events,
|
|
6578
|
+
showReviewItem: createSlackReviewItemShow(getProductionSlackAdapter)
|
|
6579
|
+
});
|
|
6433
6580
|
if (dashboard && !dashboard.disabled) pluginApiRoutes = getPluginApiRoutes();
|
|
6434
6581
|
} catch (error) {
|
|
6435
6582
|
restoreRuntimeConfig$1();
|
|
@@ -6447,6 +6594,7 @@ async function createApp(options) {
|
|
|
6447
6594
|
attachmentStorage,
|
|
6448
6595
|
bindMessageAgent: (request) => bindMessageAgent(request, { queue: conversationWorkQueue }),
|
|
6449
6596
|
bindSpawnAgent: (request) => bindSpawnAgent(request, { queue: conversationWorkQueue }),
|
|
6597
|
+
conversationWorkQueue,
|
|
6450
6598
|
tracePropagation
|
|
6451
6599
|
});
|
|
6452
6600
|
const runtimeServiceOverrides = {
|