@sentry/junior 0.222.0 → 0.224.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 (61) hide show
  1. package/dist/{agent-hooks-DkriF84n.js → agent-hooks-DNLBwmsh.js} +60 -16
  2. package/dist/{agent-7kKtDe-6.js → agent-runner-9g6TQk9y.js} +879 -69
  3. package/dist/api/schema.d.ts +160 -11
  4. package/dist/api/schema.js +2 -2
  5. package/dist/api.js +16 -16
  6. package/dist/{app-C0zbsnr7.d.ts → app-BbSD6EtD.d.ts} +1 -1
  7. package/dist/app.d.ts +1 -1
  8. package/dist/app.js +1598 -69
  9. package/dist/{briefs-CNH1ztn5.js → briefs-CjXnGKzZ.js} +6 -6
  10. package/dist/{catalog-runtime-XH_LFA_7.js → catalog-runtime-CVZ9TEuK.js} +1 -1
  11. package/dist/{checkpoint-CRod8es5.js → checkpoint-Ceck_fLO.js} +12 -9
  12. package/dist/cli/chat.js +14 -15
  13. package/dist/cli/check.js +3 -3
  14. package/dist/cli/main.js +1 -1
  15. package/dist/cli/plugins.js +3 -3
  16. package/dist/cli/snapshot-create.js +3 -3
  17. package/dist/cli/upgrade.js +3 -3
  18. package/dist/{client-CcSboWx_.js → client-B27ufhzl.js} +56 -6
  19. package/dist/{config-CGYv-YPE.js → config-BAhl7-RC.js} +6 -7
  20. package/dist/{conversation-BBDt88di.js → conversation-1HUoZr2l.js} +33 -5
  21. package/dist/{conversation-privacy-BGjdDVWD.js → conversation-privacy-C-4JQzKD.js} +1 -1
  22. package/dist/credential-sync-BXHIxbtS.js +940 -0
  23. package/dist/{execution-stats-D97ePWuM.js → execution-stats-DikPOxJ1.js} +3 -3
  24. package/dist/{executor-BLBZ-UB8.js → executor-BG2Armby.js} +1 -1
  25. package/dist/{history-CaV8Ww7M.d.ts → history-yaSjGMD_.d.ts} +18 -0
  26. package/dist/instrumentation.js +2 -2
  27. package/dist/{job-queue-_8gxwDzt.js → job-queue-D4oAVrL9.js} +1 -1
  28. package/dist/{logging-qmsMdU0y.js → logging-iY0ssa2q.js} +1 -1
  29. package/dist/nitro.d.ts +1 -1
  30. package/dist/nitro.js +3 -3
  31. package/dist/{pending-auth-_1Ry-_Bw.js → oauth-authorization-BIijVS2N.js} +6 -48
  32. package/dist/{oauth-callback-server-Mjfm46A5.js → oauth-callback-server-DPkK3kzi.js} +4 -4
  33. package/dist/{profile-DbG8AlNd.js → profile-6KWhg_-U.js} +5 -9
  34. package/dist/{prompt-B8rCQI7K.js → prompt-90miy8VV.js} +1 -1
  35. package/dist/{registry-DGyQCsiy.js → registry-Ctgdjt-W.js} +1 -1
  36. package/dist/{resolve-D5SqDbCA.js → resolve-CB45ZukQ.js} +2 -2
  37. package/dist/{runner-BRAFjJhe.js → runner-BwKCqoF3.js} +9 -10
  38. package/dist/{sandbox-egress-signals-CdI1aVHT.js → sandbox-egress-signals-Cmy8ODat.js} +1 -1
  39. package/dist/{schema-CTOODa3L.js → schema-RpbW5Od2.js} +1 -1
  40. package/dist/{short-title-CB2fxEyx.js → short-title-xEkZwkcV.js} +2 -2
  41. package/dist/{skills-BoS3b8cg.js → skills-c2bMDVRS.js} +2 -2
  42. package/dist/{spawn-BXCU1udr.js → spawn-Cg6lyjE7.js} +2 -2
  43. package/dist/{stats-qUXQZq-w.js → stats-DP9zl3Jg.js} +19 -17
  44. package/dist/{task-queue-Di_q8922.js → task-queue-SL4uLfBB.js} +3 -3
  45. package/dist/{task-runner-4rCppQMo.js → task-runner-fntmnqzx.js} +9 -84
  46. package/dist/{thread-state-2Paza8DV.js → thread-state-Wfr0GdKS.js} +5 -5
  47. package/dist/{auth-pause-state-3OT67TA8.js → turn-failure-response-DLjSEm-R.js} +74 -84
  48. package/dist/{turn-lifecycle-f-y0Irpi.js → turn-lifecycle-5_jvLqRz.js} +1 -1
  49. package/dist/turn-session-routing-CQaysLoe.js +77 -0
  50. package/dist/{validation-Be9lSORU.js → validation-CnZkCN7o.js} +1 -1
  51. package/dist/{version-BydrOZMQ.js → version-C6lA-h_o.js} +1 -1
  52. package/dist/version.js +1 -1
  53. package/dist/{web-authorization-BdfOkTuG.js → web-authorization-Bbx999_q.js} +1 -1
  54. package/dist/{web-input-DegYUyKU.js → web-input-BoE1xE-K.js} +7 -7
  55. package/dist/{work-Citqpnfc.js → work-4Nhz9mVv.js} +52 -10
  56. package/dist/{worker-Bn_HFyvM.js → worker-CdWPsNL2.js} +5 -5
  57. package/package.json +4 -4
  58. package/dist/agent-runner-D0ImXS7U.js +0 -679
  59. package/dist/credential-sync-BfzGNruT.js +0 -2757
  60. package/dist/experimental-CIXf7Uye.js +0 -46
  61. package/dist/turn-execution--F7a9kEU.js +0 -47
package/dist/app.js CHANGED
@@ -1,41 +1,39 @@
1
- import { $ as AuthorizationFlowDisabledError, B as recordFinishedTurnForAutomatedLimit, C as renderBlockText, D as getNextRunAtMs, E as scheduledAutomationRunAttributes, F as ingestEvent, G as clearSandboxEgressSignals, H as normalizeSandboxEgressTracePropagationDomains, I as EVENT_WAIT_MS, J as parseSandboxEgressCredentialToken, K as consumeSandboxEgressAuthRequiredSignal, L as isEventMailboxMetadata, P as processWorkspaceSnapshotJob, Q as isVisionImageMediaType, R as renderTaskInput, S as renderAttachmentText, T as logScheduledAutomationRunSkipped, U as shouldPropagateSandboxEgressTrace, V as resolveSandboxEgressProviderForHost, W as SANDBOX_EGRESS_PROXY_PATH, X as createUserTokenStore, Z as buildSteeringPiMessage, _ as readPublicArtifact, b as readAttachmentVisionSummary, c as deleteMcpStoredOAuthCredentials, et as shouldEmitDevAgentTrace, f as PluginCredentialFailureError, g as publicArtifactHeaders, m as latestProgressStatus, n as createContextCompactor, nt as setConfigDefaults, o as deleteMcpAuthSessionsForUserProvider, q as consumeSandboxEgressPermissionDeniedSignal, r as executeCredentialedEgressRequest, s as deleteMcpServerSessionId, t as executeAgentRun, tt as getConfigDefaults, v as unpublishedArtifactHeaders, w as listThreadReplies, x as recordSlackAttachment, z as maybePostAutomatedTurnLimitNotice } from "./agent-7kKtDe-6.js";
2
- import { b as isProviderRetryError, i as completeText, m as escapeXml, r as completeObject } from "./client-CcSboWx_.js";
3
- import { A as JUNIOR_WORKSPACE_SNAPSHOT_JOB_CALLBACK_ROUTE, C as withSpan, D as JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE, F as isRecord$1, I as toOptionalNumber, L as toOptionalString, S as withLogContext, _ as setSpanAttributes, d as logInfo, f as logWarn, g as setSentryUser, l as logError, n as createChatSdkLogger, o as getActiveTraceId, r as createRequestContext, t as buildTurnFailureResponse, u as logException, v as setSpanStatus, x as withContext, y as setTags } from "./logging-qmsMdU0y.js";
1
+ import { $ as clearSandboxEgressSignals, A as renderAttachmentText, Ct as shouldEmitDevAgentTrace, E as unpublishedArtifactHeaders, F as getNextRunAtMs, G as isEventMailboxMetadata, H as processWorkspaceSnapshotJob, J as recordFinishedTurnForAutomatedLimit, K as renderTaskInput, M as listThreadReplies, N as logScheduledAutomationRunSkipped, O as readAttachmentVisionSummary, P as scheduledAutomationRunAttributes, Q as SANDBOX_EGRESS_PROXY_PATH, S as latestProgressStatus, St as AuthorizationFlowDisabledError, T as readPublicArtifact, Tt as setConfigDefaults, U as ingestEvent, W as EVENT_WAIT_MS, X as normalizeSandboxEgressTracePropagationDomains, Y as resolveSandboxEgressProviderForHost, Z as shouldPropagateSandboxEgressTrace, _t as buildSteeringPiMessage, a as isExperimentalFeatureEnabled, c as createContextCompactor, ct as escapeSlackMrkdwnText, dt as normalizeSlackStatusText, et as consumeSandboxEgressAuthRequiredSignal, f as deleteMcpAuthSessionsForUserProvider, ft as truncateStatusText, gt as createUserTokenStore, ht as removeReactionFromMessage, i as getExperimentalFeatures, it as formatProviderLabel, j as renderBlockText, k as recordSlackAttachment, l as executeCredentialedEgressRequest, lt as formatSlackLink, m as deleteMcpStoredOAuthCredentials, mt as postSlackMessage, nt as parseSandboxEgressCredentialToken, o as setExperimentalFeatures, p as deleteMcpServerSessionId, pt as addReactionToMessage, q as maybePostAutomatedTurnLimitNotice, s as executeAgentRun, st as startOAuthFlow, t as createAgentRunner, tt as consumeSandboxEgressPermissionDeniedSignal, ut as normalizeSlackReplyMarkdown, v as PluginCredentialFailureError, vt as isVisionImageMediaType, w as publicArtifactHeaders, wt as getConfigDefaults, x as isPendingAuthLatestRequest, y as clearPendingAuth, yt as RetryableDeliveryError } from "./agent-runner-9g6TQk9y.js";
2
+ import { b as isProviderRetryError, i as completeText, m as escapeXml, r as completeObject } from "./client-B27ufhzl.js";
3
+ import { A as JUNIOR_WORKSPACE_SNAPSHOT_JOB_CALLBACK_ROUTE, C as withSpan, D as JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE, F as isRecord$1, I as toOptionalNumber, L as toOptionalString, S as withLogContext, _ as setSpanAttributes, d as logInfo, f as logWarn, g as setSentryUser, l as logError, n as createChatSdkLogger, o as getActiveTraceId, r as createRequestContext, t as buildTurnFailureResponse, u as logException, v as setSpanStatus, x as withContext, y as setTags } from "./logging-iY0ssa2q.js";
4
4
  import { r as continueTrace } from "./sentry-CgWZM_kV.js";
5
- import { t as JUNIOR_VERSION } from "./version-BydrOZMQ.js";
6
- import { a as getChatConfig, c as getSlackClientId, d as getSlackSigningSecret, f as setBotModelConfig, i as configureFunctionMaxDurationSeconds, l as getSlackClientSecret, m as setSlackReactionConfig, n as botConfig, p as setProfiles, s as getSlackBotToken, u as getSlackReactionConfig } from "./config-CGYv-YPE.js";
5
+ import { t as JUNIOR_VERSION } from "./version-C6lA-h_o.js";
6
+ import { a as getChatConfig, c as getSlackClientId, d as getSlackSigningSecret, f as setBotModelConfig, h as normalizeSlackEmojiName, i as configureFunctionMaxDurationSeconds, l as getSlackClientSecret, m as setSlackReactionConfig, n as botConfig, p as setProfiles, s as getSlackBotToken, u as getSlackReactionConfig } from "./config-BAhl7-RC.js";
7
7
  import { a as modelIdForProfile, n as defaultModelId } from "./model-profile-CZmVoGdX.js";
8
- import { _ as parseSlackMessageTs, a as createActor, c as isActorUserId, o as createSlackActor, p as parseSlackChannelId, s as createSlackResumeActor, t as credentialContextForActor, u as parseActorUserId } from "./context-Bplcgypl.js";
9
- import { B as downloadPrivateSlackFile, F as bindSlackDirectCredentialSubject, G as isDmChannel, Gt as getDb, Ht as getConversationEventStore, I as verifyEventAutomationCredentialSubject, Jt as withConversationEventLock, K as normalizeSlackConversationId, Kt as getSqlExecutor, L as verifyScheduledAutomationCredentialSubject, N as bindEventAutomationCredentialSubject, P as bindScheduledAutomationCredentialSubject, Pt as resolveRootVisibility, Q as readActorIdentity, R as verifySlackDirectCredentialSubject, U as getSlackClient, Wt as getConversationStore, X as runWithWorkspaceTeamId, Xt as withConversationMutationLock, Y as getWorkspaceTeamId, _t as createPluginState, a as getPluginRoutes, bt as createPluginLogger, ct as collectEventMatchKeys, d as setPlugins, dn as requireSlackDestination, dt as findMatchingWatches, f as validatePlugins, gt as setDashboardConversationLinkOptions, h as setBriefsConfig, ht as getDashboardTaskLink, i as getPluginApiRoutes, ln as createSlackDestination, mt as getDashboardConversationLink, on as deleteProviderIdentityForSlackUser, ot as cancelSubscriptions, pt as JUNIOR_THREAD_STATE_TTL_MS, q as runWithSlackInstallationToken, u as getPlugins, xt as readConversationAccessFromSql, z as SlackActionError } from "./agent-hooks-DkriF84n.js";
8
+ import { _ as parseSlackMessageTs, a as createActor, c as isActorUserId, l as isUserActor, o as createSlackActor, p as parseSlackChannelId, s as createSlackResumeActor, t as credentialContextForActor, u as parseActorUserId } from "./context-Bplcgypl.js";
9
+ import { B as downloadPrivateSlackFile, F as bindSlackDirectCredentialSubject, G as isDmChannel, H as getHeaderString, I as verifyEventAutomationCredentialSubject, Jt as getSqlExecutor, K as normalizeSlackConversationId, Kt as getConversationStore, L as verifyScheduledAutomationCredentialSubject, Lt as listConversationAnnotations, N as bindEventAutomationCredentialSubject, Nt as buildSentryConversationUrl, P as bindScheduledAutomationCredentialSubject, Pt as resolveRootVisibility, Q as readActorIdentity, Qt as withConversationMutationLock, R as verifySlackDirectCredentialSubject, U as getSlackClient, Wt as getConversationEventStore, X as runWithWorkspaceTeamId, Xt as withConversationEventLock, Y as getWorkspaceTeamId, _t as createPluginState, a as getPluginRoutes, bt as createPluginLogger, cn as deleteProviderIdentityForSlackUser, ct as collectEventMatchKeys, d as setPlugins, dn as createSlackDestination, dt as findMatchingWatches, f as validatePlugins, gt as setDashboardConversationLinkOptions, h as setBriefsConfig, ht as getDashboardTaskLink, i as getPluginApiRoutes, mt as getDashboardConversationLink, o as getPluginSlackConversationLink, ot as cancelSubscriptions, pn as requireSlackDestination, pt as JUNIOR_THREAD_STATE_TTL_MS, q as runWithSlackInstallationToken, qt as getDb, u as getPlugins, xt as readConversationAccessFromSql, z as SlackActionError } from "./agent-hooks-DNLBwmsh.js";
10
10
  import { n as juniorDestinations } from "./destinations-BeBiJeEM.js";
