@sentry/junior 0.232.0 → 0.234.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-hooks-n9qfV697.js → agent-hooks-BcH6E48y.js} +89 -12
- package/dist/{agent-runner-D_FVvrwA.js → agent-runner-DUkGlK89.js} +224 -768
- package/dist/api/schema.d.ts +538 -2
- package/dist/api/schema.js +4 -4
- package/dist/api.js +230 -19
- package/dist/app.js +31 -21
- package/dist/{briefs-B7wDx-LM.js → briefs-lyAQbDxJ.js} +4 -4
- package/dist/{catalog-runtime-FU8TmK-W.js → catalog-runtime-BSgsPVnr.js} +1 -1
- package/dist/{checkpoint-DuA1tIcd.js → checkpoint-BNfXlq_1.js} +7 -7
- package/dist/cli/chat.js +13 -13
- package/dist/cli/check.js +3 -3
- package/dist/cli/main.js +1 -1
- package/dist/cli/plugins.js +3 -3
- package/dist/cli/snapshot-create.js +3 -3
- package/dist/cli/upgrade.js +3 -3
- package/dist/{client-BMqP14EE.js → client-BX5lJRqW.js} +2 -2
- package/dist/{config-Q7ayKDYh.js → config-DuCzUoCo.js} +2 -2
- package/dist/{conversation-DsuDtoYR.js → conversation-CPjHYOV5.js} +13 -2
- package/dist/{conversation-privacy-DNNsgprf.js → conversation-privacy-CSxvdSoV.js} +1 -1
- package/dist/{credential-sync-DbgAGIhd.js → credential-sync-BO4DfH9a.js} +13 -13
- package/dist/{executor-DDonSWxn.js → executor--jWf-Nnm.js} +1 -1
- package/dist/{images-Bj8PcvZ5.js → images-BaL19wFC.js} +3 -3
- package/dist/instrumentation.js +2 -2
- package/dist/{job-queue-Dqc7qQbR.js → job-queue-cwHVOwVZ.js} +1 -1
- package/dist/{logging-X4U7SInw.js → logging-Cox1Mgcv.js} +1 -1
- package/dist/nitro.js +3 -3
- package/dist/{oauth-authorization-CetcZcwQ.js → oauth-authorization-CC8a1JKP.js} +1 -1
- package/dist/{oauth-callback-server-o34WSoGm.js → oauth-callback-server-Z7QSt416.js} +2 -2
- package/dist/{pending-auth-XoxEa4O5.js → pending-auth-DQhC3eSR.js} +1 -1
- package/dist/{profile-DzqqgpDo.js → profile-BdApelHS.js} +4 -4
- package/dist/{prompt-D5RPLy92.js → prompt-3D0sSbTC.js} +1 -1
- package/dist/{registry-RrIfDnkS.js → registry-DiY6B8CN.js} +1 -1
- package/dist/{resolve-Ccu6uGtp.js → resolve-VpJoiDpc.js} +2 -2
- package/dist/{runner-DDQCEV1P.js → runner-BIMegnPE.js} +9 -9
- package/dist/{sandbox-egress-signals-CT3xOtNG.js → sandbox-egress-signals-Cbtxe492.js} +1 -1
- package/dist/{schema-B-Taar1W.js → schema-CxwnlTDN.js} +4 -117
- package/dist/{skills-NB_I9BuR.js → skills-WttpPJUy.js} +2 -2
- package/dist/{spawn-DmwIhiNu.js → spawn-DhfMKt4y.js} +2 -2
- package/dist/{task-queue-B5CERq_E.js → task-queue-CZqUNJD0.js} +3 -3
- package/dist/{task-runner-COivEGS3.js → task-runner-BZaqoUCT.js} +16 -13
- package/dist/{thread-state-C7yyfiVM.js → thread-state-8tXZhu0y.js} +5 -5
- package/dist/{turn-failure-response-D990svo-.js → turn-failure-response-DFJ9L9NQ.js} +8 -8
- package/dist/{turn-lifecycle-C-LUNJC6.js → turn-lifecycle-WbpTUQ_r.js} +1 -1
- package/dist/{turn-session-routing-CoF0GbaY.js → turn-session-routing--EhI76rH.js} +1 -1
- package/dist/types-Du-HbEun.js +621 -0
- package/dist/{validation-f0y71ety.js → validation-0F9IgG3b.js} +1 -1
- package/dist/{version-CkRTIRkV.js → version-A7-RRBOE.js} +1 -1
- package/dist/version.js +1 -1
- package/dist/{web-authorization-BBwQsNCZ.js → web-authorization-BJggXofy.js} +6 -6
- package/dist/{web-input-Bkn-JGWd.js → web-input-B-siN5Dr.js} +212 -164
- package/dist/web-message-id-CVVCTWUb.js +464 -0
- package/dist/{work-vWPq9P5x.js → work-CNc0ejGG.js} +7 -7
- package/dist/{worker-BgZog5Jp.js → worker-BHbqpUAu.js} +5 -5
- package/package.json +5 -5
- package/dist/web-message-id-xPI_EFSC.js +0 -239
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-B4FMCO8f.js";
|
|
2
|
-
import { b as getGatewayApiKey, c as hasCompactedConversationContext, d as unwrapCurrentInstruction, f as escapeXml, g as getProviderErrorAttributes, h as findProviderError, i as completeText, l as isCompactionSummaryText, m as createProviderError, n as GEN_AI_SERVER_ADDRESS, o as resolveGatewayModel, p as ProviderError, r as completeObject, s as COMPACTION_SUMMARY_PREFIX, t as GEN_AI_PROVIDER_NAME, u as renderCurrentInstruction, v as isProviderRetryError, x as resolveGatewayCredential, y as MISSING_GATEWAY_CREDENTIALS_ERROR } from "./client-
|
|
3
|
-
import { C as withLogContext, F as normalizeIdentityEmail, I as isRecord$3, L as toOptionalNumber, R as toOptionalString, a as extractGenAiUsageSummary, b as setTags, c as getLogContextAttributesForSpan, d as logException, f as logInfo, g as serializeGenAiAttribute, i as extractGenAiUsageAttributes, l as getTracePropagationHeaders, m as normalizeGenAiFinishReason, p as logWarn, s as getLogContextAttributes, u as logError, v as setSpanAttributes, w as withSpan, x as summarizeMessageText, y as setSpanStatus } from "./logging-
|
|
2
|
+
import { b as getGatewayApiKey, c as hasCompactedConversationContext, d as unwrapCurrentInstruction, f as escapeXml, g as getProviderErrorAttributes, h as findProviderError, i as completeText, l as isCompactionSummaryText, m as createProviderError, n as GEN_AI_SERVER_ADDRESS, o as resolveGatewayModel, p as ProviderError, r as completeObject, s as COMPACTION_SUMMARY_PREFIX, t as GEN_AI_PROVIDER_NAME, u as renderCurrentInstruction, v as isProviderRetryError, x as resolveGatewayCredential, y as MISSING_GATEWAY_CREDENTIALS_ERROR } from "./client-BX5lJRqW.js";
|
|
3
|
+
import { C as withLogContext, F as normalizeIdentityEmail, I as isRecord$3, L as toOptionalNumber, R as toOptionalString, a as extractGenAiUsageSummary, b as setTags, c as getLogContextAttributesForSpan, d as logException, f as logInfo, g as serializeGenAiAttribute, i as extractGenAiUsageAttributes, l as getTracePropagationHeaders, m as normalizeGenAiFinishReason, p as logWarn, s as getLogContextAttributes, u as logError, v as setSpanAttributes, w as withSpan, x as summarizeMessageText, y as setSpanStatus } from "./logging-Cox1Mgcv.js";
|
|
4
4
|
import { m as startInactiveSpan, n as captureMessage, v as withActiveSpan } from "./sentry-CgWZM_kV.js";
|
|
5
|
-
import { a as toCanonicalInputMessage, c as toGenAiPayloadMetadata, i as runWithConversationPrivacy, l as toGenAiPayloadTraceAttributes, o as toCanonicalOutputMessage, r as resolveConversationPrivacy, s as toGenAiMessagesTraceAttributes, t as canExposeConversationPayload } from "./conversation-privacy-
|
|
6
|
-
import { a as getChatConfig, h as normalizeSlackEmojiName, n as botConfig } from "./config-
|
|
5
|
+
import { a as toCanonicalInputMessage, c as toGenAiPayloadMetadata, i as runWithConversationPrivacy, l as toGenAiPayloadTraceAttributes, o as toCanonicalOutputMessage, r as resolveConversationPrivacy, s as toGenAiMessagesTraceAttributes, t as canExposeConversationPayload } from "./conversation-privacy-CSxvdSoV.js";
|
|
6
|
+
import { a as getChatConfig, h as normalizeSlackEmojiName, n as botConfig } from "./config-DuCzUoCo.js";
|
|
7
7
|
import { a as formatModelProfiles, c as profileConfig, i as formatModelProfile, l as TURN_REASONING_LEVELS, n as ModelProfileNotConfiguredError, o as modelIdForProfile, s as modelProfileSchema } from "./model-profile-BwU8mqwD.js";
|
|
8
8
|
import { b as parseSlackMessageTs, d as parseStoredSlackActor, g as parseSlackUserId, h as parseSlackTeamId, i as credentialUserSubjectId, l as isUserActor, m as parseSlackChannelReferenceId, n as credentialContextSchema, p as parseSlackChannelId, t as credentialContextForActor } from "./context-0Et9iVSB.js";
|
|
9
|
-
import { $t as getConversationStore, A as tryWorkspaceRepoCheckoutPath, B as downloadPrivateSlackFile, C as invalidateReadySnapshot, Ct as createPluginLogger, D as SANDBOX_RUNTIME, E as setWorkspaceSnapshotBuild, G as isDmChannel$1, Ht as saveObjectAnnotations, J as withSlackRetries, K as normalizeSlackConversationId, M as resolveChannelCapabilities, O as deleteWorkspaceSnapshotBuilders, Q as readActorIdentity, Qt as getConversationMessageSearchStore, Rt as matchSlackAttachments, S as WorkspaceValidationError, T as setWorkspaceSnapshot, U as getSlackClient, Ut as createPluginAnnotations, V as getFilePermalink, W as isConversationScopedChannel, Zt as getConversationEventStore, _ as deleteWorkspace, at as STOP_WATCHING_TOOL_NAME, b as listWorkspaces, bn as sameDestination$1, c as getPluginTools, cn as addAgentTurnUsage, dt as deliverWatch, en as getDb, fn as hasAgentTurnUsage, ft as findMatchingWatches, g as createWorkspace, gt as getDashboardConversationLink, ht as JUNIOR_THREAD_STATE_TTL_MS, it as RESOURCE_WATCH_TOOL_SOURCE, j as workspaceRepoCheckoutPath, k as getWorkspaceSnapshotBuilder, l as getPluginUserPromptContributions, lt as createTimerWatch, n as applyPluginFormatMarkdown, nt as RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS, ot as cancelSubscriptions, pt as listWatches, r as createPluginHookRunner, rt as RESOURCE_SUBSCRIPTION_MAX_TTL_MS, s as getPluginSystemPromptContributions, st as cancelWatch, tn as getSqlExecutor, tt as getSlackToolContext, u as getPlugins, ut as createWatch, v as getWorkspace, vn as parseDestination, vt as getDashboardTaskLink, w as loadSnapshotsForProfile, x as updateWorkspace, y as getWorkspaceByName, z as SlackActionError } from "./agent-hooks-
|
|
9
|
+
import { $t as getConversationStore, A as tryWorkspaceRepoCheckoutPath, B as downloadPrivateSlackFile, C as invalidateReadySnapshot, Ct as createPluginLogger, D as SANDBOX_RUNTIME, E as setWorkspaceSnapshotBuild, G as isDmChannel$1, Ht as saveObjectAnnotations, J as withSlackRetries, K as normalizeSlackConversationId, M as resolveChannelCapabilities, O as deleteWorkspaceSnapshotBuilders, Q as readActorIdentity, Qt as getConversationMessageSearchStore, Rt as matchSlackAttachments, S as WorkspaceValidationError, T as setWorkspaceSnapshot, U as getSlackClient, Ut as createPluginAnnotations, V as getFilePermalink, W as isConversationScopedChannel, Zt as getConversationEventStore, _ as deleteWorkspace, at as STOP_WATCHING_TOOL_NAME, b as listWorkspaces, bn as sameDestination$1, c as getPluginTools, cn as addAgentTurnUsage, dt as deliverWatch, en as getDb, fn as hasAgentTurnUsage, ft as findMatchingWatches, g as createWorkspace, gt as getDashboardConversationLink, ht as JUNIOR_THREAD_STATE_TTL_MS, it as RESOURCE_WATCH_TOOL_SOURCE, j as workspaceRepoCheckoutPath, k as getWorkspaceSnapshotBuilder, l as getPluginUserPromptContributions, lt as createTimerWatch, n as applyPluginFormatMarkdown, nt as RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS, ot as cancelSubscriptions, pt as listWatches, r as createPluginHookRunner, rt as RESOURCE_SUBSCRIPTION_MAX_TTL_MS, s as getPluginSystemPromptContributions, st as cancelWatch, tn as getSqlExecutor, tt as getSlackToolContext, u as getPlugins, ut as createWatch, v as getWorkspace, vn as parseDestination, vt as getDashboardTaskLink, w as loadSnapshotsForProfile, x as updateWorkspace, y as getWorkspaceByName, z as SlackActionError } from "./agent-hooks-BcH6E48y.js";
|
|
10
10
|
import { n as juniorDestinations } from "./destinations-BeBiJeEM.js";
|
|
11
|
-
import { D as juniorIdentities, E as juniorConversations, a as juniorSchedulerRuns, c as juniorStats, d as AGENT_INVOCATION_MAILBOX_STATUSES, g as juniorConversationBriefs, k as juniorArtifacts, l as juniorEventAutomations, o as juniorSchedulerTasks, w as juniorAttachments, x as juniorConversationAnnotations } from "./executor-
|
|
12
|
-
import { F as briefLinkSchema, G as KNOWN_CONVERSATION_EVENT_TYPES, I as briefOutcomeStatusSchema, L as conversationBriefSchema, N as isVisionImageMediaType, U as removedCardSchema, et as contextProvenance, nt as instructionProvenanceFor, rt as sameActorIdentity, tt as instructionActors } from "./conversation-
|
|
11
|
+
import { D as juniorIdentities, E as juniorConversations, a as juniorSchedulerRuns, c as juniorStats, d as AGENT_INVOCATION_MAILBOX_STATUSES, g as juniorConversationBriefs, k as juniorArtifacts, l as juniorEventAutomations, o as juniorSchedulerTasks, w as juniorAttachments, x as juniorConversationAnnotations } from "./executor--jWf-Nnm.js";
|
|
12
|
+
import { F as briefLinkSchema, G as KNOWN_CONVERSATION_EVENT_TYPES, I as briefOutcomeStatusSchema, L as conversationBriefSchema, N as isVisionImageMediaType, U as removedCardSchema, et as contextProvenance, nt as instructionProvenanceFor, rt as sameActorIdentity, tt as instructionActors } from "./conversation-CPjHYOV5.js";
|
|
13
13
|
import { a as listReferenceFiles, c as soulPathCandidates, l as worldPathCandidates } from "./package-discovery-7a9YqLhE.js";
|
|
14
|
-
import { a as resolveAuthTokenPlaceholder, c as fetchWithBoundedOAuthErrorBodies, d as CredentialUnavailableError, i as resolvePluginHeaderEnvRefs, l as getOAuthProviderErrorAttributes, o as resolvePluginCommandEnv, s as OAuthProviderError } from "./registry-
|
|
15
|
-
import { n as pluginCatalogRuntime } from "./catalog-runtime-
|
|
16
|
-
import { a as parseSkillInvocation, n as findSkillByName, r as loadSkillsByName, t as discoverSkills } from "./skills-
|
|
17
|
-
import { $ as fallbackShortTitle, C as isToolResultError, D as stripRuntimeTurnContext, E as retainRuntimeTurnContext, F as loadTurnRoute, G as recordToolExecutionStarted, H as recordGuardianActionReviewed, I as openConversationProjection, K as recordTurnRoute, L as recordAgentsInstructionsUpdated, M as loadConnectedMcpProviders, N as loadConversationProjection, O as trimTrailingAssistantMessages, R as recordAttachmentsDelivered, S as isContinuablePiBoundary, T as normalizeToolNameFromResult, U as recordMcpProviderConnected, V as recordAuthorizationRequested, _ as getTerminalAssistantMessages,
|
|
18
|
-
import { A as SANDBOX_DATA_ROOT, C as getDefaultRedisStateAdapterFor, D as fenceLock, E as acquireActiveLock, F as sandboxSkillFile, I as sandboxSkillPathResolution, M as SANDBOX_SKILLS_ROOT, N as SANDBOX_WORKSPACE_ROOT, O as withLock, P as sandboxSkillDir, _ as workspaceSnapshotNotReadyUserMessage, a as isAbortError, b as buildNonInteractiveShellScript, c as isSandboxCommandStreamInterruptedError, d as isSnapshottingError, f as throwSandboxOperationError, g as isWorkspaceSnapshotNotReadyError, h as getWorkspaceSnapshotNotReadyError, i as ensureDockerDaemon, j as SANDBOX_REPOS_ROOT, k as sleep$2, l as isSandboxMissingError, m as WorkspaceSnapshotNotReadyError, n as hash, o as isAlreadyExistsError, p as wrapSandboxSetupError, r as isStale, s as isSandboxApiTransientError, t as create, u as isSandboxUnavailableError, v as getVercelSandboxCredentials, w as getStateAdapter, x as runNonInteractiveCommand, y as buildCommandScript } from "./profile-
|
|
19
|
-
import { n as canReusePendingAuthLink, t as abandonReplacedPendingAuth } from "./pending-auth-
|
|
20
|
-
import { a as NO_REPLY_MARKER, c as CooperativeTurnYieldError, i as sanitizeAssistantText, l as TurnInputCommitLostError, n as decideReply, o as isNoReplyMarker, p as isTurnInputCommitLostError, r as getAssistantReplyText, t as authorizationId } from "./oauth-authorization-
|
|
21
|
-
import { r as EVENT_AUTHOR_ID } from "./prompt-
|
|
22
|
-
import { a as createSandboxSession, c as postinstall, d as getSandboxResources, i as bindSandboxFileSystem, l as setSnapshotSpanAttributes, n as isMissingError, o as stopSession, r as resolve$1, s as dependencies } from "./resolve-
|
|
23
|
-
import { a as getVercelConversationWorkQueue, f as getTurnRequestDeadline, n as sendNextWorkspaceSnapshotJob, r as sendWorkspaceSnapshotJob } from "./job-queue-
|
|
14
|
+
import { a as resolveAuthTokenPlaceholder, c as fetchWithBoundedOAuthErrorBodies, d as CredentialUnavailableError, i as resolvePluginHeaderEnvRefs, l as getOAuthProviderErrorAttributes, o as resolvePluginCommandEnv, s as OAuthProviderError } from "./registry-DiY6B8CN.js";
|
|
15
|
+
import { n as pluginCatalogRuntime } from "./catalog-runtime-BSgsPVnr.js";
|
|
16
|
+
import { a as parseSkillInvocation, n as findSkillByName, r as loadSkillsByName, t as discoverSkills } from "./skills-WttpPJUy.js";
|
|
17
|
+
import { $ as fallbackShortTitle, C as isToolResultError, D as stripRuntimeTurnContext, E as retainRuntimeTurnContext, F as loadTurnRoute, G as recordToolExecutionStarted, H as recordGuardianActionReviewed, I as openConversationProjection, K as recordTurnRoute, L as recordAgentsInstructionsUpdated, M as loadConnectedMcpProviders, N as loadConversationProjection, O as trimTrailingAssistantMessages, R as recordAttachmentsDelivered, S as isContinuablePiBoundary, T as normalizeToolNameFromResult, U as recordMcpProviderConnected, V as recordAuthorizationRequested, _ as getTerminalAssistantMessages, d as TurnSliceLimitExceededError, et as resolveTaskTitle, f as assertTurnToolCallLimit, ft as estimateTextTokens, g as getPiMessageRole, gt as piMessageFromHistoryItem, h as extractAssistantText, ht as historyItemFromPiMessage, k as TURN_CONTEXT_TAG, m as isTurnExecutionLimitExceededError, mt as getAgentContextInputLimitTokens, n as loadTurnCheckpoint, pt as getAgentContextCompactionTriggerTokens, r as saveTurnCheckpoint, t as continuableMessages, v as getUserMessageInstructionText, w as isToolResultMessage, x as isAssistantMessage, y as hasRuntimeTurnContext } from "./checkpoint-BNfXlq_1.js";
|
|
18
|
+
import { A as SANDBOX_DATA_ROOT, C as getDefaultRedisStateAdapterFor, D as fenceLock, E as acquireActiveLock, F as sandboxSkillFile, I as sandboxSkillPathResolution, M as SANDBOX_SKILLS_ROOT, N as SANDBOX_WORKSPACE_ROOT, O as withLock, P as sandboxSkillDir, _ as workspaceSnapshotNotReadyUserMessage, a as isAbortError, b as buildNonInteractiveShellScript, c as isSandboxCommandStreamInterruptedError, d as isSnapshottingError, f as throwSandboxOperationError, g as isWorkspaceSnapshotNotReadyError, h as getWorkspaceSnapshotNotReadyError, i as ensureDockerDaemon, j as SANDBOX_REPOS_ROOT, k as sleep$2, l as isSandboxMissingError, m as WorkspaceSnapshotNotReadyError, n as hash, o as isAlreadyExistsError, p as wrapSandboxSetupError, r as isStale, s as isSandboxApiTransientError, t as create, u as isSandboxUnavailableError, v as getVercelSandboxCredentials, w as getStateAdapter, x as runNonInteractiveCommand, y as buildCommandScript } from "./profile-BdApelHS.js";
|
|
19
|
+
import { n as canReusePendingAuthLink, t as abandonReplacedPendingAuth } from "./pending-auth-DQhC3eSR.js";
|
|
20
|
+
import { a as NO_REPLY_MARKER, c as CooperativeTurnYieldError, i as sanitizeAssistantText, l as TurnInputCommitLostError, n as decideReply, o as isNoReplyMarker, p as isTurnInputCommitLostError, r as getAssistantReplyText, t as authorizationId } from "./oauth-authorization-CC8a1JKP.js";
|
|
21
|
+
import { r as EVENT_AUTHOR_ID } from "./prompt-3D0sSbTC.js";
|
|
22
|
+
import { a as createSandboxSession, c as postinstall, d as getSandboxResources, i as bindSandboxFileSystem, l as setSnapshotSpanAttributes, n as isMissingError, o as stopSession, r as resolve$1, s as dependencies } from "./resolve-VpJoiDpc.js";
|
|
23
|
+
import { a as getVercelConversationWorkQueue, f as getTurnRequestDeadline, n as sendNextWorkspaceSnapshotJob, r as sendWorkspaceSnapshotJob } from "./job-queue-cwHVOwVZ.js";
|
|
24
|
+
import { a as automationTitleSchema, c as ScheduleIntentError, g as taskOutcomeInputSchema, i as automationInstructionSchema, l as compileScheduleIntent, m as eventAutomationSchema, n as scheduledAutomationSchema, p as getZonedDateTimeParts, r as scheduledRunSchema, t as SCHEDULED_AUTOMATION_SYSTEM_ACTOR, u as scheduleIntentSchema } from "./types-Du-HbEun.js";
|
|
24
25
|
import { n as privateTraceResultAttributes } from "./private-trace-result-BLvBjy_e.js";
|
|
25
26
|
import { streamSimple } from "@earendil-works/pi-ai/compat";
|
|
26
27
|
import { createGatewayProvider } from "@ai-sdk/gateway";
|
|
@@ -31,7 +32,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
31
32
|
import { z } from "zod";
|
|
32
33
|
import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
33
34
|
import { and, asc, desc, eq, gt, gte, inArray, isNotNull, isNull, lt, lte, ne, notExists, notInArray, or, sql } from "drizzle-orm";
|
|
34
|
-
import { EgressAuthRequired, EgressPolicyDenied, PluginToolInputError, actorSchema,
|
|
35
|
+
import { EgressAuthRequired, EgressPolicyDenied, PluginToolInputError, actorSchema, destinationSchema, eventMatchSchema, eventMatches, eventSchema, missingToolAnnotationKeys, normalizeEventIdentifier, objectTypeSchema, ownedObjectAnnotationSchema, pluginAuthorizationSchema, pluginCredentialHeaderTransformSchema, pluginCredentialResultSchema, pluginGrantSchema, pluginProviderAccountSchema, pluginStoredTokensSchema, slackDestinationSchema, sourceSchema, stableEventMatchKey, taskOutcomeSchema, watchResultSchema } from "@sentry/junior-plugin-api";
|
|
35
36
|
import { alias } from "drizzle-orm/pg-core";
|
|
36
37
|
import { Agent, estimateContextTokens } from "@earendil-works/pi-agent-core";
|
|
37
38
|
import fs from "node:fs/promises";
|
|
@@ -9154,6 +9155,7 @@ var McpToolManager = class {
|
|
|
9154
9155
|
};
|
|
9155
9156
|
const cards = await this.options.onToolSuccess?.({
|
|
9156
9157
|
arguments: resolvedArgs,
|
|
9158
|
+
content: toAgentToolContent(result),
|
|
9157
9159
|
provider: plugin.manifest.name,
|
|
9158
9160
|
...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : void 0,
|
|
9159
9161
|
toolName: tool.name
|
|
@@ -10251,37 +10253,28 @@ function createEventTools(context, catalog) {
|
|
|
10251
10253
|
stopWatchingResources: createStopWatchingResourcesTool(context)
|
|
10252
10254
|
};
|
|
10253
10255
|
}
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
/**
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
createdBy: eventAutomationPrincipalSchema,
|
|
10277
|
-
credentialMode: z.enum(["system", "creator"]),
|
|
10278
|
-
destination: eventAutomationDestinationSchema,
|
|
10279
|
-
destinationVisibility: destinationVisibilitySchema,
|
|
10280
|
-
/** Explicit visible effects after successful work. An empty list is silent. */
|
|
10281
|
-
outcomes: z.array(eventAutomationOutcomeSchema).max(5),
|
|
10282
|
-
task: z.object({ text: z.string().min(1) }).strict(),
|
|
10283
|
-
trigger: eventAutomationTriggerSchema
|
|
10284
|
-
}).strict();
|
|
10256
|
+
//#endregion
|
|
10257
|
+
//#region src/chat/automations/revision.ts
|
|
10258
|
+
/** Storage revisions prevent stale edits from replacing newer values. */
|
|
10259
|
+
var AutomationConflictError = class extends ToolInputError {
|
|
10260
|
+
constructor() {
|
|
10261
|
+
super("This Automation changed after it was loaded. Reload it before saving.");
|
|
10262
|
+
this.name = "AutomationConflictError";
|
|
10263
|
+
}
|
|
10264
|
+
};
|
|
10265
|
+
function canonicalJson$1(value) {
|
|
10266
|
+
if (Array.isArray(value)) return value.map(canonicalJson$1);
|
|
10267
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, child]) => [key, canonicalJson$1(child)]));
|
|
10268
|
+
return value;
|
|
10269
|
+
}
|
|
10270
|
+
/** Identify the stored values being edited, including lifecycle and creator authority. */
|
|
10271
|
+
function automationRevision(task) {
|
|
10272
|
+
return createHash("sha256").update(JSON.stringify(canonicalJson$1(task))).digest("hex");
|
|
10273
|
+
}
|
|
10274
|
+
/** Reject a stale edit while the storage owner holds the write lock. */
|
|
10275
|
+
function requireAutomationRevision(task, revision) {
|
|
10276
|
+
if (!task || automationRevision(task) !== revision) throw new AutomationConflictError();
|
|
10277
|
+
}
|
|
10285
10278
|
//#endregion
|
|
10286
10279
|
//#region src/chat/event-automations/store.ts
|
|
10287
10280
|
const retainedEventAutomationSchema = eventAutomationSchema.extend({
|
|
@@ -10293,7 +10286,8 @@ function eventAutomationJsonPayload(task) {
|
|
|
10293
10286
|
const { status: _status, title: _title, ...payload } = task;
|
|
10294
10287
|
return payload;
|
|
10295
10288
|
}
|
|
10296
|
-
|
|
10289
|
+
/** Decode a retained event Automation with its SQL-backed fields. */
|
|
10290
|
+
function parseEventAutomationRow(row) {
|
|
10297
10291
|
const raw = row.task && typeof row.task === "object" ? { ...row.task } : row.task;
|
|
10298
10292
|
if (raw && typeof raw === "object" && "title" in raw) delete raw.title;
|
|
10299
10293
|
if (raw && typeof raw === "object" && !("outcomes" in raw) && "destination" in raw) raw.outcomes = [{
|
|
@@ -10313,7 +10307,7 @@ function parseTask(row) {
|
|
|
10313
10307
|
destination
|
|
10314
10308
|
};
|
|
10315
10309
|
}),
|
|
10316
|
-
status: row.status
|
|
10310
|
+
status: row.status ?? "active",
|
|
10317
10311
|
...title ? { title } : void 0
|
|
10318
10312
|
};
|
|
10319
10313
|
}
|
|
@@ -10327,7 +10321,7 @@ async function getEventAutomation(db, id) {
|
|
|
10327
10321
|
task: juniorEventAutomations.task,
|
|
10328
10322
|
title: juniorEventAutomations.title
|
|
10329
10323
|
}).from(juniorEventAutomations).where(eq(juniorEventAutomations.id, id)).limit(1);
|
|
10330
|
-
return rows[0] ?
|
|
10324
|
+
return rows[0] ? parseEventAutomationRow(rows[0]) : void 0;
|
|
10331
10325
|
}
|
|
10332
10326
|
/** Create one retry-stable event automation, or revive a deleted row with the new payload. */
|
|
10333
10327
|
async function createEventAutomation(db, task) {
|
|
@@ -10362,14 +10356,19 @@ async function createEventAutomation(db, task) {
|
|
|
10362
10356
|
task: juniorEventAutomations.task,
|
|
10363
10357
|
title: juniorEventAutomations.title
|
|
10364
10358
|
});
|
|
10365
|
-
return rows[0] ?
|
|
10359
|
+
return rows[0] ? parseEventAutomationRow(rows[0]) : await getEventAutomation(db, parsed.id) ?? {
|
|
10366
10360
|
...parsed,
|
|
10367
10361
|
status: "active",
|
|
10368
10362
|
...title ? { title } : void 0
|
|
10369
10363
|
};
|
|
10370
10364
|
}
|
|
10371
10365
|
/** Replace an existing non-deleted event automation. */
|
|
10372
|
-
async function saveEventAutomation(db, task) {
|
|
10366
|
+
async function saveEventAutomation(db, task, expectedRevision) {
|
|
10367
|
+
if (expectedRevision !== void 0) return db.transaction(async (tx) => {
|
|
10368
|
+
const rows = await tx.select().from(juniorEventAutomations).where(eq(juniorEventAutomations.id, task.id)).for("update");
|
|
10369
|
+
requireAutomationRevision(rows[0] ? parseEventAutomationRow(rows[0]) : void 0, expectedRevision);
|
|
10370
|
+
return saveEventAutomation(tx, task);
|
|
10371
|
+
});
|
|
10373
10372
|
const parsed = eventAutomationSchema.parse(eventAutomationJsonPayload(task));
|
|
10374
10373
|
const title = task.title?.trim() || null;
|
|
10375
10374
|
const rows = await db.update(juniorEventAutomations).set({
|
|
@@ -10382,7 +10381,7 @@ async function saveEventAutomation(db, task) {
|
|
|
10382
10381
|
task: juniorEventAutomations.task,
|
|
10383
10382
|
title: juniorEventAutomations.title
|
|
10384
10383
|
});
|
|
10385
|
-
return rows[0] ?
|
|
10384
|
+
return rows[0] ? parseEventAutomationRow(rows[0]) : void 0;
|
|
10386
10385
|
}
|
|
10387
10386
|
/** Mark one existing event automation deleted while retaining the row for history. */
|
|
10388
10387
|
async function deleteEventAutomation(db, id) {
|
|
@@ -10391,7 +10390,7 @@ async function deleteEventAutomation(db, id) {
|
|
|
10391
10390
|
task: juniorEventAutomations.task,
|
|
10392
10391
|
title: juniorEventAutomations.title
|
|
10393
10392
|
});
|
|
10394
|
-
return rows[0] ?
|
|
10393
|
+
return rows[0] ? parseEventAutomationRow(rows[0]) : void 0;
|
|
10395
10394
|
}
|
|
10396
10395
|
/** List live event automations in one Slack workspace. */
|
|
10397
10396
|
async function listEventAutomationsForTeam(db, teamId) {
|
|
@@ -10399,7 +10398,7 @@ async function listEventAutomationsForTeam(db, teamId) {
|
|
|
10399
10398
|
status: juniorEventAutomations.status,
|
|
10400
10399
|
task: juniorEventAutomations.task,
|
|
10401
10400
|
title: juniorEventAutomations.title
|
|
10402
|
-
}).from(juniorEventAutomations).where(and(eq(juniorEventAutomations.teamId, teamId), activeEventAutomationWhere())).orderBy(asc(juniorEventAutomations.createdAtMs), asc(juniorEventAutomations.id))).map(
|
|
10401
|
+
}).from(juniorEventAutomations).where(and(eq(juniorEventAutomations.teamId, teamId), activeEventAutomationWhere())).orderBy(asc(juniorEventAutomations.createdAtMs), asc(juniorEventAutomations.id))).map(parseEventAutomationRow);
|
|
10403
10402
|
}
|
|
10404
10403
|
function viewerSlackIdentities(user) {
|
|
10405
10404
|
return user.identities.filter((identity) => identity.provider === "slack" && Boolean(identity.providerTenantId) && Boolean(identity.providerSubjectId));
|
|
@@ -10408,17 +10407,6 @@ function viewerSlackIdentities(user) {
|
|
|
10408
10407
|
function eventAutomationBelongsToUser(task, user) {
|
|
10409
10408
|
return viewerSlackIdentities(user).some((identity) => identity.providerTenantId === task.destination.teamId && identity.providerSubjectId === task.createdBy.slackUserId);
|
|
10410
10409
|
}
|
|
10411
|
-
/** List a bounded newest-first page of live event automations created by one user. */
|
|
10412
|
-
async function listEventAutomationsCreatedBy(db, user, limit, query) {
|
|
10413
|
-
const identities = viewerSlackIdentities(user);
|
|
10414
|
-
const ownership = or(...identities.map((identity) => and(eq(juniorEventAutomations.teamId, identity.providerTenantId), sql`${juniorEventAutomations.task}->'createdBy'->>'slackUserId' = ${identity.providerSubjectId}`)));
|
|
10415
|
-
if (!ownership) return [];
|
|
10416
|
-
return (await db.select({
|
|
10417
|
-
status: juniorEventAutomations.status,
|
|
10418
|
-
task: juniorEventAutomations.task,
|
|
10419
|
-
title: juniorEventAutomations.title
|
|
10420
|
-
}).from(juniorEventAutomations).where(and(ownership, activeEventAutomationWhere(), query ? sql`strpos(lower(coalesce(${juniorEventAutomations.title}, ${juniorEventAutomations.task}->'task'->>'text')), ${query}) > 0` : void 0)).orderBy(desc(juniorEventAutomations.createdAtMs), desc(juniorEventAutomations.id)).limit(limit)).map(parseTask);
|
|
10421
|
-
}
|
|
10422
10410
|
/**
|
|
10423
10411
|
* List deleted event automations created by one user, newest first.
|
|
10424
10412
|
* Used only to keep historical runs after the live automation list drops the row.
|
|
@@ -10431,16 +10419,7 @@ async function listDeletedEventAutomationsCreatedBy(db, user, limit) {
|
|
|
10431
10419
|
status: juniorEventAutomations.status,
|
|
10432
10420
|
task: juniorEventAutomations.task,
|
|
10433
10421
|
title: juniorEventAutomations.title
|
|
10434
|
-
}).from(juniorEventAutomations).where(and(ownership, eq(juniorEventAutomations.status, "deleted"))).orderBy(desc(juniorEventAutomations.createdAtMs), desc(juniorEventAutomations.id)).limit(limit)).map(
|
|
10435
|
-
}
|
|
10436
|
-
/** List live event automations whose current Slack destination is public. */
|
|
10437
|
-
async function listPublicEventAutomationsForTeams(db, teamIds, limit, query) {
|
|
10438
|
-
if (teamIds.length === 0) return [];
|
|
10439
|
-
return (await db.select({
|
|
10440
|
-
status: juniorEventAutomations.status,
|
|
10441
|
-
task: juniorEventAutomations.task,
|
|
10442
|
-
title: juniorEventAutomations.title
|
|
10443
|
-
}).from(juniorEventAutomations).innerJoin(juniorDestinations, and(eq(juniorDestinations.provider, "slack"), eq(juniorDestinations.providerTenantId, juniorEventAutomations.teamId), sql`${juniorDestinations.providerDestinationId} = ${juniorEventAutomations.task}->'destination'->>'channelId'`)).where(and(inArray(juniorEventAutomations.teamId, teamIds), activeEventAutomationWhere(), query ? sql`strpos(lower(coalesce(${juniorEventAutomations.title}, ${juniorEventAutomations.task}->'task'->>'text')), ${query}) > 0` : void 0, eq(juniorDestinations.visibility, "public"))).orderBy(desc(juniorEventAutomations.createdAtMs), desc(juniorEventAutomations.id)).limit(limit)).map(parseTask);
|
|
10422
|
+
}).from(juniorEventAutomations).where(and(ownership, eq(juniorEventAutomations.status, "deleted"))).orderBy(desc(juniorEventAutomations.createdAtMs), desc(juniorEventAutomations.id)).limit(limit)).map(parseEventAutomationRow);
|
|
10444
10423
|
}
|
|
10445
10424
|
/**
|
|
10446
10425
|
* Collect match keys from live event automations for these identifiers and event types.
|
|
@@ -10454,10 +10433,10 @@ async function collectEventAutomationMatchKeys(db, input) {
|
|
|
10454
10433
|
status: juniorEventAutomations.status,
|
|
10455
10434
|
task: juniorEventAutomations.task,
|
|
10456
10435
|
title: juniorEventAutomations.title
|
|
10457
|
-
}).from(juniorEventAutomations).where(and(eq(juniorEventAutomations.teamId, input.teamId), eq(juniorEventAutomations.namespace, input.namespace), inArray(juniorEventAutomations.identifier, identifiers),
|
|
10436
|
+
}).from(juniorEventAutomations).where(and(eq(juniorEventAutomations.teamId, input.teamId), eq(juniorEventAutomations.namespace, input.namespace), inArray(juniorEventAutomations.identifier, identifiers), eq(juniorEventAutomations.status, "active"))).orderBy(asc(juniorEventAutomations.createdAtMs), asc(juniorEventAutomations.id));
|
|
10458
10437
|
const keys = /* @__PURE__ */ new Set();
|
|
10459
10438
|
for (const row of rows) {
|
|
10460
|
-
const task =
|
|
10439
|
+
const task = parseEventAutomationRow(row);
|
|
10461
10440
|
if (!task.trigger.events.some((eventType) => eventTypes.has(eventType))) continue;
|
|
10462
10441
|
for (const key of Object.keys(task.trigger.match ?? {})) keys.add(key);
|
|
10463
10442
|
}
|
|
@@ -10469,16 +10448,24 @@ async function findMatchingEventAutomations(db, event, teamId) {
|
|
|
10469
10448
|
status: juniorEventAutomations.status,
|
|
10470
10449
|
task: juniorEventAutomations.task,
|
|
10471
10450
|
title: juniorEventAutomations.title
|
|
10472
|
-
}).from(juniorEventAutomations).where(and(eq(juniorEventAutomations.teamId, teamId), eq(juniorEventAutomations.namespace, event.namespace), eq(juniorEventAutomations.identifier, event.identifier),
|
|
10451
|
+
}).from(juniorEventAutomations).where(and(eq(juniorEventAutomations.teamId, teamId), eq(juniorEventAutomations.namespace, event.namespace), eq(juniorEventAutomations.identifier, event.identifier), eq(juniorEventAutomations.status, "active"))).orderBy(asc(juniorEventAutomations.createdAtMs), asc(juniorEventAutomations.id))).map(parseEventAutomationRow).filter((task) => task.trigger.events.includes(event.eventType) && eventMatches(task.trigger.match, event.data));
|
|
10452
|
+
}
|
|
10453
|
+
/** Change lifecycle under the same row lock as edits, without changing credentials. */
|
|
10454
|
+
async function setEventAutomationStatus(db, id, status, revision) {
|
|
10455
|
+
return db.transaction(async (tx) => {
|
|
10456
|
+
const rows = await tx.select().from(juniorEventAutomations).where(eq(juniorEventAutomations.id, id)).for("update");
|
|
10457
|
+
const current = rows[0] ? parseEventAutomationRow(rows[0]) : void 0;
|
|
10458
|
+
requireAutomationRevision(current, revision);
|
|
10459
|
+
if (!current || current.status === "deleted") throw new Error("Automation no longer exists.");
|
|
10460
|
+
await tx.update(juniorEventAutomations).set({ status }).where(eq(juniorEventAutomations.id, id));
|
|
10461
|
+
return {
|
|
10462
|
+
...current,
|
|
10463
|
+
status
|
|
10464
|
+
};
|
|
10465
|
+
});
|
|
10473
10466
|
}
|
|
10474
10467
|
//#endregion
|
|
10475
10468
|
//#region src/chat/task-outcomes.ts
|
|
10476
|
-
const taskMessageDestinationInputSchema = z.enum(["current_conversation", "task_creator"]).describe("Where to send the message. Use current_conversation for posts, digests, summaries, and channel reminders. Use task_creator only when the user asks for a direct reminder or notification.");
|
|
10477
|
-
/** Input accepted by task authoring tools before a user becomes a DM Destination. */
|
|
10478
|
-
const taskOutcomeInputSchema = z.object({
|
|
10479
|
-
action: z.literal("send_message"),
|
|
10480
|
-
destination: taskMessageDestinationInputSchema
|
|
10481
|
-
}).strict();
|
|
10482
10469
|
/** Resolve explicit message outcomes to the Slack Destinations stored on an Automation. */
|
|
10483
10470
|
async function resolveTaskOutcomes(outcomes, currentDestination, creatorSlackUserId) {
|
|
10484
10471
|
if (outcomes === void 0) return [];
|
|
@@ -10526,6 +10513,11 @@ function moveTaskOutcomes(outcomes, currentDestination, nextDestination) {
|
|
|
10526
10513
|
//#region src/chat/event-automations/tool-support.ts
|
|
10527
10514
|
const compactEventAutomationResultSchema = z.object({
|
|
10528
10515
|
id: z.string().min(1),
|
|
10516
|
+
status: z.enum([
|
|
10517
|
+
"active",
|
|
10518
|
+
"paused",
|
|
10519
|
+
"deleted"
|
|
10520
|
+
]),
|
|
10529
10521
|
title: z.string().min(1).nullable(),
|
|
10530
10522
|
dashboardUrl: z.string().url().nullable(),
|
|
10531
10523
|
instruction: z.string().min(1),
|
|
@@ -10625,6 +10617,7 @@ function eventAutomationTriggerAvailable(task, catalog) {
|
|
|
10625
10617
|
function compactEventAutomation(task, catalog, requesterSlackUserId) {
|
|
10626
10618
|
return compactEventAutomationResultSchema.parse({
|
|
10627
10619
|
id: task.id,
|
|
10620
|
+
status: task.status,
|
|
10628
10621
|
title: task.title?.trim() || null,
|
|
10629
10622
|
dashboardUrl: getDashboardTaskLink(task.id) ?? null,
|
|
10630
10623
|
instruction: task.task.text,
|
|
@@ -10828,13 +10821,78 @@ function createListEventAutomationsTool(context, catalog) {
|
|
|
10828
10821
|
});
|
|
10829
10822
|
}
|
|
10830
10823
|
//#endregion
|
|
10831
|
-
//#region src/chat/
|
|
10832
|
-
/**
|
|
10824
|
+
//#region src/chat/automations/edit-rules.ts
|
|
10825
|
+
/** Shared edit rules. Transport access checks run before these rules. */
|
|
10826
|
+
var AutomationEditError = class extends ToolInputError {
|
|
10827
|
+
field;
|
|
10828
|
+
constructor(message, field) {
|
|
10829
|
+
super(message);
|
|
10830
|
+
this.field = field;
|
|
10831
|
+
this.name = "AutomationEditError";
|
|
10832
|
+
}
|
|
10833
|
+
};
|
|
10834
|
+
/** Apply shared title, instruction, outcome, and creator credential rules. */
|
|
10835
|
+
async function editAutomationFields(current, input, isCreator, changesExecution) {
|
|
10836
|
+
const kind = "schedule" in current ? "scheduled" : "event";
|
|
10837
|
+
if (input.credentialMode === "creator" && !isCreator) throw new AutomationEditError(`Only the ${kind} automation creator can enable creator credential use.`, "credentialMode");
|
|
10838
|
+
if (input.outcomes !== void 0 && !isCreator) throw new AutomationEditError(`Only the ${kind} automation creator can change message destinations.`, "outcomes");
|
|
10839
|
+
let outcomes = current.outcomes;
|
|
10840
|
+
if (input.outcomes !== void 0) {
|
|
10841
|
+
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");
|
|
10842
|
+
outcomes = [];
|
|
10843
|
+
for (const outcome of input.outcomes) if (typeof outcome.destination === "string") outcomes.push(...await resolveTaskOutcomes([{
|
|
10844
|
+
action: outcome.action,
|
|
10845
|
+
destination: outcome.destination
|
|
10846
|
+
}], current.destination, current.createdBy.slackUserId));
|
|
10847
|
+
else outcomes.push({
|
|
10848
|
+
action: outcome.action,
|
|
10849
|
+
destination: outcome.destination
|
|
10850
|
+
});
|
|
10851
|
+
}
|
|
10852
|
+
return {
|
|
10853
|
+
title: input.title ?? current.title,
|
|
10854
|
+
task: { text: input.instruction ?? current.task.text },
|
|
10855
|
+
credentialMode: changesExecution && !isCreator ? "system" : input.credentialMode ?? current.credentialMode,
|
|
10856
|
+
outcomes
|
|
10857
|
+
};
|
|
10858
|
+
}
|
|
10859
|
+
//#endregion
|
|
10860
|
+
//#region src/chat/event-automations/edit.ts
|
|
10833
10861
|
function changesEventAutomationTrigger(current, next) {
|
|
10834
10862
|
const currentEvents = [...current.events].sort();
|
|
10835
10863
|
const nextEvents = [...next.events].sort();
|
|
10836
10864
|
return current.namespace !== next.namespace || current.identifier !== next.identifier || currentEvents.length !== nextEvents.length || currentEvents.some((event, index) => event !== nextEvents[index]) || stableEventMatchKey(current.match) !== stableEventMatchKey(next.match);
|
|
10837
10865
|
}
|
|
10866
|
+
/** Build a validated edit while retaining an unchanged, possibly unavailable trigger. */
|
|
10867
|
+
async function editEventAutomation(current, input, isCreator, catalog) {
|
|
10868
|
+
let trigger = current.trigger;
|
|
10869
|
+
if (input.trigger !== void 0) {
|
|
10870
|
+
if (changesEventAutomationTrigger(current.trigger, input.trigger) || current.trigger.resourceType !== input.trigger.resourceType) try {
|
|
10871
|
+
const match = requireSupportedEventAutomationTrigger(catalog, input.trigger);
|
|
10872
|
+
trigger = {
|
|
10873
|
+
...input.trigger,
|
|
10874
|
+
identifier: normalizeCatalogEventIdentifier(catalog, input.trigger.namespace, input.trigger.identifier),
|
|
10875
|
+
events: [...new Set(input.trigger.events)],
|
|
10876
|
+
match
|
|
10877
|
+
};
|
|
10878
|
+
} catch (error) {
|
|
10879
|
+
if (error instanceof ToolInputError) throw new AutomationEditError(error.message, "trigger");
|
|
10880
|
+
throw error;
|
|
10881
|
+
}
|
|
10882
|
+
else trigger = {
|
|
10883
|
+
...current.trigger,
|
|
10884
|
+
label: input.trigger.label
|
|
10885
|
+
};
|
|
10886
|
+
}
|
|
10887
|
+
const changesExecution = input.instruction !== void 0 && input.instruction !== current.task.text || changesEventAutomationTrigger(current.trigger, trigger);
|
|
10888
|
+
return {
|
|
10889
|
+
...current,
|
|
10890
|
+
...await editAutomationFields(current, input, isCreator, changesExecution),
|
|
10891
|
+
trigger
|
|
10892
|
+
};
|
|
10893
|
+
}
|
|
10894
|
+
//#endregion
|
|
10895
|
+
//#region src/chat/tools/update-event-automation.ts
|
|
10838
10896
|
/** Create the core tool that updates an event automation. */
|
|
10839
10897
|
function createUpdateEventAutomationTool(context, catalog) {
|
|
10840
10898
|
return zodTool({
|
|
@@ -10849,7 +10907,8 @@ function createUpdateEventAutomationTool(context, catalog) {
|
|
|
10849
10907
|
description: "Update the instruction, registered trigger, or credential use for an event automation.",
|
|
10850
10908
|
inputSchema: z.object({
|
|
10851
10909
|
automationId: z.string().min(1),
|
|
10852
|
-
|
|
10910
|
+
title: automationTitleSchema.optional(),
|
|
10911
|
+
instruction: automationInstructionSchema.nullable().optional(),
|
|
10853
10912
|
trigger: registeredEventAutomationTriggerSchema(catalog).nullable().optional(),
|
|
10854
10913
|
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(),
|
|
10855
10914
|
credentialMode: z.enum(["system", "creator"]).nullable().describe("Set creator to make the task's original creator credentials available, or system to disable them. Creator always means the task's createdBy actor, never the current requester. Only that original creator may enable creator mode. Omit or use null to leave unchanged.").optional()
|
|
@@ -10865,43 +10924,19 @@ function createUpdateEventAutomationTool(context, catalog) {
|
|
|
10865
10924
|
};
|
|
10866
10925
|
},
|
|
10867
10926
|
outputSchema: eventAutomationToolResultSchema,
|
|
10868
|
-
async execute(input
|
|
10927
|
+
async execute(input) {
|
|
10869
10928
|
const current = await writableEventAutomation(context, input.automationId);
|
|
10870
|
-
const match = input.trigger ? requireSupportedEventAutomationTrigger(catalog, input.trigger) : void 0;
|
|
10871
10929
|
const { actor } = requireEventAutomationSlackContext(context);
|
|
10872
10930
|
const isCreator = actor.userId === current.createdBy.slackUserId;
|
|
10873
|
-
if (input.
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
...match ? { match } : void 0
|
|
10883
|
-
} : current.trigger;
|
|
10884
|
-
const nextInstruction = input.instruction != null ? input.instruction : current.task.text;
|
|
10885
|
-
const instructionChanged = nextInstruction !== current.task.text;
|
|
10886
|
-
const changesExecution = instructionChanged || changesEventAutomationTrigger(current.trigger, nextTrigger);
|
|
10887
|
-
const next = {
|
|
10888
|
-
...current,
|
|
10889
|
-
credentialMode: changesExecution && !isCreator ? "system" : input.credentialMode ?? current.credentialMode,
|
|
10890
|
-
outcomes: input.outcomes == null ? current.outcomes : await resolveTaskOutcomes(input.outcomes, current.destination, current.createdBy.slackUserId),
|
|
10891
|
-
task: { text: nextInstruction },
|
|
10892
|
-
trigger: nextTrigger
|
|
10893
|
-
};
|
|
10894
|
-
if (instructionChanged) {
|
|
10895
|
-
const title = await generateShortTitle({
|
|
10896
|
-
signal: options.signal,
|
|
10897
|
-
completeText,
|
|
10898
|
-
kind: "task",
|
|
10899
|
-
sourceText: nextInstruction
|
|
10900
|
-
});
|
|
10901
|
-
if (title) next.title = title;
|
|
10902
|
-
else delete next.title;
|
|
10903
|
-
}
|
|
10904
|
-
const saved = await saveEventAutomation(getDb(), next);
|
|
10931
|
+
if (input.title === void 0 && input.instruction === void 0 && input.trigger === void 0 && input.outcomes == null && input.credentialMode == null) throw new ToolInputError("Event automation update requires a change.");
|
|
10932
|
+
const next = await editEventAutomation(current, {
|
|
10933
|
+
title: input.title,
|
|
10934
|
+
instruction: input.instruction ?? void 0,
|
|
10935
|
+
trigger: input.trigger ?? void 0,
|
|
10936
|
+
outcomes: input.outcomes ?? void 0,
|
|
10937
|
+
credentialMode: input.credentialMode ?? void 0
|
|
10938
|
+
}, isCreator, catalog);
|
|
10939
|
+
const saved = await saveEventAutomation(getDb(), next, automationRevision(current));
|
|
10905
10940
|
if (!saved) throw new ToolInputError("Event automation was not found.");
|
|
10906
10941
|
return eventAutomationToolResult(context.conversationId, saved, catalog, actor.userId);
|
|
10907
10942
|
}
|
|
@@ -10920,113 +10955,6 @@ function createEventAutomationTools(context, catalog) {
|
|
|
10920
10955
|
};
|
|
10921
10956
|
}
|
|
10922
10957
|
//#endregion
|
|
10923
|
-
//#region src/chat/scheduled-automations/types.ts
|
|
10924
|
-
/** Durable scheduled-automation domain types owned by Junior core. */
|
|
10925
|
-
const scheduledAutomationStatusSchema = z.enum([
|
|
10926
|
-
"active",
|
|
10927
|
-
"blocked",
|
|
10928
|
-
"completed",
|
|
10929
|
-
"deleted"
|
|
10930
|
-
]);
|
|
10931
|
-
const scheduledAutomationCredentialModeSchema = z.enum(["system", "creator"]);
|
|
10932
|
-
const scheduledRunStatusSchema = z.enum([
|
|
10933
|
-
"pending",
|
|
10934
|
-
"running",
|
|
10935
|
-
"completed",
|
|
10936
|
-
"failed",
|
|
10937
|
-
"blocked",
|
|
10938
|
-
"skipped"
|
|
10939
|
-
]);
|
|
10940
|
-
const scheduledAutomationPrincipalSchema = z.object({
|
|
10941
|
-
slackUserId: actorUserIdSchema,
|
|
10942
|
-
fullName: z.string().optional(),
|
|
10943
|
-
userName: z.string().optional()
|
|
10944
|
-
}).strict();
|
|
10945
|
-
const scheduledAutomationRecurrenceSchema = z.object({
|
|
10946
|
-
dayOfMonth: z.number().optional(),
|
|
10947
|
-
frequency: z.enum([
|
|
10948
|
-
"daily",
|
|
10949
|
-
"weekly",
|
|
10950
|
-
"monthly",
|
|
10951
|
-
"yearly"
|
|
10952
|
-
]),
|
|
10953
|
-
interval: z.number(),
|
|
10954
|
-
month: z.number().optional(),
|
|
10955
|
-
startDate: z.string(),
|
|
10956
|
-
time: z.object({
|
|
10957
|
-
hour: z.number(),
|
|
10958
|
-
minute: z.number()
|
|
10959
|
-
}).strict(),
|
|
10960
|
-
weekdays: z.array(z.number()).optional()
|
|
10961
|
-
}).strict();
|
|
10962
|
-
const scheduledAutomationScheduleSchema = z.object({
|
|
10963
|
-
description: z.string(),
|
|
10964
|
-
kind: z.enum(["one_off", "recurring"]),
|
|
10965
|
-
recurrence: scheduledAutomationRecurrenceSchema.optional(),
|
|
10966
|
-
timezone: z.string()
|
|
10967
|
-
}).strict();
|
|
10968
|
-
const scheduledAutomationExecutionActorSchema = z.object({
|
|
10969
|
-
platform: z.literal("system"),
|
|
10970
|
-
name: z.string()
|
|
10971
|
-
}).strict();
|
|
10972
|
-
/** Scheduled automations target a Slack conversation, never one message thread. */
|
|
10973
|
-
const scheduledAutomationDestinationSchema = slackDestinationSchema.omit({ threadTs: true });
|
|
10974
|
-
const scheduledAutomationOutcomeSchema = z.object({
|
|
10975
|
-
action: z.literal("send_message"),
|
|
10976
|
-
destination: scheduledAutomationDestinationSchema
|
|
10977
|
-
}).strict();
|
|
10978
|
-
/** Validate the current scheduled-automation domain shape. */
|
|
10979
|
-
const scheduledAutomationSchema = z.object({
|
|
10980
|
-
id: z.string(),
|
|
10981
|
-
conversationAccess: z.object({
|
|
10982
|
-
audience: z.enum([
|
|
10983
|
-
"direct",
|
|
10984
|
-
"group",
|
|
10985
|
-
"channel"
|
|
10986
|
-
]),
|
|
10987
|
-
visibility: z.enum(["private", "public"])
|
|
10988
|
-
}).strict(),
|
|
10989
|
-
createdAtMs: z.number(),
|
|
10990
|
-
createdBy: scheduledAutomationPrincipalSchema,
|
|
10991
|
-
/** Authoritative provider identity that created the task. */
|
|
10992
|
-
creatorIdentityId: z.string(),
|
|
10993
|
-
/** Selects system credentials or task-bound creator credential delegation. */
|
|
10994
|
-
credentialMode: scheduledAutomationCredentialModeSchema,
|
|
10995
|
-
destination: scheduledAutomationDestinationSchema,
|
|
10996
|
-
executionActor: scheduledAutomationExecutionActorSchema.optional(),
|
|
10997
|
-
lastRunAtMs: z.number().optional(),
|
|
10998
|
-
nextRunAtMs: z.number().optional(),
|
|
10999
|
-
originalRequest: z.string().optional(),
|
|
11000
|
-
runNowAtMs: z.number().optional(),
|
|
11001
|
-
schedule: scheduledAutomationScheduleSchema,
|
|
11002
|
-
status: scheduledAutomationStatusSchema,
|
|
11003
|
-
statusReason: z.string().optional(),
|
|
11004
|
-
/** Explicit visible effects after successful work. An empty list is silent. */
|
|
11005
|
-
outcomes: z.array(scheduledAutomationOutcomeSchema).max(5),
|
|
11006
|
-
task: z.object({ text: z.string() }).strict(),
|
|
11007
|
-
/** SQL-backed short display title generated from the task instruction. */
|
|
11008
|
-
title: z.string().optional(),
|
|
11009
|
-
updatedAtMs: z.number()
|
|
11010
|
-
}).strict();
|
|
11011
|
-
/** Validate the current scheduled-run domain shape. */
|
|
11012
|
-
const scheduledRunSchema = z.object({
|
|
11013
|
-
id: z.string(),
|
|
11014
|
-
attempt: z.number(),
|
|
11015
|
-
claimedAtMs: z.number(),
|
|
11016
|
-
completedAtMs: z.number().optional(),
|
|
11017
|
-
dispatchId: z.string().optional(),
|
|
11018
|
-
errorMessage: z.string().optional(),
|
|
11019
|
-
resultMessageTs: z.string().optional(),
|
|
11020
|
-
scheduledForMs: z.number(),
|
|
11021
|
-
startedAtMs: z.number().optional(),
|
|
11022
|
-
status: scheduledRunStatusSchema,
|
|
11023
|
-
taskId: z.string()
|
|
11024
|
-
}).strict();
|
|
11025
|
-
const SCHEDULED_AUTOMATION_SYSTEM_ACTOR = Object.freeze({
|
|
11026
|
-
platform: "system",
|
|
11027
|
-
name: "scheduled-automation"
|
|
11028
|
-
});
|
|
11029
|
-
//#endregion
|
|
11030
10958
|
//#region src/chat/scheduled-automations/tasks.ts
|
|
11031
10959
|
const SCHEDULER_KEY_PREFIX = "junior:scheduler";
|
|
11032
10960
|
const retainedScheduledAutomationSchema = scheduledAutomationSchema.omit({
|
|
@@ -11035,13 +10963,6 @@ const retainedScheduledAutomationSchema = scheduledAutomationSchema.omit({
|
|
|
11035
10963
|
}).extend({
|
|
11036
10964
|
destination: slackDestinationSchema,
|
|
11037
10965
|
outcomes: z.array(taskOutcomeSchema).max(5),
|
|
11038
|
-
status: z.enum([
|
|
11039
|
-
"active",
|
|
11040
|
-
"blocked",
|
|
11041
|
-
"completed",
|
|
11042
|
-
"deleted",
|
|
11043
|
-
"paused"
|
|
11044
|
-
]),
|
|
11045
10966
|
version: z.number().optional()
|
|
11046
10967
|
}).strict();
|
|
11047
10968
|
/** Decode a retained scheduler task row and reject invalid routing context. */
|
|
@@ -11081,15 +11002,6 @@ function parseScheduledAutomationRow(row) {
|
|
|
11081
11002
|
if (creatorIdentityId === void 0) return void 0;
|
|
11082
11003
|
const fallbackTitle = row.title === null ? z.string().safeParse(legacyTitle) : void 0;
|
|
11083
11004
|
const title = (row.title !== null ? row.title : fallbackTitle?.success ? fallbackTitle.data : void 0)?.trim() || void 0;
|
|
11084
|
-
if (status === "paused") {
|
|
11085
|
-
const { nextRunAtMs: _nextRunAtMs, runNowAtMs: _runNowAtMs, ...retained } = task;
|
|
11086
|
-
return {
|
|
11087
|
-
...retained,
|
|
11088
|
-
creatorIdentityId,
|
|
11089
|
-
status: "deleted",
|
|
11090
|
-
...title ? { title } : void 0
|
|
11091
|
-
};
|
|
11092
|
-
}
|
|
11093
11005
|
return {
|
|
11094
11006
|
...task,
|
|
11095
11007
|
creatorIdentityId,
|
|
@@ -11168,7 +11080,7 @@ async function readListedScheduledAutomations(db, teamId) {
|
|
|
11168
11080
|
creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
|
|
11169
11081
|
record: juniorSchedulerTasks.record,
|
|
11170
11082
|
title: juniorSchedulerTasks.title
|
|
11171
|
-
}).from(juniorSchedulerTasks).where(and(notInArray(juniorSchedulerTasks.status, ["deleted"
|
|
11083
|
+
}).from(juniorSchedulerTasks).where(and(notInArray(juniorSchedulerTasks.status, ["deleted"]), teamId === void 0 ? void 0 : eq(juniorSchedulerTasks.teamId, teamId))).orderBy(asc(juniorSchedulerTasks.createdAtMs), asc(juniorSchedulerTasks.id))).map(parseScheduledAutomationRow).filter(isListedScheduledAutomation);
|
|
11172
11084
|
}
|
|
11173
11085
|
/** List decoded scheduled automations in stable creation order. */
|
|
11174
11086
|
async function listScheduledAutomations(db) {
|
|
@@ -11212,10 +11124,11 @@ async function createScheduledAutomation(db, task) {
|
|
|
11212
11124
|
});
|
|
11213
11125
|
}
|
|
11214
11126
|
/** Save a scheduled automation and clear its blocked occurrence on reactivation. */
|
|
11215
|
-
async function saveScheduledAutomation(db, task) {
|
|
11127
|
+
async function saveScheduledAutomation(db, task, expectedRevision) {
|
|
11216
11128
|
const next = requireStoredTask(task);
|
|
11217
11129
|
await withScheduledAutomationLock(db, task.id, async (tx) => {
|
|
11218
11130
|
const current = await readScheduledAutomation(tx, task.id);
|
|
11131
|
+
if (expectedRevision !== void 0) requireAutomationRevision(current, expectedRevision);
|
|
11219
11132
|
await writeScheduledAutomation(tx, next, current);
|
|
11220
11133
|
});
|
|
11221
11134
|
return next;
|
|
@@ -11224,472 +11137,6 @@ async function saveScheduledAutomation(db, task) {
|
|
|
11224
11137
|
async function saveScheduledAutomationInLock(db, task, current) {
|
|
11225
11138
|
await writeScheduledAutomation(db, requireStoredTask(task), current);
|
|
11226
11139
|
}
|
|
11227
|
-
/** List scheduled automations whose current Slack destination is public. */
|
|
11228
|
-
async function listPublicScheduledAutomationsForTeams(db, teamIds, input) {
|
|
11229
|
-
if (teamIds.length === 0) return [];
|
|
11230
|
-
return (await db.select({
|
|
11231
|
-
creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
|
|
11232
|
-
record: juniorSchedulerTasks.record,
|
|
11233
|
-
title: juniorSchedulerTasks.title
|
|
11234
|
-
}).from(juniorSchedulerTasks).innerJoin(juniorDestinations, and(eq(juniorDestinations.provider, "slack"), eq(juniorDestinations.providerTenantId, juniorSchedulerTasks.teamId), sql`${juniorDestinations.providerDestinationId} = ${juniorSchedulerTasks.record}->'destination'->>'channelId'`)).where(and(inArray(juniorSchedulerTasks.teamId, teamIds), notInArray(juniorSchedulerTasks.status, [
|
|
11235
|
-
"completed",
|
|
11236
|
-
"deleted",
|
|
11237
|
-
"paused"
|
|
11238
|
-
]), input.query ? sql`strpos(lower(coalesce(${juniorSchedulerTasks.title}, ${juniorSchedulerTasks.record}->'task'->>'text')), ${input.query}) > 0` : void 0, eq(juniorDestinations.visibility, "public"))).orderBy(desc(juniorSchedulerTasks.createdAtMs), desc(juniorSchedulerTasks.id)).limit(input.limit)).map(parseScheduledAutomationRow).filter(isListedScheduledAutomation);
|
|
11239
|
-
}
|
|
11240
|
-
//#endregion
|
|
11241
|
-
//#region src/chat/scheduled-automations/cadence.ts
|
|
11242
|
-
const FORMATTERS = /* @__PURE__ */ new Map();
|
|
11243
|
-
function getFormatter(timezone) {
|
|
11244
|
-
const existing = FORMATTERS.get(timezone);
|
|
11245
|
-
if (existing) return existing;
|
|
11246
|
-
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
11247
|
-
timeZone: timezone,
|
|
11248
|
-
hour12: false,
|
|
11249
|
-
year: "numeric",
|
|
11250
|
-
month: "2-digit",
|
|
11251
|
-
day: "2-digit",
|
|
11252
|
-
hour: "2-digit",
|
|
11253
|
-
minute: "2-digit",
|
|
11254
|
-
second: "2-digit"
|
|
11255
|
-
});
|
|
11256
|
-
FORMATTERS.set(timezone, formatter);
|
|
11257
|
-
return formatter;
|
|
11258
|
-
}
|
|
11259
|
-
function normalizeHour(hour) {
|
|
11260
|
-
return hour === 24 ? 0 : hour;
|
|
11261
|
-
}
|
|
11262
|
-
function getLocalDateWeekday(date) {
|
|
11263
|
-
return new Date(Date.UTC(date.year, date.month - 1, date.day)).getUTCDay();
|
|
11264
|
-
}
|
|
11265
|
-
function getWeekStart(date) {
|
|
11266
|
-
return addDays(date, -((getLocalDateWeekday(date) + 6) % 7));
|
|
11267
|
-
}
|
|
11268
|
-
/** Resolve a UTC timestamp into calendar parts for a named time zone. */
|
|
11269
|
-
function getZonedDateTimeParts(timestampMs, timezone) {
|
|
11270
|
-
const parts = getFormatter(timezone).formatToParts(new Date(timestampMs));
|
|
11271
|
-
const values = new Map(parts.map((part) => [part.type, part.value]));
|
|
11272
|
-
const year = Number(values.get("year"));
|
|
11273
|
-
const month = Number(values.get("month"));
|
|
11274
|
-
const day = Number(values.get("day"));
|
|
11275
|
-
return {
|
|
11276
|
-
year,
|
|
11277
|
-
month,
|
|
11278
|
-
day,
|
|
11279
|
-
hour: normalizeHour(Number(values.get("hour"))),
|
|
11280
|
-
minute: Number(values.get("minute")),
|
|
11281
|
-
second: Number(values.get("second")),
|
|
11282
|
-
weekday: getLocalDateWeekday({
|
|
11283
|
-
year,
|
|
11284
|
-
month,
|
|
11285
|
-
day
|
|
11286
|
-
})
|
|
11287
|
-
};
|
|
11288
|
-
}
|
|
11289
|
-
function getTimeZoneOffsetMs(timestampMs, timezone) {
|
|
11290
|
-
const parts = getZonedDateTimeParts(timestampMs, timezone);
|
|
11291
|
-
return Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second) - timestampMs;
|
|
11292
|
-
}
|
|
11293
|
-
function localDateTimeToTimestampMs(args) {
|
|
11294
|
-
const localAsUtcMs = Date.UTC(args.date.year, args.date.month - 1, args.date.day, args.time.hour, args.time.minute, 0);
|
|
11295
|
-
const offsets = /* @__PURE__ */ new Set();
|
|
11296
|
-
for (const probeDeltaMs of [
|
|
11297
|
-
-1296e5,
|
|
11298
|
-
-432e5,
|
|
11299
|
-
0,
|
|
11300
|
-
432e5,
|
|
11301
|
-
1296e5
|
|
11302
|
-
]) offsets.add(getTimeZoneOffsetMs(localAsUtcMs + probeDeltaMs, args.timezone));
|
|
11303
|
-
const matches = [...offsets].map((offsetMs) => localAsUtcMs - offsetMs).filter((timestampMs) => {
|
|
11304
|
-
const parts = getZonedDateTimeParts(timestampMs, args.timezone);
|
|
11305
|
-
return parts.year === args.date.year && parts.month === args.date.month && parts.day === args.date.day && parts.hour === args.time.hour && parts.minute === args.time.minute;
|
|
11306
|
-
});
|
|
11307
|
-
if (matches.length === 0) return;
|
|
11308
|
-
return Math.min(...matches);
|
|
11309
|
-
}
|
|
11310
|
-
/** Resolve local time, rejecting DST gaps and choosing the earlier instant in a fold. */
|
|
11311
|
-
function resolveLocalScheduleAtMs(args) {
|
|
11312
|
-
const date = parseLocalDate(args.date);
|
|
11313
|
-
if (!date) return;
|
|
11314
|
-
return localDateTimeToTimestampMs({
|
|
11315
|
-
date,
|
|
11316
|
-
time: args.time,
|
|
11317
|
-
timezone: args.timezone
|
|
11318
|
-
});
|
|
11319
|
-
}
|
|
11320
|
-
function daysBetween(left, right) {
|
|
11321
|
-
return Math.floor((Date.UTC(right.year, right.month - 1, right.day) - Date.UTC(left.year, left.month - 1, left.day)) / 864e5);
|
|
11322
|
-
}
|
|
11323
|
-
function weeklyRecurrenceMatchesDate(date, start, recurrence) {
|
|
11324
|
-
if (compareDate(date, start) < 0) return false;
|
|
11325
|
-
return normalizeWeekdays(recurrence.weekdays).includes(getLocalDateWeekday(date)) && Math.floor(daysBetween(getWeekStart(start), getWeekStart(date)) / 7) % recurrence.interval === 0;
|
|
11326
|
-
}
|
|
11327
|
-
function greatestCommonDivisor(left, right) {
|
|
11328
|
-
let a = Math.abs(left);
|
|
11329
|
-
let b = Math.abs(right);
|
|
11330
|
-
while (b !== 0) [a, b] = [b, a % b];
|
|
11331
|
-
return a;
|
|
11332
|
-
}
|
|
11333
|
-
function findNextRunAtMs(args) {
|
|
11334
|
-
const start = parseLocalDate(args.recurrence.startDate);
|
|
11335
|
-
const interval = args.recurrence.interval;
|
|
11336
|
-
if (!start || !Number.isInteger(interval) || interval <= 0) return;
|
|
11337
|
-
const searchFrom = compareDate(args.searchFrom, start) < 0 ? start : args.searchFrom;
|
|
11338
|
-
if (args.recurrence.frequency === "daily") {
|
|
11339
|
-
const offsetDays = daysBetween(start, searchFrom);
|
|
11340
|
-
let candidateDate = addDays(start, Math.ceil(offsetDays / interval) * interval);
|
|
11341
|
-
const gregorianCycleDays = 146097;
|
|
11342
|
-
const candidateCount = gregorianCycleDays / greatestCommonDivisor(gregorianCycleDays, interval);
|
|
11343
|
-
for (let attempts = 0; attempts < candidateCount; attempts += 1) {
|
|
11344
|
-
const candidate = buildCandidate({
|
|
11345
|
-
date: candidateDate,
|
|
11346
|
-
recurrence: args.recurrence,
|
|
11347
|
-
timezone: args.timezone
|
|
11348
|
-
});
|
|
11349
|
-
if (candidate !== void 0 && candidate > args.afterMs) return candidate;
|
|
11350
|
-
candidateDate = addDays(candidateDate, interval);
|
|
11351
|
-
}
|
|
11352
|
-
return;
|
|
11353
|
-
}
|
|
11354
|
-
if (args.recurrence.frequency === "weekly") {
|
|
11355
|
-
if (normalizeWeekdays(args.recurrence.weekdays).length === 0) return;
|
|
11356
|
-
let candidateDate = searchFrom;
|
|
11357
|
-
const gregorianCycleDays = 146097;
|
|
11358
|
-
const cadenceDays = interval * 7;
|
|
11359
|
-
const searchDays = gregorianCycleDays * cadenceDays / greatestCommonDivisor(gregorianCycleDays, cadenceDays);
|
|
11360
|
-
for (let attempts = 0; attempts < searchDays; attempts += 1) {
|
|
11361
|
-
if (weeklyRecurrenceMatchesDate(candidateDate, start, args.recurrence)) {
|
|
11362
|
-
const candidate = buildCandidate({
|
|
11363
|
-
date: candidateDate,
|
|
11364
|
-
recurrence: args.recurrence,
|
|
11365
|
-
timezone: args.timezone
|
|
11366
|
-
});
|
|
11367
|
-
if (candidate !== void 0 && candidate > args.afterMs) return candidate;
|
|
11368
|
-
}
|
|
11369
|
-
candidateDate = addDays(candidateDate, 1);
|
|
11370
|
-
}
|
|
11371
|
-
return;
|
|
11372
|
-
}
|
|
11373
|
-
if (args.recurrence.frequency === "monthly") {
|
|
11374
|
-
const startMonth = start.year * 12 + start.month - 1;
|
|
11375
|
-
const searchMonth = searchFrom.year * 12 + searchFrom.month - 1;
|
|
11376
|
-
let candidateMonth = startMonth + Math.ceil((searchMonth - startMonth) / interval) * interval;
|
|
11377
|
-
const gregorianCycleMonths = 4800;
|
|
11378
|
-
const candidateCount = gregorianCycleMonths / greatestCommonDivisor(gregorianCycleMonths, interval);
|
|
11379
|
-
for (let attempts = 0; attempts < candidateCount; attempts += 1) {
|
|
11380
|
-
const candidateDate = {
|
|
11381
|
-
year: Math.floor(candidateMonth / 12),
|
|
11382
|
-
month: candidateMonth % 12 + 1,
|
|
11383
|
-
day: args.recurrence.dayOfMonth ?? 0
|
|
11384
|
-
};
|
|
11385
|
-
if (compareDate(candidateDate, searchFrom) >= 0) {
|
|
11386
|
-
const candidate = buildCandidate({
|
|
11387
|
-
date: candidateDate,
|
|
11388
|
-
recurrence: args.recurrence,
|
|
11389
|
-
timezone: args.timezone
|
|
11390
|
-
});
|
|
11391
|
-
if (candidate !== void 0 && candidate > args.afterMs) return candidate;
|
|
11392
|
-
}
|
|
11393
|
-
candidateMonth += interval;
|
|
11394
|
-
}
|
|
11395
|
-
return;
|
|
11396
|
-
}
|
|
11397
|
-
let candidateYear = start.year + Math.ceil((searchFrom.year - start.year) / interval) * interval;
|
|
11398
|
-
const candidateCount = 400 / greatestCommonDivisor(400, interval);
|
|
11399
|
-
for (let attempts = 0; attempts < candidateCount; attempts += 1) {
|
|
11400
|
-
const candidateDate = {
|
|
11401
|
-
year: candidateYear,
|
|
11402
|
-
month: args.recurrence.month ?? 0,
|
|
11403
|
-
day: args.recurrence.dayOfMonth ?? 0
|
|
11404
|
-
};
|
|
11405
|
-
if (compareDate(candidateDate, searchFrom) >= 0) {
|
|
11406
|
-
const candidate = buildCandidate({
|
|
11407
|
-
date: candidateDate,
|
|
11408
|
-
recurrence: args.recurrence,
|
|
11409
|
-
timezone: args.timezone
|
|
11410
|
-
});
|
|
11411
|
-
if (candidate !== void 0 && candidate > args.afterMs) return candidate;
|
|
11412
|
-
}
|
|
11413
|
-
candidateYear += interval;
|
|
11414
|
-
}
|
|
11415
|
-
}
|
|
11416
|
-
/** Compute the first recurring calendar occurrence strictly after a timestamp. */
|
|
11417
|
-
function getFirstRunAtMs(args) {
|
|
11418
|
-
return findNextRunAtMs({
|
|
11419
|
-
...args,
|
|
11420
|
-
searchFrom: getLocalDate(args.afterMs, args.timezone)
|
|
11421
|
-
});
|
|
11422
|
-
}
|
|
11423
|
-
function compareDate(left, right) {
|
|
11424
|
-
return Date.UTC(left.year, left.month - 1, left.day) - Date.UTC(right.year, right.month - 1, right.day);
|
|
11425
|
-
}
|
|
11426
|
-
function addDays(date, days) {
|
|
11427
|
-
const next = new Date(Date.UTC(date.year, date.month - 1, date.day + days));
|
|
11428
|
-
return {
|
|
11429
|
-
year: next.getUTCFullYear(),
|
|
11430
|
-
month: next.getUTCMonth() + 1,
|
|
11431
|
-
day: next.getUTCDate()
|
|
11432
|
-
};
|
|
11433
|
-
}
|
|
11434
|
-
function daysInMonth(year, month) {
|
|
11435
|
-
return new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
11436
|
-
}
|
|
11437
|
-
function parseLocalDate(value) {
|
|
11438
|
-
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
11439
|
-
if (!match) return;
|
|
11440
|
-
const year = Number(match[1]);
|
|
11441
|
-
const month = Number(match[2]);
|
|
11442
|
-
const day = Number(match[3]);
|
|
11443
|
-
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day) || month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) return;
|
|
11444
|
-
return {
|
|
11445
|
-
year,
|
|
11446
|
-
month,
|
|
11447
|
-
day
|
|
11448
|
-
};
|
|
11449
|
-
}
|
|
11450
|
-
function getLocalDate(timestampMs, timezone) {
|
|
11451
|
-
const parts = getZonedDateTimeParts(timestampMs, timezone);
|
|
11452
|
-
return {
|
|
11453
|
-
year: parts.year,
|
|
11454
|
-
month: parts.month,
|
|
11455
|
-
day: parts.day
|
|
11456
|
-
};
|
|
11457
|
-
}
|
|
11458
|
-
function normalizeWeekdays(values) {
|
|
11459
|
-
return [...new Set((values ?? []).filter((value) => value >= 0 && value <= 6))].sort((a, b) => a - b);
|
|
11460
|
-
}
|
|
11461
|
-
function buildCandidate(args) {
|
|
11462
|
-
return localDateTimeToTimestampMs({
|
|
11463
|
-
date: args.date,
|
|
11464
|
-
time: args.recurrence.time,
|
|
11465
|
-
timezone: args.timezone
|
|
11466
|
-
});
|
|
11467
|
-
}
|
|
11468
|
-
/** Return the next fire time after a completed run, when the task recurs. */
|
|
11469
|
-
function getNextRunAtMs(task, scheduledForMs, afterMs = scheduledForMs) {
|
|
11470
|
-
if (task.schedule.kind !== "recurring") return;
|
|
11471
|
-
const recurrence = task.schedule.recurrence;
|
|
11472
|
-
if (!recurrence || !Number.isFinite(recurrence.interval) || recurrence.interval <= 0) return;
|
|
11473
|
-
const timezone = task.schedule.timezone;
|
|
11474
|
-
const afterDate = getLocalDate(afterMs, timezone);
|
|
11475
|
-
const nextScheduledDate = addDays(getLocalDate(scheduledForMs, timezone), 1);
|
|
11476
|
-
return findNextRunAtMs({
|
|
11477
|
-
afterMs,
|
|
11478
|
-
recurrence,
|
|
11479
|
-
searchFrom: compareDate(afterDate, nextScheduledDate) < 0 ? nextScheduledDate : afterDate,
|
|
11480
|
-
timezone
|
|
11481
|
-
});
|
|
11482
|
-
}
|
|
11483
|
-
//#endregion
|
|
11484
|
-
//#region src/chat/scheduled-automations/schedule-intent.ts
|
|
11485
|
-
/**
|
|
11486
|
-
* Owns model-facing schedule intent and materializes it against the trusted
|
|
11487
|
-
* server clock into the canonical schedule persisted by core.
|
|
11488
|
-
*/
|
|
11489
|
-
const localDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Use a local date in YYYY-MM-DD format.");
|
|
11490
|
-
const localTimeSchema = z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "Use local time in HH:MM format.");
|
|
11491
|
-
const timezoneSchema = z.string().min(1).max(80).describe("IANA timezone, for example America/Los_Angeles. Omit or use null for the scheduler default.").nullable().optional();
|
|
11492
|
-
const weekdaySchema = z.enum([
|
|
11493
|
-
"sunday",
|
|
11494
|
-
"monday",
|
|
11495
|
-
"tuesday",
|
|
11496
|
-
"wednesday",
|
|
11497
|
-
"thursday",
|
|
11498
|
-
"friday",
|
|
11499
|
-
"saturday"
|
|
11500
|
-
]);
|
|
11501
|
-
const oneOffScheduleIntentSchema = z.object({
|
|
11502
|
-
kind: z.literal("one_off").describe("A schedule that runs once."),
|
|
11503
|
-
timezone: timezoneSchema,
|
|
11504
|
-
timing: z.discriminatedUnion("type", [z.object({
|
|
11505
|
-
type: z.literal("after").describe("Run after a relative delay from the server clock."),
|
|
11506
|
-
value: z.number().int().positive().max(1e5),
|
|
11507
|
-
unit: z.enum([
|
|
11508
|
-
"second",
|
|
11509
|
-
"minute",
|
|
11510
|
-
"hour",
|
|
11511
|
-
"day",
|
|
11512
|
-
"week"
|
|
11513
|
-
])
|
|
11514
|
-
}).strict(), z.object({
|
|
11515
|
-
type: z.literal("at").describe("Run at an explicit local calendar date and time."),
|
|
11516
|
-
date: localDateSchema.describe("Requested local calendar date in YYYY-MM-DD format."),
|
|
11517
|
-
time: localTimeSchema.describe("Requested local wall-clock time in HH:MM format.")
|
|
11518
|
-
}).strict()]).describe("Relative delay or explicit local time for the one-off run.")
|
|
11519
|
-
}).strict();
|
|
11520
|
-
const recurringScheduleIntentSchema = z.object({
|
|
11521
|
-
kind: z.literal("recurring").describe("A repeating calendar schedule."),
|
|
11522
|
-
frequency: z.enum([
|
|
11523
|
-
"daily",
|
|
11524
|
-
"weekly",
|
|
11525
|
-
"monthly",
|
|
11526
|
-
"yearly"
|
|
11527
|
-
]).describe("Recurring tasks can run at most once per day."),
|
|
11528
|
-
interval: z.number().int().positive().max(365).describe("Repeat every N frequency units. Defaults to 1.").nullable().optional(),
|
|
11529
|
-
time: localTimeSchema.describe("Local wall-clock time for each occurrence in HH:MM format."),
|
|
11530
|
-
weekdays: z.array(weekdaySchema).min(1).max(7).describe("Required for weekly schedules.").nullable().optional(),
|
|
11531
|
-
dayOfMonth: z.number().int().min(1).max(31).describe("Required for monthly and yearly schedules.").nullable().optional(),
|
|
11532
|
-
month: z.number().int().min(1).max(12).describe("Required for yearly schedules, where January is 1.").nullable().optional(),
|
|
11533
|
-
startDate: localDateSchema.describe("Optional local calendar date that anchors the recurrence. Omit to start with the next matching occurrence.").nullable().optional(),
|
|
11534
|
-
timezone: timezoneSchema
|
|
11535
|
-
}).strict().superRefine((schedule, context) => {
|
|
11536
|
-
if (schedule.frequency === "weekly" && schedule.weekdays == null) context.addIssue({
|
|
11537
|
-
code: "custom",
|
|
11538
|
-
message: "Weekly schedules require weekdays.",
|
|
11539
|
-
path: ["weekdays"]
|
|
11540
|
-
});
|
|
11541
|
-
if ((schedule.frequency === "monthly" || schedule.frequency === "yearly") && schedule.dayOfMonth == null) context.addIssue({
|
|
11542
|
-
code: "custom",
|
|
11543
|
-
message: `${schedule.frequency} schedules require dayOfMonth.`,
|
|
11544
|
-
path: ["dayOfMonth"]
|
|
11545
|
-
});
|
|
11546
|
-
if (schedule.frequency === "yearly" && schedule.month == null) context.addIssue({
|
|
11547
|
-
code: "custom",
|
|
11548
|
-
message: "Yearly schedules require month.",
|
|
11549
|
-
path: ["month"]
|
|
11550
|
-
});
|
|
11551
|
-
if (schedule.frequency !== "weekly" && schedule.weekdays != null) context.addIssue({
|
|
11552
|
-
code: "custom",
|
|
11553
|
-
message: "weekdays applies only to weekly schedules.",
|
|
11554
|
-
path: ["weekdays"]
|
|
11555
|
-
});
|
|
11556
|
-
if (schedule.frequency !== "monthly" && schedule.frequency !== "yearly" && schedule.dayOfMonth != null) context.addIssue({
|
|
11557
|
-
code: "custom",
|
|
11558
|
-
message: "dayOfMonth applies only to monthly or yearly schedules.",
|
|
11559
|
-
path: ["dayOfMonth"]
|
|
11560
|
-
});
|
|
11561
|
-
if (schedule.frequency !== "yearly" && schedule.month != null) context.addIssue({
|
|
11562
|
-
code: "custom",
|
|
11563
|
-
message: "month applies only to yearly schedules.",
|
|
11564
|
-
path: ["month"]
|
|
11565
|
-
});
|
|
11566
|
-
});
|
|
11567
|
-
const scheduleIntentSchema = z.union([oneOffScheduleIntentSchema, recurringScheduleIntentSchema]);
|
|
11568
|
-
const WEEKDAY_INDEX = {
|
|
11569
|
-
sunday: 0,
|
|
11570
|
-
monday: 1,
|
|
11571
|
-
tuesday: 2,
|
|
11572
|
-
wednesday: 3,
|
|
11573
|
-
thursday: 4,
|
|
11574
|
-
friday: 5,
|
|
11575
|
-
saturday: 6
|
|
11576
|
-
};
|
|
11577
|
-
const DURATION_MS = {
|
|
11578
|
-
second: 1e3,
|
|
11579
|
-
minute: 6e4,
|
|
11580
|
-
hour: 36e5,
|
|
11581
|
-
day: 864e5,
|
|
11582
|
-
week: 6048e5
|
|
11583
|
-
};
|
|
11584
|
-
function parseLocalTime(value) {
|
|
11585
|
-
const [hour, minute] = value.split(":").map(Number);
|
|
11586
|
-
return {
|
|
11587
|
-
hour,
|
|
11588
|
-
minute
|
|
11589
|
-
};
|
|
11590
|
-
}
|
|
11591
|
-
function localDateAt(timestampMs, timezone) {
|
|
11592
|
-
const parts = getZonedDateTimeParts(timestampMs, timezone);
|
|
11593
|
-
return [
|
|
11594
|
-
parts.year,
|
|
11595
|
-
parts.month,
|
|
11596
|
-
parts.day
|
|
11597
|
-
].map((value, index) => index === 0 ? String(value).padStart(4, "0") : String(value).padStart(2, "0")).join("-");
|
|
11598
|
-
}
|
|
11599
|
-
function isValidTimeZone$1(timezone) {
|
|
11600
|
-
try {
|
|
11601
|
-
new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format();
|
|
11602
|
-
return true;
|
|
11603
|
-
} catch {
|
|
11604
|
-
return false;
|
|
11605
|
-
}
|
|
11606
|
-
}
|
|
11607
|
-
function plural(value, unit) {
|
|
11608
|
-
return value === 1 ? unit : `${unit}s`;
|
|
11609
|
-
}
|
|
11610
|
-
function formatWeekdays(weekdays) {
|
|
11611
|
-
const labels = weekdays.map((weekday) => `${weekday[0].toUpperCase()}${weekday.slice(1)}`);
|
|
11612
|
-
if (labels.length < 2) return labels[0] ?? "";
|
|
11613
|
-
return `${labels.slice(0, -1).join(", ")} and ${labels.at(-1)}`;
|
|
11614
|
-
}
|
|
11615
|
-
function recurringDescription(schedule, timezone) {
|
|
11616
|
-
const interval = schedule.interval ?? 1;
|
|
11617
|
-
const cadenceUnit = {
|
|
11618
|
-
daily: "day",
|
|
11619
|
-
weekly: "week",
|
|
11620
|
-
monthly: "month",
|
|
11621
|
-
yearly: "year"
|
|
11622
|
-
}[schedule.frequency];
|
|
11623
|
-
const cadence = interval === 1 ? cadenceUnit : `${interval} ${cadenceUnit}s`;
|
|
11624
|
-
let detail = "";
|
|
11625
|
-
if (schedule.frequency === "weekly") detail = ` on ${formatWeekdays(schedule.weekdays)}`;
|
|
11626
|
-
else if (schedule.frequency === "monthly") detail = ` on day ${schedule.dayOfMonth}`;
|
|
11627
|
-
else if (schedule.frequency === "yearly") detail = ` on ${String(schedule.month).padStart(2, "0")}-${String(schedule.dayOfMonth).padStart(2, "0")}`;
|
|
11628
|
-
return `Every ${cadence}${detail} at ${schedule.time} (${timezone})`;
|
|
11629
|
-
}
|
|
11630
|
-
var ScheduleIntentError = class extends Error {};
|
|
11631
|
-
/** Materialize model-interpreted schedule intent against the trusted server clock. */
|
|
11632
|
-
function compileScheduleIntent(args) {
|
|
11633
|
-
const timezone = args.intent.timezone ?? args.defaultTimezone;
|
|
11634
|
-
if (!isValidTimeZone$1(timezone)) throw new ScheduleIntentError("timezone must be a valid IANA time zone.");
|
|
11635
|
-
if (args.intent.kind === "one_off") {
|
|
11636
|
-
if (args.intent.timing.type === "after") return {
|
|
11637
|
-
nextRunAtMs: args.nowMs + args.intent.timing.value * DURATION_MS[args.intent.timing.unit],
|
|
11638
|
-
schedule: {
|
|
11639
|
-
description: `In ${args.intent.timing.value} ${plural(args.intent.timing.value, args.intent.timing.unit)}`,
|
|
11640
|
-
kind: "one_off",
|
|
11641
|
-
timezone
|
|
11642
|
-
}
|
|
11643
|
-
};
|
|
11644
|
-
const nextRunAtMs = resolveLocalScheduleAtMs({
|
|
11645
|
-
date: args.intent.timing.date,
|
|
11646
|
-
time: parseLocalTime(args.intent.timing.time),
|
|
11647
|
-
timezone
|
|
11648
|
-
});
|
|
11649
|
-
if (nextRunAtMs === void 0) throw new ScheduleIntentError("The requested local date or time does not exist in that timezone.");
|
|
11650
|
-
if (nextRunAtMs <= args.nowMs) throw new ScheduleIntentError("The requested one-off schedule must be in the future.");
|
|
11651
|
-
return {
|
|
11652
|
-
nextRunAtMs,
|
|
11653
|
-
schedule: {
|
|
11654
|
-
description: `Once on ${args.intent.timing.date} at ${args.intent.timing.time} (${timezone})`,
|
|
11655
|
-
kind: "one_off",
|
|
11656
|
-
timezone
|
|
11657
|
-
}
|
|
11658
|
-
};
|
|
11659
|
-
}
|
|
11660
|
-
const recurrence = {
|
|
11661
|
-
frequency: args.intent.frequency,
|
|
11662
|
-
interval: args.intent.interval ?? 1,
|
|
11663
|
-
startDate: args.intent.startDate ?? localDateAt(args.nowMs, timezone),
|
|
11664
|
-
time: parseLocalTime(args.intent.time),
|
|
11665
|
-
...args.intent.frequency === "weekly" ? { weekdays: [...new Set(args.intent.weekdays.map((weekday) => WEEKDAY_INDEX[weekday]))].sort((left, right) => left - right) } : void 0,
|
|
11666
|
-
...args.intent.frequency === "monthly" || args.intent.frequency === "yearly" ? { dayOfMonth: args.intent.dayOfMonth ?? void 0 } : void 0,
|
|
11667
|
-
...args.intent.frequency === "yearly" ? { month: args.intent.month ?? void 0 } : void 0
|
|
11668
|
-
};
|
|
11669
|
-
const searchRecurrence = args.intent.startDate ? recurrence : {
|
|
11670
|
-
...recurrence,
|
|
11671
|
-
interval: 1
|
|
11672
|
-
};
|
|
11673
|
-
const nextRunAtMs = getFirstRunAtMs({
|
|
11674
|
-
afterMs: args.nowMs,
|
|
11675
|
-
recurrence: searchRecurrence,
|
|
11676
|
-
timezone
|
|
11677
|
-
});
|
|
11678
|
-
if (nextRunAtMs === void 0) throw new ScheduleIntentError("The recurring schedule has no valid future occurrence.");
|
|
11679
|
-
const materializedRecurrence = args.intent.startDate ? recurrence : {
|
|
11680
|
-
...recurrence,
|
|
11681
|
-
startDate: localDateAt(nextRunAtMs, timezone)
|
|
11682
|
-
};
|
|
11683
|
-
return {
|
|
11684
|
-
nextRunAtMs,
|
|
11685
|
-
schedule: {
|
|
11686
|
-
description: recurringDescription(args.intent, timezone),
|
|
11687
|
-
kind: "recurring",
|
|
11688
|
-
recurrence: materializedRecurrence,
|
|
11689
|
-
timezone
|
|
11690
|
-
}
|
|
11691
|
-
};
|
|
11692
|
-
}
|
|
11693
11140
|
//#endregion
|
|
11694
11141
|
//#region src/chat/scheduled-automations/telemetry.ts
|
|
11695
11142
|
/** Safe structured telemetry attributes for scheduled-automation lifecycle events. */
|
|
@@ -11764,6 +11211,7 @@ const compactTaskResultSchema = z.object({
|
|
|
11764
11211
|
status: z.enum([
|
|
11765
11212
|
"active",
|
|
11766
11213
|
"blocked",
|
|
11214
|
+
"paused",
|
|
11767
11215
|
"completed",
|
|
11768
11216
|
"deleted"
|
|
11769
11217
|
]),
|
|
@@ -12081,17 +11529,18 @@ function createSlackScheduleDeleteAutomationTool(context) {
|
|
|
12081
11529
|
inputSchema: z.object({ automationId: z.string().min(1).describe("ID of the task to delete. Must be from this active Slack conversation.") }),
|
|
12082
11530
|
outputSchema: scheduleAutomationToolResultSchema.extend({ removedCards: z.array(removedCardSchema) }),
|
|
12083
11531
|
execute: async ({ automationId }) => {
|
|
11532
|
+
const lookup = await getWritableTask({
|
|
11533
|
+
context,
|
|
11534
|
+
taskId: automationId
|
|
11535
|
+
});
|
|
12084
11536
|
const next = {
|
|
12085
|
-
...
|
|
12086
|
-
context,
|
|
12087
|
-
taskId: automationId
|
|
12088
|
-
}),
|
|
11537
|
+
...lookup,
|
|
12089
11538
|
updatedAtMs: Date.now(),
|
|
12090
11539
|
status: "deleted",
|
|
12091
11540
|
nextRunAtMs: void 0,
|
|
12092
11541
|
runNowAtMs: void 0
|
|
12093
11542
|
};
|
|
12094
|
-
await saveScheduledAutomation(getDb(), next);
|
|
11543
|
+
await saveScheduledAutomation(getDb(), next, automationRevision(lookup));
|
|
12095
11544
|
await createPluginAnnotations({
|
|
12096
11545
|
conversationId: context.conversationId,
|
|
12097
11546
|
db: getDb(),
|
|
@@ -12225,12 +11674,46 @@ function createSlackScheduleRunAutomationNowTool(context) {
|
|
|
12225
11674
|
updatedAtMs: nowMs,
|
|
12226
11675
|
runNowAtMs: nowMs
|
|
12227
11676
|
};
|
|
12228
|
-
await saveScheduledAutomation(getDb(), next);
|
|
11677
|
+
await saveScheduledAutomation(getDb(), next, automationRevision(lookup));
|
|
12229
11678
|
return { automation: compactTask(next, context.actor?.userId) };
|
|
12230
11679
|
}
|
|
12231
11680
|
});
|
|
12232
11681
|
}
|
|
12233
11682
|
//#endregion
|
|
11683
|
+
//#region src/chat/scheduled-automations/edit.ts
|
|
11684
|
+
/** Build an edit without restarting completed work or changing creator identity. */
|
|
11685
|
+
async function editScheduledAutomation(current, input, isCreator, nowMs) {
|
|
11686
|
+
if (current.status === "completed" || current.status === "deleted") throw new AutomationEditError("Completed scheduled automations cannot be updated. Create a new automation instead.");
|
|
11687
|
+
let compiled;
|
|
11688
|
+
if (input.schedule !== void 0) try {
|
|
11689
|
+
compiled = compileScheduleIntent({
|
|
11690
|
+
defaultTimezone: current.schedule.timezone || getDefaultScheduleTimezone(),
|
|
11691
|
+
intent: input.schedule,
|
|
11692
|
+
nowMs
|
|
11693
|
+
});
|
|
11694
|
+
} catch (error) {
|
|
11695
|
+
if (error instanceof ScheduleIntentError) throw new AutomationEditError(error.message, "schedule");
|
|
11696
|
+
throw error;
|
|
11697
|
+
}
|
|
11698
|
+
const nextRunAtMs = compiled?.nextRunAtMs ?? current.nextRunAtMs;
|
|
11699
|
+
if (input.status === "active" && !nextRunAtMs) throw new AutomationEditError("Active scheduled automations require a schedule with a future occurrence.", "schedule");
|
|
11700
|
+
if (current.status === "paused" && input.status !== void 0) throw new AutomationEditError("Use Resume in the Automation settings to resume paused work.", "status");
|
|
11701
|
+
const status = input.status ?? current.status;
|
|
11702
|
+
const changesExecution = input.instruction !== void 0 && input.instruction !== current.task.text;
|
|
11703
|
+
return {
|
|
11704
|
+
...current,
|
|
11705
|
+
...await editAutomationFields(current, input, isCreator, changesExecution),
|
|
11706
|
+
updatedAtMs: nowMs,
|
|
11707
|
+
schedule: compiled?.schedule ?? current.schedule,
|
|
11708
|
+
nextRunAtMs,
|
|
11709
|
+
...compiled !== void 0 || input.status !== void 0 ? {
|
|
11710
|
+
runNowAtMs: status === "active" && !compiled ? current.runNowAtMs : void 0,
|
|
11711
|
+
statusReason: status === "blocked" || status === "paused" ? current.statusReason : void 0
|
|
11712
|
+
} : void 0,
|
|
11713
|
+
status
|
|
11714
|
+
};
|
|
11715
|
+
}
|
|
11716
|
+
//#endregion
|
|
12234
11717
|
//#region src/chat/scheduled-automations/tools/update.ts
|
|
12235
11718
|
/** Create a tool that edits a scheduled automation, including moving it here. */
|
|
12236
11719
|
function createSlackScheduleUpdateAutomationTool(context) {
|
|
@@ -12246,7 +11729,8 @@ function createSlackScheduleUpdateAutomationTool(context) {
|
|
|
12246
11729
|
executionMode: "sequential",
|
|
12247
11730
|
inputSchema: z.object({
|
|
12248
11731
|
automationId: z.string().min(1).describe("Scheduled automation ID returned by slackScheduleListAutomations."),
|
|
12249
|
-
|
|
11732
|
+
title: automationTitleSchema.optional(),
|
|
11733
|
+
instruction: automationInstructionSchema.optional(),
|
|
12250
11734
|
schedule: scheduleIntentSchema.describe("Complete replacement schedule. Omit to keep it unchanged.").nullable().optional(),
|
|
12251
11735
|
outcomes: z.array(taskOutcomeInputSchema).max(5).describe("Replacement messages to send after successful work. Use an empty list to send nothing. Omit to keep unchanged.").optional(),
|
|
12252
11736
|
status: z.enum(["active", "blocked"]).describe("Set active to resume a blocked task, or blocked to stop dispatch.").optional(),
|
|
@@ -12254,7 +11738,7 @@ function createSlackScheduleUpdateAutomationTool(context) {
|
|
|
12254
11738
|
credentialMode: z.enum(["system", "creator"]).nullable().describe("Use creator credentials or system credentials. Only the task creator may enable creator mode. Omit to keep unchanged.").optional()
|
|
12255
11739
|
}).strict(),
|
|
12256
11740
|
outputSchema: scheduleAutomationToolResultSchema,
|
|
12257
|
-
execute: async (input
|
|
11741
|
+
execute: async (input) => {
|
|
12258
11742
|
const activeDestination = requireActiveChannel(context);
|
|
12259
11743
|
const actor = requireActor(context, activeDestination);
|
|
12260
11744
|
const db = getDb();
|
|
@@ -12276,52 +11760,24 @@ function createSlackScheduleUpdateAutomationTool(context) {
|
|
|
12276
11760
|
if ((await db.select({ id: juniorSchedulerRuns.id }).from(juniorSchedulerRuns).where(and(eq(juniorSchedulerRuns.taskId, lookup.id), inArray(juniorSchedulerRuns.status, ["pending", "running"]))).limit(1)).length > 0) throwToolInputError("Scheduled automation cannot be moved while an occurrence is already running. Try again after the current run finishes.");
|
|
12277
11761
|
}
|
|
12278
11762
|
const nowMs = context.now?.() ?? Date.now();
|
|
12279
|
-
let compiled;
|
|
12280
|
-
if (input.schedule) try {
|
|
12281
|
-
compiled = compileScheduleIntent({
|
|
12282
|
-
defaultTimezone: lookup.schedule.timezone || getDefaultScheduleTimezone(),
|
|
12283
|
-
intent: input.schedule,
|
|
12284
|
-
nowMs
|
|
12285
|
-
});
|
|
12286
|
-
} catch (error) {
|
|
12287
|
-
if (error instanceof ScheduleIntentError) throwToolInputError(error.message);
|
|
12288
|
-
throw error;
|
|
12289
|
-
}
|
|
12290
|
-
const nextRunAtMs = compiled?.nextRunAtMs ?? lookup.nextRunAtMs;
|
|
12291
11763
|
const status = normalizeStatus(input.status);
|
|
12292
11764
|
if (input.status && !status) throwToolInputError("status must be active or blocked.");
|
|
12293
|
-
|
|
12294
|
-
const
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
|
|
12302
|
-
|
|
12303
|
-
destination
|
|
12304
|
-
|
|
12305
|
-
|
|
12306
|
-
runNowAtMs: nextStatus === "active" && !compiled ? lookup.runNowAtMs : void 0,
|
|
12307
|
-
status: nextStatus,
|
|
12308
|
-
statusReason: nextStatus === "blocked" ? lookup.statusReason : void 0,
|
|
12309
|
-
schedule: compiled?.schedule ?? lookup.schedule,
|
|
12310
|
-
outcomes: input.outcomes === void 0 ? changingDestination ? moveTaskOutcomes(lookup.outcomes, lookup.destination, nextDestination) : lookup.outcomes : await resolveTaskOutcomes(input.outcomes, nextDestination, lookup.createdBy.slackUserId),
|
|
12311
|
-
task: { text: nextInstruction }
|
|
12312
|
-
};
|
|
12313
|
-
if (instructionChanged) {
|
|
12314
|
-
const title = await generateShortTitle({
|
|
12315
|
-
signal: options.signal,
|
|
12316
|
-
completeText,
|
|
12317
|
-
kind: "task",
|
|
12318
|
-
sourceText: nextInstruction
|
|
12319
|
-
});
|
|
12320
|
-
if (title) next.title = title;
|
|
12321
|
-
else delete next.title;
|
|
11765
|
+
const instructionChanged = input.instruction !== void 0 && input.instruction !== lookup.task.text;
|
|
11766
|
+
const next = await editScheduledAutomation(lookup, {
|
|
11767
|
+
title: input.title,
|
|
11768
|
+
instruction: input.instruction,
|
|
11769
|
+
schedule: input.schedule ?? void 0,
|
|
11770
|
+
status,
|
|
11771
|
+
credentialMode: input.credentialMode ?? void 0,
|
|
11772
|
+
outcomes: changingDestination ? void 0 : input.outcomes
|
|
11773
|
+
}, isCreator, nowMs);
|
|
11774
|
+
if (changingDestination) {
|
|
11775
|
+
next.destination = requestedDestination;
|
|
11776
|
+
next.conversationAccess = getConversationAccess(activeDestination, context.source);
|
|
11777
|
+
next.outcomes = input.outcomes === void 0 ? moveTaskOutcomes(lookup.outcomes, lookup.destination, requestedDestination) : await resolveTaskOutcomes(input.outcomes, requestedDestination, lookup.createdBy.slackUserId);
|
|
12322
11778
|
}
|
|
12323
|
-
if (!changingDestination && !instructionChanged && !
|
|
12324
|
-
const committed = await saveScheduledAutomation(db, next);
|
|
11779
|
+
if (!changingDestination && !instructionChanged && !input.schedule && input.title === void 0 && status === void 0 && (input.credentialMode === void 0 || input.credentialMode === null || input.credentialMode === lookup.credentialMode) && input.outcomes === void 0 && moveDestination) return scheduleAutomationToolResult(context.conversationId, lookup, actor.slackUserId);
|
|
11780
|
+
const committed = await saveScheduledAutomation(db, next, automationRevision(lookup));
|
|
12325
11781
|
if (changingDestination) logInfo("scheduled_automation.move.completed", scheduledAutomationAttributes(committed));
|
|
12326
11782
|
return scheduleAutomationToolResult(context.conversationId, committed, actor.slackUserId);
|
|
12327
11783
|
}
|
|
@@ -21934,7 +21390,7 @@ function createAgentRunner(execute, options) {
|
|
|
21934
21390
|
} } : void 0
|
|
21935
21391
|
};
|
|
21936
21392
|
if (run.instruction.storedAttachments?.length) {
|
|
21937
|
-
const { loadInputImages } = await import("./images-
|
|
21393
|
+
const { loadInputImages } = await import("./images-BaL19wFC.js").then((n) => n.n);
|
|
21938
21394
|
const storage = nextRun.environment?.attachmentStorage;
|
|
21939
21395
|
if (!storage) throw new Error("Attachment storage is unavailable.");
|
|
21940
21396
|
nextRun.instruction = {
|
|
@@ -21985,4 +21441,4 @@ async function runAgentWithTimeout(agentRunner, run, timeoutMs) {
|
|
|
21985
21441
|
}
|
|
21986
21442
|
}
|
|
21987
21443
|
//#endregion
|
|
21988
|
-
export {
|
|
21444
|
+
export { deleteEventAutomation as $, isFinalAttempt as $t, requestAttachmentDeletion as A, actorFromRun as An, clearExpiredConversationLease as At, listScheduledAutomations as B, listActiveConversationIds as Bt, readPublicArtifact as C, postSlackMessage as Cn, ackMessages as Ct, renderBlockText as D, createUserTokenStore as Dn, cancelHumanFacingPendingMessages as Dt, renderAttachmentText as E, slackExternalRefIdSchema as En, beginConversationResume as Et, resolveSlackChannelIdFromThreadId as F, setConfigDefaults as Fn, drainConversationMailbox as Ft, saveScheduledAutomationInLock as G, requestAnotherSlice as Gt, parseScheduledAutomationRow as H, recordConversationRetry as Ht, listThreadReplies as I, ensureConversationWake as It, editEventAutomation as J, stopConversationWork as Jt, scheduledRunId as K, requestConversationWork as Kt, editScheduledAutomation as L, getConversation as Lt, parseSlackThreadId as M, AuthorizationFlowDisabledError as Mn, completeConversationWork as Mt, readSlackRawMessageContext as N, shouldEmitDevAgentTrace as Nn, countPendingConversationMessages as Nt, collectAttachmentGarbage as O, buildSteeringPiMessage as On, checkInConversationWork as Ot, resolveSlackChannelIdFromMessage as P, getConfigDefaults as Pn, deadLetterAttempt as Pt, collectEventAutomationMatchKeys as Q, hasRunnableConversationWork$1 as Qt, logScheduledAutomationRunSkipped as R, getConversationWorkState as Rt, publicArtifactHeaders as S, addReactionToMessage as Sn, pluginEventCatalog as St, lookupSlackUserProfile as T, slackEntitySchema as Tn, appendAndEnqueueInboundMessage as Tt, readScheduledAutomation as U, releaseConversationWork as Ut, listScheduledAutomationsForTeam as V, recordAttemptFailure as Vt, saveScheduledAutomation as W, removeActiveConversation as Wt, eventAutomationTriggerAvailable as X, CONVERSATION_WORK_STALE_ENQUEUE_MS as Xt, AutomationEditError as Y, CONVERSATION_WORK_CHECK_IN_INTERVAL_MS as Yt, effectiveTaskOutcomes as Z, hasConversationStop as Zt, putMcpStoredOAuthCredentials as _, escapeSlackMrkdwnText as _n, ingestEvent as _t, isExperimentalFeatureEnabled as a, normalizeSandboxEgressTracePropagationDomains as an, saveEventAutomation as at, readStats as b, normalizeSlackStatusText as bn, getEventCatalog as bt, createContextCompactor as c, clearSandboxEgressSignals as cn, automationRevision as ct, deleteMcpAuthSession as d, parseSandboxEgressCredentialToken as dn, agentInvocationSchema as dt, isInvalidConversationRecordError as en, eventAutomationBelongsToUser as et, deleteMcpAuthSessionsForUserProvider as f, sandboxEgressSignalsResponseSchema as fn, createAgentInvocationSchema as ft, getMcpStoredOAuthCredentials as g, startOAuthFlow as gn, processWorkspaceSnapshotJob as gt, getMcpAuthSession as h, resolveBaseUrl as hn, getMcpProviderErrorAttributes as ht, getExperimentalFeatures as i, resolveSandboxEgressProviderForHost as in, parseEventAutomationRow as it, storeAttachment as j, toolInvocationDestination as jn, completeConversationStop as jt, readLiveAttachment as k, RetryableDeliveryError as kn, clearConsumedConversationWake as kt, executeCredentialedEgressRequest as l, consumeSandboxEgressAuthRequiredSignal as ln, requireAutomationRevision as lt, deleteMcpStoredOAuthCredentials as m, parseOAuthStatePayload as mn, AgentInvocationLimitError as mt, runAgentWithTimeout as n, maybePostAutomatedTurnLimitNotice as nn, getEventAutomation as nt, setExperimentalFeatures as o, shouldPropagateSandboxEgressTrace as on, setEventAutomationStatus as ot, deleteMcpServerSessionId as p, formatProviderLabel as pn, AgentInvocationBusyError as pt, withScheduledAutomationLock as q, startConversationWork as qt, experimental_exports as r, recordFinishedTurnForAutomatedLimit as rn, listDeletedEventAutomationsCreatedBy as rt, executeAgentRun as s, SANDBOX_EGRESS_PROXY_PATH as sn, AutomationConflictError as st, createAgentRunner as t, renderTaskInput as tn, findMatchingEventAutomations as tt, finalizeMcpAuthorization as u, consumeSandboxEgressPermissionDeniedSignal as un, agentBindingSchema as ut, PluginCredentialFailureError as v, formatSlackLink as vn, EVENT_WAIT_MS as vt, unpublishedArtifactHeaders as w, removeReactionFromMessage as wn, appendAndEnqueueExclusiveInboundMessage as wt, resolvePluginOAuthAccount as x, truncateStatusText as xn, eventGuidance as xt, latestProgressStatus as y, normalizeSlackReplyMarkdown as yn, isEventMailboxMetadata as yt, scheduledAutomationRunAttributes as z, hasRunnableConversationWork as zt };
|