@sentry/junior 0.267.0 → 0.268.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 (80) hide show
  1. package/dist/{actor-DZSokpwj.js → actor-CP9K2Idu.js} +2 -1
  2. package/dist/{adapter-BybMEezj.js → adapter-04AcD2SO.js} +4 -6
  3. package/dist/{admin-DOs1_EiW.js → admin-DIU1QyG3.js} +1 -1
  4. package/dist/{agent-invocations-CxKldAeB.js → agent-invocations-C1RNOCs8.js} +2 -0
  5. package/dist/{agent-runner-DgNATN4D.js → agent-runner-CAYfFmU7.js} +4409 -3020
  6. package/dist/api/schema.d.ts +2 -5629
  7. package/dist/api/schema.js +4 -4
  8. package/dist/api.d.ts +1 -1
  9. package/dist/api.js +154 -32
  10. package/dist/{app-DAB3fzyp.d.ts → app-z6Uuyq3j.d.ts} +3 -2
  11. package/dist/app.d.ts +1 -1
  12. package/dist/app.js +332 -98
  13. package/dist/{briefs-CWKjka7v.js → briefs-DQbV2jNr.js} +2 -2
  14. package/dist/{catalog-runtime-cMlAI5LY.js → catalog-runtime-BD9GSpc6.js} +1 -1
  15. package/dist/cli/chat.js +15 -15
  16. package/dist/cli/check.js +3 -3
  17. package/dist/cli/main.js +3 -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-BjgbMawj.js → client-C1yDYjoS.js} +2 -2
  23. package/dist/{config-CdB6yJj5.js → config-DrTeF80T.js} +2 -2
  24. package/dist/{connections-RE_65jzK.js → connections-D46f8vvi.js} +10 -11
  25. package/dist/{conversation-privacy-BzgJC5k0.js → conversation-privacy-CxlOCiUo.js} +1 -1
  26. package/dist/{credential-sync-DhEGEpgq.js → credential-sync-sm9fegLY.js} +29 -34
  27. package/dist/{credentialed-CIGwoLU2.js → credentialed-lukuD-cc.js} +39 -13
  28. package/dist/db-BU4LQD8s.d.ts +6 -0
  29. package/dist/{executor-VYrx_K4w.js → executor-4vhvsMZJ.js} +78 -4
  30. package/dist/instrumentation.js +2 -2
  31. package/dist/{assistant-reply-CT7VcXQe.js → interruption-marker-CkncDT8d.js} +8 -2
  32. package/dist/{job-queue-C0BZSa7r.js → job-queue-In9QKz-I.js} +2 -2
  33. package/dist/{list-1XQP-o6K.js → list-BkgnXEEB.js} +298 -807
  34. package/dist/{logging-DuZ1A4ab.js → logging-DLSpkSSr.js} +1 -1
  35. package/dist/{message-NKSbbnA6.js → message-BObyg59s.js} +4 -4
  36. package/dist/nitro.d.ts +1 -1
  37. package/dist/nitro.js +3 -3
  38. package/dist/{oauth-callback-server-qAHLPtml.js → oauth-callback-server-B-WbSdoH.js} +2 -2
  39. package/dist/{registry-FQutO926.js → registry-BgecKva0.js} +2 -2
  40. package/dist/{web-message-id-CJSUiksE.js → reporting-window-DVCD_10C.js} +1 -10
  41. package/dist/{resolve-Bk4yeuHy.js → resolve-Cpa4Bu1O.js} +3 -3
  42. package/dist/{result-B9e9ATOU.js → result-D3TSmZZ8.js} +8 -8
  43. package/dist/{runner-C3S2ObH-.js → runner-DS5OKhEp.js} +10 -10
  44. package/dist/{sandbox-egress-signals-DqpGzzMB.js → sandbox-egress-signals-CnDjY5rx.js} +1 -1
  45. package/dist/{schema-X7Fj4fv1.js → schema-0050HFnB.js} +80 -4
  46. package/dist/schema-ZngqcOWM.d.ts +5813 -0
  47. package/dist/{service-account-Do30HUNv.js → service-account-CKV2WTY7.js} +3 -3
  48. package/dist/{service-account-BQasl-qq.js → service-account-DGjnDw9y.js} +3 -3
  49. package/dist/{session-stop-DkOjt46_.js → session-stop-Dam1EI7B.js} +494 -18
  50. package/dist/{skills-CQHrwfOa.js → skills-CfAnfImD.js} +2 -2
  51. package/dist/{spawn-BHuqN14f.js → spawn-cmq9p4Ly.js} +2 -2
  52. package/dist/{store-6EetVP-m.js → store-C5PN02QO.js} +4 -4
  53. package/dist/{store-B8CABbmR.d.ts → store-xMuuXRE0.d.ts} +13 -7
  54. package/dist/{store-DvNZIsCr.js → store-zPEoJPBB.js} +3 -3
  55. package/dist/{task-queue-B0DLgd2P.js → task-queue-_mf28dSu.js} +3 -3
  56. package/dist/{task-runner-B-PD_JOj.js → task-runner-DMI2prDO.js} +11 -12
  57. package/dist/{tasks-0ilmuCux.js → tasks-SB9z-VTU.js} +124 -21
  58. package/dist/{thread-state-DRmdJ2QN.js → thread-state-BbbPh0Sn.js} +8 -18
  59. package/dist/{turn-failure-response-G11cs2gE.js → turn-failure-response-BKKmnMB5.js} +10 -15
  60. package/dist/{turn-lifecycle-OzLIwvi1.js → turn-lifecycle-oP0kVeGz.js} +1 -1
  61. package/dist/{validation-CxEmt1id.js → validation--JsAGLwc.js} +1 -1
  62. package/dist/{version-Bapedn4A.js → version-jHwJp4hC.js} +1 -1
  63. package/dist/version.js +1 -1
  64. package/dist/{viewer-DrN3oC2X.js → viewer-CpXTGlep.js} +323 -235
  65. package/dist/{user-input-request-BDFBdF8r.js → web-message-id-DlHNANtd.js} +17 -1
  66. package/dist/{work-DUQoNcqP.js → work-3N-GHY9m.js} +10 -10
  67. package/dist/{worker-D1l4Ugk0.js → worker-CCdbcItd.js} +6 -6
  68. package/migrations/0065_user_customization.sql +1 -0
  69. package/migrations/0066_review_items.sql +53 -0
  70. package/migrations/0067_review_item_dedupe_tenant.sql +2 -0
  71. package/migrations/0068_review_item_slack_cards.sql +10 -0
  72. package/migrations/meta/0065_snapshot.json +3511 -0
  73. package/migrations/meta/0066_snapshot.json +4099 -0
  74. package/migrations/meta/0067_snapshot.json +3921 -0
  75. package/migrations/meta/0068_snapshot.json +4176 -0
  76. package/migrations/meta/_journal.json +29 -1
  77. package/package.json +5 -5
  78. package/dist/db-Bg-zC3r5.d.ts +0 -5
  79. package/dist/images-CecjXAAz.js +0 -64
  80. package/dist/turn-session-routing-Dyut9l7Y.js +0 -76
@@ -1,42 +1,41 @@
1
- import { $n as buildDeterministicTurnId, At as PluginCredentialFailureError, Bt as wasPendingAuthStopped, Ct as isHumanConversationMessage, Dt as turnHasReply, E as sameDefinitionValue, Et as recordDeliveredAssistantMessage, G as commitMessages, Gt as completeTurnRecord, Ht as loadTurnCheckpoint, Jn as trimTrailingAssistantMessages, Jt as listTurnSummaries, Kt as failTurnRecord, Ot as upsertConversationMessage, Pn as persistWithRetry, Pt as AuthorizationFlowDisabledError, Qn as buildDeterministicAssistantMessageId, Rt as clearPendingAuth, S as automationDefinition, St as getConversationMessageSlackTs, Tt as normalizeConversationText, Ut as saveTurnCheckpoint, W as commitAcceptedReply, Wt as abandonTurnRecord, X as loadTurnProjection, Y as loadProjection, Yt as recordTurnSummary, _ as parseEventAutomationRow, bt as createConversationMemoryService, c as withScheduledAutomationLock, cr as isExperimentalFeatureEnabled, dr as configValueSchema, dt as persistConversationMessages, f as deleteEventAutomation, gt as fallbackShortTitle, h as listDeletedEventAutomationsCreatedBy, i as readScheduledAutomation, j as automationEventCatalog, jn as buildTurnErrorResponse, mt as resolveConversationTitle, nr as getSubscribedReplyPreflightDecision, nt as recordAuthorizationCompleted, o as saveScheduledAutomationInLock, ot as recordSubscribedReplyRoute, pr as piMessageFromHistoryItem, pt as persistConversationMessageSummaries, q as loadConversationProjection, qt as getTurnRecord, r as parseScheduledAutomationRow, rr as getThreadStopDecision, tr as decideSubscribedThreadReply, ut as hydrateConversationMessages, vt as appendThreadContextMessages, w as listAutomationVersions, wt as markConversationMessage, xt as estimateConversationContextTokens, yt as buildConversationContext, z as registeredEventTriggerProblems } from "./tasks-0ilmuCux.js";
2
- import { a as completeText, p as escapeXml, r as completeDecision, y as isProviderRetryError } from "./client-BjgbMawj.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-CdB6yJj5.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-DuZ1A4ab.js";
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";
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
- import { a as isUserActor, d as parseSlackTeamId, i as isActorUserId, l as parseSlackChannelId, n as createSlackActor, o as parseActorUserId, p as parseSlackMessageTs, r as createSlackResumeActor, t as createActor } from "./actor-DZSokpwj.js";
8
- import { T as addAgentTurnUsage, V as requireSlackDestination, _ as loadConversationSlackTenantIds, b as locationFromRow, f as getConversationStore, g as withConversationEventLock, k as hasAgentTurnUsage, m as getSqlExecutor, p as getDb, u as getConversationEventStore, w as parseSessionSource, z as createSlackDestination } from "./viewer-DrN3oC2X.js";
9
- import { c as juniorIdentities, d as juniorDestinations, l as juniorUsers, o as conversationReadColumns, s as juniorConversations } from "./agent-invocations-CxKldAeB.js";
10
- import { a as juniorAutomationRuns, f as juniorConversationBriefs, h as juniorConversationEvents, s as juniorAutomations, v as juniorCodeChanges } from "./executor-VYrx_K4w.js";
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";
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
11
  import { d as contextProvenance, p as instructionProvenanceFor } from "./history-C84mlAwO.js";