11
- import { E as juniorConversations, a as juniorSchedulerRuns, f as juniorAgentBindings, g as juniorConversationBriefs, o as juniorSchedulerTasks, p as juniorAgentInvocations, y as juniorConversationEvents } from "./executor-BLBZ-UB8.js";
12
- import { q as instructionProvenanceFor } from "./conversation-BBDt88di.js";
13
- import { n as pluginCatalogRuntime } from "./catalog-runtime-XH_LFA_7.js";
14
- import { $ as resolveConversationTitle, F as loadProjection, G as recordSubscribedReplyRoute, J as appendConversationMessages, M as commitMessages, P as loadConversationProjection, Q as persistConversationMessageSummaries, X as persistConversationMessages, Y as hydrateConversationMessages, Z as projectConversationMessages, a as completeTurnRecord, at as isHumanConversationMessage, c as getTurnRecordForResume, ct as recordDeliveredAssistantMessage, d as persistWithRetry, dt as estimateTextTokens, et as appendThreadContextMessages, i as abandonTurnRecord, it as getConversationMessageSlackTs, j as commitAcceptedReply, k as trimTrailingAssistantMessages, l as listTurnSummaries, lt as turnHasReply, m as buildTurnErrorResponse, n as loadTurnCheckpoint, nt as createConversationMemoryService, o as failTurnRecord, ot as markConversationMessage, r as saveTurnCheckpoint, rt as estimateConversationContextTokens, s as getTurnRecord, st as normalizeConversationText, tt as buildConversationContext, u as recordTurnSummary, ut as upsertConversationMessage } from "./checkpoint-CRod8es5.js";
15
- import { E as acquireActiveLock, O as withActiveLock, T as MUTATION_LOCK_TTL_MS$1, k as withLock$1, w as getStateAdapter } from "./profile-DbG8AlNd.js";
16
- import { Bt as hasRunnableConversationWork$1, Dt as hasRunnableConversationWork, Et as getConversationWorkState, F as effectiveTaskOutcomes, Ht as isInvalidConversationRecordError, It as stopConversationWork, Kt as buildDeterministicAssistantMessageId, Mt as removeActiveConversation, Ot as listActiveConversationIds, Pt as requestConversationWork, Rt as CONVERSATION_WORK_STALE_ENQUEUE_MS, S as withScheduledAutomationLock, Tt as getConversation, U as findMatchingEventAutomations, X as getEventCatalog, a as requestAttachmentDeletion, b as saveScheduledAutomationInLock, c as parseSlackThreadId, g as listScheduledAutomationsForTeam, h as listScheduledAutomations, o as storeAttachment, pt as appendAndEnqueueInboundMessage, qt as buildDeterministicTurnId, r as collectAttachmentGarbage, tt as eventGuidance, v as readScheduledAutomation, vt as clearExpiredConversationLease, w as scheduledRunSchema, wt as ensureConversationWake, x as scheduledRunId, z as collectEventAutomationMatchKeys } from "./stats-qUXQZq-w.js";
17
- import { S as startActiveTurn, _ as isTurnInputCommitLostError, b as markTurnCompleted, d as ConversationTurnBoundaryError, f as CooperativeTurnYieldError, g as isCooperativeTurnYieldError, h as getConversationTurnBoundaryError, m as TurnInputDeferredError, p as TurnInputCommitLostError, r as clearPendingAuth, s as getAssistantReplyText, v as isTurnInputDeferredError, x as markTurnFailed, y as markTurnClosed } from "./pending-auth-_1Ry-_Bw.js";
18
- import { h as postSlackMessage, i as formatProviderLabel, s as startOAuthFlow, t as createAgentRunner, v as RetryableDeliveryError } from "./agent-runner-D0ImXS7U.js";
19
- import { a as EVENT_SYSTEM_ACTOR, i as EVENT_MESSAGE_AUTHOR, o as isEventConversationMessage } from "./prompt-B8rCQI7K.js";
20
- import { f as setSandboxResourceConfig, u as getSandboxResourceConfig } from "./resolve-D5SqDbCA.js";
11
+ import { E as juniorConversations, a as juniorSchedulerRuns, f as juniorAgentBindings, g as juniorConversationBriefs, o as juniorSchedulerTasks, p as juniorAgentInvocations, y as juniorConversationEvents } from "./executor-BG2Armby.js";
12
+ import { Y as instructionProvenanceFor, k as annotationCard } from "./conversation-1HUoZr2l.js";
13
+ import { n as pluginCatalogRuntime } from "./catalog-runtime-CVZ9TEuK.js";
14
+ import { $ as appendThreadContextMessages, A as commitAcceptedReply, B as recordAuthorizationCompleted, J as hydrateConversationMessages, N as loadConversationProjection, O as trimTrailingAssistantMessages, P as loadProjection, Q as resolveConversationTitle, W as recordSubscribedReplyRoute, X as projectConversationMessages, Y as persistConversationMessages, Z as persistConversationMessageSummaries, a as completeTurnRecord, at as markConversationMessage, c as listTurnSummaries, ct as turnHasReply, et as buildConversationContext, i as abandonTurnRecord, it as isHumanConversationMessage, j as commitMessages, l as recordTurnSummary, lt as upsertConversationMessage, n as loadTurnCheckpoint, nt as estimateConversationContextTokens, o as failTurnRecord, ot as normalizeConversationText, p as buildTurnErrorResponse, q as appendConversationMessages, r as saveTurnCheckpoint, rt as getConversationMessageSlackTs, s as getTurnRecord, st as recordDeliveredAssistantMessage, tt as createConversationMemoryService, u as persistWithRetry, ut as estimateTextTokens } from "./checkpoint-Ceck_fLO.js";
15
+ import { E as acquireActiveLock, O as withLock$1, T as MUTATION_LOCK_TTL_MS$1, w as getStateAdapter } from "./profile-6KWhg_-U.js";
16
+ import { Bt as hasRunnableConversationWork$1, Dt as hasRunnableConversationWork, Et as getConversationWorkState, F as effectiveTaskOutcomes, Ht as isInvalidConversationRecordError, It as stopConversationWork, Kt as buildDeterministicAssistantMessageId, Mt as removeActiveConversation, Ot as listActiveConversationIds, Pt as requestConversationWork, Rt as CONVERSATION_WORK_STALE_ENQUEUE_MS, S as withScheduledAutomationLock, Tt as getConversation, U as findMatchingEventAutomations, X as getEventCatalog, a as requestAttachmentDeletion, b as saveScheduledAutomationInLock, c as parseSlackThreadId, d as resolveSlackChannelIdFromThreadId, g as listScheduledAutomationsForTeam, h as listScheduledAutomations, l as readSlackRawMessageContext, o as storeAttachment, pt as appendAndEnqueueInboundMessage, qt as buildDeterministicTurnId, r as collectAttachmentGarbage, tt as eventGuidance, u as resolveSlackChannelIdFromMessage, v as readScheduledAutomation, vt as clearExpiredConversationLease, w as scheduledRunSchema, wt as ensureConversationWake, x as scheduledRunId, z as collectEventAutomationMatchKeys } from "./stats-DP9zl3Jg.js";
17
+ import { _ as markTurnFailed, c as CooperativeTurnYieldError, d as getConversationTurnBoundaryError, f as isCooperativeTurnYieldError, g as markTurnCompleted, h as markTurnClosed, l as TurnInputCommitLostError, m as isTurnInputDeferredError, p as isTurnInputCommitLostError, r as getAssistantReplyText, s as ConversationTurnBoundaryError, u as TurnInputDeferredError, v as startActiveTurn } from "./oauth-authorization-BIijVS2N.js";
18
+ import { a as EVENT_SYSTEM_ACTOR, i as EVENT_MESSAGE_AUTHOR, o as isEventConversationMessage } from "./prompt-90miy8VV.js";
19
+ import { f as setSandboxResourceConfig, u as getSandboxResourceConfig } from "./resolve-CB45ZukQ.js";
21
20
  import { l as conversationQueueMessageSchema, n as resolveConversationWorkQueueTopic, r as verifyConversationQueueMessage, t as getVercelConversationWorkQueue, u as isConversationQueueMessageRejectedError } from "./vercel-queue-DIkVcfK4.js";
22
- import { a as getTurnRequestDeadline, i as verifyWorkspaceSnapshotJobMessage, t as WORKSPACE_SNAPSHOT_JOB_QUEUE_TOPIC } from "./job-queue-_8gxwDzt.js";
23
- import { i as setExperimentalFeatures, n as getExperimentalFeatures, r as isExperimentalFeatureEnabled } from "./experimental-CIXf7Uye.js";
24
- import { n as validatePluginRegistrations, t as validatePluginEgressCredentialHooks } from "./validation-Be9lSORU.js";
21
+ import { a as getTurnRequestDeadline, i as verifyWorkspaceSnapshotJobMessage, t as WORKSPACE_SNAPSHOT_JOB_QUEUE_TOPIC } from "./job-queue-D4oAVrL9.js";
22
+ import { n as validatePluginRegistrations, t as validatePluginEgressCredentialHooks } from "./validation-CnZkCN7o.js";
25
23
  import { a as pluginRuntimeRegistrationsFromPluginSet, n as pluginCatalogConfigFromEnv, r as pluginCatalogConfigFromPluginSet, t as defineJuniorPlugins } from "./plugins-egKWjgue.js";
26
- import { S as GET$3, _ as readViewerAutomationSummary, a as webActorFromEmail, b as legacyWebMailboxMetadataSchema, i as recordWebConversationActivity, l as resolveSlackChannelTypeFromMessage, n as conversationTurnIdForMessage, o as resolveConversationDestination, s as conversationVisibilityFromSlackChannelType, t as appendAndEnqueueWebMessage, u as resolveSlackConversationContext, x as createVercelAttachmentStorage, y as lookupSlackUser } from "./web-input-DegYUyKU.js";
27
- import { u as recordAutomationExecution } from "./execution-stats-D97ePWuM.js";
28
- import { _ as markDispatchCompleted, a as claimDispatchMailboxAppend, c as getDispatchConversationId, d as getDispatchTurnId, f as getPluginDispatchProjection, g as markDispatchBlocked, h as markDispatchAwaitingResume, i as resolveTurnSessionRouting, l as getDispatchInputMessageId, m as listPendingDispatchMailboxAppends, n as scheduleSessionCompletedPluginTasks, o as confirmDispatchMailboxAppend, p as isTerminalDispatchStatus, s as createOrGetDispatch, t as processPluginTask, u as getDispatchRecord, v as markDispatchFailed, y as markDispatchRunning } from "./task-runner-4rCppQMo.js";
29
- import { i as resolveAgentInvocationId, o as listPendingAgentInvocationMailboxAppends, r as enqueueAgentInvocation, t as createAgentInvocationWorker } from "./work-Citqpnfc.js";
30
- import { n as getTurnLifecycle, t as ConversationTurnLifecycleService } from "./turn-lifecycle-f-y0Irpi.js";
31
- import { n as executeTurn, t as AgentRunError } from "./turn-execution--F7a9kEU.js";
32
- 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-2Paza8DV.js";
33
- import { a as requireTurnFailureEventId, c as getTurnUserMessage, d as getTurnUserSlackMessageTs, f as buildDeliveredTurnStatePatch, i as getAgentTurnDiagnosticsAttributes, n as persistAuthPauseTurnState, r as finalizeFailedTurnReplyWithEvent, s as loadPendingMessageCards, t as completeAuthPauseTurn, u as getTurnUserReplyAttachmentContext } from "./auth-pause-state-3OT67TA8.js";
34
- import { a as registerVercelPluginTaskDevConsumer, n as createVercelPluginTaskCallback, s as queueCallback } from "./task-queue-Di_q8922.js";
35
- import { C as isSlackTitlePermissionError, D as createSlackAdapterAssistantStatusSession, E as splitSlackReplyText, S as isRetryableSlackPostError, T as buildSlackOutputMessage, _ as getThreadId, a as GET$4, b as getSlackApiErrorCode, c as wakePausedTurn, d as shouldKeepProcessingReactionForToolInvocation, f as startProcessingReaction, g as getRunId, h as getMessageTs, i as publishAppHomeView, l as resumeSlackTurn, m as getChannelId, o as createPausedTurns, p as getAssistantThreadContext, r as GET$5, s as getPausedTurnRequest, t as receiveLocalOAuthCredential, u as buildAuthPauseResponse, v as getThreadTs, w as sendSlackReply, x as getSlackErrorObservabilityAttributes, y as stripLeadingBotMention } from "./credential-sync-BfzGNruT.js";
36
- import { n as deleteWebAuthorization, t as createWebAuthorization } from "./web-authorization-BdfOkTuG.js";
37
- import { t as processConversationWork } from "./worker-Bn_HFyvM.js";
38
- import { t as bindSpawnAgent } from "./spawn-BXCU1udr.js";
24
+ import { S as GET$3, _ as readViewerAutomationSummary, a as webActorFromEmail, b as legacyWebMailboxMetadataSchema, i as recordWebConversationActivity, l as resolveSlackChannelTypeFromMessage, n as conversationTurnIdForMessage, o as resolveConversationDestination, s as conversationVisibilityFromSlackChannelType, t as appendAndEnqueueWebMessage, u as resolveSlackConversationContext, x as createVercelAttachmentStorage, y as lookupSlackUser } from "./web-input-BoE1xE-K.js";
25
+ import { u as recordAutomationExecution } from "./execution-stats-DikPOxJ1.js";
26
+ import { _ as markDispatchFailed, a as confirmDispatchMailboxAppend, c as getDispatchInputMessageId, d as getPluginDispatchProjection, f as isTerminalDispatchStatus, g as markDispatchCompleted, h as markDispatchBlocked, i as claimDispatchMailboxAppend, l as getDispatchRecord, m as markDispatchAwaitingResume, n as scheduleSessionCompletedPluginTasks, o as createOrGetDispatch, p as listPendingDispatchMailboxAppends, s as getDispatchConversationId, t as processPluginTask, u as getDispatchTurnId, v as markDispatchRunning } from "./task-runner-fntmnqzx.js";
27
+ import { c as listPendingAgentInvocationMailboxAppends, i as resolveAgentInvocationId, o as AgentRunError, r as enqueueAgentInvocation, s as executeTurn, t as createAgentInvocationWorker } from "./work-4Nhz9mVv.js";
28
+ import { n as getTurnLifecycle, t as ConversationTurnLifecycleService } from "./turn-lifecycle-5_jvLqRz.js";
29
+ import { a as persistThreadState, c as configValueSchema, i as persistThreadRuntimeState, n as getPersistedSandboxState, o as persistThreadStateById, r as getPersistedThreadState, s as coerceThreadConversationState, t as getLocationConfigurationService } from "./thread-state-Wfr0GdKS.js";
30
+ import { a as GET$4, c as wakePausedTurn, i as publishAppHomeView, l as getTurnAuthorization, o as createPausedTurns, r as GET$5, s as getPausedTurnRequest, t as receiveLocalOAuthCredential } from "./credential-sync-BXHIxbtS.js";
31
+ import { n as resolveTurnSessionRouting } from "./turn-session-routing-CQaysLoe.js";
32
+ import { n as deleteWebAuthorization, t as createWebAuthorization } from "./web-authorization-Bbx999_q.js";
33
+ import { a as registerVercelPluginTaskDevConsumer, n as createVercelPluginTaskCallback, s as queueCallback } from "./task-queue-SL4uLfBB.js";
34
+ import { t as processConversationWork } from "./worker-CdWPsNL2.js";
35
+ import { t as bindSpawnAgent } from "./spawn-Cg6lyjE7.js";
36
+ 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-DLjSEm-R.js";
39
37
  import { createRequire } from "node:module";
