@sentry/junior 0.244.0 → 0.246.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/{admin-CF9lQg0M.js → admin-Bh-MYw-j.js} +1 -1
- package/dist/{agent-hooks-Cj_rSeWd.js → agent-hooks-DpklSWZg.js} +10 -10
- package/dist/{agent-runner-DtEtbaOb.js → agent-runner-DSKpHjHW.js} +568 -191
- package/dist/api/schema.d.ts +86 -3
- package/dist/api/schema.js +3 -3
- package/dist/api.js +372 -245
- package/dist/{app-DRLh-8zs.d.ts → app-BYB7szGb.d.ts} +2 -0
- package/dist/app.d.ts +1 -1
- package/dist/app.js +179 -146
- package/dist/{briefs-BMzTiGJO.js → briefs-cZ5vWqP_.js} +4 -4
- package/dist/{catalog-runtime-CpxhLwZG.js → catalog-runtime-CTaQXYqS.js} +1 -1
- package/dist/cli/chat.js +17 -13
- package/dist/cli/check.js +3 -3
- package/dist/cli/main.js +2 -2
- package/dist/cli/plugins.js +4 -4
- package/dist/cli/snapshot-create.js +3 -3
- package/dist/cli/upgrade.js +3 -3
- package/dist/{client-BVaGEb1-.js → client--xtsAxKP.js} +2 -2
- package/dist/{config-q9xXVN_5.js → config-BK53Ip5C.js} +11 -2
- package/dist/{conversation-BxvgH64Q.js → conversation-CvBG57Io.js} +11 -1
- package/dist/{conversation-privacy-Dyh_63Ee.js → conversation-privacy-BDBFw6Ws.js} +1 -1
- package/dist/{credential-sync-Bsn-6m16.js → credential-sync-BiuhC9Ds.js} +14 -14
- package/dist/{executor-DA7eHhmC.js → executor-bmmuRY-w.js} +2 -2
- package/dist/{images-CvMreau5.js → images-DZliDtSH.js} +3 -3
- package/dist/instrumentation.js +2 -2
- package/dist/{job-queue-mVyF9jgv.js → job-queue-Cz3M7gg4.js} +1 -1
- package/dist/{logging-z7_w-KN_.js → logging-DVtMYOdD.js} +1 -1
- package/dist/message-BSN-Ma5f.js +317 -0
- package/dist/nitro.d.ts +1 -1
- package/dist/nitro.js +3 -3
- package/dist/{oauth-authorization-Db1yGVvG.js → oauth-authorization-By_MVRDC.js} +1 -1
- package/dist/{oauth-callback-server-_MfOtSxS.js → oauth-callback-server-BrUo6w1e.js} +2 -2
- package/dist/{pending-auth-OndKPW4M.js → pending-auth-BuQLZ6dK.js} +2 -2
- package/dist/{profile-yeKVNECl.js → profile-BevMNSlr.js} +4 -4
- package/dist/{prompt-CX36ZlbV.js → prompt-C9Q9cjD3.js} +1 -1
- package/dist/{registry-DvzzL8-v.js → registry-C2rSEio1.js} +1 -1
- package/dist/{resolve-4u5gEU04.js → resolve-qPhrznTf.js} +2 -2
- package/dist/{runner-Y6A6pww4.js → runner-CNh5eMWx.js} +10 -10
- package/dist/{sandbox-egress-signals-D7AOkuoe.js → sandbox-egress-signals-BnDEKu8d.js} +1 -1
- package/dist/{schema-Dyyi9oRc.js → schema-BWMRg8JS.js} +28 -4
- package/dist/{skills-DFduSvuO.js → skills-C6zJbudq.js} +2 -2
- package/dist/{spawn-DYg7jhhR.js → spawn-C3efZSx3.js} +2 -2
- package/dist/{task-queue-BQ0vnumm.js → task-queue-BX4vG6uA.js} +3 -3
- package/dist/{task-runner-D4jvUpFV.js → task-runner-T51kll0U.js} +10 -10
- package/dist/{tasks-DBZAgTx7.js → tasks-B9OIjl2K.js} +82 -11
- package/dist/{thread-state-hj70P2Cp.js → thread-state-AGZtVyXp.js} +6 -6
- package/dist/{turn-failure-response-S2pwtR8b.js → turn-failure-response-DJ8KU2bo.js} +9 -9
- package/dist/{turn-lifecycle-CNFOMAJq.js → turn-lifecycle-DZJkJ95q.js} +1 -1
- package/dist/{turn-session-routing-1eTgv6ns.js → turn-session-routing-mRqLbMZW.js} +1 -1
- package/dist/{validation-DcsBjzdi.js → validation-Dg0Ihxzo.js} +1 -1
- package/dist/{version-DVCHJYcY.js → version-Deub4try.js} +1 -1
- package/dist/version.js +1 -1
- package/dist/{viewer-BBnCMR1g.js → viewer-D5hbx6TE.js} +3 -3
- package/dist/{web-authorization-CTltRLsB.js → web-authorization-DCxixz5S.js} +6 -6
- package/dist/{web-input-CIsRGF07.js → web-input-0sorYVLh.js} +74 -187
- package/dist/{work-fje9VXgQ.js → work-rm2X7jj4.js} +13 -6
- package/dist/{worker-Byd2Et9r.js → worker-bnGsAOP3.js} +5 -5
- package/package.json +4 -4
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import { b as getGatewayApiKey, c as hasCompactedConversationContext, d as unwrapCurrentInstruction, f as escapeXml, g as getProviderErrorAttributes, h as findProviderError, i as completeText, l as isCompactionSummaryText, m as createProviderError, n as GEN_AI_SERVER_ADDRESS, o as resolveGatewayModel, p as ProviderError, r as completeObject, s as COMPACTION_SUMMARY_PREFIX, t as GEN_AI_PROVIDER_NAME, u as renderCurrentInstruction, v as isProviderRetryError, x as resolveGatewayCredential, y as MISSING_GATEWAY_CREDENTIALS_ERROR } from "./client
|
|
2
|
-
import { C as withLogContext, F as normalizeIdentityEmail, I as isRecord$3, L as toOptionalNumber, R as toOptionalString, a as extractGenAiUsageSummary, b as setTags, c as getLogContextAttributesForSpan, d as logException, f as logInfo, g as serializeGenAiAttribute, i as extractGenAiUsageAttributes, l as getTracePropagationHeaders, m as normalizeGenAiFinishReason, p as logWarn, u as logError, v as setSpanAttributes, w as withSpan, x as summarizeMessageText, y as setSpanStatus } from "./logging-
|
|
1
|
+
import { b as getGatewayApiKey, c as hasCompactedConversationContext, d as unwrapCurrentInstruction, f as escapeXml, g as getProviderErrorAttributes, h as findProviderError, i as completeText, l as isCompactionSummaryText, m as createProviderError, n as GEN_AI_SERVER_ADDRESS, o as resolveGatewayModel, p as ProviderError, r as completeObject, s as COMPACTION_SUMMARY_PREFIX, t as GEN_AI_PROVIDER_NAME, u as renderCurrentInstruction, v as isProviderRetryError, x as resolveGatewayCredential, y as MISSING_GATEWAY_CREDENTIALS_ERROR } from "./client--xtsAxKP.js";
|
|
2
|
+
import { C as withLogContext, F as normalizeIdentityEmail, I as isRecord$3, L as toOptionalNumber, R as toOptionalString, a as extractGenAiUsageSummary, b as setTags, c as getLogContextAttributesForSpan, d as logException, f as logInfo, g as serializeGenAiAttribute, i as extractGenAiUsageAttributes, l as getTracePropagationHeaders, m as normalizeGenAiFinishReason, p as logWarn, u as logError, v as setSpanAttributes, w as withSpan, x as summarizeMessageText, y as setSpanStatus } from "./logging-DVtMYOdD.js";
|
|
3
3
|
import { m as startInactiveSpan, v as withActiveSpan } from "./sentry-CgWZM_kV.js";
|
|
4
|
-
import { a as toCanonicalInputMessage, c as toGenAiPayloadMetadata, i as runWithConversationPrivacy, l as toGenAiPayloadTraceAttributes, o as toCanonicalOutputMessage, r as resolveConversationPrivacy, s as toGenAiMessagesTraceAttributes, t as canExposeConversationPayload } from "./conversation-privacy-
|
|
5
|
-
import { i as getChatConfig, n as botConfig, v as normalizeSlackEmojiName } from "./config-
|
|
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-BDBFw6Ws.js";
|
|
5
|
+
import { i as getChatConfig, n as botConfig, v as normalizeSlackEmojiName } from "./config-BK53Ip5C.js";
|
|
6
6
|
import { a as formatModelProfiles, c as profileConfig, i as formatModelProfile, l as TURN_REASONING_LEVELS, n as ModelProfileNotConfiguredError, o as modelIdForProfile, s as modelProfileSchema } from "./model-profile-BwU8mqwD.js";
|
|
7
7
|
import { a as isUserActor, d as parseSlackTeamId, f as parseSlackUserId, g as parseSlackMessageTs, l as parseSlackChannelId, s as parseStoredSlackActor, u as parseSlackChannelReferenceId } from "./actor-BTtLaeWy.js";
|
|
8
|
-
import { F as sameDestination$1, N as parseDestination, T as hasAgentTurnUsage, c as getConversationEventStore, d as getDb, f as getSqlExecutor, l as getConversationMessageSearchStore, r as readActorIdentity, u as getConversationStore, x as addAgentTurnUsage } from "./viewer-
|
|
9
|
-
import { a as juniorAgentInvocations, c as juniorIdentities, d as juniorDestinations, i as juniorAgentBindings, n as AGENT_INVOCATION_PARENT_MAILBOX_STATUSES,
|
|
10
|
-
import { C as juniorArtifacts, S as juniorAttachments, a as juniorSchedulerRuns, l as juniorStats, m as juniorConversationBriefs, y as juniorConversationAnnotations } from "./executor-
|
|
8
|
+
import { F as sameDestination$1, N as parseDestination, T as hasAgentTurnUsage, c as getConversationEventStore, d as getDb, f as getSqlExecutor, l as getConversationMessageSearchStore, r as readActorIdentity, u as getConversationStore, x as addAgentTurnUsage } from "./viewer-D5hbx6TE.js";
|
|
9
|
+
import { a as juniorAgentInvocations, c as juniorIdentities, d as juniorDestinations, i as juniorAgentBindings, n as AGENT_INVOCATION_PARENT_MAILBOX_STATUSES, s as juniorConversations, t as AGENT_INVOCATION_MAILBOX_STATUSES } from "./agent-invocations-BO6ddZax.js";
|
|
10
|
+
import { C as juniorArtifacts, S as juniorAttachments, a as juniorSchedulerRuns, l as juniorStats, m as juniorConversationBriefs, y as juniorConversationAnnotations } from "./executor-bmmuRY-w.js";
|
|
11
11
|
import { d as instructionActors, f as instructionProvenanceFor, p as sameActorIdentity, t as KNOWN_CONVERSATION_EVENT_TYPES, u as contextProvenance } from "./history-Nte1oVed.js";
|
|
12
12
|
import { a as listReferenceFiles, c as soulPathCandidates, l as worldPathCandidates } from "./package-discovery-BVvSnQBB.js";
|
|
13
|
-
import { a as resolveAuthTokenPlaceholder, c as fetchWithBoundedOAuthErrorBodies, d as credentialContextForActor, f as credentialContextSchema, h as CredentialUnavailableError, i as resolvePluginHeaderEnvRefs, l as getOAuthProviderErrorAttributes, m as credentialUserSubjectId, o as resolvePluginCommandEnv, p as credentialSubjectSchema, s as OAuthProviderError } from "./registry-
|
|
14
|
-
import { n as pluginCatalogRuntime } from "./catalog-runtime-
|
|
15
|
-
import { a as parseSkillInvocation, i as parseSkillFile, n as findSkillByName, r as loadSkillsByName, t as discoverSkills } from "./skills-
|
|
16
|
-
import {
|
|
17
|
-
import { A as getWorkspaceSnapshotBuilder, B as cancelWatch, Bt as isConversationScopedChannel, C as WorkspaceValidationError, D as setWorkspaceSnapshotBuild, E as setWorkspaceSnapshot, F as RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS, Ft as SlackActionError, G as findMatchingWatches, H as createTimerWatch, Ht as normalizeSlackConversationId, I as RESOURCE_SUBSCRIPTION_MAX_TTL_MS, It as downloadPrivateSlackFile, K as listWatches, L as RESOURCE_WATCH_TOOL_SOURCE, Lt as getFilePermalink, M as workspaceRepoCheckoutPath, Mt as defaultTimezone, N as resolveChannelCapabilities, Nt as resolveUserTimezone, O as SANDBOX_RUNTIME, Ot as saveObjectAnnotations, P as getSlackToolContext, R as STOP_WATCHING_TOOL_NAME, S as updateWorkspace, T as loadSnapshotsForProfile, U as createWatch, Vt as isDmChannel$1, W as deliverWatch, Wt as withSlackRetries, _ as deleteWorkspace, b as listWorkspaces, c as getPluginTools, et as verifyTimerWatchCredentialSubject, g as createWorkspace, it as getDashboardTaskLink, j as tryWorkspaceRepoCheckoutPath, k as deleteWorkspaceSnapshotBuilders, kt as createPluginAnnotations, l as getPluginUserPromptContributions, lt as createPluginLogger, nt as getDashboardConversationLink, r as createPluginHookRunner, s as getPluginSystemPromptContributions, tt as JUNIOR_THREAD_STATE_TTL_MS, u as getPlugins, v as getWorkspace, w as invalidateReadySnapshot, wt as matchSlackAttachments, y as getWorkspaceByName, z as cancelSubscriptions, zt as getSlackClient } from "./agent-hooks-
|
|
18
|
-
import {
|
|
19
|
-
import { A as SANDBOX_DATA_ROOT, C as getDefaultRedisStateAdapterFor, D as fenceLock, E as acquireActiveLock, F as sandboxSkillFile, I as sandboxSkillPathResolution, M as SANDBOX_SKILLS_ROOT, N as SANDBOX_WORKSPACE_ROOT, O as withLock, P as sandboxSkillDir, _ as workspaceSnapshotNotReadyUserMessage, a as isAbortError, b as buildNonInteractiveShellScript, c as isSandboxCommandStreamInterruptedError, d as isSnapshottingError, f as throwSandboxOperationError, g as isWorkspaceSnapshotNotReadyError, h as getWorkspaceSnapshotNotReadyError, i as ensureDockerDaemon, j as SANDBOX_REPOS_ROOT, k as sleep$2, l as isSandboxMissingError, m as WorkspaceSnapshotNotReadyError, n as hash, o as isAlreadyExistsError, p as wrapSandboxSetupError, r as isStale, s as isSandboxApiTransientError, t as create, u as isSandboxUnavailableError, v as getVercelSandboxCredentials, w as getStateAdapter, x as runNonInteractiveCommand, y as buildCommandScript } from "./profile-
|
|
20
|
-
import { n as canReusePendingAuthLink, t as abandonReplacedPendingAuth } from "./pending-auth-
|
|
21
|
-
import { a as NO_REPLY_MARKER, c as CooperativeTurnYieldError, i as sanitizeAssistantText, l as TurnInputCommitLostError, n as decideReply, o as isNoReplyMarker, p as isTurnInputCommitLostError, r as getAssistantReplyText, t as authorizationId } from "./oauth-authorization-
|
|
13
|
+
import { a as resolveAuthTokenPlaceholder, c as fetchWithBoundedOAuthErrorBodies, d as credentialContextForActor, f as credentialContextSchema, h as CredentialUnavailableError, i as resolvePluginHeaderEnvRefs, l as getOAuthProviderErrorAttributes, m as credentialUserSubjectId, o as resolvePluginCommandEnv, p as credentialSubjectSchema, s as OAuthProviderError } from "./registry-C2rSEio1.js";
|
|
14
|
+
import { n as pluginCatalogRuntime } from "./catalog-runtime-CTaQXYqS.js";
|
|
15
|
+
import { a as parseSkillInvocation, i as parseSkillFile, n as findSkillByName, r as loadSkillsByName, t as discoverSkills } from "./skills-C6zJbudq.js";
|
|
16
|
+
import { At as openConversationProjection, B as postSlackEphemeralMessage, Bt as recordTurnRoute, Et as loadLastModelProfile, F as slackMention, Ft as recordAuthorizationRequested, It as recordGuardianActionReviewed, J as saveTurnCheckpoint, Jt as fallbackShortTitle, K as continuableMessages, L as addReactionToMessage, Lt as recordMcpProviderConnected, M as formatSlackLink, Mt as recordAttachmentsDelivered, R as getSlackMessagePermalink, Tt as loadConversationProjection, U as uploadFilesToConversation, V as postSlackMessage, Yt as resolveTaskTitle, _t as retainRuntimeTurnContext, a as readScheduledAutomation, at as isTurnExecutionLimitExceededError, b as saveEventAutomation, bt as TURN_CONTEXT_TAG, cn as getAgentContextCompactionTriggerTokens, ct as getTerminalAssistantMessages, dn as piMessageFromHistoryItem, f as createEventAutomation, ft as isAssistantMessage, g as getEventAutomation, gt as normalizeToolNameFromResult, ht as isToolResultMessage, j as escapeSlackMrkdwnText, jt as recordAgentsInstructionsUpdated, k as automationRevision, kt as loadTurnRoute, ln as getAgentContextInputLimitTokens, lt as getUserMessageInstructionText, mt as isToolResultError, nt as TurnSliceLimitExceededError, o as saveScheduledAutomation, ot as extractAssistantText, p as deleteEventAutomation, pt as isContinuablePiBoundary, q as loadTurnCheckpoint, r as listScheduledAutomationsForTeam, rt as assertTurnToolCallLimit, sn as estimateTextTokens, st as getPiMessageRole, t as createScheduledAutomation, un as historyItemFromPiMessage, ut as hasRuntimeTurnContext, v as listEventAutomationsForTeam, vt as stripRuntimeTurnContext, wt as loadConnectedMcpProviders, x as setEventAutomationStatus, xt as ToolInputError, yt as trimTrailingAssistantMessages, z as openSlackDirectMessage, zt as recordToolExecutionStarted } from "./tasks-B9OIjl2K.js";
|
|
17
|
+
import { A as getWorkspaceSnapshotBuilder, B as cancelWatch, Bt as isConversationScopedChannel, C as WorkspaceValidationError, D as setWorkspaceSnapshotBuild, E as setWorkspaceSnapshot, F as RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS, Ft as SlackActionError, G as findMatchingWatches, H as createTimerWatch, Ht as normalizeSlackConversationId, I as RESOURCE_SUBSCRIPTION_MAX_TTL_MS, It as downloadPrivateSlackFile, K as listWatches, L as RESOURCE_WATCH_TOOL_SOURCE, Lt as getFilePermalink, M as workspaceRepoCheckoutPath, Mt as defaultTimezone, N as resolveChannelCapabilities, Nt as resolveUserTimezone, O as SANDBOX_RUNTIME, Ot as saveObjectAnnotations, P as getSlackToolContext, R as STOP_WATCHING_TOOL_NAME, S as updateWorkspace, T as loadSnapshotsForProfile, U as createWatch, Vt as isDmChannel$1, W as deliverWatch, Wt as withSlackRetries, _ as deleteWorkspace, b as listWorkspaces, c as getPluginTools, et as verifyTimerWatchCredentialSubject, g as createWorkspace, it as getDashboardTaskLink, j as tryWorkspaceRepoCheckoutPath, k as deleteWorkspaceSnapshotBuilders, kt as createPluginAnnotations, l as getPluginUserPromptContributions, lt as createPluginLogger, nt as getDashboardConversationLink, r as createPluginHookRunner, s as getPluginSystemPromptContributions, tt as JUNIOR_THREAD_STATE_TTL_MS, u as getPlugins, v as getWorkspace, w as invalidateReadySnapshot, wt as matchSlackAttachments, y as getWorkspaceByName, z as cancelSubscriptions, zt as getSlackClient } from "./agent-hooks-DpklSWZg.js";
|
|
18
|
+
import { B as conversationBriefSchema, I as isVisionImageMediaType, K as removedCardSchema, R as briefLinkSchema, z as briefOutcomeStatusSchema } from "./conversation-CvBG57Io.js";
|
|
19
|
+
import { A as SANDBOX_DATA_ROOT, C as getDefaultRedisStateAdapterFor, D as fenceLock, E as acquireActiveLock, F as sandboxSkillFile, I as sandboxSkillPathResolution, M as SANDBOX_SKILLS_ROOT, N as SANDBOX_WORKSPACE_ROOT, O as withLock, P as sandboxSkillDir, _ as workspaceSnapshotNotReadyUserMessage, a as isAbortError, b as buildNonInteractiveShellScript, c as isSandboxCommandStreamInterruptedError, d as isSnapshottingError, f as throwSandboxOperationError, g as isWorkspaceSnapshotNotReadyError, h as getWorkspaceSnapshotNotReadyError, i as ensureDockerDaemon, j as SANDBOX_REPOS_ROOT, k as sleep$2, l as isSandboxMissingError, m as WorkspaceSnapshotNotReadyError, n as hash, o as isAlreadyExistsError, p as wrapSandboxSetupError, r as isStale, s as isSandboxApiTransientError, t as create, u as isSandboxUnavailableError, v as getVercelSandboxCredentials, w as getStateAdapter, x as runNonInteractiveCommand, y as buildCommandScript } from "./profile-BevMNSlr.js";
|
|
20
|
+
import { n as canReusePendingAuthLink, t as abandonReplacedPendingAuth } from "./pending-auth-BuQLZ6dK.js";
|
|
21
|
+
import { a as NO_REPLY_MARKER, c as CooperativeTurnYieldError, i as sanitizeAssistantText, l as TurnInputCommitLostError, n as decideReply, o as isNoReplyMarker, p as isTurnInputCommitLostError, r as getAssistantReplyText, t as authorizationId } from "./oauth-authorization-By_MVRDC.js";
|
|
22
22
|
import { i as isValidTimeZone, r as getZonedDateTimeParts } from "./cadence-D00SCzT1.js";
|
|
23
|
-
import { r as EVENT_AUTHOR_ID } from "./prompt-
|
|
24
|
-
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-
|
|
25
|
-
import { a as getVercelConversationWorkQueue, f as getTurnRequestDeadline, n as sendNextWorkspaceSnapshotJob, r as sendWorkspaceSnapshotJob } from "./job-queue-
|
|
23
|
+
import { r as EVENT_AUTHOR_ID } from "./prompt-C9Q9cjD3.js";
|
|
24
|
+
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-qPhrznTf.js";
|
|
25
|
+
import { a as getVercelConversationWorkQueue, f as getTurnRequestDeadline, n as sendNextWorkspaceSnapshotJob, r as sendWorkspaceSnapshotJob } from "./job-queue-Cz3M7gg4.js";
|
|
26
26
|
import { F as taskOutcomeInputSchema, M as compileScheduleIntent, N as scheduleIntentSchema, O as automationInstructionToolSchema, T as SCHEDULED_AUTOMATION_SYSTEM_ACTOR, j as ScheduleIntentError, k as automationTitleSchema } from "./automation-gznOMaQ2.js";
|
|
27
27
|
import { r as isExperimentalFeatureEnabled } from "./experimental-Qn9rKLgx.js";
|
|
28
28
|
import { n as privateTraceResultAttributes } from "./private-trace-result-BLvBjy_e.js";
|
|
@@ -34,7 +34,7 @@ import { isDeepStrictEqual } from "node:util";
|
|
|
34
34
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
35
35
|
import { z } from "zod";
|
|
36
36
|
import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
37
|
-
import { and, asc, count, desc, eq, gt, gte, inArray, isNotNull, isNull, lt, lte, ne, notExists, or, sql } from "drizzle-orm";
|
|
37
|
+
import { and, asc, count, countDistinct, desc, eq, gt, gte, inArray, isNotNull, isNull, lt, lte, ne, notExists, or, sql } from "drizzle-orm";
|
|
38
38
|
import { EgressAuthRequired, EgressPolicyDenied, PluginToolInputError, actorSchema, destinationSchema, eventMatchSchema, eventSchema, missingToolAnnotationKeys, normalizeEventIdentifier, objectTypeSchema, ownedObjectAnnotationSchema, pluginAuthorizationSchema, pluginCredentialHeaderTransformSchema, pluginCredentialResultSchema, pluginGrantSchema, pluginProviderAccountSchema, pluginStoredTokensSchema, sourceSchema, stableEventMatchKey, watchResultSchema } from "@sentry/junior-plugin-api";
|
|
39
39
|
import { alias } from "drizzle-orm/pg-core";
|
|
40
40
|
import { Agent, estimateContextTokens } from "@earendil-works/pi-agent-core";
|
|
@@ -895,6 +895,26 @@ function remindMissingAutomationResult(messages) {
|
|
|
895
895
|
}];
|
|
896
896
|
}
|
|
897
897
|
//#endregion
|
|
898
|
+
//#region src/chat/agent-invocations/blocked-report.ts
|
|
899
|
+
/** Blocked report of a child agent. See "Blocked Reports" in README.md. */
|
|
900
|
+
/** Model-facing tool that ends a child agent run as blocked. */
|
|
901
|
+
const REPORT_BLOCKED_TOOL_NAME = "reportBlocked";
|
|
902
|
+
const blockedReportSchema = z.object({ missing: z.string().trim().min(1) });
|
|
903
|
+
/**
|
|
904
|
+
* Read the last successful blocked report from the new messages of one run.
|
|
905
|
+
*
|
|
906
|
+
* Do not pass the whole history. A named child keeps its history, so a report
|
|
907
|
+
* from an earlier agent invocation must not end the current one.
|
|
908
|
+
*/
|
|
909
|
+
function readBlockedReport(messages) {
|
|
910
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
911
|
+
const message = messages[index];
|
|
912
|
+
if (!isToolResultMessage(message) || isToolResultError(message) || normalizeToolNameFromResult(message) !== "reportBlocked") continue;
|
|
913
|
+
const parsed = blockedReportSchema.safeParse(message.details);
|
|
914
|
+
if (parsed.success) return parsed.data;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
//#endregion
|
|
898
918
|
//#region src/chat/services/turn-result.ts
|
|
899
919
|
/** Process raw agent messages into a structured AgentRunResult. */
|
|
900
920
|
function buildTurnResult(input) {
|
|
@@ -903,6 +923,7 @@ function buildTurnResult(input) {
|
|
|
903
923
|
const toolResults = newMessages.filter(isToolResultMessage);
|
|
904
924
|
const assistantMessages = newMessages.filter(isAssistantMessage);
|
|
905
925
|
const terminalAssistantMessages = getTerminalAssistantMessages(newMessages);
|
|
926
|
+
const blocked = readBlockedReport(newMessages);
|
|
906
927
|
const rawPrimaryText = sanitizeAssistantText(terminalAssistantMessages.map((message) => extractAssistantText(message)).join("\n\n")).trim();
|
|
907
928
|
const primaryText = terminalAssistantMessages.map((message) => decideReply(message)).filter((output) => output.kind === "deliver").map((output) => output.text).join("\n\n");
|
|
908
929
|
const terminalTexts = terminalAssistantMessages.map((message) => sanitizeAssistantText(extractAssistantText(message)));
|
|
@@ -922,7 +943,7 @@ function buildTurnResult(input) {
|
|
|
922
943
|
};
|
|
923
944
|
if (!isProviderError) logInfo("ai.no_reply_marker.accepted", markerAttributes);
|
|
924
945
|
}
|
|
925
|
-
if (!primaryText && !completedWithoutTerminalText && !isProviderError) logWarn("ai.model_response.empty", {
|
|
946
|
+
if (!primaryText && !completedWithoutTerminalText && !blocked && !isProviderError) logWarn("ai.model_response.empty", {
|
|
926
947
|
"app.ai.tool_results": toolResults.length,
|
|
927
948
|
"app.ai.tool_error_results": toolErrorCount,
|
|
928
949
|
"app.ai.generated_files": input.generatedFileCount
|
|
@@ -932,7 +953,7 @@ function buildTurnResult(input) {
|
|
|
932
953
|
let outcome;
|
|
933
954
|
if (isProviderError) outcome = "provider_error";
|
|
934
955
|
else if (suppressedPrimaryText) outcome = "execution_failure";
|
|
935
|
-
else if (primaryText || completedWithoutTerminalText) outcome = "success";
|
|
956
|
+
else if (primaryText || completedWithoutTerminalText || blocked) outcome = "success";
|
|
936
957
|
else outcome = "execution_failure";
|
|
937
958
|
if (shouldTrace) logInfo("agent.message.generated", {
|
|
938
959
|
"app.message.kind": "assistant_outbound",
|
|
@@ -944,6 +965,7 @@ function buildTurnResult(input) {
|
|
|
944
965
|
});
|
|
945
966
|
return {
|
|
946
967
|
text: primaryText,
|
|
968
|
+
...blocked ? { blocked } : void 0,
|
|
947
969
|
sandboxRef,
|
|
948
970
|
piMessages: input.piMessages,
|
|
949
971
|
diagnostics: buildDiagnostics(input, {
|
|
@@ -1873,12 +1895,19 @@ const AUTOMATION_RUN_RULES = [
|
|
|
1873
1895
|
"- A tool result with `timed_out: true` means that attempt did not finish. Before you retry work that may have side effects, inspect authoritative state and do not repeat a mutation that already applied.",
|
|
1874
1896
|
`- Assistant text is never delivered. End the run with \`${FINISH_AUTOMATION_RUN_TOOL_NAME}\`. Do not post the result with another tool.`
|
|
1875
1897
|
];
|
|
1898
|
+
const CHILD_AGENT_RUN_RULES = [
|
|
1899
|
+
"- This run is a task that a parent agent gave you. The task is the job. The parent agent reads your final reply; no person reads this run.",
|
|
1900
|
+
"- Do the work now. Do not ask questions, ask for approval, or offer options.",
|
|
1901
|
+
`- Your final text reply goes to the parent as finished work. When the task cannot be finished, end the run with \`${REPORT_BLOCKED_TOOL_NAME}\` instead.`,
|
|
1902
|
+
"- A tool result with `timed_out: true` means that attempt did not finish. Before you retry work that may have side effects, inspect authoritative state and do not repeat a mutation that already applied."
|
|
1903
|
+
];
|
|
1876
1904
|
const SAFETY_RULES = [
|
|
1877
1905
|
"- Stay within the user's request and the runtime's available capabilities; do not pursue independent goals, persistence, replication, credential gathering, or access expansion.",
|
|
1878
1906
|
"- Respect stop, pause, audit, and approval boundaries. Do not bypass safeguards or persuade the user to weaken them.",
|
|
1879
1907
|
"- Do not change system prompts, tool policies, security settings, credentials, or runtime configuration unless the user explicitly requests that exact administrative action and an available tool permits it."
|
|
1880
1908
|
];
|
|
1881
1909
|
const AUTOMATION_FAILURE_RULES = ["- Keep commands, error output, raw tool payloads, and internal routing metadata out of the message.", `- When a failure stops the job, put its cause in the \`${FINISH_AUTOMATION_RUN_TOOL_NAME}\` reason, not in the message.`];
|
|
1910
|
+
const CHILD_AGENT_FAILURE_RULES = [`- When a failure or a missing part stops the task, put what you checked and what is missing in \`${REPORT_BLOCKED_TOOL_NAME}\`, not in a text reply.`, "- Keep raw tool payloads and internal routing metadata out of the final reply."];
|
|
1882
1911
|
const FAILURE_RULES = [
|
|
1883
1912
|
"- For tool/runtime failures, run the named check before diagnosing and report the exact failed command plus stderr/exit code.",
|
|
1884
1913
|
"- If a fact cannot be verified after focused checks, say what you checked and what blocked a stronger answer.",
|
|
@@ -1892,7 +1921,7 @@ function renderRuleSection(tag, lines) {
|
|
|
1892
1921
|
].join("\n");
|
|
1893
1922
|
}
|
|
1894
1923
|
function buildBehaviorSection(platform, mode) {
|
|
1895
|
-
const runSections = mode === "automation" ? [renderRuleSection("automation-run", AUTOMATION_RUN_RULES)] : [
|
|
1924
|
+
const runSections = mode === "automation" ? [renderRuleSection("automation-run", AUTOMATION_RUN_RULES)] : mode === "child_agent" ? [renderRuleSection("child-agent-run", CHILD_AGENT_RUN_RULES)] : [
|
|
1896
1925
|
renderRuleSection("task-execution", TASK_EXECUTION_RULES),
|
|
1897
1926
|
renderRuleSection("conversation", CONVERSATION_RULES),
|
|
1898
1927
|
...platform === "slack" ? [renderRuleSection("slack-actions", SLACK_ACTION_RULES)] : []
|
|
@@ -1900,12 +1929,12 @@ function buildBehaviorSection(platform, mode) {
|
|
|
1900
1929
|
const automation = mode === "automation";
|
|
1901
1930
|
return [
|
|
1902
1931
|
renderRuleSection("tool-policy", TOOL_POLICY_RULES),
|
|
1903
|
-
...
|
|
1932
|
+
...mode !== "conversation" ? [] : [renderRuleSection("tool-call-style", TOOL_CALL_STYLE_RULES)],
|
|
1904
1933
|
renderRuleSection("skill-policy", SKILL_POLICY_RULES),
|
|
1905
1934
|
renderRuleSection("planning", PLANNING_RULES),
|
|
1906
1935
|
...runSections,
|
|
1907
1936
|
renderRuleSection("safety", SAFETY_RULES),
|
|
1908
|
-
renderRuleSection("failure-handling", automation ? AUTOMATION_FAILURE_RULES : FAILURE_RULES)
|
|
1937
|
+
renderRuleSection("failure-handling", automation ? AUTOMATION_FAILURE_RULES : mode === "child_agent" ? CHILD_AGENT_FAILURE_RULES : FAILURE_RULES)
|
|
1909
1938
|
].join("\n\n");
|
|
1910
1939
|
}
|
|
1911
1940
|
function buildAutomationOutputSection(platform) {
|
|
@@ -1918,6 +1947,13 @@ function buildAutomationOutputSection(platform) {
|
|
|
1918
1947
|
}
|
|
1919
1948
|
function buildOutputSection(platform, mode) {
|
|
1920
1949
|
if (mode === "automation") return buildAutomationOutputSection(platform);
|
|
1950
|
+
if (mode === "child_agent") return [
|
|
1951
|
+
`<output format="markdown">`,
|
|
1952
|
+
"- Start with the result, not internal process narration.",
|
|
1953
|
+
"- Use concise Markdown: short paragraphs, bullets, links, and fenced code blocks.",
|
|
1954
|
+
"- Reply with text only when the task is finished.",
|
|
1955
|
+
"</output>"
|
|
1956
|
+
].join("\n");
|
|
1921
1957
|
if (platform === "local") return [
|
|
1922
1958
|
`<output format="markdown">`,
|
|
1923
1959
|
"- Start with the answer or result, not internal process narration.",
|
|
@@ -2071,6 +2107,10 @@ const STATIC_SYSTEM_PROMPTS = {
|
|
|
2071
2107
|
local: buildStaticSystemPrompt("local", "automation"),
|
|
2072
2108
|
slack: buildStaticSystemPrompt("slack", "automation")
|
|
2073
2109
|
},
|
|
2110
|
+
child_agent: {
|
|
2111
|
+
local: buildStaticSystemPrompt("local", "child_agent"),
|
|
2112
|
+
slack: buildStaticSystemPrompt("slack", "child_agent")
|
|
2113
|
+
},
|
|
2074
2114
|
conversation: {
|
|
2075
2115
|
local: buildStaticSystemPrompt("local", "conversation"),
|
|
2076
2116
|
slack: buildStaticSystemPrompt("slack", "conversation")
|
|
@@ -2365,7 +2405,7 @@ async function assemblePrompt(args) {
|
|
|
2365
2405
|
userContentParts: requestContentParts
|
|
2366
2406
|
}) : void 0;
|
|
2367
2407
|
const needsBootstrapContextForPrompt = shouldPromptAgent && !replayedPrompt && !hasRuntimeTurnContext(promptHistoryMessages);
|
|
2368
|
-
const promptMode = args.run.dispatch?.declaresResult ? "automation" : "conversation";
|
|
2408
|
+
const promptMode = args.run.dispatch?.declaresResult ? "automation" : args.run.source.kind === "agent_invocation" ? "child_agent" : "conversation";
|
|
2369
2409
|
const platform = (args.run.delivery || promptMode === "automation") && args.run.location?.provider === "slack" ? "slack" : "local";
|
|
2370
2410
|
const pluginSystemPrompt = buildPluginSystemPromptContributions(await getPluginSystemPromptContributions(platform));
|
|
2371
2411
|
const baseInstructions = [buildSystemPrompt(platform, promptMode), pluginSystemPrompt].filter((section) => Boolean(section)).join("\n\n");
|
|
@@ -5154,22 +5194,14 @@ function hasEventCatalogEntries(catalog) {
|
|
|
5154
5194
|
return Object.keys(catalog).length > 0;
|
|
5155
5195
|
}
|
|
5156
5196
|
/**
|
|
5157
|
-
* True when at least one plugin namespace is enabled.
|
|
5158
|
-
*
|
|
5159
|
-
* Core Workspace snapshot events stay in the catalog for search and temporary
|
|
5160
|
-
* watches. Durable event automations still require a plugin publisher.
|
|
5161
|
-
*/
|
|
5162
|
-
function hasPluginEventCatalogEntries(catalog) {
|
|
5163
|
-
return Object.keys(pluginEventCatalog(catalog)).length > 0;
|
|
5164
|
-
}
|
|
5165
|
-
/**
|
|
5166
5197
|
* Catalog entries that durable event automations may select.
|
|
5167
5198
|
*
|
|
5168
5199
|
* Core Workspace snapshot events only wake temporary watches. Snapshot finish
|
|
5169
5200
|
* does not run event-automation ingest, so those namespaces stay out of create/update
|
|
5170
|
-
* schemas and trigger availability.
|
|
5201
|
+
* schemas and trigger availability. The core `slack` namespace stays in: Slack
|
|
5202
|
+
* ingress runs event-automation ingest for channel messages.
|
|
5171
5203
|
*/
|
|
5172
|
-
function
|
|
5204
|
+
function automationEventCatalog(catalog) {
|
|
5173
5205
|
return Object.fromEntries(Object.entries(catalog).filter(([namespace]) => namespace !== CORE_EVENT_NAMESPACE));
|
|
5174
5206
|
}
|
|
5175
5207
|
function enumSchema(values, unavailableMessage) {
|
|
@@ -5301,23 +5333,100 @@ function workspaceSnapshotFinishedEvent(input) {
|
|
|
5301
5333
|
return event;
|
|
5302
5334
|
}
|
|
5303
5335
|
//#endregion
|
|
5336
|
+
//#region src/chat/slack/channel-message-events.ts
|
|
5337
|
+
/** Core-owned event namespace for Slack Events. Not a plugin publisher. */
|
|
5338
|
+
const SLACK_EVENT_NAMESPACE = "slack";
|
|
5339
|
+
/** The trigger identifier that names the channel of the automation. */
|
|
5340
|
+
const SLACK_CURRENT_CHANNEL_IDENTIFIER = "current_channel";
|
|
5341
|
+
const CHANNEL_RESOURCE_TYPE = "channel";
|
|
5342
|
+
const MESSAGE_POSTED_EVENT = "message.posted";
|
|
5343
|
+
const CHANNEL_ID_PATTERN = /^[CG][A-Z0-9]{2,}$/;
|
|
5344
|
+
/** Event registration for new top-level Slack channel messages. */
|
|
5345
|
+
function slackChannelMessageEvents() {
|
|
5346
|
+
return { resourceTypes: [{
|
|
5347
|
+
type: CHANNEL_RESOURCE_TYPE,
|
|
5348
|
+
identifier: {
|
|
5349
|
+
format: `"${SLACK_CURRENT_CHANNEL_IDENTIFIER}" for the channel where the automation is created`,
|
|
5350
|
+
pattern: /^(?:current_channel|[CG][A-Z0-9]{2,})$/
|
|
5351
|
+
},
|
|
5352
|
+
supportedEvents: [MESSAGE_POSTED_EVENT],
|
|
5353
|
+
suggestedEvents: [],
|
|
5354
|
+
guidance: { [MESSAGE_POSTED_EVENT]: "A person posted a new top-level message in this channel. A reply goes in that message's thread. If the instructions do not apply to this message, end with no action." }
|
|
5355
|
+
}] };
|
|
5356
|
+
}
|
|
5357
|
+
/**
|
|
5358
|
+
* Bind a Slack channel-message automation to its own channel.
|
|
5359
|
+
*
|
|
5360
|
+
* Resolve `current_channel` to the destination channel and reject other
|
|
5361
|
+
* channels, so private channel text cannot reach another channel.
|
|
5362
|
+
*/
|
|
5363
|
+
function requireSlackChannelMessageScope(automation) {
|
|
5364
|
+
if (automation.trigger.namespace !== "slack") return automation;
|
|
5365
|
+
const channelId = automation.destination.channelId;
|
|
5366
|
+
if (!CHANNEL_ID_PATTERN.test(channelId)) throw new ToolInputError("A Slack channel-message automation works only in a channel, not in a DM.");
|
|
5367
|
+
const identifier = automation.trigger.identifier === "current_channel" ? channelId : automation.trigger.identifier;
|
|
5368
|
+
if (identifier !== channelId) throw new ToolInputError(`A Slack channel-message automation can watch only the channel where it posts. Use identifier "${SLACK_CURRENT_CHANNEL_IDENTIFIER}", or create the automation in the channel to watch.`);
|
|
5369
|
+
return {
|
|
5370
|
+
...automation,
|
|
5371
|
+
credentialMode: "system",
|
|
5372
|
+
trigger: {
|
|
5373
|
+
...automation.trigger,
|
|
5374
|
+
identifier
|
|
5375
|
+
}
|
|
5376
|
+
};
|
|
5377
|
+
}
|
|
5378
|
+
/**
|
|
5379
|
+
* Build the Event for one new top-level channel message, or nothing.
|
|
5380
|
+
*
|
|
5381
|
+
* Thread replies and bot posts, including Slackbot, do not count. Bots can
|
|
5382
|
+
* post often and use up the run limit of the automation.
|
|
5383
|
+
*/
|
|
5384
|
+
function slackChannelMessageEvent(input) {
|
|
5385
|
+
if (input.botId || input.subtype === "bot_message" || input.userId === "USLACKBOT") return;
|
|
5386
|
+
if (input.threadTs && input.threadTs !== input.ts) return void 0;
|
|
5387
|
+
if (!CHANNEL_ID_PATTERN.test(input.channelId)) return void 0;
|
|
5388
|
+
return {
|
|
5389
|
+
eventKey: `${input.channelId}:${input.ts}`,
|
|
5390
|
+
eventType: MESSAGE_POSTED_EVENT,
|
|
5391
|
+
identifier: input.channelId,
|
|
5392
|
+
occurredAtMs: Math.floor(Number(input.ts) * 1e3),
|
|
5393
|
+
trustedSummary: `<@${input.userId}> posted a new message in <#${input.channelId}>.`,
|
|
5394
|
+
data: {
|
|
5395
|
+
channelId: input.channelId,
|
|
5396
|
+
messageTs: input.ts,
|
|
5397
|
+
userId: input.userId
|
|
5398
|
+
},
|
|
5399
|
+
...input.text?.trim() ? { untrustedText: input.text } : void 0
|
|
5400
|
+
};
|
|
5401
|
+
}
|
|
5402
|
+
/** Return the source message thread for a channel-message run. */
|
|
5403
|
+
function slackChannelMessageReplyThreadTs(event) {
|
|
5404
|
+
if (event.namespace !== "slack") return void 0;
|
|
5405
|
+
const messageTs = event.data?.messageTs;
|
|
5406
|
+
return typeof messageTs === "string" ? messageTs : void 0;
|
|
5407
|
+
}
|
|
5408
|
+
//#endregion
|
|
5304
5409
|
//#region src/chat/events/runtime-catalog.ts
|
|
5305
5410
|
/**
|
|
5306
5411
|
* Enabled event registrations for search, guidance, and tool schemas.
|
|
5307
5412
|
*
|
|
5308
|
-
* Includes core Workspace snapshot events under the `junior` namespace,
|
|
5309
|
-
*
|
|
5413
|
+
* Includes core Workspace snapshot events under the `junior` namespace, Slack
|
|
5414
|
+
* channel-message events under the `slack` namespace, plus every enabled plugin
|
|
5415
|
+
* registration. Plugins may not use the `junior` or `slack` namespaces.
|
|
5310
5416
|
*/
|
|
5311
5417
|
function getEventCatalog() {
|
|
5312
|
-
const catalog = {
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5418
|
+
const catalog = {
|
|
5419
|
+
[CORE_EVENT_NAMESPACE]: { resourceTypes: [...workspaceSnapshotEvents().resourceTypes, {
|
|
5420
|
+
type: "timer",
|
|
5421
|
+
supportedEvents: ["timer.fired"],
|
|
5422
|
+
suggestedEvents: []
|
|
5423
|
+
}] },
|
|
5424
|
+
[SLACK_EVENT_NAMESPACE]: slackChannelMessageEvents()
|
|
5425
|
+
};
|
|
5317
5426
|
for (const plugin of getPlugins()) {
|
|
5318
5427
|
const registration = plugin.events;
|
|
5319
5428
|
if (!registration || registration.isEnabled?.() === false) continue;
|
|
5320
|
-
if (plugin.manifest.name === "junior") throw new Error(`Plugin "${
|
|
5429
|
+
if (plugin.manifest.name === "junior" || plugin.manifest.name === "slack") throw new Error(`Plugin "${plugin.manifest.name}" cannot register events; that namespace is reserved for core events`);
|
|
5321
5430
|
catalog[plugin.manifest.name] = registration;
|
|
5322
5431
|
}
|
|
5323
5432
|
return catalog;
|
|
@@ -9618,6 +9727,8 @@ function createUpdatePlanTool() {
|
|
|
9618
9727
|
//#region src/chat/agent-invocations/types.ts
|
|
9619
9728
|
const exactStringSchema = z.string().min(1).refine((value) => value === value.trim());
|
|
9620
9729
|
const agentNameSchema = exactStringSchema.max(64);
|
|
9730
|
+
/** `messageAgent` target for the parent of a child agent. */
|
|
9731
|
+
const PARENT_AGENT_TARGET = "parent";
|
|
9621
9732
|
const agentInvocationMailboxStatusSchema = z.enum(AGENT_INVOCATION_MAILBOX_STATUSES);
|
|
9622
9733
|
const agentInvocationParentMailboxStatusSchema = z.enum(AGENT_INVOCATION_PARENT_MAILBOX_STATUSES);
|
|
9623
9734
|
const agentBindingSchema = z.object({
|
|
@@ -9677,13 +9788,19 @@ const createAgentInvocationSchema = z.object({
|
|
|
9677
9788
|
reasoningLevel: z.enum(TURN_REASONING_LEVELS).optional(),
|
|
9678
9789
|
title: exactStringSchema.optional()
|
|
9679
9790
|
}).strict();
|
|
9791
|
+
/** How a finished task of a child agent ended. A stop is not a result. */
|
|
9792
|
+
const CHILD_AGENT_RESULTS = [
|
|
9793
|
+
"completed",
|
|
9794
|
+
"blocked",
|
|
9795
|
+
"failed"
|
|
9796
|
+
];
|
|
9680
9797
|
//#endregion
|
|
9681
9798
|
//#region src/chat/agent-invocations/errors.ts
|
|
9682
|
-
/**
|
|
9683
|
-
var
|
|
9799
|
+
/** A child agent already uses this name in the parent Conversation. */
|
|
9800
|
+
var AgentNameInUseError = class extends Error {
|
|
9684
9801
|
constructor(name) {
|
|
9685
|
-
super(`
|
|
9686
|
-
this.name = "
|
|
9802
|
+
super(`A child agent named "${name}" already exists`);
|
|
9803
|
+
this.name = "AgentNameInUseError";
|
|
9687
9804
|
}
|
|
9688
9805
|
};
|
|
9689
9806
|
/** Parent already has the maximum number of non-terminal child invocations. */
|
|
@@ -9693,10 +9810,28 @@ var AgentInvocationLimitError = class extends Error {
|
|
|
9693
9810
|
this.name = "AgentInvocationLimitError";
|
|
9694
9811
|
}
|
|
9695
9812
|
};
|
|
9813
|
+
/** A child agent already has the maximum number of open tasks. */
|
|
9814
|
+
var AgentMessageQueueFullError = class extends Error {
|
|
9815
|
+
constructor(limit) {
|
|
9816
|
+
super(`The child agent already has ${limit} open tasks`);
|
|
9817
|
+
this.name = "AgentMessageQueueFullError";
|
|
9818
|
+
}
|
|
9819
|
+
};
|
|
9820
|
+
/** A messageAgent target is not the parent or a child of the sender. */
|
|
9821
|
+
var AgentMessageTargetError = class extends Error {
|
|
9822
|
+
constructor(message) {
|
|
9823
|
+
super(message);
|
|
9824
|
+
this.name = "AgentMessageTargetError";
|
|
9825
|
+
}
|
|
9826
|
+
};
|
|
9696
9827
|
//#endregion
|
|
9697
9828
|
//#region src/chat/agent-invocations/store.ts
|
|
9698
9829
|
const CREATE_LOCK_PREFIX = "junior:agent_invocation:create";
|
|
9699
|
-
|
|
9830
|
+
/**
|
|
9831
|
+
* Return the SQL lock that one parent Conversation takes to start, message,
|
|
9832
|
+
* or stop its child agents.
|
|
9833
|
+
*/
|
|
9834
|
+
function getChildAgentLockName(parentConversationId) {
|
|
9700
9835
|
return `${CREATE_LOCK_PREFIX}:${parentConversationId}`;
|
|
9701
9836
|
}
|
|
9702
9837
|
const TERMINAL_AGENT_INVOCATION_STATUSES = [
|
|
@@ -9705,6 +9840,7 @@ const TERMINAL_AGENT_INVOCATION_STATUSES = [
|
|
|
9705
9840
|
"failed",
|
|
9706
9841
|
"stopped"
|
|
9707
9842
|
];
|
|
9843
|
+
/** Statuses of an agent invocation that has not finished. */
|
|
9708
9844
|
const NON_TERMINAL_AGENT_INVOCATION_STATUSES = [
|
|
9709
9845
|
"pending",
|
|
9710
9846
|
"running",
|
|
@@ -9732,6 +9868,13 @@ function getNamedAgentConversationId(parentConversationId, name) {
|
|
|
9732
9868
|
function getAgentInvocationIdForResult(resultInvocationId) {
|
|
9733
9869
|
return stableId("agent-invocation", "result", resultInvocationId);
|
|
9734
9870
|
}
|
|
9871
|
+
/**
|
|
9872
|
+
* Return the stable identity of the agent invocation that gives one message
|
|
9873
|
+
* from a child agent to the child Conversation that started that child agent.
|
|
9874
|
+
*/
|
|
9875
|
+
function getAgentInvocationIdForMessage(messageId) {
|
|
9876
|
+
return stableId("agent-invocation", "message", messageId);
|
|
9877
|
+
}
|
|
9735
9878
|
/** Return the stable turn identity advanced by one agent invocation. */
|
|
9736
9879
|
function getAgentInvocationTurnId(invocationId) {
|
|
9737
9880
|
return `agent-invocation:${invocationId}`;
|
|
@@ -9751,6 +9894,7 @@ function bindingFromRow(row) {
|
|
|
9751
9894
|
parentConversationId: row.parentConversationId
|
|
9752
9895
|
});
|
|
9753
9896
|
}
|
|
9897
|
+
/** Parse one agent invocation row. */
|
|
9754
9898
|
function invocationFromRow(row) {
|
|
9755
9899
|
return agentInvocationSchema.parse({
|
|
9756
9900
|
actor: row.actor,
|
|
@@ -9804,87 +9948,74 @@ async function getLatestAgentInvocationForConversation(childConversationId) {
|
|
|
9804
9948
|
const rows = await getSqlExecutor().db().select().from(juniorAgentInvocations).where(eq(juniorAgentInvocations.childConversationId, childConversationId)).orderBy(desc(juniorAgentInvocations.createdAt)).limit(1);
|
|
9805
9949
|
return rows[0] ? invocationFromRow(rows[0]) : void 0;
|
|
9806
9950
|
}
|
|
9807
|
-
/** Bound the agent invocations that one child agent listing reads. */
|
|
9808
|
-
const CHILD_AGENT_LIST_INVOCATION_LIMIT = 200;
|
|
9809
|
-
/** Return the child state that one child's agent invocations show. */
|
|
9810
|
-
function childAgentFromInvocations(invocations, resultInvocationIds) {
|
|
9811
|
-
const latest = invocations[0];
|
|
9812
|
-
let current = latest;
|
|
9813
|
-
for (const invocation of invocations) if (!isTerminalAgentInvocation(invocation)) current = invocation;
|
|
9814
|
-
const task = invocations.find((invocation) => !resultInvocationIds.has(invocation.invocationId)) ?? invocations[invocations.length - 1];
|
|
9815
|
-
const terminal = isTerminalAgentInvocation(current) ? current : void 0;
|
|
9816
|
-
return {
|
|
9817
|
-
...latest.agentName ? { agentName: latest.agentName } : void 0,
|
|
9818
|
-
childConversationId: latest.childConversationId,
|
|
9819
|
-
...terminal && terminal.status !== "completed" ? { errorMessage: terminal.errorMessage } : void 0,
|
|
9820
|
-
status: terminal?.parentMailboxStatus === "replaced" ? "pending" : terminal?.parentMailboxStatus === "skipped" ? "stopped" : current.status,
|
|
9821
|
-
task: task.input,
|
|
9822
|
-
...task.title ? { title: task.title } : void 0,
|
|
9823
|
-
updatedAtMs: Math.max(...invocations.map((invocation) => invocation.updatedAtMs))
|
|
9824
|
-
};
|
|
9825
|
-
}
|
|
9826
9951
|
/**
|
|
9827
|
-
*
|
|
9828
|
-
*
|
|
9829
|
-
*
|
|
9952
|
+
* Read the name and the newest task title of one child Conversation, for
|
|
9953
|
+
* labels. Agent invocations that give a nested result to the child have no
|
|
9954
|
+
* title, so an agent invocation with a title comes first.
|
|
9830
9955
|
*/
|
|
9831
|
-
async function
|
|
9832
|
-
const
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
const invocations = byChild.get(invocation.childConversationId) ?? [];
|
|
9838
|
-
invocations.push(invocation);
|
|
9839
|
-
byChild.set(invocation.childConversationId, invocations);
|
|
9840
|
-
}
|
|
9841
|
-
const grandchildren = await db.select({ invocationId: juniorAgentInvocations.invocationId }).from(juniorAgentInvocations).where(inArray(juniorAgentInvocations.parentConversationId, [...byChild.keys()]));
|
|
9842
|
-
const resultInvocationIds = new Set(grandchildren.map((row) => getAgentInvocationIdForResult(row.invocationId)));
|
|
9843
|
-
return [...byChild.values()].map((invocations) => childAgentFromInvocations(invocations, resultInvocationIds)).sort((left, right) => right.updatedAtMs - left.updatedAtMs);
|
|
9956
|
+
async function getChildAgentLabel(childConversationId) {
|
|
9957
|
+
const [row] = await getSqlExecutor().db().select({
|
|
9958
|
+
agentName: juniorAgentInvocations.agentName,
|
|
9959
|
+
title: juniorAgentInvocations.title
|
|
9960
|
+
}).from(juniorAgentInvocations).where(eq(juniorAgentInvocations.childConversationId, childConversationId)).orderBy(sql`${juniorAgentInvocations.title} is null`, desc(juniorAgentInvocations.createdAt)).limit(1);
|
|
9961
|
+
return row;
|
|
9844
9962
|
}
|
|
9845
9963
|
async function getNonTerminalAgentInvocationForConversation(childConversationId) {
|
|
9846
9964
|
const rows = await getSqlExecutor().db().select().from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.childConversationId, childConversationId), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES))).orderBy(asc(juniorAgentInvocations.createdAt)).limit(1);
|
|
9847
9965
|
return rows[0] ? invocationFromRow(rows[0]) : void 0;
|
|
9848
9966
|
}
|
|
9849
|
-
async function
|
|
9850
|
-
return (await getSqlExecutor().db().select({ value: count() }).from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.
|
|
9967
|
+
async function countNonTerminalAgentInvocationsForChild(childConversationId) {
|
|
9968
|
+
return (await getSqlExecutor().db().select({ value: count() }).from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.childConversationId, childConversationId), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES))))[0]?.value ?? 0;
|
|
9851
9969
|
}
|
|
9852
9970
|
/**
|
|
9853
|
-
*
|
|
9854
|
-
*
|
|
9971
|
+
* A child agent can only start or give work to child agents while it has
|
|
9972
|
+
* open work. A stopped child has none. A root Conversation has no agent
|
|
9973
|
+
* invocation, so it always passes.
|
|
9974
|
+
*/
|
|
9975
|
+
async function assertCanGiveChildWork(parentConversationId) {
|
|
9976
|
+
if (await getLatestAgentInvocationForConversation(parentConversationId) && !await getNonTerminalAgentInvocationForConversation(parentConversationId)) throw new Error(`Agent conversation ${parentConversationId} has no open work and cannot give work to child agents`);
|
|
9977
|
+
}
|
|
9978
|
+
/**
|
|
9979
|
+
* Count the child agents of one parent that have open work. Messages queued
|
|
9980
|
+
* for a busy child do not count again.
|
|
9981
|
+
*/
|
|
9982
|
+
async function countActiveChildAgentsForParent(parentConversationId) {
|
|
9983
|
+
return (await getSqlExecutor().db().select({ value: countDistinct(juniorAgentInvocations.childConversationId) }).from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.parentConversationId, parentConversationId), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES))))[0]?.value ?? 0;
|
|
9984
|
+
}
|
|
9985
|
+
/**
|
|
9986
|
+
* Create or replay the first invocation of one new child agent. A name gives
|
|
9987
|
+
* the child a stable Conversation id that later messages can address. A name
|
|
9988
|
+
* that is already used throws: later work for that child goes through
|
|
9989
|
+
* `createAgentInvocationForMessage`.
|
|
9855
9990
|
*/
|
|
9856
9991
|
async function createAgentInvocation(rawInput, nowMs = Date.now()) {
|
|
9857
9992
|
const input = createAgentInvocationSchema.parse(rawInput);
|
|
9858
9993
|
const invocationId = getAgentInvocationId(input.parentConversationId, input.idempotencyKey);
|
|
9859
9994
|
const childConversationId = input.agentName ? getNamedAgentConversationId(input.parentConversationId, input.agentName) : getUnnamedAgentConversationId(invocationId);
|
|
9860
|
-
const lockName =
|
|
9995
|
+
const lockName = getChildAgentLockName(input.parentConversationId);
|
|
9861
9996
|
return await getSqlExecutor().withLock(lockName, async () => {
|
|
9862
9997
|
const existing = await getAgentInvocation(invocationId);
|
|
9863
9998
|
if (existing) {
|
|
9864
9999
|
if (!sameCreateInput(existing, input)) throw new Error(`Agent invocation idempotency key was reused with different input for ${invocationId}`);
|
|
9865
10000
|
return existing;
|
|
9866
10001
|
}
|
|
9867
|
-
|
|
9868
|
-
if (
|
|
10002
|
+
await assertCanGiveChildWork(input.parentConversationId);
|
|
10003
|
+
if (input.agentName && await getAgentBinding({
|
|
10004
|
+
name: input.agentName,
|
|
10005
|
+
parentConversationId: input.parentConversationId
|
|
10006
|
+
})) throw new AgentNameInUseError(input.agentName);
|
|
10007
|
+
if (await countActiveChildAgentsForParent(input.parentConversationId) >= 8) throw new AgentInvocationLimitError(8);
|
|
9869
10008
|
await getConversationStore().createChild({
|
|
9870
10009
|
childConversationId,
|
|
9871
10010
|
parentConversationId: input.parentConversationId,
|
|
9872
10011
|
nowMs,
|
|
9873
10012
|
source: "internal"
|
|
9874
10013
|
});
|
|
9875
|
-
if (input.agentName) {
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
}).onConflictDoNothing();
|
|
9881
|
-
const binding = await getAgentBinding({
|
|
9882
|
-
name: input.agentName,
|
|
9883
|
-
parentConversationId: input.parentConversationId
|
|
9884
|
-
});
|
|
9885
|
-
if (!binding || binding.childConversationId !== childConversationId) throw new Error(`Named agent binding did not resolve to ${childConversationId}`);
|
|
9886
|
-
if (await getNonTerminalAgentInvocationForConversation(childConversationId)) throw new AgentInvocationBusyError(input.agentName);
|
|
9887
|
-
}
|
|
10014
|
+
if (input.agentName) await getSqlExecutor().db().insert(juniorAgentBindings).values({
|
|
10015
|
+
childConversationId,
|
|
10016
|
+
name: input.agentName,
|
|
10017
|
+
parentConversationId: input.parentConversationId
|
|
10018
|
+
});
|
|
9888
10019
|
await getSqlExecutor().db().insert(juniorAgentInvocations).values({
|
|
9889
10020
|
invocationId,
|
|
9890
10021
|
parentConversationId: input.parentConversationId,
|
|
@@ -9910,13 +10041,14 @@ async function createAgentInvocation(rawInput, nowMs = Date.now()) {
|
|
|
9910
10041
|
});
|
|
9911
10042
|
}
|
|
9912
10043
|
/**
|
|
9913
|
-
* Create or replay the agent invocation that gives one child agent result
|
|
9914
|
-
* the child Conversation that started
|
|
9915
|
-
* the same parent, Actor, and credentials as
|
|
9916
|
-
* active-count checks because it continues
|
|
10044
|
+
* Create or replay the agent invocation that gives one child agent result or
|
|
10045
|
+
* message to the child Conversation that started that child agent. It
|
|
10046
|
+
* continues that Conversation for the same parent, Actor, and credentials as
|
|
10047
|
+
* `latest`. It skips the queue and active-count checks because it continues
|
|
10048
|
+
* work that already passed them.
|
|
9917
10049
|
*/
|
|
9918
|
-
async function
|
|
9919
|
-
const invocationId =
|
|
10050
|
+
async function createAgentInvocationFromLatest(args, nowMs = Date.now()) {
|
|
10051
|
+
const { invocationId } = args;
|
|
9920
10052
|
await getSqlExecutor().db().insert(juniorAgentInvocations).values({
|
|
9921
10053
|
invocationId,
|
|
9922
10054
|
parentConversationId: args.latest.parentConversationId,
|
|
@@ -9938,6 +10070,73 @@ async function createAgentInvocationForResult(args, nowMs = Date.now()) {
|
|
|
9938
10070
|
if (!invocation || invocation.childConversationId !== args.latest.childConversationId) throw new Error(`Agent invocation creation raced for ${invocationId}`);
|
|
9939
10071
|
return invocation;
|
|
9940
10072
|
}
|
|
10073
|
+
/** Find a direct child agent of one parent by name or child Conversation id. */
|
|
10074
|
+
async function findChildAgentConversationId(args) {
|
|
10075
|
+
const binding = await getAgentBinding({
|
|
10076
|
+
name: args.target,
|
|
10077
|
+
parentConversationId: args.parentConversationId
|
|
10078
|
+
});
|
|
10079
|
+
if (binding) return binding.childConversationId;
|
|
10080
|
+
return (await getSqlExecutor().db().select({ childConversationId: juniorAgentInvocations.childConversationId }).from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.parentConversationId, args.parentConversationId), eq(juniorAgentInvocations.childConversationId, args.target))).limit(1))[0]?.childConversationId;
|
|
10081
|
+
}
|
|
10082
|
+
/**
|
|
10083
|
+
* Return whether a pending agent invocation still waits for older open work
|
|
10084
|
+
* on its child.
|
|
10085
|
+
*/
|
|
10086
|
+
async function isQueuedBehindOpenWork(invocation) {
|
|
10087
|
+
if (invocation.status !== "pending") return false;
|
|
10088
|
+
return (await getSqlExecutor().db().select({ invocationId: juniorAgentInvocations.invocationId }).from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.childConversationId, invocation.childConversationId), lt(juniorAgentInvocations.createdAt, new Date(invocation.createdAtMs)), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES))).limit(1)).length > 0;
|
|
10089
|
+
}
|
|
10090
|
+
/**
|
|
10091
|
+
* Create or replay the agent invocation for one message from a parent to one
|
|
10092
|
+
* of its child agents. The child continues its history. If the child already
|
|
10093
|
+
* has open work, the new agent invocation waits for that work and the parent
|
|
10094
|
+
* gets one report for both. A stopped child starts again.
|
|
10095
|
+
*/
|
|
10096
|
+
async function createAgentInvocationForMessage(input, nowMs = Date.now()) {
|
|
10097
|
+
const invocationId = getAgentInvocationId(input.parentConversationId, input.idempotencyKey);
|
|
10098
|
+
return await getSqlExecutor().withLock(getChildAgentLockName(input.parentConversationId), async () => {
|
|
10099
|
+
const existing = await getAgentInvocation(invocationId);
|
|
10100
|
+
if (existing) {
|
|
10101
|
+
if (existing.childConversationId !== input.childConversationId || existing.input !== input.input) throw new Error(`Agent invocation idempotency key was reused with different input for ${invocationId}`);
|
|
10102
|
+
return {
|
|
10103
|
+
invocation: existing,
|
|
10104
|
+
queued: await isQueuedBehindOpenWork(existing)
|
|
10105
|
+
};
|
|
10106
|
+
}
|
|
10107
|
+
const latest = await getLatestAgentInvocationForConversation(input.childConversationId);
|
|
10108
|
+
if (!latest || latest.parentConversationId !== input.parentConversationId) throw new Error(`${input.childConversationId} is not a child agent of ${input.parentConversationId}`);
|
|
10109
|
+
await assertCanGiveChildWork(input.parentConversationId);
|
|
10110
|
+
const openCount = await countNonTerminalAgentInvocationsForChild(input.childConversationId);
|
|
10111
|
+
if (openCount >= 4) throw new AgentMessageQueueFullError(4);
|
|
10112
|
+
if (openCount === 0 && await countActiveChildAgentsForParent(input.parentConversationId) >= 8) throw new AgentInvocationLimitError(8);
|
|
10113
|
+
await getSqlExecutor().db().insert(juniorAgentInvocations).values({
|
|
10114
|
+
invocationId,
|
|
10115
|
+
parentConversationId: input.parentConversationId,
|
|
10116
|
+
childConversationId: input.childConversationId,
|
|
10117
|
+
agentName: latest.agentName ?? null,
|
|
10118
|
+
input: input.input,
|
|
10119
|
+
title: null,
|
|
10120
|
+
actor: input.actor,
|
|
10121
|
+
credentialContext: input.credentialContext ?? null,
|
|
10122
|
+
source: { kind: "agent_invocation" },
|
|
10123
|
+
destination: input.destination,
|
|
10124
|
+
reasoningLevel: latest.reasoningLevel ?? null,
|
|
10125
|
+
parentToolCallId: input.parentToolCallId,
|
|
10126
|
+
status: "pending",
|
|
10127
|
+
mailboxStatus: "pending",
|
|
10128
|
+
createdAt: new Date(nowMs),
|
|
10129
|
+
updatedAt: new Date(nowMs),
|
|
10130
|
+
terminalAt: null
|
|
10131
|
+
}).onConflictDoNothing();
|
|
10132
|
+
const invocation = await getAgentInvocation(invocationId);
|
|
10133
|
+
if (!invocation) throw new Error(`Agent invocation creation raced for ${invocationId}`);
|
|
10134
|
+
return {
|
|
10135
|
+
invocation,
|
|
10136
|
+
queued: openCount > 0
|
|
10137
|
+
};
|
|
10138
|
+
});
|
|
10139
|
+
}
|
|
9941
10140
|
/**
|
|
9942
10141
|
* Return whether a child Conversation has work that will start another agent
|
|
9943
10142
|
* invocation on it: another open agent invocation, an open child agent, or a
|
|
@@ -10020,76 +10219,90 @@ async function completeAgentInvocation(args) {
|
|
|
10020
10219
|
}).where(and(eq(juniorAgentInvocations.invocationId, args.invocationId), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES)));
|
|
10021
10220
|
return await getAgentInvocation(args.invocationId);
|
|
10022
10221
|
}
|
|
10023
|
-
/**
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
* Conversation's children, so no child agent can start in the tree until the
|
|
10027
|
-
* caller's transaction ends.
|
|
10028
|
-
*/
|
|
10029
|
-
async function lockAgentConversationTree(childConversationId) {
|
|
10030
|
-
const executor = getSqlExecutor();
|
|
10031
|
-
const conversationIds = [childConversationId];
|
|
10032
|
-
let parents = conversationIds;
|
|
10033
|
-
while (parents.length > 0) {
|
|
10034
|
-
for (const parent of parents) await executor.withLock(createLockName(parent), async () => void 0);
|
|
10035
|
-
parents = (await executor.db().selectDistinct({ childConversationId: juniorAgentInvocations.childConversationId }).from(juniorAgentInvocations).where(inArray(juniorAgentInvocations.parentConversationId, parents))).map((row) => row.childConversationId).filter((id) => !conversationIds.includes(id));
|
|
10036
|
-
conversationIds.push(...parents);
|
|
10037
|
-
}
|
|
10038
|
-
return conversationIds;
|
|
10039
|
-
}
|
|
10040
|
-
/**
|
|
10041
|
-
* Stop the agent invocations of one direct child agent and of every child
|
|
10042
|
-
* agent below it. Returns undefined when the child does not belong to the
|
|
10043
|
-
* parent.
|
|
10044
|
-
*/
|
|
10045
|
-
async function stopChildAgentInvocations(args) {
|
|
10046
|
-
if ((await getSqlExecutor().db().select({ invocationId: juniorAgentInvocations.invocationId }).from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.parentConversationId, args.parentConversationId), eq(juniorAgentInvocations.childConversationId, args.childConversationId))).limit(1)).length === 0) return;
|
|
10047
|
-
return await stopAgentInvocationTree(args.childConversationId, args.nowMs);
|
|
10222
|
+
/** Return whether an agent invocation has finished. */
|
|
10223
|
+
function isTerminalAgentInvocation(invocation) {
|
|
10224
|
+
return TERMINAL_AGENT_INVOCATION_STATUSES.includes(invocation.status);
|
|
10048
10225
|
}
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
*/
|
|
10054
|
-
async function stopAgentInvocationTree(conversationId, nowMs = Date.now()) {
|
|
10055
|
-
return await getSqlExecutor().withLock(createLockName(conversationId), () => stopLockedAgentInvocationTree(conversationId, nowMs));
|
|
10226
|
+
//#endregion
|
|
10227
|
+
//#region src/chat/agent-invocations/list.ts
|
|
10228
|
+
function isChildAgentResult(status) {
|
|
10229
|
+
return CHILD_AGENT_RESULTS.includes(status);
|
|
10056
10230
|
}
|
|
10057
|
-
|
|
10058
|
-
|
|
10059
|
-
|
|
10060
|
-
|
|
10061
|
-
const
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
}).where(and(inArray(juniorAgentInvocations.childConversationId, conversationIds), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES))).returning();
|
|
10069
|
-
await db.update(juniorAgentInvocations).set({
|
|
10070
|
-
parentMailboxStatus: "skipped",
|
|
10071
|
-
updatedAt: now
|
|
10072
|
-
}).where(or(and(inArray(juniorAgentInvocations.parentConversationId, conversationIds), inArray(juniorAgentInvocations.parentMailboxStatus, ["pending", "appended"])), and(inArray(juniorAgentInvocations.childConversationId, conversationIds), eq(juniorAgentInvocations.parentMailboxStatus, "replaced"))));
|
|
10231
|
+
/** Bound the agent invocations that one child agent listing reads. */
|
|
10232
|
+
const CHILD_AGENT_LIST_INVOCATION_LIMIT = 200;
|
|
10233
|
+
/** Return the child state that one child's agent invocations show. */
|
|
10234
|
+
function childAgentFromInvocations(invocations, resultInvocationIds) {
|
|
10235
|
+
const latest = invocations[0];
|
|
10236
|
+
let current = latest;
|
|
10237
|
+
for (const invocation of invocations) if (!isTerminalAgentInvocation(invocation)) current = invocation;
|
|
10238
|
+
const tasks = invocations.filter((invocation) => !resultInvocationIds.has(invocation.invocationId));
|
|
10239
|
+
const task = tasks.find((invocation) => invocation.parentToolCallId) ?? tasks[0] ?? invocations[invocations.length - 1];
|
|
10240
|
+
const terminal = isTerminalAgentInvocation(current) ? current : void 0;
|
|
10241
|
+
const lastResult = invocations.filter(isTerminalAgentInvocation).filter((invocation) => invocation.parentMailboxStatus !== "replaced" && invocation.parentMailboxStatus !== "skipped").map((invocation) => invocation.status).find(isChildAgentResult);
|
|
10073
10242
|
return {
|
|
10074
|
-
|
|
10075
|
-
|
|
10243
|
+
...latest.agentName ? { agentName: latest.agentName } : void 0,
|
|
10244
|
+
childConversationId: latest.childConversationId,
|
|
10245
|
+
...terminal && terminal.status !== "completed" ? { errorMessage: terminal.errorMessage } : void 0,
|
|
10246
|
+
...lastResult ? { lastResult } : void 0,
|
|
10247
|
+
status: terminal?.parentMailboxStatus === "replaced" ? "pending" : terminal?.parentMailboxStatus === "skipped" ? "stopped" : current.status,
|
|
10248
|
+
task: task.input,
|
|
10249
|
+
...task.title ? { title: task.title } : void 0,
|
|
10250
|
+
updatedAtMs: Math.max(...invocations.map((invocation) => invocation.updatedAtMs))
|
|
10076
10251
|
};
|
|
10077
10252
|
}
|
|
10078
|
-
/**
|
|
10079
|
-
|
|
10080
|
-
|
|
10253
|
+
/**
|
|
10254
|
+
* List the direct child agents of one parent Conversation, with the newest
|
|
10255
|
+
* activity first. The state comes from SQL agent invocation records, not from
|
|
10256
|
+
* model output.
|
|
10257
|
+
*/
|
|
10258
|
+
async function listChildAgents(parentConversationId) {
|
|
10259
|
+
const db = getSqlExecutor().db();
|
|
10260
|
+
const rows = await db.select().from(juniorAgentInvocations).where(eq(juniorAgentInvocations.parentConversationId, parentConversationId)).orderBy(desc(juniorAgentInvocations.createdAt)).limit(CHILD_AGENT_LIST_INVOCATION_LIMIT);
|
|
10261
|
+
if (rows.length === 0) return [];
|
|
10262
|
+
const byChild = /* @__PURE__ */ new Map();
|
|
10263
|
+
for (const invocation of rows.map(invocationFromRow)) {
|
|
10264
|
+
const invocations = byChild.get(invocation.childConversationId) ?? [];
|
|
10265
|
+
invocations.push(invocation);
|
|
10266
|
+
byChild.set(invocation.childConversationId, invocations);
|
|
10267
|
+
}
|
|
10268
|
+
const grandchildren = await db.select({ invocationId: juniorAgentInvocations.invocationId }).from(juniorAgentInvocations).where(inArray(juniorAgentInvocations.parentConversationId, [...byChild.keys()]));
|
|
10269
|
+
const resultInvocationIds = new Set(grandchildren.map((row) => getAgentInvocationIdForResult(row.invocationId)));
|
|
10270
|
+
return [...byChild.values()].map((invocations) => childAgentFromInvocations(invocations, resultInvocationIds)).sort((left, right) => right.updatedAtMs - left.updatedAtMs);
|
|
10081
10271
|
}
|
|
10082
10272
|
//#endregion
|
|
10083
10273
|
//#region src/chat/tools/runtime/list-agents.ts
|
|
10084
10274
|
const TASK_MAX_LENGTH = 1e3;
|
|
10085
10275
|
const ERROR_MAX_LENGTH = 500;
|
|
10276
|
+
/**
|
|
10277
|
+
* What a child agent does now. The words match the Conversation execution
|
|
10278
|
+
* status, plus `stopped` after stopAgent.
|
|
10279
|
+
*/
|
|
10280
|
+
const CHILD_AGENT_STATUSES = [
|
|
10281
|
+
"pending",
|
|
10282
|
+
"running",
|
|
10283
|
+
"paused",
|
|
10284
|
+
"idle",
|
|
10285
|
+
"stopped"
|
|
10286
|
+
];
|
|
10287
|
+
function childAgentStatus(agent) {
|
|
10288
|
+
switch (agent.status) {
|
|
10289
|
+
case "pending":
|
|
10290
|
+
case "running":
|
|
10291
|
+
case "paused":
|
|
10292
|
+
case "stopped": return agent.status;
|
|
10293
|
+
case "blocked":
|
|
10294
|
+
case "completed":
|
|
10295
|
+
case "failed": return "idle";
|
|
10296
|
+
}
|
|
10297
|
+
}
|
|
10086
10298
|
function truncate(text, maxLength) {
|
|
10087
10299
|
return text.length > maxLength ? `${text.slice(0, maxLength)}…` : text;
|
|
10088
10300
|
}
|
|
10089
10301
|
const listAgentsOutputSchema = juniorToolOutputSchema.extend({ agents: z.array(z.object({
|
|
10090
10302
|
conversation_id: z.string().min(1).describe("Child conversation ID. Pass it to searchConversationEvents to read the child's history."),
|
|
10091
|
-
name: agentNameSchema.optional().describe("Child-agent name. Pass it to
|
|
10092
|
-
status: z.enum(
|
|
10303
|
+
name: agentNameSchema.optional().describe("Child-agent name. Pass it to messageAgent."),
|
|
10304
|
+
status: z.enum(CHILD_AGENT_STATUSES).describe("What the child agent does now. pending: waiting to run. running or paused: working. idle: finished its work and waits for a message. stopped: stopAgent stopped it."),
|
|
10305
|
+
last_result: z.enum(CHILD_AGENT_RESULTS).optional().describe("How the latest finished task of the child agent ended"),
|
|
10093
10306
|
task: z.string().describe("Latest task given to the child agent"),
|
|
10094
10307
|
title: z.string().optional().describe("Short display title of the latest task"),
|
|
10095
10308
|
error: z.string().optional().describe("Why the child agent is blocked, failed, or stopped"),
|
|
@@ -10098,7 +10311,7 @@ const listAgentsOutputSchema = juniorToolOutputSchema.extend({ agents: z.array(z
|
|
|
10098
10311
|
/** Create the read-only tool that lists the child agents of one conversation. */
|
|
10099
10312
|
function createListAgentsTool(conversationId) {
|
|
10100
10313
|
return zodTool({
|
|
10101
|
-
description: "List the child agents that this conversation started, with the state recorded by the runtime. Use it to check which child agents are still working, or to find a
|
|
10314
|
+
description: "List the child agents that this conversation started, with the state recorded by the runtime. Use it to check which child agents are still working, or to find a child agent before you give it more work with messageAgent. Results still arrive as new tasks; do not poll with this tool.",
|
|
10102
10315
|
annotations: {
|
|
10103
10316
|
destructiveHint: false,
|
|
10104
10317
|
idempotentHint: true,
|
|
@@ -10110,7 +10323,8 @@ function createListAgentsTool(conversationId) {
|
|
|
10110
10323
|
execute: async () => ({ agents: (await listChildAgents(conversationId)).map((agent) => ({
|
|
10111
10324
|
conversation_id: agent.childConversationId,
|
|
10112
10325
|
...agent.agentName ? { name: agent.agentName } : void 0,
|
|
10113
|
-
status: agent
|
|
10326
|
+
status: childAgentStatus(agent),
|
|
10327
|
+
...agent.lastResult ? { last_result: agent.lastResult } : void 0,
|
|
10114
10328
|
task: truncate(agent.task, TASK_MAX_LENGTH),
|
|
10115
10329
|
...agent.title ? { title: agent.title } : void 0,
|
|
10116
10330
|
...agent.errorMessage ? { error: truncate(agent.errorMessage, ERROR_MAX_LENGTH) } : void 0,
|
|
@@ -10119,12 +10333,59 @@ function createListAgentsTool(conversationId) {
|
|
|
10119
10333
|
});
|
|
10120
10334
|
}
|
|
10121
10335
|
//#endregion
|
|
10336
|
+
//#region src/chat/tools/runtime/message-agent.ts
|
|
10337
|
+
/** Bound the text of one message so a loop cannot fill a mailbox quickly. */
|
|
10338
|
+
const MESSAGE_AGENT_TEXT_MAX_LENGTH = 8e3;
|
|
10339
|
+
const messageAgentOutputSchema = juniorToolOutputSchema.extend({
|
|
10340
|
+
conversation_id: z.string().min(1).describe("Target conversation ID"),
|
|
10341
|
+
delivery: z.enum(["started", "queued"]).describe("started: the child agent starts work on the message now. queued: the target reads the message after its current work.")
|
|
10342
|
+
});
|
|
10343
|
+
/** Create the tool that sends one message to the parent or a child agent. */
|
|
10344
|
+
function createMessageAgentTool(messageAgent) {
|
|
10345
|
+
return zodTool({
|
|
10346
|
+
description: `Send a message to your parent agent or to a child agent that this conversation started. Use it to give an existing child agent more work, an answer, or a correction, and to send your parent agent a finding or a question while you keep working. A message to a child continues its history. If the child is still working, it reads the message after its current work, and one result covers both. The result arrives later as a new task in this conversation; do not wait or poll for it. A message to your parent does not end your task. Use spawnAgent only to start a new child agent.`,
|
|
10347
|
+
annotations: {
|
|
10348
|
+
destructiveHint: false,
|
|
10349
|
+
idempotentHint: true,
|
|
10350
|
+
openWorldHint: false,
|
|
10351
|
+
readOnlyHint: false
|
|
10352
|
+
},
|
|
10353
|
+
inputSchema: z.object({
|
|
10354
|
+
to: z.string().trim().min(1).describe(`"${PARENT_AGENT_TARGET}" for your parent agent, or the name or conversation_id of a child agent from spawnAgent or listAgents`),
|
|
10355
|
+
text: z.string().trim().min(1).max(MESSAGE_AGENT_TEXT_MAX_LENGTH).describe("Message text. Include all context the target needs.")
|
|
10356
|
+
}).strict(),
|
|
10357
|
+
outputSchema: messageAgentOutputSchema,
|
|
10358
|
+
execute: async (input, options) => {
|
|
10359
|
+
if (!options.toolCallId) throw new ToolInputError("messageAgent requires an active tool call ID");
|
|
10360
|
+
let result;
|
|
10361
|
+
try {
|
|
10362
|
+
result = await messageAgent({
|
|
10363
|
+
text: input.text,
|
|
10364
|
+
to: input.to
|
|
10365
|
+
}, {
|
|
10366
|
+
...options.signal ? { signal: options.signal } : void 0,
|
|
10367
|
+
toolCallId: options.toolCallId
|
|
10368
|
+
});
|
|
10369
|
+
} catch (error) {
|
|
10370
|
+
if (error instanceof AgentMessageTargetError) throw new ToolInputError(`${error.message}. Use listAgents to find a child agent.`, { cause: error });
|
|
10371
|
+
if (error instanceof AgentMessageQueueFullError) throw new ToolInputError(`${error.message}. Wait for its result, then send one message with everything it needs.`, { cause: error });
|
|
10372
|
+
if (error instanceof AgentInvocationLimitError) throw new ToolInputError(`${error.message}. Wait for active children to finish before you give more work.`, { cause: error });
|
|
10373
|
+
throw error;
|
|
10374
|
+
}
|
|
10375
|
+
return {
|
|
10376
|
+
conversation_id: result.conversationId,
|
|
10377
|
+
delivery: result.delivery
|
|
10378
|
+
};
|
|
10379
|
+
}
|
|
10380
|
+
});
|
|
10381
|
+
}
|
|
10382
|
+
//#endregion
|
|
10122
10383
|
//#region src/chat/tools/runtime/spawn-agent.ts
|
|
10123
10384
|
const spawnAgentOutputSchema = juniorToolOutputSchema.extend({ conversation_id: z.string().min(1).describe("Child conversation ID. Pass it to searchConversationEvents to read the child's history.") });
|
|
10124
10385
|
/** Create the asynchronous child-agent tool for one parent run. */
|
|
10125
10386
|
function createSpawnAgentTool(spawnAgent) {
|
|
10126
10387
|
return zodTool({
|
|
10127
|
-
description: "Start
|
|
10388
|
+
description: "Start a new asynchronous child agent on a delegated task. Give it a name when you may send it more work later. To give more work to a child agent that already exists, use messageAgent instead. This call confirms durable scheduling, not task completion. The result arrives later as a new task in this conversation; do not wait or poll for it.",
|
|
10128
10389
|
annotations: {
|
|
10129
10390
|
destructiveHint: false,
|
|
10130
10391
|
idempotentHint: true,
|
|
@@ -10134,7 +10395,7 @@ function createSpawnAgentTool(spawnAgent) {
|
|
|
10134
10395
|
inputSchema: z.object({
|
|
10135
10396
|
task: z.string().trim().min(1).describe("Task for the child agent"),
|
|
10136
10397
|
title: z.string().trim().min(1).max(60).nullable().optional().describe("Optional short display title for the task. Omit to generate one from the task."),
|
|
10137
|
-
name: agentNameSchema.nullable().optional().describe("
|
|
10398
|
+
name: agentNameSchema.refine((name) => name !== PARENT_AGENT_TARGET, { message: `"${PARENT_AGENT_TARGET}" is reserved` }).nullable().optional().describe("New child-agent name that messageAgent can address, or omit for a one-off agent"),
|
|
10138
10399
|
reasoning_level: z.enum(TURN_REASONING_LEVELS).nullable().optional().describe("Optional reasoning level for this delegated task only")
|
|
10139
10400
|
}).strict(),
|
|
10140
10401
|
outputSchema: spawnAgentOutputSchema,
|
|
@@ -10158,7 +10419,7 @@ function createSpawnAgentTool(spawnAgent) {
|
|
|
10158
10419
|
toolCallId: options.toolCallId
|
|
10159
10420
|
});
|
|
10160
10421
|
} catch (error) {
|
|
10161
|
-
if (error instanceof
|
|
10422
|
+
if (error instanceof AgentNameInUseError) throw new ToolInputError(`${error.message}. Use messageAgent to give it more work, or choose a different name.`, { cause: error });
|
|
10162
10423
|
if (error instanceof AgentInvocationLimitError) throw new ToolInputError(`${error.message}. Wait for active children to finish before spawning more.`, { cause: error });
|
|
10163
10424
|
throw error;
|
|
10164
10425
|
}
|
|
@@ -10169,18 +10430,33 @@ function createSpawnAgentTool(spawnAgent) {
|
|
|
10169
10430
|
//#endregion
|
|
10170
10431
|
//#region src/chat/agent-invocations/markers.ts
|
|
10171
10432
|
/**
|
|
10172
|
-
* Dashboard
|
|
10433
|
+
* Dashboard records for agent invocations. The parent Conversation gets one
|
|
10173
10434
|
* `subagent_started` and one `subagent_ended` event for each agent invocation.
|
|
10435
|
+
* The child Conversation gets `message` events for its input and its result,
|
|
10436
|
+
* because a child has no Location that records visible messages.
|
|
10174
10437
|
*/
|
|
10175
10438
|
const SUBAGENT_KIND = "agent";
|
|
10176
10439
|
/**
|
|
10177
|
-
* Record in the parent Conversation that one agent invocation started
|
|
10178
|
-
*
|
|
10440
|
+
* Record in the parent Conversation that one agent invocation started, and
|
|
10441
|
+
* record its input as a user message in the child Conversation. The dashboard
|
|
10442
|
+
* pairs the parent event with `subagent_ended` to show the child and its
|
|
10179
10443
|
* status. It must be written before the child mailbox append, so the child
|
|
10180
10444
|
* cannot end first.
|
|
10181
10445
|
*/
|
|
10182
10446
|
async function recordSubagentStarted(invocation) {
|
|
10183
|
-
|
|
10447
|
+
const eventStore = getConversationEventStore();
|
|
10448
|
+
const messageId = getAgentInvocationMessageId(invocation.invocationId);
|
|
10449
|
+
await eventStore.append(invocation.childConversationId, [{
|
|
10450
|
+
idempotencyKey: `message:${messageId}`,
|
|
10451
|
+
data: {
|
|
10452
|
+
type: "message",
|
|
10453
|
+
messageId,
|
|
10454
|
+
role: "user",
|
|
10455
|
+
text: invocation.input
|
|
10456
|
+
},
|
|
10457
|
+
createdAtMs: invocation.createdAtMs
|
|
10458
|
+
}]);
|
|
10459
|
+
await eventStore.append(invocation.parentConversationId, [{
|
|
10184
10460
|
idempotencyKey: `subagent_started:${invocation.invocationId}`,
|
|
10185
10461
|
data: {
|
|
10186
10462
|
type: "subagent_started",
|
|
@@ -10205,12 +10481,14 @@ const SUBAGENT_OUTCOMES = {
|
|
|
10205
10481
|
stopped: "aborted"
|
|
10206
10482
|
};
|
|
10207
10483
|
/**
|
|
10208
|
-
* Record in the parent Conversation how one agent invocation ended
|
|
10484
|
+
* Record in the parent Conversation how one agent invocation ended, and record
|
|
10485
|
+
* a completed result as an assistant message in the child Conversation. The
|
|
10209
10486
|
* heartbeat result repair also calls this, because the child work can be
|
|
10210
10487
|
* acknowledged before the worker reports the result.
|
|
10211
10488
|
*/
|
|
10212
10489
|
async function recordSubagentEnded(invocation) {
|
|
10213
|
-
|
|
10490
|
+
const eventStore = getConversationEventStore();
|
|
10491
|
+
await eventStore.append(invocation.parentConversationId, [{
|
|
10214
10492
|
idempotencyKey: `subagent_ended:${invocation.invocationId}`,
|
|
10215
10493
|
data: {
|
|
10216
10494
|
type: "subagent_ended",
|
|
@@ -10219,6 +10497,19 @@ async function recordSubagentEnded(invocation) {
|
|
|
10219
10497
|
},
|
|
10220
10498
|
createdAtMs: invocation.terminalAtMs
|
|
10221
10499
|
}]);
|
|
10500
|
+
const result = invocation.status === "completed" ? invocation.result.trim() : "";
|
|
10501
|
+
if (!result) return;
|
|
10502
|
+
const messageId = `agent-invocation:${invocation.invocationId}:reply`;
|
|
10503
|
+
await eventStore.append(invocation.childConversationId, [{
|
|
10504
|
+
idempotencyKey: `message:${messageId}`,
|
|
10505
|
+
data: {
|
|
10506
|
+
type: "message",
|
|
10507
|
+
messageId,
|
|
10508
|
+
role: "assistant",
|
|
10509
|
+
text: result
|
|
10510
|
+
},
|
|
10511
|
+
createdAtMs: invocation.terminalAtMs
|
|
10512
|
+
}]);
|
|
10222
10513
|
}
|
|
10223
10514
|
//#endregion
|
|
10224
10515
|
//#region src/chat/agent-invocations/stop.ts
|
|
@@ -10251,6 +10542,61 @@ async function finishStoppedTree(tree, conversationIdsToStop, state) {
|
|
|
10251
10542
|
state
|
|
10252
10543
|
});
|
|
10253
10544
|
}
|
|
10545
|
+
/**
|
|
10546
|
+
* List one child Conversation and every Conversation below it, child first.
|
|
10547
|
+
* It takes the create lock of each Conversation before it reads that
|
|
10548
|
+
* Conversation's children, so no child agent can start in the tree until the
|
|
10549
|
+
* caller's transaction ends.
|
|
10550
|
+
*/
|
|
10551
|
+
async function lockAgentConversationTree(childConversationId) {
|
|
10552
|
+
const executor = getSqlExecutor();
|
|
10553
|
+
const conversationIds = [childConversationId];
|
|
10554
|
+
let parents = conversationIds;
|
|
10555
|
+
while (parents.length > 0) {
|
|
10556
|
+
for (const parent of parents) await executor.withLock(getChildAgentLockName(parent), async () => void 0);
|
|
10557
|
+
parents = (await executor.db().selectDistinct({ childConversationId: juniorAgentInvocations.childConversationId }).from(juniorAgentInvocations).where(inArray(juniorAgentInvocations.parentConversationId, parents))).map((row) => row.childConversationId).filter((id) => !conversationIds.includes(id));
|
|
10558
|
+
conversationIds.push(...parents);
|
|
10559
|
+
}
|
|
10560
|
+
return conversationIds;
|
|
10561
|
+
}
|
|
10562
|
+
/**
|
|
10563
|
+
* Stop the agent invocations of one direct child agent and of every child
|
|
10564
|
+
* agent below it. Returns undefined when the child does not belong to the
|
|
10565
|
+
* parent.
|
|
10566
|
+
*/
|
|
10567
|
+
async function stopChildAgentInvocations(args) {
|
|
10568
|
+
if ((await getSqlExecutor().db().select({ invocationId: juniorAgentInvocations.invocationId }).from(juniorAgentInvocations).where(and(eq(juniorAgentInvocations.parentConversationId, args.parentConversationId), eq(juniorAgentInvocations.childConversationId, args.childConversationId))).limit(1)).length === 0) return;
|
|
10569
|
+
return await stopAgentInvocationTree(args.childConversationId, args.nowMs);
|
|
10570
|
+
}
|
|
10571
|
+
/**
|
|
10572
|
+
* Stop the agent invocations of one agent Conversation and of every child
|
|
10573
|
+
* agent below it. Open agent invocations become `stopped`. Results that would
|
|
10574
|
+
* still start or report work in the tree become `skipped`.
|
|
10575
|
+
*/
|
|
10576
|
+
async function stopAgentInvocationTree(conversationId, nowMs = Date.now()) {
|
|
10577
|
+
return await getSqlExecutor().withLock(getChildAgentLockName(conversationId), () => stopLockedAgentInvocationTree(conversationId, nowMs));
|
|
10578
|
+
}
|
|
10579
|
+
async function stopLockedAgentInvocationTree(conversationId, nowMs) {
|
|
10580
|
+
const db = getSqlExecutor().db();
|
|
10581
|
+
const now = new Date(nowMs);
|
|
10582
|
+
const conversationIds = await lockAgentConversationTree(conversationId);
|
|
10583
|
+
const stopped = await db.update(juniorAgentInvocations).set({
|
|
10584
|
+
errorMessage: "Stopped by the parent conversation",
|
|
10585
|
+
parentMailboxStatus: "skipped",
|
|
10586
|
+
result: null,
|
|
10587
|
+
status: "stopped",
|
|
10588
|
+
terminalAt: now,
|
|
10589
|
+
updatedAt: now
|
|
10590
|
+
}).where(and(inArray(juniorAgentInvocations.childConversationId, conversationIds), inArray(juniorAgentInvocations.status, NON_TERMINAL_AGENT_INVOCATION_STATUSES))).returning();
|
|
10591
|
+
await db.update(juniorAgentInvocations).set({
|
|
10592
|
+
parentMailboxStatus: "skipped",
|
|
10593
|
+
updatedAt: now
|
|
10594
|
+
}).where(or(and(inArray(juniorAgentInvocations.parentConversationId, conversationIds), inArray(juniorAgentInvocations.parentMailboxStatus, ["pending", "appended"])), and(inArray(juniorAgentInvocations.childConversationId, conversationIds), eq(juniorAgentInvocations.parentMailboxStatus, "replaced"))));
|
|
10595
|
+
return {
|
|
10596
|
+
conversationIds,
|
|
10597
|
+
stopped: stopped.map(invocationFromRow).filter(isTerminalAgentInvocation)
|
|
10598
|
+
};
|
|
10599
|
+
}
|
|
10254
10600
|
//#endregion
|
|
10255
10601
|
//#region src/chat/tools/runtime/stop-agent.ts
|
|
10256
10602
|
const stopAgentOutputSchema = juniorToolOutputSchema.extend({
|
|
@@ -10260,7 +10606,7 @@ const stopAgentOutputSchema = juniorToolOutputSchema.extend({
|
|
|
10260
10606
|
/** Create the tool that stops one child agent of one conversation. */
|
|
10261
10607
|
function createStopAgentTool(conversationId) {
|
|
10262
10608
|
return zodTool({
|
|
10263
|
-
description: "Stop a child agent that this conversation started, and every child agent it started. Use it when the child agent's work is no longer needed or goes in the wrong direction. A stopped child agent sends no result. To give
|
|
10609
|
+
description: "Stop a child agent that this conversation started, and every child agent it started. Use it when the child agent's work is no longer needed or goes in the wrong direction. A stopped child agent sends no result. To give it new work later, use messageAgent.",
|
|
10264
10610
|
annotations: {
|
|
10265
10611
|
destructiveHint: true,
|
|
10266
10612
|
idempotentHint: true,
|
|
@@ -10321,6 +10667,23 @@ function createFinishAutomationRunTool(options) {
|
|
|
10321
10667
|
});
|
|
10322
10668
|
}
|
|
10323
10669
|
//#endregion
|
|
10670
|
+
//#region src/chat/tools/runtime/report-blocked.ts
|
|
10671
|
+
/** Create the tool that ends a child agent run as blocked. */
|
|
10672
|
+
function createReportBlockedTool() {
|
|
10673
|
+
return zodTool({
|
|
10674
|
+
annotations: {
|
|
10675
|
+
destructiveHint: false,
|
|
10676
|
+
idempotentHint: true,
|
|
10677
|
+
openWorldHint: false,
|
|
10678
|
+
readOnlyHint: false
|
|
10679
|
+
},
|
|
10680
|
+
description: "End this delegated task as blocked. Call this when missing access, credentials, input, or a decision stops the task, also when a search for the missing part found nothing. Do not guess, mock, or fake results to work around it.",
|
|
10681
|
+
inputSchema: z.object({ missing: z.string().trim().min(1).describe("What the task needs and who can give it. Add the work you already did, so the parent does not repeat it.") }).strict(),
|
|
10682
|
+
outputSchema: juniorToolOutputSchema.extend({ missing: z.string() }),
|
|
10683
|
+
execute: async (input) => ({ missing: input.missing })
|
|
10684
|
+
});
|
|
10685
|
+
}
|
|
10686
|
+
//#endregion
|
|
10324
10687
|
//#region src/chat/tools/list-watches.ts
|
|
10325
10688
|
const listedResourceWatchSchema = z.object({
|
|
10326
10689
|
id: z.string().min(1),
|
|
@@ -10592,6 +10955,7 @@ function createWatchEventsTool(context, catalog) {
|
|
|
10592
10955
|
outputSchema: outputSchema$2,
|
|
10593
10956
|
async execute(input) {
|
|
10594
10957
|
if (input.namespace === "junior" && input.resourceType === "timer") throw new ToolInputError("Use watchTimer to create a timer Watch.");
|
|
10958
|
+
if (input.namespace === "slack") throw new ToolInputError("Slack channel messages can trigger only an event automation in that channel. Use createEventAutomation.");
|
|
10595
10959
|
const events = cleanStrings(input.events);
|
|
10596
10960
|
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}".`);
|
|
10597
10961
|
const intent = input.intent.trim();
|
|
@@ -10756,7 +11120,7 @@ const eventAutomationListToolResultSchema = juniorToolOutputSchema.extend({
|
|
|
10756
11120
|
}).strict();
|
|
10757
11121
|
/** Build the validated event trigger accepted by event-automation tools. */
|
|
10758
11122
|
function registeredEventAutomationTriggerSchema(catalog) {
|
|
10759
|
-
const plugins =
|
|
11123
|
+
const plugins = automationEventCatalog(catalog);
|
|
10760
11124
|
return z.object({
|
|
10761
11125
|
namespace: eventNamespaceSchema(plugins),
|
|
10762
11126
|
identifier: z.string().trim().min(1).max(300),
|
|
@@ -10768,7 +11132,7 @@ function registeredEventAutomationTriggerSchema(catalog) {
|
|
|
10768
11132
|
}
|
|
10769
11133
|
/** Reject an event-automation trigger that is no longer supported by the catalog. */
|
|
10770
11134
|
function requireSupportedEventAutomationTrigger(catalog, trigger) {
|
|
10771
|
-
const plugins =
|
|
11135
|
+
const plugins = automationEventCatalog(catalog);
|
|
10772
11136
|
for (const eventType of trigger.events) if (!pluginSupportsEvent(plugins, trigger.namespace, trigger.resourceType, eventType)) throw new ToolInputError(`Resource type "${trigger.namespace}:${trigger.resourceType}" does not support event "${eventType}".`);
|
|
10773
11137
|
return requireSupportedEventMatch(plugins, {
|
|
10774
11138
|
match: trigger.match,
|
|
@@ -10810,7 +11174,7 @@ async function writableEventAutomation(context, id) {
|
|
|
10810
11174
|
return task;
|
|
10811
11175
|
}
|
|
10812
11176
|
function eventAutomationTriggerAvailable(task, catalog) {
|
|
10813
|
-
const registration =
|
|
11177
|
+
const registration = automationEventCatalog(catalog)[task.trigger.namespace];
|
|
10814
11178
|
return Boolean(registration && task.trigger.events.every((eventType) => registration.resourceTypes.some((resourceType) => resourceType.supportedEvents.includes(eventType))));
|
|
10815
11179
|
}
|
|
10816
11180
|
/** Project an event automation into the bounded tool-result shape. */
|
|
@@ -10934,7 +11298,7 @@ function createEventAutomationTool(context, catalog) {
|
|
|
10934
11298
|
instruction: input.instruction,
|
|
10935
11299
|
title: input.title
|
|
10936
11300
|
});
|
|
10937
|
-
const task = {
|
|
11301
|
+
const task = requireSlackChannelMessageScope({
|
|
10938
11302
|
id,
|
|
10939
11303
|
destinationVisibility: source.visibility,
|
|
10940
11304
|
createdAtMs: Date.now(),
|
|
@@ -10956,7 +11320,7 @@ function createEventAutomationTool(context, catalog) {
|
|
|
10956
11320
|
events: [...new Set(input.trigger.events)],
|
|
10957
11321
|
...match ? { match } : void 0
|
|
10958
11322
|
}
|
|
10959
|
-
};
|
|
11323
|
+
});
|
|
10960
11324
|
return eventAutomationToolResult(context.conversationId, await createEventAutomation(db, task), catalog, actor.userId);
|
|
10961
11325
|
}
|
|
10962
11326
|
});
|
|
@@ -11088,11 +11452,17 @@ async function editEventAutomation(current, input, isCreator, catalog) {
|
|
|
11088
11452
|
};
|
|
11089
11453
|
}
|
|
11090
11454
|
const changesExecution = input.instruction !== void 0 && input.instruction !== current.task.text || changesEventAutomationTrigger(current.trigger, trigger);
|
|
11091
|
-
|
|
11455
|
+
const next = {
|
|
11092
11456
|
...current,
|
|
11093
11457
|
...await editAutomationFields(current, input, isCreator, changesExecution),
|
|
11094
11458
|
trigger
|
|
11095
11459
|
};
|
|
11460
|
+
try {
|
|
11461
|
+
return requireSlackChannelMessageScope(next);
|
|
11462
|
+
} catch (error) {
|
|
11463
|
+
if (error instanceof ToolInputError) throw new AutomationEditError(error.message, "trigger");
|
|
11464
|
+
throw error;
|
|
11465
|
+
}
|
|
11096
11466
|
}
|
|
11097
11467
|
//#endregion
|
|
11098
11468
|
//#region src/chat/tools/update-event-automation.ts
|
|
@@ -11165,7 +11535,7 @@ function createUpdateEventAutomationTool(context, catalog) {
|
|
|
11165
11535
|
function createEventAutomationTools(context, catalog) {
|
|
11166
11536
|
if (context.source.kind !== "slack" || context.destination.platform !== "slack" || context.actor?.platform !== "slack") return {};
|
|
11167
11537
|
return {
|
|
11168
|
-
|
|
11538
|
+
createEventAutomation: createEventAutomationTool(context, catalog),
|
|
11169
11539
|
listEventAutomations: createListEventAutomationsTool(context, catalog),
|
|
11170
11540
|
updateEventAutomation: createUpdateEventAutomationTool(context, catalog),
|
|
11171
11541
|
deleteEventAutomation: createDeleteEventAutomationTool(context, catalog)
|
|
@@ -16700,6 +17070,8 @@ function createTools(availableSkills, hooks = {}, context, options = {}) {
|
|
|
16700
17070
|
tools.listAgents = createListAgentsTool(context.conversationId);
|
|
16701
17071
|
tools.stopAgent = createStopAgentTool(context.conversationId);
|
|
16702
17072
|
}
|
|
17073
|
+
if (context.messageAgent) tools.messageAgent = createMessageAgentTool(context.messageAgent);
|
|
17074
|
+
if (context.source.kind === "agent_invocation") tools[REPORT_BLOCKED_TOOL_NAME] = createReportBlockedTool();
|
|
16703
17075
|
if (context.automation) tools[FINISH_AUTOMATION_RUN_TOOL_NAME] = createFinishAutomationRunTool(context.automation);
|
|
16704
17076
|
if (context.mcpToolManager) {
|
|
16705
17077
|
tools.searchMcpTools = createSearchMcpToolsTool(context.mcpToolManager);
|
|
@@ -19656,6 +20028,7 @@ async function wireAgentTools(args) {
|
|
|
19656
20028
|
surface: args.surface,
|
|
19657
20029
|
...args.currentActor ? { resolveActorIdentity: async () => await readActorIdentity(args.currentActor) } : void 0,
|
|
19658
20030
|
...args.durability.spawnAgent ? { spawnAgent: args.durability.spawnAgent } : void 0,
|
|
20031
|
+
...args.durability.messageAgent ? { messageAgent: args.durability.messageAgent } : void 0,
|
|
19659
20032
|
...args.requestHandoff ? { handoff: args.requestHandoff } : void 0,
|
|
19660
20033
|
...args.run.dispatch?.declaresResult ? { automation: { sendsMessage: args.run.dispatch.outcomes?.length !== 0 } } : void 0
|
|
19661
20034
|
};
|
|
@@ -20493,7 +20866,8 @@ async function compactActiveContextIfNeeded(args, deps) {
|
|
|
20493
20866
|
const EXCLUSIVE_TOOL_NAMES = [
|
|
20494
20867
|
HANDOFF_TOOL_NAME,
|
|
20495
20868
|
"switchWorkspace",
|
|
20496
|
-
FINISH_AUTOMATION_RUN_TOOL_NAME
|
|
20869
|
+
FINISH_AUTOMATION_RUN_TOOL_NAME,
|
|
20870
|
+
REPORT_BLOCKED_TOOL_NAME
|
|
20497
20871
|
];
|
|
20498
20872
|
/** Return the exclusive tool name when one is present among the calls. */
|
|
20499
20873
|
function exclusiveToolName(toolCalls) {
|
|
@@ -21229,7 +21603,7 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
|
|
|
21229
21603
|
const sourceResult = timedOutResult ?? result;
|
|
21230
21604
|
const projectedResult = wiring.projectActionReviewResult(toolCall.id, sourceResult);
|
|
21231
21605
|
const override = timedOutResult || projectedResult !== result ? projectedResult : void 0;
|
|
21232
|
-
if (toolCall.name === "finishAutomationRun" && !isError && !override) return { terminate: true };
|
|
21606
|
+
if ((toolCall.name === "finishAutomationRun" || toolCall.name === "reportBlocked") && !isError && !override) return { terminate: true };
|
|
21233
21607
|
return override;
|
|
21234
21608
|
},
|
|
21235
21609
|
prepareNextTurnWithContext: async (nextTurn, hookSignal) => {
|
|
@@ -21589,9 +21963,11 @@ function createAgentRunner(execute, options) {
|
|
|
21589
21963
|
const streamFn = options?.streamFn;
|
|
21590
21964
|
const tracePropagation = options?.tracePropagation;
|
|
21591
21965
|
const bindSpawnAgent = options?.bindSpawnAgent;
|
|
21966
|
+
const bindMessageAgent = options?.bindMessageAgent;
|
|
21592
21967
|
const canBindSpawn = Boolean(bindSpawnAgent) && isExperimentalFeatureEnabled("subagents");
|
|
21593
21968
|
return { run: async (run) => {
|
|
21594
21969
|
const spawnAgent = bindSpawnAgent && canBindSpawn && !isAgentRunFeatureDisabled(run.disabledFeatures, "subagents") ? bindSpawnAgent(run) : void 0;
|
|
21970
|
+
const messageAgent = bindMessageAgent && canBindSpawn && (spawnAgent || run.source.kind === "agent_invocation") ? bindMessageAgent(run) : void 0;
|
|
21595
21971
|
const nextRun = {
|
|
21596
21972
|
...run,
|
|
21597
21973
|
environment: {
|
|
@@ -21599,13 +21975,14 @@ function createAgentRunner(execute, options) {
|
|
|
21599
21975
|
attachmentStorage: run.environment?.attachmentStorage ?? attachmentStorage,
|
|
21600
21976
|
sandboxTracePropagation: run.environment?.sandboxTracePropagation ?? tracePropagation
|
|
21601
21977
|
},
|
|
21602
|
-
...spawnAgent ? { durability: {
|
|
21978
|
+
...spawnAgent || messageAgent ? { durability: {
|
|
21603
21979
|
...run.durability,
|
|
21604
|
-
spawnAgent
|
|
21980
|
+
...spawnAgent ? { spawnAgent } : void 0,
|
|
21981
|
+
...messageAgent ? { messageAgent } : void 0
|
|
21605
21982
|
} } : void 0
|
|
21606
21983
|
};
|
|
21607
21984
|
if (run.instruction.storedAttachments?.length) {
|
|
21608
|
-
const { loadInputImages } = await import("./images-
|
|
21985
|
+
const { loadInputImages } = await import("./images-DZliDtSH.js").then((n) => n.n);
|
|
21609
21986
|
const storage = nextRun.environment?.attachmentStorage;
|
|
21610
21987
|
if (!storage) throw new Error("Attachment storage is unavailable.");
|
|
21611
21988
|
nextRun.instruction = {
|
|
@@ -21621,4 +21998,4 @@ function createAgentRunner(execute, options) {
|
|
|
21621
21998
|
} };
|
|
21622
21999
|
}
|
|
21623
22000
|
//#endregion
|
|
21624
|
-
export {
|
|
22001
|
+
export { getAgentInvocationIdForMessage as $, stopConversationWork as $t, resolveSlackChannelIdFromMessage as A, finishedRunReply as An, appendAndEnqueueInboundMessage as At, effectiveTaskOutcomes as B, drainConversationMailbox as Bt, renderBlockText as C, buildSteeringPiMessage as Cn, SLACK_EVENT_NAMESPACE as Ct, storeAttachment as D, AuthorizationFlowDisabledError as Dn, eventGuidance as Dt, requestAttachmentDeletion as E, toolInvocationDestination as En, automationEventCatalog as Et, logScheduledAutomationRunSkipped as F, clearExpiredConversationLease as Ft, listChildAgents as G, listActiveConversationIds as Gt, stopChildAgentsOf as H, getConversation as Ht, scheduledAutomationRunAttributes as I, completeConversationStop as It, createAgentInvocationForMessage as J, releaseConversationWork as Jt, completeAgentInvocation as K, recordAttemptFailure as Kt, editEventAutomation as L, completeConversationWork as Lt, getConversationInfo as M, shouldEmitDevAgentTrace as Mn, cancelHumanFacingPendingMessages as Mt, listThreadReplies as N, getConfigDefaults as Nn, checkInConversationWork as Nt, parseSlackThreadId as O, readBlockedReport as On, ackMessages as Ot, editScheduledAutomation as P, setConfigDefaults as Pn, clearConsumedConversationWake as Pt, getAgentInvocation as Q, startConversationWork as Qt, AutomationEditError as R, countPendingConversationMessages as Rt, renderAttachmentText as S, createUserTokenStore as Sn, getEventCatalog as St, readLiveAttachment as T, actorFromRun as Tn, slackChannelMessageReplyThreadTs as Tt, recordSubagentEnded as U, getConversationWorkState as Ut, resolveTaskOutcomes as V, ensureConversationWake as Vt, recordSubagentStarted as W, hasRunnableConversationWork as Wt, findChildAgentConversationId as X, requestAnotherSlice as Xt, createAgentInvocationFromLatest as Y, removeActiveConversation as Yt, getActiveAgentInvocationForConversation as Z, requestConversationWork as Zt, resolvePluginOAuthAccount as _, sandboxEgressSignalsResponseSchema as _n, processWorkspaceSnapshotJob as _t, buildAuthPauseResponse as a, isInvalidConversationRecordError as an, getLatestAgentInvocationForConversation as at, unpublishedArtifactHeaders as b, resolveBaseUrl as bn, isEventMailboxMetadata as bt, deleteMcpAuthSessionsForUserProvider as c, recordFinishedTurnForAutomatedLimit as cn, listPendingAgentInvocationResults as ct, getMcpAuthSession as d, shouldPropagateSandboxEgressTrace as dn, markAgentInvocationPaused as dt, CONVERSATION_WORK_CHECK_IN_INTERVAL_MS as en, getAgentInvocationIdForResult as et, getMcpStoredOAuthCredentials as f, SANDBOX_EGRESS_PROXY_PATH as fn, markAgentInvocationRunning as ft, readStats as g, parseSandboxEgressCredentialToken as gn, getMcpProviderErrorAttributes as gt, latestProgressStatus as h, consumeSandboxEgressPermissionDeniedSignal as hn, PARENT_AGENT_TARGET as ht, executeCredentialedEgressRequest as i, isFinalAttempt as in, getChildAgentLabel as it, resolveSlackChannelIdFromThreadId as j, runDispatchOutcome as jn, beginConversationResume as jt, readSlackRawMessageContext as k, deliversFinalText as kn, appendAndEnqueueExclusiveInboundMessage as kt, deleteMcpServerSessionId as l, resolveSandboxEgressProviderForHost as ln, markAgentInvocationMailboxAppended as lt, PluginCredentialFailureError as m, consumeSandboxEgressAuthRequiredSignal as mn, AgentMessageTargetError as mt, executeAgentRun as n, hasConversationStop as nn, getAgentInvocationResultMessageId as nt, finalizeMcpAuthorization as o, renderTaskInput as on, isTerminalAgentInvocation as ot, putMcpStoredOAuthCredentials as p, clearSandboxEgressSignals as pn, reportReplacedAgentInvocation as pt, createAgentInvocation as q, recordConversationRetry as qt, createContextCompactor as r, hasRunnableConversationWork$1 as rn, getAgentInvocationTurnId as rt, deleteMcpAuthSession as s, maybePostAutomatedTurnLimitNotice as sn, listPendingAgentInvocationMailboxAppends as st, createAgentRunner as t, CONVERSATION_WORK_STALE_ENQUEUE_MS as tn, getAgentInvocationMessageId as tt, deleteMcpStoredOAuthCredentials as u, normalizeSandboxEgressTracePropagationDomains as un, markAgentInvocationParentMailboxAppended as ut, publicArtifactHeaders as v, formatProviderLabel as vn, ingestEvent as vt, collectAttachmentGarbage as w, RetryableDeliveryError as wn, slackChannelMessageEvent as wt, lookupSlackUserProfile as x, startOAuthFlow as xn, timerWatchCredentialSubject as xt, readPublicArtifact as y, parseOAuthStatePayload as yn, EVENT_WAIT_MS as yt, eventAutomationTriggerAvailable as z, deadLetterAttempt as zt };
|