@sentry/junior 0.271.0 → 0.273.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 (75) hide show
  1. package/dist/{adapter-Bqbtr1RV.js → adapter-umap1cth.js} +1 -1
  2. package/dist/{admin-CWBjzyN0.js → admin-0o6E_3lo.js} +1 -1
  3. package/dist/{agent-runner-BiHsH8O_.js → agent-runner-D7Qz-lGO.js} +899 -631
  4. package/dist/api/schema.d.ts +2 -2
  5. package/dist/api/schema.js +5 -5
  6. package/dist/api.d.ts +1 -1
  7. package/dist/api.js +62 -537
  8. package/dist/{app-C81xfpNa.d.ts → app-BcyhJq8x.d.ts} +4 -9
  9. package/dist/app.d.ts +1 -1
  10. package/dist/app.js +673 -134
  11. package/dist/{assistant-reply-DgtX9DaJ.js → assistant-reply-wZ7i4OFa.js} +1 -1
  12. package/dist/{automation-Dwh_xcqM.js → automation-yffXcZt9.js} +1 -1
  13. package/dist/{briefs-0FeSJHQ3.js → briefs-CgMnlpIp.js} +4 -4
  14. package/dist/{catalog-runtime-Bw9kjGVu.js → catalog-runtime-TygDsPOU.js} +1 -1
  15. package/dist/cli/chat.js +15 -15
  16. package/dist/cli/check.js +3 -3
  17. package/dist/cli/init.js +1 -1
  18. package/dist/cli/main.js +3 -3
  19. package/dist/cli/plugins.js +4 -4
  20. package/dist/cli/snapshot-create.js +4 -4
  21. package/dist/cli/upgrade.d.ts +1 -1
  22. package/dist/cli/upgrade.js +3 -3
  23. package/dist/{client-B0jlQSgO.js → client-DhqbV0Fe.js} +63 -5
  24. package/dist/{reporting-schema-CZREhYc-.js → common-Eu6sx09c.js} +4 -1
  25. package/dist/{config-DrAeD8sr.js → config-C6FTd7PX.js} +7 -15
  26. package/dist/{connections-BzN2K0G9.js → connections-Dq72KajR.js} +11 -11
  27. package/dist/{conversation-AhOVzN_k.js → conversation-T4oabbBi.js} +22 -1
  28. package/dist/{conversation-privacy-D1NhUxyo.js → conversation-privacy-BeRgrsph.js} +1 -1
  29. package/dist/{credential-sync-DpNWl4yy.js → credential-sync-BmCbHAp4.js} +15 -16
  30. package/dist/{db-DSabI7jJ.d.ts → db-DOzBGLn5.d.ts} +11 -1
  31. package/dist/{executor-C2cRw0hZ.js → executor-BafgvQcc.js} +25 -7
  32. package/dist/instrumentation.js +114 -3
  33. package/dist/{job-queue-CEeLYkSj.js → job-queue-C4lphW9Y.js} +3 -3
  34. package/dist/{logging-DuwXUuIY.js → logging-CNs1e04n.js} +6 -1
  35. package/dist/message-CETC5lsl.js +186 -0
  36. package/dist/nitro.d.ts +1 -1
  37. package/dist/nitro.js +3 -3
  38. package/dist/{oauth-callback-server-oM5TJt34.js → oauth-callback-server-DcQZsKMm.js} +2 -2
  39. package/dist/{credentialed-mh7HBqey.js → projection-BOpi891B.js} +4576 -49
  40. package/dist/{prompt-BPSdXYXV.js → prompt-B1ySjUyf.js} +1 -1
  41. package/dist/{registry-BPtbAVFo.js → registry-B-yUrv3h.js} +2 -2
  42. package/dist/{resolve-D44G1jJm.js → resolve-DqVtd4Q9.js} +3 -3
  43. package/dist/{result-CvP8c5bb.js → result-DQwTiLTw.js} +16 -12
  44. package/dist/{runner-FIGIKvk2.js → runner-Dco5qMq6.js} +11 -11
  45. package/dist/{sandbox-egress-signals-C_JzucwP.js → sandbox-egress-signals-DFc_9Mcv.js} +1 -1
  46. package/dist/{schema-DKZgO-xf.js → schema-5xHDGlBW.js} +10 -9
  47. package/dist/{schema-DPCvePop.d.ts → schema-9OGdsaxF.d.ts} +28 -3
  48. package/dist/{service-account-1mJGRAvx.js → service-account-CWfk9Cs-.js} +3 -3
  49. package/dist/{service-account-CHO3YXpt.js → service-account-smdPFWNy.js} +3 -3
  50. package/dist/{session-stop-DuUB1rwQ.js → session-stop-DaT_7tYa.js} +50 -24
  51. package/dist/{skills-DHK7L9Eo.js → skills-BbtJSF2-.js} +2 -2
  52. package/dist/{spawn-CKl_ySIq.js → spawn-Ceikdjkp.js} +2 -2
  53. package/dist/{list-DjJJ5K0x.js → stop-dKUc-x2K.js} +1017 -469
  54. package/dist/{store-Di0E4EvU.js → store-C1xuZcLd.js} +48 -31
  55. package/dist/{store-BF2XEeQz.js → store-DaxUuOmD.js} +3 -3
  56. package/dist/{task-queue-CC7IFtbT.js → task-queue-ymAh_-P1.js} +4 -4
  57. package/dist/{task-runner-DKZtMFwa.js → task-runner-vlH6pEm3.js} +6 -6
  58. package/dist/{thread-state-BQ1T3eeF.js → thread-state-BkUpE9Ae.js} +4 -5
  59. package/dist/{turn-failure-response-B5eSFoHn.js → turn-failure-response-DIsD7ip_.js} +7 -8
  60. package/dist/{turn-lifecycle-CTLEIEOn.js → turn-lifecycle-BdPKdPS8.js} +1 -1
  61. package/dist/turn-session-routing-BdCTDq9m.js +1625 -0
  62. package/dist/{validation-Cjo-3yt3.js → validation-BQjCfh84.js} +1 -1
  63. package/dist/{version-D30MBLLw.js → version-BpiFGZZW.js} +1 -1
  64. package/dist/version.js +1 -1
  65. package/dist/{viewer-D3jhuP66.js → viewer-C6Y1aXjZ.js} +49 -21
  66. package/dist/{work-CL9QwQcl.js → work-ChPh_YEh.js} +27 -39
  67. package/dist/{worker-S2P7IJDI.js → worker-Be_sK_ou.js} +6 -6
  68. package/migrations/0072_conversation_read_positions.sql +10 -0
  69. package/migrations/meta/0072_snapshot.json +4064 -0
  70. package/migrations/meta/_journal.json +7 -0
  71. package/package.json +4 -4
  72. package/dist/message-CRe3WBKc.js +0 -59
  73. package/dist/turn-session-routing-BFVp8TbU.js +0 -6072
  74. package/migrations/AGENTS.md +0 -8
  75. package/dist/{oauth-relay-CTygMkNx.js → oauth-relay-C6ZAyJgf.js} +1 -1
@@ -1,45 +1,45 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-nwUcYXjP.js";
2
- import { $ as saveEventAutomation, A as readAutomationRuns, Ar as trimTrailingAssistantMessages, B as readScheduledAutomation, Br as estimateTextTokens, Bt as recordMcpProviderConnected, C as readAutomationExecutionByConversationId, Cn as loadTurnCheckpoint, Cr as isAssistantMessage, Ct as requireEventIdentifier, D as readAutomationExecutionStatusHours, Dr as normalizeToolNameFromResult, Dt as loadConnectedMcpProviders, E as readAutomationExecutionStatusDays, Er as isToolResultMessage, Fr as buildDeterministicTurnId, Ft as recordAttachmentsDelivered, Hr as getAgentContextInputLimitTokens, Ht as recordToolExecutionStarted, I as createScheduledAutomation, J as deleteEventAutomation, Jr as historyItemFromPiMessage, L as deleteScheduledAutomation, Mr as SESSION_CONTEXT_SECTION_TAG, Mt as loadTurnRoute, Nr as TURN_CONTEXT_TAG, Nt as openConversationProjection, O as readAutomationExecutionSummaries, Or as retainRuntimeTurnContext, Ot as loadConversationProjection, Pt as recordAgentsInstructionsUpdated, Q as parseEventAutomationRow, Qt as resolveTaskTitle, Rt as recordAuthorizationRequested, Sn as continuableMessages, St as registeredResourceTypeSchema, T as readAutomationExecutionHours, Tr as isToolResultError, Ut as recordTurnRoute, V as saveScheduledAutomation, Vr as getAgentContextCompactionTriggerTokens, Wr as isExperimentalFeatureEnabled, X as getEventAutomation, Xr as getConfigDefaults, Yr as piMessageFromHistoryItem, Z as listDeletedEventAutomationsCreatedBy, Zt as fallbackShortTitle, _n as abandonReplacedPendingAuth, _r as extractAssistantText, _t as eventWatchUnavailable, at as listAutomationVersions, br as getUserMessageInstructionText, bt as registeredEventTriggerProblems, c as getDispatchIdForConversation, cr as TURN_LIMIT_WRAP_UP_MESSAGE, dn as PluginCredentialFailureError, dr as TurnToolCallLimitExceededError, et as setEventAutomationStatus, fn as createPluginAuthOrchestration, ft as automationEventCatalog, gn as AuthorizationPauseError, gt as eventNamespaceSchema, hn as AuthorizationFlowDisabledError, hr as CODEMODE_TOOL_NAME, jr as REQUEST_CONTEXT_SECTION_TAG, k as readAutomationExecutions, kr as stripRuntimeTurnContext, kt as loadLastModelProfile, l as getDispatchInputMessageId, lr as TurnBudgetExceededError, lt as automationRevision, mn as AuthPausePersistenceError, mr as isTurnExecutionLimitExceededError, ot as readAutomationVersion, pn as authorizationId, pr as createTurnLimitGate, pt as describeRegisteredEventTrigger, q as createEventAutomation, rt as automationDefinition, st as sameDefinitionValue, t as resolveConversationRouting, u as getDispatchRecord, un as createPluginEgress, ur as TurnSliceLimitExceededError, ut as requireAutomationRevision, vn as canReusePendingAuthLink, vr as getPiMessageRole, vt as hasEventCatalogEntries, w as readAutomationExecutionDays, wn as saveTurnCheckpoint, wr as isContinuablePiBoundary, wt as requireValidEventTrigger, x as emptyAutomationRunWindows, xr as hasRuntimeTurnContext, xt as registeredEventTypeSchema, yr as getTerminalAssistantMessages, yt as registeredEventMatchSchema, z as parseScheduledAutomationRow, zt as recordGuardianActionReviewed } from "./turn-session-routing-BFVp8TbU.js";
3
- import { _ as getProviderErrorAttributes, a as completeText, b as getGatewayApiKey, c as COMPACTION_SUMMARY_PREFIX, d as renderCurrentInstruction, f as unwrapCurrentInstruction, g as findProviderError, h as createProviderError, i as completeObject, l as hasCompactedConversationContext, m as ProviderError, n as GEN_AI_SERVER_ADDRESS, p as escapeXml, s as resolveGatewayModel, t as GEN_AI_PROVIDER_NAME, u as isCompactionSummaryText, x as resolveGatewayCredential, y as isProviderRetryError } from "./client-B0jlQSgO.js";
4
- import { n as botConfig, v as normalizeSlackEmojiName } from "./config-DrAeD8sr.js";
5
- import { B as isRecord$4, C as summarizeMessageText, E as withSpan, H as toOptionalString, S as setTags, T as withLogContext, _ as runBestEffort, a as extractGenAiUsageSummary, b as setSpanAttributes, f as logError, g as normalizeGenAiFinishReason, h as logWarn, i as extractGenAiUsageAttributes, l as getLogContextAttributesForSpan, m as logInfo, p as logException, u as getTracePropagationHeaders, v as serializeGenAiAttribute, x as setSpanStatus, z as normalizeIdentityEmail } from "./logging-DuwXUuIY.js";
2
+ import { $ as loadTurnCheckpoint, $n as canAgentChangeReviewItem, $r as isAlreadyExistsError, $t as isAssistantMessage, Ai as findMatchingWatches, An as getPluginSystemPromptContributions, Ar as formatProviderLabel, B as createPluginEgress, Ba as matchSlackAttachments, Bn as closeReviewItem, Br as addReactionToMessage, Bt as TurnSliceLimitExceededError, Ci as STOP_WATCHING_TOOL_NAME, Cn as getOAuthAccountProviders, Cr as hash, D as fallbackShortTitle, Di as createTimerWatch, En as createPluginHookRunner, Er as getSandboxProvider, G as AuthorizationFlowDisabledError, Ga as createPluginAnnotations, Gi as getSlackClient, Gn as insertReviewItem, Gt as CODEMODE_TOOL_NAME, H as createPluginAuthOrchestration, Hi as downloadPrivateSlackFile, Hn as dismissOpenReviewItems, Hr as getSlackMessagePermalink, Ir as normalizeSlackReplyMarkdown, J as canReusePendingAuthLink, Ja as defaultTimezone, Ji as normalizeSlackConversationId, Jn as searchReviewItems, Jr as updateSlackMessage, Jt as getPiMessageRole, K as AuthorizationPauseError, Ki as isConversationScopedChannel, Kr as postSlackMessage, Mi as listWatches, Mn as getPluginUserPromptContributions, Mr as resolveBaseUrl, Nn as getPlugins, O as resolveTaskTitle, Oi as createWatch, Pr as escapeSlackMrkdwnText, Q as continuableMessages, Qr as isAbortError, Rr as slackMention, Rt as TURN_LIMIT_WRAP_UP_MESSAGE, Si as RESOURCE_WATCH_TOOL_SOURCE, Sn as createUserTokenStore, Sr as create, Ti as cancelWatch, Tr as ensureDockerDaemon, U as authorizationId, Ui as getFilePermalink, Un as getOpenReviewItemByDedupeKey, Ur as openSlackDirectMessage, Ut as createTurnLimitGate, V as PluginCredentialFailureError, Vi as SlackActionError, Vr as deleteSlackMessage, Vt as TurnToolCallLimitExceededError, W as AuthPausePersistenceError, Wa as saveObjectAnnotations, Wn as getReviewItem, Wt as isTurnExecutionLimitExceededError, Xi as withSlackRetries, Xn as updateReviewItemContent, Xt as getUserMessageInstructionText, Ya as resolveUserTimezone, Yi as runWithSlackInstallationToken, Yr as uploadFilesToConversation, Yt as getTerminalAssistantMessages, Zi as sleep, Zn as PLUGIN_REVIEW_ITEM_KEY_PREFIX, Zt as hasRuntimeTurnContext, _a as sandboxSkillDir, _i as workspaceRepoCheckoutPath, _n as getAgentContextCompactionTriggerTokens, _r as WorkspaceValidationError, a as loadLastModelProfile, ai as workspaceSnapshotNotReadyUserMessage, an as stripRuntimeTurnContext, bi as RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS, bn as isExperimentalFeatureEnabled, br as setWorkspaceSnapshot, c as loadTurnRoute, ca as buildCommandScript, ci as consumeSandboxEgressPermissionDeniedSignal, cn as SESSION_CONTEXT_SECTION_TAG, cr as resolveAssignee, d as recordAttachmentsDelivered, da as SANDBOX_COMMAND_PATH, dn as buildDeterministicTurnId, dr as deleteWorkspace, ei as throwSandboxOperationError, en as isContinuablePiBoundary, eo as historyItemFromPiMessage, er as canReadReviewItem, et as saveTurnCheckpoint, fa as SANDBOX_DATA_ROOT, fi as buildSandboxEgressNetworkPolicy, fr as getWorkspace, g as recordMcpProviderConnected, ga as SANDBOX_WORKSPACE_ROOT, gi as tryWorkspaceRepoCheckoutPath, gn as estimateTextTokens, gr as updateWorkspace, h as recordGuardianActionReviewed, ha as SANDBOX_SKILLS_ROOT, hi as SANDBOX_RUNTIME, i as loadConversationProjection, ia as getDashboardTaskLink, ii as isWorkspaceSnapshotNotReadyError, in as retainRuntimeTurnContext, io as getConfigDefaults, ir as requireReviewItemLinks, ji as getWatch, jn as getPluginTools, ki as deliverWatch, kr as deliverOAuthAuthorization, l as openConversationProjection, la as buildNonInteractiveShellScript, li as createSandboxEgressCredentialToken, ln as TURN_CONTEXT_TAG, lr as resolveAssignees, m as recordAuthorizationRequested, ma as SANDBOX_REPOS_ROOT, mr as listWorkspaces, ni as WorkspaceSnapshotNotReadyError, nn as isToolResultMessage, no as JUNIOR_NATIVE_EVENT_NAMESPACE, nr as readReviewItemAgentScope, oa as ToolInputError, oi as clearSandboxEgressSignals, on as trimTrailingAssistantMessages, or as EVERYONE_ASSIGNEE, pa as SANDBOX_JUNIOR_ROOT, pi as resolveSandboxCommandEnvironment, pr as getWorkspaceByName, q as abandonReplacedPendingAuth, qt as extractAssistantText, r as loadConnectedMcpProviders, ra as getDashboardReviewItemLink, ri as getWorkspaceSnapshotNotReadyError, rn as normalizeToolNameFromResult, ro as conversationNoteEvent, rr as requireReviewItemAssignees, sa as PluginToolDeniedError, si as consumeSandboxEgressAuthRequiredSignal, sn as REQUEST_CONTEXT_SECTION_TAG, sr as automationCreatorUserId, ta as getDashboardConversationLink, ti as wrapSandboxSetupError, tn as isToolResultError, to as piMessageFromHistoryItem, u as recordAgentsInstructionsUpdated, ua as runNonInteractiveCommand, ur as createWorkspace, v as recordToolExecutionStarted, va as sandboxSkillFile, vi as resolveChannelCapabilities, vn as getAgentContextInputLimitTokens, vr as invalidateReadySnapshot, wi as cancelSubscriptions, wr as isStale, xi as RESOURCE_SUBSCRIPTION_MAX_TTL_MS, xr as setWorkspaceSnapshotBuild, y as recordTurnRoute, ya as sandboxSkillPathResolution, yi as getSlackToolContext, yr as loadSnapshotsForProfile, zt as TurnBudgetExceededError } from "./projection-BOpi891B.js";
3
+ import { S as resolveGatewayCredential, _ as findProviderError, a as completeText, b as isProviderRetryError, c as resolveGatewayModel, d as isCompactionSummaryText, f as renderCurrentInstruction, g as createProviderError, h as ProviderError, i as completeObject, l as COMPACTION_SUMMARY_PREFIX, m as escapeXml, n as GEN_AI_SERVER_ADDRESS, p as unwrapCurrentInstruction, t as GEN_AI_PROVIDER_NAME, u as hasCompactedConversationContext, v as getProviderErrorAttributes, x as getGatewayApiKey } from "./client-DhqbV0Fe.js";
4
+ import { _ as normalizeSlackEmojiName, n as botConfig } from "./config-C6FTd7PX.js";
5
+ import { B as isRecord$4, C as summarizeMessageText, E as withSpan, H as toOptionalString, S as setTags, T as withLogContext, _ as runBestEffort, a as extractGenAiUsageSummary, b as setSpanAttributes, f as logError, g as normalizeGenAiFinishReason, h as logWarn, i as extractGenAiUsageAttributes, l as getLogContextAttributesForSpan, m as logInfo, p as logException, u as getTracePropagationHeaders, v as serializeGenAiAttribute, x as setSpanStatus, z as normalizeIdentityEmail } from "./logging-CNs1e04n.js";
6
6
  import { g as withActiveSpan, i as getActiveSpan, m as startInactiveSpan } from "./sentry-MDkYAVPV.js";