40
38
  import { pathToFileURL } from "node:url";
41
39
  import { Hono } from "hono";
@@ -1920,6 +1918,189 @@ async function GET(request) {
1920
1918
  });
1921
1919
  }
1922
1920
  //#endregion
1921
+ //#region src/chat/slack/automation-card.ts
1922
+ function preview(text, length) {
1923
+ const compact = text.replace(/\s+/g, " ").trim();
1924
+ return compact.length > length ? `${compact.slice(0, length - 1)}…` : compact;
1925
+ }
1926
+ /** Show an Automation as a native Work Object, not an operation receipt. */
1927
+ function renderSlackAutomationCard(card) {
1928
+ const title = preview(card.title, 160);
1929
+ const trigger = preview(card.trigger, 500);
1930
+ const warning = card.warning ? preview(card.warning, 500) : null;
1931
+ const text = [
1932
+ card.url ? formatSlackLink(card.url, title) : escapeSlackMrkdwnText(title),
1933
+ escapeSlackMrkdwnText(trigger),
1934
+ warning ? escapeSlackMrkdwnText(warning) : null
1935
+ ].filter(Boolean).join("\n");
1936
+ if (!card.url) return {
1937
+ entity: null,
1938
+ text
1939
+ };
1940
+ return {
1941
+ text,
1942
+ entity: {
1943
+ entity_type: "slack#/entities/item",
1944
+ external_ref: {
1945
+ id: card.id,
1946
+ type: "automation"
1947
+ },
1948
+ url: card.url,
1949
+ entity_payload: {
1950
+ attributes: {
1951
+ title: { text: title },
1952
+ display_type: "Automation"
1953
+ },
1954
+ custom_fields: [{
1955
+ key: "trigger",
1956
+ label: "When",
1957
+ type: "string",
1958
+ value: trigger,
1959
+ long: true
1960
+ }, ...warning ? [{
1961
+ key: "warning",
1962
+ label: "Needs attention",
1963
+ type: "string",
1964
+ value: warning,
1965
+ long: true
1966
+ }] : []]
1967
+ }
1968
+ }
1969
+ };
1970
+ }
1971
+ //#endregion
1972
+ //#region src/chat/slack/object-card.ts
1973
+ /** Render verified annotation facts as a native Task or Item preview. */
1974
+ function renderSlackObjectCard(card, conversationId) {
1975
+ if (card.objectType === "automation" && card.plugin === "junior") return renderSlackAutomationCard({
1976
+ id: card.key,
1977
+ title: card.title,
1978
+ url: card.url,
1979
+ trigger: card.trigger ?? "",
1980
+ warning: card.warning ?? null
1981
+ });
1982
+ const title = card.title.slice(0, 160);
1983
+ const text = [
1984
+ card.url ? formatSlackLink(card.url, title) : escapeSlackMrkdwnText(title),
1985
+ escapeSlackMrkdwnText(card.label),
1986
+ card.status ? escapeSlackMrkdwnText(card.status) : null
1987
+ ].filter(Boolean).join("\n");
1988
+ if (!card.url) return {
1989
+ entity: null,
1990
+ text
1991
+ };
1992
+ const task = card.objectType === "task";
1993
+ return {
1994
+ text,
1995
+ entity: {
1996
+ entity_type: task ? "slack#/entities/task" : "slack#/entities/item",
1997
+ external_ref: {
1998
+ id: JSON.stringify([
1999
+ conversationId,
2000
+ card.plugin,
2001
+ card.key
2002
+ ]),
2003
+ type: "annotation"
2004
+ },
2005
+ url: card.url,
2006
+ entity_payload: {
2007
+ attributes: {
2008
+ title: { text: title },
2009
+ display_id: card.label,
2010
+ display_type: card.objectType === "code_change" ? "Pull request" : task ? "Issue" : "Item",
2011
+ product_name: card.plugin
2012
+ },
2013
+ fields: task && card.status ? { status: { value: card.status } } : void 0,
2014
+ custom_fields: !task && card.status ? [{
2015
+ key: "status",
2016
+ label: "Status",
2017
+ type: "string",
2018
+ value: card.status
2019
+ }] : []
2020
+ }
2021
+ }
2022
+ };
2023
+ }
2024
+ //#endregion
2025
+ //#region src/chat/slack/annotation-details.ts
2026
+ const eventSchema$1 = z.object({
2027
+ trigger_id: z.string().min(1),
2028
+ user: z.string().min(1),
2029
+ external_ref: z.object({
2030
+ type: z.literal("annotation"),
2031
+ id: z.string().max(4096)
2032
+ })
2033
+ });
2034
+ const refSchema = z.tuple([
2035
+ z.string().min(1),
2036
+ z.string().min(1),
2037
+ z.string().min(1)
2038
+ ]);
2039
+ /** Show saved annotation facts only to a viewer who can read their Conversation. */
2040
+ async function presentSlackAnnotationDetails(event, teamId) {
2041
+ const triggerId = event.trigger_id;
2042
+ if (typeof triggerId !== "string" || !triggerId) return;
2043
+ const client = getSlackClient();
2044
+ const missing = () => client.entity.presentDetails({
2045
+ trigger_id: triggerId,
2046
+ error: { status: "not_found" }
2047
+ });
2048
+ const parsed = eventSchema$1.safeParse(event);
2049
+ if (!parsed.success || !teamId) {
2050
+ await missing();
2051
+ return;
2052
+ }
2053
+ let value;
2054
+ try {
2055
+ value = JSON.parse(parsed.data.external_ref.id);
2056
+ } catch {
2057
+ await missing();
2058
+ return;
2059
+ }
2060
+ const ref = refSchema.safeParse(value);
2061
+ if (!ref.success) {
2062
+ await missing();
2063
+ return;
2064
+ }
2065
+ const [conversationId, plugin, key] = ref.data;
2066
+ const db = getDb();
2067
+ const identity = await readActorIdentity({
2068
+ platform: "slack",
2069
+ teamId,
2070
+ userId: parsed.data.user
2071
+ });
2072
+ if (!identity?.user) {
2073
+ await missing();
2074
+ return;
2075
+ }
2076
+ const [location] = await db.select({
2077
+ teamId: juniorDestinations.providerTenantId,
2078
+ provider: juniorDestinations.provider
2079
+ }).from(juniorConversations).innerJoin(juniorDestinations, eq(juniorDestinations.id, juniorConversations.destinationId)).where(eq(juniorConversations.conversationId, conversationId));
2080
+ if (location?.provider !== "slack" || location.teamId !== teamId) {
2081
+ await missing();
2082
+ return;
2083
+ }
2084
+ if (!(await readConversationAccessFromSql(db, [conversationId], identity.user)).get(conversationId)?.canViewPrivateContent) {
2085
+ await missing();
2086
+ return;
2087
+ }
2088
+ const annotation = (await listConversationAnnotations(db, conversationId)).find((item) => item.plugin === plugin && item.key === key);
2089
+ if (!annotation) {
2090
+ await missing();
2091
+ return;
2092
+ }
2093
+ const entity = renderSlackObjectCard(annotationCard(annotation), conversationId).entity;
2094
+ if (!entity) {
2095
+ await missing();
2096
+ return;
2097
+ }
2098
+ await client.entity.presentDetails({
2099
+ trigger_id: parsed.data.trigger_id,
2100
+ metadata: entity
2101
+ });
2102
+ }
2103
+ //#endregion
1923
2104
  //#region src/chat/services/subscribed-reply-router-policy.ts
1924
2105
  /**
1925
2106
  * Passive subscribed-thread reply router policy.
@@ -1997,11 +2178,11 @@ const BARE_STOP_RE = /^stop(?:\s*[.!…]+)?$/i;
1997
2178
  const ACKNOWLEDGMENT_ONLY_RE = /^(?:thanks(?: you)?|thank you|thx|ty|got it|sounds good|sgtm|lgtm|ok(?:ay)?|cool|nice|perfect|awesome|great|makes sense|understood|roger|yep|yup|kk|on it|will do)(?:[.!]+)?$/i;
1998
2179
  const DIRECTED_FOLLOW_UP_CUE_RE = /\b(?:you said|you just said|your last response|your last answer|what did you just say|what do you mean|what did you mean|explain(?: that| this| it| more)?|clarify(?: that| this| it)?|expand(?: on)?(?: that| this| it)?|elaborate(?: on)?(?: that| this| it)?|say more)\b/i;
1999
2180
  const TERSE_CLARIFICATION_RE = /^(?:which one|which ones|why|how so|what do you mean|what did you mean|say more|explain that|clarify that|expand on that|elaborate on that)\??$/i;
2000
- function escapeRegExp$1(value) {
2181
+ function escapeRegExp$2(value) {
2001
2182
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2002
2183
  }
2003
2184
  function containsAssistantInvocation(text, botUserName) {
2004
- const escapedUserName = escapeRegExp$1(botUserName);
2185
+ const escapedUserName = escapeRegExp$2(botUserName);
2005
2186
  const plainNameMentionRe = new RegExp(`(^|\\s)@${escapedUserName}\\b`, "i");
2006
2187
  const labeledEntityMentionRe = new RegExp(`<@[^>|]+\\|${escapedUserName}>`, "i");
2007
2188
  return plainNameMentionRe.test(text) || labeledEntityMentionRe.test(text);
@@ -2292,6 +2473,53 @@ async function decideSubscribedThreadReply(args) {
2292
2473
  }
2293
2474
  }
2294
2475
  //#endregion
2476
+ //#region src/chat/slack/errors.ts
2477
+ /** Extract Slack's stable API error code from Web API errors. */
2478
+ function getSlackApiErrorCode(error) {
2479
+ if (!error || typeof error !== "object") return;
2480
+ const candidate = error;
2481
+ if (typeof candidate.data?.error === "string" && candidate.data.error.trim().length > 0) return candidate.data.error;
2482
+ if (typeof candidate.code === "string" && candidate.code.trim().length > 0) return candidate.code;
2483
+ }
2484
+ /** Convert Slack Web API error details into the repository's trace attributes. */
2485
+ function getSlackErrorObservabilityAttributes(error) {
2486
+ if (!error || typeof error !== "object") return {};
2487
+ const candidate = error;
2488
+ const attributes = {};
2489
+ if (typeof candidate.code === "string" && candidate.code.trim().length > 0) attributes["app.slack.error_code"] = candidate.code;
2490
+ if (typeof candidate.data?.error === "string" && candidate.data.error.trim().length > 0) attributes["app.slack.api_error"] = candidate.data.error;
2491
+ const requestId = getHeaderString(candidate.headers, "x-slack-req-id");
2492
+ if (requestId) attributes["app.slack.request_id"] = requestId;
2493
+ if (typeof candidate.statusCode === "number" && Number.isFinite(candidate.statusCode)) attributes["http.response.status_code"] = candidate.statusCode;
2494
+ return attributes;
2495
+ }
2496
+ const TRANSIENT_SLACK_API_ERRORS = /* @__PURE__ */ new Set([
2497
+ "fatal_error",
2498
+ "internal_error",
2499
+ "rate_limited",
2500
+ "ratelimited",
2501
+ "request_timeout",
2502
+ "service_unavailable"
2503
+ ]);
2504
+ /** Recover transient or ambiguous Slack failures, but stop on explicit rejection. */
2505
+ function isRetryableSlackPostError(error) {
2506
+ if (!error || typeof error !== "object") return true;
2507
+ const candidate = error;
2508
+ if (typeof candidate.statusCode === "number") {
2509
+ if (candidate.statusCode === 429 || candidate.statusCode >= 500) return true;
2510
+ if (candidate.statusCode >= 400) return false;
2511
+ }
2512
+ const apiError = error instanceof SlackActionError ? error.apiError : typeof candidate.data?.error === "string" ? candidate.data.error : void 0;
2513
+ if (apiError) return TRANSIENT_SLACK_API_ERRORS.has(apiError);
2514
+ if (error instanceof SlackActionError) return error.code === "rate_limited" || error.code === "internal_error";
2515
+ return true;
2516
+ }
2517
+ /** Report whether Slack rejected assistant title updates for stable auth reasons. */
2518
+ function isSlackTitlePermissionError(error) {
2519
+ const code = getSlackApiErrorCode(error);
2520
+ return code === "no_permission" || code === "missing_scope" || code === "not_allowed_token_type";
2521
+ }
2522
+ //#endregion
2295
2523
  //#region src/chat/slack/message/content.ts
2296
2524
  /**
2297
2525
  * Slack message content projection.
@@ -2314,8 +2542,11 @@ function readRawBlocks(raw) {
2314
2542
  return raw.blocks;
2315
2543
  }
2316
2544
  function renderTopLevelText(message) {
2545
+ const blocks = readRawBlocks(message.raw);
2546
+ const blockText = renderBlockText(blocks);
2547
+ if (Array.isArray(blocks) && blocks.some((block) => block?.type === "section") && blockText) return blockText;
2317
2548
  if (message.formatted.children.length > 0) return stringifyMarkdown(message.formatted).trim();
2318
- return message.text.trim() || renderBlockText(readRawBlocks(message.raw));
2549
+ return message.text.trim() || blockText;
2319
2550
  }
2320
2551
  /** Parse the agent-visible content and attachment state of a Slack message. */
2321
2552
  function parseContent(message) {
@@ -2329,6 +2560,166 @@ function parseContent(message) {
2329
2560
  };
2330
2561
  }
2331
2562
  //#endregion
