@sentry/junior 0.223.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.
- package/dist/{agent-hooks-DZU5nsd1.js → agent-hooks-DNLBwmsh.js} +6 -6
- package/dist/{agent-Bzbf92vv.js → agent-runner-9g6TQk9y.js} +809 -34
- package/dist/api.js +14 -14
- package/dist/app.js +1507 -59
- package/dist/{briefs-BJPfCrlT.js → briefs-CjXnGKzZ.js} +2 -2
- package/dist/{catalog-runtime-BthunUDD.js → catalog-runtime-CVZ9TEuK.js} +1 -1
- package/dist/{checkpoint-Deu-OVb3.js → checkpoint-Ceck_fLO.js} +7 -7
- package/dist/cli/chat.js +14 -15
- package/dist/cli/check.js +3 -3
- package/dist/cli/main.js +1 -1
- package/dist/cli/plugins.js +3 -3
- package/dist/cli/snapshot-create.js +3 -3
- package/dist/cli/upgrade.js +3 -3
- package/dist/{client-DjC6-6iU.js → client-B27ufhzl.js} +2 -2
- package/dist/{config-CtoUVs72.js → config-BAhl7-RC.js} +2 -2
- package/dist/{conversation-privacy-xS1t92xa.js → conversation-privacy-C-4JQzKD.js} +1 -1
- package/dist/credential-sync-BXHIxbtS.js +940 -0
- package/dist/{execution-stats-CrXs9vSH.js → execution-stats-DikPOxJ1.js} +3 -3
- package/dist/{executor-AOhG9X1t.js → executor-BG2Armby.js} +1 -1
- package/dist/instrumentation.js +2 -2
- package/dist/{job-queue-CNd9cpIy.js → job-queue-D4oAVrL9.js} +1 -1
- package/dist/{logging-DiN3o3nB.js → logging-iY0ssa2q.js} +1 -1
- package/dist/nitro.js +3 -3
- package/dist/{pending-auth-D7vmttn_.js → oauth-authorization-BIijVS2N.js} +6 -48
- package/dist/{oauth-callback-server-Ca0VLUEw.js → oauth-callback-server-DPkK3kzi.js} +4 -4
- package/dist/{profile-BsNrw8lj.js → profile-6KWhg_-U.js} +5 -9
- package/dist/{registry-Dankt-Pl.js → registry-Ctgdjt-W.js} +1 -1
- package/dist/{resolve-B38CpJe2.js → resolve-CB45ZukQ.js} +2 -2
- package/dist/{runner-Ck5rEjCm.js → runner-BwKCqoF3.js} +8 -9
- package/dist/{sandbox-egress-signals-BOfFkvly.js → sandbox-egress-signals-Cmy8ODat.js} +1 -1
- package/dist/{short-title-BHd_jUFX.js → short-title-xEkZwkcV.js} +2 -2
- package/dist/{skills-DbP4TDft.js → skills-c2bMDVRS.js} +2 -2
- package/dist/{spawn-DzXc_PKO.js → spawn-Cg6lyjE7.js} +2 -2
- package/dist/{stats-pNGp3G5q.js → stats-DP9zl3Jg.js} +6 -6
- package/dist/{task-queue-Cy9y1foT.js → task-queue-SL4uLfBB.js} +3 -3
- package/dist/{task-runner-DI1FBJFJ.js → task-runner-fntmnqzx.js} +8 -83
- package/dist/{thread-state-DXgxOYpZ.js → thread-state-Wfr0GdKS.js} +5 -5
- package/dist/{auth-pause-state-Bn2vX4PA.js → turn-failure-response-DLjSEm-R.js} +73 -72
- package/dist/{turn-lifecycle-Bjf6y2GO.js → turn-lifecycle-5_jvLqRz.js} +1 -1
- package/dist/turn-session-routing-CQaysLoe.js +77 -0
- package/dist/{validation-D5CIslEu.js → validation-CnZkCN7o.js} +1 -1
- package/dist/{version-_ZQFlslB.js → version-C6lA-h_o.js} +1 -1
- package/dist/version.js +1 -1
- package/dist/{web-authorization-6mmfS_o-.js → web-authorization-Bbx999_q.js} +1 -1
- package/dist/{web-input-BJpWm6go.js → web-input-BoE1xE-K.js} +7 -7
- package/dist/{work-DvIlJR3C.js → work-4Nhz9mVv.js} +52 -10
- package/dist/{worker-D1qidg-Y.js → worker-CdWPsNL2.js} +5 -5
- package/package.json +4 -4
- package/dist/agent-runner-2VQ9WhkG.js +0 -679
- package/dist/credential-sync-CKdXEUDO.js +0 -2811
- package/dist/experimental-CIXf7Uye.js +0 -46
- package/dist/turn-execution-BNOXrZ-M.js +0 -47
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { C as
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-B4FMCO8f.js";
|
|
2
|
+
import { C as resolveGatewayCredential, S as getGatewayApiKey, _ as findProviderError, b as isProviderRetryError, c as COMPACTION_SUMMARY_PREFIX, d as isCompactionSummaryText, f as renderCurrentInstruction, g as createProviderError, h as ProviderError, i as completeText, l as MODEL_HANDOFF_SUMMARY_PREFIX, m as escapeXml, n as GEN_AI_SERVER_ADDRESS, o as resolveGatewayModel, p as unwrapCurrentInstruction, r as completeObject, s as ACTIVE_TURN_COMPACTION_SUMMARY_PREFIX, t as GEN_AI_PROVIDER_NAME, u as hasCompactedConversationContext, v as getProviderErrorAttributes, x as MISSING_GATEWAY_CREDENTIALS_ERROR } from "./client-B27ufhzl.js";
|
|
3
|
+
import { C as withSpan, F as isRecord$2, P as normalizeIdentityEmail, S as withLogContext, _ as setSpanAttributes, a as extractGenAiUsageSummary, b as summarizeMessageText, c as getTracePropagationHeaders, d as logInfo, f as logWarn, h as serializeGenAiAttribute, i as extractGenAiUsageAttributes, l as logError, p as normalizeGenAiFinishReason, s as getLogContextAttributesForSpan, u as logException, v as setSpanStatus, y as setTags } from "./logging-iY0ssa2q.js";
|
|
3
4
|
import { m as startInactiveSpan, v as withActiveSpan } from "./sentry-CgWZM_kV.js";
|
|
4
|
-
import { a as toCanonicalInputMessage, c as toGenAiPayloadMetadata, i as runWithConversationPrivacy, l as toGenAiPayloadTraceAttributes, o as toCanonicalOutputMessage, r as resolveConversationPrivacy, s as toGenAiMessagesTraceAttributes, t as canExposeConversationPayload } from "./conversation-privacy-
|
|
5
|
-
import { h as normalizeSlackEmojiName, n as botConfig } from "./config-
|
|
5
|
+
import { a as toCanonicalInputMessage, c as toGenAiPayloadMetadata, i as runWithConversationPrivacy, l as toGenAiPayloadTraceAttributes, o as toCanonicalOutputMessage, r as resolveConversationPrivacy, s as toGenAiMessagesTraceAttributes, t as canExposeConversationPayload } from "./conversation-privacy-C-4JQzKD.js";
|
|
6
|
+
import { h as normalizeSlackEmojiName, n as botConfig } from "./config-BAhl7-RC.js";
|
|
6
7
|
import { a as modelIdForProfile, c as TURN_REASONING_LEVELS, i as formatModelProfiles, o as modelProfileSchema, r as formatModelProfile, s as profileConfig, t as ModelProfileNotConfiguredError } from "./model-profile-CZmVoGdX.js";
|
|
7
8
|
import { _ as parseSlackMessageTs, g as parseSlackUserId, h as parseSlackTeamId, i as credentialUserSubjectId, l as isUserActor, m as parseSlackChannelReferenceId, n as credentialContextSchema, p as parseSlackChannelId, t as credentialContextForActor } from "./context-Bplcgypl.js";
|
|
8
|
-
import { A as tryWorkspaceRepoCheckoutPath, B as downloadPrivateSlackFile, C as invalidateReadySnapshot, D as SANDBOX_RUNTIME, E as setWorkspaceSnapshotBuild, Ft as saveObjectAnnotations, Gt as getConversationMessageSearchStore, It as createPluginAnnotations, J as withSlackRetries, Jt as getSqlExecutor, K as normalizeSlackConversationId, Kt as getConversationStore, M as resolveChannelCapabilities, O as deleteWorkspaceSnapshotBuilders, Q as readActorIdentity, S as WorkspaceValidationError, T as setWorkspaceSnapshot, U as getSlackClient, V as getFilePermalink, W as isConversationScopedChannel, Wt as getConversationEventStore, _ as deleteWorkspace, at as STOP_WATCHING_TOOL_NAME, b as listWorkspaces, bt as createPluginLogger, c as getPluginTools, dt as findMatchingWatches, fn as parseDestination, ft as listWatches, g as createWorkspace, ht as getDashboardTaskLink, it as RESOURCE_WATCH_TOOL_SOURCE, j as workspaceRepoCheckoutPath, k as getWorkspaceSnapshotBuilder, l as getPluginUserPromptContributions, lt as createWatch, mt as getDashboardConversationLink, nn as addAgentTurnUsage, nt as RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS, on as hasAgentTurnUsage, ot as cancelSubscriptions, pt as JUNIOR_THREAD_STATE_TTL_MS, qt as getDb, r as createPluginHookRunner, s as getPluginSystemPromptContributions, st as cancelWatch, tt as getSlackToolContext, u as getPlugins, ut as deliverWatch, v as getWorkspace, w as loadSnapshotsForProfile, x as updateWorkspace, y as getWorkspaceByName, z as SlackActionError } from "./agent-hooks-
|
|
9
|
+
import { A as tryWorkspaceRepoCheckoutPath, B as downloadPrivateSlackFile, C as invalidateReadySnapshot, D as SANDBOX_RUNTIME, E as setWorkspaceSnapshotBuild, Ft as saveObjectAnnotations, G as isDmChannel$1, Gt as getConversationMessageSearchStore, It as createPluginAnnotations, J as withSlackRetries, Jt as getSqlExecutor, K as normalizeSlackConversationId, Kt as getConversationStore, M as resolveChannelCapabilities, O as deleteWorkspaceSnapshotBuilders, Q as readActorIdentity, S as WorkspaceValidationError, T as setWorkspaceSnapshot, U as getSlackClient, V as getFilePermalink, W as isConversationScopedChannel, Wt as getConversationEventStore, _ as deleteWorkspace, at as STOP_WATCHING_TOOL_NAME, b as listWorkspaces, bt as createPluginLogger, c as getPluginTools, dt as findMatchingWatches, fn as parseDestination, ft as listWatches, g as createWorkspace, ht as getDashboardTaskLink, it as RESOURCE_WATCH_TOOL_SOURCE, j as workspaceRepoCheckoutPath, k as getWorkspaceSnapshotBuilder, l as getPluginUserPromptContributions, lt as createWatch, mt as getDashboardConversationLink, n as applyPluginFormatMarkdown, nn as addAgentTurnUsage, nt as RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS, on as hasAgentTurnUsage, ot as cancelSubscriptions, pt as JUNIOR_THREAD_STATE_TTL_MS, qt as getDb, r as createPluginHookRunner, s as getPluginSystemPromptContributions, st as cancelWatch, tt as getSlackToolContext, u as getPlugins, ut as deliverWatch, v as getWorkspace, w as loadSnapshotsForProfile, x as updateWorkspace, y as getWorkspaceByName, z as SlackActionError } from "./agent-hooks-DNLBwmsh.js";
|
|
9
10
|
import { n as juniorDestinations } from "./destinations-BeBiJeEM.js";
|
|
10
|
-
import { D as juniorIdentities, E as juniorConversations, a as juniorSchedulerRuns, d as AGENT_INVOCATION_MAILBOX_STATUSES, g as juniorConversationBriefs, k as juniorArtifacts, w as juniorAttachments, x as juniorConversationAnnotations } from "./executor-
|
|
11
|
+
import { D as juniorIdentities, E as juniorConversations, a as juniorSchedulerRuns, d as AGENT_INVOCATION_MAILBOX_STATUSES, g as juniorConversationBriefs, k as juniorArtifacts, w as juniorAttachments, x as juniorConversationAnnotations } from "./executor-BG2Armby.js";
|
|
11
12
|
import { F as briefLinkSchema, I as briefOutcomeStatusSchema, J as instructionActors, L as conversationBriefSchema, X as sameActorIdentity, Y as instructionProvenanceFor, j as messageCardRefSchema, q as contextProvenance, z as KNOWN_CONVERSATION_EVENT_TYPES } from "./conversation-1HUoZr2l.js";
|
|
12
13
|
import { a as listReferenceFiles, c as soulPathCandidates, l as worldPathCandidates } from "./package-discovery-7a9YqLhE.js";
|
|
13
|
-
import { a as resolveAuthTokenPlaceholder, c as fetchWithBoundedOAuthErrorBodies, d as CredentialUnavailableError, i as resolvePluginHeaderEnvRefs, l as getOAuthProviderErrorAttributes, o as resolvePluginCommandEnv, s as OAuthProviderError } from "./registry-
|
|
14
|
-
import { n as pluginCatalogRuntime } from "./catalog-runtime-
|
|
15
|
-
import { a as parseSkillInvocation, n as findSkillByName, r as loadSkillsByName, t as discoverSkills } from "./skills-
|
|
16
|
-
import {
|
|
17
|
-
import { n as generateShortTitle, r as resolveTaskTitle, t as fallbackShortTitle } from "./short-title-
|
|
18
|
-
import { A as
|
|
19
|
-
import { $ as workspaceSnapshotFinishedEvent, B as createEventAutomation, C as SCHEDULED_AUTOMATION_SYSTEM_ACTOR, D as eventAutomationMatchesDestination, E as eventAutomationListToolResultSchema, F as effectiveTaskOutcomes, Gt as makeStructuredToolOutput, I as moveTaskOutcomes, Jt as ToolInputError, L as resolveTaskOutcomes, M as requireEventAutomationSlackContext, N as requireSupportedEventAutomationTrigger, O as eventAutomationToolResult, P as writableEventAutomation, Q as WORKSPACE_SNAPSHOT_READY_EVENT, R as taskOutcomeInputSchema, T as compactEventAutomation, Ut as injectContinuationToolName, V as deleteEventAutomation, W as getEventAutomation, Wt as juniorToolOutputSchema, X as getEventCatalog, Y as saveEventAutomation, Z as WORKSPACE_SNAPSHOT_FAILED_EVENT, at as normalizeCatalogEventIdentifier, c as parseSlackThreadId, ct as registeredEventTypeSchema, et as workspaceSnapshotWatch, f as createScheduledAutomation, g as listScheduledAutomationsForTeam, i as readLiveAttachment, it as hasPluginEventCatalogEntries, j as registeredEventAutomationTriggerSchema, k as eventAutomationToolResultSchema, lt as registeredResourceTypeSchema, nt as eventNamespaceSchema, ot as pluginSupportsEvent, pt as appendAndEnqueueInboundMessage, q as listEventAutomationsForTeam, rt as hasEventCatalogEntries, s as storeAttachments, st as registeredEventMatchSchema, t as incrementStat, tt as eventGuidance, ut as requireSupportedEventMatch, v as readScheduledAutomation, y as saveScheduledAutomation } from "./stats-
|
|
20
|
-
import {
|
|
21
|
-
import { C as toolInvocationDestination, S as surfaceFromRun, _ as uploadFilesToConversation, b as assertRunConsistency, h as postSlackMessage, i as formatProviderLabel, m as getSlackMessagePermalink, o as resolveBaseUrl, p as addReactionToMessage, r as deliverOAuthAuthorization, s as startOAuthFlow, v as RetryableDeliveryError, x as isAgentRunFeatureDisabled, y as actorFromRun } from "./agent-runner-2VQ9WhkG.js";
|
|
14
|
+
import { a as resolveAuthTokenPlaceholder, c as fetchWithBoundedOAuthErrorBodies, d as CredentialUnavailableError, i as resolvePluginHeaderEnvRefs, l as getOAuthProviderErrorAttributes, o as resolvePluginCommandEnv, s as OAuthProviderError } from "./registry-Ctgdjt-W.js";
|
|
15
|
+
import { n as pluginCatalogRuntime } from "./catalog-runtime-CVZ9TEuK.js";
|
|
16
|
+
import { a as parseSkillInvocation, n as findSkillByName, r as loadSkillsByName, t as discoverSkills } from "./skills-c2bMDVRS.js";
|
|
17
|
+
import { C as isToolResultError, D as stripRuntimeTurnContext, E as retainRuntimeTurnContext, F as loadTurnRoute, G as recordToolExecutionStarted, H as recordGuardianActionReviewed, I as openConversationProjection, K as recordTurnRoute, L as recordAgentsInstructionsUpdated, M as loadConnectedMcpProviders, N as loadConversationProjection, O as trimTrailingAssistantMessages, R as recordAttachmentsDelivered, S as isContinuablePiBoundary, T as normalizeToolNameFromResult, U as recordMcpProviderConnected, V as recordAuthorizationRequested, _ as getTerminalAssistantMessages, d as TurnSliceLimitExceededError, dt as getAgentContextCompactionTriggerTokens, f as assertTurnToolCallLimit, ft as getAgentContextInputLimitTokens, g as getPiMessageRole, h as extractAssistantText, i as abandonTurnRecord, k as TURN_CONTEXT_TAG, m as isTurnExecutionLimitExceededError, mt as piMessageFromHistoryItem, n as loadTurnCheckpoint, pt as historyItemFromPiMessage, r as saveTurnCheckpoint, t as continuableMessages, ut as estimateTextTokens, v as getUserMessageInstructionText, w as isToolResultMessage, x as isAssistantMessage, y as hasRuntimeTurnContext } from "./checkpoint-Ceck_fLO.js";
|
|
18
|
+
import { n as generateShortTitle, r as resolveTaskTitle, t as fallbackShortTitle } from "./short-title-xEkZwkcV.js";
|
|
19
|
+
import { A as SANDBOX_DATA_ROOT, D as fenceLock, E as acquireActiveLock, F as sandboxSkillFile, I as sandboxSkillPathResolution, M as SANDBOX_SKILLS_ROOT, N as SANDBOX_WORKSPACE_ROOT, O as withLock, P as sandboxSkillDir, _ as workspaceSnapshotNotReadyUserMessage, a as isAbortError, b as buildNonInteractiveShellScript, c as isSandboxCommandStreamInterruptedError, d as isSnapshottingError, f as throwSandboxOperationError, g as isWorkspaceSnapshotNotReadyError, h as getWorkspaceSnapshotNotReadyError, i as ensureDockerDaemon, j as SANDBOX_REPOS_ROOT, k as sleep$1, l as isSandboxMissingError, m as WorkspaceSnapshotNotReadyError, n as hash, o as isAlreadyExistsError, p as wrapSandboxSetupError, r as isStale, s as isSandboxApiTransientError, t as create, u as isSandboxUnavailableError, v as getVercelSandboxCredentials, w as getStateAdapter, x as runNonInteractiveCommand, y as buildCommandScript } from "./profile-6KWhg_-U.js";
|
|
20
|
+
import { $ as workspaceSnapshotFinishedEvent, B as createEventAutomation, C as SCHEDULED_AUTOMATION_SYSTEM_ACTOR, D as eventAutomationMatchesDestination, E as eventAutomationListToolResultSchema, F as effectiveTaskOutcomes, Gt as makeStructuredToolOutput, I as moveTaskOutcomes, Jt as ToolInputError, L as resolveTaskOutcomes, M as requireEventAutomationSlackContext, N as requireSupportedEventAutomationTrigger, O as eventAutomationToolResult, P as writableEventAutomation, Q as WORKSPACE_SNAPSHOT_READY_EVENT, R as taskOutcomeInputSchema, T as compactEventAutomation, Ut as injectContinuationToolName, V as deleteEventAutomation, W as getEventAutomation, Wt as juniorToolOutputSchema, X as getEventCatalog, Y as saveEventAutomation, Z as WORKSPACE_SNAPSHOT_FAILED_EVENT, at as normalizeCatalogEventIdentifier, c as parseSlackThreadId, ct as registeredEventTypeSchema, et as workspaceSnapshotWatch, f as createScheduledAutomation, g as listScheduledAutomationsForTeam, i as readLiveAttachment, it as hasPluginEventCatalogEntries, j as registeredEventAutomationTriggerSchema, k as eventAutomationToolResultSchema, lt as registeredResourceTypeSchema, nt as eventNamespaceSchema, ot as pluginSupportsEvent, pt as appendAndEnqueueInboundMessage, q as listEventAutomationsForTeam, qt as buildDeterministicTurnId, rt as hasEventCatalogEntries, s as storeAttachments, st as registeredEventMatchSchema, t as incrementStat, tt as eventGuidance, ut as requireSupportedEventMatch, v as readScheduledAutomation, y as saveScheduledAutomation } from "./stats-DP9zl3Jg.js";
|
|
21
|
+
import { a as NO_REPLY_MARKER, c as CooperativeTurnYieldError, i as sanitizeAssistantText, l as TurnInputCommitLostError, n as decideReply, o as isNoReplyMarker, p as isTurnInputCommitLostError, r as getAssistantReplyText, t as authorizationId } from "./oauth-authorization-BIijVS2N.js";
|
|
22
22
|
import { r as EVENT_AUTHOR_ID } from "./prompt-90miy8VV.js";
|
|
23
|
-
import { a as createSandboxSession, c as postinstall, d as getSandboxResources, i as bindSandboxFileSystem, l as setSnapshotSpanAttributes, n as isMissingError, o as stopSession, r as resolve$1, s as dependencies } from "./resolve-
|
|
23
|
+
import { a as createSandboxSession, c as postinstall, d as getSandboxResources, i as bindSandboxFileSystem, l as setSnapshotSpanAttributes, n as isMissingError, o as stopSession, r as resolve$1, s as dependencies } from "./resolve-CB45ZukQ.js";
|
|
24
24
|
import { t as getVercelConversationWorkQueue } from "./vercel-queue-DIkVcfK4.js";
|
|
25
|
-
import { a as getTurnRequestDeadline, n as sendNextWorkspaceSnapshotJob, r as sendWorkspaceSnapshotJob } from "./job-queue-
|
|
25
|
+
import { a as getTurnRequestDeadline, n as sendNextWorkspaceSnapshotJob, r as sendWorkspaceSnapshotJob } from "./job-queue-D4oAVrL9.js";
|
|
26
26
|
import { n as privateTraceResultAttributes } from "./private-trace-result-BLvBjy_e.js";
|
|
27
27
|
import { streamSimple } from "@earendil-works/pi-ai/compat";
|
|
28
28
|
import { createGatewayProvider } from "@ai-sdk/gateway";
|
|
@@ -31,7 +31,7 @@ import path from "node:path";
|
|
|
31
31
|
import { isDeepStrictEqual } from "node:util";
|
|
32
32
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
33
33
|
import { z } from "zod";
|
|
34
|
-
import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
|
|
34
|
+
import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
35
35
|
import { and, desc, eq, gt, gte, inArray, isNull, lt, ne, notExists, or, sql } from "drizzle-orm";
|
|
36
36
|
import { EgressAuthRequired, EgressPolicyDenied, PluginToolInputError, actorSchema, destinationSchema, eventSchema, missingToolAnnotationKeys, ownedObjectAnnotationSchema, pluginAuthorizationSchema, pluginCredentialHeaderTransformSchema, pluginCredentialResultSchema, pluginGrantSchema, pluginProviderAccountSchema, pluginStoredTokensSchema, sourceSchema, stableEventMatchKey, watchResultSchema } from "@sentry/junior-plugin-api";
|
|
37
37
|
import { alias } from "drizzle-orm/pg-core";
|
|
@@ -1140,6 +1140,73 @@ var AuthorizationFlowDisabledError = class extends Error {
|
|
|
1140
1140
|
}
|
|
1141
1141
|
};
|
|
1142
1142
|
//#endregion
|
|
1143
|
+
//#region src/chat/agent/types.ts
|
|
1144
|
+
/** Resume the agent turn after a transient or ambiguous delivery failure. */
|
|
1145
|
+
var RetryableDeliveryError = class extends Error {
|
|
1146
|
+
constructor(cause) {
|
|
1147
|
+
super("Assistant delivery was transient or ambiguous", { cause });
|
|
1148
|
+
this.name = "RetryableDeliveryError";
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
/** Return whether one optional agent capability is disabled for this run. */
|
|
1152
|
+
function isAgentRunFeatureDisabled(disabledFeatures, feature) {
|
|
1153
|
+
return disabledFeatures?.includes(feature) ?? false;
|
|
1154
|
+
}
|
|
1155
|
+
/** Resolve the explicit actor, dispatch actor, or credential actor for this run. */
|
|
1156
|
+
function actorFromRun(run) {
|
|
1157
|
+
if (run.dispatch?.actor) return run.dispatch.actor;
|
|
1158
|
+
if (run.actor) return run.actor;
|
|
1159
|
+
if (run.credentialContext && !("type" in run.credentialContext.actor)) return run.credentialContext.actor;
|
|
1160
|
+
}
|
|
1161
|
+
/** Reject contradictory provider coordinates before the run touches state. */
|
|
1162
|
+
function assertRunConsistency(run) {
|
|
1163
|
+
const { destination, source } = run;
|
|
1164
|
+
switch (source.kind) {
|
|
1165
|
+
case "slack":
|
|
1166
|
+
if (destination.platform !== "slack") throw new TypeError("Run source and destination platforms do not match");
|
|
1167
|
+
if (source.teamId !== destination.teamId) throw new TypeError("Slack source and destination teams do not match");
|
|
1168
|
+
break;
|
|
1169
|
+
case "local":
|
|
1170
|
+
if (destination.platform !== "local") throw new TypeError("Run source and destination platforms do not match");
|
|
1171
|
+
if (source.conversationId !== destination.conversationId) throw new TypeError("Source and destination conversation IDs do not match");
|
|
1172
|
+
if (run.surface !== "internal" && destination.conversationId !== run.conversationId) throw new TypeError("Source, destination, and run conversation IDs do not match");
|
|
1173
|
+
break;
|
|
1174
|
+
case "web":
|
|
1175
|
+
if (run.surface !== "internal" && source.conversationId !== run.conversationId) throw new TypeError("Web source and run conversation IDs do not match");
|
|
1176
|
+
switch (destination.platform) {
|
|
1177
|
+
case "local":
|
|
1178
|
+
if (source.conversationId !== destination.conversationId) throw new TypeError("Source and destination conversation IDs do not match");
|
|
1179
|
+
if (run.surface !== "internal" && destination.conversationId !== run.conversationId) throw new TypeError("Source, destination, and run conversation IDs do not match");
|
|
1180
|
+
}
|
|
1181
|
+
break;
|
|
1182
|
+
case "event":
|
|
1183
|
+
case "scheduled_automation":
|
|
1184
|
+
case "event_automation":
|
|
1185
|
+
case "plugin_dispatch":
|
|
1186
|
+
case "agent_invocation": return;
|
|
1187
|
+
}
|
|
1188
|
+
const actor = run.dispatch?.actor ?? run.actor;
|
|
1189
|
+
if (!actor || actor.platform === "system") return;
|
|
1190
|
+
if (actor.platform !== source.kind) throw new TypeError(`Actor platform "${actor.platform}" does not match Source kind "${source.kind}"`);
|
|
1191
|
+
if (actor.platform === "slack" && source.kind === "slack" && actor.teamId !== source.teamId) throw new TypeError("Slack Actor team does not match Source team");
|
|
1192
|
+
}
|
|
1193
|
+
/** Route tool side effects to the tool channel when one overrides the destination. */
|
|
1194
|
+
function toolInvocationDestination(run) {
|
|
1195
|
+
if (run.destination.platform !== "slack" || !run.toolChannelId) return run.destination;
|
|
1196
|
+
return {
|
|
1197
|
+
platform: "slack",
|
|
1198
|
+
teamId: run.destination.teamId,
|
|
1199
|
+
channelId: run.toolChannelId
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
/** Infer the run surface when the caller did not state one. */
|
|
1203
|
+
function surfaceFromRun(run) {
|
|
1204
|
+
if (run.surface) return run.surface;
|
|
1205
|
+
if (run.source.kind === "slack") return "slack";
|
|
1206
|
+
if (run.source.kind === "web") return "api";
|
|
1207
|
+
return "internal";
|
|
1208
|
+
}
|
|
1209
|
+
//#endregion
|
|
1143
1210
|
//#region src/chat/tool-support/action-review-history.ts
|
|
1144
1211
|
const MAX_VISIBLE_HISTORY_CHARS = 12e3;
|
|
1145
1212
|
const priorRejectionSchema = z.object({
|
|
@@ -2524,6 +2591,570 @@ function formatSandboxCommandResult(params) {
|
|
|
2524
2591
|
});
|
|
2525
2592
|
}
|
|
2526
2593
|
//#endregion
|
|
2594
|
+
//#region src/chat/slack/outbound.ts
|
|
2595
|
+
const MAX_SLACK_MESSAGE_TEXT_CHARS = 4e4;
|
|
2596
|
+
function requireSlackConversationId(channelId, action) {
|
|
2597
|
+
const normalized = normalizeSlackConversationId(channelId);
|
|
2598
|
+
if (!normalized) throw new Error(`${action} requires a valid channel ID`);
|
|
2599
|
+
return normalized;
|
|
2600
|
+
}
|
|
2601
|
+
function requireSlackThreadTimestamp(threadTs, action) {
|
|
2602
|
+
const normalized = threadTs.trim();
|
|
2603
|
+
if (!normalized) throw new Error(`${action} requires a thread timestamp`);
|
|
2604
|
+
return normalized;
|
|
2605
|
+
}
|
|
2606
|
+
function requireSlackMessageTimestamp(timestamp, action) {
|
|
2607
|
+
const normalized = parseSlackMessageTs(timestamp);
|
|
2608
|
+
if (!normalized) throw new Error(`${action} requires a target message timestamp`);
|
|
2609
|
+
return normalized;
|
|
2610
|
+
}
|
|
2611
|
+
function requireSlackMessageText(text, action) {
|
|
2612
|
+
if (text.trim().length === 0) throw new Error(`${action} requires non-empty text`);
|
|
2613
|
+
if (text.length > MAX_SLACK_MESSAGE_TEXT_CHARS) throw new Error(`${action} text exceeds Slack's 40000 character truncation limit`);
|
|
2614
|
+
return text;
|
|
2615
|
+
}
|
|
2616
|
+
/** Resolve a Slack message permalink without making read-only workflows fail on lookup errors. */
|
|
2617
|
+
async function getSlackMessagePermalink(args) {
|
|
2618
|
+
try {
|
|
2619
|
+
return (await withSlackRetries(() => getSlackClient().chat.getPermalink({
|
|
2620
|
+
channel: args.channelId,
|
|
2621
|
+
message_ts: args.messageTs
|
|
2622
|
+
}), 3, {
|
|
2623
|
+
action: "chat.getPermalink",
|
|
2624
|
+
idempotent: true,
|
|
2625
|
+
spanAttributes: {
|
|
2626
|
+
"app.slack.channel_id": args.channelId,
|
|
2627
|
+
"app.slack.message_ts": args.messageTs
|
|
2628
|
+
}
|
|
2629
|
+
})).permalink;
|
|
2630
|
+
} catch {
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
/** Post Slack `mrkdwn` text to a conversation or thread via the shared outbound boundary. */
|
|
2635
|
+
async function postSlackMessage(input) {
|
|
2636
|
+
const channelId = requireSlackConversationId(input.channelId, "Slack message posting");
|
|
2637
|
+
const text = requireSlackMessageText(input.text, "Slack message posting");
|
|
2638
|
+
const threadTs = input.threadTs ? requireSlackThreadTimestamp(input.threadTs, "Slack thread message posting") : void 0;
|
|
2639
|
+
const response = await withSlackRetries(() => getSlackClient().chat.postMessage({
|
|
2640
|
+
channel: channelId,
|
|
2641
|
+
text,
|
|
2642
|
+
unfurl_links: false,
|
|
2643
|
+
unfurl_media: false,
|
|
2644
|
+
...input.blocks?.length ? { blocks: input.blocks } : void 0,
|
|
2645
|
+
...input.entities?.length ? { metadata: { entities: input.entities } } : void 0,
|
|
2646
|
+
...threadTs ? { thread_ts: threadTs } : void 0
|
|
2647
|
+
}), 3, {
|
|
2648
|
+
action: "chat.postMessage",
|
|
2649
|
+
spanAttributes: {
|
|
2650
|
+
"app.slack.channel_id": channelId,
|
|
2651
|
+
...threadTs ? { "app.slack.thread_ts": threadTs } : void 0
|
|
2652
|
+
}
|
|
2653
|
+
});
|
|
2654
|
+
const messageTs = parseSlackMessageTs(response.ts);
|
|
2655
|
+
if (!messageTs) throw new Error("Slack message posted without ts");
|
|
2656
|
+
return {
|
|
2657
|
+
ts: messageTs,
|
|
2658
|
+
...input.includePermalink ? { permalink: await getSlackMessagePermalink({
|
|
2659
|
+
channelId,
|
|
2660
|
+
messageTs
|
|
2661
|
+
}) } : void 0
|
|
2662
|
+
};
|
|
2663
|
+
}
|
|
2664
|
+
/**
|
|
2665
|
+
* Post an ephemeral Slack message. Delivery is best-effort on Slack's side, but
|
|
2666
|
+
* request validation and Web API behavior are centralized here.
|
|
2667
|
+
*/
|
|
2668
|
+
async function postSlackEphemeralMessage(input) {
|
|
2669
|
+
const channelId = requireSlackConversationId(input.channelId, "Slack ephemeral message posting");
|
|
2670
|
+
const userId = parseSlackUserId(input.userId);
|
|
2671
|
+
if (!userId) throw new Error("Slack ephemeral message posting requires a user ID");
|
|
2672
|
+
const text = requireSlackMessageText(input.text, "Slack ephemeral message posting");
|
|
2673
|
+
const threadTs = input.threadTs ? requireSlackThreadTimestamp(input.threadTs, "Slack ephemeral thread message posting") : void 0;
|
|
2674
|
+
return { messageTs: (await withSlackRetries(() => getSlackClient().chat.postEphemeral({
|
|
2675
|
+
channel: channelId,
|
|
2676
|
+
user: userId,
|
|
2677
|
+
text,
|
|
2678
|
+
...input.blocks?.length ? { blocks: input.blocks } : void 0,
|
|
2679
|
+
...threadTs ? { thread_ts: threadTs } : void 0
|
|
2680
|
+
}), 3, {
|
|
2681
|
+
action: "chat.postEphemeral",
|
|
2682
|
+
spanAttributes: {
|
|
2683
|
+
"app.slack.channel_id": channelId,
|
|
2684
|
+
"app.slack.user_id": userId,
|
|
2685
|
+
...threadTs ? { "app.slack.thread_ts": threadTs } : void 0
|
|
2686
|
+
}
|
|
2687
|
+
})).message_ts };
|
|
2688
|
+
}
|
|
2689
|
+
/** Upload files into a Slack conversation or thread via the shared outbound file boundary. */
|
|
2690
|
+
async function uploadFilesToConversation(input) {
|
|
2691
|
+
const channelId = requireSlackConversationId(input.channelId, "Slack file upload");
|
|
2692
|
+
const threadTs = input.threadTs ? requireSlackThreadTimestamp(input.threadTs, "Slack file upload") : void 0;
|
|
2693
|
+
if (input.files.length === 0) throw new Error("Slack file upload requires at least one file");
|
|
2694
|
+
const fileUploads = input.files.map((file) => {
|
|
2695
|
+
const filename = file.filename.trim();
|
|
2696
|
+
if (!filename) throw new Error("Slack file upload requires every file to have a filename");
|
|
2697
|
+
return {
|
|
2698
|
+
file: file.data,
|
|
2699
|
+
filename
|
|
2700
|
+
};
|
|
2701
|
+
});
|
|
2702
|
+
return { files: ((await withSlackRetries(() => getSlackClient().filesUploadV2({
|
|
2703
|
+
channel_id: channelId,
|
|
2704
|
+
...threadTs ? { thread_ts: threadTs } : void 0,
|
|
2705
|
+
file_uploads: fileUploads
|
|
2706
|
+
}), 3, {
|
|
2707
|
+
action: "filesUploadV2",
|
|
2708
|
+
spanAttributes: {
|
|
2709
|
+
"app.slack.channel_id": channelId,
|
|
2710
|
+
...threadTs ? { "app.slack.thread_ts": threadTs } : void 0
|
|
2711
|
+
}
|
|
2712
|
+
})).files ?? []).flatMap((completion) => (completion.files ?? []).map((file) => file.id ? { id: file.id } : {})) };
|
|
2713
|
+
}
|
|
2714
|
+
/** Add a reaction to a Slack message, treating `already_reacted` as idempotent success. */
|
|
2715
|
+
async function addReactionToMessage(input) {
|
|
2716
|
+
const channelId = requireSlackConversationId(input.channelId, "Slack reaction");
|
|
2717
|
+
const timestamp = requireSlackMessageTimestamp(input.timestamp, "Slack reaction");
|
|
2718
|
+
const emoji = normalizeSlackEmojiName(input.emoji);
|
|
2719
|
+
if (!emoji) throw new Error("Slack reaction requires a valid emoji alias name");
|
|
2720
|
+
try {
|
|
2721
|
+
await withSlackRetries(() => getSlackClient().reactions.add({
|
|
2722
|
+
channel: channelId,
|
|
2723
|
+
timestamp,
|
|
2724
|
+
name: emoji
|
|
2725
|
+
}), 3, {
|
|
2726
|
+
action: "reactions.add",
|
|
2727
|
+
idempotent: true,
|
|
2728
|
+
spanAttributes: {
|
|
2729
|
+
"app.slack.channel_id": channelId,
|
|
2730
|
+
"app.slack.message_ts": timestamp,
|
|
2731
|
+
"app.slack.reaction": emoji
|
|
2732
|
+
}
|
|
2733
|
+
});
|
|
2734
|
+
} catch (error) {
|
|
2735
|
+
if (error instanceof SlackActionError && error.code === "already_reacted") return { ok: true };
|
|
2736
|
+
throw error;
|
|
2737
|
+
}
|
|
2738
|
+
return { ok: true };
|
|
2739
|
+
}
|
|
2740
|
+
/** Remove a reaction from a Slack message, treating `no_reaction` as idempotent success. */
|
|
2741
|
+
async function removeReactionFromMessage(input) {
|
|
2742
|
+
const channelId = requireSlackConversationId(input.channelId, "Slack reaction removal");
|
|
2743
|
+
const timestamp = requireSlackMessageTimestamp(input.timestamp, "Slack reaction removal");
|
|
2744
|
+
const emoji = normalizeSlackEmojiName(input.emoji);
|
|
2745
|
+
if (!emoji) throw new Error("Slack reaction removal requires a valid emoji alias name");
|
|
2746
|
+
try {
|
|
2747
|
+
await withSlackRetries(() => getSlackClient().reactions.remove({
|
|
2748
|
+
channel: channelId,
|
|
2749
|
+
timestamp,
|
|
2750
|
+
name: emoji
|
|
2751
|
+
}), 3, {
|
|
2752
|
+
action: "reactions.remove",
|
|
2753
|
+
idempotent: true,
|
|
2754
|
+
spanAttributes: {
|
|
2755
|
+
"app.slack.channel_id": channelId,
|
|
2756
|
+
"app.slack.message_ts": timestamp,
|
|
2757
|
+
"app.slack.reaction": emoji
|
|
2758
|
+
}
|
|
2759
|
+
});
|
|
2760
|
+
} catch (error) {
|
|
2761
|
+
if (error instanceof SlackActionError && error.code === "no_reaction") return { ok: true };
|
|
2762
|
+
throw error;
|
|
2763
|
+
}
|
|
2764
|
+
return { ok: true };
|
|
2765
|
+
}
|
|
2766
|
+
//#endregion
|
|
2767
|
+
//#region src/chat/slack/status-format.ts
|
|
2768
|
+
const SLACK_STATUS_MAX_LENGTH = 50;
|
|
2769
|
+
/** Truncate a status string to fit Slack's 50-char assistant status limit. */
|
|
2770
|
+
function truncateStatusText(text) {
|
|
2771
|
+
const trimmed = text.trim();
|
|
2772
|
+
if (!trimmed) return "";
|
|
2773
|
+
if (trimmed.length <= SLACK_STATUS_MAX_LENGTH) return trimmed;
|
|
2774
|
+
return `${trimmed.slice(0, 47).trimEnd()}...`;
|
|
2775
|
+
}
|
|
2776
|
+
//#endregion
|
|
2777
|
+
//#region src/chat/slack/mrkdwn.ts
|
|
2778
|
+
/** Escape dynamic text for Slack mrkdwn without changing intended formatting. */
|
|
2779
|
+
function escapeSlackMrkdwnText(text) {
|
|
2780
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
2781
|
+
}
|
|
2782
|
+
/** Escape a URL for Slack explicit link syntax while preserving query semantics. */
|
|
2783
|
+
function escapeSlackLinkUrl(url) {
|
|
2784
|
+
return url.replaceAll("&", "&").replaceAll("<", "%3C").replaceAll(">", "%3E");
|
|
2785
|
+
}
|
|
2786
|
+
/** Build a Slack explicit link from dynamic URL and label values. */
|
|
2787
|
+
function formatSlackLink(url, label) {
|
|
2788
|
+
return `<${escapeSlackLinkUrl(url)}|${escapeSlackMrkdwnText(label)}>`;
|
|
2789
|
+
}
|
|
2790
|
+
function readInlineCodeSpan(line, start) {
|
|
2791
|
+
if (line[start] !== "`") return;
|
|
2792
|
+
let n = 1;
|
|
2793
|
+
while (line[start + n] === "`") n++;
|
|
2794
|
+
const marker = "`".repeat(n);
|
|
2795
|
+
let search = start + n;
|
|
2796
|
+
while (search < line.length) {
|
|
2797
|
+
const close = line.indexOf(marker, search);
|
|
2798
|
+
if (close === -1) return;
|
|
2799
|
+
const after = close + n;
|
|
2800
|
+
if (line[after] !== "`") return {
|
|
2801
|
+
text: line.slice(start, after),
|
|
2802
|
+
end: after
|
|
2803
|
+
};
|
|
2804
|
+
search = after + 1;
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
function readExistingSlackAngleToken(line, start) {
|
|
2808
|
+
if (line[start] !== "<") return;
|
|
2809
|
+
const close = line.indexOf(">", start + 1);
|
|
2810
|
+
if (close === -1) return;
|
|
2811
|
+
const body = line.slice(start + 1, close);
|
|
2812
|
+
if (/^(?:https?:\/\/|@|#|!)/.test(body)) return {
|
|
2813
|
+
text: line.slice(start, close + 1),
|
|
2814
|
+
end: close + 1
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
function readMarkdownLink(line, start) {
|
|
2818
|
+
if (line[start] !== "[") return;
|
|
2819
|
+
const labelEnd = line.indexOf("](", start + 1);
|
|
2820
|
+
if (labelEnd === -1) return;
|
|
2821
|
+
const destStart = labelEnd + 2;
|
|
2822
|
+
if (!line.startsWith("http://", destStart) && !line.startsWith("https://", destStart)) return;
|
|
2823
|
+
const closeParens = line.indexOf(")", destStart);
|
|
2824
|
+
if (closeParens === -1) return;
|
|
2825
|
+
return {
|
|
2826
|
+
text: line.slice(start, closeParens + 1),
|
|
2827
|
+
end: closeParens + 1
|
|
2828
|
+
};
|
|
2829
|
+
}
|
|
2830
|
+
function hasUnmatchedClosingParen(text) {
|
|
2831
|
+
let balance = 0;
|
|
2832
|
+
for (const ch of text) if (ch === "(") balance++;
|
|
2833
|
+
else if (ch === ")") balance--;
|
|
2834
|
+
return balance < 0;
|
|
2835
|
+
}
|
|
2836
|
+
function readBareUrl(line, start) {
|
|
2837
|
+
let end = start;
|
|
2838
|
+
while (end < line.length) {
|
|
2839
|
+
const ch = line[end];
|
|
2840
|
+
if (/\s/.test(ch) || ch === "<" || ch === ">" || ch === "\"" || ch === "`" || ch === "|" || ch === "*") break;
|
|
2841
|
+
end++;
|
|
2842
|
+
}
|
|
2843
|
+
if (end === start) return;
|
|
2844
|
+
let raw = line.slice(start, end);
|
|
2845
|
+
let suffix = "";
|
|
2846
|
+
const peel = () => {
|
|
2847
|
+
suffix = raw.slice(-1) + suffix;
|
|
2848
|
+
raw = raw.slice(0, -1);
|
|
2849
|
+
};
|
|
2850
|
+
const shouldPeel = () => raw.endsWith("_") || /[.,!?;:]$/.test(raw) || raw.endsWith(")") && hasUnmatchedClosingParen(raw);
|
|
2851
|
+
while (raw.length > 0 && shouldPeel()) peel();
|
|
2852
|
+
if (!/^https?:\/\/.+/.test(raw)) return;
|
|
2853
|
+
return {
|
|
2854
|
+
url: raw,
|
|
2855
|
+
suffix,
|
|
2856
|
+
end
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2859
|
+
/** Wrap bare http(s) URLs on a single line as Slack explicit `<url>` links. */
|
|
2860
|
+
function wrapBareUrlsOnLine(line) {
|
|
2861
|
+
let result = "";
|
|
2862
|
+
let i = 0;
|
|
2863
|
+
while (i < line.length) {
|
|
2864
|
+
const codeSpan = readInlineCodeSpan(line, i);
|
|
2865
|
+
if (codeSpan) {
|
|
2866
|
+
result += codeSpan.text;
|
|
2867
|
+
i = codeSpan.end;
|
|
2868
|
+
continue;
|
|
2869
|
+
}
|
|
2870
|
+
const angleToken = readExistingSlackAngleToken(line, i);
|
|
2871
|
+
if (angleToken) {
|
|
2872
|
+
result += angleToken.text;
|
|
2873
|
+
i = angleToken.end;
|
|
2874
|
+
continue;
|
|
2875
|
+
}
|
|
2876
|
+
const mdLink = readMarkdownLink(line, i);
|
|
2877
|
+
if (mdLink) {
|
|
2878
|
+
result += mdLink.text;
|
|
2879
|
+
i = mdLink.end;
|
|
2880
|
+
continue;
|
|
2881
|
+
}
|
|
2882
|
+
if (line.startsWith("https://", i) || line.startsWith("http://", i)) {
|
|
2883
|
+
const parsed = readBareUrl(line, i);
|
|
2884
|
+
if (parsed) {
|
|
2885
|
+
result += `<${parsed.url}>${parsed.suffix}`;
|
|
2886
|
+
i = parsed.end;
|
|
2887
|
+
continue;
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
result += line[i];
|
|
2891
|
+
i++;
|
|
2892
|
+
}
|
|
2893
|
+
return result;
|
|
2894
|
+
}
|
|
2895
|
+
/**
|
|
2896
|
+
* Pre-wrap bare http(s) URLs outside fenced code blocks as Slack explicit
|
|
2897
|
+
* links, preventing Slack's auto-linker from consuming adjacent formatting
|
|
2898
|
+
* markers into the URL.
|
|
2899
|
+
*
|
|
2900
|
+
* Uses the same fence-toggle rule as `ensureBlockSpacing` so both passes
|
|
2901
|
+
* agree on which lines are code.
|
|
2902
|
+
*/
|
|
2903
|
+
function wrapBareUrls(text) {
|
|
2904
|
+
const lines = text.split("\n");
|
|
2905
|
+
const out = [];
|
|
2906
|
+
let inCodeBlock = false;
|
|
2907
|
+
for (const line of lines) {
|
|
2908
|
+
if (line.trimStart().startsWith("```")) {
|
|
2909
|
+
inCodeBlock = !inCodeBlock;
|
|
2910
|
+
out.push(line);
|
|
2911
|
+
continue;
|
|
2912
|
+
}
|
|
2913
|
+
out.push(inCodeBlock ? line : wrapBareUrlsOnLine(line));
|
|
2914
|
+
}
|
|
2915
|
+
return out.join("\n");
|
|
2916
|
+
}
|
|
2917
|
+
/** Insert blank lines between content blocks so Slack renders them with visual separation. */
|
|
2918
|
+
function ensureBlockSpacing(text) {
|
|
2919
|
+
const codeBlockPattern = /^```/;
|
|
2920
|
+
const listItemPattern = /^[-*•]\s|^\d+\.\s/;
|
|
2921
|
+
const lines = text.split("\n");
|
|
2922
|
+
const result = [];
|
|
2923
|
+
let inCodeBlock = false;
|
|
2924
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2925
|
+
const line = lines[i];
|
|
2926
|
+
if (codeBlockPattern.test(line.trimStart())) {
|
|
2927
|
+
if (!inCodeBlock) {
|
|
2928
|
+
const prev = result.length > 0 ? result[result.length - 1] : void 0;
|
|
2929
|
+
if (prev !== void 0 && prev.trim() !== "") result.push("");
|
|
2930
|
+
}
|
|
2931
|
+
inCodeBlock = !inCodeBlock;
|
|
2932
|
+
result.push(line);
|
|
2933
|
+
continue;
|
|
2934
|
+
}
|
|
2935
|
+
if (inCodeBlock) {
|
|
2936
|
+
result.push(line);
|
|
2937
|
+
continue;
|
|
2938
|
+
}
|
|
2939
|
+
const prev = result.length > 0 ? result[result.length - 1] : void 0;
|
|
2940
|
+
if (prev !== void 0 && prev.trim() !== "" && line.trim() !== "" && !(listItemPattern.test(prev.trimStart()) && listItemPattern.test(line.trimStart()))) result.push("");
|
|
2941
|
+
result.push(line);
|
|
2942
|
+
}
|
|
2943
|
+
return result.join("\n");
|
|
2944
|
+
}
|
|
2945
|
+
/**
|
|
2946
|
+
* Normalize model-authored Slack markdown for delivery via `markdown_text`
|
|
2947
|
+
* or `{ type: "markdown" }` blocks.
|
|
2948
|
+
*
|
|
2949
|
+
* Plugins may first rewrite ordinary Markdown. Core then applies Slack-only
|
|
2950
|
+
* delivery shaping: bare URL wrapping and block spacing. Slack reply delivery
|
|
2951
|
+
* owns chunking and continuation markers separately.
|
|
2952
|
+
*/
|
|
2953
|
+
function normalizeSlackReplyMarkdown(text) {
|
|
2954
|
+
let normalized = applyPluginFormatMarkdown(text).replace(/\r\n?/g, "\n").replace(/[ \t]+$/gm, "");
|
|
2955
|
+
normalized = wrapBareUrls(normalized);
|
|
2956
|
+
normalized = ensureBlockSpacing(normalized);
|
|
2957
|
+
return normalized.replace(/\n{3,}/g, "\n\n").trim();
|
|
2958
|
+
}
|
|
2959
|
+
/** Normalize assistant status text before handing it to Slack. */
|
|
2960
|
+
function normalizeSlackStatusText(text) {
|
|
2961
|
+
const trimmed = text.trim();
|
|
2962
|
+
if (!trimmed) return "";
|
|
2963
|
+
return truncateStatusText(trimmed.replace(/(?:\.\s*)+$/, "").trim());
|
|
2964
|
+
}
|
|
2965
|
+
//#endregion
|
|
2966
|
+
//#region src/chat/slack/oauth-authorization-message.ts
|
|
2967
|
+
/** Present private OAuth authorization with a clear action and text fallback. */
|
|
2968
|
+
function buildSlackOAuthAuthorizationMessage(args) {
|
|
2969
|
+
return {
|
|
2970
|
+
text: `${formatSlackLink(args.authorizationUrl, args.label)}. ${args.completionText}`,
|
|
2971
|
+
blocks: [{
|
|
2972
|
+
type: "section",
|
|
2973
|
+
text: {
|
|
2974
|
+
type: "mrkdwn",
|
|
2975
|
+
text: `*${escapeSlackMrkdwnText(args.label)}*\n${escapeSlackMrkdwnText(args.completionText)}`
|
|
2976
|
+
},
|
|
2977
|
+
accessory: {
|
|
2978
|
+
type: "button",
|
|
2979
|
+
text: {
|
|
2980
|
+
type: "plain_text",
|
|
2981
|
+
text: "Connect"
|
|
2982
|
+
},
|
|
2983
|
+
style: "primary",
|
|
2984
|
+
url: args.authorizationUrl,
|
|
2985
|
+
action_id: "oauth_connect"
|
|
2986
|
+
}
|
|
2987
|
+
}]
|
|
2988
|
+
};
|
|
2989
|
+
}
|
|
2990
|
+
//#endregion
|
|
2991
|
+
//#region src/chat/oauth-flow.ts
|
|
2992
|
+
const OAUTH_STATE_TTL_MS = 6e5;
|
|
2993
|
+
function optionalString(value) {
|
|
2994
|
+
return typeof value === "string" ? value : void 0;
|
|
2995
|
+
}
|
|
2996
|
+
/** Parse OAuth callback state that was persisted before a provider redirect. */
|
|
2997
|
+
function parseOAuthStatePayload(value) {
|
|
2998
|
+
if (!isRecord$2(value)) return;
|
|
2999
|
+
if (typeof value.userId !== "string" || typeof value.provider !== "string") return;
|
|
3000
|
+
const actor = value.actor === void 0 ? void 0 : actorSchema.safeParse(value.actor);
|
|
3001
|
+
if (value.actor !== void 0 && (!actor || !actor.success)) return;
|
|
3002
|
+
const destination = parseDestination(value.destination);
|
|
3003
|
+
if (value.destination !== void 0 && !destination) return;
|
|
3004
|
+
const source = value.source === void 0 ? void 0 : sourceSchema.safeParse(value.source);
|
|
3005
|
+
if (value.source !== void 0 && (!source || !source.success)) return;
|
|
3006
|
+
return {
|
|
3007
|
+
userId: value.userId,
|
|
3008
|
+
provider: value.provider,
|
|
3009
|
+
...actor?.success ? { actor: actor.data } : void 0,
|
|
3010
|
+
...optionalString(value.channelId) ? { channelId: optionalString(value.channelId) } : void 0,
|
|
3011
|
+
...destination ? { destination } : void 0,
|
|
3012
|
+
...source?.success ? { source: source.data } : void 0,
|
|
3013
|
+
...optionalString(value.threadTs) ? { threadTs: optionalString(value.threadTs) } : void 0,
|
|
3014
|
+
...optionalString(value.resumeConversationId) ? { resumeConversationId: optionalString(value.resumeConversationId) } : void 0,
|
|
3015
|
+
...optionalString(value.resumeSessionId) ? { resumeSessionId: optionalString(value.resumeSessionId) } : void 0,
|
|
3016
|
+
...optionalString(value.scope) ? { scope: optionalString(value.scope) } : void 0
|
|
3017
|
+
};
|
|
3018
|
+
}
|
|
3019
|
+
/** Return the manifest-owned display label for a provider. */
|
|
3020
|
+
function formatProviderLabel(provider) {
|
|
3021
|
+
const displayName = pluginCatalogRuntime.getDisplayName(provider);
|
|
3022
|
+
if (!displayName) throw new Error(`Unknown plugin provider display name: "${provider}"`);
|
|
3023
|
+
return displayName;
|
|
3024
|
+
}
|
|
3025
|
+
/** Resolve the public base URL from environment variables (JUNIOR_BASE_URL or Vercel). */
|
|
3026
|
+
function resolveBaseUrl() {
|
|
3027
|
+
const explicit = process.env.JUNIOR_BASE_URL?.trim();
|
|
3028
|
+
if (explicit) return explicit;
|
|
3029
|
+
const vercelProd = process.env.VERCEL_PROJECT_PRODUCTION_URL?.trim();
|
|
3030
|
+
if (vercelProd) return `https://${vercelProd}`;
|
|
3031
|
+
const vercelUrl = process.env.VERCEL_URL?.trim();
|
|
3032
|
+
if (vercelUrl) return `https://${vercelUrl}`;
|
|
3033
|
+
}
|
|
3034
|
+
/**
|
|
3035
|
+
* Authorization links must only be visible to the requesting user.
|
|
3036
|
+
* Try in-context private delivery first, then fall back to a DM.
|
|
3037
|
+
*/
|
|
3038
|
+
async function deliverPrivateMessage(input) {
|
|
3039
|
+
let client;
|
|
3040
|
+
try {
|
|
3041
|
+
client = getSlackClient();
|
|
3042
|
+
} catch {
|
|
3043
|
+
logWarn("oauth.private_delivery.skipped", { "app.reason": "missing_bot_token" });
|
|
3044
|
+
return false;
|
|
3045
|
+
}
|
|
3046
|
+
if (input.channelId) try {
|
|
3047
|
+
if (isDmChannel$1(input.channelId)) await postSlackMessage({
|
|
3048
|
+
channelId: input.channelId,
|
|
3049
|
+
text: input.text,
|
|
3050
|
+
blocks: input.blocks,
|
|
3051
|
+
threadTs: input.threadTs
|
|
3052
|
+
});
|
|
3053
|
+
else await postSlackEphemeralMessage({
|
|
3054
|
+
channelId: input.channelId,
|
|
3055
|
+
userId: input.userId,
|
|
3056
|
+
text: input.text,
|
|
3057
|
+
blocks: input.blocks,
|
|
3058
|
+
threadTs: input.threadTs
|
|
3059
|
+
});
|
|
3060
|
+
return "in_context";
|
|
3061
|
+
} catch (error) {
|
|
3062
|
+
logWarn("oauth.private_delivery.failed", {
|
|
3063
|
+
"app.slack.error": error instanceof Error ? error.message : String(error),
|
|
3064
|
+
"app.slack.channel": input.channelId
|
|
3065
|
+
});
|
|
3066
|
+
}
|
|
3067
|
+
try {
|
|
3068
|
+
const dmChannelId = (await withSlackRetries(() => client.conversations.open({ users: input.userId }), 3, {
|
|
3069
|
+
action: "conversations.open",
|
|
3070
|
+
spanAttributes: { "app.slack.user_id": input.userId }
|
|
3071
|
+
})).channel?.id;
|
|
3072
|
+
if (!dmChannelId) {
|
|
3073
|
+
logWarn("oauth.dm.fallback.failed", { "app.reason": "no_dm_channel_id" });
|
|
3074
|
+
return false;
|
|
3075
|
+
}
|
|
3076
|
+
await postSlackMessage({
|
|
3077
|
+
channelId: dmChannelId,
|
|
3078
|
+
text: input.text,
|
|
3079
|
+
blocks: input.blocks
|
|
3080
|
+
});
|
|
3081
|
+
return "fallback_dm";
|
|
3082
|
+
} catch (error) {
|
|
3083
|
+
logWarn("oauth.dm.fallback.failed", { "app.slack.error": error instanceof Error ? error.message : String(error) });
|
|
3084
|
+
return false;
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
/** Present one OAuth request through the active surface's private delivery. */
|
|
3088
|
+
async function deliverOAuthAuthorization(request, input) {
|
|
3089
|
+
if (input.authorization) {
|
|
3090
|
+
await input.authorization.deliver(request);
|
|
3091
|
+
return "in_context";
|
|
3092
|
+
}
|
|
3093
|
+
return await deliverPrivateMessage({
|
|
3094
|
+
channelId: input.channelId,
|
|
3095
|
+
threadTs: input.threadTs,
|
|
3096
|
+
userId: input.userId,
|
|
3097
|
+
...buildSlackOAuthAuthorizationMessage(request)
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
/** Initiate an OAuth authorization code flow for a provider and deliver the auth link to the user. */
|
|
3101
|
+
async function startOAuthFlow(provider, input) {
|
|
3102
|
+
const providerConfig = pluginCatalogRuntime.getOAuthConfig(provider);
|
|
3103
|
+
if (!providerConfig) return {
|
|
3104
|
+
ok: false,
|
|
3105
|
+
error: `Provider "${provider}" does not support OAuth authorization`
|
|
3106
|
+
};
|
|
3107
|
+
const clientId = process.env[providerConfig.clientIdEnv]?.trim();
|
|
3108
|
+
if (!clientId) return {
|
|
3109
|
+
ok: false,
|
|
3110
|
+
error: `Missing ${providerConfig.clientIdEnv} environment variable`
|
|
3111
|
+
};
|
|
3112
|
+
const baseUrl = resolveBaseUrl();
|
|
3113
|
+
if (!baseUrl) return {
|
|
3114
|
+
ok: false,
|
|
3115
|
+
error: "Cannot determine base URL (set JUNIOR_BASE_URL or deploy to Vercel)"
|
|
3116
|
+
};
|
|
3117
|
+
const state = input.authorization ? await input.authorization.createState() : randomBytes(32).toString("hex");
|
|
3118
|
+
const requestedScope = input.scope ?? providerConfig.scope;
|
|
3119
|
+
await getStateAdapter().set(`oauth-state:${state}`, {
|
|
3120
|
+
userId: input.actorId,
|
|
3121
|
+
provider,
|
|
3122
|
+
...input.actor ? { actor: input.actor } : void 0,
|
|
3123
|
+
...input.channelId ? { channelId: input.channelId } : void 0,
|
|
3124
|
+
...input.destination ? { destination: input.destination } : void 0,
|
|
3125
|
+
...input.source ? { source: input.source } : void 0,
|
|
3126
|
+
...input.threadTs ? { threadTs: input.threadTs } : void 0,
|
|
3127
|
+
...input.resumeConversationId ? { resumeConversationId: input.resumeConversationId } : void 0,
|
|
3128
|
+
...input.resumeSessionId ? { resumeSessionId: input.resumeSessionId } : void 0,
|
|
3129
|
+
...requestedScope ? { scope: requestedScope } : void 0
|
|
3130
|
+
}, OAUTH_STATE_TTL_MS);
|
|
3131
|
+
const authorizeParams = new URLSearchParams({
|
|
3132
|
+
client_id: clientId,
|
|
3133
|
+
state,
|
|
3134
|
+
redirect_uri: `${baseUrl}${providerConfig.callbackPath}`,
|
|
3135
|
+
response_type: "code"
|
|
3136
|
+
});
|
|
3137
|
+
if (requestedScope) authorizeParams.set("scope", requestedScope);
|
|
3138
|
+
for (const [key, value] of Object.entries(providerConfig.authorizeParams ?? {})) authorizeParams.set(key, value);
|
|
3139
|
+
logInfo("jr_rpc.oauth.started", {
|
|
3140
|
+
"app.credential.provider": provider,
|
|
3141
|
+
...input.activeSkillName ? { "app.skill.name": input.activeSkillName } : void 0
|
|
3142
|
+
});
|
|
3143
|
+
return {
|
|
3144
|
+
ok: true,
|
|
3145
|
+
delivery: await deliverOAuthAuthorization({
|
|
3146
|
+
authorizationUrl: `${providerConfig.authorizeEndpoint}?${authorizeParams.toString()}`,
|
|
3147
|
+
label: `Connect to ${formatProviderLabel(provider)}`,
|
|
3148
|
+
completionText: input.resumeSessionId ? "Once you've authorized, Junior will continue automatically." : "Once you've authorized, you'll see a confirmation in Slack."
|
|
3149
|
+
}, {
|
|
3150
|
+
authorization: input.authorization,
|
|
3151
|
+
channelId: input.channelId,
|
|
3152
|
+
threadTs: input.threadTs,
|
|
3153
|
+
userId: input.actorId
|
|
3154
|
+
})
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
//#endregion
|
|
2527
3158
|
//#region src/chat/sandbox/egress/schemas.ts
|
|
2528
3159
|
const finiteNumberSchema = z.number().refine(Number.isFinite);
|
|
2529
3160
|
const httpStatusSchema = z.number().int().min(100).max(599);
|
|
@@ -10220,13 +10851,12 @@ function createSendFilesTool(context, state, materializeFile, attachments) {
|
|
|
10220
10851
|
files: materializedFiles,
|
|
10221
10852
|
storage: attachments.storage
|
|
10222
10853
|
}) : [];
|
|
10223
|
-
const uploads = materializedFiles.map((file) => ({
|
|
10224
|
-
data: file.data,
|
|
10225
|
-
filename: file.filename
|
|
10226
|
-
}));
|
|
10227
10854
|
await uploadFilesToConversation({
|
|
10228
10855
|
channelId: activeChannelId,
|
|
10229
|
-
files:
|
|
10856
|
+
files: materializedFiles.map((file) => ({
|
|
10857
|
+
data: file.data,
|
|
10858
|
+
filename: file.filename
|
|
10859
|
+
})),
|
|
10230
10860
|
...threadTs ? { threadTs } : void 0
|
|
10231
10861
|
});
|
|
10232
10862
|
const delivered = stored.map((attachment, index) => {
|
|
@@ -13802,10 +14432,51 @@ function buildSandboxInput(toolName, params) {
|
|
|
13802
14432
|
return params;
|
|
13803
14433
|
}
|
|
13804
14434
|
//#endregion
|
|
13805
|
-
//#region src/chat/
|
|
13806
|
-
|
|
13807
|
-
|
|
13808
|
-
|
|
14435
|
+
//#region src/chat/services/pending-auth.ts
|
|
14436
|
+
const AUTH_LINK_REUSE_WINDOW_MS = 6e5;
|
|
14437
|
+
const NON_REQUEST_SKIPPED_REASON_PREFIXES = ["directed_to_other_party:", "side_conversation:"];
|
|
14438
|
+
function isSkippedNonRequest(reason) {
|
|
14439
|
+
return NON_REQUEST_SKIPPED_REASON_PREFIXES.some((prefix) => reason?.startsWith(prefix));
|
|
14440
|
+
}
|
|
14441
|
+
/** Decide whether the same agent-run session can reuse its fresh auth link. */
|
|
14442
|
+
function canReusePendingAuthLink(args) {
|
|
14443
|
+
const { pendingAuth } = args;
|
|
14444
|
+
if (!pendingAuth) return false;
|
|
14445
|
+
return pendingAuth.kind === args.kind && pendingAuth.provider === args.provider && pendingAuth.actorId === args.actorId && pendingAuth.scope === args.scope && pendingAuth.sessionId === args.sessionId && pendingAuth.linkSentAtMs + AUTH_LINK_REUSE_WINDOW_MS > (args.nowMs ?? Date.now());
|
|
14446
|
+
}
|
|
14447
|
+
function getConversationPendingAuth(args) {
|
|
14448
|
+
const pendingAuth = args.conversation.processing.pendingAuth;
|
|
14449
|
+
if (!pendingAuth) return;
|
|
14450
|
+
if (pendingAuth.kind !== args.kind || pendingAuth.provider !== args.provider || pendingAuth.actorId !== args.actorId || pendingAuth.scope !== args.scope) return;
|
|
14451
|
+
return pendingAuth;
|
|
14452
|
+
}
|
|
14453
|
+
function clearPendingAuth(conversation, sessionId) {
|
|
14454
|
+
if (!conversation.processing.pendingAuth) return;
|
|
14455
|
+
if (sessionId && conversation.processing.pendingAuth.sessionId !== sessionId) return;
|
|
14456
|
+
conversation.processing.pendingAuth = void 0;
|
|
14457
|
+
}
|
|
14458
|
+
/** Mark the prior blocked turn abandoned after a new auth attempt replaces it. */
|
|
14459
|
+
async function abandonReplacedPendingAuth(args) {
|
|
14460
|
+
if (args.previousPendingAuth && args.previousPendingAuth.sessionId !== args.nextPendingAuth.sessionId && args.conversationId) await abandonTurnRecord({
|
|
14461
|
+
conversationId: args.conversationId,
|
|
14462
|
+
turnId: args.previousPendingAuth.sessionId,
|
|
14463
|
+
errorMessage: "Abandoned by a newer auth-blocked request in the same conversation."
|
|
14464
|
+
});
|
|
14465
|
+
}
|
|
14466
|
+
/**
|
|
14467
|
+
* Decide whether an auth callback still belongs to the latest real human
|
|
14468
|
+
* request, ignoring bot-authored and passive bystander rows while treating
|
|
14469
|
+
* failed human turns and opt-outs as newer freshness blockers.
|
|
14470
|
+
*/
|
|
14471
|
+
function isPendingAuthLatestRequest(conversation, pendingAuth) {
|
|
14472
|
+
for (let index = conversation.messages.length - 1; index >= 0; index -= 1) {
|
|
14473
|
+
const message = conversation.messages[index];
|
|
14474
|
+
if (message?.role !== "user") continue;
|
|
14475
|
+
if (message.author?.isBot) continue;
|
|
14476
|
+
if (isSkippedNonRequest(message.meta?.skippedReason)) continue;
|
|
14477
|
+
return buildDeterministicTurnId(message.id) === pendingAuth.sessionId;
|
|
14478
|
+
}
|
|
14479
|
+
return false;
|
|
13809
14480
|
}
|
|
13810
14481
|
//#endregion
|
|
13811
14482
|
//#region src/chat/services/plugin-auth-orchestration.ts
|
|
@@ -15251,12 +15922,11 @@ function createMcpAuthOrchestration(input) {
|
|
|
15251
15922
|
const authSession = await getMcpAuthSession(authSessionId);
|
|
15252
15923
|
if (!authSession?.authorizationUrl) throw new Error(`Missing MCP authorization URL for plugin "${provider}"`);
|
|
15253
15924
|
await recordPendingAuth(nextPendingAuth);
|
|
15254
|
-
|
|
15925
|
+
if (!await deliverOAuthAuthorization({
|
|
15255
15926
|
authorizationUrl: authSession.authorizationUrl,
|
|
15256
|
-
label: `
|
|
15927
|
+
label: `Connect to ${providerLabel}`,
|
|
15257
15928
|
completionText: "Once you've authorized, Junior will continue automatically."
|
|
15258
|
-
}
|
|
15259
|
-
if (!await deliverOAuthAuthorization(authorizationRequest, {
|
|
15929
|
+
}, {
|
|
15260
15930
|
authorization: input.authorization,
|
|
15261
15931
|
channelId: authSession.channelId,
|
|
15262
15932
|
threadTs: authSession.threadTs,
|
|
@@ -17397,7 +18067,7 @@ function applyHandoff(args) {
|
|
|
17397
18067
|
* assistant message through the delivery port; the result carries diagnostics,
|
|
17398
18068
|
* artifacts, and transcript state for destination-owned completion.
|
|
17399
18069
|
*/
|
|
17400
|
-
const AGENT_ABORT_SETTLE_GRACE_MS = 5e3;
|
|
18070
|
+
const AGENT_ABORT_SETTLE_GRACE_MS$1 = 5e3;
|
|
17401
18071
|
async function readRunConversationVisibility(conversationId) {
|
|
17402
18072
|
const conversationStore = getConversationStore();
|
|
17403
18073
|
const conversation = await conversationStore.get({ conversationId });
|
|
@@ -18126,7 +18796,7 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
|
|
|
18126
18796
|
"app.ai.turn_timeout_ms": turnTimeoutBudgetMs,
|
|
18127
18797
|
"app.ai.turn_deadline_remaining_ms": Math.max(0, turnDeadlineAtMs - Date.now())
|
|
18128
18798
|
});
|
|
18129
|
-
if (!await waitForAbortSettlement(run, AGENT_ABORT_SETTLE_GRACE_MS)) logWarn("agent.turn.abort_settle.timed_out", { "app.ai.abort_settle_grace_ms": AGENT_ABORT_SETTLE_GRACE_MS });
|
|
18799
|
+
if (!await waitForAbortSettlement(run, AGENT_ABORT_SETTLE_GRACE_MS$1)) logWarn("agent.turn.abort_settle.timed_out", { "app.ai.abort_settle_grace_ms": AGENT_ABORT_SETTLE_GRACE_MS$1 });
|
|
18130
18800
|
runResume.captureResumeSnapshot(runResume.getResumeSnapshot(currentAgentMessages()));
|
|
18131
18801
|
}
|
|
18132
18802
|
const pendingAuthPause = getPendingAuthPause();
|
|
@@ -18348,4 +19018,109 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
|
|
|
18348
19018
|
}
|
|
18349
19019
|
}
|
|
18350
19020
|
//#endregion
|
|
18351
|
-
|
|
19021
|
+
//#region src/chat/experimental.ts
|
|
19022
|
+
var experimental_exports = /* @__PURE__ */ __exportAll({
|
|
19023
|
+
EXPERIMENTAL_FEATURES: () => EXPERIMENTAL_FEATURES,
|
|
19024
|
+
getExperimentalFeatures: () => getExperimentalFeatures,
|
|
19025
|
+
isExperimentalFeatureEnabled: () => isExperimentalFeatureEnabled,
|
|
19026
|
+
setExperimentalFeatures: () => setExperimentalFeatures
|
|
19027
|
+
});
|
|
19028
|
+
/**
|
|
19029
|
+
* Known unstable product features that must be opted into explicitly.
|
|
19030
|
+
* Add new keys here as features graduate from private experiments; remove them
|
|
19031
|
+
* once they become stable defaults.
|
|
19032
|
+
*/
|
|
19033
|
+
const EXPERIMENTAL_FEATURES = ["passive-routing", "subagents"];
|
|
19034
|
+
const EXPERIMENTAL_FEATURE_SET = new Set(EXPERIMENTAL_FEATURES);
|
|
19035
|
+
let configuredExperimental = {};
|
|
19036
|
+
function isExperimentalFeature(value) {
|
|
19037
|
+
return EXPERIMENTAL_FEATURE_SET.has(value);
|
|
19038
|
+
}
|
|
19039
|
+
/** Replace app-level experimental opt-ins and return the previous setting. */
|
|
19040
|
+
function setExperimentalFeatures(config) {
|
|
19041
|
+
const previous = { ...configuredExperimental };
|
|
19042
|
+
if (config === void 0) {
|
|
19043
|
+
configuredExperimental = {};
|
|
19044
|
+
return previous;
|
|
19045
|
+
}
|
|
19046
|
+
const next = {};
|
|
19047
|
+
for (const [rawName, enabled] of Object.entries(config)) {
|
|
19048
|
+
if (enabled === void 0) continue;
|
|
19049
|
+
if (typeof enabled !== "boolean") throw new Error(`experimental.${rawName} must be a boolean when provided`);
|
|
19050
|
+
if (!isExperimentalFeature(rawName)) throw new Error(`experimental.${rawName} is not a known experimental feature. Known features: ${EXPERIMENTAL_FEATURES.join(", ")}`);
|
|
19051
|
+
next[rawName] = enabled;
|
|
19052
|
+
}
|
|
19053
|
+
configuredExperimental = next;
|
|
19054
|
+
return previous;
|
|
19055
|
+
}
|
|
19056
|
+
/** Return a copy of the app-level experimental opt-ins configured by createApp(). */
|
|
19057
|
+
function getExperimentalFeatures() {
|
|
19058
|
+
return { ...configuredExperimental };
|
|
19059
|
+
}
|
|
19060
|
+
/** Return whether one experimental feature is enabled via createApp({ experimental }). */
|
|
19061
|
+
function isExperimentalFeatureEnabled(feature) {
|
|
19062
|
+
return configuredExperimental[feature] === true;
|
|
19063
|
+
}
|
|
19064
|
+
//#endregion
|
|
19065
|
+
//#region src/chat/runtime/agent-runner.ts
|
|
19066
|
+
const AGENT_ABORT_SETTLE_GRACE_MS = 5e3;
|
|
19067
|
+
/** Compose the agent executor with stable host dependencies. */
|
|
19068
|
+
function createAgentRunner(execute, options) {
|
|
19069
|
+
const attachmentStorage = options?.attachmentStorage;
|
|
19070
|
+
const streamFn = options?.streamFn;
|
|
19071
|
+
const tracePropagation = options?.tracePropagation;
|
|
19072
|
+
const bindSpawnAgent = options?.bindSpawnAgent;
|
|
19073
|
+
const canBindSpawn = Boolean(bindSpawnAgent) && isExperimentalFeatureEnabled("subagents");
|
|
19074
|
+
return { run: async (run) => {
|
|
19075
|
+
const spawnAgent = bindSpawnAgent && canBindSpawn && !isAgentRunFeatureDisabled(run.disabledFeatures, "subagents") ? bindSpawnAgent(run) : void 0;
|
|
19076
|
+
return await execute({
|
|
19077
|
+
...run,
|
|
19078
|
+
environment: {
|
|
19079
|
+
...run.environment,
|
|
19080
|
+
attachmentStorage: run.environment?.attachmentStorage ?? attachmentStorage,
|
|
19081
|
+
sandboxTracePropagation: run.environment?.sandboxTracePropagation ?? tracePropagation
|
|
19082
|
+
},
|
|
19083
|
+
...spawnAgent ? { durability: {
|
|
19084
|
+
...run.durability,
|
|
19085
|
+
spawnAgent
|
|
19086
|
+
} } : void 0
|
|
19087
|
+
}, streamFn);
|
|
19088
|
+
} };
|
|
19089
|
+
}
|
|
19090
|
+
async function waitForAgentSettlement(runPromise) {
|
|
19091
|
+
let timeoutId;
|
|
19092
|
+
const settleGrace = new Promise((resolve) => {
|
|
19093
|
+
timeoutId = setTimeout(resolve, AGENT_ABORT_SETTLE_GRACE_MS);
|
|
19094
|
+
timeoutId.unref?.();
|
|
19095
|
+
});
|
|
19096
|
+
await Promise.race([runPromise.then(() => void 0, () => void 0), settleGrace]);
|
|
19097
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
19098
|
+
}
|
|
19099
|
+
/** Abort a timed-out agent run and wait briefly for its work to settle. */
|
|
19100
|
+
async function runAgentWithTimeout(agentRunner, run, timeoutMs) {
|
|
19101
|
+
if (typeof timeoutMs !== "number") return await agentRunner.run(run);
|
|
19102
|
+
const timeoutError = /* @__PURE__ */ new Error(`executeAgentRun timed out after ${timeoutMs}ms`);
|
|
19103
|
+
const abortController = new AbortController();
|
|
19104
|
+
const signal = run.signal ? AbortSignal.any([run.signal, abortController.signal]) : abortController.signal;
|
|
19105
|
+
const runPromise = agentRunner.run({
|
|
19106
|
+
...run,
|
|
19107
|
+
signal
|
|
19108
|
+
});
|
|
19109
|
+
let timeoutId;
|
|
19110
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
19111
|
+
timeoutId = setTimeout(() => {
|
|
19112
|
+
abortController.abort(timeoutError);
|
|
19113
|
+
reject(timeoutError);
|
|
19114
|
+
}, timeoutMs);
|
|
19115
|
+
});
|
|
19116
|
+
try {
|
|
19117
|
+
return await Promise.race([runPromise, timeoutPromise]);
|
|
19118
|
+
} catch (error) {
|
|
19119
|
+
if (abortController.signal.aborted) await waitForAgentSettlement(runPromise);
|
|
19120
|
+
throw error;
|
|
19121
|
+
} finally {
|
|
19122
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
19123
|
+
}
|
|
19124
|
+
}
|
|
19125
|
+
//#endregion
|
|
19126
|
+
export { clearSandboxEgressSignals as $, renderAttachmentText as A, AgentInvocationLimitError as B, resolvePluginOAuthAccount as C, shouldEmitDevAgentTrace as Ct, lookupSlackUserProfile as D, unpublishedArtifactHeaders as E, getNextRunAtMs as F, isEventMailboxMetadata as G, processWorkspaceSnapshotJob as H, agentBindingSchema as I, recordFinishedTurnForAutomatedLimit as J, renderTaskInput as K, agentInvocationSchema as L, listThreadReplies as M, logScheduledAutomationRunSkipped as N, readAttachmentVisionSummary as O, scheduledAutomationRunAttributes as P, SANDBOX_EGRESS_PROXY_PATH as Q, createAgentInvocationSchema as R, latestProgressStatus as S, AuthorizationFlowDisabledError as St, readPublicArtifact as T, setConfigDefaults as Tt, ingestEvent as U, getMcpProviderErrorAttributes as V, EVENT_WAIT_MS as W, normalizeSandboxEgressTracePropagationDomains as X, resolveSandboxEgressProviderForHost as Y, shouldPropagateSandboxEgressTrace as Z, putMcpStoredOAuthCredentials as _, buildSteeringPiMessage as _t, isExperimentalFeatureEnabled as a, parseOAuthStatePayload as at, getConversationPendingAuth as b, actorFromRun as bt, createContextCompactor as c, escapeSlackMrkdwnText as ct, deleteMcpAuthSession as d, normalizeSlackStatusText as dt, consumeSandboxEgressAuthRequiredSignal as et, deleteMcpAuthSessionsForUserProvider as f, truncateStatusText as ft, getMcpStoredOAuthCredentials as g, createUserTokenStore as gt, getMcpAuthSession as h, removeReactionFromMessage as ht, getExperimentalFeatures as i, formatProviderLabel as it, renderBlockText as j, recordSlackAttachment as k, executeCredentialedEgressRequest as l, formatSlackLink as lt, deleteMcpStoredOAuthCredentials as m, postSlackMessage as mt, runAgentWithTimeout as n, parseSandboxEgressCredentialToken as nt, setExperimentalFeatures as o, resolveBaseUrl as ot, deleteMcpServerSessionId as p, addReactionToMessage as pt, maybePostAutomatedTurnLimitNotice as q, experimental_exports as r, sandboxEgressSignalsResponseSchema as rt, executeAgentRun as s, startOAuthFlow as st, createAgentRunner as t, consumeSandboxEgressPermissionDeniedSignal as tt, finalizeMcpAuthorization as u, normalizeSlackReplyMarkdown as ut, PluginCredentialFailureError as v, isVisionImageMediaType as vt, publicArtifactHeaders as w, getConfigDefaults as wt, isPendingAuthLatestRequest as x, toolInvocationDestination as xt, clearPendingAuth as y, RetryableDeliveryError as yt, AgentInvocationBusyError as z };
|