7
- 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-D1NhUxyo.js";
7
+ 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-BeRgrsph.js";
8
8
  import { a as formatModelProfile, c as modelProfileSchema, l as profileConfig, n as ModelProfileNotConfiguredError, o as formatModelProfiles, r as boundProfile, s as modelIdForProfile, u as TURN_REASONING_LEVELS } from "./model-profile-B998sjVd.js";
9
9
  import { d as parseSlackChannelReferenceId, f as parseSlackTeamId, h as parseSlackMessageTs, n as createSlackActor, o as isUserActor, p as parseSlackUserId, u as parseSlackChannelId } from "./actor-B4DYygOB.js";
10
- import { C as getSqlExecutor, M as resolveActorIdentityId, Q as sameDestination, S as getDb, U as addAgentTurnUsage, W as agentTurnCostUsd, X as parseDestination, a as readActorIdentityFromSql, b as getConversationMessageSearchStore, f as identityAvatarUrl, h as readUserAvatarUrls, i as readActorIdentity, j as SYSTEM_IDENTITY_ID, q as hasAgentTurnUsage, x as getConversationStore, y as getConversationEventStore } from "./viewer-D3jhuP66.js";
10
+ import { $ as sameDestination, C as getSqlExecutor, G as agentTurnCostUsd, J as hasAgentTurnUsage, M as SYSTEM_IDENTITY_ID, N as resolveActorIdentityId, S as getDb, W as addAgentTurnUsage, Z as parseDestination, a as readActorIdentityFromSql, b as getConversationMessageSearchStore, f as identityAvatarUrl, h as readUserAvatarUrls, i as readActorIdentity, x as getConversationStore, y as getConversationEventStore } from "./viewer-C6Y1aXjZ.js";
11
11
  import { a as juniorAgentInvocations, c as juniorIdentities, d as juniorDestinations, i as juniorAgentBindings, l as juniorUsers, s as juniorConversations } from "./agent-invocations-C1RNOCs8.js";
12
- import { T as juniorArtifacts, d as juniorAutomations, f as juniorStats, g as juniorConversationBriefs, l as juniorAutomationRuns, s as juniorReviewItemSlackCards, w as juniorAttachments, x as juniorConversationAnnotations } from "./executor-C2cRw0hZ.js";
12
+ import { E as juniorArtifacts, S as juniorConversationAnnotations, T as juniorAttachments, _ as juniorConversationBriefs, d as juniorAutomations, f as juniorStats, l as juniorAutomationRuns, s as juniorReviewItemSlackCards } from "./executor-BafgvQcc.js";
13
13
  import { _ as sameActorIdentity, g as instructionProvenanceFor, h as instructionActors, i as KNOWN_CONVERSATION_EVENT_TYPES, m as contextProvenance, n as detectImageMediaType, p as agentInstructionProvenance, r as isVisionImageMediaType } from "./media-U6Tuz6Mi.js";
14
14
  import { a as listReferenceFiles, c as soulPathCandidates, l as worldPathCandidates } from "./package-discovery-DJBompdd.js";
15
- import { c as fetchWithBoundedOAuthErrorBodies, d as actorLogContext, f as credentialContextForActor, g as credentialUserSubjectId, i as resolvePluginHeaderEnvRefs, l as getOAuthProviderErrorAttributes, s as OAuthProviderError } from "./registry-BPtbAVFo.js";
16
- import { n as pluginCatalogRuntime } from "./catalog-runtime-Bw9kjGVu.js";
17
- import { a as parseSkillInvocation, i as parseSkillFile, n as findSkillByName, r as loadSkillsByName, t as discoverSkills } from "./skills-DHK7L9Eo.js";
18
- import { $ as loadSnapshotsForProfile, $n as sandboxSkillDir, $t as workspaceRepoCheckoutPath, A as updateReviewItemContent, An as normalizeSlackConversationId, B as EVERYONE_ASSIGNEE, Bn as getDashboardTaskLink, Bt as clearSandboxEgressSignals, C as getOpenReviewItemByDedupeKey, Cn as SlackActionError, Ct as openSlackDirectMessage, Dn as getSlackClient, Et as postSlackMessage, Ft as wrapSandboxSetupError, G as deleteWorkspace, Gn as buildNonInteractiveShellScript, H as resolveAssignee, Hn as ToolInputError, Ht as consumeSandboxEgressPermissionDeniedSignal, I as readReviewItemAgentScope, Ir as conversationNoteEvent, It as WorkspaceSnapshotNotReadyError, J as listWorkspaces, Jn as SANDBOX_DATA_ROOT, Jt as resolveSandboxCommandEnvironment, K as getWorkspace, Kn as runNonInteractiveCommand, L as requireReviewItemAssignees, Ln as getDashboardConversationLink, Lt as getWorkspaceSnapshotNotReadyError, Mn as withSlackRetries, Mr as JUNIOR_NATIVE_EVENT_NAMESPACE, Mt as isAbortError, N as canAgentChangeReviewItem, Nn as sleep, Nt as isAlreadyExistsError, O as searchReviewItems, On as isConversationScopedChannel, Or as defaultTimezone, Ot as updateSlackMessage, P as canReadReviewItem, Pt as throwSandboxOperationError, Q as invalidateReadySnapshot, Qn as SANDBOX_WORKSPACE_ROOT, Qt as tryWorkspaceRepoCheckoutPath, R as requireReviewItemLinks, Rt as isWorkspaceSnapshotNotReadyError, S as dismissOpenReviewItems, St as getSlackMessagePermalink, T as insertReviewItem, Tn as getFilePermalink, Tr as createPluginAnnotations, U as resolveAssignees, Un as PluginToolDeniedError, Ut as createSandboxEgressCredentialToken, V as automationCreatorUserId, Vt as consumeSandboxEgressAuthRequiredSignal, W as createWorkspace, Wn as buildCommandScript, X as updateWorkspace, Xn as SANDBOX_REPOS_ROOT, Yn as SANDBOX_JUNIOR_ROOT, Z as WorkspaceValidationError, Zn as SANDBOX_SKILLS_ROOT, Zt as SANDBOX_RUNTIME, an as STOP_WATCHING_TOOL_NAME, at as ensureDockerDaemon, b as closeReviewItem, br as matchSlackAttachments, bt as addReactionToMessage, d as getPluginTools, dn as deliverWatch, en as resolveChannelCapabilities, er as sandboxSkillFile, et as setWorkspaceSnapshot, f as getPluginUserPromptContributions, fn as findMatchingWatches, ft as resolveBaseUrl, gt as normalizeSlackReplyMarkdown, in as RESOURCE_WATCH_TOOL_SOURCE, it as isStale, j as PLUGIN_REVIEW_ITEM_KEY_PREFIX, jn as runWithSlackInstallationToken, kr as resolveUserTimezone, kt as uploadFilesToConversation, ln as createTimerWatch, lt as deliverOAuthAuthorization, mn as listWatches, mt as escapeSlackMrkdwnText, n as createUserTokenStore, nn as RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS, nt as create, o as createPluginHookRunner, on as cancelSubscriptions, ot as getSandboxProvider, p as getPlugins, pn as getWatch, q as getWorkspaceByName, qn as SANDBOX_COMMAND_PATH, qt as buildSandboxEgressNetworkPolicy, r as getOAuthAccountProviders, rn as RESOURCE_SUBSCRIPTION_MAX_TTL_MS, rt as hash, sn as cancelWatch, tn as getSlackToolContext, tr as sandboxSkillPathResolution, tt as setWorkspaceSnapshotBuild, u as getPluginSystemPromptContributions, un as createWatch, ut as formatProviderLabel, vt as slackMention, w as getReviewItem, wn as downloadPrivateSlackFile, wr as saveObjectAnnotations, xt as deleteSlackMessage, zn as getDashboardReviewItemLink, zt as workspaceSnapshotNotReadyUserMessage } from "./credentialed-mh7HBqey.js";
15
+ import { c as fetchWithBoundedOAuthErrorBodies, d as actorLogContext, f as credentialContextForActor, g as credentialUserSubjectId, i as resolvePluginHeaderEnvRefs, l as getOAuthProviderErrorAttributes, s as OAuthProviderError } from "./registry-B-yUrv3h.js";
16
+ import { n as pluginCatalogRuntime } from "./catalog-runtime-TygDsPOU.js";
17
+ import { a as parseSkillInvocation, i as parseSkillFile, n as findSkillByName, r as loadSkillsByName, t as discoverSkills } from "./skills-BbtJSF2-.js";
19
18
  import { a as withLock, i as fenceLock } from "./locks-D-0h111u.js";
20
- import { r as getStateAdapter } from "./adapter-Bqbtr1RV.js";
19
+ import { r as getStateAdapter } from "./adapter-umap1cth.js";
21
20
  import { i as isValidTimeZone, r as getZonedDateTimeParts, t as getFirstRunAtMs } from "./cadence-R7Em4RjE.js";
22
- import { B as removedCardSchema, J as conversationBriefSchema, K as briefLinkSchema, q as briefOutcomeStatusSchema } from "./conversation-AhOVzN_k.js";
23
- import { r as EVENT_AUTHOR_ID } from "./prompt-BPSdXYXV.js";
24
- import { A as AgentMessageQueueFullError, F as agentNameSchema, H as TurnInputCommitLostError, I as AGENT_TYPES, K as isTurnInputCommitLostError, M as AgentNameInUseError, N as CHILD_AGENT_RESULTS, P as PARENT_AGENT_TARGET, V as CooperativeTurnYieldError, _ as getLatestAgentInvocationForConversation, d as getAgentInvocationIdForResult, f as getAgentInvocationMessageId, g as getChildAgentLockName, j as AgentMessageTargetError, k as AgentInvocationLimitError, t as NON_TERMINAL_AGENT_INVOCATION_STATUSES, v as invocationFromRow, y as isTerminalAgentInvocation, z as formatAgentTypes } from "./store-Di0E4EvU.js";
25
- import { a as isNoReplyMarker, i as NO_REPLY_MARKER, n as getAssistantReplyText, r as sanitizeAssistantText, t as decideReply } from "./assistant-reply-DgtX9DaJ.js";
26
- import { A as automationResultSchema$1, F as remindMissingAutomationResult, P as readAutomationResult, _ as WORKSPACE_SNAPSHOT_FAILED_EVENT, a as EVENT_WAIT_MS, b as workspaceSnapshotWatch, d as getEventCatalog, k as FINISH_AUTOMATION_RUN_TOOL_NAME, n as markConversationsStopped, o as enqueueEventNotification, p as requireSlackChannelMessageScope, t as getConversationStoppedAtMs, v as WORKSPACE_SNAPSHOT_READY_EVENT, w as resolveSlackReplyTarget, y as workspaceSnapshotFinishedEvent } from "./session-stop-DuUB1rwQ.js";
21
+ import { J as briefOutcomeStatusSchema, V as removedCardSchema, Y as conversationBriefSchema, q as briefLinkSchema } from "./conversation-T4oabbBi.js";
22
+ import { r as EVENT_AUTHOR_ID } from "./prompt-B1ySjUyf.js";
23
+ import { A as AgentMessagePausedError, F as CHILD_AGENT_RESULTS, I as PARENT_AGENT_TARGET, J as isTurnInputCommitLostError, L as agentNameSchema, M as AgentMessageTargetError, N as AgentNameInUseError, R as AGENT_TYPES, U as CooperativeTurnYieldError, V as formatAgentTypes, W as TurnInputCommitLostError, _ as invocationFromRow, d as getAgentInvocationMessageId, g as getLatestAgentInvocationForConversation, h as getChildAgentLockName, j as AgentMessageQueueFullError, k as AgentInvocationLimitError, t as NON_TERMINAL_AGENT_INVOCATION_STATUSES, u as getAgentInvocationIdForResult, v as isTerminalAgentInvocation } from "./store-C1xuZcLd.js";
24
+ import { a as isNoReplyMarker, i as NO_REPLY_MARKER, n as getAssistantReplyText, r as sanitizeAssistantText, t as decideReply } from "./assistant-reply-wZ7i4OFa.js";
25
+ import { E as resolveSlackReplyTarget, L as readAutomationResult, M as FINISH_AUTOMATION_RUN_TOOL_NAME, N as automationResultSchema$1, R as remindMissingAutomationResult, _ as slackChannelMessageTrigger, a as EVENT_WAIT_MS, b as workspaceSnapshotFinishedEvent, d as getEventCatalog, n as markConversationsStopped, o as enqueueEventNotification, p as requireSlackChannelMessageScope, t as getConversationStoppedAtMs, v as WORKSPACE_SNAPSHOT_FAILED_EVENT, x as workspaceSnapshotWatch, y as WORKSPACE_SNAPSHOT_READY_EVENT } from "./session-stop-DaT_7tYa.js";
27
26
  import { C as userCustomizationTextSchema, b as webMessageId, g as formatUserInputAnswer, h as buttonUserInputField, m as USER_INPUT_NOTE, p as USER_INPUT_LIMITS, s as sumUtcHoursIntoSixHours, v as normalizeUserInputAnswer, x as USER_CUSTOMIZATION_MAX_CHARS, y as userInputRequestSchema } from "./reporting-window-DgHgwQB3.js";
28
- import { a as setSnapshotSpanAttributes, i as postinstall, n as resolve$1, r as dependencies, s as getSandboxResources } from "./resolve-D44G1jJm.js";
29
- import { a as getVercelConversationWorkQueue, f as getTurnRequestDeadline, n as sendNextWorkspaceSnapshotJob, r as sendWorkspaceSnapshotJob } from "./job-queue-CEeLYkSj.js";
30
- import { D as stopConversationWork, n as appendAndEnqueueExclusiveInboundMessage, r as appendAndEnqueueInboundMessage } from "./store-BF2XEeQz.js";
31
- import { _ as getMcpProviderErrorAttributes, a as serviceAccountOnly, c as createMcpServiceAccountClientProvider, d as PluginMcpClient, f as McpProviderError, g as getMcpAwareTelemetryMessage, h as getMcpAwareErrorType, i as serviceAccountAllows, l as getMcpServiceAccountCredentials, m as McpToolError, p as McpServiceAccountRequiredError, u as McpAuthorizationRequiredError, v as toMcpProviderError } from "./service-account-CHO3YXpt.js";
32
- import { A as SCHEDULED_AUTOMATION_SYSTEM_ACTOR, D as taskOutcomeInputSchema, E as automationTitleSchema, F as scheduleIntentSchema, I as weekdaySchema, N as ScheduleIntentError, P as compileScheduleIntent, T as automationInstructionToolSchema, k as eventAutomationTriggerSchema, l as automationListQuerySchema } from "./automation-Dwh_xcqM.js";
27
+ import { a as setSnapshotSpanAttributes, i as postinstall, n as resolve$1, r as dependencies, s as getSandboxResources } from "./resolve-DqVtd4Q9.js";
28
+ import { a as getVercelConversationWorkQueue, f as getTurnRequestDeadline, n as sendNextWorkspaceSnapshotJob, r as sendWorkspaceSnapshotJob } from "./job-queue-C4lphW9Y.js";
29
+ import { D as stopConversationWork, n as appendAndEnqueueExclusiveInboundMessage, r as appendAndEnqueueInboundMessage } from "./store-DaxUuOmD.js";
30
+ import { $ as saveEventAutomation, A as readAutomationRuns, B as readScheduledAutomation, C as readAutomationExecutionByConversationId, Ct as requireEventIdentifier, D as readAutomationExecutionStatusHours, E as readAutomationExecutionStatusDays, I as createScheduledAutomation, J as deleteEventAutomation, L as deleteScheduledAutomation, O as readAutomationExecutionSummaries, Q as parseEventAutomationRow, St as registeredResourceTypeSchema, T as readAutomationExecutionHours, V as saveScheduledAutomation, X as getEventAutomation, Z as listDeletedEventAutomationsCreatedBy, _t as eventWatchUnavailable, at as listAutomationVersions, bt as registeredEventTriggerProblems, c as getDispatchIdForConversation, et as setEventAutomationStatus, ft as automationEventCatalog, gt as eventNamespaceSchema, k as readAutomationExecutions, l as getDispatchInputMessageId, lt as automationRevision, ot as readAutomationVersion, pt as describeRegisteredEventTrigger, q as createEventAutomation, rt as automationDefinition, st as sameDefinitionValue, t as resolveConversationRouting, u as getDispatchRecord, ut as requireAutomationRevision, vt as hasEventCatalogEntries, w as readAutomationExecutionDays, wt as requireValidEventTrigger, x as emptyAutomationRunWindows, xt as registeredEventTypeSchema, yt as registeredEventMatchSchema, z as parseScheduledAutomationRow } from "./turn-session-routing-BdCTDq9m.js";
31
+ import { _ as getMcpProviderErrorAttributes, a as serviceAccountOnly, c as createMcpServiceAccountClientProvider, d as PluginMcpClient, f as McpProviderError, g as getMcpAwareTelemetryMessage, h as getMcpAwareErrorType, i as serviceAccountAllows, l as getMcpServiceAccountCredentials, m as McpToolError, p as McpServiceAccountRequiredError, u as McpAuthorizationRequiredError, v as toMcpProviderError } from "./service-account-smdPFWNy.js";
32
+ import { A as SCHEDULED_AUTOMATION_SYSTEM_ACTOR, D as taskOutcomeInputSchema, E as automationTitleSchema, F as scheduleIntentSchema, I as weekdaySchema, N as ScheduleIntentError, P as compileScheduleIntent, T as automationInstructionToolSchema, k as eventAutomationTriggerSchema, l as automationListQuerySchema } from "./automation-yffXcZt9.js";
33
33
  import { n as privateTraceResultAttributes } from "./private-trace-result-CE7_lEdq.js";