2563
+ //#region src/chat/runtime/thread-context.ts
2564
+ function escapeRegExp$1(value) {
2565
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2566
+ }
2567
+ function readOptionalStringField(value, key) {
2568
+ if (!(key in value)) return;
2569
+ return toOptionalString(value[key]);
2570
+ }
2571
+ function stripLeadingBotMention(text, options = {}) {
2572
+ if (!text.trim()) return text;
2573
+ let next = text;
2574
+ if (options.stripLeadingSlackMentionToken) {
2575
+ if (options.botUserId) {
2576
+ const botUserId = escapeRegExp$1(options.botUserId);
2577
+ const mentionByBotUserIdRe = new RegExp(`^\\s*(?:<@${botUserId}(?:\\|[^>]+)?>|@${botUserId})[\\s,:-]*`, "i");
2578
+ next = next.replace(mentionByBotUserIdRe, "").trim();
2579
+ } else next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
2580
+ }
2581
+ const mentionByNameRe = new RegExp(`^\\s*@${escapeRegExp$1(botConfig.userName)}\\b[\\s,:-]*`, "i");
2582
+ next = next.replace(mentionByNameRe, "").trim();
2583
+ const mentionByLabeledEntityRe = new RegExp(`^\\s*<@[^>|]+\\|${escapeRegExp$1(botConfig.userName)}>[\\s,:-]*`, "i");
2584
+ next = next.replace(mentionByLabeledEntityRe, "").trim();
2585
+ return next;
2586
+ }
2587
+ function getThreadId(thread, _message) {
2588
+ return toOptionalString(thread.id);
2589
+ }
2590
+ function getRunId(thread, message) {
2591
+ return readOptionalStringField(thread, "runId") ?? readOptionalStringField(message, "runId");
2592
+ }
2593
+ function getChannelId(thread, message) {
2594
+ return resolveSlackChannelIdFromThreadId(toOptionalString(thread.id)) ?? normalizeSlackConversationId(toOptionalString(thread.channelId)) ?? resolveSlackChannelIdFromMessage(message);
2595
+ }
2596
+ function getThreadTs(threadId) {
2597
+ return parseSlackThreadId(threadId)?.threadTs;
2598
+ }
2599
+ /**
2600
+ * Resolve Slack assistant-thread API context for the current turn.
2601
+ *
2602
+ * Slack assistant-thread methods must use the live inbound thread context
2603
+ * Slack provided on the current message. Slack's assistant utilities build
2604
+ * `setStatus`/`setTitle` from `message.channel` plus `message.thread_ts ?? message.ts`
2605
+ * for non-DM message events, while `message.im` still requires an explicit
2606
+ * `thread_ts`. Do not synthesize assistant-thread roots from persisted state.
2607
+ */
2608
+ function getAssistantThreadContext(message) {
2609
+ const rawContext = readSlackRawMessageContext(message);
2610
+ const channelId = rawContext?.channelId;
2611
+ if (channelId) {
2612
+ const threadTs = isDmChannel(channelId) ? rawContext.threadTs : rawContext.threadTs ?? rawContext.messageTs;
2613
+ if (threadTs) return {
2614
+ channelId,
2615
+ threadTs
2616
+ };
2617
+ }
2618
+ const parsedThreadId = parseSlackThreadId(readOptionalStringField(message, "threadId"));
2619
+ if (!parsedThreadId || isDmChannel(parsedThreadId.channelId)) return;
2620
+ return parsedThreadId;
2621
+ }
2622
+ /** Resolve the native Slack timestamp for a message that can target Slack APIs. */
2623
+ function getMessageTs(message) {
2624
+ const directTs = readOptionalStringField(message, "ts");
2625
+ const parsedDirectTs = parseSlackMessageTs(directTs);
2626
+ if (parsedDirectTs) return parsedDirectTs;
2627
+ const rawContext = readSlackRawMessageContext(message);
2628
+ if (!rawContext) return;
2629
+ const candidates = [rawContext.messageTs, rawContext.nestedMessageTs];
2630
+ for (const candidate of candidates) {
2631
+ const ts = parseSlackMessageTs(candidate);
2632
+ if (ts) return ts;
2633
+ }
2634
+ }
2635
+ //#endregion
2636
+ //#region src/chat/providers/slack/processing-reaction.ts
2637
+ const noProcessingReaction = {
2638
+ complete: async () => void 0,
2639
+ keep: () => void 0,
2640
+ stop: async () => void 0
2641
+ };
2642
+ function isProcessingReactionEmoji(value) {
2643
+ return typeof value === "string" && normalizeSlackEmojiName(value) === getChatConfig().slack.processingReactionEmoji;
2644
+ }
2645
+ /** Return true when a Slack reaction tool invocation keeps the processing reaction. */
2646
+ function shouldKeepProcessingReactionForToolInvocation(input) {
2647
+ return input.toolName === "addReaction" && isProcessingReactionEmoji(input.params.emoji);
2648
+ }
2649
+ /** Start Junior's automatic Slack processing reaction for one inbound message. */
2650
+ async function startProcessingReaction(args) {
2651
+ if (args.message.author.isMe) return noProcessingReaction;
2652
+ const channelId = getChannelId(args.thread, args.message);
2653
+ const messageTs = getMessageTs(args.message);
2654
+ if (!channelId || !messageTs) return noProcessingReaction;
2655
+ return startProcessingReactionForMessage({
2656
+ channelId,
2657
+ timestamp: messageTs
2658
+ });
2659
+ }
2660
+ /** Start Junior's automatic Slack processing reaction for a known Slack message. */
2661
+ async function startProcessingReactionForMessage(args) {
2662
+ try {
2663
+ await addReactionToMessage({
2664
+ channelId: args.channelId,
2665
+ timestamp: args.timestamp,
2666
+ emoji: getChatConfig().slack.processingReactionEmoji
2667
+ });
2668
+ } catch (error) {
2669
+ logException(error, "slack.processing.reaction_add.failed", {
2670
+ "app.slack.action": "reactions.add",
2671
+ "messaging.message.id": args.timestamp,
2672
+ ...getSlackErrorObservabilityAttributes(error)
2673
+ });
2674
+ return noProcessingReaction;
2675
+ }
2676
+ let shouldRemove = true;
2677
+ const removeProcessingReaction = async () => {
2678
+ if (!shouldRemove) return false;
2679
+ try {
2680
+ await removeReactionFromMessage({
2681
+ channelId: args.channelId,
2682
+ timestamp: args.timestamp,
2683
+ emoji: getChatConfig().slack.processingReactionEmoji
2684
+ });
2685
+ return true;
2686
+ } catch (error) {
2687
+ logException(error, "slack.processing.reaction_remove.failed", {
2688
+ "app.slack.action": "reactions.remove",
2689
+ "messaging.message.id": args.timestamp,
2690
+ ...getSlackErrorObservabilityAttributes(error)
2691
+ });
2692
+ return false;
2693
+ }
2694
+ };
2695
+ return {
2696
+ complete: async () => {
2697
+ const shouldAddCompleted = shouldRemove;
2698
+ await removeProcessingReaction();
2699
+ if (!shouldAddCompleted) return;
2700
+ try {
2701
+ await addReactionToMessage({
2702
+ channelId: args.channelId,
2703
+ timestamp: args.timestamp,
2704
+ emoji: getChatConfig().slack.completedReactionEmoji
2705
+ });
2706
+ } catch (error) {
2707
+ logException(error, "slack.processing.reaction_complete.failed", {
2708
+ "app.slack.action": "reactions.add",
2709
+ "messaging.message.id": args.timestamp,
2710
+ ...getSlackErrorObservabilityAttributes(error)
2711
+ });
2712
+ }
2713
+ },
2714
+ keep: () => {
2715
+ shouldRemove = false;
2716
+ },
2717
+ stop: async () => {
2718
+ await removeProcessingReaction();
2719
+ }
2720
+ };
2721
+ }
2722
+ //#endregion
2332
2723
  //#region src/chat/slack/message-control.ts
2333
2724
  /** Leading Slack mention forms that may precede an explicit steering marker. */
2334
2725
  const LEADING_MENTION_BEFORE_STEERING_RE = /^(\s*(?:<@[^>]+>|@[A-Za-z0-9._-]+\b)[\s,:-]*)!!\s*/;