12
- import { d as actorLogContext, f as credentialContextForActor } from "./registry-FQutO926.js";
13
- import { B as resolveSlackChannelIdFromThreadId, H as listThreadReplies, J as describeEventAutomationTrigger, L as parseSlackThreadId, R as readSlackRawMessageContext, V as getConversationInfo, Y as effectiveTaskOutcomes, at as conversationSubtreeColumns, b as renderBlockText, ct as buildSteeringPiMessage, i as buildAuthPauseResponse, it as conversationSubtree, lt as withoutSavedSteeringMessages, mt as shouldEmitDevAgentTrace, n as executeAgentRun, p as latestProgressStatus, r as createContextCompactor, t as createAgentRunner, ut as RetryableDeliveryError, w as storeAttachment, y as renderAttachmentText, z as resolveSlackChannelIdFromMessage } from "./agent-runner-DgNATN4D.js";
14
- import { $ as postSlackMessage, $t as downloadPrivateSlackFile, Bn as conversationNotArchivedForEmail, Fn as conversationArchiveTimes, Ft as cancelSubscriptions, G as normalizeSlackReplyMarkdown, Hn as viewerConversationMembership, In as conversationArchivedForEmail, J as truncateStatusText, K as normalizeSlackStatusText, Kn as buildSentryConversationUrl, Ln as conversationArchivedForUser, Nn as createPluginLogger, Pn as readConversationAccessFromSql, Qt as SlackActionError, Rn as conversationHasParticipantUser, U as escapeSlackMrkdwnText, Vn as conversationNotArchivedForUser, W as formatSlackLink, Xn as recordSlackAttachment, Y as addReactionToMessage, Yn as readAttachmentVisionSummary, Zt as verifyTimerWatchCredentialSubject, an as normalizeSlackConversationId, ar as createPluginCache, dn as JUNIOR_THREAD_STATE_TTL_MS, et as removeReactionFromMessage, fn as getDashboardConversationLink, in as isDmChannel, ir as lookupSlackUser, l as getPluginSlackConversationLink, n as createUserTokenStore, nn as getSlackClient, on as runWithSlackInstallationToken, p as getPlugins, pn as getDashboardObjectIconLink, qn as getSlackFileId, sn as withSlackRetries, tn as getHeaderString, tr as listConversationAnnotationsById, zn as conversationIsMemberRoot } from "./credentialed-CIGwoLU2.js";
15
- import { a as MUTATION_LOCK_TTL_MS, c as withLock, i as getStateAdapter, o as acquireActiveLock } from "./adapter-BybMEezj.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
16
  import { W as readMessageAttachments, g as conversationFeedSchema, q as isVisionImageMediaType } from "./conversation-DSo310Ps.js";
17
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-6EetVP-m.js";
19
- import { n as getAssistantReplyText } from "./assistant-reply-CT7VcXQe.js";
20
- import { C as maybePostAutomatedTurnLimitNotice, M as runDispatchOutcome, O as deliversFinalText, c as isEventMailboxMetadata, d as getEventCatalog, i as skipInputBeforeConversationStop, k as finishedRunReply, l as renderEventBatchText, s as eventBatchMessageEvents, u as timerWatchCredentialSubject, w as recordFinishedTurnForAutomatedLimit, x as joinMailboxText } from "./session-stop-DkOjt46_.js";
21
- import { a as getVercelConversationWorkQueue, f as getTurnRequestDeadline } from "./job-queue-C0BZSa7r.js";
22
- import { D as stopConversationWork, g as getConversationWorkState, h as getConversation, m as ensureConversationWake, n as appendAndEnqueueExclusiveInboundMessage, r as appendAndEnqueueInboundMessage, w as requestConversationWork } from "./store-DvNZIsCr.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";
23
24
  import { j as eventAutomationTriggerSchema, l as automationListQuerySchema } from "./automation-CmeFBSEh.js";
24
- import { c as sumUtcHoursIntoSixHours, m as healthReportSchema, t as webMessageId } from "./web-message-id-CJSUiksE.js";
25
- 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-B-PD_JOj.js";
26
- 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-B9e9ATOU.js";
27
- import { n as getTurnLifecycle, t as ConversationTurnLifecycleService } from "./turn-lifecycle-OzLIwvi1.js";
28
- import { i as resolveAgentInvocationId, o as AgentRunError, s as executeTurn, t as createAgentInvocationWorker } from "./work-DUQoNcqP.js";
29
- import { a as persistThreadState, i as persistThreadRuntimeState, n as getPersistedSandboxState, o as persistThreadStateById, r as getPersistedThreadState, s as coerceThreadConversationState, t as getLocationConfigurationService } from "./thread-state-DRmdJ2QN.js";
30
- import { c as deleteWebAuthorization, d as createPausedTurns, f as getPausedTurnRequest, h as getTurnAuthorization, m as wakePausedTurn, s as createWebAuthorization } from "./connections-RE_65jzK.js";
31
- import { n as resolveTurnSessionRouting } from "./turn-session-routing-Dyut9l7Y.js";
32
- import { t as bindMessageAgent } from "./message-NKSbbnA6.js";
33
- import { t as bindSpawnAgent } from "./spawn-BHuqN14f.js";
34
- import { a as persistAuthPauseTurnState, c as getTurnUserSlackMessageTs, d as loadPendingMessageCards, i as completeAuthPauseTurn, l as buildDeliveredTurnStatePatch, n as getAgentTurnDiagnosticsAttributes, o as getTurnUserMessage, r as requireTurnFailureEventId, s as getTurnUserReplyAttachmentContext, t as finalizeFailedTurnReplyWithEvent, u as getInterruptionMarker } from "./turn-failure-response-G11cs2gE.js";
35
- import { r as storeInputImages } from "./images-CecjXAAz.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";
36
35
  import { z } from "zod";
37
36
  import { createHash } from "node:crypto";
38
37
  import { and, asc, count, desc, eq, exists, inArray, isNotNull, isNull, ne, notInArray, or, sql } from "drizzle-orm";
39
- import { conversationSidebarAnnotationSchema, createSlackSource, createWebSource, localDestinationSchema, objectFactFields, objectPresentation, objectTypeSchema } from "@sentry/junior-plugin-api";
38
+ import { conversationSidebarAnnotationSchema, createSlackSource, objectTypeSchema } from "@sentry/junior-plugin-api";
40
39
  import { alias } from "drizzle-orm/pg-core";
41
40
  import { del, get, put } from "@vercel/blob";
42
41
  import { Message, ThreadImpl, stringifyMarkdown } from "chat";
@@ -380,207 +379,140 @@ function createAttachmentStorage(config) {
380
379
  default: throw new Error(`Unsupported attachment storage provider: ${String(config.provider)}`);
381
380
  }
382
381
  }