34
34
  import { streamSimple } from "@earendil-works/pi-ai/compat";
35
35
  import { createGatewayProvider } from "@ai-sdk/gateway";
36
- import { generateText } from "ai";
36
+ import { NoImageGeneratedError, generateImage, generateText } from "ai";
37
37
  import path from "node:path";
38
38
  import { isDeepStrictEqual } from "node:util";
39
- import { z } from "zod";
40
39
  import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
40
+ import { z } from "zod";
41
41
  import { and, asc, count, desc, eq, exists, gt, gte, inArray, isNotNull, isNull, lt, lte, ne, notExists, notInArray, or, sql } from "drizzle-orm";
42
- import { PluginToolInputError, createWebSource, eventMatchFieldSchema, eventReachSchema, eventSchema, eventTriggerText, localDestinationSchema, missingToolAnnotationKeys, ownedObjectAnnotationSchema, sourceSchema, stableEventMatchKey, watchResultSchema } from "@sentry/junior-plugin-api";
42
+ import { PluginToolInputError, createWebSource, eventMatchFieldSchema, eventMatchSchema, eventReachSchema, eventSchema, eventTriggerText, localDestinationSchema, missingToolAnnotationKeys, ownedObjectAnnotationSchema, sourceSchema, stableEventMatchKey, watchResultSchema } from "@sentry/junior-plugin-api";
43
43
  import { alias } from "drizzle-orm/pg-core";
44
44
  import { Agent, estimateContextTokens } from "@earendil-works/pi-agent-core";
45
45
  import fs from "node:fs/promises";
@@ -48,6 +48,7 @@ import http from "node:http";
48
48
  import dns from "node:dns/promises";
49
49
  import net from "node:net";
50
50
  import { SignJWT, importPKCS8 } from "jose";
51
+ import { performance as performance$1 } from "node:perf_hooks";
51
52
  import { createAssistantMessageEventStream, isRetryableAssistantError } from "@earendil-works/pi-ai";
52
53
  import { Bash, defineCommand } from "just-bash";
53
54
  import { structuredPatch } from "diff";
@@ -202,172 +203,6 @@ var SkillSandbox = class {
202
203
  throw new Error("No active skill is loaded. Call loadSkill first or pass skill_name explicitly.");
203
204
  }
204
205
  };
205
- // src/chat/tools/sandbox/file-utils.ts
206
- const MAX_TEXT_CHARS = 6e4;
207
- const RIPGREP_EXCLUDED_GLOBS = ["!**/.git/**", "!**/node_modules/**"];
208
- const DEFAULT_SEARCH_COMMAND_TIMEOUT_MS = 3e4;
209
- const SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules"]);
210
- /** Normalize model-supplied numeric knobs before they reach filesystem tools. */
211
- function positiveInteger$1(value) {
212
- if (typeof value !== "number" || !Number.isFinite(value)) return;
213
- const integer = Math.floor(value);
214
- return integer > 0 ? integer : void 0;
215
- }
216
- /** Compare and slice text in one line-ending space while preserving callers' output rules. */
217
- function normalizeToLf$1(value) {
218
- return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
219
- }
220
- /** Keep tool output inside the model-facing budget with an explicit notice. */
221
- function truncateText$1(value, maxChars = MAX_TEXT_CHARS) {
222
- if (value.length <= maxChars) return {
223
- content: value,
224
- truncated: false
225
- };
226
- const removed = value.length - maxChars;
227
- return {
228
- content: `${value.slice(0, maxChars)}\n\n[output truncated: ${removed} characters removed]`,
229
- truncated: true
230
- };
231
- }
232
- /** Detect model-facing missing paths without swallowing sandbox lifecycle/API failures. */
233
- function isMissingPathError(error) {
234
- if (!error || typeof error !== "object") return false;
235
- const candidate = error;
236
- if (candidate.code === "ENOENT") return true;
237
- return typeof candidate.message === "string" && candidate.message.startsWith("File not found:");
238
- }
239
- /** Reject an expected missing search/list path through the tool-error channel. */
240
- function missingPathSearchResult(params) {
241
- const textPath = params.displayPath ?? params.missingPath ?? params.path;
242
- throw new ToolInputError(`Path not found: ${textPath}`);
243
- }
244
- function escapeRegExp(value) {
245
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
246
- }
247
- function globToRegExp(pattern) {
248
- let source = "";
249
- for (let index = 0; index < pattern.length; index += 1) {
250
- const char = pattern.charAt(index);
251
- const next = pattern[index + 1];
252
- if (char === "*" && next === "*") {
253
- if (pattern[index + 2] === "/") {
254
- source += "(?:.*/)?";
255
- index += 2;
256
- continue;
257
- }
258
- source += ".*";
259
- index += 1;
260
- continue;
261
- }
262
- if (char === "*") {
263
- source += "[^/]*";
264
- continue;
265
- }
266
- if (char === "?") {
267
- source += "[^/]";
268
- continue;
269
- }
270
- source += escapeRegExp(char);
271
- }
272
- return new RegExp(`^${source}$`);
273
- }
274
- function matchesGlob(relativePath, pattern) {
275
- const matcher = globToRegExp(pattern);
276
- if (matcher.test(relativePath)) return true;
277
- if (pattern.startsWith("**/") && matchesGlob(relativePath, pattern.slice(3))) return true;
278
- return !pattern.includes("/") && matcher.test(path.posix.basename(relativePath));
279
- }
280
- /** Keep sandbox filesystem tools scoped to the mounted workspace root. */
281
- function resolveWorkspacePath(input, fallback = ".") {
282
- const requested = (input ?? "").trim() || fallback;
283
- const absolute = requested.startsWith("/") ? requested : path.posix.join(SANDBOX_WORKSPACE_ROOT, requested);
284
- const normalized = path.posix.normalize(absolute);
285
- if (normalized !== SANDBOX_WORKSPACE_ROOT && !normalized.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)) throw new ToolInputError(`Path must stay within ${SANDBOX_WORKSPACE_ROOT}: ${requested}`);
286
- return normalized;
287
- }
288
- /** Anchor ripgrep paths and globs to the requested search root. */
289
- function getRipgrepSearchLocation(root, rootIsDirectory) {
290
- if (rootIsDirectory) return {
291
- cwd: root,
292
- target: "."
293
- };
294
- return {
295
- cwd: path.posix.dirname(root),
296
- target: path.posix.basename(root)
297
- };
298
- }
299
- /** Share bounded workspace traversal across search tools so their skip rules stay aligned. */
300
- async function collectFiles(params) {
301
- const files = [];
302
- let limitReached = false;
303
- let missingPath;
304
- const visit = async (dirPath) => {
305
- if (missingPath) return;
306
- let entries;
307
- try {
308
- entries = (await params.fs.readdir(dirPath)).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
309
- } catch (error) {
310
- if (isMissingPathError(error)) {
311
- missingPath = dirPath;
312
- return;
313
- }
314
- throw error;
315
- }
316
- for (const entry of entries) {
317
- const fullPath = path.posix.join(dirPath, entry);
318
- let stat;
319
- try {
320
- stat = await params.fs.stat(fullPath);
321
- } catch (error) {
322
- if (isMissingPathError(error)) {
323
- missingPath = fullPath;
324
- return;
325
- }
326
- throw error;
327
- }
328
- if (stat.isDirectory()) {
329
- if (!SKIPPED_DIRECTORIES.has(entry)) await visit(fullPath);
330
- if (limitReached) return;
331
- continue;
332
- }
333
- const relativePath = path.posix.relative(params.root, fullPath);
334
- if (!params.pattern || matchesGlob(relativePath, params.pattern)) {
335
- files.push(fullPath);
336
- if (params.limit && files.length >= params.limit) {
337
- limitReached = true;
338
- return;
339
- }
340
- }
341
- }
342
- };
343
- let stat;
344
- try {
345
- stat = await params.fs.stat(params.root);
346
- } catch (error) {
347
- if (isMissingPathError(error)) return {
348
- files,
349
- limitReached: false,
350
- missingPath: params.root,
351
- missingRoot: true
352
- };
353
- throw error;
354
- }
355
- if (!stat.isDirectory()) {
356
- const relativePath = path.posix.basename(params.root);
357
- return {
358
- files: !params.pattern || matchesGlob(relativePath, params.pattern) ? [params.root] : [],
359
- limitReached: false,
360
- missingRoot: false
361
- };
362
- }
363
- await visit(params.root);
364
- return {
365
- files,
366
- limitReached,
367
- missingPath,
368
- missingRoot: missingPath === params.root
369
- };
370
- }
371
206
  // src/chat/repository-instructions.ts
372
207
  const AGENTS_FILENAME = "AGENTS.md";
373
208
  const MAX_REPOSITORY_INSTRUCTIONS_BYTES = 32768;
@@ -382,81 +217,207 @@ const REPOSITORY_SKILLS_HEADER = [
382
217
  ].join("\n");
383
218
  const AGENTS_REPLACEMENT_NOTICE = "These AGENTS.md instructions replace all previously provided AGENTS.md instructions.";
384
219
  const AGENTS_REMOVAL_NOTICE = "The previously provided AGENTS.md instructions no longer apply.";
385
- async function pathExists(fs, target) {
386
- try {
387
- await fs.stat(target);
388
- return true;
389
- } catch (error) {
390
- if (isMissingPathError(error)) return false;
391
- throw error;
392
- }
220
+ /**
221
+ * Read limit for each AGENTS.md and SKILL.md file. It is 3 bytes past the
222
+ * budget: a UTF-8 character has at most 4 bytes, so a character cut at this
223
+ * limit starts after the budget, and the budgeted text is the same as with a
224
+ * full read.
225
+ */
226
+ const FILE_READ_LIMIT_BYTES = 32771;
227
+ /**
228
+ * Print every path and file that the instructions read needs, one record per
229
+ * line. The cwd is the workspace root, and all paths are relative to it.
230
+ *
231
+ * Arguments: the file read limit, then `repos` to read every Git worktree in
232
+ * it or "", then one group for each selected directory. A group holds the
233
+ * directories from the selected directory up to the workspace root, then "".
234
+ * A group stops at its first directory with `.git`.
235
+ *
236
+ * Records: `R <dir>` for a Git worktree in repos/, `G <dir>/.git`, `D <path>`
237
+ * for a directory in a skill root, `E <path>` for another entry in a skill
238
+ * root, and `F <base64> <path>` for an AGENTS.md or SKILL.md file. File
239
+ * tests follow symlinks like `stat -L`. Globs skip dot entries like `ls -1`,
240
+ * which the Vercel Sandbox `readdir` runs.
241
+ */
242
+ const READ_SCRIPT = `
243
+ limit=$1
244
+ repos=$2
245
+ shift 2
246
+
247
+ nl='
248
+ '
249
+ # A glob result is an entry when it exists. Like the lines of \`ls -1\`, a
250
+ # name with a line break does not name an entry.
251
+ listed() {
252
+ case $1 in *"$nl"*) return 1 ;; esac
253
+ [ -e "$1" ] || [ -L "$1" ]
393
254
  }
394
- async function directoryExists(fs, target) {
395
- try {
396
- return (await fs.stat(target)).isDirectory();
397
- } catch (error) {
398
- if (isMissingPathError(error)) return false;
399
- throw error;
400
- }
255
+
256
+ emit_file() {
257
+ [ -f "$1" ] || return 0
258
+ if [ ! -r "$1" ]; then
259
+ printf 'cannot read %s\\n' "$1" >&2
260
+ exit 1
261
+ fi
262
+ printf 'F '
263
+ head -c "$limit" < "$1" | base64 | tr -d '\\n'
264
+ printf ' %s\\n' "$1"
265
+ }
266
+
267
+ emit_skills() {
268
+ for entry in "$1/.agents/skills"/* "$1/.claude/skills"/*; do
269
+ listed "$entry" || continue
270
+ # The first skill root wins for each name. Skip entries that it shadows.
271
+ case $entry in
272
+ "$1/.claude/skills/"*) listed "$1/.agents/skills/\${entry##*/}" && continue ;;
273
+ esac
274
+ if [ -d "$entry" ]; then
275
+ printf 'D %s\\n' "$entry"
276
+ emit_file "$entry/SKILL.md"
277
+ else
278
+ printf 'E %s\\n' "$entry"
279
+ fi
280
+ done
401
281
  }
402
- async function findGitRoot(fs, cwd) {
282
+
283
+ if [ -n "$repos" ]; then
284
+ for repo in "$repos"/*; do
285
+ if listed "$repo" && [ -d "$repo" ] && [ -e "$repo/.git" ]; then
286
+ printf 'R %s\\n' "$repo"
287
+ set -- "$@" "$repo" ""
288
+ fi
289
+ done
290
+ fi
291
+
292
+ found=
293
+ for dir in "$@"; do
294
+ if [ -z "$dir" ]; then
295
+ found=
296
+ elif [ -z "$found" ]; then
297
+ emit_file "$dir/AGENTS.md"
298
+ if [ -e "$dir/.git" ]; then
299
+ printf 'G %s/.git\\n' "$dir"
300
+ emit_skills "$dir"
301
+ found=1
302
+ fi
303
+ fi
304
+ done
305
+ `;
306
+ const BASE64_PATTERN = /^[A-Za-z0-9+/]*={0,2}$/;
307
+ function compareStrings(left, right) {
308
+ return left < right ? -1 : left > right ? 1 : 0;
309
+ }
310
+ /** Directories that the Git root search checks, from cwd up to the workspace root. */
311
+ function gitRootCandidates(cwd) {
312
+ const candidates = [];
403
313
  let current = cwd;
404
314
  for (;;) {
405
- if (await pathExists(fs, path.posix.join(current, ".git"))) return current;
406
- if (current === SANDBOX_WORKSPACE_ROOT) return;
315
+ candidates.push(current);
316
+ if (current === SANDBOX_WORKSPACE_ROOT) return candidates;
407
317
  const parent = path.posix.dirname(current);
408
- if (parent === current || parent !== SANDBOX_WORKSPACE_ROOT && !parent.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)) return;
318
+ if (parent === current || parent !== SANDBOX_WORKSPACE_ROOT && !parent.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)) return candidates;
409
319
  current = parent;
410
320
  }
411
321
  }
