openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
16,216 lines • 770 kB
JavaScript
import { c as normalizeOptionalLowercaseString, g as readStringValue, l as normalizeOptionalString, n as localeLowercasePreservingWhitespace, o as normalizeLowercaseStringOrEmpty, t as hasNonEmptyString } from "./string-coerce-1VuiLaaR.mjs";
import { D as resolveExpiresAtMsFromDurationMs, F as resolveTimerTimeoutMs, g as isFutureDateTimestampMs, h as finiteSecondsToTimerSafeMilliseconds, m as clampTimerTimeoutMs, o as asDateTimestampMs, u as asPositiveFiniteNumber, y as parseDateStringTimestampMs } from "./number-coercion-CkhMiRw8.mjs";
import { r as consumeRootOptionToken } from "./cli-root-options-DGcy1qRg.mjs";
import { c as readErrorCauses, d as toErrorObject, r as collectErrorGraphCandidates } from "./error-coercion-W9BtigRm.mjs";
import { a as resolveGlobalSingleton } from "./global-singleton-0m-JziJW.mjs";
import { t as createDeferredCore } from "./deferred-D0La5CRk.mjs";
import { n as captureAsyncWorkTracker, r as getAsyncWorkSignal } from "./async-work-scope-DDiH0P7W.mjs";
import { o as resolveUserPath } from "./home-dir-Dc6nN6HB.mjs";
import { n as parseBooleanValue } from "./boolean-CFfzB3ix.mjs";
import { n as isTruthyEnvValue, s as isFastTestRuntimeEnv } from "./env-Ca5Je5YP.mjs";
import { i as extractBalancedJsonPrefix } from "./src-BirUqOZp.mjs";
import { n as estimateStringChars } from "./cjk-chars-6ld30jSx.mjs";
import { t as expectDefined } from "./expect-lbe3Hgrh.mjs";
import { a as asOptionalRecord, c as isRecord, i as asOptionalObjectRecord, o as asRecord, u as readStringField } from "./record-coerce-TIsquoVR.mjs";
import { n as safeParseJsonRecord, t as safeParseJson } from "./json-coercion-pfKbZnCJ.mjs";
import { t as stableStringify } from "./stable-stringify-Djn3uxNo.mjs";
import { _ as normalizeUniqueTrimmedStringList, d as normalizeStringEntries, n as filterStringEntries, p as normalizeTrimmedStringList, y as uniqueStrings } from "./string-normalization-D6DKCpwE.mjs";
import { n as sliceUtf16Safe, r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.mjs";
import { d as sleepWithAbort$1, i as createAbortError$1, s as raceWithTimeout } from "./src-D1BHGNTj.mjs";
import { t as isAbortError } from "./abort-signal-QqAmLWTl.mjs";
import { t as sleep } from "./sleep-dMoNLoKC.mjs";
import { n as appendRegularFile } from "./fs-safe-BBzelG8F.mjs";
import { a as openRootFile } from "./boundary-file-read-CCth2Hwg.mjs";
import "./utils-CitNzLyF.mjs";
import { r as resolveStateDir } from "./state-dir-CYkBUd_G.mjs";
import "./paths-BP0DYpE8.mjs";
import { a as isSecretValueRegisteredForRedaction } from "./secret-redaction-registry-kd5IVWi3.mjs";
import { t as formatErrorMessage } from "./errors-BvOkmQM5.mjs";
import { t as notifyListeners } from "./listeners-BogSNJ-R.mjs";
import { t as isPromiseLike } from "./promise-like-D7-l5Fsp.mjs";
import { O as freezeDiagnosticTraceContext, s as emitTrustedDiagnosticEvent, t as areDiagnosticsEnabledForProcess } from "./diagnostic-events-DT4ejwoa.mjs";
import { t as createSubsystemLogger } from "./subsystem-4NzR43uz.mjs";
import { n as resolveCommitHash } from "./git-commit-DuAuzeEY.mjs";
import { t as VERSION } from "./version-D0DaWqMn.mjs";
import { n as sha256Hex } from "./node-crypto-BG4u3UnX.mjs";
import { n as resolveRuntimeProcessEntrypointUrl } from "./runtime-process-url-CYKlArl-.mjs";
import { t as SessionTranscriptWriterClaimReboundError } from "./session-transcript-writer-claim-error-eP-_sDN9.mjs";
import { t as WorkerTaskPool } from "./worker-task-pool-r__tVCXa.mjs";
import { t as createLazyImportLoader } from "./lazy-promise-DGqyc4Y4.mjs";
import { r as isIncognitoSessionKey } from "./session-key-CmZ4o9-l.mjs";
import { _ as toAgentStoreSessionKey, d as resolveLinkedDirectPeerId } from "./session-key-DY3MOX4N.mjs";
import { h as parseAgentSessionKey, i as isCronRunSessionKey, s as isSubagentSessionKey } from "./session-key-utils-CDyBC9Gn.mjs";
import { n as normalizeAccountId } from "./account-id-ifCOO7_9.mjs";
import { c as resolveSessionStorePathCore } from "./paths-Bonddxfk.mjs";
import { r as normalizeProviderId } from "./provider-id-D1VImdKk.mjs";
import { r as isCloudModelRef } from "./model-catalog-refs-D3xvQZh0.mjs";
import { a as resolveAgentDir, i as resolveAgentContextLimits } from "./agent-scope-config-BEk6-ThS.mjs";
import { t as normalizeChatType } from "./chat-type-qwvaeaJu.mjs";
import { f as hasRuntimeContextMarker } from "./types-CQc6JNG4.mjs";
import { c as extractToolResultIds, o as extractToolCallsFromAssistant, r as classifyToolUseResultPairing, s as extractToolResultId } from "./tool-result-pairing--BK1RaBE.mjs";
import { r as emitSessionTranscriptUpdate } from "./transcript-events-DSEdnn1V.mjs";
import { n as normalizeMessageChannel } from "./message-channel-core-D9WI2Y8r.mjs";
import { i as sameSessionTranscriptTargetBinding } from "./transcript-target-binding-DpcuSIFy.mjs";
import { r as resolvePersistedSessionStoreOwnerForTarget } from "./session-store-owner-DZX-SiyK.mjs";
import "./backoff-BrzXG7iD.mjs";
import { r as MAX_IMAGE_BYTES } from "./constants-GzjKtFvA.mjs";
import "./http-body-BBg_CVrI.mjs";
import { t as cancelUnreadResponseBody } from "./http-response-body-ljSrZsaY.mjs";
import { p as readProviderJsonObjectResponse } from "./provider-http-errors-BEswZaxl.mjs";
import { t as _usingCtx } from "./usingCtx-BhiZp_rS.mjs";
import { t as redactConfigObject } from "./redact-snapshot-BnRirwia.mjs";
import { d as sanitizeSupportSnapshotValue, r as redactPathForSupport } from "./diagnostic-support-redaction-evpUN_kb.mjs";
import "./defaults-Jag53zwP.mjs";
import { o as loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot-DZdnLZdU.mjs";
import { _ as resolveSessionAgentIds, h as resolveSessionAgentId, s as resolveAgentExecutionContract } from "./agent-scope-bJOokm5r.mjs";
import { n as getPluginInstance } from "./plugin-instance-scope-Cm6nCHVN.mjs";
import { n as isAutomationsToolName } from "./automations-tool-name-DaxQ5QU2.mjs";
import { a as formatToolExecutionGatedMessage, c as normalizeToolPolicyName, n as couldNormalizeToolNamePrefixToAllowedTool, o as isToolExecutionAllowed, r as createToolExecutionMatcher } from "./tool-policy-shared-CNWjndK7.mjs";
import { n as createRuntimeToolMatcher, r as createToolPolicyMatcher, s as isToolAllowedByPolicyName } from "./tool-policy-match-BXLYReRw.mjs";
import { d as toolPolicyRestrictsTools, u as replaceWithEffectiveToolAllowlist } from "./tool-policy-BrMCyUgN.mjs";
import { i as classifyAgentRunTerminalOutcome, n as AGENT_RUN_RESTART_ABORT_STOP_REASON } from "./agent-run-terminal-outcome-CrMEEYuY.mjs";
import { j as validateAgentRunDelegatedAuthority, u as getAgentRunContext } from "./agent-run-registry-Cm8FlLIi.mjs";
import { _ as readAgentAssistantSource, a as emitAgentEvent, l as emitAgentEventWithAssistantSourceIfCurrent, n as bindAgentAssistantSource, r as captureAgentRunLifecycleGeneration, s as emitAgentEventForRunContext, u as emitAgentRunOutputTokens } from "./agent-events-jWdQ_ESE.mjs";
import { a as composeSessionSourceAssertion, d as sessionEntryCommitGuardOptions } from "./session-source-authority-h1UO3u0I.mjs";
import { l as getActivePluginRegistry, m as listImportedRuntimePluginIds, v as requireActivePluginRegistry } from "./runtime-U4i1bMh7.mjs";
import { t as settlesWithin } from "./settle-within-CTpJLoBG.mjs";
import { a as projectModelThinkingCompat } from "./model-catalog-lookup-_nlJdlgO.mjs";
import { r as splitShellArgs, t as hasTopLevelShellControlOperator } from "./shell-argv-Dy8gqirH.mjs";
import { r as getRuntimeConfig } from "./io.runtime-B134MOMz.mjs";
import "./io-BGddrCVX.mjs";
import { t as isDeliverableMessageChannel } from "./message-channel-normalize-JIxBUi40.mjs";
import "./message-channel-AvA3r_Xp.mjs";
import { n as withGuardedFetchRequestAuthority } from "./fetch-request-authority-DJ_0g9EM.mjs";
import { i as runOwnedAgentCleanup, n as recordAgentCleanupFailure, r as runAgentCleanupStep } from "./run-cleanup-timeout-CHdeH1f3.mjs";
import { n as acquireSessionMcpRuntime } from "./agent-bundle-mcp-manager-api-B24ukpdI.mjs";
import { t as loadSessionMcpConfig } from "./agent-bundle-mcp-runtime-config-F_ShvHkb.mjs";
import { S as supportsClaudeInHistorySystemMessages } from "./anthropic-B3HirzdP.mjs";
import { A as formatFileOperations, D as extractCompactionMessageText, E as computeFileLists, O as extractCurrentCompactionUserRequest, T as MAX_FILE_OPS_SECTION_CHARS, _ as formatRequiredAskContext, g as fitCompactionSummary, k as formatCompactionUserLabel, l as capCompactionSummary, o as MAX_COMPACTION_SUMMARY_CHARS, s as SUMMARY_TRUNCATED_MARKER } from "./agent-core-D7okjbP7.mjs";
import { n as readProviderRefusalReview, t as isProviderRefusalAssistantError } from "./diagnostics-CAGhEztD.mjs";
import "./src-D3dCIQYS.mjs";
import { f as getOpenClawSystemUpdateKind, p as isOpenClawSystemUpdateMessage } from "./messages-1Ao6vvqg.mjs";
import { _ as stripRuntimeContextCustomMessages, d as relocateCurrentRuntimeContextCarrierToTail, f as resolvePendingRuntimeContextReplay } from "./internal-runtime-context-D5iQhnLo.mjs";
import { Vt as projectChatErrorDetail } from "./sessions-CLLc4meF.mjs";
import { d as resolveProviderRequestHeaders } from "./provider-request-config-zrhxha-3.mjs";
import { d as withOwnedSessionTranscriptWriterFence, f as withOwnedSessionTranscriptWrites, h as withSessionTranscriptWriteAssertion, l as runWithOwnedSessionTranscriptWrite, n as bindOwnedSessionTranscriptWrites, o as getOwnedSessionTranscriptInitialWriter, r as captureOwnedTranscriptWriteAssertion, s as getOwnedSessionTranscriptReader, t as assertOwnedTranscriptWriteCommit } from "./transcript-write-context-DwwjCPCE.mjs";
import { t as AgentRunTerminalOutcomeError } from "./agent-run-terminal-error-CQe-sgQJ.mjs";
import { l as isPreDispatchToolCallRejectionMessage, r as TERMINATED_TRANSPORT_MESSAGE_RE, t as INCOMPLETE_ASSISTANT_STREAM_RE } from "./message-patterns-DU8N-ZQt.mjs";
import { c as isSignalTimeoutReason, l as isTimeoutError } from "./error-Cmm-AuUb.mjs";
import { a as createAgentRunDirectAbortError, o as createAgentRunRestartAbortError, s as createAgentRunSupersededAbortError, u as isAgentRunRestartAbortReason } from "./run-termination-5c-0T0hN.mjs";
import { s as resolveContextEngineOwnerPluginId } from "./registry-BSFHwbcu.mjs";
import { n as getGlobalHookRunner } from "./hook-runner-global-Cn3xHNvt.mjs";
import { r as SILENT_REPLY_TOKEN, s as isSilentReplyText } from "./tokens-Ljy0DfNV.mjs";
import { o as hasReplyPayloadContent } from "./payload-DD64v4IB.mjs";
import { C as markReplyPayloadForSourceSuppressionDelivery, D as setReplyPayloadMetadata, O as shouldKeepPayloadDuringSilentTurn, c as getReplyPayloadMetadata, n as addReplyPayloadMediaFailures, o as copyReplyPayloadMetadata, r as appendReplyMediaFailures } from "./reply-payload-BEgsL2xJ.mjs";
import { $ as clearBatchAdmittedToolCallsForRun, X as finalizeToolTerminalPresentation, at as peekPreExecutionBlockedToolCall, et as consumeAdjustedParamsForToolCall, it as peekAdjustedParamsForToolCall, l as wrapToolWithBeforeToolCallHook, nt as consumeStructuredReplaySafeToolCall, rt as consumeTrackedToolExecutionStarted, s as recordStructuredReplayTrustForToolCall, tt as consumePreExecutionBlockedToolCall, y as admitToolCallBatch } from "./agent-tools.before-tool-call-CQkX3nOj.mjs";
import { a as SessionWorkStartChangedError } from "./work-start-error-Dh9d0ImY.mjs";
import { _ as resolveAdmittedRunActiveAssertion, g as readRunOperatorAuthority, n as assertOperatorModelAllowed, r as bindOperatorModelExecution } from "./admitted-run-context-D0yzk2io.mjs";
import { r as resolveChannelAccountEntry } from "./account-lookup-BAaS-Jak.mjs";
import { c as mintSecretSentinel, l as resolveSecretSentinel, s as looksLikeSecretSentinel } from "./sentinel-BG8eTG8r.mjs";
import { t as sanitizeForConsole } from "./console-sanitize-CaBHLewO.mjs";
import { s as resolveProviderThinkingLevel } from "./thinking-7OesKvXK.mjs";
import { t as createStreamIteratorWrapper } from "./stream-iterator-wrapper-BKsGhmOL.mjs";
import { r as wrapStreamFnTextTransforms } from "./plugin-text-transforms-BtsSqs-a.mjs";
import { r as getModelProviderRuntimePluginHandle } from "./provider-hook-runtime-DXt-lONr.mjs";
import { T as resolveProviderTextTransforms } from "./provider-runtime-DyOfn8ff.mjs";
import { a as resolveToolCallArgumentsEncoding, n as extractModelCompat } from "./provider-model-compat-BmMLKyRu.mjs";
import { n as normalizeGoogleApiBaseUrl } from "./google-api-base-url-B5JCFm92.mjs";
import { t as retryAsync } from "./retry-Bgt50HOo.mjs";
import { f as readPersistedMediaFacts, n as applyRunHydrationSuppression, r as attachRuntimePromptMediaFacts, s as isImageMediaFact } from "./media-facts-BIPREXi-.mjs";
import { i as getAgentWorkspaceAccess } from "./workspace-access-sHrkH_3z.mjs";
import { r as readWorkspaceBootstrapFile, t as MAX_WORKSPACE_BOOTSTRAP_FILE_BYTES } from "./workspace-bootstrap-read-ClACC-rY.mjs";
import "./model-selection-Cpoq0B2R.mjs";
import { n as applySkillEnvOverridesFromSnapshot, t as applySkillEnvOverrides } from "./env-overrides-BMfjBHeX.mjs";
import { o as resolveModelAuthMode } from "./model-auth-CGgrsVoA.mjs";
import { t as collectTextContentBlocks } from "./content-blocks-DDtxq3Ff.mjs";
import { r as resolveImageSanitizationLimits } from "./image-sanitization-DhkMOJXD.mjs";
import { _ as parseSqliteSessionFileMarker } from "./session-accessor.sqlite-scope-BkgwxatT.mjs";
import { r as isSessionTranscriptProjectionUnavailableError } from "./session-transcript-projection-error-B7C6SQei.mjs";
import { n as resolveSessionTranscriptReadFence } from "./session-transcript-read-fence-DqVaDIch.mjs";
import { i as resolveLiveToolResultMaxChars } from "./tool-result-limits-DNBSu_jg.mjs";
import { u as stripHeartbeatToken } from "./heartbeat-nZ1C5oCa.mjs";
import { t as listSessionEntriesReadOnly } from "./session-accessor.sqlite-entry-list.read-yRt0ntMz.mjs";
import { t as buildSessionContext } from "./session-T8thNqx3.mjs";
import { t as readMessageIdempotencyKey } from "./transcript-message-identity-0wQEn3Eb.mjs";
import { c as isCodeModeExecTool, d as normalizeCodeModeExecBeforeHookParams, n as CODE_MODE_WAIT_TOOL_NAME, t as CODE_MODE_EXEC_TOOL_NAME } from "./code-mode-control-tools-FkhKD315.mjs";
import { D as wrapStreamFnCodeModeSource, h as redactTranscriptMessage } from "./session-accessor.sqlite-transcript-store-D63Niwm2.mjs";
import { a as readNestedToolActivity, i as projectNestedToolActivityForHooks, n as createNestedToolActivity } from "./nested-tool-activity-Da7SpARz.mjs";
import { l as readTranscriptSenderIdentity } from "./user-turn-transcript.metadata-DHDwAwgF.mjs";
import { _ as shouldPreserveUserFacingSessionStateForInputProvenance, f as isMainSessionRestartRecoveryInputProvenance, h as normalizeInputProvenance } from "./input-provenance-B5YZS2Do.mjs";
import { l as resolveAssistantMessagePhase, o as parseAssistantTextSignature } from "./chat-message-content-C-d0FKNG.mjs";
import { i as deriveSessionTotalTokens, l as makeZeroUsageSnapshot, o as hasNonzeroUsage, s as hasObservedModelUsage, u as normalizeUsage } from "./usage-B5bs3hAI.mjs";
import { t as withSessionTranscriptReadSource } from "./session-transcript-read-source-V0JvL2x_.mjs";
import { n as prepareSessionTranscriptHydration } from "./session-transcript-hydration-ClYSEJcc.mjs";
import { t as prepareProviderPrompt } from "./provider-prompt-serialization-CCnDrxPb.mjs";
import "./undici-global-dispatcher-wnCXUVlp.mjs";
import { r as isSessionContextMetadataEntry } from "./session-manager-codec-NRzy0b5d.mjs";
import { n as readSessionTranscriptAnchorsAsync } from "./session-transcript-anchor-read-D6gfv_oG.mjs";
import { i as prepareSessionManagerHydration } from "./session-manager-reload-BXFIyUNy.mjs";
import { n as sessionManagerOpenTranscriptCohort, r as sessionManagerReloadTranscriptCohort } from "./session-manager-core-CZpl_3wS.mjs";
import { a as projectScrubbedPlainTextToolCallMessage, i as normalizePlainTextToolCallStreamEvents, n as createPromotedPlainTextToolCallEvents, r as projectStandalonePlainTextToolCallMessage } from "./src-Bn8W7Izv.mjs";
import { a as scanReasoningTags, i as parseReasoningTagAt, n as findMarkdownCodeRegions } from "./reasoning-tags-BulDqlk3.mjs";
import "./code-regions-Brr79Na1.mjs";
import { c as trimTextPreservingCode, i as createTextProjection, s as trimTextFilter } from "./text-projection-DmHct7rN.mjs";
import { g as findFinalTagMatches, m as hasOrphanReasoningCloseBoundary, v as downgradedToolCallTextFilter, y as stripDowngradedToolCallText } from "./assistant-visible-text-CWq-s28E.mjs";
import { U as extractTextFromChatContent, k as parseExecApprovalResultText, m as renderAuthProfileFailoverCopy, t as renderAgentHarnessPreflightUserMessage } from "./user-facing-text-BCPDY-39.mjs";
import { r as isCloudCodeAssistFormatError } from "./classify-core-Oyk-1wHl.mjs";
import { n as isTransientNetworkError } from "./retryable-network-errors-D2gJmBqw.mjs";
import { a as renderAssistantRequestFailureCopy } from "./assistant-request-failure-copy-bk0US-bY.mjs";
import { c as patchSessionEntryCore } from "./session-accessor.sqlite-entry-DLa7_9ui.mjs";
import { i as parseInlineDirectives, t as findDirectiveCodePrefix } from "./directive-tags-DLtws5Pc.mjs";
import { t as applyAssistantDeliveryDirectives } from "./transcript-assistant-delivery-CGqOQ0cS.mjs";
import { c as resolveStagedInputMediaPaths } from "./staged-inputs-CCmC9PQx.mjs";
import { n as isQueryStopWordToken, t as extractKeywords } from "./query-expansion-8IlqIzbI.mjs";
import { M as recoverSessionTranscriptProjection } from "./session-accessor-C3ljJ974.mjs";
import { a as readSessionEntrySummariesInWorker } from "./session-entry-read-runtime-BdnQjoL6.mjs";
import { a as acceptProviderReviewAcknowledgment, d as readProviderReviewAcknowledgment, o as assertSessionProviderReviewWorkStart } from "./session-work-start-Bkld8CTK.mjs";
import { o as resolveSessionTranscriptRuntimeTarget, r as resolveSessionKeyBySessionIdAsync } from "./session-accessor.transcript-target-DdAdqCJ1.mjs";
import { i as isToolCallBlockType } from "./tool-block-contract-CgqnHcSD.mjs";
import { o as normalizeHeartbeatToolResponse } from "./heartbeat-tool-response-CBuqAXpY.mjs";
import { t as filterHeartbeatTranscriptArtifacts } from "./heartbeat-filter-DruZVPZx.mjs";
import { l as recordSessionCompacted } from "./session-state-events-tNQ7VRZz.mjs";
import { a as resolveSessionPermissionExecMode } from "./session-permission-exec-mode-w9RzCNiZ.mjs";
import { i as getPluginToolSideEffectOwnerKey, r as getPluginToolMeta } from "./tool-metadata-D5DsuLcG.mjs";
import { n as textResult } from "./tool-results-BCM3fdVS.mjs";
import { n as copyAgentToolAvailability, o as markAgentToolExecutionUnavailable, r as finalizeAgentToolAvailability } from "./agent-tool-availability-ChrbIW4j.mjs";
import { i as copyAgentToolMetadata, p as getChannelAgentToolMeta } from "./agent-tool-metadata-DU21au_v.mjs";
import { _ as getInternalToolTurnCompletion, a as attachInternalToolExecutionPreparer, i as attachInternalToolBatchLifecycle, m as getInternalToolExecutionPreparer, u as copyInternalToolResultState, y as setInternalToolTurnCompletion } from "./internal-hooks-CSjTREUl.mjs";
import { i as readToolEffectReceipt, n as consumeToolExecutionNotStarted, r as markToolExecutionNotStarted, t as buildToolEffectReceipt } from "./tool-effect-receipt-BHVAm8DM.mjs";
import { a as ACTIVE_EMBEDDED_RUNS_BY_RUN_ID, i as ACTIVE_EMBEDDED_RUNS, o as ACTIVE_EMBEDDED_RUN_REGISTRATIONS, y as setActiveEmbeddedRunLifecycleGeneration } from "./run-state-D054QiMr.mjs";
import { n as resolveAgentTimeoutMs, t as DEFAULT_AGENT_TIMEOUT_MS } from "./timeout-BBs3o51w.mjs";
import { i as MessageInjectionWithdrawnError, t as MessageInjectionAcceptedUnconfirmedError } from "./message-injection-authority-D0WxWVfd.mjs";
import { h as getReplyOperationSessionReader } from "./reply-run-registry.state-BgHFgrrj.mjs";
import { t as isEmbeddedMode } from "./embedded-mode--Mk71zY-.mjs";
import { t as hasPromptImageInput } from "./prompt-image-input-Urne2gUL.mjs";
import { _ as beginDiagnosticRetryWait, f as markDiagnosticRunProgress, i as createDiagnosticEmbeddedRunOwner, r as closeDiagnosticEmbeddedRunOwner } from "./diagnostic-run-activity-NzwvGiAW.mjs";
import { b as resolveCompactionTimeoutMs } from "./diagnostic-jtgHaFVw.mjs";
import { N as setActiveEmbeddedRun, g as markActiveEmbeddedRunAbandoned, i as clearActiveEmbeddedRun } from "./runs-Qhulcs4s.mjs";
import { a as findMediaGenerationOperation, d as isTerminalMediaGenerationStatus, f as listMediaGenerationOperations } from "./media-generation-activity-Bu95TPd8.mjs";
import { t as parseReplyDirectives } from "./reply-directives-BbFfofv9.mjs";
import { a as resolveSendableOutboundReplyParts } from "./reply-payload-parts-XnYlwTNV.mjs";
import "./reply-payload-eiK8mKuN.mjs";
import "./tool-fs-policy-D543Bcua.mjs";
import { n as getAgentScopedMediaLocalRoots } from "./local-roots-Bd0Cpppp.mjs";
import { a as joinWithRunLivenessDeadline, i as isOpenClawAbortableWrapper, n as abortable, o as withAbortListener, r as createAbortableError, t as RUN_LIVENESS_JOIN_TIMEOUT_MS } from "./abortable-Cd6QbDOo.mjs";
import "./failover-error-JM6M2oyF.mjs";
import { n as isTerminalAssistantError } from "./retry-CbmDlMZf.mjs";
import { n as classifyAssistantFailoverReason } from "./assistant-message-failures-OdyZvSi1.mjs";
import { a as validateAnthropicTurns, d as normalizeOpenAIResponsesToolCallIds, h as formatUserFacingAssistantErrorText, i as shouldMergeConsecutiveUserTurns, l as downgradeOpenAIFunctionCallReasoningPairs, o as validateGeminiTurns, r as shouldAllowProviderOwnedThinkingReplay, t as mergeConsecutiveUserMessages } from "./embedded-agent-helpers-DcjN-7C9.mjs";
import { i as isThinkingLikeBlock, r as sanitizeToolCallIdsForCloudCodeAssist, t as hasToolCallInput } from "./tool-call-id-CBojAlOj.mjs";
import { t as createCompletedToolCallPredicate } from "./tool-call-shared-7s9Zv43U.mjs";
import { i as resolveCurrentSourceMessagingToolPartial, n as isMessagingToolDuplicateNormalized, r as normalizeTextForComparison } from "./messaging-dedupe-D188k2i5.mjs";
import { n as buildTextObservationFields, r as shouldSuppressRawErrorConsoleSuffix, t as buildApiErrorObservationFields } from "./embedded-agent-error-observation-BACZoB66.mjs";
import { t as resolvePreferredSessionKeyForSessionIdMatches } from "./session-id-resolution-BnecGj-H.mjs";
import { o as resolveStoredSessionKeyForSessionId, r as resolveExistingSessionKeyForRequest } from "./session-CMbIh8dV.mjs";
import { i as SummaryOutputBudgetError, n as CompactionError } from "./types-CPdr3cVV.mjs";
import { a as formatUnknownCompactionReasonDetail, i as classifyCompactionReason, t as resolveReplyFailoverFacts } from "./request-error-facts-CtJZTTS5.mjs";
import { t as buildProviderAuthRecoveryHint } from "./provider-auth-recovery-hint-CR9bd50i.mjs";
import { n as resolveDiagnosticModelContentCapturePolicy } from "./diagnostic-llm-content-P2SGagoP.mjs";
import { i as REQUIRED_PARAM_GROUPS, o as missingRequiredParamLabels } from "./agent-tools.before-tool-call.decision-CKWdJok8.mjs";
import { a as readToolResultDetails, n as isToolResultError } from "./tool-result-error-CN2enaAJ.mjs";
import { t as buildToolMutationState } from "./tool-mutation-BTW6eoAN.mjs";
import { n as hashToolCall, r as observeRepeatedToolError } from "./tool-loop-detection-E2pymHui.mjs";
import { a as isMessagingToolSendAction, o as isMessagingToolTargetEvidenceAction, r as isMessagingTool, s as isPluginNativeMessagingTool } from "./embedded-agent-messaging-NL2tVtK9.mjs";
import { n as claimPendingAgentQuestionAnswer, t as cancelPendingAgentQuestionForSession } from "./gateway-question-C8X_AymR.mjs";
import { r as normalizeAskUserParams } from "./ask-user-tool-normalization-CCjngY5-.mjs";
import { d as parseFenceSpans, f as scanFenceSpans, l as findFenceSpanAt } from "./chunk-DRxifHKN.mjs";
import { i as isCommandBearingToolCall, r as inferToolMetaFromArgsCore } from "./tool-display-D_9ViFCQ.mjs";
import { n as isAgentPlanProgressToolName } from "./progress-card-input-DpZWr3eq.mjs";
import { t as formatToolAggregate } from "./tool-meta-DkAjuu5Y.mjs";
import { I as resolveFileMutationToolName, L as countStreamingFileMutationLines } from "./streaming-DF0Ecn4m.mjs";
import { n as prepareCronRootSessionGeneration } from "./session-delivery-generation-B8Iv03w1.mjs";
import { a as reserveAskUserPromptDelivery, l as sendQuestionToolPrompt, n as cancelAskUserPromptDelivery, o as settleAskUserPromptDelivery, s as waitForAskUserPromptReady } from "./ask-user-tool-DfDCY7gI.mjs";
import { i as resolveReplyExpectation, r as resolveReplyCompletion } from "./reply-completion-B5n1Cnxm.mjs";
import { n as OPENCLAW_EMBEDDED_CONTEXT_ENGINE_HOST, r as assertContextEngineHostSupport } from "./host-compat-DWLjZN89.mjs";
import { t as BUILTIN_AGENT_HARNESS_METADATA } from "./builtin-openclaw-metadata-4LZRMXv7.mjs";
import { c as mergeAgentRunAttemptTerminal, d as setAgentRunAttemptTerminalFailure, r as buildAgentRunTerminalOutcomeFromAttempt, u as projectAgentRunAttemptTerminal } from "./agent-run-terminal-outcome-8xoMCk5L.mjs";
import { t as createStageTimingTracker } from "./stage-timing-BUG4Fnsv.mjs";
import { r as materializeBundleMcpToolsForRun } from "./agent-bundle-mcp-materialize-BZFLfYd6.mjs";
import { s as readMcpAppChannelView } from "./mcp-ui-resource-CRolTrlz.mjs";
import "./agent-bundle-mcp-tools-CBaoEtzu.mjs";
import { r as resolveOsSummary } from "./os-summary-DH0HB36b.mjs";
import { a as summarizeToolValidationError, n as hasTerminalControlCharacter, t as createToolValidationErrorSummary } from "./tool-error-summary-CezRoLUS.mjs";
import { n as splitTrailingDirective, t as createStreamingDirectiveAccumulator } from "./streaming-directives-NE2_kiRp.mjs";
import { r as resolveCompletedActivityWrappers } from "./agent-activity-presentation-M4eIWvHv.mjs";
import { r as projectAgentToolActivity, t as emitAgentActivityEvent } from "./agent-activity-events-xgyk7cqw.mjs";
import { t as buildGuardedModelFetch } from "./provider-transport-fetch-D21BLkwV.mjs";
import { t as resolveSelectedModelCredential } from "./model-auth-selected-credential-ieVmdIDY.mjs";
import { o as markdownToIR } from "./ir-CO-hva7w.mjs";
import { o as resolveUserTimezone, r as formatDateStamp } from "./date-time-BpsRMb9A.mjs";
import { a as prepareHarnessContextEnginePrompt, c as buildPrePromptContextBudgetStatus, d as estimateToolSchemaTokenPressure, f as formatPrePromptPrecheckLog, n as bootstrapHarnessContextEngine, p as shouldPreemptivelyCompactBeforePrompt, r as finalizeHarnessContextEngineTurn, s as PREEMPTIVE_OVERFLOW_ERROR_TEXT } from "./context-engine-lifecycle-DA8PeUIj.mjs";
import { t as runWithAsyncWorkResources } from "./async-work-resources-BvYhIOBb.mjs";
import { t as resolveSessionPlacementComputer } from "./session-placement-computer-Bem5hZb0.mjs";
import { t as resolveConversationCapabilityProfile } from "./conversation-capability-profile-Bzbp--xF.mjs";
import { a as prepareSkillLibrarySelection } from "./selection-CLpBV2Ib.mjs";
import { t as resolveSkillResourceCandidates } from "./resource-candidates-DNAiWHXM.mjs";
import { l as bindCodeModeSessionStore, m as wrapToolWithAbortSignal, p as raceWithAbortSignal } from "./tool-contract-error-CznWpQuP.mjs";
import { r as messageToolOwnsVisibleReply } from "./source-reply-delivery-mode-CBzJmohC.mjs";
import { r as projectConversationToolNames } from "./conversation-tool-policy-pipeline-qvZEbKgk.mjs";
import { i as createOpenClawCodingToolsInternalAsync } from "./agent-tools-Bpi7xWYy.mjs";
import { r as assertMemoryFlushPersistenceToolAvailable } from "./core-coding-tools-DFJcX7yX.mjs";
import { s as createSkillInstructionDeliveryCache } from "./agent-tools.read-KaO_d_Xi.mjs";
import { d as createToolDefinitionFromAgentTool, f as wrapToolDefinition } from "./tools-BYG6Y2SC.mjs";
import { t as getAgentToolAssistantTurnId } from "./tool-execution-context-BwYF5HLu.mjs";
import { t as decodeHtmlEntities } from "./html-entities-CvDVeY8C.mjs";
import { C as filterLocalModelLeanTools, S as retainToolSearchImplementation, T as resolveLocalModelLeanPreserveToolNames, b as TOOL_SEARCH_CONTROL_TOOL_NAMES, c as resolveToolSearchConfig, d as clearToolSearchCatalog, f as collectUniqueCatalogToolNames, l as addClientToolsToToolCatalog, s as resolveToolSearchCatalogTool, v as TOOL_CALL_RAW_TOOL_NAME, w as isLocalModelLeanEnabled, x as TOOL_SEARCH_RAW_TOOL_NAME, y as TOOL_DESCRIBE_RAW_TOOL_NAME } from "./tool-search-BorBxVGT.mjs";
import { t as resolveNativeWebSearchRoute } from "./native-web-search-bW6OOGeW.mjs";
import "./channel-tools-DbPPjer2.mjs";
import { a as normalizeSecretsRequestParams } from "./openclaw-tools-CSSYZDKR.mjs";
import { t as resolveSkillWorkshopToolConstructionBlock } from "./tool-availability-BWNOfZ4J.mjs";
import { t as isHeartbeatLifecycleRunKind } from "./bootstrap-mode-HvSedbJl.mjs";
import { i as wrapUntrustedPromptDataBlock } from "./sanitize-for-prompt-bzCyHFCH.mjs";
import { w as releasePendingAgentSteeringItems } from "./subagent-registry-CU3B54DJ.mjs";
import { t as hasAnyNonEmptyString } from "./delivery-evidence-values-CSe-71lq.mjs";
import { n as filterRuntimeCompatibleTools } from "./tool-schema-projection-DePPU1ko.mjs";
import { t as resolveToolLoopDetectionConfig } from "./tool-loop-detection-config-TObMWR7t.mjs";
import { r as loadPairedComputerUseAvailabilityForSurface } from "./computer-tool-odja-mmi.mjs";
import { r as captureFinalEffectiveCronCreatorToolAllowlist } from "./cron-tool-D1QAKUFW.mjs";
import { r as collectAgentInternalEventMedia } from "./internal-events-BxRyNeVO.mjs";
import { b as resolveSourceReplyDelivery, c as hasCommittedMessagingToolDeliveryEvidence, m as hasMessagingToolDeliveryEvidence } from "./delivery-evidence-DfR9-cfA.mjs";
import { a as extractAssistantVisibleText, c as extractThinkingFromTaggedText, d as prepareAssistantVisibleText, f as promoteThinkingTagsToBlocks, i as extractAssistantThinking, n as createThinkingTagStreamState, o as extractEmbeddedAssistantText, p as sanitizeAssistantVisibleStreamText, r as extractAssistantCommentaryText, s as extractThinkingFromTaggedStream, t as createAssistantVisibleStreamText, u as isAssistantMessage } from "./embedded-agent-utils-BDIGXaqj.mjs";
import { d as parseJsonMessageParam } from "./message-action-normalization-DoBdQPE-.mjs";
import { c as readEmbeddedMessageDeliveryFact, r as isDeliveredCoreCurrentChannelWidgetResult, s as projectPluginMessageDeliveryFact } from "./embedded-agent-message-delivery-DQX5J0bu.mjs";
import { t as event_stream_exports } from "./event-stream-xGBL9CyU.mjs";
import { i as normalizeAcceptedSessionSpawnResult, n as hasCompletionMessageSessionSpawn, t as hasAcceptedSessionSpawn } from "./accepted-session-spawn-B717Q0Hs.mjs";
import { n as supportsModelTools } from "./model-tool-support-DIQSEumC.mjs";
import { i as markCoreTtsAttemptResult, r as getCoreTtsToolResultMediaUrls } from "./tts-tool-result-provenance-BbSbWcUn.mjs";
import { i as isAgentToolRestartSafe, n as collectSideEffectToolOwners, r as isAgentToolReplaySafe, t as collectReplaySafeToolNames } from "./tool-replay-safety-BvINajgM.mjs";
import { t as log$6 } from "./logger-Wi08e7sJ.mjs";
import { t as runBestEffortCallback } from "./embedded-agent-subscribe.callback-BBye3ou6.mjs";
import { r as remapSkillReferencePaths } from "./sandbox-skills-Dcyg2gP3.mjs";
import { t as prepareInstalledSkillCatalog } from "./installed-skill-runtime-DbhuKfkX.mjs";
import { d as setSteeringRuntimeContextRetention, l as prependRuntimeContextForModel, n as applyRuntimeContextCarrierRetention } from "./runtime-context-prompt-DkBUcs_i.mjs";
import { r as recordPersistedRuntimeToolSchemaQuarantine, t as clearRecoveredPersistedRuntimeToolSchemaQuarantines } from "./tool-schema-quarantine-health-95wh3Kzs.mjs";
import { n as resolveSkillsPrompt, t as buildSkillSnapshot } from "./workspace-skill-prompt-D9WI4c-x.mjs";
import { t as prepareRuntimeSkillEntries } from "./skills-prompt-B98c20Xe.mjs";
import { t as FULL_BOOTSTRAP_COMPLETED_CUSTOM_TYPE } from "./bootstrap-files-BVIOGOj-.mjs";
import { $ as buildContextEnginePromptCacheInfo, A as createYieldAbortedResponse, B as isRunnerToolCallBlock, C as sanitizeSessionHistory, D as selectCompactionTimeoutSnapshot, E as resolveRunTimeoutDuringCompaction, F as installEmbeddedAttemptContextGuards, G as beginPromptCacheObservation, I as prepareEmbeddedAttemptSetup, J as declarePromptHistoryRewrite, K as collectPromptCacheTools, L as startEmbeddedAttemptDiagnostics, M as queueSessionsYieldInterruptMessage, N as isRunnerAbortError, O as shouldFlagCompactionTimeout, P as prepareEmbeddedAttemptBootstrap, S as resolveExistingAttemptTranscriptState, T as isZeroUsageEmptyStopAssistantTurn, X as readLastCacheTtlTimestamp, Y as isCacheTtlEligibleProvider, _ as logDecisionToolRequest, a as prepareEmbeddedAttemptPromptAssembly, b as removeTrailingMidTurnPrecheckAssistantError, c as createPromptBuildToolPolicy, d as submitEmbeddedAttemptPrompt, et as findCurrentAttemptAssistantMessage, f as wrapStreamFnWithMessageTransform, i as measureEmbeddedAgentPreparation, j as isSessionsYieldAbortReason, k as SESSIONS_YIELD_ABORT_REASON, l as observeEmbeddedAttemptPrompt, m as normalizeMessagesForLlmBoundary, nt as resolvePromptCacheTouchTimestamp, o as prepareEmbeddedAttemptPromptContext, p as cleanupEmbeddedAttemptResources, q as completePromptCacheObservation, r as createPreparedEmbeddedAgentSettingsManager, t as prepareEmbeddedAttemptSystemPrompt, tt as findLatestUncompactedAttemptUsageSnapshot, u as handleEmbeddedAttemptPromptError, v as loadAttemptSessionEntryAfterQuotaMaintenance, w as validateReplayTurns, x as resolveAttemptTrajectorySessionFile, y as normalizeCompactionRecoveryTranscriptTail, z as resolveAttemptTranscriptPolicy } from "./attempt-system-prompt-prepare-ChKsbjSh.mjs";
import { i as sanitizeToolUseResultPairingForModel, r as sanitizeToolUseResultPairing, t as repairToolUseResultPairing } from "./session-transcript-repair-BkR36ZME.mjs";
import { i as resolveExplicitCachedContent, r as resolveCacheRetention, t as isGooglePromptCacheEligible } from "./prompt-cache-retention-BEbTNOXT.mjs";
import { a as persistSessionSystemPrompt, c as prepareSessionSystemPrompt, d as retainEmbeddedSessionPromptState, f as retireSessionSystemPrompt, l as recordRuntimeContextProjection, o as persistToolResultProjections, t as beginSessionSystemPrompt } from "./session-prompt-state-BkfEGfU5.mjs";
import { A as resolveCompactionInstructions, O as sanitizeCompactionReplayMessages, R as sanitizeCompactionMessages, S as retireQueuedUserMessage, c as agentSessionSetContextReplacementHook, g as createCompactionRequestBudget, k as stripStaleAssistantUsageBeforeLatestCompaction, l as recordSessionModelUsage, m as agentSessionSetPromptPreparation, n as DefaultResourceLoader, p as agentSessionQueuePromptContext, u as setSessionModelUsageSink, v as getSteeringMessageIdentity, x as createSessionManagerRuntimeRegistry, y as subscribeSteeringMessagePersistenceFailure } from "./settings-manager-2KjnbEXR.mjs";
import { S as wrapStreamObjectSettlement, b as mapAssistantMessageStream, h as dropThinkingBlocks, m as dropReasoningFromHistory, o as restoreCacheTtlToolResultProjections, p as assessLastAssistantMessage, u as truncateOversizedToolResultsInSessionManager, v as stripThinkingBlocksFromMessage, x as wrapStreamObjectEvents, y as wrapAnthropicStreamWithRecovery } from "./tool-result-truncation-CgpKdRPP.mjs";
import { n as guardSessionManager } from "./messages-BZGmsTOt.mjs";
import { t as rewriteTranscriptEntriesInSessionManager } from "./transcript-rewrite-28vuJHxU.mjs";
import { t as mapThinkingLevel } from "./utils-D58sQxb1.mjs";
import { _ as resolveEmbeddedCompactionThinkingLevel, i as buildAfterTurnRuntimeContextFromUsage, o as mergeOrphanedTrailingUserPrompt, r as buildAfterTurnRuntimeContext, t as runContextEngineMaintenance } from "./context-engine-maintenance-lAvtCTBg.mjs";
import { a as materializeProviderContext, n as detectAndLoadPromptImages, t as buildPromptImageFailureNotice } from "./images-CvT6r_I_.mjs";
import { t as createBundleLspToolRuntime } from "./agent-bundle-lsp-runtime-Bx83dtR4.mjs";
import { n as normalizeAgentRuntimeTools, t as logAgentRuntimeToolDiagnostics } from "./tools-CjVSnm_O.mjs";
import { t as applyFinalEffectiveToolPolicy } from "./effective-tool-policy-DwPDbA-1.mjs";
import { a as shouldCreateBundleMcpRuntimeForAttempt, i as shouldCreateBundleLspRuntimeForAttempt, n as mergeForcedEmbeddedAttemptToolsAllow, r as resolveEmbeddedAttemptToolConstructionPlan, t as applyEmbeddedAttemptToolsAllow } from "./attempt-tool-construction-plan-Cf_IxbdX.mjs";
import { i as sessionManagerPrepareCurrentTurnReplay, n as captureCompletedTurnMessageAnchor, o as sessionManagerReadTranscriptStart, r as completedTurnMessageAnchor, t as SessionManager } from "./session-manager-BpvVpTVC.mjs";
import { t as SessionTranscriptMessageCommittedError } from "./session-manager-message-error-C6BAQhGS.mjs";
import { n as withSessionManagerWrite, t as appendSessionTranscriptNote } from "./session-manager-write-admission-DQneVl5b.mjs";
import { a as extractToolResultText, c as prepareToolResult, d as sanitizeToolResult, f as truncateLiveExecOutput, i as extractToolErrorMessage, l as readAsyncStartedTaskIds, n as capLiveExecResult, o as isAsyncStartedToolResult, r as extractToolErrorCode, s as isToolResultTimedOut, t as buildToolLifecycleErrorResult, u as sanitizeToolArgs } from "./embedded-agent-tool-results-D87rtFux.mjs";
import { r as runAgentEndSideEffectsAsync } from "./agent-end-side-effects-Cc9DHY-6.mjs";
import { i as runAgentHarnessBeforeAgentFinalizeHook, s as bindAgentHarnessHookMessages } from "./lifecycle-hook-helpers-BOCYz9r1.mjs";
import { t as generateSummary } from "./sessions-EXJV0Vms.mjs";
import { l as resolveFinalAssistantRawText, m as buildEmbeddedAgentHookContext, p as resolveReportedModelRef, u as resolveFinalAssistantVisibleText } from "./helpers-D9CUoQGB.mjs";
import { a as buildEmbeddedAttemptToolRunContext, n as projectSettledTurnFinalizationAttemptResult, o as captureToolAuthoredSourceReply, s as buildEmbeddedAgentEndContext, u as projectTrajectorySessionTarget } from "./settled-turn-finalization-result-CUcocDhr.mjs";
import { n as buildStreamErrorAssistantMessage } from "./stream-message-shared-CYuIJ_XF.mjs";
import { i as toNormalizedUsage, r as mergeUsageIntoAccumulator, t as createUsageAccumulator } from "./usage-accumulator-zbnmV2Ez.mjs";
import { n as resolveHeartbeatSummaryForAgent } from "./heartbeat-summary-DanEvL-Q.mjs";
import { t as streamWithPayloadPatch } from "./stream-payload-utils-BoUixBWV.mjs";
import { c as hasCopilotVisionInput, o as buildCopilotDynamicHeaders } from "./copilot-dynamic-headers-C8eJEO7a.mjs";
import { a as resolveEmbeddedAgentBaseStreamFn, i as resolveEmbeddedAgentApiKey, n as wrapStreamFnWithDiagnosticModelCallEvents, r as createOpenAIServiceTierObservationWrapper, s as selectEmbeddedAgentStream, t as resolveSessionBoundaryPromptCacheKey } from "./session-boundary-prompt-cache-key-SDHAWKdr.mjs";
import { n as runBeforeAgentRunGate, t as buildAgentRunBlockedUserMessage } from "./before-agent-run-0bHKoKHh.mjs";
import { t as safeJsonStringify } from "./safe-json-q5hA23KQ.mjs";
import { t as createTrajectoryRuntimeRecorder } from "./runtime-DQCcIerA.mjs";
import { r as createCodeModePermissionChangeReason, t as createAgentHarnessToolSurfaceRuntimeCore } from "./tool-surface-bridge-Ccc1MrSf.mjs";
import { r as resolveAttemptSpawnWorkspaceDir, t as appendAttemptCacheTtlIfNeeded } from "./attempt-thread-helpers-WgxT8ZaC.mjs";
import { t as resolveRawAssistantAnswerText } from "./assistant-answer-text-C6522I6O.mjs";
import { t as TOOL_FAILURE_INSTRUCTION } from "./tool-outcome-instructions-D1-OxlK6.mjs";
import { b as isCodeModeDiagnosticEnabled, s as createCodexNativeWebSearchWrapper, x as logCodeModeDiagnostic } from "./proxy-C1UOLOq7.mjs";
import { t as registerProviderStreamForModel } from "./provider-stream-BMdyPkWO.mjs";
import { a as resolvePreparedExtraParams, n as resolveAgentTransportOverride, r as resolveExplicitSettingsTransport, t as applyExtraParamsToAgent } from "./extra-params-DVg-z_v9.mjs";
import { t as EmbeddedBlockChunker } from "./embedded-agent-block-chunker-CxsctPo2.mjs";
import { t as projectProgressCardChannelUpdate } from "./progress-card-channel-summary-DLV-IV8W.mjs";
import { i as resolveMessageToolSourceReplyFinal, n as isDeliveredMessagingToolResult, r as readMessageToolSourceReplyText, t as isDeliveredMessageToolOnlySourceReplyResult } from "./embedded-agent-message-tool-source-reply-BWgEyCLd.mjs";
import { a as isDeliveredMessagingToolSendToCurrentSource, i as extractToolAuthoredSourceReplyPayload, n as extractMessagingToolSendResult, r as extractMessagingToolSourceReplyPayload, t as extractMessagingToolSend } from "./embedded-agent-messaging-extraction-BhsNO44c.mjs";
import { a as isCoreToolResultMediaTrustedName, i as filterToolResultMediaUrls, n as collectMessagingMediaUrlsFromToolResult, r as extractToolResultMediaArtifact, t as collectMessagingMediaUrlsFromRecord } from "./embedded-agent-tool-media-ByROOZAi.mjs";
import { N as observeAgentRunApprovalWait } from "./code-mode-state-BkXPjhOA.mjs";
import { t as redactAgentDiagnosticPayload } from "./diagnostic-redaction-Cm_Q_ktX.mjs";
import { a as resolveEffectiveCompactionMode, i as isSilentOverflowProneModel, n as applyAgentAutoCompactionGuard } from "./agent-settings-B14a7q7k.mjs";
import { a as toToolDefinitions, i as toClientToolDefinitions, n as findClientToolNameConflicts, t as createClientToolNameConflictError } from "./agent-tool-definition-adapter-TatBtYxj.mjs";
import { t as createAgentSession } from "./sdk-COTqBZzn.mjs";
import { n as resolveCronStyleNow } from "./current-time-DAycv5_-.mjs";
import { t as createAgentToolResultMiddlewareRunner } from "./tool-result-middleware-Xo--IllJ.mjs";
import { t as runHostPreparedIsolatedCompletion } from "./host-prepared-isolated-completion-Bm5yqqzI.mjs";
import fs from "node:fs";
import path from "node:path";
import { AsyncLocalStorage } from "node:async_hooks";
import fs$1 from "node:fs/promises";
import { isDeepStrictEqual } from "node:util";
import crypto$1, { randomUUID } from "node:crypto";
import { performance } from "node:perf_hooks";
import { setImmediate } from "node:timers/promises";
import { isProxy } from "node:util/types";
import { hasOnlyAssistantReasoningContent, readAssistantThinkingAppend, sanitizeSurrogates, sortPromptCacheToolsByName, splitSystemPromptCacheBoundary, stripSystemPromptCacheBoundary } from "@openclaw/ai/internal/shared";
import { getEventStreamCompletion, getFirstStreamEventTimeoutMs, onLlmRequestActivity, parseStreamingJson } from "@openclaw/ai/internal/runtime";
import { OPENAI_RESPONSES_APIS } from "@openclaw/ai/internal/openai-responses-payload-policy";
import { bindResponsesInputMessage, modelRequestBodyState, responsesPromptObserver, responsesRequestLifecycle } from "@openclaw/ai/internal/openai";
import { CompactionReplayRefreshRequiredError, isAnthropicServerToolClearingEnabled, mergeTransportHeaders, preserveCompactionReplayWindow, replaceCompactionReplayOwnerContent, resolveCompactionReplayEligibility, sanitizeTransportPayloadText, stripCompactionReplayCheckpoint, stripCompactionReplayCheckpointInPlace, withProviderAcceptanceObserver } from "@openclaw/ai/transports";
import { isResponsesOutputLimitToolCallError } from "@openclaw/ai/diagnostics";
import { isAnthropicOAuthApiKey, isDirectAnthropicModel } from "@openclaw/ai/internal/anthropic";
//#region src/shared/tool-activity-heartbeat.ts
const { runListeners, runLastActivityMs } = resolveGlobalSingleton(Symbol.for("openclaw.toolActivityHeartbeat"), () => ({
runListeners: /* @__PURE__ */ new Map(),
runLastActivityMs: /* @__PURE__ */ new Map()
}), (state) => {
for (const listeners of state.runListeners.values()) notifyListeners(listeners, void 0);
state.runListeners.clear();
state.runLastActivityMs.clear();
});
function notifyToolActivity(runId) {
runLastActivityMs.set(runId, Date.now());
const listeners = runListeners.get(runId);
if (!listeners) return;
for (const listener of listeners) listener();
}
function onToolActivity(runId, listener) {
let listeners = runListeners.get(runId);
if (!listeners) {
listeners = /* @__PURE__ */ new Set();
runListeners.set(runId, listeners);
}
listeners.add(listener);
return () => {
listeners.delete(listener);
if (listeners.size === 0 && runListeners.get(runId) === listeners) runListeners.delete(runId);
};
}
function getLastToolActivityMs(runId) {
return runLastActivityMs.get(runId) ?? 0;
}
function clearToolActivityRun(runId) {
runListeners.delete(runId);
runLastActivityMs.delete(runId);
}
//#endregion
//#region src/agents/tool-schema-quarantine.ts
/**
* Runtime tool-schema quarantine logging.
*
* Model providers can reject unsupported schema shapes, so runtime projection
* reports quarantined tools with trusted diagnostics before the model call.
*/
const log$5 = createSubsystemLogger("agents/tools");
function readDiagnosticPluginId(params) {
try {
const tool = params.tools[params.diagnostic.toolIndex];
return tool ? getPluginToolMeta(tool)?.pluginId : void 0;
} catch {
return;
}
}
function pluginOwner(pluginId) {
return pluginId ? `plugin:${pluginId}` : void 0;
}
function toolQuarantineKey(params) {
return JSON.stringify([params.owner ?? "", params.toolName]);
}
function readToolIdentity(tool) {
try {
if (typeof tool.name !== "string" || tool.name.length === 0) return;
const owner = pluginOwner(getPluginToolMeta(tool)?.pluginId);
return owner ? {
owner,
toolName: tool.name
} : { toolName: tool.name };
} catch {
return;
}
}
function listHealthyToolIdentities(params) {
const failingKeys = new Set(params.diagnostics.map((diagnostic) => toolQuarantineKey({
owner: pluginOwner(readDiagnosticPluginId({
tools: params.tools,
diagnostic
})),
toolName: diagnostic.toolName
})));
const healthy = [];
for (const tool of params.tools) {
const identity = readToolIdentity(tool);
if (identity && !failingKeys.has(toolQuarantineKey(identity))) healthy.push(identity);
}
return healthy;
}
/** Emits diagnostics and logs for tools removed from runtime schema projection. */
async function logRuntimeToolSchemaQuarantine(params) {
const recovered = clearRecoveredPersistedRuntimeToolSchemaQuarantines(() => listHealthyToolIdentities({
diagnostics: params.diagnostics,
tools: params.tools
}));
const writes = [];
try {
if (params.diagnostics.length === 0) {
await recovered;
return;
}
const summary = params.diagnostics.map((diagnostic) => {
const pluginId = readDiagnosticPluginId({
tools: params.tools,
diagnostic
});
const owner = pluginId ? ` plugin=${pluginId}` : "";
emitTrustedDiagnosticEvent({
type: "tool.execution.blocked",
runId: params.runId,
agentId: params.agentId,
...params.sessionKey ? { sessionKey: params.sessionKey } : {},
...params.sessionId ? { sessionId: params.sessionId } : {},
toolName: diagnostic.toolName,
toolSource: pluginId ? "plugin" : "core",
...pluginId ? { toolOwner: pluginId } : {},
deniedReason: "unsupported_tool_schema",
reason: diagnostic.violations.join(", ")
});
const persistedOwner = pluginOwner(pluginId);
writes.push(recordPersistedRuntimeToolSchemaQuarantine({
toolName: diagnostic.toolName,
...persistedOwner ? { owner: persistedOwner } : {},
reason: diagnostic.violations.join(", "),
failedAt: /* @__PURE__ */ new Date()
}).catch(() => {}));
return `${diagnostic.toolName}${owner}: ${diagnostic.violations.join(", ")}`;
}).join("; ");
log$5.warn(`[tools] quarantined ${params.diagnostics.length} unsupported tool schema${params.diagnostics.length === 1 ? "" : "s"} before model runtime projection: ${summary}. Run openclaw doctor for details.`);
await recovered;
} finally {
await Promise.allSettled([recovered, ...writes]);
}
}
/** Joins diagnostic writes after a synchronous SDK or permission-publication callback finishes. */
async function withRuntimeToolSchemaQuarantine(run) {
const pending = [];
const record = (params) => {
pending.push(logRuntimeToolSchemaQuarantine(params));
};
let result;
try {
result = await run(record);
} catch (error) {
await Promise.allSettled(pending);
throw error;
}
const settled = await Promise.allSettled(pending);
for (const operation of settled) if (operation.status === "rejected") throw operation.reason;
return result;
}
//#endregion
//#region src/agents/embedded-agent-runner/skill-runtime.ts
/** Prepares readable skills and owns environment rollback until the caller takes custody. */
async function prepareEmbeddedSkills(params) {
const executionAllow = params.attempt.toolExecutionAllow;
if (params.attempt.operation === "settled-tool-finalization" || executionAllow && !isToolExecutionAllowed(executionAllow, "read")) return {
restoreSkillEnv: () => {},
skillUsagePaths: void 0,
skillsPrompt: "",
skillsSnapshotForRun: void 0,
skillReadResources: void 0,
codeModeSkills: [],
installedSkills: []
};
const { skillsEligibility, skillUsagePaths, skillsPromptWorkspaceDir, skillsSnapshot: preparedSnapshot, skillsWorkspaceDir, shouldLoadSkillEntries, skillEntries, loadSkillEntries, preserveEntryOrder, mapEntries } = await prepareRuntimeSkillEntries({
sandbox: params.sandbox,
skillsAnchorWorkspace: params.attempt.bootstrapWorkspaceDir ?? params.effectiveWorkspace,
skillsSnapshot: params.attempt.skillsSnapshot,
assertCurrent: params.assertCurrent,
config: params.attempt.config,
agentId: params.sessionAgentId,
executionWorkspaceDir: params.effectiveWorkspace
});
let restoreSkillEnv = () => {};
try {
const promptSkillEntries = mapEntries(shouldLoadSkillEntries ? skillEntries : void 0);
const skillsSnapshot = preparedSnapshot ?? await buildSkillSnapshot(skillsPromptWorkspaceDir, {
entries: promptSkillEntries ?? [],
config: params.attempt.config,
agentId: params.sessionAgentId,
eligibility: skillsEligibility,
preserveEntryOrder,
assertCurrent: params.assertCurrent
});
const skillsPrompt = await resolveSkillsPrompt({
assertCurrent: params.assertCurrent,
contextTokenBudget: params.attempt.contextTokenBudget,
skillsSnapshot,
entries: promptSkillEntries,
loadEntries: async () => mapEntries(await loadSkillEntries()) ?? [],
config: params.attempt.config,
workspaceDir: skillsPromptWorkspaceDir,
agentId: params.sessionAgentId,
eligibility: skillsEligibility,
preserveEntryOrder
});
const libraryEntries = params.sandbox?.enabled ? [] : await prepareSkillLibrarySelection(skillsSnapshot.librarySelections ?? [], {}, params.assertCurrent ?? (() => {}));
params.assertCurrent?.();
restoreSkillEnv = params.applySkillEnvironment === false ? () => {} : preparedSnapshot ? applySkillEnvOverridesFromSnapshot({
snapshot: preparedSnapshot,
config: params.attempt.config
}) : applySkillEnvOverrides({
skills: skillEntries,
config: params.attempt.config
});
const installedSkills = prepareInstalledSkillCatalog({
snapshot: skillsSnapshot,
workspaceDir: skillsWorkspaceDir,
sandbox: params.sandbox,
assertCurrent: params.assertCurrent
});
const codeModeSkills = params.includeCodeModeSkills ? installedSkills : [];
const skillReadResources = params.sandbox?.enabled ? void 0 : resolveSkillResourceCandidates(skillsSnapshot, libraryEntries);
return {
restoreSkillEnv,
skillReadResources,
skillUsagePaths,
skillsPrompt,
skillsSnapshotForRun: skillsSnapshot,
codeModeSkills,
installedSkills
};
} catch (error) {
restoreSkillEnv();
throw error;
}
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-bundle-tools.ts
async function prepareEmbeddedAttemptBundleTools(params) {
const { cronCreatorToolAllowlist, cronCreatorToolAllowlistCaptureRef, effectiveToolsAllow, inheritedToolAllowlist, localModelLeanPreserveToolNames, runtimeCapabilityProfile, toolsEnabled, toolsRaw } = params.preparedToolBase;
const normalizeTools = (tools, recordQuarantine) => normalizeAgentRuntimeTools({
runtimePlan: params.attempt.runtimePlan,
tools,
provider: params.attempt.provider,
config: params.attempt.config,
workspaceDir: params.setup.effectiveWorkspace,
env: process.env,
modelId: params.attempt.modelId,
modelApi: params.attempt.model.api,
model: params.attempt.model,
runtimeHandle: params.setup.getProviderRuntimeHandle(),
onPreNormalizationSchemaDiagnostics: (diagnostics, sourceTools) => recordQuarantine({
diagnostics,
tools: sourceTools,
runId: params.attempt.runId,
agentId: params.setup.sessionAgentId,
sessionKey: params.attempt.sessionKey,
sessionId: params.attempt.sessionId
})
});
const tools = await withRuntimeToolSchemaQuarantine((record) => normalizeTools(toolsEnabled ? toolsRaw : [], record));
const auxiliaryToolsEnabled = toolsEnabled && params.attempt.trigger !== "memory" && !params.attempt.disableTools && !params.isRawModelRun && !params.attempt.forceRestartSafeTools;
let clientTools = auxiliaryToolsEnabled ? params.attempt.clientTools : void 0;
if (clientTools && effectiveToolsAllow) {
const matchesRuntime = createRuntimeToolMatcher(effectiveToolsAllow);
clientTools = clientTools.filter((definition) => matchesRuntime(definition.function.name));
}
const bundleMetadataSnapshot = params.setup.getCurrentAttemptPluginMetadataSnapshot();
const bundleManifestRegistry = bundleMetadataSnapshot?.pluginIds === void 0 ? bundleMetadataSnapshot?.manifestRegistry : void 0;
const mcpConfig = {
workspaceDir: params.setup.effectiveWorkspace,
cfg: params.attempt.config,
manifestRegistry: bundleManifestRegistry,
toolOverrides: params.attempt.toolOverrides,
toolDenylist: runtimeCapabilityProfile.policy.explicitToolDenylist
};
const bundleMcpAcquisition = shouldCreateBundleMcpRuntimeForAttempt({
toolsEnabled: auxiliaryToolsEnabled,
toolsAllow: params.attempt.toolsAllow,
resolveConfiguredMcpNamespaces: () => {
const configuredNames = Object.keys(params.attempt.config?.mcp?.servers ?? {});
if (configuredNames.length === 0) return [];
const { loaded, safeServerNamesByServer } = loadSessionMcpConfig({
...mcpConfig,
logDiagnostics: false
});
return configuredNames.flatMap((name) => {
const safeName = Object.hasOwn(loaded.mcpServers, name) ? safeServerNamesByServer.get(name) : void 0;
return safeName ? [`${safeName}__`] : [];
});
}
}) ? await acquireSessionMcpRuntime({
...mcpConfig,
sessionId: params.attempt.sessionId,
sessionKey: params.attempt.sessionKey,
agentDir: params.agentDir,
requesterSenderId: params.attempt.senderId,
agentAccountId: params.attempt.agentAccountId,
messageChannel: params.attempt.messageChannel ?? params.attempt.messageProvider
}) : void 0;
const bundleMcpRuntime = bundleMcpAcquisition ? await materializeBundleMcpToolsForRun({
...bundleMcpAcquisition,
agentId: params.setup.sessionAgentId,
reservedToolNames: [...tools.map((tool) => tool.name), ...clientTools?.map((tool) => tool.function.name) ?? []]
}) : void 0;
let bundleLspRuntime;
try {
bundleLspRuntime = shouldCreateBundleLspRuntimeForAttempt({
toolsEnabled: auxiliaryToolsEnabled,
toolsAllow: params.attempt.toolsAllow
}) ? await createBundleLspToolRuntime({
workspaceDir: params.setup.effectiveWorkspace,
cfg: params.attempt.config,
abortSignal: params.attempt.abortSignal,
manifestRegistry: bundleManifestRegistry,
reservedToolNames: [
...tools.map((tool) => tool.name),
...clientTools?.map((tool) => tool.function.name) ?? [],
...bundleMcpRuntime?.tools.map((tool) => tool.name) ?? []
]
}) : void 0;
const applyRuntimeAllowlist = (bundleTools) => applyEmbeddedAttemptToolsAllow(bundleTools, effectiveToolsAllow, { toolMeta: (tool) => getPluginToolMeta(tool) });
const applyBundlePolicy = (bundledTools) => applyFinalEffectiveToolPolicy({
bundledTools,
config: params.attempt.config,
workspaceDir: params.setup.effectiveWorkspace,
metadataSnapshot: bundleMetadataSnapshot,
conversationCapabilityProfile: runtimeCapabilityProfile,
warn: (message) => log$6.warn(message)
});
const filteredBundledTools = applyBundlePolicy([...applyRuntimeAllowlist(bundleMcpRuntime?.tools ?? []), ...applyRuntimeAllowlist(bundleLspRuntime?.tools ?? [])]);
if (bundleMcpRuntime?.restrictAppTools) bundleMcpRuntime.restrictAppTools(applyBundlePolicy(applyRuntimeAllowlist(bundleMcpRuntime.appTools ?? bundleMcpRuntime.tools)));
const normalizedBundledTools = (filteredBundledTools.length > 0 ? await withRuntimeToolSchemaQuarantine((record) => normalizeTools(filteredBundledTools, record)) : filteredBundledTools).map((tool) => wrapToolWithBeforeToolCallHook(tool, params.preparedToolBase.toolHookContext));
const projectTools = (coreTools, recordQuarantine) => {
const projectedTools = filterLocalModelLeanTools({
tools: [...coreTools, ...normalizedBundledTools].map((tool) => wrapToolWithAbortSignal(tool, params.preparedToolBase.toolAbortSignal)),
config: params.attempt.config,
agentId: params.setup.sessionAgentId,
preserveToolNames: localModelLeanPreserveToolNames
});
const schemaProjection = filterRuntimeCompatibleTools(projectedTools);
captureFinalEffectiveCronCreatorToolAllowlist(cronCreatorToolAllowlist, cronCreatorToolAllowlistCaptureRef, schemaProjection.tools, (tool) => getPluginToolMeta(tool));
if (inheritedToolAllowlist?.length) replaceWithEffectiveToolAllowlist(inheritedToolAllowlist, schemaProjection.tools);
recordQuarantine({
diagnostics: schemaProjection.diagnostics,
tools: projectedTools,
runId: params.attempt.runId,
agentId: params.setup.sessionAgentId,
sessionKey: params.attempt.sessionKey,
sessionId: params.attempt.sessionId
});
return schemaProjection.tools;
};
const uncompactedEffectiveTools = await withRuntimeToolSchemaQuarantine((record) => projectTools(tools, record));
return {
bundleLspRuntime,
bundleMcpRuntime,
clientTools,
tools,
uncompactedEffectiveTools,
refreshTools: (recordQuarantine) => {
const nextTools = normalizeTools(toolsEnabled ? toolsRaw : [], recordQuarantine);
tools.splice(0, tools.length, ...nextTools);
const nextEffectiveTools = projectTools(tools, recordQuarantine);
uncompactedEffectiveTools.splice(0, uncompactedEffectiveTools.length, ...nextEffectiveTools);
}
};
} catch (error) {
if ((await Promise.allSettled([bundleMcpRuntime, bundleLspRuntime].map(async (runtime) => await runtime?.dispose()))).some((result) => result.status === "rejected")) recordAgentCleanupFailure();
throw error;
}
}
//#endregion
//#region src/agents/embedded-agent-runner/wait-for-idle-before-flush.ts
/**
* Waits for tool-result streams to become idle before flushing output.
*/
const DEFAULT_WAIT_FOR_IDLE_TIMEOUT_MS = 3e4;
async function flushPendingToolResultsAfterIdle(opts) {
const waitForAgentIdleBestEffort = async () => {
const { agent, timeoutMs = DEFAULT_WAIT_FOR_IDLE_TIMEOUT_MS, abortSignal } = opts;
const waitForIdle = agent?.waitForIdle;
if (abortSignal?.aborted || typeof waitForIdle !== "function") return;
const resolvedTimeoutMs = resolveTimerTimeoutMs(timeoutMs, DEFAULT_WAIT_FOR_IDLE_TIMEOUT_MS);
try {
await raceWithTimeout(waitForIdle.call(agent).then(() => void 0), resolvedTimeoutMs, () => void 0, {
ref: false,
signal: abortSignal
});
} catch {}
};
if (!(opts.timeoutMs !== void 0 && opts.timeoutMs <= 0)) await waitForAgentIdleBestEffort();
const { sessionManager } = opts;
if (sessionManager?.flushPendingToolResultsAsync && sessionManager.hasPendingToolResults?.() !== false) await sessionManager.flushPendingToolResultsAsync();
}
//#endregion
//#region src/trajectory/metadata.ts
function toSortedUniqueStrings(values) {
if (!values || values.length === 0) return;
return [...new Set(values)].filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean).toSorted((left, right) => left.localeCompare(right));
}
function buildPluginsFromActiveRegistry() {
const registry = getActivePluginRegistry();
if (!registry || registry.plugins.length === 0) return null;
return {
source: "active-registry",
importedRuntimePluginIds: listImportedRuntimePluginIds(),
entries: registry.plugins.map((plugin) => ({
id: plugin.id,
name: plugin.name,
version: plugin.version,
description: plugin.description,
origin: plugin.origin,
enabled: plugin.enabled,
explicitlyEnabled: plugin.explicitlyEnabled,
activated: plugin.activated,
imported: plugin.imported,
activationSource: plugin.activationSource,
activationReason: plugin.activationReason,
status: plugin.status,
error: plugin.error,
format: plugin.format,
bundleFormat: plugin.bundleFormat,
bundleCapabilities: plugin.bundleCapabilities,
kind: plugin.kind,
source: plugin.source,
rootDir: plugin.rootDir,
workspaceDir: plugin.workspaceDir,
toolNames: toSortedUniqueStrings(plugin.toolNames),
hookNames: toSortedUniqueStrings(plugin.hookNames),
channelIds: toSortedUniqueStrings(plugin.channelIds),
cliBackendIds: toSortedUniqueStrings(plugin.cliBackendIds),
providerIds: toSortedUniqueStrings(plugin.providerIds),
speechProviderIds: toSortedUniqueStrings(plugin.speechProviderIds),
realtimeTranscriptionProviderIds: toSortedUniqueStrings(plugin.realtimeTranscriptionProviderIds),
realtimeVoiceProviderIds: toSortedUniqueStrings(plugin.realtimeVoiceProviderIds),
mediaUnderstandingProviderIds: toSortedUniqueStrings(plugin.mediaUnderstandingProviderIds),
imageGenerationProviderIds: toSortedUniqueStrings(plugin.imageGenerationProviderIds),
videoGenerationProviderIds: toSortedUniqueStrings(plugin.videoGenerationProviderIds),
musicGenerationProviderIds: toSortedUniqueStrings(plugin.musicGenerationProviderIds),
webFetchProviderIds: toSortedUniqueStrings(plugin.webFetchProviderIds),
webSearchProviderIds: toSortedUniqueStrings(plugin.webSearchProviderIds),
agentHarnessIds: toSortedUniqueStrings(plugin.agentHarnessIds)
})).toSorted((left, right) => left.id.localeCompare(right.id))
};
}
function buildPluginsFromManifest(params) {
return {
source: "manifest-registry",
entries: (params.pluginMetadataSnapshot ?? loadPluginMetadataSnapshot({
config: params.config ?? {},
workspaceDir: params.workspaceDir,
env: params.env ?? process.env
})).plugins.map((plugin) => ({
id: plugin.id,
name: plugin.name,
version: plugin.version,
description: plugin.description,
origin: plugin.origin,
enabledByDefault: plugin.enabledByDefault,
format: plugin.format,
bundleFormat: plugin.bundleFormat,
bundleCapabilities: toSortedUniqueStrings(plugin.bundleCapabilities),
kind: plugin.kind,
source: plugin.source,
rootDir: plugin.rootDir,
workspaceDir: plugin.workspaceDir,
channels: toSortedUniqueStrings(plugin.channels),
providers: toSortedUniqueStrings(plugin.providers),
cliBackends: toSortedUniqueStrings(plugin.cliBackends),
hooks: toSortedUniqueStrings(plugin.hooks),
skills: toSortedUniqueStrings(plugin.skills)
})).toSorted((left, right) => left.id.localeCompare(right.id))
};
}
function buildSkillsCapture(skillsSnapshot, redaction) {
if (!skillsSnapshot) return;
const filteredResolvedSkills = skillsSnapshot.resolvedSkills?.filter((skill) => typeof skill.name === "string" && skill.name.length > 0) ?? [];
const entries = filteredResolvedSkills.length > 0 ? filteredResolvedSkills.map((skill) => ({
id: skill.name,
name: skill.name,
description: skill.description,
filePath: redactPathForSupport(skill.filePath, redaction),
baseDir: redactPathForSupport(skill.baseDir, redaction),
source: skill.source,
sourceInfo: sanitizeSupportSnapshotValue(skill.sourceInfo, redaction),
disableModelInvocation: skill.disableModelInvocation,
available: true
})) : skillsSnapshot.skills.filter((skill) => typeof skill.name === "string" && skill.name.length > 0).map((skill) => ({
id: skill.name,
name: skill.name,
primaryEnv: skill.primaryEnv,
requiredEnv: skill.requiredEnv,
available: true
}));
return {
snapshotVersion: skillsSnapshot.version,
skillFilter: toSortedUniqueStrings(skillsSnapshot.skillFilter),
entries: entries.toSorted((left, right) => (left.name ?? "").localeCompare(right.name ?? ""))
};
}
function buildTrajectoryRunMetadata(params) {
const env = params.env ?? process.env;
const redaction = {
env,
stateDir: resolveStateDir(env)
};
const os = resolveOsSummary();
const plugins = buildPluginsFromActiveRegistry() ?? buildPluginsFromManifest({
config: params.config,
pluginMetadataSnapshot: params.pluginMetadataSnapshot,
workspaceDir: params.workspaceDir,
env
});
return {
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
harness: {
type: "openclaw",
name: "OpenClaw",
version: VERSION,
gitSha: resolveCommitHash({
cwd: params.workspaceDir,
env,
moduleUrl: import.meta.url
}) ?? void 0,
os,
runtime: { node: process.version },
invocation: sanitizeSupportSnapshotValue([...process.argv], redaction, "programArguments"),
entrypoint: process.argv[1] ? redactPathForSupport(process.argv[1], redaction) : void 0,
workspaceDir: redactPathForSupport(params.workspaceDir, redaction),
sessionFile: params.sessionFile ? redactPathForSupport(params.sessionFile, redaction) : void 0
},
model: {
provider: params.provider,
name: params.modelId,
api: params.modelApi,
fastMode: params.fastMode ?? false,
thinkLevel: params.thinkLevel,
reasoningLevel: params.reasoningLevel ?? "off"
},
config: {
redacted: params.config ? redactConfigObject(params.config) : void 0,
runtime: {
timeoutMs: params.timeoutMs,
trigger: params.trigger,
disableTools: params.disableTools ?? false,
toolResultFormat: params.toolResultFormat,
toolsAllow: toSortedUniqueStrings(params.toolsAllow)
}
},
plugins,
skills: buildSkillsCapture(params.skillsSnapshot, redaction),
prompting: {
skillsPrompt: params.skillsSnapshot?.prompt,
userPromptPrefixText: params.userPromptPrefixText,
systemPromptReport: params.systemPromptReport
},
redaction: {
config: {
mode: "redactConfigObject",
secretsMasked: true
},
payloads: {
mode: "sanitizeDiagnosticPayload",
credentialsRemoved: true,
imageDataRedacted: true
},
harness: {
mode: "diagnostic-support-redaction",
programArgumentsRedacted: true,
localPathsRedacted: true
}
},
metadata: {
sessionKey: params.sessionKey,
agentId: params.agentId,
messageProvider: params.messageProvider,
messageChannel: params.messageChannel
}
};
}
function buildTrajectoryArtifacts(params) {
const { status, ...artifacts } = params;
return {
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
finalStatus: status,
...artifacts
};
}
//#endregion
//#region src/agents/embedded-agent-subscribe.handlers.tools.start.ts
const TRACE_REQUIRED_PARAM_GROUPS = {
read: [{
keys: ["path", "file_path"],
label: "path"
}],
write: REQUIRED_PARAM_GROUPS.write,
edit: REQUIRED_PARAM_GROUPS.edit
};
function reserveQuestionPromptDelivery(toolName, toolCallId, sessionKey, runId, agentId, args) {
try {
const { questions, timeoutSeconds } = toolName === "secrets" ? normalizeSecretsRequestParams(args) : normalizeAskUserParams(args);
return reserveAskUserPromptDelivery({
toolCallId,
sessionKey,
runId,
agentId,
questions,
timeoutSeconds
});
} catch {
return;
}
}
function traceToolExecutionStart(params) {
if (!params.ctx.log.trace || params.ctx.log.isEnabled?.("trace") !== true) return;
const args = params.args;
const argsType = Array.isArray(args) ? "array" : typeof args;
const argsKeys = args && typeof args === "object" && !Array.isArray(args) ? Object.keys(args).toSorted() : void 0;
const groups = TRACE_REQUIRED_PARAM_GROUPS[params.toolName];
const record = args && typeof args === "object" ? args : void 0;
const requiredParamsMissing = groups?.length ? missingRequiredParamLabels(record, groups) : [];
params.ctx.log.trace("embedded run tool start", {
event: "embedded_tool_execution_start",
tags: [
"tool_start",
"embedded",
"trace"
],
runId: params.ctx.params.runId,
toolName: params.toolName,
toolCallId: params.toolCallId,
argsType,
...argsKeys?.length ? { argsKeys } : {},
...params.ctx.params.sessionKey ? { sessionKey: params.ctx.params.sessionKey } : {},
...params.ctx.params.sessionId ? { sessionId: params.ctx.params.sessionId } : {},
...params.ctx.params.agentId ? { agentId: params.ctx.params.agentId } : {},
...requiredParamsMissing.length ? { requiredParamsMissing } : {}
});
}
const TOOL_START_WARNING_PREVIEW_MAX_CHARS = 200;
function buildToolStartWarningArgsPreview(rawArgsPreview) {
if (rawArgsPreview == null) return;
const wasTruncated = rawArgsPreview.length > TOOL_START_WARNING_PREVIEW_MAX_CHARS;
const bounded = truncateUtf16Safe(rawArgsPreview, TOOL_START_WARNING_PREVIEW_MAX_CHARS);
const preview = sanitizeForConsole(bounded, TOOL_START_WARNING_PREVIEW_MAX_CHARS);
return wasTruncated && preview ? `${preview}…` : preview;
}
/** Track tool execution start data for after_tool_call hook. */
const toolStartData = /* @__PURE__ */ new Map();
function buildToolStartKey(runId, toolCallId) {
return `${runId}:${toolCallId}`;
}
function countActiveToolExecutions(runId) {
const prefix = `${runId}:`;
let count = 0;
for (const key of toolStartData.keys()) if (key.startsWith(prefix)) count += 1;
return count;
}
/** Cleans up tool start data for a run that has been unsubscribed or aborted. */
function cleanupRunToolStartData(runId) {
const prefix = `${runId}:`;
for (const key of toolStartData.keys()) if (key.startsWith(prefix)) toolStartData.delete(key);
}
function buildToolCallSummary(toolName, args, meta, instanceReplaySafe, ownerKey, structuredReplaySafe) {
const mutation = buildToolMutationState(toolName, args, ownerKey ? { ownerKey } : void 0);
return {
meta,
commandBearing: isCommandBearingToolCall(toolName, args),
instanceReplaySafe,
mutatingAction: mutation.mutatingAction,
...ownerKey ? { ownerKey } : {},
replaySafe: instanceReplaySafe && !mutation.mutatingAction || structuredReplaySafe && mutation.replaySafe
};
}
function isExecToolName(toolName) {
return toolName === "exec" || toolName === "bash";
}
function buildCommandItemId(toolCallId) {
return `command:${toolCallId}`;
}
function buildCommandItemTitle(toolName, meta) {
return meta ? `command ${meta}` : `${toolName} command`;
}
function emitTrackedItemEvent(ctx, itemData, emitLiveUpdate = true) {
if (itemData.phase === "start") {
ctx.state.itemActiveIds.add(itemData.itemId);
ctx.state.itemStartedCount += 1;
} else if (itemData.phase === "end") {
ctx.state.itemActiveIds.delete(itemData.itemId);
ctx.state.itemCompletedCount += 1;
}
if (itemData.phase !== "update" || emitLiveUpdate) emitAgentActivityEvent({
runId: ctx.params.runId,
...ctx.params.sessionKey ? { sessionKey: ctx.params.sessionKey } : {},
stream: "item",
data: itemData
});
emitAgentEventCallbackBestEffort(ctx, {
stream: "item",
data: itemData
});
}
function emitAgentEventCallbackBestEffort(ctx, event) {
runBestEffortCallback({
label: "tool agent event",
log: ctx.log,
callback: () => ctx.params.onAgentEvent?.(event)
});
}
function emitToolActivityEvent(ctx, event) {
emitAgentActivityEvent({
runId: ctx.params.runId,
...ctx.params.sessionKey ? { sessionKey: ctx.params.sessionKey } : {},
...event
});
emitAgentEventCallbackBestEffort(ctx, {
stream: event.stream,
data: event.data
});
}
function finalizeToolActivity(ctx) {
const prefix = `${ctx.params.runId}:`;
const active = [...toolStartData].flatMap(([key, start]) => key.startsWith(prefix) ? [{
runId: ctx.params.runId,
callId: key.slice(prefix.length),
parentToolCallId: start.parentToolCallId,
activity: void 0
}] : []);
if (active.length !== ctx.state.itemActiveIds.size || ctx.state.itemStartedCount !== ctx.state.itemCompletedCount + active.length || ctx.state.toolMetas.length !== ctx.state.itemCompletedCount) return;
const calls = [...ctx.state.toolMetas.map((meta) => ({
runId: ctx.params.runId,
callId: meta.toolCallId,
parentToolCallId: meta.parentToolCallId,
activity: meta.activity
})), ...active];
for (const call of resolveCompletedActivityWrappers(calls)) if (call.activity && !call.activity.hideFromChannelProgress) emitToolActivityEvent(ctx, {
stream: "item",
data: {
...call.activity,
hideFromChannelProgress: true
}
});
}
function extendExecMeta(toolName, args, meta) {
if (!isExecToolName(toolName) || !args || typeof args !== "object") return meta;
const record = args;
const flags = ["pty", "elevated"].filter((flag) => record[flag] === true);
if (flags.length === 0) return meta;
const suffix = flags.join(" · ");
return meta ? `${meta} · ${suffix}` : suffix;
}
function handleToolExecutionStart(ctx, evt) {
const startToolName = normalizeToolPolicyName(evt.toolName);
ctx.state.liveEditDiffStateById.delete(evt.toolCallId);
const questionPromptReservation = (startToolName === "ask_user" || startToolName === "secrets" && evt.args !== null && typeof evt.args === "object" && "action" in evt.args && evt.args.action === "request") && ctx.params.onToolResult && (startToolName === "ask_user" || isDeliverableMessageChannel(ctx.params.messageChannel ?? "")) ? reserveQuestionPromptDelivery(startToolName === "ask_user" ? "ask_user" : "secrets", evt.toolCallId, ctx.params.sessionKey, ctx.params.runId, ctx.params.agentId, evt.args) : void 0;
const cancelQuestionPromptReservation = () => {
if (questionPromptReservation) cancelAskUserPromptDelivery(evt.toolCallId, ctx.params.sessionKey, ctx.params.runId, ctx.params.agentId);
};
const flushBeforeStart = (flush, next) => {
let result;
try {
result = flush();
} catch (error) {
cancelQuestionPromptReservation();
throw error;
}
if (isPromiseLike(result)) return result.then(next, (error) => {
cancelQuestionPromptReservation();
throw error;
});
return next();
};
const continueToolExecutionStart = () => {
const rawToolName = evt.toolName;
const toolName = normalizeToolPolicyName(rawToolName);
const toolCallId = evt.toolCallId;
const args = evt.args;
const runId = ctx.params.runId;
ctx.state.toolExecutionSinceLastBlockReply = true;
runBestEffortCallback({
label: "tool execution phase",
log: ctx.log,
callback: () => ctx.params.onExecutionPhase?.({
phase: "tool_execution_started",
tool: toolName,
toolCallId,
source: "embedded-agent"
})
});
const startedAt = Date.now();
toolStartData.set(buildToolStartKey(runId, toolCallId), {
startTime: startedAt,
args,
hideFromChannelProgress: evt.hideFromChannelProgress,
parentToolCallId: evt.parentToolCallId,
...ctx.params.hasRepliedRef ? { hasRepliedRef: { value: ctx.params.hasRepliedRef.value } } : {}
});
traceToolExecutionStart({
ctx,
toolName,
toolCallId,
args
});
if (toolName === "read") {
const record = args && typeof args === "object" ? args : {};
if (!(typeof record.path === "string" ? record.path : typeof record.file_path === "string" ? record.file_path : "").trim()) {
const argsType = typeof args;
const argsPreview = buildToolStartWarningArgsPreview(readStringValue(args));
const safeRunId = sanitizeForConsole(runId) ?? "-";
const safeSessionKey = sanitizeForConsole(ctx.params.sessionKey);
const safeSessionId = sanitizeForConsole(ctx.params.sessionId);
const safeAgentId = sanitizeForConsole(ctx.params.agentId);
const consoleMessageParts = [
"read tool called without path:",
`runId=${safeRunId}`,
`toolCallId=${sanitizeForConsole(toolCallId) ?? "tool-call"}`,
`argsType=${argsType}`
];
if (safeSessionKey) consoleMessageParts.push(`sessionKey=${safeSessionKey}`);
if (safeSessionId) consoleMessageParts.push(`sessionId=${safeSessionId}`);
if (safeAgentId) consoleMessageParts.push(`agentId=${safeAgentId}`);
if (argsPreview) consoleMessageParts.push(`argsPreview=${argsPreview}`);
const consoleMessage = consoleMessageParts.join(" ");
const message = `read tool called without path: toolCallId=${toolCallId} argsType=${argsType}${argsPreview ? ` argsPreview=${argsPreview}` : ""}`;
ctx.log.warn(message, {
event: "embedded_read_tool_start_warning",
tags: [
"tool_start",
"read",
"embedded",
"validation"
],
runId: ctx.params.runId,
toolCallId,
argsType,
...safeSessionKey ? { sessionKey: ctx.params.sessionKey } : {},
...safeSessionId ? { sessionId: ctx.params.sessionId } : {},
...safeAgentId ? { agentId: ctx.params.agentId } : {},
...argsPreview ? { argsPreview } : {},
consoleMessage
});
}
}
const meta = extendExecMeta(toolName, args, inferToolMetaFromArgsCore(toolName, args, { detailMode: ctx.params.toolProgressDetail ?? "explain" }));
const callSummary = buildToolCallSummary(toolName, args, meta, evt.replaySafe === true || ctx.params.replaySafeToolNames?.has(rawToolName) === true || ctx.params.replaySafeToolNames?.has(toolName) === true, ctx.params.sideEffectToolOwners?.get(toolName), false);
ctx.state.toolMetaById.set(toolCallId, callSummary);
ctx.log.debug(`embedded run tool start: runId=${ctx.params.runId} tool=${toolName} toolCallId=${toolCallId}`);
const shouldEmitToolEvents = ctx.shouldEmitToolResult();
const itemData = {
...projectAgentToolActivity({
toolCallId,
name: toolName,
phase: "start",
args,
meta,
hideFromChannelProgress: evt.hideFromChannelProgress
}),
startedAt
};
const hideFromChannelProgress = evt.hideFromChannelProgress === true;
emitTrackedItemEvent(ctx, itemData);
const createStartEvent = () => ({
stream: "tool",
data: {
phase: "start",
name: toolName,
toolCallId,
...evt.parentToolCallId ? { parentToolCallId: evt.parentToolCallId } : {},
args: sanitizeToolArgs(args),
...hideFromChannelProgress ? { hideFromChannelProgress: true } : {}
}
});
emitAgentEvent({
runId: ctx.params.runId,
...createStartEvent()
});
emitAgentEventCallbackBestEffort(ctx, createStartEvent());
if (ctx.params.onToolResult && shouldEmitToolEvents && !isAgentPlanProgressToolName(toolName) && !ctx.state.toolSummaryById.has(toolCallId)) {
ctx.state.toolSummaryById.add(toolCallId);
ctx.emitToolSummary(toolName, meta, callSummary.commandBearing);
}
const publishPrompt = ctx.params.onToolResult;
if (questionPromptReservation && publishPrompt) {
const questionId = questionPromptReservation.questionId;
waitForAskUserPromptReady(questionId).then(async (questions) => {
if (!questions) return;
await sendQuestionToolPrompt({
toolName: toolName === "secrets" ? "secrets" : "ask_user",
questionId,
questions,
config: ctx.params.config,
send: publishPrompt
});
}).then(() => settleAskUserPromptDelivery(questionId), (error) => {
settleAskUserPromptDelivery(questionId, error);
ctx.log.warn(`failed to deliver ${toolName} prompt: ${String(error)}`);
});
}
};
if (evt.lifecycleProvenance === "nested") return continueToolExecutionStart();
return flushBeforeStart(() => ctx.flushBlockReplyBuffer(), () => flushBeforeStart(() => ctx.params.onBlockReplyFlush?.({
reason: "tool_start",
assistantMessageIndex: ctx.state.assistantMessageIndex
}), continueToolExecutionStart));
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-nested-tool-activity.ts
function createAttemptNestedToolActivityState() {
return {
scopeId: randomUUID(),
successfulToolNames: /* @__PURE__ */ new Set()
};
}
/** Rehydrate accepted hook evidence only at delivery; SQLite owns the payload between calls. */
async function readAttemptNestedToolActivity(manager, state) {
const accepted = state.accepted;
if (!accepted) return [];
const { firstEntryId, lastEntryId, sessionId, target } = accepted;
const assertCurrent = () => {
if (manager.getSessionId() !== sessionId || !sameSessionTranscriptTargetBinding(target, manager.getSessionTarget())) throw new Error("Nested tool activity belongs to a replaced session");
};
assertCurrent();
const hydration = target && prepareSessionTranscriptHydration(target);
const entries = hydration ? (await hydration.readMaintenance({
operation: "nested-activity",
scopeId: state.scopeId,
firstEntryId,
lastEntryId
})).events ?? [] : manager.getEntries();
hydration?.assertCurrent();
assertCurrent();
const first = entries.findIndex((entry) => asOptionalRecord(entry)?.id === firstEntryId);
const last = entries.findIndex((entry) => asOptionalRecord(entry)?.id === lastEntryId);
if (first < 0 || last < first) throw new Error("Accepted nested tool activity is no longer available");
return entries.slice(first, last + 1).flatMap((entry) => {
const message = asOptionalRecord(asOptionalRecord(entry)?.message);
if (message?.customType !== "openclaw.nested-tool.v1" || asOptionalRecord(message.details)?.scopeId !== state.scopeId) return [];
const activity = readNestedToolActivity(message);
if (!activity) throw new Error("Accepted nested tool activity is no longer valid");
return [activity];
});
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-terminal-anchor.ts
/**
* Cache-TTL bookkeeping advances the leaf without adding a message anchor.
* Other entry kinds retain their terminal semantics.
*/
function resolveTerminalMessageEntryId(sessionManager) {
let entryId = sessionManager.getLeafId();
while (entryId) {
const entry = sessionManager.getEntry(entryId);
if (!entry || entry.type !== "custom" || entry.customType !== "openclaw.cache-ttl") return entry ? entryId : null;
entryId = entry.parentId;
}
return null;
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-terminal-evidence.ts
/** Reads this attempt's response without reviving an older transcript turn. */
function resolveCurrentAttemptAssistant(attempt) {
return attempt.currentAttemptAssistant ?? attempt.currentAttemptCompletedAssistant;
}
/** Uses current-attempt evidence when available and otherwise preserves fail-closed legacy state. */
function isCurrentAttemptReplaySafe(attempt) {
const replayMetadata = attempt.currentAttemptReplayMetadata ?? attempt.replayMetadata;
return replayMetadata.replaySafe && !replayMetadata.hadPotentialSideEffects;
}
/**
* Marks whether retrying the attempt can safely replay the prompt. Concrete
* tool-instance policy, async work, committed delivery, spawned sessions, and
* cron writes all contribute side-effect evidence.
*/
function buildAttemptReplayMetadata(params) {
const hadPotentialSideEffects = params.toolMetas.some((entry) => entry.replaySafe !== true || entry.asyncStarted === true) || hasMessagingToolDeliveryEvidence(params) || hasAcceptedSessionSpawn(params.acceptedSessionSpawns) || (params.successfulCronAdds ?? 0) > 0;
return {
hadPotentialSideEffects,
replaySafe: !hadPotentialSideEffects
};
}
function hasAttemptTerminalState(attempt) {
return Boolean(attempt.lastToolError || attempt.clientToolCalls || attempt.yieldDetected || attempt.didSendDeterministicApprovalPrompt || attempt.heartbeatToolResponse || attempt.toolMediaUrls?.some((url) => url.trim().length > 0) || attempt.toolAudioAsVoice || attempt.toolTrustedLocalMedia || attempt.hasToolMediaBlockReply || attempt.didDeliverSourceReplyViaMessageTool || attempt.messagingToolSourceReplyPayloads?.length || hasMessagingToolDeliveryEvidence(attempt) || hasAcceptedSessionSpawn(attempt.acceptedSessionSpawns) || hasAsyncActivity(attempt.toolMetas) || (attempt.successfulCronAdds ?? 0) > 0);
}
function hasAsyncActivity(toolMetas) {
return (toolMetas ?? []).some((entry) => entry.asyncStarted === true);
}
/**
* A visible parent that delegates its entire response to completion children or
* to a still-running detached media run must remain alive for completion delivery.
* Existing output, explicit silence, and non-user turns keep their established
* terminal ownership instead.
*/
function shouldContinueInteractiveAcceptedSessionSpawns(params) {
const { attempt, run } = params;
const delegatedToMediaRun = resolveSourceReplyDelivery(attempt) === "missing" && attempt.toolMetas.some((entry) => {
const runId = entry.asyncStarted === true ? entry.asyncTaskRunId?.trim() : void 0;
const operation = runId ? findMediaGenerationOperation(runId) : void 0;
return operation !== void 0 && !isTerminalMediaGenerationStatus(operation.status);
});
if (!(hasCompletionMessageSessionSpawn(attempt.acceptedSessionSpawns) || delegatedToMediaRun) || attempt.terminal.kind !== "ok" || attempt.yieldDetected === true || run.replyOperation?.turnKind !== "visible" || run.currentInboundEventKind === "room_event" || run.silentExpected === true || run.inputProvenance?.kind !== void 0 && run.inputProvenance.kind !== "external_user") return false;
if (attempt.assistantTexts.some((text) => text.trim().length > 0 && !isSilentReplyText(text, "NO_REPLY"))) return false;
return !hasAttemptTerminalState({
...attempt,
acceptedSessionSpawns: [],
...delegatedToMediaRun ? {
toolMetas: [],
didSendViaMessagingTool: false,
didDeliverSourceReplyViaMessageTool: false,
messagingToolSentTexts: [],
messagingToolSentMediaUrls: [],
messagingToolSentTargets: [],
messagingToolSourceReplyPayloads: []
} : {}
});
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-trajectory-status.ts
/** Terminal error marker for runs that produced no user-visible delivery or durable progress. */
const NON_DELIVERABLE_TERMINAL_TURN_REASON = "non_deliverable_terminal_turn";
/**
* Chooses assistant text that can safely count as terminal output. Provider error
* and abort stop reasons cannot fall back to the raw last visible text because
* that text may describe an interrupted generation rather than a completed reply.
*/
function resolveTerminalAssistantTexts(params) {
if (hasAnyNonEmptyString(params.assistantTexts)) return params.assistantTexts;
if (params.lastAssistantStopReason === "error" || params.lastAssistantStopReason === "aborted") return params.assistantTexts;
const fallbackText = params.lastAssistantVisibleText?.trim();
return fallbackText ? [fallbackText] : params.assistantTexts;
}
/**
* Classifies the final attempt trajectory from visible output, durable side
* effects, and interruption state. Empty terminal turns are errors unless a
* caller proves a silent success, message delivery, spawned session, async task,
* or other durable progress.
*/
function resolveAttemptTrajectoryTerminal(params) {
if (params.interrupted || params.failed) return { status: params.interrupted ? "interrupted" : "error" };
return params.silentExpected === true || params.emptyAssistantReplyIsSilent === true || params.didSendDeterministicApprovalPrompt || hasCommittedMessagingToolDeliveryEvidence(params) || hasAcceptedSessionSpawn(params.acceptedSessionSpawns) || params.heartbeatToolResponse !== void 0 || (params.clientToolCalls?.length ?? 0) > 0 || params.yieldDetected === true || params.lastToolError !== void 0 || hasAsyncActivity(params.toolMetas) || params.lastAssistantStopReason !== "toolUse" && (params.hasTerminalOutput || hasAnyNonEmptyString(params.assistantTexts) || params.synthesizedPayloadCount > 0 || params.lastAssistantStopReason !== "length" && params.successfulCronAdds > 0) ? { status: "success" } : {
status: "error",
terminalError: NON_DELIVERABLE_TERMINAL_TURN_REASON
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/terminal-outcome.ts
/** Projects private attempt metadata into the canonical agent terminal outcome. */
function resolveEmbeddedRunAttemptTerminalOutcome(params) {
return buildAgentRunTerminalOutcomeFromAttempt({
terminal: params.attempt.terminal,
promptTimeoutOutcome: params.attempt.promptTimeoutOutcome,
assistant: params.assistant && isProviderRefusalAssistantError(params.assistant) ? {
...params.assistant,
errorMessage: formatUserFacingAssistantErrorText(params.assistant)
} : params.assistant,
abortSignal: params.abortSignal
});
}
/** Owner-recorded timeout failure cannot be inferred away from partial or completed-looking output. */
function isEmbeddedRunTimeoutFinal(attempt) {
return projectAgentRunAttemptTerminal(attempt.terminal).timedOut && (attempt.promptTimeoutOutcome?.replayInvalid === true || attempt.codexAppServerFailure?.kind === "turn_completion_idle_timeout");
}
function isEmbeddedRunTerminalTimeout(outcome) {
return classifyAgentRunTerminalOutcome(outcome) === "timeout";
}
function isEmbeddedRunTerminalAbort(outcome) {
return classifyAgentRunTerminalOutcome(outcome) === "cancellation";
}
function isEmbeddedRunTerminalInterrupted(outcome) {
return isEmbeddedRunTerminalTimeout(outcome) || isEmbeddedRunTerminalAbort(outcome);
}
/** Captures signal ownership with the outcome before async recovery can change the signal. */
function resolveEmbeddedRunAttemptTerminalState(params) {
const outcome = resolveEmbeddedRunAttemptTerminalOutcome(params);
return {
outcome,
signalOwnedInterruption: isEmbeddedRunTerminalInterrupted(outcome) && params.abortSignal?.aborted === true
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-finalize.ts
function finalizeEmbeddedAttempt(params) {
const { result, trajectoryRecorder } = params;
if (!trajectoryRecorder) return result;
const terminalState = projectAgentRunAttemptTerminal(result.terminal);
const assistant = terminalState.cleanupYieldAborted ? result.lastAssistant : result.currentAttemptCompletedAssistant ?? result.currentAttemptAssistant;
const completionOutcome = resolveEmbeddedRunAttemptTerminalOutcome({
attempt: result,
assistant
});
const stopReason = terminalState.cleanupYieldAborted && completionOutcome.status === "ok" ? "end_turn" : completionOutcome.stopReason;
const toolEvidence = () => ({
toolMetas: result.toolMetas,
didSendViaMessagingTool: result.didSendViaMessagingTool,
messagingToolSentTexts: result.messagingToolSentTexts,
messagingToolSentMediaUrls: result.messagingToolSentMediaUrls,
messagingToolSentTargets: result.messagingToolSentTargets,
successfulCronAdds: result.successfulCronAdds ?? 0,
lastToolError: result.lastToolError
});
const terminal = resolveAttemptTrajectoryTerminal({
...toolEvidence(),
failed: completionOutcome.status === "error",
interrupted: isEmbeddedRunTerminalInterrupted(completionOutcome),
assistantTexts: resolveTerminalAssistantTexts({
assistantTexts: result.assistantTexts,
lastAssistantStopReason: stopReason,
lastAssistantVisibleText: resolveFinalAssistantVisibleText(assistant)
}),
didSendDeterministicApprovalPrompt: result.didSendDeterministicApprovalPrompt === true,
synthesizedPayloadCount: params.synthesizedPayloadCount,
acceptedSessionSpawns: result.acceptedSessionSpawns,
heartbeatToolResponse: result.heartbeatToolResponse,
clientToolCalls: result.clientToolCalls,
yieldDetected: result.yieldDetected,
silentExpected: params.silentExpected,
emptyAssistantReplyIsSilent: params.emptyAssistantReplyIsSilent,
lastAssistantStopReason: stopReason,
hasTerminalOutput: params.hasTerminalOutput
});
const promptError = terminalState.promptError ? formatErrorMessage(terminalState.promptError) : void 0;
const terminalFields = {
aborted: terminalState.aborted,
externalAbort: terminalState.externalAbort,
timedOut: terminalState.timedOut,
idleTimedOut: terminalState.idleTimedOut,
timedOutDuringCompaction: terminalState.timedOutDuringCompaction,
timedOutDuringToolExecution: terminalState.timedOutDuringToolExecution,
timedOutByRunBudget: terminalState.timedOutByRunBudget,
promptError
};
const modelFields = {
...terminalFields,
promptErrorSource: terminalState.promptErrorSource,
terminalError: terminal.terminalError,
usage: result.attemptUsage,
promptCache: result.promptCache,
compactionCount: result.compactionCount,
assistantTexts: result.assistantTexts,
stopReason,
finalPromptText: result.finalPromptText
};
trajectoryRecorder.recordEvent("model.completed", {
...modelFields,
messagesSnapshot: result.messagesSnapshot
});
trajectoryRecorder.recordEvent("trace.artifacts", buildTrajectoryArtifacts({
status: terminal.status,
...modelFields,
compactionCount: result.compactionCount ?? 0,
itemLifecycle: result.itemLifecycle,
...toolEvidence()
}));
const sessionEndData = {
status: terminal.status,
...terminalFields,
terminalError: terminal.terminalError,
stopReason
};
if (params.deferredLifecycleOwner) params.deferredLifecycleOwner.recordSessionEnd(sessionEndData);
else trajectoryRecorder.recordEvent("session.ended", sessionEndData);
return result;
}
async function completeEmbeddedAttemptAfterTurn(input, settled, prompt) {
const { attempt, activeContextEngine, agentDir, resolveActiveContextEnginePluginId, state: executionState } = input;
const { withOwnedTranscriptWrite } = input.sessionLock;
const { effectiveWorkspace, sessionAgentId } = input.setup;
const { sessionRuntime, bootstrap, bundleTools, toolBase } = input.prepared;
const { sessionManager, cacheTrace, anthropicPayloadLogger } = sessionRuntime;
const { diagnosticTrace } = input.diagnostics;
const { shouldRecordCompletedBootstrapTurn } = bootstrap;
const skillWorkshopAvailable = bundleTools.uncompactedEffectiveTools.some((tool) => tool.name === "skill_workshop");
const { hookRunner } = sessionRuntime.agentSession;
const { promptStartedAt, yieldAborted, transcriptLeafId } = prompt;
const { sessionIdUsed, promptError, messagesSnapshot } = settled;
const { nestedToolActivityState } = toolBase;
const { prePromptMessageCount } = sessionRuntime.state;
const contextEngineAfterTurnCheckpoint = sessionRuntime.contextGuards.getAfterTurnCheckpoint();
const { lastCallUsage, promptCache, compactionOccurredThisAttempt } = settled;
const { beforeAgentFinalizeRevisionReason } = prompt;
if (activeContextEngine && !beforeAgentFinalizeRevisionReason) {
const lifecycleState = projectAgentRunAttemptTerminal(executionState.terminal);
const terminalEntryId = attempt.onContextEngineTurnCandidate ? resolveTerminalMessageEntryId(sessionManager) : void 0;
await finalizeHarnessContextEngineTurn({
...attempt,
abortSignal: input.runAbortController.signal,
contextEngine: activeContextEngine,
promptError: Boolean(promptError),
aborted: lifecycleState.aborted,
yieldAborted,
sessionIdUsed,
messagesSnapshot,
prePromptMessageCount: contextEngineAfterTurnCheckpoint ?? prePromptMessageCount,
tokenBudget: attempt.contextTokenBudget,
turnCandidate: attempt.onContextEngineTurnCandidate ? {
admission: attempt.userTurnTranscriptRecorder?.getAdmissionReceipt(),
terminalEntryId,
[completedTurnMessageAnchor]: captureCompletedTurnMessageAnchor(sessionManager, terminalEntryId),
record: attempt.onContextEngineTurnCandidate
} : void 0,
runtimeContext: attempt.onContextEngineTurnCandidate ? void 0 : buildAfterTurnRuntimeContextFromUsage({
attempt,
workspaceDir: effectiveWorkspace,
agentDir,
tokenBudget: attempt.contextTokenBudget,
lastCallUsage,
promptCache,
activeAgentId: sessionAgentId,
contextEnginePluginId: resolveActiveContextEnginePluginId()
}),
contextEngineHostSupport: OPENCLAW_EMBEDDED_CONTEXT_ENGINE_HOST,
providerId: attempt.provider,
runMaintenance: async (contextParams) => await runContextEngineMaintenance({
...contextParams,
contextEngine: activeContextEngine,
sessionManager,
withSessionManagerRewriteLock: withOwnedTranscriptWrite,
config: attempt.config,
agentId: sessionAgentId,
contextEngineAgentId: attempt.contextEngineAgentId
}),
sessionManager,
warn: (message) => log$6.warn(message),
isHeartbeat: isHeartbeatLifecycleRunKind(attempt.bootstrapContextRunKind)
});
}
const shouldPersistBootstrapCompletion = () => {
const lifecycleState = projectAgentRunAttemptTerminal(executionState.terminal);
const assistant = settled.currentAttemptCompletedAssistant ?? settled.currentAttemptAssistant;
return shouldRecordCompletedBootstrapTurn && !promptError && assistant?.stopReason !== "error" && !lifecycleState.aborted && !lifecycleState.timedOutDuringCompaction && !compactionOccurredThisAttempt;
};
if (!beforeAgentFinalizeRevisionReason && shouldPersistBootstrapCompletion()) await withOwnedTranscriptWrite(() => withSessionManagerWrite(sessionManager, async () => {
if (!shouldPersistBootstrapCompletion()) return;
try {
await sessionManager.appendCustomEntryAsync(FULL_BOOTSTRAP_COMPLETED_CUSTOM_TYPE, {
timestamp: Date.now(),
runId: attempt.runId,
sessionId: attempt.sessionId
});
} catch (entryErr) {
log$6.warn(`failed to persist bootstrap completion entry: ${String(entryErr)}`);
}
}));
const lifecycleAfterTurn = projectAgentRunAttemptTerminal(executionState.terminal);
cacheTrace?.recordStage("session:after", {
messages: messagesSnapshot,
note: lifecycleAfterTurn.timedOutDuringCompaction ? "compaction timeout" : promptError ? "prompt error" : void 0
});
anthropicPayloadLogger?.recordUsage(messagesSnapshot, promptError);
if (attempt.operation !== "settled-tool-finalization" && attempt.sessionPersistence !== "detached" && !beforeAgentFinalizeRevisionReason) {
const lifecycleForAgentEnd = projectAgentRunAttemptTerminal(executionState.terminal);
const agentEndError = promptError && !lifecycleForAgentEnd.aborted ? formatErrorMessage(promptError) : void 0;
const sourceTarget = sessionManager.getSessionTarget();
let terminalEntry;
let entry = sessionManager.getLeafEntry();
while (entry && entry.id !== transcriptLeafId) {
if (!terminalEntry && entry.type === "message") terminalEntry = entry;
entry = entry.parentId ? sessionManager.getEntry(entry.parentId) : void 0;
}
const reachedPromptBoundary = transcriptLeafId === null || entry?.id === transcriptLeafId;
await runAgentEndSideEffectsAsync({
[completedTurnMessageAnchor]: sourceTarget && terminalEntry && reachedPromptBoundary ? captureCompletedTurnMessageAnchor(sessionManager, terminalEntry.id) : void 0,
skillExperienceReviewSource: sourceTarget && terminalEntry && reachedPromptBoundary ? {
...sourceTarget,
entryId: terminalEntry.id
} : void 0,
event: bindAgentHarnessHookMessages({
messages: messagesSnapshot,
success: !lifecycleForAgentEnd.aborted && !promptError,
error: agentEndError,
durationMs: Date.now() - promptStartedAt
}, async () => projectNestedToolActivityForHooks(messagesSnapshot, await readAttemptNestedToolActivity(sessionManager, nestedToolActivityState))),
ctx: buildEmbeddedAgentEndContext({
run: attempt,
agentId: sessionAgentId,
agentDir,
trace: freezeDiagnosticTraceContext(diagnosticTrace),
skillWorkshopAvailable,
compacted: compactionOccurredThisAttempt
}),
hookRunner
});
}
}
function createAttemptAbortError(signal) {
if (signal.reason instanceof Error) return signal.reason;
return createAbortError$1("request aborted", { cause: signal.reason });
}
function createTimeoutAbortReason() {
return Object.assign(/* @__PURE__ */ new Error("request timed out"), { name: "TimeoutError" });
}
function recordAttemptAbort(state, signal, runId, isTimeout) {
state.terminal = mergeAgentRunAttemptTerminal(state.terminal, {
kind: "aborted",
source: signal.reason === SESSIONS_YIELD_ABORT_REASON ? "yield_cleanup" : "runtime"
});
if (isTimeout) {
state.terminal = mergeAgentRunAttemptTerminal(state.terminal, {
kind: "timeout",
phase: "prompt",
source: "runtime"
});
if (!projectAgentRunAttemptTerminal(state.terminal).timedOutDuringCompaction && countActiveToolExecutions(runId) > 0) state.terminal = mergeAgentRunAttemptTerminal(state.terminal, {
kind: "timeout",
phase: "tool_execution",
source: "observation"
});
}
}
/** Owns the external AbortSignal listener and its handoff to the live session. */
function createEmbeddedAttemptExternalAbortController(input) {
let abortActiveSession;
let abortRun;
let isCompactionPendingOrRetrying;
let isCompactionInFlight;
let removeListener;
let abortHandled = false;
const onAbort = () => {
const signal = input.abortSignal;
if (!signal || abortHandled) return;
abortHandled = true;
input.state.terminal = mergeAgentRunAttemptTerminal(input.state.terminal, {
kind: "aborted",
source: "external"
});
const reason = signal.reason;
const isTimeout = reason ? isSignalTimeoutReason(reason) : false;
if (shouldFlagCompactionTimeout({
isTimeout,
isCompactionPendingOrRetrying: isCompactionPendingOrRetrying?.() ?? false,
isCompactionInFlight: isCompactionInFlight?.() ?? false
})) input.state.terminal = mergeAgentRunAttemptTerminal(input.state.terminal, {
kind: "timeout",
phase: "compaction",
source: "observation"
});
if (abortRun) {
abortRun(isTimeout, reason);
return;
}
recordAttemptAbort(input.state, input.runAbortController.signal, input.runId, isTimeout);
input.state.terminal = mergeAgentRunAttemptTerminal(input.state.terminal, {
kind: "failed",
source: "prompt",
error: createAttemptAbortError(signal)
});
if (!input.runAbortController.signal.aborted) input.runAbortController.abort(isTimeout ? reason ?? createTimeoutAbortReason() : reason);
abortActiveSession?.(input.runAbortController.signal.reason);
};
const readFiredAbortError = () => {
const signal = input.abortSignal;
if (!signal?.aborted) return;
onAbort();
return createAttemptAbortError(signal);
};
return {
arm: () => {
const signal = input.abortSignal;
if (!signal || removeListener) return;
if (signal.aborted) {
onAbort();
return;
}
signal.addEventListener("abort", onAbort, { once: true });
removeListener = () => {
signal.removeEventListener("abort", onAbort);
removeListener = void 0;
};
},
dispose: () => {
removeListener?.();
abortActiveSession = void 0;
abortRun = void 0;
isCompactionPendingOrRetrying = void 0;
isCompactionInFlight = void 0;
},
setActiveSessionAbort: (abort) => {
abortActiveSession = abort;
},
setCompactionState: (state) => {
isCompactionPendingOrRetrying = state.isPendingOrRetrying;
isCompactionInFlight = state.isInFlight;
},
setRunAbort: (abort) => {
abortRun = abort;
},
throwIfFired: () => {
const abortError = readFiredAbortError();
if (abortError) throw abortError;
},
throwIfFiredAfterPrepCleanup: async () => {
const abortError = readFiredAbortError();
if (!abortError) return;
await input.cleanupAfterEarlyAbort();
throw abortError;
}
};
}
/** Builds the live-session abort handler shared by timeouts and explicit cancellation. */
function createEmbeddedAttemptRunAbort(input) {
let abortAccepted = false;
return (isTimeout = false, reason) => {
if (abortAccepted) return;
abortAccepted = true;
recordAttemptAbort(input.state, input.runAbortController.signal, input.attempt.runId, isTimeout);
if (isTimeout) {
const timeoutReason = reason instanceof Error ? reason : createTimeoutAbortReason();
input.attempt.onAttemptTimeout?.(timeoutReason);
input.runAbortController.abort(timeoutReason);
} else input.runAbortController.abort(reason);
if (input.activeSession.isCompacting) try {
input.activeSession.abortCompaction();
} catch (error) {
if (!input.isProbeSession) input.log.warn(`embedded run abortCompaction failed: runId=${input.attempt.runId} sessionId=${input.attempt.sessionId} err=${String(error)}`);
}
input.abortActiveSession(input.runAbortController.signal.reason);
const queueHandle = input.getQueueHandle();
if (isTimeout && queueHandle) markActiveEmbeddedRunAbandoned({
sessionId: input.attempt.sessionId,
handle: queueHandle,
sessionKey: input.attempt.sessionKey,
sessionFile: input.attempt.sessionFile,
reason: "timeout"
});
};
}
//#endregion
//#region src/auto-reply/handoff-summarizer.ts
/**
* Builds the recovery briefing injected as the first user-side turn after a
* model failover. The user role is used (not assistant) so the new model
* treats the content as input rather than its own prior output.
*/
function buildHierarchyReinforcementMessage(snapshot) {
const subagentReport = snapshot.activeSubagents.map((s) => `- Subagent ${s.sessionId} (${s.role ?? "leaf"}): ${s.lastStatus ?? "running"}`).join("\n");
const hasSubagents = snapshot.activeSubagents.length > 0;
return {
role: "user",
content: [
"[SYSTEM HANDOFF] The previous model is no longer active and a fallback model is now active.",
...hasSubagents ? [
"You are the new LEADER (Orchestrator). Do not perform tasks already delegated to subordinates.",
"",
"ACTIVE SUBORDINATE UNITS:",
subagentReport
] : ["Review the current state below and continue the conversation from where the previous model left off."],
"",
"CURRENT STATE SUMMARY:",
snapshot.summary,
...hasSubagents ? [
"",
"INSTRUCTIONS:",
"1. Review the state and subordinate reports.",
"2. Provide strategic guidance and commands to subordinates.",
"3. Do not repeat work already performed by subordinates."
] : []
].join("\n"),
timestamp: Date.now()
};
}
//#endregion
//#region src/agents/embedded-agent-runner/history.ts
const THREAD_SUFFIX_REGEX = /^(.*)(?::(?:thread|topic):\d+)$/i;
const SESSION_HISTORY_PRELUDE = Symbol.for("openclaw.sessionHistoryPrelude");
function stripThreadSuffix(value) {
return value.match(THREAD_SUFFIX_REGEX)?.[1] ?? value;
}
/**
* Leading non-conversation messages (e.g. compactionSummary, branchSummary)
* placed at index 0 by buildSessionContext are always preserved, since they
* carry summarized pre-compaction context that history limiting must not drop.
*/
function limitHistoryTurns(messages, limit) {
if (!limit || limit <= 0 || messages.length === 0) return messages;
const conversationStart = messages.findIndex((message) => !message?.[SESSION_HISTORY_PRELUDE] && (message?.role === "user" || message?.role === "assistant"));
if (conversationStart < 0) return messages;
let userCount = 0;
for (let i = conversationStart; i < messages.length; i++) if (messages[i]?.role === "user") userCount++;
const targetUserTurns = Math.floor(limit);
const maxUserTurns = Math.ceil(targetUserTurns * 1.5);
if (userCount <= maxUserTurns) return messages;
const evictionBatchSize = maxUserTurns - targetUserTurns + 1;
let turnsRemaining = targetUserTurns + (userCount - targetUserTurns) % evictionBatchSize;
const firstKeptIndex = messages.findLastIndex((message, index) => index >= conversationStart && message?.role === "user" && --turnsRemaining === 0);
return [...messages.slice(0, conversationStart), ...messages.slice(firstKeptIndex < 0 ? messages.length : firstKeptIndex)];
}
function getHistoryLimitFromSessionKey(sessionKey, config, route) {
if (!sessionKey || !config) return;
const parts = sessionKey.split(":");
const providerParts = parts.length >= 3 && parts[0] === "agent" ? parts.slice(2) : parts;
const provider = normalizeProviderId(providerParts[0] ?? "");
if (!provider) return;
const candidates = [0, 1].flatMap((accountSegments) => {
const kind = normalizeChatType(providerParts[1 + accountSegments]);
const accountId = accountSegments ? providerParts[1] : void 0;
if (!kind || route?.chatType && kind !== route.chatType || accountSegments && kind !== "direct") return [];
return [{
kind,
accountId,
userId: providerParts.slice(2 + accountSegments).join(":")
}];
});
const rawPeerId = route?.peerId?.trim();
const logicalPeerId = rawPeerId ? normalizeOptionalLowercaseString(resolveLinkedDirectPeerId({
identityLinks: config.session?.identityLinks,
channel: provider,
peerId: rawPeerId
}) ?? rawPeerId) : void 0;
const matching = logicalPeerId ? candidates.filter((candidate) => candidate.kind !== "direct" || candidate.userId === logicalPeerId || stripThreadSuffix(candidate.userId) === logicalPeerId) : candidates;
const resolved = matching.length === 1 ? matching[0] : void 0;
const kind = resolved?.kind ?? (candidates.every((candidate) => candidate.kind === candidates[0]?.kind) ? candidates[0]?.kind : void 0);
const userId = resolved && (logicalPeerId ?? stripThreadSuffix(resolved.userId));
const routedAccountId = route?.accountId ?? (candidates.length === 1 ? candidates[0]?.accountId : resolved?.accountId);
const providerConfig = asOptionalRecord(Object.entries(config.channels ?? {}).find(([channel]) => normalizeProviderId(channel) === provider)?.[1]);
if (!providerConfig) return;
const trimmedAccountId = routedAccountId?.trim();
const accountConfig = trimmedAccountId ? resolveChannelAccountEntry(providerConfig.accounts, normalizeAccountId(trimmedAccountId), provider, normalizeAccountId) : void 0;
if (kind === "direct") {
const perDmLimit = userId ? (accountConfig?.dms ?? providerConfig.dms)?.[userId]?.historyLimit : void 0;
return perDmLimit !== void 0 ? perDmLimit : accountConfig?.dmHistoryLimit ?? providerConfig.dmHistoryLimit;
}
return kind === "channel" || kind === "group" ? accountConfig?.historyLimit ?? providerConfig.historyLimit : void 0;
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-history-prepare.ts
async function prepareEmbeddedAttemptHistory(input, assertActive) {
const { attempt, activeContextEngine, isRawModelRun } = input;
const { agentSession: { activeSession, settingsManager, setActiveSessionSystemPrompt }, boundary: { orphanRepair }, cacheTrace, isOpenAIResponsesApi, sessionManager, transcriptPolicy, transport: { compactionReplayEnabled } } = input.prepared.sessionRuntime;
const { capabilityToolNames, replayAllowedToolNames } = input.prepared.toolCatalog.toolSearchRunPlan;
const { effectiveWorkspace, sessionAgentId } = input.setup;
const sandboxed = input.setup.sandbox?.enabled === true;
const isSettledTurnFinalization = attempt.operation === "settled-tool-finalization";
let systemPromptText = input.prepared.sessionRuntime.state.systemPromptText;
const setSystemPrompt = (nextSystemPrompt) => {
systemPromptText = nextSystemPrompt;
setActiveSessionSystemPrompt(nextSystemPrompt);
};
if (isRawModelRun) {
activeSession.agent.reset();
setSystemPrompt("");
cacheTrace?.recordStage("session:raw-model-run", {
messages: activeSession.messages,
system: systemPromptText
});
} else {
const replayContext = () => ({
modelApi: attempt.model.api,
modelId: attempt.modelId,
provider: attempt.provider,
config: attempt.config,
workspaceDir: effectiveWorkspace,
env: process.env,
model: attempt.model,
sessionId: attempt.sessionId,
policy: transcriptPolicy
});
const prior = await sanitizeSessionHistory({
...replayContext(),
messages: activeSession.messages,
allowedToolNames: replayAllowedToolNames,
sessionManager
});
cacheTrace?.recordStage("session:sanitized", { messages: prior });
const validated = await validateReplayTurns({
...replayContext(),
messages: prior
});
if (attempt.sessionKey && attempt.sessionPersistence !== "detached" && !isSettledTurnFinalization) {
const storePath = resolveSessionStorePathCore(attempt.config?.session?.store, { agentId: sessionAgentId });
const sessionEntry = await loadAttemptSessionEntryAfterQuotaMaintenance({
agentId: sessionAgentId,
storePath,
sessionKey: attempt.sessionKey
}, assertActive);
assertActive();
const suspension = sessionEntry?.quotaSuspension;
if (sessionEntry && suspension?.state === "resuming") {
const entries = await readSessionEntrySummariesInWorker({
agentId: sessionAgentId,
storePath
});
assertActive();
const subagents = entries.flatMap(({ entry }) => entry.spawnedBy === sessionEntry.sessionId ? [{
sessionId: entry.sessionId,
role: entry.subagentRole,
lastStatus: entry.status
}] : []);
validated.push(buildHierarchyReinforcementMessage({
summary: suspension.summary ?? "No recovery briefing was captured.",
activeSubagents: subagents
}));
await patchSessionEntryCore({
agentId: sessionAgentId,
storePath,
sessionKey: attempt.sessionKey
}, (entry) => {
if (entry.sessionId !== sessionEntry.sessionId || entry.quotaSuspension?.state !== "resuming") return null;
return { quotaSuspension: {
...entry.quotaSuspension,
state: "active"
} };
}, {
skipMaintenance: true,
takeCacheOwnership: true,
...sessionEntryCommitGuardOptions(assertActive)
});
assertActive();
}
}
let limited = validated;
if (!isSettledTurnFinalization) {
const heartbeatSummary = attempt.config && sessionAgentId ? resolveHeartbeatSummaryForAgent(attempt.config, sessionAgentId) : void 0;
const heartbeatFiltered = filterHeartbeatTranscriptArtifacts(validated, heartbeatSummary?.ackMaxChars, heartbeatSummary?.prompt);
const truncated = preserveCompactionReplayWindow(heartbeatFiltered, limitHistoryTurns(heartbeatFiltered, getHistoryLimitFromSessionKey(attempt.sessionKey, attempt.config, {
accountId: attempt.agentAccountId,
peerId: attempt.conversationRoutePeerId,
chatType: attempt.chatType
})), attempt.model, {
sessionId: attempt.sessionId,
authProfileId: attempt.runtimePlan?.auth.forwardedAuthProfileId,
enabled: compactionReplayEnabled
});
limited = transcriptPolicy.repairToolUseResultPairing ? sanitizeToolUseResultPairingForModel(truncated, isOpenAIResponsesApi) : truncated;
}
cacheTrace?.recordStage("session:limited", { messages: limited });
if (limited.length > 0 || prior.length > 0) activeSession.agent.state.messages = limited;
}
const prompt = orphanRepair?.contextEnginePrompt ?? attempt.prompt ?? "";
const { messages, systemPrompt, ...prepared } = await prepareHarnessContextEnginePrompt({
...attempt,
contextEngine: activeContextEngine,
agentId: sessionAgentId,
appendOnlyRuntimeContext: transcriptPolicy.appendOnlyRuntimeContext,
messages: activeSession.messages,
availableTools: new Set(capabilityToolNames),
citationsMode: attempt.config?.memory?.citations,
sandboxed,
promptBudget: {
contextTokens: attempt.contextTokenBudget ?? attempt.model.contextWindow ?? attempt.model.maxTokens,
reserveTokens: settingsManager.getCompactionReserveTokens(),
systemPrompt: systemPromptText,
prompt
},
contextEngineHostSupport: OPENCLAW_EMBEDDED_CONTEXT_ENGINE_HOST,
providerId: attempt.provider,
transcriptReadFence: attempt.userTurnTranscriptRecorder?.getAdmissionReceipt(),
...attempt.prompt !== void 0 ? { prompt } : {},
repairToolUseResultPairing: transcriptPolicy.repairToolUseResultPairing,
isOpenAIResponsesApi,
warn: (message) => log$6.warn(message)
});
activeSession.agent.state.messages = messages;
if (systemPrompt !== systemPromptText) setSystemPrompt(systemPrompt);
return prepared;
}
//#endregion
//#region src/infra/gemini-auth.ts
/** Accepts an API key or serialized OAuth credentials containing a token. */
function parseGeminiAuth(apiKey) {
const token = apiKey.startsWith("{") ? safeParseJsonRecord(apiKey)?.token : void 0;
return { headers: {
...typeof token === "string" && token ? { Authorization: `Bearer ${token}` } : { "x-goog-api-key": apiKey },
"Content-Type": "application/json"
} };
}
//#endregion
//#region src/agents/embedded-agent-runner/google-prompt-cache.ts
const GOOGLE_PROMPT_CACHE_CUSTOM_TYPE = "openclaw.google-prompt-cache";
const GOOGLE_PROMPT_CACHE_RESPONSE_MAX_BYTES = 1048576;
const GOOGLE_PROMPT_CACHE_RETRY_BACKOFF_MS = 6e5;
const GOOGLE_PROMPT_CACHE_SHORT_REFRESH_WINDOW_MS = 3e4;
const GOOGLE_PROMPT_CACHE_LONG_REFRESH_WINDOW_MS = 3e5;
function stringifyGooglePromptCacheKeyPart(value) {
if (typeof value === "string") return value;
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
return "";
}
function readLatestGooglePromptCacheEntry(sessionManager, matchKey) {
try {
const entries = sessionManager.getEntries();
for (let i = entries.length - 1; i >= 0; i -= 1) {
const entry = entries[i];
if (entry?.type !== "custom" || entry?.customType !== GOOGLE_PROMPT_CACHE_CUSTOM_TYPE) continue;
const data = entry.data;
if (!data || typeof data !== "object") continue;
const cacheData = data;
if (stableStringify({
provider: stringifyGooglePromptCacheKeyPart(cacheData.provider),
modelId: stringifyGooglePromptCacheKeyPart(cacheData.modelId),
modelApi: typeof cacheData.modelApi === "string" || cacheData.modelApi == null ? cacheData.modelApi : null,
baseUrl: stringifyGooglePromptCacheKeyPart(cacheData.baseUrl),
systemPromptDigest: stringifyGooglePromptCacheKeyPart(cacheData.systemPromptDigest),
cacheConfigDigest: typeof cacheData.cacheConfigDigest === "string" ? cacheData.cacheConfigDigest : void 0
}) === matchKey) return data;
}
} catch {
return null;
}
return null;
}
async function appendGooglePromptCacheEntry(sessionManager, entry) {
try {
await sessionManager.appendCustomEntryAsync(GOOGLE_PROMPT_CACHE_CUSTOM_TYPE, entry);
} catch (err) {
if (err instanceof SessionTranscriptWriterClaimReboundError) throw err;
}
}
function readFutureExpireTime(value, now) {
const timestamp = parseDateStringTimestampMs(value);
return typeof value === "string" && isFutureDateTimestampMs(timestamp, { nowMs: now }) ? {
value,
timestamp
} : null;
}
function readGooglePromptCacheName(value) {
if (typeof value !== "string" || !value.startsWith("cachedContents/")) return null;
const id = value.slice(15);
if (!id || id === "." || id === "..") return null;
try {
return encodeURIComponent(id) === id ? value : null;
} catch {
return null;
}
}
function mapManagedGoogleToolChoice(choice) {
if (!choice) return;
if (typeof choice === "object" && choice.type === "function") {
const functionName = choice.function?.name;
return typeof functionName === "string" ? {
mode: "ANY",
allowedFunctionNames: [functionName]
} : { mode: "ANY" };
}
switch (choice) {
case "none": return { mode: "NONE" };
case "any":
case "required": return { mode: "ANY" };
default: return { mode: "AUTO" };
}
}
function resolveGooglePromptCacheAuthHeaders(params) {
const sentinel = looksLikeSecretSentinel(params.apiKey);
const apiKey = sentinel ? resolveSecretSentinel(params.apiKey) : params.apiKey;
if (apiKey === void 0) throw new Error(`Secret sentinel ${params.apiKey} is not registered in this process; refusing Google prompt-cache auth`);
const headers = parseGeminiAuth(apiKey).headers;
if (!sentinel && !isSecretValueRegisteredForRedaction(params.apiKey)) return headers;
return Object.fromEntries(Object.entries(headers).map(([name, value]) => {
return [name, name.toLowerCase() === "authorization" || name.toLowerCase() === "x-goog-api-key" ? mintSecretSentinel(value, { label: `model-auth:${params.provider}` }) : value];
}));
}
async function requestGooglePromptCache(params) {
const refreshing = "cachedContent" in params;
const url = refreshing ? `${params.baseUrl}/${params.cachedContent}?updateMask=ttl` : `${params.baseUrl}/cachedContents`;
const authHeaders = resolveGooglePromptCacheAuthHeaders({
apiKey: params.apiKey,
provider: params.model.provider
});
const headers = resolveProviderRequestHeaders({
provider: params.model.provider,
api: params.model.api,
baseUrl: params.baseUrl,
capability: "llm",
transport: "http",
defaultHeaders: authHeaders,
callerHeaders: params.headers,
precedence: "caller-wins"
}) ?? mergeTransportHeaders(authHeaders, params.headers);
const ttl = params.cacheRetention === "long" ? "3600s" : "300s";
const body = JSON.stringify(refreshing ? { ttl } : {
model: params.modelId.startsWith("models/") ? params.modelId : `models/${params.modelId}`,
ttl,
systemInstruction: { parts: [{ text: params.systemPrompt }] },
...params.tools ? { tools: params.tools } : {},
...params.toolConfig ? { toolConfig: params.toolConfig } : {}
});
let response;
try {
response = await params.fetchImpl(url, {
method: refreshing ? "PATCH" : "POST",
headers,
body,
signal: params.signal
});
if (!response.ok) throw new Error(`Google prompt cache request failed (${response.status})`);
const payload = await readProviderJsonObjectResponse(response, "Google prompt cache", {
maxBytes: GOOGLE_PROMPT_CACHE_RESPONSE_MAX_BYTES,
onOverflow: ({ size, maxBytes }) => /* @__PURE__ */ new Error(`Google prompt cache response too large: ${size} bytes (limit: ${maxBytes} bytes)`),
requestHeaders: headers
});
const expiry = readFutureExpireTime(payload.expireTime, params.now);
const cachedContent = refreshing ? params.cachedContent : readGooglePromptCacheName(payload.name);
if (!cachedContent || !expiry) throw new Error("Google prompt cache response is invalid");
return {
cachedContent,
expireTime: expiry.value
};
} catch {
params.signal?.throwIfAborted();
return null;
} finally {
await cancelUnreadResponseBody(response);
}
}
async function ensureGooglePromptCache(params) {
const baseUrl = normalizeGoogleApiBaseUrl(params.model.baseUrl);
const now = asDateTimestampMs(Date.now());
if (now === void 0) return null;
const cacheIdentity = {
provider: params.provider,
modelId: params.model.id,
modelApi: params.model.api,
baseUrl,
systemPromptDigest: sha256Hex(params.systemPrompt),
cacheConfigDigest: params.cacheConfigDigest
};
const latestEntry = readLatestGooglePromptCacheEntry(params.sessionManager, stableStringify(cacheIdentity));
const entryMetadata = {
timestamp: now,
...cacheIdentity,
cacheRetention: params.cacheRetention
};
if (latestEntry?.status === "failed" && isFutureDateTimestampMs(latestEntry.retryAfter, { nowMs: now })) return null;
const fetchImpl = buildGuardedModelFetch(params.model);
const requestOptions = {
...params,
baseUrl,
fetchImpl,
headers: params.model.headers,
now
};
const refreshWindowMs = params.cacheRetention === "long" ? GOOGLE_PROMPT_CACHE_LONG_REFRESH_WINDOW_MS : GOOGLE_PROMPT_CACHE_SHORT_REFRESH_WINDOW_MS;
const cachedContent = latestEntry?.status === "ready" ? readGooglePromptCacheName(latestEntry.cachedContent) : null;
const expiry = latestEntry?.status === "ready" && cachedContent ? readFutureExpireTime(latestEntry.expireTime, now) : null;
if (expiry && expiry.timestamp - now > refreshWindowMs) return cachedContent;
const reusableCachedContent = expiry ? cachedContent : null;
const result = await requestGooglePromptCache({
...requestOptions,
...reusableCachedContent ? { cachedContent: reusableCachedContent } : { modelId: params.model.id }
});
if (result) await appendGooglePromptCacheEntry(params.sessionManager, {
status: "ready",
...entryMetadata,
...result
});
else if (!reusableCachedContent) await appendGooglePromptCacheEntry(params.sessionManager, {
status: "failed",
...entryMetadata,
retryAfter: resolveExpiresAtMsFromDurationMs(GOOGLE_PROMPT_CACHE_RETRY_BACKOFF_MS, { nowMs: now }) ?? 0
});
return result?.cachedContent ?? reusableCachedContent;
}
async function prepareGooglePromptCacheStreamFn(params) {
if (!params.streamFn || resolveExplicitCachedContent(params.extraParams) || !isGooglePromptCacheEligible({
modelApi: params.model.api,
modelId: params.modelId
})) return;
const resolvedRetention = resolveCacheRetention(params.extraParams, params.provider, params.model.api, params.modelId);
if (resolvedRetention !== "short" && resolvedRetention !== "long") return;
const apiKey = params.apiKey?.trim();
if (!apiKey) return;
const inner = params.streamFn;
return async (model, context, options) => {
const split = splitSystemPromptCacheBoundary(context.systemPrompt ?? "");
const systemPrompt = sanitizeTransportPayloadText(stripSystemPromptCacheBoundary(split?.stablePrefix ?? ""));
if (!split || !systemPrompt.trim()) return inner(model, context, options);
const tools = context.tools?.length ? [{ functionDeclarations: sortPromptCacheToolsByName(context.tools).map((tool) => ({
name: tool.name,
description: tool.description,
parametersJsonSchema: tool.parameters
})) }] : void 0;
const toolChoice = tools ? mapManagedGoogleToolChoice(options?.toolChoice) : void 0;
const toolConfig = toolChoice ? { functionCallingConfig: toolChoice } : void 0;
const cacheConfigDigest = tools || toolConfig ? stableStringify({
tools,
toolConfig
}) : void 0;
const cachedContent = await ensureGooglePromptCache({
apiKey,
cacheConfigDigest,
tools,
toolConfig,
cacheRetention: resolvedRetention,
model: params.model,
provider: params.provider,
sessionManager: params.sessionManager,
signal: params.signal,
systemPrompt
});
if (!cachedContent) {
log$6.debug(`google prompt cache unavailable for ${params.provider}/${params.modelId}; continuing without cachedContent`);
return inner(model, context, options);
}
return streamWithPayloadPatch(inner, model, {
...context,
systemPrompt: void 0,
tools: void 0,
messages: prependRuntimeContextForModel(context.messages, sanitizeTransportPayloadText(stripSystemPromptCacheBoundary(split.dynamicSuffix)))
}, options, (payload) => {
payload.cachedContent = cachedContent;
});
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/pre-persisted-user-turn.ts
function isInterruptedTurnEntry(entry, runId) {
if (entry.type === "custom_message") return entry.customType === "openclaw:turn-aborted";
if (entry.type !== "message") return false;
const message = entry.message;
if (message.role === "custom") return readNestedToolActivity(message)?.details.runId === runId;
if (Reflect.get(message, "__openclaw")?.runId !== runId) return false;
if (message.role !== "assistant") return message.role === "toolResult";
return message.stopReason === "toolUse" || message.stopReason === "aborted" && (message.errorCode !== void 0 ? message.errorCode === "OPENCLAW_RESTART_ABORT" : message.errorMessage === "agent run aborted for restart") && message.content.every((part) => part.type === "text" && part.text === "");
}
function selectCurrentUserTurnFromCohort(params) {
const { sessionManager, prepared, assertView } = params;
assertView();
const facts = prepared.kind === "bounded" ? prepared.transcript : void 0;
if (!facts || facts.replayValidated !== "current" && !(facts.replayValidated === "initial" && params.allowInitial && !params.initialWriter?.committedFence)) throw new SessionTranscriptWriterClaimReboundError();
const entryId = sessionManager.resolveCurrentTurnEntryId((entry) => isInterruptedTurnEntry(entry, params.runId));
const entry = entryId ? sessionManager.getEntry(entryId) : void 0;
if (entryId && !entry) return;
const matchesUser = entry?.type === "message" && entry.message.role === "user" && isDeepStrictEqual(entry.message, params.message);
const anchor = facts.anchors.find((candidate) => candidate.entryId === entryId);
if (matchesUser && !anchor) return;
return { current: matchesUser && anchor ? {
entryId: anchor.entryId,
anchor,
version: prepared.snapshot.version,
assertCurrent: assertView
} : void 0 };
}
/** The receipt selects a hint; initial replay still consumes freshly hydrated persisted bytes. */
function prepareInitialPersistedUserTurnCohort(params) {
const { message, recorder } = params;
const hint = recorder?.getAdmissionReceipt()?.entryId;
if (!message?.idempotencyKey || !recorder || !hint) return;
const scope = withOwnedSessionTranscriptWriterFence(params.target);
const owner = getOwnedSessionTranscriptReader(scope);
if (!owner) return;
const assertOwned = captureOwnedTranscriptWriteAssertion(scope);
const initialWriter = getOwnedSessionTranscriptInitialWriter({ sessionTarget: scope });
const allowInitial = Boolean(initialWriter) && !initialWriter?.committedFence;
const selection = {
sessionKey: scope.sessionKey,
entryIds: [hint],
includeMessagePresence: true,
replayValidation: {
expectedLifecycleRevision: scope.expectedLifecycleRevision,
expectedWriterRunId: scope.expectedWriterRunId,
allowInitial,
admission: resolveSessionTranscriptReadFence(scope)
}
};
let initial;
let messagePresence;
return {
selection,
consume(sessionManager, prepared, assertView) {
assertOwned();
owner.assertCurrent();
messagePresence = prepared.kind === "bounded" ? prepared.transcript?.messagePresence : void 0;
const selected = selectCurrentUserTurnFromCohort({
sessionManager,
message,
runId: params.runId,
prepared,
assertView,
allowInitial,
initialWriter
});
if (selected) {
if (selected.current) recorder.markRuntimePersisted(message, selected.current.anchor, { appended: false });
initial = { anchor: selected.current?.anchor };
}
},
readInitial: () => initial,
readMessagePresence: () => messagePresence
};
}
/** Re-adopt the current turn without reopening arbitrary historical keyed users. */
async function preparePersistedCurrentUserTurn(params) {
const { sessionManager, message, recorder, runId } = params;
const sessionTarget = sessionManager.getSessionTarget();
if (!sessionTarget || !message?.idempotencyKey || !recorder) return;
const scope = withOwnedSessionTranscriptWriterFence(sessionTarget);
const assertOwned = captureOwnedTranscriptWriteAssertion(scope);
const initialWriter = getOwnedSessionTranscriptInitialWriter({ sessionTarget: scope });
const reader = prepareSessionManagerHydration(scope, {
signal: params.signal,
manager: sessionManager
});
let originalSource;
const assertCurrent = () => {
assertOwned();
reader.assertCurrent();
};
const binding = reader.incognitoBinding;
const incognito = binding && {
actor: binding.actor,
authority: { assertCurrent },
target: {
sessionKey: scope.sessionKey,
sessionId: scope.sessionId
}
};
const withSource = (signal, operation) => {
assertCurrent();
const native = () => operation(scope, assertCurrent);
return binding ? binding.actor.sessions.withSharedState(native) : withSessionTranscriptReadSource(scope, native, ({ scope: captured, expectedIdentity, assertCurrent: assertSource }) => {
originalSource ??= {
storePath: captured.storePath,
identity: expectedIdentity
};
if (captured.storePath !== originalSource.storePath || expectedIdentity?.key !== originalSource.identity?.key || expectedIdentity?.birthtime !== originalSource.identity?.birthtime) throw new Error("Persisted user turn changed its database owner before replay admission");
return operation({
...scope,
agentId: captured.agentId,
storePath: captured.storePath
}, assertSource);
}, signal);
};
const validate = async (target, assertSource, prepared, signal, consume) => {
signal?.throwIfAborted();
assertCurrent();
const allowInitial = !prepared && Boolean(initialWriter) && !initialWriter?.committedFence;
let accepted = false;
await readSessionTranscriptAnchorsAsync(target, {
entryIds: prepared ? [prepared.entryId] : [],
replayValidation: {
expectedLifecycleRevision: scope.expectedLifecycleRevision,
expectedWriterRunId: scope.expectedWriterRunId,
allowInitial,
admission: resolveSessionTranscriptReadFence(target)
},
...prepared ? { contextValidation: { version: prepared.version } } : {}
}, signal, (facts) => {
assertSource();
assertCurrent();
prepared?.assertCurrent();
if (facts.replayValidated !== "current" && !(facts.replayValidated === "initial" && allowInitial && !initialWriter?.committedFence)) throw new SessionTranscriptWriterClaimReboundError();
if (prepared && !facts.contextValidated) throw new SessionWorkStartChangedError("Persisted user turn changed before replay admission. Retry the turn.");
const anchor = facts.anchors[0];
consume(prepared && anchor ? {
...prepared,
anchor
} : void 0);
accepted = true;
}, incognito);
assertCurrent();
if (!accepted) throw new SessionWorkStartChangedError("Persisted user turn changed before replay admission. Retry the turn.");
};
const readCurrentTurn = (signal, consume) => withSource(signal, async (target, assertSource) => {
const matchesUser = (entry) => entry?.type === "message" && entry.message.role === "user" && isDeepStrictEqual(entry.message, message);
const select = () => sessionManager[sessionManagerPrepareCurrentTurnReplay]((entry) => isInterruptedTurnEntry(entry, runId), matchesUser, signal);
let witness;
const accept = (current) => {
witness = current;
consume(current);
};
if (getOwnedSessionTranscriptReader(target)) {
const allowInitial = Boolean(initialWriter) && !initialWriter?.committedFence;
const hint = sessionManager.resolveCurrentTurnEntryId((entry) => isInterruptedTurnEntry(entry, runId));
let consumed = false;
await sessionManager[sessionManagerReloadTranscriptCohort]({
sessionKey: scope.sessionKey,
entryIds: hint ? [hint] : [],
replayValidation: {
expectedLifecycleRevision: scope.expectedLifecycleRevision,
expectedWriterRunId: scope.expectedWriterRunId,
allowInitial,
admission: resolveSessionTranscriptReadFence(target)
}
}, (prepared, assertView) => {
assertSource();
assertCurrent();
const selected = selectCurrentUserTurnFromCohort({
sessionManager,
message,
runId,
prepared,
assertView,
allowInitial,
initialWriter
});
if (selected) {
consumed = true;
accept(selected.current);
}
}, signal);
if (consumed) return witness;
}
await sessionManager.reloadPersistedTranscriptAsync(signal);
assertSource();
assertCurrent();
await validate(target, assertSource, await select(), signal, accept);
return witness;
});
const initial = params.initial ? params.initial.anchor : (await readCurrentTurn(params.signal, (prepared) => {
if (prepared) recorder.markRuntimePersisted(message, prepared.anchor, { appended: false });
}))?.anchor;
if (!initial) return;
let pending = true;
return async (signal = params.signal) => {
if (!pending) return;
const replaySignal = signal && params.signal && signal !== params.signal ? AbortSignal.any([signal, params.signal]) : signal;
replaySignal?.throwIfAborted();
const accept = (prepared, onAdmitted) => {
if (!prepared || prepared.anchor.entryId !== initial.entryId || prepared.anchor.generation !== initial.generation) throw new SessionWorkStartChangedError("Persisted user turn changed before replay admission. Retry the turn.");
if (onAdmitted) {
if (!pending) throw new Error("Persisted user turn replay was already consumed");
pending = false;
onAdmitted();
}
};
assertCurrent();
const selected = getOwnedSessionTranscriptReader(scope);
if (selected) {
selected.assertCurrent();
return async (onAdmitted) => {
await readCurrentTurn(replaySignal, (prepared) => accept(prepared, onAdmitted));
};
}
const current = await readCurrentTurn(replaySignal, (prepared) => {
accept(prepared);
});
return async (onAdmitted) => {
await withSource(replaySignal, (target, assertSource) => validate(target, assertSource, current, replaySignal, (prepared) => {
accept(prepared, onAdmitted);
}));
};
};
}
function sessionMessagesContainIdempotencyKey(messages, idempotencyKey) {
return messages.some((message) => "idempotencyKey" in message && message.idempotencyKey === idempotencyKey);
}
function reconcilePrePersistedCurrentUserTurn(params) {
const idempotencyKey = params.currentUserTurnMessage?.idempotencyKey;
if (typeof idempotencyKey !== "string" || idempotencyKey.length === 0) return false;
const durableTurnMatches = params.durableUserTurnMessage?.idempotencyKey === idempotencyKey;
if (!params.userTurnAlreadyPersisted && !durableTurnMatches) return false;
const messages = params.activeSession.agent.state.messages;
const tail = messages.at(-1);
const activeTailMatches = tail?.role === "user" && "idempotencyKey" in tail && tail.idempotencyKey === idempotencyKey;
if (activeTailMatches) params.activeSession.agent.state.messages = messages.slice(0, -1);
return activeTailMatches || durableTurnMatches || params.currentUserTurnMessage?.excludeFromContext === true;
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-before-agent-run.ts
async function runEmbeddedAttemptBeforeAgentRun(input) {
const block = await runBeforeAgentRunGate(input.hookRunner, {
prompt: input.modelPrompt,
systemPrompt: input.systemPrompt,
messages: input.hookMessages,
channelId: input.hookContext.channelId,
accountId: input.attempt.agentAccountId,
senderId: input.attempt.senderId ?? void 0,
senderIsOwner: input.attempt.senderIsOwner
}, input.hookContext);
if (!block) return;
const redactedUserMessage = buildAgentRunBlockedUserMessage(input.attempt.runId, block);
if (!sessionMessagesContainIdempotencyKey(input.activeSession.messages, redactedUserMessage.idempotencyKey)) try {
await input.withOwnedTranscriptWrite(() => withSessionManagerWrite(input.sessionManager, async () => {
await input.sessionManager.appendMessageAsync(redactedUserMessage);
input.sessionManager.flushPendingPersistence();
}));
input.activeSession.agent.state.messages = sanitizeCompactionReplayMessages(input.sessionManager.buildSessionContext().messages);
} catch (err) {
log$6.warn(`before_agent_run block: failed to persist redacted user message: ${err instanceof Error ? err.message : String(err)}`);
}
return {
blockedBy: block.blockedBy,
promptError: new Error(block.message)
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-prompt-preflight.ts
function buildPreflightRecoveryBudgetSnapshot(snapshot) {
return {
estimatedPromptTokens: snapshot.estimatedPromptTokens,
promptBudgetBeforeReserve: snapshot.promptBudgetBeforeReserve,
overflowTokens: snapshot.overflowTokens
};
}
async function handleEmbeddedAttemptMidTurnPrecheck(input) {
const { attempt, request } = input;
const preflightRecovery = {
route: request.route,
source: "mid-turn",
...buildPreflightRecoveryBudgetSnapshot(request)
};
const logMidTurnPrecheck = (route, extra) => {
log$6.warn(`[context-overflow-midturn-precheck] sessionKey=${attempt.sessionKey ?? attempt.sessionId} provider=${attempt.provider}/${attempt.modelId} route=${route} estimatedPromptTokens=${request.estimatedPromptTokens} promptBudgetBeforeReserve=${request.promptBudgetBeforeReserve} overflowTokens=${request.overflowTokens} toolResultReducibleChars=${request.toolResultReducibleChars} effectiveReserveTokens=${request.effectiveReserveTokens} prePromptMessageCount=${input.prePromptMessageCount} ` + (extra ? `${extra} ` : "") + `sessionFile=${attempt.sessionFile}`);
};
if (request.route === "truncate_tool_results_only") {
const contextTokenBudget = attempt.contextTokenBudget ?? 2e5;
const toolResultMaxChars = resolveLiveToolResultMaxChars({ contextWindowTokens: contextTokenBudget });
const truncationResult = await truncateOversizedToolResultsInSessionManager({
sessionManager: input.sessionManager,
projectionState: input.toolResultPromptProjectionState,
contextWindowTokens: contextTokenBudget,
maxCharsOverride: toolResultMaxChars,
sessionFile: attempt.sessionFile,
sessionId: attempt.sessionId,
sessionKey: attempt.sessionKey,
agentId: input.sessionAgentId
});
if (truncationResult.truncated || truncationResult.reason === "no oversized or aggregate tool results") {
if (truncationResult.truncated) input.replaceSessionMessages(sanitizeCompactionReplayMessages(input.sessionManager.buildSessionContext().messages));
const truncatedCount = truncationResult.truncated ? truncationResult.truncatedCount : 0;
logMidTurnPrecheck(request.route, `handled=true truncatedCount=${truncatedCount}` + (truncationResult.truncated ? "" : ` truncateSkippedReason=${truncationResult.reason}`));
return { preflightRecovery: {
...preflightRecovery,
handled: true,
truncatedCount
} };
}
preflightRecovery.route = "compact_only";
logMidTurnPrecheck(preflightRecovery.route, `truncateFallbackReason=${truncationResult.reason ?? "unknown"}`);
} else logMidTurnPrecheck(request.route);
return {
preflightRecovery,
promptError: new Error(PREEMPTIVE_OVERFLOW_ERROR_TEXT)
};
}
async function prepareEmbeddedAttemptPromptPreflight(input) {
const { attempt } = input;
const boundaryOptions = {
appendOnlyRuntimeContext: input.appendOnlyRuntimeContext,
...input.timezone ? { timezone: input.timezone } : {},
...input.includeBoundaryTimestamp ? {} : { includeTimestamp: false }
};
const unwindowedMessages = input.contextEnginePromptAuthority === "preassembly_may_overflow" ? input.unwindowedContextEngineMessagesForPrecheck : void 0;
const unwindowedLlmBoundaryMessagesForPrecheck = unwindowedMessages ? normalizeMessagesForLlmBoundary(unwindowedMessages, boundaryOptions) : void 0;
if (input.state.skipPromptSubmission) return { ...input.state };
if ((input.pendingInputTokens ?? 0) >= input.contextTokenBudget) return {
...input.state,
preflightRecovery: { route: "compact_only" },
promptError: new Error(PREEMPTIVE_OVERFLOW_ERROR_TEXT),
promptErrorSource: "precheck",
skipPromptSubmission: true
};
let preemptiveCompaction;
try {
preemptiveCompaction = shouldPreemptivelyCompactBeforePrompt({
messages: input.hookMessagesForCurrentPrompt,
unwindowedMessages: unwindowedLlmBoundaryMessagesForPrecheck,
systemPrompt: input.systemPrompt,
prompt: input.promptForPrecheck,
contextTokenBudget: input.contextTokenBudget,
reserveTokens: input.reserveTokens,
toolResultMaxChars: input.toolResultMaxChars,
...typeof input.toolSchemaTokens === "number" ? { toolSchemaTokens: input.toolSchemaTokens } : {},
replay: {
model: attempt.model,
sessionId: attempt.sessionId,
authProfileId: attempt.runtimePlan?.auth.forwardedAuthProfileId,
enabled: input.compactionReplayEnabled
}
});
} catch (error) {
if (!(error instanceof CompactionReplayRefreshRequiredError)) throw error;
return {
...input.state,
promptError: error,
promptErrorSource: "precheck",
skipPromptSubmission: true
};
}
if (input.contextEngineAssemblySucceeded && input.activeContextEngine?.info.ownsCompaction && input.contextEnginePromptAuthority !== "preassembly_may_overflow" && !preemptiveCompaction.compactionReplay) {
log$6.info(`[context-overflow-precheck] skipped: context engine "${input.activeContextEngine.info.id}" owns compaction`);
return { ...input.state };
}
const precheckSummary = {
result: preemptiveCompaction,
provider: attempt.provider,
modelId: attempt.modelId,
messageCount: input.sessionMessageCount,
contextTokenBudget: input.contextTokenBudget,
reserveTokens: input.reserveTokens,
...attempt.sessionId ? { sessionId: attempt.sessionId } : {},
...unwindowedMessages ? { unwindowedMessageCount: unwindowedMessages.length } : {}
};
const contextBudgetStatus = buildPrePromptContextBudgetStatus(precheckSummary);
log$6.debug(formatPrePromptPrecheckLog({
...precheckSummary,
...attempt.sessionKey ? { sessionKey: attempt.sessionKey } : {},
...attempt.sessionFile ? { sessionFile: attempt.sessionFile } : {}
}));
const checkpointPressure = preemptiveCompaction.compactionReplay;
if (checkpointPressure && checkpointPressure.route !== "fits") return {
...input.state,
contextBudgetStatus,
preflightRecovery: {
route: checkpointPressure.route,
...buildPreflightRecoveryBudgetSnapshot(checkpointPressure)
},
promptError: new Error(PREEMPTIVE_OVERFLOW_ERROR_TEXT),
promptErrorSource: "precheck",
skipPromptSubmission: true
};
if (preemptiveCompaction.route !== "fits") log$6.info(`[context-pressure-diagnostic] admitted provider attempt for ${attempt.provider}/${attempt.modelId} route=${preemptiveCompaction.route} estimatedPromptTokens=${preemptiveCompaction.estimatedPromptTokens} promptBudgetBeforeReserve=${preemptiveCompaction.promptBudgetBeforeReserve}`);
return {
...input.state,
contextBudgetStatus
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/prompt-image-preparation.ts
/** Prepares ordered prompt images using the admitted media and filesystem policy. */
async function prepareEmbeddedAttemptPromptExecution(input) {
if (input.skipPromptSubmission) return {
images: [],
imageFactIndexes: [],
detectedRefs: [],
failedMediaCount: 0,
loadedCount: 0,
skippedCount: 0
};
const { attempt } = input;
const result = await detectAndLoadPromptImages({
prompt: input.prompt,
workspaceDir: input.effectiveWorkspace,
agentWorkspaceDir: attempt.workspaceDir,
model: attempt.model,
existingImages: attempt.images,
imageOrder: attempt.imageOrder,
media: attempt.media,
userTurnTranscriptRecorder: attempt.userTurnTranscriptRecorder,
maxBytes: MAX_IMAGE_BYTES,
maxDimensionPx: resolveImageSanitizationLimits(attempt.config).maxDimensionPx,
workspaceOnly: input.effectiveFsWorkspaceOnly,
localRoots: input.effectiveFsWorkspaceOnly ? void 0 : getAgentScopedMediaLocalRoots(attempt.config ?? {}, input.mediaOwnerAgentId),
sandbox: input.sandbox?.enabled && input.sandbox.fsBridge ? {
root: input.sandbox.workspaceDir,
bridge: input.sandbox.fsBridge
} : void 0
});
if (!input.pluginHarness) return result;
if (result.failedMediaCount) throw new Error(`failed to hydrate ${result.failedMediaCount} structured image attachment(s) for plugin harness input`);
const message = await attempt.userTurnTranscriptRecorder?.resolveMessage();
const media = ((message ? readPersistedMediaFacts(message) : void 0) ?? attempt.media)?.filter((fact) => !isImageMediaFact(fact));
return {
...result,
imageOrder: result.images.length ? result.images.map(() => "inline") : void 0,
media: media?.length ? media : void 0
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-prompt-phase.ts
async function runEmbeddedAttemptPromptPhase(input, promptState) {
const { attempt, activeContextEngine, isRawModelRun, prepared, preparedStreamRuntime, runAbortController } = input;
const { sessionRuntime, promptToolPolicy } = prepared;
const { agentSession: { activeSession, hookRunner, setActiveSessionSystemPrompt, settingsManager }, boundary: { boundaryTimezone, includeBoundaryTimestamp, orphanRepair, setCurrentUserTimestampOverride }, cacheTrace, contextGuards, preparedUserTurnMessage, sessionManager, sessionPromptState, state: sessionRuntimeState, toolResultPromptProjectionState, trajectoryRecorder, transcriptPolicy: { appendOnlyRuntimeContext, inHistorySystemUpdates }, transport: { effectiveAgentTransport, effectiveExtraParams, streamStrategy, compactionReplayEnabled } } = sessionRuntime;
const { effectiveFsWorkspaceOnly, effectiveWorkspace, sandbox, sessionAgentId } = input.setup;
const { history: { contextEngineAssemblySucceeded, contextEnginePromptAuthority, unwindowedContextEngineMessagesForPrecheck }, promptActiveSession, stream: { stopAcceptingSteerMessages } } = preparedStreamRuntime;
const { withOwnedTranscriptWrite } = input.sessionLock;
const withTranscriptWrite = (write) => withOwnedTranscriptWrite(() => withSessionManagerWrite(sessionManager, write));
const observeForegroundRequests = (onRequest) => {
const streamFn = activeSession.agent.streamFn;
activeSession.agent.streamFn = (model, context, options) => {
if (!activeSession.isCompacting) onRequest(model, context);
return streamFn(model, context, options);
};
};
const { diagnosticTrace, runTrace } = input.diagnostics;
const { systemPromptReport, runtimeInfo } = prepared.systemPrompt;
let systemPromptText = sessionRuntimeState.systemPromptText;
const toolSearchCompacted = prepared.toolCatalog.toolSearch.compacted;
let skipPromptSubmission = false;
let leasedSteering;
const setFailure = (error, source) => {
input.state.terminal = setAgentRunAttemptTerminalFailure(input.state.terminal, error !== null && error !== void 0 ? {
error,
source: source ?? "prompt"
} : null);
};
const publishDispatchState = (state) => {
skipPromptSubmission = state.skipPromptSubmission;
promptState.contextBudgetStatus = state.contextBudgetStatus;
promptState.preflightRecovery = state.preflightRecovery;
setFailure(state.promptError, state.promptErrorSource);
};
const releaseLeasedSteering = async (error) => {
if (!leasedSteering) return;
await releasePendingAgentSteeringItems({
runIds: leasedSteering.runIds,
leaseId: leasedSteering.leaseId,
error: error ? formatErrorMessage(error) : void 0
});
leasedSteering = void 0;
};
const handleMidTurnPrecheckRequest = async (request) => {
const outcome = await handleEmbeddedAttemptMidTurnPrecheck({
attempt,
request,
sessionAgentId,
sessionManager,
toolResultPromptProjectionState,
prePromptMessageCount: sessionRuntimeState.prePromptMessageCount,
replaceSessionMessages: (messages) => {
activeSession.agent.state.messages = messages;
}
});
promptState.preflightRecovery = outcome.preflightRecovery;
if (outcome.promptError) setFailure(outcome.promptError, "precheck");
};
const promptStartedAt = Date.now();
let transcriptLeafId = null;
try {
const promptAssembly = await prepareEmbeddedAttemptPromptAssembly({
attempt,
activeSession,
sessionManager,
hookRunner,
hookAgentId: sessionAgentId,
diagnosticTrace,
isRawModelRun,
...orphanRepair ? { orphanRepair } : {},
sessionAgentId,
runtimeModel: runtimeInfo.model,
systemPromptText,
runAbortSignal: input.runAbortController.signal,
setActiveSessionSystemPrompt,
applyPromptBuildToolsAllow: (toolsAllow, decisionIsCurrent) => {
return promptToolPolicy.apply(toolsAllow, decisionIsCurrent).callableToolNames;
},
prepareSystemPrompt: async (currentSystemPrompt) => {
const refresh = await prepared.systemPrompt.prepareToolPrompt?.(promptToolPolicy.current.effectiveTools);
return refresh ? refresh(currentSystemPrompt) : currentSystemPrompt;
},
setLeasedSteering: (lease) => {
leasedSteering = lease;
}
});
systemPromptText = sessionRuntimeState.systemPromptText;
if (prepared.toolCatalog.emptyExplicitToolAllowlistError) {
setFailure(prepared.toolCatalog.emptyExplicitToolAllowlistError, "precheck");
skipPromptSubmission = true;
log$6.warn(`[tools] ${prepared.toolCatalog.emptyExplicitToolAllowlistError.message}`);
}
const { hookCtx, promptBuildPrependContext, promptBuildAppendContext } = promptAssembly;
transcriptLeafId = promptAssembly.transcriptLeafId;
leasedSteering = promptAssembly.leasedSteering ?? leasedSteering;
const promptContext = await prepareEmbeddedAttemptPromptContext({
sessionVersion: sessionManager.getHeader()?.version,
attempt,
capabilityToolNames: prepared.toolCatalog.toolSearchRunPlan.capabilityToolNames,
messages: activeSession.messages,
prompt: promptAssembly,
replaceSessionMessages: (messages) => {
activeSession.agent.state.messages = messages;
},
appendOnlyRuntimeContext,
inHistorySystemUpdates,
...boundaryTimezone ? { boundaryTimezone } : {},
includeBoundaryTimestamp,
isRawModelRun,
...preparedUserTurnMessage ? { preparedUserTurnMessage } : {},
sessionAgentId,
...systemPromptReport ? { systemPromptReport } : {},
systemPromptText,
toolResultPromptProjectionState
});
if (runAbortController.signal.aborted) throw createAbortableError(runAbortController.signal);
promptAssembly.assertHostActive?.();
const { hookMessagesForCurrentPrompt, promptForModel, systemPromptForHook } = promptContext;
sessionRuntimeState.prePromptMessageCount = promptContext.prePromptMessageCount;
setCurrentUserTimestampOverride(promptContext.currentUserTimestampOverride);
const beforeAgentRunOutcome = attempt.operation === "settled-tool-finalization" ? void 0 : await runEmbeddedAttemptBeforeAgentRun({
attempt,
activeSession,
hookContext: hookCtx,
hookMessages: hookMessagesForCurrentPrompt,
hookRunner,
modelPrompt: promptForModel,
sessionManager,
systemPrompt: systemPromptForHook,
withOwnedTranscriptWrite
});
if (beforeAgentRunOutcome) {
input.state.beforeAgentRunBlockedBy = beforeAgentRunOutcome.blockedBy;
setFailure(beforeAgentRunOutcome.promptError, "hook:before_agent_run");
skipPromptSubmission = true;
}
if (!skipPromptSubmission) {
const { resolvedApiKey } = attempt;
const googlePromptCacheStreamFn = await prepareGooglePromptCacheStreamFn({
apiKey: await resolveEmbeddedAgentApiKey({
provider: attempt.provider,
resolvedApiKey,
authStorage: attempt.authStorage
}),
extraParams: effectiveExtraParams,
model: attempt.model,
modelId: attempt.modelId,
provider: attempt.provider,
sessionManager: {
appendCustomEntryAsync: async (customType, data) => {
await withTranscriptWrite(async () => {
runAbortController.signal.throwIfAborted();
await sessionManager.appendCustomEntryAsync(customType, data);
});
},
getEntries: () => sessionManager.getEntries()
},
signal: runAbortController.signal,
streamFn: activeSession.agent.streamFn
});
if (googlePromptCacheStreamFn) activeSession.agent.streamFn = googlePromptCacheStreamFn;
const { onModelRequest } = preparedStreamRuntime.cache;
if (onModelRequest) observeForegroundRequests(onModelRequest);
}
const imageResult = await prepareEmbeddedAttemptPromptExecution({
mediaOwnerAgentId: sessionAgentId,
effectiveFsWorkspaceOnly,
effectiveWorkspace,
sandbox,
attempt,
prompt: promptContext.promptSubmission.prompt,
skipPromptSubmission
});
const reserveTokens = settingsManager.getCompactionReserveTokens();
const terminal = projectAgentRunAttemptTerminal(input.state.terminal);
let state = {
contextBudgetStatus: promptState.contextBudgetStatus,
preflightRecovery: promptState.preflightRecovery,
promptError: terminal.promptError,
promptErrorSource: terminal.promptErrorSource,
skipPromptSubmission: observeEmbeddedAttemptPrompt({
...promptContext,
cacheTrace,
diagnosticTrace,
hookAgentId: sessionAgentId,
hookRunner,
isRawModelRun,
runTrace,
streamStrategy,
systemPromptText,
toolSearchCompacted,
trajectoryRecorder,
transport: effectiveAgentTransport,
effectiveTools: promptToolPolicy.current.effectiveTools,
tools: promptToolPolicy.current.tools,
uncompactedEffectiveTools: promptToolPolicy.current.uncompactedEffectiveTools,
attempt,
imageCount: imageResult.images.length,
reserveTokens,
sessionMessages: activeSession.messages,
skipPromptSubmission,
transcriptLeafId
}).skipPromptSubmission
};
publishDispatchState(state);
let compactionRequestBudget;
if (!state.skipPromptSubmission) {
const userTurnRecorder = attempt.userTurnTranscriptRecorder;
const pendingUserIdempotencyKey = attempt.skipPreparedUserTurnMessage !== true && userTurnRecorder?.hasPersisted() === true ? (userTurnRecorder.getPersistedMessage?.() ?? userTurnRecorder.message)?.idempotencyKey : void 0;
const foregroundBudget = {
contextWindow: promptContext.contextTokenBudget,
reserveTokens
};
const pendingContextMessages = promptContext.runtimeContextMessageForCurrentTurn ? [promptContext.runtimeContextMessageForCurrentTurn] : [];
compactionRequestBudget = createCompactionRequestBudget({
...foregroundBudget,
systemPrompt: promptContext.systemPromptForHook,
tools: activeSession.agent.state.tools,
pendingPrompt: promptContext.llmBoundaryPromptForPrecheck,
pendingImageCount: imageResult.images.length,
...appendOnlyRuntimeContext ? { pendingQueuedContextMessages: resolvePendingRuntimeContextReplay({
messages: activeSession.messages,
pendingContextMessages,
persistedUserIdempotencyKey: pendingUserIdempotencyKey
}).pendingContextMessages } : { pendingContextMessages },
pendingAdditivePrompt: [promptBuildPrependContext, promptBuildAppendContext].filter(Boolean).join("\n\n"),
pendingUserIdempotencyKey
});
attempt.onCompactionRequestBudget?.(compactionRequestBudget);
observeForegroundRequests((_model, context) => {
attempt.onCompactionRequestBudget?.(createCompactionRequestBudget({
...foregroundBudget,
systemPrompt: context.systemPrompt,
tools: context.tools
}));
});
}
state = await prepareEmbeddedAttemptPromptPreflight({
appendOnlyRuntimeContext,
compactionReplayEnabled,
contextEngineAssemblySucceeded,
contextEnginePromptAuthority,
includeBoundaryTimestamp,
...boundaryTimezone ? { timezone: boundaryTimezone } : {},
...unwindowedContextEngineMessagesForPrecheck ? { unwindowedContextEngineMessagesForPrecheck } : {},
attempt,
...activeContextEngine ? { activeContextEngine } : {},
contextTokenBudget: promptContext.contextTokenBudget,
hookMessagesForCurrentPrompt: promptContext.hookMessagesForCurrentPrompt,
promptForPrecheck: promptContext.llmBoundaryPromptForPrecheck,
pendingInputTokens: compactionRequestBudget?.pendingTokens,
reserveTokens,
sessionMessageCount: activeSession.messages.length,
state,
systemPrompt: promptContext.systemPromptForHook,
toolResultMaxChars: promptContext.promptToolResultMaxChars,
toolSchemaTokens: estimateToolSchemaTokenPressure(activeSession.agent.state.tools)
});
publishDispatchState(state);
if (!state.skipPromptSubmission) await submitEmbeddedAttemptPrompt({
...promptBuildAppendContext ? { appendContext: promptBuildAppendContext } : {},
attempt,
activeSession,
contextTokenBudget: promptContext.contextTokenBudget,
compactionRequestBudget,
images: imageResult.images,
...leasedSteering ? { leasedSteering } : {},
modelPrompt: promptContext.promptForModel,
onFinalPromptText: (prompt) => {
promptState.finalPromptText = prompt;
},
assertHostActive: promptAssembly.assertHostActive,
withTranscriptWrite,
getUserTranscriptContexts: sessionRuntime.boundary.getUserTranscriptContexts,
preparePrimaryModelRequest: () => promptToolPolicy.prepareForDispatch(async () => {
promptAssembly.decisionPrefilter.restrictionApplied = false;
promptAssembly.decisionPrefilter.status = "retained";
promptAssembly.decisionPrefilter.reason = "selection-changed";
const refresh = await prepared.systemPrompt.prepareToolPrompt?.(promptToolPolicy.current.effectiveTools);
input.runAbortController.signal.throwIfAborted();
promptAssembly.assertHostActive?.();
const refreshedPrompt = refresh?.(activeSession.agent.state.systemPrompt);
const projection = refreshedPrompt !== void 0 && sessionRuntime.prepareSystemPromptUpdate ? await sessionRuntime.prepareSystemPromptUpdate(refreshedPrompt, refresh?.freshlyRendered === true) : void 0;
if (refreshedPrompt !== void 0 && !projection) setActiveSessionSystemPrompt(refreshedPrompt);
return () => ({
tools: activeSession.agent.state.tools.slice(),
systemPrompt: projection?.systemPrompt ?? activeSession.agent.state.systemPrompt,
...projection ? { promptUpdate: {
update: projection.update,
commit: () => {
projection.commit();
setActiveSessionSystemPrompt(projection.systemPrompt);
}
} } : {}
});
}),
onPrimaryModelRequest: (tools) => {
logDecisionToolRequest({
decision: promptAssembly.decisionPrefilter,
baseline: promptToolPolicy.readDecisionBaseline(),
readFinal: () => tools,
requiredNames: promptToolPolicy.decisionRequiredNames,
trace: runTrace
});
},
onSteeringAcknowledged: () => {
leasedSteering = void 0;
},
persistToolResultProjections: async () => {
if (!isRawModelRun && (inHistorySystemUpdates || toolResultPromptProjectionState.frozen.size > 0)) await withTranscriptWrite(async () => {
runAbortController.signal.throwIfAborted();
await persistToolResultProjections(toolResultPromptProjectionState, (customType, data) => sessionManager.appendCustomEntryAsync(customType, data));
if (sessionRuntime.prepareSystemPromptUpdate) await persistSessionSystemPrompt(sessionPromptState, (customType, data) => sessionManager.appendCustomEntryAsync(customType, data));
});
},
...promptBuildPrependContext ? { prependContext: promptBuildPrependContext } : {},
...promptContext.runtimeContextMessageForCurrentTurn ? { runtimeContextMessage: promptContext.runtimeContextMessageForCurrentTurn } : {},
runtimeOnly: promptContext.promptSubmission.runtimeOnly === true,
setNextUserMessagePersistence: sessionManager.setNextUserMessagePersistence,
systemPrompt: promptContext.systemPromptForHook,
toolResultAggregateMaxChars: promptContext.promptToolResultAggregateMaxChars,
toolResultMaxChars: promptContext.promptToolResultMaxChars,
transcriptLeafId,
transcriptPrompt: promptContext.promptForSession,
appendOnlyRuntimeContext,
promptActiveSession,
toolResultPromptProjectionState,
trajectoryRecorder
});
else await releaseLeasedSteering(state.promptError ?? "prompt submission skipped");
publishDispatchState(state);
} catch (error) {
const promptErrorOutcome = await handleEmbeddedAttemptPromptError({
activeSession,
attempt,
error,
handleMidTurnPrecheckRequest,
markYieldAborted: () => {
promptState.yieldAborted = true;
input.state.terminal = mergeAgentRunAttemptTerminal(input.state.terminal, {
kind: "aborted",
source: "yield_cleanup"
});
},
releaseLeasedSteering,
withOwnedTranscriptWrite,
...input.lifecycle.readYieldState()
});
if (promptErrorOutcome.promptFailure && !(projectAgentRunAttemptTerminal(input.state.terminal).timedOutByRunBudget && isOpenClawAbortableWrapper(promptErrorOutcome.promptFailure.error) && promptErrorOutcome.promptFailure.error instanceof Error && promptErrorOutcome.promptFailure.error.cause === runAbortController.signal.reason)) setFailure(promptErrorOutcome.promptFailure.error, promptErrorOutcome.promptFailure.source);
} finally {
stopAcceptingSteerMessages();
log$6.debug(`embedded run prompt end: runId=${attempt.runId} sessionId=${attempt.sessionId} durationMs=${Date.now() - promptStartedAt}`);
}
const pendingMidTurnPrecheckRequest = contextGuards.takePendingMidTurnPrecheckRequest();
if (pendingMidTurnPrecheckRequest) await withTranscriptWrite(async () => {
await removeTrailingMidTurnPrecheckAssistantError({
activeSession,
sessionManager
});
const terminal = projectAgentRunAttemptTerminal(input.state.terminal);
if (!promptState.preflightRecovery && terminal.promptErrorSource !== "precheck") {
setFailure(null, null);
await handleMidTurnPrecheckRequest(pendingMidTurnPrecheckRequest);
}
});
return {
promptStartedAt,
transcriptLeafId
};
}
//#endregion
//#region src/agents/embedded-agent-runner/replay-state.ts
function createEmbeddedRunReplayState(state) {
return {
replayInvalid: state?.replayInvalid === true,
hadPotentialSideEffects: state?.hadPotentialSideEffects === true
};
}
/** Merges replay state monotonically so unsafe observations cannot be cleared accidentally. */
function mergeEmbeddedRunReplayState(current, next) {
if (!next) return current;
return {
replayInvalid: current.replayInvalid || next.replayInvalid === true,
hadPotentialSideEffects: current.hadPotentialSideEffects || next.hadPotentialSideEffects === true
};
}
function observeReplayMetadata(current, metadata) {
return mergeEmbeddedRunReplayState(current, {
replayInvalid: !metadata?.replaySafe,
hadPotentialSideEffects: metadata ? metadata.hadPotentialSideEffects : true
});
}
function replayMetadataFromState(state) {
return {
hadPotentialSideEffects: state.hadPotentialSideEffects,
replaySafe: !state.replayInvalid && !state.hadPotentialSideEffects
};
}
//#endregion
//#region src/agents/execution-contract.ts
/**
* Resolves strict agentic execution contracts for provider/model pairs.
*/
/** Match bare model names consistently with provider/name and provider:name references. */
function stripProviderPrefix(modelId) {
const normalizedModelId = modelId.trim();
return (/^([^/:]+)[/:](.+)$/.exec(normalizedModelId)?.[2] ?? normalizedModelId).toLowerCase();
}
const STRICT_AGENTIC_MODEL_ID_PATTERN = /^gpt-5(?:[.o-]|$)/i;
/** The embedded mock provider exercises the same incomplete-turn recovery as OpenAI. */
function isStrictAgenticSupportedProviderModel(params) {
const provider = normalizeLowercaseStringOrEmpty(params.provider ?? "");
if (provider !== "openai" && provider !== "mock-openai") return false;
const bareModelId = stripProviderPrefix(typeof params.modelId === "string" ? params.modelId : "");
return STRICT_AGENTIC_MODEL_ID_PATTERN.test(bareModelId);
}
/** Unsupported routes remain inert; supported routes honor an explicit default opt-out. */
function isStrictAgenticExecutionContractActive(params) {
const { sessionAgentId } = resolveSessionAgentIds({
sessionKey: params.sessionKey,
config: params.config,
agentId: params.agentId ?? void 0
});
const explicit = resolveAgentExecutionContract(params.config, sessionAgentId);
return isStrictAgenticSupportedProviderModel({
provider: params.provider,
modelId: params.modelId
}) && explicit !== "default";
}
//#endregion
//#region src/agents/embedded-agent-runner/run/incomplete-turn-classification.ts
function readAssistantSnapshotText(message) {
return message.role === "assistant" ? parseReplyDirectives(extractEmbeddedAssistantText(message)).text.trim() : "";
}
/** Keeps pre-tool commentary distinct from a composed answer at both recovery gates. */
function hasComposedVisibleAnswerAfterSettledTools(params) {
const latestUserIndex = params.messagesSnapshot.findLastIndex((message) => message.role === "user");
const currentMessages = params.messagesSnapshot.slice(latestUserIndex + 1);
const hasMediaOutput = (text) => {
const parsed = parseReplyDirectives(text);
return Boolean(parsed.mediaUrls?.length || parsed.audioAsVoice);
};
if (params.assistantTexts.some(hasMediaOutput) || currentMessages.some((message) => message.role === "assistant" && hasMediaOutput(resolveRawAssistantAnswerText(message)))) return true;
const lastToolResultIndex = currentMessages.findLastIndex((message) => message.role === "toolResult");
if (lastToolResultIndex < 0) return params.assistantTexts.some((text) => parseReplyDirectives(text).text.trim().length > 0);
if (currentMessages.slice(lastToolResultIndex + 1).some((message) => readAssistantSnapshotText(message).length > 0)) return true;
const preToolTexts = new Set(currentMessages.slice(0, lastToolResultIndex).map(readAssistantSnapshotText));
return params.assistantTexts.some((text) => {
const trimmed = parseReplyDirectives(text).text.trim();
return trimmed.length > 0 && !preToolTexts.has(trimmed);
});
}
/** Excludes only plain progress text; structured or tool-owned payloads remain delivery evidence. */
function countSettledTurnDeliveryPayloads(params) {
const hasNoAssistantText = params.attempt.assistantTexts.every((text) => !parseReplyDirectives(text).text.trim());
const hasComposedVisibleAnswer = params.attempt.keptAnswer !== void 0 || hasComposedVisibleAnswerAfterSettledTools(params.attempt);
const canFinalizeProviderError = params.attempt.settledTurnFinalizationContext && !hasComposedVisibleAnswer;
return (params.payloads ?? []).filter((payload) => {
const metadata = getReplyPayloadMetadata(payload);
if (payload.isError === true && Object.keys(payload).every((key) => key === "text" || key === "isError") && (hasNoAssistantText && metadata?.toolErrorWarning || canFinalizeProviderError && metadata?.terminalProviderError)) return false;
const hasDeliveryMetadata = metadata && Object.keys(metadata).some((key) => key !== "assistantMessageIndex" && key !== "assistantTranscriptOwned" && key !== "assistantTranscriptIdempotencyKey");
if (hasComposedVisibleAnswer || hasDeliveryMetadata) return true;
const text = typeof payload.text === "string" ? payload.text.trim() : "";
return Object.entries(payload).some(([key, value]) => key !== "text" && value !== void 0 && value !== false) || !text || !params.attempt.assistantTexts.some((candidate) => candidate.trim() === text);
}).length;
}
function hasPositiveOutputTokenUsage(message) {
return asPositiveFiniteNumber(message?.usage?.output) !== void 0;
}
function isIncompleteTerminalAssistantTurn(params) {
const stopReason = params.lastAssistant?.stopReason;
return stopReason === "toolUse" || stopReason === "length" && !params.hasTerminalOutput && !params.hasAssistantVisibleText;
}
const GEMINI_INCOMPLETE_TURN_PROVIDER_IDS = /* @__PURE__ */ new Set([
"google",
"google-vertex",
"google-antigravity",
"google-gemini-cli"
]);
const GEMINI_INCOMPLETE_TURN_MODEL_ID_PATTERN = /^gemini(?:[.-]|$)/;
const OLLAMA_INCOMPLETE_TURN_PROVIDER_ID_PATTERN = /^ollama(?:-|$)/;
function isOllamaIncompleteTurnProvider(provider) {
return OLLAMA_INCOMPLETE_TURN_PROVIDER_ID_PATTERN.test(normalizeLowercaseStringOrEmpty(provider ?? ""));
}
const RETRY_GUARD_MODEL_APIS = /* @__PURE__ */ new Set([
"openai-completions",
"anthropic-messages",
"bedrock-converse-stream",
"openai-responses",
"openai-chatgpt-responses",
"azure-openai-responses",
"openclaw-openai-responses-transport",
"openclaw-openai-chatgpt-responses-transport",
"openclaw-azure-openai-responses-transport"
]);
function joinAssistantTexts(assistantTexts) {
return (assistantTexts ?? []).join("\n\n").trim();
}
function shouldApplyNonVisibleTurnRetryGuard(params) {
return params.executionContract === "strict-agentic" || isStrictAgenticSupportedProviderModel(params) || GEMINI_INCOMPLETE_TURN_PROVIDER_IDS.has(normalizeLowercaseStringOrEmpty(params.provider ?? "")) && GEMINI_INCOMPLETE_TURN_MODEL_ID_PATTERN.test(stripProviderPrefix(typeof params.modelId === "string" ? params.modelId : "")) || RETRY_GUARD_MODEL_APIS.has(normalizeLowercaseStringOrEmpty(params.modelApi ?? "")) || isOllamaIncompleteTurnProvider(params.provider);
}
function classifyAssistantTurn(params) {
const assistant = resolveCurrentAttemptAssistant(params.attempt);
const output = parseReplyDirectives(assistant ? resolveRawAssistantAnswerText(assistant) : joinAssistantTexts(params.attempt.assistantTexts));
const visibleText = output.text.trim();
const reasoningOnly = Boolean(assistant && assessLastAssistantMessage(assistant) === "incomplete-text");
const nonVisibleEligibleForSilentReply = params.payloadCount === 0 && visibleText.length === 0 && assistant?.stopReason !== "error" && assistant?.stopReason !== "aborted" && !isIncompleteTerminalAssistantTurn({
hasAssistantVisibleText: false,
lastAssistant: assistant
});
return {
assistant,
visibleText,
silent: output.isSilent,
reasoningOnly,
emptyResponse: nonVisibleEligibleForSilentReply && !reasoningOnly,
nonVisibleEligibleForSilentReply
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/incomplete-turn-recovery.ts
const REASONING_ONLY_RETRY_INSTRUCTION = "The previous assistant turn recorded reasoning but did not produce a user-visible answer. Continue from that partial turn and produce the visible answer now. Do not restate the reasoning or restart from scratch.";
const EMPTY_RESPONSE_RETRY_INSTRUCTION = "The previous attempt did not produce a user-visible answer. Continue from the current state and produce the visible answer now. Do not restart from scratch.";
const TOOL_USE_WITHOUT_CALL_RETRY_INSTRUCTION = "The previous assistant turn stopped for tool use but contained no tool call, so nothing ran. Continue from the current state: call the tool you need through the tool interface instead of writing the call as text, or produce the visible answer now. Do not restart from scratch.";
const REJECTED_TOOL_CALL_RETRY_INSTRUCTION = "The previous assistant turn's tool call was rejected before it ran because the provider returned incomplete or malformed tool-call arguments, so nothing ran for that call. Continue from the current state: re-issue the call you need through the tool interface with complete, valid JSON arguments, or produce the visible answer now. Do not repeat completed tool calls or restart from scratch.";
const SETTLED_TOOL_TERMINAL_CONTINUATION_INSTRUCTION = "The previous assistant turn completed its tool calls but did not produce a user-visible answer. Continue from the current transcript and produce the final user-visible answer now. Do not repeat completed tool calls or restart from scratch. Tools are unavailable in this step: it is a text-only pass, so reply with plain text and do not attempt any tool call.";
function shouldRetrySilentErrorAssistantTurn(params) {
if (joinAssistantTexts(params.attempt.assistantTexts).length > 0 || hasAttemptTerminalState(params.attempt) || !isCurrentAttemptReplaySafe(params.attempt)) return false;
const assistant = params.assistant;
if (!assistant || assistant.stopReason !== "error" || isTerminalAssistantError(assistant) || isResponsesOutputLimitToolCallError(assistant)) return false;
const { content } = assistant;
if (!Array.isArray(content)) return false;
if (content.every((block) => block.type === "text" && !block.text.trim())) return !hasPositiveOutputTokenUsage(assistant) || assistant.errorCode === "malformed_tool_call_arguments" || isPreDispatchToolCallRejectionMessage(assistant.errorMessage);
return hasOnlyAssistantReasoningContent(assistant);
}
function shouldSkipNonVisibleTurnRetry(params) {
return Boolean(params.aborted || params.timedOut || params.attempt.terminal.kind === "failed" || params.attempt.clientToolCalls || params.attempt.yieldDetected || params.attempt.didSendDeterministicApprovalPrompt || params.attempt.lastToolError && !params.consumedToolFailure || hasAcceptedSessionSpawn(params.attempt.acceptedSessionSpawns) || params.attempt.itemLifecycle.activeCount > 0 || params.attempt.itemLifecycle.completedCount < params.attempt.itemLifecycle.startedCount || hasAsyncActivity(params.attempt.toolMetas) || params.tolerateSideEffects !== true && params.attempt.replayMetadata.hadPotentialSideEffects);
}
/** Allows configured silent handling for replay-safe empty, reasoning-only, or explicit silent turns. */
function shouldTreatEmptyAssistantReplyAsSilent(params) {
const completion = resolveReplyCompletion(resolveReplyExpectation(params), params.payloadCount === 0 ? "empty" : "ready");
const assistant = classifyAssistantTurn(params);
return completion.outcome === "silent" && !shouldSkipNonVisibleTurnRetry({
...params,
tolerateSideEffects: true
}) && resolveSourceReplyDelivery(params.attempt) === "missing" && (!params.onlyExplicitSilentReply || assistant.silent) && assistant.nonVisibleEligibleForSilentReply;
}
/**
* Builds the retry instruction for reasoning-only turns that consumed provider
* output budget but produced no visible assistant text.
*/
function resolveReasoningOnlyRetryInstruction(params) {
if (shouldSkipNonVisibleTurnRetry(params) || !shouldApplyNonVisibleTurnRetryGuard(params)) return null;
const assistant = resolveCurrentAttemptAssistant(params.attempt);
return joinAssistantTexts(params.attempt.assistantTexts).length === 0 && assistant && assistant.stopReason !== "error" && !assistant.openclawDelivery?.tts?.text?.trim() && Array.isArray(assistant.content) && assistant.content.length > 0 && assessLastAssistantMessage(assistant) !== "valid" ? REASONING_ONLY_RETRY_INSTRUCTION : null;
}
function readSettledToolCalls(message) {
if (!Array.isArray(message?.content)) return [];
return message.content.flatMap((item) => {
const block = item;
return block?.type === "toolCall" ? [{
id: typeof block.id === "string" ? block.id : null,
name: typeof block.name === "string" ? block.name : null
}] : [];
});
}
/** Proves settlement and intentional termination for the exact current-turn tool-call batch. */
function resolveSettledToolBatchEvidence(attempt, options) {
const snapshot = attempt.messagesSnapshot ?? [];
let assistant = attempt.currentAttemptAssistant;
let assistantIndex = assistant ? snapshot.indexOf(assistant) : -1;
const latestUserIndex = snapshot.findLastIndex((message, index) => message.role === "user" && !(options?.midTurn && assistantIndex >= 0 && index > assistantIndex));
if (assistantIndex <= latestUserIndex || readSettledToolCalls(assistant).length === 0) {
assistantIndex = snapshot.findLastIndex((message, index) => index > latestUserIndex && message.role === "assistant" && readSettledToolCalls(message).length > 0);
const candidate = assistantIndex >= 0 ? snapshot[assistantIndex] : void 0;
assistant = candidate?.role === "assistant" ? candidate : void 0;
}
const requestedToolCalls = readSettledToolCalls(assistant);
const settledToolResults = new Map((assistantIndex >= 0 ? snapshot.slice(assistantIndex + 1) : []).flatMap((message) => {
const { toolCallId, toolName, isError } = message;
return message.role === "toolResult" && typeof toolCallId === "string" && typeof toolName === "string" ? [[toolCallId, {
toolName,
isError: isError === true
}]] : [];
}));
const allToolCallsRecorded = requestedToolCalls.length > 0 && requestedToolCalls.every(({ id, name }) => id !== null && name !== null && settledToolResults.get(id)?.toolName === name);
const allToolsProvenSettled = allToolCallsRecorded && attempt.itemLifecycle.startedCount > 0 && attempt.itemLifecycle.completedCount === attempt.itemLifecycle.startedCount && attempt.itemLifecycle.activeCount === 0;
const parkedCodeModeRun = allToolCallsRecorded && requestedToolCalls.some(({ id }) => attempt.toolMetas.some((entry) => entry.toolCallId === id && entry.codeModeSuspended === true));
const failedToolNames = new Set(requestedToolCalls.flatMap(({ id, name }) => id !== null && name !== null && settledToolResults.get(id)?.isError === true ? [name] : []));
const hasStaleToolError = Boolean(attempt.lastToolError && assistant?.stopReason === "toolUse" && allToolsProvenSettled && failedToolNames.size === 0);
const hasUnsettledToolError = Boolean(attempt.lastToolError && !hasStaleToolError && (assistant?.stopReason !== "toolUse" || !allToolsProvenSettled || !failedToolNames.has(attempt.lastToolError.toolName)));
const intentionalTermination = allToolsProvenSettled && assistant?.stopReason === "toolUse" && failedToolNames.size === 0 && !hasUnsettledToolError && !hasAsyncActivity(attempt.toolMetas) && requestedToolCalls.every(({ id, name }) => {
const metadata = attempt.toolMetas.findLast((entry) => entry.toolCallId === id && entry.toolName === name);
return metadata?.terminate === true && metadata.isError !== true;
});
return {
assistant,
allToolCallsRecorded,
allToolsProvenSettled,
parkedCodeModeRun,
failedToolNames,
hasStaleToolError,
hasUnsettledToolError,
intentionalTermination
};
}
/** Builds one fresh continuation after settled tools ended without a visible final answer. */
function resolveSettledToolTerminalContinuationInstruction(params) {
const { attempt } = params;
const { assistant: toolBatchAssistant, allToolsProvenSettled, failedToolNames, hasUnsettledToolError, intentionalTermination } = resolveSettledToolBatchEvidence(attempt);
const terminal = attempt.terminal;
const idlePromptTimeout = terminal.kind === "timeout" && terminal.phase === "prompt" && terminal.source === "idle" && attempt.currentAttemptReplayMetadata?.hadPotentialSideEffects === true;
const assistantState = classifyAssistantTurn(params);
const emptyStopAfterSettledTools = Boolean(params.allowEmptyStopContinuation && attempt.currentAttemptAssistant?.stopReason === "stop" && attempt.toolMetas.length > 0 && attempt.toolMetas.every((tool) => tool.isError !== true && tool.asyncStarted !== true) && attempt.itemLifecycle.startedCount > 0 && attempt.itemLifecycle.completedCount === attempt.itemLifecycle.startedCount && attempt.itemLifecycle.activeCount === 0 && !hasAcceptedSessionSpawn(attempt.acceptedSessionSpawns) && assistantState.emptyResponse);
if (params.payloadCount !== 0 || !params.allowEmptyStopContinuation && assistantState.silent && assistantState.nonVisibleEligibleForSilentReply || params.hasTerminalToolPresentation || params.aborted || (params.timedOut || terminal.kind === "timeout") && !idlePromptTimeout || terminal.kind === "failed" && !attempt.settledTurnFinalizationContext || (toolBatchAssistant?.stopReason === "toolUse" ? !allToolsProvenSettled : !emptyStopAfterSettledTools) || intentionalTermination || hasUnsettledToolError || hasAsyncActivity(attempt.toolMetas) || hasAcceptedSessionSpawn(attempt.acceptedSessionSpawns) || attempt.clientToolCalls || attempt.yieldDetected || attempt.didSendDeterministicApprovalPrompt || attempt.hasToolMediaBlockReply || resolveSourceReplyDelivery(attempt) !== "missing" || !shouldApplyNonVisibleTurnRetryGuard(params)) return null;
return allToolsProvenSettled && failedToolNames.size > 0 ? `${SETTLED_TOOL_TERMINAL_CONTINUATION_INSTRUCTION} ${TOOL_FAILURE_INSTRUCTION}` : SETTLED_TOOL_TERMINAL_CONTINUATION_INSTRUCTION;
}
/**
* Builds the retry instruction for empty assistant turns when the provider/model
* is eligible for non-visible turn recovery.
*/
function resolveEmptyResponseRetryInstruction(params) {
const assistantState = classifyAssistantTurn(params);
const assistant = assistantState.assistant ?? null;
const rejectedBeforeDispatch = params.attempt.itemLifecycle.completedCount > 0 && assistantState.visibleText.length === 0 && !params.attempt.hasToolMediaBlockReply && resolveSourceReplyDelivery(params.attempt) === "missing" && assistant?.stopReason === "error" && (assistant.errorCode === "malformed_tool_call_arguments" || isPreDispatchToolCallRejectionMessage(assistant.errorMessage)) && readSettledToolCalls(assistant).length === 0;
let consumedToolFailure = false;
if (rejectedBeforeDispatch && params.attempt.lastToolError?.executionStarted === false) {
const messages = params.attempt.messagesSnapshot;
const failureIndex = messages.findLastIndex((message) => message.role === "toolResult" && message.isError);
const failure = messages[failureIndex];
const latestUserIndex = messages.findLastIndex((message) => message.role === "user");
consumedToolFailure = failureIndex > latestUserIndex && failureIndex < messages.indexOf(assistant) && failure?.role === "toolResult" && failure.toolName === params.attempt.lastToolError.toolName && messages.slice(latestUserIndex + 1, failureIndex + 1).every((message) => message.role !== "toolResult" || !message.isError || readToolEffectReceipt(message)?.state === "not_started");
}
if (shouldSkipNonVisibleTurnRetry({
...params,
tolerateSideEffects: rejectedBeforeDispatch,
consumedToolFailure
})) return null;
const toolUseWithoutCall = assistant?.stopReason === "toolUse" && readSettledToolCalls(assistant).length === 0 && params.attempt.toolMetas.length === 0 && params.attempt.itemLifecycle.startedCount === 0;
if (!assistantState.emptyResponse && !toolUseWithoutCall && !rejectedBeforeDispatch) return null;
if (assistant?.stopReason === "stop" && isOllamaIncompleteTurnProvider(params.provider) && !hasPositiveOutputTokenUsage(assistant)) return null;
if (shouldApplyNonVisibleTurnRetryGuard(params) || isZeroUsageEmptyStopAssistantTurn(assistant)) {
if (rejectedBeforeDispatch) return REJECTED_TOOL_CALL_RETRY_INSTRUCTION;
return toolUseWithoutCall ? TOOL_USE_WITHOUT_CALL_RETRY_INSTRUCTION : EMPTY_RESPONSE_RETRY_INSTRUCTION;
}
return null;
}
//#endregion
//#region src/agents/embedded-agent-runner/run/incomplete-turn-resolution.ts
/**
* Builds the user-visible incomplete-turn warning when a terminal attempt did
* not produce a safe final assistant response and no committed delivery/progress
* already completed the task.
*/
function resolveIncompleteTurnPayloadText(params) {
const assistantState = classifyAssistantTurn(params);
const assistant = assistantState.assistant;
const hasTerminalOutput = hasAttemptTerminalState(params.attempt);
const incompleteTerminalAssistant = isIncompleteTerminalAssistantTurn({
hasAssistantVisibleText: params.payloadCount > 0,
hasTerminalOutput,
lastAssistant: assistant
});
const thinkingOnlyTerminal = params.payloadCount !== 0 && params.attempt.keptAnswer === void 0 && !assistantState.visibleText.length && !assistant?.openclawDelivery?.tts?.text?.trim() && !hasTerminalOutput && Boolean(assistant && hasOnlyAssistantReasoningContent(assistant));
if (params.payloadCount !== 0 && !incompleteTerminalAssistant && !thinkingOnlyTerminal || params.aborted && params.externalAbort || params.timedOut || params.attempt.clientToolCalls || params.attempt.yieldDetected || params.attempt.didSendDeterministicApprovalPrompt || params.attempt.heartbeatToolResponse || params.attempt.lastToolError || params.hasIntentionalTerminalCompletion || params.attempt.hasToolMediaBlockReply || resolveSourceReplyDelivery(params.attempt) !== "missing" || params.attempt.messagingToolSourceReplyPayloads?.some((payload) => payload.toolAuthored === true) || hasCompletionMessageSessionSpawn(params.attempt.acceptedSessionSpawns)) return null;
if (hasAsyncActivity(params.attempt.toolMetas) || !params.aborted && assistant?.stopReason === "stop" && (params.attempt.itemLifecycle.activeCount > 0 || params.attempt.itemLifecycle.completedCount < params.attempt.itemLifecycle.startedCount)) return null;
const stopReason = assistant?.stopReason;
if (!incompleteTerminalAssistant && !assistantState.reasoningOnly && !thinkingOnlyTerminal && !assistantState.emptyResponse && stopReason !== "error") return null;
const { promptError } = projectAgentRunAttemptTerminal(params.attempt.terminal);
if (!promptError && assistant?.stopReason === "error" && assistant.errorCode === "incomplete_tool_call") return formatUserFacingAssistantErrorText(assistant);
const preflightFailureText = renderAgentHarnessPreflightUserMessage(promptError);
const failureFacts = promptError && preflightFailureText === void 0 ? resolveReplyFailoverFacts(promptError, formatErrorMessage(promptError)) : void 0;
const promptFailureText = preflightFailureText ?? (failureFacts ? failureFacts.providerRequestError?.userMessage ?? failureFacts.formatFailureText ?? renderAssistantRequestFailureCopy({
reason: failureFacts.reason,
status: failureFacts.status,
code: failureFacts.code
}) : void 0);
if (params.hadPotentialSideEffects || params.attempt.replayMetadata.hadPotentialSideEffects) return `${promptFailureText ?? "⚠️ Agent couldn't generate a response."} Note: some tool actions may have already been executed — please verify before retrying.`;
if (preflightFailureText !== void 0) return preflightFailureText;
if (assistant && isProviderRefusalAssistantError(assistant)) return formatUserFacingAssistantErrorText(assistant);
const authFailure = params.terminalAuthFailure;
const reason = authFailure?.assistantProfileFailureReason;
if (authFailure && (reason === "auth" || reason === "auth_permanent")) return renderAuthProfileFailoverCopy({
reason,
provider: authFailure.provider,
allInCooldown: false,
recoveryHint: buildProviderAuthRecoveryHint({
provider: authFailure.provider,
config: authFailure.runParams.config,
workspaceDir: authFailure.runParams.workspaceDir,
env: process.env
})
});
return promptFailureText ?? (stopReason === "length" ? "⚠️ The model reached its output token limit before generating an answer. Please try again." : "⚠️ Agent couldn't generate a response. Please try again.");
}
/**
* Allows one retry when the provider returned no assistant turn at all and the
* attempt has no side effects, active lifecycle items, delivery, or terminal
* assistant/tool state.
*/
function shouldRetryMissingAssistantTurn(params) {
return !(params.payloadCount !== 0 || params.aborted || params.promptError || params.timedOut || params.attempt.clientToolCalls || resolveCurrentAttemptAssistant(params.attempt) || params.attempt.yieldDetected || params.attempt.didSendDeterministicApprovalPrompt || params.attempt.lastToolError || joinAssistantTexts(params.attempt.assistantTexts).length > 0 || hasCommittedMessagingToolDeliveryEvidence(params.attempt) || hasAcceptedSessionSpawn(params.attempt.acceptedSessionSpawns) || hasAsyncActivity(params.attempt.toolMetas) || (params.attempt.itemLifecycle?.startedCount ?? 0) > 0 || (params.attempt.itemLifecycle?.activeCount ?? 0) > 0 || params.attempt.replayMetadata.hadPotentialSideEffects);
}
/** Same-attempt work that can resume execution or deliver its completion. */
function hasRunContinuationEvidence(attempt) {
return (attempt.clientToolCalls?.length ?? 0) > 0 || attempt.didSendDeterministicApprovalPrompt === true || hasCompletionMessageSessionSpawn(attempt.acceptedSessionSpawns) || attempt.runtimeContinuationStarted === true || attempt.yieldMessageWaitRegistered === true || hasAsyncActivity(attempt.toolMetas);
}
/** A deliberate yield can also leave accepted work or already-sent output. */
function hasYieldContinuationEvidence(attempt) {
return hasAcceptedSessionSpawn(attempt.acceptedSessionSpawns) || hasCommittedMessagingToolDeliveryEvidence(attempt) || (attempt.successfulCronAdds ?? 0) > 0 || hasRunContinuationEvidence(attempt);
}
const YIELD_DIAGNOSTIC_TEXT = "⚠️ Turn yielded without a continuation source. Send a message to resume.";
const TRUNCATED_REPLY_NOTICE_TEXT = "⚠️ Reply truncated at the model's output token limit. The text above is partial — ask to continue it.";
function readMessageTextContent(message) {
const content = message.content;
return normalizeOptionalString(typeof content === "string" ? content : collectTextContentBlocks(content).map((item) => item.trim()).filter((item) => item.length > 0).join("\n"));
}
function hasTrailingSilentToolResult(messages) {
for (let i = messages.length - 1; i >= 0; i -= 1) {
const message = messages[i];
if (!message) continue;
const role = normalizeLowercaseStringOrEmpty(message?.role);
if (role === "toolresult" || role === "tool_result" || role === "tool") {
if (message.isError === true) return false;
const text = readMessageTextContent(message) ?? normalizeOptionalString(message.details?.aggregated);
return isSilentReplyText(text, SILENT_REPLY_TOKEN);
}
if (role === "assistant" && !readMessageTextContent(message)) continue;
return false;
}
return false;
}
/** Emits the silent-reply token for cron turns whose last successful tool result is silent. */
function resolveSilentToolResultReplyPayload(params) {
if (!params.isCronTrigger || params.payloadCount !== 0 || params.aborted || params.timedOut || (params.attempt.toolMetas?.length ?? 0) === 0 || params.attempt.clientToolCalls || params.attempt.yieldDetected || params.attempt.didSendDeterministicApprovalPrompt || params.attempt.lastToolError || (params.attempt.messagesSnapshot?.length ?? 0) === 0) return null;
return hasTrailingSilentToolResult(params.attempt.messagesSnapshot) ? { text: SILENT_REPLY_TOKEN } : null;
}
/**
* Marks replay invalid whenever the recorded attempt might not be safe to
* replay or the current run ended in a compaction/incomplete-turn state that
* needs a fresh prompt boundary.
*/
function resolveReplayInvalidFlag(params) {
const terminal = projectAgentRunAttemptTerminal(params.attempt.terminal);
const replaySafeProviderRefusal = params.attempt.replayMetadata.replaySafe && isProviderRefusalAssistantError(resolveCurrentAttemptAssistant(params.attempt));
return !params.attempt.replayMetadata.replaySafe || terminal.promptErrorSource === "compaction" || terminal.timedOutDuringCompaction || Boolean(params.incompleteTurnText) && !replaySafeProviderRefusal;
}
/** Classifies the persisted run state used by session recovery and resume logic. */
function resolveRunLivenessState(params) {
if (params.incompleteTurnText) return "abandoned";
const terminal = projectAgentRunAttemptTerminal(params.attempt.terminal);
if (!terminal.timedOutDuringToolExecution && (terminal.promptErrorSource === "compaction" || terminal.timedOutDuringCompaction)) return "paused";
if ((params.aborted || params.timedOut) && params.payloadCount === 0) return "blocked";
if (resolveCurrentAttemptAssistant(params.attempt)?.stopReason === "error") return "blocked";
return "working";
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-result.ts
/** Keeps attempt-owned state available while retry attempts replace their result object. */
function createAttemptCarryover() {
let latestMcpAppChannelView;
let latestMcpConnectAction;
let heartbeatToolResponse;
let modelAttempt;
return {
apply(attempt) {
modelAttempt = attempt.modelAttempt;
latestMcpAppChannelView = attempt.latestMcpAppChannelView ?? latestMcpAppChannelView;
attempt.latestMcpAppChannelView = latestMcpAppChannelView;
latestMcpConnectAction = attempt.latestMcpConnectAction ?? latestMcpConnectAction;
attempt.latestMcpConnectAction = latestMcpConnectAction;
heartbeatToolResponse = attempt.heartbeatToolResponse ?? heartbeatToolResponse;
attempt.heartbeatToolResponse = heartbeatToolResponse;
},
get modelAttempt() {
return modelAttempt;
}
};
}
/**
* Captures the settled transcript the tool-free finalizer needs when a settled
* post-tool turn dies on its final provider call. The consumer fails closed
* without this context, so the attempt-result owner has to supply it; the codex
* app-server harness already does the same for its own attempts.
*/
function resolveSettledTurnFinalizationContext(params) {
const terminal = projectAgentRunAttemptTerminal(params.terminal);
const failure = terminal.promptErrorSource === "prompt" ? terminal.promptError : params.assistant?.stopReason === "error" ? {
message: params.assistant.errorMessage,
code: params.assistant.errorCode
} : void 0;
if (terminal.aborted || terminal.timedOut || terminal.timedOutDuringCompaction || terminal.timedOutDuringToolExecution || terminal.promptErrorSource !== null && terminal.promptErrorSource !== "prompt" || !isTransientSettledTurnFailure(failure)) return;
if (hasComposedVisibleAnswerAfterSettledTools(params)) return;
if (!params.messagesSnapshot.some((message) => message.role === "toolResult")) return;
return {
source: "openclaw-transcript",
messages: Object.freeze([...params.messagesSnapshot])
};
}
function isTransientSettledTurnFailure(failure) {
if (isTransientNetworkError(failure)) return true;
const message = typeof failure === "object" && failure !== null && "message" in failure && typeof failure.message === "string" ? failure.message.trim() : "";
return INCOMPLETE_ASSISTANT_STREAM_RE.test(message) || TERMINATED_TRANSPORT_MESSAGE_RE.test(message);
}
function normalizeEmbeddedAttemptToolMetas(entries) {
return entries.filter((entry) => typeof entry.toolName === "string" && entry.toolName.trim().length > 0).map((entry) => Object.assign({
toolName: entry.toolName,
meta: entry.meta,
replaySafe: entry.replaySafe === true
}, entry.toolCallId ? { toolCallId: entry.toolCallId } : {}, typeof entry.isError === "boolean" ? { isError: entry.isError } : {}, entry.terminate === true ? { terminate: true } : {}, entry.asyncStarted === true ? { asyncStarted: true } : {}, entry.asyncTaskRunId ? { asyncTaskRunId: entry.asyncTaskRunId } : {}, entry.asyncTaskId ? { asyncTaskId: entry.asyncTaskId } : {}, entry.codeModeSuspended === true ? { codeModeSuspended: true } : {}));
}
function completeEmbeddedAttemptResult(input, settled, prompt) {
const { attempt } = input;
const { sessionRuntime, bootstrap, systemPrompt } = input.prepared;
const { agentSession: { clientToolCallSlots, hasDeliveredSourceReply, hookRunner }, trajectoryRecorder } = sessionRuntime;
const { subscription, deferredLifecycleOwner } = input.preparedStreamRuntime.stream;
const { bootstrapPromptWarning } = bootstrap;
const hookAgentId = input.setup.sessionAgentId;
const state = {
terminal: input.state.terminal,
preflightRecovery: prompt.preflightRecovery,
sessionIdUsed: prompt.sessionIdUsed,
sessionFileUsed: prompt.sessionFileUsed,
diagnosticTrace: input.diagnostics.diagnosticTrace,
systemPromptReport: systemPrompt.systemPromptReport,
finalPromptText: prompt.finalPromptText,
messagesSnapshot: prompt.messagesSnapshot,
...prompt.beforeAgentFinalizeRevisionReason ? { beforeAgentFinalizeRevisionReason: prompt.beforeAgentFinalizeRevisionReason } : {},
lastAssistant: settled.lastAssistant,
currentAttemptAssistant: settled.currentAttemptAssistant,
currentAttemptCompletedAssistant: settled.currentAttemptCompletedAssistant,
hasSuccessfulModelResponse: subscription.hasSuccessfulModelResponse(),
successfulNestedToolNames: settled.successfulNestedToolNames,
attemptUsage: settled.attemptUsage,
promptCache: sessionRuntime.state.promptCache,
contextBudgetStatus: prompt.contextBudgetStatus,
yieldDetected: input.lifecycle.readYieldState().yieldDetected,
yieldAcknowledgment: input.lifecycle.readYieldState().yieldAcknowledgment,
yieldMessageWaitRegistered: input.lifecycle.readYieldState().yieldMessageWaitRegistered,
didDeliverSourceReplyViaMessageTool: hasDeliveredSourceReply()
};
const terminal = projectAgentRunAttemptTerminal(state.terminal);
const { assistantTexts, setTerminalLifecycleMeta } = subscription;
const toolMetasNormalized = normalizeEmbeddedAttemptToolMetas(subscription.toolMetas);
if (attempt.operation !== "settled-tool-finalization" && hookRunner?.hasHooks("llm_output") && terminal.promptErrorSource !== "hook:before_agent_run") {
const contextWindow = {
...attempt.contextWindowInfo?.tokens ? { contextTokenBudget: attempt.contextWindowInfo.tokens } : {},
...attempt.contextWindowInfo?.source ? { contextWindowSource: attempt.contextWindowInfo.source } : {},
...attempt.contextWindowInfo?.referenceTokens ? { contextWindowReferenceTokens: attempt.contextWindowInfo.referenceTokens } : {}
};
hookRunner.runLlmOutput({
runId: attempt.runId,
sessionId: attempt.sessionId,
provider: attempt.provider,
model: attempt.modelId,
...contextWindow,
resolvedRef: attempt.runtimePlan?.observability.resolvedRef ?? `${attempt.provider}/${attempt.modelId}`,
...attempt.runtimePlan?.observability.harnessId ? { harnessId: attempt.runtimePlan.observability.harnessId } : {},
assistantTexts,
lastAssistant: state.lastAssistant,
usage: state.attemptUsage
}, {
...buildEmbeddedAgentHookContext(attempt, hookAgentId, freezeDiagnosticTraceContext(state.diagnosticTrace)),
...contextWindow
}).catch((err) => {
log$6.warn(`llm_output hook failed: ${String(err)}`);
});
}
const acceptedSessionSpawns = subscription.getAcceptedSessionSpawns();
const messagingToolSentMediaUrls = subscription.getMessagingToolSentMediaUrls();
const sentMediaUrls = new Set(messagingToolSentMediaUrls.map((url) => url.trim()));
const toolAutoDeliveryMediaUrls = subscription.getToolAutoDeliveryMediaUrls().filter((url) => !sentMediaUrls.has(url.trim()));
const replayEvidence = {
toolMetas: toolMetasNormalized,
didSendViaMessagingTool: subscription.didSendViaMessagingTool(),
messagingToolSentTexts: subscription.getMessagingToolSentTexts(),
messagingToolSentMediaUrls,
acceptedSessionSpawns,
successfulCronAdds: subscription.getSuccessfulCronAdds()
};
const observedReplayMetadata = buildAttemptReplayMetadata({
...replayEvidence,
toolMetas: []
});
const pendingToolMediaReply = subscription.getPendingToolMediaReply();
const replayMetadata = replayMetadataFromState(observeReplayMetadata(subscription.getReplayState(), observedReplayMetadata));
const currentAttemptReplayMetadata = buildAttemptReplayMetadata(replayEvidence);
const completedClientToolCalls = clientToolCallSlots.flatMap((slot) => slot.completed && slot.params ? [{
name: slot.name,
params: slot.params
}] : []);
const clientToolCalls = completedClientToolCalls.length > 0 ? completedClientToolCalls : void 0;
const didSendDeterministicApprovalPromptNow = subscription.didSendDeterministicApprovalPrompt();
const lastToolError = subscription.getLastToolError();
const heartbeatToolResponse = subscription.getHeartbeatToolResponse();
const terminalAssistant = state.currentAttemptAssistant;
const closingText = terminalAssistant ? extractEmbeddedAssistantText(terminalAssistant).trim() : "";
const progressIsReply = subscription.endsWithSourceProgress() && state.terminal.kind === "ok" && terminalAssistant?.stopReason === "stop" && (!closingText || isSilentReplyText(closingText, "NO_REPLY"));
const completeLastProgress = (sends) => {
const index = sends.findLastIndex((send) => send.sourceReplyFinal === false);
return progressIsReply && index >= 0 ? sends.with(index, Object.assign({}, sends[index], { sourceReplyFinal: true })) : sends;
};
const messagingToolSourceReplyPayloads = completeLastProgress(subscription.getMessagingToolSourceReplyPayloads());
const hasToolMediaBlockReplyNow = subscription.hasToolMediaBlockReply();
const settledTurnFinalizationContext = resolveSettledTurnFinalizationContext({
assistant: state.currentAttemptCompletedAssistant ?? state.currentAttemptAssistant,
assistantTexts,
messagesSnapshot: state.messagesSnapshot,
terminal: state.terminal
});
const result = {
...state,
...settledTurnFinalizationContext ? { settledTurnFinalizationContext } : {},
replayMetadata,
currentAttemptReplayMetadata,
itemLifecycle: subscription.getItemLifecycle(),
assistantTurns: subscription.getAssistantTurnCount(),
setTerminalLifecycleMeta,
bootstrapPromptWarningSignaturesSeen: bootstrapPromptWarning.warningSignaturesSeen,
bootstrapPromptWarningSignature: bootstrapPromptWarning.signature,
assistantTexts,
answerSegments: subscription.answerSegments,
keptAnswer: subscription.getKeptAnswer(),
latestMcpAppChannelView: subscription.getLatestMcpAppChannelView(),
latestMcpConnectAction: subscription.getLatestMcpConnectAction(),
lastAssistantTextMessageIndex: subscription.getLastAssistantTextMessageIndex(),
toolMetas: toolMetasNormalized,
acceptedSessionSpawns,
lastToolError,
didSendViaMessagingTool: replayEvidence.didSendViaMessagingTool,
didSendDeterministicApprovalPrompt: didSendDeterministicApprovalPromptNow,
messagingToolSentTexts: replayEvidence.messagingToolSentTexts,
messagingToolSentMediaUrls,
messagingToolSentTargets: completeLastProgress(subscription.getMessagingToolSentTargets()),
messagingToolSourceReplyPayloads,
heartbeatToolResponse,
sourceReplyDelivered: subscription.getSourceReplyDelivered(),
sourceReplyDeliveryState: progressIsReply ? "delivered" : subscription.getSourceReplyDeliveryState(),
toolMediaUrls: pendingToolMediaReply?.mediaUrls,
toolAudioAsVoice: pendingToolMediaReply?.audioAsVoice,
toolTrustedLocalMedia: pendingToolMediaReply?.trustedLocalMedia,
hasToolMediaBlockReply: hasToolMediaBlockReplyNow,
successfulCronAdds: replayEvidence.successfulCronAdds,
cloudCodeAssistFormatError: Boolean(state.lastAssistant?.errorMessage && isCloudCodeAssistFormatError(state.lastAssistant.errorMessage)),
compactionCount: subscription.getCompactionCount(),
compactionTokensAfter: subscription.getLastCompactionTokensAfter(),
clientToolCalls,
yieldDetected: state.yieldDetected || void 0
};
const resultEvidence = {
...result,
yieldDetected: state.yieldDetected
};
const { didSendViaMessagingTool: _coarseDelivery, ...terminalEvidence } = resultEvidence;
const hasTerminalOutput = hasAttemptTerminalState(terminalEvidence);
const pendingToolMediaPayloadCount = pendingToolMediaReply?.mediaUrls?.some((url) => url.trim().length > 0) || pendingToolMediaReply?.audioAsVoice === true ? 1 : 0;
const visibleBlockReplyCount = subscription.getVisibleBlockReplyCount();
const silentToolResultReplyPayload = resolveSilentToolResultReplyPayload({
isCronTrigger: attempt.trigger === "cron",
payloadCount: pendingToolMediaPayloadCount,
aborted: terminal.aborted,
timedOut: terminal.timedOut,
attempt: resultEvidence
});
const synthesizedPayloadCount = visibleBlockReplyCount + pendingToolMediaPayloadCount + messagingToolSourceReplyPayloads.length + (silentToolResultReplyPayload ? 1 : 0);
const emptyAssistantReplyIsSilent = shouldTreatEmptyAssistantReplyAsSilent({
terminalReplyExpectation: resolveReplyExpectation(attempt),
payloadCount: 0,
aborted: terminal.aborted,
timedOut: terminal.timedOut,
attempt: resultEvidence
});
return finalizeEmbeddedAttempt({
result: toolAutoDeliveryMediaUrls.length > 0 ? markCoreTtsAttemptResult(result, toolAutoDeliveryMediaUrls, attempt.admittedRunContext.operationalRunInstance) : result,
trajectoryRecorder,
deferredLifecycleOwner,
synthesizedPayloadCount,
emptyAssistantReplyIsSilent,
hasTerminalOutput,
silentExpected: attempt.silentExpected
});
}
//#endregion
//#region src/agents/embedded-agent-runner/provider-prompt-state.ts
const providerPromptStates = resolveGlobalSingleton(Symbol.for("openclaw.providerPromptStates"), () => /* @__PURE__ */ new Map());
/** Returns run-local retry state; restarts and new run ids intentionally have no baseline. */
function getProviderPromptState(runId) {
const state = providerPromptStates.get(runId) ?? {};
providerPromptStates.set(runId, state);
return state;
}
function clearProviderPromptState(runId) {
providerPromptStates.delete(runId);
}
const promptHashPool = resolveGlobalSingleton(Symbol.for("openclaw.providerPromptHashPool"), () => new WorkerTaskPool({
workerUrl: resolveRuntimeProcessEntrypointUrl("providerPromptState"),
workerClass: "compute",
sharedCompute: true
}));
function providerPromptWorkerBytes(value, seen = /* @__PURE__ */ new WeakSet()) {
if (value === null || typeof value !== "object") {
if (typeof value === "function" || typeof value === "symbol" || typeof value === "bigint") return;
return typeof value === "string" ? value.length * 2 : 8;
}
if (isProxy(value) || "toJSON" in value) return;
const array = Array.isArray(value);
const prototype = Object.getPrototypeOf(value);
if (array && (prototype !== Array.prototype || Object.hasOwn(value, Symbol.iterator)) || !array && prototype !== Object.prototype && prototype !== null) return;
if (seen.has(value)) return 0;
seen.add(value);
const keys = Object.keys(value);
if (array && keys.length !== value.length) return;
let bytes = 64;
for (const [index, key] of keys.entries()) {
if (array && key !== String(index)) return;
const descriptor = Object.getOwnPropertyDescriptor(value, key);
const childBytes = "value" in descriptor ? providerPromptWorkerBytes(descriptor.value, seen) : void 0;
if (childBytes === void 0) return;
bytes += key.length * 2 + 16 + childBytes;
}
return bytes;
}
/** Captures the final provider request identity without retaining payload content. */
async function recordProviderPrompt(params) {
const scope = stableStringify({
provider: params.model.provider,
api: params.model.api,
model: params.model.id,
baseUrl: params.model.baseUrl,
effectiveContextTokenBudget: params.effectiveContextTokenBudget
});
const inputBytes = providerPromptWorkerBytes(params.payload);
const task = {
payload: params.payload,
encode: params.encode === true
};
const payload = inputBytes !== void 0 ? await promptHashPool.run(() => task, {
inputBytes,
signal: params.signal
}).catch((error) => {
if (params.signal?.aborted) throw error;
return prepareProviderPrompt(task);
}) : prepareProviderPrompt(task);
const snapshot = {
scopeDigest: sha256Hex(scope),
digest: payload.digest,
byteWeight: payload.byteWeight,
...payload.cachePrefix ? { cachePrefix: payload.cachePrefix } : {}
};
const rejected = params.state.lastRejected;
if (rejected?.scopeDigest === snapshot.scopeDigest && rejected.digest === snapshot.digest) throw new Error(`Context overflow: refusing to resend the byte-identical provider payload after a context rejection (payloadBytes=${snapshot.byteWeight}).`);
params.state.lastAttempt = snapshot;
return payload.encoded;
}
function markLastProviderPromptContextRejected(state) {
const attempted = state.lastAttempt;
if (attempted) state.lastRejected = attempted;
return attempted;
}
/** Hashes the post-onPayload body for context-retry admission. */
function wrapStreamFnWithProviderPromptState(params) {
return async (model, context, options) => {
params.state.lastAttempt = void 0;
const originalOnPayload = options?.onPayload;
const observedOptions = {
...options,
onPayload: async (payload, payloadModel) => {
const replacement = await originalOnPayload?.(payload, payloadModel);
const finalPayload = replacement === void 0 ? payload : replacement;
if (modelRequestBodyState(observedOptions).enabled) return finalPayload;
await recordProviderPrompt({
state: params.state,
model: payloadModel,
payload: finalPayload,
signal: options?.signal,
effectiveContextTokenBudget: params.effectiveContextTokenBudget
});
return finalPayload;
}
};
modelRequestBodyState(observedOptions).encode = async (payload) => {
return await recordProviderPrompt({
state: params.state,
model,
payload,
signal: options?.signal,
effectiveContextTokenBudget: params.effectiveContextTokenBudget,
encode: true
});
};
if (params.recordEvent) responsesPromptObserver.set(observedOptions, (observation) => params.recordEvent?.("provider.prompt.observed", { ...observation }));
return params.streamFn(model, context, observedOptions);
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-async-tasks.ts
const DEFAULT_ASYNC_TASK_POLL_INTERVAL_MS = 500;
const COMPLETION_REQUIRED_TASK_KINDS = /* @__PURE__ */ new Set([
"image_generation",
"music_generation",
"video_generation"
]);
function createAbortError(signal) {
return createAbortError$1("aborted", { cause: signal.reason });
}
function throwIfAborted(signal) {
if (signal?.aborted) throw createAbortError(signal);
}
async function sleepWithAbort(ms, signal, sleepFn) {
if (!signal) {
await sleepFn(ms);
return;
}
throwIfAborted(signal);
await withAbortListener(signal, () => sleepFn(ms), createAbortError);
}
function isPendingCompletionTask(task) {
return COMPLETION_REQUIRED_TASK_KINDS.has(task.taskKind) && !isTerminalMediaGenerationStatus(task.status);
}
function* iterateAsyncTaskRunIds(toolMetas, sessionKey) {
for (const meta of toolMetas) {
const runId = meta.asyncStarted === true ? meta.asyncTaskRunId?.trim() : void 0;
if (runId) yield runId;
}
const normalizedSessionKey = sessionKey?.trim();
if (!normalizedSessionKey) return;
for (const task of listMediaGenerationOperations(normalizedSessionKey)) if (isPendingCompletionTask(task)) {
const runId = task.runId?.trim();
if (runId) yield runId;
}
}
function requiresCompletionRequiredAsyncTaskWait(params) {
throwIfAborted(params.abortSignal);
const sessionKey = params.sessionKey?.trim();
if (!sessionKey || !isCronRunSessionKey(sessionKey)) return false;
return iterateAsyncTaskRunIds(params.toolMetas, sessionKey).next().done === false;
}
function shouldWaitForCompletionRequiredAsyncTasks(params) {
if (params.yieldDetected === true) return false;
return requiresCompletionRequiredAsyncTaskWait(params);
}
async function waitForCompletionRequiredAsyncTasks(params) {
const now = params.now ?? Date.now;
const sleepFn = params.sleep ?? sleep;
const pollIntervalMs = params.pollIntervalMs ?? (isFastTestRuntimeEnv() ? 10 : DEFAULT_ASYNC_TASK_POLL_INTERVAL_MS);
const waitedRunIds = /* @__PURE__ */ new Set();
const timedOutRunIds = /* @__PURE__ */ new Set();
const terminalTasksByRunId = /* @__PURE__ */ new Map();
waitForTasks: while (true) {
throwIfAborted(params.abortSignal);
const runIds = [...new Set(iterateAsyncTaskRunIds(params.getToolMetas(), params.sessionKey))].filter((runId) => !waitedRunIds.has(runId));
if (runIds.length === 0) break;
for (const runId of runIds) waitedRunIds.add(runId);
let pendingRunIds = runIds;
while (pendingRunIds.length > 0) {
throwIfAborted(params.abortSignal);
pendingRunIds = pendingRunIds.filter((runId) => {
const task = findMediaGenerationOperation(runId);
if (!task || !isTerminalMediaGenerationStatus(task.status)) return true;
const taskRunId = task.runId?.trim();
if (taskRunId) terminalTasksByRunId.set(taskRunId, task);
return false;
});
if (pendingRunIds.length === 0) break;
const deadlineAtMs = params.getDeadlineAtMs();
const remainingMs = deadlineAtMs === void 0 ? pollIntervalMs : deadlineAtMs - now();
if (remainingMs <= 0) {
for (const runId of pendingRunIds) timedOutRunIds.add(runId);
break waitForTasks;
}
await sleepWithAbort(Math.min(pollIntervalMs, remainingMs), params.abortSignal, sleepFn);
}
}
return {
waitedRunIds: [...waitedRunIds],
timedOutRunIds: [...timedOutRunIds],
terminalTasks: [...terminalTasksByRunId.values()]
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/compaction-retry-aggregate-timeout.ts
function hasActiveCompactionRetryWork(params) {
return params.isCompactionInFlight || params.isSessionStreaming;
}
/**
* Waits for compaction retry completion with an aggregate timeout so a lost
* retry resolution cannot hold the session lane indefinitely.
*/
async function waitForCompactionRetryWithAggregateTimeout(params) {
const timeoutMs = resolveTimerTimeoutMs(params.aggregateTimeoutMs, 1);
const waitPromise = params.waitForCompactionRetry().then(() => ({ kind: "done" }), (error) => ({
kind: "rejected",
error
}));
while (true) {
let timer;
try {
const result = await params.abortable(Promise.race([waitPromise, new Promise((resolve) => {
timer = setTimeout(() => resolve("timeout"), timeoutMs);
})]));
if (result !== "timeout") {
if (result.kind === "done") return { timedOut: false };
throw result.error;
}
if (params.isCompactionRetryStillActive?.()) continue;
return { timedOut: true };
} finally {
if (timer !== void 0) clearTimeout(timer);
}
}
}
//#endregion
//#region src/agents/embedded-agent-runner/run/provider-review-continuation.ts
function countUserInputSlots(input) {
return input.reduce((count, item) => count + (isRecord(item) && item.role === "user" ? 1 : 0), 0);
}
/** Acknowledgment decorates one request; later tool calls retain ordinary transport behavior. */
function wrapStreamFnWithProviderReviewContinuation(params) {
const acknowledgment = params.acknowledgment;
if (!acknowledgment) return params.streamFn;
if (readProviderReviewAcknowledgment(acknowledgment).phase === "accepted") throw new Error("Provider review continuation cannot start another transport attempt");
let pendingCallStarted = false;
let acceptedByThisWrapper = false;
return async (model, context, options) => {
const transportSignals = /* @__PURE__ */ new Set();
const assertCurrent = () => {
options?.signal?.throwIfAborted();
for (const signal of transportSignals) signal.throwIfAborted();
params.assertCurrent();
readProviderReviewAcknowledgment(acknowledgment);
};
assertCurrent();
const snapshot = readProviderReviewAcknowledgment(acknowledgment);
const assertWorkStart = () => assertSessionProviderReviewWorkStart({
target: snapshot.target,
acknowledgment,
runId: params.runId,
provider: model.provider,
model: model.id,
runtimeId: "openclaw",
api: model.api,
assertCurrent
});
await assertWorkStart();
assertCurrent();
if (snapshot.phase === "accepted") {
if (!acceptedByThisWrapper) throw new Error("Provider review continuation belongs to another transport attempt");
return params.streamFn(model, context, options);
}
if (pendingCallStarted || model.provider !== "openai" || model.api !== "openai-chatgpt-responses") throw new Error("Provider review continuation cannot be retried or change transport");
const message = snapshot.review.review?.continuation?.message;
const latestIndex = context.messages.findLastIndex((item) => !hasRuntimeContextMarker(item));
const latest = context.messages[latestIndex];
if (!message || latest?.role !== "user") throw new Error("Provider review continuation requires its exact next user input");
const continuationInput = {
...latest,
content: message
};
const isContinuationInput = bindResponsesInputMessage(continuationInput);
pendingCallStarted = true;
let payloadPrepared = false;
let dispatched = false;
let acceptedResponseId;
let acceptance;
const requestOptions = {
...options,
onPayload: async (payload, payloadModel) => {
if (payloadPrepared) throw new Error("Provider review continuation cannot rebuild or retry its request");
if (!isRecord(payload) || !Array.isArray(payload.input)) throw new Error("Provider continuation payload has no user input");
const inputIndex = payload.input.findIndex(isContinuationInput);
if (inputIndex < 0) throw new Error("Provider continuation payload changed its next user input");
const inputSuffix = structuredClone(payload.input.slice(inputIndex + 1));
const userInputSlots = countUserInputSlots(payload.input);
const replacement = await options?.onPayload?.(payload, payloadModel);
await assertWorkStart();
assertCurrent();
const finalPayload = replacement === void 0 ? payload : replacement;
if (!isRecord(finalPayload) || !Array.isArray(finalPayload.input)) throw new Error("Provider continuation payload has no user input");
if (payloadModel.provider !== snapshot.review.provider || payloadModel.id !== snapshot.review.model || payloadModel.api !== snapshot.review.api || finalPayload.model !== snapshot.review.model) throw new Error("Provider continuation payload changed the reviewed runtime or model");
const continuationInputs = finalPayload.input.filter(isContinuationInput);
const nextInput = continuationInputs[0];
const nextInputIndex = finalPayload.input.indexOf(nextInput);
const finalInputSuffix = finalPayload.input.slice(nextInputIndex + 1);
if (continuationInputs.length !== 1 || !isRecord(nextInput) || nextInput.role !== "user" || !isDeepStrictEqual(finalInputSuffix, inputSuffix)) {
if (countUserInputSlots(finalPayload.input) > userInputSlots && finalInputSuffix.length > inputSuffix.length) throw new Error("Provider continuation payload added user input");
throw new Error("Provider continuation payload changed its next user input");
}
if (countUserInputSlots(finalPayload.input) > userInputSlots) throw new Error("Provider continuation payload added user input");
const rawMetadata = finalPayload.client_metadata;
if (rawMetadata !== void 0 && !isRecord(rawMetadata)) throw new Error("Provider continuation metadata is malformed");
const metadata = rawMetadata ?? {};
const rawTurnMetadata = metadata["x-codex-turn-metadata"];
let turnMetadata = {};
if (rawTurnMetadata !== void 0) {
const parsed = typeof rawTurnMetadata === "string" ? safeParseJsonRecord(rawTurnMetadata) : void 0;
if (!parsed) throw new Error("Provider continuation turn metadata is malformed");
turnMetadata = parsed;
}
payloadPrepared = true;
return {
...finalPayload,
input: finalPayload.input.with(nextInputIndex, {
...nextInput,
content: [{
type: "input_text",
text: message
}]
}),
client_metadata: {
...metadata,
"x-codex-turn-metadata": JSON.stringify({
...turnMetadata,
misalignment_override: JSON.stringify({ timestamp: Date.now() })
})
}
};
}
};
responsesRequestLifecycle.set(requestOptions, {
assertCurrent,
beforeDispatch: async (signal) => {
if (dispatched || !payloadPrepared) throw new Error("Provider review continuation already dispatched or was not prepared");
if (signal) transportSignals.add(signal);
await assertWorkStart();
assertCurrent();
if (dispatched) throw new Error("Provider review continuation already dispatched");
dispatched = true;
},
accepted: (responseId, signal) => {
if (signal) transportSignals.add(signal);
assertCurrent();
if (!dispatched || acceptedResponseId && acceptedResponseId !== responseId) throw new Error("Provider continuation response does not match its request");
if (!acceptance) {
acceptedResponseId = responseId;
acceptance = acceptProviderReviewAcknowledgment(acknowledgment, {
runId: params.runId,
assertCurrent
}).then(() => {
assertCurrent();
acceptedByThisWrapper = true;
});
}
return acceptance;
},
settle: async () => {
await acceptance;
}
});
return params.streamFn(model, {
...context,
messages: context.messages.with(latestIndex, continuationInput)
}, requestOptions);
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-stream-settle.ts
async function settleEmbeddedAttemptStream(input) {
const { attempt, activeSession, sessionManager, subscription, state } = input;
try {
if (shouldWaitForCompletionRequiredAsyncTasks({
sessionKey: attempt.sessionKey,
toolMetas: subscription.toolMetas,
yieldDetected: state.yieldAborted,
abortSignal: input.runAbortSignal
})) {
const asyncTaskWait = await waitForCompletionRequiredAsyncTasks({
getToolMetas: () => subscription.toolMetas.filter((entry) => typeof entry.toolName === "string" && entry.toolName.trim().length > 0),
sessionKey: attempt.sessionKey,
getDeadlineAtMs: () => {
const deadlineAtMs = input.getRunAbortDeadlineAtMs();
return deadlineAtMs === void 0 ? void 0 : Math.max(Date.now(), deadlineAtMs - 500);
},
abortSignal: input.runAbortSignal
});
if (asyncTaskWait.timedOutRunIds.length > 0 && !input.readLifecycleState().aborted) {
state.promptError = /* @__PURE__ */ new Error(`Timed out waiting for async task completion: ${asyncTaskWait.timedOutRunIds.join(", ")}`);
state.promptErrorSource = "prompt";
}
}
} catch (err) {
const lifecycle = input.readLifecycleState();
if (!lifecycle.timedOut && !lifecycle.aborted || !isRunnerAbortError(err)) throw err;
}
const wasCompactingBefore = activeSession.isCompacting;
const snapshot = activeSession.messages.slice();
const wasCompactingAfter = activeSession.isCompacting;
const preCompactionSnapshot = wasCompactingBefore || wasCompactingAfter ? null : snapshot;
const preCompactionSessionId = activeSession.sessionId;
const aggregateTimeoutMs = 6e4;
try {
if (input.onBlockReplyFlush) {
const currentAssistant = findCurrentAttemptAssistantMessage({
messagesSnapshot: snapshot,
prePromptMessageCount: input.prePromptMessageCount
});
const attemptAccepted = !state.promptError && !input.readLifecycleState().aborted && !input.readLifecycleState().timedOut && !state.yieldAborted && currentAssistant?.stopReason === "stop";
await joinWithRunLivenessDeadline({
joinWork: () => input.onBlockReplyFlush?.({
reason: "pre_compaction",
attemptAccepted
}),
runAbortSignal: input.runAbortSignal,
onTimeout: () => {
log$6.warn(`block-reply flush did not settle within ${RUN_LIVENESS_JOIN_TIMEOUT_MS}ms; proceeding with settlement: runId=${attempt.runId}`);
}
});
}
if ((state.yieldAborted ? { timedOut: false } : await waitForCompactionRetryWithAggregateTimeout({
waitForCompactionRetry: subscription.waitForCompactionRetry,
abortable: input.abortable,
aggregateTimeoutMs,
isCompactionRetryStillActive: () => hasActiveCompactionRetryWork({
isCompactionInFlight: subscription.isCompactionInFlight(),
isSessionStreaming: activeSession.isStreaming
})
})).timedOut) {
input.markTimedOutDuringCompaction();
if (!input.isProbeSession) log$6.warn(`compaction retry aggregate timeout (${aggregateTimeoutMs}ms): proceeding with pre-compaction state runId=${attempt.runId} sessionId=${attempt.sessionId}`);
}
} catch (err) {
if (!isRunnerAbortError(err)) throw err;
if (!state.promptError) {
state.promptError = err;
state.promptErrorSource = "compaction";
}
if (!input.isProbeSession) log$6.debug(`compaction wait aborted: runId=${attempt.runId} sessionId=${attempt.sessionId}`);
}
const captureStreamSnapshot = () => {
const { timedOutDuringCompaction } = input.readLifecycleState();
const compactionOccurredThisAttempt = subscription.getCompactionCount() > 0;
const snapshotSelection = selectCompactionTimeoutSnapshot({
timedOutDuringCompaction,
preCompactionSnapshot,
preCompactionSessionId,
currentSnapshot: activeSession.messages.slice(),
currentSessionId: activeSession.sessionId
});
if (timedOutDuringCompaction && !input.isProbeSession) log$6.warn(`using ${snapshotSelection.source} snapshot: timed out during compaction runId=${attempt.runId} sessionId=${attempt.sessionId}`);
const messagesSnapshot = snapshotSelection.messagesSnapshot;
const lastAssistant = messagesSnapshot.findLast((message) => message.role === "assistant");
const currentAttemptAssistant = findCurrentAttemptAssistantMessage({
messagesSnapshot,
prePromptMessageCount: input.prePromptMessageCount
});
const currentAttemptCompletedAssistant = subscription.getCurrentAttemptAssistant();
const attemptUsage = subscription.getUsageTotals();
const transcriptUsageSnapshot = findLatestUncompactedAttemptUsageSnapshot({
messagesSnapshot,
prePromptMessageCount: input.prePromptMessageCount,
compactionOccurred: compactionOccurredThisAttempt
});
const completedAssistantUsage = normalizeUsage(currentAttemptCompletedAssistant?.usage);
const lastCallUsage = subscription.getLastAssistantUsage() ?? (hasNonzeroUsage(completedAssistantUsage) ? completedAssistantUsage : transcriptUsageSnapshot?.usage);
const usageAssistant = hasNonzeroUsage(completedAssistantUsage) ? currentAttemptCompletedAssistant : transcriptUsageSnapshot?.assistant;
const fallbackLastCacheTouchAt = readLastCacheTtlTimestamp(sessionManager, {
provider: attempt.provider,
modelId: attempt.modelId
});
const promptCache = buildContextEnginePromptCacheInfo({
retention: input.cache.retention,
lastCallUsage,
observation: input.cache.getObservation?.(),
lastCacheTouchAt: resolvePromptCacheTouchTimestamp({
lastCallUsage,
assistantTimestamp: usageAssistant?.timestamp,
fallbackLastCacheTouchAt
})
});
return {
compactionOccurredThisAttempt,
messagesSnapshot,
sessionIdUsed: snapshotSelection.sessionIdUsed,
lastAssistant,
currentAttemptAssistant,
currentAttemptCompletedAssistant,
attemptUsage,
lastCallUsage,
promptCache
};
};
let captured;
try {
captured = await input.withOwnedTranscriptWrite(() => withSessionManagerWrite(sessionManager, async () => {
const { timedOutDuringCompaction } = input.readLifecycleState();
const compactionOccurredThisAttempt = subscription.getCompactionCount() > 0;
const cacheTtlCompat = attempt.model.compat;
await appendAttemptCacheTtlIfNeeded({
sessionManager,
timedOutDuringCompaction,
compactionOccurredThisAttempt,
config: attempt.config,
provider: attempt.provider,
modelId: attempt.modelId,
modelApi: attempt.model.api,
modelRoute: {
baseUrl: attempt.model.baseUrl,
supportsPromptCacheKey: cacheTtlCompat?.supportsPromptCacheKey
},
isCacheTtlEligibleProvider,
toolResultPromptProjectionState: input.toolResultPromptProjectionState
});
if (timedOutDuringCompaction) {
const removedEntries = await normalizeCompactionRecoveryTranscriptTail({
activeSession,
sessionManager
});
if (removedEntries > 0 && !input.isProbeSession) log$6.warn(`normalized compaction timeout transcript tail: removedEntries=${removedEntries} runId=${attempt.runId} sessionId=${attempt.sessionId}`);
}
const streamSnapshot = captureStreamSnapshot();
if (state.promptError && state.promptErrorSource === "prompt" && !streamSnapshot.compactionOccurredThisAttempt && !attempt.abortSignal?.aborted) try {
await sessionManager.appendCustomEntryAsync("openclaw:prompt-error", {
timestamp: Date.now(),
runId: attempt.runId,
sessionId: attempt.sessionId,
provider: attempt.provider,
model: attempt.modelId,
api: attempt.model.api,
error: formatErrorMessage(state.promptError)
});
} catch (entryErr) {
log$6.warn(`failed to persist prompt error entry: ${String(entryErr)}`);
}
if (input.shouldFlushForContextEngine) sessionManager.flushPendingPersistence();
return streamSnapshot;
}));
} catch (error) {
const abortedByAttempt = attempt.abortSignal?.aborted && error === attempt.abortSignal.reason;
const abortedByRun = input.runAbortSignal.aborted && error === input.runAbortSignal.reason;
if (!abortedByAttempt && !abortedByRun) throw error;
captured = captureStreamSnapshot();
}
return {
promptError: state.promptError,
promptErrorSource: state.promptErrorSource,
timedOutDuringCompaction: input.readLifecycleState().timedOutDuringCompaction,
...captured,
successfulNestedToolNames: [...input.nestedToolActivityState.successfulToolNames]
};
}
async function prepareEmbeddedAttemptTransport(input) {
const attempt = input.attempt;
const session = input.session;
const assertAdmittedCurrent = resolveAdmittedRunActiveAssertion(attempt.admittedRunContext, input.abortSignal);
const assertRunCurrent = input.assertCronRootCurrent ? () => {
assertAdmittedCurrent?.();
input.assertCronRootCurrent?.();
} : assertAdmittedCurrent;
const defaultSessionStreamFn = resolveEmbeddedAgentBaseStreamFn({ session });
const resolvedTransport = resolveExplicitSettingsTransport({
settingsManager: input.settingsManager,
sessionTransport: session.agent.transport
});
const streamExtraParamsOverride = {
...attempt.streamParams,
fastMode: attempt.fastMode,
...attempt.trigger === "memory" ? {
anthropicServerCompaction: false,
responsesServerCompaction: false
} : {}
};
const selectedAuth = attempt.runtimePlan?.auth;
const auth = selectedAuth?.selectedAuthMode ? {
mode: selectedAuth.selectedAuthMode,
authFlow: selectedAuth.selectedAuthFlow
} : void 0;
const extraParamsContext = {
extraParamsOverride: streamExtraParamsOverride,
thinkingLevel: input.providerThinkingLevel,
agentId: input.sessionAgentId,
workspaceDir: input.workspaceDir,
model: attempt.model,
resolvedTransport
};
const effectiveExtraParams = attempt.runtimePlan?.transport.resolveExtraParams(extraParamsContext) ?? resolvePreparedExtraParams({
...extraParamsContext,
cfg: attempt.config,
provider: attempt.provider,
modelId: attempt.modelId,
providerRuntimeHandle: input.getProviderRuntimeHandle(),
agentDir: input.agentDir,
auth
});
const providerStreamFn = registerProviderStreamForModel({
model: attempt.model,
cfg: attempt.config,
agentDir: input.agentDir,
workspaceDir: input.workspaceDir,
auth
});
const directProviderStreamFn = providerStreamFn ? wrapStreamFnWithMessageTransform(providerStreamFn, (messages) => messages, async ({ context, ...provider }) => {
assertRunCurrent?.();
const prepared = await materializeProviderContext({
...provider,
context,
workspaceDir: input.workspaceDir,
agentWorkspaceDir: attempt.workspaceDir,
workspaceOnly: input.workspaceOnly,
localRoots: input.workspaceOnly ? void 0 : getAgentScopedMediaLocalRoots(attempt.config ?? {}, input.sessionAgentId),
onCurrentTurnImageFailure: input.onCurrentTurnImageFailure,
sandbox: input.sandbox?.enabled && input.sandbox.fsBridge ? {
root: input.sandbox.workspaceDir,
bridge: input.sandbox.fsBridge
} : void 0
});
assertRunCurrent?.();
return prepared;
}) : void 0;
const transportApiKey = await resolveEmbeddedAgentApiKey({
provider: attempt.model.provider,
resolvedApiKey: attempt.resolvedApiKey,
authStorage: attempt.authStorage
});
const { streamFn, strategy: streamStrategy, wrapApiKey } = selectEmbeddedAgentStream({
currentStreamFn: defaultSessionStreamFn,
providerStreamFn: directProviderStreamFn,
sessionId: attempt.sessionId,
promptCacheKey: attempt.promptCacheKey,
signal: input.abortSignal,
model: attempt.model,
resolvedApiKey: attempt.resolvedApiKey,
transportAuthAvailable: Boolean(transportApiKey?.trim()),
authProfileId: attempt.runtimePlan?.auth.forwardedAuthProfileId,
authStorage: attempt.authStorage,
assertCurrent: assertRunCurrent
});
session.agent.streamFn = streamFn;
session.agent.streamFn = wrapStreamFnWithProviderPromptState({
streamFn: session.agent.streamFn,
...input.providerPromptState
});
session.agent.streamFn = wrapStreamFnWithProviderReviewContinuation({
streamFn: session.agent.streamFn,
acknowledgment: attempt.providerReviewAcknowledgment,
runId: attempt.runId,
assertCurrent: () => {
input.abortSignal.throwIfAborted();
assertRunCurrent?.();
}
});
const providerTextTransforms = resolveProviderTextTransforms({
provider: attempt.provider,
config: attempt.config,
workspaceDir: input.workspaceDir,
runtimeHandle: input.getProviderRuntimeHandle()
});
if (providerTextTransforms?.input?.length) session.agent.streamFn = wrapStreamFnTextTransforms({
streamFn: session.agent.streamFn,
input: providerTextTransforms.input,
transformSystemPrompt: false
});
const nativeWebSearchPolicyContext = {
webSearchEnabled: attempt.disableTools !== true && attempt.toolOverrides?.webSearch !== false && (!attempt.toolExecutionAllow || isToolExecutionAllowed(attempt.toolExecutionAllow, "web_search")),
runtimeToolAllowlist: attempt.toolsAllow,
sessionKey: input.sandboxSessionKey,
sandboxToolPolicy: input.sandbox?.tools,
messageProvider: attempt.messageProvider ?? attempt.messageChannel,
agentAccountId: attempt.agentAccountId,
groupId: attempt.groupId,
groupChannel: attempt.groupChannel,
groupSpace: attempt.groupSpace,
spawnedBy: attempt.spawnedBy,
senderId: attempt.senderId,
senderName: attempt.senderName,
senderUsername: attempt.senderUsername,
senderE164: attempt.senderE164
};
const { nativeWebSearchAllowedByToolPolicy } = applyExtraParamsToAgent(session.agent, attempt.config, attempt.provider, attempt.modelId, streamExtraParamsOverride, input.providerThinkingLevel, input.sessionAgentId, input.workspaceDir, attempt.model, input.agentDir, resolvedTransport, {
preparedExtraParams: effectiveExtraParams,
auth,
nativeWebSearchPolicyContext
});
if (input.codeModeControlsEnabled) session.agent.streamFn = createCodexNativeWebSearchWrapper(session.agent.streamFn, {
config: attempt.config,
agentDir: input.agentDir,
agentId: input.sessionAgentId,
...nativeWebSearchPolicyContext,
nativeWebSearchAllowedByToolPolicy,
codeModeToolSurfaceEnabled: true
});
const effectivePromptCacheRetention = resolveCacheRetention(effectiveExtraParams, attempt.provider, attempt.model.api, attempt.modelId);
const agentTransportOverride = resolveAgentTransportOverride({
settingsManager: input.settingsManager,
effectiveExtraParams
});
const effectiveAgentTransport = agentTransportOverride ?? session.agent.transport;
if (agentTransportOverride && session.agent.transport !== agentTransportOverride) {
const previousTransport = session.agent.transport;
log$6.debug(`embedded agent transport override: ${previousTransport} -> ${agentTransportOverride} (${attempt.provider}/${attempt.modelId})`);
}
session.agent.transport = effectiveAgentTransport;
const contextPruning = attempt.config?.agents?.defaults?.contextPruning;
const serverToolClearingEnabled = contextPruning?.mode === "cache-ttl" && isAnthropicServerToolClearingEnabled(attempt.model, transportApiKey);
if (serverToolClearingEnabled) {
const baseStreamFn = session.agent.streamFn;
session.agent.streamFn = (model, context, options) => {
const requestOptions = {
...options,
cacheTtlPruning: { tools: contextPruning?.tools }
};
return baseStreamFn(model, context, requestOptions);
};
}
session.agent.streamFn = wrapApiKey(session.agent.streamFn);
const runtime = attempt.preparedModelRuntime;
const profileId = attempt.authProfileId;
const credential = profileId ? attempt.authProfileStore?.profiles[profileId] : void 0;
const selectedCredential = runtime && resolveSelectedModelCredential({
provider: attempt.model.provider,
profileId,
mode: credential?.type ?? attempt.runtimePlan?.auth.selectedAuthMode
});
if (runtime?.accountCatalog && selectedCredential && selectedCredential.source !== "harness" && selectedCredential.requirement === "api-key" && attempt.model.provider === "openai" && attempt.model.api === "openai-responses") {
const record = runtime.accountCatalog.prepareServiceTierObserver({
selectedCredential,
credential
});
session.agent.streamFn = createOpenAIServiceTierObservationWrapper(session.agent.streamFn, (model, observation) => !input.abortSignal.aborted && record({
modelId: model.id,
runtimeId: "openclaw",
api: model.api,
baseUrl: model.baseUrl,
...observation
}));
}
return {
serverToolClearingEnabled,
compactionReplayEnabled: resolveCompactionReplayEligibility(attempt.model, {
extraParams: effectiveExtraParams,
apiKey: transportApiKey
}),
effectiveAgentTransport,
effectiveExtraParams,
effectivePromptCacheRetention,
providerTextTransforms,
streamStrategy
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-settle.ts
const FAILED_PROMPT_MEDIA_NOTE_TYPE = "openclaw.system-note";
const FAILED_PROMPT_MEDIA_NOTE_SOURCE = "prompt-image-hydration";
function cleanupEmbeddedAttemptStreamExecution(input) {
const { attempt, state } = input;
const terminal = projectAgentRunAttemptTerminal(state.terminal);
input.clearAttemptTimeoutTimers();
if (!input.isProbeSession && (terminal.aborted || terminal.timedOut) && !terminal.timedOutDuringCompaction) log$6.debug(`run cleanup: runId=${attempt.runId} sessionId=${attempt.sessionId} aborted=${terminal.aborted} timedOut=${terminal.timedOut}`);
let firstCleanupError;
const cleanups = [["unsubscribe", input.unsubscribe], ["backend detach", () => attempt.replyOperation?.detachBackend(input.queueHandle)]];
if (!input.deferredLifecycleOwner) cleanups.push(["active run cleanup", () => clearActiveEmbeddedRun(attempt.sessionId, input.queueHandle, attempt.sessionKey, attempt.sessionFile)]);
for (const [name, cleanup] of cleanups) try {
cleanup();
} catch (error) {
firstCleanupError ??= error instanceof Error ? error : new Error(String(error));
log$6.error(`CRITICAL: ${name} failed, possible resource leak: runId=${attempt.runId} ${String(error)}`);
}
return firstCleanupError;
}
async function runEmbeddedAttemptSettledPhase(input) {
const { attempt, state } = input;
const { sessionRuntime, toolBase } = input.prepared;
const { agentSession: { activeSession }, sessionManager, state: sessionRuntimeState, toolResultPromptProjectionState, transport: { effectivePromptCacheRetention } } = sessionRuntime;
const { nestedToolActivityState } = toolBase;
const promptState = {
contextBudgetStatus: void 0,
preflightRecovery: void 0,
yieldAborted: false
};
const preparedStreamRuntime = input.preparedStreamRuntime;
const { abortable, isProbeSession, onBlockReplyFlush, stream: preparedStream, timeout: attemptTimeout } = preparedStreamRuntime;
const { subscription, queueHandle, getBeforeAgentFinalizeRevisionReason, getBeforeAgentFinalizeRevisionEntryId } = preparedStream;
const { unsubscribe, waitForPendingEvents } = subscription;
const { getRunAbortDeadlineAtMs, clearTimers: clearAttemptTimeoutTimers } = attemptTimeout;
let settledStream;
let messagesSnapshot = [];
let sessionIdUsed = activeSession.sessionId;
const sessionFileUsed = attempt.sessionFile;
let cleanupError;
const readTerminal = () => projectAgentRunAttemptTerminal(state.terminal);
const setFailure = (error, source) => {
state.terminal = setAgentRunAttemptTerminalFailure(state.terminal, error !== null && error !== void 0 ? {
error,
source: source ?? "prompt"
} : null);
};
const markTimedOutDuringCompaction = () => {
state.terminal = mergeAgentRunAttemptTerminal(state.terminal, {
kind: "timeout",
phase: "compaction",
source: "observation"
});
};
try {
const { promptStartedAt, transcriptLeafId } = await runEmbeddedAttemptPromptPhase(input, promptState);
const isFailureFreeRunBudgetTimeout = () => {
const terminal = readTerminal();
return terminal.timedOutByRunBudget && !terminal.failed;
};
const runBudgetTimeoutTerminal = isFailureFreeRunBudgetTimeout();
const drainPendingEventsBounded = (afterRunBudgetTimeout) => joinWithRunLivenessDeadline({
joinWork: afterRunBudgetTimeout ? () => waitForPendingEvents({ includePartialReplies: false }) : waitForPendingEvents,
...afterRunBudgetTimeout ? {} : { runAbortSignal: input.runAbortController.signal },
onTimeout: () => {
log$6.warn(`pending subscription events did not settle within ${RUN_LIVENESS_JOIN_TIMEOUT_MS}ms; proceeding to stream settlement: runId=${attempt.runId}`);
}
});
if (!runBudgetTimeoutTerminal) await drainPendingEventsBounded(false);
if (runBudgetTimeoutTerminal || isFailureFreeRunBudgetTimeout()) await drainPendingEventsBounded(true);
if (isFailureFreeRunBudgetTimeout()) subscription.flushPartialAssistantText();
const beforeAgentFinalizeRevisionReason = getBeforeAgentFinalizeRevisionReason();
const beforeAgentFinalizeRevisionEntryId = getBeforeAgentFinalizeRevisionEntryId();
let rewoundBeforeAgentFinalizeRevision = false;
if (beforeAgentFinalizeRevisionReason && beforeAgentFinalizeRevisionEntryId) await input.sessionLock.withOwnedTranscriptWrite(() => withSessionManagerWrite(sessionManager, async () => {
const rejectedEntry = sessionManager.getEntry(beforeAgentFinalizeRevisionEntryId);
if (rejectedEntry?.type !== "message" || rejectedEntry.message.role !== "assistant") throw new Error(`before_agent_finalize persisted assistant entry is missing or invalid (entry=${beforeAgentFinalizeRevisionEntryId})`);
await sessionManager.appendLeafControlAsync({
targetId: rejectedEntry.parentId,
appendParentId: rejectedEntry.parentId
});
rewoundBeforeAgentFinalizeRevision = true;
}));
try {
if (input.getRepairedRejectedProviderReplay() && !rewoundBeforeAgentFinalizeRevision) activeSession.agent.state.messages = sanitizeCompactionReplayMessages(sessionManager.buildSessionContext().messages);
const settleTerminal = readTerminal();
const streamSettleState = {
promptError: settleTerminal.promptError,
promptErrorSource: settleTerminal.promptErrorSource,
yieldAborted: promptState.yieldAborted
};
try {
settledStream = await settleEmbeddedAttemptStream({
attempt,
activeSession,
sessionManager,
toolResultPromptProjectionState,
withOwnedTranscriptWrite: input.sessionLock.withOwnedTranscriptWrite,
state: streamSettleState,
getRunAbortDeadlineAtMs,
shouldFlushForContextEngine: Boolean(input.activeContextEngine && !getBeforeAgentFinalizeRevisionReason()),
subscription,
readLifecycleState: readTerminal,
markTimedOutDuringCompaction,
runAbortSignal: input.runAbortController.signal,
isProbeSession,
onBlockReplyFlush,
abortable,
prePromptMessageCount: sessionRuntimeState.prePromptMessageCount,
nestedToolActivityState,
cache: {
getObservation: preparedStreamRuntime.cache.getObservation,
retention: effectivePromptCacheRetention
}
});
} catch (error) {
setFailure(streamSettleState.promptError, streamSettleState.promptErrorSource);
throw error;
}
} finally {
if (rewoundBeforeAgentFinalizeRevision) await input.sessionLock.withOwnedTranscriptWrite(() => {
activeSession.agent.state.messages = sanitizeCompactionReplayMessages(sessionManager.buildSessionContext().messages);
});
}
setFailure(settledStream.promptError, settledStream.promptErrorSource);
if (settledStream.timedOutDuringCompaction) markTimedOutDuringCompaction();
messagesSnapshot = settledStream.messagesSnapshot;
sessionIdUsed = settledStream.sessionIdUsed;
sessionRuntimeState.promptCache = settledStream.promptCache;
await completeEmbeddedAttemptAfterTurn(input, settledStream, {
yieldAborted: promptState.yieldAborted,
transcriptLeafId,
promptStartedAt,
...beforeAgentFinalizeRevisionReason ? { beforeAgentFinalizeRevisionReason } : {}
});
const imageFailureNoteKey = sessionRuntimeState.currentTurnImageFailureCount > 0 ? `${FAILED_PROMPT_MEDIA_NOTE_SOURCE}:${sha256Hex(attempt.runId)}` : void 0;
if (imageFailureNoteKey && !activeSession.messages.some((message) => readMessageIdempotencyKey(message) === imageFailureNoteKey || message.role === "custom" && message.customType === FAILED_PROMPT_MEDIA_NOTE_TYPE && asOptionalRecord(message.details)?.source === FAILED_PROMPT_MEDIA_NOTE_SOURCE && asOptionalRecord(message.details)?.runId === attempt.runId)) {
const note = {
role: "custom",
customType: FAILED_PROMPT_MEDIA_NOTE_TYPE,
content: buildPromptImageFailureNotice(sessionRuntimeState.currentTurnImageFailureCount),
display: true,
idempotencyKey: imageFailureNoteKey,
details: {
source: FAILED_PROMPT_MEDIA_NOTE_SOURCE,
runId: attempt.runId,
failedMediaCount: sessionRuntimeState.currentTurnImageFailureCount
},
timestamp: Date.now()
};
const target = sessionManager.getSessionTarget();
const sessionId = sessionManager.getSessionId();
const assertBinding = () => {
if (sessionManager.getSessionId() !== sessionId || !sameSessionTranscriptTargetBinding(target, sessionManager.getSessionTarget())) throw new SessionTranscriptWriterClaimReboundError();
};
let committedMessageId;
try {
await input.sessionLock.withOwnedTranscriptWrite(async () => {
assertBinding();
if (target) {
const appendAndPublish = async () => {
assertBinding();
const committed = await withSessionTranscriptWriteAssertion(target, assertBinding, () => appendSessionTranscriptNote(target, note, attempt.config ? { config: attempt.config } : void 0));
committedMessageId = committed.messageId;
assertBinding();
assertOwnedTranscriptWriteCommit(target);
if (committed.appended || committed.currentTail) {
activeSession.agent.state.messages = [...activeSession.messages, committed.message];
messagesSnapshot = [...messagesSnapshot, committed.message];
}
};
if (isIncognitoSessionKey(target.sessionKey)) await withSessionManagerWrite(sessionManager, appendAndPublish);
else await appendAndPublish();
} else await withSessionManagerWrite(sessionManager, async () => {
assertBinding();
await sessionManager.appendMessageAsync(note);
assertBinding();
activeSession.agent.state.messages = [...activeSession.messages, note];
messagesSnapshot = [...messagesSnapshot, note];
});
});
} catch (error) {
if (committedMessageId && target) throw new SessionTranscriptMessageCommittedError(committedMessageId, error, target);
throw error;
}
}
} finally {
cleanupError = cleanupEmbeddedAttemptStreamExecution({
attempt,
clearAttemptTimeoutTimers,
isProbeSession,
queueHandle,
state,
unsubscribe,
deferredLifecycleOwner: preparedStreamRuntime.stream.deferredLifecycleOwner
});
}
if (cleanupError !== void 0) throw cleanupError;
const beforeAgentFinalizeRevisionReason = getBeforeAgentFinalizeRevisionReason();
const result = completeEmbeddedAttemptResult(input, settledStream, {
...promptState,
sessionIdUsed,
sessionFileUsed,
messagesSnapshot,
...beforeAgentFinalizeRevisionReason ? { beforeAgentFinalizeRevisionReason } : {}
});
state.trajectoryEndRecorded = true;
return result;
}
//#endregion
//#region packages/markdown-core/src/code-spans.ts
/** Creates the carry-forward state used when scanning inline code across chunks. */
function createInlineCodeState() {
return {
open: false,
ticks: 0
};
}
/** Builds a lookup for fenced and inline code spans while preserving scanner state. */
function buildCodeSpanIndex(text, inlineState, fenceState) {
const { spans: fenceSpans, state: nextFenceState } = scanFenceSpans(text, fenceState);
const { spans: inlineSpans, state: nextInlineState } = parseInlineCodeSpans(text, fenceSpans, inlineState ?? createInlineCodeState());
return {
inlineState: nextInlineState,
fenceState: nextFenceState,
isInside: (index) => isInsideSpan(index, fenceSpans) || isInsideSpan(index, inlineSpans)
};
}
function parseInlineCodeSpans(text, fenceSpans, initialState) {
const spans = [];
let open = initialState.open;
let ticks = initialState.ticks;
let openStart = open ? 0 : -1;
let i = 0;
let fenceIndex = 0;
while (i < text.length) {
const fence = fenceSpans[fenceIndex];
if (fence && i >= fence.start) {
i = fence.end;
fenceIndex += 1;
continue;
}
if (text[i] !== "`") {
i += 1;
continue;
}
const runStart = i;
let runLength = 0;
while (i < text.length && text[i] === "`") {
runLength += 1;
i += 1;
}
if (!open) {
open = true;
ticks = runLength;
openStart = runStart;
continue;
}
if (runLength === ticks) {
spans.push({
start: openStart,
end: i
});
open = false;
ticks = 0;
openStart = -1;
}
}
if (open) spans.push({
start: openStart,
end: text.length
});
return {
spans,
state: {
open,
ticks
}
};
}
function isInsideSpan(index, spans) {
let low = 0;
let high = spans.length;
while (low < high) {
const middle = Math.floor((low + high) / 2);
const span = spans[middle];
if (!span) return false;
if (index >= span.end) low = middle + 1;
else high = middle;
}
const span = spans[low];
return span !== void 0 && index >= span.start && index < span.end;
}
//#endregion
//#region src/agents/embedded-agent-runner/tool-send-receipts.ts
const registry$1 = createSessionManagerRuntimeRegistry();
function recordEmbeddedToolReceipt(sessionManager, toolCallId, details, includeMessageDelivery) {
const record = asOptionalRecord(details);
const snapshot = (value) => {
const valueRecord = asOptionalRecord(value);
return valueRecord ? { ...valueRecord } : value;
};
const toolSend = record?.toolSend;
const messageDelivery = includeMessageDelivery ? record?.messageDelivery : void 0;
if (toolSend === void 0 && messageDelivery === void 0) return;
const receipts = registry$1.get(sessionManager) ?? /* @__PURE__ */ new Map();
receipts.set(toolCallId, { details: {
...toolSend !== void 0 ? { toolSend: snapshot(toolSend) } : {},
...messageDelivery !== void 0 ? { messageDelivery: snapshot(messageDelivery) } : {}
} });
registry$1.set(sessionManager, receipts);
}
function consumeEmbeddedToolReceipt(sessionManager, toolCallId) {
const receipts = registry$1.get(sessionManager);
const receipt = receipts?.get(toolCallId);
if (!receipts || !receipt) return;
receipts.delete(toolCallId);
if (receipts.size === 0) registry$1.set(sessionManager, null);
return receipt;
}
//#endregion
//#region src/agents/embedded-agent-subscribe.handlers.messages.replies.ts
/**
* Owns pending assistant reply directives and tool-media handoff.
*/
function hasReplyDirectiveMetadata(parsed) {
return Boolean(parsed && (parsed.audioAsVoice || parsed.replyToId || parsed.replyToTag || parsed.replyToCurrent));
}
function mergeReplyDirectiveResults(first, second) {
if (!first) return second ?? null;
if (!second) return first;
return {
text: `${first.text ?? ""}${second.text ?? ""}`,
replyToId: second.replyToId ?? first.replyToId,
replyToCurrent: first.replyToCurrent || second.replyToCurrent,
replyToTag: first.replyToTag || second.replyToTag,
audioAsVoice: first.audioAsVoice || second.audioAsVoice || void 0,
isSilent: first.isSilent || second.isSilent
};
}
/** Moves queued tool media into a non-reasoning assistant reply payload. */
function consumePendingToolMediaIntoReply(state, payload) {
if (payload.isReasoning) return payload;
const pendingMedia = readPendingToolMediaReply(state);
if (!pendingMedia) return payload;
let mergedPayload;
if ((payload.mediaUrls ?? []).some((url) => url.trim().length > 0)) {
const metadataByUrl = new Map((pendingMedia.mediaUrls ?? []).map((url, index) => [url, pendingMedia.attachments?.[index] ?? {}]));
const selectedAttachments = (payload.mediaUrls ?? []).map((url) => metadataByUrl.get(url.trim()) ?? {});
const allSelectedMediaIsPending = (payload.mediaUrls?.length ?? 0) > 0 && (payload.mediaUrls ?? []).every((url) => metadataByUrl.has(url.trim()));
const payloadWithMetadata = payload.attachments?.length || selectedAttachments.every((entry) => Object.keys(entry).length === 0) ? payload : {
...payload,
attachments: selectedAttachments
};
mergedPayload = allSelectedMediaIsPending && (payload.mediaUrls ?? []).every((url) => state.pendingToolMediaTrustByUrl.get(url.trim()) === true) ? {
...payloadWithMetadata,
trustedLocalMedia: true
} : payloadWithMetadata;
} else mergedPayload = {
...payload,
...pendingMedia,
audioAsVoice: payload.audioAsVoice || pendingMedia.audioAsVoice || void 0,
...payload.trustedLocalMedia ? { trustedLocalMedia: true } : {}
};
state.pendingToolMediaUrls = [];
state.pendingToolMediaAttachments = [];
state.pendingToolMediaTrustByUrl.clear();
state.pendingToolAudioAsVoice = false;
return mergedPayload;
}
/** Restores reserved tool media after its outbound delivery was rejected. */
function restorePendingToolMediaReply(state, payload) {
const pendingUrls = state.pendingToolMediaUrls;
const pendingAttachments = state.pendingToolMediaAttachments ?? [];
const restoredUrls = payload.mediaUrls ?? [];
const restoredAttachments = payload.attachments ?? [];
const seen = new Set(restoredUrls);
state.pendingToolMediaUrls = [...restoredUrls, ...pendingUrls.filter((url) => !seen.has(url))];
state.pendingToolMediaAttachments = [...restoredUrls.map((_, index) => restoredAttachments[index] ?? {}), ...pendingUrls.flatMap((url, index) => seen.has(url) ? [] : [pendingAttachments[index] ?? {}])];
for (const [index, url] of restoredUrls.entries()) if (payload.trustedLocalMedia || restoredAttachments[index]?.trustedLocalMedia) state.pendingToolMediaTrustByUrl.set(url, true);
else if (!state.pendingToolMediaTrustByUrl.has(url)) state.pendingToolMediaTrustByUrl.set(url, false);
state.pendingToolAudioAsVoice ||= payload.audioAsVoice === true;
state.pendingToolMediaDeliveryFailed = true;
}
/** Reads queued tool media without clearing it. */
function readPendingToolMediaReply(state) {
if (state.pendingToolMediaUrls.length === 0 && !state.pendingToolAudioAsVoice) return null;
const seen = /* @__PURE__ */ new Set();
const mediaUrls = [];
const attachments = [];
for (const [index, url] of state.pendingToolMediaUrls.entries()) {
if (seen.has(url)) continue;
seen.add(url);
mediaUrls.push(url);
const { trustedLocalMedia: _untrustedInput, ...attachment } = state.pendingToolMediaAttachments?.[index] ?? {};
attachments.push({
...attachment,
...state.pendingToolMediaTrustByUrl.get(url) === true ? { trustedLocalMedia: true } : {}
});
}
const alignedAttachments = attachments.some((entry) => Object.keys(entry).length > 0) ? attachments : void 0;
const allPendingMediaTrusted = mediaUrls.length > 0 && mediaUrls.every((url) => state.pendingToolMediaTrustByUrl.get(url) === true);
return {
mediaUrls: mediaUrls.length ? mediaUrls : void 0,
attachments: alignedAttachments,
audioAsVoice: state.pendingToolAudioAsVoice || void 0,
...allPendingMediaTrusted ? { trustedLocalMedia: true } : {}
};
}
function recordPendingAssistantReplyDirectives(state, parsed, audioDirectiveCounts) {
if (!parsed) return;
const current = state.pendingAssistantReplyDirectives;
const audioAsVoice = Boolean(parsed.audioAsVoice || current?.audioAsVoice && audioDirectiveCounts && audioDirectiveCounts.current > (current.audioDirectiveStart ?? 0));
if (!audioAsVoice && !hasReplyDirectiveMetadata(parsed)) {
state.pendingAssistantReplyDirectives = void 0;
return;
}
state.pendingAssistantReplyDirectives = {
audioAsVoice: audioAsVoice || void 0,
...audioAsVoice && audioDirectiveCounts ? { audioDirectiveStart: current?.audioDirectiveStart ?? audioDirectiveCounts.previous } : {},
replyToId: parsed.replyToId,
replyToTag: parsed.replyToTag || void 0,
replyToCurrent: parsed.replyToCurrent || void 0
};
}
/** Merges pending reply directives into one reply payload and clears them. */
function consumePendingAssistantReplyDirectivesIntoReply(state, payload) {
if (payload.isReasoning || !state.pendingAssistantReplyDirectives) return payload;
const pending = state.pendingAssistantReplyDirectives;
state.pendingAssistantReplyDirectives = void 0;
return {
...payload,
audioAsVoice: payload.audioAsVoice || pending.audioAsVoice || void 0,
replyToId: payload.replyToId ?? pending.replyToId,
replyToTag: Boolean(payload.replyToTag || pending.replyToTag) || void 0,
replyToCurrent: Boolean(payload.replyToCurrent || pending.replyToCurrent) || void 0
};
}
function hasAssistantVisibleReply(params) {
return resolveSendableOutboundReplyParts(params).hasContent || Boolean(params.audioAsVoice);
}
/** Exact tool-owned media that the managed WebChat pipeline may hide from display text. */
function resolveManagedStreamMediaUrls(state, mediaUrls) {
return uniqueStrings(mediaUrls.filter((url) => state.pendingToolMediaTrustByUrl.get(url.trim()) === true));
}
//#endregion
//#region src/agents/embedded-agent-subscribe.handlers.compaction.ts
function emitCompactionAgentEvent(ctx, data) {
const event = {
stream: "compaction",
data
};
emitAgentEvent({
runId: ctx.params.runId,
...event
});
runBestEffortCallback({
label: "compaction agent event",
log: ctx.log,
callback: () => ctx.params.onAgentEvent?.(event)
});
}
function runBestEffortCompactionHook(ctx, phase) {
if (ctx.state.unsubscribed || ctx.params.isTerminalAborted?.()) return;
const hookRunner = getGlobalHookRunner();
const hookName = phase === "before" ? "before_compaction" : "after_compaction";
if (!hookRunner?.hasHooks(hookName)) return;
const metrics = {
messageCount: ctx.params.session.messages?.length ?? 0,
sessionFile: ctx.params.session.sessionFile
};
const context = { sessionKey: ctx.params.sessionKey };
(phase === "before" ? hookRunner.runBeforeCompaction({
...metrics,
messages: ctx.params.session.messages
}, context) : hookRunner.runAfterCompaction({
...metrics,
compactedCount: ctx.getCompactionCount()
}, context)).catch((err) => {
ctx.log.warn(`${hookName} hook failed: ${String(err)}`);
});
}
function handleCompactionStart(ctx, evt) {
const rawReason = evt.reason;
const reason = rawReason === "manual" || rawReason === "threshold" || rawReason === "overflow" ? rawReason : "threshold";
const kind = reason === "manual" ? "manual compaction" : "auto-compaction";
ctx.state.compactionInFlight = true;
ctx.state.livenessState = "paused";
ctx.ensureCompactionPromise();
ctx.log.info(`embedded run ${kind} start`, {
event: "embedded_run_compaction_start",
runId: ctx.params.runId,
reason,
consoleMessage: `embedded run ${kind} start: runId=${ctx.params.runId} reason=${reason}`
});
emitCompactionAgentEvent(ctx, {
phase: "start",
...evt.itemId ? { itemId: evt.itemId } : {}
});
runBestEffortCompactionHook(ctx, "before");
}
function handleCompactionEnd(ctx, evt) {
const reason = evt.reason;
const kind = reason === "manual" ? "manual compaction" : "auto-compaction";
const outcome = evt.outcome;
ctx.state.compactionInFlight = false;
const completed = outcome.status === "completed";
const willRetry = completed && outcome.willRetry;
let recording;
if (completed) {
ctx.incrementCompactionCount();
ctx.noteCompactionTokensAfter(outcome.tokensAfter);
const observedCompactionCount = ctx.getCompactionCount();
if (ctx.params.sessionPersistence !== "detached") recording = recordSessionCompacted({
sessionKey: ctx.params.sessionKey,
operationId: `${ctx.params.runId}:${observedCompactionCount}`,
agentId: ctx.params.agentId,
runId: ctx.params.runId
});
ctx.log.info(`embedded run ${kind} complete`, {
event: "embedded_run_compaction_end",
runId: ctx.params.runId,
reason,
completed: true,
willRetry,
compactionCount: observedCompactionCount,
consoleMessage: `embedded run ${kind} complete: runId=${ctx.params.runId} reason=${reason} compactionCount=${observedCompactionCount} willRetry=${willRetry}`
});
if (ctx.params.compactionCountOwner !== "caller" && ctx.params.sessionPersistence !== "detached") import("./embedded-agent-subscribe.handlers.compaction.runtime.js").then(({ default: reconcile }) => reconcile({
sessionKey: ctx.params.sessionKey,
agentId: ctx.params.agentId,
configStore: ctx.params.config?.session?.store,
observedCompactionCount
})).catch((err) => {
ctx.log.warn(`late compaction count reconcile failed: ${String(err)}`);
});
}
if (willRetry) {
ctx.noteCompactionRetry();
ctx.resetForCompactionRetry();
ctx.log.debug(`embedded run compaction retry: runId=${ctx.params.runId}`);
} else {
if (outcome.status !== "aborted") ctx.state.livenessState = "working";
ctx.maybeResolveCompactionWait();
const messages = ctx.params.session.messages;
if (completed && Array.isArray(messages)) stripStaleAssistantUsageBeforeLatestCompaction(messages, {
mutate: true,
whenMissingCompactionSummary: "zeroAssistantUsage"
});
}
const outcomeReason = outcome.status === "skipped" || outcome.status === "failed" ? outcome.reason : void 0;
if (!completed) {
const reasonClass = outcomeReason ? classifyCompactionReason(outcomeReason) : "aborted";
const reasonDetail = reasonClass === "unknown" ? formatUnknownCompactionReasonDetail(outcomeReason) : void 0;
const metadata = {
event: "embedded_run_compaction_end",
runId: ctx.params.runId,
reason,
outcome: outcome.status,
completed: false,
willRetry: false,
reasonClass,
...outcomeReason ? { outcomeReason } : {},
...reasonDetail ? { reasonDetail } : {},
consoleMessage: `embedded run ${kind} ${outcome.status}: runId=${ctx.params.runId} reason=${reasonClass}${reasonDetail ? ` detail=${reasonDetail}` : ""}`
};
const benign = outcome.status === "aborted" || outcome.status === "skipped" && (reasonClass === "no_compactable_entries" || reasonClass === "below_threshold" || reasonClass === "already_compacted");
ctx.log[benign ? "info" : "warn"](`embedded run ${kind} ${outcome.status}`, metadata);
}
emitCompactionAgentEvent(ctx, {
phase: "end",
...evt.itemId ? { itemId: evt.itemId } : {},
completed,
willRetry,
outcome: outcome.status,
...outcomeReason ? { reason: outcomeReason } : {}
});
if (completed && !willRetry) runBestEffortCompactionHook(ctx, "after");
return recording;
}
//#endregion
//#region src/agents/embedded-agent-subscribe.handlers.lifecycle.ts
/**
* Handles lifecycle and compaction events from subscribed embedded-agent sessions.
*/
function runTerminalHook(callback, failed) {
let result;
try {
result = callback();
} catch (error) {
failed(error);
return;
}
return isPromiseLike(result) ? Promise.resolve(result).catch((error) => {
failed(error);
}) : result;
}
function emitLifecycleAgentEvent(ctx, data, eventData = data) {
emitAgentEvent({
runId: ctx.params.runId,
...ctx.params.sessionKey ? { sessionKey: ctx.params.sessionKey } : {},
...ctx.params.sessionId ? { sessionId: ctx.params.sessionId } : {},
...ctx.params.agentId ? { agentId: ctx.params.agentId } : {},
...ctx.params.lifecycleGeneration ? { lifecycleGeneration: ctx.params.lifecycleGeneration } : {},
stream: "lifecycle",
data: eventData
});
runBestEffortCallback({
label: "lifecycle agent event",
log: ctx.log,
callback: () => ctx.params.onAgentEvent?.({
stream: "lifecycle",
data
})
});
}
function handleAgentStart(ctx) {
ctx.state.deferBlockReplyDelivery = typeof ctx.params.onBeforeTerminalDelivery === "function" && ctx.params.deferTerminalDelivery !== false;
ctx.log.debug(`embedded run agent start: runId=${ctx.params.runId}`);
emitLifecycleAgentEvent(ctx, {
phase: "start",
startedAt: Date.now()
});
}
function handleAgentEnd(ctx, evt) {
ctx.state.liveEditDiffStateById.clear();
const lastAssistant = ctx.state.lastAssistant;
const isError = isAssistantMessage(lastAssistant) && lastAssistant.stopReason === "error";
let lifecycleErrorText;
let errorObservation;
const hasStreamedAssistantVisibleText = Array.isArray(ctx.state.assistantTexts) && ctx.state.assistantTexts.some((text) => hasAssistantVisibleReply({ text }));
const completedAssistantFallbackText = isAssistantMessage(lastAssistant) && lastAssistant.stopReason !== "error" && lastAssistant.stopReason !== "aborted" ? resolveFinalAssistantVisibleText(lastAssistant) : void 0;
const hasAssistantVisibleText = hasStreamedAssistantVisibleText || hasAssistantVisibleReply({ text: completedAssistantFallbackText ?? "" });
const hadLivenessPreservingSideEffect = ctx.state.hadDeterministicSideEffect === true || hasCommittedMessagingToolDeliveryEvidence(ctx.state) || hasAcceptedSessionSpawn(ctx.state.acceptedSessionSpawns) || ctx.state.heartbeatToolResponse !== void 0 || (ctx.state.successfulCronAdds ?? 0) > 0;
const deferredMediaUrls = ctx.state.deferredBlockReplies.flatMap((payload) => payload.mediaUrls ?? []);
const hasTerminalOutput = hasAttemptTerminalState({
yieldDetected: ctx.state.yielded,
didSendDeterministicApprovalPrompt: ctx.state.deterministicApprovalPromptSent,
heartbeatToolResponse: ctx.state.heartbeatToolResponse,
lastToolError: ctx.state.lastToolError,
toolMediaUrls: [...ctx.state.pendingToolMediaUrls, ...deferredMediaUrls],
toolAudioAsVoice: ctx.state.pendingToolAudioAsVoice || ctx.state.deferredBlockReplies.some((payload) => payload.audioAsVoice),
hasToolMediaBlockReply: ctx.state.hasToolMediaBlockReply,
didDeliverSourceReplyViaMessageTool: ctx.state.messageToolOnlySourceReplyDelivered || ctx.params.hasDeliveredMessageToolOnlySourceReply?.() === true,
messagingToolSourceReplyPayloads: ctx.state.messagingToolSourceReplyPayloads,
messagingToolSentTexts: ctx.state.messagingToolSentTexts,
messagingToolSentMediaUrls: ctx.state.messagingToolSentMediaUrls,
messagingToolSentTargets: ctx.state.messagingToolSentTargets,
successfulCronAdds: ctx.state.successfulCronAdds,
acceptedSessionSpawns: ctx.state.acceptedSessionSpawns,
toolMetas: ctx.state.toolMetas
});
const hadBeforeFinalizeSideEffect = hadLivenessPreservingSideEffect || ctx.state.replayState.hadPotentialSideEffects;
const incompleteTerminalAssistant = isIncompleteTerminalAssistantTurn({
hasAssistantVisibleText,
hasTerminalOutput,
lastAssistant: isAssistantMessage(lastAssistant) ? lastAssistant : null
});
const replayInvalid = ctx.state.replayState.replayInvalid || incompleteTerminalAssistant ? true : void 0;
const derivedWorkingTerminalState = isError ? "blocked" : replayInvalid && !hadLivenessPreservingSideEffect && (!hasAssistantVisibleText || incompleteTerminalAssistant) ? "abandoned" : ctx.state.livenessState;
const livenessState = ctx.state.livenessState === "working" ? derivedWorkingTerminalState : ctx.state.livenessState;
if (isError && lastAssistant) {
const rawError = lastAssistant.errorMessage?.trim();
const failoverReason = classifyAssistantFailoverReason(lastAssistant, { providerOwner: ctx.params.providerOwner ?? null });
const errorText = formatUserFacingAssistantErrorText(lastAssistant, {
cfg: ctx.params.config,
sessionKey: ctx.params.sessionKey,
agentId: ctx.params.agentId,
provider: lastAssistant.provider,
model: lastAssistant.model,
providerOwner: ctx.params.providerOwner
});
const observedError = buildApiErrorObservationFields(rawError, {
provider: lastAssistant.provider,
providerOwner: ctx.params.providerOwner
});
const safeErrorText = buildTextObservationFields(errorText, { provider: lastAssistant.provider }).textPreview ?? "LLM request failed.";
lifecycleErrorText = safeErrorText;
errorObservation = projectChatErrorDetail({
provider: lastAssistant.provider,
model: lastAssistant.model,
failoverReason,
...observedError,
httpStatus: observedError.httpCode ? Number(observedError.httpCode) : void 0
});
const safeRunId = sanitizeForConsole(ctx.params.runId) ?? "-";
const safeModel = sanitizeForConsole(lastAssistant.model) ?? "unknown";
const safeProvider = sanitizeForConsole(lastAssistant.provider) ?? "unknown";
const safeRawErrorPreview = sanitizeForConsole(observedError.rawErrorPreview);
const rawErrorConsoleSuffix = safeRawErrorPreview && !shouldSuppressRawErrorConsoleSuffix(observedError.providerRuntimeFailureKind) ? ` rawError=${safeRawErrorPreview}` : "";
ctx.log.warn("embedded run agent end", {
event: "embedded_run_agent_end",
tags: [
"error_handling",
"lifecycle",
"agent_end",
"assistant_error"
],
runId: ctx.params.runId,
isError: true,
error: safeErrorText,
failoverReason,
model: lastAssistant.model,
provider: lastAssistant.provider,
...observedError,
consoleMessage: `embedded run agent end: runId=${safeRunId} isError=true model=${safeModel} provider=${safeProvider} error=${safeErrorText}${rawErrorConsoleSuffix}`
});
} else ctx.log.debug(`embedded run agent end: runId=${ctx.params.runId} isError=${isError}`);
const emitLifecycleTerminal = () => {
finalizeToolActivity(ctx);
const terminalStopReason = ctx.params.resolveTerminalStopReason?.() ?? ctx.state.terminalStopReason ?? (!isError && isAssistantMessage(lastAssistant) ? lastAssistant.stopReason : void 0);
const terminalAborted = typeof ctx.state.terminalAborted === "boolean" ? ctx.state.terminalAborted : ctx.params.isTerminalAborted?.();
const toolErrorSummary = terminalAborted === true && ctx.state.lastToolError ? summarizeToolValidationError(ctx.state.lastToolError) : void 0;
const data = {
phase: ctx.params.terminalLifecyclePhase === "finishing" ? "finishing" : isError ? "error" : "end",
...isError ? { error: lifecycleErrorText ?? "LLM request failed." } : {},
...errorObservation ? { errorObservation } : {},
...terminalStopReason ? { stopReason: terminalStopReason } : {},
...ctx.state.yielded === true ? { yielded: true } : {},
...ctx.state.timeoutPhase ? { timeoutPhase: ctx.state.timeoutPhase } : {},
...typeof ctx.state.providerStarted === "boolean" ? { providerStarted: ctx.state.providerStarted } : {},
...typeof terminalAborted === "boolean" ? { aborted: terminalAborted } : {},
...toolErrorSummary ? { toolErrorSummary } : {},
...livenessState ? { livenessState } : {},
...replayInvalid ? { replayInvalid } : {}
};
emitLifecycleAgentEvent(ctx, data, {
...data,
endedAt: Date.now()
});
};
const finalizeAgentEnd = () => {
if (ctx.state.pendingCompactionRetry > 0) ctx.resolveCompactionRetry();
else ctx.maybeResolveCompactionWait();
};
const flushPendingMediaAndChannel = () => {
if (ctx.params.onBlockReply && !ctx.state.pendingToolMediaDeliveryFailed) {
const pendingToolMediaReply = readPendingToolMediaReply(ctx.state);
if (pendingToolMediaReply && hasAssistantVisibleReply(pendingToolMediaReply)) ctx.emitBlockReply(pendingToolMediaReply);
}
const flushChannel = () => {
const result = ctx.params.onBlockReplyFlush?.({ reason: "terminal" });
return isPromiseLike(result) ? result : void 0;
};
const postMediaFlushResult = ctx.flushBlockReplyBuffer();
return isPromiseLike(postMediaFlushResult) ? postMediaFlushResult.then(flushChannel) : flushChannel();
};
const rethrowAfterLifecycleTerminal = (error) => {
const emitted = emitLifecycleTerminalOnce();
if (isPromiseLike(emitted)) return Promise.resolve(emitted).then(() => {
throw error;
});
throw error;
};
let lifecycleTerminalEmitted = false;
const emitLifecycleTerminalOnce = () => {
if (lifecycleTerminalEmitted) return;
lifecycleTerminalEmitted = true;
const beforeLifecycleTerminal = runTerminalHook(() => ctx.params.onBeforeLifecycleTerminal?.(), (err) => ctx.log.debug(`before lifecycle terminal failed: ${String(err)}`));
if (isPromiseLike(beforeLifecycleTerminal)) return Promise.resolve(beforeLifecycleTerminal).then(emitLifecycleTerminal);
emitLifecycleTerminal();
};
const applyBeforeTerminalDecision = (decision) => {
if (decision?.suppressTerminalDelivery === true) {
ctx.clearAssistantStream();
ctx.clearDeferredBlockReplies();
finalizeAgentEnd();
return;
}
if (decision?.continueCurrentTurn === true) {
ctx.releaseDeferredReplies();
finalizeAgentEnd();
return ctx.flushBlockReplyBuffer();
}
try {
ctx.releaseDeferredReplies();
const flushBlockReplyBufferResult = ctx.flushBlockReplyBuffer({ final: true });
finalizeAgentEnd();
const flushPendingMediaAndChannelResult = isPromiseLike(flushBlockReplyBufferResult) ? Promise.resolve(flushBlockReplyBufferResult).then(flushPendingMediaAndChannel) : flushPendingMediaAndChannel();
if (isPromiseLike(flushPendingMediaAndChannelResult)) return Promise.resolve(flushPendingMediaAndChannelResult).then(emitLifecycleTerminalOnce, rethrowAfterLifecycleTerminal);
return emitLifecycleTerminalOnce();
} catch (error) {
return rethrowAfterLifecycleTerminal(error);
}
};
const beforeTerminalDelivery = runTerminalHook(() => {
const answerAssistant = ctx.state.keptAnswer?.assistant ?? lastAssistant;
return ctx.params.onBeforeTerminalDelivery?.({
messages: evt?.messages ?? [],
willRetry: evt?.willRetry === true,
...evt?.assistantEntryId ? { assistantEntryId: evt.assistantEntryId } : {},
...answerAssistant ? { lastAssistant: answerAssistant } : {},
assistantTexts: ctx.state.assistantTexts,
hasAssistantVisibleText,
isError,
incompleteTerminalAssistant,
hadDeterministicSideEffect: hadBeforeFinalizeSideEffect,
hasPendingContinuation: ctx.state.yielded || ctx.state.deterministicApprovalPromptPending || ctx.state.deterministicApprovalPromptSent || hasCompletionMessageSessionSpawn(ctx.state.acceptedSessionSpawns) || hasAsyncActivity(ctx.state.toolMetas)
});
}, (error) => ctx.log.warn(`before terminal delivery failed: ${String(error)}`));
if (isPromiseLike(beforeTerminalDelivery)) return Promise.resolve(beforeTerminalDelivery).then(applyBeforeTerminalDecision);
return applyBeforeTerminalDecision(beforeTerminalDelivery);
}
//#endregion
//#region src/agents/embedded-agent-subscribe.handlers.messages.stream.ts
function isSubscribeTranscriptOnlyOpenClawAssistantMessage(message) {
if (!message || message.role !== "assistant") return false;
const provider = normalizeOptionalString(message.provider) ?? "";
const model = normalizeOptionalString(message.model) ?? "";
return provider === "openclaw" && (model === "delivery-mirror" || model === "gateway-injected");
}
function readAssistantMessageApi(message) {
return message?.role === "assistant" ? normalizeOptionalString(message.api) : void 0;
}
function isResponsesApiAssistantMessage(message) {
return OPENAI_RESPONSES_APIS.has(readAssistantMessageApi(message) ?? "");
}
function isOpenAiCompletionsAssistantMessage(message) {
const api = readAssistantMessageApi(message);
return api === "openai-completions" || api === "openclaw-openai-completions-transport";
}
function isAssistantTextPhasePending(message, eventType) {
const api = readAssistantMessageApi(message);
return api === "ollama" || isOpenAiCompletionsAssistantMessage(message) || api === "anthropic-messages" && eventType !== "text_end";
}
function extractStandaloneMessageToolText(text, params = {}) {
if (!params.allowCurrentSourceReply && !params.allowRoutedReply) return;
const record = safeParseJsonRecord(text.trim());
const args = asOptionalRecord(record?.arguments);
const hasRoute = Boolean(normalizeOptionalString(args?.target) || normalizeOptionalString(args?.to) || normalizeOptionalString(args?.channel) || normalizeOptionalString(args?.accountId) || Array.isArray(args?.targets));
if (normalizeOptionalString(record?.name) !== "message" || normalizeOptionalString(args?.action) !== "send" || (hasRoute ? !params.allowRoutedReply : !params.allowCurrentSourceReply)) return;
return normalizeOptionalString(args?.message);
}
function isAssistantTextBlock(value) {
return asOptionalObjectRecord(value)?.type === "text";
}
function resolveAssistantStreamItemId(params) {
const content = params.message?.content;
if (!Array.isArray(content)) return;
const contentIndex = resolveAssistantStreamContentIndex(params.contentIndex);
const hasIndexedTextBlock = isAssistantTextBlock(contentIndex !== void 0 ? content[contentIndex] : void 0);
const candidateStart = hasIndexedTextBlock && contentIndex !== void 0 ? contentIndex : content.length - 1;
const candidateEnd = hasIndexedTextBlock ? candidateStart : 0;
for (let index = candidateStart; index >= candidateEnd; index -= 1) {
const block = content[index];
if (!isAssistantTextBlock(block)) continue;
const signature = parseAssistantTextSignature(block);
if (signature?.id) return signature.id;
}
}
function resolveAssistantStreamContentIndex(value) {
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
}
function resolveAssistantStreamBlockIndex(message, contentIndex, itemId) {
if (!Array.isArray(message.content)) return;
if (isAssistantTextBlock(contentIndex === void 0 ? void 0 : message.content[contentIndex])) return contentIndex;
const index = itemId ? message.content.findLastIndex((candidate) => isAssistantTextBlock(candidate) && parseAssistantTextSignature(candidate)?.id === itemId) : -1;
return index >= 0 ? index : void 0;
}
function scopeAssistantMessageToStreamBlock(message, contentIndex, itemId) {
const index = resolveAssistantStreamBlockIndex(message, contentIndex, itemId);
if (index === void 0 || !Array.isArray(message.content)) return message;
const block = message.content[index];
if (!block) return message;
return {
...message,
content: [block]
};
}
function emitAssistantCommentaryStreamData(ctx, message, finalMessage = false, preparedText) {
const isResponsesCommentary = isResponsesApiAssistantMessage(message);
const { lastAssistantStreamContentIndex: index, lastAssistantStreamItemId: itemId } = ctx.state;
const commentaryMessage = isResponsesCommentary ? scopeAssistantMessageToStreamBlock(message, index, itemId) : message;
const text = !isResponsesCommentary && preparedText !== void 0 ? preparedText : extractAssistantCommentaryText(commentaryMessage);
if (text && (finalMessage || !isResponsesCommentary || ctx.state.deltaBuffer !== text)) {
const commentaryItemId = isResponsesCommentary ? itemId : resolveAssistantStreamItemId({ message });
ctx.emitAssistantStreamData({
text,
delta: "",
replace: true,
phase: "commentary",
itemId: commentaryItemId
}, { finalMessage });
}
}
function emitReasoningEnd(ctx) {
if (!ctx.state.reasoningStreamOpen) return;
ctx.flushAssistantStream();
ctx.state.reasoningStreamOpen = false;
runBestEffortCallback({
label: "reasoning end",
log: ctx.log,
callback: () => ctx.params.onReasoningEnd?.()
});
}
function emitPersistentReasoning(ctx, text) {
if (!ctx.state.includeReasoning || !text || !ctx.params.onBlockReply || ctx.params.silentExpected || shouldSuppressDeterministicApprovalOutput(ctx.state) || hasMessageToolOnlySourceDelivery(ctx) || text === ctx.state.lastReasoningSent) return;
const previous = ctx.state.lastReasoningSent;
const pending = previous && text.startsWith(previous) ? text.slice(previous.length).trimStart() : text;
ctx.state.lastReasoningSent = text;
if (pending) ctx.emitBlockReply({
text: pending,
isReasoning: true
});
}
function emitAssistantMessageStart(ctx) {
ctx.flushAssistantStream();
runBestEffortCallback({
label: "assistant message start",
log: ctx.log,
callback: () => ctx.params.onAssistantMessageStart?.()
});
}
function openReasoningStream(ctx) {
if (!ctx.state.reasoningStreamOpen) ctx.flushAssistantStream();
ctx.state.reasoningStreamOpen = true;
}
function shouldSuppressDeterministicApprovalOutput(state) {
return state.deterministicApprovalPromptPending || state.deterministicApprovalPromptSent;
}
function hasMessageToolOnlySourceDelivery(ctx) {
return ctx.params.sourceReplyDeliveryMode === "message_tool_only" && (ctx.state.messageToolOnlySourceReplyDelivered || ctx.params.hasDeliveredMessageToolOnlySourceReply?.() === true || (ctx.state.messagingToolSourceReplyPayloads?.length ?? 0) > 0);
}
function resolveAssistantTextChunk(params) {
const { evtType, delta, content, accumulatedText } = params;
if (evtType === "text_delta" || delta) return delta;
if (!content) return "";
if (content.startsWith(accumulatedText)) return content.slice(accumulatedText.length);
return accumulatedText.includes(content) ? "" : content;
}
function resolveStreamingReply(params) {
let directiveCodePrefix = params.appendDelta !== null ? params.directiveCodePrefix : void 0;
if (!params.parsedStreamDirectives && params.evtType === "text_delta") {
const text = params.previousCleaned;
return {
text,
delta: "",
replace: false,
hasText: Boolean(text.trim()),
replyDirectives: null,
audioDirectiveCount: params.previousAudioDirectiveCount,
directiveCodePrefix: directiveCodePrefix && params.rawDirectiveSource?.length === directiveCodePrefix.checkedRawLength ? directiveCodePrefix : void 0
};
}
let text;
let delta;
let isAppend = false;
let replyDirectives = params.parsedStreamDirectives;
let audioDirectiveCount = params.previousAudioDirectiveCount;
if (directiveCodePrefix && params.rawDirectiveSource?.includes("[[", Math.max(0, directiveCodePrefix.checkedRawLength - 1))) directiveCodePrefix = void 0;
if (params.evtType !== "text_end" && params.parsedStreamDirectives && !params.parsedStreamDirectives.isSilent && !hasReplyDirectiveMetadata(params.parsedStreamDirectives) && !/(?:^|\n)[^\S\n]*MEDIA:\s*\S[^\n]*(?:\n|$)/i.test(params.visibleDelta) && params.parsedStreamDirectives.text === params.visibleDelta && params.appendDelta !== null && params.previousText === params.previousCleaned && (directiveCodePrefix || !params.rawDirectiveSource?.includes("[["))) {
text = params.next;
delta = params.appendDelta;
isAppend = true;
if (directiveCodePrefix && params.rawDirectiveSource !== void 0) directiveCodePrefix = {
...directiveCodePrefix,
checkedRawLength: params.rawDirectiveSource.length
};
}
if (text === void 0 && params.evtType !== "text_end" && params.parsedStreamDirectives && !params.parsedStreamDirectives.isSilent && params.parsedStreamDirectives.text === params.visibleDelta && params.appendDelta !== null && params.previousCleaned && params.previousText.length > params.previousCleaned.length && !/(?:^|\n)[^\S\n]*MEDIA:\s*\S[^\n]*(?:\n|$)/i.test(params.visibleDelta)) {
const marker = params.previousText.lastIndexOf("[[");
const tailStart = params.previousText.indexOf("]]", marker + 2) + 2;
const tail = params.previousText.slice(tailStart);
const source = params.rawDirectiveSource ?? params.next;
if (marker >= 0 && tailStart > marker + 2 && tail.trim() && params.previousCleaned.endsWith(tail.trimStart()) && !/[`~\t]| {4}/u.test(params.previousText.slice(0, marker)) && !source.includes("[[", params.previousText.length - 1) && !source.includes("]:")) {
text = params.previousCleaned + params.appendDelta;
delta = params.appendDelta;
isAppend = true;
}
}
if (text === void 0) {
directiveCodePrefix = void 0;
audioDirectiveCount = 0;
const source = params.rawDirectiveSource === void 0 ? params.next : stripDowngradedToolCallText(params.rawDirectiveSource);
const parsed = parseReplyDirectives(params.evtType === "text_end" ? source : splitTrailingDirective(source).text, { onAudioDirective: () => {
audioDirectiveCount += 1;
} });
text = params.rawDirectiveSource === void 0 ? parsed.text : trimTextPreservingCode(parsed.text);
if (params.evtType !== "text_end" && params.appendDelta !== null && source === params.rawDirectiveSource && text === params.next && !parsed.isSilent && !hasReplyDirectiveMetadata(parsed) && !parsed.mediaUrls?.length) {
const prefix = findDirectiveCodePrefix(source, params.visibleDelta);
if (prefix && params.next.startsWith(source.slice(0, prefix.end))) directiveCodePrefix = prefix;
}
if (replyDirectives) replyDirectives = {
...replyDirectives,
replyToId: parsed.replyToId,
replyToCurrent: parsed.replyToCurrent,
replyToTag: parsed.replyToTag,
audioAsVoice: audioDirectiveCount > params.previousAudioDirectiveCount
};
}
const replace = Boolean(!isAppend && params.previousCleaned && !text.startsWith(params.previousCleaned));
return {
text,
delta: replace ? "" : delta ?? text.slice(params.previousCleaned.length),
replace,
hasText: Boolean(isAppend ? text : text.trim()),
replyDirectives,
audioDirectiveCount,
directiveCodePrefix
};
}
//#endregion
//#region src/agents/embedded-agent-subscribe.handlers.messages.snapshot.ts
function extractAssistantStreamSnapshot(ctx, message, options) {
let observedMessage = message;
if (options?.throughIndex !== void 0 && Array.isArray(message.content)) {
const content = message.content.slice(0, options.throughIndex + 1);
if (options.observedText !== void 0) {
const current = content[options.throughIndex];
if (current?.type === "text") content[options.throughIndex] = {
type: "text",
text: options.observedText,
textSignature: current.textSignature
};
else if (content.length === 0) content.push({
type: "text",
text: options.observedText
});
}
observedMessage = {
...message,
content
};
} else if (options?.observedText !== void 0) observedMessage = {
...message,
content: [{
type: "text",
text: options.observedText
}]
};
const state = {
reasoningDepth: 0,
final: false,
inlineCode: createInlineCodeState()
};
let rawText = "";
let blockSource = "";
let finalAnswer = true;
const parts = [];
const renderText = prepareAssistantVisibleText(observedMessage, (part, final, phase, index) => {
const separator = rawText && !state.pendingTagFragment && !state.pendingFenceFragment ? "\n" : "";
parts.push({
separator,
index
});
rawText += `${separator}${part}`;
const preparedFinal = phase === "final_answer" && !ctx.params.enforceFinalTag;
finalAnswer &&= preparedFinal;
const visible = preparedFinal ? `${separator}${part}` : ctx.stripBlockTags(`${separator}${part}`, state, { final: final && options?.final !== false });
blockSource += visible;
return preparedFinal ? part : visible;
});
const visibleBlockSource = finalAnswer ? sanitizeAssistantVisibleStreamText(blockSource, "final_answer", { preserveTrailingWhitespace: true }) : stripDowngradedToolCallText(blockSource, { preserveTrailingWhitespace: true });
const blockReply = parseReplyDirectives(options?.final === false ? splitTrailingDirective(visibleBlockSource, { preserveTrailingWhitespace: true }).text : visibleBlockSource, { preserveTrailingWhitespace: true });
let text;
return {
get text() {
return text ??= renderText();
},
rawText,
state,
parts,
blockText: blockReply.text,
message: observedMessage
};
}
/** Reconcile one prepared source frame without translating raw or rendered offsets. */
function reconcileBlockReplySnapshot(ctx, previous, next) {
const prefixAt = (snapshot, index) => {
if (index === 0) return "";
if (index > (snapshot.parts.at(-1)?.index ?? 0)) return snapshot.blockText;
return extractAssistantStreamSnapshot(ctx, snapshot.message, {
throughIndex: index,
observedText: "",
final: false
}).blockText;
};
const scope = ctx.state.blockReplyScopeStart;
const scopePosition = (snapshot) => {
if (!scope) return {
index: 0,
valid: true
};
const indexed = resolveAssistantStreamBlockIndex(snapshot.message, scope.contentIndex, void 0);
const anchor = indexed !== void 0 && (!scope.itemId || resolveAssistantStreamItemId({
contentIndex: indexed,
message: snapshot.message
}) === scope.itemId) ? indexed : resolveAssistantStreamBlockIndex(snapshot.message, void 0, scope.itemId) ?? indexed;
return {
index: (anchor ?? scope.contentIndex) + (scope.after ? 1 : 0),
valid: anchor !== void 0
};
};
const oldScope = scopePosition(previous);
const nextScope = scopePosition(next);
const origin = oldScope.index;
const oldPrefix = prefixAt(previous, origin);
const nextPrefix = prefixAt(next, nextScope.index);
const oldText = previous.blockText.slice(oldPrefix.length);
const nextText = next.blockText.slice(nextPrefix.length);
const consumed = ctx.blockChunker.consumedLength;
let oldCode;
let nextCode;
const crossesCode = (snapshot, offset, includeCodeEnd = false) => {
if (offset <= 0 || offset > snapshot.blockText.length || offset === snapshot.blockText.length && !includeCodeEnd) return false;
return (snapshot === previous ? oldCode ??= findMarkdownCodeRegions(previous.blockText) : nextCode ??= findMarkdownCodeRegions(next.blockText)).some((region) => region.start < offset && (offset < region.end || includeCodeEnd && offset === region.end));
};
const scopeRetained = oldScope.valid && nextScope.valid && !crossesCode(previous, oldPrefix.length) && !crossesCode(next, nextPrefix.length);
if (scopeRetained && origin > 0 && consumed === 0 && !ctx.blockChunker.hasBuffered()) {
if (next.blockText.startsWith(nextPrefix)) {
ctx.blockChunker.append(nextText);
return;
}
}
const retained = Math.min(consumed, nextText.length);
const retainedPrefix = scopeRetained && oldPrefix === nextPrefix && previous.blockText.startsWith(oldPrefix) && next.blockText.startsWith(nextPrefix) && oldText.slice(0, retained) === nextText.slice(0, retained);
if (retainedPrefix && (nextText.length >= consumed || !oldText.slice(retained, consumed).trim())) {
if (consumed === oldText.length && !ctx.blockChunker.hasBuffered()) for (const part of next.parts) {
const index = part.index ?? 0;
if (!index || !part.separator) continue;
const boundary = prefixAt(next, index);
const padding = boundary.slice(previous.blockText.length);
if (boundary.startsWith(previous.blockText) && padding && !padding.trim() && !crossesCode(next, boundary.length)) {
ctx.blockChunker.append(padding);
ctx.blockChunker.drain({
force: true,
emit: () => {}
});
break;
}
}
ctx.blockChunker.replace(nextText, 0, retainedPrefix);
return;
}
let restartIndex = next.parts[0]?.index ?? 0;
let restartPrefix = "";
for (const part of next.parts) {
const index = part.index ?? 0;
if (!index || !part.separator) continue;
const oldBoundary = prefixAt(previous, index);
const nextBoundary = prefixAt(next, index);
if (oldBoundary === nextBoundary && previous.blockText.startsWith(oldBoundary) && next.blockText.startsWith(nextBoundary) && oldBoundary.length <= oldPrefix.length + consumed && !crossesCode(previous, oldBoundary.length) && !crossesCode(next, nextBoundary.length)) {
restartIndex = index;
restartPrefix = nextBoundary;
}
}
if (retainedPrefix && nextText.length < consumed && restartPrefix !== next.blockText) {
ctx.blockChunker.replace(nextText, 0, retainedPrefix);
return;
}
const sourceBreaks = [];
if (scopeRetained && oldScope.index === nextScope.index && oldPrefix === nextPrefix && consumed > 0) {
const limit = Math.min(oldText.length, nextText.length);
let prefixLength = 0;
while (prefixLength < limit && oldText.charCodeAt(prefixLength) === nextText.charCodeAt(prefixLength)) prefixLength += 1;
let suffixLength = 0;
while (suffixLength < limit - prefixLength && oldText.charCodeAt(oldText.length - suffixLength - 1) === nextText.charCodeAt(nextText.length - suffixLength - 1)) suffixLength += 1;
const suffixStart = oldText.length - suffixLength;
const shift = next.blockText.length - previous.blockText.length;
for (const end of ctx.blockChunker.preparedSourceBreaks) {
const unchangedPrefix = end <= prefixLength;
const oldBoundary = oldPrefix.length + end;
const nextBoundary = oldBoundary + (unchangedPrefix ? 0 : shift);
if (!unchangedPrefix && (suffixLength === 0 || end < suffixStart) || end > Math.min(consumed, oldText.length) || nextBoundary <= restartPrefix.length || crossesCode(previous, oldBoundary, true) || crossesCode(next, nextBoundary, true)) continue;
sourceBreaks.push(nextBoundary - restartPrefix.length);
}
}
ctx.state.blockReplyScopeStart = {
contentIndex: restartIndex,
itemId: resolveAssistantStreamItemId({
contentIndex: restartIndex,
message: next.message
})
};
ctx.blockChunker.reset(sourceBreaks, restartPrefix.length);
ctx.blockChunker.append(next.blockText.slice(restartPrefix.length));
}
//#endregion
//#region src/agents/embedded-agent-subscribe.raw-stream.ts
/**
* Appends raw embedded-agent stream payloads for diagnostics when enabled.
*/
let rawStreamReady = false;
function appendRawStream(createPayload, sessionKey) {
if (!isTruthyEnvValue(process.env.OPENCLAW_RAW_STREAM) || isIncognitoSessionKey(sessionKey)) return;
const rawStreamPath = process.env.OPENCLAW_RAW_STREAM_PATH?.trim() || path.join(resolveStateDir(), "logs", "raw-stream.jsonl");
if (!rawStreamReady) {
rawStreamReady = true;
try {
fs.mkdirSync(path.dirname(rawStreamPath), { recursive: true });
} catch {}
}
try {
appendRegularFile({
filePath: rawStreamPath,
content: `${JSON.stringify(createPayload())}\n`,
rejectSymlinkParents: true
}).catch(() => {});
} catch {}
}
//#endregion
//#region src/shared/text/assistant-transcript-role-headers.ts
/** Detect transcript-role headers in assistant Markdown through the canonical parser. */
function detectAssistantTranscriptRoleHeaderText(text) {
if (!text.includes("[") && !text.includes("<") && !text.includes("&")) return null;
const annotation = markdownToIR(text, {
assistantTranscriptRoleHeaders: true,
enableSpoilers: true,
linkify: false,
tableMode: "off"
}).annotations?.[0];
if (!annotation || annotation.type !== "assistant_transcript_role") return null;
return {
kind: annotation.kind,
role: annotation.role
};
}
//#endregion
//#region src/shared/text/tool-call-shaped-text.ts
const TOOL_TEXT_PREFILTER_RE = /(?:tool[_\s-]?calls?|function[_\s-]?call|["'](?:name|tool_name|function|arguments|args|input|parameters|tool_calls)["']|<\s*tool_call\b|Action\s*:|\[END_TOOL_REQUEST\])/i;
const MAX_SCAN_CHARS = 2e4;
const MAX_JSON_CANDIDATES = 20;
const MAX_JSON_CANDIDATE_CHARS = 8e3;
function readToolName(record) {
return normalizeOptionalString(record.name) ?? normalizeOptionalString(record.tool_name) ?? normalizeOptionalString(record.tool) ?? normalizeOptionalString(record.function_name);
}
function hasToolArgs(record) {
return "arguments" in record || "args" in record || "input" in record || "parameters" in record;
}
function classifyJsonValue(value) {
if (Array.isArray(value)) {
for (const item of value) {
const detection = classifyJsonValue(item);
if (detection) return detection;
}
return null;
}
const record = asOptionalRecord(value);
if (!record) return null;
const toolCalls = record.tool_calls ?? record.toolCalls;
if (Array.isArray(toolCalls)) return classifyJsonValue(toolCalls) ?? { kind: "json_tool_call" };
const functionRecord = asOptionalRecord(record.function);
if (functionRecord) {
const toolName = readToolName(functionRecord);
if (toolName && hasToolArgs(functionRecord)) return {
kind: "json_tool_call",
toolName
};
}
const toolName = readToolName(record);
if (toolName && hasToolArgs(record)) return {
kind: "json_tool_call",
toolName
};
const type = normalizeOptionalString(record.type)?.toLowerCase();
if (toolName && (type === "tool_call" || type === "toolcall" || type === "tooluse" || type === "tool_use" || type === "function_call" || type === "functioncall")) return {
kind: "json_tool_call",
toolName
};
return null;
}
function findBalancedJsonEnd(text, start) {
const stack = [text[start] === "{" ? "}" : "]"];
let inString = false;
let escaped = false;
for (let index = start + 1; index < text.length; index += 1) {
if (index - start > MAX_JSON_CANDIDATE_CHARS) return null;
const ch = text[index];
if (inString) {
if (escaped) escaped = false;
else if (ch === "\\") escaped = true;
else if (ch === "\"") inString = false;
continue;
}
if (ch === "\"") {
inString = true;
continue;
}
if (ch === "{" || ch === "[") {
stack.push(ch === "{" ? "}" : "]");
continue;
}
if (ch === "}" || ch === "]") {
if (stack.at(-1) !== ch) return null;
stack.pop();
if (stack.length === 0) return index + 1;
}
}
return null;
}
function* iterateJsonCandidates(text) {
for (const match of text.matchAll(/```(?:json|tool|tool_call|function_call)?[^\n\r]*[\r\n]([\s\S]*?)```/gi)) {
const candidate = match[1]?.trim();
if (candidate && candidate.length <= MAX_JSON_CANDIDATE_CHARS) yield candidate;
}
let count = 0;
for (let index = 0; index < text.length && count < MAX_JSON_CANDIDATES; index += 1) {
const ch = text[index];
if (ch !== "{" && ch !== "[") continue;
const end = findBalancedJsonEnd(text, index);
if (end === null) continue;
const candidate = text.slice(index, end).trim();
if (candidate.length > 1) {
count += 1;
yield candidate;
}
index = end - 1;
}
}
function detectJsonToolCall(text) {
for (const candidate of iterateJsonCandidates(text)) try {
const detection = classifyJsonValue(JSON.parse(candidate));
if (detection) return detection;
} catch {}
return null;
}
function detectXmlToolCall(text) {
if (!/<\s*tool_call\b/i.test(text)) return null;
if (!/<\s*function=/i.test(text) && !/["']name["']\s*:\s*["'][^"']{1,120}["']/i.test(text)) return null;
const toolName = /<\s*function=([A-Za-z0-9_.:-]{1,120})\b/i.exec(text)?.[1] ?? /["']name["']\s*:\s*["']([^"']{1,120})["']/i.exec(text)?.[1]?.trim();
return {
kind: "xml_tool_call",
...toolName ? { toolName } : {}
};
}
const LEGACY_TOOL_CALL_OPEN_RE = /\[\s*TOOL_CALL\s*\]\s*{/gi;
const LEGACY_TOOL_NAME_RE = /\btool\s*=>\s*["']([A-Za-z_][A-Za-z0-9_.:-]{0,119})["']/gi;
const LEGACY_ARGS_RE = /\bargs\s*=>/gi;
const LEGACY_FIELD_GAP_CHARS = 8e3;
function findLegacyBracketedToolName(text) {
const argsStarts = Array.from(text.matchAll(LEGACY_ARGS_RE), (match) => match.index);
const callable = [];
let nextArgs = 0;
for (const match of text.matchAll(LEGACY_TOOL_NAME_RE)) {
const end = match.index + match[0].length;
while (nextArgs < argsStarts.length && argsStarts[nextArgs] < end) nextArgs += 1;
if (nextArgs < argsStarts.length && argsStarts[nextArgs] - end <= LEGACY_FIELD_GAP_CHARS) callable.push({
start: match.index,
name: match[1]
});
}
let nextCallable = 0;
for (const opener of text.matchAll(LEGACY_TOOL_CALL_OPEN_RE)) {
const end = opener.index + opener[0].length;
while (nextCallable < callable.length && callable[nextCallable].start < end) nextCallable += 1;
const tool = callable[nextCallable];
if (!tool) return;
if (tool.start - end <= LEGACY_FIELD_GAP_CHARS) return tool.name;
}
}
function detectBracketedToolCall(text) {
const legacyToolName = findLegacyBracketedToolName(text);
if (legacyToolName) return {
kind: "bracketed_tool_call",
toolName: legacyToolName
};
const match = /^\s*\[([A-Za-z_][A-Za-z0-9_.:-]{0,119})\]\s+[\s\S]*?\[END_TOOL_REQUEST\]\s*$/i.exec(text);
if (!match?.[1]) return null;
return {
kind: "bracketed_tool_call",
toolName: match[1]
};
}
function detectReactAction(text) {
const match = /(?:^|\n)[^\S\n]*Action\s*:\s*([A-Za-z_][A-Za-z0-9_.:-]{0,119})[^\S\n]*\n\s*Action Input\s*:/i.exec(text);
if (!match?.[1]) return null;
return {
kind: "react_action",
toolName: match[1]
};
}
/** Detects assistant-visible text that looks like an unexecuted tool call instead of prose. */
function detectToolCallShapedText(text) {
const trimmed = text.slice(0, MAX_SCAN_CHARS).trim();
if (!trimmed || !TOOL_TEXT_PREFILTER_RE.test(trimmed)) return null;
return detectBracketedToolCall(trimmed) ?? detectXmlToolCall(trimmed) ?? detectJsonToolCall(trimmed) ?? detectReactAction(trimmed);
}
//#endregion
//#region src/agents/embedded-agent-subscribe.tool-text-diagnostics.ts
/**
* Warns when assistant text appears to expose raw tool-call syntax.
*/
function isRegisteredToolName(toolName, registeredToolNames) {
if (!toolName || !registeredToolNames) return;
const normalized = normalizeToolPolicyName(toolName);
for (const registeredToolName of registeredToolNames) if (normalizeToolPolicyName(registeredToolName) === normalized) return true;
return false;
}
/** Log safe metadata for suspicious assistant-authored text shapes. */
function warnIfAssistantEmittedSuspiciousText(ctx, assistantMessage) {
const structuredToolInvocation = Array.isArray(assistantMessage.content) && assistantMessage.content.some((block) => {
if (!block || typeof block !== "object") return false;
const rawType = Reflect.get(block, "type");
const type = typeof rawType === "string" ? rawType.trim() : "";
return isToolCallBlockType(type) || Array.isArray(Reflect.get(block, "tool_calls")) || Array.isArray(Reflect.get(block, "toolCalls"));
});
const text = extractTextFromChatContent(assistantMessage.content, {
joinWith: "\n",
normalizeText: (chunk) => chunk.trim()
}) ?? "";
const toolDetection = structuredToolInvocation ? null : detectToolCallShapedText(text);
if (toolDetection) {
const provider = normalizeOptionalString(assistantMessage.provider);
const model = normalizeOptionalString(assistantMessage.model);
const registeredTool = isRegisteredToolName(toolDetection.toolName, ctx.builtinToolNames);
const sessionId = normalizeOptionalString(ctx.params.session.id);
ctx.log.warn("Assistant reply looks like a tool call, but no structured tool invocation was emitted; treating it as text.", {
runId: ctx.params.runId,
...sessionId ? { sessionId } : {},
...provider ? { provider } : {},
...model ? { model } : {},
pattern: toolDetection.kind,
...toolDetection.toolName ? { toolName: toolDetection.toolName } : {},
...registeredTool !== void 0 ? { registeredTool } : {}
});
}
const roleDetection = detectAssistantTranscriptRoleHeaderText(text);
if (!roleDetection) return;
const provider = normalizeOptionalString(assistantMessage.provider);
const model = normalizeOptionalString(assistantMessage.model);
const sessionId = normalizeOptionalString(ctx.params.session.id);
ctx.log.warn("Assistant reply contains transcript-role-looking text; treating it as inert assistant text.", {
runId: ctx.params.runId,
...sessionId ? { sessionId } : {},
...provider ? { provider } : {},
...model ? { model } : {},
pattern: roleDetection.kind,
role: roleDetection.role
});
}
//#endregion
//#region src/agents/embedded-agent-subscribe.handlers.messages.lifecycle.ts
function handleMessageStart(ctx, evt) {
const msg = evt.message;
if (msg?.role !== "assistant" || isSubscribeTranscriptOnlyOpenClawAssistantMessage(msg)) return;
ctx.state.lastReasoningSent = void 0;
ctx.resetAssistantMessageState(ctx.state.assistantTexts.length);
ctx.state.assistantMessageStartIndex = ctx.state.assistantMessageIndex;
emitAssistantMessageStart(ctx);
}
function handleMessageEnd(ctx, evt) {
const msg = evt.message;
if (msg.role === "user" && ctx.state.lastAssistant) {
ctx.state.answerSegments.push({
textEnd: ctx.state.assistantTexts.length,
messageEnd: ctx.state.assistantMessageIndex,
finalMessageStart: ctx.state.assistantMessageStartIndex,
lastAssistant: ctx.state.lastAssistant,
keptAnswer: ctx.state.keptAnswer
});
ctx.state.inputAnswer = void 0;
ctx.state.keptAnswer = void 0;
ctx.state.sourceReplyDeliveryState = "missing";
ctx.state.messageToolOnlySourceReplyDelivered = false;
ctx.state.deterministicApprovalPromptPending = false;
ctx.state.deterministicApprovalPromptSent = false;
ctx.state.currentSourceMessagingToolSentTextsNormalized.length = 0;
ctx.state.lastToolTurnOnlySourceProgress = void 0;
ctx.state.lastAssistant = void 0;
return;
}
if (msg?.role !== "assistant" || isSubscribeTranscriptOnlyOpenClawAssistantMessage(msg)) return;
ctx.state.assistantTurnCount += 1;
const assistantMessage = msg;
const assistantPhase = resolveAssistantMessagePhase(assistantMessage);
const suppressVisibleAssistantOutput = assistantPhase === "commentary";
const suppressDeterministicApprovalOutput = shouldSuppressDeterministicApprovalOutput(ctx.state);
const suppressMessageToolOnlySourceReplyOutput = hasMessageToolOnlySourceDelivery(ctx);
const canEmitReply = () => !ctx.params.silentExpected && !suppressDeterministicApprovalOutput && !suppressMessageToolOnlySourceReplyOutput;
const appendRawMessage = (getText) => appendRawStream(() => ({
ts: Date.now(),
event: "assistant_message_end",
runId: ctx.params.runId,
sessionId: ctx.params.session.id,
rawText: getText(),
rawThinking: extractAssistantThinking(assistantMessage)
}), ctx.params.sessionKey);
if (!suppressMessageToolOnlySourceReplyOutput) emitReasoningEnd(ctx);
ctx.noteLastAssistant(assistantMessage);
ctx.state.keptAnswer = void 0;
if (suppressVisibleAssistantOutput) {
appendRawMessage(() => extractEmbeddedAssistantText(assistantMessage));
emitAssistantCommentaryStreamData(ctx, assistantMessage, true);
emitPersistentReasoning(ctx, ctx.state.includeReasoning ? extractAssistantThinking(assistantMessage) : "");
return;
}
const sourceContent = assistantMessage.content;
promoteThinkingTagsToBlocks(assistantMessage);
let rawText;
const getRawText = () => rawText ??= extractEmbeddedAssistantText(assistantMessage);
const snapshot = extractAssistantStreamSnapshot(ctx, assistantMessage);
const rawVisibleText = snapshot.text;
appendRawMessage(getRawText);
warnIfAssistantEmittedSuspiciousText(ctx, assistantMessage);
const messageToolText = extractStandaloneMessageToolText(rawVisibleText, {
allowRoutedReply: isOpenAiCompletionsAssistantMessage(assistantMessage),
allowCurrentSourceReply: ctx.params.sourceReplyDeliveryMode === "message_tool_only" && ctx.builtinToolNames?.has("message") === true
});
const text = messageToolText === void 0 ? rawVisibleText : extractAssistantVisibleText(scopeAssistantMessageToStreamBlock(assistantMessage, snapshot.parts[0]?.index, void 0), () => messageToolText);
const rawThinking = ctx.state.includeReasoning || ctx.state.streamReasoning ? extractAssistantThinking(assistantMessage) || extractThinkingFromTaggedText(getRawText()) : "";
const trimmedText = text.trim();
ctx.resetPartialReplyDirectives();
const parsedText = parseReplyDirectives(text);
recordPendingAssistantReplyDirectives(ctx.state, parsedText);
const cleanedText = parsedText.text;
const { mediaUrls } = resolveSendableOutboundReplyParts(parsedText, { text: "" });
const managedMediaUrls = resolveManagedStreamMediaUrls(ctx.state, mediaUrls);
const sourceMessage = {
...assistantMessage,
content: sourceContent
};
const sourceSnapshot = sourceContent === assistantMessage.content ? snapshot : extractAssistantStreamSnapshot(ctx, sourceMessage);
const resolveSourceIndex = (contentIndex, itemId) => resolveAssistantStreamBlockIndex(sourceMessage, contentIndex, itemId) ?? -1;
const lastIndex = resolveSourceIndex(ctx.state.lastAssistantStreamContentIndex, ctx.state.lastAssistantStreamItemId);
if (ctx.state.lastBlockReplyText == null) {
ctx.blockChunker.reset();
ctx.state.blockReplyScopeStart = void 0;
}
if (ctx.blockChunker.consumedLength === 0 && !ctx.blockChunker.hasBuffered()) {
const preparedIndex = ctx.state.lastAssistantTextMessageIndex >= ctx.state.assistantMessageStartIndex ? resolveSourceIndex(ctx.state.lastAssistantTextContentIndex, ctx.state.lastAssistantTextItemId) : -1;
if (preparedIndex >= 0) ctx.state.blockReplyScopeStart = {
contentIndex: preparedIndex,
itemId: resolveAssistantStreamItemId({
contentIndex: preparedIndex,
message: sourceMessage
}),
after: true
};
}
reconcileBlockReplySnapshot(ctx, extractAssistantStreamSnapshot(ctx, sourceMessage, {
throughIndex: lastIndex >= 0 ? lastIndex : void 0,
observedText: ctx.state.streamBlockText,
final: ctx.state.streamBlockFinal
}), text === rawVisibleText ? sourceSnapshot : {
...snapshot,
blockText: cleanedText
});
const lastSourceIndex = sourceSnapshot.parts.at(-1)?.index;
if (lastIndex >= 0 && lastSourceIndex !== void 0 && lastSourceIndex > lastIndex) ctx.state.lastAssistantStreamContentIndex = lastSourceIndex;
const finalizeMessageEnd = () => {
ctx.state.deltaBuffer = "";
ctx.state.streamBlockText = "";
ctx.state.streamBlockFinal = false;
ctx.state.blockReplyScopeStart = void 0;
ctx.state.thinkingTagStream = createThinkingTagStreamState();
ctx.state.deltaBufferIsCommentary = false;
ctx.state.hasFlushedPartialText = false;
ctx.blockChunker.reset();
const { reasoningDepth, final, inlineCode } = ctx.state.partialBlockState;
ctx.state.partialBlockState = {
reasoningDepth,
final,
inlineCode
};
ctx.state.assistantStream = void 0;
ctx.state.reasoningStreamOpen = false;
};
if (canEmitReply()) ctx.emitAssistantStreamData({
text: cleanedText,
delta: "",
mediaUrls: mediaUrls.length ? mediaUrls : void 0,
managedMediaUrls: managedMediaUrls.length ? managedMediaUrls : void 0,
phase: assistantPhase
}, { finalMessage: true });
const finalAssistantText = ctx.params.silentExpected && !isSilentReplyText(trimmedText, "NO_REPLY") ? "" : text;
const lastVisibleItemRecorded = lastSourceIndex !== void 0 && ctx.state.lastAssistantTextMessageIndex >= ctx.state.assistantMessageStartIndex && resolveSourceIndex(ctx.state.lastAssistantTextContentIndex, ctx.state.lastAssistantTextItemId) === lastSourceIndex;
const addedDuringMessage = ctx.state.assistantTexts.length > ctx.state.assistantTextBaseline || lastVisibleItemRecorded;
const chunkerHasBuffered = Boolean(ctx.params.onBlockReply) && ctx.blockChunker.hasBuffered();
ctx.finalizeAssistantTexts({
text: finalAssistantText,
addedDuringMessage,
chunkerHasBuffered
});
const answersInput = !ctx.params.silentExpected && assistantMessage.stopReason === "stop" && assistantMessage.endTurn !== false && !parsedText.isSilent && Boolean(cleanedText.trim() || mediaUrls.length > 0);
const addsNothing = assistantMessage.stopReason === "stop" && (parsedText.isSilent || !cleanedText.trim()) && mediaUrls.length === 0 && !parsedText.audioAsVoice && !assistantMessage.openclawDelivery?.audioAsVoice && !assistantMessage.openclawDelivery?.tts?.text?.trim();
ctx.state.keptAnswer = addsNothing ? ctx.state.inputAnswer : void 0;
if (!addsNothing && assistantMessage.stopReason !== "toolUse" && assistantMessage.endTurn !== false) ctx.state.inputAnswer = void 0;
const onBlockReply = ctx.params.onBlockReply;
emitPersistentReasoning(ctx, rawThinking);
if (canEmitReply() && onBlockReply) {
const pending = ctx.flushBlockReplyBuffer({
assistantMessageIndex: ctx.state.assistantMessageIndex,
final: true,
finalReply: parsedText
});
if (pending) pending.catch((err) => {
ctx.log.debug(`message_end block reply flush failed: ${String(err)}`);
});
}
if (answersInput) ctx.state.inputAnswer = {
assistant: applyAssistantDeliveryDirectives(structuredClone(assistantMessage)),
messageIndex: ctx.state.lastAssistantTextMessageIndex
};
if (!ctx.params.silentExpected && rawThinking) ctx.emitReasoningStream(rawThinking);
if (!ctx.params.silentExpected && ctx.state.blockReplyBreak === "message_end" && ctx.params.onBlockReplyFlush) {
const flushBlockReplyBufferResult = ctx.flushBlockReplyBuffer();
if (isPromiseLike(flushBlockReplyBufferResult)) return flushBlockReplyBufferResult.then(() => ctx.params.onBlockReplyFlush?.({ reason: "message_end" })).finally(finalizeMessageEnd);
const onBlockReplyFlushResult = ctx.params.onBlockReplyFlush({ reason: "message_end" });
if (isPromiseLike(onBlockReplyFlushResult)) return onBlockReplyFlushResult.finally(finalizeMessageEnd);
}
finalizeMessageEnd();
}
//#endregion
//#region src/agents/embedded-agent-live-edit-diff.ts
const LIVE_EDIT_DIFF_MIN_INTERVAL_MS = 250;
const LIVE_EDIT_DIFF_MAX_PARTIAL_JSON_CHARS = 1048576;
const LIVE_EDIT_DIFF_MAX_TRACKED_CALLS = 64;
function readToolCallBlock$1(event) {
const contentIndex = event.contentIndex;
const partial = event.partial;
if (typeof contentIndex !== "number" || !Number.isInteger(contentIndex) || contentIndex < 0 || !partial || typeof partial !== "object") return;
const content = partial.content;
const block = Array.isArray(content) ? content[contentIndex] : void 0;
return block && typeof block === "object" && !Array.isArray(block) ? block : void 0;
}
function readToolCallId(event) {
const toolCall = event.toolCall;
if (toolCall && typeof toolCall === "object" && !Array.isArray(toolCall)) {
const id = toolCall.id;
if (typeof id === "string" && id) return id;
}
const block = readToolCallBlock$1(event);
return typeof block?.id === "string" && block.id ? block.id : void 0;
}
/** Update one run's bounded best-effort diff counters from a model stream event. */
function updateLiveEditDiffProgress(stateByToolCallId, event) {
if (!event) return;
const eventType = event.type;
if (eventType === "toolcall_end") {
const toolCallId = readToolCallId(event);
if (toolCallId) stateByToolCallId.delete(toolCallId);
return;
}
if (eventType !== "toolcall_delta") return;
const block = readToolCallBlock$1(event);
const toolCallId = typeof block?.id === "string" ? block.id : "";
const name = typeof block?.name === "string" ? block.name : "";
const kind = resolveFileMutationToolName(name);
const partialJson = typeof block?.partialJson === "string" ? block.partialJson : "";
if (!toolCallId || !kind || !partialJson) return;
if (partialJson.length > LIVE_EDIT_DIFF_MAX_PARTIAL_JSON_CHARS) {
stateByToolCallId.delete(toolCallId);
return;
}
let progress = stateByToolCallId.get(toolCallId);
if (!progress) {
if (stateByToolCallId.size >= LIVE_EDIT_DIFF_MAX_TRACKED_CALLS) return;
progress = {
added: 0,
removed: 0,
emittedAdded: 0,
emittedRemoved: 0,
lastCheckedAtMs: 0
};
stateByToolCallId.set(toolCallId, progress);
}
const now = Date.now();
if (progress.lastCheckedAtMs > 0 && now - progress.lastCheckedAtMs < LIVE_EDIT_DIFF_MIN_INTERVAL_MS) return;
progress.lastCheckedAtMs = now;
const counted = countStreamingFileMutationLines(kind, parseStreamingJson(partialJson));
progress.added = Math.max(progress.added, counted.added);
progress.removed = Math.max(progress.removed, counted.removed);
if (progress.added === progress.emittedAdded && progress.removed === progress.emittedRemoved) return;
progress.emittedAdded = progress.added;
progress.emittedRemoved = progress.removed;
return {
toolCallId,
name: normalizeLowercaseStringOrEmpty(name),
diff: {
added: progress.added,
removed: progress.removed
}
};
}
//#endregion
//#region src/agents/embedded-agent-subscribe.handlers.messages.update.ts
function handleMessageUpdate(ctx, evt) {
const msg = evt.message;
if (msg?.role !== "assistant" || isSubscribeTranscriptOnlyOpenClawAssistantMessage(msg)) return;
ctx.noteLastAssistant(msg);
const assistantRecord = asOptionalObjectRecord(evt.assistantMessageEvent);
const evtType = typeof assistantRecord?.type === "string" ? assistantRecord.type : "";
if (evtType !== "text_delta") ctx.flushAssistantStream();
const liveEditDiff = updateLiveEditDiffProgress(ctx.state.liveEditDiffStateById, assistantRecord);
if (liveEditDiff) {
const data = {
phase: "input_delta",
...liveEditDiff
};
emitAgentEvent({
runId: ctx.params.runId,
stream: "tool",
data
});
runBestEffortCallback({
label: "live edit diff agent event",
log: ctx.log,
callback: () => ctx.params.onAgentEvent?.({
stream: "tool",
data
})
});
}
const eventAssistantMessage = assistantRecord?.partial && typeof assistantRecord.partial === "object" ? assistantRecord.partial : msg;
const isResponsesTextEvent = isResponsesApiAssistantMessage(eventAssistantMessage) && (evtType === "text_start" || evtType === "text_delta" || evtType === "text_end");
const recordRawStream = (event, eventType, delta, content) => appendRawStream(() => ({
ts: Date.now(),
event,
runId: ctx.params.runId,
sessionId: ctx.params.session.id,
evtType: eventType,
delta,
content
}), ctx.params.sessionKey);
if (resolveAssistantMessagePhase(msg) === "commentary" && !isResponsesTextEvent) {
ctx.flushAssistantStream();
const commentaryText = extractAssistantCommentaryText(msg);
if (commentaryText) {
recordRawStream("assistant_text_stream", "commentary_update", "", commentaryText);
emitAssistantCommentaryStreamData(ctx, msg, false, commentaryText);
}
return;
}
const suppressDeterministicApprovalOutput = shouldSuppressDeterministicApprovalOutput(ctx.state);
const suppressMessageToolOnlySourceReplyOutput = hasMessageToolOnlySourceDelivery(ctx);
if (evtType === "thinking_start" || evtType === "thinking_delta" || evtType === "thinking_end") {
if (!suppressMessageToolOnlySourceReplyOutput && (evtType === "thinking_start" || evtType === "thinking_delta")) openReasoningStream(ctx);
const thinkingDelta = typeof assistantRecord?.delta === "string" ? assistantRecord.delta : "";
const thinkingContent = typeof assistantRecord?.content === "string" ? assistantRecord.content : "";
recordRawStream("assistant_thinking_stream", evtType, thinkingDelta, thinkingContent);
const block = Array.isArray(msg.content) && msg.content.length === 1 ? msg.content[0] : void 0;
const nativeThinking = block?.type === "thinking" ? block : void 0;
ctx.emitReasoningStream(nativeThinking ?? (extractAssistantThinking(msg) || thinkingContent || thinkingDelta), nativeThinking ? thinkingContent || thinkingDelta : void 0);
if (evtType === "thinking_end" && !suppressMessageToolOnlySourceReplyOutput) {
if (!ctx.state.reasoningStreamOpen) openReasoningStream(ctx);
emitReasoningEnd(ctx);
if (ctx.state.includeReasoning && ctx.state.blockReplyBreak === "text_end") emitPersistentReasoning(ctx, extractAssistantThinking(msg) || thinkingContent);
}
return;
}
if (evtType !== "text_delta" && evtType !== "text_start" && evtType !== "text_end") return;
const delta = typeof assistantRecord?.delta === "string" ? assistantRecord.delta : "";
const content = typeof assistantRecord?.content === "string" ? assistantRecord.content : "";
recordRawStream("assistant_text_stream", evtType, delta, content);
const partialAssistant = eventAssistantMessage;
const priorBlockText = ctx.state.streamBlockText;
const priorBlockFinal = ctx.state.streamBlockFinal;
const priorBlockIndex = ctx.state.lastAssistantStreamContentIndex;
const streamContentIndex = resolveAssistantStreamContentIndex(assistantRecord?.contentIndex);
const streamItemId = resolveAssistantStreamItemId({
contentIndex: streamContentIndex,
message: partialAssistant
});
const blockSourceIndex = resolveAssistantStreamBlockIndex(partialAssistant, streamContentIndex, streamItemId) ?? streamContentIndex;
const priorSourceIndex = resolveAssistantStreamBlockIndex(partialAssistant, priorBlockIndex, ctx.state.lastAssistantStreamItemId) ?? priorBlockIndex;
const streamAssistant = scopeAssistantMessageToStreamBlock(partialAssistant, streamContentIndex, streamItemId);
const deliveryPhase = resolveAssistantMessagePhase(streamAssistant);
const isPhasePendingResponsesTextItem = evtType !== "text_end" && !deliveryPhase && Boolean(streamItemId) && isResponsesApiAssistantMessage(partialAssistant);
const isPhasePendingText = !deliveryPhase && isAssistantTextPhasePending(partialAssistant, evtType);
const isReasoningCompletionsText = isOpenAiCompletionsAssistantMessage(partialAssistant) && partialAssistant.openclawDelivery?.textPhaseRequiresTerminal === true;
const hasResponsesContentIndex = streamContentIndex !== void 0 && isResponsesApiAssistantMessage(partialAssistant);
let streamItemChanged = false;
let deliveryItemId = streamItemId;
if ((deliveryPhase || isPhasePendingResponsesTextItem || hasResponsesContentIndex) && (streamContentIndex !== void 0 || streamItemId)) {
const previousStreamContentIndex = ctx.state.lastAssistantStreamContentIndex;
const previousStreamItemId = ctx.state.lastAssistantStreamItemId;
if (previousStreamContentIndex !== void 0 && streamContentIndex !== void 0 && previousStreamContentIndex !== streamContentIndex || (previousStreamContentIndex === void 0 || streamContentIndex === void 0) && Boolean(previousStreamItemId && streamItemId && previousStreamItemId !== streamItemId)) {
streamItemChanged = true;
ctx.flushBlockReplyBuffer({ assistantMessageIndex: ctx.state.assistantMessageIndex });
ctx.resetAssistantMessageState(ctx.state.assistantTexts.length);
ctx.state.blockReplyScopeStart = {
contentIndex: streamContentIndex ?? blockSourceIndex ?? 0,
itemId: streamItemId
};
emitAssistantMessageStart(ctx);
} else if (previousStreamContentIndex !== void 0 && streamContentIndex === previousStreamContentIndex && previousStreamItemId) deliveryItemId = previousStreamItemId;
ctx.state.lastAssistantStreamItemId = deliveryItemId;
}
if (streamContentIndex !== void 0) {
if (streamContentIndex !== ctx.state.lastAssistantStreamContentIndex) {
ctx.flushAssistantStream();
ctx.state.streamBlockText = "";
ctx.state.streamBlockFinal = false;
}
ctx.state.lastAssistantStreamContentIndex = streamContentIndex;
}
const chunk = resolveAssistantTextChunk({
evtType,
delta,
content,
accumulatedText: ctx.state.streamBlockText
});
ctx.state.streamBlockText += chunk;
ctx.state.streamBlockFinal = evtType === "text_end";
if (evtType === "text_start" && isResponsesApiAssistantMessage(partialAssistant)) return;
if (deliveryPhase === "commentary") {
if (ctx.state.assistantStream?.projection) ctx.state.assistantStream.projection.directiveCodePrefix = void 0;
const isResponsesCommentary = isResponsesApiAssistantMessage(partialAssistant);
const hadResponsesCommentaryText = isResponsesCommentary && Boolean(ctx.state.deltaBuffer);
if (isResponsesCommentary && chunk) {
ctx.state.deltaBuffer += chunk;
ctx.state.deltaBufferIsCommentary = true;
}
const commentaryText = isResponsesCommentary ? ctx.state.deltaBuffer : extractAssistantCommentaryText(streamAssistant);
if (commentaryText && (chunk || !hadResponsesCommentaryText || evtType === "text_end")) ctx.emitAssistantStreamData({
text: commentaryText,
delta: "",
replace: true,
phase: "commentary",
itemId: deliveryItemId
}, { finalMessage: evtType === "text_end" });
return;
}
if (isPhasePendingResponsesTextItem) return;
const skipLiveStream = ctx.params.suppressLiveStreamOutput === true;
const shouldUsePhaseAwareBlockReply = Boolean(deliveryPhase);
const reprojectBlockReply = shouldUsePhaseAwareBlockReply && !ctx.params.enforceFinalTag && ctx.state.assistantStream?.projection?.kind !== "final" && ctx.blockChunker.consumedLength === 0;
const finalText = evtType === "text_end";
if (isReasoningCompletionsText) return;
if (chunk) {
ctx.state.deltaBuffer += chunk;
ctx.state.deltaBufferIsCommentary = false;
}
if (skipLiveStream) return;
ctx.emitReasoningStream(extractThinkingFromTaggedStream(ctx.state.deltaBuffer, ctx.state.thinkingTagStream, chunk));
const wasThinking = ctx.state.partialBlockState.reasoningDepth > 0;
let visibleDelta = "";
let textIsAppend = false;
let appendDelta = null;
let previousText = ctx.state.assistantStream?.raw ?? "";
const shouldReadPartialText = streamItemChanged || evtType === "text_end" && assistantRecord?.partial !== void 0 || shouldUsePhaseAwareBlockReply && (evtType === "text_end" || !chunk);
const isTerminalSnapshot = evtType === "text_end" && shouldReadPartialText;
let selectedAssistant = shouldUsePhaseAwareBlockReply || isResponsesTextEvent ? streamAssistant : partialAssistant;
if (isTerminalSnapshot && selectedAssistant === partialAssistant && streamContentIndex !== void 0 && Array.isArray(partialAssistant.content) && streamContentIndex + 1 < partialAssistant.content.length) selectedAssistant = {
...partialAssistant,
content: partialAssistant.content.slice(0, streamContentIndex + 1)
};
const snapshot = isTerminalSnapshot ? extractAssistantStreamSnapshot(ctx, selectedAssistant) : void 0;
let next = shouldReadPartialText ? (snapshot?.text ?? extractAssistantVisibleText(selectedAssistant)).trimEnd() : void 0;
if (snapshot) {
ctx.state.deltaBuffer = snapshot.rawText;
ctx.state.thinkingTagStream = createThinkingTagStreamState();
ctx.state.partialBlockState = snapshot.state;
ctx.resetPartialReplyDirectives();
}
if (!next && evtType !== "text_end") next = void 0;
const hasSnapshotText = next !== void 0;
let nextRawStreamText = next ?? "";
let projection;
if (next === void 0 && deliveryPhase === "final_answer" && (reprojectBlockReply || chunk)) {
visibleDelta = reprojectBlockReply ? ctx.state.deltaBuffer : ctx.params.enforceFinalTag ? ctx.stripBlockTags(chunk, ctx.state.partialBlockState, { final: finalText }) : chunk;
next = visibleDelta;
textIsAppend = true;
if (reprojectBlockReply) ctx.resetPartialReplyDirectives();
} else if (next === void 0 && deliveryPhase !== "final_answer") {
const pendingTagFragment = ctx.state.partialBlockState.pendingTagFragment;
const reasoningTags = scanReasoningTags(chunk, false);
if (Boolean(pendingTagFragment) || reasoningTags.tags.length > 0 || reasoningTags.pendingStart !== void 0) {
const recomputeState = {
reasoningDepth: 0,
final: false,
inlineCode: createInlineCodeState()
};
const recomputedRawText = ctx.stripBlockTags(ctx.state.deltaBuffer, recomputeState, { final: finalText });
const isFullStreamReplacement = !recomputedRawText.startsWith(previousText);
if (isFullStreamReplacement) ctx.resetPartialReplyDirectives();
textIsAppend = !isFullStreamReplacement;
next = recomputedRawText;
visibleDelta = isFullStreamReplacement ? recomputedRawText : recomputedRawText.slice(previousText.length);
nextRawStreamText = recomputedRawText;
ctx.state.partialBlockState = recomputeState;
} else {
visibleDelta = chunk || evtType === "text_end" ? ctx.stripBlockTags(chunk, ctx.state.partialBlockState, { final: finalText }) : "";
if (ctx.state.partialBlockState.pendingTagFragment) {
visibleDelta = "";
next = ctx.state.assistantStream?.text ?? "";
nextRawStreamText = ctx.state.assistantStream?.raw ?? "";
} else {
next = visibleDelta;
textIsAppend = true;
}
}
} else if (!isTerminalSnapshot && next !== void 0 && (chunk || evtType === "text_end")) visibleDelta = ctx.stripBlockTags(chunk, ctx.state.partialBlockState, { final: finalText });
if (next !== void 0) {
const unchangedBlockAppend = textIsAppend && visibleDelta === chunk;
if (!hasSnapshotText) {
const kind = deliveryPhase !== "final_answer" ? "raw" : ctx.params.enforceFinalTag ? "delivery" : "final";
const previousStream = reprojectBlockReply ? void 0 : ctx.state.assistantStream;
projection = previousStream?.projection;
if (!projection || projection.kind !== kind) {
projection = {
kind,
projector: kind === "raw" ? createTextProjection([downgradedToolCallTextFilter(), trimTextFilter("both", { preserveCodeIndentation: true })]) : createAssistantVisibleStreamText(kind === "final" ? "final_answer" : void 0)
};
projection.projector.replace(previousStream?.raw ?? "");
}
previousText = projection.projector.text;
if (!textIsAppend) projection.directiveCodePrefix = void 0;
const projected = textIsAppend ? projection.projector.append(visibleDelta) : projection.projector.replace(nextRawStreamText);
nextRawStreamText = projection.projector.source;
next = projected.text;
appendDelta = projected.delta;
if (kind !== "raw") visibleDelta = projected.delta ?? (previousText.startsWith(next) ? "" : next);
}
if (!suppressMessageToolOnlySourceReplyOutput) {
if (!wasThinking && ctx.state.partialBlockState.reasoningDepth > 0) openReasoningStream(ctx);
else if (wasThinking && ctx.state.partialBlockState.reasoningDepth === 0) emitReasoningEnd(ctx);
}
const parsedStreamDirectives = isTerminalSnapshot ? ctx.consumePartialReplyDirectives(next, { final: finalText }) : mergeReplyDirectiveResults(visibleDelta ? ctx.consumePartialReplyDirectives(visibleDelta) : null, evtType === "text_end" ? ctx.consumePartialReplyDirectives("", { final: finalText }) : null);
const previousCleaned = ctx.state.assistantStream?.text ?? "";
const { text: cleanedText, delta: replyDelta, replace, hasText, replyDirectives, audioDirectiveCount, directiveCodePrefix } = resolveStreamingReply({
evtType,
next,
previousText,
previousCleaned,
visibleDelta,
appendDelta,
parsedStreamDirectives,
previousAudioDirectiveCount: ctx.state.lastAssistantAudioDirectiveCount,
rawDirectiveSource: projection?.kind === "raw" ? nextRawStreamText : void 0,
directiveCodePrefix: projection?.kind === "raw" ? projection.directiveCodePrefix : void 0
});
if (projection?.kind === "raw") projection.directiveCodePrefix = directiveCodePrefix;
recordPendingAssistantReplyDirectives(ctx.state, replyDirectives, {
previous: ctx.state.lastAssistantAudioDirectiveCount,
current: audioDirectiveCount
});
ctx.state.lastAssistantAudioDirectiveCount = audioDirectiveCount;
const hasAudio = Boolean(replyDirectives?.audioAsVoice);
const hasVisibleReply = hasText || hasAudio;
const deltaText = hasVisibleReply ? replyDelta : "";
let shouldEmit = (hasVisibleReply || replace) && (replace ? cleanedText !== previousCleaned || hasAudio : Boolean(deltaText || hasAudio));
if (!isPhasePendingText) {
if (evtType === "text_delta" && unchangedBlockAppend && !snapshot && !reprojectBlockReply && !replace && !ctx.params.enforceFinalTag && (priorBlockIndex === streamContentIndex || priorBlockIndex === void 0 && !priorBlockText) && next === nextRawStreamText.trim() && (previousText === previousCleaned && next === cleanedText || appendDelta !== null && replyDelta === appendDelta)) ctx.blockChunker.append(chunk);
else reconcileBlockReplySnapshot(ctx, extractAssistantStreamSnapshot(ctx, partialAssistant, {
throughIndex: streamItemChanged ? blockSourceIndex : priorSourceIndex,
observedText: streamItemChanged ? "" : priorBlockText,
final: streamItemChanged ? false : priorBlockFinal
}), extractAssistantStreamSnapshot(ctx, partialAssistant, {
throughIndex: blockSourceIndex,
...snapshot ? {} : { observedText: ctx.state.streamBlockText },
final: finalText
}));
}
if (isTerminalSnapshot) ctx.state.streamBlockText = content;
ctx.state.assistantStream = {
raw: nextRawStreamText,
text: cleanedText,
projection
};
if (ctx.params.silentExpected || suppressDeterministicApprovalOutput || suppressMessageToolOnlySourceReplyOutput) shouldEmit = false;
if (shouldEmit) {
const currentSourcePartial = ctx.params.sourceReplyDeliveryMode !== "message_tool_only" ? resolveCurrentSourceMessagingToolPartial(ctx.state, {
evtType,
text: cleanedText,
visibleDelta
}) : {
hold: false,
text: cleanedText
};
const releaseHeldSnapshot = currentSourcePartial.text !== cleanedText;
ctx.emitAssistantStreamData({
text: currentSourcePartial.text,
delta: releaseHeldSnapshot ? currentSourcePartial.text : deltaText,
replace: releaseHeldSnapshot || replace || void 0,
phase: deliveryPhase
}, { emitPartialReply: !currentSourcePartial.hold });
}
}
if (ctx.params.silentExpected || suppressDeterministicApprovalOutput || suppressMessageToolOnlySourceReplyOutput || ctx.state.blockReplyBreak !== "text_end") return;
if (ctx.params.onBlockReply && ctx.blockChunking) ctx.blockChunker.drain({
force: false,
emit: ctx.emitBlockChunk
});
if (evtType === "text_end") {
const assistantMessageIndex = ctx.state.assistantMessageIndex;
const onFlushError = (err) => {
ctx.log.debug(`text_end block reply flush failed: ${String(err)}`);
};
try {
const pending = ctx.flushBlockReplyBuffer({
assistantMessageIndex,
final: finalText
});
if (pending) return pending.catch(onFlushError);
} catch (err) {
onFlushError(err);
}
}
}
//#endregion
//#region src/agents/tool-error-state.ts
/** Track the run's last tool failure until the same tool succeeds. */
function createToolErrorState() {
let lastToolError;
const terminalState = () => lastToolError ? { lastToolError } : {};
return {
read: terminalState,
recordFailure(failure) {
lastToolError = failure;
return terminalState();
},
recordSuccess(toolName) {
if (lastToolError && normalizeLowercaseStringOrEmpty(lastToolError.toolName) === normalizeLowercaseStringOrEmpty(toolName)) lastToolError = void 0;
return terminalState();
}
};
}
//#endregion
//#region src/agents/tool-terminal-outcome.ts
function readTimeoutDiagnostic(result) {
const details = readToolResultDetails(result);
const timeoutMs = details?.timeoutMs;
if (details?.timedOut !== true || typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) return;
return {
kind: "timeout",
timeoutMs,
...details.partial === true && Array.isArray(details.results) && details.results.length > 0 ? { partialResults: details.results.length } : {}
};
}
/** Build one attempt-scoped facts-in/state-out terminal observer for every harness. */
function createToolTerminalObserver(runId) {
const errors = createToolErrorState();
return (observation) => {
const effectReceipt = readToolEffectReceipt(observation.result);
const trackedExecutionStarted = observation.toolCallId ? consumeTrackedToolExecutionStarted(observation.toolCallId, runId) : void 0;
const trackedArguments = observation.toolCallId ? peekAdjustedParamsForToolCall(observation.toolCallId, runId) : void 0;
const executionPrevented = observation.toolCallId ? peekPreExecutionBlockedToolCall(observation.toolCallId, runId) : false;
const executionStarted = (trackedExecutionStarted ?? observation.executionStarted ?? true) && !executionPrevented && effectReceipt?.state !== "not_started";
const executedArguments = asOptionalRecord(trackedArguments) ?? asOptionalRecord(observation.arguments);
const mutation = observation.ownerMutation ? buildToolMutationState(observation.toolName, executedArguments, { ownerKey: observation.ownerMutation.ownerKey }) : observation.nativeMutation ?? buildToolMutationState(observation.toolName, executedArguments);
const replaySafe = observation.replaySafe ?? mutation.replaySafe;
const details = readToolResultDetails(observation.result);
let lastToolError;
if (!executionStarted && details?.status === "skipped" && details.deniedReason === "steering") lastToolError = errors.read().lastToolError;
else if (observation.outcome === "failure") {
const mutatingAction = executionStarted && mutation.mutatingAction;
const terminalDiagnostic = observation.failure?.terminalDiagnostic ?? (executionStarted ? readTimeoutDiagnostic(observation.result) : void 0);
const failure = {
toolName: observation.toolName,
...observation.meta ? { meta: observation.meta } : {},
...observation.failure,
...terminalDiagnostic ? { terminalDiagnostic } : {},
executionStarted,
mutatingAction
};
lastToolError = errors.recordFailure(failure).lastToolError;
} else if (observation.toolName === "message" && projectPluginMessageDeliveryFact(observation.result)?.status === "suppressed") lastToolError = errors.read().lastToolError;
else lastToolError = errors.recordSuccess(observation.toolName).lastToolError;
return {
...lastToolError ? { lastToolError } : {},
executionStarted,
...executedArguments ? { executedArguments } : {},
sideEffectEvidence: executionStarted && !replaySafe,
effectReceipt: effectReceipt ?? buildToolEffectReceipt({
executionStarted,
mutatingAction: mutation.mutatingAction,
replaySafe,
outcome: observation.outcome
})
};
};
}
//#endregion
//#region src/agents/embedded-agent-subscribe.handlers.tools.results.ts
const execApprovalReplyModuleLoader = createLazyImportLoader(() => import("./exec-approval-reply-B7aiRwQ6.mjs"));
const hookRunnerGlobalModuleLoader = createLazyImportLoader(() => import("./plugins/hook-runner-global.js"));
const fallbackToolTerminalObservers = /* @__PURE__ */ new WeakMap();
function resolveFallbackToolTerminalObserver(ctx) {
const existing = fallbackToolTerminalObservers.get(ctx.state);
if (existing) return existing;
const created = createToolTerminalObserver(ctx.params.runId);
fallbackToolTerminalObservers.set(ctx.state, created);
return created;
}
function isMiddlewareToolResultError(result) {
return asOptionalRecord(asOptionalObjectRecord(result)?.details)?.middlewareError === true;
}
const PROCESS_TERMINATION_REASONS = /* @__PURE__ */ new Set([
"manual-cancel",
"overall-timeout",
"no-output-timeout",
"spawn-error",
"signal",
"exit"
]);
function readSafeProcessSessionId(value) {
const sessionId = readStringValue(value)?.trim();
if (!sessionId || sessionId.length > 160 || hasTerminalControlCharacter(sessionId)) return;
return sessionId;
}
function buildProcessTerminalDiagnostic(toolName, args, sanitizedResult) {
if (toolName !== "process") return;
const action = normalizeOptionalLowercaseString(args.action);
if (action !== "poll" && action !== "log") return;
const details = readToolResultDetails(sanitizedResult);
const sessionId = readSafeProcessSessionId(details?.sessionId);
if (!sessionId) return;
const exitReason = normalizeOptionalLowercaseString(details?.exitReason);
const hasCanonicalExitReason = PROCESS_TERMINATION_REASONS.has(exitReason ?? "");
if (action === "log" && !hasCanonicalExitReason) return;
const timeoutKind = exitReason === "overall-timeout" || exitReason === "no-output-timeout" ? exitReason : void 0;
let reason;
if (details?.timedOut === true || timeoutKind) reason = {
kind: "timeout",
...timeoutKind ? { timeoutKind } : {}
};
else if (typeof details?.exitSignal === "string" && details.exitSignal.trim().length > 0 && details.exitSignal.trim().length <= 32 || typeof details?.exitSignal === "number" && Number.isFinite(details.exitSignal)) {
const signal = typeof details.exitSignal === "string" ? details.exitSignal.trim() : details.exitSignal;
if (!hasTerminalControlCharacter(String(signal))) reason = {
kind: "signal",
signal
};
} else if (typeof details?.exitCode === "number" && Number.isSafeInteger(details.exitCode) && details.exitCode !== 0) reason = {
kind: "exit",
exitCode: details.exitCode
};
if (!reason) return;
return {
kind: "process",
sessionId,
reason
};
}
function loadHookRunnerGlobal() {
return hookRunnerGlobalModuleLoader.load();
}
function isCronAddAction(args) {
return normalizeOptionalLowercaseString(asOptionalObjectRecord(args)?.action) === "add";
}
function applyCurrentMessageProvider(toolName, args, currentProvider) {
if (toolName !== "message" || readStringValue(args.provider) || readStringValue(args.channel) || !currentProvider) return args;
return {
...args,
provider: currentProvider
};
}
function applyToolSendReceiptForExtraction(result, receiptResult) {
const toolSend = readToolResultDetails(receiptResult)?.toolSend;
if (toolSend === void 0) return result;
return {
...asOptionalRecord(result),
details: {
...readToolResultDetails(result),
toolSend
}
};
}
function readExecToolDetails(result) {
const details = readToolResultDetails(result);
if (!details || typeof details.status !== "string") return null;
return details;
}
function extractExecOutput(result) {
const execDetails = readExecToolDetails(result);
const output = execDetails && "aggregated" in execDetails ? execDetails.aggregated : extractToolResultText(result);
return typeof output === "string" ? output : void 0;
}
function extractLiveExecOutput(result) {
const output = extractExecOutput(result);
return typeof output === "string" ? truncateLiveExecOutput(output) : void 0;
}
function isOpenClawExecutable(token) {
return normalizeOptionalLowercaseString(token)?.split(/[\\/]/).at(-1) === "openclaw";
}
function isOpenClawPackageSpec(token) {
const packageSpec = normalizeOptionalLowercaseString(token);
return packageSpec?.startsWith("openclaw@") === true && packageSpec.length > 9;
}
function skipOpenClawPackageRunner(tokens, startIndex) {
let commandIndex = startIndex;
let acceptsPackageSpec = false;
let runner = normalizeOptionalLowercaseString(tokens[commandIndex]);
if (runner === "corepack" && normalizeOptionalLowercaseString(tokens[commandIndex + 1]) === "pnpm") {
commandIndex += 1;
runner = "pnpm";
}
if (runner === "pnpm") {
const subcommand = normalizeOptionalLowercaseString(tokens[commandIndex + 1]);
if (subcommand === "exec" || subcommand === "dlx") {
commandIndex += 2;
acceptsPackageSpec = subcommand === "dlx";
} else commandIndex = startIndex;
} else if (runner === "npx" || runner === "bunx") {
commandIndex += 1;
acceptsPackageSpec = true;
while (true) {
const option = normalizeOptionalLowercaseString(tokens[commandIndex]);
if (option === "-y" || option === "--yes" || option === "--no-install" || option === "--bun" || option?.startsWith("--package=") || option?.startsWith("--yes=")) {
commandIndex += 1;
continue;
}
if (option === "-p" || option === "--package") {
commandIndex += 2;
continue;
}
break;
}
}
if (tokens[commandIndex] === "--") commandIndex += 1;
return {
commandIndex,
acceptsPackageSpec
};
}
function isOpenClawCronAddShellCommand(args) {
const record = asOptionalObjectRecord(args);
const command = readStringValue(record?.command) ?? readStringValue(record?.cmd);
if (!command || hasTopLevelShellControlOperator(command)) return false;
const tokens = splitShellArgs(command);
if (!tokens || tokens.length < 3) return false;
let commandIndex = 0;
if (normalizeOptionalLowercaseString(tokens[commandIndex]) === "env") commandIndex += 1;
while (/^[A-Za-z_][A-Za-z0-9_]*=/u.test(tokens[commandIndex] ?? "")) commandIndex += 1;
const packageRunner = skipOpenClawPackageRunner(tokens, commandIndex);
commandIndex = packageRunner.commandIndex;
let cliArgIndex = commandIndex + 1;
for (let consumed = consumeRootOptionToken(tokens, cliArgIndex); consumed > 0; consumed = consumeRootOptionToken(tokens, cliArgIndex)) cliArgIndex += consumed;
const action = normalizeOptionalLowercaseString(tokens[cliArgIndex + 1]);
const actionArgs = tokens.slice(cliArgIndex + 2);
return (isOpenClawExecutable(tokens[commandIndex]) || packageRunner.acceptsPackageSpec && isOpenClawPackageSpec(tokens[commandIndex])) && (normalizeOptionalLowercaseString(tokens[cliArgIndex]) === "cron" || normalizeOptionalLowercaseString(tokens[cliArgIndex]) === "automations") && (action === "add" || action === "create") && !actionArgs.some((token) => token === "-h" || token === "--help");
}
function didShellCronAddSucceed(args, result) {
if (!isOpenClawCronAddShellCommand(args)) return false;
const details = readExecToolDetails(result);
return details?.status === "completed" && details.exitCode === 0;
}
function readApplyPatchSummary(result) {
const details = readToolResultDetails(result);
const summary = asOptionalRecord(details?.summary);
if (!summary) return null;
return {
added: filterStringEntries(summary.added),
modified: filterStringEntries(summary.modified),
deleted: filterStringEntries(summary.deleted)
};
}
function buildPatchSummaryText(summary) {
const parts = [
"added",
"modified",
"deleted"
].flatMap((kind) => summary[kind].length > 0 ? [`${summary[kind].length} ${kind}`] : []);
return parts.length > 0 ? parts.join(", ") : "no file changes recorded";
}
function readMessagingText(record) {
for (const key of [
"content",
"message",
"text",
"body"
]) {
const value = readStringValue(record[key]);
if (value) return value;
}
}
function hasMessagingRichContent(record) {
const payload = {
presentation: record.presentation,
interactive: record.interactive,
channelData: record.channelData
};
try {
parseJsonMessageParam(payload, "presentation");
parseJsonMessageParam(payload, "interactive");
} catch {
return false;
}
return hasReplyPayloadContent(payload);
}
function queuePendingToolMedia(ctx, mediaReply, allowedMediaUrls, autoDeliveryMediaUrls) {
const indexByUrl = new Map(ctx.state.pendingToolMediaUrls.map((url, index) => [url.trim(), index]));
const attachments = ctx.state.pendingToolMediaAttachments ??= ctx.state.pendingToolMediaUrls.map(() => ({}));
const attachmentsByUrl = new Map(mediaReply.mediaUrls.map((url, index) => [url.trim(), mediaReply.attachments?.[index]]));
for (const mediaUrl of allowedMediaUrls) {
const normalized = mediaUrl.trim();
if (!normalized) continue;
if (mediaReply.trustedLocalMedia) ctx.state.pendingToolMediaTrustByUrl.set(normalized, true);
else if (!ctx.state.pendingToolMediaTrustByUrl.has(normalized)) ctx.state.pendingToolMediaTrustByUrl.set(normalized, false);
if (autoDeliveryMediaUrls.has(normalized)) ctx.state.toolAutoDeliveryMediaUrls.add(normalized);
else ctx.state.toolAutoDeliveryMediaUrls.delete(normalized);
const attachment = attachmentsByUrl.get(normalized);
const existingIndex = indexByUrl.get(normalized);
if (existingIndex !== void 0) {
if (attachment && Object.keys(attachments[existingIndex] ?? {}).length === 0) attachments[existingIndex] = attachment;
continue;
}
indexByUrl.set(normalized, ctx.state.pendingToolMediaUrls.length);
ctx.state.pendingToolMediaUrls.push(normalized);
attachments.push(attachment ?? {});
}
if (mediaReply.audioAsVoice) ctx.state.pendingToolAudioAsVoice = true;
}
function readExecApprovalPendingDetails(result) {
const outer = asOptionalObjectRecord(result);
const details = asOptionalRecord(outer?.details) ?? outer;
if (details?.status !== "approval-pending") return null;
const approvalId = readStringValue(details.approvalId) ?? "";
const approvalSlug = readStringValue(details.approvalSlug) ?? "";
const command = typeof details.command === "string" ? details.command : "";
const host = details.host === "node" ? "node" : details.host === "gateway" ? "gateway" : null;
if (!approvalId || !approvalSlug || !command || !host) return null;
return {
approvalId,
approvalSlug,
expiresAtMs: typeof details.expiresAtMs === "number" ? details.expiresAtMs : void 0,
allowedDecisions: Array.isArray(details.allowedDecisions) ? details.allowedDecisions.filter((decision) => decision === "allow-once" || decision === "allow-always" || decision === "deny") : void 0,
host,
command,
cwd: readStringValue(details.cwd),
nodeId: readStringValue(details.nodeId),
warningText: readStringValue(details.warningText)
};
}
function readExecApprovalUnavailableDetails(result) {
const outer = asOptionalObjectRecord(result);
const details = asOptionalRecord(outer?.details) ?? outer;
if (details?.status !== "approval-unavailable") return null;
const reason = details.reason === "initiating-platform-disabled" || details.reason === "initiating-platform-unsupported" || details.reason === "no-approval-route" ? details.reason : null;
if (!reason) return null;
return {
reason,
warningText: readStringValue(details.warningText),
channel: readStringValue(details.channel),
channelLabel: readStringValue(details.channelLabel),
accountId: readStringValue(details.accountId),
sentApproverDms: details.sentApproverDms === true,
host: details.host === "gateway" || details.host === "node" ? details.host : void 0,
nodeId: readStringValue(details.nodeId)
};
}
async function emitToolResultOutput(params) {
const { ctx, toolName, rawToolName, meta, isToolError, result, sanitizedResult } = params;
const details = asOptionalRecord(asOptionalObjectRecord(result)?.details);
const hasStructuredMedia = asOptionalRecord(details?.media) !== void 0;
const approvalPending = readExecApprovalPendingDetails(result);
const approvalUnavailable = !isToolError && approvalPending ? null : readExecApprovalUnavailableDetails(result);
if (!isToolError && (approvalPending || approvalUnavailable)) {
if (!ctx.params.onToolResult) return;
if (approvalPending) ctx.state.deterministicApprovalPromptPending = true;
try {
const replies = await execApprovalReplyModuleLoader.load();
if (approvalPending) {
await ctx.params.onToolResult(replies.buildTypedExecApprovalPendingReplyPayload(approvalPending));
ctx.state.deterministicApprovalPromptSent = true;
} else if (approvalUnavailable) await ctx.params.onToolResult?.(replies.buildExecApprovalUnavailableReplyPayload(approvalUnavailable));
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
ctx.log.warn(`failed to deliver exec approval prompt: ${message}`);
const approvalMeta = meta ? `${meta} · approval prompt delivery` : "approval prompt delivery";
const terminal = (ctx.params.observeToolTerminal ?? resolveFallbackToolTerminalObserver(ctx))({
toolName,
meta: approvalMeta,
executionStarted: false,
outcome: "failure",
failure: { error: `Approval prompt delivery failed: ${message}` }
});
ctx.state.lastToolError = terminal.lastToolError;
} finally {
if (approvalPending) ctx.state.deterministicApprovalPromptPending = false;
}
return;
}
const mediaReply = isToolError ? void 0 : extractToolResultMediaArtifact(result);
const mediaUrls = mediaReply ? filterToolResultMediaUrls(rawToolName, mediaReply.mediaUrls, result, ctx.trustedLocalMediaToolNames) : [];
if (!(toolName === "tts" && rawToolName.trim() === "tts" && ctx.builtinToolNames?.has("tts") === true && !isToolError && hasStructuredMedia && mediaUrls.length > 0) && ctx.shouldEmitToolOutput()) {
const outputText = extractToolResultText(sanitizedResult);
if (outputText) ctx.emitToolOutput(rawToolName, meta, outputText, hasStructuredMedia ? void 0 : result);
if (!hasStructuredMedia) return;
}
if (isToolError || !mediaReply || mediaUrls.length === 0) return;
queuePendingToolMedia(ctx, mediaReply, mediaUrls, new Set(mediaReply.trustedLocalMedia === true ? getCoreTtsToolResultMediaUrls(result) : []));
}
//#endregion
//#region src/agents/mcp-connect-action.ts
function readMcpConnectAction(result) {
const connect = asOptionalRecord(asOptionalRecord(asOptionalRecord(result)?.details)?.mcpConnect);
const serverName = typeof connect?.serverName === "string" ? connect.serverName.trim() : "";
const authorizationUrl = typeof connect?.authorizationUrl === "string" ? connect.authorizationUrl.trim() : "";
const parsed = URL.parse(authorizationUrl);
if (!serverName || parsed?.protocol !== "http:" && parsed?.protocol !== "https:") return;
return {
serverName,
authorizationUrl
};
}
//#endregion
//#region src/agents/embedded-agent-subscribe.handlers.tools.completion.ts
/** Handles a tool-execution result and commits replay, media, hook, and error state. */
async function handleToolExecutionEnd(ctx, evt, readResult) {
const toolName = normalizeToolPolicyName(evt.toolName);
const toolCallId = evt.toolCallId;
ctx.state.liveEditDiffStateById.delete(toolCallId);
if (toolName === "ask_user") cancelAskUserPromptDelivery(toolCallId, ctx.params.sessionKey, ctx.params.runId);
const runId = ctx.params.runId;
const result = evt.result;
const toolSendReceiptResult = ctx.consumeToolSendReceipt?.(toolCallId);
const observerIsError = evt.isError || isToolResultError(result);
const sanitizedResult = readResult();
const approvalUnavailable = isExecToolName(toolName) && readExecToolDetails(sanitizedResult)?.status === "approval-unavailable";
const isToolError = observerIsError && !approvalUnavailable;
if (!isToolError) {
const channelView = readMcpAppChannelView(result);
if (channelView) ctx.state.latestMcpAppChannelView = channelView;
const connectAction = readMcpConnectAction(result);
if (connectAction) ctx.state.latestMcpConnectAction = connectAction;
}
try {
ctx.params.onAgentToolResult?.({
toolName,
result: sanitizedResult,
isError: observerIsError
});
} catch (error) {
ctx.log.warn(`onAgentToolResult handler failed: tool=${toolName} error=${String(error)}`);
}
const eventResult = isExecToolName(toolName) ? capLiveExecResult(sanitizedResult) : sanitizedResult;
const toolStartKey = buildToolStartKey(runId, toolCallId);
const startData = toolStartData.get(toolStartKey);
toolStartData.delete(toolStartKey);
ctx.state.execLiveUpdateStateById?.delete(toolCallId);
const initialCallSummary = ctx.state.toolMetaById.get(toolCallId);
const initialArgs = asRecord(startData?.args);
const adjustedArgs = consumeAdjustedParamsForToolCall(toolCallId, runId);
const trackedExecutionStarted = consumeTrackedToolExecutionStarted(toolCallId, runId);
const executionPrevented = consumePreExecutionBlockedToolCall(toolCallId, runId);
const structuredReplaySafe = consumeStructuredReplaySafeToolCall(toolCallId, runId);
const startArgs = asOptionalObjectRecord(adjustedArgs) ?? initialArgs;
const explicitHideFromChannelProgress = evt.hideFromChannelProgress === true || startData?.hideFromChannelProgress === true;
const callSummary = buildToolCallSummary(toolName, startArgs, initialCallSummary?.meta, initialCallSummary?.instanceReplaySafe === true, initialCallSummary?.ownerKey, structuredReplaySafe);
const executionStarted = (trackedExecutionStarted ?? evt.executionStarted ?? true) && !executionPrevented;
const meta = callSummary.meta;
const asyncStarted = !isToolError && isAsyncStartedToolResult(sanitizedResult);
const asyncTaskIds = asyncStarted ? readAsyncStartedTaskIds(sanitizedResult) : {};
const codeModeSuspended = !isToolError && ctx.params.codeModeExecToolNames?.has(toolName) === true && readToolResultDetails(sanitizedResult)?.status === "waiting";
const resultRecord = asOptionalObjectRecord(result);
const terminate = resultRecord !== void 0 && "terminate" in resultRecord && resultRecord.terminate === true;
const terminalMeta = {
toolName,
toolCallId,
...startData?.parentToolCallId ? { parentToolCallId: startData.parentToolCallId } : {},
meta,
replaySafe: callSummary.replaySafe,
isError: observerIsError,
...terminate ? { terminate: true } : {},
...asyncStarted ? {
asyncStarted: true,
...asyncTaskIds
} : {},
...codeModeSuspended ? { codeModeSuspended: true } : {}
};
ctx.state.toolMetas.push(terminalMeta);
const acceptedSessionSpawn = toolName === "sessions_spawn" && !isToolError ? normalizeAcceptedSessionSpawnResult(sanitizedResult) : null;
if (acceptedSessionSpawn) ctx.state.acceptedSessionSpawns.push(acceptedSessionSpawn);
ctx.state.toolMetaById.delete(toolCallId);
ctx.state.toolSummaryById.delete(toolCallId);
const errorMessage = isToolError ? extractToolErrorMessage(sanitizedResult) : void 0;
const errorCode = isToolError ? extractToolErrorCode(sanitizedResult) : void 0;
const terminalDiagnostic = isToolError ? buildProcessTerminalDiagnostic(toolName, startArgs, sanitizedResult) : void 0;
const terminalErrorMessage = terminalDiagnostic && errorMessage && hasTerminalControlCharacter(errorMessage) ? void 0 : errorMessage;
const validationErrorSummary = isToolError && evt.executionStarted === false && evt.errorKind === "argument-validation" ? createToolValidationErrorSummary(toolName) : void 0;
const terminal = (ctx.params.observeToolTerminal ?? resolveFallbackToolTerminalObserver(ctx))({
toolCallId,
toolName,
arguments: startArgs,
result,
...meta ? { meta } : {},
executionStarted,
replaySafe: callSummary.replaySafe,
outcome: isToolError ? "failure" : "success",
...callSummary.ownerKey ? { ownerMutation: { ownerKey: callSummary.ownerKey } } : {},
...isToolError ? { failure: {
...errorCode ? { errorCode } : {},
...terminalErrorMessage ? { error: terminalErrorMessage } : {},
...terminalDiagnostic ? { terminalDiagnostic } : {},
...validationErrorSummary ? { validationErrorSummary } : {},
timedOut: isToolResultTimedOut(sanitizedResult) || void 0,
middlewareError: isMiddlewareToolResultError(sanitizedResult) || void 0
} } : {}
});
ctx.state.lastToolError = terminal.lastToolError;
const terminalErrorStatus = terminal.executionStarted ? "failed" : "blocked";
const toolErrorSummary = ctx.state.lastToolError ? summarizeToolValidationError(ctx.state.lastToolError) : void 0;
if (asyncStarted) ctx.state.hadDeterministicSideEffect = true;
if (terminal.sideEffectEvidence || acceptedSessionSpawn || asyncStarted) ctx.state.replayState = mergeEmbeddedRunReplayState(ctx.state.replayState, {
replayInvalid: true,
hadPotentialSideEffects: true
});
const messagingArgs = applyCurrentMessageProvider(toolName, startArgs, ctx.params.messageChannel);
const isMessagingInvocation = isMessagingTool(toolName);
const isMessagingSend = isMessagingInvocation && isMessagingToolSendAction(toolName, startArgs);
const hasMessagingTargetEvidence = isMessagingInvocation && isMessagingToolTargetEvidenceAction(toolName, startArgs);
const messageDelivery = readEmbeddedMessageDeliveryFact(readToolResultDetails(toolSendReceiptResult)?.messageDelivery);
const didDeliverMessagingResult = isMessagingInvocation && (messageDelivery ? messageDelivery.status === "settled" && (!isToolError || messageDelivery.partialDelivery) : isPluginNativeMessagingTool(toolName) && isDeliveredMessagingToolResult({
toolName,
args: startArgs,
result,
hookResult: toolSendReceiptResult,
isError: isToolError
}));
const messageText = isMessagingSend ? readMessagingText(startArgs) : void 0;
const argumentMediaUrls = isMessagingSend ? collectMessagingMediaUrlsFromRecord(startArgs) : [];
const hasRichContent = isMessagingSend && hasMessagingRichContent(startArgs);
const messageTarget = hasMessagingTargetEvidence ? extractMessagingToolSend(toolName, messagingArgs, {
config: ctx.params.config,
currentChannelId: ctx.params.currentChannelId,
currentMessagingTarget: ctx.params.currentMessagingTarget,
currentThreadId: ctx.params.currentThreadId,
currentMessageId: ctx.params.currentMessageId,
replyToMode: ctx.params.replyToMode,
hasRepliedRef: startData?.hasRepliedRef
}) : void 0;
const committedMediaUrls = didDeliverMessagingResult && isMessagingSend ? [...argumentMediaUrls, ...collectMessagingMediaUrlsFromToolResult(result)] : [];
const extractionResult = applyToolSendReceiptForExtraction(result, toolSendReceiptResult);
const confirmedMessageTarget = messageTarget && extractMessagingToolSendResult(messageTarget, extractionResult);
const deliveredMessageToolSourceReply = didDeliverMessagingResult && isDeliveredMessageToolOnlySourceReplyResult({
sourceReplyDeliveryMode: ctx.params.sourceReplyDeliveryMode,
toolName,
args: startArgs,
result,
hookResult: toolSendReceiptResult,
isError: isToolError,
allowExplicitSourceRoute: isDeliveredMessagingToolSendToCurrentSource({
send: confirmedMessageTarget,
config: ctx.params.config,
currentProvider: ctx.params.messageChannel,
currentAccountId: ctx.params.currentAccountId,
currentChannelId: ctx.params.currentChannelId,
currentMessagingTarget: ctx.params.currentMessagingTarget,
currentThreadId: ctx.params.currentThreadId,
sessionKey: ctx.params.sessionKey,
deliveredPayload: extractionResult
}),
deliveryConfirmed: didDeliverMessagingResult
});
const deliveredCurrentSourceReply = deliveredMessageToolSourceReply || isDeliveredCoreCurrentChannelWidgetResult({
coreBuiltinToolNames: ctx.params.coreBuiltinToolNames,
sourceReplyDeliveryMode: ctx.params.sourceReplyDeliveryMode,
toolName,
result,
isToolError
});
const sourceReplyFinal = deliveredMessageToolSourceReply || messageDelivery?.sourceReplyDelivered ? resolveMessageToolSourceReplyFinal(startArgs) : void 0;
ctx.state.sourceReplyDelivered ||= messageDelivery?.sourceReplyDelivered;
if (sourceReplyFinal !== false && (messageDelivery?.sourceReplyDelivered || deliveredCurrentSourceReply)) ctx.state.sourceReplyDeliveryState = "delivered";
if (didDeliverMessagingResult && messageText) {
ctx.state.messagingToolSentTexts.push(messageText);
ctx.state.messagingToolSentTextsNormalized.push(normalizeTextForComparison(messageText));
ctx.log.debug(`Committed messaging text: tool=${toolName} len=${messageText.length}`);
ctx.trimMessagingToolSent();
}
if (didDeliverMessagingResult && confirmedMessageTarget) {
ctx.state.messagingToolSentTargets.push({
...confirmedMessageTarget,
...messageText ? { text: messageText } : {},
...committedMediaUrls.length > 0 ? { mediaUrls: committedMediaUrls.slice() } : {},
...hasRichContent ? { hasRichContent: true } : {},
...sourceReplyFinal !== void 0 ? { sourceReplyFinal } : {}
});
ctx.trimMessagingToolSent();
}
if (deliveredCurrentSourceReply) {
ctx.state.messageToolOnlySourceReplyDelivered = true;
if (deliveredMessageToolSourceReply) {
const sourceReplyText = readMessageToolSourceReplyText(startArgs);
const normalizedSourceReplyText = sourceReplyText ? normalizeTextForComparison(sourceReplyText) : "";
if (normalizedSourceReplyText) {
ctx.state.currentSourceMessagingToolSentTextsNormalized.push(normalizedSourceReplyText);
ctx.trimMessagingToolSent();
}
}
ctx.params.onDeliveredMessageToolOnlySourceReply?.();
}
if (didDeliverMessagingResult && isMessagingSend) {
if (committedMediaUrls.length > 0) {
ctx.state.messagingToolSentMediaUrls.push(...committedMediaUrls);
ctx.trimMessagingToolSent();
}
const sourceReplyPayload = extractMessagingToolSourceReplyPayload(result);
if (sourceReplyPayload) {
ctx.state.messagingToolSourceReplyPayloads.push({
...sourceReplyPayload,
...sourceReplyFinal !== void 0 ? { sourceReplyFinal } : {}
});
ctx.trimMessagingToolSent();
}
}
ctx.state.turnToolsOnlySourceProgress = (ctx.state.turnToolsOnlySourceProgress ?? true) && sourceReplyFinal === false && isMessagingSend && !isToolError && !messageDelivery?.partialDelivery;
ctx.state.lastToolTurnOnlySourceProgress = ctx.state.turnToolsOnlySourceProgress;
const toolAuthoredSourceReply = !isToolError && !startData?.parentToolCallId && ctx.params.sourceReplyCapableToolNames?.has(toolName) === true ? captureToolAuthoredSourceReply({
result,
toolCallId,
idempotencyScope: evt.assistantTurnId ?? runId
}) : void 0;
if (toolAuthoredSourceReply) {
ctx.state.messagingToolSourceReplyPayloads.push(toolAuthoredSourceReply);
ctx.trimMessagingToolSent();
}
if (!isToolError && (isAutomationsToolName(toolName) && isCronAddAction(startArgs) || isExecToolName(toolName) && didShellCronAddSucceed(startArgs, result))) ctx.state.successfulCronAdds += 1;
if (!isToolError && toolName === "heartbeat_respond" && ctx.state.heartbeatToolResponse === void 0 && asOptionalObjectRecord(resultRecord?.details)?.status === "accepted") {
const response = normalizeHeartbeatToolResponse(resultRecord?.details);
if (response) {
ctx.state.heartbeatToolResponse = response;
runBestEffortCallback({
label: "heartbeat tool response",
log: ctx.log,
callback: () => ctx.params.onHeartbeatToolResponse?.(response)
});
}
}
const planUpdate = !isToolError && toolName === "progress_card" ? projectProgressCardChannelUpdate(startArgs) : void 0;
if (planUpdate) {
const planEvent = {
stream: "plan",
data: {
phase: "update",
title: "Plan updated",
source: "openclaw",
...planUpdate
}
};
emitAgentEvent({
runId: ctx.params.runId,
...planEvent
});
emitAgentEventCallbackBestEffort(ctx, planEvent);
}
const endedAt = Date.now();
const execDetails = readExecToolDetails(sanitizedResult);
const itemData = {
...projectAgentToolActivity({
toolCallId,
name: toolName,
phase: "result",
args: startArgs,
meta,
result: sanitizedResult,
status: isToolError ? terminalErrorStatus : "completed",
hideFromChannelProgress: explicitHideFromChannelProgress
}),
startedAt: startData?.startTime,
endedAt,
...errorMessage ? { error: errorMessage } : {}
};
const hideFromChannelProgress = explicitHideFromChannelProgress;
terminalMeta.activity = itemData;
const createResultData = () => ({
phase: "result",
name: toolName,
toolCallId,
...startData?.parentToolCallId ? { parentToolCallId: startData.parentToolCallId } : {},
meta,
isError: isToolError,
commandBearing: callSummary.commandBearing,
...toolErrorSummary ? { toolErrorSummary } : {},
...hideFromChannelProgress ? { hideFromChannelProgress: true } : {}
});
emitAgentEvent({
runId: ctx.params.runId,
stream: "tool",
data: {
...createResultData(),
result: eventResult
}
});
emitTrackedItemEvent(ctx, itemData);
emitAgentEventCallbackBestEffort(ctx, {
stream: "tool",
data: createResultData()
});
if (isExecToolName(toolName)) {
const commandItemId = buildCommandItemId(toolCallId);
if (execDetails?.status === "approval-pending" || execDetails?.status === "approval-unavailable") {
const approvalStatus = execDetails.status === "approval-pending" ? "pending" : "unavailable";
emitToolActivityEvent(ctx, {
stream: "approval",
data: {
phase: "requested",
kind: "exec",
status: approvalStatus,
title: approvalStatus === "pending" ? "Command approval requested" : "Command approval unavailable",
itemId: commandItemId,
toolCallId,
...execDetails.status === "approval-pending" ? {
approvalId: execDetails.approvalId,
approvalSlug: execDetails.approvalSlug
} : {},
command: execDetails.command,
host: execDetails.host,
...execDetails.status === "approval-unavailable" ? { reason: execDetails.reason } : {},
message: execDetails.warningText
}
});
} else {
const output = extractLiveExecOutput(eventResult);
const rawOutput = extractExecOutput(sanitizedResult);
const commandStatus = execDetails?.status === "failed" || isToolError ? terminalErrorStatus : "completed";
emitToolActivityEvent(ctx, {
stream: "command_output",
data: {
itemId: commandItemId,
phase: "end",
title: buildCommandItemTitle(toolName, meta),
toolCallId,
name: toolName,
...output ? { output } : {},
status: commandStatus,
...execDetails && "exitCode" in execDetails ? { exitCode: execDetails.exitCode } : {},
...execDetails && "durationMs" in execDetails && typeof execDetails.durationMs === "number" && Number.isFinite(execDetails.durationMs) && execDetails.durationMs >= 0 ? { durationMs: execDetails.durationMs } : {},
...execDetails && "cwd" in execDetails && typeof execDetails.cwd === "string" ? { cwd: execDetails.cwd } : {}
}
});
if (typeof rawOutput === "string") {
const parsedApprovalResult = parseExecApprovalResultText(rawOutput);
if (parsedApprovalResult.kind === "denied") emitToolActivityEvent(ctx, {
stream: "approval",
data: {
phase: "resolved",
kind: "exec",
status: normalizeOptionalLowercaseString(parsedApprovalResult.metadata)?.includes("approval-request-failed") ? "failed" : "denied",
title: "Command approval resolved",
itemId: commandItemId,
toolCallId,
message: parsedApprovalResult.body || parsedApprovalResult.raw
}
});
}
}
}
if (resolveFileMutationToolName(toolName) === "apply_patch") {
const patchSummary = readApplyPatchSummary(sanitizedResult);
const patchItemId = `patch:${toolCallId}`;
if (patchSummary) emitToolActivityEvent(ctx, {
stream: "patch",
data: {
itemId: patchItemId,
phase: "end",
title: meta ? `patch ${meta}` : "apply patch",
toolCallId,
name: toolName,
added: patchSummary.added,
modified: patchSummary.modified,
deleted: patchSummary.deleted,
summary: buildPatchSummaryText(patchSummary)
}
});
}
ctx.log.debug(`embedded run tool end: runId=${ctx.params.runId} tool=${toolName} toolCallId=${toolCallId}`);
if (!planUpdate) await emitToolResultOutput({
ctx,
toolName,
rawToolName: evt.toolName,
meta,
isToolError,
result,
sanitizedResult
});
await Promise.resolve(ctx.params.onToolStreamBoundary?.()).catch((error) => {
ctx.log.debug(`embedded run tool stream boundary callback failed: ${String(error)}`);
});
const hookRunnerAfter = ctx.hookRunner ?? (await loadHookRunnerGlobal()).getGlobalHookRunner();
if (hookRunnerAfter?.hasHooks("after_tool_call")) {
const durationMs = startData?.startTime != null ? Date.now() - startData.startTime : void 0;
const hookEvent = {
toolName,
params: startArgs,
runId,
toolCallId,
result: sanitizedResult,
error: isToolError ? extractToolErrorMessage(sanitizedResult) : void 0,
durationMs
};
hookRunnerAfter.runAfterToolCall(hookEvent, {
toolName,
agentId: ctx.params.agentId,
sessionKey: ctx.params.sessionKey,
sessionId: ctx.params.sessionId,
runId,
toolCallId
}).catch((err) => {
ctx.log.warn(`after_tool_call hook failed: tool=${toolName} error=${String(err)}`);
});
}
terminal.executedArguments ??= startArgs;
return Object.assign(terminal, { isError: observerIsError });
}
//#endregion
//#region src/agents/embedded-agent-subscribe.handlers.tools.progress.ts
const LIVE_EXEC_UPDATE_MIN_INTERVAL_MS = 250;
function readChannelToolProgress(result) {
const progress = asOptionalRecord(asOptionalObjectRecord(result)?.progress);
if (progress?.visibility !== "channel" || progress.privacy !== "public") return;
const text = readStringValue(progress.text)?.trim();
return text ? truncateLiveExecOutput(text) : void 0;
}
function prepareLiveExecUpdate(ctx, toolCallId, partialResult, itemMetadata) {
const now = Date.now();
const state = ctx.state.execLiveUpdateStateById ??= /* @__PURE__ */ new Map();
const previous = state.get(toolCallId);
if (previous && now - previous.lastEmittedAtMs < LIVE_EXEC_UPDATE_MIN_INTERVAL_MS) {
const emitItems = previous.itemMetadata.name !== itemMetadata.name || previous.itemMetadata.meta !== itemMetadata.meta || previous.itemMetadata.commandBearing !== itemMetadata.commandBearing || previous.itemMetadata.hideFromChannelProgress !== itemMetadata.hideFromChannelProgress;
if (emitItems) previous.itemMetadata = itemMetadata;
return { emitItems };
}
const result = capLiveExecResult(sanitizeToolResult(partialResult));
state.set(toolCallId, {
lastEmittedAtMs: Date.now(),
itemMetadata
});
return {
update: { result },
emitItems: true
};
}
/** Handles partial tool output and emits throttled live UI updates. */
function handleToolExecutionUpdate(ctx, evt) {
const toolName = normalizeToolPolicyName(evt.toolName);
const toolCallId = evt.toolCallId;
const startData = toolStartData.get(buildToolStartKey(ctx.params.runId, toolCallId));
const parentToolCallId = startData?.parentToolCallId;
const args = peekAdjustedParamsForToolCall(toolCallId, ctx.params.runId) ?? startData?.args;
if (startData && evt.hideFromChannelProgress === true) startData.hideFromChannelProgress = true;
const explicitHideFromChannelProgress = evt.hideFromChannelProgress === true || startData?.hideFromChannelProgress === true;
const partial = evt.partialResult;
const isExecTool = isExecToolName(toolName);
const toolMeta = ctx.state.toolMetaById.get(toolCallId);
const execProgress = isExecTool ? prepareLiveExecUpdate(ctx, toolCallId, partial, {
name: toolName,
meta: toolMeta?.meta,
commandBearing: toolMeta?.commandBearing,
hideFromChannelProgress: explicitHideFromChannelProgress
}) : void 0;
const execUpdate = execProgress?.update;
const liveResult = isExecTool ? execUpdate?.result : sanitizeToolResult(partial);
const toolProgress = isExecTool ? void 0 : readChannelToolProgress(liveResult);
const itemData = {
...projectAgentToolActivity({
toolCallId,
name: toolName,
phase: "update",
args,
meta: toolMeta?.meta,
hideFromChannelProgress: explicitHideFromChannelProgress
}),
commandBearing: toolMeta?.commandBearing,
...toolProgress ? {
progressText: toolProgress,
meta: void 0
} : {}
};
const updateData = (includeResult) => ({
phase: "update",
name: toolName,
toolCallId,
...parentToolCallId ? { parentToolCallId } : {},
...includeResult ? { partialResult: liveResult } : {},
...explicitHideFromChannelProgress ? { hideFromChannelProgress: true } : {}
});
const emitDetailedLiveUpdate = !toolProgress && (!isExecTool || execUpdate !== void 0);
if (emitDetailedLiveUpdate) emitAgentEvent({
runId: ctx.params.runId,
stream: "tool",
data: updateData(true)
});
emitTrackedItemEvent(ctx, itemData, execProgress?.emitItems);
if (!toolProgress) emitAgentEventCallbackBestEffort(ctx, {
stream: "tool",
data: updateData(false)
});
if (isExecTool) {
const output = extractLiveExecOutput(liveResult);
if (emitDetailedLiveUpdate && output) emitToolActivityEvent(ctx, {
stream: "command_output",
data: {
itemId: buildCommandItemId(toolCallId),
phase: "delta",
title: buildCommandItemTitle(toolName, toolMeta?.meta),
toolCallId,
name: toolName,
output,
status: "running"
}
});
}
}
//#endregion
//#region src/agents/embedded-agent-subscribe.trajectory.ts
function recordEmbeddedToolTrajectoryEvent(ctx, event, readResult) {
const recorder = ctx.params.trajectoryRecorder;
if (!recorder) return;
try {
if (event.type === "tool_execution_start") recorder.recordEvent("tool.call", {
toolCallId: event.toolCallId,
name: normalizeToolPolicyName(event.toolName),
args: sanitizeToolArgs(event.args)
});
else if (event.type === "tool_execution_end") {
const name = normalizeToolPolicyName(event.toolName);
const result = readResult();
recorder.recordEvent("tool.result", {
toolCallId: event.toolCallId,
name,
success: !(event.isError || isToolResultError(event.result)),
result: isExecToolName(name) ? capLiveExecResult(result) : result
});
}
} catch (error) {
ctx.log.debug(`tool trajectory capture failed: ${String(error)}`);
}
}
//#endregion
//#region src/agents/embedded-agent-subscribe.handlers.ts
/**
* Dispatches serialized embedded-agent subscription events to specific handlers.
*/
/** Create the serialized event dispatcher for subscribed embedded-agent sessions. */
function createEmbeddedAgentSessionEventHandler(ctx) {
let assistantSource;
const scheduleEvent = (evt, handler) => {
const onError = (err) => {
ctx.log.debug(`${evt.type} handler failed: ${String(err)}`);
};
const run = () => {
try {
if (evt.type !== "message_update") ctx.flushAssistantStream();
return handler();
} catch (err) {
onError(err);
return;
}
};
const result = ctx.state.pendingEventChain ? ctx.state.pendingEventChain.then(run) : run();
if (!isPromiseLike(result)) return;
const task = Promise.resolve(result).then(() => {}, onError).finally(() => {
if (ctx.state.pendingEventChain === task) ctx.state.pendingEventChain = null;
});
ctx.state.pendingEventChain = task;
return task;
};
return (evt) => {
if ((evt.type === "message_start" || evt.type === "message_update" || evt.type === "message_end") && evt.message.role === "assistant" && !isSubscribeTranscriptOnlyOpenClawAssistantMessage(evt.message)) {
if (evt.type === "message_start" || !assistantSource) assistantSource = {};
bindAgentAssistantSource(evt.message, assistantSource);
}
ctx.captureModelEvent(evt);
const readResult = evt.type === "tool_execution_end" ? prepareToolResult(evt.result) : void 0;
recordEmbeddedToolTrajectoryEvent(ctx, evt, readResult);
switch (evt.type) {
case "message_start":
scheduleEvent(evt, () => handleMessageStart(ctx, evt));
return;
case "message_update":
scheduleEvent(evt, () => handleMessageUpdate(ctx, evt));
return;
case "message_end":
scheduleEvent(evt, () => handleMessageEnd(ctx, evt));
return;
case "turn_start":
scheduleEvent(evt, () => {
ctx.state.turnToolsOnlySourceProgress = void 0;
});
return;
case "turn_end":
scheduleEvent(evt, () => ctx.noteLastAssistant(evt.message));
return;
case "tool_execution_start":
scheduleEvent(evt, () => handleToolExecutionStart(ctx, evt));
return;
case "tool_execution_update":
scheduleEvent(evt, () => handleToolExecutionUpdate(ctx, evt));
return;
case "tool_execution_end":
scheduleEvent(evt, () => handleToolExecutionEnd(ctx, evt, readResult));
return;
case "agent_start":
scheduleEvent(evt, () => handleAgentStart(ctx));
return;
case "compaction_start":
scheduleEvent(evt, () => handleCompactionStart(ctx, evt));
return;
case "compaction_end":
scheduleEvent(evt, () => handleCompactionEnd(ctx, evt));
return;
case "agent_end": return scheduleEvent(evt, () => handleAgentEnd(ctx, evt));
}
};
}
//#endregion
//#region src/agents/embedded-agent-subscribe.model-state.ts
function preserveAssistantUsage(message, pending) {
if (!pending) return;
const final = normalizeUsage(message.usage);
if (hasNonzeroUsage(final)) {
if (pending.cost?.totalOrigin === "provider-billed" && final.cost?.totalOrigin !== "provider-billed") message.usage.cost = {
...makeZeroUsageSnapshot().cost,
...message.usage.cost,
...pending.cost
};
return;
}
const input = pending.input ?? 0;
const output = pending.output ?? 0;
const cacheRead = pending.cacheRead ?? 0;
const cacheWrite = pending.cacheWrite ?? 0;
message.usage = {
...makeZeroUsageSnapshot(),
input,
output,
cacheRead,
cacheWrite,
...pending.cacheWrite1h !== void 0 ? { cacheWrite1h: pending.cacheWrite1h } : {},
...pending.contextUsage ? { contextUsage: { ...pending.contextUsage } } : {},
totalTokens: pending.total ?? input + output + cacheRead + cacheWrite,
...pending.reasoningTokens !== void 0 ? { reasoningTokens: pending.reasoningTokens } : {}
};
Object.assign(message.usage.cost, pending.cost);
}
/** Owns model facts at event ingress, independently of queued reply delivery. */
function createEmbeddedModelState(params, log) {
const totals = createUsageAccumulator();
let pending;
let lastUsage;
let retryUsage;
let completed;
let successfulModelResponse = false;
let publishedMessageModel;
const runContext = getAgentRunContext(params.runId);
const publishMessageModel = (message, reset) => {
if (reset) publishedMessageModel = void 0;
const provider = message.provider?.trim();
const model = message.responseModel?.trim() || message.model?.trim();
const identity = provider && model ? `${provider}\0${model}` : void 0;
if (!identity || identity === publishedMessageModel) return;
const event = {
runId: params.runId,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
agentId: params.agentId,
lifecycleGeneration: params.lifecycleGeneration,
stream: "lifecycle",
data: {
phase: "model",
provider,
model
}
};
if (runContext) emitAgentEventForRunContext(event, runContext);
else emitAgentEvent(event);
publishedMessageModel = identity;
};
const recordPendingUsage = (raw) => {
const usage = normalizeUsage(raw);
if (!hasObservedModelUsage(usage)) return;
const cost = usage.cost;
const next = pending && !hasNonzeroUsage(usage) ? {
...pending,
cost
} : usage;
if (pending?.cost?.totalOrigin === "provider-billed" && cost?.totalOrigin !== "provider-billed") next.cost = pending.cost;
pending = next;
};
const recordModelUsage = (usage) => {
if (!hasObservedModelUsage(usage)) return;
mergeUsageIntoAccumulator(totals, usage);
if (!hasNonzeroUsage(usage) || !params.lifecycleGeneration) return;
const data = emitAgentRunOutputTokens({
runId: params.runId,
lifecycleGeneration: params.lifecycleGeneration,
outputTokens: usage.output ?? 0
});
if (data && params.onAgentEvent) runBestEffortCallback({
label: "usage agent event",
log,
callback: () => params.onAgentEvent?.({
stream: "usage",
data
})
});
};
const recordContextAccounting = (message, successful) => params.onContextAccountingEvent?.({
kind: "model",
contextTokens: deriveSessionTotalTokens({ lastCallUsage: normalizeUsage(message.usage) }),
successful
});
return {
captureModelEvent: (evt) => {
if (evt.type === "compaction_end") {
if (evt.outcome.status === "completed" && evt.outcome.willRetry) {
completed = void 0;
retryUsage = lastUsage ?? retryUsage;
lastUsage = void 0;
}
return;
}
if (evt.type !== "message_start" && evt.type !== "message_update" && evt.type !== "message_end" && evt.type !== "turn_end") return;
const message = evt.message;
if (message.role !== "assistant" || isSubscribeTranscriptOnlyOpenClawAssistantMessage(message)) return;
publishMessageModel(message, evt.type === "message_start");
switch (evt.type) {
case "turn_end":
if (!successfulModelResponse && (message.stopReason === "stop" || message.stopReason === "toolUse") && !isProviderRefusalAssistantError(message)) {
successfulModelResponse = true;
recordContextAccounting(message, true);
}
return;
case "message_start":
pending = void 0;
return;
case "message_update":
if (evt.assistantMessageEvent.type === "text_end") recordPendingUsage(message.usage);
return;
case "message_end":
recordPendingUsage(message.usage);
preserveAssistantUsage(message, pending);
if (hasNonzeroUsage(pending)) lastUsage = { ...pending };
recordModelUsage(pending);
runBestEffortCallback({
label: "model usage observation",
log,
callback: () => params.onModelUsage?.(pending)
});
pending = void 0;
completed = applyAssistantDeliveryDirectives(structuredClone(message));
lastUsage ??= message.stopReason === "error" ? retryUsage : void 0;
retryUsage = void 0;
recordContextAccounting(message, false);
}
},
recordAuxiliaryUsage: (usage) => recordModelUsage(normalizeUsage(usage)),
getUsageTotals: () => toNormalizedUsage(totals),
getLastAssistantUsage: () => normalizeUsage(lastUsage),
getCurrentAttemptAssistant: () => completed ? structuredClone(completed) : void 0,
hasSuccessfulModelResponse: () => successfulModelResponse
};
}
//#endregion
//#region src/agents/embedded-agent-subscribe.reply-delivery.ts
const isStreamAppend = ({ data, finalMessage }) => !finalMessage && !data.replace && !data.mediaUrls?.length && !data.managedMediaUrls?.length;
const mergeStreamAppend = (previous, next) => ({
...next,
delta: previous.delta + next.delta
});
function createReplyDelivery({ params, state, log }) {
const assistantTexts = state.assistantTexts;
const deferredAssistantScopes = [];
const lastEmittedCommentaryByItem = /* @__PURE__ */ new Map();
const pendingBlockReplyTasks = /* @__PURE__ */ new Set();
const pendingPartialReplyTasks = /* @__PURE__ */ new Set();
let streamScope = {};
const drainPartialReply = (scope) => {
if (!scope.delivery || !scope.pending || state.unsubscribed || scope === streamScope && scope.active) return;
const data = scope.delivery.data;
scope.pending = false;
scope.active = true;
const settled = () => {
if (scope === streamScope) {
scope.active = false;
drainPartialReply(scope);
}
};
runBestEffortCallback({
callback: () => params.onPartialReply?.(data),
label: "assistant partial reply",
log,
pending: pendingPartialReplyTasks,
onSuccess: settled,
onError: settled
});
};
const streamId = randomUUID();
let messageIndex = -1;
let assistantItemId = "";
const raw = {
text: "",
prefix: "",
blockIndex: -1
};
const display = { ...raw };
let finalText = "";
let finalized = false;
const advanceStream = (stream, data, finalMessage) => {
if (stream.blockIndex !== state.assistantMessageIndex) {
stream.prefix = stream.text;
stream.blockIndex = state.assistantMessageIndex;
}
const text = finalMessage ? data.text : stream.prefix && data.text ? `${stream.prefix}\n${data.text}` : stream.prefix || data.text;
const replace = finalMessage ? !text.startsWith(stream.text) : data.replace === true;
const delta = finalMessage ? replace ? "" : text.slice(stream.text.length) : stream.prefix && stream.text.length === stream.prefix.length && data.delta ? `\n${data.delta}` : data.delta;
const eventData = text !== stream.text || data.mediaUrls?.length || data.managedMediaUrls?.length ? {
...data,
text,
delta,
replace: replace || void 0,
itemId: assistantItemId
} : void 0;
stream.text = text;
return eventData;
};
const publishAgentEvent = (event, assistantSource, assistantProjection) => {
const owned = {
runId: params.runId,
lifecycleGeneration: params.lifecycleGeneration,
...event
};
if (!emitAgentEventWithAssistantSourceIfCurrent(owned, assistantSource, assistantProjection)) return;
if (params.onAgentEvent) runBestEffortCallback({
label: "assistant agent event",
log,
callback: () => params.onAgentEvent?.(event)
});
};
const emitAssistantStreamDataSafely = (scope) => {
if (!scope.delivery || scope.emitted || state.unsubscribed) return;
const delivery = scope.delivery;
const { eventData } = delivery;
scope.emitted = true;
scope.pending ||= delivery.emitPartialReply && Boolean(params.onPartialReply) && state.shouldEmitPartialReplies;
const itemId = eventData?.itemId ?? "";
const progressText = eventData?.phase === "commentary" ? eventData.text.trimEnd() : "";
const preamblePhase = delivery.finalMessage ? "end" : "update";
const commentarySignature = `${preamblePhase}\0${progressText}`;
const event = progressText.trim() ? {
stream: "item",
data: {
kind: "preamble",
title: "Preamble",
phase: preamblePhase,
progressText,
...itemId ? { itemId } : {}
}
} : !eventData || eventData.phase === "commentary" ? void 0 : {
stream: "assistant",
data: eventData
};
if (event && (event.stream !== "item" || lastEmittedCommentaryByItem.get(itemId) !== commentarySignature)) {
if (event.stream === "item") lastEmittedCommentaryByItem.set(itemId, commentarySignature);
publishAgentEvent(event, delivery.assistantSource, delivery.assistantProjection);
}
drainPartialReply(scope);
};
const emitAssistantStreamData = (data, options) => {
if (state.unsubscribed) return;
const assistantSource = readAgentAssistantSource(state.lastAssistant);
let eventData;
let assistantProjection;
if (data.phase === "commentary") {
if (messageIndex === state.assistantMessageStartIndex && display.text !== finalText) {
display.prefix = display.text = finalText;
assistantProjection = {
itemId: assistantItemId,
text: finalText,
replace: true
};
for (const scope of [streamScope, ...deferredAssistantScopes]) {
const delivery = scope.delivery;
if (delivery && delivery.blockIndex >= messageIndex && delivery.data.phase !== "final_answer") delivery.assistantProjection = assistantProjection;
}
}
eventData = data;
} else {
if (messageIndex !== state.assistantMessageStartIndex) {
messageIndex = state.assistantMessageStartIndex;
assistantItemId = `${streamId}:${messageIndex}`;
for (const stream of [raw, display]) {
stream.prefix = stream.text = "";
stream.blockIndex = state.assistantMessageIndex;
}
finalText = "";
finalized = false;
}
if (!finalized || options?.finalMessage) {
eventData = advanceStream(raw, data, options?.finalMessage === true);
const displayData = advanceStream(display, data, options?.finalMessage === true);
assistantProjection = {
itemId: assistantItemId,
text: display.text,
replace: displayData?.replace === true
};
finalText = data.phase === "final_answer" ? display.text : display.text.startsWith(finalText) ? finalText : "";
finalized = options?.finalMessage === true;
}
if (options?.finalMessage && state.lastAssistant?.stopReason === "error") {
const itemId = assistantItemId;
const text = raw.text;
const displayText = display.text;
params.assistantErrorTranscript?.bindStream(state.lastAssistant, (visible) => {
clearAssistantStream();
publishAgentEvent({
stream: "assistant",
data: {
itemId,
text: visible ? text : "",
delta: "",
replace: true
}
}, assistantSource, {
itemId,
text: visible ? displayText : "",
replace: true
});
});
}
}
const delivery = {
assistantSource,
assistantProjection,
data,
eventData,
emitPartialReply: options?.emitPartialReply === true,
finalMessage: options?.finalMessage === true,
blockIndex: state.assistantMessageIndex
};
if (!eventData && !delivery.emitPartialReply) return;
const previous = streamScope.delivery;
const deferred = state.deferBlockReplyDelivery && data.phase !== "commentary";
const coalesce = previous && isStreamAppend(previous) && isStreamAppend(delivery) && previous.blockIndex === delivery.blockIndex && previous.assistantSource === delivery.assistantSource && previous.data.phase === data.phase && previous.data.itemId === data.itemId && previous.emitPartialReply === delivery.emitPartialReply && Boolean(previous.eventData) === Boolean(eventData);
const scope = coalesce ? streamScope : flushAssistantStream(delivery);
if (coalesce) {
if (!scope.emitted || scope.pending) delivery.data = mergeStreamAppend(previous.data, data);
if (!scope.emitted && previous.eventData && eventData) delivery.eventData = mergeStreamAppend(previous.eventData, eventData);
scope.delivery = delivery;
scope.emitted = false;
}
if (!deferred) emitAssistantStreamDataSafely(scope);
};
const flushAssistantStream = (delivery) => {
const previous = streamScope;
const scope = { delivery };
streamScope = scope;
if (delivery && state.deferBlockReplyDelivery && delivery.data.phase !== "commentary") deferredAssistantScopes.push(scope);
if (!state.deferBlockReplyDelivery) for (const deferred of deferredAssistantScopes.splice(0)) {
emitAssistantStreamDataSafely(deferred);
deferred.delivery = void 0;
}
if (!state.deferBlockReplyDelivery || previous.delivery?.data.phase === "commentary") {
emitAssistantStreamDataSafely(previous);
drainPartialReply(previous);
previous.delivery = void 0;
}
return scope;
};
const clearAssistantStream = () => {
streamScope.delivery = void 0;
streamScope = {};
deferredAssistantScopes.length = 0;
};
const noteLastAssistant = (msg) => {
if (msg.role === "assistant") state.lastAssistant = msg;
};
const deferredToolMediaReplies = /* @__PURE__ */ new WeakMap();
const emitBlockReplySafely = (payload, options) => {
if (!params.onBlockReply) return;
const assistantMessageIndex = getReplyPayloadMetadata(payload)?.assistantMessageIndex;
runBestEffortCallback({
callback: () => assistantMessageIndex === void 0 ? params.onBlockReply?.(payload) : params.onBlockReply?.(payload, { assistantMessageIndex }),
label: "block reply",
log,
pending: pendingBlockReplyTasks,
onSuccess: () => {
if (!payload.isReasoning && hasAssistantVisibleReply(payload)) {
state.visibleBlockReplyCount += 1;
if (options?.pendingToolMedia) {
state.pendingToolMediaDeliveryFailed = false;
state.hasToolMediaBlockReply = true;
}
for (const url of options?.autoDeliveryMediaUrls ?? []) state.toolAutoDeliveryMediaUrls.delete(url);
}
},
onError: () => {
if (options?.pendingToolMedia) restorePendingToolMediaReply(state, options.pendingToolMedia);
}
});
};
const emitBlockReply = (payload, options) => {
flushAssistantStream();
const withAssistantDirectives = consumePendingAssistantReplyDirectivesIntoReply(state, payload);
const pendingToolMedia = payload.isReasoning || options?.consumePendingToolMedia === false ? null : readPendingToolMediaReply(state);
const withToolMedia = options?.consumePendingToolMedia === false ? withAssistantDirectives : consumePendingToolMediaIntoReply(state, withAssistantDirectives);
const sentMediaUrls = new Set(state.messagingToolSentMediaUrls.map((url) => url.trim()));
const autoDeliveryMediaUrls = params.sourceReplyDeliveryMode === "message_tool_only" ? (pendingToolMedia?.mediaUrls ?? []).filter((url) => state.toolAutoDeliveryMediaUrls.has(url.trim()) && !sentMediaUrls.has(url.trim())) : [];
const pendingAttachments = new Map((pendingToolMedia?.mediaUrls ?? []).map((url, index) => [url.trim(), pendingToolMedia?.attachments?.[index] ?? {}]));
const blockPayload = autoDeliveryMediaUrls.length === 0 ? withToolMedia : markReplyPayloadForSourceSuppressionDelivery({
mediaUrls: autoDeliveryMediaUrls,
mediaUrl: autoDeliveryMediaUrls[0],
attachments: autoDeliveryMediaUrls.map((url) => pendingAttachments.get(url.trim()) ?? {}),
audioAsVoice: pendingToolMedia?.audioAsVoice || void 0,
trustedLocalMedia: true
});
const assistantTranscriptMediaUrls = Array.from(new Set(payload.mediaUrls ?? []));
copyReplyPayloadMetadata(payload, blockPayload);
if (params.silentExpected && !shouldKeepPayloadDuringSilentTurn(blockPayload)) return;
const taggedPayload = options?.assistantMessageIndex !== void 0 ? setReplyPayloadMetadata(blockPayload, {
assistantMessageIndex: options.assistantMessageIndex,
assistantMessageStartIndex: state.assistantMessageStartIndex,
...assistantTranscriptMediaUrls.length > 0 ? { assistantTranscriptMediaUrls } : {}
}) : blockPayload;
if (blockPayload.text && options?.blockSourceText !== void 0) setReplyPayloadMetadata(taggedPayload, {
blockSourceText: options.blockSourceText,
blockSourceRange: options.blockSourceRange
});
if (state.deferBlockReplyDelivery) {
if (pendingToolMedia) deferredToolMediaReplies.set(taggedPayload, {
pendingToolMedia,
autoDeliveryMediaUrls
});
state.deferredBlockReplies.push(taggedPayload);
return;
}
emitBlockReplySafely(taggedPayload, {
pendingToolMedia,
autoDeliveryMediaUrls
});
};
const releaseDeferredReplies = () => {
const isSuperseded = (index) => {
if (index === void 0) return false;
return index < (state.answerSegments.find((candidate) => index <= candidate.messageEnd)?.finalMessageStart ?? state.assistantMessageStartIndex);
};
for (const scope of deferredAssistantScopes) {
const delivery = scope.delivery;
if (delivery && isSuperseded(delivery.blockIndex)) {
if (!delivery.data.mediaUrls?.length) scope.delivery = void 0;
else {
delivery.data = {
...delivery.data,
text: "",
delta: ""
};
if (delivery.eventData) delivery.eventData = {
...delivery.eventData,
text: "",
delta: ""
};
if (delivery.assistantProjection) delivery.assistantProjection = {
...delivery.assistantProjection,
text: ""
};
}
}
}
const replies = state.deferredBlockReplies.splice(0);
for (const payload of replies) {
const index = getReplyPayloadMetadata(payload)?.assistantMessageIndex;
if (!payload.isReasoning && isSuperseded(index)) {
payload.text = void 0;
setReplyPayloadMetadata(payload, { blockSourceText: void 0 });
}
}
state.deferBlockReplyDelivery = false;
flushAssistantStream();
for (const payload of replies) {
if (!hasAssistantVisibleReply(payload)) continue;
const deferredToolMedia = deferredToolMediaReplies.get(payload);
emitBlockReplySafely(payload, deferredToolMedia);
}
};
const clearDeferredBlockReplies = () => {
state.deferredBlockReplies.length = 0;
};
const rememberAssistantText = (text, normalizedText) => {
state.lastAssistantTextMessageIndex = state.assistantMessageIndex;
state.lastAssistantTextContentIndex = state.lastAssistantStreamContentIndex;
state.lastAssistantTextItemId = state.lastAssistantStreamItemId;
state.lastAssistantTextTrimmed = text.trimEnd();
const normalized = normalizedText ?? normalizeTextForComparison(text);
state.lastAssistantTextNormalized = normalized.length > 0 ? normalized : void 0;
};
const shouldSkipAssistantText = (text, normalizedText) => {
if (state.lastAssistantTextMessageIndex !== state.assistantMessageIndex || state.lastAssistantTextContentIndex !== state.lastAssistantStreamContentIndex) return false;
const trimmed = text.trimEnd();
if (trimmed && trimmed === state.lastAssistantTextTrimmed) return true;
const normalized = normalizedText ?? normalizeTextForComparison(text);
return normalized.length > 0 && normalized === state.lastAssistantTextNormalized;
};
const pushAssistantText = (text, normalizedText) => {
if (!text || params.silentExpected && !isSilentReplyText(text, "NO_REPLY") || shouldSkipAssistantText(text, normalizedText)) return;
assistantTexts.push(text);
rememberAssistantText(text, normalizedText);
};
const replaceCurrentAssistantText = (text) => {
const count = assistantTexts.length - state.assistantTextBaseline;
if (!text) assistantTexts.splice(state.assistantTextBaseline, count);
else if (count > 0) {
assistantTexts.splice(state.assistantTextBaseline, count, text);
rememberAssistantText(text);
} else pushAssistantText(text);
};
const finalizeAssistantTexts = (args) => {
const { text, addedDuringMessage, chunkerHasBuffered } = args;
if (state.hasFlushedPartialText) {
replaceCurrentAssistantText(text);
state.hasFlushedPartialText = false;
} else if (state.includeReasoning && text && !params.onBlockReply) {
replaceCurrentAssistantText(text);
state.suppressBlockChunks = true;
} else if (!addedDuringMessage && text && (!chunkerHasBuffered || isSilentReplyText(text, "NO_REPLY"))) pushAssistantText(text);
state.assistantTextBaseline = assistantTexts.length;
};
const waitForPendingEvents = async (options) => {
const includePartialReplies = options?.includePartialReplies !== false;
while (true) {
const eventChain = state.pendingEventChain;
const partialReplyTasks = includePartialReplies ? [...pendingPartialReplyTasks] : [];
if (!eventChain && partialReplyTasks.length === 0) return;
await Promise.allSettled([...eventChain ? [eventChain] : [], ...partialReplyTasks]);
}
};
return {
assistantTexts,
clearAssistantStream,
clearDeferredBlockReplies,
emitAssistantStreamData,
emitBlockReply,
finalizeAssistantTexts,
flushAssistantStream,
noteLastAssistant,
releaseDeferredReplies,
pendingBlockReplyTasks,
pushAssistantText,
replaceCurrentAssistantText,
shouldSkipAssistantText,
waitForPendingEvents
};
}
//#endregion
//#region src/agents/embedded-agent-subscribe.run-state.ts
function createEmbeddedAgentSubscribeState(params) {
const reasoningMode = params.reasoningMode ?? "off";
const canShowReasoning = params.thinkingLevel !== "off";
const initialPendingToolMedia = collectAgentInternalEventMedia(params.internalEvents);
return {
assistantTexts: [],
answerSegments: [],
toolMetas: [],
acceptedSessionSpawns: [],
toolMetaById: /* @__PURE__ */ new Map(),
toolSummaryById: /* @__PURE__ */ new Set(),
liveEditDiffStateById: /* @__PURE__ */ new Map(),
itemActiveIds: /* @__PURE__ */ new Set(),
itemStartedCount: 0,
itemCompletedCount: 0,
assistantTurnCount: 0,
lastToolError: void 0,
blockReplyBreak: params.blockReplyBreak ?? "text_end",
reasoningMode,
includeReasoning: reasoningMode === "on" && canShowReasoning,
shouldEmitPartialReplies: !(reasoningMode === "on" && !params.onBlockReply),
streamReasoning: (params.streamReasoningInNonStreamModes === true ? reasoningMode !== "on" : reasoningMode === "stream") && canShowReasoning && typeof params.onReasoningStream === "function",
deltaBuffer: "",
streamBlockText: "",
streamBlockFinal: false,
blockReplyScopeStart: void 0,
thinkingTagStream: createThinkingTagStreamState(),
deltaBufferIsCommentary: false,
hasFlushedPartialText: false,
partialBlockState: {
reasoningDepth: 0,
final: false,
inlineCode: createInlineCodeState()
},
lastAssistantAudioDirectiveCount: 0,
assistantStream: void 0,
lastStreamedReasoning: void 0,
lastBlockReplyText: void 0,
lastDeliveredBlockReplyText: void 0,
deferBlockReplyDelivery: typeof params.onBeforeTerminalDelivery === "function" && params.deferTerminalDelivery !== false,
deferredBlockReplies: [],
toolExecutionSinceLastBlockReply: false,
reasoningStreamOpen: false,
assistantMessageIndex: 0,
assistantMessageStartIndex: 0,
lastAssistantStreamContentIndex: void 0,
lastAssistantStreamItemId: void 0,
lastAssistantTextMessageIndex: -1,
lastAssistantTextContentIndex: void 0,
lastAssistantTextItemId: void 0,
lastAssistantTextNormalized: void 0,
lastAssistantTextTrimmed: void 0,
assistantTextBaseline: 0,
suppressBlockChunks: false,
lastReasoningSent: void 0,
compactionInFlight: false,
lastCompactionTokensAfter: void 0,
pendingCompactionRetry: 0,
unsubscribed: false,
replayState: createEmbeddedRunReplayState(params.initialReplayState),
livenessState: "working",
hadDeterministicSideEffect: false,
pendingEventChain: null,
messagingToolSentTexts: [],
messagingToolSentTextsNormalized: [],
currentSourceMessagingToolSentTextsNormalized: [],
currentSourceMessagingToolHeldPartial: void 0,
messagingToolSentTargets: [],
heartbeatToolResponse: void 0,
messagingToolSentMediaUrls: [],
messagingToolSourceReplyPayloads: [],
messageToolOnlySourceReplyDelivered: false,
sourceReplyDeliveryState: "missing",
successfulCronAdds: 0,
pendingToolMediaUrls: initialPendingToolMedia.mediaUrls,
pendingToolMediaAttachments: initialPendingToolMedia.attachments,
pendingToolMediaTrustByUrl: initialPendingToolMedia.trustByUrl,
toolAutoDeliveryMediaUrls: /* @__PURE__ */ new Set(),
pendingToolAudioAsVoice: false,
pendingToolMediaDeliveryFailed: false,
hasToolMediaBlockReply: false,
visibleBlockReplyCount: 0,
pendingAssistantReplyDirectives: void 0,
deterministicApprovalPromptPending: false,
deterministicApprovalPromptSent: false
};
}
//#endregion
//#region src/agents/embedded-agent-subscribe.stream-rendering.ts
function isPotentialTrailingFinalTagFragment(fragment) {
if (!fragment.startsWith("<") || fragment.includes(">")) return false;
const body = fragment.toLowerCase().slice(1).trimStart().replace(/^\//, "").trimStart();
if (!body) return true;
const namePart = body.split(/[\s/>]/, 1)[0] ?? "";
if (!namePart) return true;
return "final".startsWith(namePart);
}
function splitTrailingBlockTagFragment(text, isInsideCodeSpan) {
for (let cursor = 0; cursor < text.length;) {
const start = text.indexOf("<", cursor);
if (start === -1) break;
cursor = start + 1;
if (isInsideCodeSpan(start)) continue;
const parsed = parseReasoningTagAt(text, start, false);
if (parsed.kind === "pending") return {
text: text.slice(0, start),
pendingTagFragment: text.slice(start)
};
if (parsed.kind === "tag") cursor = start + parsed.tag.text.length;
}
const fragmentStart = text.lastIndexOf("<");
if (fragmentStart === -1 || isInsideCodeSpan(fragmentStart)) return { text };
const fragment = text.slice(fragmentStart);
if (!isPotentialTrailingFinalTagFragment(fragment)) return { text };
return {
text: text.slice(0, fragmentStart),
pendingTagFragment: fragment
};
}
function splitTrailingFenceFragment(text, startsAtLineStart) {
const lineStart = text.lastIndexOf("\n") + 1;
const line = text.slice(lineStart);
if (!startsAtLineStart && lineStart === 0 || !/^(?: {0,3})(?:`+|~+)$/.test(line)) return { text };
return {
text: text.slice(0, lineStart),
pendingFenceFragment: line
};
}
function createStreamRendering({ params, state, log, blockChunker, emitBlockReply, flushAssistantStream, pendingBlockReplyTasks, pushAssistantText, shouldSkipAssistantText }) {
const messagingToolSentTextsNormalized = state.messagingToolSentTextsNormalized;
const partialReplyDirectiveAccumulator = createStreamingDirectiveAccumulator();
let reasoningProjection = createTextProjection([trimTextFilter("both")]);
const coveredBlockSources = /* @__PURE__ */ new Map();
const acceptedBlockSourceGenerations = /* @__PURE__ */ new Map();
let reasoningRaw;
const stripBlockTags = (text, stateLocal, options) => {
const input = `${stateLocal.pendingFenceFragment ?? ""}${stateLocal.pendingTagFragment ?? ""}${text}`;
stateLocal.pendingFenceFragment = void 0;
stateLocal.pendingTagFragment = void 0;
if (!input) return text;
const { text: fenceInput, pendingFenceFragment } = options?.final ? {
text: input,
pendingFenceFragment: void 0
} : splitTrailingFenceFragment(input, stateLocal.fence?.atLineStart ?? true);
stateLocal.pendingFenceFragment = pendingFenceFragment;
if (!fenceInput) return "";
const inlineStateStart = stateLocal.inlineCode ?? createInlineCodeState();
const fenceStateStart = stateLocal.fence;
const initialCodeSpans = buildCodeSpanIndex(fenceInput, inlineStateStart, fenceStateStart);
const { text: scanText, pendingTagFragment } = options?.final ? {
text: fenceInput,
pendingTagFragment: void 0
} : splitTrailingBlockTagFragment(fenceInput, initialCodeSpans.isInside);
stateLocal.pendingTagFragment = pendingTagFragment;
if (!scanText) return "";
const codeSpans = scanText === fenceInput ? initialCodeSpans : buildCodeSpanIndex(scanText, inlineStateStart, fenceStateStart);
let processed = "";
let lastIndex = 0;
let lastCodeIndex = 0;
let reasoningDepth = stateLocal.reasoningDepth;
let hiddenInlineState = stateLocal.reasoningInlineCode ? { ...stateLocal.reasoningInlineCode } : createInlineCodeState();
let hiddenFenceState = stateLocal.reasoningFence?.open ? {
atLineStart: stateLocal.reasoningFence.atLineStart,
open: { ...stateLocal.reasoningFence.open }
} : stateLocal.reasoningFence ? { atLineStart: stateLocal.reasoningFence.atLineStart } : void 0;
let hiddenPendingFenceFragment = stateLocal.reasoningPendingFenceFragment;
stateLocal.reasoningPendingFenceFragment = void 0;
const advanceHiddenCodeState = (segment) => {
const hiddenInput = `${hiddenPendingFenceFragment ?? ""}${segment}`;
hiddenPendingFenceFragment = void 0;
if (!hiddenInput) return;
const { text: hiddenFenceInput, pendingFenceFragment: pendingFenceFragmentLocal } = options?.final ? {
text: hiddenInput,
pendingFenceFragment: void 0
} : splitTrailingFenceFragment(hiddenInput, hiddenFenceState?.atLineStart ?? true);
hiddenPendingFenceFragment = pendingFenceFragmentLocal;
if (!hiddenFenceInput) return;
const next = buildCodeSpanIndex(hiddenFenceInput, hiddenInlineState, hiddenFenceState);
hiddenInlineState = next.inlineState;
hiddenFenceState = next.fenceState;
};
for (let cursor = 0; cursor < scanText.length;) {
const idx = scanText.indexOf("<", cursor);
if (idx === -1) break;
cursor = idx + 1;
if (reasoningDepth > 0) advanceHiddenCodeState(scanText.slice(lastCodeIndex, idx));
const isInsideHiddenCode = reasoningDepth > 0 && (hiddenInlineState.open || Boolean(hiddenFenceState?.open));
lastCodeIndex = idx;
if (reasoningDepth === 0 && codeSpans.isInside(idx) || isInsideHiddenCode) continue;
const parsed = parseReasoningTagAt(scanText, idx, true);
if (parsed.kind !== "tag") continue;
const match = parsed.tag;
const isClose = match.isClose;
cursor = idx + match.text.length;
lastCodeIndex = cursor;
if (match.isSelfClosing) {
if (reasoningDepth === 0) processed += scanText.slice(lastIndex, idx);
lastIndex = cursor;
continue;
}
if (reasoningDepth === 0) {
if (isClose) {
const afterIndex = cursor;
const before = scanText.slice(lastIndex, idx);
const after = scanText.slice(afterIndex);
if (hasOrphanReasoningCloseBoundary({
before,
after
})) processed = "";
else processed += before;
lastIndex = afterIndex;
continue;
}
processed += scanText.slice(lastIndex, idx);
}
reasoningDepth += isClose ? -1 : 1;
if (reasoningDepth === 1 && !isClose) {
hiddenInlineState = createInlineCodeState();
hiddenFenceState = void 0;
hiddenPendingFenceFragment = void 0;
}
lastIndex = cursor;
}
if (reasoningDepth > 0) advanceHiddenCodeState(scanText.slice(lastCodeIndex));
if (reasoningDepth === 0) processed += scanText.slice(lastIndex);
stateLocal.reasoningDepth = reasoningDepth;
stateLocal.reasoningInlineCode = reasoningDepth > 0 ? hiddenInlineState : void 0;
stateLocal.reasoningFence = reasoningDepth > 0 ? hiddenFenceState : void 0;
stateLocal.reasoningPendingFenceFragment = reasoningDepth > 0 ? hiddenPendingFenceFragment : void 0;
const finalCodeSpans = processed === scanText ? codeSpans : buildCodeSpanIndex(processed, inlineStateStart, fenceStateStart);
if (!params.enforceFinalTag) {
stateLocal.inlineCode = finalCodeSpans.inlineState;
stateLocal.fence = finalCodeSpans.fenceState;
return stripFinalTagsOutsideCodeSpans(processed, finalCodeSpans.isInside);
}
let result = "";
let lastFinalIndex = 0;
let inFinal = stateLocal.final;
let everInFinal = stateLocal.final;
for (const match of findFinalTagMatches(processed)) {
const idx = match.index;
if (finalCodeSpans.isInside(idx)) continue;
const isClose = match.isClose;
const isSelfClosing = match.isSelfClosing;
if (inFinal && (isSelfClosing || isClose)) {
result += processed.slice(lastFinalIndex, idx);
inFinal = false;
} else if (!inFinal && (isSelfClosing || !isClose)) {
inFinal = true;
everInFinal = true;
} else continue;
lastFinalIndex = idx + match.text.length;
}
if (inFinal) result += processed.slice(lastFinalIndex);
stateLocal.final = inFinal;
if (!everInFinal) {
stateLocal.inlineCode = createInlineCodeState();
stateLocal.fence = finalCodeSpans.fenceState;
stateLocal.finalInlineCode = void 0;
stateLocal.finalFence = void 0;
return "";
}
const finalResultInlineStateStart = stateLocal.finalInlineCode ?? createInlineCodeState();
const finalResultFenceStateStart = stateLocal.finalFence;
const resultCodeSpans = buildCodeSpanIndex(result, finalResultInlineStateStart, finalResultFenceStateStart);
stateLocal.inlineCode = finalCodeSpans.inlineState;
stateLocal.fence = finalCodeSpans.fenceState;
stateLocal.finalInlineCode = inFinal ? resultCodeSpans.inlineState : void 0;
stateLocal.finalFence = inFinal ? resultCodeSpans.fenceState : void 0;
return stripFinalTagsOutsideCodeSpans(result, resultCodeSpans.isInside);
};
const stripFinalTagsOutsideCodeSpans = (text, isInside) => {
let output = "";
let lastIndex = 0;
for (const match of findFinalTagMatches(text)) {
const idx = match.index;
if (isInside(idx)) continue;
output += text.slice(lastIndex, idx);
lastIndex = idx + match.text.length;
}
output += text.slice(lastIndex);
return output;
};
const emitBlockChunk = (text, options) => {
if (state.suppressBlockChunks || params.silentExpected || shouldSuppressDeterministicApprovalOutput(state)) return;
const blockReplyText = options?.final === false ? text : text.trimEnd();
const hasPendingAudioDirective = state.pendingAssistantReplyDirectives?.audioAsVoice === true;
if (!blockReplyText.trim() && !options?.finalReply && !hasPendingAudioDirective) return;
const markBlockReplyTextHandled = () => {
if (blockReplyText) {
state.lastBlockReplyText = blockReplyText;
state.lastDeliveredBlockReplyText = blockReplyText;
}
state.toolExecutionSinceLastBlockReply = false;
};
if (hasMessageToolOnlySourceDelivery({
params,
state
})) {
markBlockReplyTextHandled();
return;
}
let chunk = blockReplyText;
let slicedPrefixReplay = false;
const lastDeliveredBlockReplyText = state.lastDeliveredBlockReplyText;
const blockReplySuffix = lastDeliveredBlockReplyText ? blockReplyText.slice(lastDeliveredBlockReplyText.length) : "";
const prefixReplayCandidate = Boolean(!state.deferBlockReplyDelivery && state.blockReplyBreak === "text_end" && state.toolExecutionSinceLastBlockReply && lastDeliveredBlockReplyText && lastDeliveredBlockReplyText.trimEnd().endsWith(":") && blockReplyText.length > lastDeliveredBlockReplyText.length && blockReplyText.startsWith(lastDeliveredBlockReplyText));
if (prefixReplayCandidate && !/^\s/.test(blockReplySuffix)) {
chunk = blockReplySuffix;
slicedPrefixReplay = true;
}
if (!chunk && !options?.finalReply && !hasPendingAudioDirective) return;
const normalizedChunk = normalizeTextForComparison(chunk);
const normalizedReplaySuffix = prefixReplayCandidate ? normalizeTextForComparison(blockReplySuffix.trimStart()) : "";
if (isMessagingToolDuplicateNormalized(normalizedChunk, messagingToolSentTextsNormalized) || prefixReplayCandidate && isMessagingToolDuplicateNormalized(normalizedReplaySuffix, messagingToolSentTextsNormalized)) {
log.debug(`Skipping block reply - already sent via messaging tool: ${truncateUtf16Safe(chunk, 50)}...`);
if (prefixReplayCandidate) markBlockReplyTextHandled();
return;
}
let sourceRangeAlreadyCovered = false;
const assistantMessageIndex = options?.assistantMessageIndex ?? state.assistantMessageIndex;
const blockSourceText = options?.sourceText;
const sourceStart = options?.sourceStart;
const sourceEnd = options?.sourceEnd;
const blockSourceRange = blockSourceText !== void 0 && sourceStart !== void 0 && sourceEnd !== void 0 ? [sourceStart, sourceEnd] : void 0;
if (blockSourceRange) {
const covered = coveredBlockSources.get(assistantMessageIndex) ?? [];
const [start, end] = blockSourceRange;
let cursor = start;
let coveredText = "";
for (const entry of covered.toSorted((a, b) => a.range[0] - b.range[0])) {
const [coveredStart, coveredEnd] = entry.range;
if (coveredEnd <= cursor) continue;
if (coveredStart > cursor) break;
const overlap = cursor - coveredStart;
const length = Math.min(end, coveredEnd) - cursor;
coveredText += entry.text.slice(overlap, overlap + length);
cursor += length;
if (cursor >= end) break;
}
if (cursor >= end && coveredText === blockSourceText) sourceRangeAlreadyCovered = true;
}
if (options?.reconciledSourceBreak && options.sourceGeneration !== void 0) acceptedBlockSourceGenerations.set(assistantMessageIndex, options.sourceGeneration);
const sameSourceGeneration = options?.sourceGeneration !== void 0 && acceptedBlockSourceGenerations.get(assistantMessageIndex) === options.sourceGeneration;
if (chunk && (sourceRangeAlreadyCovered || (!blockSourceRange || !sameSourceGeneration || options?.reconciledSourceBreak) && shouldSkipAssistantText(chunk, normalizedChunk))) {
if (slicedPrefixReplay) markBlockReplyTextHandled();
if (!options?.finalReply) return;
chunk = "";
}
if (!params.onBlockReply) {
pushAssistantText(chunk, normalizedChunk);
markBlockReplyTextHandled();
return;
}
let splitResult = {
text: chunk,
replyToTag: false,
isSilent: false
};
if (options?.finalReply) {
let pendingText = splitResult.text;
if (trimTextPreservingCode(pendingText) === options.finalReply.text) {
pendingText = options.finalReply.text;
chunk = pendingText;
}
splitResult = {
...splitResult,
...options.finalReply,
text: pendingText
};
}
const { text: cleanedText, mediaUrls, mediaFailures, audioAsVoice, replyToId, replyToTag, replyToCurrent } = splitResult;
const hasPendingFinalMedia = Boolean(options?.finalReply?.text && state.pendingToolMediaUrls.length);
if (!cleanedText && !mediaFailures?.length && (!mediaUrls || mediaUrls.length === 0) && !audioAsVoice && !hasPendingAudioDirective && !hasPendingFinalMedia) {
if (slicedPrefixReplay) markBlockReplyTextHandled();
return;
}
pushAssistantText(chunk, normalizedChunk);
const payload = {
text: cleanedText || appendReplyMediaFailures(cleanedText, mediaFailures ?? []),
mediaUrls: mediaUrls?.length ? mediaUrls : void 0,
audioAsVoice,
replyToId,
replyToTag,
replyToCurrent
};
addReplyPayloadMediaFailures(payload, mediaFailures);
if (splitResult.isSilent) setReplyPayloadMetadata(payload, { silentReply: true });
const emittedBlockSourceRange = chunk === text.trimEnd() && cleanedText === chunk && !sourceRangeAlreadyCovered && !options?.reconciledSourceBreak ? blockSourceRange : void 0;
emitBlockReply(payload, {
assistantMessageIndex,
blockSourceText: chunk === text.trimEnd() && cleanedText === chunk && (options?.sourceStart === void 0 || emittedBlockSourceRange !== void 0) ? blockSourceText : void 0,
blockSourceRange: emittedBlockSourceRange,
consumePendingToolMedia: options?.finalReply !== void 0 || hasPendingAudioDirective || Boolean(mediaUrls?.length || audioAsVoice)
});
if (emittedBlockSourceRange) {
const covered = coveredBlockSources.get(assistantMessageIndex) ?? [];
covered.push({
range: emittedBlockSourceRange,
text: blockSourceText ?? ""
});
coveredBlockSources.set(assistantMessageIndex, covered);
if (options?.sourceGeneration !== void 0) acceptedBlockSourceGenerations.set(assistantMessageIndex, options.sourceGeneration);
}
markBlockReplyTextHandled();
};
const consumePartialReplyDirectives = (text, options) => partialReplyDirectiveAccumulator.consume(text, options);
const resetPartialReplyDirectives = () => {
partialReplyDirectiveAccumulator.reset();
state.lastAssistantAudioDirectiveCount = 0;
state.pendingAssistantReplyDirectives = void 0;
};
const flushBlockReplyBuffer = (options) => {
flushAssistantStream();
if (!params.onBlockReply) return;
let pendingChunk;
if (blockChunker.hasBuffered()) blockChunker.drain({
force: true,
emit: (text, metadata) => {
if (pendingChunk !== void 0) emitBlockChunk(pendingChunk.text, {
sourceText: pendingChunk.sourceText,
sourceGeneration: pendingChunk.sourceGeneration,
reconciledSourceBreak: pendingChunk.reconciledSourceBreak,
sourceStart: pendingChunk.sourceStart,
sourceEnd: pendingChunk.sourceEnd,
assistantMessageIndex: options?.assistantMessageIndex
});
pendingChunk = {
text,
...metadata
};
}
});
if (pendingChunk !== void 0 || options?.final || state.pendingAssistantReplyDirectives?.audioAsVoice === true) emitBlockChunk(pendingChunk?.text ?? "", {
...options,
final: options?.final === true,
sourceText: pendingChunk?.sourceText,
sourceGeneration: pendingChunk?.sourceGeneration,
reconciledSourceBreak: pendingChunk?.reconciledSourceBreak,
sourceStart: pendingChunk?.sourceStart,
sourceEnd: pendingChunk?.sourceEnd
});
if (pendingBlockReplyTasks.size === 0) return;
return (async () => {
while (pendingBlockReplyTasks.size > 0) await Promise.allSettled(pendingBlockReplyTasks);
})();
};
const emitReasoningStream = (input, fallback) => {
if (params.silentExpected) return;
const text = typeof input === "string" ? input : input.thinking;
const append = typeof input !== "string" && reasoningRaw !== void 0 ? readAssistantThinkingAppend(input, reasoningRaw) : void 0;
const previousProjectedText = reasoningProjection.text;
let projected = append !== void 0 ? reasoningProjection.append(append) : reasoningProjection.replace(text);
reasoningRaw = text;
if (!projected.text && fallback?.trim()) {
projected = reasoningProjection.replace(fallback);
reasoningRaw = void 0;
}
const trimmed = projected.text;
if (!trimmed || trimmed === state.lastStreamedReasoning) return;
flushAssistantStream();
const prior = state.lastStreamedReasoning ?? "";
const delta = previousProjectedText === prior && projected.delta !== null ? projected.delta : trimmed.startsWith(prior) ? trimmed.slice(prior.length) : trimmed;
state.lastStreamedReasoning = trimmed;
emitAgentEvent({
runId: params.runId,
stream: "thinking",
data: {
text: trimmed,
delta
}
});
if (state.streamReasoning && !hasMessageToolOnlySourceDelivery({
params,
state
}) && params.onReasoningStream) runBestEffortCallback({
label: "reasoning stream",
log,
callback: () => params.onReasoningStream?.({
text: trimmed,
...state.reasoningMode === "stream" ? {} : { requiresReasoningProgressOptIn: true }
})
});
};
const resetAssistantMessageState = (nextAssistantTextBaseline) => {
flushAssistantStream();
state.deltaBuffer = "";
state.streamBlockText = "";
state.streamBlockFinal = false;
state.blockReplyScopeStart = void 0;
state.thinkingTagStream = createThinkingTagStreamState();
state.deltaBufferIsCommentary = false;
state.hasFlushedPartialText = false;
blockChunker.reset();
resetPartialReplyDirectives();
state.partialBlockState = {
reasoningDepth: 0,
final: false,
inlineCode: createInlineCodeState()
};
state.assistantStream = void 0;
state.currentSourceMessagingToolHeldPartial = void 0;
state.lastBlockReplyText = void 0;
state.lastStreamedReasoning = void 0;
reasoningProjection = createTextProjection([trimTextFilter("both")]);
reasoningRaw = void 0;
state.reasoningStreamOpen = false;
state.suppressBlockChunks = false;
state.assistantMessageIndex += 1;
state.lastAssistantStreamContentIndex = void 0;
state.lastAssistantStreamItemId = void 0;
state.assistantTextBaseline = nextAssistantTextBaseline;
};
return {
consumePartialReplyDirectives,
resetPartialReplyDirectives,
emitBlockChunk,
emitReasoningStream,
flushBlockReplyBuffer,
resetAssistantMessageState,
stripBlockTags
};
}
//#endregion
//#region src/agents/embedded-agent-subscribe.tool-lifecycle.ts
function createEmbeddedToolLifecycleRunner(ctx) {
return async (toolParams) => {
ctx.flushAssistantStream();
const startEvent = {
type: "tool_execution_start",
toolName: toolParams.toolName,
toolCallId: toolParams.toolCallId,
parentToolCallId: toolParams.parentToolCallId,
args: toolParams.args,
replaySafe: toolParams.replaySafe,
hideFromChannelProgress: toolParams.hideFromChannelProgress,
lifecycleProvenance: "nested"
};
recordEmbeddedToolTrajectoryEvent(ctx, startEvent, void 0);
await handleToolExecutionStart(ctx, startEvent);
let executionStarted = false;
const finishToolLifecycle = async (isError, result) => {
const wasExecuted = executionStarted;
ctx.flushAssistantStream();
const endEvent = {
type: "tool_execution_end",
toolName: toolParams.toolName,
toolCallId: toolParams.toolCallId,
isError,
executionStarted: wasExecuted,
result,
hideFromChannelProgress: toolParams.hideFromChannelProgress
};
const readSanitizedResult = prepareToolResult(result);
recordEmbeddedToolTrajectoryEvent(ctx, endEvent, readSanitizedResult);
const terminal = await handleToolExecutionEnd(ctx, endEvent, readSanitizedResult);
return {
result,
readSanitizedResult,
isError: terminal.isError,
executedArguments: terminal.executedArguments ?? toolParams.args,
effectReceipt: terminal.effectReceipt
};
};
let completedResult;
try {
completedResult = await toolParams.execute(() => {
executionStarted = true;
});
} catch (error) {
const trustedNoStart = consumeToolExecutionNotStarted(error);
const result = buildToolLifecycleErrorResult(error);
if (trustedNoStart) markToolExecutionNotStarted(result);
const terminal = await finishToolLifecycle(true, result);
await toolParams.onTerminal?.(terminal);
throw error;
}
const terminal = await finishToolLifecycle(false, completedResult);
await toolParams.onTerminal?.(terminal);
return completedResult;
};
}
//#endregion
//#region src/agents/embedded-agent-subscribe.ts
/**
* Subscribes to embedded-agent sessions and streams formatted replies/events.
*/
const embeddedLog = createSubsystemLogger("agent/embedded");
function resolveEmbeddedAgentSessionLogger(messageChannel) {
const normalizedChannel = normalizeMessageChannel(messageChannel);
if (normalizedChannel && isDeliverableMessageChannel(normalizedChannel)) return createSubsystemLogger(`gateway/channels/${normalizedChannel}`);
return embeddedLog;
}
function subscribeEmbeddedAgentSession(input) {
let params = input;
const onAgentEvent = params.onAgentEvent;
if (onAgentEvent) {
let transcriptStartPublished = false;
const sessionManager = params.session.sessionManager;
params = {
...params,
onAgentEvent: (event) => {
if (transcriptStartPublished || event.stream === "lifecycle" && typeof event.data.phase !== "string") return onAgentEvent(event);
const transcriptStart = sessionManager[sessionManagerReadTranscriptStart]();
transcriptStartPublished = true;
return onAgentEvent({
...event,
transcriptStart
});
}
};
}
const log = resolveEmbeddedAgentSessionLogger(params.messageChannel);
const useMarkdown = (params.toolResultFormat ?? "markdown") === "markdown";
const state = createEmbeddedAgentSubscribeState(params);
const { captureModelEvent, recordAuxiliaryUsage, getUsageTotals, getLastAssistantUsage, getCurrentAttemptAssistant, hasSuccessfulModelResponse } = createEmbeddedModelState(params, log);
let compactionCount = 0;
let compactionRetry;
const assistantTexts = state.assistantTexts;
const toolMetas = state.toolMetas;
const toolMetaById = state.toolMetaById;
const toolSummaryById = state.toolSummaryById;
const messagingToolSentTexts = state.messagingToolSentTexts;
const messagingToolSentTextsNormalized = state.messagingToolSentTextsNormalized;
const messagingToolSentTargets = state.messagingToolSentTargets;
const messagingToolSentMediaUrls = state.messagingToolSentMediaUrls;
const messagingToolSourceReplyPayloads = state.messagingToolSourceReplyPayloads;
const didSendViaMessagingTool = () => hasCommittedMessagingToolDeliveryEvidence({
messagingToolSentTexts,
messagingToolSentMediaUrls,
messagingToolSentTargets
});
const replyDelivery = createReplyDelivery({
params,
state,
log
});
const { clearAssistantStream, clearDeferredBlockReplies, emitAssistantStreamData, emitBlockReply, finalizeAssistantTexts, flushAssistantStream, noteLastAssistant, releaseDeferredReplies } = replyDelivery;
const trimMessagingToolSent = () => {
if (messagingToolSentTexts.length > 200) {
const overflow = messagingToolSentTexts.length - 200;
messagingToolSentTexts.splice(0, overflow);
messagingToolSentTextsNormalized.splice(0, overflow);
}
for (const history of [
state.currentSourceMessagingToolSentTextsNormalized,
messagingToolSentTargets,
messagingToolSentMediaUrls,
messagingToolSourceReplyPayloads
]) if (history.length > 200) history.splice(0, history.length - 200);
};
const ensureCompactionPromise = () => {
if (!compactionRetry) {
compactionRetry = createDeferredCore();
compactionRetry.promise.catch((err) => {
log.debug(`compaction promise rejected (no waiter): ${String(err)}`);
});
}
};
const noteCompactionRetry = () => {
state.pendingCompactionRetry += 1;
ensureCompactionPromise();
};
const maybeResolveCompactionWait = () => {
if (state.pendingCompactionRetry !== 0 || state.compactionInFlight) return;
compactionRetry?.resolve();
compactionRetry = void 0;
};
const resolveCompactionRetry = () => {
if (state.pendingCompactionRetry <= 0) return;
state.pendingCompactionRetry -= 1;
maybeResolveCompactionWait();
};
const incrementCompactionCount = () => {
compactionCount += 1;
};
const noteCompactionTokensAfter = (value) => {
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return;
state.lastCompactionTokensAfter = Math.floor(value);
};
const blockChunking = params.blockReplyChunking;
const blockChunker = new EmbeddedBlockChunker(blockChunking);
const shouldEmitToolResult = () => typeof params.shouldEmitToolResult === "function" ? params.shouldEmitToolResult() : params.verboseLevel === "on" || params.verboseLevel === "full";
const shouldEmitToolOutput = () => typeof params.shouldEmitToolOutput === "function" ? params.shouldEmitToolOutput() : params.verboseLevel === "full";
const emitToolResultMessage = (toolName, message, result) => {
if (!params.onToolResult) return;
const parsed = parseInlineDirectives(message, {
stripAudioTag: true,
stripReplyTags: true
});
const mediaArtifact = result ? extractToolResultMediaArtifact(result) : void 0;
const filteredMediaUrls = filterToolResultMediaUrls(toolName, mediaArtifact?.mediaUrls ?? [], result, params.trustedLocalMediaToolNames);
if (params.sourceReplyDeliveryMode === "message_tool_only" && parsed.text && filteredMediaUrls.length === 0 && didSendViaMessagingTool()) return;
if (!parsed.text && filteredMediaUrls.length === 0) return;
runBestEffortCallback({
label: "tool result",
log,
callback: () => params.onToolResult?.({
text: parsed.text,
mediaUrls: filteredMediaUrls.length ? filteredMediaUrls : void 0,
...mediaArtifact?.audioAsVoice ? { audioAsVoice: true } : {}
})
});
};
const emitToolSummary = (toolName, meta, commandBearing) => {
const visibleMeta = params.verboseLevel === "full" || !commandBearing ? meta : void 0;
const agg = formatToolAggregate(toolName, visibleMeta ? [visibleMeta] : void 0, { markdown: useMarkdown });
emitToolResultMessage(toolName, agg);
};
const emitToolOutput = (toolName, meta, output, result) => {
if (!output) return;
const agg = formatToolAggregate(toolName, meta ? [meta] : void 0, { markdown: useMarkdown });
const trimmed = output.trim();
const message = `${agg}\n${trimmed ? useMarkdown ? `\`\`\`txt\n${trimmed}\n\`\`\`` : trimmed : "(no output)"}`;
emitToolResultMessage(toolName, message, result);
};
const streamRendering = createStreamRendering({
params,
state,
log,
blockChunker,
emitBlockReply: replyDelivery.emitBlockReply,
flushAssistantStream,
pendingBlockReplyTasks: replyDelivery.pendingBlockReplyTasks,
pushAssistantText: replyDelivery.pushAssistantText,
shouldSkipAssistantText: replyDelivery.shouldSkipAssistantText
});
const resetForCompactionRetry = () => {
state.hadDeterministicSideEffect = state.hadDeterministicSideEffect === true || didSendViaMessagingTool() || state.successfulCronAdds > 0 || state.acceptedSessionSpawns.length > 0 || state.visibleBlockReplyCount > 0;
assistantTexts.length = 0;
state.answerSegments.length = 0;
state.inputAnswer = void 0;
state.keptAnswer = void 0;
state.lastAssistant = void 0;
state.lastAssistantTextMessageIndex = -1;
state.lastAssistantTextContentIndex = void 0;
state.lastAssistantTextItemId = void 0;
state.lastAssistantTextNormalized = void 0;
state.lastAssistantTextTrimmed = void 0;
toolMetas.length = 0;
toolMetaById.clear();
toolSummaryById.clear();
state.liveEditDiffStateById.clear();
state.itemActiveIds.clear();
state.itemStartedCount = 0;
state.itemCompletedCount = 0;
if (state.lastToolError?.mutatingAction !== true) state.lastToolError = void 0;
messagingToolSentTexts.length = 0;
messagingToolSentTextsNormalized.length = 0;
state.currentSourceMessagingToolSentTextsNormalized.length = 0;
messagingToolSentTargets.length = 0;
messagingToolSentMediaUrls.length = 0;
state.pendingToolMediaUrls = [];
state.pendingToolMediaAttachments = [];
state.pendingToolMediaTrustByUrl.clear();
state.toolAutoDeliveryMediaUrls.clear();
state.pendingToolAudioAsVoice = false;
state.pendingToolMediaDeliveryFailed = false;
state.visibleBlockReplyCount = 0;
state.deferBlockReplyDelivery = typeof params.onBeforeTerminalDelivery === "function" && params.deferTerminalDelivery !== false;
clearAssistantStream();
clearDeferredBlockReplies();
state.deterministicApprovalPromptPending = false;
state.deterministicApprovalPromptSent = false;
state.lastDeliveredBlockReplyText = void 0;
state.lastReasoningSent = void 0;
state.toolExecutionSinceLastBlockReply = false;
state.replayState = mergeEmbeddedRunReplayState(state.replayState, params.initialReplayState);
state.livenessState = "working";
streamRendering.resetAssistantMessageState(0);
};
const flushPartialAssistantText = () => {
const text = state.deltaBuffer;
if (!text) return;
if (state.deltaBufferIsCommentary) {
state.hasFlushedPartialText = false;
return;
}
const visibleText = stripDowngradedToolCallText(streamRendering.stripBlockTags(text, {
reasoningDepth: 0,
final: false,
inlineCode: createInlineCodeState()
}, { final: true })).trimEnd();
if (assistantTexts.length > state.assistantTextBaseline || state.hasFlushedPartialText) replyDelivery.replaceCurrentAssistantText(visibleText);
else if (visibleText) replyDelivery.pushAssistantText(visibleText);
state.hasFlushedPartialText = Boolean(visibleText);
};
const ctx = {
...streamRendering,
params,
state,
log,
blockChunking,
blockChunker,
hookRunner: params.hookRunner,
builtinToolNames: params.builtinToolNames,
trustedLocalMediaToolNames: params.trustedLocalMediaToolNames,
noteLastAssistant,
shouldEmitToolResult,
shouldEmitToolOutput,
emitToolSummary,
emitToolOutput,
emitAssistantStreamData,
emitBlockReply,
flushAssistantStream,
releaseDeferredReplies,
clearAssistantStream,
clearDeferredBlockReplies,
resetForCompactionRetry,
finalizeAssistantTexts,
trimMessagingToolSent,
consumeToolSendReceipt: (toolCallId) => consumeEmbeddedToolReceipt(params.session.sessionManager, toolCallId),
ensureCompactionPromise,
noteCompactionRetry,
resolveCompactionRetry,
maybeResolveCompactionWait,
captureModelEvent,
incrementCompactionCount,
noteCompactionTokensAfter,
getUsageTotals,
getLastAssistantUsage,
getCompactionCount: () => compactionCount,
getLastCompactionTokensAfter: () => state.lastCompactionTokensAfter
};
const sessionUnsubscribe = params.session.subscribe(createEmbeddedAgentSessionEventHandler(ctx));
setSessionModelUsageSink(params.session.sessionManager, recordAuxiliaryUsage);
const unsubscribe = () => {
if (state.unsubscribed) return;
state.unsubscribed = true;
clearAssistantStream();
cleanupRunToolStartData(params.runId);
state.liveEditDiffStateById.clear();
if (compactionRetry) {
log.debug(`unsubscribe: rejecting compaction wait runId=${params.runId}`);
const { reject } = compactionRetry;
compactionRetry = void 0;
reject(createAbortError$1("Unsubscribed during compaction"));
}
if (params.session.isCompacting) {
log.debug(`unsubscribe: aborting in-flight compaction runId=${params.runId}`);
try {
params.session.abortCompaction();
} catch (err) {
log.warn(`unsubscribe: compaction abort failed runId=${params.runId} err=${String(err)}`);
}
}
setSessionModelUsageSink(params.session.sessionManager, null);
sessionUnsubscribe();
};
return {
assistantTexts,
answerSegments: state.answerSegments,
getCurrentAttemptAssistant,
getKeptAnswer: () => state.keptAnswer,
hasSuccessfulModelResponse,
getLastAssistantTextMessageIndex: () => state.lastAssistantTextMessageIndex >= 0 ? state.lastAssistantTextMessageIndex : void 0,
toolMetas,
getAcceptedSessionSpawns: () => state.acceptedSessionSpawns.slice(),
getLatestMcpAppChannelView: () => state.latestMcpAppChannelView ? { ...state.latestMcpAppChannelView } : void 0,
getLatestMcpConnectAction: () => state.latestMcpConnectAction ? { ...state.latestMcpConnectAction } : void 0,
runToolLifecycle: createEmbeddedToolLifecycleRunner(ctx),
unsubscribe,
setTerminalLifecycleMeta: (meta) => {
if (typeof meta.replayInvalid === "boolean") state.replayState = {
...state.replayState,
replayInvalid: meta.replayInvalid
};
if (meta.livenessState) state.livenessState = meta.livenessState;
if (typeof meta.stopReason === "string") state.terminalStopReason = meta.stopReason;
if (typeof meta.yielded === "boolean") state.yielded = meta.yielded;
if (meta.timeoutPhase) state.timeoutPhase = meta.timeoutPhase;
if (typeof meta.providerStarted === "boolean") state.providerStarted = meta.providerStarted;
if (typeof meta.aborted === "boolean") state.terminalAborted = meta.aborted;
},
isCompacting: () => state.compactionInFlight || state.pendingCompactionRetry > 0,
isCompactionInFlight: () => state.compactionInFlight,
getMessagingToolSentTexts: () => messagingToolSentTexts.slice(),
getMessagingToolSentMediaUrls: () => messagingToolSentMediaUrls.slice(),
getMessagingToolSentTargets: () => messagingToolSentTargets.slice(),
getMessagingToolSourceReplyPayloads: () => messagingToolSourceReplyPayloads.slice(),
getSourceReplyDelivered: () => state.sourceReplyDelivered,
getSourceReplyDeliveryState: () => state.sourceReplyDeliveryState,
endsWithSourceProgress: () => state.lastToolTurnOnlySourceProgress === true,
getHeartbeatToolResponse: () => state.heartbeatToolResponse ? { ...state.heartbeatToolResponse } : void 0,
getPendingToolMediaReply: () => readPendingToolMediaReply(state),
getToolAutoDeliveryMediaUrls: () => [...state.toolAutoDeliveryMediaUrls],
hasToolMediaBlockReply: () => state.hasToolMediaBlockReply,
getVisibleBlockReplyCount: () => state.visibleBlockReplyCount,
getSuccessfulCronAdds: () => state.successfulCronAdds,
getReplayState: () => ({ ...state.replayState }),
didSendViaMessagingTool,
didSendDeterministicApprovalPrompt: () => state.deterministicApprovalPromptSent,
getLastToolError: () => state.lastToolError ? { ...state.lastToolError } : void 0,
getUsageTotals,
getLastAssistantUsage,
getCompactionCount: () => compactionCount,
getLastCompactionTokensAfter: () => state.lastCompactionTokensAfter,
getAssistantTurnCount: () => state.assistantTurnCount,
waitForPendingEvents: replyDelivery.waitForPendingEvents,
flushPartialAssistantText,
getItemLifecycle: () => ({
startedCount: state.itemStartedCount,
completedCount: state.itemCompletedCount,
activeCount: state.itemActiveIds.size
}),
waitForCompactionRetry: () => {
if (state.unsubscribed) return Promise.reject(createAbortError$1("Unsubscribed during compaction wait"));
if (state.compactionInFlight || state.pendingCompactionRetry > 0) {
ensureCompactionPromise();
return compactionRetry?.promise ?? Promise.resolve();
}
return new Promise((resolve, reject) => {
queueMicrotask(() => {
if (state.unsubscribed) {
reject(createAbortError$1("Unsubscribed during compaction wait"));
return;
}
if (state.compactionInFlight || state.pendingCompactionRetry > 0) {
ensureCompactionPromise();
(compactionRetry?.promise ?? Promise.resolve()).then(resolve, reject);
} else resolve();
});
});
}
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-queue-message.ts
const DEFAULT_QUEUE_TRANSCRIPT_COMMIT_TIMEOUT_MS = 12e4;
var EmbeddedSteeringAcceptedUnconfirmedError = class extends Error {
constructor(message, options) {
super(message, options);
this.name = "EmbeddedSteeringAcceptedUnconfirmedError";
}
};
function hasAcceptedSteeringCustody(error) {
return collectErrorGraphCandidates(error, readErrorCauses).some((candidate) => candidate instanceof MessageInjectionAcceptedUnconfirmedError);
}
/**
* Removes one pending steered user message from both the runtime queue and its
* exact identity-owned display entry.
*/
async function cancelQueuedSteeringMessage(activeSession, queueIdentity) {
const cancelSteeringMessage = activeSession.agent?.cancelSteeringMessage;
if (!cancelSteeringMessage) return false;
const message = cancelSteeringMessage.call(activeSession.agent, (queuedMessage) => getSteeringMessageIdentity(queuedMessage) === queueIdentity);
if (!message) return false;
try {
if (!retireQueuedUserMessage(message)) log$6.warn("failed to retire queued steering display entry during cancellation");
} catch (error) {
log$6.warn(`failed to retire queued steering display entry: ${String(error)}`);
}
return true;
}
/**
* Tracks one steer until commit or terminal cleanup. Admission-only receipts
* resolve after enqueue, but retain exact-message cleanup until the run ends.
* Commit-waiting callers also retain their delivery deadline.
*/
async function steerWithTranscriptLifecycle(activeSession, text, options, canInject, prepareInjection) {
const { abortSignal, onQueueAccepted, onQueueSettled, waitForTranscriptCommit, images, userTurnTranscriptRecorder, media, imageOrder, currentInboundContext, queueIdentity = crypto.randomUUID() } = options;
const timeoutMs = options.deliveryTimeoutMs ?? DEFAULT_QUEUE_TRANSCRIPT_COMMIT_TIMEOUT_MS;
await new Promise((resolve, reject) => {
let settled = false;
let accepted = false;
let abortRequested = abortSignal?.aborted === true;
let acceptanceReported = false;
let cancellation;
let acceptanceOpen = true;
const observerErrors = [];
const notifyObserver = (callback) => {
try {
callback?.();
} catch (error) {
observerErrors.push(error);
}
};
const reportAcceptance = (value) => {
if (acceptanceReported) return;
acceptanceReported = true;
notifyObserver(() => onQueueAccepted?.(value));
};
const reportRejection = () => {
if (!accepted) reportAcceptance(false);
};
const finish = (err) => {
if (settled) return;
settled = true;
clearTimeout(timer);
notifyObserver(unsubscribe);
notifyObserver(unsubscribePersistenceFailure);
abortSignal?.removeEventListener("abort", onAbort);
notifyObserver(onQueueSettled);
const errors = err === void 0 ? observerErrors : [err, ...observerErrors];
if (errors.length > 0) {
const failure = errors.length === 1 ? errors[0] : new AggregateError(errors, "Steering completion observers failed", { cause: err === void 0 ? errors[0] : err });
reject(accepted && observerErrors.length > 0 && !hasAcceptedSteeringCustody(err) && !(err instanceof MessageInjectionWithdrawnError) ? new EmbeddedSteeringAcceptedUnconfirmedError("Queued steering was accepted but its completion observer failed", { cause: failure }) : toErrorObject(failure, "Non-Error rejection"));
return;
}
resolve();
};
const rejectAfterCancellation = (message) => {
acceptanceOpen = false;
cancellation ??= cancelQueuedSteeringMessage(activeSession, queueIdentity).then(async (removed) => {
if (!removed) await steering.catch((error) => {
accepted ||= hasAcceptedSteeringCustody(error);
});
if (!removed && accepted) {
log$6.warn("failed to find queued steering message for cancellation");
throw new EmbeddedSteeringAcceptedUnconfirmedError(message);
}
return removed;
});
cancellation.then((removed) => {
reportRejection();
finish(removed ? new MessageInjectionWithdrawnError(message) : new Error(message));
}, (error) => {
if (!(error instanceof EmbeddedSteeringAcceptedUnconfirmedError)) log$6.warn(`failed to cancel queued steering message: ${String(error)}`);
reportRejection();
finish(error instanceof EmbeddedSteeringAcceptedUnconfirmedError ? error : accepted ? new EmbeddedSteeringAcceptedUnconfirmedError(message, { cause: error }) : new Error(message, { cause: error }));
});
};
const timer = setTimeout(() => rejectAfterCancellation("queued steering message was not committed to the transcript before timeout"), Math.max(1, timeoutMs));
timer.unref?.();
const unsubscribe = activeSession.subscribe((event) => {
if (!event || typeof event !== "object") return;
const record = event;
if (record.type === "message_end" && getSteeringMessageIdentity(record.message) === queueIdentity) {
accepted = true;
finish();
return;
}
const type = record.type;
if (type === "agent_settled" || type === "agent_handoff") rejectAfterCancellation(`active session ${type === "agent_handoff" ? "handed off" : "ended"} before queued steering message was committed to the transcript`);
});
const unsubscribePersistenceFailure = subscribeSteeringMessagePersistenceFailure(queueIdentity, (error) => {
acceptanceOpen = false;
steering.then(() => {
if (settled) return;
accepted = true;
reportAcceptance(true);
finish(error);
}, (admissionError) => {
if (settled) return;
accepted ||= hasAcceptedSteeringCustody(admissionError) || hasAcceptedSteeringCustody(error);
reportRejection();
finish(new AggregateError([error, admissionError], toErrorObject(error, "Steering persistence failed").message, { cause: error }));
});
});
if (abortRequested) {
acceptanceOpen = false;
reportAcceptance(false);
finish(/* @__PURE__ */ new Error("queued steering message was cancelled before acceptance"));
return;
}
const steering = activeSession.steer(text, images, userTurnTranscriptRecorder, media, imageOrder, queueIdentity, () => acceptanceOpen && (canInject?.() ?? true), currentInboundContext, prepareInjection);
steering.then(() => {
accepted = true;
if (!acceptanceOpen) return;
reportAcceptance(true);
if (observerErrors.length > 0) {
finish();
return;
}
if (abortRequested) rejectAfterCancellation("queued steering message was cancelled before delivery");
else if (waitForTranscriptCommit !== true && acceptanceOpen) {
clearTimeout(timer);
unsubscribePersistenceFailure();
abortSignal?.removeEventListener("abort", onAbort);
resolve();
}
}, (err) => {
accepted ||= hasAcceptedSteeringCustody(err);
if (!acceptanceOpen) return;
reportRejection();
finish(err);
});
function onAbort() {
abortRequested = true;
rejectAfterCancellation(`queued steering message was cancelled before ${accepted ? "delivery" : "acceptance"}`);
}
abortSignal?.addEventListener("abort", onAbort, { once: true });
});
}
function resolveQuestionAuthority(canInject, authority) {
return authority ?? (canInject ? {
kind: "run",
assertCurrent: () => {
if (!canInject()) throw new Error("active session is finalizing");
}
} : void 0);
}
async function steerActiveSessionWithOptionalDeliveryWait(activeSession, text, options, sessionKey, canInject, authority, prepareInjection) {
const isInboundUserMessage = options?.isInboundUserMessage === true;
const isPlainTextAnswer = !hasPromptImageInput(options);
if (isInboundUserMessage && !isPlainTextAnswer) try {
await cancelPendingAgentQuestionForSession({
sessionKey,
resolvedBy: "image-reply",
authority: resolveQuestionAuthority(canInject, authority)
});
} catch (error) {
if (canInject && !canInject()) throw error;
if (error instanceof Error && error.name === "QuestionDispatchRefusedError") throw error;
log$6.warn(`failed to cancel ask_user before image steering: ${String(error)}`);
}
if (isInboundUserMessage && isPlainTextAnswer && await claimEmbeddedPendingUserInputAnswer(text, options, sessionKey, canInject, authority)) {
options?.onQueueAccepted?.(true);
options?.onQueueSettled?.();
return;
}
if (!options || options.waitForTranscriptCommit === void 0 && !options.onQueueSettled) {
let steered = false;
try {
await activeSession.steer(text, options?.images, options?.userTurnTranscriptRecorder, options?.media, options?.imageOrder, options?.queueIdentity, canInject, options?.currentInboundContext, prepareInjection);
steered = true;
options?.onQueueAccepted?.(true);
} catch (error) {
if (steered) throw new MessageInjectionAcceptedUnconfirmedError({ cause: error });
if (!hasAcceptedSteeringCustody(error)) {
let observerFailure;
try {
options?.onQueueAccepted?.(false);
} catch (observerError) {
observerFailure = { error: observerError };
}
if (observerFailure) throw new AggregateError([error, observerFailure.error], "Steering rejection observer failed", { cause: error });
}
throw error;
}
return;
}
try {
await steerWithTranscriptLifecycle(activeSession, text, options, canInject, prepareInjection);
} catch (error) {
if (error instanceof EmbeddedSteeringAcceptedUnconfirmedError) return {
transcriptCommit: "unconfirmed",
errorMessage: error.message
};
throw error;
}
}
async function claimEmbeddedPendingUserInputAnswer(text, options, sessionKey, canInject, authority) {
if (options?.isInboundUserMessage !== true || hasPromptImageInput(options)) return false;
return await claimPendingAgentQuestionAnswer({
sessionKey,
text,
authority: resolveQuestionAuthority(canInject, authority),
sourceRecorder: options.userTurnTranscriptRecorder
});
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-steering-admission.ts
/** Owns steering through preparation rollback, prompt closure, and stream release. */
function withEmbeddedAttemptSteeringAdmission(session, signal, prepare) {
let phase = "accepting";
let unsubscribeSteering;
let unsubscribeStream;
const stop = () => {
if (phase === "closed") return;
phase = "closed";
signal.removeEventListener("abort", stop);
unsubscribeSteering?.();
};
const unsubscribe = () => {
const releaseStream = unsubscribeStream;
unsubscribeStream = void 0;
try {
stop();
} finally {
releaseStream?.();
}
};
try {
unsubscribeSteering = session.subscribe((event) => {
if (event.type === "agent_settled" || event.type === "agent_handoff") stop();
});
signal.addEventListener("abort", stop, { once: true });
if (signal.aborted) stop();
return prepare({
get closed() {
return phase === "closed";
},
get accepting() {
return phase === "accepting";
},
set accepting(value) {
if (phase !== "closed") phase = value ? "accepting" : "paused";
},
stop,
bindStreamUnsubscribe: (releaseStream) => {
unsubscribeStream = releaseStream;
return unsubscribe;
}
});
} catch (error) {
try {
unsubscribe();
} catch (cleanupError) {
log$6.error(`CRITICAL: embedded stream subscription cleanup failed: ${formatErrorMessage(cleanupError)}`);
}
throw error;
}
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-tool-search-executor.ts
/** One owner for nested execution, acceptance, and durable display activity. */
function createSubscribedToolSearchExecutor(params) {
const { attempt, subscription } = params;
const { scopeId: activityScope } = params.nestedToolActivityState;
let nestedStartOrder = 0;
return async (toolParams) => {
const runSignal = params.runSignal;
const signal = AbortSignal.any([toolParams.signal ?? runSignal, runSignal]);
let input = toolParams.input;
let preparationFailure;
try {
if (toolParams.tool.prepareArguments) input = toolParams.tool.prepareArguments(toolParams.input);
} catch (error) {
preparationFailure = { error };
}
const lifecycleArgs = preparationFailure ? toolParams.input : input;
const yieldRunSignal = toolParams.toolName === "sessions_yield" ? runSignal : void 0;
const startedAt = Date.now();
const startOrder = nestedStartOrder++;
const manager = params.sessionManager;
const afterEntryId = manager.getAppendParentId();
if (toolParams.source === "openclaw" && toolParams.sourceName === "core") recordStructuredReplayTrustForToolCall(toolParams.toolCallId, toolParams.tool, attempt.runId);
return await raceWithAbortSignal(subscription.runToolLifecycle({
toolName: toolParams.toolName,
toolCallId: toolParams.toolCallId,
parentToolCallId: toolParams.parentToolCallId,
args: lifecycleArgs,
replaySafe: toolParams.replaySafe ?? params.isReplaySafeTool(toolParams.tool),
hideFromChannelProgress: "hideFromChannelProgress" in toolParams.tool && toolParams.tool.hideFromChannelProgress === true,
onTerminal: async (terminal) => {
const message = {
...createNestedToolActivity({
runId: attempt.runId,
scopeId: activityScope,
afterEntryId,
startOrder,
parentToolCallId: toolParams.parentToolCallId,
toolCallId: toolParams.toolCallId,
toolName: toolParams.toolName,
input: terminal.executedArguments,
result: terminal.readSanitizedResult(),
isError: terminal.isError,
startedAt,
timestamp: Date.now()
}),
idempotencyKey: `${activityScope}:${toolParams.toolCallId}`
};
await runWithOwnedSessionTranscriptWrite({
sessionTarget: manager.getSessionTarget(),
sessionKey: attempt.sessionKey
}, () => withSessionManagerWrite(manager, async () => {
if (!params.isCurrent()) return;
if (isRecord(terminal.result)) copyInternalToolResultState(terminal.result, message);
const target = manager.getSessionTarget();
const sessionId = manager.getSessionId();
const entryId = await manager.appendMessageAsync(message);
if (!params.isCurrent()) return;
const activity = params.nestedToolActivityState;
if (!terminal.isError) activity.successfulToolNames.add(toolParams.toolName);
if (entryId) {
activity.accepted ??= {
firstEntryId: entryId,
lastEntryId: entryId,
sessionId,
target
};
activity.accepted.lastEntryId = entryId;
}
}));
notifyToolActivity(attempt.runId);
},
execute: async (onImplementationStart) => await raceWithAbortSignal(retainToolSearchImplementation((async () => {
if (preparationFailure) throw preparationFailure.error;
signal.throwIfAborted();
const preparer = getInternalToolExecutionPreparer(toolParams.tool);
if (!preparer) {
onImplementationStart();
return await toolParams.tool.execute(toolParams.toolCallId, input, signal, toolParams.onUpdate);
}
const prepared = await preparer({
toolCallId: toolParams.toolCallId,
args: input,
signal,
onUpdate: toolParams.onUpdate
});
try {
if (prepared.kind === "immediate") {
if (prepared.outcome.kind === "error") throw prepared.outcome.error;
return prepared.outcome.result;
}
return await prepared.execute(onImplementationStart);
} finally {
prepared.dispose();
}
})().then((result) => {
signal.throwIfAborted();
recordEmbeddedToolReceipt(manager, toolParams.toolCallId, result.details, toolParams.source === "openclaw" && toolParams.sourceName === "core" && toolParams.toolName === "message");
return toolParams.acceptResultBeforeProjection(result);
})), signal, yieldRunSignal)
}), signal, yieldRunSignal);
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-trajectory-flush.ts
async function flushEmbeddedAttemptTrajectoryRecorder(params) {
await runAgentCleanupStep({
...params,
step: "openclaw-trajectory-flush",
getTimeoutDetails: () => params.trajectoryRecorder?.describeFlushState(),
cleanup: async () => {
await params.trajectoryRecorder?.flush();
}
});
}
//#endregion
//#region src/agents/embedded-agent-runner/run/deferred-lifecycle-owner.ts
function createEmbeddedAttemptDeferredLifecycleOwner(params) {
let pending = params;
let sessionEndData;
let closeRetryWait;
const releaseRetryWait = () => {
closeRetryWait?.();
closeRetryWait = void 0;
};
const releaseActiveRun = (owner) => {
try {
owner.clearActiveRun();
} catch (error) {
log$6.error(`CRITICAL: deferred active run cleanup failed, possible resource leak: runId=${owner.runId} ${formatErrorMessage(error)}`);
}
};
return {
beginRetryWait: (deadlineAtMs, signal) => {
if (!pending) return;
releaseRetryWait();
const close = beginDiagnosticRetryWait({
owner: pending.diagnosticOwner,
deadlineAtMs,
signal,
assertCurrent: () => {
if (!pending?.isCurrent()) throw createAgentRunSupersededAbortError();
}
});
closeRetryWait = close;
return (completed) => {
if (close(completed)) pending?.onRetryWaitCompleted();
if (closeRetryWait === close) closeRetryWait = void 0;
};
},
recordSessionEnd: (data) => {
if (pending) sessionEndData = data;
},
discard: () => {
if (pending) {
const owner = pending;
pending = void 0;
sessionEndData = void 0;
releaseRetryWait();
releaseActiveRun(owner);
}
},
complete: async () => {
if (!pending) return;
const owner = pending;
pending = void 0;
releaseRetryWait();
try {
if (owner.trajectoryRecorder && sessionEndData) {
owner.trajectoryRecorder.recordEvent("session.ended", sessionEndData);
await flushEmbeddedAttemptTrajectoryRecorder({
runId: owner.runId,
sessionId: owner.sessionId,
trajectoryRecorder: owner.trajectoryRecorder,
log: log$6
});
}
} finally {
sessionEndData = void 0;
releaseActiveRun(owner);
}
}
};
}
function createDeferredEmbeddedRunLifecycleManager(params) {
const startedAtMs = Date.now();
const controller = new AbortController();
const signal = params.abortSignal ? AbortSignal.any([params.abortSignal, controller.signal]) : controller.signal;
let current;
const replaceOwner = (owner) => {
const previous = current;
current = owner;
previous?.discard();
};
const abort = (reason) => {
if (controller.signal.aborted) return;
controller.abort(reason === "restart" ? createAgentRunRestartAbortError() : reason === "superseded" ? createAgentRunSupersededAbortError() : createAgentRunDirectAbortError());
};
let cliOwner;
return {
signal,
abort,
adopt: replaceOwner,
beginRetryWait: (deadlineAtMs, retrySignal) => current?.beginRetryWait(deadlineAtMs, retrySignal ? AbortSignal.any([signal, retrySignal]) : signal),
handoffToCli: () => {
const diagnosticOwner = createDiagnosticEmbeddedRunOwner(params);
cliOwner = {
kind: "embedded",
runId: params.runId,
startedAtMs,
diagnosticOwner,
closeDiagnostics: () => closeDiagnosticEmbeddedRunOwner(diagnosticOwner),
queueMessage: async () => {
throw new Error("active run is switching runtimes");
},
isStreaming: () => false,
isStopped: () => signal.aborted,
isAborted: () => signal.aborted,
isAbortable: () => !signal.aborted,
isCompacting: () => false,
cancel: abort,
abort
};
setActiveEmbeddedRun(params.sessionId, cliOwner, params.sessionKey, params.sessionFile, params.agentId);
replaceOwner();
return diagnosticOwner;
},
complete: async () => {
const owner = current;
current = void 0;
try {
await owner?.complete();
} finally {
if (cliOwner) clearActiveEmbeddedRun(params.sessionId, cliOwner, params.sessionKey, params.sessionFile);
}
}
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/provider-refusal.ts
function resolveProviderRefusal(message) {
const refusal = message?.diagnostics?.find((diagnostic) => diagnostic.type === "provider_refusal");
if (!refusal) return;
const provider = readStringField(refusal.details, "provider");
const category = readStringField(refusal.details, "category");
if (!provider && !category) return;
const review = readProviderRefusalReview(refusal.details?.review);
const nativeThreadId = readStringField(refusal.details, "nativeThreadId");
const nativeTurnId = readStringField(refusal.details, "nativeTurnId");
return {
provider,
category,
...review ? { review } : {},
...nativeThreadId ? { nativeThreadId } : {},
...nativeTurnId ? { nativeTurnId } : {}
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-stream-prepare.ts
function prepareEmbeddedAttemptStream(input) {
return withEmbeddedAttemptSteeringAdmission(input.agentSession.activeSession, input.runAbortController.signal, (admission) => prepareStream(input, admission));
}
function prepareStream(input, admission) {
const { attempt, agentSession } = input;
const { activeSession, hookRunner } = agentSession;
let beforeAgentFinalizeRevisionReason;
let beforeAgentFinalizeRevisionEntryId;
let activeQueueAdmissions = 0;
const isSteeringAdmissionOpen = () => {
const state = input.getRunState();
return admission.accepting && !state.aborted && !state.yieldDetected && !input.runAbortController.signal.aborted;
};
const shouldRunBeforeAgentFinalize = attempt.operation !== "settled-tool-finalization" && hookRunner?.hasHooks("before_agent_finalize");
const completionCheck = attempt.operation !== "settled-tool-finalization" && !attempt.silentExpected && resolveReplyExpectation(attempt) === "required" ? attempt.completionCheck : void 0;
const onBeforeTerminalDelivery = shouldRunBeforeAgentFinalize || completionCheck ? async (event) => {
if (beforeAgentFinalizeRevisionReason || event.willRetry || event.isError || event.incompleteTerminalAssistant || !event.hasAssistantVisibleText) return;
const lastAssistant = event.lastAssistant;
const lastAssistantMessage = normalizeOptionalString(resolveFinalAssistantVisibleText(lastAssistant)) ?? normalizeOptionalString(resolveFinalAssistantRawText(lastAssistant)) ?? normalizeOptionalString(event.assistantTexts.join("\n\n"));
if (!lastAssistantMessage) return;
const state = input.getRunState();
const hasCompletedClientToolCall = agentSession.clientToolCallSlots.some((slot) => slot.completed);
if (state.aborted || input.runAbortController.signal.aborted || state.promptError || state.timedOut || hasCompletedClientToolCall || state.yieldDetected || attempt.silentExpected && isSilentReplyText(lastAssistantMessage, "NO_REPLY")) return;
const maxRevisionAttempts = attempt.maxBeforeAgentFinalizeRevisions ?? 0;
const hookRevisionLimitReached = maxRevisionAttempts > 0 && (attempt.beforeAgentFinalizeRevisionAttempts ?? 0) >= maxRevisionAttempts;
if (shouldRunBeforeAgentFinalize && hookRevisionLimitReached) log$6.warn(`before_agent_finalize revision limit reached; skipping further finalize hooks runId=${attempt.runId} sessionId=${attempt.sessionId} attempts=${attempt.beforeAgentFinalizeRevisionAttempts ?? 0}/${maxRevisionAttempts}`);
admission.accepting = false;
if (activeQueueAdmissions > 0 || activeSession.pendingMessageCount > 0 || activeSession.agent.hasQueuedMessages()) {
admission.accepting = true;
return;
}
let keepAdmissionClosed = false;
try {
let outcome = { action: "continue" };
if (shouldRunBeforeAgentFinalize && !hookRevisionLimitReached) {
const reportedModelRef = resolveReportedModelRef({
provider: attempt.provider,
model: attempt.modelId,
assistant: lastAssistant
});
outcome = await runAgentHarnessBeforeAgentFinalizeHook({
event: bindAgentHarnessHookMessages({
runId: attempt.runId,
sessionId: attempt.sessionId,
...attempt.sessionKey ? { sessionKey: attempt.sessionKey } : {},
provider: reportedModelRef.provider,
model: reportedModelRef.model,
...attempt.cwd ?? attempt.workspaceDir ? { cwd: attempt.cwd ?? attempt.workspaceDir } : {},
...attempt.sessionFile ? { transcriptPath: attempt.sessionFile } : {},
stopHookActive: false,
lastAssistantMessage,
messages: activeSession.messages
}, async () => {
const activities = await readAttemptNestedToolActivity(activeSession.sessionManager, input.nestedToolActivityState);
if (input.runAbortController.signal.aborted || input.getRunState().aborted || admission.closed || ACTIVE_EMBEDDED_RUNS.get(attempt.sessionId) !== queueHandle) throw new Error("Nested tool finalization is no longer current");
return projectNestedToolActivityForHooks(activeSession.messages, activities);
}),
ctx: {
...buildEmbeddedAgentHookContext(attempt, input.hookAgentId, freezeDiagnosticTraceContext(input.diagnosticTrace)),
modelProviderId: reportedModelRef.provider,
modelId: reportedModelRef.model
},
hookRunner
});
}
if (outcome.action === "finalize") return;
if (outcome.action === "revise") {
if (event.hadDeterministicSideEffect) log$6.warn(`before_agent_finalize requested revision after potential side effects; not rewinding runId=${attempt.runId} sessionId=${attempt.sessionId}`);
else if (!event.assistantEntryId) log$6.warn(`before_agent_finalize revision lacks a persisted assistant entry; not rewinding runId=${attempt.runId} sessionId=${attempt.sessionId}`);
else {
keepAdmissionClosed = true;
beforeAgentFinalizeRevisionEntryId = event.assistantEntryId;
beforeAgentFinalizeRevisionReason = outcome.reason;
return { suppressTerminalDelivery: true };
}
}
const currentState = input.getRunState();
if (!completionCheck?.unfinishedPlan || completionCheck.checked || lastAssistant?.stopReason !== "stop" || resolveProviderRefusal(lastAssistant) || event.hasPendingContinuation || agentSession.hasDeliveredSourceReply() || input.runAbortController.signal.aborted || currentState.aborted || currentState.timedOut || currentState.promptError || currentState.yieldDetected || admission.closed || activeSession.pendingMessageCount > 0 || activeSession.agent.hasQueuedMessages() || !hasCurrentRegistration()) return;
completionCheck.checked = true;
await activeSession.sendCustomMessage({
customType: "openclaw.plan-completion-check",
display: false,
content: `This run’s latest successfully saved plan still has unfinished steps. Before ending, check whether those steps remain required and authorized under the latest user instructions. Continue feasible work from the current transcript; do not repeat completed actions, and reconcile uncertain effects before retrying. If work is complete, reconcile the plan. If user input, approval, an external dependency, or an explicit pause prevents further work, report that concrete limitation; if your previous reply already reported it, end with only ${SILENT_REPLY_TOKEN}. Do not invent completion or new authority.`
}, { deliverAs: "followUp" });
return { continueCurrentTurn: true };
} finally {
if (!keepAdmissionClosed) admission.accepting = true;
}
} : void 0;
const clearActiveRun = () => clearActiveEmbeddedRun(attempt.sessionId, queueHandle, attempt.sessionKey, attempt.sessionFile);
let toolMetasForTerminal = [];
let deferredLifecycleOwner;
const streamSubscription = subscribeEmbeddedAgentSession({
...attempt,
...agentSession,
session: activeSession,
onModelUsage: input.onModelUsage,
messageChannel: input.runtimeChannel,
hookRunner: getGlobalHookRunner() ?? void 0,
reasoningMode: attempt.reasoningLevel ?? "off",
thinkingLevel: attempt.thinkLevel,
hasDeliveredMessageToolOnlySourceReply: agentSession.hasDeliveredSourceReply,
onDeliveredMessageToolOnlySourceReply: agentSession.markSourceReplyDelivered,
trajectoryRecorder: input.trajectoryRecorder,
onBlockReply: input.onBlockReply,
onBlockReplyFlush: input.onBlockReplyFlush,
onBeforeTerminalDelivery,
deferTerminalDelivery: shouldRunBeforeAgentFinalize === true,
terminalLifecyclePhase: attempt.deferTerminalLifecycle ? "finishing" : "end",
isTerminalAborted: () => input.getRunState().aborted,
resolveTerminalStopReason: () => isAgentRunRestartAbortReason(input.runAbortController.signal.reason) ? AGENT_RUN_RESTART_ABORT_STOP_REASON : void 0,
onBeforeLifecycleTerminal: async () => {
if (deferredLifecycleOwner) return;
let requiresTaskWait = false;
try {
requiresTaskWait = requiresCompletionRequiredAsyncTaskWait({
sessionKey: attempt.sessionKey,
toolMetas: toolMetasForTerminal,
abortSignal: input.runAbortController.signal
});
} catch (error) {
if (!input.runAbortController.signal.aborted || !isRunnerAbortError(error)) throw error;
}
if (deferredLifecycleOwner || requiresTaskWait) return;
clearActiveRun();
},
providerOwner: getModelProviderRuntimePluginHandle(attempt.model)?.plugin,
currentAccountId: attempt.agentAccountId,
currentThreadId: attempt.currentThreadTs,
agentId: input.hookAgentId
});
const unsubscribe = admission.bindStreamUnsubscribe(streamSubscription.unsubscribe);
const subscription = {
...streamSubscription,
unsubscribe
};
toolMetasForTerminal = subscription.toolMetas;
const toolSearchCatalogExecutor = createSubscribedToolSearchExecutor({
attempt,
runSignal: input.runAbortController.signal,
sessionManager: activeSession.sessionManager,
subscription,
isCurrent: () => ACTIVE_EMBEDDED_RUNS.get(attempt.sessionId) === queueHandle && !input.getRunState().aborted,
isReplaySafeTool: input.isReplaySafeTool,
nestedToolActivityState: input.nestedToolActivityState
});
let externalAbortAccepted = false;
const abortActiveRunExternally = (reason) => {
if (externalAbortAccepted) return;
externalAbortAccepted = true;
input.markExternalAbort();
attempt.onDeferredLifecycleAbort?.(reason);
attempt.onAttemptAbort?.();
const abortReason = reason === "restart" ? createAgentRunRestartAbortError() : reason === "superseded" ? createAgentRunSupersededAbortError() : void 0;
input.abortRun(false, abortReason);
};
const hasCurrentRegistration = () => {
registration?.toolAuthority?.assertActive();
return registration !== void 0 && ACTIVE_EMBEDDED_RUN_REGISTRATIONS.get(queueHandle) === registration && ACTIVE_EMBEDDED_RUNS.get(attempt.sessionId) === queueHandle && ACTIVE_EMBEDDED_RUNS_BY_RUN_ID.get(attempt.runId) === queueHandle;
};
const canInject = () => isSteeringAdmissionOpen() && hasCurrentRegistration();
const composeInjectionGuard = (assertCurrent) => () => {
assertCurrent?.();
return canInject();
};
const questionAuthority = (assertCurrent, kind, toolAuthorityPreparation) => ({
kind,
toolAuthorityPreparation,
assertCurrent: () => {
if (!composeInjectionGuard(assertCurrent)()) throw new Error("active session is finalizing");
}
});
const queueMessage = async (text, options, assertCurrent, authorityKind = assertCurrent ? "source-bound" : "run", prepareCurrent, toolAuthorityPreparation) => {
const canInjectMessage = composeInjectionGuard(assertCurrent);
if (!canInjectMessage()) throw new Error("active session is finalizing");
activeQueueAdmissions++;
try {
if (options?.steeringMode) activeSession.agent.steeringMode = options.steeringMode;
return await steerActiveSessionWithOptionalDeliveryWait(activeSession, text, options, attempt.sessionKey, canInjectMessage, questionAuthority(assertCurrent, authorityKind, toolAuthorityPreparation), prepareCurrent);
} finally {
activeQueueAdmissions--;
}
};
const claimPendingUserInputAnswer = (text, options, assertCurrent, authorityKind = assertCurrent ? "source-bound" : "run", preparation) => claimEmbeddedPendingUserInputAnswer(text, options, attempt.sessionKey, composeInjectionGuard(assertCurrent), questionAuthority(assertCurrent, authorityKind, preparation));
const cancelPendingUserInput = (resolvedBy, assertCurrent, authorityKind = assertCurrent ? "source-bound" : "run", preparation) => cancelPendingAgentQuestionForSession({
sessionKey: attempt.sessionKey,
resolvedBy,
authority: questionAuthority(assertCurrent, authorityKind, preparation)
});
const messageInjection = {
version: 2,
isAvailable: isSteeringAdmissionOpen,
queueMessage,
claimPendingUserInputAnswer,
cancelPendingUserInput,
queueMessageAsync: (text, options, preparation, kind) => queueMessage(text, options, preparation.assertCurrent, kind, preparation.prepareCurrent, preparation),
claimPendingUserInputAnswerAsync: (text, options, preparation, kind) => claimPendingUserInputAnswer(text, options, preparation.assertCurrent, kind, preparation),
cancelPendingUserInputAsync: (resolvedBy, preparation, kind) => cancelPendingUserInput(resolvedBy, preparation.assertCurrent, kind, preparation)
};
const heartbeatReplyOperation = attempt.replyOperation?.turnKind === "heartbeat" ? attempt.replyOperation : void 0;
const canApplyPermissionMode = () => admission.accepting && !input.runAbortController.signal.aborted && ACTIVE_EMBEDDED_RUNS_BY_RUN_ID.get(attempt.runId) === queueHandle;
const applyPermissionMode = input.applyPermissionMode;
const queueHandle = {
kind: "embedded",
runId: attempt.runId,
permissionChangeOwner: attempt.permissionChange?.owner,
diagnosticOwner: input.diagnosticOwner,
closeDiagnostics: () => closeDiagnosticEmbeddedRunOwner(input.diagnosticOwner),
startedAtMs: attempt.startedAtMs,
get toolAuthorityFingerprint() {
return attempt.toolAuthorityFingerprint;
},
applyPermissionMode: applyPermissionMode ? async (mode, revokeApprovals) => {
if (!canApplyPermissionMode()) return false;
if ((attempt.permissionMode ?? null) === mode) return true;
try {
await applyPermissionMode(mode, revokeApprovals);
return canApplyPermissionMode();
} catch (error) {
input.abortRun(false, error);
throw error;
}
} : void 0,
claimPendingUserInputAnswer,
cancelPendingUserInput,
preemptByVisibleTurn: heartbeatReplyOperation ? () => heartbeatReplyOperation.supersede() : void 0,
queueMessage,
messageInjection,
messageInjectionV2: messageInjection,
isStreaming: () => activeSession.isStreaming,
isAborted: () => input.getRunState().aborted,
isStopped: () => !isSteeringAdmissionOpen(),
isCompacting: () => subscription.isCompacting(),
supportsTranscriptCommitWait: true,
supportsQueueMessageImages: true,
sourceReplyDeliveryMode: attempt.sourceReplyDeliveryMode,
terminalReplyExpectation: resolveReplyExpectation(attempt),
taskSuggestionDeliveryMode: attempt.taskSuggestionDeliveryMode,
cancel: abortActiveRunExternally,
abort: abortActiveRunExternally
};
attempt.replyOperation?.attachBackend(queueHandle);
setActiveEmbeddedRunLifecycleGeneration(queueHandle, attempt.lifecycleGeneration ?? captureAgentRunLifecycleGeneration(attempt.runId));
setActiveEmbeddedRun(attempt.sessionId, queueHandle, attempt.sessionKey, attempt.sessionFile, input.hookAgentId);
const registration = ACTIVE_EMBEDDED_RUN_REGISTRATIONS.get(queueHandle);
if (attempt.deferTerminalLifecycle && attempt.onDeferredLifecycleOwner) {
deferredLifecycleOwner = createEmbeddedAttemptDeferredLifecycleOwner({
runId: attempt.runId,
sessionId: attempt.sessionId,
diagnosticOwner: input.diagnosticOwner,
onRetryWaitCompleted: () => attempt.replyOperation?.recordActivity(),
isCurrent: () => registration?.delegatedAuthority !== void 0 && validateAgentRunDelegatedAuthority(registration.delegatedAuthority) && ACTIVE_EMBEDDED_RUN_REGISTRATIONS.get(queueHandle) === registration && ACTIVE_EMBEDDED_RUNS.get(attempt.sessionId) === queueHandle,
trajectoryRecorder: input.trajectoryRecorder ?? null,
clearActiveRun: () => {
try {
unsubscribe();
} finally {
clearActiveRun();
}
}
});
try {
attempt.onDeferredLifecycleOwner(deferredLifecycleOwner);
} catch (error) {
deferredLifecycleOwner.discard();
throw error;
}
}
return {
subscription,
queueHandle,
deferredLifecycleOwner,
toolSearchCatalogExecutor,
getBeforeAgentFinalizeRevisionReason: () => beforeAgentFinalizeRevisionReason,
getBeforeAgentFinalizeRevisionEntryId: () => beforeAgentFinalizeRevisionEntryId,
stopAcceptingSteerMessages: admission.stop
};
}
//#endregion
//#region src/agents/embedded-agent-runner/prompt-cache-request-observer.ts
/** Pairs foreground request inputs with completion usage before billing aggregation. */
function createPromptCacheRequestObserver(params, onObservation, onRequest) {
let requestIndex = 0;
let request;
let observation;
return {
onModelRequest: (model, context) => {
requestIndex += 1;
request = beginPromptCacheObservation({
...params,
provider: model.provider,
modelId: model.id,
modelApi: model.api,
systemPrompt: context.systemPrompt ?? "",
tools: collectPromptCacheTools(context.tools ?? []),
messages: context.messages
});
onRequest?.({
...request,
requestIndex
});
},
onModelUsage: (usage, providerPrompt) => {
if (!request) return;
const cacheBreak = completePromptCacheObservation({
...params,
usage,
providerPrompt
});
observation = {
requestIndex,
broke: Boolean(cacheBreak),
previousCacheRead: request.previousCacheRead ?? void 0,
requestGapMs: request.requestGapMs,
providerPrefix: cacheBreak?.providerPrefix,
promptTokens: usage?.contextUsage?.state === "available" ? usage.contextUsage.promptTokens : void 0,
input: usage?.input,
cacheRead: usage?.cacheRead,
cacheWrite: usage?.cacheWrite,
changes: cacheBreak?.changes ?? request.changes
};
const { snapshot } = request;
request = void 0;
onObservation(observation, snapshot);
},
getObservation: () => observation
};
}
//#endregion
//#region src/agents/embedded-agent-runner/thinking-replay-repair.ts
/** Repairs persisted provider replay state after provider-confirmed rejection. */
async function rewriteRejectedReplayInSessionManager(params, repair) {
if (repair.replacements.length === 0) return {
repaired: false,
repairedCount: 0,
reason: repair.emptyReason
};
const rewriteResult = await rewriteTranscriptEntriesInSessionManager({
sessionManager: params.sessionManager,
replacements: repair.replacements
});
if (!rewriteResult.changed) return {
repaired: false,
repairedCount: 0,
reason: rewriteResult.reason
};
const target = params.sessionManager.getSessionTarget();
if (target || params.sessionFile) emitSessionTranscriptUpdate({
...params.sessionFile ? { sessionFile: params.sessionFile } : {},
...target ? { target } : {
sessionKey: params.sessionKey,
...params.agentId ? { agentId: params.agentId } : {}
}
});
log$6.warn(`[session-recovery] ${repair.logMessage}: repaired=${rewriteResult.rewrittenEntries} sessionKey=${params.sessionKey ?? params.sessionId ?? "unknown"}`);
return {
repaired: true,
repairedCount: rewriteResult.rewrittenEntries,
reason: rewriteResult.reason
};
}
function repairRejectedThinkingReplayInSessionManager(params) {
return rewriteRejectedReplayInSessionManager(params, {
replacements: params.sessionManager.getBranch().flatMap((entry) => {
if (entry.type !== "message") return [];
const replacement = stripThinkingBlocksFromMessage(entry.message);
return replacement === entry.message ? [] : [{
entryId: entry.id,
message: replacement
}];
}),
emptyReason: "no thinking blocks on active branch",
logMessage: "stripped thinking blocks after provider rejected replay"
});
}
function repairRejectedCompactionReplayInSessionManager(params) {
const owner = params.sessionManager.getBranch().findLast((entry) => entry.type === "message" && entry.message.role === "assistant" && (entry.message.providerReplay?.type === "openai-responses-compaction" || entry.message.providerReplay?.type === "openai-responses-retained-compaction") && entry.message.providerReplay.data === params.checkpoint.data && (params.checkpoint.id === void 0 || entry.message.providerReplay.id === params.checkpoint.id));
return rewriteRejectedReplayInSessionManager(params, {
replacements: owner?.type === "message" && owner.message.role === "assistant" ? [{
entryId: owner.id,
message: stripCompactionReplayCheckpoint(owner.message)
}] : [],
emptyReason: "no OpenAI Responses compaction checkpoint on active branch",
logMessage: "stripped compaction checkpoint after provider rejected replay"
});
}
//#endregion
//#region src/shared/message-content-blocks.ts
/** Visit object-shaped content blocks in an assistant/user message payload. */
function visitObjectContentBlocks(message, visitor) {
if (!message || typeof message !== "object") return;
const content = message.content;
if (!Array.isArray(content)) return;
for (const block of content) {
if (!block || typeof block !== "object") continue;
visitor(block);
}
}
//#endregion
//#region src/agents/embedded-agent-runner/tool-call-argument-decoding.ts
function decodeHtmlEntitiesInObject(value) {
if (typeof value === "string") return decodeHtmlEntities(value);
if (Array.isArray(value)) return value.map(decodeHtmlEntitiesInObject);
if (value && typeof value === "object") {
const result = {};
for (const [key, entry] of Object.entries(value)) result[key] = decodeHtmlEntitiesInObject(entry);
return result;
}
return value;
}
const decodedToolCallArguments = /* @__PURE__ */ new WeakSet();
function decodeToolCallArgumentsHtmlEntitiesInMessage(message) {
visitObjectContentBlocks(message, (block) => {
const typedBlock = block;
if (typedBlock.type !== "toolCall" || typeof typedBlock.arguments !== "object" || !typedBlock.arguments) return;
if (decodedToolCallArguments.has(typedBlock.arguments)) return;
const decoded = decodeHtmlEntitiesInObject(typedBlock.arguments);
decodedToolCallArguments.add(decoded);
typedBlock.arguments = decoded;
});
}
/** Wraps a stream function so tool-call arguments are decoded before consumers inspect them. */
function createHtmlEntityToolCallArgumentDecodingWrapper(baseStreamFn) {
return (model, context, options) => mapAssistantMessageStream(baseStreamFn(model, context, options), (stream) => {
const originalResult = stream.result.bind(stream);
stream.result = async () => {
const message = await originalResult();
decodeToolCallArgumentsHtmlEntitiesInMessage(message);
return message;
};
return wrapStreamObjectEvents(stream, (event) => {
decodeToolCallArgumentsHtmlEntitiesInMessage(event.partial);
decodeToolCallArgumentsHtmlEntitiesInMessage(event.message);
});
});
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-stop-reason-recovery.ts
const UNHANDLED_STOP_REASON_RE = /^Unhandled stop reason:\s*(.+)$/i;
function normalizeUnhandledStopReasonMessage(message) {
if (typeof message !== "string") return;
const stopReason = message.trim().match(UNHANDLED_STOP_REASON_RE)?.[1]?.trim();
return stopReason ? `The model stopped because the provider returned an unhandled stop reason: ${stopReason}. Please rephrase and try again.` : void 0;
}
function normalizeUnhandledStopReasonError(error) {
const message = normalizeUnhandledStopReasonMessage(formatErrorMessage(error));
if (!message) throw error;
return message;
}
function patchUnhandledStopReasonInAssistantMessage(message) {
if (!message || typeof message !== "object") return;
const assistant = message;
const normalizedMessage = normalizeUnhandledStopReasonMessage(assistant.errorMessage);
if (!normalizedMessage) return;
assistant.stopReason = "error";
assistant.errorMessage = normalizedMessage;
}
function buildUnhandledStopReasonErrorStream(model, errorMessage) {
const stream = (0, event_stream_exports.createAssistantMessageEventStream)();
queueMicrotask(() => {
stream.push({
type: "error",
reason: "error",
error: buildStreamErrorAssistantMessage({
model,
errorMessage
})
});
stream.end();
});
return stream;
}
function wrapStreamHandleUnhandledStopReason(model, stream) {
const originalResult = stream.result.bind(stream);
stream.result = async () => {
try {
const message = await originalResult();
patchUnhandledStopReasonInAssistantMessage(message);
return message;
} catch (err) {
return buildStreamErrorAssistantMessage({
model,
errorMessage: normalizeUnhandledStopReasonError(err)
});
}
};
const originalAsyncIterator = stream[Symbol.asyncIterator].bind(stream);
stream[Symbol.asyncIterator] = function() {
const iterator = originalAsyncIterator();
let emittedSyntheticTerminal = false;
return createStreamIteratorWrapper({
iterator,
next: async (streamIterator) => {
if (emittedSyntheticTerminal) return {
done: true,
value: void 0
};
try {
const result = await streamIterator.next();
if (!result.done && result.value && typeof result.value === "object") {
const event = result.value;
patchUnhandledStopReasonInAssistantMessage(event.error);
}
return result;
} catch (err) {
const normalizedMessage = normalizeUnhandledStopReasonError(err);
emittedSyntheticTerminal = true;
return {
done: false,
value: {
type: "error",
reason: "error",
error: buildStreamErrorAssistantMessage({
model,
errorMessage: normalizedMessage
})
}
};
}
}
});
};
return stream;
}
/**
* Wraps provider streams so raw "Unhandled stop reason" failures are rewritten
* into stable error messages. Recovery covers synchronous creation failures,
* async stream creation failures, iterator errors, and `result()` errors.
*/
function wrapStreamFnHandleSensitiveStopReason(baseFn) {
return (model, context, options) => {
try {
const maybeStream = baseFn(model, context, options);
if (maybeStream && typeof maybeStream === "object" && "then" in maybeStream) return Promise.resolve(maybeStream).then((stream) => wrapStreamHandleUnhandledStopReason(model, stream), (err) => buildUnhandledStopReasonErrorStream(model, normalizeUnhandledStopReasonError(err)));
return wrapStreamHandleUnhandledStopReason(model, maybeStream);
} catch (err) {
return buildUnhandledStopReasonErrorStream(model, normalizeUnhandledStopReasonError(err));
}
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-tool-call-name-resolution.ts
function resolveCaseInsensitiveAllowedToolName(rawName, allowedToolNames) {
const folded = normalizeLowercaseStringOrEmpty(rawName);
let caseInsensitiveMatch = null;
for (const name of allowedToolNames) {
if (normalizeLowercaseStringOrEmpty(name) !== folded) continue;
if (caseInsensitiveMatch && caseInsensitiveMatch !== name) return null;
caseInsensitiveMatch = name;
}
return caseInsensitiveMatch;
}
function resolveExactAllowedToolName(rawName, allowedToolNames) {
if (allowedToolNames.has(rawName)) return rawName;
const normalized = normalizeToolPolicyName(rawName);
if (allowedToolNames.has(normalized)) return normalized;
return resolveCaseInsensitiveAllowedToolName(rawName, allowedToolNames) ?? resolveCaseInsensitiveAllowedToolName(normalized, allowedToolNames);
}
function buildStructuredToolNameCandidates(rawName) {
const trimmed = rawName.trim();
if (!trimmed) return [];
const candidates = /* @__PURE__ */ new Set();
const addCandidate = (value) => {
const candidate = value.trim();
if (candidate) {
candidates.add(candidate);
candidates.add(normalizeToolPolicyName(candidate));
}
};
addCandidate(trimmed);
const structuredSeeds = [trimmed];
const xmlFragmentOffset = Math.min(...[
"\"",
"'",
"<"
].map((separator) => trimmed.indexOf(separator)).filter((offset) => offset > 0));
if (Number.isFinite(xmlFragmentOffset)) {
const prefix = trimmed.slice(0, xmlFragmentOffset);
addCandidate(prefix);
structuredSeeds.push(prefix);
}
for (const seed of structuredSeeds) {
const normalizedDelimiter = seed.replace(/\//g, ".");
addCandidate(normalizedDelimiter);
const segments = normalizeStringEntries(normalizedDelimiter.split("."));
for (let index = 1; index < segments.length; index += 1) addCandidate(segments.slice(index).join("."));
}
return [...candidates];
}
function resolveStructuredAllowedToolName(rawName, allowedToolNames) {
const candidateNames = buildStructuredToolNameCandidates(rawName);
for (const candidate of candidateNames) if (allowedToolNames.has(candidate)) return candidate;
for (const candidate of candidateNames) {
const caseInsensitiveMatch = resolveCaseInsensitiveAllowedToolName(candidate, allowedToolNames);
if (caseInsensitiveMatch) return caseInsensitiveMatch;
}
return null;
}
function inferToolNameFromToolCallId(rawId, allowedToolNames) {
if (!rawId || !allowedToolNames || allowedToolNames.size === 0) return null;
const id = rawId.trim();
if (!id) return null;
const candidateTokens = /* @__PURE__ */ new Set();
const addCounterVariants = (value) => {
candidateTokens.add(value);
candidateTokens.add(value.replace(/[:._/-]\d+$/, ""));
candidateTokens.add(value.replace(/\d+$/, ""));
};
const addToken = (value) => {
const trimmed = value.trim();
if (!trimmed) return;
const normalizedDelimiter = trimmed.replace(/\//g, ".");
addCounterVariants(trimmed);
addCounterVariants(normalizedDelimiter);
for (const prefixPattern of [/^functions?[._-]?/i, /^tools?[._-]?/i]) {
const stripped = normalizedDelimiter.replace(prefixPattern, "");
if (stripped !== normalizedDelimiter) addCounterVariants(stripped);
}
};
for (const seed of [id, id.split(":")[0] ?? id]) addToken(seed);
let singleMatch = null;
for (const candidate of candidateTokens) {
const matched = resolveStructuredAllowedToolName(candidate, allowedToolNames);
if (!matched) continue;
if (singleMatch && singleMatch !== matched) return null;
singleMatch = matched;
}
return singleMatch;
}
function looksLikeMalformedToolNameCounter(rawName) {
const normalizedDelimiter = rawName.trim().replace(/\//g, ".");
return /^(?:functions?|tools?)[._-]?/i.test(normalizedDelimiter) && /(?:[:._-]\d+|\d+)$/.test(normalizedDelimiter);
}
function resolveToolCallName(rawName, allowedToolNames, rawToolCallId, requireAllowed = false) {
const trimmed = rawName.trim();
if (!trimmed) return inferToolNameFromToolCallId(rawToolCallId, allowedToolNames) ?? (requireAllowed ? null : rawName);
if (!allowedToolNames || allowedToolNames.size === 0) return trimmed;
const resolved = resolveExactAllowedToolName(trimmed, allowedToolNames) || inferToolNameFromToolCallId(trimmed, allowedToolNames);
if (resolved) return resolved;
if (looksLikeMalformedToolNameCounter(trimmed)) return requireAllowed ? null : trimmed;
return resolveStructuredAllowedToolName(trimmed, allowedToolNames) ?? (requireAllowed ? null : trimmed);
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-tool-call-replay-sanitization.ts
const REPLAY_TOOL_CALL_NAME_MAX_CHARS = 64;
function isReplaySafeThinkingTurn(content, allowedToolNames, isCompleted) {
const seenToolCallIds = /* @__PURE__ */ new Set();
for (const block of content) {
if (!isRunnerToolCallBlock(block)) continue;
const toolCallId = typeof block.id === "string" ? block.id.trim() : "";
if (!toolCallId || seenToolCallIds.has(toolCallId)) return false;
seenToolCallIds.add(toolCallId);
const resolvedName = resolveReplayToolCallName(block, toolCallId, isCompleted(block) ? void 0 : allowedToolNames);
if (!resolvedName || block.name !== resolvedName) return false;
}
return true;
}
function collectFollowingToolResults(messages, index) {
const ids = /* @__PURE__ */ new Set();
let sawNonToolResult = false;
let displaced = false;
for (let nextIndex = index + 1; nextIndex < messages.length; nextIndex += 1) {
const message = messages[nextIndex];
if (!message || typeof message !== "object") {
sawNonToolResult = true;
continue;
}
if (message.role === "assistant" && assistantTurnHasReplayToolCall(message)) break;
if (message.role === "toolResult") {
const resultIds = extractToolResultIds(message);
for (const id of resultIds) ids.add(id);
displaced ||= resultIds.length > 0 && sawNonToolResult;
continue;
}
sawNonToolResult = true;
}
return {
ids,
displaced
};
}
function resolveReplayToolCallName(block, rawId, allowedToolNames) {
if (!hasToolCallInput(block) || !hasNonEmptyString(rawId)) return null;
const rawName = typeof block.name === "string" ? block.name : "";
if (rawName.length > 128) return null;
const trimmed = resolveToolCallName(rawName, allowedToolNames, rawId, true)?.trim();
if (!trimmed || trimmed.length > REPLAY_TOOL_CALL_NAME_MAX_CHARS || /\s/.test(trimmed)) return null;
return trimmed;
}
function sanitizeReplayToolCallInputs(messages, allowedToolNames, allowProviderOwnedThinkingReplay) {
let changed = false;
let droppedAssistantMessages = 0;
const out = [];
const preservedThinkingToolCallIds = /* @__PURE__ */ new Set();
const priorToolCallIds = /* @__PURE__ */ new Set();
const isCompleted = createCompletedToolCallPredicate(messages);
for (const [index, message] of messages.entries()) {
if (!message) {
changed = true;
continue;
}
if (typeof message !== "object" || message.role !== "assistant" || !Array.isArray(message.content)) {
out.push(message);
continue;
}
if (allowProviderOwnedThinkingReplay && isSignedThinkingReplayAssistantSpan(message)) {
const replaySafeToolCalls = extractToolCallsFromAssistant(message);
const followingToolResults = collectFollowingToolResults(messages, index);
if (isReplaySafeThinkingTurn(message.content, allowedToolNames, isCompleted) && replaySafeToolCalls.every((toolCall) => !preservedThinkingToolCallIds.has(toolCall.id) && (!followingToolResults.displaced || !priorToolCallIds.has(toolCall.id)) && followingToolResults.ids.has(toolCall.id))) {
for (const toolCall of replaySafeToolCalls) {
preservedThinkingToolCallIds.add(toolCall.id);
priorToolCallIds.add(toolCall.id);
}
changed ||= followingToolResults.displaced;
out.push(message);
} else {
changed = true;
droppedAssistantMessages += 1;
}
continue;
}
const nextContent = [];
let messageChanged = false;
for (const block of message.content) {
if (!isRunnerToolCallBlock(block)) {
nextContent.push(block);
continue;
}
const resolvedName = resolveReplayToolCallName(block, block.id, isCompleted(block) ? void 0 : allowedToolNames);
if (!resolvedName) {
messageChanged = true;
continue;
}
messageChanged ||= block.name !== resolvedName;
nextContent.push(block.name === resolvedName ? block : {
...block,
name: resolvedName
});
}
changed ||= messageChanged;
if (nextContent.length === 0 && messageChanged) {
droppedAssistantMessages += 1;
continue;
}
const nextMessage = messageChanged ? replaceCompactionReplayOwnerContent(message, nextContent) : message;
for (const toolCall of extractToolCallsFromAssistant(nextMessage)) priorToolCallIds.add(toolCall.id);
out.push(nextMessage);
}
return {
messages: changed ? out : messages,
droppedAssistantMessages
};
}
function isSignedThinkingReplayAssistantSpan(message) {
return assistantTurnHasReplayToolCall(message) && message.content.some(isThinkingLikeBlock);
}
function sanitizeAnthropicReplayToolResults(messages, disallowEmbeddedUserToolResultsForSignedThinkingReplay) {
let changed = false;
const out = [];
for (const [index, message] of messages.entries()) {
if (!message) {
changed = true;
continue;
}
if (typeof message !== "object" || message.role !== "user" || !Array.isArray(message.content)) {
out.push(message);
continue;
}
const previous = messages[index - 1];
const shouldStripEmbeddedToolResults = disallowEmbeddedUserToolResultsForSignedThinkingReplay && isSignedThinkingReplayAssistantSpan(previous);
const validToolUseIds = /* @__PURE__ */ new Set();
if (assistantTurnHasReplayToolCall(previous)) {
for (const block of previous.content) if (isRunnerToolCallBlock(block) && hasNonEmptyString(block.id)) validToolUseIds.add(block.id.trim());
}
const nextContent = message.content.filter((block) => {
const typedBlock = asOptionalObjectRecord(block);
if (typedBlock?.type !== "toolResult" && typedBlock?.type !== "tool") return true;
if (shouldStripEmbeddedToolResults) return false;
const resultIds = normalizeUniqueTrimmedStringList([
typedBlock.toolUseId,
typedBlock.toolCallId,
typedBlock.tool_use_id,
typedBlock.tool_call_id
]);
return validToolUseIds.size > 0 && resultIds.some((id) => validToolUseIds.has(id));
});
if (nextContent.length === message.content.length) {
out.push(message);
continue;
}
changed = true;
out.push({
...message,
content: nextContent.length > 0 ? nextContent : [{
type: "text",
text: "[tool results omitted]"
}]
});
}
return changed ? out : messages;
}
function assistantTurnHasReplayToolCall(message) {
if (!message || typeof message !== "object" || message.role !== "assistant") return false;
return Array.isArray(message.content) && message.content.some(isRunnerToolCallBlock);
}
function stripTrailingAssistantPrefillTurns(messages) {
let end = messages.length;
while (end > 0) {
const message = messages[end - 1];
if (!message || typeof message !== "object" || message.role !== "assistant" || assistantTurnHasReplayToolCall(message)) break;
end -= 1;
}
return end === messages.length ? messages : messages.slice(0, end);
}
function shouldApplyReplayToolCallIdSanitizer(params) {
return params.sanitizeToolCallIds && Boolean(params.toolCallIdMode) && !params.isOpenAIResponsesApi;
}
function sanitizeReplayToolCallIdsForStream(params) {
const paired = params.repairToolUseResultPairing ? sanitizeToolUseResultPairing(params.messages) : params.messages;
return sanitizeToolCallIdsForCloudCodeAssist(paired, params.mode, {
preserveNativeAnthropicToolUseIds: params.preserveNativeAnthropicToolUseIds,
duplicateToolCallIdStyle: params.duplicateToolCallIdStyle,
preserveReplaySafeThinkingToolCallIds: params.preserveReplaySafeThinkingToolCallIds,
allowedToolNames: params.allowedToolNames
});
}
function sanitizeOpenAIResponsesReplayForStream(messages) {
const repaired = sanitizeToolUseResultPairingForModel(messages, true);
return downgradeOpenAIFunctionCallReasoningPairs(normalizeOpenAIResponsesToolCallIds(repaired));
}
function wrapStreamFnSanitizeMalformedToolCalls(baseFn, allowedToolNames, transcriptPolicy, provider) {
return (model, context, options) => {
const messages = context?.messages;
if (!Array.isArray(messages)) return baseFn(model, context, options);
const modelApi = model.api;
const allowProviderOwnedThinkingReplay = shouldAllowProviderOwnedThinkingReplay({
modelApi,
provider,
policy: {
validateAnthropicTurns: transcriptPolicy?.validateAnthropicTurns === true,
preserveSignatures: transcriptPolicy?.preserveSignatures === true,
dropThinkingBlocks: transcriptPolicy?.dropThinkingBlocks === true
}
});
const sanitized = sanitizeReplayToolCallInputs(messages, allowedToolNames, allowProviderOwnedThinkingReplay);
const isOpenAIResponsesApi = modelApi === "openai-responses" || modelApi === "openai-chatgpt-responses" || modelApi === "azure-openai-responses";
const replayInputsChanged = sanitized.messages !== messages;
let nextMessages = isOpenAIResponsesApi ? sanitizeToolUseResultPairingForModel(sanitized.messages, true) : replayInputsChanged ? sanitizeToolUseResultPairing(sanitized.messages) : sanitized.messages;
let strippedTrailingAssistantPrefill = false;
if (transcriptPolicy?.validateAnthropicTurns) nextMessages = sanitizeAnthropicReplayToolResults(nextMessages, allowProviderOwnedThinkingReplay);
if (transcriptPolicy?.validateAnthropicTurns || transcriptPolicy?.validateGeminiTurns) {
const beforeStrip = nextMessages;
nextMessages = stripTrailingAssistantPrefillTurns(nextMessages);
strippedTrailingAssistantPrefill ||= nextMessages !== beforeStrip;
}
if (nextMessages === messages) {
if (modelApi !== "bedrock-converse-stream") return baseFn(model, context, options);
nextMessages = mergeConsecutiveUserMessages(nextMessages);
} else if (sanitized.droppedAssistantMessages > 0 || transcriptPolicy?.validateAnthropicTurns || strippedTrailingAssistantPrefill) {
if (transcriptPolicy?.validateGeminiTurns) nextMessages = validateGeminiTurns(nextMessages);
if (transcriptPolicy?.validateAnthropicTurns) nextMessages = validateAnthropicTurns(nextMessages, { mergeConsecutiveUserTurns: shouldMergeConsecutiveUserTurns(transcriptPolicy, modelApi) });
}
if (nextMessages === messages) return baseFn(model, context, options);
return baseFn(model, {
...context,
messages: nextMessages
}, options);
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-tool-call-stream-normalization.ts
const BLANK_TOOL_CALL_NAME_DESCRIPTION = "blank tool name";
function normalizeToolCallsInMessage(message, allowedToolNames, fallbackIdByContentIndex, earlierToolCallIds, seenToolCallIds) {
if (!message || typeof message !== "object") return;
const content = message.content;
if (!Array.isArray(content)) return;
let usedIds;
let unknownToolName;
let sawAllowedToolCall = false;
let sawIncompleteToolCall = false;
let sawBlankStringToolCall = false;
const hasAllowedToolNames = allowedToolNames !== void 0;
for (const block of content) {
if (!isRunnerToolCallBlock(block)) continue;
usedIds ??= /* @__PURE__ */ new Set();
const rawId = typeof block.id === "string" ? block.id : void 0;
const normalized = resolveToolCallName(typeof block.name === "string" ? block.name : "", allowedToolNames, rawId);
if (normalized && normalized !== block.name) block.name = normalized;
const trimmedId = rawId?.trim();
if (trimmedId) usedIds.add(trimmedId);
const rawBlockName = block.name;
const hasStringName = typeof rawBlockName === "string";
const rawName = hasStringName ? rawBlockName.trim() : "";
if (!rawName) {
if (hasStringName) sawBlankStringToolCall = true;
else sawIncompleteToolCall = true;
continue;
}
if (!hasAllowedToolNames) continue;
if (hasStringName && allowedToolNames?.has(rawBlockName)) {
sawAllowedToolCall = true;
continue;
}
const normalizedUnknownToolName = normalizeToolPolicyName(rawName);
if (!unknownToolName) unknownToolName = normalizedUnknownToolName;
else if (unknownToolName !== normalizedUnknownToolName) sawIncompleteToolCall = true;
}
if (!usedIds) return;
const assignedIds = /* @__PURE__ */ new Set();
for (const [contentIndex, block] of content.entries()) {
if (!isRunnerToolCallBlock(block)) continue;
const trimmedId = typeof block.id === "string" ? block.id.trim() : "";
if (trimmedId && !earlierToolCallIds.has(trimmedId) && !assignedIds.has(trimmedId)) {
if (block.id !== trimmedId) block.id = trimmedId;
assignedIds.add(trimmedId);
continue;
}
let fallbackId = fallbackIdByContentIndex[contentIndex];
while (!fallbackId || earlierToolCallIds.has(fallbackId) || usedIds.has(fallbackId) || assignedIds.has(fallbackId)) fallbackId = `call_${randomUUID().replace(/-/g, "").slice(0, 24)}`;
fallbackIdByContentIndex[contentIndex] = fallbackId;
block.id = fallbackId;
usedIds.add(fallbackId);
assignedIds.add(fallbackId);
}
for (const id of assignedIds) seenToolCallIds.add(id);
if (!hasAllowedToolNames) return sawBlankStringToolCall ? {
kind: "malformed",
toolName: BLANK_TOOL_CALL_NAME_DESCRIPTION
} : void 0;
if (sawAllowedToolCall) return { kind: "allowed" };
if (sawBlankStringToolCall && !sawIncompleteToolCall && unknownToolName === void 0) return {
kind: "malformed",
toolName: BLANK_TOOL_CALL_NAME_DESCRIPTION
};
if (sawIncompleteToolCall) return { kind: "incomplete" };
return unknownToolName ? {
kind: "unknown",
toolName: unknownToolName
} : { kind: "incomplete" };
}
function rewriteUnknownToolLoopMessage(message, toolName) {
if (!message || typeof message !== "object") return;
message.content = [{
type: "text",
text: `I can't use the tool "${toolName}" here because it isn't available. I need to stop retrying it and answer without that tool.`
}];
stripCompactionReplayCheckpointInPlace(message);
}
function guardUnknownToolLoopInMessage(message, toolCallState, state, params) {
if (toolCallState?.kind === "allowed") {
if (params.projection !== "partial") {
state.lastUnknownToolName = void 0;
state.count = 0;
}
return false;
}
if (toolCallState?.kind === "malformed") {
if (params.projection === "result") {
rewriteUnknownToolLoopMessage(message, toolCallState.toolName);
return true;
}
return false;
}
const threshold = params.threshold;
if (threshold === void 0 || threshold <= 0) return false;
if (toolCallState?.kind !== "unknown") {
if (params.countAttempt && params.projection === "result") {
state.lastUnknownToolName = void 0;
state.count = 0;
}
return false;
}
const unknownToolName = toolCallState.toolName;
const countableMessage = message && typeof message === "object" ? message : void 0;
if (params.countAttempt && !(countableMessage && state.countedMessages.has(countableMessage))) {
if (countableMessage) state.countedMessages.add(countableMessage);
state.count = state.lastUnknownToolName === unknownToolName ? state.count + 1 : 1;
state.lastUnknownToolName = unknownToolName;
}
if (state.lastUnknownToolName === unknownToolName && state.count > threshold) rewriteUnknownToolLoopMessage(message, unknownToolName);
return params.countAttempt;
}
function wrapStreamTrimToolCallNames(stream, allowedToolNames, options) {
const fallbackIdByContentIndex = [];
let streamAttemptAlreadyCounted = false;
const normalize = (message) => normalizeToolCallsInMessage(message, allowedToolNames, fallbackIdByContentIndex, options.earlierToolCallIds, options.seenToolCallIds);
const guard = (message, toolCallState, projection) => guardUnknownToolLoopInMessage(message, toolCallState, options.state, {
threshold: options.unknownToolThreshold,
countAttempt: projection !== "partial" && !streamAttemptAlreadyCounted,
projection
});
const originalResult = stream.result.bind(stream);
stream.result = async () => {
const message = await originalResult();
guard(message, normalize(message), "result");
return message;
};
wrapStreamObjectEvents(stream, (event) => {
const partialState = normalize(event.partial);
const messageState = normalize(event.message);
if (event.message && typeof event.message === "object") {
const countedStreamAttempt = guard(event.message, messageState, "message");
streamAttemptAlreadyCounted ||= countedStreamAttempt;
}
if (event.partial !== event.message) guard(event.partial, partialState, "partial");
});
return stream;
}
function wrapStreamFnTrimToolCallNames(baseFn, allowedToolNames, guardOptions) {
const unknownToolGuardState = {
count: 0,
countedMessages: /* @__PURE__ */ new WeakSet()
};
const seenToolCallIds = /* @__PURE__ */ new Set();
return (model, context, streamOptions) => {
for (const message of context.messages ?? []) {
if (message.role !== "assistant") continue;
for (const block of message.content) if (isRunnerToolCallBlock(block) && typeof block.id === "string" && block.id.trim()) seenToolCallIds.add(block.id.trim());
}
const earlierToolCallIds = new Set(seenToolCallIds);
return mapAssistantMessageStream(baseFn(model, context, streamOptions), (stream) => wrapStreamTrimToolCallNames(stream, allowedToolNames, {
unknownToolThreshold: guardOptions?.unknownToolThreshold,
state: unknownToolGuardState,
earlierToolCallIds,
seenToolCallIds
}));
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-tool-call-text-promotion.ts
const STANDALONE_TEXT_TOOL_CALL_PROMOTION_STOP_REASONS = /* @__PURE__ */ new Set(["stop", "toolUse"]);
function wrapStreamPromoteStandaloneTextToolCalls(stream, allowedToolNames) {
const matcher = {
hasExactName: (name) => Boolean(resolveToolCallName(name, allowedToolNames, void 0, true)),
hasNamePrefix: (prefix) => couldNormalizeToolNamePrefixToAllowedTool(prefix, allowedToolNames)
};
const promotedIdBySource = /* @__PURE__ */ new Map();
const normalizeTerminalMessage = (params) => {
const scrubbed = projectScrubbedPlainTextToolCallMessage({
forceIncompleteCandidates: true,
matcher,
message: params.message,
preserveEmptyTextBlocks: params.preserveEmptyTextBlocks,
resolveProtectedRanges: findMarkdownCodeRegions,
requireAssistantRole: true
});
if (scrubbed) {
stripCompactionReplayCheckpointInPlace(scrubbed.message);
return {
kind: "scrubbed",
...scrubbed
};
}
if (!params.allowPromotion) return;
let ordinal = 0;
const createStableToolCallBlock = (block, name) => {
const sourceKey = `${ordinal}:${block.start}:${block.end}`;
ordinal += 1;
const id = promotedIdBySource.get(sourceKey) || `call_${randomUUID().replace(/-/g, "").slice(0, 24)}`;
promotedIdBySource.set(sourceKey, id);
return {
type: "toolCall",
id,
name,
arguments: block.arguments,
partialArgs: JSON.stringify(block.arguments)
};
};
const promoted = projectStandalonePlainTextToolCallMessage({
allowedStopReasons: STANDALONE_TEXT_TOOL_CALL_PROMOTION_STOP_REASONS,
allowedToolNames,
createToolCallBlock: createStableToolCallBlock,
isRetainableNonTextBlock: isThinkingLikeBlock,
message: params.message,
requireAssistantRole: true,
resolveProtectedRanges: findMarkdownCodeRegions,
resolveToolName: (name) => resolveToolCallName(name, allowedToolNames, void 0, true)
});
if (promoted) stripCompactionReplayCheckpointInPlace(promoted.message);
return promoted ? {
kind: "promoted",
...promoted
} : void 0;
};
const originalResult = stream.result.bind(stream);
stream.result = async () => {
const message = await originalResult();
const reason = message && typeof message === "object" ? message.stopReason : void 0;
return normalizeTerminalMessage({
allowPromotion: STANDALONE_TEXT_TOOL_CALL_PROMOTION_STOP_REASONS.has(reason),
message
})?.message ?? message;
};
const originalAsyncIterator = stream[Symbol.asyncIterator].bind(stream);
const wrappedAsyncIterator = function() {
const sourceIterator = originalAsyncIterator();
const normalized = normalizePlainTextToolCallStreamEvents({ [Symbol.asyncIterator]: () => sourceIterator }, {
createPromotedToolCallEvents: createPromotedPlainTextToolCallEvents,
matcher,
normalizeTerminalMessage,
protectedRangesFenceCompatible: true,
resolveProtectedRanges: findMarkdownCodeRegions
})[Symbol.asyncIterator]();
let started = false;
return createStreamIteratorWrapper({
iterator: normalized,
next: (iterator) => {
started = true;
return iterator.next();
},
onReturn: async (iterator, value) => {
const unopened = !started;
started = true;
try {
return await iterator.return?.(value) ?? {
done: true,
value: void 0
};
} finally {
if (unopened) await sourceIterator.return?.(value);
}
}
});
};
if (!Reflect.set(stream, Symbol.asyncIterator, wrappedAsyncIterator)) throw new TypeError("Cannot replace stream async iterator");
return stream;
}
function wrapStreamFnPromoteStandaloneTextToolCalls(baseFn, allowedToolNames) {
if (!allowedToolNames || allowedToolNames.size === 0) return baseFn;
return (model, context, streamOptions) => mapAssistantMessageStream(baseFn(model, context, streamOptions), (stream) => wrapStreamPromoteStandaloneTextToolCalls(stream, allowedToolNames));
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt.tool-call-argument-repair.ts
const MAX_TOOLCALL_REPAIR_BUFFER_CHARS = 64e3;
const MAX_TOOLCALL_REPAIR_LEADING_CHARS = 96;
const MAX_TOOLCALL_REPAIR_TRAILING_CHARS = 3;
const TOOLCALL_REPAIR_ALLOWED_LEADING_RE = /^[a-z0-9\s"'`.:/_\\-]+$/i;
const TOOLCALL_REPAIR_ALLOWED_TRAILING_RE = /^[^\s{}[\]":,\\]{1,3}$/;
const TOOLCALL_REPAIR_SMART_QUOTES = /* @__PURE__ */ new Set([
"“",
"”",
"„",
"‟"
]);
const MAX_TOOLCALL_REPAIR_MEMBER_KEY_CHARS = 96;
const TOOLCALL_REPAIR_KNOWN_ARG_KEYS = /* @__PURE__ */ new Set([
"args",
"backupDir",
"cmd",
"command",
"content",
"cwd",
"edits",
"file",
"file_path",
"filePath",
"filepath",
"from",
"line_end",
"line_start",
"lines",
"message",
"new_str",
"new_string",
"newText",
"old_str",
"old_string",
"oldText",
"path",
"paths",
"pattern",
"query",
"replacement",
"text",
"timeoutMs",
"title",
"to",
"url",
"urls",
"workdir"
]);
const TOOLCALL_REPAIR_FREEFORM_VALUE_KEYS = /* @__PURE__ */ new Set([
"content",
"message",
"new_str",
"new_string",
"newText",
"old_str",
"old_string",
"oldText",
"text"
]);
const TOOLCALL_REPAIR_FREEFORM_SUCCESSOR_KEYS = {
old_str: "new_str",
old_string: "new_string",
oldText: "newText"
};
const TOOLCALL_REPAIR_JSON_STRING_ESCAPES = {
"\"": "\"",
"\\": "\\",
"/": "/",
b: "\b",
f: "\f",
n: "\n",
r: "\r",
t: " "
};
function shouldAttemptMalformedToolCallRepair(partialJson, delta) {
if (/[}\]]/.test(delta)) return true;
const trimmedDelta = delta.trim();
return trimmedDelta.length > 0 && trimmedDelta.length <= MAX_TOOLCALL_REPAIR_TRAILING_CHARS && /[}\]]/.test(partialJson);
}
function isAllowedToolCallRepairLeadingPrefix(prefix) {
return !prefix || prefix.length <= MAX_TOOLCALL_REPAIR_LEADING_CHARS && TOOLCALL_REPAIR_ALLOWED_LEADING_RE.test(prefix) && (/^[.:'"`-]/.test(prefix) || /^(?:functions?|tools?)[._:/-]?/i.test(prefix));
}
function skipWhitespace(raw, index) {
let next = index;
while (next < raw.length && raw[next]?.trim() === "") next += 1;
return next;
}
function isToolCallRepairSmartQuote(char) {
return char !== void 0 && TOOLCALL_REPAIR_SMART_QUOTES.has(char);
}
function findAsciiQuotedStringEnd(raw, startIndex) {
for (let i = startIndex + 1; i < raw.length; i += 1) if (raw[i] === "\\") i += 1;
else if (raw[i] === "\"") return i + 1;
}
function readAsciiQuotedString(raw, startIndex) {
const endIndex = findAsciiQuotedStringEnd(raw, startIndex);
const parsed = endIndex === void 0 ? void 0 : safeParseJson(raw.slice(startIndex, endIndex));
return typeof parsed === "string" && endIndex !== void 0 ? {
value: parsed,
endIndex
} : void 0;
}
function readSmartQuotedString(raw, startIndex, closesAt, maxChars = Infinity) {
for (let i = startIndex + 1; i < raw.length; i += 1) {
if (isToolCallRepairSmartQuote(raw[i]) && closesAt(i)) return {
value: raw.slice(startIndex + 1, i),
endIndex: i + 1
};
if (i - startIndex > maxChars) return;
}
}
function readObjectKey(raw, startIndex) {
const char = raw[startIndex];
return char === "\"" ? readAsciiQuotedString(raw, startIndex) : isToolCallRepairSmartQuote(char) ? readSmartQuotedString(raw, startIndex, (index) => raw[skipWhitespace(raw, index + 1)] === ":", MAX_TOOLCALL_REPAIR_MEMBER_KEY_CHARS) : void 0;
}
function readObjectMemberKeyAfterComma(raw, commaIndex) {
const key = readObjectKey(raw, skipWhitespace(raw, commaIndex + 1));
if (!key || raw[skipWhitespace(raw, key.endIndex)] !== ":") return;
return key.value;
}
function normalizeToolCallRepairToolName(value) {
const trimmed = value.trim();
return /^[a-z0-9_-]{1,128}$/i.test(trimmed) ? trimmed.toLowerCase() : void 0;
}
function extractToolNameFromLeadingPrefix(prefix) {
const match = /(?:^|[.\s])(?:functions?|tools?)[._:/-]?([a-z0-9_-]+)/i.exec(prefix);
return match?.[1] ? normalizeToolCallRepairToolName(match[1]) : void 0;
}
function shouldCloseSmartQuotedValueAt(raw, quoteIndex, valueKey, toolName) {
const nextIndex = skipWhitespace(raw, quoteIndex + 1);
const nextChar = raw[nextIndex];
if (nextIndex >= raw.length || nextChar === "}") return true;
if (nextChar !== ",") return false;
const nextKey = readObjectMemberKeyAfterComma(raw, nextIndex);
if (!nextKey) return false;
if (!TOOLCALL_REPAIR_FREEFORM_VALUE_KEYS.has(valueKey)) return TOOLCALL_REPAIR_KNOWN_ARG_KEYS.has(nextKey) || toolName === "read" && valueKey === "path" && (nextKey === "offset" || nextKey === "limit");
return TOOLCALL_REPAIR_FREEFORM_SUCCESSOR_KEYS[valueKey] === nextKey;
}
function decodeSmartQuotedJsonStringEscapes(value) {
return value.replace(/\\(?:(["\\/bfnrt])|u([0-9a-fA-F]{4}))/g, (match, escaped, hex) => {
if (typeof hex === "string") return String.fromCharCode(Number.parseInt(hex, 16));
return typeof escaped === "string" ? TOOLCALL_REPAIR_JSON_STRING_ESCAPES[escaped] ?? match : match;
});
}
function readJsonValue(raw, startIndex) {
let depth = 0;
for (let i = startIndex; i < raw.length; i += 1) {
const char = raw[i];
if (char === "\"") {
const endIndex = findAsciiQuotedStringEnd(raw, i);
if (endIndex === void 0) return;
i = endIndex - 1;
} else if (char === "{" || char === "[") depth += 1;
else if (char === "}" || char === "]") {
if (depth === 0) return parseJsonValuePrefix(raw, startIndex, i);
depth -= 1;
} else if (char === "," && depth === 0) return parseJsonValuePrefix(raw, startIndex, i);
}
return parseJsonValuePrefix(raw, startIndex, raw.length);
}
function parseJsonValuePrefix(raw, startIndex, endIndex) {
const value = safeParseJson(raw.slice(startIndex, endIndex).trim());
return value === void 0 ? void 0 : {
value,
endIndex
};
}
function readCommaSeparatedBody(raw, startIndex, closing, readEntry) {
let index = skipWhitespace(raw, startIndex + 1);
if (raw[index] === closing) return index + 1;
while (index < raw.length) {
const endIndex = readEntry(index);
if (endIndex === void 0) return;
index = skipWhitespace(raw, endIndex);
if (raw[index] === ",") {
index = skipWhitespace(raw, index + 1);
continue;
}
return raw[index] === closing ? index + 1 : void 0;
}
}
function readObjectValue(raw, startIndex, key, toolName) {
const char = raw[startIndex];
if (char === "\"") return readAsciiQuotedString(raw, startIndex);
if (isToolCallRepairSmartQuote(char)) {
const parsed = readSmartQuotedString(raw, startIndex, (index) => shouldCloseSmartQuotedValueAt(raw, index, key, toolName));
return parsed && {
...parsed,
value: decodeSmartQuotedJsonStringEscapes(parsed.value)
};
}
if (key === "edits" && char === "[") {
const edits = [];
const endIndex = readCommaSeparatedBody(raw, startIndex, "]", (index) => {
const edit = parseSmartQuotedToolCallObject(raw, index);
if (!edit) return;
edits.push(edit.value);
return edit.endIndex;
});
return endIndex === void 0 ? void 0 : {
value: edits,
endIndex
};
}
return readJsonValue(raw, startIndex);
}
function parseSmartQuotedToolCallObject(raw, startIndex, toolName) {
if (raw[startIndex] !== "{") return;
const args = {};
const seenKeys = /* @__PURE__ */ new Set();
const endIndex = readCommaSeparatedBody(raw, startIndex, "}", (index) => {
const key = readObjectKey(raw, index);
if (!key || seenKeys.has(key.value)) return;
seenKeys.add(key.value);
const colonIndex = skipWhitespace(raw, key.endIndex);
if (raw[colonIndex] !== ":") return;
const value = readObjectValue(raw, skipWhitespace(raw, colonIndex + 1), key.value, toolName);
if (!value) return;
args[key.value] = value.value;
return value.endIndex;
});
return endIndex === void 0 ? void 0 : {
value: args,
endIndex
};
}
function tryExtractUsableToolCallArgumentsFromJson(raw) {
const extracted = extractBalancedJsonPrefix(raw);
if (!extracted) return;
const leadingPrefix = raw.slice(0, extracted.startIndex).trim();
if (!isAllowedToolCallRepairLeadingPrefix(leadingPrefix)) return;
const suffix = raw.slice(extracted.startIndex + extracted.json.length).trim();
if (leadingPrefix.length === 0 && suffix.length === 0) return;
return finishToolCallArgumentRepair(safeParseJsonRecord(extracted.json), leadingPrefix, suffix);
}
function finishToolCallArgumentRepair(args, leadingPrefix, trailingSuffix) {
if (!args || trailingSuffix.length > MAX_TOOLCALL_REPAIR_TRAILING_CHARS || trailingSuffix.length > 0 && !TOOLCALL_REPAIR_ALLOWED_TRAILING_RE.test(trailingSuffix)) return;
return {
args,
kind: "repaired",
leadingPrefix,
trailingSuffix
};
}
function tryExtractSmartQuotedToolCallArguments(raw, toolNameFromContext) {
if (!/[\u201c\u201d\u201e\u201f]/.test(raw)) return;
const startIndex = raw.indexOf("{");
if (startIndex < 0) return;
const leadingPrefix = raw.slice(0, startIndex).trim();
if (!isAllowedToolCallRepairLeadingPrefix(leadingPrefix)) return;
const parsed = parseSmartQuotedToolCallObject(raw, startIndex, toolNameFromContext ?? extractToolNameFromLeadingPrefix(leadingPrefix));
if (!parsed) return;
return finishToolCallArgumentRepair(parsed.value, leadingPrefix, raw.slice(parsed.endIndex).trim());
}
function tryExtractUsableToolCallArguments(raw, toolNameFromContext) {
if (!raw.trim()) return;
const parsedRaw = safeParseJsonRecord(raw);
if (parsedRaw) return {
args: parsedRaw,
kind: "preserved",
leadingPrefix: "",
trailingSuffix: ""
};
return tryExtractUsableToolCallArgumentsFromJson(raw) ?? tryExtractSmartQuotedToolCallArguments(raw, toolNameFromContext);
}
function readToolCallBlock(message, contentIndex) {
const content = asOptionalObjectRecord(message)?.content;
const block = Array.isArray(content) ? content[contentIndex] : void 0;
return isRunnerToolCallBlock(block) ? block : void 0;
}
function wrapStreamRepairMalformedToolCallArguments(stream) {
const stateByIndex = /* @__PURE__ */ new Map();
const originalResult = stream.result.bind(stream);
stream.result = async () => {
const message = await originalResult();
for (const [index, state] of stateByIndex) {
const block = readToolCallBlock(message, index);
if (block && state.repairedArgs) block.arguments = state.repairedArgs;
}
stateByIndex.clear();
return message;
};
wrapStreamObjectEvents(stream, (event) => {
const index = event.contentIndex;
if (typeof index !== "number" || !Number.isInteger(index)) return;
if (event.type === "toolcall_delta" && typeof event.delta === "string") {
const state = stateByIndex.get(index) ?? { partialJson: "" };
if (state.disabled) return;
stateByIndex.set(index, state);
state.partialJson += event.delta;
if (state.partialJson.length > MAX_TOOLCALL_REPAIR_BUFFER_CHARS) {
state.partialJson = "";
state.repairedArgs = void 0;
state.disabled = true;
return;
}
const hadRepairState = state.repairedArgs !== void 0;
if (!shouldAttemptMalformedToolCallRepair(state.partialJson, event.delta) && !hadRepairState) return;
const blocks = [readToolCallBlock(event.partial, index), readToolCallBlock(event.message, index)];
const toolName = blocks.map((block) => typeof block?.name === "string" ? normalizeToolCallRepairToolName(block.name) : void 0).find((name) => name !== void 0);
const repair = tryExtractUsableToolCallArguments(state.partialJson, toolName);
const hadPreexistingArgs = state.hadPreexistingArgs || !hadRepairState && blocks.some((block) => isRecord(block?.arguments) && Object.keys(block.arguments).length > 0);
state.repairedArgs = repair?.args;
if (repair) state.hadPreexistingArgs = hadPreexistingArgs;
if (repair || !hadPreexistingArgs) {
for (const block of blocks) if (block) block.arguments = repair?.args ?? {};
}
if (repair?.kind === "repaired" && !state.loggedRepair) {
state.loggedRepair = true;
log$6.warn(`repairing malformed tool call arguments with ${repair.leadingPrefix.length} leading chars and ${repair.trailingSuffix.length} trailing chars`);
}
}
if (event.type === "toolcall_end") {
const repairedArgs = stateByIndex.get(index)?.repairedArgs;
if (repairedArgs) {
for (const block of [
asOptionalObjectRecord(event.toolCall),
readToolCallBlock(event.partial, index),
readToolCallBlock(event.message, index)
]) if (block) block.arguments = repairedArgs;
stateByIndex.set(index, {
partialJson: "",
repairedArgs
});
} else stateByIndex.delete(index);
}
});
return stream;
}
function wrapStreamFnRepairMalformedToolCallArguments(baseFn) {
return (model, context, options) => mapAssistantMessageStream(baseFn(model, context, options), wrapStreamRepairMalformedToolCallArguments);
}
function shouldRepairMalformedToolCallArguments(params) {
const modelApi = params.modelApi ?? "";
return normalizeProviderId(params.provider ?? "") === "kimi" && modelApi === "anthropic-messages" || modelApi === "openai-completions" || modelApi === "azure-openai-responses" || modelApi === "openai-chatgpt-responses";
}
//#endregion
//#region src/agents/embedded-agent-runner/run/llm-idle-timeout.ts
const DEFAULT_LLM_IDLE_TIMEOUT_MS = 12e4;
const SELF_HOSTED_LLM_IDLE_TIMEOUT_MS = 3e5;
const CLOUD_LLM_FIRST_EVENT_TIMEOUT_MS = DEFAULT_LLM_IDLE_TIMEOUT_MS;
const LOCAL_LLM_FIRST_EVENT_TIMEOUT_MS = 3e5;
const CRON_LLM_IDLE_TIMEOUT_MS = 6e4;
const LOCAL_PROVIDER_AUTH_MARKERS = /* @__PURE__ */ new Set(["custom-local", "ollama-local"]);
const SELF_HOSTED_PROVIDER_ID_PREFIXES = [
"ollama",
"lmstudio",
"vllm",
"sglang",
"llama-cpp"
];
const EXPLICIT_LOCAL_HOSTNAMES = /* @__PURE__ */ new Set([
"docker.orb.internal",
"host.docker.internal",
"host.orb.internal"
]);
/**
* Local endpoints can stay silent during prompt evaluation. Classify the URL
* hostname without DNS on this hot path. IPv4-mapped IPv6 deliberately covers
* loopback only, matching the cron model preflight policy.
*/
function isLocalProviderHostname(hostname) {
let host = hostname;
if (host.startsWith("[") && host.endsWith("]")) host = host.slice(1, -1);
if (host === "localhost" || host === "0.0.0.0" || host === "::1" || host === "::ffff:7f00:1" || host === "::ffff:127.0.0.1" || host.endsWith(".local")) return true;
if (/^f[cd][0-9a-f]{2}:/.test(host) || /^fe[89ab][0-9a-f]:/.test(host)) return true;
if (!/^\d+\.\d+\.\d+\.\d+$/.test(host)) return false;
const octets = host.split(".").map((part) => Number.parseInt(part, 10));
if (octets.some((p) => !Number.isInteger(p) || p < 0 || p > 255)) return false;
const [a, b] = octets;
return a === 127 || a === 10 || a === 172 && b !== void 0 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 100 && b !== void 0 && b >= 64 && b <= 127;
}
function isBareProviderHostname(hostname) {
return /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(hostname);
}
function isSelfHostedProviderId(provider) {
const normalized = provider?.trim().toLowerCase();
if (!normalized || normalized === "ollama-cloud") return false;
return SELF_HOSTED_PROVIDER_ID_PREFIXES.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix}-`));
}
function findConfiguredProviderConfig(cfg, provider) {
const normalizedProvider = provider?.trim().toLowerCase();
if (!normalizedProvider) return;
const providers = cfg?.models?.providers;
return providers?.[normalizedProvider] || Object.entries(providers ?? {}).find(([key]) => key.trim().toLowerCase() === normalizedProvider)?.[1];
}
function hasLocalProviderAuthMarker(apiKey) {
return typeof apiKey === "string" && LOCAL_PROVIDER_AUTH_MARKERS.has(apiKey.trim().toLowerCase());
}
function hasConfiguredLocalProviderSignal(params) {
const providerConfig = findConfiguredProviderConfig(params.cfg, params.provider);
return Boolean(providerConfig?.localService || hasLocalProviderAuthMarker(providerConfig?.apiKey));
}
/**
* Classifies the model endpoint locality shared by the idle and first-event
* watchdogs. Ollama `*:cloud` models stay "cloud" even behind a local proxy.
*/
function resolveRuntimeModelLocality(params) {
const baseUrl = params?.model?.baseUrl;
const hostname = typeof baseUrl === "string" ? URL.parse(baseUrl)?.hostname.toLowerCase() : void 0;
const notCloudModel = !isCloudModelRef(params?.model?.id);
return {
isLocalRuntimeModel: Boolean(hostname && isLocalProviderHostname(hostname) && notCloudModel),
isSelfHostedRuntimeModel: notCloudModel && (isSelfHostedProviderId(params?.model?.provider) || Boolean(hostname && (EXPLICIT_LOCAL_HOSTNAMES.has(hostname) || isBareProviderHostname(hostname) && hasConfiguredLocalProviderSignal({
cfg: params?.cfg,
provider: params?.model?.provider
}))))
};
}
function resolveLlmTimeoutBounds(params) {
const runTimeoutMs = asPositiveFiniteNumber(params?.runTimeoutMs);
const agentTimeoutMs = finiteSecondsToTimerSafeMilliseconds(params?.cfg?.agents?.defaults?.timeoutSeconds);
const hasExplicitRunTimeout = runTimeoutMs !== void 0;
const boundedRunTimeoutMs = hasExplicitRunTimeout && runTimeoutMs < 2147e6 ? runTimeoutMs : void 0;
return {
boundedRunTimeoutMs,
agentTimeoutMs,
timeoutBounds: [boundedRunTimeoutMs, hasExplicitRunTimeout ? void 0 : agentTimeoutMs].filter((value) => value !== void 0 && value < 2147e6)
};
}
const clampTimeoutMs = (valueMs) => clampTimerTimeoutMs(valueMs) ?? 1;
function resolveLlmTimeoutMs(phase, params) {
const { boundedRunTimeoutMs, agentTimeoutMs, timeoutBounds } = resolveLlmTimeoutBounds(params);
const { isLocalRuntimeModel, isSelfHostedRuntimeModel } = resolveRuntimeModelLocality(params);
const modelRequestTimeoutMs = asPositiveFiniteNumber(params?.modelRequestTimeoutMs);
if (modelRequestTimeoutMs !== void 0) return clampTimeoutMs(Math.min(modelRequestTimeoutMs, ...timeoutBounds));
if (phase === "first-event") return clampTimeoutMs(Math.min(isLocalRuntimeModel || isSelfHostedRuntimeModel ? LOCAL_LLM_FIRST_EVENT_TIMEOUT_MS : CLOUD_LLM_FIRST_EVENT_TIMEOUT_MS, ...timeoutBounds));
const classIdleTimeoutMs = isSelfHostedRuntimeModel ? SELF_HOSTED_LLM_IDLE_TIMEOUT_MS : DEFAULT_LLM_IDLE_TIMEOUT_MS;
const budgetMs = boundedRunTimeoutMs ?? agentTimeoutMs;
if (budgetMs === void 0) return isLocalRuntimeModel ? 0 : classIdleTimeoutMs;
const boundedCron = boundedRunTimeoutMs !== void 0 && params?.trigger === "cron";
if (isLocalRuntimeModel || boundedCron && isSelfHostedRuntimeModel) return clampTimeoutMs(budgetMs);
return clampTimeoutMs(Math.min(budgetMs, boundedCron ? CRON_LLM_IDLE_TIMEOUT_MS : classIdleTimeoutMs));
}
function resolveLlmIdleTimeoutMs(params) {
return resolveLlmTimeoutMs("idle", params);
}
function resolveLlmFirstEventTimeoutMs(params) {
return resolveLlmTimeoutMs("first-event", params);
}
/**
* Wraps a stream function with idle timeout detection for both stream creation
* and iterator progress. Each successful `next()` resets the timer; a timeout
* aborts the provider request and surfaces the same Error to the caller.
* Creation ends at provider acceptance or stream activity, not when a producer
* returns its stream object. `scope: "creation-only"` leaves local providers
* free of gap policing after that boundary.
*
* When `runId` is provided, run-scoped tool activity can reset the active wait
* and recent activity before stream creation bridges into the first wait.
*/
function streamWithIdleTimeout(baseFn, timeoutMs, onIdleTimeout, opts) {
const guardIterationGaps = opts?.scope !== "creation-only";
const runId = opts?.runId;
const progressTimeoutMs = clampTimeoutMs(timeoutMs * 2);
return (model, context, options) => {
const trackCleanup = captureAsyncWorkTracker();
const streamAbortController = new AbortController();
const firstEventTimeoutMs = clampTimeoutMs(getFirstStreamEventTimeoutMs(options) ?? timeoutMs);
const startTimer = (delay, reject, budget = timeoutMs, reason = "no response from model") => {
const timer = setTimeout(() => {
if (requestDeadline?.reject === reject) requestTimedOut = true;
clearRequestDeadline();
const error = /* @__PURE__ */ new Error(`LLM idle timeout (${Math.floor(budget / 1e3)}s): ${reason}`);
streamAbortController.abort(error);
onIdleTimeout?.(error);
reject(error);
}, delay);
timer.unref?.();
return timer;
};
let requestDeadline;
let iterationWatchdogActive = false;
let requestTimedOut = false;
let requestTimeout = new Promise((_, reject) => {
requestDeadline = {
timer: startTimer(firstEventTimeoutMs, reject, firstEventTimeoutMs),
reject,
accepted: false
};
});
requestTimeout.catch(() => {});
const clearRequestDeadline = () => {
clearTimeout(requestDeadline?.timer);
requestDeadline = void 0;
if (!requestTimedOut) requestTimeout = void 0;
unsubscribeCreationActivity();
};
const finishCreation = () => {
if (!requestDeadline || requestDeadline.accepted) return;
requestDeadline.accepted = true;
unsubscribeCreationActivity();
clearTimeout(requestDeadline.timer);
if (guardIterationGaps && !iterationWatchdogActive) requestDeadline.timer = startTimer(timeoutMs, requestDeadline.reject);
else clearRequestDeadline();
};
const unsubscribeCreationActivity = onLlmRequestActivity(streamAbortController.signal, finishCreation);
const sourceSignal = options?.signal;
const abortFromSourceSignal = () => {
clearRequestDeadline();
streamAbortController.abort(sourceSignal?.reason);
};
if (sourceSignal?.aborted) abortFromSourceSignal();
else sourceSignal?.addEventListener("abort", abortFromSourceSignal, { once: true });
const cleanupSourceSignal = () => {
clearRequestDeadline();
sourceSignal?.removeEventListener("abort", abortFromSourceSignal);
};
const withSourceAbort = (promise) => sourceSignal ? abortable(sourceSignal, promise) : promise;
const withRequestTimeout = (promise) => requestTimeout ? Promise.race([promise, requestTimeout]) : promise;
let maybeStream;
try {
maybeStream = baseFn(model, context, withProviderAcceptanceObserver({
...options,
signal: streamAbortController.signal
}, finishCreation));
} catch (error) {
cleanupSourceSignal();
throw error;
}
const wrapStream = (stream) => {
const producerCompletion = getEventStreamCompletion(stream);
producerCompletion?.then(cleanupSourceSignal, cleanupSourceSignal);
const originalAsyncIterator = stream[Symbol.asyncIterator].bind(stream);
stream[Symbol.asyncIterator] = function() {
const iterator = originalAsyncIterator();
let returning;
const returnIterator = (value) => {
returning ??= trackCleanup(() => Promise.resolve().then(() => iterator.return?.(value) ?? {
done: true,
value: void 0
}));
returning.catch(() => recordAgentCleanupFailure());
return returning;
};
let idleTimer;
let progressTimer;
let rejectIdleTimeout;
let streamFirstArmDone = false;
let settled = false;
const clearTimers = () => {
clearTimeout(idleTimer);
clearTimeout(progressTimer);
idleTimer = progressTimer = void 0;
};
const rejectTimeout = (error) => {
clearTimers();
rejectIdleTimeout?.(error);
};
const armTimer = (progress = true) => {
clearTimeout(idleTimer);
if (!guardIterationGaps || settled || !producerCompletion && !rejectIdleTimeout) {
clearTimers();
return;
}
const activeToolMs = runId ? getLastToolActivityMs(runId) : 0;
const recentActivity = activeToolMs > 0 && Date.now() - activeToolMs < timeoutMs;
const effectiveTimeout = !streamFirstArmDone && recentActivity ? Math.max(1, timeoutMs - Math.max(0, Date.now() - activeToolMs)) : timeoutMs;
streamFirstArmDone = true;
idleTimer = startTimer(effectiveTimeout, rejectTimeout);
iterationWatchdogActive = true;
if (requestDeadline?.accepted) clearRequestDeadline();
if (progress || !progressTimer) {
clearTimeout(progressTimer);
progressTimer = startTimer(progressTimeoutMs, rejectTimeout, progressTimeoutMs, "no model progress");
}
};
const unsubscribeLlmActivity = onLlmRequestActivity(streamAbortController.signal, (progress) => {
armTimer(progress);
if (progress && runId && areDiagnosticsEnabledForProcess()) markDiagnosticRunProgress({
runId,
reason: "model_call:stream_progress"
});
});
const unsubscribeStreamToolActivity = runId ? onToolActivity(runId, armTimer) : void 0;
const settle = () => {
if (settled) return;
settled = true;
rejectIdleTimeout = void 0;
clearTimers();
unsubscribeLlmActivity();
unsubscribeStreamToolActivity?.();
cleanupSourceSignal();
};
producerCompletion?.then(settle, settle);
return createStreamIteratorWrapper({
iterator,
next: async (streamIterator) => {
let pendingNext;
try {
const timeoutPromise = new Promise((_, reject) => {
rejectIdleTimeout = reject;
armTimer(false);
});
pendingNext = streamIterator.next();
const result = await withSourceAbort(withRequestTimeout(Promise.race([pendingNext, timeoutPromise])));
finishCreation();
if (result.done) {
settle();
return result;
}
rejectIdleTimeout = void 0;
armTimer();
return result;
} catch (error) {
settle();
trackCleanup(() => Promise.allSettled([pendingNext, returnIterator()])).catch(() => recordAgentCleanupFailure());
throw error;
}
},
onReturn(_streamIterator, value) {
settle();
return returnIterator(value);
},
onThrow(streamIterator, error) {
settle();
return streamIterator.throw?.(error) ?? Promise.reject(toErrorObject(error, "Non-Error rejection"));
}
});
};
return stream;
};
if (maybeStream && typeof maybeStream === "object" && "then" in maybeStream) {
const source = Promise.resolve(maybeStream);
return withSourceAbort(withRequestTimeout(source)).then(wrapStream, (error) => {
cleanupSourceSignal();
trackCleanup(async () => {
const late = await source.catch(() => void 0);
if (late) await late[Symbol.asyncIterator]().return?.();
}).catch(() => recordAgentCleanupFailure());
throw error;
});
}
return wrapStream(maybeStream);
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-stream.ts
function wrapStreamFnWithCompactionReplayRepair(streamFn, onRejected) {
return async (model, context, options) => {
const trackRepair = captureAsyncWorkTracker();
const repairs = [];
const joinRepairs = async () => {
let joined = 0;
while (joined !== repairs.length) {
joined = repairs.length;
const rejected = (await Promise.allSettled(repairs)).find((result) => result.status === "rejected");
if (rejected?.status === "rejected") throw rejected.reason;
}
};
const replayOptions = options;
const nextOptions = {
...options,
onCompactionRejected: (checkpoint) => {
const repair = trackRepair(() => onRejected(checkpoint));
repairs.push(repair);
repair.catch(() => {});
replayOptions?.onCompactionRejected?.(checkpoint);
}
};
let response;
try {
response = await streamFn(model, context, nextOptions);
} catch (error) {
await joinRepairs();
throw error;
}
return wrapStreamObjectSettlement(response, joinRepairs);
};
}
function installEmbeddedAttemptStreamGuards(input, callbacks) {
const { attempt } = input;
const { agentSession: { activeSession: session, codeModeExecToolNames }, anthropicPayloadLogger, cacheTrace, contextGuards, isOpenAIResponsesApi, sessionManager, state: { systemPromptText }, transcriptPolicy, transport: { effectiveAgentTransport, effectivePromptCacheRetention, streamStrategy, providerTextTransforms } } = input.prepared.sessionRuntime;
const { liveAllowedToolNames, replayAllowedToolNames } = input.prepared.toolCatalog.toolSearchRunPlan;
const installStreamWrapper = (wrapper, ...args) => {
session.agent.streamFn = wrapper(session.agent.streamFn, ...args);
};
const { sessionAgentId } = input.setup;
const { signal: abortSignal } = input.runAbortController;
const operatorAuthority = readRunOperatorAuthority(attempt);
if (operatorAuthority) {
const providerStream = session.agent.streamFn;
session.agent.streamFn = (model, context, options) => {
assertOperatorModelAllowed(operatorAuthority, {
provider: attempt.provider,
model: attempt.modelId
});
return providerStream(model, context, options);
};
}
const repairRejectedReplay = async (kind, checkpoint) => {
try {
const repairParams = {
sessionManager,
sessionFile: attempt.sessionFile,
sessionId: attempt.sessionId,
sessionKey: attempt.sessionKey,
agentId: sessionAgentId
};
await withSessionManagerWrite(sessionManager, async () => {
abortSignal.throwIfAborted();
let repair;
if (kind === "compaction") {
if (!checkpoint) {
log$6.warn(`[session-recovery] unable to repair rejected compaction replay: checkpoint identity unavailable sessionId=${session.sessionId}`);
return;
}
repair = await repairRejectedCompactionReplayInSessionManager({
...repairParams,
checkpoint
});
} else repair = await repairRejectedThinkingReplayInSessionManager(repairParams);
if (repair.repaired) {
callbacks.onRejectedProviderReplayRepaired();
return;
}
log$6.warn(`[session-recovery] provider rejected ${kind} replay but transcript repair made no changes: sessionId=${session.sessionId} reason=${repair.reason ?? "unknown"}`);
});
} catch (error) {
log$6.warn(`[session-recovery] unable to repair rejected ${kind} replay: sessionId=${session.sessionId} error=${String(error)}`);
}
};
const providerPromptState = getProviderPromptState(attempt.runId);
const cacheObserver = createPromptCacheRequestObserver({
sessionId: attempt.sessionId,
sessionKey: attempt.sessionKey,
promptCacheKey: attempt.promptCacheKey,
cacheRetention: effectivePromptCacheRetention,
streamStrategy,
transport: effectiveAgentTransport
}, (observation, snapshot) => {
if (observation.broke) {
const changes = observation.changes?.map((change) => `${change.code}(${change.detail})`).join(", ") ?? "no tracked cache input change";
log$6.warn(`[prompt-cache] cache read dropped ${observation.previousCacheRead} -> ${observation.cacheRead} runId=${attempt.runId} request=${observation.requestIndex} for ${snapshot.provider}/${snapshot.modelId} via ${streamStrategy}; ${changes}; requestGapMs=${observation.requestGapMs ?? "unknown"} promptTokens=${observation.promptTokens ?? "unknown"} providerPrefix=${observation.providerPrefix ?? "unavailable"}`);
}
cacheTrace?.recordStage("cache:result", { options: { ...observation } });
}, (request) => {
cacheTrace?.recordStage("cache:state", { options: {
...request,
previousCacheRead: request.previousCacheRead ?? void 0
} });
});
if (cacheTrace) {
cacheTrace.recordStage("session:loaded", {
messages: session.messages,
system: systemPromptText,
note: "after session create"
});
session.agent.streamFn = cacheTrace.wrapStreamFn(session.agent.streamFn);
}
if (transcriptPolicy.dropThinkingBlocks || transcriptPolicy.dropReasoningFromHistory) installStreamWrapper(wrapStreamFnWithMessageTransform, (messages) => {
const reasoningSanitized = transcriptPolicy.dropReasoningFromHistory ? dropReasoningFromHistory(messages) : messages;
return transcriptPolicy.dropThinkingBlocks ? dropThinkingBlocks(reasoningSanitized) : reasoningSanitized;
});
if (transcriptPolicy.preserveSignatures || transcriptPolicy.dropThinkingBlocks || transcriptPolicy.dropReasoningFromHistory) installStreamWrapper(wrapAnthropicStreamWithRecovery, {
id: session.sessionId,
onRecoveredAnthropicThinking: () => repairRejectedReplay("thinking")
});
const replayToolCallIdSanitizerDecision = {
sanitizeToolCallIds: transcriptPolicy.sanitizeToolCallIds,
toolCallIdMode: transcriptPolicy.toolCallIdMode,
isOpenAIResponsesApi
};
if (shouldApplyReplayToolCallIdSanitizer(replayToolCallIdSanitizerDecision)) {
const mode = replayToolCallIdSanitizerDecision.toolCallIdMode;
installStreamWrapper(wrapStreamFnWithMessageTransform, (messages, model) => sanitizeReplayToolCallIdsForStream({
messages,
mode,
allowedToolNames: replayAllowedToolNames,
preserveNativeAnthropicToolUseIds: transcriptPolicy.preserveNativeAnthropicToolUseIds,
duplicateToolCallIdStyle: transcriptPolicy.duplicateToolCallIdStyle,
preserveReplaySafeThinkingToolCallIds: shouldAllowProviderOwnedThinkingReplay({
modelApi: model.api,
provider: attempt.provider,
policy: transcriptPolicy
}),
repairToolUseResultPairing: transcriptPolicy.repairToolUseResultPairing
}));
}
if (isOpenAIResponsesApi) {
installStreamWrapper(wrapStreamFnWithCompactionReplayRepair, (checkpoint) => repairRejectedReplay("compaction", checkpoint));
installStreamWrapper(wrapStreamFnWithMessageTransform, sanitizeOpenAIResponsesReplayForStream);
}
const innerStreamFn = session.agent.streamFn;
session.agent.streamFn = (model, context, options) => {
if (input.lifecycle.readYieldState().yieldDetected && abortSignal.aborted && isSessionsYieldAbortReason(abortSignal.reason)) return createYieldAbortedResponse(model);
return innerStreamFn(model, context, options);
};
installStreamWrapper(wrapStreamFnSanitizeMalformedToolCalls, replayAllowedToolNames, transcriptPolicy, attempt.provider);
installStreamWrapper(wrapStreamFnPromoteStandaloneTextToolCalls, liveAllowedToolNames);
installStreamWrapper(wrapStreamFnTrimToolCallNames, liveAllowedToolNames, { unknownToolThreshold: 10 });
if (shouldRepairMalformedToolCallArguments({
provider: attempt.provider,
modelApi: attempt.model.api
})) installStreamWrapper(wrapStreamFnRepairMalformedToolCallArguments);
if (resolveToolCallArgumentsEncoding(attempt.model) === "html-entities") installStreamWrapper(createHtmlEntityToolCallArgumentDecodingWrapper);
if (providerTextTransforms?.output?.length) session.agent.streamFn = wrapStreamFnTextTransforms({
streamFn: session.agent.streamFn,
output: providerTextTransforms.output
});
if (anthropicPayloadLogger) session.agent.streamFn = anthropicPayloadLogger.wrapStreamFn(session.agent.streamFn);
installStreamWrapper(wrapStreamFnHandleSensitiveStopReason);
const configuredRunTimeoutMs = resolveAgentTimeoutMs({ cfg: attempt.config });
const resolvedRunTimeoutMs = attempt.runTimeoutOverrideMs ?? (attempt.timeoutMs !== configuredRunTimeoutMs ? attempt.timeoutMs : void 0);
const timeoutOptions = {
cfg: attempt.config,
runTimeoutMs: resolvedRunTimeoutMs,
modelRequestTimeoutMs: attempt.model.requestTimeoutMs,
model: {
baseUrl: attempt.model.baseUrl,
id: attempt.modelId,
provider: attempt.provider
}
};
const idleTimeoutMs = resolveLlmIdleTimeoutMs({
...timeoutOptions,
trigger: attempt.trigger
});
const firstEventTimeoutMs = resolveLlmFirstEventTimeoutMs(timeoutOptions);
if (idleTimeoutMs > 0 || firstEventTimeoutMs > 0) installStreamWrapper(streamWithIdleTimeout, idleTimeoutMs > 0 ? idleTimeoutMs : firstEventTimeoutMs, (error) => callbacks.onIdleTimeout(error), idleTimeoutMs > 0 ? { runId: attempt.runId } : {
runId: attempt.runId,
scope: "creation-only"
});
if (firstEventTimeoutMs > 0) {
const baseStreamFn = session.agent.streamFn;
session.agent.streamFn = (model, context, options) => {
const optionsWithFirstEvent = options;
return baseStreamFn(model, context, {
...options,
firstEventTimeoutMs: optionsWithFirstEvent?.firstEventTimeoutMs ?? firstEventTimeoutMs,
onFirstEventTimeout: optionsWithFirstEvent?.onFirstEventTimeout ?? callbacks.onIdleTimeout
});
};
}
let diagnosticModelCallSeq = 0;
let modelResponseTerminal = false;
installStreamWrapper(wrapStreamFnWithDiagnosticModelCallEvents, {
config: attempt.config,
runId: attempt.runId,
agentId: sessionAgentId,
...attempt.sessionKey && { sessionKey: attempt.sessionKey },
...attempt.sessionId && { sessionId: attempt.sessionId },
provider: attempt.provider,
model: attempt.modelId,
api: attempt.model.api,
transport: effectiveAgentTransport,
requestTimeoutMs: idleTimeoutMs || Math.min(attempt.timeoutMs, 18e5),
...attempt.contextWindowInfo?.tokens ? { contextTokenBudget: attempt.contextWindowInfo.tokens } : {},
...attempt.contextWindowInfo?.source ? { contextWindowSource: attempt.contextWindowInfo.source } : {},
...attempt.contextWindowInfo?.referenceTokens ? { contextWindowReferenceTokens: attempt.contextWindowInfo.referenceTokens } : {},
trace: input.diagnostics.runTrace,
contentCapture: resolveDiagnosticModelContentCapturePolicy(attempt.config),
nextCallId: () => `${attempt.runId}:model:${diagnosticModelCallSeq += 1}`,
ownerGeneration: callbacks.diagnosticOwner.generation,
onSucceeded: contextGuards.recordCacheTouch,
onTerminal: () => {
modelResponseTerminal = true;
},
onStarted: () => {
modelResponseTerminal = false;
attempt.onExecutionPhase?.({
phase: "model_call_started",
provider: attempt.provider,
model: attempt.modelId,
firstModelCallStarted: true
});
},
suppressPluginHooks: attempt.operation === "settled-tool-finalization"
});
if (codeModeExecToolNames?.size) installStreamWrapper(wrapStreamFnCodeModeSource, codeModeExecToolNames);
return {
onModelRequest: cacheObserver.onModelRequest,
onModelUsage: (usage) => {
if (modelResponseTerminal) {
modelResponseTerminal = false;
cacheObserver.onModelUsage(usage, providerPromptState.lastAttempt);
}
},
getPromptCacheObservation: cacheObserver.getObservation
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-timeout-prepare.ts
/** Owns the execution deadline, approval pauses, and one compaction grace. */
function prepareEmbeddedAttemptTimeout(input) {
const { activeSession, attempt, runAbortSignal } = input;
let deadline = { kind: "unlimited" };
let abortTimer;
let abortWarnTimer;
const approvalWait = observeAgentRunApprovalWait(attempt);
const clearTimers = () => {
deadline = { kind: "closed" };
approvalWait.dispose();
runAbortSignal.removeEventListener("abort", clearTimers);
clearTimeout(abortTimer);
clearTimeout(abortWarnTimer);
};
const timeout = {
getRunAbortDeadlineAtMs: () => deadline.kind === "bounded" ? deadline.deadlineAtMs : void 0,
clearTimers
};
if (runAbortSignal.aborted) {
clearTimers();
return timeout;
}
runAbortSignal.addEventListener("abort", clearTimers, { once: true });
const scheduleAbortTimer = (delayMs, compactionGraceUsed) => {
const armed = {
kind: "bounded",
deadlineAtMs: Date.now() + Math.max(1, delayMs),
compactionGraceUsed
};
deadline = armed;
abortTimer = setTimeout(() => {
if (deadline !== armed) return;
const compaction = {
isCompactionPendingOrRetrying: input.compactionState.isCompacting(),
isCompactionInFlight: activeSession.isCompacting
};
if (resolveRunTimeoutDuringCompaction({
...compaction,
graceAlreadyUsed: armed.compactionGraceUsed
}) === "extend") {
if (!input.isProbeSession) log$6.warn(`embedded run timeout reached during compaction; extending deadline: runId=${attempt.runId} sessionId=${attempt.sessionId} extraMs=${input.compactionTimeoutMs}`);
scheduleAbortTimer(input.compactionTimeoutMs, true);
return;
}
clearTimers();
abortWarnTimer = setTimeout(() => {
if (activeSession.isStreaming && !input.isProbeSession) log$6.warn(`embedded run abort still streaming: runId=${attempt.runId} sessionId=${attempt.sessionId}`);
}, 1e4);
if (!input.isProbeSession) log$6.warn(armed.compactionGraceUsed ? `embedded run timeout after compaction grace: runId=${attempt.runId} sessionId=${attempt.sessionId} timeoutMs=${attempt.timeoutMs} compactionGraceMs=${input.compactionTimeoutMs}` : `embedded run timeout: runId=${attempt.runId} sessionId=${attempt.sessionId} timeoutMs=${attempt.timeoutMs}`);
if (shouldFlagCompactionTimeout({
isTimeout: true,
...compaction
})) input.markTimedOutDuringCompaction();
input.markTimedOutByRunBudget();
input.abortRun(true);
}, Math.max(1, delayMs));
attempt.onAttemptDeadlineChanged?.({
kind: "bounded",
deadlineAtMs: armed.deadlineAtMs
});
};
approvalWait.onChange = (pending) => {
if (pending && deadline.kind === "bounded") {
deadline = {
kind: "paused",
remainingMs: Math.max(1, deadline.deadlineAtMs - Date.now()),
compactionGraceUsed: deadline.compactionGraceUsed
};
clearTimeout(abortTimer);
attempt.onAttemptDeadlineChanged?.({ kind: "unlimited" });
} else if (!pending && deadline.kind === "paused") scheduleAbortTimer(deadline.remainingMs, deadline.compactionGraceUsed);
};
if (attempt.timeoutMs >= 2147e6) attempt.onAttemptDeadlineChanged?.({ kind: "unlimited" });
else scheduleAbortTimer(attempt.timeoutMs, false);
if (!runAbortSignal.aborted) attempt.onAttemptTimeoutArmed?.();
return timeout;
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-execution-phase.ts
async function runEmbeddedAttemptExecutionPhase(input) {
const { attempt, state } = input;
const { sessionRuntime, systemPrompt, toolBase } = input.prepared;
const { agentSession: { activeSession, replaySafeTools }, settleTracker: { abortActiveSession, trackPromptSettlePromise } } = sessionRuntime;
const assertActive = resolveAdmittedRunActiveAssertion(attempt.admittedRunContext, input.runAbortController.signal);
if (!assertActive) {
input.runAbortController.signal.throwIfAborted();
throw new Error("embedded attempt requires an active admitted run");
}
activeSession[agentSessionSetContextReplacementHook]((tokensAfter) => {
declarePromptHistoryRewrite({
...attempt,
reason: "compaction"
});
toolBase.skillInstructionDeliveryCache.clear();
attempt.onContextAccountingEvent?.({
kind: "compaction",
tokensAfter
});
}, assertActive);
let repairedRejectedProviderReplay = false;
const diagnosticOwner = createDiagnosticEmbeddedRunOwner({
sessionId: attempt.sessionId,
sessionKey: attempt.sessionKey,
runId: attempt.runId
});
const mergeTerminal = (incoming) => {
state.terminal = mergeAgentRunAttemptTerminal(state.terminal, incoming);
};
const idleTimeoutTriggerRef = {};
const { onModelRequest, onModelUsage, getPromptCacheObservation } = installEmbeddedAttemptStreamGuards(input, {
onRejectedProviderReplayRepaired: () => {
repairedRejectedProviderReplay = true;
},
onIdleTimeout: (error) => idleTimeoutTriggerRef.current?.(error),
diagnosticOwner
});
input.setup.prepStages.mark("stream-setup");
input.setup.emitPrepStageSummary("stream-ready");
let preparedHistory;
try {
preparedHistory = await prepareEmbeddedAttemptHistory(input, assertActive);
} catch (error) {
await cleanupEmbeddedAttemptResources({
flushPendingToolResultsAfterIdle,
session: activeSession,
sessionManager: sessionRuntime.sessionManager,
aborted: attempt.abortSignal?.aborted,
abortSignal: attempt.abortSignal
});
throw error;
}
const isProbeSession = attempt.sessionId?.startsWith("probe-") ?? false;
const queueHandleRef = {};
const abortRun = createEmbeddedAttemptRunAbort({
abortActiveSession,
activeSession,
attempt,
getQueueHandle: () => queueHandleRef.current,
isProbeSession,
log: log$6,
runAbortController: input.runAbortController,
state: input.state
});
input.externalAbortController.setRunAbort(abortRun);
idleTimeoutTriggerRef.current = (error) => {
if (input.runAbortController.signal.aborted) return;
mergeTerminal({
kind: "timeout",
phase: activeSession.isCompacting ? "compaction" : "prompt",
source: "idle"
});
abortRun(true, error);
};
const abortable$1 = (promise) => abortable(input.runAbortController.signal, promise);
const promptActiveSession = (prompt, options) => withOwnedSessionTranscriptWrites(input.sessionLock.ownedTranscriptWriteContext, async () => {
if (input.runAbortController.signal.aborted) return abortable$1(Promise.resolve());
const runPrompt = () => activeSession.prompt(prompt, options);
return abortable$1(trackPromptSettlePromise(input.sessionLock.assertCronRootCurrent ? withGuardedFetchRequestAuthority(input.sessionLock.assertCronRootCurrent, runPrompt) : runPrompt()));
});
const bindTranscriptCallback = (callback) => callback ? bindOwnedSessionTranscriptWrites(input.sessionLock.ownedTranscriptWriteContext, callback) : void 0;
const onBlockReply = bindTranscriptCallback(attempt.onBlockReply);
const onBlockReplyFlush = bindTranscriptCallback(attempt.onBlockReplyFlush);
const preparedStream = prepareEmbeddedAttemptStream({
attempt,
agentSession: sessionRuntime.agentSession,
onModelUsage,
applyPermissionMode: input.lifecycle.applyPermissionMode,
runAbortController: input.runAbortController,
abortRun,
markExternalAbort: () => mergeTerminal({
kind: "aborted",
source: "external"
}),
getRunState: () => {
const terminal = projectAgentRunAttemptTerminal(state.terminal);
return {
aborted: terminal.aborted,
promptError: terminal.promptError,
timedOut: terminal.timedOut,
yieldDetected: input.lifecycle.readYieldState().yieldDetected
};
},
onBlockReply,
onBlockReplyFlush,
runtimeChannel: systemPrompt.runtimeChannel,
hookAgentId: input.setup.sessionAgentId,
diagnosticTrace: input.diagnostics.diagnosticTrace,
nestedToolActivityState: toolBase.nestedToolActivityState,
isReplaySafeTool: (tool) => replaySafeTools.has(tool),
diagnosticOwner,
trajectoryRecorder: sessionRuntime.trajectoryRecorder
});
state.deferredLifecycleOwner = preparedStream.deferredLifecycleOwner;
input.lifecycle.setToolSearchCatalogExecutor(preparedStream.toolSearchCatalogExecutor);
input.externalAbortController.setCompactionState({
isPendingOrRetrying: preparedStream.subscription.isCompacting,
isInFlight: () => activeSession.isCompacting
});
queueHandleRef.current = preparedStream.queueHandle;
const attemptTimeout = prepareEmbeddedAttemptTimeout({
attempt,
activeSession,
runAbortSignal: input.runAbortController.signal,
compactionState: preparedStream.subscription,
compactionTimeoutMs: input.sessionLock.compactionTimeoutMs,
isProbeSession,
abortRun,
markTimedOutDuringCompaction: () => mergeTerminal({
kind: "timeout",
phase: "compaction",
source: "observation"
}),
markTimedOutByRunBudget: () => mergeTerminal({
kind: "timeout",
phase: "prompt",
source: "run_budget"
})
});
const preparedStreamRuntime = {
abortable: abortable$1,
cache: {
onModelRequest,
getObservation: getPromptCacheObservation
},
history: preparedHistory,
isProbeSession,
onBlockReplyFlush,
promptActiveSession,
stream: preparedStream,
timeout: attemptTimeout
};
return await runEmbeddedAttemptSettledPhase({
...input,
preparedStreamRuntime,
getRepairedRejectedProviderReplay: () => repairedRejectedProviderReplay
});
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-preparation.ts
let nextPreparationStart = Promise.resolve();
const MAX_PREPARATION_STARTS_PER_SLICE = 16;
const PREPARATION_SLICE_MS = 8;
let preparationStarts = MAX_PREPARATION_STARTS_PER_SLICE;
let preparationSyncMs = 0;
/** Dispatches attempt stages without letting concurrent starts monopolize the event loop. */
function createEmbeddedAttemptPreparation(options) {
return async (stage, run) => {
nextPreparationStart = nextPreparationStart.then(async () => {
if (preparationStarts >= MAX_PREPARATION_STARTS_PER_SLICE || preparationSyncMs >= PREPARATION_SLICE_MS) {
await setImmediate();
preparationStarts = 0;
preparationSyncMs = 0;
}
preparationStarts++;
});
await nextPreparationStart;
const startedAt = performance.now();
try {
options.assertCurrent();
return measureEmbeddedAgentPreparation(stage, run, { config: options.config });
} finally {
preparationSyncMs += performance.now() - startedAt;
}
};
}
//#endregion
//#region src/agents/queued-file-writer.ts
/** Per-path ordered append writer for diagnostic logs. */
const log$4 = createSubsystemLogger("agents/diagnostic-writer");
/** Returns the cached writer for a path or creates a new ordered append queue. */
function getQueuedFileWriter(writers, filePath) {
const existing = writers.get(filePath);
if (existing) return existing;
const dir = path.dirname(filePath);
let reportedFailure = false;
const reportFailure = (error) => {
if (!reportedFailure) {
reportedFailure = true;
log$4.warn("Diagnostic log write failed; check the configured log path", {
filePath,
error
});
}
};
let queue = fs$1.mkdir(dir, {
recursive: true,
mode: 448
}).catch(reportFailure);
const writer = {
filePath,
write: (line) => {
queue = queue.then(() => appendRegularFile({
filePath,
content: line,
rejectSymlinkParents: true
})).catch(reportFailure);
return "queued";
},
flush: async () => {
await queue;
}
};
writers.set(filePath, writer);
return writer;
}
//#endregion
//#region src/agents/trace-base.ts
/** Build a trace base object while preserving undefined optional fields. */
function buildAgentTraceBase(params) {
return {
runId: params.runId,
sessionId: params.sessionId,
sessionKey: params.sessionKey,
provider: params.provider,
modelId: params.modelId,
modelApi: params.modelApi,
workspaceDir: params.workspaceDir
};
}
//#endregion
//#region src/agents/anthropic-payload-log.ts
/**
* Optional Anthropic request/usage JSONL diagnostics.
* Redacts payload content before writing and stores digests for correlation
* without persisting raw secret-bearing request bodies.
*/
const writers$1 = /* @__PURE__ */ new Map();
const log$3 = createSubsystemLogger("agent/anthropic-payload");
function formatError(error) {
const message = error instanceof Error ? error.message : typeof error === "string" ? error : void 0;
if (message !== void 0) {
const redacted = redactAgentDiagnosticPayload(message);
return typeof redacted === "string" ? redacted : message;
}
if (typeof error === "number" || typeof error === "boolean" || typeof error === "bigint") return String(error);
if (error && typeof error === "object") return safeJsonStringify(redactAgentDiagnosticPayload(error)) ?? "unknown error";
}
function digest$1(value) {
const serialized = safeJsonStringify(value);
if (!serialized) return;
return crypto$1.createHash("sha256").update(serialized).digest("hex");
}
function findLastAssistantUsage(messages) {
for (let i = messages.length - 1; i >= 0; i -= 1) {
const msg = messages[i];
if (msg?.role === "assistant" && msg.usage && typeof msg.usage === "object") return msg.usage;
}
return null;
}
function createAnthropicPayloadLogger(params) {
const env = params.env ?? process.env;
const enabled = parseBooleanValue(env.OPENCLAW_ANTHROPIC_PAYLOAD_LOG) ?? false;
const fileOverride = env.OPENCLAW_ANTHROPIC_PAYLOAD_LOG_FILE?.trim();
const filePath = fileOverride ? resolveUserPath(fileOverride) : path.join(resolveStateDir(env), "logs", "anthropic-payload.jsonl");
if (!enabled || isIncognitoSessionKey(params.sessionKey)) return null;
const writer = params.writer ?? getQueuedFileWriter(writers$1, filePath);
const base = buildAgentTraceBase(params);
const record = (event) => {
const line = safeJsonStringify(event);
if (!line) return;
writer.write(`${line}\n`);
};
const wrapStreamFn = (streamFn) => (model, context, options) => {
if (model?.api !== "anthropic-messages") return streamFn(model, context, options);
const nextOnPayload = (payload) => {
const redactedPayload = redactAgentDiagnosticPayload(payload);
record({
...base,
ts: (/* @__PURE__ */ new Date()).toISOString(),
stage: "request",
payload: redactedPayload,
payloadDigest: digest$1(redactedPayload)
});
return options?.onPayload?.(payload, model);
};
return streamFn(model, context, {
...options,
onPayload: nextOnPayload
});
};
const recordUsage = (messages, error) => {
const usage = findLastAssistantUsage(messages);
const errorMessage = formatError(error);
if (!usage && !errorMessage) return;
record({
...base,
ts: (/* @__PURE__ */ new Date()).toISOString(),
stage: "usage",
...usage ? { usage: redactAgentDiagnosticPayload(usage) } : {},
error: errorMessage
});
if (usage) log$3.info("anthropic usage", {
runId: params.runId,
sessionId: params.sessionId,
usage
});
};
log$3.info("anthropic payload logger enabled", { filePath: writer.filePath });
return {
wrapStreamFn,
recordUsage
};
}
//#endregion
//#region src/agents/cache-trace.ts
const writers = /* @__PURE__ */ new Map();
function digest(value) {
const serialized = stableStringify(value, sanitizeSurrogates);
return crypto$1.createHash("sha256").update(serialized).digest("hex");
}
function summarizeMessages(messages) {
const messageFingerprints = messages.map(digest);
return {
messageCount: messages.length,
messageRoles: messages.map((msg) => msg.role),
messageFingerprints,
messagesDigest: digest(messageFingerprints.join("|"))
};
}
function createCacheTrace(params) {
const env = params.env ?? process.env;
const enabled = parseBooleanValue(env.OPENCLAW_CACHE_TRACE) ?? params.cfg?.diagnostics?.cacheTrace?.enabled ?? false;
const fileOverride = env.OPENCLAW_CACHE_TRACE_FILE?.trim();
const filePath = fileOverride ? resolveUserPath(fileOverride) : path.join(resolveStateDir(env), "logs", "cache-trace.jsonl");
const includeMessages = parseBooleanValue(env.OPENCLAW_CACHE_TRACE_MESSAGES) ?? true;
const includePrompt = parseBooleanValue(env.OPENCLAW_CACHE_TRACE_PROMPT) ?? true;
const includeSystem = parseBooleanValue(env.OPENCLAW_CACHE_TRACE_SYSTEM) ?? true;
if (!enabled || isIncognitoSessionKey(params.sessionKey)) return null;
const writer = params.writer ?? getQueuedFileWriter(writers, filePath);
let seq = 0;
const base = buildAgentTraceBase(params);
const recordStage = (stage, payload = {}) => {
const event = {
...base,
ts: (/* @__PURE__ */ new Date()).toISOString(),
seq: seq += 1,
stage
};
if (payload.prompt !== void 0 && includePrompt) event.prompt = redactAgentDiagnosticPayload(payload.prompt);
if (payload.system !== void 0 && includeSystem) {
event.system = redactAgentDiagnosticPayload(payload.system);
event.systemDigest = digest(payload.system);
}
if (payload.options) event.options = redactAgentDiagnosticPayload(payload.options);
if (payload.model) event.model = redactAgentDiagnosticPayload(payload.model);
const messages = payload.messages;
if (Array.isArray(messages)) {
Object.assign(event, summarizeMessages(messages));
if (includeMessages) event.messages = redactAgentDiagnosticPayload(messages);
}
if (payload.note) event.note = redactAgentDiagnosticPayload(payload.note);
if (payload.error) event.error = redactAgentDiagnosticPayload(payload.error);
const line = safeJsonStringify(event);
if (!line) return;
writer.write(`${line}\n`);
};
const wrapStreamFn = (streamFn) => (model, context, options) => {
const traceContext = context;
recordStage("stream:context", {
model: {
id: model?.id,
provider: model?.provider,
api: model?.api
},
system: traceContext.systemPrompt ?? traceContext.system,
messages: traceContext.messages ?? [],
options: options ?? {}
});
return streamFn(model, context, options);
};
return {
filePath,
recordStage,
wrapStreamFn
};
}
//#endregion
//#region src/agents/agent-hooks/compaction-safeguard-runtime.ts
const registry = createSessionManagerRuntimeRegistry();
const setCompactionSafeguardRuntime = registry.set;
const getCompactionSafeguardRuntime = registry.get;
/** Records cancellation atomically; intentional declines carry no provider error. */
function setCompactionSafeguardCancellation(sessionManager, reason, error) {
const current = getCompactionSafeguardRuntime(sessionManager);
const trimmed = reason?.trim();
if (!current && !trimmed) return;
const next = { ...current };
if (trimmed) next.cancellation = {
reason: trimmed,
...error !== void 0 ? { error } : {}
};
else delete next.cancellation;
setCompactionSafeguardRuntime(sessionManager, Object.keys(next).length > 0 ? next : null);
}
/** Consumes this attempt's cancellation without clearing session configuration. */
function consumeCompactionSafeguardCancellation(sessionManager) {
const cancellation = getCompactionSafeguardRuntime(sessionManager)?.cancellation;
if (cancellation) setCompactionSafeguardCancellation(sessionManager, void 0);
return cancellation ?? null;
}
//#endregion
//#region src/auto-reply/reply/post-compaction-context.ts
const log$2 = createSubsystemLogger("post-compaction-context");
const MAX_CONTEXT_CHARS = 1800;
const DEFAULT_POST_COMPACTION_SECTIONS = ["Session Startup", "Red Lines"];
const LEGACY_POST_COMPACTION_SECTIONS = ["Every Session", "Safety"];
function matchesSectionSet(sectionNames, expectedSections) {
if (sectionNames.length !== expectedSections.length) return false;
const actual = sectionNames.map(normalizeLowercaseStringOrEmpty).toSorted();
const expected = expectedSections.map(normalizeLowercaseStringOrEmpty).toSorted();
return actual.every((name, index) => name === expected[index]);
}
async function readPostCompactionContext(workspaceDir, options) {
const { cfg, agentId, nowMs } = options ?? {};
const configuredSections = cfg?.agents?.defaults?.compaction?.postCompactionSections;
if (!Array.isArray(configuredSections) || configuredSections.length === 0) return null;
const agentsPath = path.join(workspaceDir, "AGENTS.md");
try {
let content;
const access = getAgentWorkspaceAccess(workspaceDir);
if (access) {
const data = await access.bridge.readFile({
filePath: "AGENTS.md",
maxBytes: MAX_WORKSPACE_BOOTSTRAP_FILE_BYTES
});
if (data.length > 2097152) return null;
content = new TextDecoder("utf-8", { fatal: true }).decode(data);
} else {
const opened = await openRootFile({
absolutePath: agentsPath,
rootPath: workspaceDir,
boundaryLabel: "workspace root"
});
if (!opened.ok) return null;
try {
content = await readWorkspaceBootstrapFile(opened.fd);
} catch (err) {
if (err instanceof RangeError) {
log$2.warn(`Ignoring oversized AGENTS.md ${agentsPath}: file exceeds the ${MAX_WORKSPACE_BOOTSTRAP_FILE_BYTES}-byte limit`);
return null;
}
throw err;
} finally {
fs.closeSync(opened.fd);
}
}
const foundSectionNames = [];
let sections = extractSections(content, configuredSections, foundSectionNames);
const isDefaultSections = matchesSectionSet(configuredSections, DEFAULT_POST_COMPACTION_SECTIONS);
if (sections.length === 0 && isDefaultSections) sections = extractSections(content, LEGACY_POST_COMPACTION_SECTIONS, foundSectionNames);
if (sections.length === 0) return null;
const resolvedNowMs = nowMs ?? Date.now();
const timezone = resolveUserTimezone(cfg?.agents?.defaults?.userTimezone);
const dateStamp = formatDateStamp(resolvedNowMs, timezone);
const maxContextChars = resolveAgentContextLimits(cfg, agentId)?.postCompactionMaxChars ?? MAX_CONTEXT_CHARS;
const { timeLine } = resolveCronStyleNow(cfg ?? {}, resolvedNowMs);
const combined = sections.join("\n\n").replaceAll("YYYY-MM-DD", dateStamp);
const safeContent = combined.length > maxContextChars ? truncateUtf16Safe(combined, maxContextChars) + "\n...[truncated]..." : combined;
return `[Post-compaction context refresh]\n\n${isDefaultSections ? "Session was just compacted. The conversation summary above is a hint, NOT a substitute for your startup sequence. Run your Session Startup sequence - read the required files before responding to the user." : `Session was just compacted. The conversation summary above is a hint, NOT a substitute for your full startup sequence. Re-read the sections injected below (${foundSectionNames.join(", ")}) and follow your configured startup procedure before responding to the user.`}\n\n${isDefaultSections ? "Critical rules from AGENTS.md:" : `Injected sections from AGENTS.md (${foundSectionNames.join(", ")}):`}\n\n${safeContent}\n\n${timeLine}`;
} catch {
return null;
}
}
/**
* Extract named sections from markdown content.
* Matches H2 (##) or H3 (###) headings case-insensitively.
* Skips content inside fenced code blocks.
* Captures until the next heading of same or higher level (including H1), or end of string.
*/
function extractSections(content, sectionNames, foundNames) {
const results = [];
const fenceSpans = parseFenceSpans(content);
const headings = [];
let lineStart = 0;
for (const line of content.split("\n")) {
const match = findFenceSpanAt(fenceSpans, lineStart) ? null : line.match(/^(#{1,3})\s+(.+?)\s*$/);
if (match) headings.push({
start: lineStart,
level: expectDefined(match[1], "heading match capture group 1").length,
name: normalizeLowercaseStringOrEmpty(match[2])
});
lineStart += line.length + 1;
}
for (const name of sectionNames) {
const normalizedName = normalizeLowercaseStringOrEmpty(name);
const start = headings.find((heading) => heading.level >= 2 && heading.name === normalizedName);
if (!start) continue;
const end = headings.find((heading) => heading.start > start.start && heading.level <= start.level);
results.push(content.slice(start.start, end?.start).trim());
foundNames?.push(name);
}
return results;
}
async function appendPostCompactionRefreshPrompt(params) {
const refreshPrompt = await readPostCompactionContext(params.followupRun.run.workspaceDir, {
cfg: params.cfg,
agentId: params.followupRun.run.agentId
});
if (!refreshPrompt) return;
const existingPrompt = normalizeOptionalString(params.followupRun.run.extraSystemPrompt);
if (existingPrompt?.includes(refreshPrompt)) return;
params.followupRun.run.extraSystemPrompt = [existingPrompt, refreshPrompt].filter(Boolean).join("\n\n");
}
//#endregion
//#region src/plugins/compaction-provider.ts
function getCompactionProvider(id) {
const registry = requireActivePluginRegistry();
const registration = registry.compactionProviders.find((entry) => entry.provider.id === id);
if (!registration) return;
const record = registry.plugins.find((entry) => entry.id === registration.ownerPluginId);
return (record && getPluginInstance(record))?.wrap(registration.provider) ?? registration.provider;
}
//#endregion
//#region src/agents/compaction-real-conversation.ts
/**
* Classifies transcript messages that contain real user-visible conversation
* for compaction and history pruning.
*/
const TOOL_RESULT_REAL_CONVERSATION_LOOKBACK = 20;
const NON_CONVERSATION_BLOCK_TYPES = /* @__PURE__ */ new Set(["thinking", "reasoning"]);
const CONVERSATION_ANCHOR_ROLES = /* @__PURE__ */ new Set([
"user",
"custom",
"bashExecution",
"branchSummary",
"compactionSummary"
]);
function hasMeaningfulText(text) {
const trimmed = text.trim();
if (!trimmed || isSilentReplyText(trimmed)) return false;
const heartbeat = stripHeartbeatToken(trimmed, { mode: "message" });
return !heartbeat.didStrip || heartbeat.text.trim().length > 0;
}
/** Returns whether a message has content worth preserving as conversation. */
function hasMeaningfulConversationContent(message) {
if ("excludeFromContext" in message && message.excludeFromContext === true) return false;
if (message.role === "custom") {
const custom = message;
return custom.display !== false && hasMeaningfulMessageContent(custom.content);
}
if (message.role === "bashExecution") return hasMeaningfulText(`${typeof message.command === "string" ? message.command : ""}\n${typeof message.output === "string" ? message.output : ""}`);
if (message.role === "branchSummary" || message.role === "compactionSummary") {
const summary = message.summary;
return typeof summary === "string" && hasMeaningfulText(summary);
}
const content = message.content;
return hasMeaningfulMessageContent(content);
}
function hasMeaningfulMessageContent(content) {
if (typeof content === "string") return hasMeaningfulText(content);
if (!Array.isArray(content)) return false;
let sawMeaningfulNonTextBlock = false;
for (const block of content) {
if (!block || typeof block !== "object") continue;
const { type, text } = block;
if (type === "text") {
if (typeof text === "string" && hasMeaningfulText(text)) return true;
} else if (!isToolCallBlockType(type) && !NON_CONVERSATION_BLOCK_TYPES.has(String(type))) sawMeaningfulNonTextBlock = true;
}
return sawMeaningfulNonTextBlock;
}
function isToolResultConversationAnchor(message) {
return CONVERSATION_ANCHOR_ROLES.has(message.role) && hasMeaningfulConversationContent(message);
}
/** Returns whether a transcript message should count as real conversation. */
function isRealConversationMessage(message, messages, index) {
if (message.role === "assistant" || CONVERSATION_ANCHOR_ROLES.has(message.role)) return hasMeaningfulConversationContent(message);
if (message.role !== "toolResult") return false;
const start = Math.max(0, index - TOOL_RESULT_REAL_CONVERSATION_LOOKBACK);
for (let i = index - 1; i >= start; i -= 1) {
const candidate = messages[i];
if (candidate && isToolResultConversationAnchor(candidate)) return true;
}
return false;
}
/** Classifies an ordered transcript without retaining earlier pages. */
function createRealConversationClassifier() {
let anchorLookback = 0;
return (message) => {
const real = message.role === "toolResult" ? anchorLookback > 0 : isRealConversationMessage(message, [], 0);
anchorLookback = isToolResultConversationAnchor(message) ? TOOL_RESULT_REAL_CONVERSATION_LOOKBACK : Math.max(0, anchorLookback - 1);
return real;
};
}
//#endregion
//#region src/agents/compaction.ts
const log$1 = createSubsystemLogger("compaction");
const DEFAULT_SUMMARY_FALLBACK = "No prior history.";
const IDENTIFIER_PRESERVATION_INSTRUCTIONS = "Preserve all opaque identifiers exactly as written (no shortening or reconstruction), including UUIDs, hashes, IDs, hostnames, IPs, ports, URLs, and file names.";
function buildCompactionSummarizationInstructions(customInstructions, instructions) {
const custom = customInstructions?.trim();
const identifierPreservation = instructions?.identifierPolicy === "off" ? void 0 : instructions?.identifierPolicy === "custom" ? instructions.identifierInstructions?.trim() || IDENTIFIER_PRESERVATION_INSTRUCTIONS : IDENTIFIER_PRESERVATION_INSTRUCTIONS;
if (!custom) return identifierPreservation;
return identifierPreservation ? `${identifierPreservation}\n\nAdditional focus:\n${custom}` : `Additional focus:\n${custom}`;
}
/**
* Summarizes compaction history in one model request. The request serializer
* bounds its input (see serializeConversationWithinBudget), so latency and cost
* do not grow with the session or the context window.
*/
async function summarizeCompactionHistory(params) {
const messages = sanitizeCompactionMessages(params.messages);
if (messages.length === 0) return params.previousSummary ?? DEFAULT_SUMMARY_FALLBACK;
const instructions = buildCompactionSummarizationInstructions(params.customInstructions, params.summarizationInstructions);
try {
return await retryAsync(() => generateSummary(messages, params.model, params.reserveTokens, params.apiKey, params.headers, params.signal, instructions, params.previousSummary, params.thinkingLevel, params.streamFn, params.usageSink, params.summaryPrompt), {
attempts: 3,
minDelayMs: 500,
maxDelayMs: 5e3,
jitter: .2,
label: "compaction/generateSummary",
sleep: (ms) => sleepWithAbort$1(ms, params.signal),
shouldRetry: (err) => !params.signal.aborted && !(err instanceof SummaryOutputBudgetError) && (isAbortError(err) || !isTimeoutError(err))
});
} catch (err) {
if (params.signal.aborted) throw err;
log$1.warn(`Summarization failed: ${formatErrorMessage(err)}`);
throw new CompactionError("summarization_failed", `Summarization failed for ${messages.length} messages: ${formatErrorMessage(err)}`, err instanceof Error ? err : void 0);
}
}
//#endregion
//#region src/agents/agent-hooks/compaction-message-format.ts
/** Summarize non-text blocks so preserved context records their presence without payload data. */
function formatCompactionNonTextPlaceholder(content) {
if (content == null || typeof content === "string") return null;
if (!Array.isArray(content)) return "[non-text content]";
const typeCounts = /* @__PURE__ */ new Map();
for (const block of content) {
if (!block || typeof block !== "object") continue;
const typeRaw = Reflect.get(block, "type");
const type = typeof typeRaw === "string" && typeRaw.trim().length > 0 ? typeRaw : "unknown";
if (type !== "text") typeCounts.set(type, (typeCounts.get(type) ?? 0) + 1);
}
return typeCounts.size > 0 ? `[non-text content: ${Array.from(typeCounts, ([type, count]) => count > 1 ? `${type} x${count}` : type).join(", ")}]` : null;
}
//#endregion
//#region src/agents/agent-hooks/compaction-safeguard-quality.ts
/** Quality contract, fallback, and audit helpers for compaction safeguard summaries. */
const MAX_EXTRACTED_IDENTIFIERS = 12;
const MAX_UNTRUSTED_INSTRUCTION_CHARS = 4e3;
const MAX_ASK_OVERLAP_TOKENS = 12;
const MIN_ASK_OVERLAP_TOKENS_FOR_DOUBLE_MATCH = 3;
const REQUIRED_SUMMARY_SECTIONS = [
"## Decisions",
"## Open TODOs",
"## Constraints/Rules",
"## Pending user asks",
"## Exact identifiers"
];
const QUALITY_PROTECTED_SECTION_START = 3;
const PENDING_ASK_SECTION_INDEX = 3;
const EXACT_IDENTIFIERS_SECTION_INDEX = 4;
const MAX_PROTECTED_SECTION_CONTENT_SHARE = .25;
const LATEST_USER_REQUEST_CONTEXT_LABEL = "Latest user request context:";
const STRICT_EXACT_IDENTIFIERS_INSTRUCTION = "For ## Exact identifiers, preserve literal values exactly as seen (IDs, URLs, file paths, ports, hashes, dates, times).";
const POLICY_OFF_EXACT_IDENTIFIERS_INSTRUCTION = "For ## Exact identifiers, include identifiers only when needed for continuity; do not enforce literal-preservation rules.";
/** Demotes canonical headings when a summary is embedded as supporting context. */
function nestRequiredSummaryHeadings(text) {
return text.replace(/^##[ \t]+\S.*$/gmu, (heading) => REQUIRED_SUMMARY_SECTIONS.some((required) => required === heading.trim()) ? heading.replace("##", "###") : heading);
}
/** Wraps operator-provided compaction instruction text as untrusted prompt data. */
function wrapUntrustedInstructionBlock(label, text) {
return wrapUntrustedPromptDataBlock({
label,
text,
maxChars: MAX_UNTRUSTED_INSTRUCTION_CHARS
});
}
function resolveExactIdentifierSectionInstruction(summarizationInstructions) {
const policy = summarizationInstructions?.identifierPolicy ?? "strict";
if (policy === "off") return POLICY_OFF_EXACT_IDENTIFIERS_INSTRUCTION;
const custom = policy === "custom" ? summarizationInstructions?.identifierInstructions?.trim() : void 0;
if (custom) return wrapUntrustedInstructionBlock("For ## Exact identifiers, apply this operator-defined policy text", custom) || STRICT_EXACT_IDENTIFIERS_INSTRUCTION;
return STRICT_EXACT_IDENTIFIERS_INSTRUCTION;
}
/** Build the required structured summary instructions for compaction. */
function buildCompactionStructureInstructions(customInstructions, summarizationInstructions, latestUnresolvedUserRequest) {
const identifierSectionInstruction = resolveExactIdentifierSectionInstruction(summarizationInstructions);
const sectionsTemplate = [
"Produce a compact, factual summary with these exact section headings:",
...REQUIRED_SUMMARY_SECTIONS,
identifierSectionInstruction,
"Do not omit unresolved asks from the user.",
"Record completed requests outside ## Pending user asks; list only unresolved user requests there.",
"Use tool results to update task status: a check that ran and returned a failing result is completed, not an open TODO. Record its result under ## Decisions and keep only the remaining remediation in ## Open TODOs (e.g. failing tests -> fix the failures, not run the same tests again).",
"Treat prior summaries as drafts to update: reconcile them with all supplied messages, including preserved turns and split-turn progress, and remove stale duplicate detail.",
"Apply explicit corrections and observed results in the main sections. Preserve unaffected facts; retain superseded values only as clearly labeled history, never as competing current decisions.",
"A factual correction is not a pending task unless the user requested work that remains undone. Distinguish requested, attempted, completed, and failed actions without inferring overall success from a completed check.",
"Before returning, check that all sections agree on current facts and status. Appending a correction in context is insufficient if the main summary still asserts the old state. If evidence does not resolve a conflict, record the uncertainty."
].join("\n");
const latestRequestBlock = latestUnresolvedUserRequest ? wrapUntrustedInstructionBlock("Latest unresolved user request", latestUnresolvedUserRequest) : "";
const latestRequestInstruction = latestRequestBlock ? [
"Make the exact request below the first item in ## Pending user asks.",
"Its run owner will resume it after compaction, so summary prose cannot mark it complete.",
latestRequestBlock
].join("\n") : "";
const custom = customInstructions?.trim();
return [
sectionsTemplate,
latestRequestInstruction,
custom && wrapUntrustedInstructionBlock("Additional context from /compact", custom)
].filter(Boolean).join("\n\n");
}
function normalizedSummaryLines(summary) {
return summary.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0);
}
function parseRequiredSummarySectionContents(summary) {
const contents = REQUIRED_SUMMARY_SECTIONS.map(() => new Array());
const preamble = [];
let sectionIndex = -1;
for (const line of summary.split(/\r?\n/u)) {
const nextHeading = REQUIRED_SUMMARY_SECTIONS[sectionIndex + 1];
if (nextHeading && line.trim() === nextHeading) {
sectionIndex += 1;
continue;
}
(sectionIndex < 0 ? preamble : contents[sectionIndex])?.push(line);
}
if (sectionIndex !== REQUIRED_SUMMARY_SECTIONS.length - 1) return null;
contents[0]?.unshift(...preamble);
return contents.map((lines) => lines.join("\n").trim());
}
function extractPendingAskSection(summary) {
return summary.split(/^## Pending user asks[ \t]*$/mu, 2)[1]?.split(/^##[ \t]+\S.*$/mu, 1)[0]?.trim() ?? "";
}
function formatLatestUserRequestContext(request) {
return `${LATEST_USER_REQUEST_CONTEXT_LABEL} ${JSON.stringify(request)}`;
}
function extractLeadingPendingAsk(summary) {
return normalizedSummaryLines(extractPendingAskSection(summary))[0] ?? "";
}
function isEmptyPendingAsk(value) {
return /^(?:none|none captured|no pending asks)[.!]?$/iu.test(value);
}
/**
* Plan truncation that keeps the audit facts and lets everything else shrink.
* Only the headings, the bounded latest-ask context, and the audited source
* identifiers are untrimmable. Model-written section text — including the
* "## Exact identifiers" list — is optional content; protecting it verbatim let
* a re-distilled identifier dump grow past the whole artifact budget while the
* real sections were starved to empty headings.
*/
function createSummaryQualityRetentionPlan(summary, truncatedMarker, params) {
const requiredAskContext = params.requiredAskContext?.trim() ?? "";
const latestUnresolvedUserRequest = params.latestUnresolvedUserRequest?.trim() ?? "";
const bodyHasLatestAsk = hasAskOverlap(params.auditSummary ?? summary, params.latestAsk);
const requiredContextBlock = !latestUnresolvedUserRequest && (bodyHasLatestAsk || params.latestAskInRetainedTurn) && requiredAskContext ? `## Latest user request context\n${JSON.stringify(requiredAskContext)}` : "";
const parsedSummary = requiredContextBlock && summary.startsWith(`${requiredContextBlock}\n\n`) ? summary.slice(requiredContextBlock.length + 2) : summary;
const contents = parseRequiredSummarySectionContents(parsedSummary);
if (!contents) return null;
const auditedIdentifiers = (params.identifierPolicy ?? "strict") === "strict" ? params.identifiers : [];
const marker = truncatedMarker.trim();
const pendingAsk = contents[PENDING_ASK_SECTION_INDEX] ?? "";
const protectedAskContext = latestUnresolvedUserRequest ? formatLatestUserRequestContext(latestUnresolvedUserRequest) : !params.latestAskInRetainedTurn && requiredAskContext && (!bodyHasLatestAsk || hasAskOverlap(pendingAsk, params.latestAsk) && !pendingAsk.includes(requiredAskContext)) ? `${LATEST_USER_REQUEST_CONTEXT_LABEL}\n${JSON.stringify(requiredAskContext)}` : "";
const protectedTails = REQUIRED_SUMMARY_SECTIONS.map((_, index) => index === PENDING_ASK_SECTION_INDEX ? protectedAskContext : index === EXACT_IDENTIFIERS_SECTION_INDEX ? auditedIdentifiers.join("\n") : "");
const bodyHasIdentifiers = auditedIdentifiers.every((identifier) => summaryIncludesIdentifier(summary, identifier));
const bodyHasRequiredAskContext = latestUnresolvedUserRequest ? extractLeadingPendingAsk(parsedSummary) === protectedAskContext : !requiredAskContext ? true : requiredContextBlock ? summary.startsWith(requiredContextBlock) : contents[PENDING_ASK_SECTION_INDEX]?.includes(protectedAskContext);
const renderSections = (sectionContents) => REQUIRED_SUMMARY_SECTIONS.map((heading, index) => {
const content = sectionContents[index];
return content ? `${heading}\n${content}` : heading;
});
const joinSectionContent = (index, optional) => {
const tail = protectedTails[index];
if (!tail) return optional;
if (index === EXACT_IDENTIFIERS_SECTION_INDEX) return [optional, ...auditedIdentifiers.filter((identifier) => !summaryIncludesIdentifier(optional, identifier))].filter(Boolean).join("\n");
const leading = normalizedSummaryLines(optional)[0] ?? "";
if (leading === tail) return optional;
return latestUnresolvedUserRequest ? [tail, isEmptyPendingAsk(leading) ? "" : optional].filter(Boolean).join("\n") : [isEmptyPendingAsk(optional) ? "" : optional, tail].filter(Boolean).join("\n");
};
const minimumBlocks = REQUIRED_SUMMARY_SECTIONS.map((heading, index) => `${heading}\n\n${protectedTails[index] ?? ""}`);
const minimumSummary = [
...requiredContextBlock ? [requiredContextBlock] : [],
...minimumBlocks.slice(0, QUALITY_PROTECTED_SECTION_START),
marker,
...minimumBlocks.slice(QUALITY_PROTECTED_SECTION_START)
].join("\n\n");
const protectedCapFor = (maxChars) => Math.floor(Math.max(0, maxChars - minimumSummary.length) * MAX_PROTECTED_SECTION_CONTENT_SHARE);
const protectedWithinCap = (maxChars) => contents.slice(QUALITY_PROTECTED_SECTION_START).every((content) => content.length <= protectedCapFor(maxChars));
return {
minimumChars: minimumSummary.length,
needsRebuild: (maxChars) => !latestUnresolvedUserRequest && !bodyHasLatestAsk || !bodyHasRequiredAskContext || !bodyHasIdentifiers || !protectedWithinCap(maxChars),
render(maxChars) {
if (summary.length <= maxChars && bodyHasRequiredAskContext && bodyHasIdentifiers && protectedWithinCap(maxChars)) return {
text: summary,
trimmed: false
};
if (maxChars < minimumSummary.length) return null;
const contentBudget = maxChars - minimumSummary.length;
const protectedCap = protectedCapFor(maxChars);
const allocations = contents.map((content, index) => index >= QUALITY_PROTECTED_SECTION_START ? Math.min(content.length, protectedCap) : 0);
const optionalBudget = Math.max(0, contentBudget - allocations.reduce((total, chars) => total + chars, 0));
const optionalContents = contents.slice(0, QUALITY_PROTECTED_SECTION_START);
const optionalTotal = optionalContents.reduce((total, content) => total + content.length, 0);
for (const [index, content] of optionalContents.entries()) allocations[index] = optionalTotal > 0 ? Math.floor(optionalBudget * content.length / optionalTotal) : 0;
let remainder = optionalBudget - allocations.slice(0, QUALITY_PROTECTED_SECTION_START).reduce((total, chars) => total + chars, 0);
for (const [index, content] of optionalContents.entries()) {
const allocation = allocations[index] ?? 0;
const extra = Math.min(remainder, Math.max(0, content.length - allocation));
allocations[index] = allocation + extra;
remainder -= extra;
}
const trimmed = contents.some((content, index) => content.length > (allocations[index] ?? 0));
const sectionContents = contents.map((content, index) => joinSectionContent(index, truncateUtf16Safe(content, allocations[index] ?? 0)));
const blocks = renderSections(sectionContents);
return {
text: [
...requiredContextBlock ? [requiredContextBlock] : [],
...blocks.slice(0, QUALITY_PROTECTED_SECTION_START),
...trimmed ? [marker] : [],
...blocks.slice(QUALITY_PROTECTED_SECTION_START)
].join("\n\n"),
trimmed
};
}
};
}
/** Return a structured fallback summary when model output is missing/invalid. */
function buildStructuredFallbackSummary(previousSummary) {
const trimmedPreviousSummary = previousSummary?.trim() ?? "";
if (trimmedPreviousSummary && parseRequiredSummarySectionContents(trimmedPreviousSummary)) return trimmedPreviousSummary;
const values = [
trimmedPreviousSummary || "No prior history.",
"None.",
"None.",
"None.",
"None captured."
];
return REQUIRED_SUMMARY_SECTIONS.map((heading, index) => `${heading}\n${values[index]}`).join("\n\n");
}
/** Appends a bounded post-compaction section to an existing summary. */
function appendSummarySection(summary, section) {
if (!section) return summary;
if (!summary.trim()) return section.trimStart();
return `${summary}${section}`;
}
function sanitizeExtractedIdentifier(value) {
return value.trim().replace(/^[("'`[{<]+/, "").replace(/[)\]"'`,;:.!?<>]+$/, "");
}
function isPureHexIdentifier(value) {
return /^[A-Fa-f0-9]{8,}$/.test(value);
}
function normalizeOpaqueIdentifier(value) {
return isPureHexIdentifier(value) ? value.toUpperCase() : value;
}
function summaryIncludesIdentifier(summary, identifier) {
if (isPureHexIdentifier(identifier)) return summary.toUpperCase().includes(identifier.toUpperCase());
return summary.includes(identifier);
}
/** Extracts likely exact identifiers that summaries should preserve literally. */
function extractOpaqueIdentifiers(text) {
return uniqueStrings(Array.from(text.matchAll(/(https?:\/\/\S+|(?<![A-Za-z0-9._-])\/[\w.-]{2,}(?:\/[\w.-]+)+|[A-Za-z]:\\[\w\\.-]+|(?<![A-Za-z0-9._-])[A-Za-z0-9._-]+\.[A-Za-z0-9._/-]+:\d{1,5})|(?:(?:(?:\d+\.\d+|\.\d+)(?:[eE][+-]?\d+)?|\d+\.[eE][+-]?\d+|\d+\.?[eE][+-]\d+|(?![A-Fa-f0-9]{8,}(?![A-Fa-f0-9]))\d+\.?[eE]\d+)(?:(?=[A-Za-z]+(?![A-Za-z0-9]))(?=[A-Za-z]*[G-Zg-z])[A-Za-z]+)?(?![A-Za-z0-9])|(?<![A-Za-z0-9_-])(?=[A-Za-z0-9_-]*(?:[A-Fa-f0-9]{8,}|\d{6,}))([A-Za-z0-9_-]+))/g), (match) => match[1] ?? match[2] ?? "").map((value) => normalizeOpaqueIdentifier(sanitizeExtractedIdentifier(value))).filter((value) => value.length >= 4)).slice(0, MAX_EXTRACTED_IDENTIFIERS);
}
function tokenizeAskOverlapText(text, includeFallbackTokens = false) {
const normalized = localeLowercasePreservingWhitespace(text.normalize("NFKC")).trim();
if (!normalized) return [];
const keywords = extractKeywords(normalized);
if (keywords.length > 0 && !includeFallbackTokens) return keywords;
const tokens = normalized.split(/[^\p{L}\p{N}]+/u).map((token) => token.trim()).filter((token) => token.length > 0);
return uniqueStrings([...keywords, ...tokens]);
}
function resolveAskOverlapRequirement(latestAsk) {
if (!latestAsk) return null;
const askTokens = tokenizeAskOverlapText(latestAsk).slice(0, MAX_ASK_OVERLAP_TOKENS);
if (askTokens.length === 0) return null;
const meaningfulAskTokens = askTokens.filter((token) => token.length > 1 && !isQueryStopWordToken(token));
const tokensToCheck = meaningfulAskTokens.length > 0 ? meaningfulAskTokens : askTokens;
return {
tokens: tokensToCheck,
requiredMatches: tokensToCheck.length >= MIN_ASK_OVERLAP_TOKENS_FOR_DOUBLE_MATCH ? 2 : 1
};
}
function hasAskOverlap(summary, latestAsk) {
const requirement = resolveAskOverlapRequirement(latestAsk);
if (!requirement) return true;
const summaryTokens = new Set(tokenizeAskOverlapText(summary, true));
return requirement.tokens.filter((token) => summaryTokens.has(token)).length >= requirement.requiredMatches;
}
/** Audits a candidate summary for required sections, pending asks, and identifier preservation. */
function auditSummaryQuality(params) {
const reasons = [];
const lines = new Set(normalizedSummaryLines(params.structuralSummary));
for (const section of REQUIRED_SUMMARY_SECTIONS) {
if (!lines.has(section)) reasons.push(`missing_section:${section}`);
if (params.sourceSummaries?.some((source) => normalizedSummaryLines(source).filter((line) => line === section).length > 1)) reasons.push(`duplicate_section:${section}`);
}
if ((params.identifierPolicy ?? "strict") === "strict") {
const missingIdentifiers = params.identifiers.filter((identifier) => !summaryIncludesIdentifier(params.summary, identifier));
if (missingIdentifiers.length > 0) reasons.push(`missing_identifiers:${missingIdentifiers.slice(0, 3).join(",")}`);
}
const leadingPendingAsk = extractLeadingPendingAsk(params.structuralSummary);
if (params.latestUnresolvedUserRequest && leadingPendingAsk !== formatLatestUserRequestContext(params.latestUnresolvedUserRequest)) reasons.push("latest_user_ask_not_foregrounded");
else if (!params.latestUnresolvedUserRequest && !hasAskOverlap(params.summary, params.latestAsk)) reasons.push("latest_user_ask_not_reflected");
const retainedPendingAsk = extractLeadingPendingAsk(params.retainedTurnSummary ?? "");
if (params.latestUnresolvedUserRequest ? retainedPendingAsk && !isEmptyPendingAsk(retainedPendingAsk) : params.retainedTurnSummary !== void 0 && resolveAskOverlapRequirement(params.latestAsk) && hasAskOverlap(extractPendingAskSection(params.retainedTurnSummary), params.latestAsk)) reasons.push("retained_turn_ask_marked_pending");
return {
ok: reasons.length === 0,
reasons
};
}
//#endregion
//#region src/agents/agent-hooks/compaction-suffix.ts
const CONTEXT_TRUNCATED_MARKER = "\n\n[Earlier compaction context truncated to fit budget]\n\n";
function assembleCompactionSuffix(parts, reserveSplitTurn = false) {
let text = "";
let reservedChars = 0;
const contextRanges = [];
for (const [name, part] of Object.entries(parts)) {
const section = typeof part === "string" ? part : part?.text;
if (!section) continue;
const leadingTrim = text ? 0 : section.length - section.trimStart().length;
const appended = leadingTrim > 0 ? section.slice(leadingTrim) : section;
const start = text.length;
text = appendSummarySection(text, section);
if (reserveSplitTurn && name === "generatedSplitTurnSection") reservedChars = text.length;
if (typeof part !== "string") contextRanges.push({
start,
end: start + appended.length,
segmentStarts: part.segmentStarts.filter((segmentStart) => segmentStart >= leadingTrim).map((segmentStart) => start + segmentStart - leadingTrim)
});
}
if (text && !/^\s/.test(text)) {
text = `\n\n${text}`;
reservedChars += reservedChars > 0 ? 2 : 0;
for (const range of contextRanges) {
range.start += 2;
range.end += 2;
range.segmentStarts = range.segmentStarts.map((segmentStart) => segmentStart + 2);
}
}
return {
text,
contextRanges,
...reservedChars > 0 ? { reservedChars } : {}
};
}
function resolveSuffixTailStart(suffix, tailBudget) {
const desiredStart = Math.max(0, suffix.text.length - tailBudget);
const containingRange = suffix.contextRanges.find((range) => desiredStart > range.start && desiredStart < range.end);
return containingRange ? containingRange.segmentStarts.find((start) => start >= desiredStart) ?? containingRange.end : desiredStart;
}
function capCompactionSuffix(suffix, maxChars) {
if (suffix.text.length <= maxChars) return suffix.text;
if (maxChars <= 0) return "";
const reserved = suffix.reservedChars ?? 0;
if (reserved > 0) {
if (reserved >= maxChars) return capCompactionSummary(suffix.text.slice(0, reserved), maxChars);
const rest = {
text: suffix.text.slice(reserved),
contextRanges: suffix.contextRanges.filter((range) => range.start >= reserved).map((range) => ({
start: range.start - reserved,
end: range.end - reserved,
segmentStarts: range.segmentStarts.map((start) => start - reserved)
}))
};
return `${suffix.text.slice(0, reserved)}${capCompactionSuffix(rest, maxChars - reserved)}`;
}
if (maxChars < 56) return sliceUtf16Safe(suffix.text, resolveSuffixTailStart(suffix, maxChars));
const tailBudget = maxChars - 56;
const start = resolveSuffixTailStart(suffix, tailBudget);
return tailBudget > 0 ? `${CONTEXT_TRUNCATED_MARKER}${sliceUtf16Safe(suffix.text, start)}` : CONTEXT_TRUNCATED_MARKER;
}
//#endregion
//#region src/agents/agent-hooks/compaction-safeguard.ts
/** Extension that safeguards compaction with structured summaries and quality repair. */
const log = createSubsystemLogger("compaction-safeguard");
const missedModelWarningSessions = /* @__PURE__ */ new WeakSet();
const SPLIT_TURN_SECTION_HEADING = "**Turn Context (split turn):**";
const MAX_TOOL_FAILURES = 8;
const MAX_TOOL_FAILURE_CHARS = 240;
const MAX_SPLIT_TURN_CONTEXT_CHARS = Math.floor(MAX_COMPACTION_SUMMARY_CHARS / 2);
const SPLIT_TURN_TRUNCATED_MARKER = "[Earlier split-turn messages truncated]\n";
const PRESERVED_TURNS_TRUNCATED_MARKER = "[Earlier preserved messages truncated]\n";
const DEFAULT_RECENT_TURNS_PRESERVE = 3;
const DEFAULT_QUALITY_GUARD_MAX_RETRIES = 1;
const MAX_RECENT_TURNS_PRESERVE = 12;
const MAX_QUALITY_GUARD_MAX_RETRIES = 3;
const MAX_RECENT_TURN_TEXT_CHARS = 600;
const compactionSafeguardDeps = { summarizeCompactionHistory };
function nestMarkdownHeadings(text) {
return text.replace(/^##(?=[ \t]+\S)/gmu, "###");
}
function normalizeLegacySplitTurnSummary(summary) {
const splitTurnStart = summary?.indexOf(SPLIT_TURN_SECTION_HEADING) ?? -1;
if (!summary || splitTurnStart < 0) return summary;
const splitTurnContentStart = splitTurnStart + 30;
return `${summary.slice(0, splitTurnContentStart)}${nestRequiredSummaryHeadings(summary.slice(splitTurnContentStart))}`;
}
/**
* The boundary-scoped range a preparation was meant to cover: everything the
* current context holds before its kept tail, minus the prior summary message
* (that is re-distilled separately). Bounded by construction — it can never
* reach behind the last reset/compaction boundary.
*/
function collectPreparationRangeMessages(sessionManager, firstKeptEntryId) {
const entries = readSessionBranch(sessionManager);
const firstKeptIndex = entries.findIndex((entry) => entry.id === firstKeptEntryId);
if (firstKeptIndex < 0) return [];
return projectBranchEntries(entries.filter((entry, index) => index < firstKeptIndex || entry.type === "compaction" || entry.type === "reset")).filter((message) => message.role !== "compactionSummary");
}
function readSessionBranch(sessionManager) {
try {
return sessionManager.getBranch();
} catch {
return [];
}
}
function projectBranchEntries(entries) {
try {
return buildSessionContext(entries).messages;
} catch {
return [];
}
}
function containsRealConversation(messages) {
return messages.some((message, index, allMessages) => isRealConversationMessage(message, allMessages, index));
}
async function resolveModelAuth(ctx, model) {
let requestAuth;
try {
requestAuth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
} catch (err) {
const error = formatErrorMessage(err);
log.warn(`Compaction safeguard: request credentials unavailable; cancelling compaction. ${error}`);
return {
ok: false,
reason: `Compaction safeguard could not resolve request credentials for ${model.provider}/${model.id}: ${error}`
};
}
if (!requestAuth.ok) {
log.warn(`Compaction safeguard: request credential resolution failed for ${model.provider}/${model.id}: ${requestAuth.error}`);
return {
ok: false,
reason: `Compaction safeguard could not resolve request credentials for ${model.provider}/${model.id}: ${requestAuth.error}`
};
}
return {
ok: true,
apiKey: requestAuth.apiKey,
headers: requestAuth.headers
};
}
function buildCompactionSummaryHeaders(params) {
if (params.model.provider !== "github-copilot") return params.headers;
const messages = params.messages;
return {
...buildCopilotDynamicHeaders({
messages,
hasImages: hasCopilotVisionInput(messages)
}),
...params.headers
};
}
function clampNonNegativeInt(value, fallback, max) {
return Math.min(max, Math.max(0, Math.floor(typeof value === "number" && Number.isFinite(value) ? value : fallback)));
}
function resolveRecentTurnsPreserve(value) {
return clampNonNegativeInt(value, DEFAULT_RECENT_TURNS_PRESERVE, MAX_RECENT_TURNS_PRESERVE);
}
function resolveQualityGuardMaxRetries(value) {
return clampNonNegativeInt(value, DEFAULT_QUALITY_GUARD_MAX_RETRIES, MAX_QUALITY_GUARD_MAX_RETRIES);
}
function formatToolFailureMeta(details) {
if (!details || typeof details !== "object") return;
const record = details;
return [typeof record.status === "string" && record.status ? `status=${record.status}` : "", typeof record.exitCode === "number" && Number.isFinite(record.exitCode) ? `exitCode=${record.exitCode}` : ""].filter(Boolean).join(" ") || void 0;
}
function collectToolFailures(messages) {
const failures = [];
const seen = /* @__PURE__ */ new Set();
for (const message of messages) {
if (message.role !== "toolResult" || !message.isError) continue;
const toolResult = message;
if (typeof toolResult.toolName === "string" && toolResult.toolName.trim() === "sessions_spawn" && normalizeAcceptedSessionSpawnResult(toolResult)) continue;
const toolCallId = typeof toolResult.toolCallId === "string" ? toolResult.toolCallId : "";
if (!toolCallId || seen.has(toolCallId)) continue;
seen.add(toolCallId);
const toolName = typeof toolResult.toolName === "string" && toolResult.toolName.trim() ? toolResult.toolName : "tool";
const meta = formatToolFailureMeta(toolResult.details);
const failureText = collectTextContentBlocks(toolResult.content).join("\n").replace(/\s+/g, " ").trim() || (meta ? "failed" : "failed (no output)");
const summary = failureText.length > MAX_TOOL_FAILURE_CHARS ? `${truncateUtf16Safe(failureText, 237)}...` : failureText;
failures.push({
toolCallId,
toolName,
summary,
meta
});
}
return failures;
}
function formatToolFailuresSection(failures) {
if (failures.length === 0) return "";
const lines = failures.slice(0, MAX_TOOL_FAILURES).map((failure) => {
const meta = failure.meta ? ` (${failure.meta})` : "";
return `- ${failure.toolName}${meta}: ${failure.summary}`;
});
if (failures.length > MAX_TOOL_FAILURES) lines.push(`- ...and ${failures.length - MAX_TOOL_FAILURES} more`);
return `\n\n## Tool Failures\n${lines.join("\n")}`;
}
function budgetCompactionSummary(summaryBody, suffix, maxChars, qualityRetention) {
const joined = `${summaryBody}${suffix.text}`;
const retentionPlan = qualityRetention ? createSummaryQualityRetentionPlan(summaryBody, SUMMARY_TRUNCATED_MARKER, qualityRetention) : null;
if (maxChars <= 0 || joined.length <= maxChars && !retentionPlan?.needsRebuild(maxChars)) return {
summary: joined,
structuralSummary: summaryBody,
bodyBudget: maxChars,
bodyTrimmed: false,
suffixTrimmed: false,
qualityRetentionInfeasible: false
};
const bodyCapacity = retentionPlan ? maxChars : summaryBody.length;
const bodyFloor = Math.min(bodyCapacity, maxChars, Math.max(1, Math.ceil(maxChars / 2), retentionPlan?.minimumChars ?? 0));
const suffixReservation = Math.min(suffix.text.length, maxChars);
const bodySlot = Math.min(bodyCapacity, Math.max(bodyFloor, maxChars - suffixReservation));
const rendered = retentionPlan?.render(bodySlot);
const cappedBody = rendered?.text ?? capCompactionSummary(summaryBody, bodySlot);
const cappedSuffix = capCompactionSuffix(suffix, Math.max(0, maxChars - cappedBody.length));
return {
summary: `${cappedBody}${cappedSuffix}`,
structuralSummary: cappedBody,
bodyBudget: bodySlot,
bodyTrimmed: rendered ? rendered.trimmed : cappedBody.length < summaryBody.length,
suffixTrimmed: cappedSuffix.length < suffix.text.length,
qualityRetentionInfeasible: retentionPlan !== null && retentionPlan.minimumChars > maxChars
};
}
function resolveSummaryReserveTokens(requestedReserveTokens, model) {
const requested = Math.max(1, Math.floor(requestedReserveTokens));
const modelMaxTokens = model.maxTokens;
if (typeof modelMaxTokens !== "number" || !Number.isFinite(modelMaxTokens) || modelMaxTokens <= 0) return requested;
return Math.max(1, Math.min(requested, Math.floor(modelMaxTokens)));
}
function splitPreservedRecentTurns(params) {
const preserveTurns = params.recentTurnsPreserve;
if (preserveTurns <= 0) return {
summarizableMessages: params.messages,
preservedMessages: []
};
const conversationIndexes = params.messages.flatMap((message, index) => message.role === "user" || message.role === "assistant" ? [index] : []);
if (conversationIndexes.length === 0) return {
summarizableMessages: params.messages,
preservedMessages: []
};
const userIndexes = conversationIndexes.filter((index) => params.messages[index]?.role === "user");
const boundaryStartIndex = userIndexes.at(-preserveTurns);
const preservedIndexSet = new Set(boundaryStartIndex === void 0 ? userIndexes : conversationIndexes.filter((index) => index >= boundaryStartIndex));
if (boundaryStartIndex === void 0) for (const index of conversationIndexes.toReversed()) {
preservedIndexSet.add(index);
if (preservedIndexSet.size >= preserveTurns * 2) break;
}
const preservedToolCallIds = /* @__PURE__ */ new Set();
for (const index of preservedIndexSet) {
const message = params.messages[index];
if (message?.role === "assistant") for (const toolCall of extractToolCallsFromAssistant(message)) preservedToolCallIds.add(toolCall.id);
}
if (preservedToolCallIds.size > 0) {
const preservedStartIndex = conversationIndexes.find((index) => preservedIndexSet.has(index));
for (let index = preservedStartIndex; index < params.messages.length; index += 1) {
const message = params.messages[index];
if (message?.role !== "toolResult") continue;
const toolResultId = extractToolResultId(message);
if (toolResultId && preservedToolCallIds.has(toolResultId)) preservedIndexSet.add(index);
}
}
const summarizableMessages = [];
const preservedMessages = [];
for (const [index, message] of params.messages.entries()) (preservedIndexSet.has(index) ? preservedMessages : summarizableMessages).push(message);
return {
summarizableMessages: repairToolUseResultPairing(summarizableMessages).messages,
preservedMessages
};
}
function formatContextMessage(message) {
let roleLabel;
if (message.role === "assistant") roleLabel = "Assistant";
else if (message.role === "user") roleLabel = formatCompactionUserLabel(message);
else if (message.role === "toolResult") {
const toolName = message.toolName;
roleLabel = `Tool result (${typeof toolName === "string" && toolName.trim() ? toolName : "tool"})`;
} else return null;
const rendered = [extractCompactionMessageText(message), formatCompactionNonTextPlaceholder(message.content)].filter(Boolean).join("\n");
if (!rendered) return null;
const trimmed = rendered.length > MAX_RECENT_TURN_TEXT_CHARS ? `${truncateUtf16Safe(rendered, MAX_RECENT_TURN_TEXT_CHARS)}...` : rendered;
return `- ${roleLabel}: ${trimmed}`;
}
function formatContextSegments(messages) {
const pairing = classifyToolUseResultPairing(messages);
const toolSegments = new Map(pairing.frames.map((frame) => [frame.assistant, [frame.assistant, ...frame.occurrences.flatMap((occurrence) => occurrence.sourceResult ? [occurrence.sourceResult] : [])]]));
return messages.flatMap((message) => {
if (message.role === "toolResult") return [];
const lines = (toolSegments.get(message) ?? [message]).map(formatContextMessage).filter((line) => Boolean(line));
return lines.length > 0 ? [lines.join("\n")] : [];
});
}
function formatBoundedContextSection(params) {
const segments = formatContextSegments(params.messages);
if (segments.length === 0) return {
text: "",
segmentStarts: []
};
let prefix = `${params.heading}\n`;
let retained = segments;
const truncated = !(prefix.length + segments.join("\n").length <= params.maxChars);
if (truncated) {
prefix += params.truncatedMarker;
retained = [];
let usedChars = prefix.length;
for (const segment of segments.toReversed()) {
const segmentChars = segment.length + (retained.length > 0 ? 1 : 0);
if (usedChars + segmentChars > params.maxChars) break;
retained.unshift(segment);
usedChars += segmentChars;
}
}
let offset = prefix.length;
return {
text: `${prefix}${retained.join("\n")}`,
segmentStarts: retained.map((segment) => {
const start = offset;
offset += segment.length + 1;
return start;
}),
...truncated ? { truncatedLoss: params.truncatedLoss } : {}
};
}
function buildPreservedTurnsSection(messages) {
return formatBoundedContextSection({
messages,
heading: "\n\n## Recent turns preserved verbatim",
maxChars: MAX_SPLIT_TURN_CONTEXT_CHARS,
truncatedMarker: PRESERVED_TURNS_TRUNCATED_MARKER,
truncatedLoss: "preserved-turn-head"
});
}
function buildSplitTurnContextSection(messages) {
return formatBoundedContextSection({
messages,
heading: "**Turn Context (split turn):**\n",
maxChars: MAX_SPLIT_TURN_CONTEXT_CHARS,
truncatedMarker: SPLIT_TURN_TRUNCATED_MARKER,
truncatedLoss: "split-turn-head"
});
}
function formatGeneratedSplitTurnSection(summary, onTruncated) {
const heading = `${SPLIT_TURN_SECTION_HEADING}\n\n`;
const summaryBudget = MAX_SPLIT_TURN_CONTEXT_CHARS - heading.length;
const nestedSummary = nestMarkdownHeadings(summary);
const cappedSummary = capCompactionSummary(nestedSummary, summaryBudget);
if (cappedSummary.length < nestedSummary.length) onTruncated();
return `${heading}${cappedSummary}`;
}
/**
* Read and format critical workspace context for compaction summary.
* Uses explicitly configured AGENTS.md section names only.
* The default "Session Startup" / "Red Lines" pair preserves the legacy
* "Every Session" / "Safety" fallback.
* Limited to 2000 chars to avoid bloating the summary.
*/
async function readWorkspaceContextForSummary(sectionNames, workspaceDir = process.cwd()) {
const MAX_SUMMARY_CONTEXT_CHARS = 2e3;
if (!Array.isArray(sectionNames) || sectionNames.length === 0) return "";
const agentsPath = path.join(workspaceDir, "AGENTS.md");
try {
const opened = await openRootFile({
absolutePath: agentsPath,
rootPath: workspaceDir,
boundaryLabel: "workspace root"
});
if (!opened.ok) return "";
let content;
try {
content = await readWorkspaceBootstrapFile(opened.fd);
} catch (err) {
if (err instanceof RangeError) {
log.warn(`Ignoring oversized AGENTS.md ${agentsPath}: file exceeds the ${MAX_WORKSPACE_BOOTSTRAP_FILE_BYTES}-byte limit`);
return "";
}
throw err;
} finally {
fs.closeSync(opened.fd);
}
let sections = extractSections(content, sectionNames);
if (sections.length === 0 && sectionNames.length === 2 && sectionNames.some((name) => name.trim().toLowerCase() === "session startup") && sectionNames.some((name) => name.trim().toLowerCase() === "red lines")) sections = extractSections(content, ["Every Session", "Safety"]);
if (sections.length === 0) return "";
const combined = sections.join("\n\n");
return `\n\n<workspace-critical-rules>\n${combined.length > MAX_SUMMARY_CONTEXT_CHARS ? `${truncateUtf16Safe(combined, MAX_SUMMARY_CONTEXT_CHARS)}\n...[truncated]...` : combined}\n</workspace-critical-rules>`;
} catch {
return "";
}
}
/** Registers compaction hooks that summarize, preserve recent turns, and audit output quality. */
function compactionSafeguardExtension(api) {
api.on("session_before_compact", async (event, ctx) => {
const { preparation, customInstructions: eventInstructions, signal, thinkingLevel, streamFn } = event;
const previousSummary = normalizeLegacySplitTurnSummary(preparation.previousSummary?.trim());
let baseMessagesToSummarize = stripRuntimeContextCustomMessages(preparation.messagesToSummarize);
let baseTurnPrefixMessages = stripRuntimeContextCustomMessages(preparation.turnPrefixMessages ?? []);
const latestUnresolvedUserRequest = preparation.latestUnresolvedUserRequest;
if (!containsRealConversation([...baseMessagesToSummarize, ...baseTurnPrefixMessages])) {
const rangeMessages = stripRuntimeContextCustomMessages(collectPreparationRangeMessages(ctx.sessionManager, preparation.firstKeptEntryId));
if (containsRealConversation(rangeMessages)) {
log.info("Compaction safeguard: summarizing the boundary-scoped preparation range after compaction preparation omitted real conversation content.");
baseMessagesToSummarize = rangeMessages;
baseTurnPrefixMessages = [];
}
}
const hasRealConversation = containsRealConversation([...baseMessagesToSummarize, ...baseTurnPrefixMessages]) || containsRealConversation(stripRuntimeContextCustomMessages(projectBranchEntries(readSessionBranch(ctx.sessionManager))));
setCompactionSafeguardCancellation(ctx.sessionManager, void 0);
const cancelCompaction = (reason, error) => {
setCompactionSafeguardCancellation(ctx.sessionManager, reason, error);
return { cancel: true };
};
if (!hasRealConversation) {
log.info("Compaction safeguard: no real conversation messages to summarize; writing compaction boundary to suppress re-trigger loop.");
return { compaction: {
summary: buildStructuredFallbackSummary(previousSummary),
firstKeptEntryId: preparation.firstKeptEntryId,
tokensBefore: preparation.tokensBefore
} };
}
const { readFiles, modifiedFiles } = computeFileLists(preparation.fileOps);
const fileOpsSummary = formatFileOperations(readFiles, modifiedFiles);
const toolFailureSection = formatToolFailuresSection(collectToolFailures([...baseMessagesToSummarize, ...baseTurnPrefixMessages]));
const runtime = getCompactionSafeguardRuntime(ctx.sessionManager);
const customInstructions = resolveCompactionInstructions(eventInstructions, runtime?.customInstructions);
const summarizationInstructions = {
identifierPolicy: runtime?.identifierPolicy,
identifierInstructions: runtime?.identifierInstructions
};
const identifierPolicy = runtime?.identifierPolicy ?? "strict";
const qualityGuardEnabled = runtime?.qualityGuardEnabled ?? false;
const providerId = runtime?.provider;
const turnPrefixMessages = baseTurnPrefixMessages;
const recentTurnsPreserve = resolveRecentTurnsPreserve(runtime?.recentTurnsPreserve);
const structuredInstructions = buildCompactionStructureInstructions(customInstructions, summarizationInstructions, latestUnresolvedUserRequest ?? void 0);
let workspaceContextPromise;
const finalizeSummaryText = async (body, sections, producerLosses = /* @__PURE__ */ new Set(), qualityRetention, degraded = false) => {
workspaceContextPromise ??= readWorkspaceContextForSummary(runtime?.postCompactionSections, runtime?.workspaceDir);
const suffix = assembleCompactionSuffix({
splitTurnSection: sections.splitTurnSection,
generatedSplitTurnSection: sections.generatedSplitTurnSection,
preservedTurnsSection: sections.preservedTurnsSection,
toolFailureSection,
fileOpsSummary,
workspaceContext: await workspaceContextPromise
}, degraded);
const fitted = fitCompactionSummary(preparation.summaryTokenBudget, (maxChars) => budgetCompactionSummary(body, suffix, maxChars, qualityRetention));
if (!fitted.ok) throw fitted.error;
const finalized = fitted.value;
const losses = new Set(producerLosses);
for (const section of Object.values(sections)) if (typeof section !== "string" && section?.truncatedLoss) losses.add(section.truncatedLoss);
if (finalized.bodyTrimmed) losses.add("summary-tail");
if (finalized.suffixTrimmed) losses.add("suffix-head");
if (losses.size > 0) log.warn(`Compaction safeguard: finalized artifact truncated; loss=${[...losses].join(",")}`);
return finalized;
};
const compactionResult = (summary, provenance) => ({ compaction: {
summary,
firstKeptEntryId: preparation.firstKeptEntryId,
tokensBefore: preparation.tokensBefore,
details: {
readFiles,
modifiedFiles,
...latestUnresolvedUserRequest ? { latestUnresolvedUserRequest } : {},
...provenance
}
} });
if (providerId) {
const compactionProvider = getCompactionProvider(providerId);
if (compactionProvider) try {
const providerResult = await compactionProvider.summarize({
messages: [...baseMessagesToSummarize, ...turnPrefixMessages],
signal,
customInstructions: structuredInstructions,
summarizationInstructions,
previousSummary
});
if (typeof providerResult === "string" && providerResult.trim()) {
const { preservedMessages } = splitPreservedRecentTurns({
messages: baseMessagesToSummarize,
recentTurnsPreserve
});
return compactionResult((await finalizeSummaryText(providerResult, {
splitTurnSection: preparation.isSplitTurn ? buildSplitTurnContextSection(turnPrefixMessages) : void 0,
preservedTurnsSection: buildPreservedTurnsSection(preservedMessages)
})).summary);
}
log.warn(`Compaction provider "${compactionProvider.id}" returned empty result, falling back to LLM.`);
} catch (err) {
if (signal?.aborted) throw err;
log.warn(`Compaction provider "${compactionProvider.id}" failed, falling back to LLM: ${formatErrorMessage(err)}`);
}
else log.warn(`Compaction provider "${providerId}" is configured but not registered. Falling back to LLM.`);
}
const model = ctx.model ?? runtime?.model;
if (!model) {
if (!missedModelWarningSessions.has(ctx.sessionManager)) {
missedModelWarningSessions.add(ctx.sessionManager);
log.warn("[compaction-safeguard] Both ctx.model and runtime.model are undefined. Compaction summarization will not run. This indicates extensionRunner.initialize() was not called and model was not passed through runtime registry.");
}
return cancelCompaction("Compaction safeguard could not resolve a summarization model.");
}
const authResult = await resolveModelAuth(ctx, model);
if (!authResult.ok) return cancelCompaction(authResult.reason);
try {
const headers = buildCompactionSummaryHeaders({
model,
messages: baseMessagesToSummarize,
headers: authResult.headers
});
const usageSink = (usage) => recordSessionModelUsage(ctx.sessionManager, usage);
const llmSummaryParams = {
model,
apiKey: authResult.apiKey ?? "",
headers,
signal,
reserveTokens: resolveSummaryReserveTokens(preparation.settings.reserveTokens, model),
summarizationInstructions,
thinkingLevel,
streamFn,
usageSink
};
const qualityGuardMaxRetries = resolveQualityGuardMaxRetries(runtime?.qualityGuardMaxRetries);
const oracleMessages = [...baseMessagesToSummarize, ...turnPrefixMessages];
const splitUserAsk = preparation.isSplitTurn ? extractCurrentCompactionUserRequest(turnPrefixMessages, extractCompactionMessageText) : null;
const latestUserAsk = preparation.requestState !== void 0 ? latestUnresolvedUserRequest ?? null : extractCurrentCompactionUserRequest(oracleMessages, extractCompactionMessageText);
const latestAskInRetainedTurn = splitUserAsk !== null && splitUserAsk === latestUserAsk;
const identifiers = extractOpaqueIdentifiers(oracleMessages.slice(-10).map(extractCompactionMessageText).filter(Boolean).join("\n"));
const { summarizableMessages: summaryTargetMessages, preservedMessages: preservedRecentMessages } = splitPreservedRecentTurns({
messages: baseMessagesToSummarize,
recentTurnsPreserve
});
const preservedTurnsSectionLocal = buildPreservedTurnsSection(preservedRecentMessages);
const latestPreparedAsk = extractCurrentCompactionUserRequest(baseMessagesToSummarize, extractCompactionMessageText);
const requiredAskContext = formatRequiredAskContext(latestUserAsk ?? "");
const includePreservedContext = !latestUnresolvedUserRequest && qualityGuardEnabled && latestPreparedAsk === latestUserAsk && !preservedTurnsSectionLocal.text.includes(requiredAskContext);
const reconciliationMessages = previousSummary || includePreservedContext || summaryTargetMessages.length > 0 || turnPrefixMessages.length > 0 ? oracleMessages : [];
let correctiveInstructions = "";
const totalAttempts = qualityGuardEnabled ? qualityGuardMaxRetries + 1 : 1;
let bestSummary;
const degrade = (summary, diagnostic) => {
log.warn(`Compaction safeguard: ${diagnostic}; using degraded summary; reasonCode=quality_guard_degraded_fallback`);
return compactionResult(summary, { qualityDegraded: true });
};
for (let attempt = 0; attempt < totalAttempts; attempt += 1) {
let splitTurnSectionLocal = "";
let splitTurnSummaryLocal = "";
let historySummary = "";
const producerLosses = /* @__PURE__ */ new Set();
try {
historySummary = reconciliationMessages.length > 0 ? await compactionSafeguardDeps.summarizeCompactionHistory({
...llmSummaryParams,
messages: reconciliationMessages,
summaryPrompt: {
kind: "custom",
instructions: structuredInstructions
},
customInstructions: correctiveInstructions,
previousSummary
}) : buildStructuredFallbackSummary(previousSummary);
if (preparation.isSplitTurn && turnPrefixMessages.length > 0) {
const splitTurnFocus = wrapUntrustedInstructionBlock("Additional context from /compact", customInstructions);
const prefixSummary = await compactionSafeguardDeps.summarizeCompactionHistory({
...llmSummaryParams,
messages: turnPrefixMessages,
summaryPrompt: { kind: "turn-prefix" },
customInstructions: [splitTurnFocus, correctiveInstructions].filter(Boolean).join("\n\n"),
previousSummary: void 0
});
splitTurnSummaryLocal = prefixSummary;
splitTurnSectionLocal = formatGeneratedSplitTurnSection(prefixSummary, () => {
producerLosses.add("split-turn-tail");
});
}
} catch (attemptError) {
if (signal?.aborted) signal.throwIfAborted();
if (bestSummary) return degrade(bestSummary.summary, `corrective generation failed; attempt=${attempt + 1}`);
throw attemptError;
}
const unbudgetedSummary = appendSummarySection(historySummary, splitTurnSectionLocal ? `\n\n${splitTurnSectionLocal}` : "");
const finalized = await finalizeSummaryText(qualityGuardEnabled ? historySummary : unbudgetedSummary, {
generatedSplitTurnSection: qualityGuardEnabled && splitTurnSectionLocal ? `\n\n${splitTurnSectionLocal}` : void 0,
preservedTurnsSection: preservedTurnsSectionLocal
}, producerLosses, qualityGuardEnabled ? {
auditSummary: unbudgetedSummary,
identifiers,
latestAsk: latestUserAsk,
latestAskInRetainedTurn,
latestUnresolvedUserRequest,
requiredAskContext,
identifierPolicy
} : void 0);
if (!qualityGuardEnabled) return compactionResult(finalized.summary);
const quality = auditSummaryQuality({
summary: finalized.summary,
structuralSummary: finalized.structuralSummary,
sourceSummaries: [historySummary, splitTurnSummaryLocal].filter(Boolean),
identifiers,
latestAsk: latestUserAsk,
latestUnresolvedUserRequest,
retainedTurnSummary: latestAskInRetainedTurn ? splitTurnSummaryLocal : void 0,
identifierPolicy
});
if (quality.ok && !finalized.qualityRetentionInfeasible) return compactionResult(finalized.summary);
const reasonCount = quality.reasons.length + Number(finalized.qualityRetentionInfeasible);
if (!bestSummary || reasonCount < bestSummary.reasonCount) bestSummary = {
summary: (await finalizeSummaryText(buildStructuredFallbackSummary(historySummary.trim() || previousSummary), {
generatedSplitTurnSection: splitTurnSectionLocal ? `\n\n${splitTurnSectionLocal}` : void 0,
preservedTurnsSection: preservedTurnsSectionLocal
}, producerLosses, {
identifiers,
latestAsk: latestUserAsk,
latestAskInRetainedTurn,
latestUnresolvedUserRequest,
requiredAskContext,
identifierPolicy
}, true)).summary,
reasonCount
};
if (finalized.qualityRetentionInfeasible) return degrade(bestSummary.summary, "required quality facts exceed finalized artifact budget");
if (reconciliationMessages.length === 0 || attempt >= totalAttempts - 1) {
const reasonCodes = [...new Set(quality.reasons.map((reason) => reason.split(":", 1)[0]))];
return degrade(bestSummary.summary, `final quality attempt failed; reasonCodes=${reasonCodes.join(",")} reasonCount=${quality.reasons.length}`);
}
const reasons = quality.reasons.join(", ");
const qualityFeedbackInstruction = identifierPolicy === "strict" ? "Fix all issues and include every required section with exact identifiers preserved." : "Fix all issues and include every required section while following the configured identifier policy.";
const budgetInstruction = `Keep the complete summary body within ${finalized.bodyBudget} UTF-16 code units so the finalized artifact remains valid after required suffixes.`;
const qualityFeedbackReasons = wrapUntrustedInstructionBlock("Quality check feedback", `Previous summary failed quality checks (${reasons}).`);
correctiveInstructions = qualityFeedbackReasons ? `${qualityFeedbackInstruction}\n${budgetInstruction}\n\n${qualityFeedbackReasons}` : `${qualityFeedbackInstruction}\n${budgetInstruction}`;
}
throw new Error("Compaction safeguard exhausted summary attempts without a decision.");
} catch (error) {
if (signal?.aborted) signal.throwIfAborted();
const message = formatErrorMessage(error);
log.warn(`Compaction summarization failed; cancelling compaction to preserve history: ${message}`);
return cancelCompaction(`Compaction safeguard could not summarize the session: ${message}`, error);
}
});
}
const testing = {
setSummarizeCompactionHistoryForTest(next) {
compactionSafeguardDeps.summarizeCompactionHistory = next ?? summarizeCompactionHistory;
},
collectToolFailures,
formatToolFailuresSection,
splitPreservedRecentTurns,
buildPreservedTurnsSection,
buildCompactionStructureInstructions,
resolveRecentTurnsPreserve,
resolveQualityGuardMaxRetries,
extractOpaqueIdentifiers,
auditSummaryQuality,
capCompactionSummary,
budgetCompactionSummary,
formatFileOperations,
MAX_FILE_OPS_SECTION_CHARS,
readWorkspaceContextForSummary,
MAX_COMPACTION_SUMMARY_CHARS,
SUMMARY_TRUNCATED_MARKER,
CONTEXT_TRUNCATED_MARKER,
MAX_SPLIT_TURN_CONTEXT_CHARS
};
if (process.env.VITEST || false) globalThis[Symbol.for("openclaw.compactionSafeguardTestApi")] = testing;
//#endregion
//#region src/agents/embedded-agent-runner/extensions.ts
function buildEmbeddedExtensionFactories(params) {
const factories = [];
if (resolveEffectiveCompactionMode(params.cfg) === "safeguard") {
const compactionCfg = params.cfg?.agents?.defaults?.compaction;
const qualityGuardCfg = compactionCfg?.qualityGuard;
setCompactionSafeguardRuntime(params.sessionManager, {
identifierPolicy: compactionCfg?.identifierPolicy,
qualityGuardEnabled: qualityGuardCfg?.enabled ?? true,
qualityGuardMaxRetries: qualityGuardCfg?.maxRetries,
model: params.model,
recentTurnsPreserve: compactionCfg?.recentTurnsPreserve,
workspaceDir: params.workspaceDir,
postCompactionSections: compactionCfg?.postCompactionSections,
provider: compactionCfg?.provider
});
factories.push(compactionSafeguardExtension);
}
const { agentId, sessionKey, runId, sessionManager } = params;
const sessionId = params.sessionId ?? sessionManager.getSessionId?.();
const runner = createAgentToolResultMiddlewareRunner({
runtime: "openclaw",
...agentId ? { agentId } : {},
...sessionId ? { sessionId } : {},
...sessionKey ? { sessionKey } : {},
...runId ? { runId } : {}
});
factories.push((agent) => {
agent.on("tool_result", async (rawEvent, ctx) => {
const event = asOptionalRecord(rawEvent) ?? {};
if (!event.toolName) return;
const eventToolCallId = typeof event.toolCallId === "string" && event.toolCallId.trim() ? event.toolCallId : void 0;
const toolCallId = eventToolCallId ?? `openclaw-${randomUUID()}`;
const current = {
content: Array.isArray(event.content) ? event.content : [],
details: event.details
};
if (eventToolCallId) recordEmbeddedToolReceipt(sessionManager, eventToolCallId, current.details, event.toolName === "message");
const inputHadErrorStatus = isToolResultError(current);
const adjustedInput = eventToolCallId ? peekAdjustedParamsForToolCall(eventToolCallId, runId) : void 0;
const result = await runner.applyToolResultMiddleware({
threadId: event.threadId,
turnId: event.turnId,
toolCallId,
toolName: event.toolName,
args: asOptionalRecord(adjustedInput ?? event.input) ?? {},
cwd: ctx.cwd,
isError: event.isError,
result: current
});
const isAcceptedSessionSpawn = event.toolName === "sessions_spawn" && normalizeAcceptedSessionSpawnResult(result) !== null;
const hasError = event.isError === true || inputHadErrorStatus || isToolResultError(result);
const isError = !isAcceptedSessionSpawn && hasError;
if (eventToolCallId) finalizeToolTerminalPresentation({
toolCallId: eventToolCallId,
runId,
result,
isError
});
return {
content: result.content,
details: result.details,
...result.terminate !== void 0 ? { terminate: result.terminate } : {},
...hasError ? { isError } : {}
};
});
});
return factories;
}
//#endregion
//#region src/agents/embedded-agent-runner/tool-name-allowlist.ts
/**
* OpenClaw built-in tools that remain present in the embedded runtime even when
* OpenClaw routes execution through custom tool definitions.
*/
const AGENT_RESERVED_TOOL_NAMES = [
"bash",
"edit",
"find",
"grep",
"ls",
"read",
"write"
];
function collectAllowedToolNames(params) {
return /* @__PURE__ */ new Set([...collectRegisteredToolNames(params.tools), ...normalizeTrimmedStringList(params.clientTools?.map((tool) => tool.function?.name))]);
}
function collectRegisteredToolNames(tools) {
return new Set(normalizeTrimmedStringList(tools.map((tool) => tool.name)));
}
function toSessionToolAllowlist(allowedToolNames) {
return [...new Set(allowedToolNames)].toSorted((a, b) => a.localeCompare(b));
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-client-tools.ts
function prepareEmbeddedAttemptClientTools(params) {
const clientToolCallSlots = [];
const clientToolCallSlotsById = /* @__PURE__ */ new Map();
const clientToolCallSlotKey = (toolCallId) => `${getAgentToolAssistantTurnId() ?? ""}\u0000${toolCallId}`;
const reserveClientToolCallSlot = (toolCallId, toolName) => {
const slotKey = clientToolCallSlotKey(toolCallId);
let slot = clientToolCallSlotsById.get(slotKey);
if (!slot) {
slot = {
toolCallId,
name: toolName,
completed: false
};
clientToolCallSlotsById.set(slotKey, slot);
clientToolCallSlots.push(slot);
}
return slot;
};
const clientToolLoopDetection = resolveToolLoopDetectionConfig({
cfg: params.attempt.config,
agentId: params.sessionAgentId
});
const sourceClientToolDefs = params.clientTools ? toClientToolDefinitions(params.clientTools, {
reserve: reserveClientToolCallSlot,
complete: (toolCallId, toolName, toolParams) => {
const slot = reserveClientToolCallSlot(toolCallId, toolName);
slot.name = toolName;
slot.params = toolParams;
slot.completed = true;
},
discard: (toolCallId) => {
const slot = clientToolCallSlotsById.get(clientToolCallSlotKey(toolCallId));
if (slot) {
slot.completed = false;
slot.params = void 0;
}
}
}, {
agentId: params.sessionAgentId,
sessionKey: params.sandboxSessionKey,
config: params.toolSearchRuntimeConfig,
sessionId: params.attempt.sessionId,
runId: params.attempt.runId,
loopDetection: clientToolLoopDetection,
onToolOutcome: params.attempt.onToolOutcome,
allocateToolOutcomeOrdinal: params.attempt.allocateToolOutcomeOrdinal
}) : [];
const buildSurface = () => {
const builtinToolNames = /* @__PURE__ */ new Set();
const trustedLocalMediaToolNames = /* @__PURE__ */ new Set();
for (const tool of params.uncompactedEffectiveTools) {
const name = (tool.name ?? "").trim();
if (!name) continue;
builtinToolNames.add(name);
const pluginMeta = getPluginToolMeta(tool);
if (pluginMeta?.trustedLocalMedia === true || !pluginMeta && isCoreToolResultMediaTrustedName(name)) trustedLocalMediaToolNames.add(name);
}
const coreBuiltinToolNames = collectRegisteredToolNames(params.uncompactedEffectiveTools.filter((tool) => !getPluginToolMeta(tool)));
const isReplaySafeTool = (tool) => isAgentToolReplaySafe(tool, params.replaySafetyOptions);
const replaySafeTools = new Set(params.uncompactedEffectiveTools.filter(isReplaySafeTool));
const replaySafeToolNames = collectReplaySafeToolNames(params.uncompactedEffectiveTools, params.replaySafetyOptions);
const codeModeExecToolNames = new Set(params.effectiveTools.filter((tool) => isCodeModeExecTool(tool)).map((tool) => tool.name));
const sourceReplyCapableToolNames = new Set(params.uncompactedEffectiveTools.filter((tool) => "canDeliverSourceReply" in tool && tool.canDeliverSourceReply === true).map((tool) => normalizeToolPolicyName(tool.name ?? "")).filter((name) => name.length > 0));
const clientConflictToolNames = params.deferredDirectoryToolsCallable ? builtinToolNames : coreBuiltinToolNames;
const clientToolNameConflicts = findClientToolNameConflicts({
tools: params.clientTools ?? [],
existingToolNames: [...clientConflictToolNames, ...AGENT_RESERVED_TOOL_NAMES]
});
if (clientToolNameConflicts.length > 0) throw createClientToolNameConflictError(clientToolNameConflicts);
let clientToolDefs = sourceClientToolDefs.map((definition) => createToolDefinitionFromAgentTool(wrapToolWithAbortSignal(wrapToolDefinition(definition), params.getToolAbortSignal?.())));
const sideEffectToolOwners = collectSideEffectToolOwners([...params.uncompactedEffectiveTools, ...clientToolDefs], { declaredOwner: (tool) => getPluginToolSideEffectOwnerKey(tool) });
const search = resolveToolSearchConfig(params.toolSearchRuntimeConfig);
const clientToolSearch = addClientToolsToToolCatalog({
tools: clientToolDefs,
enabled: params.codeModeControlsEnabledForRun || search.enabled && search.mode !== "directory",
catalogRef: params.toolSearchCatalogRef
});
clientToolDefs = clientToolSearch.tools;
if (clientToolSearch.compacted) log$6.info(params.codeModeControlsEnabledForRun ? `code-mode: cataloged ${clientToolSearch.catalogToolCount} client tools behind exec/wait` : `tool-search: cataloged ${clientToolSearch.catalogToolCount} client tools behind compact prompt surface`);
const allCustomTools = [...toToolDefinitions(params.effectiveTools, params.catalogToolHookContext, params.getToolAbortSignal?.()), ...clientToolDefs];
const sessionToolAllowlist = toSessionToolAllowlist(collectRegisteredToolNames(allCustomTools));
return {
allCustomTools,
builtinToolNames,
coreBuiltinToolNames,
clientToolCallSlots,
clientToolDefs,
replaySafeToolNames,
replaySafeTools,
codeModeExecToolNames,
sourceReplyCapableToolNames,
sideEffectToolOwners,
sessionToolAllowlist,
trustedLocalMediaToolNames
};
};
const current = buildSurface();
return {
...current,
refreshTools: () => {
const next = buildSurface();
const replaceItems = (target, source) => target.splice(0, target.length, ...source);
replaceItems(current.allCustomTools, next.allCustomTools);
replaceItems(current.clientToolDefs, next.clientToolDefs);
replaceItems(current.sessionToolAllowlist, next.sessionToolAllowlist);
const replaceSet = (target, source) => {
target.clear();
for (const item of source) target.add(item);
};
for (const key of [
"builtinToolNames",
"coreBuiltinToolNames",
"replaySafeToolNames",
"codeModeExecToolNames",
"sourceReplyCapableToolNames",
"trustedLocalMediaToolNames"
]) replaceSet(current[key], next[key]);
replaceSet(current.replaySafeTools, next.replaySafeTools);
current.sideEffectToolOwners.clear();
for (const [name, owner] of next.sideEffectToolOwners) current.sideEffectToolOwners.set(name, owner);
}
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-compaction-thinking.ts
/** Keep summary policy bound to the run's config while rechecking the active model and session level. */
function createAttemptCompactionThinkingResolver(attempt, agentId) {
return (model, inheritedLevel) => {
const compat = projectModelThinkingCompat(model.compat);
const catalog = [{
provider: model.provider,
id: model.id,
api: model.api,
reasoning: model.reasoning,
...model.thinkingLevelMap ? { thinkingLevelMap: model.thinkingLevelMap } : {},
...model.params ? { params: model.params } : {},
...compat ? { compat } : {}
}];
const route = {
provider: model.provider,
catalog,
agentRuntime: "openclaw"
};
const level = resolveEmbeddedCompactionThinkingLevel({
...route,
config: attempt.config,
modelId: model.id,
inheritedLevel,
compactionThinkingDefault: model.compactionThinkingDefault,
agentId,
sessionKey: attempt.sessionKey ?? attempt.sandboxSessionKey
});
const providerLevel = resolveProviderThinkingLevel({
...route,
model: model.id,
level
});
return mapThinkingLevel(providerLevel);
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-orphan-repair.ts
function findTrailingMessageEntryForOrphanRepair(sessionManager) {
const visited = /* @__PURE__ */ new Set();
const trailingEntries = [];
let entry = sessionManager.getLeafEntry();
while (entry && isSessionContextMetadataEntry(entry)) {
if (visited.has(entry.id)) return;
visited.add(entry.id);
trailingEntries.push(entry);
entry = entry.parentId ? sessionManager.getEntry(entry.parentId) : void 0;
}
return entry?.type === "message" ? {
messageEntry: entry,
trailingEntries: trailingEntries.toReversed()
} : void 0;
}
async function replayTrailingEntriesForOrphanRepair(sessionManager, trailingEntries) {
const replayedEntryIds = /* @__PURE__ */ new Map();
for (const entry of trailingEntries) {
let replayedId;
switch (entry.type) {
case "thinking_level_change":
replayedId = await sessionManager.appendThinkingLevelChange(entry.thinkingLevel);
break;
case "model_change":
replayedId = await sessionManager.appendModelChange(entry.provider, entry.modelId);
break;
case "custom":
replayedId = await sessionManager.appendCustomEntryAsync(entry.customType, entry.data);
break;
case "session_info":
replayedId = await sessionManager.appendSessionInfoAsync(entry.name ?? "");
break;
case "label": {
const replayedTargetId = replayedEntryIds.get(entry.targetId);
if (!replayedTargetId && !sessionManager.getEntry(entry.targetId)) continue;
replayedId = await sessionManager.appendLabelChangeAsync(replayedTargetId ?? entry.targetId, entry.label);
break;
}
default: continue;
}
replayedEntryIds.set(entry.id, replayedId);
}
}
function isUserSessionMessageEntry(entry) {
return entry.message.role === "user";
}
function resolveOrphanRepairPlan(params) {
const candidate = findTrailingMessageEntryForOrphanRepair(params.sessionManager);
if (!candidate || !isUserSessionMessageEntry(candidate.messageEntry)) return;
const merge = mergeOrphanedTrailingUserPrompt({
prompt: params.prompt,
leafMessage: candidate.messageEntry.message
});
return {
contextEnginePrompt: merge.prompt,
messageEntry: candidate.messageEntry,
trailingEntries: candidate.trailingEntries,
removeLeaf: merge.removeLeaf || !params.preserveLeaf
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-permission-prompt.ts
/** Owns permission generations and their initial/continuing prompt admission. */
function installAttemptPermissionPrompt(input) {
const { activeSession, attempt, prepareSystemPromptUpdate, setActiveSessionSystemPrompt } = input;
let permissionPreparation;
const refreshPermissionPrompt = async (prompt, signal, prepareReplay, deferPromptAdmission = false) => {
const runSignal = signal ? AbortSignal.any([signal, input.runAbortSignal]) : input.runAbortSignal;
while (true) {
runSignal.throwIfAborted();
const preparation = permissionPreparation;
try {
const preparationSignal = preparation ? AbortSignal.any([runSignal, preparation.controller.signal]) : runSignal;
const refresh = preparation ? await raceWithAbortSignal(preparation.prepare(), preparationSignal) : void 0;
runSignal.throwIfAborted();
if (preparation !== permissionPreparation) continue;
let systemPrompt;
let prepared;
if (refresh || prepareSystemPromptUpdate) {
const currentPrompt = prompt ?? activeSession.agent.state.systemPrompt;
const freshPrompt = refresh ? refresh(currentPrompt) : currentPrompt;
prepared = prepareSystemPromptUpdate ? await prepareSystemPromptUpdate(freshPrompt, refresh?.freshlyRendered === true) : void 0;
runSignal.throwIfAborted();
if (preparation !== permissionPreparation) continue;
systemPrompt = prepared?.systemPrompt ?? setActiveSessionSystemPrompt(freshPrompt);
}
const admitReplay = prepareReplay ? await raceWithAbortSignal(prepareReplay(preparationSignal), preparationSignal) : void 0;
runSignal.throwIfAborted();
if (preparation !== permissionPreparation) continue;
const admit = async (onAdmitted) => {
const commit = () => {
preparationSignal.throwIfAborted();
if (preparation !== permissionPreparation) throw new Error("Session prompt preparation is stale after permission replacement.");
if (prepared) {
prepared.commit?.();
setActiveSessionSystemPrompt(prepared.systemPrompt);
if (prepared.update) activeSession[agentSessionQueuePromptContext](prepared.update);
}
};
if (admitReplay) await admitReplay(() => onAdmitted(commit));
else onAdmitted(commit);
};
if (prepared && !deferPromptAdmission) {
await admit((commit) => commit());
return { systemPrompt };
}
return {
systemPrompt,
admitReplay: admit
};
} catch (error) {
runSignal.throwIfAborted();
if (preparation !== permissionPreparation) continue;
throw error;
}
}
};
activeSession[agentSessionSetPromptPreparation](async () => {
const prepared = await refreshPermissionPrompt(void 0, void 0, input.prepareInitialUserTurnReplay, true);
return async (onAdmitted) => {
input.runAbortSignal.throwIfAborted();
if (prepared.admitReplay) await prepared.admitReplay(onAdmitted);
else onAdmitted();
};
});
const previousPrepareNextTurn = activeSession.agent.prepareNextTurn;
const prepareNextTurn = async (signal) => {
if (attempt.pluginRuntimeRefreshPending?.()) return { stop: true };
const snapshot = await previousPrepareNextTurn?.call(activeSession.agent, signal);
if (prepareSystemPromptUpdate) return {
...snapshot,
prepareContinuation: async (context) => {
const inherited = await snapshot?.prepareContinuation?.(context);
const { systemPrompt } = await refreshPermissionPrompt(inherited?.systemPrompt ?? context.systemPrompt, signal);
let runtimeContextCleared = false;
for (let index = context.messages.length - 1; index >= 0; index--) {
const message = context.messages[index];
if (message.role === "user") break;
if (message.role === "toolResult") runtimeContextCleared = true;
else if (message.role === "custom" && !message.excludeFromContext) {
if (!isOpenClawSystemUpdateMessage(message)) {
runtimeContextCleared = true;
continue;
}
const details = asOptionalRecord(message.details);
if (details?.kind === "runtime-context" && details.turnScoped === true) {
if (runtimeContextCleared) {
input.runAbortSignal.throwIfAborted();
signal?.throwIfAborted();
activeSession[agentSessionQueuePromptContext]({
...message,
timestamp: Date.now()
});
}
break;
}
}
}
return {
systemPrompt: systemPrompt ?? inherited?.systemPrompt ?? context.systemPrompt,
tools: activeSession.agent.state.tools.slice()
};
}
};
const { systemPrompt: refreshedPrompt } = await refreshPermissionPrompt(snapshot?.context?.systemPrompt, signal);
return snapshot?.context && refreshedPrompt !== void 0 ? {
...snapshot,
context: {
...snapshot.context,
systemPrompt: refreshedPrompt,
tools: activeSession.agent.state.tools.slice()
}
} : snapshot;
};
activeSession.agent.prepareNextTurn = prepareNextTurn;
attempt.registerPluginRuntimeRefreshConsumer?.(() => activeSession.agent.prepareNextTurn === prepareNextTurn && activeSession.agent.state.isStreaming && !input.runAbortSignal.aborted);
return (prepare) => {
const previous = permissionPreparation;
permissionPreparation = prepare ? {
prepare,
controller: new AbortController()
} : void 0;
previous?.controller.abort();
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/execution-capacity.ts
const capacityOwner = new AsyncLocalStorage();
/** Keep one task and its owners alive across separately admitted capacity tickets. */
async function withReleasableExecutionCapacity(enqueue, task) {
let suspended = createDeferredCore();
let pending;
let running;
const owner = {
active: false,
outside: async (operation) => {
if (!owner.active) throw new Error("Execution capacity is not held");
owner.active = false;
const continuation = createDeferredCore();
const context = AsyncLocalStorage.snapshot();
let resume;
suspended.resolve({
run: async () => {
try {
const value = await context(operation);
resume = () => continuation.resolve(value);
} catch (error) {
resume = () => continuation.reject(error);
}
},
resume: () => resume(),
reject: continuation.reject
});
return continuation.promise;
}
};
try {
for (;;) {
const result = await enqueue(async () => {
suspended = createDeferredCore();
owner.active = true;
running ??= capacityOwner.run(owner, async () => task());
if (pending) {
pending.resume();
pending = void 0;
}
return await Promise.race([running.then((value) => ({
kind: "complete",
value
})), suspended.promise.then((wait) => ({
kind: "suspended",
wait
}))]);
});
if (result.kind === "complete") return result.value;
pending = result.wait;
await pending.run();
}
} catch (error) {
owner.active = false;
if (!pending) {
running?.catch(() => {});
throw error;
}
pending.reject(error);
await running?.catch(() => {});
throw error;
} finally {
owner.active = false;
}
}
/** Suspend only the designated wait; continuation and cleanup reacquire capacity. */
function outsideExecutionCapacity(operation) {
const owner = capacityOwner.getStore();
return owner ? owner.outside(operation) : operation();
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-session-projection-recovery.ts
/** Retry one admitted open after its projection rebuild, outside transcript write custody. */
async function openSessionAfterProjectionRecovery(input) {
input.signal.throwIfAborted();
try {
return await input.open();
} catch (error) {
if (!isSessionTranscriptProjectionUnavailableError(error) || error.reason !== "rebuilding" || error.sessionId !== input.target.sessionId) throw error;
input.assertCurrent();
input.signal.throwIfAborted();
input.replyOperation?.markWaitingForDeferredMaintenance();
try {
await outsideExecutionCapacity(async () => {
const deadline = new AbortController();
const recoveryBudgetMs = input.timeoutMs >= 2147e6 ? DEFAULT_AGENT_TIMEOUT_MS : Math.max(1, input.timeoutMs);
const timer = setTimeout(() => deadline.abort(error), recoveryBudgetMs);
timer.unref();
try {
await recoverSessionTranscriptProjection(input.target, AbortSignal.any([input.signal, deadline.signal]), input.assertCurrent);
} finally {
clearTimeout(timer);
}
});
} catch (waitError) {
input.assertCurrent();
input.signal.throwIfAborted();
if (waitError === error || waitError instanceof Error && waitError.name === "AbortError" && waitError.cause === error) throw error;
throw waitError;
} finally {
input.replyOperation?.markDeferredMaintenanceWaitEnded();
}
input.assertCurrent();
input.signal.throwIfAborted();
return await input.open();
}
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-user-transcript-context-registry.ts
/** Retains attempt-local runtime-to-transcript pairs across queued user turns. */
function createUserTranscriptContextRegistry() {
const contexts = [];
const upsert = (runtimeMessage, transcriptMessage) => {
const context = {
runtimeMessage,
transcriptMessage
};
const existingIndex = contexts.findIndex((candidate) => candidate.runtimeMessage === runtimeMessage);
if (existingIndex === -1) contexts.push(context);
else contexts[existingIndex] = context;
};
return {
clear: () => {
contexts.length = 0;
},
list: (latestRuntimeMessage, latestTranscriptMessage) => {
if (latestRuntimeMessage && latestTranscriptMessage) upsert(latestRuntimeMessage, latestTranscriptMessage);
return contexts;
},
record: upsert
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/message-tool-terminal.ts
function argsRecordForToolCall(context) {
return asOptionalRecord(context.args) ?? asOptionalRecord(context.toolCall.arguments) ?? {};
}
/**
* Ends the turn after a tool batch settles in which a `canDeliverSourceReply` tool
* authored a final source reply. The host delivers that reply itself, so another
* model turn would only restate it. The decision runs once per assistant message,
* after every call (capable or not, executed or rejected) has settled, so no sibling
* outcome can reopen the turn. It admits exactly what the tool completion handler
* captures: a direct, non-error result with a deliverable final reply.
*/
function installToolAuthoredSourceReplyTerminalHook(params) {
const capableToolNames = params.sourceReplyCapableToolNames;
if (!capableToolNames?.size) return;
const previous = getInternalToolTurnCompletion(params.agent);
setInternalToolTurnCompletion(params.agent, (context) => previous?.(context) === true || context.toolResults.some((toolResult) => !toolResult.isError && !isToolResultError(toolResult) && capableToolNames.has(normalizeToolPolicyName(toolResult.toolName)) && extractToolAuthoredSourceReplyPayload(toolResult) !== void 0));
}
function installMessageToolOnlyTerminalHook(params) {
if (params.sourceReplyDeliveryMode !== "message_tool_only") return;
const previousAfterToolCall = params.agent.afterToolCall?.bind(params.agent);
params.agent.afterToolCall = async (context, signal) => {
const hookResult = await previousAfterToolCall?.(context, signal);
const toolName = context.toolCall.name;
const toolArgs = argsRecordForToolCall(context);
const extractionArgs = toolName === "message" && params.currentProvider && typeof toolArgs.provider !== "string" && typeof toolArgs.channel !== "string" ? {
...toolArgs,
provider: params.currentProvider
} : toolArgs;
const pendingSend = extractMessagingToolSend(toolName, extractionArgs, params);
const confirmedSend = pendingSend && extractMessagingToolSendResult(pendingSend, context.result);
const deliveryFact = readEmbeddedMessageDeliveryFact(readToolResultDetails(context.result)?.messageDelivery);
const isError = hookResult?.isError ?? context.isError;
if (isDeliveredMessageToolOnlySourceReplyResult({
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
toolName,
args: toolArgs,
result: hookResult ?? context.result,
hookResult: context.result,
isError,
allowExplicitSourceRoute: isDeliveredMessagingToolSendToCurrentSource({
...params,
send: confirmedSend,
deliveredPayload: context.result
}),
...deliveryFact ? { deliveryConfirmed: deliveryFact.status === "settled" && (!isError || deliveryFact.partialDelivery) } : {}
})) {
params.onDeliveredSourceReply?.();
if (resolveMessageToolSourceReplyFinal(argsRecordForToolCall(context))) return {
...hookResult,
terminate: true
};
}
return hookResult;
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/session-context-limits.ts
/** Attempts and recovery must hydrate the same bounded model-context view. */
function resolveEmbeddedSessionContextLimits(contextTokenBudget) {
return {
maxBytes: Math.min(67108864, Math.max(1024, (contextTokenBudget ?? 128e3) * 8)),
maxEvents: 1e4
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/tool-activity-heartbeat.ts
async function withEmbeddedAttemptToolActivity(runId, operation) {
const interval = setInterval(() => notifyToolActivity(runId), 6e4);
interval.unref?.();
try {
notifyToolActivity(runId);
return await operation();
} finally {
clearInterval(interval);
notifyToolActivity(runId);
}
}
function wrapEmbeddedAttemptToolWithActivity(tool, runId) {
const originalExecute = tool.execute;
const wrappedTool = {
...tool,
execute: ((...args) => withEmbeddedAttemptToolActivity(runId, () => originalExecute(...args)))
};
copyAgentToolMetadata(tool, wrappedTool, (source) => wrapEmbeddedAttemptToolWithActivity(source, runId));
const sourcePreparer = getInternalToolExecutionPreparer(tool);
if (sourcePreparer) attachInternalToolExecutionPreparer(wrappedTool, async (params) => {
const prepared = await withEmbeddedAttemptToolActivity(runId, () => sourcePreparer(params));
return prepared.kind === "ready" ? {
...prepared,
execute: (start) => withEmbeddedAttemptToolActivity(runId, () => prepared.execute(start))
} : prepared;
});
return wrappedTool;
}
//#endregion
//#region src/agents/embedded-agent-runner/run/tool-loop-recovery.ts
/** Build the embedded-runner's private bridge into agent-core loop recovery. */
function createToolLoopBatchAdmission(ctx) {
return async ({ calls }) => {
const canonicalCalls = calls.map((call) => ({
...call,
args: call.tool ? normalizeCodeModeExecBeforeHookParams({
tool: call.tool,
params: call.args
}) : call.args
}));
try {
const { commitReadyCalls, releaseSkippedCalls, ...result } = await admitToolCallBatch(canonicalCalls, ctx);
return attachInternalToolBatchLifecycle(result, {
commitReadyCalls,
releaseSkippedCalls,
observeOutcome(outcome, state) {
const tool = canonicalCalls.find((call) => call.toolCall.id === outcome.toolCall.id)?.tool;
return observeRepeatedToolError({
...outcome,
args: tool ? normalizeCodeModeExecBeforeHookParams({
tool,
params: outcome.args
}) : outcome.args
}, state);
}
});
} catch (error) {
const first = canonicalCalls[0];
log$6.error(`tool-loop batch admission failed: ${String(error)}`);
return first ? { intervention: {
kind: "critical-tool-loop",
toolCallId: first.toolCall.id,
toolName: first.toolCall.name,
actionKey: hashToolCall(first.toolCall.name, first.args),
detector: "loop_admission_failure",
count: 1,
reason: "Tool execution was blocked because loop safety checks failed."
} } : void 0;
}
};
}
/** Ensure calls blocked by later policies cannot leave run-scoped admission markers behind. */
function installToolLoopRecoveryCleanup(params) {
params.agent.subscribe((event) => {
if (event.type === "agent_end") clearBatchAdmittedToolCallsForRun(params.runId);
});
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-session-prepare.ts
async function prepareEmbeddedAttemptAgentSession(input) {
const { attempt, prepareSystemPromptUpdate } = input;
const settingsManager = createPreparedEmbeddedAgentSettingsManager({
cwd: input.effectiveCwd,
agentDir: input.agentDir,
cfg: attempt.config,
pluginMetadataSnapshot: input.getCurrentAttemptPluginMetadataSnapshot(),
contextTokenBudget: attempt.contextTokenBudget
});
applyAgentAutoCompactionGuard({
settingsManager,
contextEngineInfo: input.activeContextEngineInfo,
compactionMode: resolveEffectiveCompactionMode(attempt.config),
silentOverflowProneProvider: isSilentOverflowProneModel({
provider: attempt.provider,
modelId: attempt.modelId,
baseUrl: attempt.model.baseUrl ?? void 0
}),
compactionForbidden: attempt.operation === "settled-tool-finalization"
});
const extensionFactories = buildEmbeddedExtensionFactories({
cfg: attempt.config,
sessionManager: input.sessionManager,
workspaceDir: input.effectiveWorkspace,
model: attempt.model,
agentId: input.sessionAgentId,
sessionId: attempt.sessionId,
sessionKey: attempt.sessionKey ?? attempt.sandboxSessionKey,
runId: attempt.runId
});
const resourceLoader = new DefaultResourceLoader({
cwd: input.effectiveCwd,
agentDir: input.agentDir,
extensionFactories
});
await resourceLoader.reload();
input.markStage("session-resource-loader");
const hookRunner = getGlobalHookRunner();
const preparedClientTools = prepareEmbeddedAttemptClientTools({
attempt,
...input.clientToolPreparation
});
const { allCustomTools, sessionToolAllowlist, ...clientToolRuntime } = preparedClientTools;
const { session: activeSession } = await createAgentSession({
systemPrompt: input.initialSystemPrompt,
cwd: input.effectiveCwd,
modelRegistry: attempt.modelRegistry,
model: attempt.model,
thinkingLevel: input.agentCoreThinkingLevel,
tools: sessionToolAllowlist,
customTools: allCustomTools,
sessionManager: input.sessionManager,
settingsManager,
resourceLoader,
resolveDeferredTool: input.clientToolPreparation.deferredDirectoryToolsCallable ? ({ toolCall }) => {
const toolAbortSignal = input.clientToolPreparation.getToolAbortSignal?.() ?? input.runAbortSignal;
const tool = resolveToolSearchCatalogTool({
config: attempt.config,
runtimeConfig: attempt.config,
agentId: input.sessionAgentId,
sessionKey: input.clientToolPreparation.sandboxSessionKey,
sessionId: attempt.sessionId,
runId: attempt.runId,
catalogRef: input.clientToolPreparation.toolSearchCatalogRef,
abortSignal: toolAbortSignal
}, toolCall.name);
const definition = tool ? toToolDefinitions([tool], input.clientToolPreparation.catalogToolHookContext, toolAbortSignal)[0] : void 0;
const hydratedTool = definition ? wrapToolDefinition(definition) : void 0;
if (hydratedTool) {
log$6.info(`tool-search: hydrated deferred directory tool ${toolCall.name}`);
const originalExecute = hydratedTool.execute;
hydratedTool.execute = (...args) => withEmbeddedAttemptToolActivity(attempt.runId, () => originalExecute(...args));
}
return hydratedTool;
} : void 0,
withSessionWriteSettlement: (operation) => input.transcriptLifecycle.withTranscriptWrite(operation),
cleanupProviderSessionResourcesOnDispose: false,
contextOverflowRecoveryOwner: attempt.contextTokenBudget === void 0 ? "session" : "caller",
resolveCompactionThinkingLevel: createAttemptCompactionThinkingResolver(attempt, input.sessionAgentId),
beforeToolBatch: input.clientToolPreparation.catalogToolHookContext ? createToolLoopBatchAdmission(input.clientToolPreparation.catalogToolHookContext) : void 0
});
input.onSessionCreated(activeSession);
installToolLoopRecoveryCleanup({
agent: activeSession.agent,
runId: attempt.runId
});
activeSession.setActiveToolsByName(sessionToolAllowlist);
const setActiveSessionSystemPrompt = (nextSystemPrompt) => {
input.onSystemPromptChanged(nextSystemPrompt);
activeSession.setBaseSystemPrompt(nextSystemPrompt.trim());
return nextSystemPrompt;
};
const setPermissionPromptPreparation = installAttemptPermissionPrompt({
activeSession,
attempt,
runAbortSignal: input.runAbortSignal,
setActiveSessionSystemPrompt,
prepareInitialUserTurnReplay: input.prepareInitialUserTurnReplay,
prepareSystemPromptUpdate
});
setActiveSessionSystemPrompt(input.initialSystemPrompt);
let didDeliverSourceReplyViaMessageTool = false;
const markSourceReplyDelivered = () => {
didDeliverSourceReplyViaMessageTool = true;
};
installMessageToolOnlyTerminalHook({
agent: activeSession.agent,
sourceReplyDeliveryMode: attempt.sourceReplyDeliveryMode,
onDeliveredSourceReply: markSourceReplyDelivered,
config: attempt.config,
currentProvider: attempt.messageChannel ?? attempt.messageProvider,
currentAccountId: attempt.agentAccountId,
currentChannelId: attempt.currentChannelId,
currentMessagingTarget: attempt.currentMessagingTarget,
currentThreadId: attempt.currentThreadTs,
currentMessageId: attempt.currentMessageId,
replyToMode: attempt.replyToMode,
hasRepliedRef: attempt.hasRepliedRef,
sessionKey: attempt.sessionKey
});
installToolAuthoredSourceReplyTerminalHook({
agent: activeSession.agent,
sourceReplyCapableToolNames: clientToolRuntime.sourceReplyCapableToolNames
});
input.markStage("agent-session");
return {
activeSession,
allCustomTools,
...clientToolRuntime,
hasDeliveredSourceReply: () => didDeliverSourceReplyViaMessageTool,
hookRunner,
markSourceReplyDelivered,
setActiveSessionSystemPrompt,
settingsManager,
refreshTools: () => {
const currentPrompt = activeSession.agent.state.systemPrompt;
preparedClientTools.refreshTools();
activeSession.replaceCustomTools(allCustomTools, sessionToolAllowlist);
setActiveSessionSystemPrompt(currentPrompt);
},
setPermissionPromptPreparation
};
}
async function prepareEmbeddedAttemptSessionBoundary(input) {
const { activeSession, attempt, isRawModelRun, sessionManager } = input;
setSteeringRuntimeContextRetention(activeSession, input.appendOnlyRuntimeContext === true);
const preserveExactPrompt = isRawModelRun || attempt.operation === "settled-tool-finalization";
if (isRawModelRun) {
activeSession.agent.reset();
input.setActiveSessionSystemPrompt("");
}
let repairedTarget;
const orphanRepair = preserveExactPrompt ? void 0 : await withSessionManagerWrite(sessionManager, async () => {
input.abortSignal?.throwIfAborted();
const target = sessionManager.getSessionTarget();
const reader = target && getOwnedSessionTranscriptReader(target);
reader?.assertCurrent();
if (reader && reconcilePrePersistedCurrentUserTurn({
activeSession,
currentUserTurnMessage: attempt.skipPreparedUserTurnMessage ? void 0 : attempt.userTurnTranscriptRecorder?.getPersistedMessage?.() ?? input.preparedUserTurnMessage,
durableUserTurnMessage: void 0,
userTurnAlreadyPersisted: attempt.userTurnTranscriptRecorder?.hasPersisted() === true
})) return;
await sessionManager.reloadPersistedTranscriptAsync(input.abortSignal);
input.abortSignal?.throwIfAborted();
const candidate = resolveOrphanRepairPlan({
sessionManager,
prompt: attempt.prompt,
preserveLeaf: attempt.skipPreparedUserTurnMessage === true || isMainSessionRestartRecoveryInputProvenance(attempt.inputProvenance)
});
const currentUserTurnMessage = attempt.skipPreparedUserTurnMessage ? void 0 : attempt.userTurnTranscriptRecorder?.getPersistedMessage?.() ?? input.preparedUserTurnMessage;
const reconciledCurrentUser = reconcilePrePersistedCurrentUserTurn({
activeSession,
currentUserTurnMessage,
durableUserTurnMessage: candidate?.messageEntry.message,
userTurnAlreadyPersisted: attempt.userTurnTranscriptRecorder?.hasPersisted() === true
});
const orphanProvenance = normalizeInputProvenance(candidate?.messageEntry.message.provenance);
const preserveUnansweredUser = shouldPreserveUserFacingSessionStateForInputProvenance(attempt.inputProvenance) && (!orphanProvenance || orphanProvenance.kind === "external_user");
if (reconciledCurrentUser || preserveUnansweredUser) return;
if (candidate?.removeLeaf) {
if (candidate.messageEntry.parentId) await sessionManager.branchAsync(candidate.messageEntry.parentId);
else await sessionManager.resetLeafAsync();
repairedTarget = sessionManager.getSessionTarget();
if (repairedTarget) await sessionManager.appendLeafControlAsync({
targetId: sessionManager.getLeafId(),
appendParentId: sessionManager.getAppendParentId()
});
await replayTrailingEntriesForOrphanRepair(sessionManager, candidate.trailingEntries);
}
return candidate;
});
if (orphanRepair?.removeLeaf) {
if (repairedTarget) {
const { waitForSessionTranscriptProjection } = await import("./config/sessions/session-transcript-reconcile.js");
await waitForSessionTranscriptProjection(repairedTarget, input.abortSignal);
input.abortSignal?.throwIfAborted();
}
sessionManager.setNextUserMessagePersistence?.("normal");
attempt.onUserMessagePersistenceInvalidated?.();
}
if (orphanRepair) {
const repairedMessages = sanitizeCompactionReplayMessages(sessionManager.buildSessionContext().messages);
activeSession.agent.state.messages = orphanRepair.removeLeaf ? repairedMessages : repairedMessages.slice(0, -1);
}
const boundaryTimezone = preserveExactPrompt ? void 0 : resolveUserTimezone(attempt.config?.agents?.defaults?.userTimezone);
let currentUserTimestampOverride;
const buildBoundaryOptions = () => {
if (preserveExactPrompt) return {
appendOnlyRuntimeContext: input.appendOnlyRuntimeContext,
inHistorySystemUpdates: input.inHistorySystemUpdates,
projectPersistedSenderContext: false
};
const userTranscriptContexts = input.getUserTranscriptContexts();
return {
sessionVersion: sessionManager.getHeader()?.version,
appendOnlyRuntimeContext: input.appendOnlyRuntimeContext,
inHistorySystemUpdates: input.inHistorySystemUpdates,
...boundaryTimezone ? { timezone: boundaryTimezone } : {},
...userTranscriptContexts?.length ? { userTranscriptContexts } : {},
...currentUserTimestampOverride ? { currentUserTimestampOverride } : {}
};
};
const baseConvertToLlm = activeSession.agent.convertToLlm.bind(activeSession.agent);
activeSession.agent.convertToLlm = async (messages) => {
let removedRuntimeContext;
const normalized = normalizeMessagesForLlmBoundary(messages, {
...buildBoundaryOptions(),
onRuntimeContextCarrierRemoved: (removed) => {
removedRuntimeContext = removed;
}
});
const converted = await baseConvertToLlm(input.appendOnlyRuntimeContext ? normalized : relocateCurrentRuntimeContextCarrierToTail(normalized));
applyRuntimeContextCarrierRetention(converted, input.appendOnlyRuntimeContext);
if (!input.appendOnlyRuntimeContext && recordRuntimeContextProjection(attempt.sessionId, removedRuntimeContext, converted)) declarePromptHistoryRewrite({
...attempt,
reason: "runtimeContextCarrier"
});
return converted;
};
return {
getUserTranscriptContexts: input.getUserTranscriptContexts,
boundaryTimezone,
includeBoundaryTimestamp: !preserveExactPrompt,
orphanRepair,
setCurrentUserTimestampOverride: (override) => {
currentUserTimestampOverride = override;
}
};
}
async function prepareEmbeddedAttemptSessionManager(input) {
const { attempt } = input;
const transcriptStateParams = {
sessionManager: attempt.sessionManager,
agentId: input.sessionAgentId,
config: attempt.config,
sessionFile: attempt.sessionFile,
sessionId: attempt.sessionId,
sessionKey: attempt.sessionKey,
sessionTarget: attempt.sessionTarget
};
let transcriptState = attempt.sessionManager ? await resolveExistingAttemptTranscriptState(transcriptStateParams) : void 0;
const apiKey = attempt.model.api === "anthropic-messages" && isDirectAnthropicModel(attempt.model) && supportsClaudeInHistorySystemMessages(attempt.model) ? await resolveEmbeddedAgentApiKey(attempt) : void 0;
const transcriptPolicy = resolveAttemptTranscriptPolicy({
runtimePlan: attempt.runtimePlan,
runtimePlanModelContext: {
workspaceDir: input.effectiveWorkspace,
modelApi: attempt.model.api,
model: attempt.model,
directApiKey: Boolean(apiKey) && !isAnthropicOAuthApiKey(apiKey)
},
provider: attempt.provider,
modelId: attempt.modelId,
config: attempt.config,
env: process.env
});
const isOpenAIResponsesApi = attempt.model.api === "openai-responses" || attempt.model.api === "azure-openai-responses" || attempt.model.api === "openai-chatgpt-responses";
const preparedUserTurnMessage = attempt.skipPreparedUserTurnMessage ? void 0 : await attempt.userTurnTranscriptRecorder?.resolveMessage();
let latestPersistedUserMessage;
let latestRuntimeUserMessage;
let latestUserTurnTranscriptRecorder = attempt.userTurnTranscriptRecorder;
const userTranscriptContextRegistry = createUserTranscriptContextRegistry();
const attachRunMediaFacts = (message, media) => attachRuntimePromptMediaFacts(message, applyRunHydrationSuppression(media, attempt.media));
const openSignal = attempt.abortSignal ? AbortSignal.any([attempt.abortSignal, input.runAbortSignal]) : input.runAbortSignal;
let publishedSessionManager;
let initialReplay;
let messagePresence;
const openSessionManager = (target) => input.withOwnedTranscriptWrite(async () => {
const limits = resolveEmbeddedSessionContextLimits(attempt.contextTokenBudget);
const initial = prepareInitialPersistedUserTurnCohort({
target,
message: preparedUserTurnMessage,
recorder: attempt.userTurnTranscriptRecorder,
runId: attempt.runId
});
if (initial) {
const manager = await SessionManager[sessionManagerOpenTranscriptCohort](target, {
...limits,
cwd: input.effectiveCwd,
signal: openSignal
}, initial.selection, (opened, prepared, assertView) => {
try {
initial.consume(opened, prepared, assertView);
} finally {
publishedSessionManager = opened;
input.onSessionManagerCreated(opened);
}
});
initialReplay = initial.readInitial();
messagePresence = initial.readMessagePresence();
return manager;
}
return SessionManager.openAsync(target, input.effectiveCwd, limits, openSignal);
});
const sessionTarget = attempt.sessionTarget;
const openedSessionManager = !attempt.sessionManager && sessionTarget ? await openSessionAfterProjectionRecovery({
open: () => openSessionManager(sessionTarget),
target: sessionTarget,
timeoutMs: attempt.timeoutMs,
signal: openSignal,
assertCurrent: input.assertCurrent,
replyOperation: attempt.replyOperation
}) : void 0;
const unguardedSessionManager = attempt.sessionManager ?? openedSessionManager ?? SessionManager.inMemory(input.effectiveCwd);
if (publishedSessionManager !== unguardedSessionManager) input.onSessionManagerCreated(unguardedSessionManager);
transcriptState ??= messagePresence === void 0 ? await resolveExistingAttemptTranscriptState(transcriptStateParams) : { hasBootstrapTranscriptState: messagePresence };
const preparedReplay = await input.withOwnedTranscriptWrite(() => preparePersistedCurrentUserTurn({
sessionManager: unguardedSessionManager,
message: preparedUserTurnMessage,
recorder: attempt.userTurnTranscriptRecorder,
runId: attempt.runId,
signal: attempt.abortSignal,
initial: initialReplay
}));
const prepareInitialUserTurnReplay = preparedReplay && (async (signal) => {
const consume = await input.withOwnedTranscriptWrite(() => preparedReplay(signal));
return consume && ((onAdmitted) => input.withOwnedTranscriptWrite(() => consume(onAdmitted)));
});
const sessionManager = guardSessionManager(unguardedSessionManager, {
agentId: input.sessionAgentId,
runId: attempt.runId,
sessionKey: attempt.sessionKey,
config: attempt.config,
contextWindowTokens: attempt.contextTokenBudget,
inputProvenance: attempt.inputProvenance,
preparedUserTurnMessage,
preparedUserTurnTranscriptRecorder: preparedUserTurnMessage ? attempt.userTurnTranscriptRecorder : void 0,
allowSyntheticToolResults: transcriptPolicy.allowSyntheticToolResults,
missingToolResultText: isOpenAIResponsesApi ? "aborted" : void 0,
allowedToolNames: input.replayAllowedToolNames,
trigger: attempt.trigger,
suppressNextUserMessagePersistence: prepareInitialUserTurnReplay !== void 0 || attempt.suppressNextUserMessagePersistence,
suppressTranscriptOnlyAssistantPersistence: attempt.suppressTranscriptOnlyAssistantPersistence,
assistantErrorTranscript: attempt.assistantErrorTranscript,
skipBeforeMessageWriteHooks: attempt.operation === "settled-tool-finalization",
prepareAssistantTranscriptMessage: attempt.prepareAssistantTranscriptMessage,
onUserMessagePreparingForPersistence: (_message, recorder) => {
latestPersistedUserMessage = void 0;
latestUserTurnTranscriptRecorder = recorder;
},
onUserMessagePersisted: (message, runtimeMessage) => {
latestPersistedUserMessage = message;
latestRuntimeUserMessage = runtimeMessage;
if (runtimeMessage) {
const media = readPersistedMediaFacts(message);
if (media?.length) attachRunMediaFacts(runtimeMessage, media);
userTranscriptContextRegistry.record(runtimeMessage, message);
}
attempt.onUserMessagePersisted?.(message);
},
onUserMessagePersistenceSuppressed: (message, runtimeMessage) => {
latestRuntimeUserMessage = runtimeMessage;
const media = runtimeMessage ? readPersistedMediaFacts(message) : void 0;
if (runtimeMessage && media?.length) attachRunMediaFacts(runtimeMessage, media);
if (prepareInitialUserTurnReplay !== void 0 && !attempt.suppressNextUserMessagePersistence && attempt.userTurnTranscriptRecorder?.hasPersisted() === true) attempt.onUserMessagePersisted?.(message);
},
onUserMessageBlocked: () => {
attempt.userTurnTranscriptRecorder?.markBlocked();
}
});
attempt.promptCacheKey = resolveSessionBoundaryPromptCacheKey({
api: attempt.model.api,
boundaryCount: sessionManager.getBoundaryCount(),
promptCacheKey: attempt.promptCacheKey,
sessionId: attempt.sessionPersistence === "detached" && attempt.sessionManager && !attempt.sessionManager.getSessionTarget() ? attempt.sessionManager.getSessionId() : attempt.sessionId
});
await input.withOwnedTranscriptWrite(async () => {
await bootstrapHarnessContextEngine({
admittedRunContext: attempt.admittedRunContext,
hadSessionFile: transcriptState.hasBootstrapTranscriptState,
contextEngine: input.activeContextEngine,
sessionId: attempt.sessionId,
sessionKey: attempt.sessionKey,
sessionTarget: attempt.sessionTarget,
sessionFile: attempt.sessionFile,
sessionManager,
transcriptReadFence: attempt.userTurnTranscriptRecorder?.getAdmissionReceipt(),
runtimeContext: buildAfterTurnRuntimeContext({
attempt,
workspaceDir: input.effectiveWorkspace,
cwd: input.effectiveCwd,
agentDir: input.agentDir,
tokenBudget: attempt.contextTokenBudget,
activeAgentId: input.sessionAgentId,
contextEnginePluginId: input.resolveActiveContextEnginePluginId()
}),
contextEngineHostSupport: OPENCLAW_EMBEDDED_CONTEXT_ENGINE_HOST,
providerId: attempt.provider,
requestedModelId: attempt.requestedModelId,
modelId: attempt.modelId,
fallbackReason: attempt.fallbackReason,
degradedReason: attempt.degradedReason,
runMaintenance: async (contextParams) => await runContextEngineMaintenance({
...contextParams,
sessionManager: contextParams.sessionManager,
config: attempt.config,
agentId: input.sessionAgentId,
contextEngineAgentId: attempt.contextEngineAgentId
}),
warn: (message) => log$6.warn(message)
});
});
latestPersistedUserMessage = void 0;
latestRuntimeUserMessage = void 0;
userTranscriptContextRegistry.clear();
return {
prepareInitialUserTurnReplay,
userMessageBoundary: {
getUserTranscriptContexts: () => {
const transcriptMessage = latestPersistedUserMessage ?? latestUserTurnTranscriptRecorder?.getPersistedMessage?.();
const runtimeMessage = latestRuntimeUserMessage ?? (attempt.suppressNextUserMessagePersistence ? transcriptMessage : void 0);
return userTranscriptContextRegistry.list(runtimeMessage, transcriptMessage);
},
preparedUserTurnMessage
},
isOpenAIResponsesApi,
preparedUserTurnMessage,
sessionManager,
transcriptPolicy
};
}
//#endregion
//#region src/agents/exec-auto-review-transcript.ts
const DEFAULT_LIMITS = {
userAssistantChars: 4e3,
toolChars: 1e3,
nonUserEntries: 40,
totalChars: 24e3
};
const OMITTED_FIELD = /^(?:details|media|(?:input_|output_)?(?:images?|image_url|audio|video)|(?:input_|output_)(?:file|document)|(?:image|audio|video|file|document)[_-]?(?:base64|bytes|data)|inline[_-]?data|base64|b64_json|blob|buffer|bytes|(?:source)?session[_-]?key|(?:origin)?session[_-]?id|(?:sender|peer|profile)[_-]?id)$/i;
function omitPayloads(key, value) {
if (OMITTED_FIELD.test(key)) return;
if (typeof value === "string" && /^data:[^\s,]*,/i.test(value.trimStart())) return "[media omitted]";
const record = asOptionalRecord(value);
return record && (/^(?:(?:input_|output_)?(?:image|image_url|audio|video|file|document)|media|base64|buffer)$/i.test(String(record.type)) || record.encoding === "base64" || [
"mimeType",
"mime_type",
"mediaType",
"media_type",
"contentType",
"content_type"
].some((field) => typeof record[field] === "string") && (record.data !== void 0 || record.content !== void 0)) ? void 0 : value;
}
function textOnly(value) {
let text = value;
if (/^\s*[[{]/.test(text)) try {
text = JSON.stringify(JSON.parse(text), omitPayloads) ?? "[media omitted]";
} catch {}
return text.replace(/\bdata:[^\s,]*,[^\s"'<>]*/gi, "[media omitted]");
}
function userOrigin(message) {
const provenance = normalizeInputProvenance(Reflect.get(message, "provenance"));
if (provenance?.kind === "inter_session" || provenance?.kind === "internal_system") return provenance.kind;
const metadata = asOptionalRecord(Reflect.get(message, "__openclaw"));
const identity = readTranscriptSenderIdentity(metadata?.senderIdentity);
if (metadata?.senderIsOwner === true || identity?.type === "profile") return "operator";
return identity?.type === "observation" ? "channel" : "unknown";
}
/** Projects current live messages into bounded, redacted reviewer evidence. */
function buildExecAutoReviewTranscript(params) {
const limit = (key, minimum = 0) => {
const value = params.limits?.[key];
return value === void 0 || !Number.isFinite(value) ? DEFAULT_LIMITS[key] : Math.max(minimum, Math.min(DEFAULT_LIMITS[key], Math.floor(value)));
};
const limits = {
userAssistantChars: limit("userAssistantChars"),
toolChars: limit("toolChars"),
nonUserEntries: limit("nonUserEntries"),
totalChars: limit("totalChars", 256)
};
const messages = params.messages.map((message) => message.role === "user" ? params.userTurnOrigins?.get(message) ?? message : message);
const identifiers = /* @__PURE__ */ new Set();
for (const message of messages) {
if (message.role !== "user") continue;
const metadata = asOptionalRecord(Reflect.get(message, "__openclaw"));
const identity = readTranscriptSenderIdentity(metadata?.senderIdentity);
const provenance = normalizeInputProvenance(Reflect.get(message, "provenance"));
for (const identifier of [
metadata?.senderId,
identity?.id,
provenance?.sourceSessionKey,
provenance?.originSessionId
]) if (typeof identifier === "string" && identifier) {
identifiers.add(identifier);
identifiers.add(JSON.stringify(identifier).slice(1, -1));
}
}
const privateIdentifiers = [...identifiers].toSorted((a, b) => b.length - a.length);
const scrubIdentifiers = (text) => privateIdentifiers.reduce((result, identifier) => result.replaceAll(identifier, "[identity omitted]"), text);
const entries = [];
const toolIds = /* @__PURE__ */ new Map();
const add = (entry) => {
const cap = entry.kind === "user" || entry.kind === "assistant" ? limits.userAssistantChars : limits.toolChars;
const text = scrubIdentifiers(textOnly(entry.text));
if (entry.toolCallId) {
let alias = toolIds.get(entry.toolCallId);
if (!alias) {
alias = `tool-${toolIds.size + 1}`;
toolIds.set(entry.toolCallId, alias);
}
entry.toolCallId = alias;
}
const toolName = entry.toolName && scrubIdentifiers(entry.toolName);
entries.push({
...entry,
text: truncateUtf16Safe(text, cap),
...toolName === void 0 ? {} : { toolName: truncateUtf16Safe(toolName, 256) },
...text.length > cap || (toolName?.length ?? 0) > 256 ? { truncated: true } : {}
});
};
for (const source of messages) {
const message = redactTranscriptMessage(source, params.config);
if (message.role === "user" || message.role === "toolResult") {
const text = typeof message.content === "string" ? message.content : message.content.flatMap((block) => block.type === "text" ? [textOnly(block.text)] : []).join("\n");
if (text) add(message.role === "user" ? {
kind: "user",
text,
origin: userOrigin(source)
} : {
kind: "tool_result",
text,
toolName: message.toolName,
toolCallId: message.toolCallId
});
} else if (message.role === "assistant") {
for (const block of message.content) if (block.type === "text" && block.text) add({
kind: "assistant",
text: block.text
});
else if (block.type === "toolCall") add({
kind: "tool_call",
text: JSON.stringify(block.arguments, omitPayloads) ?? "[media omitted]",
toolName: block.name,
toolCallId: block.id
});
}
}
const envelopeChars = estimateStringChars(JSON.stringify({
entries: [],
omittedEntries: entries.length,
truncated: false
}));
const budget = Math.max(0, limits.totalChars - envelopeChars);
const candidates = entries.map((entry) => ({
entry,
cost: estimateStringChars(JSON.stringify(entry)) + 1
}));
const selected = /* @__PURE__ */ new Set();
let used = 0;
let nonUsers = 0;
const anchors = [.../* @__PURE__ */ new Set([candidates.find(({ entry }) => entry.kind === "user"), candidates.findLast(({ entry }) => entry.kind === "user")])].filter((candidate) => candidate !== void 0);
for (const candidate of anchors) {
const { entry } = candidate;
const allowance = Math.floor(budget / anchors.length);
if (candidate.cost > allowance) {
const original = entry.text;
entry.truncated = true;
let low = 0;
let high = original.length;
while (low < high) {
const mid = Math.ceil((low + high) / 2);
entry.text = truncateUtf16Safe(original, mid);
if (estimateStringChars(JSON.stringify(entry)) + 1 <= allowance) low = mid;
else high = mid - 1;
}
entry.text = truncateUtf16Safe(original, low);
candidate.cost = estimateStringChars(JSON.stringify(entry)) + 1;
}
selected.add(candidate);
used += candidate.cost;
}
const pendingCalls = /* @__PURE__ */ new Map();
const pairs = /* @__PURE__ */ new Map();
for (const candidate of candidates) {
const { entry } = candidate;
if (entry.toolCallId && entry.kind === "tool_call") pendingCalls.set(entry.toolCallId, candidate);
else if (entry.toolCallId && entry.kind === "tool_result") {
const call = pendingCalls.get(entry.toolCallId);
if (call !== void 0) {
pairs.set(candidate, call);
pairs.set(call, candidate);
pendingCalls.delete(entry.toolCallId);
}
}
}
const visited = /* @__PURE__ */ new Set();
for (const candidate of candidates.toReversed()) {
if (selected.has(candidate) || visited.has(candidate)) continue;
const pair = pairs.get(candidate);
const group = pair === void 0 ? [candidate] : [candidate, pair];
group.forEach((member) => visited.add(member));
const nonUserCount = group.filter(({ entry }) => entry.kind !== "user").length;
const cost = group.reduce((sum, member) => sum + member.cost, 0);
if (used + cost > budget || nonUsers + nonUserCount > limits.nonUserEntries) continue;
group.forEach((member) => selected.add(member));
used += cost;
nonUsers += nonUserCount;
}
const retained = candidates.filter((candidate) => selected.has(candidate)).map(({ entry }) => entry);
const omittedEntries = entries.length - retained.length;
return {
entries: retained,
omittedEntries,
truncated: omittedEntries > 0 || retained.some((entry) => entry.truncated === true)
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-session-settle.ts
function createEmbeddedAttemptSessionSettleTracker(activeSession) {
const inFlight = /* @__PURE__ */ new Set();
let abortCleanupFailed = false;
const recordAbortCleanupFailure = () => {
if (abortCleanupFailed) recordAgentCleanupFailure();
};
const trackSettlePromise = (promise) => {
inFlight.add(promise);
const settled = () => {
inFlight.delete(promise);
};
promise.then(settled, settled);
return promise;
};
return {
abortActiveSession: (reason) => trackSettlePromise(Promise.resolve(activeSession.abort(reason)).catch((error) => {
abortCleanupFailed = true;
throw error;
})),
buildAbortSettlePromise: () => {
recordAbortCleanupFailure();
return inFlight.size === 0 ? null : Promise.allSettled(inFlight).then(recordAbortCleanupFailure);
},
trackPromptSettlePromise: trackSettlePromise
};
}
/** Keep retained review callbacks outside the attempt's tool-execution closure scope. */
function createEmbeddedAttemptSessionResources(config, signal) {
const resources = {
trajectoryRecorder: null,
buildAbortSettlePromise: () => null
};
let live = resources;
return {
resources,
reviewTranscript: () => {
if (!live?.session || signal.aborted) return;
return buildExecAutoReviewTranscript({
config,
messages: live.session.messages,
userTurnOrigins: new Map(live.getUserTranscriptContexts?.()?.map(({ runtimeMessage, transcriptMessage }) => [runtimeMessage, transcriptMessage]))
});
},
releaseReview: () => {
live = void 0;
}
};
}
async function cleanupEmbeddedAttemptSessionPhase(input) {
try {
var _usingCtx$1 = _usingCtx();
_usingCtx$1.u(input.promptStateLease);
const { attempt } = input;
const initialState = projectAgentRunAttemptTerminal(input.state.terminal);
if (input.trajectoryRecorder && !input.trajectoryEndRecorded) {
const sessionEndData = {
status: initialState.promptError ? "error" : initialState.aborted || initialState.timedOut ? "interrupted" : "cleanup",
aborted: initialState.aborted,
externalAbort: initialState.externalAbort,
timedOut: initialState.timedOut,
idleTimedOut: initialState.idleTimedOut,
timedOutDuringCompaction: initialState.timedOutDuringCompaction,
timedOutDuringToolExecution: initialState.timedOutDuringToolExecution,
timedOutByRunBudget: initialState.timedOutByRunBudget,
promptError: initialState.promptError ? formatErrorMessage(initialState.promptError) : void 0
};
if (input.deferredLifecycleOwner) input.deferredLifecycleOwner.recordSessionEnd(sessionEndData);
else input.trajectoryRecorder.recordEvent("session.ended", sessionEndData);
}
await flushEmbeddedAttemptTrajectoryRecorder({
runId: attempt.runId,
sessionId: attempt.sessionId,
log: log$6,
trajectoryRecorder: input.trajectoryRecorder
});
let cleanupError;
try {
clearToolSearchCatalog({ catalogRef: input.toolSearchCatalogRef });
await input.transcriptLifecycle.beginCleanup();
const cleanupState = projectAgentRunAttemptTerminal(input.state.terminal);
const cleanupAborted = Boolean(attempt.abortSignal?.aborted) || cleanupState.aborted || cleanupState.timedOut || cleanupState.idleTimedOut || cleanupState.timedOutDuringCompaction;
const cleanupAbortLike = cleanupAborted || initialState.cleanupYieldAborted;
await cleanupEmbeddedAttemptResources({
...input,
sessionManager: input.sessionManager,
flushPendingToolResultsAfterIdle,
aborted: cleanupAbortLike,
abortSignal: attempt.abortSignal,
abortSettlePromise: cleanupAborted ? input.buildAbortSettlePromise() : null,
runId: attempt.runId,
sessionId: attempt.sessionId
});
} catch (err) {
recordAgentCleanupFailure();
cleanupError = err;
} finally {
try {
await input.transcriptLifecycle.dispose();
} catch (err) {
recordAgentCleanupFailure();
cleanupError ??= err;
}
}
const finalState = projectAgentRunAttemptTerminal(input.state.terminal);
const beforeAgentRunBlocked = input.state.beforeAgentRunBlockedBy !== void 0;
const diagnosticTerminalAborted = finalState.aborted || finalState.timedOut || finalState.idleTimedOut;
input.emitDiagnosticRunCompleted?.(cleanupError ? "error" : beforeAgentRunBlocked ? "blocked" : finalState.promptError ? "error" : diagnosticTerminalAborted ? "aborted" : "completed", cleanupError ?? finalState.promptError, beforeAgentRunBlocked ? { blockedBy: input.state.beforeAgentRunBlockedBy } : void 0);
if (cleanupError) await Promise.reject(toErrorObject(cleanupError, "Non-Error rejection"));
} catch (_) {
_usingCtx$1.e = _;
} finally {
_usingCtx$1.d();
}
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-trajectory.ts
async function prepareEmbeddedAttemptTrajectory(input) {
const { activeSession, attempt } = input;
const candidate = input.transcriptOwner?.sessionTarget;
const sessionKey = candidate?.sessionKey;
const assertCommitAllowed = input.transcriptOwner?.assertCommitAllowed;
const initialWriter = input.transcriptOwner?.initialWriter;
const retained = candidate && sessionKey && assertCommitAllowed && candidate.agentId === input.sessionAgentId && candidate.sessionId === activeSession.sessionId && sessionKey === attempt.sessionKey && candidate.storePath && candidate.storePath === attempt.sessionTarget?.storePath ? {
sessionKey,
assertCurrent() {
assertCommitAllowed();
initialWriter?.assertActive();
}
} : void 0;
retained?.assertCurrent();
const trajectorySessionFile = retained ? retained.sessionKey : await resolveAttemptTrajectorySessionFile({
agentId: input.sessionAgentId,
config: attempt.config,
sessionFile: attempt.sessionFile,
sessionId: activeSession.sessionId,
sessionKey: attempt.sessionKey,
sessionTarget: attempt.sessionTarget
});
if (attempt.disableTrajectory || attempt.sessionPersistence === "detached") return null;
const assertActive = resolveAdmittedRunActiveAssertion(attempt.admittedRunContext, attempt.abortSignal);
if (!assertActive) throw new Error("trajectory preparation requires an active admitted run");
assertActive();
retained?.assertCurrent();
const { sessionTarget } = projectTrajectorySessionTarget(attempt.sessionTarget);
const recorder = await createTrajectoryRuntimeRecorder({
cfg: attempt.config,
env: process.env,
runId: attempt.runId,
sessionId: activeSession.sessionId,
sessionKey: attempt.sessionKey,
sessionFile: trajectorySessionFile,
sessionTarget,
provider: attempt.provider,
modelId: attempt.modelId,
modelApi: attempt.model.api,
workspaceDir: attempt.workspaceDir
});
assertActive();
retained?.assertCurrent();
recorder?.recordEvent("session.started", {
trigger: attempt.trigger,
sessionFile: attempt.sessionFile,
workspaceDir: input.effectiveWorkspace,
agentId: input.sessionAgentId,
messageProvider: attempt.messageProvider,
messageChannel: attempt.messageChannel,
localModelLean: input.localModelLeanEnabled,
toolCount: input.effectiveToolCount,
clientToolCount: input.clientToolCount
});
const fastMode = typeof attempt.fastMode === "boolean" ? attempt.fastMode : void 0;
recorder?.recordEvent("trace.metadata", buildTrajectoryRunMetadata({
env: process.env,
config: attempt.config,
...attempt.preparedModelRuntime?.metadataSnapshot ? { pluginMetadataSnapshot: attempt.preparedModelRuntime.metadataSnapshot } : {},
workspaceDir: input.effectiveWorkspace,
sessionFile: attempt.sessionFile,
sessionKey: attempt.sessionKey,
agentId: input.sessionAgentId,
trigger: attempt.trigger,
messageProvider: attempt.messageProvider,
messageChannel: attempt.messageChannel,
provider: attempt.provider,
modelId: attempt.modelId,
modelApi: attempt.model.api,
timeoutMs: attempt.timeoutMs,
fastMode,
thinkLevel: attempt.thinkLevel,
reasoningLevel: attempt.reasoningLevel,
toolResultFormat: attempt.toolResultFormat,
disableTools: attempt.disableTools,
toolsAllow: attempt.toolsAllow,
skillsSnapshot: attempt.skillsSnapshot,
systemPromptReport: input.systemPromptReport
}));
return recorder;
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-session-runtime-prepare.ts
async function prepareEmbeddedAttemptSessionRuntime(input) {
const { attempt, resources, runAbortSignal, sessionLock, toolBase } = input;
const { agentCoreThinkingLevel, effectiveCwd, effectiveFsWorkspaceOnly, effectiveWorkspace, getCurrentAttemptPluginMetadataSnapshot, getProviderRuntimeHandle, prepStages, providerThinkingLevel, sandbox, sandboxSessionKey, sessionAgentId } = input.setup;
const { catalogToolHookContext, deferredDirectoryToolsCallable, effectiveTools, toolSearchRunPlan } = input.toolCatalog;
const { clientTools, uncompactedEffectiveTools } = input.bundleTools;
const { codeModeControlsEnabledForRun, computerContextEpoch, localModelLeanEnabled, replaySafetyOptions, toolSearchCatalogRef, toolSearchRuntimeConfig } = toolBase;
const { systemPromptReport, systemPromptText } = input.systemPrompt;
const effectiveToolCount = effectiveTools.length;
const sessionPreparation = {
attempt,
...input.activeContextEngine ? { activeContextEngine: input.activeContextEngine } : {},
agentDir: input.agentDir,
effectiveCwd,
effectiveWorkspace,
sessionAgentId
};
const preparedSessionManager = await prepareEmbeddedAttemptSessionManager({
...sessionPreparation,
assertCurrent: () => {
runAbortSignal.throwIfAborted();
sessionLock.ownedTranscriptWriteContext.assertCommitAllowed?.();
sessionLock.ownedTranscriptWriteContext.initialWriter?.assertActive();
},
runAbortSignal,
onSessionManagerCreated: (manager) => {
resources.sessionManager = manager;
},
replayAllowedToolNames: toolSearchRunPlan.replayAllowedToolNames,
resolveActiveContextEnginePluginId: input.resolveActiveContextEnginePluginId,
withOwnedTranscriptWrite: sessionLock.withOwnedTranscriptWrite
});
const { isOpenAIResponsesApi, preparedUserTurnMessage, sessionManager, transcriptPolicy } = preparedSessionManager;
if (codeModeControlsEnabledForRun && toolSearchCatalogRef) bindCodeModeSessionStore(toolSearchCatalogRef, sessionManager, sessionLock.withOwnedTranscriptWrite);
const promptStateLease = retainEmbeddedSessionPromptState(attempt.sessionId);
resources.promptStateLease = promptStateLease;
const sessionPromptState = promptStateLease.state;
const usesSystemPromptSeries = !input.isRawModelRun && attempt.operation !== "settled-tool-finalization";
const promptRouteKey = JSON.stringify([
attempt.provider,
attempt.modelId,
attempt.model.api,
attempt.model.baseUrl,
transcriptPolicy.inHistorySystemUpdates === true,
attempt.bootstrapUserProfileId
]);
const retireSystemPromptUpdates = () => sessionLock.withOwnedTranscriptWrite(() => withSessionManagerWrite(sessionManager, async () => {
runAbortSignal.throwIfAborted();
await retireSessionSystemPrompt(sessionPromptState, promptRouteKey, (customType, data) => sessionManager.appendCustomEntryAsync(customType, data));
}));
if (usesSystemPromptSeries && beginSessionSystemPrompt({
state: sessionPromptState,
routeKey: promptRouteKey,
enabled: transcriptPolicy.inHistorySystemUpdates === true,
entries: sessionManager.getBranch()
})) await retireSystemPromptUpdates();
let freshSystemPrompt = systemPromptText;
let projectedSystemPrompt;
const prepareSystemPromptUpdate = usesSystemPromptSeries && transcriptPolicy.inHistorySystemUpdates ? async (systemPrompt, freshlyRendered = false) => {
if (freshlyRendered || systemPrompt !== projectedSystemPrompt) freshSystemPrompt = systemPrompt;
const prepared = prepareSessionSystemPrompt({
state: sessionPromptState,
routeKey: promptRouteKey,
systemPrompt: freshSystemPrompt,
entries: sessionManager.getBranch()
});
let restartRecorded = false;
if (prepared.restart && resources.session) {
await retireSystemPromptUpdates();
restartRecorded = true;
resources.session.agent.state.messages = resources.session.messages.filter((message) => getOpenClawSystemUpdateKind(message) !== "prompt-update");
}
projectedSystemPrompt = prepared.systemPrompt;
return {
...prepared,
commit: () => prepared.commit(restartRecorded)
};
} : void 0;
resources.getUserTranscriptContexts = preparedSessionManager.userMessageBoundary.getUserTranscriptContexts;
const state = {
currentTurnImageFailureCount: 0,
prePromptMessageCount: 0,
promptCache: void 0,
systemPromptText
};
const preparedAgentSession = await withOwnedSessionTranscriptWrites(sessionLock.ownedTranscriptWriteContext, async () => prepareEmbeddedAttemptAgentSession({
...sessionPreparation,
...input.activeContextEngine ? { activeContextEngineInfo: input.activeContextEngine.info } : {},
agentCoreThinkingLevel,
clientToolPreparation: {
catalogToolHookContext,
clientTools,
codeModeControlsEnabledForRun,
deferredDirectoryToolsCallable,
effectiveTools,
replaySafetyOptions,
sandboxSessionKey,
sessionAgentId,
toolSearchCatalogRef,
toolSearchRuntimeConfig,
uncompactedEffectiveTools,
getToolAbortSignal: () => toolBase.toolAbortSignal
},
getCurrentAttemptPluginMetadataSnapshot,
initialSystemPrompt: state.systemPromptText,
prepareSystemPromptUpdate,
markStage: (stage) => prepStages.mark(stage),
onSessionCreated: (session) => {
resources.session = session;
},
onSystemPromptChanged: (nextSystemPrompt) => {
state.systemPromptText = nextSystemPrompt;
},
runAbortSignal,
transcriptLifecycle: sessionLock.transcriptLifecycle,
sessionManager,
prepareInitialUserTurnReplay: preparedSessionManager.prepareInitialUserTurnReplay
}));
const { activeSession, setActiveSessionSystemPrompt, settingsManager } = preparedAgentSession;
const recordCurrentTurnImageFailure = (count) => {
state.currentTurnImageFailureCount = Math.max(state.currentTurnImageFailureCount, count);
};
await attempt.userTurnTranscriptRecorder?.waitForRuntimePersistence();
const boundary = await sessionLock.withOwnedTranscriptWrite(() => prepareEmbeddedAttemptSessionBoundary({
abortSignal: runAbortSignal,
activeSession,
appendOnlyRuntimeContext: transcriptPolicy.appendOnlyRuntimeContext,
inHistorySystemUpdates: transcriptPolicy.inHistorySystemUpdates,
attempt,
...preparedSessionManager.userMessageBoundary,
isRawModelRun: input.isRawModelRun,
sessionManager,
setActiveSessionSystemPrompt
}));
state.prePromptMessageCount = activeSession.messages.length;
const toolResultPromptProjectionState = sessionPromptState.toolResults;
if (!input.isRawModelRun) restoreCacheTtlToolResultProjections(toolResultPromptProjectionState, sessionManager.getToolResultProjectionEntries());
const settleTracker = createEmbeddedAttemptSessionSettleTracker(activeSession);
input.externalAbortController.setActiveSessionAbort(settleTracker.abortActiveSession);
resources.buildAbortSettlePromise = settleTracker.buildAbortSettlePromise;
input.onSessionYieldReady({
abortActiveSession: settleTracker.abortActiveSession,
activeSession
});
const contextGuards = installEmbeddedAttemptContextGuards({
...sessionPreparation,
activeSession,
computerContextEpoch,
dropThinkingBlocksForEstimate: transcriptPolicy.dropThinkingBlocks,
effectiveFsWorkspaceOnly,
getPrePromptMessageCount: () => state.prePromptMessageCount,
getPromptCache: () => state.promptCache,
onCurrentTurnImageFailure: recordCurrentTurnImageFailure,
getPromptCacheRetention: () => transport.effectivePromptCacheRetention,
getCompactionReplayEnabled: () => transport.compactionReplayEnabled,
getServerToolClearingEnabled: () => transport.serverToolClearingEnabled,
toolResultPromptProjectionState,
getSystemPrompt: () => state.systemPromptText,
isOpenAIResponsesApi,
repairToolUseResultPairing: transcriptPolicy.repairToolUseResultPairing,
sessionManager,
settingsManager,
sandbox
});
resources.removeToolResultContextGuard = contextGuards.remove;
const traceContext = {
env: process.env,
runId: attempt.runId,
sessionId: activeSession.sessionId,
sessionKey: attempt.sessionKey,
provider: attempt.provider,
modelId: attempt.modelId,
modelApi: attempt.model.api,
workspaceDir: attempt.workspaceDir
};
const cacheTrace = createCacheTrace({
cfg: attempt.config,
...traceContext
});
const anthropicPayloadLogger = createAnthropicPayloadLogger(traceContext);
const trajectoryRecorder = await prepareEmbeddedAttemptTrajectory({
...sessionPreparation,
activeSession,
transcriptOwner: sessionLock.ownedTranscriptWriteContext,
clientToolCount: preparedAgentSession.clientToolDefs.length,
effectiveToolCount,
localModelLeanEnabled,
...systemPromptReport ? { systemPromptReport } : {}
});
resources.trajectoryRecorder = trajectoryRecorder;
const transport = await prepareEmbeddedAttemptTransport({
attempt,
assertCronRootCurrent: sessionLock.assertCronRootCurrent,
session: activeSession,
settingsManager,
providerThinkingLevel,
sessionAgentId,
workspaceDir: effectiveWorkspace,
workspaceOnly: effectiveFsWorkspaceOnly,
agentDir: input.agentDir,
abortSignal: runAbortSignal,
getProviderRuntimeHandle,
onCurrentTurnImageFailure: recordCurrentTurnImageFailure,
sandboxSessionKey,
...sandbox !== void 0 ? { sandbox } : {},
codeModeControlsEnabled: codeModeControlsEnabledForRun,
providerPromptState: {
state: getProviderPromptState(attempt.runId),
effectiveContextTokenBudget: Math.max(1, Math.floor(attempt.contextTokenBudget ?? attempt.model.contextWindow ?? 2e5)),
...trajectoryRecorder ? { recordEvent: trajectoryRecorder.recordEvent } : {}
}
});
return {
agentSession: preparedAgentSession,
anthropicPayloadLogger,
boundary,
cacheTrace,
contextGuards,
isOpenAIResponsesApi,
preparedUserTurnMessage,
prepareSystemPromptUpdate,
sessionManager,
sessionPromptState,
settleTracker,
state,
toolResultPromptProjectionState,
trajectoryRecorder,
transcriptPolicy,
transport
};
}
//#endregion
//#region src/agents/tool-allowlist-guard.ts
/**
* Explicit tool allowlist guard.
*
* Collects operator/user allowlist sources and explains when no callable tools remain.
*/
/** Normalize explicit allowlist sources, dropping empty source entries. */
function collectExplicitToolAllowlistSources(sources) {
return sources.flatMap((source) => {
const entries = normalizeStringEntries(source.allow);
if (entries.length === 0) return [];
return [{
label: source.label,
entries,
...source.enforceWhenToolsDisabled === true ? { enforceWhenToolsDisabled: true } : {}
}];
});
}
/** Build an actionable error when explicit allowlists remove every callable tool. */
function buildEmptyExplicitToolAllowlistError(params) {
const toolsIntentionallyDisabled = params.disableTools === true || params.toolsAllowExplicitlyEmpty === true;
const sources = toolsIntentionallyDisabled ? params.sources.filter((source) => source.enforceWhenToolsDisabled === true) : params.sources;
if (sources.length === 0 || params.hasCallableTools) return null;
const requested = sources.map((source) => `${source.label}: ${source.entries.map(normalizeToolPolicyName).join(", ")}`).join("; ");
const workshopBlock = params.skillWorkshop && sources.every((source) => isToolAllowedByPolicyName("skill_workshop", { allow: source.entries })) ? resolveSkillWorkshopToolConstructionBlock(params.skillWorkshop) : void 0;
if (params.toolsEnabled && !toolsIntentionallyDisabled && workshopBlock) return /* @__PURE__ */ new Error(`No callable tools remain after resolving explicit tool allowlist (${requested}); ${workshopBlock.detail} ${workshopBlock.fix}`);
const reason = params.disableTools === true ? "tools are disabled for this run" : params.toolsEnabled ? "no registered tools matched" : "the selected model does not support tools";
return /* @__PURE__ */ new Error(`No callable tools remain after resolving explicit tool allowlist (${requested}); ${reason}. Fix the allowlist or enable the plugin that registers the requested tool.`);
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-tool-search-run-plan.ts
function hasExplicitlyAllowedClientTool(params) {
const names = (params.clientTools ?? []).map((tool) => tool.function?.name).filter((name) => Boolean(name?.trim()));
if (names.length === 0) return false;
const matchers = params.explicitAllowlistSources.map((source) => createToolPolicyMatcher({ allow: source.entries }));
return names.some((name) => matchers.some((matches) => matches(name)));
}
/**
* Builds the complete tool-search allowlist plan for one run. Visible tools use
* compacted prompt state, while replay tools use uncompacted state.
*/
function buildToolSearchRunPlan(params) {
const controlNames = params.controlNames ?? [...TOOL_SEARCH_CONTROL_TOOL_NAMES];
const visibleAllowedToolNames = collectAllowedToolNames({
tools: params.visibleTools,
clientTools: params.clientToolsCataloged ? void 0 : params.clientTools
});
const replayAllowedToolNames = collectAllowedToolNames({
tools: params.uncompactedTools,
clientTools: params.clientTools
});
const capabilityToolNames = collectAllowedToolNames({ tools: [...params.deferredToolsCallable ? params.uncompactedTools : params.visibleTools, ...params.catalogCapabilityTools ?? []].filter((tool) => getPluginToolMeta(tool)?.pluginId !== "bundle-mcp") });
if (params.controlsEnabled) {
for (const controlName of controlNames) if (visibleAllowedToolNames.has(controlName)) replayAllowedToolNames.add(controlName);
}
const liveAllowedToolNames = params.deferredToolsCallable ? collectUniqueCatalogToolNames(params.uncompactedTools) : visibleAllowedToolNames;
if (params.deferredToolsCallable) {
for (const controlName of TOOL_SEARCH_CONTROL_TOOL_NAMES) if (!visibleAllowedToolNames.has(controlName)) {
liveAllowedToolNames.delete(controlName);
capabilityToolNames.delete(controlName);
}
for (const visibleName of visibleAllowedToolNames) liveAllowedToolNames.add(visibleName);
}
const explicitControlAllowlistNames = new Set(params.explicitAllowlistSources.flatMap((source) => source.entries.map((entry) => normalizeToolPolicyName(entry))));
const autoAddedControlNames = new Set((params.controlsEnabled ? controlNames : []).filter((controlName) => !explicitControlAllowlistNames.has(normalizeToolPolicyName(controlName))));
const explicitlyAllowedClientTool = hasExplicitlyAllowedClientTool(params);
const emptyAllowlistVisibleToolNames = params.deferredToolsCallable ? collectAllowedToolNames({ tools: params.visibleTools }) : visibleAllowedToolNames;
return {
visibleAllowedToolNames,
replayAllowedToolNames,
liveAllowedToolNames,
capabilityToolNames,
hasCallableTools: params.catalogToolCount > 0 || (params.clientToolsCataloged || params.deferredToolsCallable === true) && explicitlyAllowedClientTool || [...emptyAllowlistVisibleToolNames].some((toolName) => !autoAddedControlNames.has(toolName))
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-tool-catalog.ts
async function prepareEmbeddedAttemptToolCatalog(input) {
const buildCatalog = (recordQuarantine) => {
const { attempt, preparedToolBase } = input;
const { codeModeControlsEnabledForRun, codeModeSkills, localModelLeanPreserveToolNames, runtimeCapabilityProfile, toolSearchConfig, toolSearchControlsEnabledForRun, toolsEnabled } = preparedToolBase;
const { clientTools, uncompactedEffectiveTools } = input.bundleTools;
const abortSignal = preparedToolBase.toolAbortSignal ?? input.abortSignal;
let effectiveTools = attempt.toolExecutionAllow ? gateToolExecution(uncompactedEffectiveTools, attempt.toolExecutionAllow) : uncompactedEffectiveTools;
const catalogToolHookContext = preparedToolBase.toolHookContext;
const compacted = preparedToolBase.toolSurfaceRuntime.compactTools(effectiveTools, {
hookContext: catalogToolHookContext,
prepared: {
abortSignal,
forceRestartSafeTools: attempt.forceRestartSafeTools,
toolExecutionAllow: attempt.toolExecutionAllow,
executeTool: input.executeCodeModeTool,
codeModeSkills,
preserveToolNames: localModelLeanPreserveToolNames
}
});
const toolSearch = compacted.catalog;
recordQuarantine({
diagnostics: compacted.diagnostics,
tools: compacted.projectedTools,
runId: attempt.runId,
agentId: input.setup.sessionAgentId,
sessionKey: attempt.sessionKey,
sessionId: attempt.sessionId
});
effectiveTools = compacted.tools.map((tool) => wrapEmbeddedAttemptToolWithActivity(wrapToolWithAbortSignal(tool, abortSignal), attempt.runId));
if (codeModeControlsEnabledForRun && isCodeModeDiagnosticEnabled()) logCodeModeDiagnostic(log$6, "final-surface", {
runId: attempt.runId,
fallbackActive: attempt.fallbackActive === true,
catalogToolCount: toolSearch.catalogToolCount,
visibleToolNames: effectiveTools.map((tool) => tool.name)
});
if (toolSearch.compacted && !toolSearch.catalogReused) log$6.info(codeModeControlsEnabledForRun ? `code-mode: cataloged ${toolSearch.catalogToolCount} tools behind exec/wait` : toolSearchConfig.mode === "directory" ? `tool-search: cataloged ${toolSearch.catalogToolCount} tools behind compact directory surface` : `tool-search: cataloged ${toolSearch.catalogToolCount} tools behind compact prompt surface`);
const deferredDirectoryToolsCallable = toolSearchControlsEnabledForRun && toolSearchConfig.mode === "directory" && toolSearch.catalogRegistered;
const { agentId, globalPolicy, globalProviderPolicy, agentPolicy, agentProviderPolicy, groupPolicy, sandboxPolicy, subagentPolicy, inheritedToolPolicy } = runtimeCapabilityProfile.policy;
const agentToolLabel = agentId ? `agents.${agentId}.tools` : "agent tools";
const explicitToolAllowlistSources = collectExplicitToolAllowlistSources([
{
label: "tools.allow",
allow: globalPolicy?.allow
},
{
label: "tools.byProvider.allow",
allow: globalProviderPolicy?.allow
},
{
label: `${agentToolLabel}.allow`,
allow: agentPolicy?.allow
},
{
label: `${agentToolLabel}.byProvider.allow`,
allow: agentProviderPolicy?.allow
},
{
label: "group tools.allow",
allow: groupPolicy?.allow
},
{
label: "sandbox tools.allow",
allow: sandboxPolicy?.allow
},
{
label: "subagent tools.allow",
allow: subagentPolicy?.allow
},
{
label: "inherited tools.allow",
allow: inheritedToolPolicy?.allow
},
{
label: "runtime toolsAllow",
allow: attempt.toolsAllow,
enforceWhenToolsDisabled: true
}
]);
const toolSearchRunPlan = buildToolSearchRunPlan({
visibleTools: effectiveTools,
uncompactedTools: uncompactedEffectiveTools,
catalogCapabilityTools: toolSearch.catalogRegistered ? uncompactedEffectiveTools : void 0,
clientTools,
clientToolsCataloged: toolSearch.catalogRegistered && (codeModeControlsEnabledForRun || toolSearchConfig.mode !== "directory"),
catalogToolCount: toolSearch.catalogToolCount,
controlsEnabled: toolSearchControlsEnabledForRun || codeModeControlsEnabledForRun,
deferredToolsCallable: deferredDirectoryToolsCallable,
controlNames: codeModeControlsEnabledForRun ? [CODE_MODE_EXEC_TOOL_NAME, CODE_MODE_WAIT_TOOL_NAME] : toolSearchConfig.mode === "directory" ? [
TOOL_SEARCH_RAW_TOOL_NAME,
TOOL_DESCRIBE_RAW_TOOL_NAME,
TOOL_CALL_RAW_TOOL_NAME
] : void 0,
explicitAllowlistSources: explicitToolAllowlistSources
});
const emptyExplicitToolAllowlistError = attempt.forceRestartSafeTools ? null : buildEmptyExplicitToolAllowlistError({
sources: explicitToolAllowlistSources,
hasCallableTools: toolSearchRunPlan.hasCallableTools,
toolsEnabled,
disableTools: attempt.disableTools,
toolsAllowExplicitlyEmpty: preparedToolBase.effectiveToolsAllow?.length === 0,
skillWorkshop: {
sandboxed: input.setup.sandbox?.enabled,
libraryAuthoring: attempt.skillLibraryAuthoring
}
});
logAgentRuntimeToolDiagnostics({
runtimePlan: attempt.runtimePlan,
tools: effectiveTools,
provider: attempt.provider,
config: attempt.config,
workspaceDir: input.setup.effectiveWorkspace,
env: process.env,
modelId: attempt.modelId,
modelApi: attempt.model.api,
model: attempt.model,
runtimeHandle: input.setup.getProviderRuntimeHandle()
});
return {
catalogToolHookContext,
deferredDirectoryToolsCallable,
effectiveTools,
emptyExplicitToolAllowlistError,
toolSearch,
toolSearchRunPlan
};
};
const current = await withRuntimeToolSchemaQuarantine(buildCatalog);
const promptPlanKeys = [
"visibleAllowedToolNames",
"liveAllowedToolNames",
"capabilityToolNames"
];
const hostPromptPlan = {
visibleAllowedToolNames: new Set(current.toolSearchRunPlan.visibleAllowedToolNames),
liveAllowedToolNames: new Set(current.toolSearchRunPlan.liveAllowedToolNames),
capabilityToolNames: new Set(current.toolSearchRunPlan.capabilityToolNames)
};
return Object.assign(current, {
applyPromptToolPolicy: (allowedNames) => {
if (!current.toolSearch.catalogRegistered) finalizeAgentToolAvailability(current.effectiveTools, { toolExecutionAllow: [...allowedNames] });
for (const key of promptPlanKeys) {
const names = current.toolSearchRunPlan[key];
names.clear();
for (const name of hostPromptPlan[key]) if (allowedNames.has(name)) names.add(name);
}
},
refreshTools: (recordQuarantine) => {
const next = buildCatalog(recordQuarantine);
current.effectiveTools.splice(0, current.effectiveTools.length, ...next.effectiveTools);
for (const key of [...promptPlanKeys, "replayAllowedToolNames"]) {
const target = current.toolSearchRunPlan[key];
if (key !== "replayAllowedToolNames") target.clear();
for (const name of next.toolSearchRunPlan[key]) target.add(name);
}
Object.assign(current.toolSearch, next.toolSearch);
for (const key of promptPlanKeys) hostPromptPlan[key] = new Set(next.toolSearchRunPlan[key]);
current.emptyExplicitToolAllowlistError = next.emptyExplicitToolAllowlistError;
current.toolSearchRunPlan.hasCallableTools = next.toolSearchRunPlan.hasCallableTools;
}
});
}
function gateToolExecution(tools, allowNames) {
const executionAllowed = createToolExecutionMatcher(allowNames);
return tools.map((tool) => executionAllowed(tool.name) || TOOL_SEARCH_CONTROL_TOOL_NAMES.has(tool.name) ? tool : markAgentToolExecutionUnavailable(copyAgentToolAvailability(tool, {
...tool,
prepareArguments: void 0,
prepareBeforeToolCallParams: void 0,
finalizeBeforeToolCallParams: void 0,
execute: async () => textResult(formatToolExecutionGatedMessage(tool.name, allowNames), { gated: true })
})));
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-tool-prepare.ts
async function prepareEmbeddedAttemptToolBase(params) {
const { attempt } = params;
const completionCheck = attempt.completionCheck;
const requireExplicitMessageTarget = attempt.requireExplicitMessageTarget ?? isSubagentSessionKey(attempt.sessionKey);
const forceDirectMessageTool = messageToolOwnsVisibleReply(attempt);
const toolRunContext = buildEmbeddedAttemptToolRunContext({
...attempt,
forceMessageTool: forceDirectMessageTool,
trace: params.runTrace
});
const toolsAllowWithForcedRuntimeTools = toolRunContext.runtimeToolAllowlist;
const toolsEnabled = supportsModelTools(attempt.model);
const isRawModelRun = attempt.modelRun === true || attempt.promptMode === "none";
const toolConstructionPlan = resolveEmbeddedAttemptToolConstructionPlan({
disableTools: attempt.disableTools,
isRawModelRun,
toolsEnabled,
toolsAllow: toolsAllowWithForcedRuntimeTools
});
const toolSurfaceRuntime = createAgentHarnessToolSurfaceRuntimeCore({
config: attempt.config,
trigger: attempt.trigger,
agentId: params.setup.sessionAgentId,
sessionKey: params.setup.sandboxSessionKey,
forceMessageTool: forceDirectMessageTool,
model: attempt.model,
modelProvider: attempt.provider,
modelId: attempt.modelId,
codeModeOverride: attempt.codeModeOverride,
disableToolSearch: attempt.disableToolSearch,
modelToolsEnabled: toolsEnabled,
disableTools: attempt.disableTools,
isRawModelRun,
toolsAllow: attempt.toolsAllow,
forceCodeModeControls: attempt.forceCodeModeTools,
runtimeToolAllowlist: toolsAllowWithForcedRuntimeTools,
sessionId: attempt.sessionId,
runId: attempt.runId,
contextTokenBudget: attempt.contextTokenBudget,
executeTool: params.toolSearchCatalogExecutor
});
const { codeModeControlsEnabled: codeModeControlsEnabledForRun, toolSearchConfig, toolSearchControlsEnabled: toolSearchControlsEnabledForRun, toolSearchRuntimeConfig } = toolSurfaceRuntime.plan;
if (isCodeModeDiagnosticEnabled()) logCodeModeDiagnostic(log$6, "activation", {
runId: attempt.runId,
active: codeModeControlsEnabledForRun,
toolsEnabled,
rawRun: isRawModelRun,
toolsDisabled: attempt.disableTools === true,
fallbackActive: attempt.fallbackActive === true,
allowlist: attempt.toolsAllow === void 0 ? "unset" : attempt.toolsAllow.length === 0 ? "empty" : "nonempty"
});
const effectiveToolsAllow = mergeForcedEmbeddedAttemptToolsAllow(toolsAllowWithForcedRuntimeTools, { forceToolNames: toolSearchControlsEnabledForRun ? [...TOOL_SEARCH_CONTROL_TOOL_NAMES] : [] });
const shouldConstructTools = toolConstructionPlan.constructTools || toolSearchControlsEnabledForRun || codeModeControlsEnabledForRun;
const computerContextEpoch = { value: 0 };
const skillInstructionDeliveryCache = createSkillInstructionDeliveryCache();
const toolSearchCatalogRef = toolSurfaceRuntime.toolSearchCatalogRef;
const nestedToolActivityState = createAttemptNestedToolActivityState();
const codeModeSkills = toolPolicyRestrictsTools({ allow: attempt.toolsAllow }) ? [] : params.codeModeSkills;
const cronCreatorToolAllowlist = [];
const cronCreatorToolAllowlistCaptureRef = {};
const inheritedToolAllowlist = [];
const generationCleanups = [];
const retiringGenerations = /* @__PURE__ */ new Set();
let retiredCleanupFailed = false;
const retireToolGeneration = (reason) => {
const cleanups = generationCleanups.splice(0);
const settled = Promise.allSettled(cleanups.map(async (cleanup) => await cleanup(reason))).then((results) => {
retiredCleanupFailed ||= results.some((result) => result.status === "rejected");
});
retiringGenerations.add(settled);
settled.then(() => retiringGenerations.delete(settled));
};
const spawnWorkspaceDir = params.setup.effectiveCwd !== params.setup.effectiveWorkspace ? params.setup.resolvedWorkspace : resolveAttemptSpawnWorkspaceDir({
sandbox: params.setup.sandbox,
resolvedWorkspace: params.setup.resolvedWorkspace
});
const buildConversationContext = () => ({
...toolRunContext,
requireExplicitMessageTarget,
config: toolSearchRuntimeConfig,
sessionKey: params.setup.sandboxSessionKey,
runSessionKey: attempt.sessionKey?.trim() || attempt.sessionId,
sessionId: attempt.sessionId,
runId: attempt.runId,
agentDir: params.agentDir,
messageProvider: attempt.messageProvider ?? attempt.messageChannel,
messageChannel: attempt.messageChannel,
modelProvider: attempt.provider,
modelId: attempt.modelId,
modelApi: attempt.model.api,
modelBaseUrl: attempt.model.baseUrl,
modelContextWindowTokens: attempt.contextTokenBudget ?? attempt.model.contextWindow,
modelHasVision: attempt.model.input?.includes("image") ?? false,
workspaceDir: params.setup.effectiveWorkspace,
cwd: params.setup.effectiveCwd,
spawnWorkspaceDir,
skillsSnapshot: params.skillsSnapshot,
runtimeToolAllowlist: effectiveToolsAllow
});
const runtimeCapabilityProfile = resolveConversationCapabilityProfile({
...buildConversationContext(),
agentId: attempt.sandboxAgentId ?? params.setup.sessionAgentId,
conversationToolPolicy: attempt.conversationToolPolicy,
sandboxToolPolicy: params.setup.sandbox?.tools,
inheritRuntimeToolAllowlist: true,
runtimePluginToolGrant: attempt.runtimePluginToolGrant,
inputProvenance: attempt.inputProvenance,
trustedInternalHandoff: attempt.trustedInternalHandoff,
pluginMetadataSnapshot: attempt.preparedModelRuntime?.metadataSnapshot
});
const computerTransport = resolveSessionPlacementComputer(attempt.admittedRunContext.operationalRunInstance);
const allowsConversationTool = (name) => projectConversationToolNames({
capabilityProfile: runtimeCapabilityProfile,
toolNames: [name],
warn: () => void 0
}).length === 1;
const computerAllowed = shouldConstructTools && allowsConversationTool("computer");
const pairedNodeComputerUse = (await loadPairedComputerUseAvailabilityForSurface({
computerAllowed,
modelHasVision: attempt.model.input?.includes("image") ?? true,
computerTransport,
embeddedMode: isEmbeddedMode(),
signal: params.runAbortController.signal
}))?.prepared;
const localModelLeanEnabled = isLocalModelLeanEnabled({
config: attempt.config,
agentId: params.setup.sessionAgentId,
sessionKey: attempt.sessionKey
});
const localModelLeanPreserveToolNames = resolveLocalModelLeanPreserveToolNames({
toolNames: runtimeCapabilityProfile.policy.explicitToolOverrideAllowlist,
forceMessageTool: attempt.forceMessageTool,
sourceReplyDeliveryMode: attempt.sourceReplyDeliveryMode
});
const replaySafetyOptions = { declaredReplaySafe: (candidate) => {
const pluginMeta = getPluginToolMeta(candidate);
if (pluginMeta) return pluginMeta.replaySafe === true;
return getChannelAgentToolMeta(candidate) ? false : void 0;
} };
const restartSafetyOptions = { declaredReplaySafe: (candidate) => {
if (getPluginToolMeta(candidate)?.mcp) return false;
return replaySafetyOptions.declaredReplaySafe(candidate);
} };
let webSearchUnconfigured = false;
const constructTools = async (sessionPermissionPolicy, abortSignal) => {
webSearchUnconfigured = false;
let constructedToolsRaw = [];
if (shouldConstructTools) {
const codingToolOptions = {
agentId: params.setup.sessionAgentId,
...buildConversationContext(),
exec: {
...attempt.execOverrides,
...sessionPermissionPolicy ? { mode: resolveSessionPermissionExecMode(sessionPermissionPolicy) } : {},
config: attempt.config,
elevated: attempt.bashElevated,
reviewTranscript: params.reviewTranscript
},
sandbox: params.setup.sandbox,
stagedMediaPaths: resolveStagedInputMediaPaths(attempt.media),
sessionPermissionPolicy,
channelContext: attempt.channelContext,
allowGatewaySubagentBinding: attempt.allowGatewaySubagentBinding,
operationalRunInstance: attempt.admittedRunContext.operationalRunInstance,
computerTransport,
pairedNodeComputerUse,
conversationRecall: attempt.conversationRecall,
memoryAudience: attempt.memoryAudience,
oneShotCliRun: attempt.oneShotCliRun,
toolSearchCatalogRef,
codeModeSkills,
installedSkills: params.installedSkills,
preparedModelRuntime: attempt.preparedModelRuntime,
requireWorkspaceOnly: attempt.requireWorkspaceOnly,
sessionReadScopeKey: attempt.sessionReadScopeKey,
sessionConfigSource: attempt.oneShotCliRun ? "pinned" : "runtime",
webSearchEnabled: attempt.toolOverrides?.webSearch !== false,
onWebSearchConfiguration: (configured) => {
webSearchUnconfigured = !configured && allowsConversationTool("web_search") && resolveNativeWebSearchRoute({
...buildConversationContext(),
agentId: params.setup.sessionAgentId,
webSearchEnabled: attempt.toolOverrides?.webSearch !== false,
sandboxToolPolicy: params.setup.sandbox?.tools,
authStore: attempt.authProfileStore,
pluginMetadataSnapshot: attempt.preparedModelRuntime?.metadataSnapshot
}).kind === "managed";
},
githubPublicationAvailable: attempt.githubPublicationAvailable,
abortSignal,
skillWorkshop: {
...attempt.skillWorkshopReviewOf ? { reviewOf: attempt.skillWorkshopReviewOf } : {},
libraryAuthoring: attempt.skillLibraryAuthoring
},
modelCompat: extractModelCompat(attempt.model),
delegationCapability: attempt.delegationCapability,
modelAuthMode: resolveModelAuthMode(attempt.model.provider, attempt.config, void 0, { workspaceDir: params.setup.effectiveWorkspace }),
includeCoreTools: toolConstructionPlan.includeCoreTools,
includeToolSearchControls: toolSearchControlsEnabledForRun,
toolSearchCatalogExecutor: params.toolSearchCatalogExecutor,
toolConstructionPlan: toolConstructionPlan.codingToolConstructionPlan,
computerContextEpoch,
skillInstructionDeliveryCache,
registerRunCleanup: (cleanup) => generationCleanups.push(cleanup),
inboundEventKind: attempt.currentInboundEventKind,
inputProvenance: attempt.inputProvenance,
disableMessageTool: attempt.disableMessageTool,
forceMessageTool: attempt.forceMessageTool,
enableHeartbeatTool: attempt.enableHeartbeatTool,
forceHeartbeatTool: attempt.forceHeartbeatTool,
inheritedToolAllowlistRef: inheritedToolAllowlist,
cronCreatorToolAllowlistRef: cronCreatorToolAllowlist,
cronCreatorToolAllowlistCaptureRef,
authProfileStore: attempt.authProfileStore,
recordToolPrepStage: params.markCoreToolStage,
onToolOutcome: attempt.onToolOutcome,
isTurnTainted: attempt.isTurnTainted,
allocateToolOutcomeOrdinal: attempt.allocateToolOutcomeOrdinal,
skillUsagePaths: params.skillUsagePaths,
conversationCapabilityProfile: runtimeCapabilityProfile,
onProgressCardPlanSaved: completionCheck ? (unfinished) => {
completionCheck.unfinishedPlan = unfinished;
} : void 0,
onYield: params.onYield
};
const allTools = await createOpenClawCodingToolsInternalAsync(codingToolOptions, params.skillReadResources, void 0, void 0, {
reader: getReplyOperationSessionReader(attempt.replyOperation),
assertCurrent: resolveAdmittedRunActiveAssertion(attempt.admittedRunContext, abortSignal)
});
const boundTools = attempt.hostCapabilities ? attempt.hostCapabilities.bindToolSurface(allTools) : allTools;
params.markCoreToolStage("attempt:create-openclaw-coding-tools");
constructedToolsRaw = applyEmbeddedAttemptToolsAllow(boundTools, effectiveToolsAllow, { toolMeta: (tool) => getPluginToolMeta(tool) });
params.markCoreToolStage("attempt:tools-allow");
}
const toolsRaw = attempt.forceRestartSafeTools ? constructedToolsRaw.filter((tool) => isAgentToolRestartSafe(tool, restartSafetyOptions)) : constructedToolsRaw;
assertMemoryFlushPersistenceToolAvailable(toolsRaw, attempt.memoryFlushTools);
if (attempt.forceRestartSafeTools) log$6.info(`restart-safe recovery tool policy retained ${toolsRaw.length}/${constructedToolsRaw.length} concrete tools`);
return toolsRaw;
};
let toolAbortController = new AbortController();
let toolAbortSignal = AbortSignal.any([params.runAbortController.signal, toolAbortController.signal]);
const baseExecOverrides = { ...attempt.permissionChange?.baseExecOverrides ?? attempt.execOverrides };
const releaseTools = async (reason) => {
toolAbortController.abort();
retireToolGeneration(reason);
await Promise.all(retiringGenerations);
if (retiredCleanupFailed) recordAgentCleanupFailure();
};
try {
const toolsRaw = await constructTools(params.setup.sessionPermissionPolicy, toolAbortSignal);
return {
toolHookContext: {
agentId: params.setup.sessionAgentId,
config: attempt.config,
cwd: params.setup.effectiveCwd,
sessionKey: params.setup.sandboxSessionKey,
sessionId: attempt.sessionId,
runId: attempt.runId,
approvalReviewerDeviceId: attempt.approvalReviewerDeviceId,
channelId: attempt.currentChannelId,
trace: params.runTrace,
loopDetection: resolveToolLoopDetectionConfig({
cfg: attempt.config,
agentId: params.setup.sessionAgentId
}),
onToolOutcome: attempt.onToolOutcome,
allocateToolOutcomeOrdinal: attempt.allocateToolOutcomeOrdinal
},
get toolAbortSignal() {
return toolAbortSignal;
},
refreshPermissionMode: async (mode, revokeApprovals) => {
toolAbortController.abort(createCodeModePermissionChangeReason());
revokeApprovals();
retireToolGeneration("cancel");
params.runAbortController.signal.throwIfAborted();
toolAbortController = new AbortController();
toolAbortSignal = AbortSignal.any([params.runAbortController.signal, toolAbortController.signal]);
attempt.permissionMode = mode ?? void 0;
attempt.execOverrides = { ...baseExecOverrides };
const policy = mode ? {
root: params.setup.sessionPermissionRoot,
mode
} : void 0;
const nextTools = await constructTools(policy, toolAbortSignal);
toolsRaw.splice(0, toolsRaw.length, ...nextTools);
},
get webSearchUnconfigured() {
return webSearchUnconfigured;
},
codeModeControlsEnabledForRun,
codeModeSkills,
computerContextEpoch,
skillInstructionDeliveryCache,
cronCreatorToolAllowlist,
cronCreatorToolAllowlistCaptureRef,
effectiveToolsAllow,
forceDirectMessageTool,
requireExplicitMessageTarget,
inheritedToolAllowlist,
localModelLeanEnabled,
localModelLeanPreserveToolNames,
replaySafetyOptions,
runtimeCapabilityProfile,
releaseTools,
toolSearchCatalogRef,
toolSurfaceRuntime,
toolSearchConfig,
toolSearchControlsEnabledForRun,
toolSearchRuntimeConfig,
nestedToolActivityState,
toolsEnabled,
toolsRaw
};
} catch (error) {
try {
await runOwnedAgentCleanup({
...attempt,
step: "embedded-tool-preparation",
cleanup: () => releaseTools("error"),
log: log$6
});
} catch {
recordAgentCleanupFailure();
}
throw error;
}
}
//#endregion
//#region src/agents/run-session-target.ts
var AgentRunSessionTargetResolutionError = class extends Error {
constructor(sessionId) {
super(`Cannot resolve a session key for existing session: ${sessionId}`);
this.code = "session-key-missing";
this.name = "AgentRunSessionTargetResolutionError";
}
};
/** Resolves the active runtime target used by current run/session internals. */
async function resolveAgentRunSessionTarget(input, prepareTarget) {
const params = {
...input,
sessionTarget: input.sessionTarget ? { ...input.sessionTarget } : void 0
};
const config = params.config ?? getRuntimeConfig();
const sessionTarget = params.sessionTarget;
const targetAgentId = normalizeOptionalString(sessionTarget?.agentId);
const targetSessionId = normalizeOptionalString(sessionTarget?.sessionId);
const targetSessionKey = normalizeOptionalString(sessionTarget?.sessionKey);
const targetStorePath = normalizeOptionalString(sessionTarget?.storePath);
const hasCompleteTypedTarget = Boolean(targetAgentId && targetSessionId && targetSessionKey && targetStorePath);
const legacySessionFile = normalizeOptionalString(params.sessionFile);
const suppliedSessionKey = normalizeOptionalString(params.sessionKey);
const legacyMarker = parseSqliteSessionFileMarker(legacySessionFile);
const recognizedCompatibilityKey = Boolean(legacySessionFile?.startsWith("agent:") || legacySessionFile?.startsWith("in-memory:"));
const fileBackedCompatibilityValue = Boolean(legacySessionFile && !recognizedCompatibilityKey && (path.isAbsolute(legacySessionFile) || legacySessionFile.includes("/") || legacySessionFile.includes("\\") || legacySessionFile.endsWith(".jsonl")));
const plainCompatibilitySessionKey = !fileBackedCompatibilityValue && legacySessionFile === (targetSessionId ?? params.sessionId) ? legacySessionFile : void 0;
if (!hasCompleteTypedTarget && legacySessionFile && !legacyMarker && (fileBackedCompatibilityValue || !plainCompatibilitySessionKey && !recognizedCompatibilityKey && legacySessionFile !== suppliedSessionKey && legacySessionFile !== targetSessionKey)) throw new Error("File-backed transcript targets are unsupported; migrate the session to SQLite first");
const agentId = targetAgentId ?? legacyMarker?.agentId ?? params.agentId;
const sessionId = targetSessionId ?? legacyMarker?.sessionId ?? params.sessionId;
const compatibilitySessionKey = (recognizedCompatibilityKey ? legacySessionFile : void 0) ?? (params.missingSessionKey === "create" ? plainCompatibilitySessionKey : void 0);
const markerEntries = legacyMarker && !hasCompleteTypedTarget ? listSessionEntriesReadOnly({
agentId: legacyMarker.agentId,
storePath: legacyMarker.storePath
}) : [];
const markerMatches = legacyMarker ? markerEntries.filter(({ entry }) => entry.sessionId === legacyMarker.sessionId) : [];
const markerSessionKey = legacyMarker && !hasCompleteTypedTarget ? resolvePreferredSessionKeyForSessionIdMatches(markerMatches.map(({ sessionKey, entry }) => [sessionKey, entry]), legacyMarker.sessionId) : void 0;
if (legacyMarker && !hasCompleteTypedTarget && !targetSessionKey && !suppliedSessionKey && markerMatches.length > 0 && !markerSessionKey) throw new Error("Legacy SQLite transcript marker session key is ambiguous");
const preliminarySessionKey = targetSessionKey ?? suppliedSessionKey ?? compatibilitySessionKey ?? markerSessionKey;
const preliminaryCompatibilityKeyAgentId = parseAgentSessionKey(compatibilitySessionKey)?.agentId;
if (!targetSessionKey && !suppliedSessionKey && preliminarySessionKey === compatibilitySessionKey && preliminaryCompatibilityKeyAgentId && agentId && preliminaryCompatibilityKeyAgentId !== agentId) throw new Error("Compatibility session key conflicts with the supplied agent identity");
const targetStoreOwner = resolvePersistedSessionStoreOwnerForTarget({
config,
sessionKey: preliminarySessionKey,
storePath: targetStorePath
});
const trustExplicitAlternateStoreAgent = Boolean(targetAgentId && targetStorePath && !parseAgentSessionKey(preliminarySessionKey)?.agentId && targetStoreOwner.kind === "none");
const shouldResolveConfiguredStoreRow = params.missingSessionKey === "resolve-existing" && !preliminarySessionKey && !targetStorePath && !legacyMarker;
const configuredStoreResolution = shouldResolveConfiguredStoreRow ? agentId ? resolveStoredSessionKeyForSessionId({
cfg: config,
sessionId,
agentId
}) : resolveExistingSessionKeyForRequest({
cfg: config,
sessionId
}) : void 0;
const fixedAgentId = (hasCompleteTypedTarget || trustExplicitAlternateStoreAgent ? targetAgentId : void 0) ?? legacyMarker?.agentId ?? configuredStoreResolution?.agentId;
const lookupAgentId = fixedAgentId ?? resolveSessionAgentId({
agentId: targetAgentId ?? params.agentId,
config,
sessionKey: preliminarySessionKey ?? (params.missingSessionKey === "create" ? sessionId : void 0)
});
const lookupStorePath = targetStorePath ?? legacyMarker?.storePath ?? configuredStoreResolution?.storePath ?? resolveSessionStorePathCore(config.session?.store, { agentId: lookupAgentId });
const storedSessionKey = configuredStoreResolution?.sessionKey ?? (params.missingSessionKey === "resolve-existing" && !preliminarySessionKey && !shouldResolveConfiguredStoreRow ? await resolveSessionKeyBySessionIdAsync({
agentId: lookupAgentId,
sessionId,
storePath: lookupStorePath
}) : void 0);
const createdSessionKey = params.missingSessionKey === "create" ? toAgentStoreSessionKey({
agentId: lookupAgentId,
requestKey: sessionId
}) : void 0;
const sessionKey = preliminarySessionKey ?? storedSessionKey ?? createdSessionKey;
const suppliedKeyAgentId = parseAgentSessionKey(suppliedSessionKey)?.agentId;
const targetKeyAgentId = parseAgentSessionKey(targetSessionKey)?.agentId;
const candidateMarkerKey = targetSessionKey ?? suppliedSessionKey;
const candidateMarkerEntry = candidateMarkerKey ? markerEntries.find(({ sessionKey: candidateKey }) => candidateKey === candidateMarkerKey)?.entry : void 0;
if (legacyMarker && !hasCompleteTypedTarget && (targetAgentId && targetAgentId !== legacyMarker.agentId || targetSessionId && targetSessionId !== legacyMarker.sessionId || params.agentId && params.agentId !== legacyMarker.agentId || targetKeyAgentId && targetKeyAgentId !== legacyMarker.agentId || suppliedKeyAgentId && suppliedKeyAgentId !== legacyMarker.agentId || targetStorePath && path.resolve(targetStorePath) !== path.resolve(legacyMarker.storePath))) throw new Error("Legacy SQLite transcript marker conflicts with the supplied session identity");
if (legacyMarker && !hasCompleteTypedTarget && candidateMarkerKey && candidateMarkerEntry && candidateMarkerEntry.sessionId !== legacyMarker.sessionId) throw new Error("Legacy SQLite transcript marker conflicts with the supplied session key");
if (!sessionKey) throw new AgentRunSessionTargetResolutionError(sessionId);
const effectiveAgentId = fixedAgentId ?? resolveSessionAgentId({
agentId: targetAgentId ?? params.agentId,
config,
fallbackAgentId: lookupAgentId,
sessionKey
});
const storePath = targetStorePath ?? legacyMarker?.storePath ?? resolveSessionStorePathCore(config.session?.store, { agentId: effectiveAgentId });
const scope = {
...effectiveAgentId ? { agentId: effectiveAgentId } : {},
sessionId,
sessionKey,
storePath,
...sessionTarget?.threadId !== void 0 ? { threadId: sessionTarget.threadId } : {}
};
const prepared = prepareTarget ? await prepareTarget(scope) : void 0;
const target = prepared?.target ?? await resolveSessionTranscriptRuntimeTarget(scope);
prepared?.assertCurrent();
return target;
}
/** Applies identity fields from the explicit target before legacy backfills run. */
function applyAgentRunSessionTargetIdentity(params) {
const target = params.sessionTarget;
if (!target) return params;
return {
...params,
agentId: normalizeOptionalString(target.agentId) ?? params.agentId,
sessionId: normalizeOptionalString(target.sessionId) ?? params.sessionId,
sessionKey: normalizeOptionalString(target.sessionKey) ?? params.sessionKey
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-transcript-lifecycle.ts
/** Serializes run-owned transcript callbacks and bounds teardown settlement. */
const TRANSCRIPT_TEARDOWN_BUDGET_MS = 3e4;
function createEmbeddedAttemptTranscriptLifecycle(params, deps = {}) {
let disposed = false;
let lifecycle = Promise.resolve();
let cleanupDrain;
let disposePromise;
let pendingWrites = 0;
const lifecycleOwner = deps.createLifecycleStore?.() ?? new AsyncLocalStorage();
const createLifecycleOwner = () => ({
active: true,
nestedTail: Promise.resolve(),
pendingOperations: /* @__PURE__ */ new Set()
});
const drainLifecycleOwner = async (owner) => {
let firstError;
while (owner.pendingOperations.size > 0) {
const pending = [...owner.pendingOperations];
owner.pendingOperations.clear();
const rejected = (await Promise.allSettled(pending)).find((result) => result.status === "rejected");
if (firstError === void 0 && rejected?.status === "rejected") firstError = toErrorObject(rejected.reason, "nested transcript write failed");
}
if (firstError !== void 0) throw firstError;
};
const runLifecycleOwner = async (owner, run) => {
let value;
let primaryError;
try {
value = await lifecycleOwner.run(owner, async () => await run());
} catch (error) {
primaryError = toErrorObject(error, "transcript write failed");
}
let drainError;
try {
await drainLifecycleOwner(owner);
} catch (error) {
drainError = toErrorObject(error, "nested transcript drain failed");
} finally {
owner.active = false;
}
if (primaryError !== void 0 && drainError !== void 0 && drainError !== primaryError && primaryError.cause === void 0) try {
primaryError.cause = drainError;
} catch {}
const failure = primaryError ?? drainError;
if (failure !== void 0) throw failure;
return value;
};
const serializeLifecycle = async (run) => {
const inheritedOwner = lifecycleOwner.getStore();
if (inheritedOwner?.active) {
const operation = inheritedOwner.nestedTail.then(async () => await runLifecycleOwner(createLifecycleOwner(), run));
inheritedOwner.nestedTail = operation.then(() => void 0, () => void 0);
const propagated = operation.then(() => void 0);
propagated.catch(() => {});
inheritedOwner.pendingOperations.add(propagated);
return await operation;
}
const previous = lifecycle;
const { promise, resolve: release } = createDeferredCore();
lifecycle = promise;
await previous;
const owner = createLifecycleOwner();
try {
return await runLifecycleOwner(owner, run);
} finally {
release();
}
};
const settleWithinTeardownBudget = async (operation) => {
if (!await settlesWithin(operation, TRANSCRIPT_TEARDOWN_BUDGET_MS)) log$6.error(`transcript teardown budget expired: runId=${params.runId ?? "unknown"} sessionId=${params.sessionId ?? "unknown"} pendingWrites=${pendingWrites} timeoutMs=${TRANSCRIPT_TEARDOWN_BUDGET_MS}`);
};
const beginCleanup = async () => {
if (lifecycleOwner.getStore()?.active) throw new Error("cannot start attempt cleanup inside a transcript write callback");
cleanupDrain ??= settleWithinTeardownBudget(serializeLifecycle(() => {
lifecycleOwner.disable();
}));
await cleanupDrain;
};
return {
withTranscriptWrite: (run) => {
const activeDescendant = lifecycleOwner.getStore()?.active === true;
if ((cleanupDrain || disposed) && !activeDescendant) {
const rejected = Promise.reject(/* @__PURE__ */ new Error(disposed ? "attempt disposed before transcript write" : "attempt cleanup started before transcript write"));
rejected.catch(() => {});
return rejected;
}
pendingWrites += 1;
const operation = serializeLifecycle(run);
operation.finally(() => {
pendingWrites -= 1;
}).catch(() => {});
return operation;
},
beginCleanup,
dispose: async () => {
if (lifecycleOwner.getStore()?.active) throw new Error("cannot dispose an attempt from inside a transcript write callback");
disposePromise ??= (async () => {
await beginCleanup();
disposed = true;
})();
await disposePromise;
}
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt-transcript-lifecycle-prepare.ts
/** Prepares the admitted writer context and teardown tracker for one attempt. */
async function prepareEmbeddedAttemptTranscriptLifecycle(input) {
const { attempt, externalAbortController } = input;
const preparedTarget = attempt.preparedSessionTarget;
preparedTarget?.assertCurrent();
const initialWriter = getOwnedSessionTranscriptInitialWriter({
sessionFile: attempt.sessionFile,
sessionKey: attempt.sessionKey,
sessionTarget: attempt.sessionManager?.getSessionTarget() ?? attempt.sessionTarget
});
const sessionTarget = preparedTarget?.target ?? await resolveAgentRunSessionTarget({
agentId: attempt.sessionTarget?.agentId,
config: attempt.config,
missingSessionKey: "resolve-existing",
sessionFile: attempt.sessionFile,
sessionId: attempt.sessionId,
sessionKey: attempt.sessionKey,
sessionTarget: attempt.sessionTarget
});
await externalAbortController.throwIfFiredAfterPrepCleanup();
preparedTarget?.assertCurrent();
initialWriter?.assertActive();
const fencedSessionTarget = {
...sessionTarget,
expectedLifecycleRevision: attempt.sessionTarget?.expectedLifecycleRevision,
expectedWriterRunId: attempt.sessionTarget?.expectedWriterRunId
};
const generation = await prepareCronRootSessionGeneration({
...sessionTarget,
sessionKey: attempt.sessionKey ?? sessionTarget.sessionKey,
lifecycleRevision: fencedSessionTarget.expectedLifecycleRevision
}, input.runAbortController ? (reason) => input.runAbortController?.abort(reason) : void 0);
const lifecycle = createEmbeddedAttemptTranscriptLifecycle({
runId: attempt.runId,
sessionId: attempt.sessionId
});
const transcriptLifecycle = {
...lifecycle,
dispose: async () => {
try {
await lifecycle.dispose();
} finally {
generation?.release();
}
}
};
const assertAdmittedActive = attempt.admittedRunContext ? resolveAdmittedRunActiveAssertion(attempt.admittedRunContext, attempt.abortSignal) : void 0;
const withTranscriptWrite = (operation) => initialWriter ? initialWriter.withTranscriptWrite(() => transcriptLifecycle.withTranscriptWrite(operation)) : transcriptLifecycle.withTranscriptWrite(operation);
const ownedTranscriptWriteContext = {
sessionFile: attempt.sessionFile,
sessionKey: attempt.sessionKey,
sessionTarget: fencedSessionTarget,
sessionReader: getReplyOperationSessionReader(attempt.replyOperation),
...initialWriter ? { initialWriter } : {},
assertCommitAllowed: composeSessionSourceAssertion([assertAdmittedActive, generation?.assertCurrent], (assertSources) => {
attempt.abortSignal?.throwIfAborted();
assertSources();
}),
withTranscriptWrite
};
externalAbortController.arm();
try {
await externalAbortController.throwIfFiredAfterPrepCleanup();
preparedTarget?.assertCurrent();
} catch (error) {
await transcriptLifecycle.dispose();
throw error;
}
return {
compactionTimeoutMs: resolveCompactionTimeoutMs(attempt.config),
assertCronRootCurrent: generation ? ownedTranscriptWriteContext.assertCommitAllowed : void 0,
ownedTranscriptWriteContext,
transcriptLifecycle,
withOwnedTranscriptWrite: (operation) => withOwnedSessionTranscriptWrites(ownedTranscriptWriteContext, async () => withTranscriptWrite(operation))
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/attempt.ts
async function runEmbeddedAttempt(input) {
const modelExecution = bindOperatorModelExecution(readRunOperatorAuthority(input), {
provider: input.provider,
model: input.modelId
});
try {
const attempt = modelExecution ? {
...input,
abortSignal: input.abortSignal ? AbortSignal.any([input.abortSignal, modelExecution.signal]) : modelExecution.signal
} : input;
const parentSignal = getAsyncWorkSignal();
const resourceAbortSignal = parentSignal ? attempt.abortSignal ? AbortSignal.any([attempt.abortSignal, parentSignal]) : parentSignal : attempt.abortSignal;
const result = await runWithAsyncWorkResources((onAcquired) => runEmbeddedAttemptOwned(attempt, (release) => onAcquired({
release,
releaseBeforeResultWhenIdle: true
}), resourceAbortSignal));
modelExecution?.assertCurrent();
return result;
} finally {
modelExecution?.release();
}
}
async function runEmbeddedAttemptOwned(input, retainToolCleanup, resourceAbortSignal) {
let params = input;
const runAbortController = new AbortController();
const setup = await measureEmbeddedAgentPreparation("attempt.setup", () => prepareEmbeddedAttemptSetup(params), { config: params.config });
const { effectiveWorkspace, emitCorePluginToolStageSummary, prepStages, sandbox, sessionAgentId } = setup;
let restoreSkillEnv;
const executionState = {
beforeAgentRunBlockedBy: void 0,
terminal: params.abortSignal?.aborted ? {
kind: "aborted",
source: "external"
} : { kind: "ok" },
trajectoryEndRecorded: false
};
let emitDiagnosticRunCompleted;
const bundleRuntimes = {
bundleMcpRuntime: void 0,
bundleLspRuntime: void 0
};
let toolSearchCatalogRef;
let toolSearchCatalogApplied = false;
let releasePreparedTools;
let unsubscribeYieldTurnEnd;
const sessionResources = createEmbeddedAttemptSessionResources(params.config, runAbortController.signal);
const cleanupStep = (step, cleanup) => runOwnedAgentCleanup({
...params,
step,
cleanup,
log: log$6
});
const cleanupEmbeddedPrepResourcesAfterEarlyExit = async () => {
if (toolSearchCatalogApplied) {
clearToolSearchCatalog({ catalogRef: toolSearchCatalogRef });
toolSearchCatalogApplied = false;
}
for (const key of ["bundleMcpRuntime", "bundleLspRuntime"]) try {
await bundleRuntimes[key]?.dispose();
} catch {
recordAgentCleanupFailure();
} finally {
bundleRuntimes[key] = void 0;
}
};
const externalAbortController = createEmbeddedAttemptExternalAbortController({
abortSignal: resourceAbortSignal,
cleanupAfterEarlyAbort: cleanupEmbeddedPrepResourcesAfterEarlyExit,
runAbortController,
runId: params.runId,
state: executionState
});
const prepare = createEmbeddedAttemptPreparation({
config: params.config,
assertCurrent: externalAbortController.throwIfFired
});
const assertActiveRun = resolveAdmittedRunActiveAssertion(params.admittedRunContext, params.abortSignal);
try {
const preparedSkills = await prepare("attempt.skills", () => prepareEmbeddedSkills({
assertCurrent: () => {
externalAbortController.throwIfFired();
assertActiveRun?.();
},
includeCodeModeSkills: true,
attempt: params,
effectiveWorkspace,
sandbox,
sessionAgentId
}));
restoreSkillEnv = preparedSkills.restoreSkillEnv;
const { codeModeSkills, installedSkills, skillReadResources, skillUsagePaths, skillsPrompt, skillsSnapshotForRun } = preparedSkills;
if (params.skillsSnapshot?.librarySelections?.length && sandbox?.enabled) {
const remapped = remapSkillReferencePaths(params.prompt, skillUsagePaths);
if (remapped !== params.prompt) params = {
...params,
prompt: remapped,
transcriptPrompt: params.transcriptPrompt ?? params.prompt
};
}
prepStages.mark("skills");
const isRawModelRun = params.modelRun === true || params.promptMode === "none";
if (isRawModelRun && log$6.isEnabled("debug")) log$6.debug(`raw model run enabled: modelRun=${params.modelRun === true} promptMode=${params.promptMode ?? "unset"}`);
const activeContextEngine = isRawModelRun ? void 0 : params.contextEngine;
if (activeContextEngine && activeContextEngine.info.id !== "legacy") assertContextEngineHostSupport({
contextEngine: activeContextEngine,
operation: "agent-run",
host: OPENCLAW_EMBEDDED_CONTEXT_ENGINE_HOST
});
const resolveActiveContextEnginePluginId = () => resolveContextEngineOwnerPluginId(activeContextEngine);
const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, sessionAgentId);
const { diagnosticTrace, runTrace, emitCompleted } = startEmbeddedAttemptDiagnostics(params);
emitDiagnosticRunCompleted = emitCompleted;
const corePluginToolStages = createStageTimingTracker(Date.now);
let toolSearchCatalogExecutor;
const executeCatalogTool = (toolParams, surface) => {
if (!toolSearchCatalogExecutor) throw new Error(`${surface} catalog executor is unavailable for this run.`);
return toolSearchCatalogExecutor(toolParams);
};
const preparedToolBase = await prepare("attempt.tool-base", () => prepareEmbeddedAttemptToolBase({
agentDir,
attempt: params,
setup,
markCoreToolStage: (name) => corePluginToolStages.mark(name),
onYield: (message, acknowledgment, messageWaitRegistered) => {
yieldDetected = true;
yieldMessage = message;
yieldAcknowledgment = acknowledgment;
yieldMessageWaitRegistered = messageWaitRegistered;
},
runAbortController,
runTrace,
skillUsagePaths,
skillReadResources,
skillsSnapshot: skillsSnapshotForRun,
codeModeSkills,
installedSkills,
reviewTranscript: sessionResources.reviewTranscript,
toolSearchCatalogExecutor: (toolParams) => executeCatalogTool(toolParams, "Tool Search")
}));
toolSearchCatalogRef = preparedToolBase.toolSearchCatalogRef;
const { codeModeControlsEnabledForRun, toolSearchControlsEnabledForRun, toolSearchRuntimeConfig, toolsEnabled, toolsRaw } = preparedToolBase;
releasePreparedTools = preparedToolBase.releaseTools;
prepStages.mark("core-plugin-tools");
emitCorePluginToolStageSummary("core-plugin-tools", corePluginToolStages.snapshot());
const preparedBootstrap = await prepare("attempt.bootstrap", () => prepareEmbeddedAttemptBootstrap({
attempt: params,
setup,
hasReadTool: toolsEnabled && toolsRaw.some((tool) => tool.name === "read"),
isRawModelRun
}));
let yieldDetected = false;
let yieldMessage = null;
let yieldAcknowledgment;
let yieldMessageWaitRegistered;
let yieldAbortSettled = null;
const preparedBundleTools = await prepare("attempt.bundle-tools", () => prepStages.measure("bundle-tools", () => prepareEmbeddedAttemptBundleTools({
agentDir,
attempt: params,
setup,
isRawModelRun,
preparedToolBase
})));
bundleRuntimes.bundleMcpRuntime = preparedBundleTools.bundleMcpRuntime;
bundleRuntimes.bundleLspRuntime = preparedBundleTools.bundleLspRuntime;
const { clientTools, uncompactedEffectiveTools } = preparedBundleTools;
toolSearchCatalogApplied = toolSearchCatalogRef !== void 0;
const preparedToolCatalog = await prepare("attempt.tool-catalog", () => prepStages.measure("tool-catalog", () => prepareEmbeddedAttemptToolCatalog({
attempt: params,
setup,
preparedToolBase,
bundleTools: {
clientTools,
uncompactedEffectiveTools
},
abortSignal: runAbortController.signal,
executeCodeModeTool: (toolParams) => executeCatalogTool(toolParams, "Code Mode")
})));
prepStages.mark("tool-preparation");
const { effectiveTools, toolSearch, toolSearchRunPlan } = preparedToolCatalog;
toolSearchCatalogApplied = toolSearch.catalogRegistered;
const preparedSystemPrompt = await prepare("attempt.system-prompt", () => prepareEmbeddedAttemptSystemPrompt({
activeContextEngine,
attempt: params,
setup,
bootstrap: preparedBootstrap,
capabilityToolNames: toolSearchRunPlan.capabilityToolNames,
requireExplicitMessageTarget: preparedToolBase.requireExplicitMessageTarget,
effectiveTools,
isRawModelRun,
modelToolsEnabled: toolsEnabled,
skillsPrompt,
codeModeActive: codeModeControlsEnabledForRun,
webSearchUnconfigured: () => preparedToolBase.webSearchUnconfigured,
toolSearchCatalogRef,
toolSearchDirectoryEnabled: toolSearchControlsEnabledForRun && toolSearch.catalogRegistered,
toolSearchRuntimeConfig
}));
const sessionLock = await prepare("attempt.transcript-lifecycle", () => prepareEmbeddedAttemptTranscriptLifecycle({
attempt: params,
runAbortController,
externalAbortController
}));
try {
const preparedSessionRuntime = await prepare("attempt.session-runtime", () => prepareEmbeddedAttemptSessionRuntime({
attempt: params,
...activeContextEngine ? { activeContextEngine } : {},
agentDir,
isRawModelRun,
resolveActiveContextEnginePluginId,
setup,
toolBase: preparedToolBase,
toolCatalog: preparedToolCatalog,
bundleTools: preparedBundleTools,
systemPrompt: preparedSystemPrompt,
sessionLock,
runAbortSignal: runAbortController.signal,
externalAbortController,
resources: sessionResources.resources,
onSessionYieldReady: ({ abortActiveSession, activeSession }) => {
unsubscribeYieldTurnEnd = activeSession.agent.subscribe((event) => {
if (event.type !== "turn_end" || !yieldDetected || runAbortController.signal.aborted) return;
queueSessionsYieldInterruptMessage(activeSession);
runAbortController.abort(SESSIONS_YIELD_ABORT_REASON);
yieldAbortSettled = abortActiveSession(SESSIONS_YIELD_ABORT_REASON);
});
}
}));
const promptToolPolicy = createPromptBuildToolPolicy({
session: preparedSessionRuntime.agentSession.activeSession,
readModelTools: () => preparedSessionRuntime.agentSession.activeSession.agent.state.tools,
effectiveTools,
uncompactedEffectiveTools,
tools: preparedBundleTools.tools,
catalogRef: preparedToolBase.toolSearchCatalogRef,
codeModeControlsEnabled: preparedToolBase.codeModeControlsEnabledForRun,
onApplied: (surface) => {
const allowedNames = /* @__PURE__ */ new Set([...surface.activeToolNames, ...surface.uncompactedEffectiveTools.map((tool) => tool.name)]);
preparedToolCatalog.applyPromptToolPolicy(allowedNames);
},
forceToolNames: [...preparedToolBase.forceDirectMessageTool ? ["message"] : [], ...params.swarmCollector && params.swarmOutputSchema ? ["structured_output"] : []]
});
const executionResult = await runEmbeddedAttemptExecutionPhase({
attempt: params,
...activeContextEngine ? { activeContextEngine } : {},
agentDir,
isRawModelRun,
resolveActiveContextEnginePluginId,
runAbortController,
externalAbortController,
prepared: {
bootstrap: preparedBootstrap,
bundleTools: preparedBundleTools,
sessionRuntime: preparedSessionRuntime,
systemPrompt: preparedSystemPrompt,
toolBase: preparedToolBase,
toolCatalog: preparedToolCatalog,
promptToolPolicy
},
sessionLock,
setup,
diagnostics: {
diagnosticTrace,
runTrace
},
state: executionState,
lifecycle: {
applyPermissionMode: (mode, revokeApprovals) => withRuntimeToolSchemaQuarantine(async (recordQuarantine) => {
await preparedToolBase.refreshPermissionMode(mode, revokeApprovals);
preparedBundleTools.refreshTools(recordQuarantine);
preparedToolCatalog.refreshTools(recordQuarantine);
preparedSessionRuntime.agentSession.refreshTools();
promptToolPolicy.refresh();
const prepareToolPrompt = preparedSystemPrompt.prepareToolPrompt;
preparedSessionRuntime.agentSession.setPermissionPromptPreparation(prepareToolPrompt ? () => prepareToolPrompt(promptToolPolicy.current.effectiveTools, { permissionChanged: true }) : void 0);
params.permissionChange?.recordApplied(mode);
}),
readYieldState: () => ({
yieldAbortSettled,
yieldDetected,
yieldMessage,
yieldAcknowledgment,
yieldMessageWaitRegistered
}),
setToolSearchCatalogExecutor: (executor) => {
toolSearchCatalogExecutor = executor;
}
}
});
const catalogSession = toolSearchCatalogRef?.current;
const providerRetry = preparedSessionRuntime.agentSession.settingsManager.getProviderRetrySettings();
return {
...executionResult,
codeModeEngaged: codeModeControlsEnabledForRun,
providerRetryMaxRetries: providerRetry.maxRetries,
providerRetryMaxDelayMs: providerRetry.maxRetryDelayMs,
...catalogSession ? { bridgeCalls: {
search: catalogSession.searchCount,
describe: catalogSession.describeCount,
call: catalogSession.callCount
} } : {}
};
} finally {
unsubscribeYieldTurnEnd?.();
unsubscribeYieldTurnEnd = void 0;
sessionResources.releaseReview();
const sessionBundleRuntimes = { ...bundleRuntimes };
bundleRuntimes.bundleMcpRuntime = void 0;
bundleRuntimes.bundleLspRuntime = void 0;
toolSearchCatalogApplied = false;
await cleanupStep("embedded-session", () => cleanupEmbeddedAttemptSessionPhase({
attempt: params,
...sessionResources.resources,
transcriptLifecycle: sessionLock.transcriptLifecycle,
...sessionBundleRuntimes,
toolSearchCatalogRef,
trajectoryEndRecorded: executionState.trajectoryEndRecorded,
deferredLifecycleOwner: executionState.deferredLifecycleOwner,
emitDiagnosticRunCompleted,
state: executionState
}));
}
} catch (cause) {
let error = cause;
try {
externalAbortController.throwIfFired();
} catch (abortError) {
error = abortError;
}
const terminalOutcome = buildAgentRunTerminalOutcomeFromAttempt({
terminal: executionState.terminal,
abortSignal: params.abortSignal
});
if (terminalOutcome.status === "timeout") throw new AgentRunTerminalOutcomeError(error, terminalOutcome);
throw error;
} finally {
const resolveCleanupReason = () => {
const terminal = projectAgentRunAttemptTerminal(executionState.terminal);
return terminal.timedOut || terminal.timedOutDuringCompaction || terminal.timedOutDuringToolExecution ? "timeout" : terminal.aborted ? "cancel" : terminal.failed ? "error" : "completion";
};
const releaseTools = async () => {
const cleanupReason = resolveCleanupReason();
try {
await cleanupStep("embedded-registered-resources", async () => {
try {
await releasePreparedTools?.(cleanupReason);
} catch {
recordAgentCleanupFailure();
}
});
} finally {
externalAbortController.dispose();
}
};
if (resolveCleanupReason() === "completion") retainToolCleanup(releaseTools);
else await releaseTools();
clearToolActivityRun(params.runId);
try {
await cleanupStep("embedded-preparation", cleanupEmbeddedPrepResourcesAfterEarlyExit);
} catch (cleanupErr) {
recordAgentCleanupFailure();
log$6.warn(`failed to clean up embedded prep resources after early attempt exit: runId=${params.runId} ${String(cleanupErr)}`);
}
const terminal = projectAgentRunAttemptTerminal(executionState.terminal);
emitDiagnosticRunCompleted?.(terminal.aborted ? "aborted" : "error", terminal.promptError ?? /* @__PURE__ */ new Error("run exited before diagnostic completion"));
restoreSkillEnv?.();
}
}
//#endregion
//#region src/agents/harness/types.ts
/**
* Checks whether an agent harness explicitly declares support for turn-scoped
* tool restrictions (such as dynamic tool prefiltering).
*/
function harnessSupportsTurnScopedToolRestrictions(harness) {
return harness?.supportsTurnScopedToolRestrictions === true;
}
//#endregion
//#region src/agents/harness/builtin-openclaw.ts
const builtInOpenClawHarnesses = /* @__PURE__ */ new WeakSet();
function buildRestrictedFinalizationAttempt(attempt) {
return {
admittedRunContext: attempt.admittedRunContext,
sessionId: attempt.sessionId,
sessionKey: attempt.sessionKey,
sessionTarget: attempt.sessionTarget,
lifecycleGeneration: attempt.lifecycleGeneration,
promptCacheKey: attempt.promptCacheKey,
sandboxSessionKey: attempt.sandboxSessionKey,
agentId: attempt.agentId,
workspaceDir: attempt.workspaceDir,
cwd: attempt.cwd,
agentDir: attempt.agentDir,
config: attempt.config,
prompt: attempt.prompt,
timeoutMs: attempt.timeoutMs,
runTimeoutOverrideMs: attempt.runTimeoutOverrideMs,
runId: attempt.runId,
abortSignal: attempt.abortSignal,
onExecutionStarted: attempt.onExecutionStarted,
onExecutionPhase: attempt.onExecutionPhase,
onLaneWait: attempt.onLaneWait,
onRunProgress: attempt.onRunProgress,
onAgentEvent: attempt.onAgentEvent,
deferTerminalLifecycle: attempt.deferTerminalLifecycle,
onAttemptTimeoutArmed: attempt.onAttemptTimeoutArmed,
onAttemptDeadlineChanged: attempt.onAttemptDeadlineChanged,
onAttemptTimeout: attempt.onAttemptTimeout,
onAttemptAbort: attempt.onAttemptAbort,
preparedModelRuntime: attempt.preparedModelRuntime,
sessionFile: attempt.sessionFile,
sessionManager: attempt.sessionManager,
sessionPersistence: attempt.sessionPersistence,
prepareAssistantTranscriptMessage: attempt.prepareAssistantTranscriptMessage,
contextTokenBudget: attempt.contextTokenBudget,
contextWindowInfo: attempt.contextWindowInfo,
resolvedApiKey: attempt.resolvedApiKey,
authProfileId: attempt.authProfileId,
authProfileIdSource: attempt.authProfileIdSource,
provider: attempt.provider,
modelId: attempt.modelId,
requestedModelId: attempt.requestedModelId,
agentHarnessId: attempt.agentHarnessId,
runtimePlan: attempt.runtimePlan,
model: attempt.model,
authStorage: attempt.authStorage,
authProfileStore: attempt.authProfileStore,
toolAuthProfileStore: attempt.toolAuthProfileStore,
modelRegistry: attempt.modelRegistry,
thinkLevel: attempt.thinkLevel,
fastMode: attempt.fastMode,
fastModeAuto: attempt.fastModeAuto,
operation: "settled-tool-finalization",
disableTools: true,
disableTrajectory: true,
skipPreparedUserTurnMessage: true,
suppressNextUserMessagePersistence: true,
initialReplayState: {
replayInvalid: false,
hadPotentialSideEffects: false
}
};
}
function createOpenClawAgentHarness() {
const harness = {
...BUILTIN_AGENT_HARNESS_METADATA,
supportsTurnScopedToolRestrictions: true,
runAttempt: (params) => runEmbeddedAttempt({
...params,
supportsTurnScopedToolRestrictions: harnessSupportsTurnScopedToolRestrictions(harness)
}),
runIsolatedCompletionV2: runHostPreparedIsolatedCompletion,
finalizeSettledTurn: async ({ attempt }) => {
const result = await runEmbeddedAttempt(buildRestrictedFinalizationAttempt(attempt));
return projectSettledTurnFinalizationAttemptResult(result);
}
};
builtInOpenClawHarnesses.add(harness);
return harness;
}
/** Distinguishes the internal runtime from an untrusted harness that copies its public id. */
function isBuiltInOpenClawAgentHarness(harness) {
return builtInOpenClawHarnesses.has(harness);
}
//#endregion
export { resolveEmbeddedRunAttemptTerminalState as $, resolveReplayInvalidFlag as A, countSettledTurnDeliveryPayloads as B, markLastProviderPromptContextRejected as C, hasRunContinuationEvidence as D, YIELD_DIAGNOSTIC_TEXT as E, resolveReasoningOnlyRetryInstruction as F, prepareEmbeddedAttemptPromptExecution as G, isStrictAgenticExecutionContractActive as H, resolveSettledToolBatchEvidence as I, isEmbeddedRunTerminalAbort as J, getHistoryLimitFromSessionKey as K, resolveSettledToolTerminalContinuationInstruction as L, resolveSilentToolResultReplyPayload as M, shouldRetryMissingAssistantTurn as N, hasYieldContinuationEvidence as O, resolveEmptyResponseRetryInstruction as P, resolveEmbeddedRunAttemptTerminalOutcome as Q, shouldRetrySilentErrorAssistantTurn as R, getProviderPromptState as S, TRUNCATED_REPLY_NOTICE_TEXT as T, createEmbeddedRunReplayState as U, hasComposedVisibleAnswerAfterSettledTools as V, observeReplayMetadata as W, isEmbeddedRunTerminalTimeout as X, isEmbeddedRunTerminalInterrupted as Y, isEmbeddedRunTimeoutFinal as Z, setCompactionSafeguardCancellation as _, resolveEmbeddedSessionContextLimits as a, flushPendingToolResultsAfterIdle as at, createToolTerminalObserver as b, collectRegisteredToolNames as c, createRealConversationClassifier as d, hasAsyncActivity as et, isRealConversationMessage as f, getCompactionSafeguardRuntime as g, consumeCompactionSafeguardCancellation as h, resolveAgentRunSessionTarget as i, shouldContinueInteractiveAcceptedSessionSpawns as it, resolveRunLivenessState as j, resolveIncompleteTurnPayloadText as k, toSessionToolAllowlist as l, readPostCompactionContext as m, isBuiltInOpenClawAgentHarness as n, isCurrentAttemptReplaySafe as nt, withReleasableExecutionCapacity as o, prepareEmbeddedSkills as ot, appendPostCompactionRefreshPrompt as p, limitHistoryTurns as q, applyAgentRunSessionTargetIdentity as r, resolveCurrentAttemptAssistant as rt, collectAllowedToolNames as s, logRuntimeToolSchemaQuarantine as st, createOpenClawAgentHarness as t, hasAttemptTerminalState as tt, buildEmbeddedExtensionFactories as u, resolveProviderRefusal as v, createAttemptCarryover as w, clearProviderPromptState as x, createDeferredEmbeddedRunLifecycleManager as y, shouldTreatEmptyAssistantReplyAsSilent as z };