@sentry/junior 0.268.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 (82) hide show
  1. package/README.md +2 -9
  2. package/dist/{adapter-04AcD2SO.js → adapter-2-CG25be.js} +3 -49
  3. package/dist/{admin-DIU1QyG3.js → admin-DSs8S5Uy.js} +1 -1
  4. package/dist/{agent-runner-CAYfFmU7.js → agent-runner-CrDrPUtY.js} +3208 -2826
  5. package/dist/api/schema.d.ts +2 -3
  6. package/dist/api/schema.js +7 -7
  7. package/dist/api.d.ts +3 -1
  8. package/dist/api.js +157 -298
  9. package/dist/{app-z6Uuyq3j.d.ts → app-BrMMzjYm.d.ts} +8 -2
  10. package/dist/app.d.ts +1 -1
  11. package/dist/app.js +121 -207
  12. package/dist/{automation-CmeFBSEh.js → automation-p87hg_AV.js} +74 -74
  13. package/dist/{briefs-DQbV2jNr.js → briefs-PtODdLNu.js} +4 -4
  14. package/dist/{catalog-runtime-BD9GSpc6.js → catalog-runtime-BsoSmTM0.js} +1 -1
  15. package/dist/cli/chat.js +16 -26
  16. package/dist/cli/check.js +3 -3
  17. package/dist/cli/main.js +5 -3
  18. package/dist/cli/plugins.js +4 -4
  19. package/dist/cli/snapshot-create.js +4 -4
  20. package/dist/cli/upgrade.d.ts +1 -1
  21. package/dist/cli/upgrade.js +3 -3
  22. package/dist/{client-C1yDYjoS.js → client-Cqf4TboL.js} +20 -15
  23. package/dist/{config-DrTeF80T.js → config-CPsE4T5L.js} +2 -2
  24. package/dist/{connections-D46f8vvi.js → connections-upFXLmhG.js} +10 -10
  25. package/dist/{conversation-DSo310Ps.js → conversation-CTpD_PGk.js} +7 -84
  26. package/dist/{conversation-privacy-CxlOCiUo.js → conversation-privacy-D6zJc8ff.js} +1 -1
  27. package/dist/{credential-sync-sm9fegLY.js → credential-sync-CS_6IC-f.js} +15 -14
  28. package/dist/{credentialed-lukuD-cc.js → credentialed-BU-OW7HP.js} +548 -71
  29. package/dist/{store-xMuuXRE0.d.ts → db-BzOCzVb1.d.ts} +4 -0
  30. package/dist/{executor-4vhvsMZJ.js → executor-CNS7ONt-.js} +6 -6
  31. package/dist/instrumentation.js +2 -2
  32. package/dist/{interruption-marker-CkncDT8d.js → interruption-marker-g6Xup3Fk.js} +1 -1
  33. package/dist/{job-queue-In9QKz-I.js → job-queue-DI-k-Bbk.js} +2 -2
  34. package/dist/{list-BkgnXEEB.js → list-CGoQdXTp.js} +193 -656
  35. package/dist/locks-D-0h111u.js +48 -0
  36. package/dist/{logging-DLSpkSSr.js → logging-n7knse-O.js} +1 -1
  37. package/dist/{history-C84mlAwO.js → media-85PDJxO_.js} +83 -1
  38. package/dist/{message-BObyg59s.js → message-BpaWd8TT.js} +4 -4
  39. package/dist/nitro.d.ts +1 -1
  40. package/dist/nitro.js +3 -3
  41. package/dist/{oauth-callback-server-B-WbSdoH.js → oauth-callback-server-CpGmEw_i.js} +2 -2
  42. package/dist/{prompt-BW9jFsqZ.js → prompt-BlQB0QQt.js} +1 -1
  43. package/dist/{registry-BgecKva0.js → registry-By36_xsm.js} +1 -1
  44. package/dist/reporting-schema-CZREhYc-.js +104 -0
  45. package/dist/{web-message-id-DlHNANtd.js → reporting-window-CVarfId9.js} +130 -7
  46. package/dist/{resolve-Cpa4Bu1O.js → resolve-CUIBcdPd.js} +3 -3
  47. package/dist/{result-D3TSmZZ8.js → result-BemsOMOO.js} +30 -11
  48. package/dist/{runner-DS5OKhEp.js → runner-BVok7PAA.js} +12 -11
  49. package/dist/{sandbox-egress-signals-CnDjY5rx.js → sandbox-egress-signals-BSUWQI95.js} +1 -1
  50. package/dist/{schema-0050HFnB.js → schema-ChnFo5f2.js} +36 -20
  51. package/dist/{schema-ZngqcOWM.d.ts → schema-jK4-1u_H.d.ts} +126 -12
  52. package/dist/{service-account-CKV2WTY7.js → service-account-DHvlLnZw.js} +4 -3
  53. package/dist/{service-account-DGjnDw9y.js → service-account-hMmESDcr.js} +3 -3
  54. package/dist/{session-stop-Dam1EI7B.js → session-stop-BQW_iFcd.js} +15 -15
  55. package/dist/{skills-CfAnfImD.js → skills-DmJJIuLF.js} +2 -2
  56. package/dist/{spawn-cmq9p4Ly.js → spawn-ChCm46i-.js} +2 -2
  57. package/dist/{store-C5PN02QO.js → store-1DVhoKOL.js} +3 -3
  58. package/dist/{store-zPEoJPBB.js → store-DCo2n5iJ.js} +3 -3
  59. package/dist/{task-queue-_mf28dSu.js → task-queue-HMC4QjFm.js} +3 -3
  60. package/dist/task-runner-cA6yjn_E.js +269 -0
  61. package/dist/{thread-state-BbbPh0Sn.js → thread-state-CHy8LS0C.js} +5 -5
  62. package/dist/{turn-failure-response-BKKmnMB5.js → turn-failure-response-Dr7erLWs.js} +9 -9
  63. package/dist/{turn-lifecycle-oP0kVeGz.js → turn-lifecycle-bf2fIYi_.js} +1 -1
  64. package/dist/{tasks-SB9z-VTU.js → turn-session-routing-BqO0qXLk.js} +928 -116
  65. package/dist/{validation--JsAGLwc.js → validation-DHR1ivfh.js} +1 -1
  66. package/dist/{version-jHwJp4hC.js → version-CXCHLgWm.js} +1 -1
  67. package/dist/version.js +1 -1
  68. package/dist/{viewer-CpXTGlep.js → viewer-DSbD6DF6.js} +201 -27
  69. package/dist/{work-3N-GHY9m.js → work-BE_qTYGi.js} +17 -36
  70. package/dist/{worker-CCdbcItd.js → worker-Cm9_sWXg.js} +15 -7
  71. package/migrations/0069_system_identity.sql +25 -0
  72. package/migrations/0070_review_item_foreign_keys.sql +5 -0
  73. package/migrations/AGENTS.md +17 -0
  74. package/migrations/meta/0069_snapshot.json +3979 -0
  75. package/migrations/meta/0070_snapshot.json +3997 -0
  76. package/migrations/meta/_journal.json +15 -1
  77. package/package.json +4 -4
  78. package/dist/db-BU4LQD8s.d.ts +0 -6
  79. package/dist/history-BgI33Kie.d.ts +0 -78
  80. package/dist/reporting-window-DVCD_10C.js +0 -227
  81. package/dist/task-runner-DMI2prDO.js +0 -1073
  82. package/migrations/README.md +0 -30
@@ -1,40 +1,40 @@
1
- import { A as resolveConversationRouting, At as upsertConversationMessage, Bt as clearPendingAuth, Ct as estimateConversationContextTokens, Dt as normalizeConversationText, E as sameDefinitionValue, Et as markConversationMessage, Gt as saveTurnCheckpoint, Ht as wasPendingAuthStopped, In as persistWithRetry, It as AuthorizationFlowDisabledError, Jt as failTurnRecord, K as commitAcceptedReply, Kt as abandonTurnRecord, Mt as PluginCredentialFailureError, N as automationEventCatalog, Nn as buildTurnErrorResponse, Ot as recordDeliveredAssistantMessage, Q as loadTurnProjection, S as automationDefinition, St as createConversationMemoryService, Tt as isHumanConversationMessage, V as registeredEventTriggerProblems, Wt as loadTurnCheckpoint, Xn as trimTrailingAssistantMessages, Xt as listTurnSummaries, Y as loadConversationProjection, Yt as getTurnRecord, Z as loadProjection, Zt as recordTurnSummary, _ as parseEventAutomationRow, ar as getThreadStopDecision, bt as appendThreadContextMessages, c as withScheduledAutomationLock, ct as recordSubscribedReplyRoute, er as buildDeterministicAssistantMessageId, f as deleteEventAutomation, ft as hydrateConversationMessages, gt as resolveConversationTitle, h as listDeletedEventAutomationsCreatedBy, hr as piMessageFromHistoryItem, ht as persistConversationMessageSummaries, i as readScheduledAutomation, ir as getSubscribedReplyPreflightDecision, it as recordAuthorizationCompleted, j as resolveTurnSessionRouting, kt as turnHasReply, o as saveScheduledAutomationInLock, pr as configValueSchema, pt as persistConversationMessages, q as commitMessages, qt as completeTurnRecord, r as parseScheduledAutomationRow, rr as decideSubscribedThreadReply, tr as buildDeterministicTurnId, ur as isExperimentalFeatureEnabled, vt as fallbackShortTitle, w as listAutomationVersions, wt as getConversationMessageSlackTs, xt as buildConversationContext } from "./tasks-SB9z-VTU.js";
2
- import { a as completeText, p as escapeXml, r as completeDecision, y as isProviderRetryError } from "./client-C1yDYjoS.js";
3
- import { c as getSlackClientSecret, i as getChatConfig, l as getSlackSigningSecret, n as botConfig, o as getSlackBotToken, s as getSlackClientId, v as normalizeSlackEmojiName, y as createS3AttachmentStorage } from "./config-DrTeF80T.js";
4
- import { B as isRecord, E as withSpan, H as toOptionalString, S as setTags, T as withLogContext, V as toOptionalNumber, b as setSpanAttributes, f as logError, h as logWarn, m as logInfo, n as createChatSdkLogger, o as getActiveTraceContext, p as logException, s as getActiveTraceId, t as buildTurnFailureResponse, y as setSentryUser } from "./logging-DLSpkSSr.js";
1
+ import { $t as appendThreadContextMessages, An as recordTurnSummary, At as loadProjection, Cn as loadTurnCheckpoint, Dn as failTurnRecord, En as completeTurnRecord, Et as commitMessages, Gt as hydrateConversationMessages, Ir as decideSubscribedThreadReply, Jr as piMessageFromHistoryItem, Jt as persistConversationMessageSummaries, Kr as configValueSchema, Kt as persistConversationMessages, Lr as getSubscribedReplyPreflightDecision, Lt as recordAuthorizationCompleted, Nr as buildDeterministicAssistantMessageId, On as getTurnRecord, Ot as loadConversationProjection, Pr as buildDeterministicTurnId, Rr as getThreadStopDecision, Tn as abandonTurnRecord, Tt as commitAcceptedReply, Ur as isExperimentalFeatureEnabled, Vt as recordSubscribedReplyRoute, Yt as resolveConversationTitle, Zt as fallbackShortTitle, _ as markDispatchCompleted, a as confirmDispatchMailboxAppend, an as markConversationMessage, cn as turnHasReply, d as getDispatchTurnId, dn as PluginCredentialFailureError, en as buildConversationContext, fr as buildTurnErrorResponse, g as markDispatchBlocked, h as markDispatchAwaitingResume, hn as AuthorizationFlowDisabledError, hr as persistWithRetry, i as claimDispatchMailboxAppend, in as isHumanConversationMessage, j as readAutomationTitlesByConversationId, jt as loadTurnProjection, kn as listTurnSummaries, kr as trimTrailingAssistantMessages, l as getDispatchInputMessageId, ln as upsertConversationMessage, n as resolveTurnSessionRouting, nn as estimateConversationContextTokens, on as normalizeConversationText, p as isTerminalDispatchStatus, rn as getConversationMessageSlackTs, s as getDispatchConversationId, sn as recordDeliveredAssistantMessage, t as resolveConversationRouting, tn as createConversationMemoryService, u as getDispatchRecord, v as markDispatchFailed, wn as saveTurnCheckpoint, xn as wasPendingAuthStopped, y as markDispatchRunning, yn as clearPendingAuth } from "./turn-session-routing-BqO0qXLk.js";
2
+ import { a as completeText, p as escapeXml, r as completeDecision, y as isProviderRetryError } from "./client-Cqf4TboL.js";
3
+ import { c as getSlackClientSecret, i as getChatConfig, l as getSlackSigningSecret, n as botConfig, o as getSlackBotToken, s as getSlackClientId, v as normalizeSlackEmojiName, y as createS3AttachmentStorage } from "./config-CPsE4T5L.js";
4
+ import { B as isRecord, E as withSpan, H as toOptionalString, S as setTags, T as withLogContext, V as toOptionalNumber, b as setSpanAttributes, f as logError, h as logWarn, m as logInfo, n as createChatSdkLogger, o as getActiveTraceContext, p as logException, s as getActiveTraceId, t as buildTurnFailureResponse, y as setSentryUser } from "./logging-n7knse-O.js";
5
5
  import { i as getActiveSpan, r as continueTrace } from "./sentry-MDkYAVPV.js";