@@ -4851,7 +5242,7 @@ async function routeParsedMessage(args) {
4851
5242
  const message = normalized.message;
4852
5243
  const canonicalThreadId = normalized.threadId;
4853
5244
  const botUserId = args.adapter.botUserId;
4854
- const isMention = Boolean(botUserId && textMentionsBot(args.event.text ?? "", botUserId));
5245
+ const isMention = Boolean(botUserId && (textMentionsBot(args.event.text ?? "", botUserId) || args.event.blocks?.some((block) => block.type === "section" && block.text?.type === "mrkdwn" && textMentionsBot(block.text.text, botUserId))));
4855
5246
  if (isMention) message.isMention = true;
4856
5247
  const isDirectMessage = isDmEvent(args.event);
4857
5248
  const isSubscribed = !isDirectMessage && !isMention && await args.state.isSubscribed(canonicalThreadId);
@@ -4961,7 +5352,9 @@ async function handleSlackEvent(args) {
4961
5352
  return;
4962
5353
  }
4963
5354
  if (event.type === "entity_details_requested") {
4964
- await presentSlackAutomationDetails(event, installation.teamId);
5355
+ const ref = event.external_ref;
5356
+ if (ref && typeof ref === "object" && "type" in ref && ref.type === "annotation") await presentSlackAnnotationDetails(event, installation.teamId);
5357
+ else await presentSlackAutomationDetails(event, installation.teamId);
4965
5358
  return;
4966
5359
  }
4967
5360
  if (event.type === "app_home_opened" && event.user) {
@@ -5359,6 +5752,616 @@ function readSlackActionToken(message) {
5359
5752
  return token.success ? token.data : void 0;
5360
5753
  }
5361
5754
  //#endregion
5755
+ //#region src/chat/slack/cards.ts
5756
+ /** Select the Slack object renderer for a built-in card. */
5757
+ function renderSlackCard(card, conversationId) {
5758
+ switch (card.kind) {
5759
+ case "object": return renderSlackObjectCard(card, conversationId);
5760
+ case "automation": return renderSlackAutomationCard(card);
5761
+ }
5762
+ }
5763
+ //#endregion
5764
+ //#region src/chat/slack/footer.ts
5765
+ /** Render compact reply attribution for the Slack footer. */
5766
+ function formatReplyAttribution(attribution) {
5767
+ return attribution.detail ? `${attribution.label} · ${attribution.detail}` : attribution.label;
5768
+ }
5769
+ /**
5770
+ * Build the compact conversation footer for visible Slack reply surfaces.
5771
+ *
5772
+ * Detailed turn metrics stay in the dashboard instead of Slack-visible copy.
5773
+ */
5774
+ function buildSlackReplyFooter(args) {
5775
+ const items = [];
5776
+ const conversationId = args.conversationId?.trim();
5777
+ if (conversationId) {
5778
+ const idItem = {
5779
+ label: "ID",
5780
+ value: conversationId
5781
+ };
5782
+ const conversationUrl = getPluginSlackConversationLink(conversationId)?.url ?? getDashboardConversationLink(conversationId) ?? buildSentryConversationUrl(conversationId);
5783
+ if (conversationUrl) idItem.url = conversationUrl;
5784
+ items.push(idItem);
5785
+ }
5786
+ return items.length > 0 || args.replyAttribution ? {
5787
+ ...args.replyAttribution ? { attribution: args.replyAttribution } : void 0,
5788
+ items
5789
+ } : void 0;
5790
+ }
5791
+ /** Build Slack blocks for a reply chunk using the Slack-flavored markdown block for the body. */
5792
+ function buildSlackReplyBlocks(text, footer) {
5793
+ const blocks = text.trim() ? [{
5794
+ type: "markdown",
5795
+ text
5796
+ }] : [];
5797
+ if (footer && (footer.attribution || footer.items.length > 0)) {
5798
+ const attributionElements = footer.attribution ? [{
5799
+ type: "plain_text",
5800
+ text: formatReplyAttribution(footer.attribution)
5801
+ }] : [];
5802
+ blocks.push({
5803
+ type: "context",
5804
+ elements: [...attributionElements, ...footer.items.map((item) => ({
5805
+ type: "mrkdwn",
5806
+ text: item.url ? `*${escapeSlackMrkdwnText(item.label)}:* ${formatSlackLink(item.url, item.value)}` : `*${escapeSlackMrkdwnText(item.label)}:* ${escapeSlackMrkdwnText(item.value)}`
5807
+ }))]
5808
+ });
5809
+ }
5810
+ return blocks.length ? blocks : void 0;
5811
+ }
5812
+ //#endregion
5813
+ //#region src/chat/slack/output.ts
5814
+ const MAX_INLINE_CHARS = 2200;
5815
+ const MAX_INLINE_LINES = 45;
5816
+ const CONTINUED_MARKER = "\n\n[Continued below]";
5817
+ function countSlackLines(text) {
5818
+ if (!text) return 0;
5819
+ return text.split("\n").length;
5820
+ }
5821
+ function fitsInlineBudget(text, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
5822
+ return text.length <= maxChars && countSlackLines(text) <= maxLines;
5823
+ }
5824
+ function findSplitIndex(text, maxChars) {
5825
+ if (text.length <= maxChars) return text.length;
5826
+ const bounded = text.slice(0, maxChars);
5827
+ const newlineIndex = bounded.lastIndexOf("\n");
5828
+ if (newlineIndex > 0) return newlineIndex;
5829
+ const spaceIndex = bounded.lastIndexOf(" ");
5830
+ if (spaceIndex > 0) return spaceIndex;
5831
+ return maxChars;
5832
+ }
5833
+ function splitByLineBudget(text, maxLines) {
5834
+ if (maxLines <= 0) return "";
5835
+ const lines = text.split("\n");
5836
+ if (lines.length <= maxLines) return text;
5837
+ return lines.slice(0, maxLines).join("\n");
5838
+ }
5839
+ function reserveInlineBudgetForSuffix(suffix, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
5840
+ return {
5841
+ maxChars: Math.max(1, maxChars - suffix.length),
5842
+ maxLines: Math.max(1, maxLines - Math.max(0, countSlackLines(suffix) - 1))
5843
+ };
5844
+ }
5845
+ function forceSplitBudget(text, budget) {
5846
+ const lineCount = countSlackLines(text);
5847
+ return {
5848
+ maxChars: text.length <= budget.maxChars ? Math.max(1, text.length - 1) : budget.maxChars,
5849
+ maxLines: lineCount <= budget.maxLines ? Math.max(1, lineCount - 1) : budget.maxLines
5850
+ };
5851
+ }
5852
+ function getFenceContinuation(text) {
5853
+ let open;
5854
+ for (const line of text.split("\n")) {
5855
+ const trimmed = line.trimStart();
5856
+ const openerMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
5857
+ if (!openerMatch) continue;
5858
+ if (!open) {
5859
+ open = {
5860
+ fence: openerMatch[1],
5861
+ openerLine: trimmed
5862
+ };
5863
+ continue;
5864
+ }
5865
+ if (new RegExp(`^${open.fence}\\s*$`).test(trimmed)) open = void 0;
5866
+ }
5867
+ if (!open) return null;
5868
+ return {
5869
+ closeSuffix: text.endsWith("\n") ? open.fence : `\n${open.fence}`,
5870
+ reopenPrefix: `${open.openerLine}\n`
5871
+ };
5872
+ }
5873
+ function appendSlackSuffix(text, marker) {
5874
+ return `${text}${getFenceContinuation(text)?.closeSuffix ?? ""}${marker}`;
5875
+ }
5876
+ function stripTrailingContinuationMarker(text) {
5877
+ return text.endsWith(CONTINUED_MARKER) ? text.slice(0, -19) : text;
5878
+ }
5879
+ function takeSlackContinuationChunk(text, budget) {
5880
+ let { prefix, rest } = takeSlackInlinePrefix(text, budget);
5881
+ if (!rest) ({prefix, rest} = takeSlackInlinePrefix(text, forceSplitBudget(text, budget)));
5882
+ let carryover = rest ? getFenceContinuation(prefix) : null;
5883
+ if (!carryover) return {
5884
+ prefix,
5885
+ rest
5886
+ };
5887
+ const carryoverBudget = reserveInlineBudgetForSuffix(`${carryover.closeSuffix}${CONTINUED_MARKER}`);
5888
+ ({prefix, rest} = takeSlackInlinePrefix(text, carryoverBudget));
5889
+ if (!rest) ({prefix, rest} = takeSlackInlinePrefix(text, forceSplitBudget(text, carryoverBudget)));
5890
+ carryover = rest ? getFenceContinuation(prefix) : null;
5891
+ if (!carryover) return {
5892
+ prefix,
5893
+ rest
5894
+ };
5895
+ return {
5896
+ prefix,
5897
+ rest: `${carryover.reopenPrefix}${rest}`
5898
+ };
5899
+ }
5900
+ /** Return the first continuation-safe chunk plus any remaining text. */
5901
+ function takeSlackContinuationPrefix(text, options) {
5902
+ const budget = {
5903
+ maxChars: options?.maxChars ?? getSlackContinuationBudget().maxChars,
5904
+ maxLines: options?.maxLines ?? getSlackContinuationBudget().maxLines
5905
+ };
5906
+ const { prefix, rest } = (() => {
5907
+ if (options?.forceSplit) return takeSlackContinuationChunk(text, budget);
5908
+ const initial = takeSlackInlinePrefix(text, budget);
5909
+ return initial.rest ? takeSlackContinuationChunk(text, budget) : initial;
5910
+ })();
5911
+ return {
5912
+ prefix,
5913
+ renderedPrefix: rest ? appendSlackSuffix(prefix, CONTINUED_MARKER) : prefix,
5914
+ rest
5915
+ };
5916
+ }
5917
+ /**
5918
+ * Take the largest Slack-safe inline prefix from `text` under the configured
5919
+ * character and line budgets. Returns the consumed prefix plus remaining text.
5920
+ */
5921
+ function takeSlackInlinePrefix(text, options) {
5922
+ const maxChars = options?.maxChars ?? MAX_INLINE_CHARS;
5923
+ const maxLines = options?.maxLines ?? MAX_INLINE_LINES;
5924
+ const normalized = text.replace(/\r\n?/g, "\n");
5925
+ if (!normalized) return {
5926
+ prefix: "",
5927
+ rest: ""
5928
+ };
5929
+ if (fitsInlineBudget(normalized, maxChars, maxLines)) return {
5930
+ prefix: normalized,
5931
+ rest: ""
5932
+ };
5933
+ const lineBounded = splitByLineBudget(normalized, maxLines);
5934
+ const cutIndex = findSplitIndex(lineBounded, maxChars);
5935
+ const prefix = lineBounded.slice(0, cutIndex).trimEnd();
5936
+ if (prefix) return {
5937
+ prefix,
5938
+ rest: normalized.slice(prefix.length).trimStart()
5939
+ };
5940
+ const hardPrefix = normalized.slice(0, Math.max(1, maxChars)).trimEnd();
5941
+ return {
5942
+ prefix: hardPrefix || normalized.slice(0, Math.max(1, maxChars)),
5943
+ rest: normalized.slice(hardPrefix.length || Math.max(1, maxChars)).trimStart()
5944
+ };
5945
+ }
5946
+ /**
5947
+ * Split a normalized Slack reply into multiple inline-safe thread messages.
5948
+ *
5949
+ * Replies split across more than two messages receive explicit continuation
5950
+ * markers on non-final chunks. When
5951
+ * `interrupted` is true, the final chunk receives an interruption marker.
5952
+ */
5953
+ function splitSlackReplyText(text, options) {
5954
+ const normalized = normalizeSlackReplyMarkdown(text);
5955
+ if (!normalized) return [];
5956
+ const chunks = [];
5957
+ const continuationBudget = reserveInlineBudgetForSuffix(CONTINUED_MARKER);
5958
+ let remaining = normalized;
5959
+ while (remaining) {
5960
+ if (options?.interrupted ? fitsInlineBudget(appendSlackSuffix(remaining, getInterruptionMarker())) : fitsInlineBudget(remaining)) {
5961
+ chunks.push(options?.interrupted ? appendSlackSuffix(remaining, getInterruptionMarker()) : remaining);
5962
+ break;
5963
+ }
5964
+ const { renderedPrefix, rest } = takeSlackContinuationPrefix(remaining, {
5965
+ ...continuationBudget,
5966
+ forceSplit: true
5967
+ });
5968
+ chunks.push(renderedPrefix);
5969
+ remaining = rest;
5970
+ }
5971
+ if (chunks.length === 2) chunks[0] = stripTrailingContinuationMarker(chunks[0] ?? "");
5972
+ return chunks;
5973
+ }
5974
+ /**
5975
+ * Reserve enough inline budget for a continuation suffix on the current chunk.
5976
+ */
5977
+ function getSlackContinuationBudget() {
5978
+ return reserveInlineBudgetForSuffix(CONTINUED_MARKER);
5979
+ }
5980
+ /** Normalize text for Slack and wrap it as a PostableMessage. */
5981
+ function buildSlackOutputMessage(text) {
5982
+ const normalized = normalizeSlackReplyMarkdown(text);
5983
+ if (!normalized) throw new Error(`Slack output normalized to empty content: original_length=${text.length} parsed_length=${normalized.length}`);
5984
+ return { markdown: normalized };
5985
+ }
5986
+ //#endregion
5987
+ //#region src/chat/slack/reply.ts
5988
+ /**
5989
+ * Send one destination-visible Slack reply.
5990
+ *
5991
+ * Chunks oversized text, builds compact attribution and conversation footer
5992
+ * context, and posts through the shared Slack outbound boundary.
5993
+ */
5994
+ async function sendSlackReply(args) {
5995
+ const posts = splitSlackReplyText(args.text).map((text) => ({
5996
+ text,
5997
+ cards: []
5998
+ }));
5999
+ const cards = (args.cards ?? []).map((card) => renderSlackCard(card, args.conversationId));
6000
+ for (let index = 0; index < cards.length; index += 5) {
6001
+ const group = cards.slice(index, index + 5);
6002
+ const lastPost = posts.at(-1);
6003
+ if (index === 0 && lastPost) lastPost.cards = group;
6004
+ else posts.push({
6005
+ text: "",
6006
+ cards: group
6007
+ });
6008
+ }
6009
+ const footer = buildSlackReplyFooter({
6010
+ conversationId: args.conversationId,
6011
+ replyAttribution: args.replyAttribution
6012
+ });
6013
+ const messageTs = [];
6014
+ let threadTs = args.threadTs;
6015
+ for (const [index, post] of posts.entries()) {
6016
+ const { text, cards } = post;
6017
+ const isFinalChunk = index === posts.length - 1;
6018
+ const blocks = buildSlackReplyBlocks(text, void 0) ?? [];
6019
+ for (const card of cards) if (!card.entity) blocks.push({
6020
+ type: "section",
6021
+ text: {
6022
+ type: "mrkdwn",
6023
+ text: card.text
6024
+ }
6025
+ });
6026
+ if (isFinalChunk || !text.trim()) blocks.push(...buildSlackReplyBlocks("", footer) ?? []);
6027
+ const entities = cards.flatMap((card) => card.entity ? [card.entity] : []);
6028
+ const accessibleText = [text, ...cards.map((card) => card.text)].filter(Boolean).join("\n\n");
6029
+ const fallbackText = isFinalChunk && args.replyAttribution ? `${accessibleText}\n\n${escapeSlackMrkdwnText(formatReplyAttribution(args.replyAttribution))}` : accessibleText;
6030
+ const response = await postSlackMessage({
6031
+ channelId: args.channelId,
6032
+ threadTs,
6033
+ text: fallbackText,
6034
+ ...blocks.length ? { blocks } : void 0,
6035
+ ...entities.length ? { entities } : void 0
6036
+ });
6037
+ if (response.ts) {
6038
+ messageTs.push(response.ts);
6039
+ threadTs ??= response.ts;
6040
+ }
6041
+ }
6042
+ return messageTs;
6043
+ }
6044
+ //#endregion
6045
+ //#region src/chat/slack/assistant-thread/status-render.ts
6046
+ const DEFAULT_STATUS_CONTEXTS = {
6047
+ thinking: "…",
6048
+ searching: "sources",
6049
+ reading: "task",
6050
+ reviewing: "results",
6051
+ drafting: "reply",
6052
+ running: "tasks"
6053
+ };
6054
+ function formatAssistantStatusText(verb, context) {
6055
+ const normalizedVerb = normalizeSlackStatusText(verb).trim().toLowerCase();
6056
+ const normalizedContext = normalizeSlackStatusText(context ?? "") || DEFAULT_STATUS_CONTEXTS[normalizedVerb] || "";
6057
+ if (!normalizedVerb) return truncateStatusText(normalizedContext || "Working");
6058
+ const displayVerb = `${normalizedVerb[0]?.toUpperCase() ?? ""}${normalizedVerb.slice(1)}`;
6059
+ return truncateStatusText(normalizedContext ? `${displayVerb} ${normalizedContext}` : displayVerb);
6060
+ }
6061
+ /** Build assistant progress text from a verb and optional context. */
6062
+ function makeAssistantStatus(verb, context) {
6063
+ return { text: formatAssistantStatusText(verb, context) };
6064
+ }
6065
+ /** Normalize a progress update into the visible Slack loading copy. */
6066
+ function renderAssistantStatus(args) {
6067
+ const visible = truncateStatusText(normalizeSlackStatusText(args.status.text));
6068
+ return {
6069
+ key: visible,
6070
+ visible
6071
+ };
6072
+ }
6073
+ /** Select and normalize the loading messages used for Slack status rotation. */
6074
+ function selectAssistantLoadingMessages(args) {
6075
+ const random = args.random ?? Math.random;
6076
+ const normalized = Array.from(new Set(args.messages.map((message) => truncateStatusText(normalizeSlackStatusText(message))).filter((message) => message.length > 0)));
6077
+ if (normalized.length === 0) return;
6078
+ const shuffled = [...normalized];
6079
+ for (let index = shuffled.length - 1; index > 0; index -= 1) {
6080
+ const otherIndex = Math.floor(random() * (index + 1));
6081
+ [shuffled[index], shuffled[otherIndex]] = [shuffled[otherIndex], shuffled[index]];
6082
+ }
6083
+ return shuffled.slice(0, 10);
6084
+ }
6085
+ //#endregion
6086
+ //#region src/chat/slack/assistant-thread/status-scheduler.ts
6087
+ const STATUS_UPDATE_DEBOUNCE_MS = 1e3;
6088
+ const STATUS_MIN_VISIBLE_MS = 1200;
6089
+ const STATUS_ROTATION_INTERVAL_MS = 3e4;
6090
+ /**
6091
+ * Pace assistant loading-state writes for a single turn.
6092
+ *
6093
+ * This layer owns only local scheduling policy: debounce, minimum visible
6094
+ * duration, refresh cadence, and write ordering. It deals in user-visible
6095
+ * progress copy; the transport decides how that maps onto Slack's `status`
6096
+ * versus `loading_messages` fields.
6097
+ */
6098
+ function createAssistantStatusScheduler(args) {
6099
+ const now = args.now ?? (() => Date.now());
6100
+ const setTimer = args.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
6101
+ const clearTimer = args.clearTimer ?? ((timer) => clearTimeout(timer));
6102
+ const random = args.random ?? Math.random;
6103
+ const loadingMessages = selectAssistantLoadingMessages({
6104
+ messages: args.loadingMessages ?? [],
6105
+ random
6106
+ });
6107
+ const defaultStatus = makeAssistantStatus("thinking");
6108
+ let active = false;
6109
+ let currentKey = "";
6110
+ let currentVisibleStatus = "";
6111
+ let currentLoadingMessages;
6112
+ let lastStatusAt = 0;
6113
+ let pendingStatus = null;
6114
+ let pendingKey = "";
6115
+ let pendingTimer = null;
6116
+ let rotationTimer = null;
6117
+ let inflightStatusUpdate = Promise.resolve();
6118
+ const enqueueStatusUpdate = (task) => {
6119
+ const request = inflightStatusUpdate.catch(() => void 0).then(async () => {
6120
+ await task();
6121
+ });
6122
+ inflightStatusUpdate = request.catch(() => void 0);
6123
+ return request;
6124
+ };
6125
+ const scheduleRotation = () => {
6126
+ if (rotationTimer) {
6127
+ clearTimer(rotationTimer);
6128
+ rotationTimer = null;
6129
+ }
6130
+ if (!active || !currentVisibleStatus) return;
6131
+ rotationTimer = setTimer(() => {
6132
+ rotationTimer = null;
6133
+ if (!active || !currentVisibleStatus) return;
6134
+ postStatus(currentVisibleStatus, currentLoadingMessages);
6135
+ }, STATUS_ROTATION_INTERVAL_MS);
6136
+ };
6137
+ const getLoadingMessagesForVisibleStatus = (visible) => visible ? [visible] : void 0;
6138
+ const getInitialStatusText = () => {
6139
+ if (loadingMessages?.length) return loadingMessages[0];
6140
+ return defaultStatus.text;
6141
+ };
6142
+ const haveSameLoadingMessages = (left, right) => {
6143
+ if (left === right) return true;
6144
+ if (!left || !right || left.length !== right.length) return false;
6145
+ return left.every((message, index) => message === right[index]);
6146
+ };
6147
+ const postStatus = async (text, nextLoadingMessages) => {
6148
+ if (!text && !currentVisibleStatus) return;
6149
+ currentVisibleStatus = text;
6150
+ currentLoadingMessages = nextLoadingMessages;
6151
+ lastStatusAt = now();
6152
+ scheduleRotation();
6153
+ await enqueueStatusUpdate(async () => {
6154
+ await args.sendStatus(text, nextLoadingMessages);
6155
+ });
6156
+ };
6157
+ const postRenderedStatus = async (status) => {
6158
+ const presentation = renderAssistantStatus({ status });
6159
+ const nextLoadingMessages = getLoadingMessagesForVisibleStatus(presentation.visible);
6160
+ currentKey = presentation.key;
6161
+ await postStatus(presentation.visible, nextLoadingMessages);
6162
+ };
6163
+ const clearPending = () => {
6164
+ if (pendingTimer) {
6165
+ clearTimer(pendingTimer);
6166
+ pendingTimer = null;
6167
+ }
6168
+ pendingStatus = null;
6169
+ pendingKey = "";
6170
+ };
6171
+ const flushPending = async () => {
6172
+ if (!active || !pendingStatus) {
6173
+ clearPending();
6174
+ return;
6175
+ }
6176
+ const next = pendingStatus;
6177
+ clearPending();
6178
+ if (renderAssistantStatus({ status: next }).key !== currentKey) await postRenderedStatus(next);
6179
+ };
6180
+ return {
6181
+ async clear() {
6182
+ active = false;
6183
+ clearPending();
6184
+ if (rotationTimer) {
6185
+ clearTimer(rotationTimer);
6186
+ rotationTimer = null;
6187
+ }
6188
+ currentKey = "";
6189
+ await postStatus("");
6190
+ },
6191
+ update(status) {
6192
+ if (!active) {
6193
+ active = true;
6194
+ clearPending();
6195
+ if (status) {
6196
+ postRenderedStatus(status);
6197
+ return;
6198
+ }
6199
+ currentKey = "initial";
6200
+ postStatus(getInitialStatusText(), loadingMessages);
6201
+ return;
6202
+ }
6203
+ if (!status) {
6204
+ clearPending();
6205
+ currentKey = "initial";
6206
+ postStatus(getInitialStatusText(), loadingMessages);
6207
+ return;
6208
+ }
6209
+ const presentation = renderAssistantStatus({ status });
6210
+ if (!presentation.visible) return;
6211
+ if (presentation.key === currentKey || presentation.key === pendingKey) return;
6212
+ if (presentation.visible === currentVisibleStatus) {
6213
+ clearPending();
6214
+ currentKey = presentation.key;
6215
+ const nextLoadingMessages = getLoadingMessagesForVisibleStatus(presentation.visible);
6216
+ if (!haveSameLoadingMessages(currentLoadingMessages, nextLoadingMessages)) postStatus(presentation.visible, nextLoadingMessages);
6217
+ return;
6218
+ }
6219
+ const elapsed = now() - lastStatusAt;
6220
+ const waitMs = Math.max(STATUS_UPDATE_DEBOUNCE_MS - elapsed, STATUS_MIN_VISIBLE_MS - elapsed, 0);
6221
+ if (waitMs <= 0) {
6222
+ clearPending();
6223
+ postRenderedStatus(status);
6224
+ return;
6225
+ }
6226
+ pendingStatus = status;
6227
+ pendingKey = presentation.key;
6228
+ if (pendingTimer) return;
6229
+ pendingTimer = setTimer(() => {
6230
+ pendingTimer = null;
6231
+ flushPending();
6232
+ }, Math.max(1, waitMs));
6233
+ }
6234
+ };
6235
+ }
6236
+ //#endregion
6237
+ //#region src/chat/slack/assistant-thread/status-send.ts
6238
+ /**
6239
+ * Slack's assistant loading UI accepts both `status` and `loading_messages`,
6240
+ * but product policy keeps `status` stable and generic. User-visible progress
6241
+ * copy belongs in `loading_messages`.
6242
+ */
6243
+ const SLACK_ASSISTANT_ACTIVE_STATUS = "is working on your request...";
6244
+ /**
6245
+ * Build a best-effort sender for Slack's assistant loading state.
6246
+ *
6247
+ * The `text` argument is internal progress copy. This transport maps it onto
6248
+ * Slack's loading surface and keeps the raw Slack `status` field fixed.
6249
+ */
6250
+ function createSlackAdapterStatusSender(args) {
6251
+ const adapter = args.getSlackAdapter();
6252
+ const boundToken = getSlackAdapterRequestToken(adapter);
6253
+ return async (text, loadingMessages) => {
6254
+ const channelId = args.channelId;
6255
+ const threadTs = args.threadTs;
6256
+ if (!channelId || !threadTs) return;
6257
+ const normalizedChannelId = normalizeSlackConversationId(channelId);
6258
+ if (!normalizedChannelId) return;
6259
+ const nextLoadingMessages = text ? loadingMessages ?? [text] : void 0;
6260
+ try {
6261
+ await runWithBoundSlackToken(adapter, boundToken, () => adapter.setAssistantStatus(normalizedChannelId, threadTs, text ? SLACK_ASSISTANT_ACTIVE_STATUS : "", nextLoadingMessages));
6262
+ } catch (error) {
6263
+ logAssistantStatusFailure({
6264
+ status: text,
6265
+ error,
6266
+ channelId,
6267
+ normalizedChannelId,
6268
+ threadTs
6269
+ });
6270
+ }
6271
+ };
6272
+ }
6273
+ /**
6274
+ * Build a best-effort sender for Slack's assistant loading state over raw Web
6275
+ * API calls. As with the adapter-backed path, the dynamic copy goes to
6276
+ * `loading_messages` while `status` stays fixed and generic.
6277
+ */
6278
+ function createSlackWebApiStatusSender(args) {
6279
+ const getClient = args.getSlackClient ?? getSlackClient;
6280
+ return async (text, loadingMessages) => {
6281
+ const channelId = args.channelId;
6282
+ const threadTs = args.threadTs;
6283
+ if (!channelId || !threadTs) return;
6284
+ const normalizedChannelId = normalizeSlackConversationId(channelId);
6285
+ if (!normalizedChannelId) return;
6286
+ const nextLoadingMessages = text ? loadingMessages ?? [text] : void 0;
6287
+ try {
6288
+ await getClient().assistant.threads.setStatus({
6289
+ channel_id: normalizedChannelId,
6290
+ thread_ts: threadTs,
6291
+ status: text ? SLACK_ASSISTANT_ACTIVE_STATUS : "",
6292
+ ...nextLoadingMessages ? { loading_messages: nextLoadingMessages } : void 0
6293
+ });
6294
+ } catch (error) {
6295
+ logAssistantStatusFailure({
6296
+ status: text,
6297
+ error,
6298
+ channelId,
6299
+ normalizedChannelId,
6300
+ threadTs
6301
+ });
6302
+ }
6303
+ };
6304
+ }
6305
+ function getSlackAdapterRequestToken(adapter) {
6306
+ const token = adapter.requestContext?.getStore()?.token;
6307
+ if (typeof token !== "string") return;
6308
+ return token.trim() || void 0;
6309
+ }
6310
+ async function runWithBoundSlackToken(adapter, token, task) {
6311
+ if (!token) return await task();
6312
+ return await adapter.withBotToken(token, task);
6313
+ }
6314
+ function logAssistantStatusFailure(args) {
6315
+ logWarn("assistant.status.update.failed", {
6316
+ "app.slack.status_text": args.status || "(clear)",
6317
+ "app.slack.channel_id_raw": args.channelId,
6318
+ "app.slack.channel_id": args.normalizedChannelId,
6319
+ "app.slack.thread_ts": args.threadTs,
6320
+ "exception.message": args.error instanceof Error ? args.error.message : String(args.error)
6321
+ });
6322
+ }
6323
+ //#endregion
6324
+ //#region src/chat/slack/assistant-thread/status.ts
6325
+ /**
6326
+ * Create a Slack adapter-backed session for Slack's assistant loading state.
6327
+ *
6328
+ * The session accepts internal progress updates and leaves it to the sender to
6329
+ * map them onto Slack's fixed generic `status` field plus dynamic
6330
+ * `loading_messages`.
6331
+ */
6332
+ function createSlackAdapterAssistantStatusSession(args) {
6333
+ return createAssistantStatusScheduler({
6334
+ sendStatus: createSlackAdapterStatusSender({
6335
+ channelId: args.channelId,
6336
+ threadTs: args.threadTs,
6337
+ getSlackAdapter: args.getSlackAdapter
6338
+ }),
6339
+ loadingMessages: args.loadingMessages ?? botConfig.loadingMessages,
6340
+ now: args.now,
6341
+ setTimer: args.setTimer,
6342
+ clearTimer: args.clearTimer,
6343
+ random: args.random
6344
+ });
6345
+ }
6346
+ /**
6347
+ * Create a Web API-backed session for Slack's assistant loading state in
6348
+ * resume/callback flows that do not use the adapter thread object.
6349
+ */
6350
+ function createSlackWebApiAssistantStatusSession(args) {
6351
+ return createAssistantStatusScheduler({
6352
+ sendStatus: createSlackWebApiStatusSender({
6353
+ channelId: args.channelId,
6354
+ threadTs: args.threadTs,
6355
+ getSlackClient: args.getSlackClient
6356
+ }),
6357
+ loadingMessages: args.loadingMessages ?? botConfig.loadingMessages,
6358
+ now: args.now,
6359
+ setTimer: args.setTimer,
6360
+ clearTimer: args.clearTimer,
6361
+ random: args.random
6362
+ });
6363
+ }
6364
+ //#endregion
5362
6365
  //#region src/chat/slack/assistant-thread/title.ts
5363
6366
  /** Set-once DM title projection for the current process. */
5364
6367
  const projectedAssistantTitles = /* @__PURE__ */ new Set();
@@ -5397,6 +6400,21 @@ async function maybeSyncAssistantTitle(args) {
5397
6400
  }
5398
6401
  }
5399
6402
  //#endregion
6403
+ //#region src/chat/services/auth-pause-response.ts
6404
+ const MAX_AUTH_REQUEST_LENGTH = 200;
6405
+ function formatAuthRequest(requestText) {
6406
+ const normalized = requestText.replace(/\s+/g, " ").trim();
6407
+ if (!normalized) return;
6408
+ return (normalized.length > MAX_AUTH_REQUEST_LENGTH ? `${normalized.slice(0, 199).trimEnd()}…` : normalized).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
6409
+ }
6410
+ /** Build the visible Slack thread note for an auth-paused turn. */
6411
+ function buildAuthPauseResponse(slackUserId, providerDisplayName, requestText) {
6412
+ const mention = slackUserId ? `<@${slackUserId}> ` : "";
6413
+ const request = requestText ? formatAuthRequest(requestText) : void 0;
6414
+ if (!request) return `${mention}I'll need you to authorize ${providerDisplayName}. I sent you a link.`;
6415
+ return `${mention}I need access to ${providerDisplayName} to continue.\n\n*Why:* ${request}\n\nI sent you a link.`;
6416
+ }
6417
+ //#endregion
5400
6418
  //#region src/chat/services/provider-default-config.ts
5401
6419
  const GITHUB_REPO_PART = String.raw`[A-Za-z0-9_.-]*[A-Za-z0-9_-]`;
5402
6420
  const GITHUB_REPO_RE = new RegExp(String.raw`^\s*(?:set|use)\s+(?:the\s+)?default\s+(?:github\s+)?repo(?:sitory)?\s+(?:to|as)\s+(${GITHUB_REPO_PART}/${GITHUB_REPO_PART})(?:\s+for\s+this\s+channel)?[.!?]?\s*$`, "i");
@@ -5937,10 +6955,10 @@ function createSlackTurn(deps) {
5937
6955
  messageId
5938
6956
  })));
