@sentry/junior 0.205.0 → 0.207.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-D4nu8MQV.js → agent-CmQlX68D.js} +603 -394
- package/dist/{agent-hooks-DXHpTJ4m.js → agent-hooks-BBX9p6aG.js} +1171 -100
- package/dist/{agent-runner-C04colea.js → agent-runner-CQD4HKK2.js} +9 -9
- package/dist/api/schema.d.ts +116 -39
- package/dist/api/schema.js +3 -3
- package/dist/api.d.ts +1 -1
- package/dist/api.js +182 -649
- package/dist/{app-Cb776lL8.d.ts → app-DfNrn_gS.d.ts} +9 -2
- package/dist/app.d.ts +1 -1
- package/dist/app.js +205 -233
- package/dist/{auth-pause-state-C2P3guS1.js → auth-pause-state-uv8on6cT.js} +7 -7
- package/dist/briefs-BXEmjD9D.js +398 -0
- package/dist/{catalog-runtime-CXw3asJD.js → catalog-runtime-1BZEOR8J.js} +1 -1
- package/dist/{checkpoint-CrViJ8Ps.js → checkpoint-B-JO5F3c.js} +9 -8
- package/dist/cli/chat.js +14 -12
- package/dist/cli/check.js +3 -3
- package/dist/cli/init.js +1 -0
- package/dist/cli/main.js +5 -1
- package/dist/cli/plugins.js +3 -3
- package/dist/cli/run.d.ts +2 -1
- package/dist/cli/run.js +2 -1
- package/dist/cli/snapshot-create.js +3 -3
- package/dist/cli/upgrade.d.ts +1 -1
- package/dist/cli/upgrade.js +3 -3
- package/dist/{config-BOtJQ7Pc.js → client-Bp297GA0.js} +6 -369
- package/dist/config-G2gv97R1.js +386 -0
- package/dist/{context-B45_WqVX.js → context-Bplcgypl.js} +13 -13
- package/dist/{schema-Rn8-XEaN.js → conversation-AA4R1VUn.js} +580 -558
- package/dist/{conversation-privacy-BZRA6Pes.js → conversation-privacy-BUfel19D.js} +1 -1
- package/dist/{credential-sync-Bep34IB_.js → credential-sync-D04NWqMR.js} +21 -20
- package/dist/{db-BeyXKJOI.d.ts → db-D74z7BZH.d.ts} +1 -1
- package/dist/{execution-stats-B6lgWxCK.js → execution-stats-Ds4bSvam.js} +77 -77
- package/dist/{executor-BlUhFL7_.js → executor-BZu1Ta06.js} +41 -13
- package/dist/instrumentation.js +2 -2
- package/dist/{job-queue-DLMqIK74.js → job-queue-rnAPfUhP.js} +1 -1
- package/dist/{logging-BMJzdDlP.js → logging-Dx0S5k2z.js} +1 -1
- package/dist/nitro.d.ts +1 -1
- package/dist/nitro.js +3 -3
- package/dist/{oauth-callback-server-WwG5_Zgm.js → oauth-callback-server-xGpwURIj.js} +2 -2
- package/dist/{pending-auth-CUbXq06G.js → pending-auth-DIZ4iZjS.js} +2 -2
- package/dist/{profile-T0tXE4zu.js → profile-d-fKypxo.js} +4 -3
- package/dist/prompt-x-0ZtTyb.js +552 -0
- package/dist/{registry-BA0__8ai.js → registry-CWKdLCTG.js} +2 -2
- package/dist/{resolve-bvD1ORdv.js → resolve-BhRB1s2A.js} +2 -2
- package/dist/{runner-BXYxfIUL.js → runner-DNml3owD.js} +10 -10
- package/dist/{sandbox-egress-signals-ngEPjSEW.js → sandbox-egress-signals-DUIl3-gW.js} +1 -1
- package/dist/schema-CwTeb-BF.js +559 -0
- package/dist/{skills-DJOlYdBs.js → skills-D9WesdJJ.js} +2 -2
- package/dist/{spawn-h0R1bTEo.js → spawn-Eh_3-s0M.js} +2 -2
- package/dist/{stats-3YZn-Swi.js → stats-CFlUGqVA.js} +292 -268
- package/dist/{store-C5qxF2ac.d.ts → store-Det8MQ9g.d.ts} +1 -1
- package/dist/{task-queue-CoXwd2eq.js → task-queue-D8I1_5wP.js} +3 -3
- package/dist/{task-runner-B2_mJZj0.js → task-runner-DO79gdho.js} +18 -18
- package/dist/{thread-state-CqZ1m-es.js → thread-state-DD6BA4xz.js} +5 -5
- package/dist/{turn-execution-fI6PZ0iI.js → turn-execution-BSIHxmQJ.js} +3 -3
- package/dist/{turn-lifecycle-CcnwrOcp.js → turn-lifecycle-DSBK4Ars.js} +1 -1
- package/dist/{usage-B5SfMb3H.js → usage-WRXkNOHp.js} +4 -125
- package/dist/{validation-DfWcvhuS.js → validation-BbHZ4r4S.js} +1 -1
- package/dist/{version-oD-oPrbO.js → version-C71UcRWw.js} +1 -1
- package/dist/version.js +1 -1
- package/dist/web-authorization-C73gJV7l.js +38 -0
- package/dist/{web-input-BOuq3oKK.js → web-input-Ce1FcNIM.js} +8 -8
- package/dist/{work-B91LQKHr.js → work-BEzDU_2x.js} +9 -9
- package/dist/{worker-BcRRTe6k.js → worker-MK2ubOv-.js} +5 -4
- package/migrations/0040_conversation_briefs.sql +17 -0
- package/migrations/0041_automation_outcomes_backfill.sql +27 -0
- package/migrations/meta/0040_snapshot.json +3516 -0
- package/migrations/meta/0041_snapshot.json +3516 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +4 -4
- package/dist/history-CqTuSAdI.js +0 -441
- package/dist/usage-schema-CKaDkH_j.js +0 -20
- package/dist/web-authorization-Crbzlmzo.js +0 -76
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { C as withSpan, F as isRecord$2, P as normalizeIdentityEmail, S as withLogContext, _ as setSpanAttributes, a as extractGenAiUsageSummary, b as summarizeMessageText, c as getTracePropagationHeaders, d as logInfo, f as logWarn, h as serializeGenAiAttribute, i as extractGenAiUsageAttributes, l as logError, p as normalizeGenAiFinishReason, s as getLogContextAttributesForSpan, u as logException, v as setSpanStatus, y as setTags } from "./logging-
|
|
1
|
+
import { C as resolveGatewayCredential, S as getGatewayApiKey, _ as findProviderError, b as isProviderRetryError, c as COMPACTION_SUMMARY_PREFIX, d as isCompactionSummaryText, f as renderCurrentInstruction, g as createProviderError, h as ProviderError, i as completeText, l as MODEL_HANDOFF_SUMMARY_PREFIX, m as escapeXml, n as GEN_AI_SERVER_ADDRESS, o as resolveGatewayModel, p as unwrapCurrentInstruction, r as completeObject, s as ACTIVE_TURN_COMPACTION_SUMMARY_PREFIX, t as GEN_AI_PROVIDER_NAME, u as hasCompactedConversationContext, v as getProviderErrorAttributes, x as MISSING_GATEWAY_CREDENTIALS_ERROR } from "./client-Bp297GA0.js";
|
|
2
|
+
import { C as withSpan, F as isRecord$2, P as normalizeIdentityEmail, S as withLogContext, _ as setSpanAttributes, a as extractGenAiUsageSummary, b as summarizeMessageText, c as getTracePropagationHeaders, d as logInfo, f as logWarn, h as serializeGenAiAttribute, i as extractGenAiUsageAttributes, l as logError, p as normalizeGenAiFinishReason, s as getLogContextAttributesForSpan, u as logException, v as setSpanStatus, y as setTags } from "./logging-Dx0S5k2z.js";
|
|
3
3
|
import { _ as withActiveSpan, m as startInactiveSpan } from "./sentry-CD3SdJl-.js";
|
|
4
|
-
import { a as toCanonicalInputMessage, c as toGenAiPayloadMetadata, i as runWithConversationPrivacy, l as toGenAiPayloadTraceAttributes, o as toCanonicalOutputMessage, r as resolveConversationPrivacy, s as toGenAiMessagesTraceAttributes, t as canExposeConversationPayload } from "./conversation-privacy-
|
|
4
|
+
import { a as toCanonicalInputMessage, c as toGenAiPayloadMetadata, i as runWithConversationPrivacy, l as toGenAiPayloadTraceAttributes, o as toCanonicalOutputMessage, r as resolveConversationPrivacy, s as toGenAiMessagesTraceAttributes, t as canExposeConversationPayload } from "./conversation-privacy-BUfel19D.js";
|
|
5
|
+
import { h as normalizeSlackEmojiName, n as botConfig } from "./config-G2gv97R1.js";
|
|
5
6
|
import { a as modelIdForProfile, c as TURN_REASONING_LEVELS, i as formatModelProfiles, o as modelProfileSchema, r as formatModelProfile, s as profileConfig, t as ModelProfileNotConfiguredError } from "./model-profile-CZmVoGdX.js";
|
|
6
|
-
import { _ as parseSlackMessageTs, g as parseSlackUserId, h as parseSlackTeamId, i as credentialUserSubjectId, l as isUserActor, m as parseSlackChannelReferenceId, n as credentialContextSchema, p as parseSlackChannelId, t as credentialContextForActor } from "./context-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { _ as parseSlackMessageTs, g as parseSlackUserId, h as parseSlackTeamId, i as credentialUserSubjectId, l as isUserActor, m as parseSlackChannelReferenceId, n as credentialContextSchema, p as parseSlackChannelId, t as credentialContextForActor } from "./context-Bplcgypl.js";
|
|
8
|
+
import { A as tryWorkspaceRepoCheckoutPath, B as downloadPrivateSlackFile, C as invalidateReadySnapshot, D as SANDBOX_RUNTIME, E as setWorkspaceSnapshotBuild, Ft as getConversationStore, It as getDb, J as withSlackRetries, K as normalizeSlackConversationId, Lt as getSqlExecutor, M as resolveChannelCapabilities, Nt as getConversationEventStore, O as deleteWorkspaceSnapshotBuilders, Pt as getConversationMessageSearchStore, Q as readActorIdentity, S as WorkspaceValidationError, T as setWorkspaceSnapshot, U as getSlackClient, V as getFilePermalink, W as isConversationScopedChannel, Xt as parseDestination, _ as deleteWorkspace, at as STOP_WATCHING_TOOL_NAME, b as listWorkspaces, bt as createPluginLogger, c as getPluginTools, dt as findMatchingWatches, ft as listWatches, g as createWorkspace, ht as getDashboardTaskLink, it as RESOURCE_WATCH_TOOL_SOURCE, j as workspaceRepoCheckoutPath, k as getWorkspaceSnapshotBuilder, l as getPluginUserPromptContributions, lt as createWatch, mt as getDashboardConversationLink, nt as RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS, ot as cancelSubscriptions, pt as JUNIOR_THREAD_STATE_TTL_MS, r as createPluginHookRunner, s as getPluginSystemPromptContributions, st as cancelWatch, tt as getSlackToolContext, u as getPlugins, ut as deliverWatch, v as getWorkspace, w as loadSnapshotsForProfile, x as updateWorkspace, y as getWorkspaceByName, z as SlackActionError } from "./agent-hooks-BBX9p6aG.js";
|
|
9
|
+
import { n as juniorDestinations } from "./destinations-BeBiJeEM.js";
|
|
10
|
+
import { D as juniorArtifacts, S as juniorAttachments, T as juniorIdentities, a as juniorSchedulerRuns, d as AGENT_INVOCATION_MAILBOX_STATUSES, g as juniorConversationBriefs, w as juniorConversations, y as juniorConversationAnnotations } from "./executor-BZu1Ta06.js";
|
|
11
|
+
import { A as briefLinkSchema, F as KNOWN_CONVERSATION_EVENT_TYPES, G as instructionProvenanceFor, K as sameActorIdentity, M as conversationBriefSchema, U as contextProvenance, W as instructionActors, j as briefOutcomeStatusSchema } from "./conversation-AA4R1VUn.js";
|
|
10
12
|
import { a as listReferenceFiles, c as soulPathCandidates, l as worldPathCandidates } from "./package-discovery-7a9YqLhE.js";
|
|
11
|
-
import { a as resolvePluginCommandEnv, c as getOAuthProviderErrorAttributes, i as resolveAuthTokenPlaceholder, o as OAuthProviderError, s as fetchWithBoundedOAuthErrorBodies, u as CredentialUnavailableError } from "./registry-
|
|
12
|
-
import { n as pluginCatalogRuntime } from "./catalog-runtime-
|
|
13
|
-
import { a as parseSkillInvocation, n as findSkillByName, r as loadSkillsByName, t as discoverSkills } from "./skills-
|
|
14
|
-
import { A as normalizeToLf$1, At as retainRuntimeTurnContext, B as openConversationProjection, C as DEFAULT_SEARCH_COMMAND_TIMEOUT_MS, Ct as hasRuntimeTurnContext, D as getRipgrepSearchLocation, Dt as isToolResultError, E as collectFiles, Et as isContinuablePiBoundary, G as recordAuthorizationRequested, H as recordAttachmentsDelivered, I as loadConnectedMcpProviders, K as recordGuardianActionReviewed, L as loadConversationProjection, M as resolveWorkspacePath, Mt as trimTrailingAssistantMessages, N as truncateText$1, Nt as TURN_CONTEXT_TAG, O as isMissingPathError, Ot as isToolResultMessage, S as resolveRepositoryInstructionsForDirectories, St as getUserMessageInstructionText, T as RIPGREP_EXCLUDED_GLOBS, Tt as isAssistantMessage, V as recordAgentsInstructionsUpdated, X as recordTurnRoute, Y as recordToolExecutionStarted, _ as AGENTS_REPLACEMENT_NOTICE, _t as historyItemFromPiMessage, b as listRepositoryDirectories, bt as getPiMessageRole, f as TurnSliceLimitExceededError, g as AGENTS_REMOVAL_NOTICE, gt as getAgentContextInputLimitTokens, h as isTurnExecutionLimitExceededError, ht as getAgentContextCompactionTriggerTokens, j as positiveInteger$1, jt as stripRuntimeTurnContext, k as missingPathSearchResult, kt as normalizeToolNameFromResult, mt as estimateTextTokens, n as loadTurnCheckpoint, p as assertTurnToolCallLimit, q as recordMcpProviderConnected, r as saveTurnCheckpoint, t as continuableMessages, v as buildAgentsInstructionsMessage, vt as piMessageFromHistoryItem, w as MAX_TEXT_CHARS, x as renderAgentsInstructions, xt as getTerminalAssistantMessages, y as findVisibleAgentsInstructions, yt as extractAssistantText, z as loadTurnRoute } from "./checkpoint-
|
|
15
|
-
import { a as hasAgentTurnUsage, c as generateShortTitle, l as resolveTaskTitle, o as ToolInputError, t as addAgentTurnUsage } from "./usage-
|
|
16
|
-
import { A as sleep$1, D as fenceLock, E as acquireActiveLock, F as sandboxSkillDir, I as sandboxSkillFile, L as sandboxSkillPathResolution, M as SANDBOX_REPOS_ROOT, N as SANDBOX_SKILLS_ROOT, P as SANDBOX_WORKSPACE_ROOT, _ 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_DATA_ROOT, k as withLock, 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-
|
|
17
|
-
import { $ as workspaceSnapshotFinishedEvent, B as
|
|
18
|
-
import { _ as isTurnInputCommitLostError, c as sanitizeAssistantText, f as CooperativeTurnYieldError, l as NO_REPLY_MARKER, n as canReusePendingAuthLink, o as decideReply, p as TurnInputCommitLostError, s as getAssistantReplyText, t as abandonReplacedPendingAuth, u as isNoReplyMarker } from "./pending-auth-
|
|
19
|
-
import { C as toolInvocationDestination, S as surfaceFromRun, _ as uploadFilesToConversation, b as assertRunConsistency, h as postSlackMessage, i as formatProviderLabel, m as getSlackMessagePermalink, o as resolveBaseUrl, p as addReactionToMessage, r as deliverOAuthAuthorization, s as startOAuthFlow, v as RetryableDeliveryError, x as isAgentRunFeatureDisabled, y as actorFromRun } from "./agent-runner-
|
|
20
|
-
import {
|
|
13
|
+
import { a as resolvePluginCommandEnv, c as getOAuthProviderErrorAttributes, i as resolveAuthTokenPlaceholder, o as OAuthProviderError, s as fetchWithBoundedOAuthErrorBodies, u as CredentialUnavailableError } from "./registry-CWKdLCTG.js";
|
|
14
|
+
import { n as pluginCatalogRuntime } from "./catalog-runtime-1BZEOR8J.js";
|
|
15
|
+
import { a as parseSkillInvocation, n as findSkillByName, r as loadSkillsByName, t as discoverSkills } from "./skills-D9WesdJJ.js";
|
|
16
|
+
import { A as normalizeToLf$1, At as retainRuntimeTurnContext, B as openConversationProjection, C as DEFAULT_SEARCH_COMMAND_TIMEOUT_MS, Ct as hasRuntimeTurnContext, D as getRipgrepSearchLocation, Dt as isToolResultError, E as collectFiles, Et as isContinuablePiBoundary, G as recordAuthorizationRequested, H as recordAttachmentsDelivered, I as loadConnectedMcpProviders, K as recordGuardianActionReviewed, L as loadConversationProjection, M as resolveWorkspacePath, Mt as trimTrailingAssistantMessages, N as truncateText$1, Nt as TURN_CONTEXT_TAG, O as isMissingPathError, Ot as isToolResultMessage, S as resolveRepositoryInstructionsForDirectories, St as getUserMessageInstructionText, T as RIPGREP_EXCLUDED_GLOBS, Tt as isAssistantMessage, V as recordAgentsInstructionsUpdated, X as recordTurnRoute, Y as recordToolExecutionStarted, _ as AGENTS_REPLACEMENT_NOTICE, _t as historyItemFromPiMessage, b as listRepositoryDirectories, bt as getPiMessageRole, f as TurnSliceLimitExceededError, g as AGENTS_REMOVAL_NOTICE, gt as getAgentContextInputLimitTokens, h as isTurnExecutionLimitExceededError, ht as getAgentContextCompactionTriggerTokens, j as positiveInteger$1, jt as stripRuntimeTurnContext, k as missingPathSearchResult, kt as normalizeToolNameFromResult, mt as estimateTextTokens, n as loadTurnCheckpoint, p as assertTurnToolCallLimit, q as recordMcpProviderConnected, r as saveTurnCheckpoint, t as continuableMessages, v as buildAgentsInstructionsMessage, vt as piMessageFromHistoryItem, w as MAX_TEXT_CHARS, x as renderAgentsInstructions, xt as getTerminalAssistantMessages, y as findVisibleAgentsInstructions, yt as extractAssistantText, z as loadTurnRoute } from "./checkpoint-B-JO5F3c.js";
|
|
17
|
+
import { a as hasAgentTurnUsage, c as generateShortTitle, l as resolveTaskTitle, o as ToolInputError, t as addAgentTurnUsage } from "./usage-WRXkNOHp.js";
|
|
18
|
+
import { A as sleep$1, D as fenceLock, E as acquireActiveLock, F as sandboxSkillDir, I as sandboxSkillFile, L as sandboxSkillPathResolution, M as SANDBOX_REPOS_ROOT, N as SANDBOX_SKILLS_ROOT, P as SANDBOX_WORKSPACE_ROOT, _ 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_DATA_ROOT, k as withLock, 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-d-fKypxo.js";
|
|
19
|
+
import { $ as workspaceSnapshotFinishedEvent, B as createEventAutomation, C as SCHEDULED_AUTOMATION_SYSTEM_ACTOR, D as eventAutomationMatchesDestination, E as eventAutomationListToolResultSchema, F as effectiveTaskOutcomes, Ht as injectContinuationToolName, I as moveTaskOutcomes, L as resolveTaskOutcomes, M as requireEventAutomationSlackContext, N as requireSupportedEventAutomationTrigger, O as eventAutomationToolResult, P as writableEventAutomation, Q as WORKSPACE_SNAPSHOT_READY_EVENT, R as taskOutcomeInputSchema, T as compactEventAutomation, Ut as juniorToolOutputSchema, V as deleteEventAutomation, W as getEventAutomation, Wt as makeStructuredToolOutput, X as getEventCatalog, Y as saveEventAutomation, Z as WORKSPACE_SNAPSHOT_FAILED_EVENT, at as normalizeCatalogEventIdentifier, c as parseSlackThreadId, ct as registeredEventTypeSchema, et as workspaceSnapshotWatch, f as createScheduledAutomation, g as listScheduledAutomationsForTeam, i as readLiveAttachment, it as hasPluginEventCatalogEntries, j as registeredEventAutomationTriggerSchema, k as eventAutomationToolResultSchema, lt as registeredResourceTypeSchema, nt as eventNamespaceSchema, ot as pluginSupportsEvent, pt as appendAndEnqueueInboundMessage, q as listEventAutomationsForTeam, rt as hasEventCatalogEntries, s as storeAttachments, st as registeredEventMatchSchema, t as incrementStat, tt as eventGuidance, ut as requireSupportedEventMatch, v as readScheduledAutomation, y as saveScheduledAutomation } from "./stats-CFlUGqVA.js";
|
|
20
|
+
import { _ as isTurnInputCommitLostError, c as sanitizeAssistantText, f as CooperativeTurnYieldError, l as NO_REPLY_MARKER, n as canReusePendingAuthLink, o as decideReply, p as TurnInputCommitLostError, s as getAssistantReplyText, t as abandonReplacedPendingAuth, u as isNoReplyMarker } from "./pending-auth-DIZ4iZjS.js";
|
|
21
|
+
import { C as toolInvocationDestination, S as surfaceFromRun, _ as uploadFilesToConversation, b as assertRunConsistency, h as postSlackMessage, i as formatProviderLabel, m as getSlackMessagePermalink, o as resolveBaseUrl, p as addReactionToMessage, r as deliverOAuthAuthorization, s as startOAuthFlow, v as RetryableDeliveryError, x as isAgentRunFeatureDisabled, y as actorFromRun } from "./agent-runner-CQD4HKK2.js";
|
|
22
|
+
import { r as EVENT_AUTHOR_ID } from "./prompt-x-0ZtTyb.js";
|
|
23
|
+
import { a as createSandboxSession, c as postinstall, d as getSandboxResources, i as bindSandboxFileSystem, l as setSnapshotSpanAttributes, n as isMissingError, o as stopSession, r as resolve$1, s as dependencies } from "./resolve-BhRB1s2A.js";
|
|
21
24
|
import { t as getVercelConversationWorkQueue } from "./vercel-queue-DIkVcfK4.js";
|
|
22
|
-
import { a as getTurnRequestDeadline, n as sendNextWorkspaceSnapshotJob, r as sendWorkspaceSnapshotJob } from "./job-queue-
|
|
25
|
+
import { a as getTurnRequestDeadline, n as sendNextWorkspaceSnapshotJob, r as sendWorkspaceSnapshotJob } from "./job-queue-rnAPfUhP.js";
|
|
23
26
|
import { n as privateTraceResultAttributes } from "./private-trace-result-BLvBjy_e.js";
|
|
24
27
|
import { streamSimple } from "@earendil-works/pi-ai/compat";
|
|
25
28
|
import { createGatewayProvider } from "@ai-sdk/gateway";
|
|
@@ -29,8 +32,8 @@ import { isDeepStrictEqual } from "node:util";
|
|
|
29
32
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
30
33
|
import { z } from "zod";
|
|
31
34
|
import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
|
|
32
|
-
import { and, eq, inArray, isNull, or, sql } from "drizzle-orm";
|
|
33
|
-
import { EgressAuthRequired, EgressPolicyDenied, PluginToolInputError, actorSchema, destinationSchema, missingToolAnnotationKeys, pluginAuthorizationSchema, pluginCredentialHeaderTransformSchema, pluginCredentialResultSchema, pluginGrantSchema, pluginProviderAccountSchema, pluginStoredTokensSchema,
|
|
35
|
+
import { and, desc, eq, gt, gte, inArray, isNull, lt, ne, notExists, or, sql } from "drizzle-orm";
|
|
36
|
+
import { EgressAuthRequired, EgressPolicyDenied, PluginToolInputError, actorSchema, destinationSchema, eventSchema, missingToolAnnotationKeys, pluginAuthorizationSchema, pluginCredentialHeaderTransformSchema, pluginCredentialResultSchema, pluginGrantSchema, pluginProviderAccountSchema, pluginStoredTokensSchema, sourceSchema, stableEventMatchKey, watchResultSchema } from "@sentry/junior-plugin-api";
|
|
34
37
|
import { alias } from "drizzle-orm/pg-core";
|
|
35
38
|
import { Agent, estimateContextTokens } from "@earendil-works/pi-agent-core";
|
|
36
39
|
import fs from "node:fs/promises";
|
|
@@ -402,21 +405,22 @@ function buildTurnResult(input) {
|
|
|
402
405
|
"app.ai.generated_files": input.generatedFileCount
|
|
403
406
|
});
|
|
404
407
|
const usedPrimaryText = Boolean(rawPrimaryText);
|
|
408
|
+
const suppressedPrimaryText = Boolean(rawPrimaryText && !noReplyRequested && !primaryText);
|
|
405
409
|
let outcome;
|
|
406
410
|
if (isProviderError) outcome = "provider_error";
|
|
411
|
+
else if (suppressedPrimaryText) outcome = "execution_failure";
|
|
407
412
|
else if (primaryText || completedWithoutTerminalText) outcome = "success";
|
|
408
413
|
else outcome = "execution_failure";
|
|
409
|
-
const resolvedOutcome = Boolean(rawPrimaryText && !noReplyRequested && !primaryText) ? "execution_failure" : outcome;
|
|
410
414
|
if (shouldTrace) logInfo("agent.message.generated", {
|
|
411
415
|
"app.message.kind": "assistant_outbound",
|
|
412
416
|
"app.message.length": primaryText.length,
|
|
413
417
|
"app.message.output": summarizeMessageText(primaryText),
|
|
414
|
-
"app.ai.outcome":
|
|
418
|
+
"app.ai.outcome": outcome,
|
|
415
419
|
"app.ai.assistant_messages": assistantMessages.length,
|
|
416
420
|
...stopReason ? { "gen_ai.response.finish_reasons": [stopReason] } : void 0
|
|
417
421
|
});
|
|
418
422
|
const resolvedDiagnostics = {
|
|
419
|
-
outcome
|
|
423
|
+
outcome,
|
|
420
424
|
modelId,
|
|
421
425
|
assistantMessageCount: assistantMessages.length,
|
|
422
426
|
reasoningLevel: executionProfile.reasoningLevel,
|
|
@@ -428,7 +432,7 @@ function buildTurnResult(input) {
|
|
|
428
432
|
usage,
|
|
429
433
|
stopReason,
|
|
430
434
|
errorMessage,
|
|
431
|
-
providerError:
|
|
435
|
+
providerError: outcome === "provider_error" && errorMessage ? createProviderError(errorMessage, {
|
|
432
436
|
modelId,
|
|
433
437
|
retryable: lastAssistant !== void 0 && isAssistantMessage(lastAssistant) && isRetryableAssistantError(lastAssistant)
|
|
434
438
|
}) : void 0
|
|
@@ -1143,12 +1147,12 @@ const TOOL_POLICY_RULES = [
|
|
|
1143
1147
|
"- After changing files, name the changed paths and summarize the completed result in the final answer.",
|
|
1144
1148
|
"- If a sandbox-backed tool reports that sandbox execution is unavailable, treat that as a blocker for local file/shell inspection; do not pretend host files were inspected.",
|
|
1145
1149
|
"- For user-provided URLs, use `webFetch`; for discovery, use `webSearch` then fetch/read promising sources; for current time/date context, use `systemTime`.",
|
|
1146
|
-
"- When
|
|
1147
|
-
"- When a tool result includes a subscription, those events are already watched; do not call
|
|
1148
|
-
"- Use
|
|
1149
|
-
"- Event
|
|
1150
|
-
"- Event
|
|
1151
|
-
"- Scheduled
|
|
1150
|
+
"- When searchEventTypes is exposed, use it only when the user asks what events are supported or the required resource type or event name is unclear. It discovers options but does not watch a resource or create an automation. When explaining how results can be used, distinguish temporary current-thread watches from durable event automations.",
|
|
1151
|
+
"- When a tool result includes a subscription, those events are already watched; do not call watchEvents for them. When a tool result includes a subscribable resource with suggestedEvents, use watchEvents only for those remaining events that serve the current intent. If suggestedEvents is empty or omitted, do not invent a watch. Do not create scheduled polling tasks for events a watch can deliver. Write a concise intent summary, and tell the user when the temporary watch expires. Stop only the requested watch by id unless the user explicitly asks to stop every watch in the thread.",
|
|
1152
|
+
"- Use createEventAutomation only when the user explicitly asks for an event automation or durable whenever-this-happens-do-X automation. Ordinary watch, notify, and tell-me-when requests use watchEvents. When an event automation's resource and events are known, create it without redundant confirmation.",
|
|
1153
|
+
"- Event automations make the automation creator's connected credentials available by default when the requested work needs user-bound authorization. Do not ask for separate confirmation merely to use credentials needed for the requested work. On creation, omit credentialMode for the creator default and set system only when the creator explicitly requires it. For later changes, creator always means the automation's original createdBy actor, never the current requester. If the requester is not that creator, do not attempt to enable creator credential use or suggest that confirmation could authorize it.",
|
|
1154
|
+
"- Event automations list for the current destination, not one thread. Public automations can also be updated or deleted by automation id from another destination in the same workspace. When listing them, use createdBy to explain creator-only credential changes and warn when trigger.available is false; an unavailable automation remains stored but cannot receive events until its plugin event is enabled again.",
|
|
1155
|
+
"- Scheduled automations make the automation creator's connected credentials available by default when the requested work needs user-bound authorization. Do not ask for separate confirmation merely to use credentials needed for the requested work. On creation, omit credentialMode for the creator default and set system only when the creator explicitly requires it. For later changes, creator always means the automation's original createdBy actor, never the current requester. If the requester is not that creator, do not attempt to enable creator credential use or suggest that confirmation could authorize it.",
|
|
1152
1156
|
"- When another model profile fits the task better, call `handoff` before substantial work. Follow the profile list and selection rules in the tool description.",
|
|
1153
1157
|
"- Run `jr-rpc config get|set|unset|list` for provider defaults and `jr-rpc plugins list` for installed plugin introspection as standalone bash commands; do not chain them with `cd`, `&&`, pipes, or provider commands.",
|
|
1154
1158
|
"- If the first result is empty, stale, ambiguous, or incomplete, try a focused alternate query, path, command, or source before concluding the answer cannot be verified."
|
|
@@ -1264,14 +1268,14 @@ function formatSourceLines(source) {
|
|
|
1264
1268
|
...source.messageTs ? [`- source.message_ts: ${escapeXml(source.messageTs)}`] : [],
|
|
1265
1269
|
...source.threadTs ? [`- source.thread_ts: ${escapeXml(source.threadTs)}`] : []
|
|
1266
1270
|
];
|
|
1267
|
-
case "
|
|
1268
|
-
"- source.kind:
|
|
1271
|
+
case "event": return [
|
|
1272
|
+
"- source.kind: event",
|
|
1269
1273
|
`- source.namespace: ${escapeXml(source.namespace)}`,
|
|
1270
1274
|
`- source.identifier: ${escapeXml(source.identifier)}`,
|
|
1271
1275
|
`- source.event_type: ${escapeXml(source.eventType)}`
|
|
1272
1276
|
];
|
|
1273
|
-
case "
|
|
1274
|
-
case "
|
|
1277
|
+
case "scheduled_automation":
|
|
1278
|
+
case "event_automation":
|
|
1275
1279
|
case "plugin_dispatch":
|
|
1276
1280
|
case "agent_invocation": return [`- source.kind: ${source.kind}`];
|
|
1277
1281
|
}
|
|
@@ -2487,8 +2491,9 @@ async function prepareWorkspaceRepositories(params) {
|
|
|
2487
2491
|
const { sandbox, workspace, signal } = params;
|
|
2488
2492
|
signal?.throwIfAborted();
|
|
2489
2493
|
await params.applyNetworkPolicy(sandbox);
|
|
2490
|
-
await params.prepareRepositories?.(sandbox, workspace, signal);
|
|
2494
|
+
const finalize = await params.prepareRepositories?.(sandbox, workspace, signal);
|
|
2491
2495
|
if (params.removeCredentialRoute) await sandbox.update({ networkPolicy: "allow-all" });
|
|
2496
|
+
await finalize?.();
|
|
2492
2497
|
}
|
|
2493
2498
|
/** Return bounded setup output for a Workspace failure. */
|
|
2494
2499
|
function workspaceSetupFailureDetail(input) {
|
|
@@ -2611,7 +2616,7 @@ function emptyState(nowMs) {
|
|
|
2611
2616
|
/** True when this Source starts an automated Turn rather than a user Turn. */
|
|
2612
2617
|
function isAutomatedTurnSource(source) {
|
|
2613
2618
|
if (!source) return false;
|
|
2614
|
-
return source.kind === "
|
|
2619
|
+
return source.kind === "event" || source.kind === "event_automation" || source.kind === "scheduled_automation" || source.kind === "plugin_dispatch" || source.kind === "agent_invocation";
|
|
2615
2620
|
}
|
|
2616
2621
|
/** Plain notice when automatic updates stop until the user replies. */
|
|
2617
2622
|
function buildAutomatedTurnLimitResponse(args) {
|
|
@@ -2823,48 +2828,15 @@ function renderTaskInput(args) {
|
|
|
2823
2828
|
return lines.join("\n");
|
|
2824
2829
|
}
|
|
2825
2830
|
//#endregion
|
|
2826
|
-
//#region src/chat/
|
|
2827
|
-
/**
|
|
2828
|
-
* System author ID stamped on resource-event mailbox messages.
|
|
2829
|
-
*
|
|
2830
|
-
* Keep this stable so conversation history can recognize the same system input
|
|
2831
|
-
* for resource-event Turns.
|
|
2832
|
-
*
|
|
2833
|
-
* TODO(dcramer): Delete this ID after resumes read resource-event Source.kind
|
|
2834
|
-
* from the Turn checkpoint instead of Message author data.
|
|
2835
|
-
*/
|
|
2836
|
-
const RESOURCE_EVENT_AUTHOR_ID = "UJRNEVENT";
|
|
2837
|
-
/** System Message author for resource-event input. */
|
|
2838
|
-
const RESOURCE_EVENT_MESSAGE_AUTHOR = {
|
|
2839
|
-
fullName: "Junior event",
|
|
2840
|
-
isBot: true,
|
|
2841
|
-
userId: RESOURCE_EVENT_AUTHOR_ID,
|
|
2842
|
-
userName: "junior-event"
|
|
2843
|
-
};
|
|
2844
|
-
/** System execution actor for every resource-event turn. */
|
|
2845
|
-
const RESOURCE_EVENT_SYSTEM_ACTOR = {
|
|
2846
|
-
platform: "system",
|
|
2847
|
-
name: "resource-event"
|
|
2848
|
-
};
|
|
2849
|
-
/**
|
|
2850
|
-
* Whether a saved Message started a resource-event Turn.
|
|
2851
|
-
*
|
|
2852
|
-
* TODO(dcramer): Delete this marker check after deployed Turn cursors all store
|
|
2853
|
-
* Source and Actor. `eventType` only supports saved synthetic Slack input.
|
|
2854
|
-
*/
|
|
2855
|
-
function isResourceEventConversationMessage(message) {
|
|
2856
|
-
return Boolean(message.meta?.eventType) || message.author?.userId === "UJRNEVENT";
|
|
2857
|
-
}
|
|
2858
|
-
//#endregion
|
|
2859
|
-
//#region src/chat/resource-events/notification.ts
|
|
2831
|
+
//#region src/chat/events/notification.ts
|
|
2860
2832
|
/**
|
|
2861
2833
|
* Render the runtime-owned conversation message for a subscribed event.
|
|
2862
2834
|
*
|
|
2863
2835
|
* Keep this short: facts the model cannot reconstruct, plus a one-line handling
|
|
2864
2836
|
* contract. Stable delivery rules live in runtime and docs, not this prompt.
|
|
2865
2837
|
*/
|
|
2866
|
-
function
|
|
2867
|
-
const guidance =
|
|
2838
|
+
function renderEventNotificationText(subscription, event) {
|
|
2839
|
+
const guidance = eventGuidance(getEventCatalog(), event.namespace, subscription.resourceType, event.eventType);
|
|
2868
2840
|
return renderTaskInput({
|
|
2869
2841
|
about: subscription.label,
|
|
2870
2842
|
instructions: subscription.intent,
|
|
@@ -2874,21 +2846,21 @@ function renderResourceEventNotificationText(subscription, event) {
|
|
|
2874
2846
|
externalText: event.untrustedText
|
|
2875
2847
|
});
|
|
2876
2848
|
}
|
|
2877
|
-
/** Whether mailbox metadata is a plain
|
|
2878
|
-
function
|
|
2849
|
+
/** Whether mailbox metadata is a plain event wake. */
|
|
2850
|
+
function isEventMailboxMetadata(value) {
|
|
2879
2851
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
2880
2852
|
const record = value;
|
|
2881
|
-
if (record.kind !== "
|
|
2882
|
-
const
|
|
2883
|
-
if (!
|
|
2884
|
-
const fields =
|
|
2853
|
+
if (record.kind !== "event") return false;
|
|
2854
|
+
const event = record.event;
|
|
2855
|
+
if (!event || typeof event !== "object" || Array.isArray(event)) return false;
|
|
2856
|
+
const fields = event;
|
|
2885
2857
|
return typeof fields.eventKey === "string" && typeof fields.eventType === "string" && typeof fields.namespace === "string" && typeof fields.identifier === "string" && typeof fields.subscriptionId === "string" && (fields.trustedSummary === void 0 || typeof fields.trustedSummary === "string");
|
|
2886
2858
|
}
|
|
2887
2859
|
/** Build plain conversation mailbox input for one matched watch. */
|
|
2888
|
-
function
|
|
2860
|
+
function createEventInboundMessage(input) {
|
|
2889
2861
|
const metadata = {
|
|
2890
|
-
kind: "
|
|
2891
|
-
|
|
2862
|
+
kind: "event",
|
|
2863
|
+
event: {
|
|
2892
2864
|
eventKey: input.event.eventKey,
|
|
2893
2865
|
eventType: input.event.eventType,
|
|
2894
2866
|
namespace: input.event.namespace,
|
|
@@ -2900,25 +2872,25 @@ function createResourceEventInboundMessage(input) {
|
|
|
2900
2872
|
return {
|
|
2901
2873
|
conversationId: input.subscription.conversationId,
|
|
2902
2874
|
createdAtMs: input.event.occurredAtMs,
|
|
2903
|
-
inboundMessageId: `
|
|
2875
|
+
inboundMessageId: `event:${input.subscription.id}:${input.event.eventKey}`,
|
|
2904
2876
|
delivery: "defer",
|
|
2905
|
-
source: "
|
|
2877
|
+
source: "event",
|
|
2906
2878
|
receivedAtMs: input.receivedAtMs ?? Date.now(),
|
|
2907
2879
|
input: {
|
|
2908
2880
|
text: input.text,
|
|
2909
|
-
authorId:
|
|
2881
|
+
authorId: EVENT_AUTHOR_ID,
|
|
2910
2882
|
metadata
|
|
2911
2883
|
}
|
|
2912
2884
|
};
|
|
2913
2885
|
}
|
|
2914
2886
|
/**
|
|
2915
|
-
* Enqueue a
|
|
2887
|
+
* Enqueue a event as normal conversation mailbox input.
|
|
2916
2888
|
*
|
|
2917
2889
|
* The watch only names the conversation. Destination stays on the conversation
|
|
2918
2890
|
* and is applied when the worker runs. After too many automated Turns with no
|
|
2919
2891
|
* user Turn, later wakes stay quiet until a user message clears the pause.
|
|
2920
2892
|
*/
|
|
2921
|
-
async function
|
|
2893
|
+
async function enqueueEventNotification(args) {
|
|
2922
2894
|
const maxTurns = botConfig.maxConsecutiveAutomatedTurns;
|
|
2923
2895
|
const decision = await admitAutomatedTurn({
|
|
2924
2896
|
conversationId: args.subscription.conversationId,
|
|
@@ -2927,12 +2899,12 @@ async function enqueueResourceEventNotification(args) {
|
|
|
2927
2899
|
state: args.state
|
|
2928
2900
|
});
|
|
2929
2901
|
if (decision.status === "paused") {
|
|
2930
|
-
logInfo("
|
|
2902
|
+
logInfo("events.automated_turn_limit.paused", {
|
|
2931
2903
|
conversationId: args.subscription.conversationId,
|
|
2932
2904
|
"app.automated_turn_limit.consecutive": decision.consecutiveAutomatedTurns,
|
|
2933
2905
|
"app.automated_turn_limit.max": maxTurns,
|
|
2934
|
-
"app.
|
|
2935
|
-
"app.
|
|
2906
|
+
"app.event.event_type": args.event.eventType,
|
|
2907
|
+
"app.event.namespace": args.event.namespace
|
|
2936
2908
|
});
|
|
2937
2909
|
if (decision.shouldPostNotice) await postAutomatedTurnLimitNoticeForConversation({
|
|
2938
2910
|
conversationId: args.subscription.conversationId,
|
|
@@ -2941,9 +2913,9 @@ async function enqueueResourceEventNotification(args) {
|
|
|
2941
2913
|
});
|
|
2942
2914
|
return { status: "duplicate" };
|
|
2943
2915
|
}
|
|
2944
|
-
const text =
|
|
2916
|
+
const text = renderEventNotificationText(args.subscription, args.event);
|
|
2945
2917
|
return await appendAndEnqueueInboundMessage({
|
|
2946
|
-
message:
|
|
2918
|
+
message: createEventInboundMessage({
|
|
2947
2919
|
event: args.event,
|
|
2948
2920
|
subscription: {
|
|
2949
2921
|
conversationId: args.subscription.conversationId,
|
|
@@ -2956,12 +2928,12 @@ async function enqueueResourceEventNotification(args) {
|
|
|
2956
2928
|
});
|
|
2957
2929
|
}
|
|
2958
2930
|
//#endregion
|
|
2959
|
-
//#region src/chat/
|
|
2960
|
-
/** Match a normalized
|
|
2961
|
-
async function
|
|
2962
|
-
const event =
|
|
2931
|
+
//#region src/chat/events/ingest.ts
|
|
2932
|
+
/** Match a normalized event and enqueue notifications into conversations. */
|
|
2933
|
+
async function ingestEvent(input, options) {
|
|
2934
|
+
const event = eventSchema.parse(input);
|
|
2963
2935
|
const nowMs = options.nowMs ?? Date.now();
|
|
2964
|
-
const subscriptions = await
|
|
2936
|
+
const subscriptions = await findMatchingWatches({
|
|
2965
2937
|
data: event.data,
|
|
2966
2938
|
eventType: event.eventType,
|
|
2967
2939
|
nowMs,
|
|
@@ -2973,7 +2945,7 @@ async function ingestResourceEvent(input, options) {
|
|
|
2973
2945
|
const errors = [];
|
|
2974
2946
|
const waitDeadlineMs = Date.now() + 1e4;
|
|
2975
2947
|
for (const subscription of subscriptions) try {
|
|
2976
|
-
if (await
|
|
2948
|
+
if (await deliverWatch({
|
|
2977
2949
|
data: event.data,
|
|
2978
2950
|
eventType: event.eventType,
|
|
2979
2951
|
namespace: event.namespace,
|
|
@@ -2984,7 +2956,7 @@ async function ingestResourceEvent(input, options) {
|
|
|
2984
2956
|
subscription,
|
|
2985
2957
|
waitDeadlineMs,
|
|
2986
2958
|
deliver: async (current) => {
|
|
2987
|
-
return (await
|
|
2959
|
+
return (await enqueueEventNotification({
|
|
2988
2960
|
event,
|
|
2989
2961
|
queue: options.queue,
|
|
2990
2962
|
state: options.state,
|
|
@@ -2995,7 +2967,7 @@ async function ingestResourceEvent(input, options) {
|
|
|
2995
2967
|
} catch (error) {
|
|
2996
2968
|
errors.push(error);
|
|
2997
2969
|
}
|
|
2998
|
-
if (errors.length > 0) throw new AggregateError(errors, "Failed to deliver one or more
|
|
2970
|
+
if (errors.length > 0) throw new AggregateError(errors, "Failed to deliver one or more watches");
|
|
2999
2971
|
return { enqueued };
|
|
3000
2972
|
}
|
|
3001
2973
|
//#endregion
|
|
@@ -3391,7 +3363,7 @@ const SNAPSHOT_BUILD_SYSTEM_ACTOR = {
|
|
|
3391
3363
|
/** Leave time to send the next job before this request ends. */
|
|
3392
3364
|
const JOB_STOP_BUFFER_MS = 4e4;
|
|
3393
3365
|
async function publishFinishedEvent(input) {
|
|
3394
|
-
await
|
|
3366
|
+
await ingestEvent(workspaceSnapshotFinishedEvent(input), { queue: getVercelConversationWorkQueue() });
|
|
3395
3367
|
}
|
|
3396
3368
|
function createSnapshotBuildHelpers() {
|
|
3397
3369
|
const credentials = credentialContextForActor(SNAPSHOT_BUILD_SYSTEM_ACTOR);
|
|
@@ -3405,9 +3377,7 @@ function createSnapshotBuildHelpers() {
|
|
|
3405
3377
|
await sandbox.update({ networkPolicy });
|
|
3406
3378
|
return networkPolicy;
|
|
3407
3379
|
};
|
|
3408
|
-
const prepareRepositories = async (sandbox, target, signal) =>
|
|
3409
|
-
await pluginHooks.prepareWorkspace(sandbox, target.repos, signal);
|
|
3410
|
-
};
|
|
3380
|
+
const prepareRepositories = async (sandbox, target, signal) => await pluginHooks.prepareWorkspace(sandbox, target.repos, signal);
|
|
3411
3381
|
return {
|
|
3412
3382
|
applyNetworkPolicy,
|
|
3413
3383
|
prepareRepositories
|
|
@@ -3917,7 +3887,14 @@ function createSandboxRuntime(options) {
|
|
|
3917
3887
|
let networkPolicyKey;
|
|
3918
3888
|
try {
|
|
3919
3889
|
networkPolicyKey = await applyNetworkPolicy(createdSandbox);
|
|
3920
|
-
if (workspace)
|
|
3890
|
+
if (workspace) {
|
|
3891
|
+
const finalize = await options.onWorkspacePrepare?.(createdSandbox, workspace, signal);
|
|
3892
|
+
if (options.createNetworkPolicy) {
|
|
3893
|
+
await createdSandbox.update({ networkPolicy: "allow-all" });
|
|
3894
|
+
networkPolicyKey = void 0;
|
|
3895
|
+
}
|
|
3896
|
+
await finalize?.();
|
|
3897
|
+
}
|
|
3921
3898
|
await prepareSandbox(createdSandbox);
|
|
3922
3899
|
} catch (error) {
|
|
3923
3900
|
if (workspace) await stopSession(createdSandbox);
|
|
@@ -7174,7 +7151,7 @@ function createSpawnAgentTool(spawnAgent) {
|
|
|
7174
7151
|
});
|
|
7175
7152
|
}
|
|
7176
7153
|
//#endregion
|
|
7177
|
-
//#region src/chat/tools/list-
|
|
7154
|
+
//#region src/chat/tools/list-watches.ts
|
|
7178
7155
|
const listedResourceWatchSchema = z.object({
|
|
7179
7156
|
id: z.string().min(1),
|
|
7180
7157
|
label: z.string().min(1),
|
|
@@ -7188,8 +7165,8 @@ const listedResourceWatchSchema = z.object({
|
|
|
7188
7165
|
}).strict();
|
|
7189
7166
|
const resultDataSchema$2 = z.object({ subscriptions: z.array(listedResourceWatchSchema) }).strict();
|
|
7190
7167
|
const outputSchema$5 = juniorToolOutputSchema.merge(resultDataSchema$2);
|
|
7191
|
-
/** Create the tool that lists active
|
|
7192
|
-
function
|
|
7168
|
+
/** Create the tool that lists active watches for this conversation. */
|
|
7169
|
+
function createListWatchesTool(context) {
|
|
7193
7170
|
return zodTool({
|
|
7194
7171
|
annotations: {
|
|
7195
7172
|
destructiveHint: false,
|
|
@@ -7197,13 +7174,13 @@ function createListResourceEventSubscriptionsTool(context) {
|
|
|
7197
7174
|
openWorldHint: false,
|
|
7198
7175
|
readOnlyHint: true
|
|
7199
7176
|
},
|
|
7200
|
-
description: "List active
|
|
7177
|
+
description: "List active watches for the current conversation.",
|
|
7201
7178
|
exposure: "deferred",
|
|
7202
7179
|
source: RESOURCE_WATCH_TOOL_SOURCE,
|
|
7203
7180
|
inputSchema: z.object({}).strict(),
|
|
7204
7181
|
outputSchema: outputSchema$5,
|
|
7205
7182
|
async execute() {
|
|
7206
|
-
return { subscriptions: (await
|
|
7183
|
+
return { subscriptions: (await listWatches({ conversationId: context.conversationId })).map((subscription) => ({
|
|
7207
7184
|
id: subscription.id,
|
|
7208
7185
|
label: subscription.label,
|
|
7209
7186
|
identifier: subscription.identifier,
|
|
@@ -7218,7 +7195,7 @@ function createListResourceEventSubscriptionsTool(context) {
|
|
|
7218
7195
|
});
|
|
7219
7196
|
}
|
|
7220
7197
|
//#endregion
|
|
7221
|
-
//#region src/chat/tools/search-
|
|
7198
|
+
//#region src/chat/tools/search-event-types.ts
|
|
7222
7199
|
const DEFAULT_SEARCH_RESULTS = 10;
|
|
7223
7200
|
const MAX_SEARCH_RESULTS = 20;
|
|
7224
7201
|
const searchedResourceTypeSchema = z.object({
|
|
@@ -7254,8 +7231,8 @@ function searchableResourceTypes(catalog) {
|
|
|
7254
7231
|
...resourceType.matchFields ? { matchFields: resourceType.matchFields } : void 0
|
|
7255
7232
|
}))).sort((left, right) => left.namespace.localeCompare(right.namespace) || left.type.localeCompare(right.type));
|
|
7256
7233
|
}
|
|
7257
|
-
/** Create the read-only tool that searches enabled
|
|
7258
|
-
function
|
|
7234
|
+
/** Create the read-only tool that searches enabled event types. */
|
|
7235
|
+
function createSearchEventTypesTool(catalog) {
|
|
7259
7236
|
return zodTool({
|
|
7260
7237
|
annotations: {
|
|
7261
7238
|
destructiveHint: false,
|
|
@@ -7263,9 +7240,9 @@ function createSearchResourceEventTypesTool(catalog) {
|
|
|
7263
7240
|
openWorldHint: false,
|
|
7264
7241
|
readOnlyHint: true
|
|
7265
7242
|
},
|
|
7266
|
-
description: "Search the
|
|
7243
|
+
description: "Search the event types currently enabled without creating anything. Includes plugin resources and core Workspace snapshot events. Use watchEvents for temporary updates in the current conversation; use createEventAutomation for durable automation. This tool does not watch a resource, create an automation, or list concrete resources.",
|
|
7267
7244
|
inputSchema: z.object({
|
|
7268
|
-
query: z.string().nullable().describe("Optional terms matching a namespace, resource type, or event name. Empty lists all enabled
|
|
7245
|
+
query: z.string().nullable().describe("Optional terms matching a namespace, resource type, or event name. Empty lists all enabled event types.").optional(),
|
|
7269
7246
|
namespace: eventNamespaceSchema(catalog).nullable().describe("Optional enabled plugin namespace to search within.").optional(),
|
|
7270
7247
|
maxResults: z.number().int().min(1).max(MAX_SEARCH_RESULTS).nullable().describe("Maximum matching resource types to return.").optional()
|
|
7271
7248
|
}).strict(),
|
|
@@ -7294,13 +7271,13 @@ function createSearchResourceEventTypesTool(catalog) {
|
|
|
7294
7271
|
});
|
|
7295
7272
|
}
|
|
7296
7273
|
//#endregion
|
|
7297
|
-
//#region src/chat/tools/stop-
|
|
7274
|
+
//#region src/chat/tools/stop-watches.ts
|
|
7298
7275
|
const resultDataSchema$1 = z.object({
|
|
7299
7276
|
watching_status: z.literal("stopped"),
|
|
7300
7277
|
stoppedIds: z.array(z.string().min(1))
|
|
7301
7278
|
}).strict();
|
|
7302
7279
|
const outputSchema$3 = juniorToolOutputSchema.merge(resultDataSchema$1);
|
|
7303
|
-
/** Create the tool that stops
|
|
7280
|
+
/** Create the tool that stops watches for this conversation. */
|
|
7304
7281
|
function createStopWatchingResourcesTool(context) {
|
|
7305
7282
|
return zodTool({
|
|
7306
7283
|
annotations: {
|
|
@@ -7309,7 +7286,7 @@ function createStopWatchingResourcesTool(context) {
|
|
|
7309
7286
|
openWorldHint: false,
|
|
7310
7287
|
readOnlyHint: false
|
|
7311
7288
|
},
|
|
7312
|
-
description: "Stop one
|
|
7289
|
+
description: "Stop one watch in the current conversation by id. Omit id only when the user explicitly asks to stop every watch in this conversation. Infer terse stop requests from context, inspect active watches when the target is unclear, and call this tool before confirming that watching stopped.",
|
|
7313
7290
|
exposure: "deferred",
|
|
7314
7291
|
source: RESOURCE_WATCH_TOOL_SOURCE,
|
|
7315
7292
|
inputSchema: z.object({ id: z.string().min(1).nullable().optional() }).strict(),
|
|
@@ -7317,14 +7294,14 @@ function createStopWatchingResourcesTool(context) {
|
|
|
7317
7294
|
async execute({ id }) {
|
|
7318
7295
|
let stoppedIds;
|
|
7319
7296
|
if (id) {
|
|
7320
|
-
const stopped = await
|
|
7297
|
+
const stopped = await cancelWatch({
|
|
7321
7298
|
conversationId: context.conversationId,
|
|
7322
7299
|
id
|
|
7323
7300
|
});
|
|
7324
|
-
if (!stopped) throw new ToolInputError("
|
|
7301
|
+
if (!stopped) throw new ToolInputError("Watch was not found in the current conversation.");
|
|
7325
7302
|
stoppedIds = [stopped.id];
|
|
7326
7303
|
} else {
|
|
7327
|
-
const subscriptions = await
|
|
7304
|
+
const subscriptions = await listWatches({ conversationId: context.conversationId });
|
|
7328
7305
|
await cancelSubscriptions({ conversationId: context.conversationId });
|
|
7329
7306
|
stoppedIds = subscriptions.map((subscription) => subscription.id);
|
|
7330
7307
|
}
|
|
@@ -7336,7 +7313,7 @@ function createStopWatchingResourcesTool(context) {
|
|
|
7336
7313
|
});
|
|
7337
7314
|
}
|
|
7338
7315
|
//#endregion
|
|
7339
|
-
//#region src/chat/tools/watch-
|
|
7316
|
+
//#region src/chat/tools/watch-events.ts
|
|
7340
7317
|
function inputSchema(catalog) {
|
|
7341
7318
|
return z.object({
|
|
7342
7319
|
identifier: z.string().trim().min(1).describe("Plugin-defined resource identifier."),
|
|
@@ -7344,7 +7321,7 @@ function inputSchema(catalog) {
|
|
|
7344
7321
|
resourceType: registeredResourceTypeSchema(catalog).describe("Plugin-defined resource type."),
|
|
7345
7322
|
label: z.string().trim().min(1).max(500).describe("Human-readable resource label."),
|
|
7346
7323
|
events: z.array(registeredEventTypeSchema(catalog)).min(1).describe("High-signal event names to deliver to this conversation when they occur."),
|
|
7347
|
-
match:
|
|
7324
|
+
match: registeredEventMatchSchema(),
|
|
7348
7325
|
intent: z.string().trim().min(1).max(4e3).describe("Concise reason this conversation wants these events, used when an event arrives."),
|
|
7349
7326
|
ttlMs: z.coerce.number().describe("How long to keep the subscription active. Defaults to 14 days and must not exceed 30 days.").optional()
|
|
7350
7327
|
}).strict().superRefine((subscription, context) => {
|
|
@@ -7384,11 +7361,11 @@ function cleanStrings(values) {
|
|
|
7384
7361
|
function ttlMs(input) {
|
|
7385
7362
|
if (input.ttlMs === void 0) return RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS;
|
|
7386
7363
|
if (!Number.isFinite(input.ttlMs) || input.ttlMs <= 0) throw new ToolInputError("ttlMs must be a positive finite number");
|
|
7387
|
-
if (input.ttlMs > 2592e6) throw new ToolInputError("
|
|
7364
|
+
if (input.ttlMs > 2592e6) throw new ToolInputError("Watches cannot exceed 30 days");
|
|
7388
7365
|
return input.ttlMs;
|
|
7389
7366
|
}
|
|
7390
|
-
/** Create the tool that temporarily watches
|
|
7391
|
-
function
|
|
7367
|
+
/** Create the tool that temporarily watches events in one conversation. */
|
|
7368
|
+
function createWatchEventsTool(context, catalog) {
|
|
7392
7369
|
return zodTool({
|
|
7393
7370
|
annotations: {
|
|
7394
7371
|
destructiveHint: false,
|
|
@@ -7396,7 +7373,7 @@ function createWatchResourceEventsTool(context, catalog) {
|
|
|
7396
7373
|
openWorldHint: true,
|
|
7397
7374
|
readOnlyHint: false
|
|
7398
7375
|
},
|
|
7399
|
-
description: "Watch one plugin resource in the current conversation for a limited time; matching events return to this conversation as updates. Use for watch, notify, or tell-me-when requests. This does not create an event
|
|
7376
|
+
description: "Watch one plugin resource in the current conversation for a limited time; matching events return to this conversation as updates. Use for watch, notify, or tell-me-when requests. This does not create an event automation or execute a durable automation instruction. Prefer a subscribable tool result when available.",
|
|
7400
7377
|
inputSchema: inputSchema(catalog),
|
|
7401
7378
|
outputSchema: outputSchema$2,
|
|
7402
7379
|
async execute(input) {
|
|
@@ -7404,13 +7381,13 @@ function createWatchResourceEventsTool(context, catalog) {
|
|
|
7404
7381
|
for (const eventType of events) if (!pluginSupportsEvent(catalog, input.namespace, input.resourceType, eventType)) throw new ToolInputError(`Resource type "${input.namespace}:${input.resourceType}" does not support event "${eventType}".`);
|
|
7405
7382
|
const intent = input.intent.trim();
|
|
7406
7383
|
if (!intent) throw new ToolInputError("intent is required");
|
|
7407
|
-
const match =
|
|
7384
|
+
const match = requireSupportedEventMatch(catalog, {
|
|
7408
7385
|
match: input.match,
|
|
7409
7386
|
namespace: input.namespace,
|
|
7410
7387
|
resourceType: input.resourceType
|
|
7411
7388
|
});
|
|
7412
7389
|
const nowMs = Date.now();
|
|
7413
|
-
const subscription = await
|
|
7390
|
+
const subscription = await createWatch({
|
|
7414
7391
|
conversationId: context.conversationId,
|
|
7415
7392
|
events,
|
|
7416
7393
|
expiresAtMs: nowMs + ttlMs(input),
|
|
@@ -7418,7 +7395,7 @@ function createWatchResourceEventsTool(context, catalog) {
|
|
|
7418
7395
|
label: input.label.trim(),
|
|
7419
7396
|
...match ? { match } : void 0,
|
|
7420
7397
|
namespace: input.namespace.trim(),
|
|
7421
|
-
identifier:
|
|
7398
|
+
identifier: normalizeCatalogEventIdentifier(catalog, input.namespace, input.identifier),
|
|
7422
7399
|
resourceType: input.resourceType.trim()
|
|
7423
7400
|
});
|
|
7424
7401
|
return {
|
|
@@ -7440,24 +7417,24 @@ function createWatchResourceEventsTool(context, catalog) {
|
|
|
7440
7417
|
});
|
|
7441
7418
|
}
|
|
7442
7419
|
//#endregion
|
|
7443
|
-
//#region src/chat/tools/
|
|
7420
|
+
//#region src/chat/tools/events.ts
|
|
7444
7421
|
/** Build the complete resource-watch tool set for this runtime context. */
|
|
7445
|
-
function
|
|
7422
|
+
function createEventTools(context, catalog) {
|
|
7446
7423
|
return {
|
|
7447
|
-
...
|
|
7448
|
-
|
|
7449
|
-
|
|
7424
|
+
...hasEventCatalogEntries(catalog) ? {
|
|
7425
|
+
searchEventTypes: createSearchEventTypesTool(catalog),
|
|
7426
|
+
watchEvents: createWatchEventsTool(context, catalog)
|
|
7450
7427
|
} : void 0,
|
|
7451
|
-
|
|
7428
|
+
listWatches: createListWatchesTool(context),
|
|
7452
7429
|
stopWatchingResources: createStopWatchingResourcesTool(context)
|
|
7453
7430
|
};
|
|
7454
7431
|
}
|
|
7455
7432
|
//#endregion
|
|
7456
|
-
//#region src/chat/tools/create-event-
|
|
7457
|
-
/** Build a retry-stable
|
|
7458
|
-
function
|
|
7433
|
+
//#region src/chat/tools/create-event-automation.ts
|
|
7434
|
+
/** Build a retry-stable automation id scoped to actor, destination, and tool call. */
|
|
7435
|
+
function buildEventAutomationId(args) {
|
|
7459
7436
|
const toolCallId = args.toolCallId?.trim();
|
|
7460
|
-
if (!toolCallId) throw new Error("Event
|
|
7437
|
+
if (!toolCallId) throw new Error("Event automation creation requires a tool-call identity.");
|
|
7461
7438
|
return `evt_${createHash("sha256").update(JSON.stringify({
|
|
7462
7439
|
actor: args.userId,
|
|
7463
7440
|
channel: args.channelId,
|
|
@@ -7465,9 +7442,9 @@ function buildEventTaskId(args) {
|
|
|
7465
7442
|
team: args.teamId
|
|
7466
7443
|
})).digest("hex").slice(0, 32)}`;
|
|
7467
7444
|
}
|
|
7468
|
-
/** Create the core tool that stores an event
|
|
7469
|
-
function
|
|
7470
|
-
const trigger =
|
|
7445
|
+
/** Create the core tool that stores an event automation. */
|
|
7446
|
+
function createEventAutomationTool(context, catalog) {
|
|
7447
|
+
const trigger = registeredEventAutomationTriggerSchema(catalog);
|
|
7471
7448
|
return zodTool({
|
|
7472
7449
|
approvalMode: "review",
|
|
7473
7450
|
annotations: {
|
|
@@ -7477,12 +7454,12 @@ function createEventTaskTool(context, catalog) {
|
|
|
7477
7454
|
readOnlyHint: false
|
|
7478
7455
|
},
|
|
7479
7456
|
executionMode: "sequential",
|
|
7480
|
-
description: "Create a durable event
|
|
7457
|
+
description: "Create a durable event automation that executes the supplied instruction for every matching event. Use for whenever-this-happens-do-X automation; ordinary watch, notify, or tell-me-when requests use watchEvents instead. The automation may use the creator's connected credentials. Prefer a subscribable tool result when available.",
|
|
7481
7458
|
inputSchema: z.object({
|
|
7482
|
-
|
|
7459
|
+
instruction: z.string().trim().min(1).max(4e3),
|
|
7483
7460
|
title: z.string().trim().min(1).max(60).nullable().describe("Optional short display title. Omit or use null to generate one from the task instruction.").optional(),
|
|
7484
7461
|
trigger,
|
|
7485
|
-
outcomes: z.array(taskOutcomeInputSchema).max(5).describe("
|
|
7462
|
+
outcomes: z.array(taskOutcomeInputSchema).max(5).describe("Successful work is silent by default. Omit this field or use an empty list when the Automation should act through tools without a status message. Add send_message only when the user asks for a notification, post, digest, summary, or other visible result.").optional(),
|
|
7486
7463
|
credentialMode: z.enum(["creator", "system"]).nullable().describe("Use creator to make the task creator's connected credentials available, or system when the creator says not to use them. Omit or use null for the creator default.").optional()
|
|
7487
7464
|
}).strict(),
|
|
7488
7465
|
prepareArguments(args) {
|
|
@@ -7491,25 +7468,25 @@ function createEventTaskTool(context, catalog) {
|
|
|
7491
7468
|
if (prepared.credentialMode === "creator" || prepared.credentialMode === null) delete prepared.credentialMode;
|
|
7492
7469
|
return prepared;
|
|
7493
7470
|
},
|
|
7494
|
-
outputSchema:
|
|
7471
|
+
outputSchema: eventAutomationToolResultSchema,
|
|
7495
7472
|
async execute(input, options) {
|
|
7496
|
-
const { actor, destination, source } =
|
|
7497
|
-
const match =
|
|
7498
|
-
const id =
|
|
7473
|
+
const { actor, destination, source } = requireEventAutomationSlackContext(context);
|
|
7474
|
+
const match = requireSupportedEventAutomationTrigger(catalog, input.trigger);
|
|
7475
|
+
const id = buildEventAutomationId({
|
|
7499
7476
|
channelId: destination.channelId,
|
|
7500
7477
|
teamId: destination.teamId,
|
|
7501
7478
|
toolCallId: options.toolCallId,
|
|
7502
7479
|
userId: actor.userId
|
|
7503
7480
|
});
|
|
7504
7481
|
const db = getDb();
|
|
7505
|
-
const existing = await
|
|
7482
|
+
const existing = await getEventAutomation(db, id);
|
|
7506
7483
|
if (existing) {
|
|
7507
|
-
if (!
|
|
7508
|
-
if (existing.status !== "deleted") return
|
|
7484
|
+
if (!eventAutomationMatchesDestination(existing, destination) || existing.createdBy.slackUserId !== actor.userId) throw new ToolInputError("Event automation operation identity is invalid.");
|
|
7485
|
+
if (existing.status !== "deleted") return eventAutomationToolResult(existing, catalog, actor.userId);
|
|
7509
7486
|
}
|
|
7510
7487
|
const title = await resolveTaskTitle({
|
|
7511
7488
|
completeText,
|
|
7512
|
-
instruction: input.
|
|
7489
|
+
instruction: input.instruction,
|
|
7513
7490
|
title: input.title
|
|
7514
7491
|
});
|
|
7515
7492
|
const task = {
|
|
@@ -7524,25 +7501,25 @@ function createEventTaskTool(context, catalog) {
|
|
|
7524
7501
|
credentialMode: input.credentialMode ?? "creator",
|
|
7525
7502
|
destination,
|
|
7526
7503
|
outcomes: await resolveTaskOutcomes(input.outcomes, destination, actor.userId),
|
|
7527
|
-
task: { text: input.
|
|
7504
|
+
task: { text: input.instruction },
|
|
7528
7505
|
...title ? { title } : void 0,
|
|
7529
7506
|
trigger: {
|
|
7530
7507
|
namespace: input.trigger.namespace,
|
|
7531
|
-
identifier:
|
|
7508
|
+
identifier: normalizeCatalogEventIdentifier(catalog, input.trigger.namespace, input.trigger.identifier),
|
|
7532
7509
|
resourceType: input.trigger.resourceType,
|
|
7533
7510
|
label: input.trigger.label,
|
|
7534
7511
|
events: [...new Set(input.trigger.events)],
|
|
7535
7512
|
...match ? { match } : void 0
|
|
7536
7513
|
}
|
|
7537
7514
|
};
|
|
7538
|
-
return
|
|
7515
|
+
return eventAutomationToolResult(await createEventAutomation(db, task), catalog, actor.userId);
|
|
7539
7516
|
}
|
|
7540
7517
|
});
|
|
7541
7518
|
}
|
|
7542
7519
|
//#endregion
|
|
7543
|
-
//#region src/chat/tools/delete-event-
|
|
7544
|
-
/** Create the core tool that deletes an event
|
|
7545
|
-
function
|
|
7520
|
+
//#region src/chat/tools/delete-event-automation.ts
|
|
7521
|
+
/** Create the core tool that deletes an event automation. */
|
|
7522
|
+
function createDeleteEventAutomationTool(context, catalog) {
|
|
7546
7523
|
return zodTool({
|
|
7547
7524
|
approvalMode: "review",
|
|
7548
7525
|
annotations: {
|
|
@@ -7552,22 +7529,23 @@ function createDeleteEventTaskTool(context, catalog) {
|
|
|
7552
7529
|
readOnlyHint: false
|
|
7553
7530
|
},
|
|
7554
7531
|
executionMode: "sequential",
|
|
7555
|
-
description: "Delete an event
|
|
7556
|
-
inputSchema: z.object({
|
|
7557
|
-
outputSchema:
|
|
7558
|
-
async execute({
|
|
7559
|
-
const
|
|
7560
|
-
const
|
|
7561
|
-
|
|
7562
|
-
|
|
7532
|
+
description: "Delete an event automation.",
|
|
7533
|
+
inputSchema: z.object({ automationId: z.string().min(1) }).strict(),
|
|
7534
|
+
outputSchema: eventAutomationToolResultSchema,
|
|
7535
|
+
async execute({ automationId }) {
|
|
7536
|
+
const { actor } = requireEventAutomationSlackContext(context);
|
|
7537
|
+
const current = await writableEventAutomation(context, automationId);
|
|
7538
|
+
const deleted = await deleteEventAutomation(getDb(), current.id);
|
|
7539
|
+
if (!deleted) throw new ToolInputError("Event automation was not found.");
|
|
7540
|
+
return eventAutomationToolResult(deleted, catalog, actor.userId);
|
|
7563
7541
|
}
|
|
7564
7542
|
});
|
|
7565
7543
|
}
|
|
7566
7544
|
//#endregion
|
|
7567
|
-
//#region src/chat/tools/list-event-
|
|
7568
|
-
const
|
|
7569
|
-
/** Create the core tool that lists event
|
|
7570
|
-
function
|
|
7545
|
+
//#region src/chat/tools/list-event-automations.ts
|
|
7546
|
+
const MAX_LISTED_EVENT_AUTOMATIONS = 50;
|
|
7547
|
+
/** Create the core tool that lists event automations for this destination. */
|
|
7548
|
+
function createListEventAutomationsTool(context, catalog) {
|
|
7571
7549
|
return zodTool({
|
|
7572
7550
|
annotations: {
|
|
7573
7551
|
destructiveHint: false,
|
|
@@ -7575,30 +7553,30 @@ function createListEventTasksTool(context, catalog) {
|
|
|
7575
7553
|
openWorldHint: false,
|
|
7576
7554
|
readOnlyHint: true
|
|
7577
7555
|
},
|
|
7578
|
-
description: "List event
|
|
7556
|
+
description: "List event automations for the current destination. Each result says whether the requester is the creator and whether its trigger is available.",
|
|
7579
7557
|
inputSchema: z.object({}).strict(),
|
|
7580
|
-
outputSchema:
|
|
7558
|
+
outputSchema: eventAutomationListToolResultSchema,
|
|
7581
7559
|
async execute() {
|
|
7582
|
-
const { destination } =
|
|
7583
|
-
const matching = (await
|
|
7584
|
-
const
|
|
7560
|
+
const { actor, destination } = requireEventAutomationSlackContext(context);
|
|
7561
|
+
const matching = (await listEventAutomationsForTeam(getDb(), destination.teamId)).filter((task) => eventAutomationMatchesDestination(task, destination));
|
|
7562
|
+
const automations = matching.slice(0, MAX_LISTED_EVENT_AUTOMATIONS).map((task) => compactEventAutomation(task, catalog, actor.userId));
|
|
7585
7563
|
return {
|
|
7586
|
-
|
|
7587
|
-
truncated: matching.length >
|
|
7564
|
+
automations,
|
|
7565
|
+
truncated: matching.length > automations.length
|
|
7588
7566
|
};
|
|
7589
7567
|
}
|
|
7590
7568
|
});
|
|
7591
7569
|
}
|
|
7592
7570
|
//#endregion
|
|
7593
|
-
//#region src/chat/tools/update-event-
|
|
7571
|
+
//#region src/chat/tools/update-event-automation.ts
|
|
7594
7572
|
/** Return whether an edit changes the task's executable event source. */
|
|
7595
|
-
function
|
|
7573
|
+
function changesEventAutomationTrigger(current, next) {
|
|
7596
7574
|
const currentEvents = [...current.events].sort();
|
|
7597
7575
|
const nextEvents = [...next.events].sort();
|
|
7598
|
-
return current.namespace !== next.namespace || current.identifier !== next.identifier || currentEvents.length !== nextEvents.length || currentEvents.some((event, index) => event !== nextEvents[index]) ||
|
|
7576
|
+
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);
|
|
7599
7577
|
}
|
|
7600
|
-
/** Create the core tool that updates an event
|
|
7601
|
-
function
|
|
7578
|
+
/** Create the core tool that updates an event automation. */
|
|
7579
|
+
function createUpdateEventAutomationTool(context, catalog) {
|
|
7602
7580
|
return zodTool({
|
|
7603
7581
|
approvalMode: "review",
|
|
7604
7582
|
annotations: {
|
|
@@ -7608,44 +7586,44 @@ function createUpdateEventTaskTool(context, catalog) {
|
|
|
7608
7586
|
readOnlyHint: false
|
|
7609
7587
|
},
|
|
7610
7588
|
executionMode: "sequential",
|
|
7611
|
-
description: "Update the instruction, registered trigger, or credential use for an event
|
|
7589
|
+
description: "Update the instruction, registered trigger, or credential use for an event automation.",
|
|
7612
7590
|
inputSchema: z.object({
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
trigger:
|
|
7591
|
+
automationId: z.string().min(1),
|
|
7592
|
+
instruction: z.string().trim().min(1).max(4e3).nullable().optional(),
|
|
7593
|
+
trigger: registeredEventAutomationTriggerSchema(catalog).nullable().optional(),
|
|
7616
7594
|
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(),
|
|
7617
7595
|
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()
|
|
7618
7596
|
}).strict(),
|
|
7619
7597
|
prepareArguments(args) {
|
|
7620
|
-
const { credentialMode, outcomes,
|
|
7598
|
+
const { credentialMode, outcomes, instruction, trigger, ...prepared } = args;
|
|
7621
7599
|
return {
|
|
7622
7600
|
...prepared,
|
|
7623
|
-
...
|
|
7601
|
+
...instruction != null ? { instruction } : void 0,
|
|
7624
7602
|
...trigger != null ? { trigger } : void 0,
|
|
7625
7603
|
...outcomes != null ? { outcomes } : void 0,
|
|
7626
7604
|
...credentialMode != null ? { credentialMode } : void 0
|
|
7627
7605
|
};
|
|
7628
7606
|
},
|
|
7629
|
-
outputSchema:
|
|
7607
|
+
outputSchema: eventAutomationToolResultSchema,
|
|
7630
7608
|
async execute(input) {
|
|
7631
|
-
const current = await
|
|
7632
|
-
const match = input.trigger ?
|
|
7633
|
-
const { actor } =
|
|
7609
|
+
const current = await writableEventAutomation(context, input.automationId);
|
|
7610
|
+
const match = input.trigger ? requireSupportedEventAutomationTrigger(catalog, input.trigger) : void 0;
|
|
7611
|
+
const { actor } = requireEventAutomationSlackContext(context);
|
|
7634
7612
|
const isCreator = actor.userId === current.createdBy.slackUserId;
|
|
7635
|
-
if (input.credentialMode === "creator" && !isCreator) throw new ToolInputError("Only the event
|
|
7636
|
-
if (input.outcomes != null && !isCreator) throw new ToolInputError("Only the event
|
|
7637
|
-
if (input.
|
|
7613
|
+
if (input.credentialMode === "creator" && !isCreator) throw new ToolInputError("Only the event automation creator can enable creator credential use.");
|
|
7614
|
+
if (input.outcomes != null && !isCreator) throw new ToolInputError("Only the event automation creator can change message destinations.");
|
|
7615
|
+
if (input.instruction === void 0 && input.trigger === void 0 && input.outcomes == null && input.credentialMode == null) throw new ToolInputError("Event automation update requires a change.");
|
|
7638
7616
|
const nextTrigger = input.trigger ? {
|
|
7639
7617
|
namespace: input.trigger.namespace,
|
|
7640
|
-
identifier:
|
|
7618
|
+
identifier: normalizeCatalogEventIdentifier(catalog, input.trigger.namespace, input.trigger.identifier),
|
|
7641
7619
|
resourceType: input.trigger.resourceType,
|
|
7642
7620
|
label: input.trigger.label,
|
|
7643
7621
|
events: [...new Set(input.trigger.events)],
|
|
7644
7622
|
...match ? { match } : void 0
|
|
7645
7623
|
} : current.trigger;
|
|
7646
|
-
const nextInstruction = input.
|
|
7624
|
+
const nextInstruction = input.instruction != null ? input.instruction : current.task.text;
|
|
7647
7625
|
const instructionChanged = nextInstruction !== current.task.text;
|
|
7648
|
-
const changesExecution = instructionChanged ||
|
|
7626
|
+
const changesExecution = instructionChanged || changesEventAutomationTrigger(current.trigger, nextTrigger);
|
|
7649
7627
|
const next = {
|
|
7650
7628
|
...current,
|
|
7651
7629
|
credentialMode: changesExecution && !isCreator ? "system" : input.credentialMode ?? current.credentialMode,
|
|
@@ -7662,26 +7640,26 @@ function createUpdateEventTaskTool(context, catalog) {
|
|
|
7662
7640
|
if (title) next.title = title;
|
|
7663
7641
|
else delete next.title;
|
|
7664
7642
|
}
|
|
7665
|
-
const saved = await
|
|
7666
|
-
if (!saved) throw new ToolInputError("Event
|
|
7667
|
-
return
|
|
7643
|
+
const saved = await saveEventAutomation(getDb(), next);
|
|
7644
|
+
if (!saved) throw new ToolInputError("Event automation was not found.");
|
|
7645
|
+
return eventAutomationToolResult(saved, catalog, actor.userId);
|
|
7668
7646
|
}
|
|
7669
7647
|
});
|
|
7670
7648
|
}
|
|
7671
7649
|
//#endregion
|
|
7672
|
-
//#region src/chat/tools/event-
|
|
7673
|
-
/** Build event
|
|
7674
|
-
function
|
|
7650
|
+
//#region src/chat/tools/event-automations.ts
|
|
7651
|
+
/** Build event automation tools for an interactive Slack actor. */
|
|
7652
|
+
function createEventAutomationTools(context, catalog) {
|
|
7675
7653
|
if (context.source.kind !== "slack" || context.destination.platform !== "slack" || context.actor?.platform !== "slack") return {};
|
|
7676
7654
|
return {
|
|
7677
|
-
...
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7655
|
+
...hasPluginEventCatalogEntries(catalog) ? { createEventAutomation: createEventAutomationTool(context, catalog) } : void 0,
|
|
7656
|
+
listEventAutomations: createListEventAutomationsTool(context, catalog),
|
|
7657
|
+
updateEventAutomation: createUpdateEventAutomationTool(context, catalog),
|
|
7658
|
+
deleteEventAutomation: createDeleteEventAutomationTool(context, catalog)
|
|
7681
7659
|
};
|
|
7682
7660
|
}
|
|
7683
7661
|
//#endregion
|
|
7684
|
-
//#region src/chat/scheduled-
|
|
7662
|
+
//#region src/chat/scheduled-automations/cadence.ts
|
|
7685
7663
|
const FORMATTERS = /* @__PURE__ */ new Map();
|
|
7686
7664
|
function getFormatter(timezone) {
|
|
7687
7665
|
const existing = FORMATTERS.get(timezone);
|
|
@@ -7924,7 +7902,7 @@ function getNextRunAtMs(task, scheduledForMs, afterMs = scheduledForMs) {
|
|
|
7924
7902
|
});
|
|
7925
7903
|
}
|
|
7926
7904
|
//#endregion
|
|
7927
|
-
//#region src/chat/scheduled-
|
|
7905
|
+
//#region src/chat/scheduled-automations/schedule-intent.ts
|
|
7928
7906
|
/**
|
|
7929
7907
|
* Owns model-facing schedule intent and materializes it against the trusted
|
|
7930
7908
|
* server clock into the canonical schedule persisted by core.
|
|
@@ -7971,9 +7949,9 @@ const recurringScheduleIntentSchema = z.object({
|
|
|
7971
7949
|
interval: z.number().int().positive().max(365).describe("Repeat every N frequency units. Defaults to 1.").nullable().optional(),
|
|
7972
7950
|
time: localTimeSchema.describe("Local wall-clock time for each occurrence in HH:MM format."),
|
|
7973
7951
|
weekdays: z.array(weekdaySchema).min(1).max(7).describe("Required for weekly schedules.").nullable().optional(),
|
|
7974
|
-
|
|
7952
|
+
dayOfMonth: z.number().int().min(1).max(31).describe("Required for monthly and yearly schedules.").nullable().optional(),
|
|
7975
7953
|
month: z.number().int().min(1).max(12).describe("Required for yearly schedules, where January is 1.").nullable().optional(),
|
|
7976
|
-
|
|
7954
|
+
startDate: localDateSchema.describe("Optional local calendar date that anchors the recurrence. Omit to start with the next matching occurrence.").nullable().optional(),
|
|
7977
7955
|
timezone: timezoneSchema
|
|
7978
7956
|
}).strict().superRefine((schedule, context) => {
|
|
7979
7957
|
if (schedule.frequency === "weekly" && schedule.weekdays == null) context.addIssue({
|
|
@@ -7981,10 +7959,10 @@ const recurringScheduleIntentSchema = z.object({
|
|
|
7981
7959
|
message: "Weekly schedules require weekdays.",
|
|
7982
7960
|
path: ["weekdays"]
|
|
7983
7961
|
});
|
|
7984
|
-
if ((schedule.frequency === "monthly" || schedule.frequency === "yearly") && schedule.
|
|
7962
|
+
if ((schedule.frequency === "monthly" || schedule.frequency === "yearly") && schedule.dayOfMonth == null) context.addIssue({
|
|
7985
7963
|
code: "custom",
|
|
7986
|
-
message: `${schedule.frequency} schedules require
|
|
7987
|
-
path: ["
|
|
7964
|
+
message: `${schedule.frequency} schedules require dayOfMonth.`,
|
|
7965
|
+
path: ["dayOfMonth"]
|
|
7988
7966
|
});
|
|
7989
7967
|
if (schedule.frequency === "yearly" && schedule.month == null) context.addIssue({
|
|
7990
7968
|
code: "custom",
|
|
@@ -7996,10 +7974,10 @@ const recurringScheduleIntentSchema = z.object({
|
|
|
7996
7974
|
message: "weekdays applies only to weekly schedules.",
|
|
7997
7975
|
path: ["weekdays"]
|
|
7998
7976
|
});
|
|
7999
|
-
if (schedule.frequency !== "monthly" && schedule.frequency !== "yearly" && schedule.
|
|
7977
|
+
if (schedule.frequency !== "monthly" && schedule.frequency !== "yearly" && schedule.dayOfMonth != null) context.addIssue({
|
|
8000
7978
|
code: "custom",
|
|
8001
|
-
message: "
|
|
8002
|
-
path: ["
|
|
7979
|
+
message: "dayOfMonth applies only to monthly or yearly schedules.",
|
|
7980
|
+
path: ["dayOfMonth"]
|
|
8003
7981
|
});
|
|
8004
7982
|
if (schedule.frequency !== "yearly" && schedule.month != null) context.addIssue({
|
|
8005
7983
|
code: "custom",
|
|
@@ -8066,8 +8044,8 @@ function recurringDescription(schedule, timezone) {
|
|
|
8066
8044
|
const cadence = interval === 1 ? cadenceUnit : `${interval} ${cadenceUnit}s`;
|
|
8067
8045
|
let detail = "";
|
|
8068
8046
|
if (schedule.frequency === "weekly") detail = ` on ${formatWeekdays(schedule.weekdays)}`;
|
|
8069
|
-
else if (schedule.frequency === "monthly") detail = ` on day ${schedule.
|
|
8070
|
-
else if (schedule.frequency === "yearly") detail = ` on ${String(schedule.month).padStart(2, "0")}-${String(schedule.
|
|
8047
|
+
else if (schedule.frequency === "monthly") detail = ` on day ${schedule.dayOfMonth}`;
|
|
8048
|
+
else if (schedule.frequency === "yearly") detail = ` on ${String(schedule.month).padStart(2, "0")}-${String(schedule.dayOfMonth).padStart(2, "0")}`;
|
|
8071
8049
|
return `Every ${cadence}${detail} at ${schedule.time} (${timezone})`;
|
|
8072
8050
|
}
|
|
8073
8051
|
var ScheduleIntentError = class extends Error {};
|
|
@@ -8103,13 +8081,13 @@ function compileScheduleIntent(args) {
|
|
|
8103
8081
|
const recurrence = {
|
|
8104
8082
|
frequency: args.intent.frequency,
|
|
8105
8083
|
interval: args.intent.interval ?? 1,
|
|
8106
|
-
startDate: args.intent.
|
|
8084
|
+
startDate: args.intent.startDate ?? localDateAt(args.nowMs, timezone),
|
|
8107
8085
|
time: parseLocalTime(args.intent.time),
|
|
8108
8086
|
...args.intent.frequency === "weekly" ? { weekdays: [...new Set(args.intent.weekdays.map((weekday) => WEEKDAY_INDEX[weekday]))].sort((left, right) => left - right) } : void 0,
|
|
8109
|
-
...args.intent.frequency === "monthly" || args.intent.frequency === "yearly" ? { dayOfMonth: args.intent.
|
|
8087
|
+
...args.intent.frequency === "monthly" || args.intent.frequency === "yearly" ? { dayOfMonth: args.intent.dayOfMonth ?? void 0 } : void 0,
|
|
8110
8088
|
...args.intent.frequency === "yearly" ? { month: args.intent.month ?? void 0 } : void 0
|
|
8111
8089
|
};
|
|
8112
|
-
const searchRecurrence = args.intent.
|
|
8090
|
+
const searchRecurrence = args.intent.startDate ? recurrence : {
|
|
8113
8091
|
...recurrence,
|
|
8114
8092
|
interval: 1
|
|
8115
8093
|
};
|
|
@@ -8119,7 +8097,7 @@ function compileScheduleIntent(args) {
|
|
|
8119
8097
|
timezone
|
|
8120
8098
|
});
|
|
8121
8099
|
if (nextRunAtMs === void 0) throw new ScheduleIntentError("The recurring schedule has no valid future occurrence.");
|
|
8122
|
-
const materializedRecurrence = args.intent.
|
|
8100
|
+
const materializedRecurrence = args.intent.startDate ? recurrence : {
|
|
8123
8101
|
...recurrence,
|
|
8124
8102
|
startDate: localDateAt(nextRunAtMs, timezone)
|
|
8125
8103
|
};
|
|
@@ -8134,10 +8112,10 @@ function compileScheduleIntent(args) {
|
|
|
8134
8112
|
};
|
|
8135
8113
|
}
|
|
8136
8114
|
//#endregion
|
|
8137
|
-
//#region src/chat/scheduled-
|
|
8138
|
-
/** Safe structured telemetry attributes for scheduled-
|
|
8139
|
-
/** Correlation attributes shared by scheduled-
|
|
8140
|
-
function
|
|
8115
|
+
//#region src/chat/scheduled-automations/telemetry.ts
|
|
8116
|
+
/** Safe structured telemetry attributes for scheduled-automation lifecycle events. */
|
|
8117
|
+
/** Correlation attributes shared by scheduled-automation create and run events. */
|
|
8118
|
+
function scheduledAutomationAttributes(task, extras = {}) {
|
|
8141
8119
|
return {
|
|
8142
8120
|
"app.task.id": task.id,
|
|
8143
8121
|
"app.task.type": "scheduled",
|
|
@@ -8154,9 +8132,9 @@ function scheduledTaskAttributes(task, extras = {}) {
|
|
|
8154
8132
|
...extras
|
|
8155
8133
|
};
|
|
8156
8134
|
}
|
|
8157
|
-
/** Correlation attributes for one scheduled-
|
|
8158
|
-
function
|
|
8159
|
-
return
|
|
8135
|
+
/** Correlation attributes for one scheduled-automation run transition. */
|
|
8136
|
+
function scheduledAutomationRunAttributes(task, run, extras = {}) {
|
|
8137
|
+
return scheduledAutomationAttributes(task, {
|
|
8160
8138
|
"app.task.run.id": run.id,
|
|
8161
8139
|
"app.task.run.status": run.status,
|
|
8162
8140
|
"app.task.run.scheduled_for": new Date(run.scheduledForMs).toISOString(),
|
|
@@ -8165,12 +8143,12 @@ function scheduledTaskRunAttributes(task, run, extras = {}) {
|
|
|
8165
8143
|
...extras
|
|
8166
8144
|
});
|
|
8167
8145
|
}
|
|
8168
|
-
/** Emit the lifecycle event for one skipped scheduled-
|
|
8169
|
-
function
|
|
8170
|
-
logInfo("
|
|
8146
|
+
/** Emit the lifecycle event for one skipped scheduled-automation run. */
|
|
8147
|
+
function logScheduledAutomationRunSkipped(task, run, extras = {}) {
|
|
8148
|
+
logInfo("scheduled_automation.run.skipped", scheduledAutomationRunAttributes(task, run, extras));
|
|
8171
8149
|
}
|
|
8172
8150
|
//#endregion
|
|
8173
|
-
//#region src/chat/scheduled-
|
|
8151
|
+
//#region src/chat/scheduled-automations/identity.ts
|
|
8174
8152
|
const SLACK_USER_ID_PATTERN = /^[UW][A-Z0-9]{5,}$/;
|
|
8175
8153
|
function clean(value) {
|
|
8176
8154
|
const normalized = value?.trim();
|
|
@@ -8186,9 +8164,9 @@ function cleanDisplay(value, slackUserId) {
|
|
|
8186
8164
|
return SLACK_USER_ID_PATTERN.test(normalized) ? void 0 : normalized;
|
|
8187
8165
|
}
|
|
8188
8166
|
/** Keep scheduler creator metadata from promoting Slack IDs into display names. */
|
|
8189
|
-
function
|
|
8167
|
+
function sanitizeScheduledAutomationPrincipal(principal) {
|
|
8190
8168
|
const slackUserId = cleanSlackUserId(principal.slackUserId);
|
|
8191
|
-
if (!slackUserId) throw new Error("Scheduled
|
|
8169
|
+
if (!slackUserId) throw new Error("Scheduled automation principal requires a Slack user id");
|
|
8192
8170
|
const fullName = cleanDisplay(principal.fullName, slackUserId);
|
|
8193
8171
|
const userName = cleanDisplay(principal.userName, slackUserId);
|
|
8194
8172
|
return {
|
|
@@ -8198,28 +8176,32 @@ function sanitizeScheduledTaskPrincipal(principal) {
|
|
|
8198
8176
|
};
|
|
8199
8177
|
}
|
|
8200
8178
|
//#endregion
|
|
8201
|
-
//#region src/chat/scheduled-
|
|
8179
|
+
//#region src/chat/scheduled-automations/tool-support.ts
|
|
8202
8180
|
const TASK_ID_PREFIX = "sched";
|
|
8203
8181
|
const DEFAULT_SCHEDULE_TIMEZONE = "America/Los_Angeles";
|
|
8204
8182
|
const compactTaskResultSchema = z.object({
|
|
8205
8183
|
id: z.string(),
|
|
8184
|
+
title: z.string().nullable(),
|
|
8206
8185
|
status: z.enum([
|
|
8207
8186
|
"active",
|
|
8208
8187
|
"blocked",
|
|
8209
8188
|
"completed",
|
|
8210
8189
|
"deleted"
|
|
8211
8190
|
]),
|
|
8212
|
-
|
|
8191
|
+
statusReason: z.string().nullable(),
|
|
8192
|
+
createdAt: z.string().datetime(),
|
|
8193
|
+
updatedAt: z.string().datetime(),
|
|
8194
|
+
originalRequest: z.string().nullable(),
|
|
8195
|
+
instruction: z.string(),
|
|
8213
8196
|
schedule: z.string(),
|
|
8214
8197
|
timezone: z.string(),
|
|
8215
8198
|
recurrence: z.unknown().nullable(),
|
|
8216
|
-
|
|
8199
|
+
nextRunAt: z.string().nullable(),
|
|
8217
8200
|
destination: z.object({
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
channel_id: z.string().min(1)
|
|
8201
|
+
channel: z.string().min(1),
|
|
8202
|
+
thread: z.string().min(1).nullable()
|
|
8221
8203
|
}).strict(),
|
|
8222
|
-
|
|
8204
|
+
conversationAccess: z.object({
|
|
8223
8205
|
audience: z.enum([
|
|
8224
8206
|
"direct",
|
|
8225
8207
|
"group",
|
|
@@ -8227,22 +8209,29 @@ const compactTaskResultSchema = z.object({
|
|
|
8227
8209
|
]),
|
|
8228
8210
|
visibility: z.enum(["private", "public"])
|
|
8229
8211
|
}).strict(),
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
}).strict()
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8212
|
+
credentialMode: z.enum(["system", "creator"]),
|
|
8213
|
+
isCreator: z.boolean(),
|
|
8214
|
+
createdBy: z.object({
|
|
8215
|
+
name: z.string().min(1).nullable(),
|
|
8216
|
+
username: z.string().min(1).nullable()
|
|
8217
|
+
}).strict(),
|
|
8218
|
+
outcomes: z.array(z.object({
|
|
8219
|
+
action: z.literal("send_message"),
|
|
8220
|
+
destination: z.object({
|
|
8221
|
+
channel: z.string().min(1),
|
|
8222
|
+
thread: z.string().min(1).nullable()
|
|
8223
|
+
}).strict()
|
|
8224
|
+
}).strict()).max(5),
|
|
8225
|
+
dashboardUrl: z.string().url().nullable(),
|
|
8226
|
+
lastRunAt: z.string().nullable(),
|
|
8227
|
+
runNowAt: z.string().nullable()
|
|
8239
8228
|
}).strict();
|
|
8229
|
+
const scheduleAutomationToolResultSchema = juniorToolOutputSchema.extend({ automation: compactTaskResultSchema }).strict();
|
|
8240
8230
|
const scheduleListToolResultSchema = juniorToolOutputSchema.extend({
|
|
8241
|
-
|
|
8242
|
-
tasks: z.array(compactTaskResultSchema),
|
|
8231
|
+
automations: z.array(compactTaskResultSchema),
|
|
8243
8232
|
truncated: z.boolean()
|
|
8244
8233
|
}).strict();
|
|
8245
|
-
/** Normalize scheduled-
|
|
8234
|
+
/** Normalize scheduled-automation validation failures into the core tool contract. */
|
|
8246
8235
|
function throwToolInputError(error) {
|
|
8247
8236
|
throw new ToolInputError(error);
|
|
8248
8237
|
}
|
|
@@ -8271,7 +8260,7 @@ function requireActor(context, destination) {
|
|
|
8271
8260
|
if (context.actor?.platform !== "slack" || context.actor.teamId !== destination.teamId) throwToolInputError("No active Slack actor context is available.");
|
|
8272
8261
|
const userId = context.actor?.userId?.trim();
|
|
8273
8262
|
if (!userId || userId.toLowerCase() === "unknown") throwToolInputError("No active Slack actor context is available.");
|
|
8274
|
-
return
|
|
8263
|
+
return sanitizeScheduledAutomationPrincipal({
|
|
8275
8264
|
slackUserId: userId,
|
|
8276
8265
|
...context.actor?.userName ? { userName: context.actor.userName } : void 0,
|
|
8277
8266
|
...context.actor?.fullName ? { fullName: context.actor.fullName } : void 0
|
|
@@ -8303,54 +8292,65 @@ function sameDestination(task, destination) {
|
|
|
8303
8292
|
/** Look up a mutable task only after enforcing active-conversation ownership. */
|
|
8304
8293
|
async function getWritableTask(args) {
|
|
8305
8294
|
const destination = requireActiveConversation(args.context);
|
|
8306
|
-
const task = await
|
|
8307
|
-
if (!task || task.status === "deleted") throwToolInputError("Scheduled
|
|
8308
|
-
if (!sameDestination(task, destination)) throwToolInputError("Scheduled
|
|
8295
|
+
const task = await readScheduledAutomation(getDb(), args.taskId);
|
|
8296
|
+
if (!task || task.status === "deleted") throwToolInputError("Scheduled automation was not found in the active Slack conversation.");
|
|
8297
|
+
if (!sameDestination(task, destination)) throwToolInputError("Scheduled automation can only be managed from the Slack destination where it currently delivers.");
|
|
8309
8298
|
return task;
|
|
8310
8299
|
}
|
|
8311
|
-
/** Project scheduled
|
|
8312
|
-
function compactTask(task) {
|
|
8300
|
+
/** Project scheduled automation state into the stable model-facing result shape. */
|
|
8301
|
+
function compactTask(task, requesterSlackUserId) {
|
|
8313
8302
|
return compactTaskResultSchema.parse({
|
|
8314
8303
|
id: task.id,
|
|
8304
|
+
title: task.title?.trim() || null,
|
|
8315
8305
|
status: task.status,
|
|
8316
|
-
|
|
8306
|
+
statusReason: task.statusReason ?? null,
|
|
8307
|
+
createdAt: new Date(task.createdAtMs).toISOString(),
|
|
8308
|
+
updatedAt: new Date(task.updatedAtMs).toISOString(),
|
|
8309
|
+
originalRequest: task.originalRequest ?? null,
|
|
8310
|
+
instruction: task.task.text,
|
|
8317
8311
|
schedule: task.schedule.description,
|
|
8318
8312
|
timezone: task.schedule.timezone,
|
|
8319
8313
|
recurrence: task.schedule.recurrence ? {
|
|
8320
8314
|
frequency: task.schedule.recurrence.frequency,
|
|
8321
8315
|
interval: task.schedule.recurrence.interval,
|
|
8322
|
-
|
|
8316
|
+
startDate: task.schedule.recurrence.startDate,
|
|
8323
8317
|
time: task.schedule.recurrence.time,
|
|
8324
8318
|
weekdays: task.schedule.recurrence.weekdays,
|
|
8325
8319
|
month: task.schedule.recurrence.month,
|
|
8326
|
-
|
|
8320
|
+
dayOfMonth: task.schedule.recurrence.dayOfMonth
|
|
8327
8321
|
} : null,
|
|
8328
|
-
|
|
8322
|
+
nextRunAt: task.nextRunAtMs ? new Date(task.nextRunAtMs).toISOString() : null,
|
|
8329
8323
|
destination: {
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
channel_id: task.destination.channelId
|
|
8324
|
+
channel: task.destination.channelId,
|
|
8325
|
+
thread: task.destination.threadTs ?? null
|
|
8333
8326
|
},
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8327
|
+
conversationAccess: task.conversationAccess,
|
|
8328
|
+
credentialMode: task.credentialMode,
|
|
8329
|
+
isCreator: task.createdBy.slackUserId === requesterSlackUserId,
|
|
8330
|
+
createdBy: {
|
|
8331
|
+
name: task.createdBy.fullName ?? null,
|
|
8332
|
+
username: task.createdBy.userName ?? null
|
|
8333
|
+
},
|
|
8334
|
+
outcomes: effectiveTaskOutcomes(task.outcomes, task.destination).map((outcome) => ({
|
|
8335
|
+
action: outcome.action,
|
|
8336
|
+
destination: {
|
|
8337
|
+
channel: outcome.destination.channelId,
|
|
8338
|
+
thread: outcome.destination.threadTs ?? null
|
|
8339
|
+
}
|
|
8340
|
+
})),
|
|
8341
|
+
dashboardUrl: getDashboardTaskLink(task.id) ?? null,
|
|
8342
|
+
lastRunAt: task.lastRunAtMs ? new Date(task.lastRunAtMs).toISOString() : null,
|
|
8343
|
+
runNowAt: task.runNowAtMs ? new Date(task.runNowAtMs).toISOString() : null
|
|
8340
8344
|
});
|
|
8341
8345
|
}
|
|
8342
8346
|
/** Build the structured result shared by single-task scheduler tools. */
|
|
8343
|
-
function
|
|
8344
|
-
return {
|
|
8345
|
-
target,
|
|
8346
|
-
task
|
|
8347
|
-
};
|
|
8347
|
+
function scheduleAutomationToolResult(task, requesterSlackUserId) {
|
|
8348
|
+
return { automation: compactTask(task, requesterSlackUserId) };
|
|
8348
8349
|
}
|
|
8349
8350
|
/** Build the structured result for listing scheduler tools. */
|
|
8350
8351
|
function scheduleListToolResult(args) {
|
|
8351
8352
|
return {
|
|
8352
|
-
|
|
8353
|
-
tasks: args.tasks,
|
|
8353
|
+
automations: args.automations,
|
|
8354
8354
|
truncated: args.truncated
|
|
8355
8355
|
};
|
|
8356
8356
|
}
|
|
@@ -8376,9 +8376,9 @@ function getDefaultScheduleTimezone() {
|
|
|
8376
8376
|
return process.env.JUNIOR_TIMEZONE?.trim() || DEFAULT_SCHEDULE_TIMEZONE;
|
|
8377
8377
|
}
|
|
8378
8378
|
//#endregion
|
|
8379
|
-
//#region src/chat/scheduled-
|
|
8380
|
-
/** Create a tool that stores a scheduled
|
|
8381
|
-
function
|
|
8379
|
+
//#region src/chat/scheduled-automations/tools/create.ts
|
|
8380
|
+
/** Create a tool that stores a scheduled automation for the active Slack context. */
|
|
8381
|
+
function createSlackScheduleCreateAutomationTool(context) {
|
|
8382
8382
|
return zodTool({
|
|
8383
8383
|
approvalMode: "review",
|
|
8384
8384
|
annotations: {
|
|
@@ -8390,19 +8390,19 @@ function createSlackScheduleCreateTaskTool(context) {
|
|
|
8390
8390
|
description: "Create a one-time or recurring Junior task in the active Slack conversation when the user asks Junior to do work later or repeatedly.",
|
|
8391
8391
|
executionMode: "sequential",
|
|
8392
8392
|
inputSchema: z.object({
|
|
8393
|
-
|
|
8393
|
+
instruction: z.string().min(1).max(4e3),
|
|
8394
8394
|
title: z.string().trim().min(1).max(60).nullable().describe("Optional short display title. Omit or use null to generate one from the task instruction.").optional(),
|
|
8395
8395
|
schedule: scheduleIntentSchema.describe("When the task runs. The scheduler computes the exact next run from this intent and the server clock."),
|
|
8396
|
-
outcomes: z.array(taskOutcomeInputSchema).max(5).describe("
|
|
8397
|
-
|
|
8396
|
+
outcomes: z.array(taskOutcomeInputSchema).max(5).describe("Successful work is silent by default. Omit this field or use an empty list when the Automation should act through tools without a status message. Add send_message only when the user asks for a reminder, post, digest, summary, or other visible result.").optional(),
|
|
8397
|
+
credentialMode: z.enum(["creator", "system"]).nullable().describe("Use creator to make the task creator's connected credentials available, or system when the creator says not to use them. Omit or use null for the creator default.").optional()
|
|
8398
8398
|
}).strict(),
|
|
8399
8399
|
prepareArguments(args) {
|
|
8400
8400
|
const prepared = { ...args };
|
|
8401
8401
|
if (prepared.title == null) delete prepared.title;
|
|
8402
|
-
if (prepared.
|
|
8402
|
+
if (prepared.credentialMode === "creator" || prepared.credentialMode === null) delete prepared.credentialMode;
|
|
8403
8403
|
return prepared;
|
|
8404
8404
|
},
|
|
8405
|
-
outputSchema:
|
|
8405
|
+
outputSchema: scheduleAutomationToolResultSchema,
|
|
8406
8406
|
execute: async (input, options) => {
|
|
8407
8407
|
const destination = requireActiveConversation(context);
|
|
8408
8408
|
const actor = requireActor(context, destination);
|
|
@@ -8412,13 +8412,13 @@ function createSlackScheduleCreateTaskTool(context) {
|
|
|
8412
8412
|
toolCallId: options.toolCallId
|
|
8413
8413
|
});
|
|
8414
8414
|
const db = getDb();
|
|
8415
|
-
const existing = await
|
|
8415
|
+
const existing = await readScheduledAutomation(db, id);
|
|
8416
8416
|
if (existing) {
|
|
8417
|
-
if (!sameDestination(existing, destination) || existing.createdBy.slackUserId !== actor.slackUserId) throwToolInputError("Scheduled
|
|
8418
|
-
return
|
|
8417
|
+
if (!sameDestination(existing, destination) || existing.createdBy.slackUserId !== actor.slackUserId) throwToolInputError("Scheduled automation operation identity is invalid.");
|
|
8418
|
+
return scheduleAutomationToolResult(existing, actor.slackUserId);
|
|
8419
8419
|
}
|
|
8420
8420
|
const identity = (await context.users.resolveActor())?.identity;
|
|
8421
|
-
if (!identity || identity.provider !== "slack" || identity.providerTenantId !== destination.teamId || identity.providerSubjectId !== actor.slackUserId) throwToolInputError("Scheduled
|
|
8421
|
+
if (!identity || identity.provider !== "slack" || identity.providerTenantId !== destination.teamId || identity.providerSubjectId !== actor.slackUserId) throwToolInputError("Scheduled automation creator identity is unavailable.");
|
|
8422
8422
|
const nowMs = context.now?.() ?? Date.now();
|
|
8423
8423
|
let compiled;
|
|
8424
8424
|
try {
|
|
@@ -8434,7 +8434,7 @@ function createSlackScheduleCreateTaskTool(context) {
|
|
|
8434
8434
|
const conversationAccess = getConversationAccess(destination, context.source);
|
|
8435
8435
|
const title = await resolveTaskTitle({
|
|
8436
8436
|
completeText,
|
|
8437
|
-
instruction: input.
|
|
8437
|
+
instruction: input.instruction,
|
|
8438
8438
|
title: input.title
|
|
8439
8439
|
});
|
|
8440
8440
|
const task = {
|
|
@@ -8444,28 +8444,28 @@ function createSlackScheduleCreateTaskTool(context) {
|
|
|
8444
8444
|
createdBy: actor,
|
|
8445
8445
|
creatorIdentityId: identity.id,
|
|
8446
8446
|
conversationAccess,
|
|
8447
|
-
credentialMode: input.
|
|
8447
|
+
credentialMode: input.credentialMode ?? "creator",
|
|
8448
8448
|
destination,
|
|
8449
|
-
executionActor:
|
|
8449
|
+
executionActor: SCHEDULED_AUTOMATION_SYSTEM_ACTOR,
|
|
8450
8450
|
nextRunAtMs: compiled.nextRunAtMs,
|
|
8451
8451
|
originalRequest: context.userText,
|
|
8452
8452
|
schedule: compiled.schedule,
|
|
8453
8453
|
status: "active",
|
|
8454
8454
|
outcomes: await resolveTaskOutcomes(input.outcomes, destination, actor.slackUserId),
|
|
8455
|
-
task: { text: input.
|
|
8455
|
+
task: { text: input.instruction },
|
|
8456
8456
|
...title ? { title } : void 0
|
|
8457
8457
|
};
|
|
8458
|
-
const committed = await
|
|
8459
|
-
if (!sameDestination(committed, destination) || committed.createdBy.slackUserId !== actor.slackUserId) throwToolInputError("Scheduled
|
|
8460
|
-
logInfo("
|
|
8461
|
-
return
|
|
8458
|
+
const committed = await createScheduledAutomation(db, task);
|
|
8459
|
+
if (!sameDestination(committed, destination) || committed.createdBy.slackUserId !== actor.slackUserId) throwToolInputError("Scheduled automation operation identity is invalid.");
|
|
8460
|
+
logInfo("scheduled_automation.create.completed", scheduledAutomationAttributes(committed));
|
|
8461
|
+
return scheduleAutomationToolResult(committed, actor.slackUserId);
|
|
8462
8462
|
}
|
|
8463
8463
|
});
|
|
8464
8464
|
}
|
|
8465
8465
|
//#endregion
|
|
8466
|
-
//#region src/chat/scheduled-
|
|
8467
|
-
/** Create a tool that removes a scheduled
|
|
8468
|
-
function
|
|
8466
|
+
//#region src/chat/scheduled-automations/tools/delete.ts
|
|
8467
|
+
/** Create a tool that removes a scheduled automation from the active Slack conversation. */
|
|
8468
|
+
function createSlackScheduleDeleteAutomationTool(context) {
|
|
8469
8469
|
return zodTool({
|
|
8470
8470
|
approvalMode: "review",
|
|
8471
8471
|
annotations: {
|
|
@@ -8476,21 +8476,21 @@ function createSlackScheduleDeleteTaskTool(context) {
|
|
|
8476
8476
|
},
|
|
8477
8477
|
description: "Delete one scheduled Junior task from the active Slack conversation. Use only task IDs returned for this conversation. Do not delete schedules from threads, other channels, or another user's DM.",
|
|
8478
8478
|
executionMode: "sequential",
|
|
8479
|
-
inputSchema: z.object({
|
|
8480
|
-
outputSchema:
|
|
8481
|
-
execute: async ({
|
|
8479
|
+
inputSchema: z.object({ automationId: z.string().min(1).describe("ID of the task to delete. Must be from this active Slack conversation.") }),
|
|
8480
|
+
outputSchema: scheduleAutomationToolResultSchema,
|
|
8481
|
+
execute: async ({ automationId }) => {
|
|
8482
8482
|
const next = {
|
|
8483
8483
|
...await getWritableTask({
|
|
8484
8484
|
context,
|
|
8485
|
-
taskId:
|
|
8485
|
+
taskId: automationId
|
|
8486
8486
|
}),
|
|
8487
8487
|
updatedAtMs: Date.now(),
|
|
8488
8488
|
status: "deleted",
|
|
8489
8489
|
nextRunAtMs: void 0,
|
|
8490
8490
|
runNowAtMs: void 0
|
|
8491
8491
|
};
|
|
8492
|
-
await
|
|
8493
|
-
return
|
|
8492
|
+
await saveScheduledAutomation(getDb(), next);
|
|
8493
|
+
return scheduleAutomationToolResult(next, context.actor?.userId);
|
|
8494
8494
|
}
|
|
8495
8495
|
});
|
|
8496
8496
|
}
|
|
@@ -8529,7 +8529,7 @@ function parseRequiredSlackUserIdParam(field, value) {
|
|
|
8529
8529
|
};
|
|
8530
8530
|
}
|
|
8531
8531
|
//#endregion
|
|
8532
|
-
//#region src/chat/scheduled-
|
|
8532
|
+
//#region src/chat/scheduled-automations/tools/list.ts
|
|
8533
8533
|
function taskMatchesQuery(task, query) {
|
|
8534
8534
|
const normalized = query.toLowerCase();
|
|
8535
8535
|
return [
|
|
@@ -8540,10 +8540,10 @@ function taskMatchesQuery(task, query) {
|
|
|
8540
8540
|
task.status
|
|
8541
8541
|
].some((value) => value?.toLowerCase().includes(normalized));
|
|
8542
8542
|
}
|
|
8543
|
-
/** Create a tool that lists scheduled
|
|
8544
|
-
function
|
|
8543
|
+
/** Create a tool that lists scheduled automations for the active conversation or the requester. */
|
|
8544
|
+
function createSlackScheduleListAutomationsTool(context) {
|
|
8545
8545
|
return zodTool({
|
|
8546
|
-
description: "List scheduled
|
|
8546
|
+
description: "List scheduled automations in this Slack conversation. Each result says whether the requester is the creator. To find one of the requester's automations elsewhere in the workspace, pass channel_id and/or query.",
|
|
8547
8547
|
annotations: {
|
|
8548
8548
|
destructiveHint: false,
|
|
8549
8549
|
idempotentHint: true,
|
|
@@ -8552,7 +8552,7 @@ function createSlackScheduleListTasksTool(context) {
|
|
|
8552
8552
|
},
|
|
8553
8553
|
inputSchema: z.object({
|
|
8554
8554
|
channel_id: slackChannelIdParam("Current destination channel ID to filter by. Use the ID from a channel mention; do not guess.").nullable().optional(),
|
|
8555
|
-
query: z.string().trim().min(1).max(200).nullable().describe("Text to match in the requester's scheduled
|
|
8555
|
+
query: z.string().trim().min(1).max(200).nullable().describe("Text to match in the requester's scheduled automations.").optional()
|
|
8556
8556
|
}).strict(),
|
|
8557
8557
|
outputSchema: scheduleListToolResultSchema,
|
|
8558
8558
|
execute: async (input) => {
|
|
@@ -8566,7 +8566,7 @@ function createSlackScheduleListTasksTool(context) {
|
|
|
8566
8566
|
}
|
|
8567
8567
|
const query = input.query?.trim() || void 0;
|
|
8568
8568
|
const crossConversation = Boolean(query) || channelId !== void 0 && channelId !== destination.channelId;
|
|
8569
|
-
const matching = (await
|
|
8569
|
+
const matching = (await listScheduledAutomationsForTeam(getDb(), destination.teamId)).filter((task) => {
|
|
8570
8570
|
if (task.destination.platform !== "slack") return false;
|
|
8571
8571
|
if (task.destination.teamId !== destination.teamId) return false;
|
|
8572
8572
|
if (task.status === "completed") return false;
|
|
@@ -8576,19 +8576,18 @@ function createSlackScheduleListTasksTool(context) {
|
|
|
8576
8576
|
if (query && !taskMatchesQuery(task, query)) return false;
|
|
8577
8577
|
return true;
|
|
8578
8578
|
}).sort((left, right) => right.createdAtMs - left.createdAtMs || right.id.localeCompare(left.id));
|
|
8579
|
-
const visible = matching.slice(0, 50).map(compactTask);
|
|
8579
|
+
const visible = matching.slice(0, 50).map((task) => compactTask(task, actor.slackUserId));
|
|
8580
8580
|
return scheduleListToolResult({
|
|
8581
|
-
|
|
8582
|
-
tasks: visible,
|
|
8581
|
+
automations: visible,
|
|
8583
8582
|
truncated: matching.length > visible.length
|
|
8584
8583
|
});
|
|
8585
8584
|
}
|
|
8586
8585
|
});
|
|
8587
8586
|
}
|
|
8588
8587
|
//#endregion
|
|
8589
|
-
//#region src/chat/scheduled-
|
|
8590
|
-
/** Create a tool that marks an existing scheduled
|
|
8591
|
-
function
|
|
8588
|
+
//#region src/chat/scheduled-automations/tools/run-now.ts
|
|
8589
|
+
/** Create a tool that marks an existing scheduled automation due immediately. */
|
|
8590
|
+
function createSlackScheduleRunAutomationNowTool(context) {
|
|
8592
8591
|
return zodTool({
|
|
8593
8592
|
approvalMode: "review",
|
|
8594
8593
|
annotations: {
|
|
@@ -8597,31 +8596,31 @@ function createSlackScheduleRunTaskNowTool(context) {
|
|
|
8597
8596
|
openWorldHint: true,
|
|
8598
8597
|
readOnlyHint: false
|
|
8599
8598
|
},
|
|
8600
|
-
description: "Queue an existing active scheduled Junior task to run as soon as possible, without changing its cadence. Use when the user asks to run an existing scheduled
|
|
8599
|
+
description: "Queue an existing active scheduled Junior task to run as soon as possible, without changing its cadence. Use when the user asks to run an existing scheduled automation now. Use only task IDs returned for this conversation.",
|
|
8601
8600
|
executionMode: "sequential",
|
|
8602
|
-
inputSchema: z.object({
|
|
8603
|
-
outputSchema:
|
|
8604
|
-
execute: async ({
|
|
8601
|
+
inputSchema: z.object({ automationId: z.string().min(1).describe("ID of the active task to run now. Must be from this active Slack conversation.") }),
|
|
8602
|
+
outputSchema: scheduleAutomationToolResultSchema,
|
|
8603
|
+
execute: async ({ automationId }) => {
|
|
8605
8604
|
const lookup = await getWritableTask({
|
|
8606
8605
|
context,
|
|
8607
|
-
taskId:
|
|
8606
|
+
taskId: automationId
|
|
8608
8607
|
});
|
|
8609
|
-
if (lookup.status !== "active") throwToolInputError("Scheduled
|
|
8608
|
+
if (lookup.status !== "active") throwToolInputError("Scheduled automation must be active before it can be run now.");
|
|
8610
8609
|
const nowMs = Date.now();
|
|
8611
8610
|
const next = {
|
|
8612
8611
|
...lookup,
|
|
8613
8612
|
updatedAtMs: nowMs,
|
|
8614
8613
|
runNowAtMs: nowMs
|
|
8615
8614
|
};
|
|
8616
|
-
await
|
|
8617
|
-
return
|
|
8615
|
+
await saveScheduledAutomation(getDb(), next);
|
|
8616
|
+
return scheduleAutomationToolResult(next, context.actor?.userId);
|
|
8618
8617
|
}
|
|
8619
8618
|
});
|
|
8620
8619
|
}
|
|
8621
8620
|
//#endregion
|
|
8622
|
-
//#region src/chat/scheduled-
|
|
8623
|
-
/** Create a tool that edits a scheduled
|
|
8624
|
-
function
|
|
8621
|
+
//#region src/chat/scheduled-automations/tools/update.ts
|
|
8622
|
+
/** Create a tool that edits a scheduled automation, including moving it here. */
|
|
8623
|
+
function createSlackScheduleUpdateAutomationTool(context) {
|
|
8625
8624
|
return zodTool({
|
|
8626
8625
|
approvalMode: "review",
|
|
8627
8626
|
annotations: {
|
|
@@ -8630,37 +8629,37 @@ function createSlackScheduleUpdateTaskTool(context) {
|
|
|
8630
8629
|
openWorldHint: true,
|
|
8631
8630
|
readOnlyHint: false
|
|
8632
8631
|
},
|
|
8633
|
-
description: "Update a scheduled
|
|
8632
|
+
description: "Update a scheduled automation. To move the requester's task into this Slack conversation, set destination to \"here\".",
|
|
8634
8633
|
executionMode: "sequential",
|
|
8635
8634
|
inputSchema: z.object({
|
|
8636
|
-
|
|
8637
|
-
|
|
8635
|
+
automationId: z.string().min(1).describe("Scheduled automation ID returned by slackScheduleListAutomations."),
|
|
8636
|
+
instruction: z.string().min(1).max(4e3).optional(),
|
|
8638
8637
|
schedule: scheduleIntentSchema.describe("Complete replacement schedule. Omit to keep it unchanged.").nullable().optional(),
|
|
8639
8638
|
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(),
|
|
8640
8639
|
status: z.enum(["active", "blocked"]).describe("Set active to resume a blocked task, or blocked to stop dispatch.").optional(),
|
|
8641
8640
|
destination: z.literal("here").nullable().describe("Set to \"here\" to move the creator's task into this Slack conversation. Omit to keep its destination.").optional(),
|
|
8642
|
-
|
|
8641
|
+
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()
|
|
8643
8642
|
}).strict(),
|
|
8644
|
-
outputSchema:
|
|
8643
|
+
outputSchema: scheduleAutomationToolResultSchema,
|
|
8645
8644
|
execute: async (input) => {
|
|
8646
8645
|
const activeDestination = requireActiveConversation(context);
|
|
8647
8646
|
const actor = requireActor(context, activeDestination);
|
|
8648
8647
|
const db = getDb();
|
|
8649
|
-
const lookup = await
|
|
8650
|
-
if (!lookup || lookup.status === "deleted") throwToolInputError("Scheduled
|
|
8651
|
-
if (lookup.status === "completed") throwToolInputError("Completed scheduled
|
|
8652
|
-
if (lookup.destination.platform !== "slack") throwToolInputError("Scheduled
|
|
8653
|
-
if (lookup.destination.teamId !== activeDestination.teamId) throwToolInputError("Scheduled
|
|
8648
|
+
const lookup = await readScheduledAutomation(db, input.automationId);
|
|
8649
|
+
if (!lookup || lookup.status === "deleted") throwToolInputError("Scheduled automation was not found.");
|
|
8650
|
+
if (lookup.status === "completed") throwToolInputError("Completed scheduled automations cannot be updated. Create a new automation instead.");
|
|
8651
|
+
if (lookup.destination.platform !== "slack") throwToolInputError("Scheduled automation destination is invalid.");
|
|
8652
|
+
if (lookup.destination.teamId !== activeDestination.teamId) throwToolInputError("Scheduled automations can only be managed within the same Slack workspace.");
|
|
8654
8653
|
const moveHere = input.destination === "here";
|
|
8655
8654
|
const alreadyHere = sameDestination(lookup, activeDestination);
|
|
8656
8655
|
const isCreator = actor.slackUserId === lookup.createdBy.slackUserId;
|
|
8657
|
-
if (!alreadyHere && !moveHere) throwToolInputError("Scheduled
|
|
8658
|
-
if (moveHere && !isCreator) throwToolInputError("Only the scheduled
|
|
8659
|
-
if (input.
|
|
8660
|
-
if (input.outcomes !== void 0 && !isCreator) throwToolInputError("Only the scheduled
|
|
8656
|
+
if (!alreadyHere && !moveHere) throwToolInputError("Scheduled automation can only be managed from the Slack destination where it currently delivers. Set destination to \"here\" to move it into this conversation.");
|
|
8657
|
+
if (moveHere && !isCreator) throwToolInputError("Only the scheduled automation creator can move this task.");
|
|
8658
|
+
if (input.credentialMode === "creator" && !isCreator) throwToolInputError("Only the scheduled automation creator can enable creator credential use.");
|
|
8659
|
+
if (input.outcomes !== void 0 && !isCreator) throwToolInputError("Only the scheduled automation creator can change message destinations.");
|
|
8661
8660
|
const changingDestination = moveHere && !alreadyHere;
|
|
8662
8661
|
if (changingDestination) {
|
|
8663
|
-
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
|
|
8662
|
+
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.");
|
|
8664
8663
|
}
|
|
8665
8664
|
const nowMs = context.now?.() ?? Date.now();
|
|
8666
8665
|
let compiled;
|
|
@@ -8677,10 +8676,10 @@ function createSlackScheduleUpdateTaskTool(context) {
|
|
|
8677
8676
|
const nextRunAtMs = compiled?.nextRunAtMs ?? lookup.nextRunAtMs;
|
|
8678
8677
|
const status = normalizeStatus(input.status);
|
|
8679
8678
|
if (input.status && !status) throwToolInputError("status must be active or blocked.");
|
|
8680
|
-
if (status === "active" && !nextRunAtMs) throwToolInputError("Active scheduled
|
|
8679
|
+
if (status === "active" && !nextRunAtMs) throwToolInputError("Active scheduled automations require a schedule with a future occurrence.");
|
|
8681
8680
|
const nextStatus = status ?? lookup.status;
|
|
8682
|
-
const credentialMode = input.
|
|
8683
|
-
const nextInstruction = input.
|
|
8681
|
+
const credentialMode = input.instruction !== void 0 && input.instruction !== lookup.task.text && !isCreator ? "system" : input.credentialMode ?? lookup.credentialMode;
|
|
8682
|
+
const nextInstruction = input.instruction !== void 0 ? input.instruction : lookup.task.text;
|
|
8684
8683
|
const instructionChanged = nextInstruction !== lookup.task.text;
|
|
8685
8684
|
const nextDestination = changingDestination ? activeDestination : lookup.destination;
|
|
8686
8685
|
const next = {
|
|
@@ -8706,16 +8705,16 @@ function createSlackScheduleUpdateTaskTool(context) {
|
|
|
8706
8705
|
if (title) next.title = title;
|
|
8707
8706
|
else delete next.title;
|
|
8708
8707
|
}
|
|
8709
|
-
if (!changingDestination && !instructionChanged && !compiled && status === void 0 && (input.
|
|
8710
|
-
const committed = await
|
|
8711
|
-
if (changingDestination) logInfo("
|
|
8712
|
-
return
|
|
8708
|
+
if (!changingDestination && !instructionChanged && !compiled && status === void 0 && (input.credentialMode === void 0 || input.credentialMode === null || input.credentialMode === lookup.credentialMode) && input.outcomes === void 0 && moveHere) return scheduleAutomationToolResult(lookup, actor.slackUserId);
|
|
8709
|
+
const committed = await saveScheduledAutomation(db, next);
|
|
8710
|
+
if (changingDestination) logInfo("scheduled_automation.move.completed", scheduledAutomationAttributes(committed));
|
|
8711
|
+
return scheduleAutomationToolResult(committed, actor.slackUserId);
|
|
8713
8712
|
}
|
|
8714
8713
|
});
|
|
8715
8714
|
}
|
|
8716
8715
|
//#endregion
|
|
8717
|
-
//#region src/chat/tools/scheduled-
|
|
8718
|
-
function
|
|
8716
|
+
//#region src/chat/tools/scheduled-automations.ts
|
|
8717
|
+
function scheduledAutomationToolContext(context) {
|
|
8719
8718
|
if (context.source.kind !== "slack" || context.destination.platform !== "slack" || context.actor?.platform !== "slack" || !context.resolveActorIdentity) return;
|
|
8720
8719
|
return {
|
|
8721
8720
|
actor: context.actor,
|
|
@@ -8724,16 +8723,16 @@ function scheduledTaskToolContext(context) {
|
|
|
8724
8723
|
...context.userText ? { userText: context.userText } : void 0
|
|
8725
8724
|
};
|
|
8726
8725
|
}
|
|
8727
|
-
/** Build scheduled-
|
|
8728
|
-
function
|
|
8729
|
-
const taskContext =
|
|
8726
|
+
/** Build scheduled-automation tools for an interactive Slack actor. */
|
|
8727
|
+
function createScheduledAutomationTools(context) {
|
|
8728
|
+
const taskContext = scheduledAutomationToolContext(context);
|
|
8730
8729
|
if (!taskContext) return {};
|
|
8731
8730
|
return {
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8731
|
+
slackScheduleCreateAutomation: createSlackScheduleCreateAutomationTool(taskContext),
|
|
8732
|
+
slackScheduleListAutomations: createSlackScheduleListAutomationsTool(taskContext),
|
|
8733
|
+
slackScheduleUpdateAutomation: createSlackScheduleUpdateAutomationTool(taskContext),
|
|
8734
|
+
slackScheduleDeleteAutomation: createSlackScheduleDeleteAutomationTool(taskContext),
|
|
8735
|
+
slackScheduleRunAutomationNow: createSlackScheduleRunAutomationNowTool(taskContext)
|
|
8737
8736
|
};
|
|
8738
8737
|
}
|
|
8739
8738
|
//#endregion
|
|
@@ -9147,6 +9146,34 @@ function createSlackChannelListMessagesTool(context) {
|
|
|
9147
9146
|
});
|
|
9148
9147
|
}
|
|
9149
9148
|
//#endregion
|
|
9149
|
+
//#region src/chat/slack/tools/conversation-brief-search.ts
|
|
9150
|
+
/** Create Slack-owned filtering and match details for Brief search. */
|
|
9151
|
+
function createSlackConversationBriefSearchPort(teamId) {
|
|
9152
|
+
return {
|
|
9153
|
+
channelFilterSchema: slackChannelRefParam,
|
|
9154
|
+
async resolveChannel(input) {
|
|
9155
|
+
return (await resolveSlackChannelRef({
|
|
9156
|
+
field: "channel_id",
|
|
9157
|
+
value: input,
|
|
9158
|
+
teamId
|
|
9159
|
+
})).channelId;
|
|
9160
|
+
},
|
|
9161
|
+
async describeMatch(match) {
|
|
9162
|
+
const reference = parseSlackThreadId(match.conversationId);
|
|
9163
|
+
if (!reference || reference.channelId !== match.providerDestinationId) return {};
|
|
9164
|
+
const permalink = await getSlackMessagePermalink({
|
|
9165
|
+
channelId: reference.channelId,
|
|
9166
|
+
messageTs: reference.threadTs
|
|
9167
|
+
});
|
|
9168
|
+
return {
|
|
9169
|
+
channel_id: reference.channelId,
|
|
9170
|
+
...match.channelName ? { channel_name: match.channelName } : void 0,
|
|
9171
|
+
...permalink ? { permalink } : void 0
|
|
9172
|
+
};
|
|
9173
|
+
}
|
|
9174
|
+
};
|
|
9175
|
+
}
|
|
9176
|
+
//#endregion
|
|
9150
9177
|
//#region src/chat/conversations/tool-source.ts
|
|
9151
9178
|
/** Deferred tool catalog source for retained Junior conversations. */
|
|
9152
9179
|
const CONVERSATIONS_TOOL_SOURCE = {
|
|
@@ -9155,8 +9182,8 @@ const CONVERSATIONS_TOOL_SOURCE = {
|
|
|
9155
9182
|
};
|
|
9156
9183
|
//#endregion
|
|
9157
9184
|
//#region src/chat/slack/tools/conversation-message-search.ts
|
|
9158
|
-
const DEFAULT_LIMIT$
|
|
9159
|
-
const MAX_LIMIT$
|
|
9185
|
+
const DEFAULT_LIMIT$3 = 20;
|
|
9186
|
+
const MAX_LIMIT$2 = 50;
|
|
9160
9187
|
const conversationMessageSearchOutputSchema = juniorToolOutputSchema.extend({
|
|
9161
9188
|
after: z.string().datetime().optional(),
|
|
9162
9189
|
annotation: z.string().min(1).optional(),
|
|
@@ -9176,17 +9203,17 @@ const conversationMessageSearchOutputSchema = juniorToolOutputSchema.extend({
|
|
|
9176
9203
|
thread_ts: z.string().min(1)
|
|
9177
9204
|
}).strict())
|
|
9178
9205
|
});
|
|
9179
|
-
function parseTimestamp(field, value) {
|
|
9206
|
+
function parseTimestamp$1(field, value) {
|
|
9180
9207
|
if (value == null || value.trim() === "") return;
|
|
9181
9208
|
const parsed = Date.parse(value);
|
|
9182
9209
|
if (!Number.isFinite(parsed)) throw new ToolInputError(`${field} must be a valid ISO-8601 timestamp`);
|
|
9183
9210
|
return parsed;
|
|
9184
9211
|
}
|
|
9185
|
-
async function resolveSearchFilters(input) {
|
|
9212
|
+
async function resolveSearchFilters$1(input) {
|
|
9186
9213
|
const query = input.query?.trim() || void 0;
|
|
9187
9214
|
const annotation = input.annotation?.trim() || void 0;
|
|
9188
|
-
const afterMs = parseTimestamp("after", input.after);
|
|
9189
|
-
const beforeMs = parseTimestamp("before", input.before);
|
|
9215
|
+
const afterMs = parseTimestamp$1("after", input.after);
|
|
9216
|
+
const beforeMs = parseTimestamp$1("before", input.before);
|
|
9190
9217
|
let channelId;
|
|
9191
9218
|
if (input.channel_id != null && input.channel_id.trim() !== "") channelId = (await resolveSlackChannelRef({
|
|
9192
9219
|
field: "channel_id",
|
|
@@ -9221,20 +9248,20 @@ function createSlackConversationMessageSearchTool(scope, currentConversationId)
|
|
|
9221
9248
|
before: z.string().datetime().nullable().describe("Include conversations active before this timestamp.").optional(),
|
|
9222
9249
|
channel_id: slackChannelRefParam.nullable().optional(),
|
|
9223
9250
|
query: z.string().trim().min(1).max(200).nullable().describe("Words or phrase to match in message text.").optional(),
|
|
9224
|
-
limit: z.number().int().min(1).max(MAX_LIMIT$
|
|
9251
|
+
limit: z.number().int().min(1).max(MAX_LIMIT$2).describe(`Maximum conversations. Default ${DEFAULT_LIMIT$3}; max ${MAX_LIMIT$2}.`).nullable().optional()
|
|
9225
9252
|
}).strict(),
|
|
9226
9253
|
outputSchema: conversationMessageSearchOutputSchema,
|
|
9227
9254
|
execute: async (input) => {
|
|
9228
9255
|
const teamId = parseSlackTeamId(scope.providerTenantId);
|
|
9229
9256
|
if (!teamId) throw new ToolInputError("Cannot search retained messages without a valid Slack workspace id.");
|
|
9230
|
-
const filters = await resolveSearchFilters({
|
|
9257
|
+
const filters = await resolveSearchFilters$1({
|
|
9231
9258
|
...input,
|
|
9232
9259
|
teamId
|
|
9233
9260
|
});
|
|
9234
9261
|
const matches = await getConversationMessageSearchStore().search({
|
|
9235
9262
|
currentConversationId,
|
|
9236
9263
|
filters,
|
|
9237
|
-
limit: input.limit ?? DEFAULT_LIMIT$
|
|
9264
|
+
limit: input.limit ?? DEFAULT_LIMIT$3,
|
|
9238
9265
|
scope
|
|
9239
9266
|
});
|
|
9240
9267
|
const matchesOutput = await Promise.all(matches.map(async (match) => {
|
|
@@ -9270,7 +9297,7 @@ function createSlackConversationMessageSearchTool(scope, currentConversationId)
|
|
|
9270
9297
|
}
|
|
9271
9298
|
//#endregion
|
|
9272
9299
|
//#region src/chat/slack/tools/public-search.ts
|
|
9273
|
-
const DEFAULT_LIMIT$
|
|
9300
|
+
const DEFAULT_LIMIT$2 = 10;
|
|
9274
9301
|
const DEFAULT_CONTENT_TYPES = ["messages"];
|
|
9275
9302
|
const CONTENT_TYPES = [
|
|
9276
9303
|
"messages",
|
|
@@ -9533,7 +9560,7 @@ function createSlackPublicSearchTool(actionToken) {
|
|
|
9533
9560
|
channel_types: ["public_channel"],
|
|
9534
9561
|
content_types: normalizedContentTypes,
|
|
9535
9562
|
include_bots: true,
|
|
9536
|
-
limit: limit ?? DEFAULT_LIMIT$
|
|
9563
|
+
limit: limit ?? DEFAULT_LIMIT$2,
|
|
9537
9564
|
...normalizedAfter !== void 0 ? { after: normalizedAfter } : void 0,
|
|
9538
9565
|
...normalizedBefore !== void 0 ? { before: normalizedBefore } : void 0,
|
|
9539
9566
|
...cursor ? { cursor } : void 0,
|
|
@@ -11709,8 +11736,8 @@ function createLoadAttachmentTool(args) {
|
|
|
11709
11736
|
}
|
|
11710
11737
|
//#endregion
|
|
11711
11738
|
//#region src/chat/tools/search-conversation-events.ts
|
|
11712
|
-
const DEFAULT_LIMIT = 25;
|
|
11713
|
-
const MAX_LIMIT = 100;
|
|
11739
|
+
const DEFAULT_LIMIT$1 = 25;
|
|
11740
|
+
const MAX_LIMIT$1 = 100;
|
|
11714
11741
|
const MAX_EVENT_DATA_BYTES = 4e3;
|
|
11715
11742
|
const MAX_EVENTS_JSON_BYTES = 2e4;
|
|
11716
11743
|
const MAX_IDENTITY_CHARS = 256;
|
|
@@ -11746,7 +11773,7 @@ function createSearchConversationEventsTool(context) {
|
|
|
11746
11773
|
conversation_id: z.string().trim().min(1).nullable().describe("Conversation ID to search. Omit or null for the current conversation.").optional(),
|
|
11747
11774
|
after_seq: z.number().int().nonnegative().nullable().describe("Exclusive event sequence lower bound; returns ascending.").optional(),
|
|
11748
11775
|
before_seq: z.number().int().nonnegative().nullable().describe("Exclusive event sequence upper bound.").optional(),
|
|
11749
|
-
limit: z.number().int().min(1).max(MAX_LIMIT).nullable().describe(`Maximum events. Default ${DEFAULT_LIMIT}; max ${MAX_LIMIT}.`).optional(),
|
|
11776
|
+
limit: z.number().int().min(1).max(MAX_LIMIT$1).nullable().describe(`Maximum events. Default ${DEFAULT_LIMIT$1}; max ${MAX_LIMIT$1}.`).optional(),
|
|
11750
11777
|
types: z.array(knownEventTypeSchema).min(1).nullable().describe("Event types to include; omit for all types.").optional()
|
|
11751
11778
|
}).strict(),
|
|
11752
11779
|
outputSchema: searchConversationEventsOutputSchema,
|
|
@@ -11755,7 +11782,7 @@ function createSearchConversationEventsTool(context) {
|
|
|
11755
11782
|
const conversationId = input.conversation_id?.trim() || currentConversationId;
|
|
11756
11783
|
const afterSeq = input.after_seq ?? void 0;
|
|
11757
11784
|
const beforeSeq = input.before_seq ?? void 0;
|
|
11758
|
-
const limit = input.limit ?? DEFAULT_LIMIT;
|
|
11785
|
+
const limit = input.limit ?? DEFAULT_LIMIT$1;
|
|
11759
11786
|
const types = input.types ?? void 0;
|
|
11760
11787
|
if (afterSeq !== void 0 && beforeSeq !== void 0 && afterSeq >= beforeSeq) throw new ToolInputError("after_seq must be less than before_seq when both are provided");
|
|
11761
11788
|
const conversationStore = getConversationStore();
|
|
@@ -11919,6 +11946,183 @@ function stripReplacementHistoryField(value) {
|
|
|
11919
11946
|
};
|
|
11920
11947
|
}
|
|
11921
11948
|
//#endregion
|
|
11949
|
+
//#region src/chat/briefs/search.ts
|
|
11950
|
+
/** Search the latest Brief of each public root Conversation in one scope. */
|
|
11951
|
+
async function searchConversationBriefs(db, args) {
|
|
11952
|
+
const query = args.filters.query?.trim() || void 0;
|
|
11953
|
+
const tsquery = query ? sql`websearch_to_tsquery('english', ${query})` : void 0;
|
|
11954
|
+
const briefs = juniorConversationBriefs;
|
|
11955
|
+
const newer = alias(juniorConversationBriefs, "newer_briefs");
|
|
11956
|
+
const searchVector = sql`to_tsvector('english', ${briefs.searchText})`;
|
|
11957
|
+
const rank = tsquery ? sql`ts_rank_cd(${searchVector}, ${tsquery})` : sql`1`;
|
|
11958
|
+
const excerpt = tsquery ? sql`ts_headline('english', ${briefs.searchText}, ${tsquery}, 'MaxFragments=2, MinWords=8, MaxWords=40, FragmentDelimiter=" … ", StartSel=**, StopSel=**')` : sql`${briefs.content}->>'summary'`;
|
|
11959
|
+
const outcomeStatus = sql`${briefs.content}->'outcome'->>'status'`;
|
|
11960
|
+
const conditions = [
|
|
11961
|
+
notExists(db.select({ one: sql`1` }).from(newer).where(and(eq(newer.conversationId, briefs.conversationId), gt(newer.version, briefs.version)))),
|
|
11962
|
+
isNull(juniorConversations.parentConversationId),
|
|
11963
|
+
ne(juniorConversations.conversationId, args.currentConversationId),
|
|
11964
|
+
eq(juniorDestinations.visibility, "public")
|
|
11965
|
+
];
|
|
11966
|
+
if (args.scope.kind === "public_provider_tenant") conditions.push(eq(juniorConversations.source, args.scope.provider), eq(juniorDestinations.provider, args.scope.provider), eq(juniorDestinations.providerTenantId, args.scope.providerTenantId));
|
|
11967
|
+
if (tsquery) conditions.push(sql`${searchVector} @@ ${tsquery}`);
|
|
11968
|
+
if (args.filters.channelId) conditions.push(eq(juniorDestinations.providerDestinationId, args.filters.channelId));
|
|
11969
|
+
if (args.filters.status) conditions.push(sql`${outcomeStatus} = ${args.filters.status}`);
|
|
11970
|
+
if (args.filters.afterMs !== void 0) conditions.push(gte(briefs.createdAt, new Date(args.filters.afterMs)));
|
|
11971
|
+
if (args.filters.beforeMs !== void 0) conditions.push(lt(briefs.createdAt, new Date(args.filters.beforeMs)));
|
|
11972
|
+
if (args.filters.annotation) {
|
|
11973
|
+
const annotation = args.filters.annotation.toLowerCase();
|
|
11974
|
+
const nestedAnnotationPrefix = `${annotation}#`;
|
|
11975
|
+
conditions.push(sql`exists (
|
|
11976
|
+
select 1
|
|
11977
|
+
from ${juniorConversationAnnotations}
|
|
11978
|
+
where ${and(eq(juniorConversationAnnotations.conversationId, juniorConversations.conversationId), sql`(
|
|
11979
|
+
lower(${juniorConversationAnnotations.key}) = ${annotation}
|
|
11980
|
+
or starts_with(
|
|
11981
|
+
lower(${juniorConversationAnnotations.key}),
|
|
11982
|
+
${nestedAnnotationPrefix}
|
|
11983
|
+
)
|
|
11984
|
+
)`)}
|
|
11985
|
+
)`);
|
|
11986
|
+
}
|
|
11987
|
+
return (await db.select({
|
|
11988
|
+
channelName: juniorDestinations.displayName,
|
|
11989
|
+
content: briefs.content,
|
|
11990
|
+
conversationId: juniorConversations.conversationId,
|
|
11991
|
+
excerpt: excerpt.as("excerpt"),
|
|
11992
|
+
providerDestinationId: juniorDestinations.providerDestinationId,
|
|
11993
|
+
rank: rank.as("rank"),
|
|
11994
|
+
title: juniorConversations.title,
|
|
11995
|
+
updatedAt: briefs.createdAt,
|
|
11996
|
+
version: briefs.version
|
|
11997
|
+
}).from(briefs).innerJoin(juniorConversations, eq(juniorConversations.conversationId, briefs.conversationId)).innerJoin(juniorDestinations, eq(juniorDestinations.id, juniorConversations.destinationId)).where(and(...conditions)).orderBy(desc(rank), desc(briefs.createdAt)).limit(args.limit)).map((row) => {
|
|
11998
|
+
const content = conversationBriefSchema.parse(row.content);
|
|
11999
|
+
return {
|
|
12000
|
+
conversationId: row.conversationId,
|
|
12001
|
+
excerpt: row.excerpt,
|
|
12002
|
+
links: content.links.slice(0, 5),
|
|
12003
|
+
outcomeStatus: content.outcome.status,
|
|
12004
|
+
providerDestinationId: row.providerDestinationId,
|
|
12005
|
+
summary: content.summary,
|
|
12006
|
+
updatedAtMs: row.updatedAt.getTime(),
|
|
12007
|
+
version: row.version,
|
|
12008
|
+
...row.channelName ? { channelName: row.channelName } : void 0,
|
|
12009
|
+
...row.title ? { title: row.title } : void 0
|
|
12010
|
+
};
|
|
12011
|
+
});
|
|
12012
|
+
}
|
|
12013
|
+
//#endregion
|
|
12014
|
+
//#region src/chat/tools/search-conversation-briefs.ts
|
|
12015
|
+
const DEFAULT_LIMIT = 10;
|
|
12016
|
+
const MAX_LIMIT = 25;
|
|
12017
|
+
const conversationBriefSearchOutputSchema = juniorToolOutputSchema.extend({
|
|
12018
|
+
after: z.string().datetime().optional(),
|
|
12019
|
+
annotation: z.string().min(1).optional(),
|
|
12020
|
+
before: z.string().datetime().optional(),
|
|
12021
|
+
channel_id: z.string().min(1).optional(),
|
|
12022
|
+
count: z.number().int().nonnegative(),
|
|
12023
|
+
matches: z.array(z.object({
|
|
12024
|
+
channel_id: z.string().min(1).optional(),
|
|
12025
|
+
channel_name: z.string().min(1).optional(),
|
|
12026
|
+
conversation_id: z.string().min(1),
|
|
12027
|
+
dashboard_url: z.string().url().optional(),
|
|
12028
|
+
excerpt: z.string(),
|
|
12029
|
+
links: z.array(briefLinkSchema).max(5),
|
|
12030
|
+
permalink: z.string().url().optional(),
|
|
12031
|
+
status: briefOutcomeStatusSchema,
|
|
12032
|
+
summary: z.string().min(1),
|
|
12033
|
+
title: z.string().min(1).optional(),
|
|
12034
|
+
updated_at: z.string().datetime(),
|
|
12035
|
+
version: z.number().int().positive()
|
|
12036
|
+
}).strict()),
|
|
12037
|
+
query: z.string().optional(),
|
|
12038
|
+
status: briefOutcomeStatusSchema.optional()
|
|
12039
|
+
});
|
|
12040
|
+
function parseTimestamp(field, value) {
|
|
12041
|
+
if (value == null || value.trim() === "") return void 0;
|
|
12042
|
+
const parsed = Date.parse(value);
|
|
12043
|
+
if (!Number.isFinite(parsed)) throw new ToolInputError(`${field} must be a valid ISO-8601 timestamp`);
|
|
12044
|
+
return parsed;
|
|
12045
|
+
}
|
|
12046
|
+
async function resolveSearchFilters(input, provider) {
|
|
12047
|
+
const query = input.query?.trim() || void 0;
|
|
12048
|
+
const annotation = input.annotation?.trim() || void 0;
|
|
12049
|
+
const afterMs = parseTimestamp("after", input.after);
|
|
12050
|
+
const beforeMs = parseTimestamp("before", input.before);
|
|
12051
|
+
let channelId;
|
|
12052
|
+
if (typeof input.channel_id === "string" && input.channel_id.trim() !== "") {
|
|
12053
|
+
if (!provider) throw new ToolInputError("channel_id is not available in this conversation.");
|
|
12054
|
+
channelId = await provider.resolveChannel(input.channel_id);
|
|
12055
|
+
}
|
|
12056
|
+
if (afterMs !== void 0 && beforeMs !== void 0 && afterMs >= beforeMs) throw new ToolInputError("`after` must be earlier than `before`");
|
|
12057
|
+
return {
|
|
12058
|
+
...afterMs !== void 0 ? { afterMs } : void 0,
|
|
12059
|
+
...annotation ? { annotation } : void 0,
|
|
12060
|
+
...beforeMs !== void 0 ? { beforeMs } : void 0,
|
|
12061
|
+
...channelId ? { channelId } : void 0,
|
|
12062
|
+
...query ? { query } : void 0,
|
|
12063
|
+
...input.status ? { status: input.status } : void 0
|
|
12064
|
+
};
|
|
12065
|
+
}
|
|
12066
|
+
/** Create a deferred tool that searches the latest public Conversation Briefs. */
|
|
12067
|
+
function createSearchConversationBriefsTool(scope, currentConversationId, provider) {
|
|
12068
|
+
return zodTool({
|
|
12069
|
+
description: "Search Briefs from past public conversations. Briefs preserve intent, outcomes, decisions, facts, and links after transcripts expire. Use this before searchConversationMessages for questions such as what was decided or which pull request handled the work.",
|
|
12070
|
+
exposure: "deferred",
|
|
12071
|
+
source: CONVERSATIONS_TOOL_SOURCE,
|
|
12072
|
+
annotations: {
|
|
12073
|
+
destructiveHint: false,
|
|
12074
|
+
idempotentHint: true,
|
|
12075
|
+
openWorldHint: false,
|
|
12076
|
+
readOnlyHint: true
|
|
12077
|
+
},
|
|
12078
|
+
inputSchema: z.object({
|
|
12079
|
+
after: z.string().datetime().nullable().describe("Include Briefs updated at or after this timestamp.").optional(),
|
|
12080
|
+
annotation: z.string().trim().min(1).max(256).nullable().describe("Linked resource key. Matches that key, or nested children that continue with #.").optional(),
|
|
12081
|
+
before: z.string().datetime().nullable().describe("Include Briefs updated before this timestamp.").optional(),
|
|
12082
|
+
...provider ? { channel_id: provider.channelFilterSchema.nullable().optional() } : void 0,
|
|
12083
|
+
limit: z.number().int().min(1).max(MAX_LIMIT).nullable().describe(`Maximum conversations. Default ${DEFAULT_LIMIT}; max ${MAX_LIMIT}.`).optional(),
|
|
12084
|
+
query: z.string().trim().min(1).max(200).nullable().describe("Words or phrase to match in current Briefs.").optional(),
|
|
12085
|
+
status: briefOutcomeStatusSchema.nullable().describe("Current Brief outcome status.").optional()
|
|
12086
|
+
}).strict(),
|
|
12087
|
+
outputSchema: conversationBriefSearchOutputSchema,
|
|
12088
|
+
execute: async (input) => {
|
|
12089
|
+
const filters = await resolveSearchFilters(input, provider);
|
|
12090
|
+
const matches = await searchConversationBriefs(getDb(), {
|
|
12091
|
+
currentConversationId,
|
|
12092
|
+
filters,
|
|
12093
|
+
limit: input.limit ?? DEFAULT_LIMIT,
|
|
12094
|
+
scope
|
|
12095
|
+
});
|
|
12096
|
+
const outputMatches = await Promise.all(matches.map(async (match) => {
|
|
12097
|
+
const providerFields = provider ? await provider.describeMatch(match) : void 0;
|
|
12098
|
+
const dashboardUrl = getDashboardConversationLink(match.conversationId);
|
|
12099
|
+
return {
|
|
12100
|
+
conversation_id: match.conversationId,
|
|
12101
|
+
status: match.outcomeStatus,
|
|
12102
|
+
updated_at: new Date(match.updatedAtMs).toISOString(),
|
|
12103
|
+
version: match.version,
|
|
12104
|
+
summary: match.summary,
|
|
12105
|
+
excerpt: match.excerpt,
|
|
12106
|
+
links: match.links,
|
|
12107
|
+
...match.title ? { title: match.title } : void 0,
|
|
12108
|
+
...dashboardUrl ? { dashboard_url: dashboardUrl } : void 0,
|
|
12109
|
+
...providerFields
|
|
12110
|
+
};
|
|
12111
|
+
}));
|
|
12112
|
+
return {
|
|
12113
|
+
...filters.afterMs !== void 0 ? { after: new Date(filters.afterMs).toISOString() } : void 0,
|
|
12114
|
+
...filters.annotation ? { annotation: filters.annotation } : void 0,
|
|
12115
|
+
...filters.beforeMs !== void 0 ? { before: new Date(filters.beforeMs).toISOString() } : void 0,
|
|
12116
|
+
...filters.channelId ? { channel_id: filters.channelId } : void 0,
|
|
12117
|
+
...filters.query ? { query: filters.query } : void 0,
|
|
12118
|
+
...filters.status ? { status: filters.status } : void 0,
|
|
12119
|
+
count: outputMatches.length,
|
|
12120
|
+
matches: outputMatches
|
|
12121
|
+
};
|
|
12122
|
+
}
|
|
12123
|
+
});
|
|
12124
|
+
}
|
|
12125
|
+
//#endregion
|
|
11922
12126
|
//#region src/chat/tools/handoff/tool.ts
|
|
11923
12127
|
const HANDOFF_TOOL_NAME = "handoff";
|
|
11924
12128
|
function formatHandoffProfiles(handoff) {
|
|
@@ -12595,7 +12799,7 @@ async function tryRecordWorkspaceSwitchStat(workspace) {
|
|
|
12595
12799
|
}
|
|
12596
12800
|
async function stopWorkspaceSnapshotWatch(input) {
|
|
12597
12801
|
try {
|
|
12598
|
-
await
|
|
12802
|
+
await cancelWatch({
|
|
12599
12803
|
conversationId: input.conversationId,
|
|
12600
12804
|
id: input.subscriptionId
|
|
12601
12805
|
});
|
|
@@ -12792,7 +12996,7 @@ function createWorkspaceTools(context) {
|
|
|
12792
12996
|
outputSchema: juniorToolOutputSchema.extend({
|
|
12793
12997
|
workspace: workspaceSchema,
|
|
12794
12998
|
status: z.enum(["ready", "building"]),
|
|
12795
|
-
subscription:
|
|
12999
|
+
subscription: watchResultSchema.optional().describe("Present when the snapshot ready and failed events are already watched for this conversation.")
|
|
12796
13000
|
}),
|
|
12797
13001
|
async execute({ name }, options) {
|
|
12798
13002
|
const workspace = await getWorkspaceByName(getDb(), name);
|
|
@@ -12802,7 +13006,7 @@ function createWorkspaceTools(context) {
|
|
|
12802
13006
|
workspaceName: workspace.name
|
|
12803
13007
|
});
|
|
12804
13008
|
const conversationId = context.conversationId.trim();
|
|
12805
|
-
const subscription = conversationId ? await
|
|
13009
|
+
const subscription = conversationId ? await createWatch({
|
|
12806
13010
|
conversationId,
|
|
12807
13011
|
events: [WORKSPACE_SNAPSHOT_READY_EVENT, WORKSPACE_SNAPSHOT_FAILED_EVENT],
|
|
12808
13012
|
expiresAtMs: Date.now() + RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS,
|
|
@@ -12867,7 +13071,7 @@ function createTools(availableSkills, hooks = {}, context, options = {}) {
|
|
|
12867
13071
|
const slackContext = getSlackToolContext(context);
|
|
12868
13072
|
const slackLocationCapabilities = slackContext ? resolveChannelCapabilities(slackContext.locationChannelId) : void 0;
|
|
12869
13073
|
const canSendFilesToActiveConversation = Boolean(slackContext && slackLocationCapabilities?.canSendFiles);
|
|
12870
|
-
const
|
|
13074
|
+
const eventCatalog = getEventCatalog();
|
|
12871
13075
|
const tools = {
|
|
12872
13076
|
...options.includeLoadSkill === false ? void 0 : { loadSkill: createLoadSkillTool(availableSkills, { onSkillLoaded: hooks.onSkillLoaded }) },
|
|
12873
13077
|
reportProgress: createReportProgressTool(),
|
|
@@ -12881,12 +13085,17 @@ function createTools(availableSkills, hooks = {}, context, options = {}) {
|
|
|
12881
13085
|
writeFile: createWriteFileTool(),
|
|
12882
13086
|
webSearch: createWebSearchTool(botConfig.webSearchModelId, hooks.toolOverrides?.webSearch),
|
|
12883
13087
|
webFetch: createWebFetchTool(hooks, { canSendFilesToActiveConversation }),
|
|
12884
|
-
...
|
|
12885
|
-
...
|
|
12886
|
-
...
|
|
13088
|
+
...createEventTools(context, eventCatalog),
|
|
13089
|
+
...createEventAutomationTools(context, eventCatalog),
|
|
13090
|
+
...createScheduledAutomationTools(context),
|
|
12887
13091
|
...createWorkspaceTools(context)
|
|
12888
13092
|
};
|
|
12889
13093
|
tools.searchConversationEvents = createSearchConversationEventsTool(context);
|
|
13094
|
+
if (context.conversationPrivacy === "public") tools.searchConversationBriefs = createSearchConversationBriefsTool(slackContext ? {
|
|
13095
|
+
kind: "public_provider_tenant",
|
|
13096
|
+
provider: "slack",
|
|
13097
|
+
providerTenantId: slackContext.teamId
|
|
13098
|
+
} : { kind: "public" }, context.conversationId, slackContext ? createSlackConversationBriefSearchPort(slackContext.teamId) : void 0);
|
|
12890
13099
|
if (context.supportsImageInput) tools.viewImage = createViewImageTool(context.workspace, context.supportsImageInput, hooks.toolOverrides?.viewImage);
|
|
12891
13100
|
if (hooks.writeGeneratedArtifacts) tools.imageGenerate = createImageGenerateTool({ writeGeneratedArtifacts: hooks.writeGeneratedArtifacts }, {
|
|
12892
13101
|
modelId: botConfig.imageGenerationModelId,
|
|
@@ -15254,7 +15463,7 @@ Assess the exact action's intrinsic risk and whether the user's intent authorize
|
|
|
15254
15463
|
## Data Exfiltration
|
|
15255
15464
|
- Sending private data, organization data, secrets, or credentials to an untrusted destination is high or critical risk.
|
|
15256
15465
|
- A user-requested read, query, or upload to a trusted destination is not exfiltration by default.
|
|
15257
|
-
- The active Junior conversation is not an untrusted destination merely because it is a public or multi-user channel. For a scheduled
|
|
15466
|
+
- The active Junior conversation is not an untrusted destination merely because it is a public or multi-user channel. For a scheduled automation or event automation, a request by the current actor for scoped work that needs their connected service access authorizes routine service-native reads and posting the requested result to the task's stored Slack destination. It does not authorize exposing credential values, broad data dumps, or delivery to another destination.
|
|
15258
15467
|
- Using an existing credential to authenticate a user-requested action is not credential exfiltration by itself.
|
|
15259
15468
|
- Ordinary product context such as internal system names, repository names, or workflow descriptions is not private organization data by itself, including on an authorized public write destination.
|
|
15260
15469
|
- Deny disclosure of secrets, credentials, or private organization data to an untrusted destination even when the user requested it.
|
|
@@ -15548,9 +15757,9 @@ function resolveToolRuntimeRoute(args) {
|
|
|
15548
15757
|
actor: args.actor?.platform === "web" ? args.actor : void 0,
|
|
15549
15758
|
source: args.run.source
|
|
15550
15759
|
};
|
|
15551
|
-
case "
|
|
15552
|
-
case "
|
|
15553
|
-
case "
|
|
15760
|
+
case "event":
|
|
15761
|
+
case "scheduled_automation":
|
|
15762
|
+
case "event_automation":
|
|
15554
15763
|
case "plugin_dispatch":
|
|
15555
15764
|
case "agent_invocation": return {
|
|
15556
15765
|
destination,
|
|
@@ -17518,4 +17727,4 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
|
|
|
17518
17727
|
}
|
|
17519
17728
|
}
|
|
17520
17729
|
//#endregion
|
|
17521
|
-
export {
|
|
17730
|
+
export { shouldEmitDevAgentTrace as $, createAgentInvocationSchema as A, resolveSandboxEgressProviderForHost as B, renderBlockText as C, getNextRunAtMs as D, scheduledAutomationRunAttributes as E, ingestEvent as F, consumeSandboxEgressAuthRequiredSignal as G, shouldPropagateSandboxEgressTrace as H, isEventMailboxMetadata as I, sandboxEgressSignalsResponseSchema as J, consumeSandboxEgressPermissionDeniedSignal as K, renderTaskInput as L, AgentInvocationLimitError as M, getMcpProviderErrorAttributes as N, agentBindingSchema as O, processWorkspaceSnapshotJob as P, AuthorizationFlowDisabledError as Q, maybePostAutomatedTurnLimitNotice as R, renderAttachmentText as S, logScheduledAutomationRunSkipped as T, SANDBOX_EGRESS_PROXY_PATH as U, normalizeSandboxEgressTracePropagationDomains as V, clearSandboxEgressSignals as W, buildSteeringPiMessage as X, createUserTokenStore as Y, isVisionImageMediaType as Z, readPublicArtifact as _, deleteMcpAuthSession as a, readAttachmentVisionSummary as b, deleteMcpStoredOAuthCredentials as c, putMcpStoredOAuthCredentials as d, getConfigDefaults as et, PluginCredentialFailureError as f, publicArtifactHeaders as g, resolvePluginOAuthAccount as h, finalizeMcpAuthorization as i, AgentInvocationBusyError as j, agentInvocationSchema as k, getMcpAuthSession as l, latestReportedProgress as m, createContextCompactor as n, deleteMcpAuthSessionsForUserProvider as o, authorizationId as p, parseSandboxEgressCredentialToken as q, executeCredentialedEgressRequest as r, deleteMcpServerSessionId as s, executeAgentRun as t, setConfigDefaults as tt, getMcpStoredOAuthCredentials as u, unpublishedArtifactHeaders as v, listThreadReplies as w, recordSlackAttachment as x, lookupSlackUserProfile as y, recordFinishedTurnForAutomatedLimit as z };
|