6
6
  import { o as modelIdForProfile, r as defaultModelId } from "./model-profile-Bg8EoCKD.js";
7
7
  import { f as parseSlackTeamId, i as isActorUserId, m as parseSlackMessageTs, n as createSlackActor, o as isUserActor, r as createSlackResumeActor, s as parseActorUserId, t as createActor, u as parseSlackChannelId } from "./actor-CP9K2Idu.js";
8
- import { L as hasAgentTurnUsage, M as parseSessionSource, N as addAgentTurnUsage, V as requireSlackDestination, _ as withConversationEventLock, d as getConversationEventStore, h as getSqlExecutor, m as getDb, p as getConversationStore, v as loadConversationSlackTenantIds, x as locationFromRow, z as createSlackDestination } from "./viewer-CpXTGlep.js";
8
+ import { C as getSqlExecutor, E as loadConversationSlackTenantIds, H as parseSessionSource, M as resolveActorIdentityId, N as saveActorAvatar, S as getDb, T as withConversationEventLock, U as addAgentTurnUsage, Y as createSlackDestination, Z as requireSlackDestination, f as identityAvatarUrl, h as readUserAvatarUrls, k as locationFromRow, q as hasAgentTurnUsage, x as getConversationStore, y as getConversationEventStore } from "./viewer-DSbD6DF6.js";
9
9
  import { c as juniorIdentities, d as juniorDestinations, l as juniorUsers, o as conversationReadColumns, s as juniorConversations } from "./agent-invocations-C1RNOCs8.js";
10
- import { S as juniorCodeChanges, d as juniorAutomations, g as juniorConversationBriefs, l as juniorAutomationRuns, y as juniorConversationEvents } from "./executor-4vhvsMZJ.js";
11
- import { d as contextProvenance, p as instructionProvenanceFor } from "./history-C84mlAwO.js";
12
- import { d as actorLogContext, f as credentialContextForActor } from "./registry-BgecKva0.js";
13
- import { A as getConversationInfo, D as readSlackRawMessageContext, E as parseSlackThreadId, Ft as buildSteeringPiMessage, H as syncReviewItemSlackCards, It as withoutSavedSteeringMessages, L as describeEventAutomationTrigger, Mt as conversationSubtreeColumns, O as resolveSlackChannelIdFromMessage, R as effectiveTaskOutcomes, Ut as shouldEmitDevAgentTrace, _t as recordWebConversationActivity, at as getReviewItem, b as renderBlockText, dt as sendReviewItemMessages, et as runWithSlackInstallation, gt as createConversationId, i as buildAuthPauseResponse, j as listThreadReplies, jt as conversationSubtree, k as resolveSlackChannelIdFromThreadId, lt as takeReviewItem, n as executeAgentRun, nt as canReadReviewItem, p as latestProgressStatus, r as createContextCompactor, rt as closeReviewItem, st as reviewItemReaderForUser, t as createAgentRunner, vt as webActorFromEmail, wt as storeAttachment, y as renderAttachmentText, yt as resolveConversationDestination, zt as RetryableDeliveryError } from "./agent-runner-CAYfFmU7.js";
14
- import { $n as recordSlackAttachment, $t as verifyTimerWatchCredentialSubject, Bn as conversationArchivedForUser, Gn as viewerConversationMembership, Hn as conversationIsMemberRoot, In as createPluginLogger, J as truncateStatusText, K as normalizeSlackStatusText, Ln as readConversationAccessFromSql, Lt as cancelSubscriptions, Qn as readAttachmentVisionSummary, Rn as conversationArchiveTimes, Un as conversationNotArchivedForEmail, Vn as conversationHasParticipantUser, Wn as conversationNotArchivedForUser, Xn as getSlackFileId, Y as addReactionToMessage, cr as createPluginCache, en as SlackActionError, in as getSlackClient, ir as listConversationAnnotationsById, ln as withSlackRetries, n as createUserTokenStore, nt as removeReactionFromMessage, on as isDmChannel, p as getPlugins, pn as JUNIOR_THREAD_STATE_TTL_MS, rn as getHeaderString, sn as normalizeSlackConversationId, sr as lookupSlackUser, tn as downloadPrivateSlackFile, zn as conversationArchivedForEmail } from "./credentialed-lukuD-cc.js";
15
- import { a as MUTATION_LOCK_TTL_MS, c as withLock, i as getStateAdapter, o as acquireActiveLock } from "./adapter-04AcD2SO.js";
16
- import { W as readMessageAttachments, g as conversationFeedSchema, q as isVisionImageMediaType } from "./conversation-DSo310Ps.js";
17
- import { a as EVENT_SYSTEM_ACTOR, o as isEventConversationMessage } from "./prompt-BW9jFsqZ.js";
18
- import { B as ConversationTurnBoundaryError, G as isCooperativeTurnYieldError, H as TurnInputCommitLostError, J as markTurnClosed, K as isTurnInputCommitLostError, U as TurnInputDeferredError, V as CooperativeTurnYieldError, W as getConversationTurnBoundaryError, X as markTurnFailed, Y as markTurnCompleted, Z as startActiveTurn, q as isTurnInputDeferredError } from "./store-C5PN02QO.js";
19
- import { r as getAssistantReplyText } from "./interruption-marker-CkncDT8d.js";
20
- import { C as maybePostAutomatedTurnLimitNotice, D as recordFinishedTurnForAutomatedLimit, I as runDispatchOutcome, M as finishedRunReply, N as postsRunNotices, T as sendSlackReply, c as isEventMailboxMetadata, d as getEventCatalog, i as skipInputBeforeConversationStop, j as deliversFinalText, l as renderEventBatchText, s as eventBatchMessageEvents, u as timerWatchCredentialSubject, w as resolveSlackReplyTarget, x as joinMailboxText } from "./session-stop-Dam1EI7B.js";
21
- import { a as isEmptyUserInputAnswer, o as normalizeUserInputAnswer } from "./web-message-id-DlHNANtd.js";
22
- import { a as getVercelConversationWorkQueue, f as getTurnRequestDeadline } from "./job-queue-In9QKz-I.js";
23
- import { D as stopConversationWork, g as getConversationWorkState, h as getConversation, m as ensureConversationWake, r as appendAndEnqueueInboundMessage, w as requestConversationWork } from "./store-zPEoJPBB.js";
24
- import { j as eventAutomationTriggerSchema, l as automationListQuerySchema } from "./automation-CmeFBSEh.js";
25
- import { p as healthReportSchema, s as sumUtcHoursIntoSixHours } from "./reporting-window-DVCD_10C.js";
26
- import { A as readAutomationExecutions, D as readAutomationExecutionStatusDays, E as readAutomationExecutionHours, M as readAutomationTitlesByConversationId, O as readAutomationExecutionStatusHours, S as emptyAutomationRunWindows, T as readAutomationExecutionDays, _ as markDispatchBlocked, a as claimDispatchMailboxAppend, b as markDispatchRunning, c as getDispatchConversationId, d as getDispatchRecord, f as getDispatchTurnId, g as markDispatchAwaitingResume, j as readAutomationRuns, k as readAutomationExecutionSummaries, l as getDispatchIdForConversation, m as isTerminalDispatchStatus, n as scheduleSessionCompletedPluginTasks, o as confirmDispatchMailboxAppend, t as processPluginTask, u as getDispatchInputMessageId, v as markDispatchCompleted, w as readAutomationExecutionByConversationId, y as markDispatchFailed } from "./task-runner-DMI2prDO.js";
27
- import { a as withAgentInvocationResultActors, c as resolveMailboxTurnWork, d as legacyWebMailboxMetadataSchema, i as startAgentInvocationsForChildInput, l as sourceFromTurnInput, o as getAuthPausedConversationTurnId, s as getEventBatchDelayMs, t as enqueueAgentInvocationResult, u as turnInputFactsFromConversationMessage } from "./result-D3TSmZZ8.js";
28
- import { n as getTurnLifecycle, t as ConversationTurnLifecycleService } from "./turn-lifecycle-oP0kVeGz.js";
29
- import { i as resolveAgentInvocationId, o as AgentRunError, s as executeTurn, t as createAgentInvocationWorker } from "./work-3N-GHY9m.js";
30
- import { a as persistThreadStateById, i as persistThreadRuntimeState, n as getPersistedSandboxState, o as coerceThreadConversationState, r as getPersistedThreadState, t as getLocationConfigurationService } from "./thread-state-BbbPh0Sn.js";
31
- import { c as deleteWebAuthorization, d as createPausedTurns, f as getPausedTurnRequest, h as getTurnAuthorization, m as wakePausedTurn, s as createWebAuthorization } from "./connections-D46f8vvi.js";
32
- import { t as bindMessageAgent } from "./message-BObyg59s.js";
33
- import { t as bindSpawnAgent } from "./spawn-cmq9p4Ly.js";
34
- import { a as persistAuthPauseTurnState, c as getTurnUserSlackMessageTs, i as completeAuthPauseTurn, l as buildDeliveredTurnStatePatch, n as getAgentTurnDiagnosticsAttributes, o as getTurnUserMessage, r as requireTurnFailureEventId, s as getTurnUserReplyAttachmentContext, t as finalizeFailedTurnReplyWithEvent, u as loadPendingMessageCards } from "./turn-failure-response-BKKmnMB5.js";
10
+ import { S as juniorCodeChanges, g as juniorConversationBriefs, y as juniorConversationEvents } from "./executor-CNS7ONt-.js";
11
+ import { g as instructionProvenanceFor, m as contextProvenance, r as isVisionImageMediaType } from "./media-85PDJxO_.js";
12
+ import { d as actorLogContext, f as credentialContextForActor } from "./registry-By36_xsm.js";
13
+ import { Et as withoutSavedSteeringMessages, F as getConversationInfo, I as listThreadReplies, M as readSlackRawMessageContext, N as resolveSlackChannelIdFromMessage, Nt as shouldEmitDevAgentTrace, P as resolveSlackChannelIdFromThreadId, St as conversationSubtreeColumns, Tt as buildSteeringPiMessage, b as renderBlockText, ct as webActorFromEmail, et as deliverReviewItemChange, i as buildAuthPauseResponse, j as parseSlackThreadId, k as runWithSlackInstallation, kt as RetryableDeliveryError, lt as resolveConversationDestination, mt as storeAttachment, n as executeAgentRun, ot as createConversationId, p as latestProgressStatus, r as createContextCompactor, st as recordWebConversationActivity, t as createAgentRunner, xt as conversationSubtree, y as renderAttachmentText } from "./agent-runner-CrDrPUtY.js";
14
+ import { An as normalizeSlackConversationId, Ar as lookupSlackUser, Cn as SlackActionError, Dn as getSlackClient, Dr as listConversationAnnotationsById, Dt as removeReactionFromMessage, E as listReplyActionItems, En as getHeaderString, F as isOnlyAssignee, In as JUNIOR_THREAD_STATE_TTL_MS, M as canActOnReviewItem, Mn as withSlackRetries, P as canReadReviewItem, Sn as verifyTimerWatchCredentialSubject, Sr as recordSlackAttachment, _t as normalizeSlackStatusText, ar as createPluginLogger, b as closeReviewItem, bt as addReactionToMessage, cr as conversationArchivedForEmail, dr as conversationIsMemberRoot, fr as conversationNotArchivedForEmail, jr as createPluginCache, k as takeReviewItem, kn as isDmChannel, lr as conversationArchivedForUser, mr as viewerConversationMembership, n as createUserTokenStore, on as cancelSubscriptions, or as readConversationAccessFromSql, p as getPlugins, pr as conversationNotArchivedForUser, sr as conversationArchiveTimes, ur as conversationHasParticipantUser, w as getReviewItem, wn as downloadPrivateSlackFile, xr as readAttachmentVisionSummary, yr as getSlackFileId, yt as truncateStatusText, z as reviewItemReaderForUser } from "./credentialed-BU-OW7HP.js";
15
+ import { a as withLock, n as MUTATION_LOCK_TTL_MS, r as acquireActiveLock } from "./locks-D-0h111u.js";
16
+ import { r as getStateAdapter } from "./adapter-2-CG25be.js";
17
+ import { W as readMessageAttachments, g as conversationFeedSchema } from "./conversation-CTpD_PGk.js";
18
+ import { a as EVENT_SYSTEM_ACTOR, o as isEventConversationMessage } from "./prompt-BlQB0QQt.js";
19
+ import { B as ConversationTurnBoundaryError, G as isCooperativeTurnYieldError, H as TurnInputCommitLostError, J as markTurnClosed, K as isTurnInputCommitLostError, U as TurnInputDeferredError, V as CooperativeTurnYieldError, W as getConversationTurnBoundaryError, X as markTurnFailed, Y as markTurnCompleted, Z as startActiveTurn, q as isTurnInputDeferredError } from "./store-1DVhoKOL.js";
20
+ import { r as getAssistantReplyText } from "./interruption-marker-g6Xup3Fk.js";
21
+ import { C as maybePostAutomatedTurnLimitNotice, D as recordFinishedTurnForAutomatedLimit, I as runDispatchOutcome, M as finishedRunReply, N as postsRunNotices, T as sendSlackReply, c as isEventMailboxMetadata, i as skipInputBeforeConversationStop, j as deliversFinalText, l as renderEventBatchText, s as eventBatchMessageEvents, u as timerWatchCredentialSubject, w as resolveSlackReplyTarget, x as joinMailboxText } from "./session-stop-BQW_iFcd.js";
22
+ import { _ as normalizeUserInputAnswer, g as isEmptyUserInputAnswer } from "./reporting-window-CVarfId9.js";
23
+ import { a as getVercelConversationWorkQueue, f as getTurnRequestDeadline } from "./job-queue-DI-k-Bbk.js";
24
+ import { D as stopConversationWork, g as getConversationWorkState, h as getConversation, m as ensureConversationWake, r as appendAndEnqueueInboundMessage, w as requestConversationWork } from "./store-DCo2n5iJ.js";
25
+ import { t as healthReportSchema } from "./reporting-schema-CZREhYc-.js";
26
+ import { a as withAgentInvocationResultActors, c as resolveMailboxTurnWork, d as legacyWebMailboxMetadataSchema, l as sourceFromTurnInput, n as enqueueAgentInvocationResult, o as getAuthPausedConversationTurnId, s as getEventBatchDelayMs, t as createChildConversationWorker, u as turnInputFactsFromConversationMessage } from "./result-BemsOMOO.js";
27
+ import { n as getTurnLifecycle, t as ConversationTurnLifecycleService } from "./turn-lifecycle-bf2fIYi_.js";
28
+ import { a as AgentRunError, o as executeTurn } from "./work-BE_qTYGi.js";
29
+ import { a as persistThreadStateById, i as persistThreadRuntimeState, n as getPersistedSandboxState, o as coerceThreadConversationState, r as getPersistedThreadState, t as getLocationConfigurationService } from "./thread-state-CHy8LS0C.js";
30
+ import { c as deleteWebAuthorization, d as createPausedTurns, f as getPausedTurnRequest, h as getTurnAuthorization, m as wakePausedTurn, s as createWebAuthorization } from "./connections-upFXLmhG.js";
31
+ import { t as bindMessageAgent } from "./message-BpaWd8TT.js";
32
+ import { t as bindSpawnAgent } from "./spawn-ChCm46i-.js";
33
+ import { n as scheduleSessionCompletedPluginTasks, t as processPluginTask } from "./task-runner-cA6yjn_E.js";
34
+ import { a as persistAuthPauseTurnState, c as getTurnUserSlackMessageTs, i as completeAuthPauseTurn, l as buildDeliveredTurnStatePatch, n as getAgentTurnDiagnosticsAttributes, o as getTurnUserMessage, r as requireTurnFailureEventId, s as getTurnUserReplyAttachmentContext, t as finalizeFailedTurnReplyWithEvent, u as loadPendingMessageCards } from "./turn-failure-response-Dr7erLWs.js";
35
35
  import { z } from "zod";