5939
6957
  }
5940
- else if (channelId && thread.adapter.name === "slack") {
6958
+ else {
5941
6959
  slackMessageTs = await sendSlackReply({
5942
6960
  cards,
5943
- channelId,
6961
+ channelId: destination.channelId,
5944
6962
  conversationId,
5945
6963
  replyAttribution: options.execution?.dispatch?.replyAttribution,
5946
6964
  text,
@@ -5950,9 +6968,6 @@ function createSlackTurn(deps) {
5950
6968
  destination,
5951
6969
  messageId: threadTs ?? messageId
5952
6970
  })));
5953
- } else for (const part of splitSlackReplyText(text)) {
5954
- const postedMessageTs = (await thread.post(buildSlackOutputMessage(part))).id;
5955
- if (postedMessageTs) slackMessageTs.push(postedMessageTs);
5956
6971
  }
5957
6972
  } catch (error) {
5958
6973
  if (isRetryableSlackPostError(error)) throw new RetryableDeliveryError(error);
@@ -6849,6 +7864,506 @@ function createJuniorSlackAdapter(config) {
6849
7864
  return createSlackAdapter(config);
6850
7865
  }
6851
7866
  //#endregion
7867
+ //#region src/chat/providers/slack/resume.ts
7868
+ /**
7869
+ * Resume a Slack Turn from saved Run state.
7870
+ *
7871
+ * Resumed Turns load their saved Run while holding the Slack thread lock.
7872
+ * Status notices are best effort. Completed assistant replies and
7873
+ * authorization pause notices use `sendSlackReply` so the footer stays
7874
+ * consistent.
7875
+ */
7876
+ function resolveReplyTimeoutMs() {
7877
+ const raw = process.env.EVAL_AGENT_REPLY_TIMEOUT_MS?.trim();
7878
+ if (!raw) return;
7879
+ const parsed = Number.parseInt(raw, 10);
7880
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
7881
+ }
7882
+ async function postSlackMessageBestEffort(channelId, threadTs, text, conversationId, replyAttribution) {
7883
+ try {
7884
+ if (conversationId) {
7885
+ await sendSlackReply({
7886
+ channelId,
7887
+ conversationId,
7888
+ replyAttribution,
7889
+ text,
7890
+ threadTs
7891
+ });
7892
+ return;
7893
+ }
7894
+ await postSlackMessage({
7895
+ channelId,
7896
+ threadTs,
7897
+ text
7898
+ });
7899
+ } catch {}
7900
+ }
7901
+ /** Create a read-only configuration service from persisted values. */
7902
+ function createReadOnlyConfigService(rawValues) {
7903
+ const values = Object.fromEntries(Object.entries(rawValues).map(([key, value]) => [key, configValueSchema.parse(value)]));
7904
+ const entries = Object.entries(values).map(([key, value]) => ({
7905
+ key,
7906
+ value,
7907
+ scope: "location",
7908
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
7909
+ }));
7910
+ return {
7911
+ get: async (key) => entries.find((entry) => entry.key === key),
7912
+ set: async () => {
7913
+ throw new Error("Configuration is read-only for a resumed Run");
7914
+ },
7915
+ unset: async () => false,
7916
+ list: async ({ prefix } = {}) => entries.filter((entry) => !prefix || entry.key.startsWith(prefix)),
7917
+ resolve: async (key) => values[key],
7918
+ resolveValues: async ({ keys, prefix } = {}) => {
7919
+ const filtered = {};
7920
+ for (const [key, value] of Object.entries(values)) {
7921
+ if (prefix && !key.startsWith(prefix)) continue;
7922
+ if (keys && !keys.includes(key)) continue;
7923
+ filtered[key] = value;
7924
+ }
7925
+ return filtered;
7926
+ }
7927
+ };
7928
+ }
7929
+ function getResumeLogContext(args) {
7930
+ const actor = args.run?.actor;
7931
+ return {
7932
+ conversationId: args.conversationId,
7933
+ messageConversationId: args.conversationId,
7934
+ userId: isUserActor(actor) ? actor.userId : void 0,
7935
+ userName: isUserActor(actor) ? actor.userName : void 0,
7936
+ destinationName: args.channelId,
7937
+ assistantUserName: botConfig.userName,
7938
+ modelId: defaultModelId(botConfig)
7939
+ };
7940
+ }
7941
+ async function postResumeFailureReply(args) {
7942
+ await postSlackMessage({
7943
+ channelId: args.channelId,
7944
+ threadTs: args.threadTs,
7945
+ text: buildTurnErrorResponse(args.error, args.eventId)
7946
+ });
7947
+ }
7948
+ async function handleResumeFailure(args) {
7949
+ const turnLifecycle = getTurnLifecycle();
7950
+ const capturedEventId = logException(args.error, args.eventName);
7951
+ const eventId = requireTurnFailureEventId(capturedEventId, args.eventName);
7952
+ let failureStatePersistError;
7953
+ try {
7954
+ await args.resume.onFailure?.(args.error);
7955
+ } catch (persistError) {
7956
+ const persistEventId = logException(persistError, "slack.resume.failure_state_persist.failed", { "app.error.original_event_id": eventId });
7957
+ try {
7958
+ await turnLifecycle.fail({
7959
+ conversationId: args.resume.conversationId,
7960
+ turnId: args.resume.turnId,
7961
+ createdAtMs: Date.now(),
7962
+ failureCode: "persistence_failed",
7963
+ ...persistEventId ? { eventId: persistEventId } : void 0
7964
+ });
7965
+ } catch (lifecycleError) {
7966
+ logException(lifecycleError, "slack.resume.failure_lifecycle_persist.failed", { "app.error.original_event_id": eventId });
7967
+ }
7968
+ failureStatePersistError = persistError;
7969
+ }
7970
+ try {
7971
+ await postResumeFailureReply({
7972
+ channelId: args.resume.channelId,
7973
+ threadTs: args.resume.threadTs,
7974
+ eventId,
7975
+ error: args.error
7976
+ });
7977
+ } catch (deliveryError) {
7978
+ const deliveryEventId = logException(deliveryError, "slack.resume.failure_delivery.failed", { "app.error.original_event_id": eventId });
7979
+ try {
7980
+ await turnLifecycle.fail({
7981
+ conversationId: args.resume.conversationId,
7982
+ turnId: args.resume.turnId,
7983
+ createdAtMs: Date.now(),
7984
+ failureCode: "delivery_failed",
7985
+ ...deliveryEventId ? { eventId: deliveryEventId } : void 0
7986
+ });
7987
+ } catch (lifecycleError) {
7988
+ logException(lifecycleError, "slack.resume.failure_lifecycle_persist.failed", { "app.error.original_event_id": eventId });
7989
+ }
7990
+ throw deliveryError;
7991
+ }
7992
+ if (failureStatePersistError) throw failureStatePersistError;
7993
+ await turnLifecycle.fail({
7994
+ conversationId: args.resume.conversationId,
7995
+ turnId: args.resume.turnId,
7996
+ createdAtMs: Date.now(),
7997
+ failureCode: args.failureCode,
7998
+ eventId
7999
+ });
8000
+ }
8001
+ function buildResumedRun(args, statusSession, delivery) {
8002
+ const savedRun = args.run;
8003
+ if (!savedRun) throw new TypeError("Slack resume requires a saved Run");
8004
+ if (!savedRun.source) throw new TypeError("Slack resume requires a Run Source");
8005
+ const source = savedRun.source;
8006
+ if (savedRun.destination.platform !== "slack") throw new TypeError("Slack resume requires a Slack Destination");
8007
+ const requestDeadline = getTurnRequestDeadline();
8008
+ const threadId = args.conversationId;
8009
+ const persistedLocationConfiguration = savedRun.environment?.locationConfiguration ?? (savedRun.environment?.configuration ? createReadOnlyConfigService(savedRun.environment.configuration) : void 0);
8010
+ const priorOnEvent = savedRun.onEvent;
8011
+ return {
8012
+ ...savedRun,
8013
+ conversationId: args.conversationId,
8014
+ turnId: args.turnId,
8015
+ instruction: {
8016
+ ...savedRun.instruction ?? { text: args.messageText },
8017
+ text: args.messageText
8018
+ },
8019
+ source: source.kind === "slack" ? {
8020
+ ...source,
8021
+ channelId: args.channelId,
8022
+ ...args.threadTs ? { threadTs: args.threadTs } : void 0
8023
+ } : source,
8024
+ deadlineAtMs: savedRun.deadlineAtMs ?? requestDeadline?.deadlineAtMs,
8025
+ environment: {
8026
+ ...savedRun.environment,
8027
+ locationConfiguration: persistedLocationConfiguration
8028
+ },
8029
+ onEvent: async (event) => {
8030
+ if (event.type === "status") statusSession.update({ text: event.text });
8031
+ await priorOnEvent?.(event);
8032
+ },
8033
+ ...savedRun.dispatch?.outcomes?.length === 0 ? void 0 : { delivery },
8034
+ durability: {
8035
+ ...savedRun.durability,
8036
+ onSandboxRefChanged: async (sandboxRef) => {
8037
+ await persistThreadStateById(threadId, { sandboxRef });
8038
+ await savedRun.durability?.onSandboxRefChanged?.(sandboxRef);
8039
+ }
8040
+ }
8041
+ };
8042
+ }
8043
+ /**
8044
+ * Resume a paused Slack Turn.
8045
+ *
8046
+ * The caller must hold the conversation work lease. This function does not
8047
+ * acquire another execution lock. A started resume owns its completion work.
8048
+ * Returns false only when `beforeStart` proves the resume is stale before
8049
+ * generation begins.
8050
+ */
8051
+ async function resumeSlackTurn(args) {
8052
+ return withLogContext(getResumeLogContext(args), () => resumeSlackTurnInContext(args));
8053
+ }
8054
+ async function resumeSlackTurnInContext(args) {
8055
+ const status = createSlackWebApiAssistantStatusSession({
8056
+ channelId: args.channelId,
8057
+ threadTs: args.threadTs
8058
+ });
8059
+ let processingReaction;
8060
+ let deferredAuthInfo;
8061
+ let deferredPauseHandler;
8062
+ let deferredFailureHandler;
8063
+ let runResultHandled = false;
8064
+ let assistantMessageDelivered = false;
8065
+ let shouldScheduleCompletedPluginTasks = false;
8066
+ let postDeliveryCommitError;
8067
+ const turnLifecycle = getTurnLifecycle();
8068
+ let failureCode = "agent_run_failed";
8069
+ let runArgs = args;
8070
+ try {
8071
+ const preparedArgs = await args.beforeStart?.();
8072
+ if (preparedArgs === false) return false;
8073
+ if (preparedArgs) runArgs = {
8074
+ ...args,
8075
+ ...preparedArgs
8076
+ };
8077
+ setTags(getResumeLogContext(runArgs));
8078
+ const savedRun = runArgs.run;
8079
+ if (!savedRun) throw new Error("Resumed Turn requires a saved Run");
8080
+ const credentialContext = savedRun.credentialContext;
8081
+ if (!credentialContext) throw new Error("Resumed Run requires a credential context");
8082
+ const routingActor = savedRun.actor;
8083
+ let resumeActor;
8084
+ if ("type" in credentialContext.actor) {
8085
+ if (!isUserActor(routingActor) || credentialContext.actor.userId !== routingActor.userId) throw new Error("Resumed Run credential actor must match its Actor");
8086
+ resumeActor = routingActor;
8087
+ } else {
8088
+ if (routingActor && (routingActor.platform !== "system" || routingActor.name !== credentialContext.actor.name)) throw new Error("Resumed Run system credential actor must match its Actor");
8089
+ resumeActor = credentialContext.actor;
8090
+ }
8091
+ if (runArgs.messageTs) processingReaction = await startProcessingReactionForMessage({
8092
+ channelId: runArgs.channelId,
8093
+ timestamp: runArgs.messageTs
8094
+ });
8095
+ if (runArgs.initialText) await postSlackMessageBestEffort(runArgs.channelId, runArgs.threadTs, runArgs.initialText);
8096
+ status.update(runArgs.initialStatus);
8097
+ const conversationId = runArgs.conversationId;
8098
+ const visibility = (await getConversationStore().get({ conversationId }))?.visibility;
8099
+ const visibleConversationId = args.conversationId;
8100
+ const sessionId = runArgs.turnId;
8101
+ let deliveryConversation;
8102
+ let acceptedDeliveryId;
8103
+ /** Load the resumed conversation once for ordered delivery recording. */
8104
+ const getDeliveryConversation = async () => {
8105
+ if (deliveryConversation) {
8106
+ const userMessageId = getTurnUserMessage(deliveryConversation, sessionId)?.id;
8107
+ return {
8108
+ conversation: deliveryConversation,
8109
+ sessionId,
8110
+ ...userMessageId ? { userMessageId } : void 0
8111
+ };
8112
+ }
8113
+ const persistedState = await getPersistedThreadState(visibleConversationId);
8114
+ const conversation = coerceThreadConversationState(persistedState);
8115
+ await hydrateConversationMessages({
8116
+ conversation,
8117
+ conversationId
8118
+ });
8119
+ deliveryConversation = conversation;
8120
+ const userMessageId = getTurnUserMessage(conversation, sessionId)?.id;
8121
+ return {
8122
+ conversation,
8123
+ sessionId,
8124
+ ...userMessageId ? { userMessageId } : void 0
8125
+ };
8126
+ };
8127
+ /** Post and record one completed assistant message for the resumed turn. */
8128
+ const deliverAssistantMessage = async (reply) => {
8129
+ const message = typeof reply === "string" ? void 0 : reply;
8130
+ const text = typeof reply === "string" ? reply : getAssistantReplyText(reply);
8131
+ if (!text?.trim()) return;
8132
+ failureCode = "delivery_failed";
8133
+ const cards = await loadPendingMessageCards(conversationId);
8134
+ const deliveryState = await getDeliveryConversation();
8135
+ let slackMessageTs = [];
8136
+ try {
8137
+ const outcomes = runArgs.run?.dispatch?.outcomes;
8138
+ if (outcomes) for (const outcome of outcomes) slackMessageTs.push(...await sendSlackReply({
8139
+ cards,
8140
+ channelId: outcome.destination.channelId,
8141
+ conversationId: runArgs.conversationId,
8142
+ replyAttribution: runArgs.run?.dispatch?.replyAttribution,
8143
+ text,
8144
+ threadTs: outcome.destination.threadTs
8145
+ }));
8146
+ else slackMessageTs = await sendSlackReply({
8147
+ cards,
8148
+ channelId: runArgs.channelId,
8149
+ conversationId: runArgs.conversationId,
8150
+ replyAttribution: runArgs.run?.dispatch?.replyAttribution,
8151
+ text,
8152
+ threadTs: runArgs.threadTs
8153
+ });
8154
+ } catch (error) {
8155
+ if (isRetryableSlackPostError(error)) throw new RetryableDeliveryError(error);
8156
+ throw error;
8157
+ }
8158
+ const messageTs = slackMessageTs.at(-1);
8159
+ assistantMessageDelivered = true;
8160
+ acceptedDeliveryId = messageTs;
8161
+ const recordedMessageId = recordDeliveredAssistantMessage({
8162
+ cards,
8163
+ conversation: deliveryState.conversation,
8164
+ sessionId: deliveryState.sessionId,
8165
+ source: "slack",
8166
+ text,
8167
+ userMessageId: deliveryState.userMessageId
8168
+ });
8169
+ if (messageTs) markConversationMessage(deliveryState.conversation, recordedMessageId, { slackTs: messageTs });
8170
+ try {
8171
+ const destination = runArgs.run?.destination;
8172
+ const providerConversationIds = runArgs.threadTs ? [runArgs.threadTs] : slackMessageTs;
8173
+ await persistWithRetry(() => commitAcceptedReply({
8174
+ ...message ? { agentMessage: message } : void 0,
8175
+ conversation: deliveryState.conversation,
8176
+ conversationMessageId: recordedMessageId,
8177
+ conversationId,
8178
+ ...runArgs.run?.dispatch && destination?.platform === "slack" && providerConversationIds.length > 0 ? { providerConversationBindings: providerConversationIds.map((providerConversationId) => ({
8179
+ provider: "slack",
8180
+ providerDestinationId: destination.channelId,
8181
+ providerTenantId: destination.teamId,
8182
+ providerConversationId
8183
+ })) } : void 0
8184
+ }));
8185
+ } catch (error) {
8186
+ logException(error, "slack.resume.assistant_message_post_delivery_persist.failed");
8187
+ }
8188
+ const dispatchId = savedRun.dispatch?.id;
8189
+ if (messageTs && dispatchId) try {
8190
+ await persistWithRetry(() => recordTurnSummary({
8191
+ conversationId: runArgs.conversationId,
8192
+ destination: savedRun.destination,
8193
+ destinationVisibility: visibility,
8194
+ dispatchId,
8195
+ resultMessageId: messageTs,
8196
+ turnId: runArgs.turnId,
8197
+ sliceId: runArgs.sliceId ?? 1,
8198
+ source: savedRun.source,
8199
+ state: "running",
8200
+ surface: savedRun.surface ?? "slack"
8201
+ }));
8202
+ } catch (error) {
8203
+ logException(error, "agent.turn.delivery_receipt_persist.failed");
8204
+ }
8205
+ failureCode = "agent_run_failed";
8206
+ };
8207
+ const deliveryState = await getDeliveryConversation();
8208
+ assistantMessageDelivered = turnHasReply(deliveryState.conversation, sessionId);
8209
+ const run = buildResumedRun(runArgs, status, deliverAssistantMessage);
8210
+ if (runArgs.inputMessageIds?.length) await turnLifecycle.start({
8211
+ conversationId: runArgs.conversationId,
8212
+ turnId: runArgs.turnId,
8213
+ createdAtMs: Date.now(),
8214
+ inputMessageIds: [...new Set(runArgs.inputMessageIds)],
8215
+ surface: "slack"
8216
+ });
8217
+ const replyTimeoutMs = resolveReplyTimeoutMs();
8218
+ const outcome = await runArgs.executeTurn(run, async (result) => {
8219
+ const finalized = finalizeFailedTurnReplyWithEvent({
8220
+ reply: result,
8221
+ logException
8222
+ });
8223
+ const reply = finalized.reply;
8224
+ const dispatchErrorMessage = run.dispatch && reply.diagnostics.outcome !== "success" ? reply.diagnostics.errorMessage ?? `Agent turn ended with ${reply.diagnostics.outcome}.` : void 0;
8225
+ if (reply.diagnostics.outcome !== "success") await deliverAssistantMessage(reply.text);
8226
+ runResultHandled = true;
8227
+ await status.clear();
8228
+ failureCode = "persistence_failed";
8229
+ if (reply.piMessages?.length) await saveTurnCheckpoint({
8230
+ mode: "completed",
8231
+ conversationId: runArgs.conversationId,
8232
+ turnId: runArgs.turnId,
8233
+ messages: reply.piMessages,
8234
+ durationMs: reply.diagnostics.durationMs,
8235
+ usage: reply.diagnostics.usage,
8236
+ destination: run.destination,
8237
+ destinationVisibility: visibility,
8238
+ dispatchId: run.dispatch?.id,
8239
+ dispatchOutcome: reply.diagnostics.outcome === "success" ? "completed" : "failed",
8240
+ ...dispatchErrorMessage ? { errorMessage: dispatchErrorMessage } : void 0,
8241
+ ...acceptedDeliveryId ? { resultMessageId: acceptedDeliveryId } : void 0,
8242
+ source: run.source,
8243
+ actor: resumeActor,
8244
+ surface: run.surface ?? "slack",
8245
+ sliceId: runArgs.sliceId
8246
+ });
8247
+ else if (run.dispatch?.id) await recordTurnSummary({
8248
+ conversationId: runArgs.conversationId,
8249
+ destination: run.destination,
8250
+ destinationVisibility: visibility,
8251
+ dispatchId: run.dispatch?.id,
8252
+ dispatchOutcome: reply.diagnostics.outcome === "success" ? "completed" : "failed",
8253
+ ...acceptedDeliveryId ? { resultMessageId: acceptedDeliveryId } : void 0,
8254
+ turnId: runArgs.turnId,
8255
+ sliceId: runArgs.sliceId ?? 1,
8256
+ source: run.source,
8257
+ state: reply.diagnostics.outcome === "success" ? "completed" : "failed",
8258
+ surface: run.surface ?? "slack"
8259
+ });
8260
+ await runArgs.commitResult?.(reply);
8261
+ if (reply.diagnostics.outcome === "success") {
8262
+ shouldScheduleCompletedPluginTasks = true;
8263
+ return { outcome: assistantMessageDelivered ? "success" : "no_reply" };
8264
+ }
8265
+ return {
8266
+ outcome: "failed",
8267
+ failureCode: "model_execution_failed",
8268
+ ...finalized.eventId ? { eventId: finalized.eventId } : void 0,
8269
+ ...finalized.failureReason ? { failureReason: finalized.failureReason } : void 0
8270
+ };
8271
+ }, replyTimeoutMs);
8272
+ if (outcome.status !== "completed") {
8273
+ await status.clear();
8274
+ const onAuthPause = runArgs.onAuthPause;
8275
+ const onSuspend = runArgs.onSuspend;
8276
+ if (outcome.status === "awaiting_auth" && onAuthPause) {
8277
+ deferredAuthInfo = {
8278
+ providerDisplayName: outcome.providerDisplayName,
8279
+ actorId: isUserActor(resumeActor) ? resumeActor.userId : void 0,
8280
+ ...outcome.requestText ? { requestText: outcome.requestText } : void 0
8281
+ };
8282
+ deferredPauseHandler = async () => {
8283
+ await onAuthPause({ providerDisplayName: outcome.providerDisplayName });
8284
+ };
8285
+ } else if (outcome.status === "suspended") {
8286
+ if (outcome.reason === "timeout" || savedRun.durability?.shouldYield?.()) throw new CooperativeTurnYieldError();
8287
+ if (onSuspend) deferredPauseHandler = async () => {
8288
+ await onSuspend(outcome.resumeVersion);
8289
+ };
8290
+ else deferredFailureHandler = async () => {
8291
+ await handleResumeFailure({
8292
+ error: /* @__PURE__ */ new Error("Resumed Run ended suspended without onSuspend"),
8293
+ eventName: "slack.resume.turn.failed",
8294
+ failureCode: "agent_run_failed",
8295
+ resume: runArgs
8296
+ });
8297
+ };
8298
+ } else deferredFailureHandler = async () => {
8299
+ await handleResumeFailure({
8300
+ error: /* @__PURE__ */ new Error(`Resumed Run ended ${outcome.status} without its required callback`),
8301
+ eventName: "slack.resume.turn.failed",
8302
+ failureCode: "agent_run_failed",
8303
+ resume: runArgs
8304
+ });
8305
+ };
8306
+ }
8307
+ if (shouldScheduleCompletedPluginTasks) try {
8308
+ const params = {
8309
+ conversationId: runArgs.conversationId,
8310
+ sessionId: runArgs.turnId
8311
+ };
8312
+ await (runArgs.scheduleSessionCompletedPluginTasks ?? scheduleSessionCompletedPluginTasks)(params);
8313
+ } catch (scheduleError) {
8314
+ logException(scheduleError, "plugin.session.completed_task_schedule.failed");
8315
+ }
8316
+ } catch (error) {
8317
+ await status.clear();
8318
+ const runError = error instanceof AgentRunError ? error.cause : error;
8319
+ if (isCooperativeTurnYieldError(runError)) throw runError;
8320
+ if (runResultHandled) {
8321
+ postDeliveryCommitError = error;
8322
+ try {
8323
+ await runArgs.onPostDeliveryCommitFailure?.(error);
8324
+ } catch (terminalizeError) {
8325
+ logException(terminalizeError, "slack.resume.post_delivery_terminalize.failed");
8326
+ }
8327
+ } else deferredFailureHandler = async () => {
8328
+ await handleResumeFailure({
8329
+ error: runError,
8330
+ eventName: "slack.resume.turn.failed",
8331
+ failureCode,
8332
+ resume: runArgs
8333
+ });
8334
+ };
8335
+ } finally {
8336
+ if (runResultHandled) await processingReaction?.complete();
8337
+ else await processingReaction?.stop();
8338
+ }
8339
+ if (postDeliveryCommitError) {
8340
+ const eventId = logException(postDeliveryCommitError, "slack.resume.success_handler.failed");
8341
+ await turnLifecycle.fail({
8342
+ conversationId: runArgs.conversationId,
8343
+ turnId: runArgs.turnId,
8344
+ createdAtMs: Date.now(),
8345
+ failureCode: "persistence_failed",
8346
+ ...eventId ? { eventId } : void 0
8347
+ });
8348
+ throw postDeliveryCommitError;
8349
+ }
8350
+ if (deferredPauseHandler) try {
8351
+ await deferredPauseHandler();
8352
+ if (deferredAuthInfo) await postSlackMessageBestEffort(runArgs.channelId, runArgs.threadTs, buildAuthPauseResponse(deferredAuthInfo.actorId, deferredAuthInfo.providerDisplayName, deferredAuthInfo.requestText), runArgs.conversationId, runArgs.run?.dispatch?.replyAttribution);
8353
+ return true;
8354
+ } catch (pauseError) {
8355
+ await handleResumeFailure({
8356
+ error: pauseError,
8357
+ eventName: "slack.resume.pause_handler.failed",
8358
+ failureCode: "persistence_failed",
8359
+ resume: runArgs
8360
+ });
8361
+ return true;
8362
+ }
8363
+ if (deferredFailureHandler) await deferredFailureHandler();
8364
+ return true;
8365
+ }
8366
+ //#endregion
6852
8367
  //#region src/chat/task-execution/paused-turn.ts
6853
8368
  /**
6854
8369
  * Worker continue for paused turns under the conversation lease.
@@ -6960,7 +8475,7 @@ async function resolveSlackResumeUserActor(args) {
6960
8475
  *
6961
8476
  * Sources, in order:
6962
8477
  * 1. Actor saved on the Turn.
6963
- * 2. Actor and credentials supplied by dispatch or OAuth.
8478
+ * 2. Actor and credentials supplied by dispatch.
6964
8479
  * 3. Legacy Event or Slack Message data.
6965
8480
  *
6966
8481
  * TODO(dcramer): Remove the routing and Message Actor fallbacks after no
@@ -6989,7 +8504,7 @@ async function resolveResumeExecutionIdentity(args) {
6989
8504
  };
6990
8505
  }
6991
8506
  function isPausedTurn(summary) {
6992
- return summary.state === "paused" && (summary.resumeReason === "timeout" || summary.resumeReason === "yield" || summary.resumeReason === "retry");
8507
+ return summary.state === "paused" && (summary.resumeReason === "timeout" || summary.resumeReason === "yield" || summary.resumeReason === "retry" || summary.resumeReason === "auth");
6993
8508
  }
6994
8509
  async function failPausedTurn(args) {
6995
8510
  const failed = await failTurnRecord({
@@ -7037,8 +8552,6 @@ async function runPausedTurnInContext(payload, options, runOptions) {
7037
8552
  turnId: payload.turnId,
7038
8553
  channelId: thread?.channelId ?? destination.channelId,
7039
8554
  ...thread?.threadTs ? { threadTs: thread.threadTs } : void 0,
7040
- lockKey: payload.conversationId,
7041
- ownsConversationLease: true,
7042
8555
  executeTurn: async (run, saveResult, timeoutMs) => await executeTurn(options.agentRunner, run, saveResult, timeoutMs),
7043
8556
  scheduleSessionCompletedPluginTasks: options.scheduleSessionCompletedPluginTasks,
7044
8557
  beforeStart: async () => {
@@ -7048,7 +8561,7 @@ async function runPausedTurnInContext(payload, options, runOptions) {
7048
8561
  conversationId: payload.conversationId,
7049
8562
  turnId: payload.turnId
7050
8563
  })).record;
7051
- if (!turn || turn.state !== "paused" || turn.resumeReason !== "timeout" && turn.resumeReason !== "yield" && turn.resumeReason !== "retry" || turn.version !== payload.expectedVersion) return false;
8564
+ if (!turn || turn.state !== "paused" || turn.resumeReason !== "timeout" && turn.resumeReason !== "yield" && turn.resumeReason !== "retry" && turn.resumeReason !== "auth" || turn.version !== payload.expectedVersion) return false;
7052
8565
  const activeTurn = turn;
7053
8566
  const { dispatch, surface } = options.routingContext ?? {};
7054
8567
  const currentState = await getPersistedThreadState(payload.conversationId);
@@ -7068,7 +8581,22 @@ async function runPausedTurnInContext(payload, options, runOptions) {
7068
8581
  });
7069
8582
  return false;
7070
8583
  }
7071
- if (conversation.processing.activeTurnId !== payload.turnId) return false;
8584
+ const authorization = turn.resumeReason === "auth" ? await getTurnAuthorization(turn) : void 0;
8585
+ if (turn.resumeReason === "auth") {
8586
+ if (!authorization) return false;
8587
+ const pendingAuth = conversation.processing.pendingAuth;
8588
+ if (pendingAuth && (pendingAuth.sessionId !== turn.turnId || pendingAuth.actorId !== authorization.actorId || pendingAuth.provider !== authorization.provider || pendingAuth.kind !== authorization.kind || pendingAuth.scope !== authorization.scope || pendingAuth.kind === "mcp" && pendingAuth.authSessionId !== authorization.authSessionId || !isPendingAuthLatestRequest(conversation, pendingAuth))) {
8589
+ await abandonTurnRecord({
8590
+ conversationId: payload.conversationId,
8591
+ turnId: turn.turnId,
8592
+ errorMessage: "Authorization no longer belongs to the latest request"
8593
+ });
8594
+ clearPendingAuth(conversation, turn.turnId);
8595
+ await persistThreadStateById(payload.conversationId, { conversation });
8596
+ return false;
8597
+ }
8598
+ if (!pendingAuth && conversation.processing.activeTurnId !== turn.turnId) return false;
8599
+ } else if (conversation.processing.activeTurnId !== payload.turnId) return false;
7072
8600
  const identity = await resolveResumeExecutionIdentity({
7073
8601
  actor: activeTurn.actor,
7074
8602
  conversationId: payload.conversationId,
@@ -7085,6 +8613,15 @@ async function runPausedTurnInContext(payload, options, runOptions) {
7085
8613
  return false;
7086
8614
  }
7087
8615
  const { actor, credentialContext } = identity;
8616
+ if (authorization && (!("userId" in actor) || actor.userId !== authorization.actorId)) throw new Error("Authorization actor does not match the paused Turn");
8617
+ if (authorization) {
8618
+ await recordAuthorizationCompleted({
8619
+ conversationId: payload.conversationId,
8620
+ ...authorization
8621
+ });
8622
+ conversation.processing.activeTurnId = turn.turnId;
8623
+ await persistThreadStateById(payload.conversationId, { conversation });
8624
+ }
7088
8625
  const locationConfiguration = getLocationConfigurationService(destination);
7089
8626
  const conversationContext = dispatchId ? void 0 : buildConversationContext(conversation, { excludeMessageId: userMessage.id });
7090
8627
  const sandboxRef = getPersistedSandboxState(currentState);
@@ -7126,8 +8663,11 @@ async function runPausedTurnInContext(payload, options, runOptions) {
7126
8663
  ...routing.location ? { location: routing.location } : void 0,
7127
8664
  source,
7128
8665
  ...surface ? { surface } : void 0,
7129
- toolChannelId: destination.channelId,
7130
- environment: { locationConfiguration },
8666
+ toolChannelId: authorization?.toolChannelId ?? destination.channelId,
8667
+ environment: {
8668
+ configuration: authorization?.configuration,
8669
+ locationConfiguration
8670
+ },
7131
8671
  state: {
7132
8672
  pendingAuth: conversation.processing.pendingAuth,
7133
8673
  sandboxRef
@@ -7278,16 +8818,10 @@ async function runNextPausedTurnInContext(conversationId, options, runOptions) {
7278
8818
  await persistThreadStateById(conversationId, { conversation });
7279
8819
  return false;
7280
8820
  }
7281
- const state = getStateAdapter();
7282
- await state.connect();
7283
- await withActiveLock(state, conversationId, async () => {
7284
- const record = await getTurnRecordForResume(conversationId, running.turnId);
7285
- if (!record || record.state !== "running") return;
7286
- await failStrandedTurnWithFallback({
7287
- conversationId,
7288
- errorMessage: "Turn lost its worker before reaching a safe boundary",
7289
- turn: record
7290
- });
8821
+ await failStrandedTurnWithFallback({
8822
+ conversationId,
8823
+ errorMessage: "Turn lost its worker before reaching a safe boundary",
8824
+ turn: running
7291
8825
  });
7292
8826
  return false;
7293
8827
  }
@@ -7298,6 +8832,7 @@ async function runNextPausedTurnInContext(conversationId, options, runOptions) {
7298
8832
  turnId: summary.turnId
7299
8833
  });
7300
8834
  if (!request) {
8835
+ if (summary.resumeReason === "auth") continue;
7301
8836
  await failPausedTurn({
7302
8837
  conversationId,
7303
8838
  summary,
@@ -10281,16 +11816,10 @@ async function createApp(options) {
10281
11816
  storage: attachmentStorage
10282
11817
  }));
10283
11818
  app.get("/api/oauth/callback/mcp/:provider", (c) => {
10284
- return GET$4(c.req.raw, c.req.param("provider"), waitUntil, {
10285
- agentRunner,
10286
- conversationWorkQueue: getVercelConversationWorkQueue()
10287
- });
11819
+ return GET$4(c.req.raw, c.req.param("provider"), waitUntil, { conversationWorkQueue: getVercelConversationWorkQueue() });
10288
11820
  });
10289
11821
  app.get("/api/oauth/callback/:provider", (c) => {
10290
- return GET$5(c.req.raw, c.req.param("provider"), waitUntil, {
10291
- agentRunner,
10292
- conversationWorkQueue: getVercelConversationWorkQueue()
10293
- });
11822
+ return GET$5(c.req.raw, c.req.param("provider"), waitUntil, { conversationWorkQueue: getVercelConversationWorkQueue() });
10294
11823
  });
10295
11824
  app.get("/api/internal/sandbox-egress/signals", (c) => {
10296
11825
  return GET(c.req.raw);