@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.
Files changed (95) hide show
  1. package/README.md +2 -9
  2. package/dist/{actor-DZSokpwj.js → actor-CP9K2Idu.js} +2 -1
  3. package/dist/{adapter-BybMEezj.js → adapter-2-CG25be.js} +3 -51
  4. package/dist/{admin-DOs1_EiW.js → admin-DSs8S5Uy.js} +1 -1
  5. package/dist/{agent-invocations-CxKldAeB.js → agent-invocations-C1RNOCs8.js} +2 -0
  6. package/dist/{agent-runner-DgNATN4D.js → agent-runner-CrDrPUtY.js} +3411 -1640
  7. package/dist/api/schema.d.ts +2 -5630
  8. package/dist/api/schema.js +7 -7
  9. package/dist/api.d.ts +3 -1
  10. package/dist/api.js +268 -287
  11. package/dist/{app-DAB3fzyp.d.ts → app-BrMMzjYm.d.ts} +10 -3
  12. package/dist/app.d.ts +1 -1
  13. package/dist/app.js +371 -223
  14. package/dist/{automation-CmeFBSEh.js → automation-p87hg_AV.js} +74 -74
  15. package/dist/{briefs-CWKjka7v.js → briefs-PtODdLNu.js} +4 -4
  16. package/dist/{catalog-runtime-cMlAI5LY.js → catalog-runtime-BsoSmTM0.js} +1 -1
  17. package/dist/cli/chat.js +16 -26
  18. package/dist/cli/check.js +3 -3
  19. package/dist/cli/main.js +5 -3
  20. package/dist/cli/plugins.js +4 -4
  21. package/dist/cli/snapshot-create.js +4 -4
  22. package/dist/cli/upgrade.d.ts +1 -1
  23. package/dist/cli/upgrade.js +3 -3
  24. package/dist/{client-BjgbMawj.js → client-Cqf4TboL.js} +20 -15
  25. package/dist/{config-CdB6yJj5.js → config-CPsE4T5L.js} +2 -2
  26. package/dist/{connections-RE_65jzK.js → connections-upFXLmhG.js} +10 -11
  27. package/dist/{conversation-DSo310Ps.js → conversation-CTpD_PGk.js} +7 -84
  28. package/dist/{conversation-privacy-BzgJC5k0.js → conversation-privacy-D6zJc8ff.js} +1 -1
  29. package/dist/{credential-sync-DhEGEpgq.js → credential-sync-CS_6IC-f.js} +30 -34
  30. package/dist/{credentialed-CIGwoLU2.js → credentialed-BU-OW7HP.js} +577 -74
  31. package/dist/{store-B8CABbmR.d.ts → db-BzOCzVb1.d.ts} +17 -7
  32. package/dist/{executor-VYrx_K4w.js → executor-CNS7ONt-.js} +78 -4
  33. package/dist/instrumentation.js +2 -2
  34. package/dist/{assistant-reply-CT7VcXQe.js → interruption-marker-g6Xup3Fk.js} +8 -2
  35. package/dist/{job-queue-C0BZSa7r.js → job-queue-DI-k-Bbk.js} +2 -2
  36. package/dist/{list-1XQP-o6K.js → list-CGoQdXTp.js} +429 -1401
  37. package/dist/locks-D-0h111u.js +48 -0
  38. package/dist/{logging-DuZ1A4ab.js → logging-n7knse-O.js} +1 -1
  39. package/dist/{history-C84mlAwO.js → media-85PDJxO_.js} +83 -1
  40. package/dist/{message-NKSbbnA6.js → message-BpaWd8TT.js} +4 -4
  41. package/dist/nitro.d.ts +1 -1
  42. package/dist/nitro.js +3 -3
  43. package/dist/{oauth-callback-server-qAHLPtml.js → oauth-callback-server-CpGmEw_i.js} +2 -2
  44. package/dist/{prompt-BW9jFsqZ.js → prompt-BlQB0QQt.js} +1 -1
  45. package/dist/{registry-FQutO926.js → registry-By36_xsm.js} +2 -2
  46. package/dist/reporting-schema-CZREhYc-.js +104 -0
  47. package/dist/reporting-window-CVarfId9.js +255 -0
  48. package/dist/{resolve-Bk4yeuHy.js → resolve-CUIBcdPd.js} +3 -3
  49. package/dist/{result-B9e9ATOU.js → result-BemsOMOO.js} +31 -12
  50. package/dist/{runner-C3S2ObH-.js → runner-BVok7PAA.js} +12 -11
  51. package/dist/{sandbox-egress-signals-DqpGzzMB.js → sandbox-egress-signals-BSUWQI95.js} +1 -1
  52. package/dist/{schema-X7Fj4fv1.js → schema-ChnFo5f2.js} +98 -6
  53. package/dist/schema-jK4-1u_H.d.ts +5927 -0
  54. package/dist/{service-account-Do30HUNv.js → service-account-DHvlLnZw.js} +4 -3
  55. package/dist/{service-account-BQasl-qq.js → service-account-hMmESDcr.js} +3 -3
  56. package/dist/{session-stop-DkOjt46_.js → session-stop-BQW_iFcd.js} +499 -23
  57. package/dist/{skills-CQHrwfOa.js → skills-DmJJIuLF.js} +2 -2
  58. package/dist/{spawn-BHuqN14f.js → spawn-ChCm46i-.js} +2 -2
  59. package/dist/{store-6EetVP-m.js → store-1DVhoKOL.js} +4 -4
  60. package/dist/{store-DvNZIsCr.js → store-DCo2n5iJ.js} +3 -3
  61. package/dist/{task-queue-B0DLgd2P.js → task-queue-HMC4QjFm.js} +3 -3
  62. package/dist/task-runner-cA6yjn_E.js +269 -0
  63. package/dist/{thread-state-DRmdJ2QN.js → thread-state-CHy8LS0C.js} +8 -18
  64. package/dist/{turn-failure-response-G11cs2gE.js → turn-failure-response-Dr7erLWs.js} +11 -16
  65. package/dist/{turn-lifecycle-OzLIwvi1.js → turn-lifecycle-bf2fIYi_.js} +1 -1
  66. package/dist/{tasks-0ilmuCux.js → turn-session-routing-BqO0qXLk.js} +959 -44
  67. package/dist/{validation-CxEmt1id.js → validation-DHR1ivfh.js} +1 -1
  68. package/dist/{version-Bapedn4A.js → version-CXCHLgWm.js} +1 -1
  69. package/dist/version.js +1 -1
  70. package/dist/{viewer-DrN3oC2X.js → viewer-DSbD6DF6.js} +503 -241
  71. package/dist/{work-DUQoNcqP.js → work-BE_qTYGi.js} +17 -36
  72. package/dist/{worker-D1l4Ugk0.js → worker-Cm9_sWXg.js} +15 -7
  73. package/migrations/0065_user_customization.sql +1 -0
  74. package/migrations/0066_review_items.sql +53 -0
  75. package/migrations/0067_review_item_dedupe_tenant.sql +2 -0
  76. package/migrations/0068_review_item_slack_cards.sql +10 -0
  77. package/migrations/0069_system_identity.sql +25 -0
  78. package/migrations/0070_review_item_foreign_keys.sql +5 -0
  79. package/migrations/AGENTS.md +17 -0
  80. package/migrations/meta/0065_snapshot.json +3511 -0
  81. package/migrations/meta/0066_snapshot.json +4099 -0
  82. package/migrations/meta/0067_snapshot.json +3921 -0
  83. package/migrations/meta/0068_snapshot.json +4176 -0
  84. package/migrations/meta/0069_snapshot.json +3979 -0
  85. package/migrations/meta/0070_snapshot.json +3997 -0
  86. package/migrations/meta/_journal.json +42 -0
  87. package/package.json +5 -5
  88. package/dist/db-Bg-zC3r5.d.ts +0 -5
  89. package/dist/history-BgI33Kie.d.ts +0 -78
  90. package/dist/images-CecjXAAz.js +0 -64
  91. package/dist/task-runner-B-PD_JOj.js +0 -1074
  92. package/dist/turn-session-routing-Dyut9l7Y.js +0 -76
  93. package/dist/user-input-request-BDFBdF8r.js +0 -116
  94. package/dist/web-message-id-CJSUiksE.js +0 -236
  95. package/migrations/README.md +0 -30