412
- /** Return Git worktrees under repos/, sorted for stable instruction capture. */
413
- async function listRepositoryDirectories(fs) {
414
- if (!await directoryExists(fs, SANDBOX_REPOS_ROOT)) return [];
415
- const entries = await fs.readdir(SANDBOX_REPOS_ROOT);
416
- const repositories = [];
417
- for (const entry of entries) {
418
- const candidate = path.posix.join(SANDBOX_REPOS_ROOT, entry);
419
- if (await directoryExists(fs, candidate) && await pathExists(fs, path.posix.join(candidate, ".git"))) repositories.push(candidate);
322
+ function findGitRoot(tree, cwd) {
323
+ return gitRootCandidates(cwd).find((directory) => tree.gitMarkers.has(path.posix.join(directory, ".git")));
324
+ }
325
+ function invalidOutput() {
326
+ return /* @__PURE__ */ new Error("Repository instructions read printed invalid output");
327
+ }
328
+ function parseRepositoryTree(stdout) {
329
+ const tree = {
330
+ entries: /* @__PURE__ */ new Map(),
331
+ files: /* @__PURE__ */ new Map(),
332
+ gitMarkers: /* @__PURE__ */ new Set(),
333
+ repositories: []
334
+ };
335
+ const lines = stdout.split("\n");
336
+ if (lines.pop() !== "") throw invalidOutput();
337
+ for (const line of lines) {
338
+ const kind = line.slice(0, 2);
339
+ let target = line.slice(2);
340
+ let content = "";
341
+ if (kind === "F ") {
342
+ const separator = target.indexOf(" ");
343
+ if (separator < 0) throw invalidOutput();
344
+ content = target.slice(0, separator);
345
+ target = target.slice(separator + 1);
346
+ if (!BASE64_PATTERN.test(content)) throw invalidOutput();
347
+ }
348
+ if (!target) throw invalidOutput();
349
+ const absolute = path.posix.join(SANDBOX_WORKSPACE_ROOT, target);
350
+ switch (kind) {
351
+ case "R ":
352
+ tree.repositories.push(absolute);
353
+ break;
354
+ case "G ":
355
+ tree.gitMarkers.add(absolute);
356
+ break;
357
+ case "D ":
358
+ case "E ": {
359
+ const root = path.posix.dirname(absolute);
360
+ const entries = tree.entries.get(root) ?? /* @__PURE__ */ new Map();
361
+ entries.set(path.posix.basename(absolute), kind === "D ");
362
+ tree.entries.set(root, entries);
363
+ break;
364
+ }
365
+ case "F ":
366
+ tree.files.set(absolute, Buffer.from(content, "base64").toString("utf8"));
367
+ break;
368
+ default: throw invalidOutput();
369
+ }
420
370
  }
421
- return repositories.sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
371
+ tree.repositories.sort(compareStrings);
372
+ return tree;
422
373
  }
423
- async function readInstructionFile(fs, filePath) {
424
- try {
425
- const content = await fs.readFile(filePath, { encoding: "utf8" });
426
- return content.trim() ? content : void 0;
427
- } catch (error) {
428
- if (isMissingPathError(error)) return;
429
- throw error;
430
- }
374
+ /** Read what the selected directories need with one sandbox command. */
375
+ async function readRepositoryTree(args) {
376
+ const relative = (target) => path.posix.relative(SANDBOX_WORKSPACE_ROOT, target) || ".";
377
+ const result = await args.runCommand({
378
+ cmd: "sh",
379
+ args: [
380
+ "-c",
381
+ READ_SCRIPT,
382
+ "sh",
383
+ String(FILE_READ_LIMIT_BYTES),
384
+ args.discover ? relative(SANDBOX_REPOS_ROOT) : "",
385
+ ...args.directories.flatMap((directory) => [...gitRootCandidates(directory).map(relative), ""])
386
+ ],
387
+ cwd: SANDBOX_WORKSPACE_ROOT
388
+ });
389
+ if (result.exitCode !== 0) throw new Error(`Repository instructions read failed with exit code ${result.exitCode}: ${result.stderr.trim()}`);
390
+ return parseRepositoryTree(result.stdout);
431
391
  }
432
- async function readOptionalDirectory(fs, directory) {
433
- try {
434
- return await fs.readdir(directory);
435
- } catch (error) {
436
- if (isMissingPathError(error)) return [];
437
- throw error;
438
- }
392
+ function readInstructionFile(tree, filePath) {
393
+ const content = tree.files.get(filePath);
394
+ return content?.trim() ? content : void 0;
439
395
  }
440
396
  /** Read model-invocable skills from the repository skill directories. */
441
- async function readRepositorySkills(fs, gitRoot) {
442
- const roots = REPOSITORY_SKILL_ROOTS.map((root) => path.posix.join(gitRoot, root));
443
- const listings = await Promise.all(roots.map(async (root) => await readOptionalDirectory(fs, root)));
397
+ function readRepositorySkills(tree, gitRoot) {
444
398
  const candidates = /* @__PURE__ */ new Map();
445
- listings.forEach((entries, index) => {
446
- for (const entry of [...entries].sort()) if (!candidates.has(entry)) candidates.set(entry, path.posix.join(roots[index], entry));
447
- });
448
- return (await Promise.all([...candidates].map(async ([name, skillDirectory]) => {
449
- if (!await directoryExists(fs, skillDirectory)) return;
450
- const filePath = path.posix.join(skillDirectory, "SKILL.md");
451
- const raw = await readInstructionFile(fs, filePath);
399
+ for (const root of REPOSITORY_SKILL_ROOTS) {
400
+ const rootPath = path.posix.join(gitRoot, root);
401
+ const entries = [...tree.entries.get(rootPath) ?? []].sort(([left], [right]) => compareStrings(left, right));
402
+ for (const [name, directory] of entries) if (!candidates.has(name)) candidates.set(name, {
403
+ directory,
404
+ path: path.posix.join(rootPath, name)
405
+ });
406
+ }
407
+ const skills = [];
408
+ for (const [name, candidate] of candidates) {
409
+ if (!candidate.directory) continue;
410
+ const filePath = path.posix.join(candidate.path, "SKILL.md");
411
+ const raw = readInstructionFile(tree, filePath);
452
412
  const parsed = raw ? parseSkillFile(raw, name) : void 0;
453
- if (!parsed?.ok || parsed.skill.disableModelInvocation) return;
454
- return {
413
+ if (!parsed?.ok || parsed.skill.disableModelInvocation) continue;
414
+ skills.push({
455
415
  description: parsed.skill.description.replace(/\s+/g, " ").trim(),
456
416
  name,
457
417
  path: filePath
458
- };
459
- }))).filter((skill) => Boolean(skill)).sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
418
+ });
419
+ }
420
+ return skills.sort((left, right) => compareStrings(left.name, right.name));
460
421
  }
461
422
  function sourceBytes(sources) {
462
423
  return sources.reduce((total, source) => total + Buffer.byteLength(source.content, "utf8"), 0);
@@ -465,7 +426,7 @@ function skillListBytes(skills) {
465
426
  return skills.reduce((total, skill) => total + repositorySkillBytes(skill), 0);
466
427
  }
467
428
  /** Read root-to-cwd AGENTS.md files within the byte budget. */
468
- async function readAgentsSources(args) {
429
+ function readAgentsSources(args) {
469
430
  const directories = [];
470
431
  let current = args.cwd;
471
432
  for (;;) {
@@ -479,7 +440,7 @@ async function readAgentsSources(args) {
479
440
  for (const directory of directories) {
480
441
  if (remaining <= 0) break;
481
442
  const filePath = path.posix.join(directory, AGENTS_FILENAME);
482
- const content = await readInstructionFile(args.fs, filePath);
443
+ const content = readInstructionFile(args.tree, filePath);
483
444
  if (!content) continue;
484
445
  const bytes = Buffer.from(content, "utf8");
485
446
  const bounded = bytes.length <= remaining ? content : bytes.subarray(0, remaining).toString("utf8");
@@ -492,11 +453,11 @@ async function readAgentsSources(args) {
492
453
  return sources;
493
454
  }
494
455
  /** Read repository skills that fit the byte budget, keeping each entry whole. */
495
- async function readRepositorySkillsWithinBudget(args) {
456
+ function readRepositorySkillsWithinBudget(args) {
496
457
  const skills = [];
497
458
  let remaining = args.maxBytes;
498
459
  if (remaining <= 0) return skills;
499
- for (const skill of await readRepositorySkills(args.fs, args.gitRoot)) {
460
+ for (const skill of readRepositorySkills(args.tree, args.gitRoot)) {
500
461
  const bytes = repositorySkillBytes(skill);
501
462
  if (bytes > remaining) break;
502
463
  skills.push(skill);
@@ -556,19 +517,33 @@ function mergeRepositoryInstructions(bundles) {
556
517
  text
557
518
  };
558
519
  }
559
- /** Resolve AGENTS.md instructions for each selected repository directory. */
560
- async function resolveRepositoryInstructionsForDirectories(args) {
561
- const uniqueDirectories = [...new Set(args.directories.filter((directory) => directory === SANDBOX_WORKSPACE_ROOT || directory.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)))].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
520
+ /**
521
+ * Read AGENTS.md instructions and repository skills for the selected
522
+ * directories with one sandbox command. Without directories, use every Git
523
+ * worktree under repos/.
524
+ */
525
+ async function readRepositoryInstructions(args) {
526
+ const selected = [...new Set((args.directories ?? []).filter((directory) => directory === SANDBOX_WORKSPACE_ROOT || directory.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)))].sort(compareStrings);
527
+ if (args.directories && selected.length === 0) return {
528
+ directories: args.directories,
529
+ instructions: void 0
530
+ };
531
+ const tree = await readRepositoryTree({
532
+ directories: selected,
533
+ discover: !args.directories,
534
+ runCommand: args.runCommand
535
+ });
536
+ const cwds = args.directories ? selected : tree.repositories;
562
537
  let remaining = MAX_REPOSITORY_INSTRUCTIONS_BYTES;
563
538
  const repositories = [];
564
- for (const directory of uniqueDirectories) {
565
- const gitRoot = await findGitRoot(args.fs, directory);
539
+ for (const directory of cwds) {
540
+ const gitRoot = findGitRoot(tree, directory);
566
541
  if (!gitRoot) continue;
567
- const sources = await readAgentsSources({
542
+ const sources = readAgentsSources({
568
543
  cwd: directory,
569
- fs: args.fs,
570
544
  gitRoot,
571
- maxBytes: remaining
545
+ maxBytes: remaining,
546
+ tree
572
547
  });
573
548
  remaining -= sourceBytes(sources);
574
549
  repositories.push({
@@ -579,16 +554,19 @@ async function resolveRepositoryInstructionsForDirectories(args) {
579
554
  }
580
555
  const bundles = [];
581
556
  for (const repository of repositories) {
582
- const skills = await readRepositorySkillsWithinBudget({
583
- fs: args.fs,
557
+ const skills = readRepositorySkillsWithinBudget({
584
558
  gitRoot: repository.gitRoot,
585
- maxBytes: remaining
559
+ maxBytes: remaining,
560
+ tree
586
561
  });
587
562
  remaining -= skillListBytes(skills);
588
563
  const bundle = buildRepositoryInstructions(repository.directory, repository.sources, skills);
589
564
  if (bundle) bundles.push(bundle);
590
565
  }
591
- return mergeRepositoryInstructions(bundles);
566
+ return {
567
+ directories: args.directories ?? cwds,
568
+ instructions: mergeRepositoryInstructions(bundles)
569
+ };
592
570
  }
593
571
  /** Render repository instructions with Codex's exact model-visible wrapper. */
594
572
  function renderAgentsInstructions(args) {
@@ -725,6 +703,7 @@ function buildChatEndAttributes(message, mode) {
725
703
  Object.assign(attributes, extractGenAiUsageAttributes(message));
726
704
  if (message.stopReason) attributes["gen_ai.response.finish_reasons"] = [normalizeGenAiFinishReason(message.stopReason)];
727
705
  if (message.model) attributes["gen_ai.response.model"] = message.model;
706
+ if (message.responseId) attributes["gen_ai.response.id"] = message.responseId;
728
707
  if (message.stopReason === "error") {
729
708
  const providerError = createProviderError(message.errorMessage ?? "");
730
709
  Object.assign(attributes, getProviderErrorAttributes(providerError));
@@ -761,6 +740,22 @@ function createResponseTiming(requestStartedAtMs) {
761
740
  }
762
741
  };
763
742
  }
743
+ function traceResponseHeaders(span, options) {
744
+ const eventLoopStart = performance$1.eventLoopUtilization();
745
+ const onResponse = options?.onResponse;
746
+ return {
747
+ ...options,
748
+ onResponse: async (response, model) => {
749
+ try {
750
+ const vercelId = response.headers["x-vercel-id"];
751
+ if (vercelId) span.setAttribute("app.ai.gateway.vercel_id", vercelId);
752
+ const { utilization } = performance$1.eventLoopUtilization(eventLoopStart);
753
+ span.setAttribute("app.ai.event_loop.utilization", Math.round(utilization * 1e3) / 1e3);
754
+ } catch {}
755
+ await onResponse?.(response, model);
756
+ }
757
+ };
758
+ }
764
759
  function relayStream(source, onEvent) {
765
760
  const relayed = createAssistantMessageEventStream();
766
761
  const finished = (async () => {
@@ -801,7 +796,7 @@ function createTracedStreamFn(baseOrOptions = streamSimple) {
801
796
  });
802
797
  try {
803
798
  const requestStartedAtMs = Date.now();
804
- const stream = await withActiveSpan(span, () => Promise.resolve(base(model, context, options)));
799
+ const stream = await withActiveSpan(span, () => Promise.resolve(base(model, context, traceResponseHeaders(span, options))));
805
800
  const responseTiming = createResponseTiming(requestStartedAtMs);
806
801
  const relay = relayStream(stream, responseTiming.observe);
807
802
  relay.finished.then(() => stream.result()).then((finalMessage) => {
@@ -847,7 +842,7 @@ function shouldEmitDevAgentTrace() {
847
842
  return process.env.NODE_ENV === "development";
848
843
  }
849
844
  // src/chat/agent-invocations/blocked-report.ts
850
- /** Blocked report of a child agent. See "Delivery rules" in AGENTS.md. */
845
+ /** Blocked report of a child agent. */
851
846
  /** Model-facing tool that ends a child agent run as blocked. */
852
847
  const REPORT_BLOCKED_TOOL_NAME = "reportBlocked";
853
848
  const blockedReportSchema = z.object({ missing: z.string().trim().min(1) });
@@ -2217,8 +2212,8 @@ function buildTurnContextPrompt(params) {
2217
2212
  /**
2218
2213
  * Conversation notes: short facts that core attaches to a Conversation without
2219
2214
  * starting a Turn. The next Turn shows them to the model in its runtime turn
2220
- * context. A failed Turn also becomes a note for the next Turn. See
2221
- * "Conversation notes" in AGENTS.md.
2215
+ * context. A failed Turn also becomes a note for the next Turn. A note is
2216
+ * context, never user intent, and only core adds one.
2222
2217
  */
2223
2218
  /**
2224
2219
  * Tell the next Turn that a Turn failed. Pi drops a failed assistant message
@@ -3961,9 +3956,10 @@ function createSandboxRuntime(options) {
3961
3956
  withSpan: withSandboxSpan
3962
3957
  });
3963
3958
  };
3959
+ const ensureDocker = async (targetSandbox) => await withSandboxSpan("sandbox.docker.ensure", "sandbox.docker.ensure", {}, async () => await ensureDockerDaemon(targetSandbox));
3964
3960
  const prepareSandbox = async (targetSandbox) => {
3965
3961
  await syncSkills(targetSandbox);
3966
- await withSandboxSpan("sandbox.docker.ensure", "sandbox.docker.ensure", {}, async () => await ensureDockerDaemon(targetSandbox));
3962
+ await ensureDocker(targetSandbox);
3967
3963
  await options.onSandboxPrepare?.(targetSandbox);
3968
3964
  };
3969
3965
  const applyNetworkPolicy = async (targetSandbox, traceHeaders) => {
@@ -4187,10 +4183,17 @@ function createSandboxRuntime(options) {
4187
4183
  }
4188
4184
  throw new Error("sandbox restore failed", { cause: error });
4189
4185
  }
4190
- let networkPolicyKey;
4191
4186
  try {
4192
- networkPolicyKey = await applyNetworkPolicy(hintedSandbox);
4193
- await prepareSandbox(hintedSandbox);
4187
+ const steps = await Promise.allSettled([
4188
+ applyNetworkPolicy(hintedSandbox),
4189
+ syncSkills(hintedSandbox),
4190
+ ensureDocker(hintedSandbox)
4191
+ ]);
4192
+ const failures = steps.flatMap((step) => step.status === "rejected" ? [step.reason] : []);
4193
+ if (failures.length > 0) throw failures.find((error) => getSandboxProvider().errors.isUnavailable(error)) ?? failures[0];
4194
+ const [policy] = steps;
4195
+ const networkPolicyKey = policy.status === "fulfilled" ? policy.value : void 0;
4196
+ await options.onSandboxPrepare?.(hintedSandbox);
4194
4197
  dependencyProfileHash = ref.profileHash ?? dependencyProfileHash;
4195
4198
  await persistSandboxRef({
4196
4199
  ...ref,
@@ -4212,17 +4215,26 @@ function createSandboxRuntime(options) {
4212
4215
  const cachedSandbox = await tryReuseCachedSandbox();
4213
4216
  if (cachedSandbox) {
4214
4217
  setSpanAttributes({ "app.sandbox.acquire.outcome": "memory" });
4215
- return cachedSandbox;
4218
+ return {
4219
+ session: cachedSandbox,
4220
+ fresh: false
4221
+ };
4216
4222
  }
4217
4223
  signal?.throwIfAborted();
4218
4224
  const hintedSandbox = await tryRestoreHintedSandbox(signal);
4219
4225
  if (hintedSandbox) {
4220
4226
  setSpanAttributes({ "app.sandbox.acquire.outcome": "resumed" });
4221
- return hintedSandbox;
4227
+ return {
4228
+ session: hintedSandbox,
4229
+ fresh: true
4230
+ };
4222
4231
  }
4223
4232
  const createdSandbox = await createFreshSandbox(signal);
4224
4233
  setSpanAttributes({ "app.sandbox.acquire.outcome": "created" });
4225
- return createdSandbox;
4234
+ return {
4235
+ session: createdSandbox,
4236
+ fresh: true
4237
+ };
4226
4238
  });
4227
4239
  };
4228
4240
  const getOrAcquireSandbox = async (signal) => {
@@ -4378,10 +4390,12 @@ function createSandboxRuntime(options) {
4378
4390
  };
4379
4391
  };
4380
4392
  const ensureReadySandbox = async (signal) => {
4381
- const session = await getOrAcquireSandbox(signal);
4382
- signal?.throwIfAborted();
4383
- await probeSession(session);
4393
+ const { session, fresh } = await getOrAcquireSandbox(signal);
4384
4394
  signal?.throwIfAborted();
4395
+ if (!fresh) {
4396
+ await probeSession(session);
4397
+ signal?.throwIfAborted();
4398
+ }
4385
4399
  await extendKeepAlive(session);
4386
4400
  startKeepAlive(session);
4387
4401
  return session;
@@ -4410,42 +4424,208 @@ function createSandboxRuntime(options) {
4410
4424
  } catch (error) {
4411
4425
  await stopSession(candidate?.session);
4412
4426
  throw error;
4413
- } finally {
4414
- signal?.removeEventListener("abort", stopSwitching);
4427
+ } finally {
4428
+ signal?.removeEventListener("abort", stopSwitching);
4429
+ }
4430
+ const previous = activeSandbox;
4431
+ if (keepAliveTimer) {
4432
+ clearTimeout(keepAliveTimer);
4433
+ keepAliveTimer = void 0;
4434
+ }
4435
+ activeWorkspace = candidate.workspace;
4436
+ dependencyProfileHash = candidate.profileHash;
4437
+ activeSandbox = {
4438
+ session: candidate.session,
4439
+ networkPolicyKey: candidate.networkPolicyKey
4440
+ };
4441
+ startKeepAlive(candidate.session);
4442
+ await stopSession(previous?.session);
4443
+ },
4444
+ async acquire(signal) {
4445
+ return (await getOrAcquireSandbox(signal)).session;
4446
+ },
4447
+ async tools(signal) {
4448
+ return createToolExecutors(await ensureReadySandbox(signal));
4449
+ },
4450
+ async refreshNetworkPolicy(traceHeaders) {
4451
+ const active = activeSandbox;
4452
+ if (!active) return;
4453
+ await applyNetworkPolicy(active.session, traceHeaders);
4454
+ },
4455
+ close() {
4456
+ closed = true;
4457
+ if (keepAliveTimer) {
4458
+ clearTimeout(keepAliveTimer);
4459
+ keepAliveTimer = void 0;
4460
+ }
4461
+ }
4462
+ };
4463
+ }
4464
+ // src/chat/tools/sandbox/file-utils.ts
4465
+ const MAX_TEXT_CHARS = 6e4;
4466
+ const RIPGREP_EXCLUDED_GLOBS = ["!**/.git/**", "!**/node_modules/**"];
4467
+ const DEFAULT_SEARCH_COMMAND_TIMEOUT_MS = 3e4;
4468
+ const SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules"]);
4469
+ /** Normalize model-supplied numeric knobs before they reach filesystem tools. */
4470
+ function positiveInteger$1(value) {
4471
+ if (typeof value !== "number" || !Number.isFinite(value)) return;
4472
+ const integer = Math.floor(value);
4473
+ return integer > 0 ? integer : void 0;
4474
+ }
4475
+ /** Compare and slice text in one line-ending space while preserving callers' output rules. */
4476
+ function normalizeToLf$1(value) {
4477
+ return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
4478
+ }
4479
+ /** Keep tool output inside the model-facing budget with an explicit notice. */
4480
+ function truncateText$1(value, maxChars = MAX_TEXT_CHARS) {
4481
+ if (value.length <= maxChars) return {
4482
+ content: value,
4483
+ truncated: false
4484
+ };
4485
+ const removed = value.length - maxChars;
4486
+ return {
4487
+ content: `${value.slice(0, maxChars)}\n\n[output truncated: ${removed} characters removed]`,
4488
+ truncated: true
4489
+ };
4490
+ }
4491
+ /** Detect model-facing missing paths without swallowing sandbox lifecycle/API failures. */
4492
+ function isMissingPathError(error) {
4493
+ if (!error || typeof error !== "object") return false;
4494
+ const candidate = error;
4495
+ if (candidate.code === "ENOENT") return true;
4496
+ return typeof candidate.message === "string" && candidate.message.startsWith("File not found:");
4497
+ }
4498
+ /** Reject an expected missing search/list path through the tool-error channel. */
4499
+ function missingPathSearchResult(params) {
4500
+ const textPath = params.displayPath ?? params.missingPath ?? params.path;
4501
+ throw new ToolInputError(`Path not found: ${textPath}`);
4502
+ }
4503
+ function escapeRegExp(value) {
4504
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4505
+ }
4506
+ function globToRegExp(pattern) {
4507
+ let source = "";
4508
+ for (let index = 0; index < pattern.length; index += 1) {
4509
+ const char = pattern.charAt(index);
4510
+ const next = pattern[index + 1];
4511
+ if (char === "*" && next === "*") {
4512
+ if (pattern[index + 2] === "/") {
4513
+ source += "(?:.*/)?";
4514
+ index += 2;
4515
+ continue;
4516
+ }
4517
+ source += ".*";
4518
+ index += 1;
4519
+ continue;
4520
+ }
4521
+ if (char === "*") {
4522
+ source += "[^/]*";
4523
+ continue;
4524
+ }
4525
+ if (char === "?") {
4526
+ source += "[^/]";
4527
+ continue;
4528
+ }
4529
+ source += escapeRegExp(char);
4530
+ }
4531
+ return new RegExp(`^${source}$`);
4532
+ }
4533
+ function matchesGlob(relativePath, pattern) {
4534
+ const matcher = globToRegExp(pattern);
4535
+ if (matcher.test(relativePath)) return true;
4536
+ if (pattern.startsWith("**/") && matchesGlob(relativePath, pattern.slice(3))) return true;
4537
+ return !pattern.includes("/") && matcher.test(path.posix.basename(relativePath));
4538
+ }
4539
+ /** Keep sandbox filesystem tools scoped to the mounted workspace root. */
4540
+ function resolveWorkspacePath(input, fallback = ".") {
4541
+ const requested = (input ?? "").trim() || fallback;
4542
+ const absolute = requested.startsWith("/") ? requested : path.posix.join(SANDBOX_WORKSPACE_ROOT, requested);
4543
+ const normalized = path.posix.normalize(absolute);
4544
+ if (normalized !== SANDBOX_WORKSPACE_ROOT && !normalized.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)) throw new ToolInputError(`Path must stay within ${SANDBOX_WORKSPACE_ROOT}: ${requested}`);
4545
+ return normalized;
4546
+ }
4547
+ /** Anchor ripgrep paths and globs to the requested search root. */
4548
+ function getRipgrepSearchLocation(root, rootIsDirectory) {
4549
+ if (rootIsDirectory) return {
4550
+ cwd: root,
4551
+ target: "."
4552
+ };
4553
+ return {
4554
+ cwd: path.posix.dirname(root),
4555
+ target: path.posix.basename(root)
4556
+ };
4557
+ }
4558
+ /** Share bounded workspace traversal across search tools so their skip rules stay aligned. */
4559
+ async function collectFiles(params) {
4560
+ const files = [];
4561
+ let limitReached = false;
4562
+ let missingPath;
4563
+ const visit = async (dirPath) => {
4564
+ if (missingPath) return;
4565
+ let entries;
4566
+ try {
4567
+ entries = (await params.fs.readdir(dirPath)).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
4568
+ } catch (error) {
4569
+ if (isMissingPathError(error)) {
4570
+ missingPath = dirPath;
4571
+ return;
4572
+ }
4573
+ throw error;
4574
+ }
4575
+ for (const entry of entries) {
4576
+ const fullPath = path.posix.join(dirPath, entry);
4577
+ let stat;
4578
+ try {
4579
+ stat = await params.fs.stat(fullPath);
4580
+ } catch (error) {
4581
+ if (isMissingPathError(error)) {
4582
+ missingPath = fullPath;
4583
+ return;
4584
+ }
4585
+ throw error;
4415
4586
  }
4416
- const previous = activeSandbox;
4417
- if (keepAliveTimer) {
4418
- clearTimeout(keepAliveTimer);
4419
- keepAliveTimer = void 0;
4587
+ if (stat.isDirectory()) {
4588
+ if (!SKIPPED_DIRECTORIES.has(entry)) await visit(fullPath);
4589
+ if (limitReached) return;
4590
+ continue;
4420
4591
  }
4421
- activeWorkspace = candidate.workspace;
4422
- dependencyProfileHash = candidate.profileHash;
4423
- activeSandbox = {
4424
- session: candidate.session,
4425
- networkPolicyKey: candidate.networkPolicyKey
4426
- };
4427
- startKeepAlive(candidate.session);
4428
- await stopSession(previous?.session);
4429
- },
4430
- async acquire(signal) {
4431
- return await getOrAcquireSandbox(signal);
4432
- },
4433
- async tools(signal) {
4434
- return createToolExecutors(await ensureReadySandbox(signal));
4435
- },
4436
- async refreshNetworkPolicy(traceHeaders) {
4437
- const active = activeSandbox;
4438
- if (!active) return;
4439
- await applyNetworkPolicy(active.session, traceHeaders);
4440
- },
4441
- close() {
4442
- closed = true;
4443
- if (keepAliveTimer) {
4444
- clearTimeout(keepAliveTimer);
4445
- keepAliveTimer = void 0;
4592
+ const relativePath = path.posix.relative(params.root, fullPath);
4593
+ if (!params.pattern || matchesGlob(relativePath, params.pattern)) {
4594
+ files.push(fullPath);
4595
+ if (params.limit && files.length >= params.limit) {
4596
+ limitReached = true;
4597
+ return;
4598
+ }
4446
4599
  }
4447
4600
  }
4448
4601
  };
4602
+ let stat;
4603
+ try {
4604
+ stat = await params.fs.stat(params.root);
4605
+ } catch (error) {
4606
+ if (isMissingPathError(error)) return {
4607
+ files,
4608
+ limitReached: false,
4609
+ missingPath: params.root,
4610
+ missingRoot: true
4611
+ };
4612
+ throw error;
4613
+ }
4614
+ if (!stat.isDirectory()) {
4615
+ const relativePath = path.posix.basename(params.root);
4616
+ return {
4617
+ files: !params.pattern || matchesGlob(relativePath, params.pattern) ? [params.root] : [],
4618
+ limitReached: false,
4619
+ missingRoot: false
4620
+ };
4621
+ }
4622
+ await visit(params.root);
4623
+ return {
4624
+ files,
4625
+ limitReached,
4626
+ missingPath,
4627
+ missingRoot: missingPath === params.root
4628
+ };
4449
4629
  }
4450
4630
  // src/chat/tools/sandbox/text-edits.ts
4451
4631
  const MAX_DIFF_LINE_CHARS = 4e3;
@@ -5706,13 +5886,12 @@ function createSandbox(options) {
5706
5886
  return {
5707
5887
  async captureRepositoryInstructions() {
5708
5888
  if (!runtime.sandboxRef()) return;
5709
- const { fs } = await runtime.tools();
5889
+ const { runCommand } = await runtime.tools();
5710
5890
  const workspaceDirectories = activeWorkspace?.repos.map((repo) => tryWorkspaceRepoCheckoutPath(repo.repo)).filter((checkoutPath) => Boolean(checkoutPath)).map((checkoutPath) => `${SANDBOX_WORKSPACE_ROOT}/${checkoutPath}`) ?? [];
5711
5891
  return await withSandboxSpan("sandbox.repository_instructions.read", "sandbox.repository_instructions.read", {}, async (setSpanAttributes) => {
5712
- const directories = workspaceDirectories.length > 0 ? workspaceDirectories : await listRepositoryDirectories(fs);
5713
- const instructions = await resolveRepositoryInstructionsForDirectories({
5714
- directories,
5715
- fs
5892
+ const { directories, instructions } = await readRepositoryInstructions({
5893
+ ...workspaceDirectories.length > 0 ? { directories: workspaceDirectories } : void 0,
5894
+ runCommand
5716
5895
  });
5717
5896
  setSpanAttributes({
5718
5897
  "app.repository_instructions.repo_count": directories.length,
@@ -6647,7 +6826,6 @@ async function enrichImagePrompt(rawPrompt) {
6647
6826
  async function shrinkImage(image, maxBytes) {
6648
6827
  const { default: sharp } = await import("sharp");
6649
6828
  const { hasAlpha } = await sharp(image.data).metadata();
6650
- const mimeType = hasAlpha ? "image/webp" : "image/jpeg";
6651
6829
  for (const edge of RESIZE_LONGEST_EDGES) {
6652
6830
  const resized = sharp(image.data).rotate().resize({
6653
6831
  width: edge,
@@ -6659,8 +6837,7 @@ async function shrinkImage(image, maxBytes) {
6659
6837
  if (data.byteLength <= maxBytes) return {
6660
6838
  image: {
6661
6839
  path: image.path,
6662
- data,
6663
- mimeType
6840
+ data
6664
6841
  },
6665
6842
  width: info.width,
6666
6843
  height: info.height
@@ -6710,17 +6887,6 @@ function extensionForMediaType$1(mediaType) {
6710
6887
  if (mediaType === "image/gif") return "gif";
6711
6888
  return "bin";
6712
6889
  }
6713
- function parseImageGenerationError(status, body, model) {
6714
- if (!body) return `image generation failed: ${status}`;
6715
- try {
6716
- const message = JSON.parse(body).error?.message?.trim();
6717
- if (!message) return `image generation failed: ${status} ${body}`;
6718
- if (message.includes("not an image model")) return `image generation failed: configured model "${model}" is not an image generation model. Set AI_IMAGE_MODEL to a compatible image model.`;
6719
- return `image generation failed: ${status} ${message}`;
6720
- } catch {
6721
- return `image generation failed: ${status} ${body}`;
6722
- }
6723
- }
6724
6890
  /** Create the image tool with sandbox artifact guidance matched to file-send capability. */
6725
6891
  function createImageGenerateTool(hooks, options, deps = {}) {
6726
6892
  return zodTool({
@@ -6737,8 +6903,10 @@ function createImageGenerateTool(hooks, options, deps = {}) {
6737
6903
  }),
6738
6904
  outputSchema: imageGenerateOutputSchema,
6739
6905
  execute: async ({ prompt, images = [] }) => {
6740
- const fetchImpl = deps.fetch ?? fetch;
6741
- const apiKey = await getGatewayApiKey();
6906
+ const gateway = createGatewayProvider({
6907
+ apiKey: await getGatewayApiKey(),
6908
+ ...deps.fetch ? { fetch: deps.fetch } : void 0
6909
+ });
6742
6910
  const model = options.modelId;
6743
6911
  const { images: inputImages, resized } = await fitImagesToRequest(await Promise.all(images.map(async (path) => ({
6744
6912
  path,
@@ -6749,58 +6917,26 @@ function createImageGenerateTool(hooks, options, deps = {}) {
6749
6917
  resize_notice: "Some input images were too large for the image model, so they were resized first. Tell the user which images were resized."
6750
6918
  } : {};
6751
6919
  const enrichedPrompt = inputImages.length > 0 ? prompt : await enrichImagePrompt(prompt);
6752
- const content = inputImages.length > 0 ? [{
6753
- type: "text",
6754
- text: enrichedPrompt
6755
- }, ...inputImages.map((image) => ({
6756
- type: "image_url",
6757
- image_url: { url: `data:${image.mimeType};base64,${image.data.toString("base64")}` }
6758
- }))] : enrichedPrompt;
6759
- const response = await fetchImpl("https://ai-gateway.vercel.sh/v1/chat/completions", {
6760
- method: "POST",
6761
- headers: {
6762
- "content-type": "application/json",
6763
- authorization: `Bearer ${apiKey}`
6764
- },
6765
- body: JSON.stringify({
6766
- model,
6767
- messages: [{
6768
- role: "user",
6769
- content
6770
- }],
6771
- modalities: ["image"]
6772
- })
6773
- });
6774
- if (!response.ok) {
6775
- const text = await response.text();
6776
- throw new Error(parseImageGenerationError(response.status, text, model));
6777
- }
6778
- const payload = await response.json();
6779
- const uploads = [];
6780
- const generatedImages = payload.choices?.[0]?.message?.images ?? [];
6781
- for (const [index, image] of generatedImages.entries()) {
6782
- let bytes = null;
6783
- let mimeType = "image/png";
6784
- const url = image.image_url?.url;
6785
- if (typeof url === "string" && url.startsWith("data:")) {
6786
- const match = url.match(/^data:([^;,]+);base64,(.+)$/);
6787
- if (!match) continue;
6788
- mimeType = match[1] ?? mimeType;
6789
- bytes = Buffer.from(match[2] ?? "", "base64");
6790
- } else if (typeof url === "string" && url.length > 0) {
6791
- const fetched = await fetchImpl(url);
6792
- if (!fetched.ok) continue;
6793
- mimeType = fetched.headers.get("content-type") ?? mimeType;
6794
- bytes = Buffer.from(await fetched.arrayBuffer());
6795
- }
6796
- if (!bytes) continue;
6797
- const extension = extensionForMediaType$1(mimeType);
6798
- uploads.push({
6799
- data: bytes,
6800
- filename: `generated-image-${Date.now()}-${index + 1}.${extension}`,
6801
- mimeType
6802
- });
6920
+ let generatedImages;
6921
+ try {
6922
+ ({images: generatedImages} = await generateImage({
6923
+ model: gateway.imageModel(model),
6924
+ prompt: inputImages.length > 0 ? {
6925
+ text: enrichedPrompt,
6926
+ images: inputImages.map((image) => image.data)
6927
+ } : enrichedPrompt,
6928
+ n: 1
6929
+ }));
6930
+ } catch (error) {
6931
+ if (NoImageGeneratedError.isInstance(error)) generatedImages = [];
6932
+ else if (error instanceof Error && error.message.includes("not an image model")) throw new Error(`image generation failed: configured model "${model}" is not an image generation model. Set AI_IMAGE_MODEL to an AI Gateway image model.`);
6933
+ else throw error;
6803
6934
  }
6935
+ const uploads = generatedImages.map((image, index) => ({
6936
+ data: Buffer.from(image.uint8Array),
6937
+ filename: `generated-image-${Date.now()}-${index + 1}.${extensionForMediaType$1(image.mediaType)}`,
6938
+ mimeType: image.mediaType
6939
+ }));
6804
6940
  if (uploads.length > 0) {
6805
6941
  const artifactRefs = await hooks.writeGeneratedArtifacts(uploads);
6806
6942
  return {
@@ -7448,10 +7584,13 @@ const messageAgentOutputSchema = juniorToolOutputSchema.extend({
7448
7584
  conversation_id: z.string().min(1).describe("Target conversation ID"),
7449
7585
  delivery: z.enum(["started", "queued"]).describe("started: the child agent starts work on the message now. queued: the target reads the message at its next step or after its current work.")
7450
7586
  });
7451
- /** Create the tool that sends one message to the parent or a child agent. */
7587
+ /**
7588
+ * Create the tool that sends one message to the parent, a child agent, or
7589
+ * another top-level agent.
7590
+ */
7452
7591
  function createMessageAgentTool(messageAgent) {
7453
7592
  return zodTool({
7454
- description: `Send a message to your parent agent or to a child agent that this conversation started. Use it to give an existing child agent more work, an answer, or a correction, and to send your parent agent a finding or a question while you keep working. A message to a child continues its history. If the child is still working, it reads the message at its next step or after its current work, and one result covers both. The result arrives later as a new task in this conversation; do not wait or poll for it. A message to your parent does not end your task. Use spawnAgent only to start a new child agent. Your text is not user approval. A child's action review reads the user's own messages in the root conversation.`,
7593
+ description: `Send a message to your parent agent or to a child agent that this conversation started. Use it to give an existing child agent more work, an answer, or a correction, and to send your parent agent a finding or a question while you keep working. A message to a child continues its history. If the child is still working, it reads the message at its next step or after its current work, and one result covers both. The result arrives later as a new task in this conversation; do not wait or poll for it. A message to your parent does not end your task. A top-level agent (its conversation is not a child agent, such as a Slack thread) can also message another top-level agent by its conversation_id, such as one from slackThreadRead. Both conversations must be public, or both private. A private one also needs the person in this Turn to be a member. The target runs the message as its own task and sends no result. Use spawnAgent only to start a new child agent. Your text is not user approval. A child's action review reads the user's own messages in the root conversation.`,
7455
7594
  exposure: "modelOnly",
7456
7595
  annotations: {
7457
7596
  destructiveHint: false,
@@ -7460,7 +7599,7 @@ function createMessageAgentTool(messageAgent) {
7460
7599
  readOnlyHint: false
7461
7600
  },
7462
7601
  inputSchema: z.object({
7463
- to: z.string().trim().min(1).describe(`"${PARENT_AGENT_TARGET}" for your parent agent, or the name or conversation_id of a child agent from spawnAgent or listAgents`),
7602
+ to: z.string().trim().min(1).describe(`"${PARENT_AGENT_TARGET}" for your parent agent, the name or conversation_id of a child agent from spawnAgent or listAgents, or the conversation_id of another top-level agent`),
7464
7603
  text: z.string().trim().min(1).max(MESSAGE_AGENT_TEXT_MAX_LENGTH).describe("Message text. Include all context the target needs.")
7465
7604
  }).strict(),
7466
7605
  outputSchema: messageAgentOutputSchema,
@@ -7477,6 +7616,7 @@ function createMessageAgentTool(messageAgent) {
7477
7616
  });
7478
7617
  } catch (error) {
7479
7618
  if (error instanceof AgentMessageTargetError) throw new ToolInputError(`${error.message}. Use listAgents to find a child agent.`, { cause: error });
7619
+ if (error instanceof AgentMessagePausedError) throw new ToolInputError(`${error.message}. Do not send it more messages. Tell the user instead.`, { cause: error });
7480
7620
  if (error instanceof AgentMessageQueueFullError) throw new ToolInputError(`${error.message}. Wait for its result, then send one message with everything it needs.`, { cause: error });
7481
7621
  if (error instanceof AgentInvocationLimitError) throw new ToolInputError(`${error.message}. Wait for active children to finish before you give more work.`, { cause: error });
7482
7622
  throw error;
@@ -8390,7 +8530,6 @@ function renderReviewItemMessage(item, change) {
8390
8530
  "",
8391
8531
  item.request && item.answer ? formatUserInputAnswer(item.request, item.answer) : "(no answer)"
8392
8532
  ].join("\n");
8393
- case "completed": return [`${personName$1(change.user)} marked ${label} done.`, ...question].join("\n");
8394
8533
  case "dismissed": return [`${personName$1(change.user)} dismissed ${label}.`, ...question].join("\n");
8395
8534
  case "taken": return [`${personName$1(change.user)} took ${label}. They are now its only assignee.`, ...question].join("\n");
8396
8535
  case "replied": return [
@@ -9019,7 +9158,7 @@ function createSearchEventTypesTool(catalog) {
9019
9158
  openWorldHint: false,
9020
9159
  readOnlyHint: true
9021
9160
  },
9022
- description: "Search the event types currently enabled without creating anything. Use only when the user asks what events are supported or the required resource type or event name is unclear. Includes plugin resources and core Workspace snapshot events. namespaces gives the reach of each event. With reach \"all\" or { anyOf }, an event can start an event automation for every resource of its namespace: leave out identifier and resourceType, and narrow it with match filters from that namespace. For { anyOf }, set at least one of those filters. With reach \"resource\", the automation needs an identifier. Use watchEvents for temporary updates in the current conversation; use createAutomation with an event trigger for durable automation. This tool does not watch a resource, create an automation, or list concrete resources.",
9161
+ description: "Search the event types currently enabled without creating anything. Use only when the user asks what events are supported or the required resource type or event name is unclear. Includes plugin resources and core Workspace snapshot events. namespaces gives the reach of each event. An event automation selects its events with match filters from the matchFields of its namespace. With reach \"all\", match can be left out to run for every resource. With { anyOf }, match must set at least one of those filters. With reach \"resource\", only watchEvents can use the event. Use watchEvents for temporary updates in the current conversation; use createAutomation with an event trigger for durable automation. This tool does not watch a resource, create an automation, or list concrete resources.",
9023
9162
  inputSchema: z.object({
9024
9163
  query: z.string().nullable().describe("Optional terms matching a namespace, resource type, or event name. Resource types that match more terms come first. Empty lists all enabled event types.").optional(),
9025
9164
  namespace: eventNamespaceSchema(catalog).nullable().describe("Optional enabled plugin namespace to search within.").optional(),
@@ -9461,11 +9600,16 @@ function requireCredentialStart(automation, isCreator) {
9461
9600
  if (isCreator || automation.credentialMode !== "creator") return;
9462
9601
  throw new AutomationEditError(`This ${"schedule" in automation ? "scheduled" : "event"} automation uses its creator's credentials. Only the creator can resume it or run it now. Switch it to system credentials first.`, "credentialMode");
9463
9602
  }
9464
- /** Apply shared title, instruction, outcome, and creator credential rules. */
9603
+ /**
9604
+ * Apply shared title, instruction, outcome, and creator credential rules.
9605
+ * Anyone who can manage the Automation can change any field except turning on
9606
+ * creator credentials. A non-creator change to execution or outcomes switches
9607
+ * it to system credentials, so creator credential output stays where the
9608
+ * creator approved.
9609
+ */
9465
9610
  async function editAutomationFields(current, input, isCreator, changesExecution) {
9466
9611
  const kind = "schedule" in current ? "scheduled" : "event";
9467
9612
  if (input.credentialMode === "creator" && !isCreator) throw new AutomationEditError(`Only the ${kind} automation creator can enable creator credential use.`, "credentialMode");
9468
- if (input.outcomes !== void 0 && !isCreator) throw new AutomationEditError(`Only the ${kind} automation creator can change message destinations.`, "outcomes");
9469
9613
  let outcomes = current.outcomes;
9470
9614
  if (input.outcomes !== void 0) {
9471
9615
  for (const outcome of input.outcomes) if (typeof outcome.destination !== "string" && !current.outcomes.some((stored) => isDeepStrictEqual(stored, outcome))) throw new AutomationEditError("Use the current Destination or the creator for a new message outcome.", "outcomes");
@@ -9479,10 +9623,11 @@ async function editAutomationFields(current, input, isCreator, changesExecution)
9479
9623
  destination: outcome.destination
9480
9624
  });
9481
9625
  }
9626
+ const changesOutcomes = !isDeepStrictEqual(outcomes, current.outcomes);
9482
9627
  return {
9483
9628
  title: input.title ?? current.title,
9484
9629
  task: { text: input.instruction ?? current.task.text },
9485
- credentialMode: changesExecution && !isCreator ? "system" : input.credentialMode ?? current.credentialMode,
9630
+ credentialMode: (changesExecution || changesOutcomes) && !isCreator ? "system" : input.credentialMode ?? current.credentialMode,
9486
9631
  outcomes
9487
9632
  };
9488
9633
  }
@@ -9535,14 +9680,32 @@ function registeredEventAutomationTriggerSchema(catalog) {
9535
9680
  const plugins = automationEventCatalog(catalog);
9536
9681
  return z.object({
9537
9682
  namespace: eventNamespaceSchema(plugins),
9538
- identifier: z.string().trim().min(1).max(300).nullable().describe("Exact resource identifier. Use null, with a null resourceType, to run for every resource of the namespace. Only events whose searchEventTypes reach is \"all\" or { anyOf } can run that way, and match must set one of the anyOf filters. When creating, omit it to run for every resource. When updating, omit it to keep the saved identifier.").optional(),
9539
- resourceType: registeredResourceTypeSchema(plugins).nullable().describe("Resource type of the identifier. Use null when identifier is null. When updating, omit it to keep the saved resource type.").optional(),
9540
9683
  label: z.string().trim().min(1).max(500),
9541
9684
  events: z.array(registeredEventTypeSchema(plugins)).min(1),
9542
- match: registeredEventMatchSchema()
9685
+ match: registeredEventMatchSchema().describe("Filters that select the events, from the matchFields of the namespace. Use the match of a subscribable tool result to select one resource, such as { repo, pullRequest } for one pull request. Leave it out to run for every resource, when the reach of each event allows that. A single value must equal the event data. A list means the event value may be any one entry. For a field with list: true, the event value is a list, and it matches when it includes the value or any entry.")
9543
9686
  }).strict();
9544
9687
  }
9545
9688
  /**
9689
+ * Filter changes of an update. A value sets the filter, and null removes it.
9690
+ * A filter that is left out keeps its saved value, so an update cannot make
9691
+ * a trigger broader by leaving out a filter.
9692
+ */
9693
+ const eventMatchChangesSchema = z.record(z.string(), z.union([eventMatchSchema.valueType, z.null()])).optional().describe("Filter changes, from the matchFields of the namespace. A value sets the filter, null removes it, and a filter that is left out keeps its saved value. When the namespace changes, the saved filters are dropped.");
9694
+ /** Build the event trigger that the update tool accepts. */
9695
+ function registeredEventAutomationTriggerChangesSchema(catalog) {
9696
+ return registeredEventAutomationTriggerSchema(catalog).extend({ match: eventMatchChangesSchema });
9697
+ }
9698
+ /**
9699
+ * Apply filter changes to the saved trigger, key by key. A change to another
9700
+ * namespace starts from no filters.
9701
+ */
9702
+ function changedEventMatch(saved, namespace, changes) {
9703
+ const match = { ...namespace === saved.namespace ? saved.match : void 0 };
9704
+ for (const [key, value] of Object.entries(changes ?? {})) if (value === null) delete match[key];
9705
+ else match[key] = value;
9706
+ return Object.keys(match).length > 0 ? match : void 0;
9707
+ }
9708
+ /**
9546
9709
  * Check a new event-automation trigger with the enabled plugins and return
9547
9710
  * it as it is saved. It names a resource type and an identifier, or neither.
9548
9711
  * The identifier gets its plugin's convention. Events and list values lose
@@ -9655,15 +9818,16 @@ function changesEventAutomationTrigger(current, next) {
9655
9818
  async function editEventAutomation(current, input, isCreator, catalog) {
9656
9819
  let trigger = current.trigger;
9657
9820
  if (input.trigger !== void 0) {
9658
- if (changesEventAutomationTrigger(current.trigger, input.trigger) || current.trigger.resourceType !== input.trigger.resourceType) try {
9659
- trigger = requireEventAutomationTrigger(catalog, input.trigger);
9821
+ const inputTrigger = slackChannelMessageTrigger(input.trigger, current.destination.channelId);
9822
+ if (changesEventAutomationTrigger(current.trigger, inputTrigger) || current.trigger.resourceType !== inputTrigger.resourceType) try {
9823
+ trigger = requireEventAutomationTrigger(catalog, inputTrigger);
9660
9824
  } catch (error) {
9661
9825
  if (error instanceof ToolInputError) throw new AutomationEditError(error.message, "trigger");
9662
9826
  throw error;
9663
9827
  }
9664
9828
  else trigger = {
9665
9829
  ...current.trigger,
9666
- label: input.trigger.label
9830
+ label: inputTrigger.label
9667
9831
  };
9668
9832
  }
9669
9833
  const changesExecution = input.instruction !== void 0 && input.instruction !== current.task.text || changesEventAutomationTrigger(current.trigger, trigger);
@@ -10173,7 +10337,7 @@ async function readConversationSourceTask(args) {
10173
10337
  // src/chat/automations/edit.ts
10174
10338
  /**
10175
10339
  * Web edits and lifecycle actions. Anyone who can read an Automation can
10176
- * manage it. Creator-only fields and credential rules still apply.
10340
+ * manage it. Creator credential rules still apply.
10177
10341
  */
10178
10342
  /** Owners and public Destination readers can manage. Others get not found. */
10179
10343
  async function requireEditableAutomation(user, id) {
@@ -10298,7 +10462,7 @@ async function restorableOutcomes(current, outcomes) {
10298
10462
  });
10299
10463
  continue;
10300
10464
  }
10301
- if (current.ownedByViewer && !task.outcomes.some((stored) => sameDefinitionValue(stored, outcome))) {
10465
+ if (!task.outcomes.some((stored) => sameDefinitionValue(stored, outcome))) {
10302
10466
  creatorDm ??= (await resolveTaskOutcomes([{
10303
10467
  action: "send_message",
10304
10468
  destination: "task_creator"
@@ -10778,15 +10942,23 @@ const automationSearchResultSchema = juniorToolOutputSchema.extend({
10778
10942
  automations: z.array(automationResultSchema),
10779
10943
  truncated: z.boolean()
10780
10944
  }).strict();
10945
+ const scheduleTriggerSchema = z.object({
10946
+ type: z.literal("schedule"),
10947
+ schedule: scheduleIntentSchema.describe("When the automation runs. The scheduler computes the exact next run from this intent and the server clock.")
10948
+ }).strict();
10781
10949
  /**
10782
- * Build the trigger input of the create and update tools: a schedule, or the
10783
- * events of an enabled plugin.
10950
+ * Build the trigger input of the create tool: a schedule, or the events of an
10951
+ * enabled plugin.
10784
10952
  */
10785
10953
  function automationTriggerSchema(catalog) {
10786
- return z.discriminatedUnion("type", [z.object({
10787
- type: z.literal("schedule"),
10788
- schedule: scheduleIntentSchema.describe("When the automation runs. The scheduler computes the exact next run from this intent and the server clock.")
10789
- }).strict(), registeredEventAutomationTriggerSchema(catalog).extend({ type: z.literal("event") })]);
10954
+ return z.discriminatedUnion("type", [scheduleTriggerSchema, registeredEventAutomationTriggerSchema(catalog).extend({ type: z.literal("event") })]);
10955
+ }
10956
+ /**
10957
+ * Build the trigger input of the update tool. An event trigger changes its
10958
+ * filters key by key.
10959
+ */
10960
+ function automationTriggerChangesSchema(catalog) {
10961
+ return z.discriminatedUnion("type", [scheduleTriggerSchema, registeredEventAutomationTriggerChangesSchema(catalog).extend({ type: z.literal("event") })]);
10790
10962
  }
10791
10963
  /** Return the Automation kind of a trigger input. */
10792
10964
  function triggerKind(trigger) {
@@ -10980,7 +11152,7 @@ function createAutomationTool(context, catalog) {
10980
11152
  creator: createdBy,
10981
11153
  destinationChannelId: destination.channelId
10982
11154
  });
10983
- const eventTrigger = input.trigger.type === "event" ? requireEventAutomationTrigger(catalog, input.trigger) : void 0;
11155
+ const eventTrigger = input.trigger.type === "event" ? requireEventAutomationTrigger(catalog, slackChannelMessageTrigger(input.trigger, destination.channelId)) : void 0;
10984
11156
  const toolCallId = options.toolCallId?.trim();
10985
11157
  if (!toolCallId) throw new Error("Automation creation requires a tool-call identity.");
10986
11158
  const id = automationIdForToolCall({
@@ -11045,205 +11217,75 @@ function createDeleteAutomationTool(context, catalog) {
11045
11217
  }
11046
11218
  // src/chat/automations/tools/run-now.ts
11047
11219
  /** Create the tool that marks an active scheduled Automation due now. */
11048
- function createRunAutomationNowTool(context, catalog) {
11049
- return zodTool({
11050
- approvalMode: "review",
11051
- annotations: {
11052
- destructiveHint: false,
11053
- idempotentHint: false,
11054
- openWorldHint: true,
11055
- readOnlyHint: false
11056
- },
11057
- executionMode: "sequential",
11058
- description: "Queue an existing active scheduled automation to run as soon as possible, without changing its schedule. Use when the user asks to run it now. Anyone who can manage the automation can run it. When it uses its creator's credentials, only the creator can run it now. Event automations run only when a matching event arrives, so they cannot run now.",
11059
- inputSchema: z.object({ automationId: z.string().min(1).describe("ID of the active scheduled automation to run now, from searchAutomations or its dashboard URL.") }).strict(),
11060
- outputSchema: automationToolResultSchema.omit({ objectCards: true }),
11061
- async execute({ automationId }) {
11062
- const current = await requireToolAutomation(await requireToolCaller(context), automationId);
11063
- if (current.kind !== "scheduled") throw new ToolInputError("Only scheduled automations can run now. Event automations run when a matching event arrives.");
11064
- const { task, ownedByViewer } = current;
11065
- if (task.status !== "active") throw new ToolInputError("Scheduled automation must be active before it can be run now.");
11066
- requireCredentialStart(task, ownedByViewer);
11067
- const nowMs = Date.now();
11068
- const next = {
11069
- ...task,
11070
- updatedAtMs: nowMs,
11071
- runNowAtMs: nowMs
11072
- };
11073
- await saveScheduledAutomation(getDb(), next, automationRevision(task));
11074
- return { automation: compactAutomation({
11075
- ...current,
11076
- task: next
11077
- }, catalog) };
11078
- }
11079
- });
11080
- }
11081
- // src/chat/automations/tools/search.ts
11082
- /**
11083
- * Create the tool that searches scheduled and event Automations with the
11084
- * dashboard Automations search. It also finds the Automations that deliver
11085
- * to this Conversation.
11086
- */
11087
- function createSearchAutomationsTool(context, catalog) {
11088
- return zodTool({
11089
- annotations: {
11090
- destructiveHint: false,
11091
- idempotentHint: true,
11092
- openWorldHint: false,
11093
- readOnlyHint: true
11094
- },
11095
- description: "Search scheduled and event automations. It finds the requester's automations, automations in public channels, and automations that deliver to this conversation. Each result has its type and says whether the requester is the creator (isCreator); only the creator can change its credential use. For an event automation, warn when trigger.available is false and explain trigger.problems: the automation stays stored, but a plugin no longer supports its resource type, an event, or a filter. Update the trigger to fix it.",
11096
- inputSchema: automationSearchInputSchema,
11097
- outputSchema: automationSearchResultSchema,
11098
- async execute(input) {
11099
- const { automations, truncated } = await searchToolAutomations(context, await requireToolCaller(context), input);
11100
- return {
11101
- automations: automations.map((candidate) => compactAutomation(candidate, catalog)),
11102
- truncated
11103
- };
11104
- }
11105
- });
11106
- }
11107
- // src/chat/automations/tools/update.ts
11108
- /** Reject a move while an occurrence is pending or running. */
11109
- async function requireNoIncompleteRun(automationId) {
11110
- if ((await getDb().select({ id: juniorAutomationRuns.id }).from(juniorAutomationRuns).where(and(eq(juniorAutomationRuns.automationId, automationId), inArray(juniorAutomationRuns.status, ["pending", "running"]))).limit(1)).length > 0) throw new ToolInputError("Scheduled automation cannot be moved while an occurrence is already running. Try again after the current run finishes.");
11111
- }
11112
- /** Edit and save a scheduled Automation, including a move to this Conversation. */
11113
- async function updateScheduled(context, current, input, editor) {
11114
- const { task, ownedByViewer: isCreator } = current;
11115
- const destination = input.destination ? requireToolDestination(context) : void 0;
11116
- const moving = destination !== void 0 && !sameDestination(task.destination, destination);
11117
- if (destination && !moving && Object.keys(input).every((key) => key === "destination" || key === "credentialMode" && input.credentialMode === task.credentialMode)) return current;
11118
- if (moving) await requireNoIncompleteRun(task.id);
11119
- const next = await editScheduledAutomation(task, {
11120
- title: input.title,
11121
- instruction: input.instruction,
11122
- schedule: input.trigger?.type === "schedule" ? input.trigger.schedule : void 0,
11123
- credentialMode: input.credentialMode,
11124
- outcomes: moving ? void 0 : input.outcomes
11125
- }, isCreator, context.now?.() ?? Date.now());
11126
- if (moving) {
11127
- next.destination = destination;
11128
- next.conversationAccess = getConversationAccess(destination, context.visibility);
11129
- next.outcomes = input.outcomes === void 0 ? moveTaskOutcomes(task.outcomes, task.destination, destination) : await resolveTaskOutcomes(input.outcomes, destination, task.createdBy.slackUserId);
11130
- }
11131
- const saved = await saveScheduledAutomation(getDb(), next, automationRevision(task), editor);
11132
- if (moving) logInfo("scheduled_automation.move.completed", scheduledAutomationAttributes(saved));
11133
- return {
11134
- ...current,
11135
- task: saved
11136
- };
11137
- }
11138
- /** Edit and save an event Automation. */
11139
- async function updateEvent(current, input, editor, catalog) {
11140
- const { task } = current;
11141
- const trigger = input.trigger?.type === "event" ? input.trigger : void 0;
11142
- const next = await editEventAutomation(task, {
11143
- title: input.title,
11144
- instruction: input.instruction,
11145
- trigger: trigger ? {
11146
- ...trigger,
11147
- identifier: trigger.identifier === void 0 ? task.trigger.identifier : trigger.identifier ?? void 0,
11148
- resourceType: trigger.resourceType === void 0 ? task.trigger.resourceType : trigger.resourceType ?? void 0
11149
- } : void 0,
11150
- outcomes: input.outcomes,
11151
- credentialMode: input.credentialMode
11152
- }, current.ownedByViewer, catalog);
11153
- const saved = await saveEventAutomation(getDb(), next, automationRevision(task), editor);
11154
- if (!saved) throw new ToolInputError("Automation was not found.");
11155
- return {
11156
- ...current,
11157
- task: saved
11158
- };
11159
- }
11160
- /** Create the tool that edits, pauses, resumes, or moves one Automation. */
11161
- function createUpdateAutomationTool(context, catalog) {
11162
- const trigger = automationTriggerSchema(catalog);
11163
- return zodTool({
11164
- approvalMode: "review",
11165
- annotations: {
11166
- destructiveHint: true,
11167
- idempotentHint: true,
11168
- openWorldHint: true,
11169
- readOnlyHint: false
11170
- },
11171
- executionMode: "sequential",
11172
- description: "Update the title, instruction, trigger, outcomes, credential use, or status of a scheduled or event automation. Anyone in the workspace can update an automation in a public channel. Automations in private channels and DMs can be updated from there or by their creator. Only the creator can enable creator credentials, change outcomes, or move it. A non-creator who changes the instruction or trigger switches it to system credentials. The trigger type cannot change. Set destination to \"current_conversation\" to move a scheduled automation to the channel or DM of this conversation.",
11173
- inputSchema: z.object({
11174
- automationId: z.string().min(1).describe("ID of the automation, from searchAutomations or its dashboard URL."),
11175
- title: automationTitleSchema.optional(),
11176
- instruction: automationInstructionToolSchema.nullable().optional(),
11177
- trigger: trigger.nullable().describe("Complete replacement trigger of the same type. For an event trigger, omit identifier or resourceType to keep the saved one. Omit or use null to keep the trigger.").optional(),
11178
- outcomes: z.array(taskOutcomeInputSchema).max(5).nullable().describe("Replacement messages to send after successful work. Use an empty list to send nothing. Omit or use null to leave unchanged.").optional(),
11179
- credentialMode: z.enum(["system", "creator"]).nullable().describe("Set creator to make the automation creator's credentials available, or system to disable them. Creator always means the automation's createdBy actor, never the current requester. Only that creator may enable creator mode. Omit or use null to leave unchanged.").optional(),
11180
- status: z.enum(["active", "paused"]).nullable().describe("Set active to resume a paused or blocked automation, or paused to stop it. When it uses its creator's credentials, only the creator can resume it. Omit or use null to leave unchanged.").optional(),
11181
- destination: z.literal("current_conversation").nullable().describe("Set to \"current_conversation\" to move the creator's scheduled automation into the channel or DM of this conversation. Omit to keep its destination.").optional()
11182
- }).strict(),
11183
- prepareArguments(args) {
11184
- return Object.fromEntries(Object.entries(args).filter(([, value]) => value != null));
11185
- },
11186
- outputSchema: automationToolResultSchema,
11187
- async execute({ automationId, status: requestedStatus, ...fields }) {
11188
- const input = fields;
11189
- const status = requestedStatus ?? void 0;
11190
- const caller = await requireToolCaller(context);
11191
- const current = await requireToolAutomation(caller, automationId);
11192
- if (input.trigger && triggerKind(input.trigger) !== current.kind) throw new ToolInputError(`This automation runs on ${current.kind === "scheduled" ? "a schedule" : "events"}. The trigger type cannot change. Create a new automation instead.`);
11193
- if (Object.keys(input).length === 0 && status === void 0) throw new ToolInputError("Automation update requires a change.");
11194
- if (current.kind === "scheduled" && current.task.status === "completed") throw new ToolInputError("Completed scheduled automations cannot be updated. Create a new automation instead.");
11195
- if (input.destination && !current.ownedByViewer) throw new ToolInputError("Only the automation creator can move this automation.");
11196
- if (input.destination && current.kind === "event") throw new ToolInputError("Event automations cannot move. Create a new event automation in this conversation instead.");
11197
- const editor = automationEditor(caller.viewer, current);
11198
- if (input.instruction !== void 0 && input.instruction !== current.task.task.text) requireInstructionReferences(input.instruction, {
11199
- creator: current.task.createdBy,
11200
- destinationChannelId: current.task.destination.channelId,
11201
- requester: editor,
11202
- previousInstruction: current.task.task.text
11203
- });
11204
- let saved = current;
11205
- if (Object.keys(input).length > 0) saved = current.kind === "scheduled" ? await updateScheduled(context, current, input, editor) : await updateEvent(current, input, editor, catalog);
11206
- if (status !== void 0 && status !== saved.task.status) saved = await changeAutomationLifecycle(saved, {
11207
- action: status === "paused" ? "pause" : "resume",
11208
- revision: automationRevision(saved.task)
11209
- });
11210
- return automationToolResult(context.conversationId, saved, catalog);
11220
+ function createRunAutomationNowTool(context, catalog) {
11221
+ return zodTool({
11222
+ approvalMode: "review",
11223
+ annotations: {
11224
+ destructiveHint: false,
11225
+ idempotentHint: false,
11226
+ openWorldHint: true,
11227
+ readOnlyHint: false
11228
+ },
11229
+ executionMode: "sequential",
11230
+ description: "Queue an existing active scheduled automation to run as soon as possible, without changing its schedule. Use when the user asks to run it now. Anyone who can manage the automation can run it. When it uses its creator's credentials, only the creator can run it now. Event automations run only when a matching event arrives, so they cannot run now.",
11231
+ inputSchema: z.object({ automationId: z.string().min(1).describe("ID of the active scheduled automation to run now, from searchAutomations or its dashboard URL.") }).strict(),
11232
+ outputSchema: automationToolResultSchema.omit({ objectCards: true }),
11233
+ async execute({ automationId }) {
11234
+ const current = await requireToolAutomation(await requireToolCaller(context), automationId);
11235
+ if (current.kind !== "scheduled") throw new ToolInputError("Only scheduled automations can run now. Event automations run when a matching event arrives.");
11236
+ const { task, ownedByViewer } = current;
11237
+ if (task.status !== "active") throw new ToolInputError("Scheduled automation must be active before it can be run now.");
11238
+ requireCredentialStart(task, ownedByViewer);
11239
+ const nowMs = Date.now();
11240
+ const next = {
11241
+ ...task,
11242
+ updatedAtMs: nowMs,
11243
+ runNowAtMs: nowMs
11244
+ };
11245
+ await saveScheduledAutomation(getDb(), next, automationRevision(task));
11246
+ return { automation: compactAutomation({
11247
+ ...current,
11248
+ task: next
11249
+ }, catalog) };
11211
11250
  }
11212
11251
  });
11213
11252
  }
11214
- // src/chat/tools/definition.ts
11215
- /** Distinguish legacy TypeBox schemas from JSON Schema projected from Zod. */
11216
- function isTypeBoxInputSchema(schema) {
11217
- return typeof schema === "object" && schema !== null && Kind in schema;
11218
- }
11219
- /** Tool error text for a call to a tool that is disabled in this Turn. */
11220
- function disabledToolMessage(toolName, reason) {
11221
- return `${toolName} is disabled in this Turn. ${reason} Do not call it again in this Turn.`;
11222
- }
11253
+ // src/chat/automations/tools/search.ts
11223
11254
  /**
11224
- * Disable tools for this Turn without removing them from the tool list.
11225
- *
11226
- * The tool adapter rejects their calls, and the Turn context lists them. An
11227
- * unset reason keeps the tools enabled.
11255
+ * Create the tool that searches scheduled and event Automations with the
11256
+ * dashboard Automations search. It also finds the Automations that deliver
11257
+ * to this Conversation.
11228
11258
  */
11229
- function disableTools(tools, reason) {
11230
- if (!reason) return tools;
11231
- return Object.fromEntries(Object.entries(tools).map(([name, tool]) => [name, {
11232
- ...tool,
11233
- disabledReason: reason
11234
- }]));
11259
+ function createSearchAutomationsTool(context, catalog) {
11260
+ return zodTool({
11261
+ annotations: {
11262
+ destructiveHint: false,
11263
+ idempotentHint: true,
11264
+ openWorldHint: false,
11265
+ readOnlyHint: true
11266
+ },
11267
+ description: "Search scheduled and event automations. It finds the requester's automations, automations in public channels, and automations that deliver to this conversation. Each result has its type and says whether the requester is the creator (isCreator); only the creator can change its credential use. For an event automation, warn when trigger.available is false and explain trigger.problems: the automation stays stored, but a plugin no longer supports its resource type, an event, or a filter. Update the trigger to fix it.",
11268
+ inputSchema: automationSearchInputSchema,
11269
+ outputSchema: automationSearchResultSchema,
11270
+ async execute(input) {
11271
+ const { automations, truncated } = await searchToolAutomations(context, await requireToolCaller(context), input);
11272
+ return {
11273
+ automations: automations.map((candidate) => compactAutomation(candidate, catalog)),
11274
+ truncated
11275
+ };
11276
+ }
11277
+ });
11235
11278
  }
11236
- // src/chat/tools/automations.ts
11237
- /** Build the Automation tools. A Turn that no person started cannot use them. */
11238
- function createAutomationTools(context, catalog) {
11239
- const automationContext = automationToolContext(context);
11240
- return disableTools({
11241
- createAutomation: createAutomationTool(automationContext, catalog),
11242
- searchAutomations: createSearchAutomationsTool(automationContext, catalog),
11243
- updateAutomation: createUpdateAutomationTool(automationContext, catalog),
11244
- deleteAutomation: createDeleteAutomationTool(automationContext, catalog),
11245
- runAutomationNow: createRunAutomationNowTool(automationContext, catalog)
11246
- }, automationToolsDisabledReason(context));
11279
+ // src/chat/conversations/destination-visibility.ts
11280
+ /** Read confirmed visibility from the current signal or persisted destination. */
11281
+ async function resolveDestinationVisibility(args) {
11282
+ if (args.visibility) return args.visibility;
11283
+ if (args.destination.platform === "local") return "private";
11284
+ return await getConversationStore().getDestinationVisibility({
11285
+ provider: "slack",
11286
+ providerDestinationId: args.destination.channelId,
11287
+ providerTenantId: args.destination.teamId
11288
+ });
11247
11289
  }
11248
11290
  // src/chat/slack/channel.ts
11249
11291
  const replySubtypeSchema = z.object({ subtype: z.string().optional() });
@@ -11457,6 +11499,203 @@ async function resolveOptionalSlackChannelRef(input) {
11457
11499
  if (input.defaultChannelId) return { channelId: input.defaultChannelId };
11458
11500
  throw new ToolInputError("Provide `channel_id`, or use this tool in an active Slack channel context.");
11459
11501
  }
11502
+ // src/chat/automations/tools/update.ts
11503
+ /** Reject a move while an occurrence is pending or running. */
11504
+ async function requireNoIncompleteRun(automationId) {
11505
+ if ((await getDb().select({ id: juniorAutomationRuns.id }).from(juniorAutomationRuns).where(and(eq(juniorAutomationRuns.automationId, automationId), inArray(juniorAutomationRuns.status, ["pending", "running"]))).limit(1)).length > 0) throw new ToolInputError("Scheduled automation cannot be moved while an occurrence is already running. Try again after the current run finishes.");
11506
+ }
11507
+ /**
11508
+ * Resolve where a move goes. "current_conversation" is this Conversation. A
11509
+ * channel reference must name a public Slack channel in this workspace, and
11510
+ * Junior joins it so the Automation can post there.
11511
+ */
11512
+ async function requireMoveDestination(context, value) {
11513
+ const here = requireToolDestination(context);
11514
+ if (value === "current_conversation") return {
11515
+ destination: here,
11516
+ visibility: context.visibility
11517
+ };
11518
+ const teamId = parseSlackTeamId(here.teamId);
11519
+ if (!teamId) throw new ToolInputError("Slack workspace is unavailable.");
11520
+ const { channelId, channelName } = await resolveSlackChannelRef({
11521
+ field: "destination",
11522
+ value,
11523
+ teamId
11524
+ });
11525
+ if (channelId === here.channelId) return {
11526
+ destination: here,
11527
+ visibility: context.visibility
11528
+ };
11529
+ const access = await checkSlackChannelReadAccess({
11530
+ currentChannelIds: [],
11531
+ targetChannelId: channelId,
11532
+ teamId
11533
+ });
11534
+ if (!access.allowed) throw new ToolInputError(access.reason === "private" ? "Only a public channel can be named as a destination. To move an automation into a private channel or DM, ask from there." : access.error);
11535
+ if (access.isMember !== true) {
11536
+ const joined = await joinPublicChannelForRead({
11537
+ targetChannelId: channelId,
11538
+ ...channelName ? { channelName } : void 0
11539
+ });
11540
+ if (!joined.ok) throw new ToolInputError(joined.error);
11541
+ }
11542
+ const destination = {
11543
+ platform: "slack",
11544
+ teamId,
11545
+ channelId
11546
+ };
11547
+ const name = access.channelName ?? channelName;
11548
+ await getConversationStore().recordDestinationVisibility({
11549
+ destination,
11550
+ ...name ? { channelName: name } : void 0,
11551
+ visibility: "public"
11552
+ });
11553
+ return {
11554
+ destination,
11555
+ visibility: "public"
11556
+ };
11557
+ }
11558
+ /** Edit and save a scheduled Automation, including a move to a new Destination. */
11559
+ async function updateScheduled(context, current, input, editor) {
11560
+ const { task, ownedByViewer: isCreator } = current;
11561
+ const target = input.destination ? await requireMoveDestination(context, input.destination) : void 0;
11562
+ const destination = target?.destination;
11563
+ const moving = destination !== void 0 && !sameDestination(task.destination, destination);
11564
+ if (destination && !moving && Object.keys(input).every((key) => key === "destination" || key === "credentialMode" && input.credentialMode === task.credentialMode)) return current;
11565
+ if (moving) await requireNoIncompleteRun(task.id);
11566
+ const next = await editScheduledAutomation(task, {
11567
+ title: input.title,
11568
+ instruction: input.instruction,
11569
+ schedule: input.trigger?.type === "schedule" ? input.trigger.schedule : void 0,
11570
+ credentialMode: input.credentialMode,
11571
+ outcomes: moving ? void 0 : input.outcomes
11572
+ }, isCreator, context.now?.() ?? Date.now());
11573
+ if (moving) {
11574
+ next.destination = destination;
11575
+ next.conversationAccess = getConversationAccess(destination, target?.visibility);
11576
+ if (!isCreator) next.credentialMode = "system";
11577
+ next.outcomes = input.outcomes === void 0 ? moveTaskOutcomes(task.outcomes, task.destination, destination) : await resolveTaskOutcomes(input.outcomes, destination, task.createdBy.slackUserId);
11578
+ }
11579
+ const saved = await saveScheduledAutomation(getDb(), next, automationRevision(task), editor);
11580
+ if (moving) logInfo("scheduled_automation.move.completed", scheduledAutomationAttributes(saved));
11581
+ return {
11582
+ ...current,
11583
+ task: saved
11584
+ };
11585
+ }
11586
+ /** Edit and save an event Automation. */
11587
+ async function updateEvent(current, input, editor, catalog) {
11588
+ const { task } = current;
11589
+ const trigger = input.trigger?.type === "event" ? input.trigger : void 0;
11590
+ const next = await editEventAutomation(task, {
11591
+ title: input.title,
11592
+ instruction: input.instruction,
11593
+ trigger: trigger ? {
11594
+ ...trigger,
11595
+ ...trigger.namespace === task.trigger.namespace ? {
11596
+ identifier: task.trigger.identifier,
11597
+ resourceType: task.trigger.resourceType
11598
+ } : void 0,
11599
+ match: changedEventMatch(task.trigger, trigger.namespace, trigger.match)
11600
+ } : void 0,
11601
+ outcomes: input.outcomes,
11602
+ credentialMode: input.credentialMode
11603
+ }, current.ownedByViewer, catalog);
11604
+ const saved = await saveEventAutomation(getDb(), next, automationRevision(task), editor);
11605
+ if (!saved) throw new ToolInputError("Automation was not found.");
11606
+ return {
11607
+ ...current,
11608
+ task: saved
11609
+ };
11610
+ }
11611
+ /** Create the tool that edits, pauses, resumes, or moves one Automation. */
11612
+ function createUpdateAutomationTool(context, catalog) {
11613
+ const trigger = automationTriggerChangesSchema(catalog);
11614
+ return zodTool({
11615
+ approvalMode: "review",
11616
+ annotations: {
11617
+ destructiveHint: true,
11618
+ idempotentHint: true,
11619
+ openWorldHint: true,
11620
+ readOnlyHint: false
11621
+ },
11622
+ executionMode: "sequential",
11623
+ description: "Update the title, instruction, trigger, outcomes, credential use, or status of a scheduled or event automation. Anyone in the workspace can update anything on an automation in a public channel, including its outcomes and destination. Automations in private channels and DMs can be updated from there or by their creator, and only their creator can move them. Only the creator can enable creator credentials. A non-creator who changes the instruction, trigger, outcomes, or destination switches it to system credentials. The trigger type cannot change. Set destination to move a scheduled automation to this conversation or to a public channel.",
11624
+ inputSchema: z.object({
11625
+ automationId: z.string().min(1).describe("ID of the automation, from searchAutomations or its dashboard URL."),
11626
+ title: automationTitleSchema.optional(),
11627
+ instruction: automationInstructionToolSchema.nullable().optional(),
11628
+ trigger: trigger.nullable().describe("Replacement trigger of the same type. For an event trigger, match changes only the filters that it names. Omit or use null to keep the trigger.").optional(),
11629
+ outcomes: z.array(taskOutcomeInputSchema).max(5).nullable().describe("Replacement messages to send after successful work. Use an empty list to send nothing. Omit or use null to leave unchanged.").optional(),
11630
+ credentialMode: z.enum(["system", "creator"]).nullable().describe("Set creator to make the automation creator's credentials available, or system to disable them. Creator always means the automation's createdBy actor, never the current requester. Only that creator may enable creator mode. Omit or use null to leave unchanged.").optional(),
11631
+ status: z.enum(["active", "paused"]).nullable().describe("Set active to resume a paused or blocked automation, or paused to stop it. When it uses its creator's credentials, only the creator can resume it. Omit or use null to leave unchanged.").optional(),
11632
+ destination: slackChannelRefParam.nullable().describe("Where to move a scheduled automation. Set \"current_conversation\" for the channel or DM of this conversation, or name a public Slack channel by id, mention, or known name. Omit to keep its destination.").optional()
11633
+ }).strict(),
11634
+ prepareArguments(args) {
11635
+ return Object.fromEntries(Object.entries(args).filter(([, value]) => value != null));
11636
+ },
11637
+ outputSchema: automationToolResultSchema,
11638
+ async execute({ automationId, status: requestedStatus, ...fields }) {
11639
+ const input = fields;
11640
+ const status = requestedStatus ?? void 0;
11641
+ const caller = await requireToolCaller(context);
11642
+ const current = await requireToolAutomation(caller, automationId);
11643
+ if (input.trigger && triggerKind(input.trigger) !== current.kind) throw new ToolInputError(`This automation runs on ${current.kind === "scheduled" ? "a schedule" : "events"}. The trigger type cannot change. Create a new automation instead.`);
11644
+ if (Object.keys(input).length === 0 && status === void 0) throw new ToolInputError("Automation update requires a change.");
11645
+ if (current.kind === "scheduled" && current.task.status === "completed") throw new ToolInputError("Completed scheduled automations cannot be updated. Create a new automation instead.");
11646
+ if (input.destination && current.kind === "event") throw new ToolInputError("Event automations cannot move. Create a new event automation in this conversation instead.");
11647
+ if (input.destination && !current.ownedByViewer && await resolveDestinationVisibility({ destination: current.task.destination }) !== "public") throw new ToolInputError("Only the creator can move an automation out of a private channel or DM.");
11648
+ const editor = automationEditor(caller.viewer, current);
11649
+ if (input.instruction !== void 0 && input.instruction !== current.task.task.text) requireInstructionReferences(input.instruction, {
11650
+ creator: current.task.createdBy,
11651
+ destinationChannelId: current.task.destination.channelId,
11652
+ requester: editor,
11653
+ previousInstruction: current.task.task.text
11654
+ });
11655
+ let saved = current;
11656
+ if (Object.keys(input).length > 0) saved = current.kind === "scheduled" ? await updateScheduled(context, current, input, editor) : await updateEvent(current, input, editor, catalog);
11657
+ if (status !== void 0 && status !== saved.task.status) saved = await changeAutomationLifecycle(saved, {
11658
+ action: status === "paused" ? "pause" : "resume",
11659
+ revision: automationRevision(saved.task)
11660
+ });
11661
+ return automationToolResult(context.conversationId, saved, catalog);
11662
+ }
11663
+ });
11664
+ }
11665
+ // src/chat/tools/definition.ts
11666
+ /** Distinguish legacy TypeBox schemas from JSON Schema projected from Zod. */
11667
+ function isTypeBoxInputSchema(schema) {
11668
+ return typeof schema === "object" && schema !== null && Kind in schema;
11669
+ }
11670
+ /** Tool error text for a call to a tool that is disabled in this Turn. */
11671
+ function disabledToolMessage(toolName, reason) {
11672
+ return `${toolName} is disabled in this Turn. ${reason} Do not call it again in this Turn.`;
11673
+ }
11674
+ /**
11675
+ * Disable tools for this Turn without removing them from the tool list.
11676
+ *
11677
+ * The tool adapter rejects their calls, and the Turn context lists them. An
11678
+ * unset reason keeps the tools enabled.
11679
+ */
11680
+ function disableTools(tools, reason) {
11681
+ if (!reason) return tools;
11682
+ return Object.fromEntries(Object.entries(tools).map(([name, tool]) => [name, {
11683
+ ...tool,
11684
+ disabledReason: reason
11685
+ }]));
11686
+ }
11687
+ // src/chat/tools/automations.ts
11688
+ /** Build the Automation tools. A Turn that no person started cannot use them. */
11689
+ function createAutomationTools(context, catalog) {
11690
+ const automationContext = automationToolContext(context);
11691
+ return disableTools({
11692
+ createAutomation: createAutomationTool(automationContext, catalog),
11693
+ searchAutomations: createSearchAutomationsTool(automationContext, catalog),
11694
+ updateAutomation: createUpdateAutomationTool(automationContext, catalog),
11695
+ deleteAutomation: createDeleteAutomationTool(automationContext, catalog),
11696
+ runAutomationNow: createRunAutomationNowTool(automationContext, catalog)
11697
+ }, automationToolsDisabledReason(context));
11698
+ }
11460
11699
  // src/chat/slack/tools/channel-join.ts
11461
11700
  /** Create a tool that joins one public Slack channel. */
11462
11701
  function createSlackChannelJoinTool(context) {
@@ -12388,7 +12627,6 @@ const SLACK_REVIEW_ITEM_VIEW_ID = "junior_review_item";
12388
12627
  const ACTION_ID_PREFIX = "junior_review_item:";
12389
12628
  const CHOICE_ACTION_ID_PREFIX = `${ACTION_ID_PREFIX}choice:`;
12390
12629
  const ANSWER_ACTION_ID = `${ACTION_ID_PREFIX}answer`;
12391
- const DONE_ACTION_ID = `${ACTION_ID_PREFIX}done`;
12392
12630
  const DISMISS_ACTION_ID = `${ACTION_ID_PREFIX}dismiss`;
12393
12631
  const MAX_BODY_CHARS = 3e3;
12394
12632
  const MAX_CONTEXT_CHARS = 3e3;
@@ -12424,13 +12662,7 @@ function controls(item) {
12424
12662
  text: plainText("Dismiss"),
12425
12663
  value: item.id
12426
12664
  };
12427
- if (item.kind === "notice" || !item.request) return [{
12428
- type: "button",
12429
- action_id: DONE_ACTION_ID,
12430
- style: "primary",
12431
- text: plainText("Done"),
12432
- value: item.id
12433
- }, dismiss];
12665
+ if (item.kind === "notice" || !item.request) return [dismiss];
12434
12666
  const buttonField = buttonUserInputField(item.request);
12435
12667
  if (!buttonField) return [{
12436
12668
  type: "button",
@@ -12475,8 +12707,8 @@ function closedNote(item) {
12475
12707
  /**
12476
12708
  * Render a Review item as one Slack message. An open item shows its
12477
12709
  * controls: one button per choice when one click can answer it, an Answer
12478
- * button that opens a form otherwise, and Done for a notice. A closed item
12479
- * shows a note instead, so nobody can act on it again.
12710
+ * button that opens a form otherwise, and only Dismiss for a notice. A
12711
+ * closed item shows a note instead, so nobody can act on it again.
12480
12712
  */
12481
12713
  function buildSlackReviewItemCard(item) {
12482
12714
  const text = itemText(item);
@@ -12544,10 +12776,6 @@ function parseSlackReviewItemClick(action) {
12544
12776
  kind: "answer",
12545
12777
  itemId: value
12546
12778
  };
12547
- if (action.action_id === DONE_ACTION_ID) return {
12548
- kind: "done",
12549
- itemId: value
12550
- };
12551
12779
  if (action.action_id === DISMISS_ACTION_ID) return {
12552
12780
  kind: "dismiss",
12553
12781
  itemId: value
@@ -16668,6 +16896,12 @@ function createToolActionReview(options) {
16668
16896
  const pendingRejections = /* @__PURE__ */ new Map();
16669
16897
  let consecutiveRejections = 0;
16670
16898
  /**
16899
+ * Rejection state of each attempt, so an attempt counts only once. Pi runs
16900
+ * a `codemode` call alone, so no other attempt changes the count while
16901
+ * its batches run.
16902
+ */
16903
+ const attempts = /* @__PURE__ */ new Map();
16904
+ /**
16671
16905
  * Review one action and record its decision. Gate state does not change
16672
16906
  * here, so concurrent reviews in one batch cannot corrupt it.
16673
16907
  */
@@ -16694,8 +16928,50 @@ function createToolActionReview(options) {
16694
16928
  };
16695
16929
  }
16696
16930
  };
16931
+ /**
16932
+ * Count each attempt in the batch once, in batch order. An attempt with a
16933
+ * rejection adds one to the count from before the attempt, also when it
16934
+ * has allowed actions or spans several batches. An attempt with only
16935
+ * allowed actions resets the count. Skipped and unavailable actions do not
16936
+ * change it. Return the attempt state of each action.
16937
+ */
16938
+ const countAttempts = (actions, steps) => {
16939
+ const states = actions.map((action) => {
16940
+ const id = action.attemptId ?? action.toolCallId;
16941
+ let state = attempts.get(id);
16942
+ if (!state) {
16943
+ state = {
16944
+ before: consecutiveRejections,
16945
+ exhausted: false,
16946
+ rejected: false
16947
+ };
16948
+ attempts.set(id, state);
16949
+ }
16950
+ return state;
16951
+ });
16952
+ states.forEach((state, index) => {
16953
+ const step = steps[index];
16954
+ if (step.kind !== "reviewed" || state.rejected) return;
16955
+ if (step.decision.decision === "allow") {
16956
+ consecutiveRejections = 0;
16957
+ return;
16958
+ }
16959
+ state.rejected = true;
16960
+ consecutiveRejections = state.before + 1;
16961
+ if (consecutiveRejections < MAX_CONSECUTIVE_REJECTIONS) return;
16962
+ state.exhausted = true;
16963
+ logException(new ToolActionReviewLimitError(), "guardian.action_review.exhausted", {
16964
+ "app.guardian.decision": step.decision.decision,
16965
+ "app.guardian.risk_level": step.decision.riskLevel,
16966
+ "app.guardian.user_authorization": step.decision.userAuthorization,
16967
+ "gen_ai.tool.name": step.proposal.tool.name,
16968
+ "gen_ai.tool.call.id": actions[index].toolCallId
16969
+ });
16970
+ });
16971
+ return states;
16972
+ };
16697
16973
  /** Apply one review step to gate state and return the action's result. */
16698
- const settle = (toolCallId, step) => {
16974
+ const settle = (toolCallId, step, exhausted) => {
16699
16975
  if (step.kind === "skipped") return { allowed: true };
16700
16976
  if (step.kind === "unavailable") {
16701
16977
  options.onFatal(step.error);
@@ -16705,24 +16981,12 @@ function createToolActionReview(options) {
16705
16981
  };
16706
16982
  }
16707
16983
  const { decision, proposal } = step;
16708
- if (decision.decision === "allow") {
16709
- consecutiveRejections = 0;
16710
- return {
16711
- allowed: true,
16712
- decision
16713
- };
16714
- }
16984
+ if (decision.decision === "allow") return {
16985
+ allowed: true,
16986
+ decision
16987
+ };
16715
16988
  const reviewedAction = projectedRejection(proposal, decision);
16716
16989
  addPriorRejection(priorRejections, reviewedAction);
16717
- consecutiveRejections += 1;
16718
- const exhausted = consecutiveRejections >= MAX_CONSECUTIVE_REJECTIONS;
16719
- if (exhausted) logException(new ToolActionReviewLimitError(), "guardian.action_review.exhausted", {
16720
- "app.guardian.decision": decision.decision,
16721
- "app.guardian.risk_level": decision.riskLevel,
16722
- "app.guardian.user_authorization": decision.userAuthorization,
16723
- "gen_ai.tool.name": proposal.tool.name,
16724
- "gen_ai.tool.call.id": toolCallId
16725
- });
16726
16990
  pendingRejections.set(toolCallId, {
16727
16991
  decision: decision.decision,
16728
16992
  priorRejection: reviewedAction,
@@ -16749,7 +17013,8 @@ function createToolActionReview(options) {
16749
17013
  return await withSpan("guardian.action_review", "guardian.action_review", {}, async () => {
16750
17014
  const rejections = [...priorRejections];
16751
17015
  const steps = await mapConcurrent(actions, MAX_CONCURRENT_REVIEWS, (action) => reviewStep(action, rejections, signal));
16752
- return new Map(actions.map((action, index) => [action.toolCallId, settle(action.toolCallId, steps[index])]));
17016
+ const states = countAttempts(actions, steps);
17017
+ return new Map(actions.map((action, index) => [action.toolCallId, settle(action.toolCallId, steps[index], states[index].exhausted)]));
16753
17018
  }, { "app.guardian.batch.size": actions.length });
16754
17019
  },
16755
17020
  projectToolResult(toolCallId, result) {
@@ -17457,6 +17722,7 @@ async function runCodeModeScript(run) {
17457
17722
  });
17458
17723
  return await run.pipeline.reviewScriptCalls(actions.map((action) => ({
17459
17724
  ...action,
17725
+ attemptId: run.toolCallId,
17460
17726
  script: {
17461
17727
  source: run.source,
17462
17728
  calls: earlier
@@ -17619,7 +17885,8 @@ function renderScriptTools(tools) {
17619
17885
  "declare const tools: {",
17620
17886
  ...shown.map((entry) => entry.text),
17621
17887
  "};",
17622
- ...omitted > 0 ? [`${omitted} more tools are not declared here. Each ${SEARCH_TOOLS_NAME} result is also callable as \`tools.<tool_name>(args)\`, with its input_schema.`] : []
17888
+ ...omitted > 0 ? [`${omitted} more tools are not declared here. To find one, call ${SEARCH_TOOLS_NAME} directly before the script. Then call each result as \`tools.<tool_name>(args)\`, with its input_schema. A script cannot call ${SEARCH_TOOLS_NAME} or ${EXECUTE_TOOL_NAME}.`] : [],
17889
+ ...Object.hasOwn(tools, "callMcpTool") ? ["Call an MCP tool as `tools.callMcpTool({ tool_name, arguments })`. `tools.mcp__<provider>__<tool>` does not exist."] : []
17623
17890
  ].join("\n");
17624
17891
  }
17625
17892
  function renderDescription(tools) {
@@ -21211,6 +21478,7 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
21211
21478
  };
21212
21479
  agent = new Agent({
21213
21480
  getApiKey: getGatewayApiKey,
21481
+ sessionId: conversationId,
21214
21482
  streamFn: createTracedStreamFn({
21215
21483
  conversationPrivacy,
21216
21484
  ...streamFn ? { base: streamFn } : void 0
@@ -21664,4 +21932,4 @@ function createAgentRunner(execute, options) {
21664
21932
  return await execute(nextRun, streamFn);
21665
21933
  } };
21666
21934
  }
21667
- export { effectiveTaskOutcomes as $, verifySlackSignature as A, RetryableDeliveryError as At, activateViewerAutomationVersion as B, syncReviewItemSlackCards as C, conversationSubtreeColumns as Ct, parseSlackReviewItemFormTarget as D, withoutSavedSteeringMessages as Dt, parseSlackReviewItemClick as E, buildSteeringPiMessage as Et, getConversationInfo as F, ViewerTaskNotFoundError as G, deleteViewerTask as H, joinPublicChannel as I, readViewerAutomationRuns as J, readConversationSourceTask as K, listThreadReplies as L, readSlackRawMessageContext as M, toolInvocationDestination as Mt, resolveSlackChannelIdFromMessage as N, readBlockedReport as Nt, parseSlackUserInputSubmission as O, readUserCustomization as Ot, resolveSlackChannelIdFromThreadId as P, shouldEmitDevAgentTrace as Pt, AutomationEditError as Q, logScheduledAutomationRunSkipped as R, readSlackReviewItemCardConversationId as S, conversationSubtree as St, buildSlackReviewItemModal as T, ingestEvent as Tt, readViewerAutomationEdit as U, changeViewerAutomationLifecycle as V, updateViewerAutomation as W, readViewerAutomationVersions as X, readViewerAutomationSummary as Y, readViewerAutomations as Z, unpublishedArtifactHeaders as _, stopConversationSession as _t, finalizeMcpAuthorization as a, conversationTurnIdForMessage as at, renderBlockText as b, listChildAgents as bt, deleteMcpServerSessionId as c, recordWebConversationActivity as ct, getMcpStoredOAuthCredentials as d, decodeInputImages as dt, listOwnedWatches as et, putMcpStoredOAuthCredentials as f, collectAttachmentGarbage as ft, readPublicArtifact as g, stopChildAgentsOf as gt, publicArtifactHeaders as h, storeAttachment as ht, buildAuthPauseResponse as i, appendAndEnqueueWebMessage as it, parseSlackThreadId as j, actorFromRun as jt, runWithSlackInstallation as k, saveUserCustomization as kt, deleteMcpStoredOAuthCredentials as l, webActorFromEmail as lt, readStats as m, requestAttachmentDeletion as mt, executeAgentRun as n, renderReviewItemMessage as nt, deleteMcpAuthSession as o, createAndEnqueueConversation as ot, latestProgressStatus as p, readLiveAttachment as pt, readViewerAutomationExecutions as q, createContextCompactor as r, acceptsWebMessages as rt, deleteMcpAuthSessionsForUserProvider as s, createConversationId as st, createAgentRunner as t, deliverReviewItemChange as tt, getMcpAuthSession as u, resolveConversationDestination as ut, lookupSlackUserProfile as v, recordSubagentEnded as vt, SLACK_REVIEW_ITEM_VIEW_ID as w, processWorkspaceSnapshotJob as wt, createSlackReviewItemShow as x, listUnreportedSiblingAgents as xt, renderAttachmentText as y, recordSubagentStarted as yt, scheduledAutomationRunAttributes as z };
21935
+ export { AutomationEditError as $, verifySlackSignature as A, saveUserCustomization as At, scheduledAutomationRunAttributes as B, syncReviewItemSlackCards as C, conversationSubtree as Ct, parseSlackReviewItemFormTarget as D, buildSteeringPiMessage as Dt, parseSlackReviewItemClick as E, ingestEvent as Et, getConversationInfo as F, shouldEmitDevAgentTrace as Ft, updateViewerAutomation as G, changeViewerAutomationLifecycle as H, joinPublicChannel as I, readViewerAutomationExecutions as J, ViewerTaskNotFoundError as K, listThreadReplies as L, readSlackRawMessageContext as M, actorFromRun as Mt, resolveSlackChannelIdFromMessage as N, toolInvocationDestination as Nt, parseSlackUserInputSubmission as O, withoutSavedSteeringMessages as Ot, resolveSlackChannelIdFromThreadId as P, readBlockedReport as Pt, readViewerAutomations as Q, resolveDestinationVisibility as R, readSlackReviewItemCardConversationId as S, listUnreportedSiblingAgents as St, buildSlackReviewItemModal as T, processWorkspaceSnapshotJob as Tt, deleteViewerTask as U, activateViewerAutomationVersion as V, readViewerAutomationEdit as W, readViewerAutomationSummary as X, readViewerAutomationRuns as Y, readViewerAutomationVersions as Z, unpublishedArtifactHeaders as _, stopChildAgentsOf as _t, finalizeMcpAuthorization as a, appendAndEnqueueWebMessage as at, renderBlockText as b, recordSubagentStarted as bt, deleteMcpServerSessionId as c, createConversationId as ct, getMcpStoredOAuthCredentials as d, resolveConversationDestination as dt, effectiveTaskOutcomes as et, putMcpStoredOAuthCredentials as f, decodeInputImages as ft, readPublicArtifact as g, storeAttachment as gt, publicArtifactHeaders as h, requestAttachmentDeletion as ht, buildAuthPauseResponse as i, acceptsWebMessages as it, parseSlackThreadId as j, RetryableDeliveryError as jt, runWithSlackInstallation as k, readUserCustomization as kt, deleteMcpStoredOAuthCredentials as l, recordWebConversationActivity as lt, readStats as m, readLiveAttachment as mt, executeAgentRun as n, deliverReviewItemChange as nt, deleteMcpAuthSession as o, conversationTurnIdForMessage as ot, latestProgressStatus as p, collectAttachmentGarbage as pt, readConversationSourceTask as q, createContextCompactor as r, renderReviewItemMessage as rt, deleteMcpAuthSessionsForUserProvider as s, createAndEnqueueConversation as st, createAgentRunner as t, listOwnedWatches as tt, getMcpAuthSession as u, webActorFromEmail as ut, lookupSlackUserProfile as v, stopConversationSession as vt, SLACK_REVIEW_ITEM_VIEW_ID as w, conversationSubtreeColumns as wt, createSlackReviewItemShow as x, listChildAgents as xt, renderAttachmentText as y, recordSubagentEnded as yt, logScheduledAutomationRunSkipped as z };