36
36
  import { createHash } from "node:crypto";
37
- import { and, asc, count, desc, eq, exists, inArray, isNotNull, isNull, ne, notInArray, or, sql } from "drizzle-orm";
37
+ import { and, asc, desc, eq, inArray, isNotNull, isNull, ne, or, sql } from "drizzle-orm";
38
38
  import { conversationSidebarAnnotationSchema, createSlackSource, objectTypeSchema } from "@sentry/junior-plugin-api";
39
39
  import { alias } from "drizzle-orm/pg-core";
40
40
  import { del, get, put } from "@vercel/blob";
@@ -79,7 +79,7 @@ function buildDispatchRoutingContext(dispatch) {
79
79
  };
80
80
  }
81
81
  /** Build the stable mailbox work item for one agent dispatch. */
82
- function buildAgentDispatchInboundMessage(dispatch, nowMs = Date.now()) {
82
+ function buildAgentDispatchInboundMessage(dispatch, nowMs = Date.now(), authorIdentityId) {
83
83
  return {
84
84
  conversationId: getDispatchConversationId(dispatch),
85
85
  createdAtMs: dispatch.createdAtMs,
@@ -88,6 +88,7 @@ function buildAgentDispatchInboundMessage(dispatch, nowMs = Date.now()) {
88
88
  inboundMessageId: getDispatchInputMessageId(dispatch.id),
89
89
  input: {
90
90
  text: "[plugin dispatch]",
91
+ ...authorIdentityId ? { authorIdentityId } : void 0,
91
92
  metadata: {
92
93
  dispatchId: dispatch.id,
93
94
  kind: "agent_dispatch"
@@ -111,7 +112,7 @@ async function enqueueAgentDispatch(dispatch, options) {
111
112
  sessionSource: claimedDispatch.source
112
113
  });
113
114
  await appendAndEnqueueInboundMessage({
114
- message: buildAgentDispatchInboundMessage(claimedDispatch, nowMs),
115
+ message: buildAgentDispatchInboundMessage(claimedDispatch, nowMs, await resolveActorIdentityId(getSqlExecutor(), claimedDispatch.actor)),
115
116
  conversationStore: options.conversationStore,
116
117
  nowMs,
117
118
  queue: options.queue,
@@ -389,21 +390,6 @@ var ReviewItemActionError = class extends Error {
389
390
  this.code = code;
390
391
  }
391
392
  };
392
- /**
393
- * Return whether a User can act on an open Review item: an assigned User,
394
- * anyone for an item assigned to Everyone, and anyone for an item with no
395
- * assignees. The caller checks that the User can see the item.
396
- */
397
- function canActOnReviewItem(item, userId) {
398
- if (item.status !== "open") return false;
399
- if (item.assignedToEveryone) return true;
400
- if (item.assignees.length === 0) return true;
401
- return item.assignees.some((assignee) => assignee.userId === userId);
402
- }
403
- /** Return whether a User is already the only assignee, so take does nothing. */
404
- function isOnlyAssignee(item, userId) {
405
- return !item.assignedToEveryone && item.assignees.length === 1 && item.assignees[0]?.userId === userId;
406
- }
407
393
  /** Read one Review item that a User can see, or fail as not found. */
408
394
  async function readReviewItemForUser(user, id) {
409
395
  const item = await getReviewItem(getDb(), id);
@@ -432,9 +418,12 @@ function reviewItemChange(action, user) {
432
418
  user
433
419
  };
434
420
  }
435
- /** Return whether a closed item is the result of this same action. */
421
+ /**
422
+ * Return whether a closed item is the result of this same action. An item
423
+ * that a person's message closed never matches a card or desk action.
424
+ */
436
425
  function isSameClose(item, action, user) {
437
- return item.closedBy?.userId === user.id && item.status === closedStatus(action) && action === "submit" === Boolean(item.answer);
426
+ return item.closeReason !== "Replied in thread" && item.closedBy?.userId === user.id && item.status === closedStatus(action) && action === "submit" === Boolean(item.answer);
438
427
  }
439
428
  function submittedAnswer(item, answer) {
440
429
  if (item.kind !== "action" || !item.request) throw new ReviewItemActionError("invalid", "Only an action item takes an answer.");
@@ -453,10 +442,11 @@ function submittedAnswer(item, answer) {
453
442
  * succeeds, so a client can retry after a failure. With `firstCloseOnly`,
454
443
  * only the call that closes the item succeeds, and every other call gets a
455
444
  * conflict. Use it when the caller adds its own input for the action, such
456
- * as a Slack card click, so one action adds one Turn. `exceptConversationId`
457
- * is the Conversation where the person acted. It gets the person's input
458
- * from its own surface, so it gets no message here. Slack cards of the item
459
- * change in place; see `syncReviewItemSlackCards` for `getSlackAdapter`.
445
+ * as a card click in a chat thread, so one action adds one Turn.
446
+ * `exceptConversationId` is the Conversation where the person acted. It gets
447
+ * the person's input from its own surface, so it gets no message here.
448
+ * `delivery` shows the change and sends the messages; see
449
+ * `deliverReviewItemChange`.
460
450
  */
461
451
  async function actOnReviewItem(args) {
462
452
  const { action, user } = args;
@@ -472,16 +462,12 @@ async function actOnReviewItem(args) {
472
462
  userId: user.id
473
463
  })) throw new ReviewItemActionError("conflict", "Review item is closed.");
474
464
  const taken = await readReviewItemForUser(user, item.id);
475
- await syncReviewItemSlackCards({
476
- getSlackAdapter: args.getSlackAdapter,
477
- itemId: taken.id
478
- });
479
- await sendReviewItemMessages({
465
+ await deliverReviewItemChange({
480
466
  change: reviewItemChange(action, user),
467
+ delivery: args.delivery,
481
468
  exceptConversationId: args.exceptConversationId,
482
469
  item: taken,
483
- nowMs,
484
- queue: args.queue
470
+ nowMs
485
471
  });
486
472
  return taken;
487
473
  }
@@ -501,19 +487,92 @@ async function actOnReviewItem(args) {
501
487
  closed = await readReviewItemForUser(user, item.id);
502
488
  }
503
489
  if (args.firstCloseOnly ? !closedNow : !isSameClose(closed, action, user)) throw new ReviewItemActionError("conflict", "Review item is closed.");
504
- await syncReviewItemSlackCards({
505
- getSlackAdapter: args.getSlackAdapter,
506
- itemId: closed.id
507
- });
508
- await sendReviewItemMessages({
490
+ await deliverReviewItemChange({
509
491
  change: reviewItemChange(action, user),
492
+ delivery: args.delivery,
510
493
  exceptConversationId: args.exceptConversationId,
511
494
  item: closed,
512
- nowMs,
513
- queue: args.queue
495
+ nowMs
514
496
  });
515
497
  return closed;
516
498
  }
499
+ /**
500
+ * Return whether a reply from this User answers an open action item, or is
501
+ * a retry of the reply from this User that closed it.
502
+ */
503
+ function replyAnswers(item, user) {
504
+ if (item.status !== "open") return item.closedBy?.userId === user?.id;
505
+ if (item.assignedToEveryone) return false;
506
+ if (item.assignees.length === 0) return true;
507
+ return user !== void 0 && item.assignees.some((assignee) => assignee.userId === user.id);
508
+ }
509
+ /** Return the answer that the text of a person's message gives to an item. */
510
+ function messageAnswer(item, text) {
511
+ if (!item.request) return void 0;
512
+ const answer = normalizeUserInputAnswer(item.request, {
513
+ other: text,
514
+ requestId: item.id,
515
+ values: {}
516
+ });
517
+ return isEmptyUserInputAnswer(answer) ? void 0 : answer;
518
+ }
519
+ /**
520
+ * Close the open action items that a person answered with a reply in their
521
+ * thread. Call it after the reply is saved to the Conversation, so the
522
+ * reply always reaches the agent. Each item that it closes saves `text` as
523
+ * its answer, so the item keeps what the person said.
524
+ *
525
+ * A reply closes an item only when all of these are true:
526
+ * - The item is an open `action` item.
527
+ * - The agent added it in this Conversation (`addedByConversationId`).
528
+ * - The replier is an assigned User, or the item has no assignees. A Slack
529
+ * replier without a Junior User closes only items with no assignees.
530
+ *
531
+ * Items assigned to Everyone, or only to other people, stay open. They need
532
+ * an answer on the card or the review desk. Each item closes as `done` with
533
+ * `REVIEW_ITEM_REPLIED_REASON`, and the replier closes it. The reply itself
534
+ * is the answer in this Conversation, so only the other linked
535
+ * Conversations get a message. A click that closes the item first wins.
536
+ * A retry of the reply, or a later reply from the same person, delivers
537
+ * the items that it closed again: the cards show the close, and only the
538
+ * missing messages go. Each message has a key, so a message that was sent
539
+ * does not go again. `delivery` shows the change and sends the messages;
540
+ * see `deliverReviewItemChange`.
541
+ */
542
+ async function closeReviewItemsAnsweredByReply(args) {
543
+ const db = getDb();
544
+ const items = await listReplyActionItems(db, args.conversationId);
545
+ if (items.length === 0) return;
546
+ if (args.canAnswer && !await args.canAnswer()) return;
547
+ const user = await args.resolveUser();
548
+ const nowMs = args.nowMs ?? Date.now();
549
+ for (const item of items.filter((candidate) => replyAnswers(candidate, user))) {
550
+ let closedItem = item;
551
+ if (item.status === "open") {
552
+ const answer = messageAnswer(item, args.text);
553
+ if (!await closeReviewItem(db, {
554
+ ...answer ? { answer } : void 0,
555
+ id: item.id,
556
+ nowMs,
557
+ reason: "Replied in thread",
558
+ status: "done",
559
+ ...user ? { closedByUserId: user.id } : void 0
560
+ })) continue;
561
+ closedItem = await getReviewItem(db, item.id);
562
+ if (!closedItem) continue;
563
+ }
564
+ await deliverReviewItemChange({
565
+ change: {
566
+ kind: "replied",
567
+ who: user ? user.displayName ?? user.email : "A person"
568
+ },
569
+ delivery: args.delivery,
570
+ exceptConversationId: args.conversationId,
571
+ item: closedItem,
572
+ nowMs
573
+ });
574
+ }
575
+ }
517
576
  // src/chat/slack/errors.ts
518
577
  /** Extract Slack's stable API error code from Web API errors. */
519
578
  function getSlackApiErrorCode(error) {
@@ -847,6 +906,7 @@ function combineTurnText(queuedMessages, latestText) {
847
906
  }
848
907
  // src/chat/services/message-actor-identity.ts
849
908
  const messageActors = /* @__PURE__ */ new WeakMap();
909
+ const messageAuthorIdentityIds = /* @__PURE__ */ new WeakMap();
850
910
  function canonicalUserId(author, actor) {
851
911
  const authorUserId = parseActorUserId(author.userId);
852
912
  if (authorUserId && authorUserId !== actor.userId) throw new Error("Message actor user id mismatch");
@@ -877,6 +937,17 @@ function bindMessageActorIdentity(message, actor) {
877
937
  applyActorToAuthor(message.author, currentActor);
878
938
  return currentActor;
879
939
  }
940
+ /**
941
+ * Keep the author Identity id that ingress resolved, next to the Actor.
942
+ * The Message write reads it, so the write does not guess the author again.
943
+ */
944
+ function bindMessageAuthorIdentityId(message, authorIdentityId) {
945
+ if (authorIdentityId) messageAuthorIdentityIds.set(message, authorIdentityId);
946
+ }
947
+ /** Read the author Identity id that ingress bound to a Chat SDK message. */
948
+ function getMessageAuthorIdentityId(message) {
949
+ return messageAuthorIdentityIds.get(message);
950
+ }
880
951
  /** Read message identity without promoting adapter display fallbacks. */
881
952
  function getMessageActorIdentity(message) {
882
953
  return messageActors.get(message) ?? actorFromAuthor(message.author);
@@ -2072,6 +2143,7 @@ function restoreMessage(args) {
2072
2143
  const message = Message.fromJSON(metadata.message);
2073
2144
  message.attachments = message.attachments.map((attachment) => args.adapter.rehydrateAttachment(attachment));
2074
2145
  rehydrateAttachmentFetchers(message);
2146
+ bindMessageAuthorIdentityId(message, args.record.input.authorIdentityId);
2075
2147
  return message;
2076
2148
  }
2077
2149
  async function bindSlackActorIdentities(args) {
@@ -2083,6 +2155,10 @@ async function bindSlackActorIdentities(args) {
2083
2155
  await Promise.all([...byAuthorId].map(async ([authorId, messages]) => {
2084
2156
  const profile = await args.lookupSlackUser(args.teamId, authorId);
2085
2157
  await Promise.all(messages.map((message) => ensureSlackMessageActorIdentity(message, args.teamId, async () => profile)));
2158
+ if (profile && messages.some(getMessageAuthorIdentityId)) await resolveActorIdentityId(getSqlExecutor(), createSlackActor(args.teamId, authorId, {
2159
+ fullName: profile.fullName,
2160
+ userName: profile.userName
2161
+ }));
2086
2162
  }));
2087
2163
  }
2088
2164
  function restoreThread(args) {
@@ -2244,6 +2320,7 @@ function createSlackConversationWorker(options) {
2244
2320
  /** Serialize a Slack message and the active trace into the durable mailbox. */
2245
2321
  function buildSlackInboundMessage(args) {
2246
2322
  const authorId = requireSlackAuthorId(args.message);
2323
+ const authorIdentityId = getMessageAuthorIdentityId(args.message);
2247
2324
  const destination = createSlackDestination({
2248
2325
  channelId: args.thread.channelId,
2249
2326
  teamId: args.installation?.teamId
@@ -2266,6 +2343,7 @@ function buildSlackInboundMessage(args) {
2266
2343
  input: {
2267
2344
  text: args.message.text || " ",
2268
2345
  authorId,
2346
+ ...authorIdentityId ? { authorIdentityId } : void 0,
2269
2347
  attachments: args.message.attachments,
2270
2348
  metadata: {
2271
2349
  platform: "slack",
@@ -2722,9 +2800,11 @@ function toConversationMessage(args) {
2722
2800
  const slackTs = getMessageTimestamp(args.entry);
2723
2801
  const messageHasPotentialImageAttachment = hasPotentialImageAttachment(args.entry.attachments);
2724
2802
  const imageAttachmentCount = messageHasPotentialImageAttachment ? countPotentialImageAttachments(args.entry.attachments) : 0;
2803
+ const authorIdentityId = args.entry.author.isMe ? void 0 : getMessageAuthorIdentityId(args.entry);
2725
2804
  return {
2726
2805
  id: args.entry.id,
2727
2806
  role: args.entry.author.isMe ? "assistant" : "user",
2807
+ ...authorIdentityId ? { authorIdentityId } : void 0,
2728
2808
  text: resolveMessageText(args),
2729
2809
  createdAtMs: args.entry.metadata.dateSent.getTime(),
2730
2810
  author: {
@@ -2814,578 +2894,6 @@ function isAllowedSignInEmail(email, allowedDomains) {
2814
2894
  const separator = normalized.lastIndexOf("@");
2815
2895
  return allowedDomains.length > 0 && separator > 0 && isAllowedDomain(normalized.slice(separator + 1), allowedDomains);
2816
2896
  }
2817
- // src/chat/automations/collection.ts
2818
- /**
2819
- * The web collection applies access before counts, filter options, and paging.
2820
- * Keep its ownership rules aligned with the direct Automation reader. A public
2821
- * Destination only grants access inside a viewer's linked Slack workspaces.
2822
- */
2823
- function viewerAutomationCollection(user) {
2824
- const db = getDb();
2825
- const teamIds = user.identities.filter((identity) => identity.provider === "slack").flatMap((identity) => identity.providerTenantId ? [identity.providerTenantId] : []);
2826
- const automation = juniorAutomations;
2827
- const destination = juniorDestinations;
2828
- const isScheduled = eq(automation.kind, "scheduled");
2829
- const owned = inArray(automation.creatorIdentityId, user.identities.map((identity) => identity.id));
2830
- const publicDestination = sql`coalesce(${destination.visibility} = 'public', false)`;
2831
- const publicToViewer = and(inArray(automation.teamId, teamIds), publicDestination, ne(automation.status, "completed"));
2832
- const runs = juniorAutomationRuns;
2833
- const lastRunNeedsAttention = sql`coalesce((select ${runs.status} in ('failed', 'blocked') from ${runs}
2834
- where ${runs.automationId} = ${automation.id} and ${runs.status} in ('blocked', 'completed', 'failed') and ${runs.completedAtMs} is not null
2835
- order by ${runs.completedAtMs} desc, ${runs.id} desc limit 1), false)`;
2836
- return db.select({
2837
- kind: automation.kind,
2838
- id: automation.id,
2839
- title: automation.title,
2840
- createdAtMs: automation.createdAtMs,
2841
- state: sql`${automation.status}`.as("state"),
2842
- lastRunNeedsAttention: lastRunNeedsAttention.as("last_run_needs_attention"),
2843
- trigger: sql`${automation.record}->'trigger'`.as("trigger"),
2844
- owned: sql`${owned}`.as("owned"),
2845
- resource: sql`case when ${isScheduled} then '' else concat_ws(' ', ${automation.identifier}, ${automation.record}->'trigger'->>'label', ${automation.namespace}) end`.as("resource"),
2846
- creator: sql`${automation.teamId} || ':' || ${automation.creatorSlackUserId}`.as("creator"),
2847
- creatorLabel: sql`coalesce(nullif(trim(${automation.record}->'createdBy'->>'fullName'), ''), nullif(trim(${automation.record}->'createdBy'->>'userName'), ''), ${automation.creatorSlackUserId})`.as("creator_label"),
2848
- destination: sql`${automation.teamId} || ':' || ${automation.channelId}`.as("destination"),
2849
- destinationLabel: sql`coalesce(nullif(${destination.displayName}, ''), ${automation.channelId})`.as("destination_label"),
2850
- isPublic: publicDestination.as("is_public"),
2851
- instruction: automation.instruction
2852
- }).from(automation).leftJoin(destination, and(eq(destination.provider, "slack"), eq(destination.providerTenantId, automation.teamId), eq(destination.providerDestinationId, automation.channelId))).where(and(ne(automation.status, "deleted"), or(owned, publicToViewer))).as("accessible_automations");
2853
- }
2854
- /**
2855
- * List the accessible event automations whose trigger cannot work with the
2856
- * enabled plugins. `eventTriggerProblems` decides, the same check that the
2857
- * tools and the dashboard use, so SQL does not keep its own copy of the rules.
2858
- */
2859
- async function unavailableEventAutomationIds(collection) {
2860
- const catalog = automationEventCatalog(getEventCatalog());
2861
- return (await getDb().select({
2862
- id: collection.id,
2863
- trigger: collection.trigger
2864
- }).from(collection).where(eq(collection.kind, "event"))).flatMap((row) => {
2865
- const trigger = eventAutomationTriggerSchema.safeParse(row.trigger);
2866
- return trigger.success && registeredEventTriggerProblems(catalog, trigger.data).length === 0 ? [] : [row.id];
2867
- });
2868
- }
2869
- /** Read an SQL page and complete, access-scoped counts and filter options. */
2870
- async function readAutomationCollection(user, input) {
2871
- const db = getDb();
2872
- const collection = viewerAutomationCollection(user);
2873
- const unavailableIds = await unavailableEventAutomationIds(collection);
2874
- const unavailable = unavailableIds.length > 0 ? inArray(collection.id, unavailableIds) : sql`false`;
2875
- const attention = and(notInArray(collection.state, ["paused", "completed"]), or(eq(collection.state, "blocked"), unavailable, eq(collection.lastRunNeedsAttention, true)));
2876
- const where = and(input.scope === "mine" ? eq(collection.owned, true) : input.scope === "public" ? eq(collection.isPublic, true) : input.scope === "attention" ? attention : void 0, input.type === "all" ? void 0 : eq(collection.kind, input.type), input.state === "all" ? void 0 : input.state === "unavailable" ? unavailable : eq(collection.state, input.state), input.creator ? eq(collection.creator, input.creator) : void 0, input.destination ? eq(collection.destination, input.destination) : void 0, input.q ? sql`strpos(lower(concat_ws(' ', ${collection.title}, ${collection.instruction}, ${collection.resource})), ${input.q.toLowerCase()}) > 0` : void 0);
2877
- const [totals, counts, creators, destinations] = await Promise.all([
2878
- db.select({ total: count() }).from(collection).where(where),
2879
- db.select({
2880
- all: count(),
2881
- mine: sql`count(*) filter (where ${collection.owned})::int`,
2882
- public: sql`count(*) filter (where ${collection.isPublic})::int`,
2883
- private: sql`count(*) filter (where not ${collection.isPublic})::int`
2884
- }).from(collection),
2885
- db.select({
2886
- value: collection.creator,
2887
- label: sql`min(${collection.creatorLabel})`
2888
- }).from(collection).groupBy(collection.creator).orderBy(sql`min(${collection.creatorLabel})`, collection.creator),
2889
- db.select({
2890
- value: collection.destination,
2891
- label: sql`min(${collection.destinationLabel})`
2892
- }).from(collection).groupBy(collection.destination).orderBy(sql`min(${collection.destinationLabel})`, collection.destination)
2893
- ]);
2894
- const total = totals[0].total;
2895
- const page = Math.min(input.page, Math.max(1, Math.ceil(total / input.pageSize)));
2896
- const order = input.sort === "oldest" ? asc(collection.createdAtMs) : input.sort === "title" ? asc(sql`lower(coalesce(nullif(trim(${collection.title}), ''), ${collection.instruction}))`) : desc(collection.createdAtMs);
2897
- return {
2898
- rows: await db.select({
2899
- id: collection.id,
2900
- kind: collection.kind
2901
- }).from(collection).where(where).orderBy(order, desc(collection.id)).limit(input.pageSize).offset((page - 1) * input.pageSize),
2902
- total,
2903
- page,
2904
- pageSize: input.pageSize,
2905
- counts: counts[0],
2906
- creators,
2907
- destinations
2908
- };
2909
- }
2910
- // src/chat/automations/creator.ts
2911
- /**
2912
- * Return whether the user created the Automation. Both kinds store the
2913
- * creating Junior identity, so one rule decides ownership.
2914
- */
2915
- function isAutomationCreator(user, automation) {
2916
- return user.identities.some((identity) => identity.id === automation.creatorIdentityId);
2917
- }
2918
- // src/chat/scheduled-automations/personal.ts
2919
- var PersonalScheduledAutomationNotFoundError = class extends Error {
2920
- constructor() {
2921
- super("Scheduled automation was not found.");
2922
- this.name = "PersonalScheduledAutomationNotFoundError";
2923
- }
2924
- };
2925
- /** Delete one scheduled automation created by the viewer. */
2926
- async function deleteViewerScheduledAutomation(db, user, id, nowMs = Date.now()) {
2927
- await withScheduledAutomationLock(db, id, async (tx) => {
2928
- const task = await readScheduledAutomation(tx, id);
2929
- if (!task || task.status === "deleted" || !isAutomationCreator(user, task)) throw new PersonalScheduledAutomationNotFoundError();
2930
- await saveScheduledAutomationInLock(tx, {
2931
- ...task,
2932
- nextRunAtMs: void 0,
2933
- runNowAtMs: void 0,
2934
- status: "deleted",
2935
- updatedAtMs: nowMs
2936
- }, task);
2937
- });
2938
- }
2939
- // src/chat/automations/read.ts
2940
- const TASK_EXECUTION_LIST_LIMIT = 100;
2941
- const AUTOMATION_VERSION_LIST_LIMIT = 100;
2942
- function creatorLabel(creator) {
2943
- return creator.fullName?.trim() || (creator.userName?.trim() ? `@${creator.userName.trim()}` : "") || creator.slackUserId;
2944
- }
2945
- function creatorKey(teamId, slackUserId) {
2946
- return `${teamId}:${slackUserId}`;
2947
- }
2948
- async function creatorProfiles(candidates) {
2949
- const selectors = new Map(candidates.map(({ task }) => {
2950
- const teamId = task.destination.teamId;
2951
- const slackUserId = task.createdBy.slackUserId;
2952
- return [creatorKey(teamId, slackUserId), {
2953
- slackUserId,
2954
- teamId
2955
- }];
2956
- }));
2957
- if (selectors.size === 0) return /* @__PURE__ */ new Map();
2958
- const rows = await getDb().select({
2959
- avatarUrl: juniorIdentities.avatarUrl,
2960
- email: juniorUsers.primaryEmailNormalized,
2961
- emailVerified: juniorIdentities.emailVerified,
2962
- slackUserId: juniorIdentities.providerSubjectId,
2963
- teamId: juniorIdentities.providerTenantId
2964
- }).from(juniorIdentities).leftJoin(juniorUsers, eq(juniorUsers.id, juniorIdentities.userId)).where(and(eq(juniorIdentities.kind, "user"), eq(juniorIdentities.provider, "slack"), or(...[...selectors.values()].map((selector) => and(eq(juniorIdentities.providerTenantId, selector.teamId), eq(juniorIdentities.providerSubjectId, selector.slackUserId))))));
2965
- return new Map(rows.map((row) => [creatorKey(row.teamId, row.slackUserId), {
2966
- email: row.emailVerified ? row.email ?? void 0 : void 0,
2967
- avatarUrl: row.avatarUrl ?? void 0
2968
- }]));
2969
- }
2970
- function destinationKey(destination) {
2971
- return `${destination.teamId}:${destination.channelId}`;
2972
- }
2973
- function displayText(value, fallback) {
2974
- return value.trim() || fallback;
2975
- }
2976
- function taskDisplayTitle(title, instruction, fallback) {
2977
- const stored = title?.trim();
2978
- if (stored) return stored;
2979
- return fallbackShortTitle(instruction, fallback);
2980
- }
2981
- async function destinationDetails(destinations) {
2982
- const selectors = new Map(destinations.map((destination) => [destinationKey(destination), destination]));
2983
- if (selectors.size === 0) return /* @__PURE__ */ new Map();
2984
- const rows = await getDb().select({
2985
- displayName: juniorDestinations.displayName,
2986
- kind: juniorDestinations.kind,
2987
- providerDestinationId: juniorDestinations.providerDestinationId,
2988
- providerTenantId: juniorDestinations.providerTenantId,
2989
- visibility: juniorDestinations.visibility
2990
- }).from(juniorDestinations).where(or(...[...selectors.values()].map((destination) => and(eq(juniorDestinations.provider, "slack"), eq(juniorDestinations.providerTenantId, destination.teamId), eq(juniorDestinations.providerDestinationId, destination.channelId)))));
2991
- return new Map(rows.map((row) => {
2992
- const name = row.displayName?.trim();
2993
- const label = name ? row.kind === "channel" ? `#${name.replace(/^#/, "")}` : name : row.kind === "dm" ? "Direct message" : row.kind === "group" ? "Group message" : `Channel ${row.providerDestinationId}`;
2994
- return [`${row.providerTenantId}:${row.providerDestinationId}`, {
2995
- label,
2996
- visibility: row.visibility === "public" ? "public" : "private"
2997
- }];
2998
- }));
2999
- }
3000
- function executionSummaryFields(stats) {
3001
- return {
3002
- lastRunStatus: stats?.lastRunStatus,
3003
- ...stats?.lastConversationId ? { lastConversationId: stats.lastConversationId } : void 0,
3004
- ...stats?.lastExecutedAtMs ? { lastRunAt: new Date(stats.lastExecutedAtMs).toISOString() } : void 0,
3005
- costUsd: stats?.costUsd ?? emptyAutomationRunWindows(),
3006
- runs: stats?.runs ?? emptyAutomationRunWindows(),
3007
- totalRuns: stats?.totalRuns ?? 0
3008
- };
3009
- }
3010
- function scheduledAutomationSummary(task, ownedByViewer, destination, stats, creator) {
3011
- if (task.status === "deleted") throw new Error("Deleted scheduled automations cannot enter the Automations view");
3012
- const nextRunAtMs = task.status === "active" ? task.runNowAtMs ?? task.nextRunAtMs : void 0;
3013
- const instruction = displayText(task.task.text, "Untitled scheduled automation");
3014
- return {
3015
- createdAt: new Date(task.createdAtMs).toISOString(),
3016
- createdBy: creatorLabel(task.createdBy),
3017
- createdByEmail: creator?.email,
3018
- createdByAvatarUrl: creator?.avatarUrl,
3019
- destination: {
3020
- channelId: task.destination.channelId,
3021
- label: destination.label,
3022
- teamId: task.destination.teamId,
3023
- visibility: destination.visibility
3024
- },
3025
- id: task.id,
3026
- instruction,
3027
- kind: "scheduled",
3028
- ...executionSummaryFields(stats),
3029
- ...nextRunAtMs !== void 0 ? { nextRunAt: new Date(nextRunAtMs).toISOString() } : void 0,
3030
- ownedByViewer,
3031
- schedule: displayText(task.schedule.description, "Schedule unavailable"),
3032
- status: task.status,
3033
- statusReason: task.statusReason,
3034
- timezone: task.schedule.timezone,
3035
- credentialMode: task.credentialMode,
3036
- outcomes: effectiveTaskOutcomes(task.outcomes, task.destination),
3037
- title: taskDisplayTitle(task.title, instruction, "Untitled scheduled automation")
3038
- };
3039
- }
3040
- function eventAutomationSummary(task, ownedByViewer, destination, stats, creator) {
3041
- const instruction = task.task.text;
3042
- const catalog = getEventCatalog();
3043
- return {
3044
- createdAt: new Date(task.createdAtMs).toISOString(),
3045
- createdBy: creatorLabel(task.createdBy),
3046
- createdByEmail: creator?.email,
3047
- createdByAvatarUrl: creator?.avatarUrl,
3048
- destination: {
3049
- channelId: task.destination.channelId,
3050
- label: destination.label,
3051
- teamId: task.destination.teamId,
3052
- visibility: destination.visibility
3053
- },
3054
- id: task.id,
3055
- instruction,
3056
- kind: "event",
3057
- ...executionSummaryFields(stats),
3058
- ownedByViewer,
3059
- resource: task.trigger.identifier ? `${task.trigger.label} · ${task.trigger.identifier}` : task.trigger.label,
3060
- source: task.trigger.namespace,
3061
- credentialMode: task.credentialMode,
3062
- outcomes: effectiveTaskOutcomes(task.outcomes, task.destination),
3063
- title: taskDisplayTitle(task.title, instruction, "Untitled event automation"),
3064
- status: task.status === "deleted" ? "active" : task.status,
3065
- statusReason: task.statusReason,
3066
- trigger: describeEventAutomationTrigger(task.trigger, catalog)
3067
- };
3068
- }
3069
- function viewerTeamIds(user) {
3070
- return [...new Set(user.identities.filter((identity) => identity.provider === "slack").map((identity) => identity.providerTenantId).filter((teamId) => Boolean(teamId)))];
3071
- }
3072
- /** Resolve an Automation the viewer owns or can read through a public Destination. */
3073
- async function resolveViewerTaskCandidate(user, id) {
3074
- const [row] = await getDb().select().from(juniorAutomations).where(eq(juniorAutomations.id, id)).limit(1);
3075
- if (!row) return void 0;
3076
- let candidate;
3077
- if (row.kind === "scheduled") {
3078
- const task = parseScheduledAutomationRow(row);
3079
- if (!task) return void 0;
3080
- candidate = {
3081
- kind: "scheduled",
3082
- ownedByViewer: isAutomationCreator(user, task),
3083
- task
3084
- };
3085
- } else {
3086
- const task = parseEventAutomationRow(row);
3087
- candidate = {
3088
- kind: "event",
3089
- ownedByViewer: isAutomationCreator(user, task),
3090
- task
3091
- };
3092
- }
3093
- const { task } = candidate;
3094
- if (task.status === "deleted") return void 0;
3095
- if (candidate.ownedByViewer) return candidate;
3096
- const destination = (await destinationDetails([task.destination])).get(destinationKey(task.destination));
3097
- return viewerTeamIds(user).includes(task.destination.teamId) && destination?.visibility === "public" ? candidate : void 0;
3098
- }
3099
- /** Read one current Automation summary with the Automations view access rules. */
3100
- async function readViewerAutomationSummary(user, id) {
3101
- const candidate = await resolveViewerTaskCandidate(user, id);
3102
- if (!candidate) return void 0;
3103
- return automationSummaryForCandidate(candidate);
3104
- }
3105
- async function automationSummaryForCandidate(candidate) {
3106
- const [destinations, creators, stats] = await Promise.all([
3107
- destinationDetails([candidate.task.destination]),
3108
- creatorProfiles([candidate]),
3109
- readAutomationExecutionSummaries([candidate.task.id])
3110
- ]);
3111
- const destination = destinations.get(destinationKey(candidate.task.destination)) ?? {
3112
- label: `Channel ${candidate.task.destination.channelId}`,
3113
- visibility: "private"
3114
- };
3115
- const creator = creators.get(creatorKey(candidate.task.destination.teamId, candidate.task.createdBy.slackUserId));
3116
- if (candidate.kind === "scheduled") return scheduledAutomationSummary(candidate.task, candidate.ownedByViewer, destination, stats.get(candidate.task.id), creator);
3117
- return eventAutomationSummary(candidate.task, candidate.ownedByViewer, destination, stats.get(candidate.task.id), creator);
3118
- }
3119
- function emptyAutomationExecutionDay(date) {
3120
- return {
3121
- costUsd: 0,
3122
- date,
3123
- event: 0,
3124
- scheduled: 0
3125
- };
3126
- }
3127
- function emptyAutomationExecutionStatusDay(date) {
3128
- return {
3129
- blocked: 0,
3130
- completed: 0,
3131
- date,
3132
- failed: 0
3133
- };
3134
- }
3135
- function automationExecutionSixHours(hours, nowMs = Date.now()) {
3136
- return sumUtcHoursIntoSixHours({
3137
- empty: emptyAutomationExecutionDay,
3138
- hours,
3139
- nowMs
3140
- });
3141
- }
3142
- function automationExecutionStatusSixHours(hours, nowMs = Date.now()) {
3143
- return sumUtcHoursIntoSixHours({
3144
- empty: emptyAutomationExecutionStatusDay,
3145
- hours,
3146
- nowMs
3147
- });
3148
- }
3149
- /** Read a page from the full accessible Automation collection. */
3150
- async function readViewerAutomations(user, input = automationListQuerySchema.parse({})) {
3151
- const { rows, ...page } = await readAutomationCollection(user, input);
3152
- const db = getDb();
3153
- const ids = rows.map((row) => row.id);
3154
- const stored = ids.length ? await db.select().from(juniorAutomations).where(inArray(juniorAutomations.id, ids)) : [];
3155
- const candidates = /* @__PURE__ */ new Map();
3156
- for (const row of stored) {
3157
- if (row.kind === "scheduled") {
3158
- const task = parseScheduledAutomationRow(row);
3159
- if (task && task.status !== "deleted") candidates.set(task.id, {
3160
- kind: "scheduled",
3161
- task,
3162
- ownedByViewer: isAutomationCreator(user, task)
3163
- });
3164
- continue;
3165
- }
3166
- const task = parseEventAutomationRow(row);
3167
- if (task.status !== "deleted") candidates.set(task.id, {
3168
- kind: "event",
3169
- task,
3170
- ownedByViewer: isAutomationCreator(user, task)
3171
- });
3172
- }
3173
- const selected = rows.flatMap((row) => {
3174
- const candidate = candidates.get(row.id);
3175
- return candidate ? [candidate] : [];
3176
- });
3177
- const [destinations, creators] = await Promise.all([destinationDetails(selected.map(({ task }) => task.destination)), creatorProfiles(selected)]);
3178
- const visible = selected.filter((candidate) => candidate.ownedByViewer || destinations.get(destinationKey(candidate.task.destination))?.visibility === "public");
3179
- const collection = viewerAutomationCollection(user);
3180
- const access = exists(getDb().select({ id: collection.id }).from(collection).where(eq(collection.id, juniorAutomationRuns.automationId)));
3181
- const [executionDays, executionHours, stats] = await Promise.all([
3182
- readAutomationExecutionDays(90, { access }),
3183
- readAutomationExecutionHours(168, { access }),
3184
- readAutomationExecutionSummaries(ids)
3185
- ]);
3186
- const automations = visible.map((candidate) => {
3187
- const destination = destinations.get(destinationKey(candidate.task.destination)) ?? {
3188
- label: `Channel ${candidate.task.destination.channelId}`,
3189
- visibility: "private"
3190
- };
3191
- const creator = creators.get(creatorKey(candidate.task.destination.teamId, candidate.task.createdBy.slackUserId));
3192
- if (candidate.kind === "scheduled") return scheduledAutomationSummary(candidate.task, candidate.ownedByViewer, destination, stats.get(candidate.task.id), creator);
3193
- return eventAutomationSummary(candidate.task, candidate.ownedByViewer, destination, stats.get(candidate.task.id), creator);
3194
- });
3195
- return {
3196
- executionDays,
3197
- executionHours,
3198
- executionSixHours: automationExecutionSixHours(executionHours),
3199
- automations,
3200
- ...page
3201
- };
3202
- }
3203
- /** Load deleted scheduled automations the viewer still owns, newest first. */
3204
- async function readDeletedOwnedScheduledAutomations(user) {
3205
- const identityIds = user.identities.map((identity) => identity.id);
3206
- if (identityIds.length === 0) return [];
3207
- return (await getDb().select().from(juniorAutomations).where(and(eq(juniorAutomations.kind, "scheduled"), eq(juniorAutomations.status, "deleted"), inArray(juniorAutomations.creatorIdentityId, identityIds))).orderBy(desc(juniorAutomations.createdAtMs), desc(juniorAutomations.id)).limit(TASK_EXECUTION_LIST_LIMIT)).map(parseScheduledAutomationRow).filter((task) => task?.status === "deleted");
3208
- }
3209
- /** Load deleted event automations the viewer still owns, newest first. */
3210
- async function readDeletedOwnedEventAutomations(user) {
3211
- return await listDeletedEventAutomationsCreatedBy(getDb(), user, TASK_EXECUTION_LIST_LIMIT);
3212
- }
3213
- function automationTitleForRun(run, automationTitles) {
3214
- return automationTitles.get(`${run.kind}:${run.automationId}`) ?? run.title?.trim() ?? "Untitled task";
3215
- }
3216
- function addDeletedAutomationRun(args, automationTitles, automations) {
3217
- const key = `${args.kind}:${args.automationId}`;
3218
- if (automationTitles.has(key)) return;
3219
- automationTitles.set(key, args.title);
3220
- automations.push({
3221
- kind: args.kind,
3222
- automationId: args.automationId
3223
- });
3224
- }
3225
- /**
3226
- * Read newest runs across live viewer-visible automations and deleted automations the
3227
- * viewer owns. Deleted task rows stay available so history does not depend on
3228
- * conversation actor membership.
3229
- */
3230
- async function readViewerAutomationRuns(user) {
3231
- const [taskList, deletedScheduled, deletedEvent] = await Promise.all([
3232
- getDb().select().from(viewerAutomationCollection(user)),
3233
- readDeletedOwnedScheduledAutomations(user),
3234
- readDeletedOwnedEventAutomations(user)
3235
- ]);
3236
- const automationTitles = /* @__PURE__ */ new Map();
3237
- const automations = [];
3238
- for (const task of taskList) {
3239
- automationTitles.set(`${task.kind}:${task.id}`, taskDisplayTitle(task.title ?? void 0, task.instruction, `Untitled ${task.kind} automation`));
3240
- automations.push({
3241
- kind: task.kind,
3242
- automationId: task.id
3243
- });
3244
- }
3245
- for (const task of deletedScheduled) {
3246
- const instruction = displayText(task.task.text, "Untitled scheduled automation");
3247
- addDeletedAutomationRun({
3248
- kind: "scheduled",
3249
- automationId: task.id,
3250
- title: taskDisplayTitle(task.title, instruction, "Untitled scheduled automation")
3251
- }, automationTitles, automations);
3252
- }
3253
- for (const task of deletedEvent) {
3254
- const instruction = displayText(task.task.text, "Untitled event automation");
3255
- addDeletedAutomationRun({
3256
- kind: "event",
3257
- automationId: task.id,
3258
- title: taskDisplayTitle(task.title, instruction, "Untitled event automation")
3259
- }, automationTitles, automations);
3260
- }
3261
- const runs = await readAutomationRuns({
3262
- automationIds: automations.map((automation) => automation.automationId),
3263
- limit: 101
3264
- });
3265
- return {
3266
- runs: runs.slice(0, TASK_EXECUTION_LIST_LIMIT).map((run) => ({
3267
- ...run,
3268
- automationTitle: automationTitleForRun(run, automationTitles)
3269
- })),
3270
- truncated: runs.length > TASK_EXECUTION_LIST_LIMIT
3271
- };
3272
- }
3273
- var ViewerTaskNotFoundError = class extends Error {
3274
- constructor() {
3275
- super("Task was not found.");
3276
- this.name = "ViewerTaskNotFoundError";
3277
- }
3278
- };
3279
- /** Read one viewer-visible task and its newest terminal executions. */
3280
- async function readViewerAutomationExecutions(user, id) {
3281
- const candidate = await resolveViewerTaskCandidate(user, id);
3282
- if (!candidate) throw new ViewerTaskNotFoundError();
3283
- const [automation, executions, executionDays, executionHours] = await Promise.all([
3284
- automationSummaryForCandidate(candidate),
3285
- readAutomationExecutions({
3286
- automationId: id,
3287
- limit: 101
3288
- }),
3289
- readAutomationExecutionStatusDays({ automationId: id }),
3290
- readAutomationExecutionStatusHours({ automationId: id })
3291
- ]);
3292
- return {
3293
- executionDays,
3294
- executionHours,
3295
- executionSixHours: automationExecutionStatusSixHours(executionHours),
3296
- executions: executions.slice(0, TASK_EXECUTION_LIST_LIMIT),
3297
- automation,
3298
- truncated: executions.length > TASK_EXECUTION_LIST_LIMIT
3299
- };
3300
- }
3301
- /** Read saved definitions for one viewer-visible Automation, newest first. */
3302
- async function readViewerAutomationVersions(user, id) {
3303
- const candidate = await resolveViewerTaskCandidate(user, id);
3304
- if (!candidate) throw new ViewerTaskNotFoundError();
3305
- const versions = await listAutomationVersions(getDb(), id, 101);
3306
- const current = automationDefinition(candidate.task);
3307
- const listed = versions.slice(0, AUTOMATION_VERSION_LIST_LIMIT);
3308
- const catalog = getEventCatalog();
3309
- return {
3310
- versions: listed.map((version) => version.kind === "event" ? {
3311
- ...version,
3312
- trigger: describeEventAutomationTrigger(version.definition.trigger, catalog)
3313
- } : version),
3314
- activeVersion: listed.find(({ definition }) => sameDefinitionValue(definition, current))?.version ?? null,
3315
- truncated: versions.length > AUTOMATION_VERSION_LIST_LIMIT
3316
- };
3317
- }
3318
- /** Name the event in a trigger, such as "opened" for `pull_request.opened`. */
3319
- function eventActionLabel(eventName) {
3320
- const [, ...action] = eventName.split(".");
3321
- return (action.length > 0 ? action : [eventName]).join(" ").replaceAll("_", " ");
3322
- }
3323
- /**
3324
- * Read whose access one run used from its dispatch record. Return nothing when
3325
- * the record is gone, because the Automation's current setting can differ.
3326
- */
3327
- async function readRunCredential(dispatchId, creator) {
3328
- if (!dispatchId) return {};
3329
- const dispatch = await getDispatchRecord(dispatchId);
3330
- if (!dispatch) return {};
3331
- const subject = dispatch.credentialSubject;
3332
- if (!subject) return { credentialMode: "system" };
3333
- return {
3334
- credentialMode: "creator",
3335
- credentialUser: subject.userId === creator.slackUserId ? creatorLabel(creator) : subject.userId
3336
- };
3337
- }
3338
- /** Resolve the source task for one conversation when an Automation run links it. */
3339
- async function readConversationSourceTask(args) {
3340
- const execution = await readAutomationExecutionByConversationId({ conversationId: args.conversationId });
3341
- if (!execution) return void 0;
3342
- const dispatchId = getDispatchIdForConversation(args.conversationId);
3343
- const base = {
3344
- kind: execution.kind,
3345
- ...dispatchId ? { triggerMessageId: getDispatchInputMessageId(dispatchId) } : void 0
3346
- };
3347
- if (!args.viewer) return base;
3348
- const candidate = await resolveViewerTaskCandidate(args.viewer, execution.automationId);
3349
- if (!candidate) return base;
3350
- const credential = await readRunCredential(dispatchId, candidate.task.createdBy);
3351
- if (candidate.kind === "scheduled") {
3352
- const instruction = displayText(candidate.task.task.text, "Untitled scheduled automation");
3353
- const schedule = candidate.task.schedule.description.trim();
3354
- return {
3355
- ...base,
3356
- ...credential,
3357
- id: candidate.task.id,
3358
- kind: "scheduled",
3359
- label: instruction,
3360
- title: taskDisplayTitle(candidate.task.title, instruction, "Untitled scheduled automation"),
3361
- ...schedule ? { triggerLabel: schedule } : void 0
3362
- };
3363
- }
3364
- const instruction = displayText(candidate.task.task.text, "Untitled event automation");
3365
- const { events, label } = candidate.task.trigger;
3366
- return {
3367
- ...base,
3368
- ...credential,
3369
- id: candidate.task.id,
3370
- kind: "event",
3371
- label: instruction,
3372
- title: taskDisplayTitle(candidate.task.title, instruction, "Untitled event automation"),
3373
- triggerLabel: events.length === 1 ? `${label} ${eventActionLabel(events[0])}` : label
3374
- };
3375
- }
3376
- /** Delete one viewer-owned scheduled or event automation. */
3377
- async function deleteViewerTask(user, id) {
3378
- const candidate = await resolveViewerTaskCandidate(user, id);
3379
- if (!candidate?.ownedByViewer) throw new ViewerTaskNotFoundError();
3380
- if (candidate.kind === "scheduled") try {
3381
- await deleteViewerScheduledAutomation(getDb(), user, id);
3382
- return;
3383
- } catch (error) {
3384
- if (error instanceof PersonalScheduledAutomationNotFoundError) throw new ViewerTaskNotFoundError();
3385
- throw error;
3386
- }
3387
- await deleteEventAutomation(getDb(), id);
3388
- }
3389
2897
  // src/chat/slack/assistant-thread/status-send.ts
3390
2898
  /**
3391
2899
  * Slack's assistant loading UI accepts both `status` and `loading_messages`,
@@ -3896,6 +3404,21 @@ function formatSlackConversationRedactedLabel(context) {
3896
3404
  if (!context) return void 0;
3897
3405
  return formatSlackConversationTypeLabel(context.type);
3898
3406
  }
3407
+ // src/chat/slack/avatar.ts
3408
+ const AVATAR_SAVE_INTERVAL_MS = 864e5;
3409
+ /**
3410
+ * Save the Slack photo of one Slack Actor on their Slack Identity. Slack
3411
+ * Turns call this, so the saved photo follows profile changes within a day.
3412
+ */
3413
+ async function saveSlackActorAvatar(actor) {
3414
+ const state = getStateAdapter();
3415
+ const savedKey = `junior:slack:avatar-saved:${actor.teamId}:${actor.userId}`;
3416
+ if (await state.get(savedKey)) return;
3417
+ const profile = await lookupSlackUser(actor.teamId, actor.userId);
3418
+ if (!profile) return;
3419
+ await saveActorAvatar(getSqlExecutor(), actor, profile.avatarUrl ?? null);
3420
+ await state.set(savedKey, true, AVATAR_SAVE_INTERVAL_MS);
3421
+ }
3899
3422
  // src/chat/services/provider-default-config.ts
3900
3423
  const GITHUB_REPO_PART = String.raw`[A-Za-z0-9_.-]*[A-Za-z0-9_-]`;
3901
3424
  const GITHUB_REPO_RE = new RegExp(String.raw`^\s*(?:set|use)\s+(?:the\s+)?default\s+(?:github\s+)?repo(?:sitory)?\s+(?:to|as)\s+(${GITHUB_REPO_PART}/${GITHUB_REPO_PART})(?:\s+for\s+this\s+channel)?[.!?]?\s*$`, "i");
@@ -4136,8 +3659,10 @@ function createSlackTurn(deps) {
4136
3659
  executionActor = options.execution.dispatch.actor;
4137
3660
  credentialContext = options.execution.credentialContext;
4138
3661
  } else {
4139
- executionActor = await ensureSlackMessageActorIdentity(message, teamId, lookupSlackUser);
4140
- if (executionActor) credentialContext = credentialContextForActor(executionActor);
3662
+ const slackActor = await ensureSlackMessageActorIdentity(message, teamId, lookupSlackUser);
3663
+ await saveSlackActorAvatar(slackActor);
3664
+ executionActor = slackActor;
3665
+ credentialContext = credentialContextForActor(slackActor);
4141
3666
  }
4142
3667
  if (!executionActor || !credentialContext) throw new Error("Slack reply execution requires an actor");
4143
3668
  const actor = "userId" in executionActor ? executionActor : void 0;
@@ -6444,6 +5969,7 @@ function createWebSteeringDrain(args) {
6444
5969
  text: normalizeConversationText(message.input.text),
6445
5970
  createdAtMs: message.createdAtMs,
6446
5971
  author,
5972
+ ...message.input.authorIdentityId ? { authorIdentityId: message.input.authorIdentityId } : void 0,
6447
5973
  meta: {
6448
5974
  ...attachments.length ? { attachments } : void 0,
6449
5975
  explicitMention: true,
@@ -6526,6 +6052,14 @@ async function completeCancelledConversationTurn(args) {
6526
6052
  await args.acknowledge();
6527
6053
  }
6528
6054
  /**
6055
+ * Return the author Identity id that every batched input shares.
6056
+ * A batch with more than one author has no single author yet.
6057
+ */
6058
+ function sharedAuthorIdentityId(messages) {
6059
+ const ids = new Set(messages.map((message) => message.input.authorIdentityId));
6060
+ return ids.size === 1 ? [...ids][0] : void 0;
6061
+ }
6062
+ /**
6529
6063
  * Create the shared mailbox worker for web and event Turns.
6530
6064
  */
6531
6065
  function createConversationTurnWorker(agentRunner, deliverMessage) {
@@ -6548,6 +6082,7 @@ function createConversationTurnWorker(agentRunner, deliverMessage) {
6548
6082
  let inputMessageIds = [];
6549
6083
  let event;
6550
6084
  let batchEvents = [];
6085
+ let authorIdentityId;
6551
6086
  const storedConversation = await getConversationStore().get({ conversationId: context.conversationId });
6552
6087
  const mailboxDestination = resolved.kind === "mailbox" ? resolved.batch[0]?.message.destination : void 0;
6553
6088
  const destination = resolveConversationDestination({
@@ -6568,6 +6103,7 @@ function createConversationTurnWorker(agentRunner, deliverMessage) {
6568
6103
  userMessageId = first.message.inboundMessageId;
6569
6104
  turnId = buildDeterministicTurnId(userMessageId);
6570
6105
  inputMessageIds = resolved.batch.map((entry) => entry.message.inboundMessageId);
6106
+ authorIdentityId = sharedAuthorIdentityId(messages);
6571
6107
  turnInputFacts = first;
6572
6108
  } else turnId = resolved.turnId;
6573
6109
  const persisted = await getPersistedThreadState(context.conversationId);
@@ -6654,6 +6190,7 @@ function createConversationTurnWorker(agentRunner, deliverMessage) {
6654
6190
  text: normalizeConversationText(text),
6655
6191
  createdAtMs: startedAtMs,
6656
6192
  author,
6193
+ ...authorIdentityId ? { authorIdentityId } : void 0,
6657
6194
  meta: {
6658
6195
  ...attachments.length ? { attachments } : void 0,
6659
6196
  explicitMention: true,
@@ -7028,12 +6565,14 @@ function createConversationWork(options) {
7028
6565
  },
7029
6566
  runTurn: runtime.runDispatchTurn
7030
6567
  });
7031
- const invocationWorker = createAgentInvocationWorker(options.agentRunner, {
6568
+ const childWorker = createChildConversationWorker(options.agentRunner, {
7032
6569
  enqueueResult: async (invocation) => await enqueueAgentInvocationResult(invocation, {
7033
6570
  queue: options.queue,
7034
6571
  state: options.state
7035
6572
  }),
7036
- maxAgentDepth: botConfig.maxAgentDepth
6573
+ maxAgentDepth: botConfig.maxAgentDepth,
6574
+ queue: options.queue,
6575
+ state: options.state
7037
6576
  });
7038
6577
  const conversationTurnWorker = createConversationTurnWorker(options.agentRunner, createSlackSystemTurnDelivery({
7039
6578
  getSlackAdapter: options.getSlackAdapter,
@@ -7048,15 +6587,9 @@ function createConversationWork(options) {
7048
6587
  const run = async (context) => {
7049
6588
  const skipped = await skipInputBeforeConversationStop(context);
7050
6589
  if (skipped) return skipped;
7051
- const childInput = await startAgentInvocationsForChildInput(context, {
7052
- queue: options.queue,
7053
- state: options.state
7054
- });
7055
- if (childInput) return childInput;
6590
+ if ((await options.conversationStore.get({ conversationId: context.conversationId }))?.parentConversationId) return await childWorker(context);
7056
6591
  const mailboxTurn = await resolveMailboxTurnWork(context);
7057
6592
  if (mailboxTurn) return await conversationTurnWorker(context, await withAgentInvocationResultActors(mailboxTurn));
7058
- const invocationId = await resolveAgentInvocationId(context);
7059
- if (invocationId) return await invocationWorker(context, invocationId);
7060
6593
  const dispatchId = await resolveAgentDispatchId(context);
7061
6594
  if (dispatchId) return await dispatchWorker(context, dispatchId);
7062
6595
  return await destinationWorker(context);
@@ -7834,7 +7367,7 @@ async function readLastUserMessageAtByConversation(db, conversationIds) {
7834
7367
  const rows = await db.select({
7835
7368
  conversationId: juniorConversationEvents.conversationId,
7836
7369
  lastUserMessageAt: sql`max(${juniorConversationEvents.createdAt})`
7837
- }).from(juniorConversationEvents).where(and(inArray(juniorConversationEvents.conversationId, conversationIds), or(and(eq(juniorConversationEvents.type, "user_message"), eq(authority, "instruction"), sql`${actorPlatform} in ('slack', 'local', 'web')`), and(eq(juniorConversationEvents.type, "message"), eq(role, "user"), isNotNull(juniorConversationEvents.actorIdentityId))))).groupBy(juniorConversationEvents.conversationId);
7370
+ }).from(juniorConversationEvents).leftJoin(juniorIdentities, eq(juniorIdentities.id, juniorConversationEvents.actorIdentityId)).where(and(inArray(juniorConversationEvents.conversationId, conversationIds), or(and(eq(juniorConversationEvents.type, "user_message"), eq(authority, "instruction"), sql`${actorPlatform} in ('slack', 'local', 'web')`), and(eq(juniorConversationEvents.type, "message"), eq(role, "user"), eq(juniorIdentities.kind, "user"))))).groupBy(juniorConversationEvents.conversationId);
7838
7371
  return new Map(rows.map((row) => [row.conversationId, new Date(row.lastUserMessageAt).toISOString()]));
7839
7372
  }
7840
7373
  // src/api/conversations/activity-preview.ts
@@ -8010,6 +7543,7 @@ async function attachActivityPreviewMentions(executor, previews) {
8010
7543
  }
8011
7544
  // src/api/conversations/participants.ts
8012
7545
  const identityColumns = {
7546
+ avatarUrl: juniorIdentities.avatarUrl,
8013
7547
  displayName: juniorIdentities.displayName,
8014
7548
  email: juniorIdentities.email,
8015
7549
  handle: juniorIdentities.handle,
@@ -8020,11 +7554,13 @@ const identityColumns = {
8020
7554
  userEmail: juniorUsers.primaryEmail,
8021
7555
  userId: juniorIdentities.userId
8022
7556
  };
8023
- function participantIdentity(row) {
7557
+ function participantIdentity(row, userAvatarUrls) {
7558
+ const avatarUrl = identityAvatarUrl(row, userAvatarUrls);
8024
7559
  const fullName = row.userDisplayName?.trim() || row.displayName?.trim();
8025
7560
  const email = row.email?.trim() || row.userEmail?.trim();
8026
7561
  const slack = row.provider === "slack";
8027
7562
  const participant = {
7563
+ ...avatarUrl ? { avatarUrl } : void 0,
8028
7564
  ...email ? { email } : void 0,
8029
7565
  ...fullName ? { fullName } : void 0,
8030
7566
  ...slack && row.providerSubjectId ? { slackUserId: row.providerSubjectId } : void 0,
@@ -8042,10 +7578,11 @@ async function readConversationParticipants(db, conversationIds) {
8042
7578
  conversationId: juniorConversationEvents.conversationId,
8043
7579
  seq: juniorConversationEvents.seq,
8044
7580
  ...identityColumns
8045
- }).from(juniorConversationEvents).innerJoin(juniorIdentities, eq(juniorIdentities.id, juniorConversationEvents.actorIdentityId)).leftJoin(juniorUsers, eq(juniorUsers.id, juniorIdentities.userId)).where(inArray(juniorConversationEvents.conversationId, [...conversationIds])).orderBy(asc(juniorConversationEvents.conversationId), asc(juniorConversationEvents.actorIdentityId), asc(juniorConversationEvents.seq))]);
7581
+ }).from(juniorConversationEvents).innerJoin(juniorIdentities, eq(juniorIdentities.id, juniorConversationEvents.actorIdentityId)).leftJoin(juniorUsers, eq(juniorUsers.id, juniorIdentities.userId)).where(and(inArray(juniorConversationEvents.conversationId, [...conversationIds]), ne(juniorIdentities.kind, "system"))).orderBy(asc(juniorConversationEvents.conversationId), asc(juniorConversationEvents.actorIdentityId), asc(juniorConversationEvents.seq))]);
7582
+ const userAvatarUrls = await readUserAvatarUrls(db, [...rootRows, ...eventRows].flatMap((row) => row.userId ? [row.userId] : []));
8046
7583
  const ordered = /* @__PURE__ */ new Map();
8047
7584
  const add = (conversationId, row) => {
8048
- const participant = participantIdentity(row);
7585
+ const participant = participantIdentity(row, userAvatarUrls);
8049
7586
  if (!participant) return;
8050
7587
  const group = ordered.get(conversationId) ?? {
8051
7588
  identities: [],
@@ -8275,4 +7812,4 @@ async function readConversationFeedFromSql(options = {}) {
8275
7812
  async function readConversationFeed(options = {}) {
8276
7813
  return conversationFeedSchema.parse(await readConversationFeedFromSql(options));
8277
7814
  }
8278
- export { AGENT_DISPATCH_MAX_AGE_MS as $, assertNoLegacyAllowedDomains as A, normalizeIncomingSlackThreadId as B, readConversationSourceTask as C, readViewerAutomationVersions as D, readViewerAutomationSummary as E, recordSkippedConversationMessage as F, parseContent as G, stopConversationTurn as H, buildSlackInboundMessage as I, canActOnReviewItem as J, ReviewItemActionError as K, clearSlackPendingReactions as L, isAllowedSignInEmail as M, readEnvList as N, readViewerAutomations as O, resolveAllowedDomains as P, getAttachmentStorage as Q, isSlackMessageStopped as R, deleteViewerTask as S, readViewerAutomationRuns as T, THREAD_OPTOUT_ACK as U, withNormalizedThreadId as V, startProcessingReaction as W, readReviewItemForUser as X, isOnlyAssignee as Y, reviewItemChange as Z, createProductionConversationWorkOptions as _, resolveMentions as a, createSlackWebApiStatusSender as b, readConversationAuxiliaryCostsFromSql as c, notCopiedByFork as d, enqueueAgentDispatch as et, childAgentTotalsReport as f, resolveSlackTeamDomains as g, conversationSummaryFromStoredConversation as h, attachConversationMentions as i, isAllowedDomain as j, resolveViewerTaskCandidate as k, ConversationForkError as l, conversationEventHistory as m, readConversationRecordFromSql as n, readHealthReport as nt, readFirstHumanMessageTexts as o, readConversationTreeMetricsFromSql as p, actOnReviewItem as q, readConversationParticipants as r, listConversationSidebarAnnotations as s, readConversationFeed as t, GET as tt, forkConversation as u, createProductionSlackWebhookServices as v, readViewerAutomationExecutions as w, ViewerTaskNotFoundError as x, getProductionSlackAdapter as y, stopSlackThread as z };
7815
+ export { stopSlackThread as A, actOnReviewItem as B, isAllowedSignInEmail as C, buildSlackInboundMessage as D, recordSkippedConversationMessage as E, bindMessageAuthorIdentityId as F, AGENT_DISPATCH_MAX_AGE_MS as G, readReviewItemForUser as H, startProcessingReaction as I, readHealthReport as J, enqueueAgentDispatch as K, stripLeadingBotMention as L, withNormalizedThreadId as M, stopConversationTurn as N, clearSlackPendingReactions as O, THREAD_OPTOUT_ACK as P, parseContent as R, isAllowedDomain as S, resolveAllowedDomains as T, reviewItemChange as U, closeReviewItemsAnsweredByReply as V, getAttachmentStorage as W, createProductionConversationWorkOptions as _, resolveMentions as a, createSlackWebApiStatusSender as b, readConversationAuxiliaryCostsFromSql as c, notCopiedByFork as d, childAgentTotalsReport as f, resolveSlackTeamDomains as g, conversationSummaryFromStoredConversation as h, attachConversationMentions as i, normalizeIncomingSlackThreadId as j, isSlackMessageStopped as k, ConversationForkError as l, conversationEventHistory as m, readConversationRecordFromSql as n, readFirstHumanMessageTexts as o, readConversationTreeMetricsFromSql as p, GET as q, readConversationParticipants as r, listConversationSidebarAnnotations as s, readConversationFeed as t, forkConversation as u, createProductionSlackWebhookServices as v, readEnvList as w, assertNoLegacyAllowedDomains as x, getProductionSlackAdapter as y, ReviewItemActionError as z };