package/dist/app.js CHANGED
@@ -1,46 +1,46 @@
1
- import { C as automationVersionOf, P as eventGuidance, Zt as CONVERSATION_WORK_STALE_ENQUEUE_MS, b as admitEventAutomationRun, c as withScheduledAutomationLock, cr as isExperimentalFeatureEnabled, er as SubscribedReplyReason, ft as projectConversationMessages, hn as isInvalidConversationRecordError, hr as setConfigDefaults, ht as settleConversationTitleWork, i as readScheduledAutomation, l as blockEventAutomation, lr as setExperimentalFeatures, lt as appendConversationMessages, mr as getConfigDefaults, n as listScheduledAutomationsForTeam, o as saveScheduledAutomationInLock, p as findMatchingEventAutomations, pn as hasRunnableConversationWork, rr as getThreadStopDecision, s as scheduledRunId, sr as getExperimentalFeatures, u as collectEventAutomationMatchKeys, x as eventAutomationRunLimitReason } from "./tasks-0ilmuCux.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-CdB6yJj5.js";
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-DuZ1A4ab.js";
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-Bapedn4A.js";
6
- import { d as parseSlackTeamId, f as parseSlackUserId, h as slackMessageTsSchema, l as parseSlackChannelId, m as slackMessageTsFromMs, o as parseActorUserId, p as parseSlackMessageTs } from "./actor-DZSokpwj.js";
7
- import { F as recordGoogleSignIn, a as resolveViewerUser, c as closeDb, f as getConversationStore, g as withConversationEventLock, m as getSqlExecutor, p as getDb, r as readActorIdentity, u as getConversationEventStore, y as withConversationMutationLock } from "./viewer-DrN3oC2X.js";
8
- import { a as juniorAgentInvocations, d as juniorDestinations, i as juniorAgentBindings, s as juniorConversations } from "./agent-invocations-CxKldAeB.js";
9
- import { a as juniorAutomationRuns, f as juniorConversationBriefs, h as juniorConversationEvents, s as juniorAutomations } from "./executor-VYrx_K4w.js";
10
- import { n as pluginCatalogRuntime } from "./catalog-runtime-cMlAI5LY.js";
11
- import { $ as stopConversationSession, C as requestAttachmentDeletion, D as getOpenSlackUserInput, E as closeSlackUserInput, F as slackUserInputAnswerNote, G as scheduledAutomationRunAttributes, L as parseSlackThreadId, M as parseSlackUserInputFormTarget, N as parseSlackUserInputSubmission, O as persistSlackUserInputReply, P as slackUserInputAction, T as claimSlackUserInput, W as logScheduledAutomationRunSkipped, Y as effectiveTaskOutcomes, _ as unpublishedArtifactHeaders, et as recordSubagentEnded, g as readPublicArtifact, h as publicArtifactHeaders, j as buildSlackUserInputModal, k as releaseSlackUserInput, n as executeAgentRun, ot as processWorkspaceSnapshotJob, st as ingestEvent, t as createAgentRunner, v as lookupSlackUserProfile, x as collectAttachmentGarbage } from "./agent-runner-DgNATN4D.js";
12
- import { An as createPluginState, Ct as normalizeSandboxEgressTracePropagationDomains, Gt as bindEventAutomationCredentialSubject, I as isVercelSandboxEgressRequest, Jt as verifyEventAutomationCredentialSubject, Kt as bindScheduledAutomationCredentialSubject, L as proxyVercelSandboxEgressRequest, Lt as collectEventMatchKeys, Nn as createPluginLogger, Pn as readConversationAccessFromSql, Q as openSlackModal, V as resolveBaseUrl, Vt as findMatchingWatches, Wt as takeDueTimerWatches, Xt as verifySlackDirectCredentialSubject, Yt as verifyScheduledAutomationCredentialSubject, Zn as resolveRootVisibility, _t as parseSandboxEgressCredentialToken, ar as createPluginCache, c as getPluginRoutes, er as listConversationAnnotations, fn as getDashboardConversationLink, h as validatePlugins, hn as setDashboardConversationLinkOptions, ht as consumeSandboxEgressPermissionDeniedSignal, in as isDmChannel, it as slackExternalRefIdSchema, ln as getWorkspaceTeamId, m as setPlugins, mn as getDashboardTaskLink, mt as consumeSandboxEgressAuthRequiredSignal, n as createUserTokenStore, nn as getSlackClient, or as createCache, p as getPlugins, pt as clearSandboxEgressSignals, q as slackMention, qt as bindSlackDirectCredentialSubject, rt as slackEntitySchema, s as getPluginApiRoutes, un as runWithWorkspaceTeamId, v as setBriefsConfig } from "./credentialed-CIGwoLU2.js";
13
- import { c as withLock$1, i as getStateAdapter, t as disconnectStateAdapter } from "./adapter-BybMEezj.js";
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-DSo310Ps.js";
16
- import { b as listPendingAgentInvocationMailboxAppends, x as listPendingAgentInvocationResults } from "./store-6EetVP-m.js";
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-DkOjt46_.js";
18
- import { c as setSandboxResourceConfig, o as getSandboxResourceConfig } from "./resolve-Bk4yeuHy.js";
19
- 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-C0BZSa7r.js";
20
- 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-DvNZIsCr.js";
21
- import { a as normalizeUserInputAnswer, i as isEmptyUserInputAnswer, n as buttonUserInputField, r as formatUserInputAnswer } from "./user-input-request-BDFBdF8r.js";
22
- import { D as scheduledRunSchema } from "./automation-CmeFBSEh.js";
23
- import { n as validatePluginRegistrations, t as validatePluginEgressCredentialHooks } from "./validation-CxEmt1id.js";
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 { $ as renderSlackObjectCard, B as recordSkippedConversationMessage, C as createProductionSlackWebhookServices, F as assertNoLegacyAllowedDomains, G as normalizeIncomingSlackThreadId, H as clearSlackPendingReactions, I as isAllowedDomain, J as THREAD_OPTOUT_ACK, K as withNormalizedThreadId, L as isAllowedSignInEmail, Q as verifySlackSignature, R as readEnvList, S as createProductionConversationWorkOptions, T as createSlackWebApiStatusSender, U as isSlackMessageStopped, V as buildSlackInboundMessage, W as stopSlackThread, X as parseContent, Y as startProcessingReaction, Z as runWithSlackInstallation, _ as appendAndEnqueueWebMessage, b as recordWebConversationActivity, et as getAttachmentStorage, j as readViewerAutomationSummary, nt as enqueueAgentDispatch, q as stopConversationTurn, rt as GET$3, t as readConversationFeed, v as conversationTurnIdForMessage, w as getProductionSlackAdapter, x as webActorFromEmail, z as resolveAllowedDomains } from "./list-1XQP-o6K.js";
26
- 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-B-PD_JOj.js";
27
- import { o as getAuthPausedConversationTurnId, t as enqueueAgentInvocationResult } from "./result-B9e9ATOU.js";
28
- import { r as enqueueAgentInvocation } from "./work-DUQoNcqP.js";
29
- import { s as coerceThreadConversationState } from "./thread-state-DRmdJ2QN.js";
30
- import { o as unlinkProvider } from "./connections-RE_65jzK.js";
31
- import { a as GET$4, i as publishAppHomeView, r as GET$5, t as receiveLocalOAuthCredential } from "./credential-sync-DhEGEpgq.js";
32
- import { a as pluginTaskQueueConsumer, c as queueCallback, n as createVercelPluginTaskCallback, o as registerVercelPluginTaskDevConsumer } from "./task-queue-B0DLgd2P.js";
33
- import { t as processConversationWork } from "./worker-D1l4Ugk0.js";
34
- import { t as bindMessageAgent } from "./message-NKSbbnA6.js";
35
- import { t as bindSpawnAgent } from "./spawn-BHuqN14f.js";
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. The metadata rows themselves survive.
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) await executor.db().delete(juniorConversationBriefs).where(inArray(juniorConversationBriefs.conversationId, ids));
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-interactive.ts
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
- * Save the first answer to the open request of a Slack thread as Slack input
1722
- * from the person who answered. The input text is the formatted answer, so
1723
- * the model reads one format on every surface. Later answers to the same
1724
- * request do nothing. A stale request, such as one that a newer request
1725
- * replaced, also does nothing.
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
- async function answerSlackUserInput(args) {
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: formatUserInputAnswer(open.request, answer),
1739
+ text: args.text,
1753
1740
  thread_ts: args.threadTs,
1754
1741
  ts: args.inputTs,
1755
- user: userId,
1756
- user_team: userTeam
1742
+ user: args.userId,
1743
+ user_team: args.payload.user?.team_id
1757
1744
  };