383
- // src/chat/slack/automation-card.ts
384
- function preview(text, length) {
385
- const compact = text.replace(/\s+/g, " ").trim();
386
- return compact.length > length ? `${compact.slice(0, length - 1)}…` : compact;
387
- }
388
- /** Show an Automation as a native Work Object, not an operation receipt. */
389
- function renderSlackAutomationCard(card) {
390
- const title = preview(card.title, 160);
391
- const iconUrl = getDashboardObjectIconLink("workflow");
392
- const trigger = preview(card.trigger, 500);
393
- const warning = card.warning ? preview(card.warning, 500) : null;
394
- const text = [
395
- "Automation",
396
- card.url ? formatSlackLink(card.url, title) : escapeSlackMrkdwnText(title),
397
- escapeSlackMrkdwnText(trigger),
398
- warning ? escapeSlackMrkdwnText(warning) : null
399
- ].filter(Boolean).join("\n");
400
- if (!card.url) return {
401
- entity: null,
402
- text
403
- };
404
- return {
405
- text,
406
- entity: {
407
- entity_type: "slack#/entities/item",
408
- external_ref: {
409
- id: card.id,
410
- type: "automation"
411
- },
412
- url: card.url,
413
- entity_payload: {
414
- attributes: {
415
- title: { text: title },
416
- display_type: "Automation",
417
- product_icon: iconUrl ? {
418
- url: iconUrl,
419
- alt_text: "Automation"
420
- } : void 0
421
- },
422
- custom_fields: [{
423
- key: "trigger",
424
- label: "When",
425
- type: "string",
426
- value: trigger,
427
- long: true
428
- }, ...warning ? [{
429
- key: "warning",
430
- label: "Needs attention",
431
- type: "string",
432
- value: warning,
433
- long: true
434
- }] : []]
435
- }
436
- }
382
+ // src/chat/review-items/actions.ts
383
+ /** An expected failure of a person action, for the API to report. */
384
+ var ReviewItemActionError = class extends Error {
385
+ code;
386
+ constructor(code, message) {
387
+ super(message);
388
+ this.name = "ReviewItemActionError";
389
+ this.code = code;
390
+ }
391
+ };
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
+ /** Read one Review item that a User can see, or fail as not found. */
408
+ async function readReviewItemForUser(user, id) {
409
+ const item = await getReviewItem(getDb(), id);
410
+ if (!item || !canReadReviewItem(item, reviewItemReaderForUser(user))) throw new ReviewItemActionError("not_found", "Review item not found.");
411
+ return item;
412
+ }
413
+ function closedStatus(action) {
414
+ return action === "dismiss" ? "dismissed" : "done";
415
+ }
416
+ /** The change that one person action makes, for messages and notes. */
417
+ function reviewItemChange(action, user) {
418
+ if (action === "take") return {
419
+ kind: "taken",
420
+ user
437
421
  };
438
- }
439
- // src/chat/slack/object-card.ts
440
- function descriptionPreview(value) {
441
- const text = value?.replace(/\r\n/g, "\n").trim() ?? "";
442
- const preview = text.split("\n").slice(0, 6).join("\n").slice(0, 500);
443
- return preview.length < text.length ? `${preview.slice(0, 499).trimEnd()}…` : preview;
444
- }
445
- /** Render a compact Task or Item preview, or the saved facts for its detail panel. */
446
- function renderSlackObjectCard(card, conversationId, surface = "preview") {
447
- if (card.objectType === "automation" && card.plugin === "junior") return renderSlackAutomationCard({
448
- id: card.key,
449
- title: card.title,
450
- url: card.url,
451
- trigger: card.trigger ?? "",
452
- warning: card.warning ?? null
453
- });
454
- const presentation = objectPresentation(card);
455
- const type = card.displayType ?? presentation.label;
456
- const iconUrl = getDashboardObjectIconLink(presentation.icon);
457
- const title = card.title.slice(0, 160);
458
- const details = surface === "details";
459
- const deployment = card.objectType === "deployment" || card.objectType === "item" && card.facts?.type === "deployment";
460
- const calendarEvent = card.objectType === "calendar_event";
461
- const status = details || deployment || calendarEvent ? card.status : void 0;
462
- const warning = details || deployment ? card.warning : void 0;
463
- const facts = objectFactFields(card.facts).filter((field) => details || calendarEvent || deployment && field.key === "environment");
464
- const description = details ? card.description?.trim() ?? "" : calendarEvent ? "" : descriptionPreview(card.description);
465
- const text = [
466
- escapeSlackMrkdwnText(type),
467
- card.url ? formatSlackLink(card.url, title) : escapeSlackMrkdwnText(title),
468
- escapeSlackMrkdwnText(card.label),
469
- status ? escapeSlackMrkdwnText(status) : null,
470
- description ? escapeSlackMrkdwnText(description) : null,
471
- warning ? escapeSlackMrkdwnText(`Needs attention: ${warning}`) : null,
472
- ...facts.map((field) => escapeSlackMrkdwnText(`${field.label}: ${field.value}`))
473
- ].filter(Boolean).join("\n");
474
- if (!card.url) return {
475
- entity: null,
476
- text
422
+ if (action === "submit") return {
423
+ kind: "submitted",
424
+ user
477
425
  };
478
- const task = card.objectType === "task";
479
- const attributes = {
480
- title: { text: title },
481
- display_id: card.label,
482
- display_type: type,
483
- product_icon: iconUrl ? {
484
- url: iconUrl,
485
- alt_text: `${type}${status ? `: ${status}` : ""}`
486
- } : void 0,
487
- product_name: card.plugin
426
+ if (action === "complete") return {
427
+ kind: "completed",
428
+ user
488
429
  };
489
- const customFields = [
490
- ...description ? [{
491
- key: "description",
492
- label: "Description",
493
- type: "string",
494
- value: description,
495
- long: true,
496
- format: "markdown"
497
- }] : [],
498
- ...facts.map((field) => ({
499
- ...field,
500
- type: "string"
501
- })),
502
- ...warning ? [{
503
- key: "warning",
504
- label: "Needs attention",
505
- type: "string",
506
- value: warning
507
- }] : []
508
- ];
509
430
  return {
510
- text,
511
- entity: {
512
- ...task ? {
513
- entity_type: "slack#/entities/task",
514
- entity_payload: {
515
- attributes,
516
- display_order: [...status ? ["status"] : [], ...customFields.map((field) => field.key)],
517
- fields: status ? { status: { value: status } } : void 0,
518
- custom_fields: customFields
519
- }
520
- } : {
521
- entity_type: "slack#/entities/item",
522
- entity_payload: {
523
- attributes,
524
- display_order: [...status ? ["status"] : [], ...customFields.map((field) => field.key)],
525
- custom_fields: [...status ? [{
526
- key: "status",
527
- label: "Status",
528
- type: "string",
529
- value: status
530
- }] : [], ...customFields]
531
- }
532
- },
533
- external_ref: {
534
- id: Buffer.from(JSON.stringify([
535
- conversationId,
536
- card.plugin,
537
- card.key
538
- ]), "utf8").toString("base64url"),
539
- type: "annotation"
540
- },
541
- url: card.url
542
- }
431
+ kind: "dismissed",
432
+ user
543
433
  };
544
434
  }
545
- // src/chat/slack/adapter-context.ts
546
- const initializedAdapters = /* @__PURE__ */ new WeakSet();
547
- async function getConnectedState$1(stateAdapter) {
548
- const state = stateAdapter ?? getStateAdapter();
549
- await state.connect();
550
- return state;
551
- }
552
- /** Initialize the Slack adapter against the repository state adapter. */
553
- async function ensureSlackAdapterInitialized(args) {
554
- if (initializedAdapters.has(args.adapter)) return;
555
- const state = await getConnectedState$1(args.state);
556
- await args.adapter.initialize({ getState: () => state });
557
- if (args.adapter.defaultBotTokenProvider && !args.adapter.botUserId) throw new Error("Slack adapter initialized without a resolved bot user id");
558
- initializedAdapters.add(args.adapter);
559
- }
560
- /** Verify a Slack request using the adapter's configured signing secret. */
561
- function verifySlackSignature(args) {
562
- const verifySignature = args.adapter.verifySignature;
563
- if (!verifySignature) throw new Error("Slack adapter does not expose signature verification");
564
- return verifySignature.call(args.adapter, args.body, args.request.headers.get("x-slack-request-timestamp"), args.request.headers.get("x-slack-signature"));
565
- }
566
- /** Run Slack work with the installation token that matches the inbound event. */
567
- async function runWithSlackInstallation(args) {
568
- await ensureSlackAdapterInitialized({
569
- adapter: args.adapter,
570
- state: args.state
435
+ /** Return whether a closed item is the result of this same action. */
436
+ function isSameClose(item, action, user) {
437
+ return item.closedBy?.userId === user.id && item.status === closedStatus(action) && action === "submit" === Boolean(item.answer);
438
+ }
439
+ function submittedAnswer(item, answer) {
440
+ if (item.kind !== "action" || !item.request) throw new ReviewItemActionError("invalid", "Only an action item takes an answer.");
441
+ const normalized = normalizeUserInputAnswer(item.request, {
442
+ other: answer?.other,
443
+ requestId: item.id,
444
+ values: answer?.values ?? {}
445
+ });
446
+ if (isEmptyUserInputAnswer(normalized)) throw new ReviewItemActionError("invalid", "The answer is empty.");
447
+ return normalized;
448
+ }
449
+ /**
450
+ * Do one person action on a Review item and tell its linked Conversations.
451
+ * The first submit, complete, or dismiss closes the item for every
452
+ * assignee. A retry of the same close sends any missing messages again and
453
+ * succeeds, so a client can retry after a failure. With `firstCloseOnly`,
454
+ * only the call that closes the item succeeds, and every other call gets a
455
+ * 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`.
460
+ */
461
+ async function actOnReviewItem(args) {
462
+ const { action, user } = args;
463
+ const nowMs = args.nowMs ?? Date.now();
464
+ const db = getDb();
465
+ const item = await readReviewItemForUser(user, args.id);
466
+ if (action === "take") {
467
+ if (item.status !== "open") throw new ReviewItemActionError("conflict", "Review item is closed.");
468
+ if (!canActOnReviewItem(item, user.id)) throw new ReviewItemActionError("forbidden", "Only an assignee can take this item.");
469
+ if (!isOnlyAssignee(item, user.id) && !await takeReviewItem(db, {
470
+ id: item.id,
471
+ nowMs,
472
+ userId: user.id
473
+ })) throw new ReviewItemActionError("conflict", "Review item is closed.");
474
+ const taken = await readReviewItemForUser(user, item.id);
475
+ await syncReviewItemSlackCards({
476
+ getSlackAdapter: args.getSlackAdapter,
477
+ itemId: taken.id
478
+ });
479
+ await sendReviewItemMessages({
480
+ change: reviewItemChange(action, user),
481
+ exceptConversationId: args.exceptConversationId,
482
+ item: taken,
483
+ nowMs,
484
+ queue: args.queue
485
+ });
486
+ return taken;
487
+ }
488
+ let closed = item;
489
+ let closedNow = false;
490
+ if (item.status === "open") {
491
+ if (!canActOnReviewItem(item, user.id)) throw new ReviewItemActionError("forbidden", "Only an assignee can act on this item.");
492
+ if (action === "complete" && item.kind === "action") throw new ReviewItemActionError("invalid", "An action item needs an answer. Submit it or dismiss it.");
493
+ const answer = action === "submit" ? submittedAnswer(item, args.answer) : void 0;
494
+ closedNow = await closeReviewItem(db, {
495
+ closedByUserId: user.id,
496
+ id: item.id,
497
+ nowMs,
498
+ status: closedStatus(action),
499
+ ...answer ? { answer } : void 0
500
+ });
501
+ closed = await readReviewItemForUser(user, item.id);
502
+ }
503
+ 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({
509
+ change: reviewItemChange(action, user),
510
+ exceptConversationId: args.exceptConversationId,
511
+ item: closed,
512
+ nowMs,
513
+ queue: args.queue
571
514
  });
572
- const internals = args.adapter;
573
- if (internals.defaultBotTokenProvider) return await args.task();
574
- const installationId = args.installation.isEnterpriseInstall ? args.installation.enterpriseId : args.installation.teamId;
575
- if (!installationId) throw new Error("Slack installation context is missing team id");
576
- if (!internals.resolveTokenForTeam || !internals.requestContext) throw new Error("Slack adapter cannot resolve workspace installations");
577
- const tokenContext = await internals.resolveTokenForTeam.call(args.adapter, installationId, args.installation.isEnterpriseInstall);
578
- if (!tokenContext) throw new Error(`Slack installation token was not found for ${installationId}`);
579
- return await internals.requestContext.run({
580
- ...tokenContext,
581
- enterpriseId: args.installation.enterpriseId,
582
- isEnterpriseInstall: args.installation.isEnterpriseInstall
583
- }, () => runWithSlackInstallationToken(tokenContext.token, args.task));
515
+ return closed;
584
516
  }
585
517
  // src/chat/slack/errors.ts
586
518
  /** Extract Slack's stable API error code from Web API errors. */
@@ -1040,10 +972,8 @@ function createSlackTurnRuntime(deps) {
1040
972
  return true;
1041
973
  };
1042
974
  const failConversationTurn = async (args) => {
1043
- const conversationId = getThreadId(args.thread, args.message) ?? getRunId(args.thread, args.message);
1044
- if (!conversationId) return;
1045
975
  await turnLifecycle.fail({
1046
- conversationId,
976
+ conversationId: args.conversationId,
1047
977
  createdAtMs: deps.now(),
1048
978
  eventId: args.eventId,
1049
979
  failureCode: args.failureCode,
@@ -1088,7 +1018,15 @@ function createSlackTurnRuntime(deps) {
1088
1018
  };
1089
1019
  const postFallbackErrorReplyWithLogging = async (args) => {
1090
1020
  try {
1091
- await args.thread.post(buildTurnErrorResponse(args.error, args.eventId));
1021
+ const channelId = getChannelId(args.thread, args.message);
1022
+ if (!channelId) throw new Error("Slack fallback error reply requires a channel id");
1023
+ const threadTs = getThreadTs(getThreadId(args.thread, args.message));
1024
+ await sendSlackReply({
1025
+ channelId,
1026
+ conversationId: args.conversationId,
1027
+ text: buildTurnErrorResponse(args.error, args.eventId),
1028
+ ...threadTs ? { threadTs } : void 0
1029
+ });
1092
1030
  } catch (postError) {
1093
1031
  logException(postError, args.postFailureEventName, {
1094
1032
  "app.slack.reply_stage": "error_fallback_post",
@@ -1144,14 +1082,14 @@ function createSlackTurnRuntime(deps) {
1144
1082
  const completedAtMs = deps.now();
1145
1083
  logSkippedSubscribedDecision(args);
1146
1084
  if (args.preparedState) await deps.onSubscribedMessageSkipped({
1147
- thread: args.thread,
1085
+ conversationId: args.conversationId,
1148
1086
  message: args.message,
1149
1087
  decision: args.decision,
1150
1088
  preparedState: args.preparedState,
1151
1089
  completedAtMs
1152
1090
  });
1153
1091
  else await deps.recordSkippedSubscribedTurn({
1154
- thread: args.thread,
1092
+ conversationId: args.conversationId,
1155
1093
  message: args.message,
1156
1094
  decision: args.decision,
1157
1095
  text: args.text,
@@ -1199,7 +1137,7 @@ function createSlackTurnRuntime(deps) {
1199
1137
  });
1200
1138
  logSkippedSubscribedDecision(skipped);
1201
1139
  await deps.recordSkippedSteeringMessage({
1202
- thread: args.thread,
1140
+ conversationId: args.conversationId,
1203
1141
  message: skipped.message,
1204
1142
  decision: skipped.decision,
1205
1143
  text: skipped.text
@@ -1213,7 +1151,7 @@ function createSlackTurnRuntime(deps) {
1213
1151
  while (args.pending.length > 0) {
1214
1152
  const skipped = args.pending.shift();
1215
1153
  await deps.recordSkippedSteeringMessage({
1216
- thread: args.thread,
1154
+ conversationId: args.conversationId,
1217
1155
  message: skipped.message,
1218
1156
  decision: skipped.decision,
1219
1157
  text: skipped.text
@@ -1227,6 +1165,7 @@ function createSlackTurnRuntime(deps) {
1227
1165
  };
1228
1166
  return {
1229
1167
  async handleNewMention(thread, message, hooks) {
1168
+ const conversationId = hooks.conversationId ?? thread.id;
1230
1169
  const processingReactions = createProcessingReactionTracker(thread);
1231
1170
  let processingReaction;
1232
1171
  const skippedSteeringMessages = [];
@@ -1235,7 +1174,7 @@ function createSlackTurnRuntime(deps) {
1235
1174
  const onTurnCompleted = async () => {
1236
1175
  completed = true;
1237
1176
  await flushSkippedSteeringMessagesBestEffort({
1238
- thread,
1177
+ conversationId,
1239
1178
  pending: skippedSteeringMessages,
1240
1179
  context: logContext({
1241
1180
  threadId: getThreadId(thread, message),
@@ -1289,6 +1228,7 @@ function createSlackTurnRuntime(deps) {
1289
1228
  },
1290
1229
  onSkipped: (skipped) => handleSkippedSteeringMessages({
1291
1230
  beforeFirstResponsePost: hooks.beforeFirstResponsePost,
1231
+ conversationId,
1292
1232
  pending: skippedSteeringMessages,
1293
1233
  skipped,
1294
1234
  thread
@@ -1302,7 +1242,7 @@ function createSlackTurnRuntime(deps) {
1302
1242
  await deps.executeSlackTurn(thread, message, {
1303
1243
  explicitMention: true,
1304
1244
  beforeFirstResponsePost: hooks.beforeFirstResponsePost,
1305
- conversationId: hooks.conversationId,
1245
+ conversationId,
1306
1246
  destination: hooks.destination,
1307
1247
  queuedMessages,
1308
1248
  ack,
@@ -1339,17 +1279,19 @@ function createSlackTurnRuntime(deps) {
1339
1279
  let lifecycleError;
1340
1280
  try {
1341
1281
  await failConversationTurn({
1282
+ conversationId,
1342
1283
  eventId,
1343
1284
  failureCode,
1344
- message,
1345
- thread
1285
+ message
1346
1286
  });
1347
1287
  } catch (error) {
1348
1288
  lifecycleError = error;
1349
1289
  }
1350
1290
  await hooks.beforeFirstResponsePost?.();
1351
1291
  await postFallbackErrorReplyWithLogging({
1292
+ conversationId,
1352
1293
  thread,
1294
+ message,
1353
1295
  eventId,
1354
1296
  error: failureCause,
1355
1297
  postFailureEventName: "mention.handler.failure_reply_post.failed"
@@ -1359,7 +1301,7 @@ function createSlackTurnRuntime(deps) {
1359
1301
  if (completed) await processingReactions.completeAll();
1360
1302
  else {
1361
1303
  await flushSkippedSteeringMessagesBestEffort({
1362
- thread,
1304
+ conversationId,
1363
1305
  pending: skippedSteeringMessages,
1364
1306
  context: logContext({
1365
1307
  threadId: getThreadId(thread, message),
@@ -1374,6 +1316,7 @@ function createSlackTurnRuntime(deps) {
1374
1316
  }
1375
1317
  },
1376
1318
  async handleSubscribedMessage(thread, message, hooks) {
1319
+ const conversationId = hooks.conversationId ?? thread.id;
1377
1320
  const processingReactions = createProcessingReactionTracker(thread);
1378
1321
  let processingReaction;
1379
1322
  const skippedSteeringMessages = [];
@@ -1382,7 +1325,7 @@ function createSlackTurnRuntime(deps) {
1382
1325
  const onTurnCompleted = async () => {
1383
1326
  completed = true;
1384
1327
  await flushSkippedSteeringMessagesBestEffort({
1385
- thread,
1328
+ conversationId,
1386
1329
  pending: skippedSteeringMessages,
1387
1330
  context: logContext({
1388
1331
  threadId: getThreadId(thread, message),
@@ -1418,6 +1361,7 @@ function createSlackTurnRuntime(deps) {
1418
1361
  userText: replaceTopLevelText(content, strippedUserText)
1419
1362
  };
1420
1363
  const threadContext = {
1364
+ conversationId,
1421
1365
  threadId,
1422
1366
  actorId,
1423
1367
  channelId,
@@ -1450,7 +1394,7 @@ function createSlackTurnRuntime(deps) {
1450
1394
  beforeFirstResponsePost: hooks.beforeFirstResponsePost
1451
1395
  });
1452
1396
  await skipSubscribedMessage({
1453
- thread,
1397
+ conversationId,
1454
1398
  message,
1455
1399
  decision,
1456
1400
  context: threadContext,
@@ -1468,7 +1412,7 @@ function createSlackTurnRuntime(deps) {
1468
1412
  if (preflightDecision && !preflightDecision.shouldReply) {
1469
1413
  const reason = preflightDecision.reasonDetail ? `${preflightDecision.reason}:${preflightDecision.reasonDetail}` : preflightDecision.reason;
1470
1414
  await skipSubscribedMessage({
1471
- thread,
1415
+ conversationId,
1472
1416
  message,
1473
1417
  decision: {
1474
1418
  shouldReply: false,
@@ -1494,7 +1438,7 @@ function createSlackTurnRuntime(deps) {
1494
1438
  beforeFirstResponsePost: hooks.beforeFirstResponsePost
1495
1439
  });
1496
1440
  await skipSubscribedMessage({
1497
- thread,
1441
+ conversationId,
1498
1442
  message,
1499
1443
  decision,
1500
1444
  context: threadContext,
@@ -1513,7 +1457,7 @@ function createSlackTurnRuntime(deps) {
1513
1457
  queuedMessages
1514
1458
  });
1515
1459
  await deps.persistPreparedState({
1516
- thread,
1460
+ conversationId,
1517
1461
  preparedState
1518
1462
  });
1519
1463
  const decision = await deps.decideSubscribedReply({
@@ -1530,7 +1474,7 @@ function createSlackTurnRuntime(deps) {
1530
1474
  beforeFirstResponsePost: hooks.beforeFirstResponsePost
1531
1475
  })) {
1532
1476
  await skipSubscribedMessage({
1533
- thread,
1477
+ conversationId,
1534
1478
  message,
1535
1479
  decision,
1536
1480
  context: threadContext,
@@ -1542,7 +1486,7 @@ function createSlackTurnRuntime(deps) {
1542
1486
  }
1543
1487
  if (!decision.shouldReply) {
1544
1488
  await skipSubscribedMessage({
1545
- thread,
1489
+ conversationId,
1546
1490
  message,
1547
1491
  decision,
1548
1492
  context: threadContext,
@@ -1561,6 +1505,7 @@ function createSlackTurnRuntime(deps) {
1561
1505
  },
1562
1506
  onSkipped: (skipped) => handleSkippedSteeringMessages({
1563
1507
  beforeFirstResponsePost: hooks.beforeFirstResponsePost,
1508
+ conversationId,
1564
1509
  pending: skippedSteeringMessages,
1565
1510
  skipped,
1566
1511
  thread
@@ -1586,7 +1531,7 @@ function createSlackTurnRuntime(deps) {
1586
1531
  const toolInvocationHook = createToolInvocationHook(processingReaction, hooks);
1587
1532
  await deps.executeSlackTurn(thread, message, {
1588
1533
  explicitMention: Boolean(message.isMention),
1589
- conversationId: hooks.conversationId,
1534
+ conversationId,
1590
1535
  destination: hooks.destination,
1591
1536
  preparedState,
1592
1537
  beforeFirstResponsePost: hooks.beforeFirstResponsePost,
@@ -1625,17 +1570,19 @@ function createSlackTurnRuntime(deps) {
1625
1570
  let lifecycleError;
1626
1571
  try {
1627
1572
  await failConversationTurn({
1573
+ conversationId,
1628
1574
  eventId,
1629
1575
  failureCode,
1630
- message,
1631
- thread
1576
+ message
1632
1577
  });
1633
1578
  } catch (error) {
1634
1579
  lifecycleError = error;
1635
1580
  }
1636
1581
  await hooks.beforeFirstResponsePost?.();
1637
1582
  await postFallbackErrorReplyWithLogging({
1583
+ conversationId,
1638
1584
  thread,
1585
+ message,
1639
1586
  eventId,
1640
1587
  error: failureCause,
1641
1588
  postFailureEventName: "subscribed_message.handler.failure_reply_post.failed"
@@ -1645,7 +1592,7 @@ function createSlackTurnRuntime(deps) {
1645
1592
  if (completed) await processingReactions.completeAll();
1646
1593
  else {
1647
1594
  await flushSkippedSteeringMessagesBestEffort({
1648
- thread,
1595
+ conversationId,
1649
1596
  pending: skippedSteeringMessages,
1650
1597
  context: logContext({
1651
1598
  threadId: getThreadId(thread, message),
@@ -3592,292 +3539,6 @@ function readSlackActionToken(message) {
3592
3539
  const token = slackActionTokenSchema.safeParse(envelope.data.raw?.action_token);
3593
3540
  return token.success ? token.data : void 0;
3594
3541
  }
3595
- // src/chat/slack/cards.ts
3596
- /** Select the Slack object renderer for a built-in card. */
3597
- function renderSlackCard(card, conversationId) {
3598
- switch (card.kind) {
3599
- case "object": return renderSlackObjectCard(card, conversationId);
3600
- case "automation": return renderSlackAutomationCard(card);
3601
- }
3602
- }
3603
- // src/chat/slack/footer.ts
3604
- /** Render compact reply attribution for the Slack footer. */
3605
- function formatReplyAttribution(attribution) {
3606
- return attribution.detail ? `${attribution.label} · ${attribution.detail}` : attribution.label;
3607
- }
3608
- /**
3609
- * Build the compact conversation footer for visible Slack reply surfaces.
3610
- *
3611
- * Detailed turn metrics stay in the dashboard instead of Slack-visible copy.
3612
- */
3613
- function buildSlackReplyFooter(args) {
3614
- const items = [];
3615
- const conversationId = args.conversationId?.trim();
3616
- if (conversationId) {
3617
- const idItem = {
3618
- label: "ID",
3619
- value: conversationId
3620
- };
3621
- const conversationUrl = getPluginSlackConversationLink(conversationId)?.url ?? getDashboardConversationLink(conversationId) ?? buildSentryConversationUrl(conversationId);
3622
- if (conversationUrl) idItem.url = conversationUrl;
3623
- items.push(idItem);
3624
- }
3625
- return items.length > 0 || args.replyAttribution ? {
3626
- ...args.replyAttribution ? { attribution: args.replyAttribution } : void 0,
3627
- items
3628
- } : void 0;
3629
- }
3630
- /** Build Slack blocks for a reply chunk using the Slack-flavored markdown block for the body. */
3631
- function buildSlackReplyBlocks(text, footer) {
3632
- const blocks = text.trim() ? [{
3633
- type: "markdown",
3634
- text
3635
- }] : [];
3636
- if (footer && (footer.attribution || footer.items.length > 0)) {
3637
- const attributionElements = footer.attribution ? [{
3638
- type: "plain_text",
3639
- text: formatReplyAttribution(footer.attribution)
3640
- }] : [];
3641
- blocks.push({
3642
- type: "context",
3643
- elements: [...attributionElements, ...footer.items.map((item) => ({
3644
- type: "mrkdwn",
3645
- text: item.url ? `*${escapeSlackMrkdwnText(item.label)}:* ${formatSlackLink(item.url, item.value)}` : `*${escapeSlackMrkdwnText(item.label)}:* ${escapeSlackMrkdwnText(item.value)}`
3646
- }))]
3647
- });
3648
- }
3649
- return blocks.length ? blocks : void 0;
3650
- }
3651
- // src/chat/slack/output.ts
3652
- const MAX_INLINE_CHARS = 2200;
3653
- const MAX_INLINE_LINES = 45;
3654
- const CONTINUED_MARKER = "\n\n[Continued below]";
3655
- function countSlackLines(text) {
3656
- if (!text) return 0;
3657
- return text.split("\n").length;
3658
- }
3659
- function fitsInlineBudget(text, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
3660
- return text.length <= maxChars && countSlackLines(text) <= maxLines;
3661
- }
3662
- function findSplitIndex(text, maxChars) {
3663
- if (text.length <= maxChars) return text.length;
3664
- const bounded = text.slice(0, maxChars);
3665
- const newlineIndex = bounded.lastIndexOf("\n");
3666
- if (newlineIndex > 0) return newlineIndex;
3667
- const spaceIndex = bounded.lastIndexOf(" ");
3668
- if (spaceIndex > 0) return spaceIndex;
3669
- return maxChars;
3670
- }
3671
- function splitByLineBudget(text, maxLines) {
3672
- if (maxLines <= 0) return "";
3673
- const lines = text.split("\n");
3674
- if (lines.length <= maxLines) return text;
3675
- return lines.slice(0, maxLines).join("\n");
3676
- }
3677
- function reserveInlineBudgetForSuffix(suffix, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
3678
- return {
3679
- maxChars: Math.max(1, maxChars - suffix.length),
3680
- maxLines: Math.max(1, maxLines - Math.max(0, countSlackLines(suffix) - 1))
3681
- };
3682
- }
3683
- function forceSplitBudget(text, budget) {
3684
- const lineCount = countSlackLines(text);
3685
- return {
3686
- maxChars: text.length <= budget.maxChars ? Math.max(1, text.length - 1) : budget.maxChars,
3687
- maxLines: lineCount <= budget.maxLines ? Math.max(1, lineCount - 1) : budget.maxLines
3688
- };
3689
- }
3690
- function getFenceContinuation(text) {
3691
- let open;
3692
- for (const line of text.split("\n")) {
3693
- const trimmed = line.trimStart();
3694
- const openerMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
3695
- if (!openerMatch?.[1]) continue;
3696
- if (!open) {
3697
- open = {
3698
- fence: openerMatch[1],
3699
- openerLine: trimmed
3700
- };
3701
- continue;
3702
- }
3703
- if (new RegExp(`^${open.fence}\\s*$`).test(trimmed)) open = void 0;
3704
- }
3705
- if (!open) return null;
3706
- return {
3707
- closeSuffix: text.endsWith("\n") ? open.fence : `\n${open.fence}`,
3708
- reopenPrefix: `${open.openerLine}\n`
3709
- };
3710
- }
3711
- function appendSlackSuffix(text, marker) {
3712
- return `${text}${getFenceContinuation(text)?.closeSuffix ?? ""}${marker}`;
3713
- }
3714
- function stripTrailingContinuationMarker(text) {
3715
- return text.endsWith(CONTINUED_MARKER) ? text.slice(0, -19) : text;
3716
- }
3717
- function takeSlackContinuationChunk(text, budget) {
3718
- let { prefix, rest } = takeSlackInlinePrefix(text, budget);
3719
- if (!rest) ({prefix, rest} = takeSlackInlinePrefix(text, forceSplitBudget(text, budget)));
3720
- let carryover = rest ? getFenceContinuation(prefix) : null;
3721
- if (!carryover) return {
3722
- prefix,
3723
- rest
3724
- };
3725
- const carryoverBudget = reserveInlineBudgetForSuffix(`${carryover.closeSuffix}${CONTINUED_MARKER}`);
3726
- ({prefix, rest} = takeSlackInlinePrefix(text, carryoverBudget));
3727
- if (!rest) ({prefix, rest} = takeSlackInlinePrefix(text, forceSplitBudget(text, carryoverBudget)));
3728
- carryover = rest ? getFenceContinuation(prefix) : null;
3729
- if (!carryover) return {
3730
- prefix,
3731
- rest
3732
- };
3733
- return {
3734
- prefix,
3735
- rest: `${carryover.reopenPrefix}${rest}`
3736
- };
3737
- }
3738
- /** Return the first continuation-safe chunk plus any remaining text. */
3739
- function takeSlackContinuationPrefix(text, options) {
3740
- const budget = {
3741
- maxChars: options?.maxChars ?? getSlackContinuationBudget().maxChars,
3742
- maxLines: options?.maxLines ?? getSlackContinuationBudget().maxLines
3743
- };
3744
- const { prefix, rest } = (() => {
3745
- if (options?.forceSplit) return takeSlackContinuationChunk(text, budget);
3746
- const initial = takeSlackInlinePrefix(text, budget);
3747
- return initial.rest ? takeSlackContinuationChunk(text, budget) : initial;
3748
- })();
3749
- return {
3750
- prefix,
3751
- renderedPrefix: rest ? appendSlackSuffix(prefix, CONTINUED_MARKER) : prefix,
3752
- rest
3753
- };
3754
- }
3755
- /**
3756
- * Take the largest Slack-safe inline prefix from `text` under the configured
3757
- * character and line budgets. Returns the consumed prefix plus remaining text.
3758
- */
3759
- function takeSlackInlinePrefix(text, options) {
3760
- const maxChars = options?.maxChars ?? MAX_INLINE_CHARS;
3761
- const maxLines = options?.maxLines ?? MAX_INLINE_LINES;
3762
- const normalized = text.replace(/\r\n?/g, "\n");
3763
- if (!normalized) return {
3764
- prefix: "",
3765
- rest: ""
3766
- };
3767
- if (fitsInlineBudget(normalized, maxChars, maxLines)) return {
3768
- prefix: normalized,
3769
- rest: ""
3770
- };
3771
- const lineBounded = splitByLineBudget(normalized, maxLines);
3772
- const cutIndex = findSplitIndex(lineBounded, maxChars);
3773
- const prefix = lineBounded.slice(0, cutIndex).trimEnd();
3774
- if (prefix) return {
3775
- prefix,
3776
- rest: normalized.slice(prefix.length).trimStart()
3777
- };
3778
- const hardPrefix = normalized.slice(0, Math.max(1, maxChars)).trimEnd();
3779
- return {
3780
- prefix: hardPrefix || normalized.slice(0, Math.max(1, maxChars)),
3781
- rest: normalized.slice(hardPrefix.length || Math.max(1, maxChars)).trimStart()
3782
- };
3783
- }
3784
- /**
3785
- * Split a normalized Slack reply into multiple inline-safe thread messages.
3786
- *
3787
- * Replies split across more than two messages receive explicit continuation
3788
- * markers on non-final chunks. When
3789
- * `interrupted` is true, the final chunk receives an interruption marker.
3790
- */
3791
- function splitSlackReplyText(text, options) {
3792
- const normalized = normalizeSlackReplyMarkdown(text);
3793
- if (!normalized) return [];
3794
- const chunks = [];
3795
- const continuationBudget = reserveInlineBudgetForSuffix(CONTINUED_MARKER);
3796
- let remaining = normalized;
3797
- while (remaining) {
3798
- if (options?.interrupted ? fitsInlineBudget(appendSlackSuffix(remaining, getInterruptionMarker())) : fitsInlineBudget(remaining)) {
3799
- chunks.push(options?.interrupted ? appendSlackSuffix(remaining, getInterruptionMarker()) : remaining);
3800
- break;
3801
- }
3802
- const { renderedPrefix, rest } = takeSlackContinuationPrefix(remaining, {
3803
- ...continuationBudget,
3804
- forceSplit: true
3805
- });
3806
- chunks.push(renderedPrefix);
3807
- remaining = rest;
3808
- }
3809
- if (chunks.length === 2) chunks[0] = stripTrailingContinuationMarker(chunks[0] ?? "");
3810
- return chunks;
3811
- }
3812
- /**
3813
- * Reserve enough inline budget for a continuation suffix on the current chunk.
3814
- */
3815
- function getSlackContinuationBudget() {
3816
- return reserveInlineBudgetForSuffix(CONTINUED_MARKER);
3817
- }
3818
- /** Normalize text for Slack and wrap it as a PostableMessage. */
3819
- function buildSlackOutputMessage(text) {
3820
- const normalized = normalizeSlackReplyMarkdown(text);
3821
- if (!normalized) throw new Error(`Slack output normalized to empty content: original_length=${text.length} parsed_length=${normalized.length}`);
3822
- return { markdown: normalized };
3823
- }
3824
- // src/chat/slack/reply.ts
3825
- /**
3826
- * Send one destination-visible Slack reply.
3827
- *
3828
- * Chunks oversized text, builds compact attribution and conversation footer
3829
- * context, and posts through the shared Slack outbound boundary.
3830
- */
3831
- async function sendSlackReply(args) {
3832
- const posts = splitSlackReplyText(args.text).map((text) => ({
3833
- text,
3834
- cards: []
3835
- }));
3836
- const cards = (args.cards ?? []).map((card) => renderSlackCard(card, args.conversationId));
3837
- for (let index = 0; index < cards.length; index += 5) {
3838
- const group = cards.slice(index, index + 5);
3839
- const lastPost = posts.at(-1);
3840
- if (index === 0 && lastPost) lastPost.cards = group;
3841
- else posts.push({
3842
- text: "",
3843
- cards: group
3844
- });
3845
- }
3846
- const footer = buildSlackReplyFooter({
3847
- conversationId: args.conversationId,
3848
- replyAttribution: args.replyAttribution
3849
- });
3850
- const messageTs = [];
3851
- let threadTs = args.threadTs;
3852
- for (const [index, post] of posts.entries()) {
3853
- const { text, cards } = post;
3854
- const isFinalChunk = index === posts.length - 1;
3855
- const blocks = buildSlackReplyBlocks(text, void 0) ?? [];
3856
- for (const card of cards) if (!card.entity) blocks.push({
3857
- type: "section",
3858
- text: {
3859
- type: "mrkdwn",
3860
- text: card.text
3861
- }
3862
- });
3863
- if (isFinalChunk || !text.trim()) blocks.push(...buildSlackReplyBlocks("", footer) ?? []);
3864
- const entities = cards.flatMap((card) => card.entity ? [card.entity] : []);
3865
- const accessibleText = [text, ...cards.map((card) => card.text)].filter(Boolean).join("\n\n");
3866
- const fallbackText = isFinalChunk && args.replyAttribution ? `${accessibleText}\n\n${escapeSlackMrkdwnText(formatReplyAttribution(args.replyAttribution))}` : accessibleText;
3867
- const response = await postSlackMessage({
3868
- channelId: args.channelId,
3869
- threadTs,
3870
- text: fallbackText,
3871
- ...blocks.length ? { blocks } : void 0,
3872
- ...entities.length ? { entities } : void 0
3873
- });
3874
- if (response.ts) {
3875
- messageTs.push(response.ts);
3876
- threadTs ??= response.ts;
3877
- }
3878
- }
3879
- return messageTs;
3880
- }
3881
3542
  // src/chat/slack/assistant-thread/status-render.ts
3882
3543
  const DEFAULT_STATUS_CONTEXTS = {
3883
3544
  thinking: "…",
@@ -4379,8 +4040,8 @@ async function saveSteeringMessages(args) {
4379
4040
  * Persist post-delivery Redis scratch with a short retry after durable SQL
4380
4041
  * completion has succeeded.
4381
4042
  */
4382
- async function persistThreadRuntimeStateWithRetry(thread, patch) {
4383
- await persistWithRetry(() => persistThreadRuntimeState(thread, patch));
4043
+ async function persistThreadRuntimeStateWithRetry(conversationId, patch) {
4044
+ await persistWithRetry(() => persistThreadRuntimeState(conversationId, patch));
4384
4045
  }
4385
4046
  /**
4386
4047
  * Resolve the Pi history for this Slack turn from the most precise durable
@@ -4432,6 +4093,10 @@ function createSlackTurn(deps) {
4432
4093
  visibility: destinationVisibility
4433
4094
  }) ?? "private"
4434
4095
  });
4096
+ const replyTarget = resolveSlackReplyTarget({
4097
+ destination,
4098
+ source
4099
+ });
4435
4100
  const slackActionToken = readSlackActionToken(message);
4436
4101
  const runId = options.execution?.dispatch?.id ?? getRunId(thread, message);
4437
4102
  const conversationId = options.conversationId ?? threadId ?? runId;
@@ -4486,6 +4151,7 @@ function createSlackTurn(deps) {
4486
4151
  destination,
4487
4152
  locationConfiguration: options.execution?.locationConfiguration,
4488
4153
  context: {
4154
+ conversationId,
4489
4155
  threadId,
4490
4156
  actorId: slackActorId,
4491
4157
  channelId,
@@ -4503,17 +4169,16 @@ function createSlackTurn(deps) {
4503
4169
  };
4504
4170
  const postAuthPauseNotice = async (providerDisplayName, requestText) => {
4505
4171
  if (!actor) throw new Error("Slack auth pause notice requires an actor");
4172
+ if (!postsRunNotices(options.execution?.dispatch)) return;
4506
4173
  const text = buildAuthPauseResponse(actor.userId, providerDisplayName, requestText);
4507
4174
  try {
4508
4175
  await beforeFirstResponsePost();
4509
- if (channelId && threadTs) await sendSlackReply({
4510
- channelId,
4176
+ await sendSlackReply({
4177
+ ...replyTarget,
4511
4178
  conversationId,
4512
4179
  replyAttribution: options.execution?.dispatch?.replyAttribution,
4513
- text,
4514
- threadTs
4180
+ text
4515
4181
  });
4516
- else await thread.post(buildSlackOutputMessage(text));
4517
4182
  } catch (error) {
4518
4183
  logException(error, "slack.auth_pause.notice_post.failed", {
4519
4184
  "app.slack.reply_stage": "thread_reply_auth_pause_notice",
@@ -4567,7 +4232,7 @@ function createSlackTurn(deps) {
4567
4232
  const deliveredMessage = [...preparedState.conversation.messages].reverse().find((candidate) => candidate.role === "assistant" && candidate.id.startsWith(`${turnId}:assistant:`) && candidate.meta?.replied === true);
4568
4233
  options.onTurnDeliveryAccepted?.(deliveredMessage?.meta?.slackTs);
4569
4234
  options.onTurnOutcome?.({ outcome: "completed" });
4570
- await persistThreadState(thread, { conversation: preparedState.conversation });
4235
+ await persistThreadStateById(conversationId, { conversation: preparedState.conversation });
4571
4236
  await options.onTurnStatePersisted?.();
4572
4237
  await options.ack?.();
4573
4238
  await options.onTurnCompleted?.();
@@ -4590,7 +4255,7 @@ function createSlackTurn(deps) {
4590
4255
  });
4591
4256
  throw error;
4592
4257
  }
4593
- await persistThreadState(thread, { conversation: preparedState.conversation });
4258
+ await persistThreadStateById(conversationId, { conversation: preparedState.conversation });
4594
4259
  await options.onTurnStatePersisted?.();
4595
4260
  await options.ack?.();
4596
4261
  return;
@@ -4628,7 +4293,11 @@ function createSlackTurn(deps) {
4628
4293
  });
4629
4294
  if (configReply) {
4630
4295
  await beforeFirstResponsePost();
4631
- await thread.post(buildSlackOutputMessage(configReply.text));
4296
+ await sendSlackReply({
4297
+ ...replyTarget,
4298
+ conversationId,
4299
+ text: configReply.text
4300
+ });
4632
4301
  markConversationMessage(preparedState.conversation, preparedState.userMessageId, {
4633
4302
  replied: true,
4634
4303
  skippedReason: void 0
@@ -4647,7 +4316,7 @@ function createSlackTurn(deps) {
4647
4316
  source: "slack"
4648
4317
  }
4649
4318
  });
4650
- await persistThreadState(thread, { conversation: preparedState.conversation });
4319
+ await persistThreadStateById(conversationId, { conversation: preparedState.conversation });
4651
4320
  await options.onTurnStatePersisted?.();
4652
4321
  await options.ack?.();
4653
4322
  return;
@@ -4688,7 +4357,7 @@ function createSlackTurn(deps) {
4688
4357
  "app.message.id": message.id,
4689
4358
  ...messageTs ? { "messaging.message.id": messageTs } : void 0
4690
4359
  });
4691
- await persistThreadState(thread, { conversation: preparedState.conversation });
4360
+ await persistThreadStateById(conversationId, { conversation: preparedState.conversation });
4692
4361
  await options.onTurnStatePersisted?.();
4693
4362
  if (actor) setSentryUser({
4694
4363
  id: actor.userId,
@@ -4775,16 +4444,15 @@ function createSlackTurn(deps) {
4775
4444
  }
4776
4445
  else {
4777
4446
  slackMessageTs = await sendSlackReply({
4447
+ ...replyTarget,
4778
4448
  cards,
4779
- channelId: destination.channelId,
4780
4449
  conversationId,
4781
4450
  replyAttribution: options.execution?.dispatch?.replyAttribution,
4782
- text,
4783
- ...threadTs ? { threadTs } : void 0
4451
+ text
4784
4452
  });
4785
4453
  messageDestinations.push(...slackMessageTs.map((messageId) => ({
4786
4454
  destination,
4787
- messageId: threadTs ?? messageId
4455
+ messageId: replyTarget.threadTs ?? messageId
4788
4456
  })));
4789
4457
  }
4790
4458
  } catch (error) {
@@ -4874,7 +4542,7 @@ function createSlackTurn(deps) {
4874
4542
  });
4875
4543
  if (compaction.compacted) {
4876
4544
  piMessages = compaction.piMessages;
4877
- await persistThreadState(thread, { conversation: preparedState.conversation });
4545
+ await persistThreadStateById(conversationId, { conversation: preparedState.conversation });
4878
4546
  }
4879
4547
  }
4880
4548
  const hasDurablePromptHistory = Boolean(piMessages?.length);
@@ -4983,11 +4651,11 @@ function createSlackTurn(deps) {
4983
4651
  drainSteeringMessages,
4984
4652
  shouldYield: options.shouldYield,
4985
4653
  onSandboxRefChanged: async (sandboxRef) => {
4986
- await persistThreadState(thread, { sandboxRef });
4654
+ await persistThreadStateById(conversationId, { sandboxRef });
4987
4655
  },
4988
4656
  recordPendingAuth: async (pendingAuth) => {
4989
4657
  preparedState.conversation.processing.pendingAuth = pendingAuth;
4990
- await persistThreadState(thread, { conversation: preparedState.conversation });
4658
+ await persistThreadStateById(conversationId, { conversation: preparedState.conversation });
4991
4659
  }
4992
4660
  }
4993
4661
  };
@@ -5066,7 +4734,7 @@ function createSlackTurn(deps) {
5066
4734
  conversation: completedState.conversation,
5067
4735
  conversationId
5068
4736
  }));
5069
- await persistThreadRuntimeStateWithRetry(thread, completedState);
4737
+ await persistThreadRuntimeStateWithRetry(conversationId, completedState);
5070
4738
  const automatedTurnLimit = await recordFinishedTurnForAutomatedLimit({
5071
4739
  conversationId,
5072
4740
  maxTurns: botConfig.maxConsecutiveAutomatedTurns,
@@ -5075,7 +4743,7 @@ function createSlackTurn(deps) {
5075
4743
  await maybePostAutomatedTurnLimitNotice({
5076
4744
  conversationId,
5077
4745
  maxTurns: botConfig.maxConsecutiveAutomatedTurns,
5078
- threadTs,
4746
+ source,
5079
4747
  update: automatedTurnLimit
5080
4748
  });
5081
4749
  } catch (saveError) {
@@ -5111,7 +4779,7 @@ function createSlackTurn(deps) {
5111
4779
  nowMs: Date.now(),
5112
4780
  sessionId: turnId
5113
4781
  });
5114
- await persistThreadState(thread, { conversation: preparedState.conversation });
4782
+ await persistThreadStateById(conversationId, { conversation: preparedState.conversation });
5115
4783
  if (conversationId) await turnLifecycle.fail({
5116
4784
  conversationId,
5117
4785
  createdAtMs: Date.now(),
@@ -5128,7 +4796,7 @@ function createSlackTurn(deps) {
5128
4796
  conversation: preparedState.conversation,
5129
4797
  sessionId: turnId
5130
4798
  });
5131
- await persistThreadState(thread, { conversation: preparedState.conversation });
4799
+ await persistThreadStateById(conversationId, { conversation: preparedState.conversation });
5132
4800
  persistedAtLeastOnce = true;
5133
4801
  shouldPersistFailureState = false;
5134
4802
  return;
@@ -5212,7 +4880,7 @@ function createSlackTurn(deps) {
5212
4880
  nowMs: Date.now(),
5213
4881
  sessionId: turnId
5214
4882
  });
5215
- await persistThreadState(thread, { conversation: preparedState.conversation });
4883
+ await persistThreadStateById(conversationId, { conversation: preparedState.conversation });
5216
4884
  await turnLifecycle.complete({
5217
4885
  conversationId,
5218
4886
  createdAtMs: Date.now(),
@@ -5269,7 +4937,7 @@ function createSlackTurn(deps) {
5269
4937
  } catch (recordError) {
5270
4938
  logException(recordError, "agent.turn.failed_session_record_persist.failed");
5271
4939
  }
5272
- await persistThreadState(thread, { conversation: preparedState.conversation });
4940
+ await persistThreadStateById(conversationId, { conversation: preparedState.conversation });
5273
4941
  if (shouldEmitDevAgentTrace()) logWarn("agent.turn.failed");
5274
4942
  }
5275
4943
  await status.clear();
@@ -5408,10 +5076,10 @@ async function seedConversationBackfill(thread, conversation, currentTurn, resol
5408
5076
  /** Build the turn-state preparer from injected conversation services. */
5409
5077
  function createPrepareTurnState(deps) {
5410
5078
  return async function prepareTurnState(args) {
5411
- const existingState = await args.thread.state;
5079
+ const conversationId = args.context.conversationId ?? args.context.threadId ?? args.context.runId;
5080
+ const existingState = conversationId ? await getPersistedThreadState(conversationId) : await args.thread.state;
5412
5081
  const sandboxRef = getPersistedSandboxState(existingState ?? {});
5413
5082
  const conversation = coerceThreadConversationState(existingState);
5414
- const conversationId = args.context.threadId ?? args.context.runId;
5415
5083
  if (conversationId && isVisionEnabled()) conversation.vision = await loadConversationVisionCache(conversationId);
5416
5084
  await hydrateConversationMessages({
5417
5085
  conversation,
@@ -5596,8 +5264,8 @@ function createSlackRuntime(options) {
5596
5264
  modelId: defaultModelId(botConfig),
5597
5265
  now: options.now ?? (() => Date.now()),
5598
5266
  prepareTurnState,
5599
- persistPreparedState: async ({ thread, preparedState }) => {
5600
- await persistThreadState(thread, { conversation: preparedState.conversation });
5267
+ persistPreparedState: async ({ conversationId, preparedState }) => {
5268
+ await persistThreadStateById(conversationId, { conversation: preparedState.conversation });
5601
5269
  },
5602
5270
  getPreparedConversationContext: (preparedState) => preparedState.conversationContext,
5603
5271
  decideSubscribedReply: async (args) => {
@@ -5612,18 +5280,18 @@ function createSlackRuntime(options) {
5612
5280
  });
5613
5281
  return decision;
5614
5282
  },
5615
- recordSkippedSteeringMessage: async ({ thread, message, decision, text }) => {
5616
- const conversation = coerceThreadConversationState(await thread.state);
5283
+ recordSkippedSteeringMessage: async ({ conversationId, message, decision, text }) => {
5284
+ const conversation = coerceThreadConversationState(await getPersistedThreadState(conversationId));
5617
5285
  recordSkippedConversationMessage({
5618
5286
  conversation,
5619
5287
  message,
5620
5288
  skippedReason: decision.reason,
5621
5289
  text: text.userText
5622
5290
  });
5623
- await persistThreadState(thread, { conversation });
5291
+ await persistThreadStateById(conversationId, { conversation });
5624
5292
  },
5625
- recordSkippedSubscribedTurn: async ({ thread, message, decision, completedAtMs, text }) => {
5626
- const conversation = coerceThreadConversationState(await thread.state);
5293
+ recordSkippedSubscribedTurn: async ({ conversationId, message, decision, completedAtMs, text }) => {
5294
+ const conversation = coerceThreadConversationState(await getPersistedThreadState(conversationId));
5627
5295
  recordSkippedConversationMessage({
5628
5296
  conversation,
5629
5297
  message,
@@ -5632,16 +5300,16 @@ function createSlackRuntime(options) {
5632
5300
  });
5633
5301
  clearSkippedTurnIfActive(conversation, message.id);
5634
5302
  conversation.processing.lastCompletedAtMs = completedAtMs;
5635
- await persistThreadState(thread, { conversation });
5303
+ await persistThreadStateById(conversationId, { conversation });
5636
5304
  },
5637
- onSubscribedMessageSkipped: async ({ thread, message, preparedState, decision, completedAtMs }) => {
5305
+ onSubscribedMessageSkipped: async ({ conversationId, message, preparedState, decision, completedAtMs }) => {
5638
5306
  markConversationMessage(preparedState.conversation, preparedState.userMessageId, {
5639
5307
  replied: false,
5640
5308
  skippedReason: decision.reason
5641
5309
  });
5642
5310
  clearSkippedTurnIfActive(preparedState.conversation, message.id);
5643
5311
  preparedState.conversation.processing.lastCompletedAtMs = completedAtMs;
5644
- await persistThreadState(thread, { conversation: preparedState.conversation });
5312
+ await persistThreadStateById(conversationId, { conversation: preparedState.conversation });
5645
5313
  },
5646
5314
  executeSlackTurn,
5647
5315
  initializeAssistantThread: async ({ channelId, threadTs, sourceChannelId }) => {
@@ -5688,23 +5356,20 @@ function createJuniorSlackAdapter(config) {
5688
5356
  * authorization pause notices use `sendSlackReply` so the footer stays
5689
5357
  * consistent.
5690
5358
  */
5691
- async function postSlackMessageBestEffort(channelId, threadTs, text, conversationId, replyAttribution) {
5359
+ /** Post a failure reply or status notice when the run's dispatch allows it. */
5360
+ async function postRunNotice(resume, text) {
5361
+ if (!postsRunNotices(resume.run?.dispatch)) return;
5362
+ await sendSlackReply({
5363
+ channelId: resume.channelId,
5364
+ conversationId: resume.conversationId,
5365
+ replyAttribution: resume.run?.dispatch?.replyAttribution,
5366
+ text,
5367
+ threadTs: resume.threadTs
5368
+ });
5369
+ }
5370
+ async function postRunNoticeBestEffort(resume, text) {
5692
5371
  try {
5693
- if (conversationId) {
5694
- await sendSlackReply({
5695
- channelId,
5696
- conversationId,
5697
- replyAttribution,
5698
- text,
5699
- threadTs
5700
- });
5701
- return;
5702
- }
5703
- await postSlackMessage({
5704
- channelId,
5705
- threadTs,
5706
- text
5707
- });
5372
+ await postRunNotice(resume, text);
5708
5373
  } catch {}
5709
5374
  }
5710
5375
  /** Create a read-only configuration service from persisted values. */
@@ -5745,13 +5410,6 @@ function getResumeLogContext(args) {
5745
5410
  modelId: defaultModelId(botConfig)
5746
5411
  };
5747
5412
  }
5748
- async function postResumeFailureReply(args) {
5749
- await postSlackMessage({
5750
- channelId: args.channelId,
5751
- threadTs: args.threadTs,
5752
- text: buildTurnErrorResponse(args.error, args.eventId)
5753
- });
5754
- }
5755
5413
  async function handleResumeFailure(args) {
5756
5414
  const turnLifecycle = getTurnLifecycle();
5757
5415
  const capturedEventId = logException(args.error, args.eventName);
@@ -5775,12 +5433,7 @@ async function handleResumeFailure(args) {
5775
5433
  failureStatePersistError = persistError;
5776
5434
  }
5777
5435
  try {
5778
- await postResumeFailureReply({
5779
- channelId: args.resume.channelId,
5780
- threadTs: args.resume.threadTs,
5781
- eventId,
5782
- error: args.error
5783
- });
5436
+ await postRunNotice(args.resume, buildTurnErrorResponse(args.error, eventId));
5784
5437
  } catch (deliveryError) {
5785
5438
  const deliveryEventId = logException(deliveryError, "slack.resume.failure_delivery.failed", { "app.error.original_event_id": eventId });
5786
5439
  try {
@@ -5899,7 +5552,7 @@ async function resumeSlackTurnInContext(args) {
5899
5552
  channelId: runArgs.channelId,
5900
5553
  timestamp: runArgs.messageTs
5901
5554
  });
5902
- if (runArgs.initialText) await postSlackMessageBestEffort(runArgs.channelId, runArgs.threadTs, runArgs.initialText);
5555
+ if (runArgs.initialText) await postRunNoticeBestEffort(runArgs, runArgs.initialText);
5903
5556
  status.update(runArgs.initialStatus);
5904
5557
  const conversationId = runArgs.conversationId;
5905
5558
  const visibleConversationId = args.conversationId;
@@ -6154,7 +5807,7 @@ async function resumeSlackTurnInContext(args) {
6154
5807
  }
6155
5808
  if (deferredPauseHandler) try {
6156
5809
  await deferredPauseHandler();
6157
- if (deferredAuthInfo) await postSlackMessageBestEffort(runArgs.channelId, runArgs.threadTs, buildAuthPauseResponse(deferredAuthInfo.actorId, deferredAuthInfo.providerDisplayName, deferredAuthInfo.requestText), runArgs.conversationId, runArgs.run?.dispatch?.replyAttribution);
5810
+ if (deferredAuthInfo) await postRunNoticeBestEffort(runArgs, buildAuthPauseResponse(deferredAuthInfo.actorId, deferredAuthInfo.providerDisplayName, deferredAuthInfo.requestText));
6158
5811
  return true;
6159
5812
  } catch (pauseError) {
6160
5813
  await handleResumeFailure({
@@ -6347,16 +6000,22 @@ async function runPausedTurn(payload, options, runOptions = {}) {
6347
6000
  return withLogContext({ conversationId: payload.conversationId }, () => runPausedTurnInContext(payload, options, runOptions));
6348
6001
  }
6349
6002
  async function runPausedTurnInContext(payload, options, runOptions) {
6350
- const thread = parseSlackThreadId(payload.conversationId);
6351
6003
  const destination = requireSlackDestination(payload.destination, "Paused turn");
6352
6004
  const wakePausedTurn$1 = options.wakePausedTurn ?? wakePausedTurn;
6005
+ const routing = await resolveConversationRouting({ conversationId: payload.conversationId });
6006
+ const pausedTurn = await getTurnRecord(payload.conversationId, payload.turnId);
6007
+ const target = resolveSlackReplyTarget({
6008
+ destination,
6009
+ location: routing?.location,
6010
+ source: pausedTurn?.source
6011
+ });
6353
6012
  return await resumeSlackTurn({
6354
6013
  messageText: "",
6355
6014
  conversationId: payload.conversationId,
6356
6015
  turnId: payload.turnId,
6357
6016
  traceContext: payload.traceContext,
6358
- channelId: thread?.channelId ?? destination.channelId,
6359
- ...thread?.threadTs ? { threadTs: thread.threadTs } : void 0,
6017
+ channelId: target.channelId,
6018
+ ...target.threadTs ? { threadTs: target.threadTs } : void 0,
6360
6019
  executeTurn: async (run, saveResult) => await executeTurn(options.agentRunner, run, saveResult),
6361
6020
  scheduleSessionCompletedPluginTasks: options.scheduleSessionCompletedPluginTasks,
6362
6021
  beforeStart: async () => {
@@ -6569,6 +6228,23 @@ async function failStrandedTurnWithFallback(args) {
6569
6228
  failureCode: "agent_run_failed",
6570
6229
  turnId: failed.turnId
6571
6230
  });
6231
+ let replyAttribution;
6232
+ if (failed.dispatchId) {
6233
+ const dispatch = await getDispatchRecord(failed.dispatchId);
6234
+ await recordTurnSummary({
6235
+ conversationId: args.conversationId,
6236
+ destination: dispatch?.destination,
6237
+ dispatchId: failed.dispatchId,
6238
+ dispatchOutcome: "failed",
6239
+ turnId: failed.turnId,
6240
+ sliceId: failed.sliceId,
6241
+ source: dispatch?.source,
6242
+ state: "failed",
6243
+ surface: failed.surface
6244
+ });
6245
+ if (!dispatch || !postsRunNotices(buildDispatchRoutingContext(dispatch).dispatch)) return true;
6246
+ replyAttribution = dispatch.replyAttribution;
6247
+ }
6572
6248
  let routing;
6573
6249
  try {
6574
6250
  routing = await resolveTurnSessionRouting({ conversationId: args.conversationId });
@@ -6579,22 +6255,15 @@ async function failStrandedTurnWithFallback(args) {
6579
6255
  });
6580
6256
  return true;
6581
6257
  }
6582
- if (failed.dispatchId) await recordTurnSummary({
6583
- conversationId: args.conversationId,
6584
- destination: routing.destination,
6585
- dispatchId: failed.dispatchId,
6586
- dispatchOutcome: "failed",
6587
- turnId: failed.turnId,
6588
- sliceId: failed.sliceId,
6589
- source: routing.source,
6590
- state: "failed",
6591
- surface: failed.surface
6258
+ const target = resolveSlackReplyTarget({
6259
+ destination: requireSlackDestination(routing.destination, "Stranded paused turn"),
6260
+ location: routing.location,
6261
+ source: failed.source
6592
6262
  });
6593
- const thread = parseSlackThreadId(args.conversationId);
6594
- const channelId = thread?.channelId ?? requireSlackDestination(routing.destination, "Stranded paused turn").channelId;
6595
- await postSlackMessage({
6596
- channelId,
6597
- ...thread?.threadTs ? { threadTs: thread.threadTs } : void 0,
6263
+ await sendSlackReply({
6264
+ ...target,
6265
+ conversationId: args.conversationId,
6266
+ replyAttribution,
6598
6267
  text: buildTurnFailureResponse(requireTurnFailureEventId(eventId, eventName))
6599
6268
  });
6600
6269
  return true;
@@ -6797,28 +6466,9 @@ function createWebSteeringDrain(args) {
6797
6466
  return accepted;
6798
6467
  };
6799
6468
  }
6800
- // src/chat/conversations/destination.ts
6801
- function requireLocalDestination(conversationId) {
6802
- const parsed = localDestinationSchema.safeParse({
6803
- platform: "local",
6804
- conversationId
6805
- });
6806
- if (!parsed.success) throw new Error(`Invalid local conversation id: ${conversationId}`);
6807
- return parsed.data;
6808
- }
6809
- /**
6810
- * Keep a stored Destination, or create the temporary local Destination.
6811
- *
6812
- * TODO(dcramer): Delete this module after Conversation and AgentRun use
6813
- * Location, and web and event Turns no longer need a local
6814
- * Destination.
6815
- */
6816
- function resolveConversationDestination(args) {
6817
- return args.existing ?? requireLocalDestination(args.conversationId);
6818
- }
6819
6469
  // src/chat/task-execution/conversation-turn.ts
6820
6470
  /** Run a mailbox Turn and store its assistant Messages. */
6821
- function stableHex$1(...parts) {
6471
+ function stableHex(...parts) {
6822
6472
  return createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 24);
6823
6473
  }
6824
6474
  function captureConversationTurnFailure(args) {
@@ -7097,7 +6747,7 @@ function createConversationTurnWorker(agentRunner, deliverMessage) {
7097
6747
  if (stopSignal?.aborted) return await completeCancelledTurn();
7098
6748
  const piMessages = await loadProjection({ conversationId: context.conversationId });
7099
6749
  failureCode = "agent_run_failed";
7100
- currentRunId = `conversation-run:${stableHex$1(turnId, String(startedAtMs))}`;
6750
+ currentRunId = `conversation-run:${stableHex(turnId, String(startedAtMs))}`;
7101
6751
  setTags({ runId: currentRunId });
7102
6752
  const outcome = await executeTurn(agentRunner, {
7103
6753
  conversationId: context.conversationId,
@@ -7195,7 +6845,7 @@ function createConversationTurnWorker(agentRunner, deliverMessage) {
7195
6845
  await maybePostAutomatedTurnLimitNotice({
7196
6846
  conversationId: context.conversationId,
7197
6847
  maxTurns: botConfig.maxConsecutiveAutomatedTurns,
7198
- threadTs: conversationLocation?.provider === "slack" ? conversationLocation.threadTs : void 0,
6848
+ source,
7199
6849
  update: automatedTurnLimit
7200
6850
  });
7201
6851
  completedSuccessfully = reply.diagnostics.outcome === "success";
@@ -7307,11 +6957,13 @@ function createSlackSystemTurnDelivery(args) {
7307
6957
  installation: { teamId: location.teamId },
7308
6958
  state: args.state,
7309
6959
  task: async () => await sendSlackReply({
6960
+ ...resolveSlackReplyTarget({
6961
+ destination: location,
6962
+ location
6963
+ }),
7310
6964
  cards,
7311
- channelId: location.channelId,
7312
6965
  conversationId,
7313
- text,
7314
- ...location.threadTs ? { threadTs: location.threadTs } : void 0
6966
+ text
7315
6967
  })
7316
6968
  });
7317
6969
  } catch (error) {
@@ -7471,167 +7123,6 @@ function createProductionConversationWorkOptions(options) {
7471
7123
  waitUntil: options.waitUntil
7472
7124
  });
7473
7125
  }
7474
- // src/chat/conversations/web-input.ts
7475
- function normalizeEmail(email) {
7476
- return email.trim().toLowerCase();
7477
- }
7478
- function stableHex(...parts) {
7479
- return createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 24);
7480
- }
7481
- /**
7482
- * Build a durable Conversation id for one web Actor and create key.
7483
- *
7484
- * Retries with the same key must address the same Conversation before the
7485
- * mailbox Message id is derived.
7486
- */
7487
- function createConversationId(args) {
7488
- return `local:web:${stableHex(normalizeEmail(args.actorEmail), args.idempotencyKey)}`;
7489
- }
7490
- /** Return the stable Turn id for one mailbox Message. */
7491
- function conversationTurnIdForMessage(messageId) {
7492
- return buildDeterministicTurnId(messageId);
7493
- }
7494
- /**
7495
- * Store the dashboard participant in the current Conversation actor field.
7496
- *
7497
- * TODO(dcramer): Remove this StoredSlackActor conversion after Conversation
7498
- * actor storage accepts web Actors directly.
7499
- */
7500
- function storedActorFromWeb(actor) {
7501
- return {
7502
- ...actor.email ? { email: normalizeEmail(actor.email) } : void 0,
7503
- ...actor.fullName ? { fullName: actor.fullName } : void 0
7504
- };
7505
- }
7506
- /** Rebuild the web Actor from durable Conversation identity. */
7507
- function webActorFromEmail(email, profile) {
7508
- const normalized = normalizeEmail(email);
7509
- return {
7510
- platform: "web",
7511
- userId: `dashboard:${stableHex(normalized)}`,
7512
- email: normalized,
7513
- ...profile?.fullName ? { fullName: profile.fullName } : void 0,
7514
- ...profile?.userName ? { userName: profile.userName } : void 0
7515
- };
7516
- }
7517
- /** Build one web mailbox entry. */
7518
- function buildWebInboundMessage(args) {
7519
- const text = args.message.trim();
7520
- if (!text && !args.attachments?.length) throw new Error("Web Message must not be empty");
7521
- if (!args.actor.email) throw new Error("Web Actor requires a verified email");
7522
- const destination = resolveConversationDestination({
7523
- conversationId: args.conversationId,
7524
- existing: args.destination
7525
- });
7526
- const nowMs = args.nowMs ?? Date.now();
7527
- return {
7528
- conversationId: args.conversationId,
7529
- createdAtMs: args.createdAtMs ?? nowMs,
7530
- delivery: "defer",
7531
- destination,
7532
- inboundMessageId: args.messageId,
7533
- input: {
7534
- authorId: args.actor.userId,
7535
- text,
7536
- ...args.attachments?.length ? { attachments: args.attachments } : void 0,
7537
- metadata: {
7538
- authorEmail: normalizeEmail(args.actor.email),
7539
- ...args.actor.fullName && { authorFullName: args.actor.fullName },
7540
- authorUserId: args.actor.userId,
7541
- ...args.actor.userName && { authorUserName: args.actor.userName },
7542
- kind: "api_turn",
7543
- messageId: args.messageId
7544
- }
7545
- },
7546
- receivedAtMs: nowMs,
7547
- source: "web"
7548
- };
7549
- }
7550
- /** Record web activity and create a Conversation root when needed. */
7551
- async function recordWebConversationActivity(args) {
7552
- const store = args.conversationStore ?? getConversationStore();
7553
- const existing = await store.get({ conversationId: args.conversationId });
7554
- const destination = resolveConversationDestination({
7555
- conversationId: args.conversationId,
7556
- existing: existing?.destination
7557
- });
7558
- const isNewRoot = !existing;
7559
- const visibility = args.rootVisibility === "private" ? "private" : "public";
7560
- const source = isNewRoot ? createWebSource(args.conversationId, visibility) : void 0;
7561
- await store.recordActivity({
7562
- conversationId: args.conversationId,
7563
- destination,
7564
- nowMs: args.nowMs,
7565
- actor: storedActorFromWeb(args.actor),
7566
- ...isNewRoot ? { source: "web" } : void 0,
7567
- ...source ? { sessionSource: source } : void 0,
7568
- ...isNewRoot ? { visibility } : void 0
7569
- });
7570
- return destination;
7571
- }
7572
- /** Create a Conversation from web input and enqueue its first Message. */
7573
- async function createAndEnqueueConversation(input, options) {
7574
- if (!input.actor.email) throw new Error("Web Actor requires a verified email");
7575
- const conversationId = createConversationId({
7576
- actorEmail: input.actor.email,
7577
- idempotencyKey: input.idempotencyKey
7578
- });
7579
- return await appendAndEnqueueWebMessage({
7580
- actor: input.actor,
7581
- conversationId,
7582
- idempotencyKey: input.idempotencyKey,
7583
- message: input.message,
7584
- images: input.images,
7585
- rootVisibility: input.visibility === "private" ? "private" : "public"
7586
- }, options);
7587
- }
7588
- async function appendAndEnqueueWebMessage(input, options) {
7589
- const text = input.message.trim();
7590
- if (!text && !input.images?.length) throw new Error("Web Message must not be empty");
7591
- if (!input.actor.email) throw new Error("Web Actor requires a verified email");
7592
- const nowMs = options.nowMs ?? Date.now();
7593
- const messageId = await webMessageId({
7594
- conversationId: input.conversationId,
7595
- idempotencyKey: input.idempotencyKey
7596
- });
7597
- const destination = await recordWebConversationActivity({
7598
- actor: input.actor,
7599
- conversationId: input.conversationId,
7600
- conversationStore: options.conversationStore,
7601
- nowMs,
7602
- ...input.rootVisibility && { rootVisibility: input.rootVisibility }
7603
- });
7604
- let attachments = [];
7605
- if (input.images?.length) {
7606
- if (!options.attachmentStorage) throw new Error("Attachment storage is unavailable.");
7607
- attachments = await storeInputImages({
7608
- conversationId: input.conversationId,
7609
- files: input.images,
7610
- storage: options.attachmentStorage
7611
- });
7612
- }
7613
- const result = await (options.exclusive ? appendAndEnqueueExclusiveInboundMessage : appendAndEnqueueInboundMessage)({
7614
- message: buildWebInboundMessage({
7615
- actor: input.actor,
7616
- conversationId: input.conversationId,
7617
- destination,
7618
- message: text,
7619
- attachments,
7620
- messageId,
7621
- nowMs
7622
- }),
7623
- conversationStore: options.conversationStore,
7624
- nowMs,
7625
- queue: options.queue,
7626
- state: options.state
7627
- });
7628
- const status = result.status === "appended" ? "accepted" : result.status;
7629
- return {
7630
- conversationId: input.conversationId,
7631
- messageId,
7632
- status
7633
- };
7634
- }
7635
7126
  // src/chat/slack/location-url.ts
7636
7127
  /** Parse a Slack workspace subdomain used in `https://{domain}.slack.com` URLs. */
7637
7128
  function parseSlackTeamDomain(value) {
@@ -8784,4 +8275,4 @@ async function readConversationFeedFromSql(options = {}) {
8784
8275
  async function readConversationFeed(options = {}) {
8785
8276
  return conversationFeedSchema.parse(await readConversationFeedFromSql(options));
8786
8277
  }
8787
- export { renderSlackObjectCard as $, readViewerAutomationRuns as A, recordSkippedConversationMessage as B, createProductionSlackWebhookServices as C, deleteViewerTask as D, ViewerTaskNotFoundError as E, assertNoLegacyAllowedDomains as F, normalizeIncomingSlackThreadId as G, clearSlackPendingReactions as H, isAllowedDomain as I, THREAD_OPTOUT_ACK as J, withNormalizedThreadId as K, isAllowedSignInEmail as L, readViewerAutomationVersions as M, readViewerAutomations as N, readConversationSourceTask as O, resolveViewerTaskCandidate as P, verifySlackSignature as Q, readEnvList as R, createProductionConversationWorkOptions as S, createSlackWebApiStatusSender as T, isSlackMessageStopped as U, buildSlackInboundMessage as V, stopSlackThread as W, parseContent as X, startProcessingReaction as Y, runWithSlackInstallation as Z, appendAndEnqueueWebMessage as _, resolveMentions as a, recordWebConversationActivity as b, readConversationAuxiliaryCostsFromSql as c, notCopiedByFork as d, getAttachmentStorage as et, childAgentTotalsReport as f, resolveSlackTeamDomains as g, conversationSummaryFromStoredConversation as h, attachConversationMentions as i, readHealthReport as it, readViewerAutomationSummary as j, readViewerAutomationExecutions as k, ConversationForkError as l, conversationEventHistory as m, readConversationRecordFromSql as n, enqueueAgentDispatch as nt, readFirstHumanMessageTexts as o, readConversationTreeMetricsFromSql as p, stopConversationTurn as q, readConversationParticipants as r, GET as rt, listConversationSidebarAnnotations as s, readConversationFeed as t, AGENT_DISPATCH_MAX_AGE_MS as tt, forkConversation as u, conversationTurnIdForMessage as v, getProductionSlackAdapter as w, webActorFromEmail as x, createAndEnqueueConversation as y, resolveAllowedDomains as z };
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 };