@sentry/junior 0.233.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-DATy_Hnt.js → agent-hooks-BcH6E48y.js} +6 -6
- package/dist/{agent-runner-BKOtZjez.js → agent-runner-DUkGlK89.js} +221 -766
- package/dist/api/schema.d.ts +463 -2
- package/dist/api/schema.js +3 -3
- package/dist/api.js +229 -18
- package/dist/app.js +30 -20
- package/dist/{briefs-ClOTHmQ-.js → briefs-lyAQbDxJ.js} +2 -2
- package/dist/{catalog-runtime-xFtmw3oA.js → catalog-runtime-BSgsPVnr.js} +1 -1
- package/dist/{checkpoint-zkEB448J.js → checkpoint-BNfXlq_1.js} +6 -6
- 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-CJUZHY9Y.js → client-BX5lJRqW.js} +2 -2
- package/dist/{config-XtycpE-i.js → config-DuCzUoCo.js} +2 -2
- package/dist/{conversation-privacy-B30pEcsR.js → conversation-privacy-CSxvdSoV.js} +1 -1
- package/dist/{credential-sync-vufd6gwK.js → credential-sync-BO4DfH9a.js} +13 -13
- package/dist/{executor-BGDb_cM-.js → executor--jWf-Nnm.js} +1 -1
- package/dist/{images-zuEwjVKU.js → images-BaL19wFC.js} +2 -2
- package/dist/instrumentation.js +2 -2
- package/dist/{job-queue-C5JaRRcE.js → job-queue-cwHVOwVZ.js} +1 -1
- package/dist/{logging-BS563NYn.js → logging-Cox1Mgcv.js} +1 -1
- package/dist/nitro.js +3 -3
- package/dist/{oauth-authorization-DF2dSmNh.js → oauth-authorization-CC8a1JKP.js} +1 -1
- package/dist/{oauth-callback-server-CW9COQ9L.js → oauth-callback-server-Z7QSt416.js} +2 -2
- package/dist/{pending-auth-8Kmti8S3.js → pending-auth-DQhC3eSR.js} +1 -1
- package/dist/{profile-C4_njQYA.js → profile-BdApelHS.js} +4 -4
- package/dist/{registry-BhiLWAY6.js → registry-DiY6B8CN.js} +1 -1
- package/dist/{resolve-ZDwJ6a3N.js → resolve-VpJoiDpc.js} +2 -2
- package/dist/{runner-D1xeORYM.js → runner-BIMegnPE.js} +8 -8
- package/dist/{sandbox-egress-signals-Dx1PzQpy.js → sandbox-egress-signals-Cbtxe492.js} +1 -1
- package/dist/{schema-DuGfOm4u.js → schema-CxwnlTDN.js} +3 -116
- package/dist/{skills-CN-ye6on.js → skills-WttpPJUy.js} +2 -2
- package/dist/{spawn-B8KYOBcU.js → spawn-DhfMKt4y.js} +2 -2
- package/dist/{task-queue-DPaZtxld.js → task-queue-CZqUNJD0.js} +3 -3
- package/dist/{task-runner-D23tSETM.js → task-runner-BZaqoUCT.js} +15 -12
- package/dist/{thread-state-BbxSiDa9.js → thread-state-8tXZhu0y.js} +5 -5
- package/dist/{turn-failure-response-c8irQDMB.js → turn-failure-response-DFJ9L9NQ.js} +7 -7
- package/dist/{turn-lifecycle-DSVG3u_p.js → turn-lifecycle-WbpTUQ_r.js} +1 -1
- package/dist/{turn-session-routing-B2hNxu_N.js → turn-session-routing--EhI76rH.js} +1 -1
- package/dist/types-Du-HbEun.js +621 -0
- package/dist/{validation-BrLB5qbC.js → validation-0F9IgG3b.js} +1 -1
- package/dist/{version-B2rspCmP.js → version-A7-RRBOE.js} +1 -1
- package/dist/version.js +1 -1
- package/dist/{web-authorization-CVSTvY_D.js → web-authorization-BJggXofy.js} +6 -6
- package/dist/{web-input-zUQoZMZw.js → web-input-B-siN5Dr.js} +210 -162
- package/dist/web-message-id-CVVCTWUb.js +464 -0
- package/dist/{work-BXbJVOZI.js → work-CNc0ejGG.js} +7 -7
- package/dist/{worker-ClkQ9S9C.js → worker-BHbqpUAu.js} +5 -5
- package/package.json +4 -4
- 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-
|
|
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
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-
|
|
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
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-
|
|
23
|
-
import { a as getVercelConversationWorkQueue, f as getTurnRequestDeadline, n as sendNextWorkspaceSnapshotJob, r as sendWorkspaceSnapshotJob } from "./job-queue-
|
|
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";
|
|
@@ -10252,37 +10253,28 @@ function createEventTools(context, catalog) {
|
|
|
10252
10253
|
stopWatchingResources: createStopWatchingResourcesTool(context)
|
|
10253
10254
|
};
|
|
10254
10255
|
}
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
/**
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
createdBy: eventAutomationPrincipalSchema,
|
|
10278
|
-
credentialMode: z.enum(["system", "creator"]),
|
|
10279
|
-
destination: eventAutomationDestinationSchema,
|
|
10280
|
-
destinationVisibility: destinationVisibilitySchema,
|
|
10281
|
-
/** Explicit visible effects after successful work. An empty list is silent. */
|
|
10282
|
-
outcomes: z.array(eventAutomationOutcomeSchema).max(5),
|
|
10283
|
-
task: z.object({ text: z.string().min(1) }).strict(),
|
|
10284
|
-
trigger: eventAutomationTriggerSchema
|
|
10285
|
-
}).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
|
+
}
|
|
10286
10278
|
//#endregion
|
|
10287
10279
|
//#region src/chat/event-automations/store.ts
|
|
10288
10280
|
const retainedEventAutomationSchema = eventAutomationSchema.extend({
|
|
@@ -10294,7 +10286,8 @@ function eventAutomationJsonPayload(task) {
|
|
|
10294
10286
|
const { status: _status, title: _title, ...payload } = task;
|
|
10295
10287
|
return payload;
|
|
10296
10288
|
}
|
|
10297
|
-
|
|
10289
|
+
/** Decode a retained event Automation with its SQL-backed fields. */
|
|
10290
|
+
function parseEventAutomationRow(row) {
|
|
10298
10291
|
const raw = row.task && typeof row.task === "object" ? { ...row.task } : row.task;
|
|
10299
10292
|
if (raw && typeof raw === "object" && "title" in raw) delete raw.title;
|
|
10300
10293
|
if (raw && typeof raw === "object" && !("outcomes" in raw) && "destination" in raw) raw.outcomes = [{
|
|
@@ -10314,7 +10307,7 @@ function parseTask(row) {
|
|
|
10314
10307
|
destination
|
|
10315
10308
|
};
|
|
10316
10309
|
}),
|
|
10317
|
-
status: row.status
|
|
10310
|
+
status: row.status ?? "active",
|
|
10318
10311
|
...title ? { title } : void 0
|
|
10319
10312
|
};
|
|
10320
10313
|
}
|
|
@@ -10328,7 +10321,7 @@ async function getEventAutomation(db, id) {
|
|
|
10328
10321
|
task: juniorEventAutomations.task,
|
|
10329
10322
|
title: juniorEventAutomations.title
|
|
10330
10323
|
}).from(juniorEventAutomations).where(eq(juniorEventAutomations.id, id)).limit(1);
|
|
10331
|
-
return rows[0] ?
|
|
10324
|
+
return rows[0] ? parseEventAutomationRow(rows[0]) : void 0;
|
|
10332
10325
|
}
|
|
10333
10326
|
/** Create one retry-stable event automation, or revive a deleted row with the new payload. */
|
|
10334
10327
|
async function createEventAutomation(db, task) {
|
|
@@ -10363,14 +10356,19 @@ async function createEventAutomation(db, task) {
|
|
|
10363
10356
|
task: juniorEventAutomations.task,
|
|
10364
10357
|
title: juniorEventAutomations.title
|
|
10365
10358
|
});
|
|
10366
|
-
return rows[0] ?
|
|
10359
|
+
return rows[0] ? parseEventAutomationRow(rows[0]) : await getEventAutomation(db, parsed.id) ?? {
|
|
10367
10360
|
...parsed,
|
|
10368
10361
|
status: "active",
|
|
10369
10362
|
...title ? { title } : void 0
|
|
10370
10363
|
};
|
|
10371
10364
|
}
|
|
10372
10365
|
/** Replace an existing non-deleted event automation. */
|
|
10373
|
-
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
|
+
});
|
|
10374
10372
|
const parsed = eventAutomationSchema.parse(eventAutomationJsonPayload(task));
|
|
10375
10373
|
const title = task.title?.trim() || null;
|
|
10376
10374
|
const rows = await db.update(juniorEventAutomations).set({
|
|
@@ -10383,7 +10381,7 @@ async function saveEventAutomation(db, task) {
|
|
|
10383
10381
|
task: juniorEventAutomations.task,
|
|
10384
10382
|
title: juniorEventAutomations.title
|
|
10385
10383
|
});
|
|
10386
|
-
return rows[0] ?
|
|
10384
|
+
return rows[0] ? parseEventAutomationRow(rows[0]) : void 0;
|
|
10387
10385
|
}
|
|
10388
10386
|
/** Mark one existing event automation deleted while retaining the row for history. */
|
|
10389
10387
|
async function deleteEventAutomation(db, id) {
|
|
@@ -10392,7 +10390,7 @@ async function deleteEventAutomation(db, id) {
|
|
|
10392
10390
|
task: juniorEventAutomations.task,
|
|
10393
10391
|
title: juniorEventAutomations.title
|
|
10394
10392
|
});
|
|
10395
|
-
return rows[0] ?
|
|
10393
|
+
return rows[0] ? parseEventAutomationRow(rows[0]) : void 0;
|
|
10396
10394
|
}
|
|
10397
10395
|
/** List live event automations in one Slack workspace. */
|
|
10398
10396
|
async function listEventAutomationsForTeam(db, teamId) {
|
|
@@ -10400,7 +10398,7 @@ async function listEventAutomationsForTeam(db, teamId) {
|
|
|
10400
10398
|
status: juniorEventAutomations.status,
|
|
10401
10399
|
task: juniorEventAutomations.task,
|
|
10402
10400
|
title: juniorEventAutomations.title
|
|
10403
|
-
}).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);
|
|
10404
10402
|
}
|
|
10405
10403
|
function viewerSlackIdentities(user) {
|
|
10406
10404
|
return user.identities.filter((identity) => identity.provider === "slack" && Boolean(identity.providerTenantId) && Boolean(identity.providerSubjectId));
|
|
@@ -10409,17 +10407,6 @@ function viewerSlackIdentities(user) {
|
|
|
10409
10407
|
function eventAutomationBelongsToUser(task, user) {
|
|
10410
10408
|
return viewerSlackIdentities(user).some((identity) => identity.providerTenantId === task.destination.teamId && identity.providerSubjectId === task.createdBy.slackUserId);
|
|
10411
10409
|
}
|
|
10412
|
-
/** List a bounded newest-first page of live event automations created by one user. */
|
|
10413
|
-
async function listEventAutomationsCreatedBy(db, user, limit, query) {
|
|
10414
|
-
const identities = viewerSlackIdentities(user);
|
|
10415
|
-
const ownership = or(...identities.map((identity) => and(eq(juniorEventAutomations.teamId, identity.providerTenantId), sql`${juniorEventAutomations.task}->'createdBy'->>'slackUserId' = ${identity.providerSubjectId}`)));
|
|
10416
|
-
if (!ownership) return [];
|
|
10417
|
-
return (await db.select({
|
|
10418
|
-
status: juniorEventAutomations.status,
|
|
10419
|
-
task: juniorEventAutomations.task,
|
|
10420
|
-
title: juniorEventAutomations.title
|
|
10421
|
-
}).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);
|
|
10422
|
-
}
|
|
10423
10410
|
/**
|
|
10424
10411
|
* List deleted event automations created by one user, newest first.
|
|
10425
10412
|
* Used only to keep historical runs after the live automation list drops the row.
|
|
@@ -10432,16 +10419,7 @@ async function listDeletedEventAutomationsCreatedBy(db, user, limit) {
|
|
|
10432
10419
|
status: juniorEventAutomations.status,
|
|
10433
10420
|
task: juniorEventAutomations.task,
|
|
10434
10421
|
title: juniorEventAutomations.title
|
|
10435
|
-
}).from(juniorEventAutomations).where(and(ownership, eq(juniorEventAutomations.status, "deleted"))).orderBy(desc(juniorEventAutomations.createdAtMs), desc(juniorEventAutomations.id)).limit(limit)).map(
|
|
10436
|
-
}
|
|
10437
|
-
/** List live event automations whose current Slack destination is public. */
|
|
10438
|
-
async function listPublicEventAutomationsForTeams(db, teamIds, limit, query) {
|
|
10439
|
-
if (teamIds.length === 0) return [];
|
|
10440
|
-
return (await db.select({
|
|
10441
|
-
status: juniorEventAutomations.status,
|
|
10442
|
-
task: juniorEventAutomations.task,
|
|
10443
|
-
title: juniorEventAutomations.title
|
|
10444
|
-
}).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);
|
|
10445
10423
|
}
|
|
10446
10424
|
/**
|
|
10447
10425
|
* Collect match keys from live event automations for these identifiers and event types.
|
|
@@ -10455,10 +10433,10 @@ async function collectEventAutomationMatchKeys(db, input) {
|
|
|
10455
10433
|
status: juniorEventAutomations.status,
|
|
10456
10434
|
task: juniorEventAutomations.task,
|
|
10457
10435
|
title: juniorEventAutomations.title
|
|
10458
|
-
}).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));
|
|
10459
10437
|
const keys = /* @__PURE__ */ new Set();
|
|
10460
10438
|
for (const row of rows) {
|
|
10461
|
-
const task =
|
|
10439
|
+
const task = parseEventAutomationRow(row);
|
|
10462
10440
|
if (!task.trigger.events.some((eventType) => eventTypes.has(eventType))) continue;
|
|
10463
10441
|
for (const key of Object.keys(task.trigger.match ?? {})) keys.add(key);
|
|
10464
10442
|
}
|
|
@@ -10470,16 +10448,24 @@ async function findMatchingEventAutomations(db, event, teamId) {
|
|
|
10470
10448
|
status: juniorEventAutomations.status,
|
|
10471
10449
|
task: juniorEventAutomations.task,
|
|
10472
10450
|
title: juniorEventAutomations.title
|
|
10473
|
-
}).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
|
+
});
|
|
10474
10466
|
}
|
|
10475
10467
|
//#endregion
|
|
10476
10468
|
//#region src/chat/task-outcomes.ts
|
|
10477
|
-
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.");
|
|
10478
|
-
/** Input accepted by task authoring tools before a user becomes a DM Destination. */
|
|
10479
|
-
const taskOutcomeInputSchema = z.object({
|
|
10480
|
-
action: z.literal("send_message"),
|
|
10481
|
-
destination: taskMessageDestinationInputSchema
|
|
10482
|
-
}).strict();
|
|
10483
10469
|
/** Resolve explicit message outcomes to the Slack Destinations stored on an Automation. */
|
|
10484
10470
|
async function resolveTaskOutcomes(outcomes, currentDestination, creatorSlackUserId) {
|
|
10485
10471
|
if (outcomes === void 0) return [];
|
|
@@ -10527,6 +10513,11 @@ function moveTaskOutcomes(outcomes, currentDestination, nextDestination) {
|
|
|
10527
10513
|
//#region src/chat/event-automations/tool-support.ts
|
|
10528
10514
|
const compactEventAutomationResultSchema = z.object({
|
|
10529
10515
|
id: z.string().min(1),
|
|
10516
|
+
status: z.enum([
|
|
10517
|
+
"active",
|
|
10518
|
+
"paused",
|
|
10519
|
+
"deleted"
|
|
10520
|
+
]),
|
|
10530
10521
|
title: z.string().min(1).nullable(),
|
|
10531
10522
|
dashboardUrl: z.string().url().nullable(),
|
|
10532
10523
|
instruction: z.string().min(1),
|
|
@@ -10626,6 +10617,7 @@ function eventAutomationTriggerAvailable(task, catalog) {
|
|
|
10626
10617
|
function compactEventAutomation(task, catalog, requesterSlackUserId) {
|
|
10627
10618
|
return compactEventAutomationResultSchema.parse({
|
|
10628
10619
|
id: task.id,
|
|
10620
|
+
status: task.status,
|
|
10629
10621
|
title: task.title?.trim() || null,
|
|
10630
10622
|
dashboardUrl: getDashboardTaskLink(task.id) ?? null,
|
|
10631
10623
|
instruction: task.task.text,
|
|
@@ -10829,13 +10821,78 @@ function createListEventAutomationsTool(context, catalog) {
|
|
|
10829
10821
|
});
|
|
10830
10822
|
}
|
|
10831
10823
|
//#endregion
|
|
10832
|
-
//#region src/chat/
|
|
10833
|
-
/**
|
|
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
|
|
10834
10861
|
function changesEventAutomationTrigger(current, next) {
|
|
10835
10862
|
const currentEvents = [...current.events].sort();
|
|
10836
10863
|
const nextEvents = [...next.events].sort();
|
|
10837
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);
|
|
10838
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
|
|
10839
10896
|
/** Create the core tool that updates an event automation. */
|
|
10840
10897
|
function createUpdateEventAutomationTool(context, catalog) {
|
|
10841
10898
|
return zodTool({
|
|
@@ -10850,7 +10907,8 @@ function createUpdateEventAutomationTool(context, catalog) {
|
|
|
10850
10907
|
description: "Update the instruction, registered trigger, or credential use for an event automation.",
|
|
10851
10908
|
inputSchema: z.object({
|
|
10852
10909
|
automationId: z.string().min(1),
|
|
10853
|
-
|
|
10910
|
+
title: automationTitleSchema.optional(),
|
|
10911
|
+
instruction: automationInstructionSchema.nullable().optional(),
|
|
10854
10912
|
trigger: registeredEventAutomationTriggerSchema(catalog).nullable().optional(),
|
|
10855
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(),
|
|
10856
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()
|
|
@@ -10866,43 +10924,19 @@ function createUpdateEventAutomationTool(context, catalog) {
|
|
|
10866
10924
|
};
|
|
10867
10925
|
},
|
|
10868
10926
|
outputSchema: eventAutomationToolResultSchema,
|
|
10869
|
-
async execute(input
|
|
10927
|
+
async execute(input) {
|
|
10870
10928
|
const current = await writableEventAutomation(context, input.automationId);
|
|
10871
|
-
const match = input.trigger ? requireSupportedEventAutomationTrigger(catalog, input.trigger) : void 0;
|
|
10872
10929
|
const { actor } = requireEventAutomationSlackContext(context);
|
|
10873
10930
|
const isCreator = actor.userId === current.createdBy.slackUserId;
|
|
10874
|
-
if (input.
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
...match ? { match } : void 0
|
|
10884
|
-
} : current.trigger;
|
|
10885
|
-
const nextInstruction = input.instruction != null ? input.instruction : current.task.text;
|
|
10886
|
-
const instructionChanged = nextInstruction !== current.task.text;
|
|
10887
|
-
const changesExecution = instructionChanged || changesEventAutomationTrigger(current.trigger, nextTrigger);
|
|
10888
|
-
const next = {
|
|
10889
|
-
...current,
|
|
10890
|
-
credentialMode: changesExecution && !isCreator ? "system" : input.credentialMode ?? current.credentialMode,
|
|
10891
|
-
outcomes: input.outcomes == null ? current.outcomes : await resolveTaskOutcomes(input.outcomes, current.destination, current.createdBy.slackUserId),
|
|
10892
|
-
task: { text: nextInstruction },
|
|
10893
|
-
trigger: nextTrigger
|
|
10894
|
-
};
|
|
10895
|
-
if (instructionChanged) {
|
|
10896
|
-
const title = await generateShortTitle({
|
|
10897
|
-
signal: options.signal,
|
|
10898
|
-
completeText,
|
|
10899
|
-
kind: "task",
|
|
10900
|
-
sourceText: nextInstruction
|
|
10901
|
-
});
|
|
10902
|
-
if (title) next.title = title;
|
|
10903
|
-
else delete next.title;
|
|
10904
|
-
}
|
|
10905
|
-
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));
|
|
10906
10940
|
if (!saved) throw new ToolInputError("Event automation was not found.");
|
|
10907
10941
|
return eventAutomationToolResult(context.conversationId, saved, catalog, actor.userId);
|
|
10908
10942
|
}
|
|
@@ -10921,113 +10955,6 @@ function createEventAutomationTools(context, catalog) {
|
|
|
10921
10955
|
};
|
|
10922
10956
|
}
|
|
10923
10957
|
//#endregion
|
|
10924
|
-
//#region src/chat/scheduled-automations/types.ts
|
|
10925
|
-
/** Durable scheduled-automation domain types owned by Junior core. */
|
|
10926
|
-
const scheduledAutomationStatusSchema = z.enum([
|
|
10927
|
-
"active",
|
|
10928
|
-
"blocked",
|
|
10929
|
-
"completed",
|
|
10930
|
-
"deleted"
|
|
10931
|
-
]);
|
|
10932
|
-
const scheduledAutomationCredentialModeSchema = z.enum(["system", "creator"]);
|
|
10933
|
-
const scheduledRunStatusSchema = z.enum([
|
|
10934
|
-
"pending",
|
|
10935
|
-
"running",
|
|
10936
|
-
"completed",
|
|
10937
|
-
"failed",
|
|
10938
|
-
"blocked",
|
|
10939
|
-
"skipped"
|
|
10940
|
-
]);
|
|
10941
|
-
const scheduledAutomationPrincipalSchema = z.object({
|
|
10942
|
-
slackUserId: actorUserIdSchema,
|
|
10943
|
-
fullName: z.string().optional(),
|
|
10944
|
-
userName: z.string().optional()
|
|
10945
|
-
}).strict();
|
|
10946
|
-
const scheduledAutomationRecurrenceSchema = z.object({
|
|
10947
|
-
dayOfMonth: z.number().optional(),
|
|
10948
|
-
frequency: z.enum([
|
|
10949
|
-
"daily",
|
|
10950
|
-
"weekly",
|
|
10951
|
-
"monthly",
|
|
10952
|
-
"yearly"
|
|
10953
|
-
]),
|
|
10954
|
-
interval: z.number(),
|
|
10955
|
-
month: z.number().optional(),
|
|
10956
|
-
startDate: z.string(),
|
|
10957
|
-
time: z.object({
|
|
10958
|
-
hour: z.number(),
|
|
10959
|
-
minute: z.number()
|
|
10960
|
-
}).strict(),
|
|
10961
|
-
weekdays: z.array(z.number()).optional()
|
|
10962
|
-
}).strict();
|
|
10963
|
-
const scheduledAutomationScheduleSchema = z.object({
|
|
10964
|
-
description: z.string(),
|
|
10965
|
-
kind: z.enum(["one_off", "recurring"]),
|
|
10966
|
-
recurrence: scheduledAutomationRecurrenceSchema.optional(),
|
|
10967
|
-
timezone: z.string()
|
|
10968
|
-
}).strict();
|
|
10969
|
-
const scheduledAutomationExecutionActorSchema = z.object({
|
|
10970
|
-
platform: z.literal("system"),
|
|
10971
|
-
name: z.string()
|
|
10972
|
-
}).strict();
|
|
10973
|
-
/** Scheduled automations target a Slack conversation, never one message thread. */
|
|
10974
|
-
const scheduledAutomationDestinationSchema = slackDestinationSchema.omit({ threadTs: true });
|
|
10975
|
-
const scheduledAutomationOutcomeSchema = z.object({
|
|
10976
|
-
action: z.literal("send_message"),
|
|
10977
|
-
destination: scheduledAutomationDestinationSchema
|
|
10978
|
-
}).strict();
|
|
10979
|
-
/** Validate the current scheduled-automation domain shape. */
|
|
10980
|
-
const scheduledAutomationSchema = z.object({
|
|
10981
|
-
id: z.string(),
|
|
10982
|
-
conversationAccess: z.object({
|
|
10983
|
-
audience: z.enum([
|
|
10984
|
-
"direct",
|
|
10985
|
-
"group",
|
|
10986
|
-
"channel"
|
|
10987
|
-
]),
|
|
10988
|
-
visibility: z.enum(["private", "public"])
|
|
10989
|
-
}).strict(),
|
|
10990
|
-
createdAtMs: z.number(),
|
|
10991
|
-
createdBy: scheduledAutomationPrincipalSchema,
|
|
10992
|
-
/** Authoritative provider identity that created the task. */
|
|
10993
|
-
creatorIdentityId: z.string(),
|
|
10994
|
-
/** Selects system credentials or task-bound creator credential delegation. */
|
|
10995
|
-
credentialMode: scheduledAutomationCredentialModeSchema,
|
|
10996
|
-
destination: scheduledAutomationDestinationSchema,
|
|
10997
|
-
executionActor: scheduledAutomationExecutionActorSchema.optional(),
|
|
10998
|
-
lastRunAtMs: z.number().optional(),
|
|
10999
|
-
nextRunAtMs: z.number().optional(),
|
|
11000
|
-
originalRequest: z.string().optional(),
|
|
11001
|
-
runNowAtMs: z.number().optional(),
|
|
11002
|
-
schedule: scheduledAutomationScheduleSchema,
|
|
11003
|
-
status: scheduledAutomationStatusSchema,
|
|
11004
|
-
statusReason: z.string().optional(),
|
|
11005
|
-
/** Explicit visible effects after successful work. An empty list is silent. */
|
|
11006
|
-
outcomes: z.array(scheduledAutomationOutcomeSchema).max(5),
|
|
11007
|
-
task: z.object({ text: z.string() }).strict(),
|
|
11008
|
-
/** SQL-backed short display title generated from the task instruction. */
|
|
11009
|
-
title: z.string().optional(),
|
|
11010
|
-
updatedAtMs: z.number()
|
|
11011
|
-
}).strict();
|
|
11012
|
-
/** Validate the current scheduled-run domain shape. */
|
|
11013
|
-
const scheduledRunSchema = z.object({
|
|
11014
|
-
id: z.string(),
|
|
11015
|
-
attempt: z.number(),
|
|
11016
|
-
claimedAtMs: z.number(),
|
|
11017
|
-
completedAtMs: z.number().optional(),
|
|
11018
|
-
dispatchId: z.string().optional(),
|
|
11019
|
-
errorMessage: z.string().optional(),
|
|
11020
|
-
resultMessageTs: z.string().optional(),
|
|
11021
|
-
scheduledForMs: z.number(),
|
|
11022
|
-
startedAtMs: z.number().optional(),
|
|
11023
|
-
status: scheduledRunStatusSchema,
|
|
11024
|
-
taskId: z.string()
|
|
11025
|
-
}).strict();
|
|
11026
|
-
const SCHEDULED_AUTOMATION_SYSTEM_ACTOR = Object.freeze({
|
|
11027
|
-
platform: "system",
|
|
11028
|
-
name: "scheduled-automation"
|
|
11029
|
-
});
|
|
11030
|
-
//#endregion
|
|
11031
10958
|
//#region src/chat/scheduled-automations/tasks.ts
|
|
11032
10959
|
const SCHEDULER_KEY_PREFIX = "junior:scheduler";
|
|
11033
10960
|
const retainedScheduledAutomationSchema = scheduledAutomationSchema.omit({
|
|
@@ -11036,13 +10963,6 @@ const retainedScheduledAutomationSchema = scheduledAutomationSchema.omit({
|
|
|
11036
10963
|
}).extend({
|
|
11037
10964
|
destination: slackDestinationSchema,
|
|
11038
10965
|
outcomes: z.array(taskOutcomeSchema).max(5),
|
|
11039
|
-
status: z.enum([
|
|
11040
|
-
"active",
|
|
11041
|
-
"blocked",
|
|
11042
|
-
"completed",
|
|
11043
|
-
"deleted",
|
|
11044
|
-
"paused"
|
|
11045
|
-
]),
|
|
11046
10966
|
version: z.number().optional()
|
|
11047
10967
|
}).strict();
|
|
11048
10968
|
/** Decode a retained scheduler task row and reject invalid routing context. */
|
|
@@ -11082,15 +11002,6 @@ function parseScheduledAutomationRow(row) {
|
|
|
11082
11002
|
if (creatorIdentityId === void 0) return void 0;
|
|
11083
11003
|
const fallbackTitle = row.title === null ? z.string().safeParse(legacyTitle) : void 0;
|
|
11084
11004
|
const title = (row.title !== null ? row.title : fallbackTitle?.success ? fallbackTitle.data : void 0)?.trim() || void 0;
|
|
11085
|
-
if (status === "paused") {
|
|
11086
|
-
const { nextRunAtMs: _nextRunAtMs, runNowAtMs: _runNowAtMs, ...retained } = task;
|
|
11087
|
-
return {
|
|
11088
|
-
...retained,
|
|
11089
|
-
creatorIdentityId,
|
|
11090
|
-
status: "deleted",
|
|
11091
|
-
...title ? { title } : void 0
|
|
11092
|
-
};
|
|
11093
|
-
}
|
|
11094
11005
|
return {
|
|
11095
11006
|
...task,
|
|
11096
11007
|
creatorIdentityId,
|
|
@@ -11169,7 +11080,7 @@ async function readListedScheduledAutomations(db, teamId) {
|
|
|
11169
11080
|
creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
|
|
11170
11081
|
record: juniorSchedulerTasks.record,
|
|
11171
11082
|
title: juniorSchedulerTasks.title
|
|
11172
|
-
}).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);
|
|
11173
11084
|
}
|
|
11174
11085
|
/** List decoded scheduled automations in stable creation order. */
|
|
11175
11086
|
async function listScheduledAutomations(db) {
|
|
@@ -11213,10 +11124,11 @@ async function createScheduledAutomation(db, task) {
|
|
|
11213
11124
|
});
|
|
11214
11125
|
}
|
|
11215
11126
|
/** Save a scheduled automation and clear its blocked occurrence on reactivation. */
|
|
11216
|
-
async function saveScheduledAutomation(db, task) {
|
|
11127
|
+
async function saveScheduledAutomation(db, task, expectedRevision) {
|
|
11217
11128
|
const next = requireStoredTask(task);
|
|
11218
11129
|
await withScheduledAutomationLock(db, task.id, async (tx) => {
|
|
11219
11130
|
const current = await readScheduledAutomation(tx, task.id);
|
|
11131
|
+
if (expectedRevision !== void 0) requireAutomationRevision(current, expectedRevision);
|
|
11220
11132
|
await writeScheduledAutomation(tx, next, current);
|
|
11221
11133
|
});
|
|
11222
11134
|
return next;
|
|
@@ -11225,472 +11137,6 @@ async function saveScheduledAutomation(db, task) {
|
|
|
11225
11137
|
async function saveScheduledAutomationInLock(db, task, current) {
|
|
11226
11138
|
await writeScheduledAutomation(db, requireStoredTask(task), current);
|
|
11227
11139
|
}
|
|
11228
|
-
/** List scheduled automations whose current Slack destination is public. */
|
|
11229
|
-
async function listPublicScheduledAutomationsForTeams(db, teamIds, input) {
|
|
11230
|
-
if (teamIds.length === 0) return [];
|
|
11231
|
-
return (await db.select({
|
|
11232
|
-
creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
|
|
11233
|
-
record: juniorSchedulerTasks.record,
|
|
11234
|
-
title: juniorSchedulerTasks.title
|
|
11235
|
-
}).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, [
|
|
11236
|
-
"completed",
|
|
11237
|
-
"deleted",
|
|
11238
|
-
"paused"
|
|
11239
|
-
]), 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);
|
|
11240
|
-
}
|
|
11241
|
-
//#endregion
|
|
11242
|
-
//#region src/chat/scheduled-automations/cadence.ts
|
|
11243
|
-
const FORMATTERS = /* @__PURE__ */ new Map();
|
|
11244
|
-
function getFormatter(timezone) {
|
|
11245
|
-
const existing = FORMATTERS.get(timezone);
|
|
11246
|
-
if (existing) return existing;
|
|
11247
|
-
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
11248
|
-
timeZone: timezone,
|
|
11249
|
-
hour12: false,
|
|
11250
|
-
year: "numeric",
|
|
11251
|
-
month: "2-digit",
|
|
11252
|
-
day: "2-digit",
|
|
11253
|
-
hour: "2-digit",
|
|
11254
|
-
minute: "2-digit",
|
|
11255
|
-
second: "2-digit"
|
|
11256
|
-
});
|
|
11257
|
-
FORMATTERS.set(timezone, formatter);
|
|
11258
|
-
return formatter;
|
|
11259
|
-
}
|
|
11260
|
-
function normalizeHour(hour) {
|
|
11261
|
-
return hour === 24 ? 0 : hour;
|
|
11262
|
-
}
|
|
11263
|
-
function getLocalDateWeekday(date) {
|
|
11264
|
-
return new Date(Date.UTC(date.year, date.month - 1, date.day)).getUTCDay();
|
|
11265
|
-
}
|
|
11266
|
-
function getWeekStart(date) {
|
|
11267
|
-
return addDays(date, -((getLocalDateWeekday(date) + 6) % 7));
|
|
11268
|
-
}
|
|
11269
|
-
/** Resolve a UTC timestamp into calendar parts for a named time zone. */
|
|
11270
|
-
function getZonedDateTimeParts(timestampMs, timezone) {
|
|
11271
|
-
const parts = getFormatter(timezone).formatToParts(new Date(timestampMs));
|
|
11272
|
-
const values = new Map(parts.map((part) => [part.type, part.value]));
|
|
11273
|
-
const year = Number(values.get("year"));
|
|
11274
|
-
const month = Number(values.get("month"));
|
|
11275
|
-
const day = Number(values.get("day"));
|
|
11276
|
-
return {
|
|
11277
|
-
year,
|
|
11278
|
-
month,
|
|
11279
|
-
day,
|
|
11280
|
-
hour: normalizeHour(Number(values.get("hour"))),
|
|
11281
|
-
minute: Number(values.get("minute")),
|
|
11282
|
-
second: Number(values.get("second")),
|
|
11283
|
-
weekday: getLocalDateWeekday({
|
|
11284
|
-
year,
|
|
11285
|
-
month,
|
|
11286
|
-
day
|
|
11287
|
-
})
|
|
11288
|
-
};
|
|
11289
|
-
}
|
|
11290
|
-
function getTimeZoneOffsetMs(timestampMs, timezone) {
|
|
11291
|
-
const parts = getZonedDateTimeParts(timestampMs, timezone);
|
|
11292
|
-
return Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second) - timestampMs;
|
|
11293
|
-
}
|
|
11294
|
-
function localDateTimeToTimestampMs(args) {
|
|
11295
|
-
const localAsUtcMs = Date.UTC(args.date.year, args.date.month - 1, args.date.day, args.time.hour, args.time.minute, 0);
|
|
11296
|
-
const offsets = /* @__PURE__ */ new Set();
|
|
11297
|
-
for (const probeDeltaMs of [
|
|
11298
|
-
-1296e5,
|
|
11299
|
-
-432e5,
|
|
11300
|
-
0,
|
|
11301
|
-
432e5,
|
|
11302
|
-
1296e5
|
|
11303
|
-
]) offsets.add(getTimeZoneOffsetMs(localAsUtcMs + probeDeltaMs, args.timezone));
|
|
11304
|
-
const matches = [...offsets].map((offsetMs) => localAsUtcMs - offsetMs).filter((timestampMs) => {
|
|
11305
|
-
const parts = getZonedDateTimeParts(timestampMs, args.timezone);
|
|
11306
|
-
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;
|
|
11307
|
-
});
|
|
11308
|
-
if (matches.length === 0) return;
|
|
11309
|
-
return Math.min(...matches);
|
|
11310
|
-
}
|
|
11311
|
-
/** Resolve local time, rejecting DST gaps and choosing the earlier instant in a fold. */
|
|
11312
|
-
function resolveLocalScheduleAtMs(args) {
|
|
11313
|
-
const date = parseLocalDate(args.date);
|
|
11314
|
-
if (!date) return;
|
|
11315
|
-
return localDateTimeToTimestampMs({
|
|
11316
|
-
date,
|
|
11317
|
-
time: args.time,
|
|
11318
|
-
timezone: args.timezone
|
|
11319
|
-
});
|
|
11320
|
-
}
|
|
11321
|
-
function daysBetween(left, right) {
|
|
11322
|
-
return Math.floor((Date.UTC(right.year, right.month - 1, right.day) - Date.UTC(left.year, left.month - 1, left.day)) / 864e5);
|
|
11323
|
-
}
|
|
11324
|
-
function weeklyRecurrenceMatchesDate(date, start, recurrence) {
|
|
11325
|
-
if (compareDate(date, start) < 0) return false;
|
|
11326
|
-
return normalizeWeekdays(recurrence.weekdays).includes(getLocalDateWeekday(date)) && Math.floor(daysBetween(getWeekStart(start), getWeekStart(date)) / 7) % recurrence.interval === 0;
|
|
11327
|
-
}
|
|
11328
|
-
function greatestCommonDivisor(left, right) {
|
|
11329
|
-
let a = Math.abs(left);
|
|
11330
|
-
let b = Math.abs(right);
|
|
11331
|
-
while (b !== 0) [a, b] = [b, a % b];
|
|
11332
|
-
return a;
|
|
11333
|
-
}
|
|
11334
|
-
function findNextRunAtMs(args) {
|
|
11335
|
-
const start = parseLocalDate(args.recurrence.startDate);
|
|
11336
|
-
const interval = args.recurrence.interval;
|
|
11337
|
-
if (!start || !Number.isInteger(interval) || interval <= 0) return;
|
|
11338
|
-
const searchFrom = compareDate(args.searchFrom, start) < 0 ? start : args.searchFrom;
|
|
11339
|
-
if (args.recurrence.frequency === "daily") {
|
|
11340
|
-
const offsetDays = daysBetween(start, searchFrom);
|
|
11341
|
-
let candidateDate = addDays(start, Math.ceil(offsetDays / interval) * interval);
|
|
11342
|
-
const gregorianCycleDays = 146097;
|
|
11343
|
-
const candidateCount = gregorianCycleDays / greatestCommonDivisor(gregorianCycleDays, interval);
|
|
11344
|
-
for (let attempts = 0; attempts < candidateCount; attempts += 1) {
|
|
11345
|
-
const candidate = buildCandidate({
|
|
11346
|
-
date: candidateDate,
|
|
11347
|
-
recurrence: args.recurrence,
|
|
11348
|
-
timezone: args.timezone
|
|
11349
|
-
});
|
|
11350
|
-
if (candidate !== void 0 && candidate > args.afterMs) return candidate;
|
|
11351
|
-
candidateDate = addDays(candidateDate, interval);
|
|
11352
|
-
}
|
|
11353
|
-
return;
|
|
11354
|
-
}
|
|
11355
|
-
if (args.recurrence.frequency === "weekly") {
|
|
11356
|
-
if (normalizeWeekdays(args.recurrence.weekdays).length === 0) return;
|
|
11357
|
-
let candidateDate = searchFrom;
|
|
11358
|
-
const gregorianCycleDays = 146097;
|
|
11359
|
-
const cadenceDays = interval * 7;
|
|
11360
|
-
const searchDays = gregorianCycleDays * cadenceDays / greatestCommonDivisor(gregorianCycleDays, cadenceDays);
|
|
11361
|
-
for (let attempts = 0; attempts < searchDays; attempts += 1) {
|
|
11362
|
-
if (weeklyRecurrenceMatchesDate(candidateDate, start, args.recurrence)) {
|
|
11363
|
-
const candidate = buildCandidate({
|
|
11364
|
-
date: candidateDate,
|
|
11365
|
-
recurrence: args.recurrence,
|
|
11366
|
-
timezone: args.timezone
|
|
11367
|
-
});
|
|
11368
|
-
if (candidate !== void 0 && candidate > args.afterMs) return candidate;
|
|
11369
|
-
}
|
|
11370
|
-
candidateDate = addDays(candidateDate, 1);
|
|
11371
|
-
}
|
|
11372
|
-
return;
|
|
11373
|
-
}
|
|
11374
|
-
if (args.recurrence.frequency === "monthly") {
|
|
11375
|
-
const startMonth = start.year * 12 + start.month - 1;
|
|
11376
|
-
const searchMonth = searchFrom.year * 12 + searchFrom.month - 1;
|
|
11377
|
-
let candidateMonth = startMonth + Math.ceil((searchMonth - startMonth) / interval) * interval;
|
|
11378
|
-
const gregorianCycleMonths = 4800;
|
|
11379
|
-
const candidateCount = gregorianCycleMonths / greatestCommonDivisor(gregorianCycleMonths, interval);
|
|
11380
|
-
for (let attempts = 0; attempts < candidateCount; attempts += 1) {
|
|
11381
|
-
const candidateDate = {
|
|
11382
|
-
year: Math.floor(candidateMonth / 12),
|
|
11383
|
-
month: candidateMonth % 12 + 1,
|
|
11384
|
-
day: args.recurrence.dayOfMonth ?? 0
|
|
11385
|
-
};
|
|
11386
|
-
if (compareDate(candidateDate, searchFrom) >= 0) {
|
|
11387
|
-
const candidate = buildCandidate({
|
|
11388
|
-
date: candidateDate,
|
|
11389
|
-
recurrence: args.recurrence,
|
|
11390
|
-
timezone: args.timezone
|
|
11391
|
-
});
|
|
11392
|
-
if (candidate !== void 0 && candidate > args.afterMs) return candidate;
|
|
11393
|
-
}
|
|
11394
|
-
candidateMonth += interval;
|
|
11395
|
-
}
|
|
11396
|
-
return;
|
|
11397
|
-
}
|
|
11398
|
-
let candidateYear = start.year + Math.ceil((searchFrom.year - start.year) / interval) * interval;
|
|
11399
|
-
const candidateCount = 400 / greatestCommonDivisor(400, interval);
|
|
11400
|
-
for (let attempts = 0; attempts < candidateCount; attempts += 1) {
|
|
11401
|
-
const candidateDate = {
|
|
11402
|
-
year: candidateYear,
|
|
11403
|
-
month: args.recurrence.month ?? 0,
|
|
11404
|
-
day: args.recurrence.dayOfMonth ?? 0
|
|
11405
|
-
};
|
|
11406
|
-
if (compareDate(candidateDate, searchFrom) >= 0) {
|
|
11407
|
-
const candidate = buildCandidate({
|
|
11408
|
-
date: candidateDate,
|
|
11409
|
-
recurrence: args.recurrence,
|
|
11410
|
-
timezone: args.timezone
|
|
11411
|
-
});
|
|
11412
|
-
if (candidate !== void 0 && candidate > args.afterMs) return candidate;
|
|
11413
|
-
}
|
|
11414
|
-
candidateYear += interval;
|
|
11415
|
-
}
|
|
11416
|
-
}
|
|
11417
|
-
/** Compute the first recurring calendar occurrence strictly after a timestamp. */
|
|
11418
|
-
function getFirstRunAtMs(args) {
|
|
11419
|
-
return findNextRunAtMs({
|
|
11420
|
-
...args,
|
|
11421
|
-
searchFrom: getLocalDate(args.afterMs, args.timezone)
|
|
11422
|
-
});
|
|
11423
|
-
}
|
|
11424
|
-
function compareDate(left, right) {
|
|
11425
|
-
return Date.UTC(left.year, left.month - 1, left.day) - Date.UTC(right.year, right.month - 1, right.day);
|
|
11426
|
-
}
|
|
11427
|
-
function addDays(date, days) {
|
|
11428
|
-
const next = new Date(Date.UTC(date.year, date.month - 1, date.day + days));
|
|
11429
|
-
return {
|
|
11430
|
-
year: next.getUTCFullYear(),
|
|
11431
|
-
month: next.getUTCMonth() + 1,
|
|
11432
|
-
day: next.getUTCDate()
|
|
11433
|
-
};
|
|
11434
|
-
}
|
|
11435
|
-
function daysInMonth(year, month) {
|
|
11436
|
-
return new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
11437
|
-
}
|
|
11438
|
-
function parseLocalDate(value) {
|
|
11439
|
-
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
11440
|
-
if (!match) return;
|
|
11441
|
-
const year = Number(match[1]);
|
|
11442
|
-
const month = Number(match[2]);
|
|
11443
|
-
const day = Number(match[3]);
|
|
11444
|
-
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day) || month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) return;
|
|
11445
|
-
return {
|
|
11446
|
-
year,
|
|
11447
|
-
month,
|
|
11448
|
-
day
|
|
11449
|
-
};
|
|
11450
|
-
}
|
|
11451
|
-
function getLocalDate(timestampMs, timezone) {
|
|
11452
|
-
const parts = getZonedDateTimeParts(timestampMs, timezone);
|
|
11453
|
-
return {
|
|
11454
|
-
year: parts.year,
|
|
11455
|
-
month: parts.month,
|
|
11456
|
-
day: parts.day
|
|
11457
|
-
};
|
|
11458
|
-
}
|
|
11459
|
-
function normalizeWeekdays(values) {
|
|
11460
|
-
return [...new Set((values ?? []).filter((value) => value >= 0 && value <= 6))].sort((a, b) => a - b);
|
|
11461
|
-
}
|
|
11462
|
-
function buildCandidate(args) {
|
|
11463
|
-
return localDateTimeToTimestampMs({
|
|
11464
|
-
date: args.date,
|
|
11465
|
-
time: args.recurrence.time,
|
|
11466
|
-
timezone: args.timezone
|
|
11467
|
-
});
|
|
11468
|
-
}
|
|
11469
|
-
/** Return the next fire time after a completed run, when the task recurs. */
|
|
11470
|
-
function getNextRunAtMs(task, scheduledForMs, afterMs = scheduledForMs) {
|
|
11471
|
-
if (task.schedule.kind !== "recurring") return;
|
|
11472
|
-
const recurrence = task.schedule.recurrence;
|
|
11473
|
-
if (!recurrence || !Number.isFinite(recurrence.interval) || recurrence.interval <= 0) return;
|
|
11474
|
-
const timezone = task.schedule.timezone;
|
|
11475
|
-
const afterDate = getLocalDate(afterMs, timezone);
|
|
11476
|
-
const nextScheduledDate = addDays(getLocalDate(scheduledForMs, timezone), 1);
|
|
11477
|
-
return findNextRunAtMs({
|
|
11478
|
-
afterMs,
|
|
11479
|
-
recurrence,
|
|
11480
|
-
searchFrom: compareDate(afterDate, nextScheduledDate) < 0 ? nextScheduledDate : afterDate,
|
|
11481
|
-
timezone
|
|
11482
|
-
});
|
|
11483
|
-
}
|
|
11484
|
-
//#endregion
|
|
11485
|
-
//#region src/chat/scheduled-automations/schedule-intent.ts
|
|
11486
|
-
/**
|
|
11487
|
-
* Owns model-facing schedule intent and materializes it against the trusted
|
|
11488
|
-
* server clock into the canonical schedule persisted by core.
|
|
11489
|
-
*/
|
|
11490
|
-
const localDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Use a local date in YYYY-MM-DD format.");
|
|
11491
|
-
const localTimeSchema = z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "Use local time in HH:MM format.");
|
|
11492
|
-
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();
|
|
11493
|
-
const weekdaySchema = z.enum([
|
|
11494
|
-
"sunday",
|
|
11495
|
-
"monday",
|
|
11496
|
-
"tuesday",
|
|
11497
|
-
"wednesday",
|
|
11498
|
-
"thursday",
|
|
11499
|
-
"friday",
|
|
11500
|
-
"saturday"
|
|
11501
|
-
]);
|
|
11502
|
-
const oneOffScheduleIntentSchema = z.object({
|
|
11503
|
-
kind: z.literal("one_off").describe("A schedule that runs once."),
|
|
11504
|
-
timezone: timezoneSchema,
|
|
11505
|
-
timing: z.discriminatedUnion("type", [z.object({
|
|
11506
|
-
type: z.literal("after").describe("Run after a relative delay from the server clock."),
|
|
11507
|
-
value: z.number().int().positive().max(1e5),
|
|
11508
|
-
unit: z.enum([
|
|
11509
|
-
"second",
|
|
11510
|
-
"minute",
|
|
11511
|
-
"hour",
|
|
11512
|
-
"day",
|
|
11513
|
-
"week"
|
|
11514
|
-
])
|
|
11515
|
-
}).strict(), z.object({
|
|
11516
|
-
type: z.literal("at").describe("Run at an explicit local calendar date and time."),
|
|
11517
|
-
date: localDateSchema.describe("Requested local calendar date in YYYY-MM-DD format."),
|
|
11518
|
-
time: localTimeSchema.describe("Requested local wall-clock time in HH:MM format.")
|
|
11519
|
-
}).strict()]).describe("Relative delay or explicit local time for the one-off run.")
|
|
11520
|
-
}).strict();
|
|
11521
|
-
const recurringScheduleIntentSchema = z.object({
|
|
11522
|
-
kind: z.literal("recurring").describe("A repeating calendar schedule."),
|
|
11523
|
-
frequency: z.enum([
|
|
11524
|
-
"daily",
|
|
11525
|
-
"weekly",
|
|
11526
|
-
"monthly",
|
|
11527
|
-
"yearly"
|
|
11528
|
-
]).describe("Recurring tasks can run at most once per day."),
|
|
11529
|
-
interval: z.number().int().positive().max(365).describe("Repeat every N frequency units. Defaults to 1.").nullable().optional(),
|
|
11530
|
-
time: localTimeSchema.describe("Local wall-clock time for each occurrence in HH:MM format."),
|
|
11531
|
-
weekdays: z.array(weekdaySchema).min(1).max(7).describe("Required for weekly schedules.").nullable().optional(),
|
|
11532
|
-
dayOfMonth: z.number().int().min(1).max(31).describe("Required for monthly and yearly schedules.").nullable().optional(),
|
|
11533
|
-
month: z.number().int().min(1).max(12).describe("Required for yearly schedules, where January is 1.").nullable().optional(),
|
|
11534
|
-
startDate: localDateSchema.describe("Optional local calendar date that anchors the recurrence. Omit to start with the next matching occurrence.").nullable().optional(),
|
|
11535
|
-
timezone: timezoneSchema
|
|
11536
|
-
}).strict().superRefine((schedule, context) => {
|
|
11537
|
-
if (schedule.frequency === "weekly" && schedule.weekdays == null) context.addIssue({
|
|
11538
|
-
code: "custom",
|
|
11539
|
-
message: "Weekly schedules require weekdays.",
|
|
11540
|
-
path: ["weekdays"]
|
|
11541
|
-
});
|
|
11542
|
-
if ((schedule.frequency === "monthly" || schedule.frequency === "yearly") && schedule.dayOfMonth == null) context.addIssue({
|
|
11543
|
-
code: "custom",
|
|
11544
|
-
message: `${schedule.frequency} schedules require dayOfMonth.`,
|
|
11545
|
-
path: ["dayOfMonth"]
|
|
11546
|
-
});
|
|
11547
|
-
if (schedule.frequency === "yearly" && schedule.month == null) context.addIssue({
|
|
11548
|
-
code: "custom",
|
|
11549
|
-
message: "Yearly schedules require month.",
|
|
11550
|
-
path: ["month"]
|
|
11551
|
-
});
|
|
11552
|
-
if (schedule.frequency !== "weekly" && schedule.weekdays != null) context.addIssue({
|
|
11553
|
-
code: "custom",
|
|
11554
|
-
message: "weekdays applies only to weekly schedules.",
|
|
11555
|
-
path: ["weekdays"]
|
|
11556
|
-
});
|
|
11557
|
-
if (schedule.frequency !== "monthly" && schedule.frequency !== "yearly" && schedule.dayOfMonth != null) context.addIssue({
|
|
11558
|
-
code: "custom",
|
|
11559
|
-
message: "dayOfMonth applies only to monthly or yearly schedules.",
|
|
11560
|
-
path: ["dayOfMonth"]
|
|
11561
|
-
});
|
|
11562
|
-
if (schedule.frequency !== "yearly" && schedule.month != null) context.addIssue({
|
|
11563
|
-
code: "custom",
|
|
11564
|
-
message: "month applies only to yearly schedules.",
|
|
11565
|
-
path: ["month"]
|
|
11566
|
-
});
|
|
11567
|
-
});
|
|
11568
|
-
const scheduleIntentSchema = z.union([oneOffScheduleIntentSchema, recurringScheduleIntentSchema]);
|
|
11569
|
-
const WEEKDAY_INDEX = {
|
|
11570
|
-
sunday: 0,
|
|
11571
|
-
monday: 1,
|
|
11572
|
-
tuesday: 2,
|
|
11573
|
-
wednesday: 3,
|
|
11574
|
-
thursday: 4,
|
|
11575
|
-
friday: 5,
|
|
11576
|
-
saturday: 6
|
|
11577
|
-
};
|
|
11578
|
-
const DURATION_MS = {
|
|
11579
|
-
second: 1e3,
|
|
11580
|
-
minute: 6e4,
|
|
11581
|
-
hour: 36e5,
|
|
11582
|
-
day: 864e5,
|
|
11583
|
-
week: 6048e5
|
|
11584
|
-
};
|
|
11585
|
-
function parseLocalTime(value) {
|
|
11586
|
-
const [hour, minute] = value.split(":").map(Number);
|
|
11587
|
-
return {
|
|
11588
|
-
hour,
|
|
11589
|
-
minute
|
|
11590
|
-
};
|
|
11591
|
-
}
|
|
11592
|
-
function localDateAt(timestampMs, timezone) {
|
|
11593
|
-
const parts = getZonedDateTimeParts(timestampMs, timezone);
|
|
11594
|
-
return [
|
|
11595
|
-
parts.year,
|
|
11596
|
-
parts.month,
|
|
11597
|
-
parts.day
|
|
11598
|
-
].map((value, index) => index === 0 ? String(value).padStart(4, "0") : String(value).padStart(2, "0")).join("-");
|
|
11599
|
-
}
|
|
11600
|
-
function isValidTimeZone$1(timezone) {
|
|
11601
|
-
try {
|
|
11602
|
-
new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format();
|
|
11603
|
-
return true;
|
|
11604
|
-
} catch {
|
|
11605
|
-
return false;
|
|
11606
|
-
}
|
|
11607
|
-
}
|
|
11608
|
-
function plural(value, unit) {
|
|
11609
|
-
return value === 1 ? unit : `${unit}s`;
|
|
11610
|
-
}
|
|
11611
|
-
function formatWeekdays(weekdays) {
|
|
11612
|
-
const labels = weekdays.map((weekday) => `${weekday[0].toUpperCase()}${weekday.slice(1)}`);
|
|
11613
|
-
if (labels.length < 2) return labels[0] ?? "";
|
|
11614
|
-
return `${labels.slice(0, -1).join(", ")} and ${labels.at(-1)}`;
|
|
11615
|
-
}
|
|
11616
|
-
function recurringDescription(schedule, timezone) {
|
|
11617
|
-
const interval = schedule.interval ?? 1;
|
|
11618
|
-
const cadenceUnit = {
|
|
11619
|
-
daily: "day",
|
|
11620
|
-
weekly: "week",
|
|
11621
|
-
monthly: "month",
|
|
11622
|
-
yearly: "year"
|
|
11623
|
-
}[schedule.frequency];
|
|
11624
|
-
const cadence = interval === 1 ? cadenceUnit : `${interval} ${cadenceUnit}s`;
|
|
11625
|
-
let detail = "";
|
|
11626
|
-
if (schedule.frequency === "weekly") detail = ` on ${formatWeekdays(schedule.weekdays)}`;
|
|
11627
|
-
else if (schedule.frequency === "monthly") detail = ` on day ${schedule.dayOfMonth}`;
|
|
11628
|
-
else if (schedule.frequency === "yearly") detail = ` on ${String(schedule.month).padStart(2, "0")}-${String(schedule.dayOfMonth).padStart(2, "0")}`;
|
|
11629
|
-
return `Every ${cadence}${detail} at ${schedule.time} (${timezone})`;
|
|
11630
|
-
}
|
|
11631
|
-
var ScheduleIntentError = class extends Error {};
|
|
11632
|
-
/** Materialize model-interpreted schedule intent against the trusted server clock. */
|
|
11633
|
-
function compileScheduleIntent(args) {
|
|
11634
|
-
const timezone = args.intent.timezone ?? args.defaultTimezone;
|
|
11635
|
-
if (!isValidTimeZone$1(timezone)) throw new ScheduleIntentError("timezone must be a valid IANA time zone.");
|
|
11636
|
-
if (args.intent.kind === "one_off") {
|
|
11637
|
-
if (args.intent.timing.type === "after") return {
|
|
11638
|
-
nextRunAtMs: args.nowMs + args.intent.timing.value * DURATION_MS[args.intent.timing.unit],
|
|
11639
|
-
schedule: {
|
|
11640
|
-
description: `In ${args.intent.timing.value} ${plural(args.intent.timing.value, args.intent.timing.unit)}`,
|
|
11641
|
-
kind: "one_off",
|
|
11642
|
-
timezone
|
|
11643
|
-
}
|
|
11644
|
-
};
|
|
11645
|
-
const nextRunAtMs = resolveLocalScheduleAtMs({
|
|
11646
|
-
date: args.intent.timing.date,
|
|
11647
|
-
time: parseLocalTime(args.intent.timing.time),
|
|
11648
|
-
timezone
|
|
11649
|
-
});
|
|
11650
|
-
if (nextRunAtMs === void 0) throw new ScheduleIntentError("The requested local date or time does not exist in that timezone.");
|
|
11651
|
-
if (nextRunAtMs <= args.nowMs) throw new ScheduleIntentError("The requested one-off schedule must be in the future.");
|
|
11652
|
-
return {
|
|
11653
|
-
nextRunAtMs,
|
|
11654
|
-
schedule: {
|
|
11655
|
-
description: `Once on ${args.intent.timing.date} at ${args.intent.timing.time} (${timezone})`,
|
|
11656
|
-
kind: "one_off",
|
|
11657
|
-
timezone
|
|
11658
|
-
}
|
|
11659
|
-
};
|
|
11660
|
-
}
|
|
11661
|
-
const recurrence = {
|
|
11662
|
-
frequency: args.intent.frequency,
|
|
11663
|
-
interval: args.intent.interval ?? 1,
|
|
11664
|
-
startDate: args.intent.startDate ?? localDateAt(args.nowMs, timezone),
|
|
11665
|
-
time: parseLocalTime(args.intent.time),
|
|
11666
|
-
...args.intent.frequency === "weekly" ? { weekdays: [...new Set(args.intent.weekdays.map((weekday) => WEEKDAY_INDEX[weekday]))].sort((left, right) => left - right) } : void 0,
|
|
11667
|
-
...args.intent.frequency === "monthly" || args.intent.frequency === "yearly" ? { dayOfMonth: args.intent.dayOfMonth ?? void 0 } : void 0,
|
|
11668
|
-
...args.intent.frequency === "yearly" ? { month: args.intent.month ?? void 0 } : void 0
|
|
11669
|
-
};
|
|
11670
|
-
const searchRecurrence = args.intent.startDate ? recurrence : {
|
|
11671
|
-
...recurrence,
|
|
11672
|
-
interval: 1
|
|
11673
|
-
};
|
|
11674
|
-
const nextRunAtMs = getFirstRunAtMs({
|
|
11675
|
-
afterMs: args.nowMs,
|
|
11676
|
-
recurrence: searchRecurrence,
|
|
11677
|
-
timezone
|
|
11678
|
-
});
|
|
11679
|
-
if (nextRunAtMs === void 0) throw new ScheduleIntentError("The recurring schedule has no valid future occurrence.");
|
|
11680
|
-
const materializedRecurrence = args.intent.startDate ? recurrence : {
|
|
11681
|
-
...recurrence,
|
|
11682
|
-
startDate: localDateAt(nextRunAtMs, timezone)
|
|
11683
|
-
};
|
|
11684
|
-
return {
|
|
11685
|
-
nextRunAtMs,
|
|
11686
|
-
schedule: {
|
|
11687
|
-
description: recurringDescription(args.intent, timezone),
|
|
11688
|
-
kind: "recurring",
|
|
11689
|
-
recurrence: materializedRecurrence,
|
|
11690
|
-
timezone
|
|
11691
|
-
}
|
|
11692
|
-
};
|
|
11693
|
-
}
|
|
11694
11140
|
//#endregion
|
|
11695
11141
|
//#region src/chat/scheduled-automations/telemetry.ts
|
|
11696
11142
|
/** Safe structured telemetry attributes for scheduled-automation lifecycle events. */
|
|
@@ -11765,6 +11211,7 @@ const compactTaskResultSchema = z.object({
|
|
|
11765
11211
|
status: z.enum([
|
|
11766
11212
|
"active",
|
|
11767
11213
|
"blocked",
|
|
11214
|
+
"paused",
|
|
11768
11215
|
"completed",
|
|
11769
11216
|
"deleted"
|
|
11770
11217
|
]),
|
|
@@ -12082,17 +11529,18 @@ function createSlackScheduleDeleteAutomationTool(context) {
|
|
|
12082
11529
|
inputSchema: z.object({ automationId: z.string().min(1).describe("ID of the task to delete. Must be from this active Slack conversation.") }),
|
|
12083
11530
|
outputSchema: scheduleAutomationToolResultSchema.extend({ removedCards: z.array(removedCardSchema) }),
|
|
12084
11531
|
execute: async ({ automationId }) => {
|
|
11532
|
+
const lookup = await getWritableTask({
|
|
11533
|
+
context,
|
|
11534
|
+
taskId: automationId
|
|
11535
|
+
});
|
|
12085
11536
|
const next = {
|
|
12086
|
-
...
|
|
12087
|
-
context,
|
|
12088
|
-
taskId: automationId
|
|
12089
|
-
}),
|
|
11537
|
+
...lookup,
|
|
12090
11538
|
updatedAtMs: Date.now(),
|
|
12091
11539
|
status: "deleted",
|
|
12092
11540
|
nextRunAtMs: void 0,
|
|
12093
11541
|
runNowAtMs: void 0
|
|
12094
11542
|
};
|
|
12095
|
-
await saveScheduledAutomation(getDb(), next);
|
|
11543
|
+
await saveScheduledAutomation(getDb(), next, automationRevision(lookup));
|
|
12096
11544
|
await createPluginAnnotations({
|
|
12097
11545
|
conversationId: context.conversationId,
|
|
12098
11546
|
db: getDb(),
|
|
@@ -12226,12 +11674,46 @@ function createSlackScheduleRunAutomationNowTool(context) {
|
|
|
12226
11674
|
updatedAtMs: nowMs,
|
|
12227
11675
|
runNowAtMs: nowMs
|
|
12228
11676
|
};
|
|
12229
|
-
await saveScheduledAutomation(getDb(), next);
|
|
11677
|
+
await saveScheduledAutomation(getDb(), next, automationRevision(lookup));
|
|
12230
11678
|
return { automation: compactTask(next, context.actor?.userId) };
|
|
12231
11679
|
}
|
|
12232
11680
|
});
|
|
12233
11681
|
}
|
|
12234
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
|
|
12235
11717
|
//#region src/chat/scheduled-automations/tools/update.ts
|
|
12236
11718
|
/** Create a tool that edits a scheduled automation, including moving it here. */
|
|
12237
11719
|
function createSlackScheduleUpdateAutomationTool(context) {
|
|
@@ -12247,7 +11729,8 @@ function createSlackScheduleUpdateAutomationTool(context) {
|
|
|
12247
11729
|
executionMode: "sequential",
|
|
12248
11730
|
inputSchema: z.object({
|
|
12249
11731
|
automationId: z.string().min(1).describe("Scheduled automation ID returned by slackScheduleListAutomations."),
|
|
12250
|
-
|
|
11732
|
+
title: automationTitleSchema.optional(),
|
|
11733
|
+
instruction: automationInstructionSchema.optional(),
|
|
12251
11734
|
schedule: scheduleIntentSchema.describe("Complete replacement schedule. Omit to keep it unchanged.").nullable().optional(),
|
|
12252
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(),
|
|
12253
11736
|
status: z.enum(["active", "blocked"]).describe("Set active to resume a blocked task, or blocked to stop dispatch.").optional(),
|
|
@@ -12255,7 +11738,7 @@ function createSlackScheduleUpdateAutomationTool(context) {
|
|
|
12255
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()
|
|
12256
11739
|
}).strict(),
|
|
12257
11740
|
outputSchema: scheduleAutomationToolResultSchema,
|
|
12258
|
-
execute: async (input
|
|
11741
|
+
execute: async (input) => {
|
|
12259
11742
|
const activeDestination = requireActiveChannel(context);
|
|
12260
11743
|
const actor = requireActor(context, activeDestination);
|
|
12261
11744
|
const db = getDb();
|
|
@@ -12277,52 +11760,24 @@ function createSlackScheduleUpdateAutomationTool(context) {
|
|
|
12277
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.");
|
|
12278
11761
|
}
|
|
12279
11762
|
const nowMs = context.now?.() ?? Date.now();
|
|
12280
|
-
let compiled;
|
|
12281
|
-
if (input.schedule) try {
|
|
12282
|
-
compiled = compileScheduleIntent({
|
|
12283
|
-
defaultTimezone: lookup.schedule.timezone || getDefaultScheduleTimezone(),
|
|
12284
|
-
intent: input.schedule,
|
|
12285
|
-
nowMs
|
|
12286
|
-
});
|
|
12287
|
-
} catch (error) {
|
|
12288
|
-
if (error instanceof ScheduleIntentError) throwToolInputError(error.message);
|
|
12289
|
-
throw error;
|
|
12290
|
-
}
|
|
12291
|
-
const nextRunAtMs = compiled?.nextRunAtMs ?? lookup.nextRunAtMs;
|
|
12292
11763
|
const status = normalizeStatus(input.status);
|
|
12293
11764
|
if (input.status && !status) throwToolInputError("status must be active or blocked.");
|
|
12294
|
-
|
|
12295
|
-
const
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
|
|
12302
|
-
|
|
12303
|
-
|
|
12304
|
-
destination
|
|
12305
|
-
|
|
12306
|
-
|
|
12307
|
-
runNowAtMs: nextStatus === "active" && !compiled ? lookup.runNowAtMs : void 0,
|
|
12308
|
-
status: nextStatus,
|
|
12309
|
-
statusReason: nextStatus === "blocked" ? lookup.statusReason : void 0,
|
|
12310
|
-
schedule: compiled?.schedule ?? lookup.schedule,
|
|
12311
|
-
outcomes: input.outcomes === void 0 ? changingDestination ? moveTaskOutcomes(lookup.outcomes, lookup.destination, nextDestination) : lookup.outcomes : await resolveTaskOutcomes(input.outcomes, nextDestination, lookup.createdBy.slackUserId),
|
|
12312
|
-
task: { text: nextInstruction }
|
|
12313
|
-
};
|
|
12314
|
-
if (instructionChanged) {
|
|
12315
|
-
const title = await generateShortTitle({
|
|
12316
|
-
signal: options.signal,
|
|
12317
|
-
completeText,
|
|
12318
|
-
kind: "task",
|
|
12319
|
-
sourceText: nextInstruction
|
|
12320
|
-
});
|
|
12321
|
-
if (title) next.title = title;
|
|
12322
|
-
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);
|
|
12323
11778
|
}
|
|
12324
|
-
if (!changingDestination && !instructionChanged && !
|
|
12325
|
-
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));
|
|
12326
11781
|
if (changingDestination) logInfo("scheduled_automation.move.completed", scheduledAutomationAttributes(committed));
|
|
12327
11782
|
return scheduleAutomationToolResult(context.conversationId, committed, actor.slackUserId);
|
|
12328
11783
|
}
|
|
@@ -21935,7 +21390,7 @@ function createAgentRunner(execute, options) {
|
|
|
21935
21390
|
} } : void 0
|
|
21936
21391
|
};
|
|
21937
21392
|
if (run.instruction.storedAttachments?.length) {
|
|
21938
|
-
const { loadInputImages } = await import("./images-
|
|
21393
|
+
const { loadInputImages } = await import("./images-BaL19wFC.js").then((n) => n.n);
|
|
21939
21394
|
const storage = nextRun.environment?.attachmentStorage;
|
|
21940
21395
|
if (!storage) throw new Error("Attachment storage is unavailable.");
|
|
21941
21396
|
nextRun.instruction = {
|
|
@@ -21986,4 +21441,4 @@ async function runAgentWithTimeout(agentRunner, run, timeoutMs) {
|
|
|
21986
21441
|
}
|
|
21987
21442
|
}
|
|
21988
21443
|
//#endregion
|
|
21989
|
-
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 };
|