1758
1745
  const parsed = args.adapter.parseMessage(event);
1759
- const message = new Message({
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.persistMessage(message);
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
- await releaseSlackUserInput(args.state, {
1778
- channelId,
1779
- messageTs
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
- /** Read the request message and thread of a block action. */
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 user input payload is incomplete");
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
- /** Answer a one-click request with the clicked choice. */
1804
- async function handleUserInputChoice(args) {
1805
- const target = blockActionTarget(args.payload);
1806
- const actionTs = parseSlackMessageTs(args.action.action_ts);
1807
- const choice = args.action.value?.trim();
1808
- if (!actionTs || !choice) throw new Error("Slack user input click payload is incomplete");
1809
- await answerSlackUserInput({
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
- adapter: args.adapter,
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: buildSlackUserInputModal(open.request, target)
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
- /** Answer a request with the values of its submitted form. */
1840
- async function handleUserInputSubmission(args) {
1953
+ /** Submit the answer of a Review item form. */
1954
+ async function handleSlackReviewItemSubmission(args) {
1841
1955
  const view = args.payload.view;
1842
- const formTarget = parseSlackUserInputFormTarget(view?.private_metadata);
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 user input form submission is incomplete");
1847
- const inputTs = slackMessageTsFromMs(Date.now());
1848
- await answerSlackUserInput({
1849
- adapter: args.adapter,
1850
- answer: (open) => parseSlackUserInputSubmission({
1851
- request: open.request,
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
- channelId,
1856
- inputTs,
1857
- messageId: `slack-user-input-answer:${view.id}`,
1858
- messageTs,
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
- isAuthorAllowed: args.isAuthorAllowed,
1861
- persistMessage: args.persistMessage,
1862
- state: args.state,
1863
- threadTs
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 from user input requests:
1868
- * block actions and submitted user input forms.
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 === "junior_user_input") {
1873
- await withSpan("chat.slack_user_input_submit", "chat.slack_user_input_submit", spanAttributes, () => handleUserInputSubmission(args));
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 kind = slackUserInputAction(action.action_id);
1879
- if (kind === "choice") {
1880
- await withSpan("chat.slack_user_input_click", "chat.slack_user_input_click", spanAttributes, () => handleUserInputChoice({
1881
- ...args,
1882
- action
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: args.canonicalThreadId
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
- if (route === "mention" && !await isSlackAuthorDomainAllowed({
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
- })) return;
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: canonicalThreadId
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
- if (route !== "mention") {
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
- return;
2808
+ } catch (error) {
2809
+ if (error instanceof SlackEventPersistenceError && error.saved) await closeAnswered();
2810
+ throw error;
2635
2811
  }
2636
- await persistSlackUserInputReply({
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$1(state, `slack:ingress:slack:${channelId}:${threadTs}`, () => handleSlackSessionStop({
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$1(state, `slack:ingress:${normalizeMessageThreadId(message).threadId}`, () => routeParsedMessage({
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$1(args.state, `slack:ingress:${threadId}`, () => persistSlackMessage({
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({ events });
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 = {