openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
8,173 lines • 371 kB
JavaScript
import { S as parseStrictInteger, f as asSafeIntegerInRange, l as asNonNegativeFiniteNumber, s as asFiniteNumber } from "./number-coercion-CLj0HTDM.js";
import { t as sanitizeForLog } from "./ansi-DrXAcdMD.js";
import "./src-vebZIeLe.js";
import { n as estimateStringChars } from "./cjk-chars-CGxY6W63.js";
import { t as expectDefined } from "./expect-CyE8FADM.js";
import { a as asOptionalRecord, c as isRecord } from "./record-coerce-DItp3I4t.js";
import { g as readStringValue, l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty, t as hasNonEmptyString } from "./string-coerce-CIXf7egm.js";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { r as defaultRuntime } from "./runtime-CF2WjnNZ.js";
import { t as expandHomePrefix } from "./home-dir-BPhrG-aM.js";
import { t as createLazyImportLoader } from "./lazy-promise-DGqyc4Y4.js";
import { n as getRuntimeConfig } from "./io.runtime-B9iJRs3w.js";
import { c as resolveAgentConfig, y as resolveDefaultAgentId } from "./agent-scope-config-DcbEhP0R.js";
import { T as isSubagentSessionKey } from "./session-key-BnWWjqNc.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { C as createChildDiagnosticTraceContext, D as freezeDiagnosticTraceContext, f as isDiagnosticsEnabled, o as emitTrustedDiagnosticEvent } from "./diagnostic-events-Cwe92uV3.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { o as measureDiagnosticsTimelineSpan } from "./diagnostics-timeline-CRX1LXmg.js";
import { a as getNodeSqliteKysely, r as executeSqliteQuerySync } from "./kysely-sync-COmh4HWh.js";
import { t as createDedupeCache } from "./dedupe-gst1CUro.js";
import "./strip-inbound-meta-D5F_RINt.js";
import { a as isSilentReplyPrefixText, c as stripLeadingSilentToken, n as SILENT_REPLY_TOKEN, o as isSilentReplyText, s as startsWithSilentToken } from "./tokens-DbeAFqg7.js";
import { a as generateSecureUuid } from "./secure-random-Ds4AFLgz.js";
import { x as findModelInCatalog, y as resolveModelRefFromString } from "./model-selection-shared-BlLyx1r2.js";
import "./defaults-CdX9UGcX.js";
import { A as resolveCollapsedSessionAuthPinSource, E as hasSessionAutoModelFallbackProvenance, O as resolveSessionModelOverrideRouteResolution, h as resolveModelFallbackAvailability, i as markAutoFallbackPrimaryProbe, j as resolveSessionAuthProfileOverrideSource, n as entryMatchesAutoFallbackPrimaryProbe, p as resolveAutoFallbackPrimaryProbe, t as clearAutoFallbackPrimaryProbeSelection } from "./agent-scope-DbtJyKUL.js";
import { d as resolveEffectiveResponseUsage, u as normalizeVerboseLevel } from "./thinking.shared-bHYuuc1L.js";
import { u as resolveOpenAIRuntimeProvider } from "./openai-routing-Ex_3E9TF.js";
import "./config-Cs0XXL3x.js";
import { r as logVerbose } from "./globals-CTaGxEqj.js";
import { f as onAgentEventForRun, i as emitAgentEvent, n as captureAgentRunLifecycleGeneration, v as withAgentRunLifecycleGeneration } from "./agent-events-CoxiItUi.js";
import { a as clearAgentRunContext, h as registerAgentRunContext } from "./agent-run-registry-CKYKdfNd.js";
import { a as getPluginRuntimeGatewayRequestScope, c as withPluginRuntimeGatewayContextResolver, r as getGatewayContextResolver, t as bindGatewayContextResolver } from "./gateway-request-scope-BCMYlsDI.js";
import { t as GatewayDrainingError } from "./gateway-work-admission-R1IpuDim.js";
import { a as resolveMessageChannel } from "./message-channel-normalize-B81s0XMO.js";
import { a as isInternalMessageChannel, o as isMarkdownCapableMessageChannel } from "./message-channel-BQrhwUEA.js";
import { r as leaseMcpAppModelContextForTurn } from "./mcp-app-model-context-dSmaJhyr.js";
import { s as peekSessionMcpRuntime } from "./agent-bundle-mcp-manager-api-DCHQm4w1.js";
import { F as ensureMessageToolRunOutcomeSchema } from "./openclaw-agent-db-maintenance-wTIy-jt-.js";
import { t as normalizeChatType } from "./chat-type-CG0X_HJM.js";
import { g as runOpenClawAgentWriteTransaction, p as openOpenClawAgentDatabase } from "./openclaw-agent-db-CWtDoRbC.js";
import { a as classifyOAuthRefreshFailureError, i as classifyOAuthRefreshFailure } from "./oauth-refresh-failure-DP-bO7C0.js";
import { o as isAudioFileName } from "./mime-CVpcq9ju.js";
import { t as resolveAgentHarnessPolicy } from "./policy-D9i1QMuw.js";
import { a as getReplyPayloadMetadata, d as isReplyPayloadStatusNotice, f as isReplyPayloadTerminalContent, g as markReplyPayloadForSourceSuppressionDelivery, i as copyReplyPayloadMetadata, t as FAST_MODE_AUTO_PROGRESS_KIND, y as setReplyPayloadMetadata } from "./reply-payload-DahKk-7n.js";
import { c as normalizeDeliveryContext, d as sessionDeliveryChannel } from "./delivery-context.shared-CXmRgetN.js";
import { a as enqueueSystemEvent } from "./system-events-C03jsM0j.js";
import { u as readChannelContextGatewayContextResolver } from "./admission-evidence-BW_2Woj4.js";
import { c as readSessionInputProfileId, r as isIngressAdoptionLostError } from "./ingress-drain-DcmwOHP5.js";
import { i as resetRegisteredAgentHarnessSessions } from "./registry-D1_C7waJ.js";
import { a as hasNonzeroUsage, i as hasBillableUsage, r as deriveSessionTotalTokens, t as deriveContextPromptTokens } from "./usage-CtmV8Xxq.js";
import { o as formatRawAssistantErrorForUi } from "./assistant-error-format-oP4GVe5A.js";
import { a as isContextOverflowError, s as isLikelyContextOverflowError, t as classifyFailoverReason } from "./classify-GlkuT1ur.js";
import { f as shouldPreserveUserFacingSessionStateForInputProvenance } from "./input-provenance-DiG-Cwnd.js";
import { rt as persistSessionResetLifecycle, wt as appendTranscriptMessage, xt as updateSessionEntry } from "./session-accessor-YsytfDtG.js";
import { d as patchSessionEntryCore, l as loadSessionEntry, u as loadSessionEntryReadOnly } from "./session-accessor.sqlite-entry-CWk3jL7s.js";
import { r as resolveGroupSessionKey } from "./group-DlTf9maP.js";
import { c as resolveSqliteScope, m as toDatabaseOptions } from "./session-accessor.sqlite-scope-2KfMzb44.js";
import { c as resolveSessionPluginTraceLines, s as resolveSessionPluginStatusLines } from "./types-BHV0IaPK.js";
import { h as resolveSessionPinnedHarnessId } from "./agent-harness-session-key-BOz3yx0-.js";
import { i as ModelSelectionLockedError, r as MODEL_SELECTION_LOCKED_RESET_MESSAGE, s as isModelSelectionLocked } from "./model-overrides-Bm7jo7Di.js";
import { n as getCliSessionBinding, t as clearAllCliSessions } from "./cli-session-binding-Aa6nlDa8.js";
import { i as hasRestartRecoverySourceClaim } from "./restart-recovery-state-DDdTIiD6.js";
import "./thinking-DterdXhr.js";
import { i as resolveCliBackendConfig } from "./cli-backends-Bs_a4MW0.js";
import { s as resolveCliRuntimeExecutionProvider, t as areRuntimeModelRefsEquivalent } from "./model-runtime-aliases-BJ2qM8I_.js";
import { i as resolveSessionRuntimeOverrideForProvider } from "./session-runtime-compat-DNAICzi6.js";
import { o as resolveModelAuthMode } from "./model-auth-C48_DZ-I.js";
import { s as isCliProvider, u as resolvePersistedOverrideModelRef } from "./model-selection-di2kjKCB.js";
import { a as isFailoverError } from "./error-Bb_OF8ag.js";
import { f as isAgentRunRestartAbortReason, h as resolveAgentRunErrorLifecycleFields, l as createAgentRunSupersededAbortError, m as resolveAgentRunAbortLifecycleFields } from "./run-termination-jabvTOp5.js";
import { u as stripLegacyBracketToolCallBlocks } from "./assistant-visible-text-BSYbiXdn.js";
import { r as sanitizeUserFacingText, t as renderUserFacingText } from "./user-facing-text-0KenW-TQ.js";
import { C as renderRateLimitReplyCopy, S as renderRateLimitOrOverloadedCopy, _ as renderControlUiAgentFailureCopy, h as renderBillingReplyCopy, i as PROVIDER_CONVERSATION_STATE_ERROR_USER_MESSAGE, r as HEARTBEAT_EXTERNAL_RUN_FAILURE_TEXT, v as renderFailoverCodeUserCopy } from "./user-copy-CxiPNpae.js";
import { c as resolveFastModeForElapsed, n as formatFastModeAutoProgressText } from "./fast-mode-CCX0YiYh.js";
import { s as resolveSourceReplyVisibilityPolicy } from "./source-reply-delivery-mode-Ct9PBal_.js";
import { a as hasNewGeneratedMediaTaskForSessionKey, r as getGeneratedMediaTaskIdsForSessionKey } from "./task-status-access-C6caarxH.js";
import { D as finalizeReplyMessageInjectionAttempt, E as beginReplyMessageInjectionTarget, H as runAfterReplyOperationClear, L as markReplyOperationExecutionStarted, M as hasReplyOperationExecutionStarted, V as retainReplyOperationUntilComplete, h as replyRunRegistry, m as markReplyOperationGlobalLaneWaitProgress } from "./reply-run-registry.registry-BMo0d96L.js";
import { t as beginReplyOperationFinalizationWork } from "./reply-run-finalization-lease-Ds9-0UNB.js";
import { n as QuestionDispatchRefusedError, t as QuestionAnswerUnconfirmedError } from "./gateway-question-dispatch-BkYi3BfY.js";
import { _ as RUN_STALE_TAKEOVER_MS } from "./diagnostic-run-activity-CJrEba4R.js";
import "./reply-run-registry-BlmBKV23.js";
import { s as logSessionTurnCreated } from "./diagnostic-ryAZMr5N.js";
import { d as resolveCronJobsStorePath, i as loadCronJobsStore } from "./store-K9I-tbar.js";
import { t as CommandLaneClearedError } from "./command-queue-C3Fv2rcU.js";
import { t as formatTokenCount } from "./token-format-D942KbWN.js";
import { v as resolveSessionGoalDisplayState } from "./sessions-9nxpeTwt.js";
import { u as stripHeartbeatToken } from "./heartbeat-ofet59jF.js";
import { n as normalizeReplyPayload } from "./normalize-reply-C1iRjnEc.js";
import { m as recordReplyUsageState, p as buildReplyUsageState } from "./deliver-prepare-6uVAVf8W.js";
import { r as formatUsd, t as estimateAggregateUsageCost } from "./usage-format-DhUP1uYp.js";
import { a as hasOutboundReplyContent } from "./reply-payload-Bfsi665c.js";
import { a as resolveSendableOutboundReplyParts } from "./reply-payload-parts-BsFA4sDv.js";
import { t as parseReplyDirectives } from "./reply-directives-DWcXX_ma.js";
import { a as resolveReplyThreadingPayloads, i as isRenderablePayload, n as applyReplyThreading, t as applyReplyTagsToPayload } from "./reply-payloads-OWAnL0iD.js";
import { n as createReplyToModeFilterForChannel, o as resolveReplyToMode, t as createReplyDeliveryContext } from "./reply-threading-b8mBrrsZ.js";
import { m as isCompactionFailureError } from "./embedded-agent-helpers-D0eiT3IT.js";
import { n as filterMessagingToolReplyPayload } from "./reply-payloads-dedupe-Di5U9fOH.js";
import { f as readPostCompactionContext, v as createDeferredEmbeddedRunLifecycleManager } from "./builtin-openclaw-B_H1oNzF.js";
import { i as isAgentHarnessPreflightError } from "./errors-70ml6R0Z.js";
import { f as resolveFailoverReasonFromError, o as findCliTimeoutError } from "./failover-error-BkNxlp8A.js";
import { a as admitReplyTurn, i as isReplyProfilerEnabled, n as createReplySessionEntryHandle, o as resolveReplyTurnKind, r as createReplyTimingTracker, t as ReplySessionGenerationInvalidatedError } from "./session-entry-handle-DbGQ7dok.js";
import { _ as markPostCompactionModelFailurePayload, a as createReplyRestartRecoveryClaimController, b as resolveReplyFailoverFacts, c as buildAuthProfileFailoverFailureText, d as buildKnownAgentRunFailureReplyPayload, f as buildPreflightCompactionFailureText, g as markAgentRunFailureReplyPayload, h as isVerboseFailureDetailEnabled, i as resolveTurnCommentaryProgressOwner, l as buildEmptyInteractiveReplyPayload, m as isNonDirectConversationContext, o as isDuplicateRestartRecoverySource, p as buildTerminalAgentRunFailureReplyPayload, r as shouldBridgeCliPreambleEvents, s as retireTerminalRestartRecoverySourceClaim, t as REPLY_ADMISSION_TICKET, u as buildExternalRunFailureReply, v as renderPostCompactionModelFailurePayload, y as resolveExternalRunFailureTextForConversation } from "./reply-admission-ticket-BvAkCgpI.js";
import { f as createAudioAsVoiceBuffer, m as createBlockReplyPipeline, p as createBlockReplyContentKey, u as requiresDurableToolResultDelivery } from "./dispatch-from-config.payloads-D95ct5gu.js";
import { a as revokeMessageActionTurnCapability, i as resolveMessageActionTurnCapabilityLifetime, n as mintMessageActionTurnCapability, t as isTrustedMessageActionTurnIngress } from "./message-action-turn-capability-CpL-pT1E.js";
import { d as isMessagingToolSendAction } from "./tool-loop-detection-C39fRNx6.js";
import { r as claimPendingAgentQuestionAnswerFromCaller } from "./gateway-question-DoL5eagC.js";
import { i as isCommandBearingToolCall, r as inferToolMetaFromArgsCore } from "./tool-display-CzOArp4x.js";
import { t as formatToolAggregate } from "./tool-meta-DUrV7YlL.js";
import { f as normalizeAgentPlanSteps } from "./streaming-B_uLiLJZ.js";
import { a as buildRestartLifecycleReplyText, c as resolveReplyOperationTerminationFields, i as resolveReplyOperationRunState, l as resolveRestartLifecycleError, n as recordReplyOperationAgentTurn, o as isReplyOperationSuperseded, s as resolveReplyOperationAbortReason, u as resolveAgentTurnExecutionStatus } from "./reply-operation-run-state-B5ZoFcCR.js";
import { r as LiveSessionModelSwitchError } from "./model-fallback-runner-DfBb8xXp.js";
import { n as clearBootstrapSnapshotOnSessionBoundary } from "./bootstrap-cache-Bpsy6pH_.js";
import { l as shouldClearFailedCliSessionBinding, r as clearCliSession } from "./cli-session-pAa_-y_N.js";
import { i as normalizePendingFinalRecoveryPayloads, n as buildRecoverablePendingFinalDeliveryText, o as sanitizePendingFinalDeliveryText, r as normalizePendingFinalDeliveryPayloads } from "./pending-final-delivery-jd8h0cqi.js";
import { s as transitionMainSessionRecovery } from "./main-session-recovery-state-DD1DvBOq.js";
import { n as resolveRoutedDeliveryThreadId } from "./routed-delivery-thread-D8RIfcVU.js";
import { i as buildGenericCliContextEngineHostSupport } from "./host-compat-xESS3bi6.js";
import { i as getMcpAppViewLease } from "./mcp-ui-resource-BIyljeqN.js";
import { o as resolveBootstrapWarningSignaturesSeen } from "./bootstrap-budget-7_XA_4pm.js";
import { t as readLatestSessionUsageFromTranscriptAsync } from "./session-transcript-readers-CYDRQsH5.js";
import { n as resolveSessionModelRef } from "./session-model-ref-CPZiclLt.js";
import { a as resolveContextTokensForModel } from "./context-B0EuZFRZ.js";
import { t as resolveFastModeState } from "./fast-mode-CTXu8q8K.js";
import { t as formatProviderModelRef } from "./model-runtime-BzIef07I.js";
import { c as refreshQueuedFollowupSession, d as admitFollowupRunLifecycle, f as completeFollowupRunLifecycle, g as resolveFollowupAbortSignal, p as isFollowupRunAborted, u as FollowupRunDeferredError } from "./settings-jmDsm5RI.js";
import { t as formatSystemTurnPrompt } from "./system-turn-prompt-CqPm0DzY.js";
import { a as extractToolResultText } from "./embedded-agent-tool-results-ZMp1V0-a.js";
import { m as hasCompletedTerminalDeliveryEvidence, p as hasCompletedSourceReplyDeliveryEvidence, u as hasCommittedSourceReplyDeliveryEvidence, v as hasVisibleCommittedMessagingToolDeliveryEvidence, y as hasVisibleOutboundDeliveryEvidence } from "./delivery-evidence-5HyXxfSi.js";
import { r as resolveDelegationCapability } from "./agent-tools-By64BZu5.js";
import { n as routeReply, t as isRoutableChannel } from "./route-reply-KWrkVHr_.js";
import { c as scheduleFollowupDrain } from "./drain-Bt23d2Dt.js";
import { i as parkSteerCandidate, n as enqueueFollowupRun, t as resolveQueueSettings } from "./queue-BpaiO3X2.js";
import { i as createAgentPatchedSessionModelFallback } from "./session-model-patch-origin-OUh-HtF1.js";
import { n as resolveEffectiveReplyRoute } from "./effective-reply-route-BeVh8uZJ.js";
import { t as hasInboundAudio } from "./inbound-media-Bq3xonSo.js";
import { n as resolveSendPolicy } from "./send-policy-CO-YZiTT.js";
import { t as resolveOriginMessageProvider } from "./origin-routing-CgwQQ0I7.js";
import { n as readChannelSourceTurnId, t as buildChannelSourceTurnId } from "./source-turn-id-6iBUXSKV.js";
import { a as withLocalSessionPlacementTurnSettlement } from "./session-placement-admission-UsKSuaws.js";
import { n as hasDeliberateSilentTerminalReply, r as hasIntentionalTerminalCompletion } from "./result-fallback-classifier-C3DQO8OC.js";
import { t as buildAgentRuntimeDeliveryPlan } from "./build-DHcslHXI.js";
import { t as runEmbeddedAgent } from "./embedded-agent-D9hkv0XX.js";
import { a as withBeforeAgentReplyObserver } from "./payloads-BaBIriOY.js";
import { n as consolidateLiveModelSwitchAfterRun } from "./live-model-switch-CHtNpBLA.js";
import { n as incrementCompactionCount } from "./session-updates-DdrrbgwI.js";
import { n as createSourceReplyDeliveryRuntime, r as readSourceReplyDeliveryRuntime, t as bindSourceReplyDeliveryRuntime } from "./source-reply-delivery-runtime-D3ljIhoI.js";
import { t as createSessionDiffBaselineCaptureClaim } from "./session-diff-baseline-capture-6ejBT0Am.js";
import { t as ensureSessionDiffBaseline } from "./session-diff-baseline-CmCpfgY7.js";
import { n as resolveEffectiveBlockStreamingConfig } from "./block-streaming-BhGjv83H.js";
import { r as mergeSessionSnapshotChanges } from "./session-snapshot-merge-Cf1TdJBp.js";
import { t as REPLY_RUN_STILL_SHUTTING_DOWN_TEXT } from "./get-reply-run-queue-Bmv2zHLu.js";
import { i as resolveActiveRunQueueAction, n as createTypingSignaler, t as resolveCurrentTurnImages } from "./current-turn-images-CcUUUKjB.js";
import { i as refreshActiveGoalContext } from "./inbound-meta-D0F9464b.js";
import { t as settleProgressVisibilityCallbackResult } from "./progress-visibility-DVUJibF4.js";
import { i as runEmbeddedAgentEntry, n as invalidateTurnCompactionContext, r as recordTurnCompaction } from "./agent-runner-compaction-accounting-CztfzYyv.js";
import { n as resolveFollowupRunToolAuthorityFingerprint, r as resolveInboundReplyToolAuthorityOverlay, t as prepareReplyToolAuthority } from "./reply-tool-authority-tlxLRVuZ.js";
import { t as buildCliMcpDelegationCapabilityBinding } from "./mcp-grant-context-REH4fXZ7.js";
import { n as runCliAgent } from "./cli-runner-dxvZUkQb.js";
import { n as persistCliSessionBindingResult, r as settleCliSessionResult, t as clearCliSessionInStore } from "./cli-session-store-C8XIYI19.js";
import { n as prepareGitCoauthorAttribution } from "./git-coauthor-attribution-CsDxBk6r.js";
import { t as createReplyMediaContext } from "./reply-media-paths.runtime-aZ6uscwf.js";
import { t as emitAgentRunStatusEvent } from "./agent-run-status-events-BrfKkD7l.js";
import { a as resolveQueuedReplyExecutionConfig, c as resolveRunThinkingLevelForFallbackCandidate, d as resolveFallbackCandidateRun, f as resolveRunAuthProfile, i as isBunFetchSocketError, l as resolveModelFallbackOptions, n as buildThreadingToolContext, o as resolveQueuedReplyRuntimeConfig, r as formatBunFetchSocketError, s as resolveRunFastModeForFallbackCandidate, t as buildEmbeddedRunExecutionParams, u as resolveRunModelHasVision } from "./agent-runner-utils-4NqM1iMb.js";
import { n as runSessionCompactionIfNeeded, t as runMemoryFlushIfNeeded } from "./agent-runner-memory-DWbyP54E.js";
import { n as resolveAgentLifecycleTerminalMetadata, t as createAgentLifecycleTerminalBackstop } from "./agent-lifecycle-terminal-B1VniicW.js";
import { a as prepareGitHubPublicationAvailability } from "./github-publication-availability-BsRK-v3w.js";
import { n as prepareChannelRunAdmission } from "./channel-run-admission-CUuWSD0O.js";
import { t as createMcpAppStandaloneTicket } from "./mcp-app-standalone-q3HdQd9L.js";
import { t as getTailscalePublishedOrigin } from "./tailscale-published-origin-BvNdSLZR.js";
import { readFileSync, watch } from "node:fs";
import { isDeepStrictEqual } from "node:util";
import path, { resolve } from "node:path";
import { homedir } from "node:os";
import crypto from "node:crypto";
//#region src/auto-reply/fallback-state.ts
/** Formats model-fallback notice state for UI/status messages and persisted transition tracking. */
const FALLBACK_REASON_PART_MAX = 80;
const TRANSIENT_FALLBACK_REASONS = /* @__PURE__ */ new Set([
"rate_limit",
"overloaded",
"timeout",
"empty_response",
"no_error_details",
"unclassified"
]);
const TRANSIENT_ERROR_DETAIL_HINT_RE = /\b(?:429|5\d\d|too many requests|usage limit|quota|try again in|retry[- ]after|seconds?|minutes?|hours?|temporarily unavailable|overloaded|service unavailable|throttl\w*)\b/i;
function truncateFallbackReasonPart(value, max = FALLBACK_REASON_PART_MAX) {
const text = value.replace(/\s+/g, " ").trim();
if (text.length <= max) return text;
return `${truncateUtf16Safe(text, max - 1).trimEnd()}…`;
}
function formatFallbackAttemptErrorPreview(attempt) {
const rawError = attempt.error?.trim();
if (!rawError) return;
if (!attempt.reason || !TRANSIENT_FALLBACK_REASONS.has(attempt.reason)) return;
if (!TRANSIENT_ERROR_DETAIL_HINT_RE.test(rawError)) return;
const formatted = formatRawAssistantErrorForUi(rawError).replace(/^⚠️\s*/, "").replace(/\s+/g, " ").trim();
if (!formatted || /unknown error/i.test(formatted)) return;
return formatted;
}
function formatFallbackAttemptReason(attempt) {
const errorPreview = formatFallbackAttemptErrorPreview(attempt);
if (errorPreview) return errorPreview;
const reason = attempt.reason?.trim();
if (reason) return reason.replace(/_/g, " ");
const code = attempt.code?.trim();
if (code) return code;
if (typeof attempt.status === "number") return `HTTP ${attempt.status}`;
return truncateFallbackReasonPart(attempt.error || "error");
}
function formatFallbackAttemptSummary(attempt) {
return `${formatProviderModelRef(attempt.provider, attempt.model)} ${formatFallbackAttemptReason(attempt)}`;
}
function buildFallbackReasonSummary(attempts) {
const firstAttempt = attempts[0];
const firstReason = firstAttempt ? formatFallbackAttemptReason(firstAttempt) : "selected model unavailable";
const moreAttempts = attempts.length > 1 ? ` (+${attempts.length - 1} more attempts)` : "";
return `${truncateFallbackReasonPart(firstReason)}${moreAttempts}`;
}
function buildFallbackAttemptSummaries(attempts) {
return attempts.map((attempt) => truncateFallbackReasonPart(formatFallbackAttemptSummary(attempt)));
}
/** Builds the visible notice shown when runtime falls back from the selected model. */
function buildFallbackNotice(params) {
const selected = formatProviderModelRef(params.selectedProvider, params.selectedModel);
const active = formatProviderModelRef(params.activeProvider, params.activeModel);
if (areRuntimeModelRefsEquivalent(selected, active, { config: params.cfg })) return null;
return `↪️ Model Fallback: ${active} (selected ${selected}; ${buildFallbackReasonSummary(params.attempts)})`;
}
/** Builds the visible notice shown when runtime returns to the selected model. */
function buildFallbackClearedNotice(params) {
const selected = formatProviderModelRef(params.selectedProvider, params.selectedModel);
const previous = normalizeOptionalString(params.previousActiveModel);
if (previous && previous !== selected) return `↪️ Model Fallback cleared: ${selected} (was ${previous})`;
return `↪️ Model Fallback cleared: ${selected}`;
}
/** Resolves fallback state transitions and the next persisted notice-state fields. */
function resolveFallbackTransition(params) {
const selectedModelRef = formatProviderModelRef(params.selectedProvider, params.selectedModel);
const activeModelRef = formatProviderModelRef(params.activeProvider, params.activeModel);
const previousState = {
selectedModel: normalizeOptionalString(params.state?.fallbackNotice?.selectedModel),
activeModel: normalizeOptionalString(params.state?.fallbackNotice?.activeModel),
reason: normalizeOptionalString(params.state?.fallbackNotice?.reason)
};
const comparisonOptions = { config: params.cfg };
const fallbackActive = !areRuntimeModelRefsEquivalent(selectedModelRef, activeModelRef, comparisonOptions);
const fallbackTransitioned = fallbackActive && (previousState.selectedModel !== selectedModelRef || previousState.activeModel !== activeModelRef);
const previousStateWasRealFallback = previousState.selectedModel === selectedModelRef && previousState.activeModel === activeModelRef ? fallbackActive : Boolean(previousState.selectedModel && previousState.activeModel && !areRuntimeModelRefsEquivalent(previousState.selectedModel, previousState.activeModel, comparisonOptions));
const fallbackCleared = !fallbackActive && previousStateWasRealFallback;
const reasonSummary = buildFallbackReasonSummary(params.attempts);
const attemptSummaries = buildFallbackAttemptSummaries(params.attempts);
const nextState = fallbackActive ? {
selectedModel: selectedModelRef,
activeModel: activeModelRef,
reason: reasonSummary
} : {
selectedModel: void 0,
activeModel: void 0,
reason: void 0
};
return {
selectedModelRef,
activeModelRef,
fallbackActive,
fallbackTransitioned,
fallbackCleared,
reasonSummary,
attemptSummaries,
previousState,
nextState,
stateChanged: previousState.selectedModel !== nextState.selectedModel || previousState.activeModel !== nextState.activeModel || previousState.reason !== nextState.reason
};
}
//#endregion
//#region src/auto-reply/reply/reply-delivery.ts
/** Normalizes reply directives and delivers block replies through streaming or direct paths. */
/** Parses inline reply directives into payload fields and silent-reply state. */
function normalizeReplyPayloadDirectives(params) {
const parseMode = params.parseMode ?? "always";
const silentToken = params.silentToken ?? "NO_REPLY";
const sourceText = params.payload.text ?? "";
const parsed = parseMode === "always" || parseMode === "auto" && (sourceText.includes("[[") || params.extractMediaDirectives !== false && /media:/i.test(sourceText) || params.extractMarkdownImages === true && /!\[[^\]]*]\(/.test(sourceText) || sourceText.includes(silentToken)) ? parseReplyDirectives(sourceText, {
currentMessageId: params.currentMessageId,
silentToken,
extractMarkdownImages: params.extractMarkdownImages,
extractMediaDirectives: params.extractMediaDirectives
}) : void 0;
let text = parsed ? parsed.text || void 0 : params.payload.text || void 0;
if (params.trimLeadingWhitespace && text) text = text.trimStart() || void 0;
const mediaUrls = params.payload.mediaUrls ?? parsed?.mediaUrls;
const mediaUrl = params.payload.mediaUrl ?? parsed?.mediaUrls?.[0] ?? mediaUrls?.[0];
return {
payload: copyReplyPayloadMetadata(params.payload, {
...params.payload,
text,
mediaUrls,
mediaUrl,
replyToId: params.payload.replyToId ?? parsed?.replyToId,
replyToTag: params.payload.replyToTag || parsed?.replyToTag,
replyToCurrent: params.payload.replyToCurrent || parsed?.replyToCurrent,
audioAsVoice: Boolean(params.payload.audioAsVoice || parsed?.audioAsVoice)
}),
isSilent: parsed?.isSilent ?? false
};
}
async function sendDirectBlockReply(params) {
const deliveryIndex = params.directlySentBlockPayloads.length;
params.directlySentBlockPayloads.push(void 0);
await params.onBlockReply(params.payload);
if (isReplyPayloadTerminalContent(params.trackingPayload)) {
params.directlySentBlockKeys.add(createBlockReplyContentKey(params.trackingPayload));
params.directlySentBlockPayloads[deliveryIndex] = params.trackingPayload;
}
}
/** Creates the handler used for assistant block replies during streaming/tool phases. */
function createBlockReplyDeliveryHandler(params) {
return async (payload) => {
if (payload.isReasoning === true && params.reasoningPayloadsEnabled !== true || payload.isCommentary === true && params.commentaryPayloadsEnabled !== true) return;
const { text, skip } = params.normalizeStreamingText(payload);
if (skip && !hasOutboundReplyContent({
...payload,
text: void 0
})) return;
const implicitCurrentMessageAllowed = payload.replyToCurrent === true ? true : payload.replyToCurrent === false ? false : params.replyThreading?.implicitCurrentMessage !== "deny";
const taggedPayload = applyReplyTagsToPayload({
...payload,
text,
mediaUrl: payload.mediaUrl ?? payload.mediaUrls?.[0],
replyToId: payload.replyToId ?? (implicitCurrentMessageAllowed ? params.currentMessageId : void 0)
}, params.currentMessageId);
if (!isRenderablePayload(taggedPayload) && !payload.audioAsVoice) return;
const normalized = normalizeReplyPayloadDirectives({
payload: taggedPayload,
currentMessageId: params.currentMessageId,
silentToken: SILENT_REPLY_TOKEN,
trimLeadingWhitespace: true,
parseMode: "auto",
extractMediaDirectives: false
});
const mediaNormalizedPayload = params.normalizeMediaPaths ? await params.normalizeMediaPaths(normalized.payload) : normalized.payload;
if (normalized.isSilent) mediaNormalizedPayload.text = void 0;
const blockPayload = copyReplyPayloadMetadata(payload, params.applyReplyToMode(mediaNormalizedPayload));
const blockHasNonTextContent = hasOutboundReplyContent({
...blockPayload,
text: void 0
});
if (!blockPayload.text && !blockHasNonTextContent && !blockPayload.audioAsVoice) return;
if (normalized.isSilent && !blockHasNonTextContent) return;
if (blockPayload.text) params.typingSignals.signalTextDelta(blockPayload.text).catch((err) => {
logVerbose(`block reply typing signal failed: ${String(err)}`);
});
if (params.blockStreamingEnabled && params.blockReplyPipeline) params.blockReplyPipeline.enqueue(blockPayload);
else if (params.blockStreamingEnabled) await sendDirectBlockReply({
onBlockReply: params.onBlockReply,
directlySentBlockKeys: params.directlySentBlockKeys,
directlySentBlockPayloads: params.directlySentBlockPayloads,
trackingPayload: blockPayload,
payload: blockPayload
});
else if (blockHasNonTextContent || blockPayload.isReasoning === true || blockPayload.isCommentary === true) await sendDirectBlockReply({
onBlockReply: params.onBlockReply,
directlySentBlockKeys: params.directlySentBlockKeys,
directlySentBlockPayloads: params.directlySentBlockPayloads,
trackingPayload: blockPayload,
payload: blockPayload
});
};
}
const RESTART_LIFECYCLE_REPLY_TEXT = "⚠️ Gateway is restarting. Please wait a few seconds and try again.";
function scheduleFollowupDrainAfterReplyOperationClear(params) {
runAfterReplyOperationClear(params.operation, (admissionSessionId) => {
const completedSessionId = params.operation.sessionId;
const runFollowupAfterClear = admissionSessionId === completedSessionId ? params.runFollowup : (queued) => params.runFollowup(queued.run.sessionId === completedSessionId ? {
...queued,
admissionSessionId
} : queued);
scheduleFollowupDrain(params.queueKey, runFollowupAfterClear);
});
}
function markBeforeAgentRunBlockedPayloads(payloads) {
return payloads.map((payload) => setReplyPayloadMetadata(payload, { beforeAgentRunBlocked: true }));
}
function buildSilentFallbackFailurePayload(params) {
if (params.isHeartbeat || params.allowEmptyAssistantReplyAsSilent === true || params.silentExpected === true || params.hasExplicitSilentReply === true || params.hasSuccessfulTerminalDelivery || !params.fallbackTransition.fallbackActive || !params.fallbackFailureKnown) return;
return markReplyPayloadForSourceSuppressionDelivery({
text: `⚠️ I couldn't reach the configured model backend ${params.fallbackTransition.selectedModelRef}. Fallback used ${params.fallbackTransition.activeModelRef}, but it produced no visible reply.`,
isError: true
});
}
function resolveSourceReplyPolicy(params) {
const sendPolicy = resolveSendPolicy({
cfg: params.cfg,
entry: params.sessionEntry,
sessionKey: params.runtimePolicySessionKey ?? params.sessionKey,
channel: params.sessionCtx.OriginatingChannel ?? params.sessionCtx.Surface ?? params.sessionCtx.Provider ?? sessionDeliveryChannel(params.sessionEntry),
chatType: params.sessionEntry?.chatType
});
return resolveSourceReplyVisibilityPolicy({
cfg: params.cfg,
ctx: params.sessionCtx,
requested: params.opts?.sourceReplyDeliveryMode,
sendPolicy
});
}
function resolveReplyRunDeliveryContext(params) {
const sourceReplyPolicy = resolveSourceReplyPolicy(params);
if (params.sessionCtx.InboundEventKind === "room_event" || sourceReplyPolicy.sendPolicyDenied || sourceReplyPolicy.suppressDelivery && sourceReplyPolicy.sourceReplyDeliveryMode !== "message_tool_only") return;
return normalizeDeliveryContext({
...resolveEffectiveReplyRoute({
ctx: params.sessionCtx,
entry: params.sessionEntry
}),
threadId: resolveRoutedDeliveryThreadId({
ctx: params.sessionCtx,
sessionKey: params.sessionCtx.SessionKey ?? params.sessionKey
})
});
}
function hasSuccessfulSourceReplyDelivery(params) {
return params.blockReplyPipeline?.didStream() && !params.blockReplyPipeline.isAborted() || (params.directlySentBlockKeys?.size ?? 0) > 0 || hasVisibleCommittedMessagingToolDeliveryEvidence(params);
}
function hasSuccessfulTerminalSourceReplyDelivery(params) {
const sentTerminalBlock = params.directlySentBlockPayloads?.some((payload) => isReplyPayloadTerminalContent(payload) && normalizeReplyPayload(payload, { applyChannelTransforms: false }) !== null);
return params.blockReplyPipeline?.didStreamTerminalReply?.() === true && !params.blockReplyPipeline.isAborted() || sentTerminalBlock === true;
}
function resolveFallbackOriginModel(params) {
if (params.runtimeModelSelection) return {
...params.runtimeModelSelection,
persistedAutoFallback: false
};
const entry = params.fallbackStateEntry;
if ((entry?.modelOverrideSource === "auto" || entry !== void 0 && entry.modelOverrideSource === void 0 && hasSessionAutoModelFallbackProvenance(entry)) && entry !== void 0) {
const originProvider = normalizeOptionalString(entry.modelOverrideFallbackOriginProvider);
const originModel = normalizeOptionalString(entry.modelOverrideFallbackOriginModel);
if (originProvider && originModel) return {
provider: originProvider,
model: originModel,
persistedAutoFallback: true
};
}
return {
provider: params.run.provider,
model: params.run.model,
persistedAutoFallback: false
};
}
function buildInlinePluginStatusPayload(params) {
const statusLines = params.includeStatusLines ? resolveSessionPluginStatusLines(params.entry) : [];
const traceLines = params.includeTraceLines ? resolveSessionPluginTraceLines(params.entry) : [];
const lines = [...statusLines, ...traceLines];
if (lines.length === 0) return;
return { text: lines.join("\n") };
}
function normalizeAssistantFinalDeliveryText(text) {
const parsed = normalizeReplyPayloadDirectives({
payload: { text },
trimLeadingWhitespace: true,
parseMode: "auto"
});
return sanitizePendingFinalDeliveryText(parsed.payload.text ?? "");
}
function refreshSessionEntryFromStore(params) {
const { storePath, sessionKey, fallbackEntry, activeSessionStore } = params;
if (!storePath || !sessionKey) return fallbackEntry;
try {
const latestEntry = loadSessionEntryReadOnly({
storePath,
sessionKey
});
if (!latestEntry) return fallbackEntry;
if (params.expectedGeneration && (latestEntry.sessionId !== params.expectedGeneration.sessionId || latestEntry.lifecycleRevision !== params.expectedGeneration.lifecycleRevision)) return fallbackEntry;
if (activeSessionStore) activeSessionStore[sessionKey] = latestEntry;
return latestEntry;
} catch {
return fallbackEntry;
}
}
function resolveAdmittedRunSessionFile(params) {
if (params.sessionKey?.trim()) return params.sessionKey.trim();
return params.sessionFile;
}
async function handleReplyAgentRunError(error, context) {
const { cfg, resolveVisibleReplyDelivery, isHeartbeat, isRestartRecoveryArmed, replyOperation, resolvedVerboseLevel, returnWithQueuedFollowupDrain, sessionCtx } = context;
if (isReplyOperationSuperseded(replyOperation)) return { text: SILENT_REPLY_TOKEN };
if (replyOperation.result?.kind === "aborted" && replyOperation.result.code === "aborted_by_user") return returnWithQueuedFollowupDrain({ text: SILENT_REPLY_TOKEN });
if (replyOperation.result?.kind === "aborted" && replyOperation.result.code === "aborted_for_restart") {
if (isRestartRecoveryArmed()) return returnWithQueuedFollowupDrain({ text: SILENT_REPLY_TOKEN });
return returnWithQueuedFollowupDrain(markReplyPayloadForSourceSuppressionDelivery({ text: RESTART_LIFECYCLE_REPLY_TEXT }));
}
if (error instanceof GatewayDrainingError) {
replyOperation.fail("gateway_draining", error);
return returnWithQueuedFollowupDrain(markReplyPayloadForSourceSuppressionDelivery({ text: RESTART_LIFECYCLE_REPLY_TEXT }));
}
if (error instanceof CommandLaneClearedError) {
replyOperation.fail("command_lane_cleared", error);
return returnWithQueuedFollowupDrain(markReplyPayloadForSourceSuppressionDelivery({ text: RESTART_LIFECYCLE_REPLY_TEXT }));
}
const knownFailurePayload = buildKnownAgentRunFailureReplyPayload({
err: error,
sessionCtx,
resolvedVerboseLevel,
cfg
});
if (knownFailurePayload) {
replyOperation.fail("run_failed", error);
return returnWithQueuedFollowupDrain(knownFailurePayload);
}
const visibleReplyDelivered = await resolveVisibleReplyDelivery();
if (!isHeartbeat && visibleReplyDelivered && !replyOperation.abortSignal.aborted) {
replyOperation.fail("run_failed", error);
return returnWithQueuedFollowupDrain(buildTerminalAgentRunFailureReplyPayload({
visibleReplyDelivered: true,
sessionCtx,
cfg
}));
}
replyOperation.fail("run_failed", error);
returnWithQueuedFollowupDrain(void 0);
throw error;
}
async function cleanupReplyAgentRun(context) {
const { blockReplyPipeline, clearRestartRecoveryDeliveryClaim, providedReplyOperation, queueKey, replyOperation, runFollowupTurn, sessionKey, shouldDrainQueuedFollowupsAfterClear, typing } = context;
try {
await clearRestartRecoveryDeliveryClaim();
} catch (error) {
logVerbose(`failed to clear restart recovery delivery context for ${sessionKey ?? "unknown"}: ${String(error)}`);
}
if (shouldDrainQueuedFollowupsAfterClear) {
scheduleFollowupDrainAfterReplyOperationClear({
operation: replyOperation,
queueKey,
runFollowup: runFollowupTurn
});
if (!providedReplyOperation) replyOperation.complete();
} else if (!providedReplyOperation) replyOperation.complete();
blockReplyPipeline?.stop();
typing.markRunComplete();
typing.markDispatchIdle();
}
//#endregion
//#region src/agents/session-model-auto-revert.ts
/** One-run rollback for agent-selected session models. */
const REVERT_REASONS = /* @__PURE__ */ new Set([
"auth",
"auth_permanent",
"billing",
"model_not_found"
]);
async function reconcileAgentPatchedSessionModel(params) {
const reason = params.outcome.success ? void 0 : params.outcome.reason ?? resolveFailoverReasonFromError(params.outcome.error);
if (!params.outcome.success && (!reason || !REVERT_REASONS.has(reason))) return "kept";
let note;
let sessionId;
let result = "none";
await patchSessionEntryCore({
agentId: params.agentId,
sessionKey: params.sessionKey,
storePath: params.storePath
}, (entry) => {
const marker = entry.modelFallback;
if (marker?.source !== "agent-patch") return null;
if (params.expectedMarkerTs !== void 0 && marker.ts !== params.expectedMarkerTs) {
if (params.outcome.success && params.validatedFallback && marker.ts > params.expectedMarkerTs && params.expectedMarkerTs > (marker.lastValidatedPatchTs ?? -1)) {
result = "promoted";
return { modelFallback: {
...params.validatedFallback,
ts: marker.ts,
lastValidatedPatchTs: params.expectedMarkerTs
} };
}
return null;
}
sessionId = entry.sessionId;
if (params.outcome.success) {
result = "cleared";
return { modelFallback: void 0 };
}
const failed = resolveSessionModelRef(params.cfg, entry, params.agentId);
result = "reverted";
note = `System note: model ${failed.provider}/${failed.model} failed; reverted to ${marker.prevProvider}/${marker.prevModel}.`;
return {
model: marker.prevModel,
modelProvider: marker.prevProvider,
modelOverride: marker.prevModelOverride,
providerOverride: marker.prevProviderOverride,
modelOverrideSource: marker.prevModelOverrideSource,
modelOverrideRouteResolution: marker.prevModelOverrideRouteResolution,
modelOverrideFallbackOriginProvider: marker.prevModelOverrideFallbackOriginProvider,
modelOverrideFallbackOriginModel: marker.prevModelOverrideFallbackOriginModel,
authProfileOverride: marker.prevAuthProfileOverride,
authProfileOverrideSource: marker.prevAuthProfileOverrideSource,
authProfileOverrideCompactionCount: marker.prevAuthProfileOverrideCompactionCount,
contextWindow: marker.prevContextWindow,
thinkingLevel: marker.prevThinkingLevel,
modelFallback: void 0,
liveModelSwitchPending: void 0
};
});
if (note && sessionId) try {
const timestamp = params.now ?? Date.now();
await appendTranscriptMessage({
agentId: params.agentId,
sessionId,
sessionKey: params.sessionKey,
storePath: params.storePath
}, {
config: params.cfg,
message: {
role: "custom",
customType: "openclaw.system-note",
content: note,
display: true,
timestamp
},
...params.now === void 0 ? {} : { now: params.now }
});
} catch {}
return result;
}
function createAgentPatchedSessionModelRunGuard(params) {
let markerTs;
let validatedFallback;
if (params.sessionKey) try {
const entry = loadSessionEntry({
agentId: params.agentId,
sessionKey: params.sessionKey,
storePath: params.storePath
});
const marker = entry?.modelFallback;
markerTs = marker?.source === "agent-patch" ? marker.ts : void 0;
if (entry && markerTs !== void 0) {
const current = resolveSessionModelRef(params.cfg, entry, params.agentId);
validatedFallback = createAgentPatchedSessionModelFallback({
model: current.model,
provider: current.provider,
entry,
ts: markerTs
});
}
} catch {
markerTs = void 0;
}
let failure = {};
let reconciled = false;
const captureFailure = (error, reason) => {
if (markerTs === void 0) return false;
const classifiedReason = reason ? reason : resolveFailoverReasonFromError(error);
const revertReason = classifiedReason && REVERT_REASONS.has(classifiedReason) ? classifiedReason : void 0;
failure = {
error,
...revertReason ? { reason: revertReason } : {}
};
return revertReason !== void 0;
};
const captureFallbackFailure = (attempts) => {
const attempt = attempts[0];
return attempt ? captureFailure(new Error(attempt.error), attempt.reason) : void 0;
};
const reconcile = async (success) => {
if (reconciled || !params.sessionKey || markerTs === void 0) return;
reconciled = true;
try {
await reconcileAgentPatchedSessionModel({
cfg: params.cfg,
...params.agentId ? { agentId: params.agentId } : {},
sessionKey: params.sessionKey,
...params.storePath ? { storePath: params.storePath } : {},
expectedMarkerTs: markerTs,
...validatedFallback ? { validatedFallback } : {},
outcome: success ? { success: true } : {
success: false,
...failure
}
});
} catch (error) {
params.onError?.(error);
}
};
return {
captureFailure,
captureFallbackFailure,
async fail(error, reason) {
captureFailure(error, reason);
await reconcile(false);
},
async finish(success) {
await reconcile(success);
}
};
}
//#endregion
//#region src/auto-reply/reply/agent-runner-auto-fallback.ts
function sessionEntryMatchesSnapshot(entry, snapshot) {
return isDeepStrictEqual(entry, snapshot);
}
function sessionEntryOnlyUpdatedAtChanged(entry, snapshot) {
if (entry.updatedAt === snapshot.updatedAt) return false;
const entryWithoutUpdatedAt = {
...entry,
updatedAt: snapshot.updatedAt
};
return isDeepStrictEqual(entryWithoutUpdatedAt, snapshot);
}
/** Decides whether to retry after rechecking auto-fallback primary probe state. */
function resolveRunAfterAutoFallbackPrimaryProbeRecheck(params) {
const probe = params.run.autoFallbackPrimaryProbe;
if (!probe || !params.sessionKey || !params.entry) return params.run;
const resolveEntrySelectionRun = () => {
const entryRef = resolvePersistedOverrideModelRef({
defaultProvider: params.run.provider,
overrideProvider: params.entry?.providerOverride,
overrideModel: params.entry?.modelOverride
});
const hasEntryModelOverride = Boolean(entryRef);
const authProfileId = normalizeOptionalString(params.entry?.authProfileOverride);
const fallbackRun = {
...params.run,
provider: entryRef?.provider ?? params.run.provider,
model: entryRef?.model ?? params.run.model,
requestedRouteResolution: entryRef ? resolveSessionModelOverrideRouteResolution(params.entry) : params.run.requestedRouteResolution,
autoFallbackPrimaryProbe: void 0
};
if (hasEntryModelOverride) {
fallbackRun.hasSessionModelOverride = true;
fallbackRun.hasAutoFallbackProvenance = hasSessionAutoModelFallbackProvenance(params.entry) || void 0;
} else {
delete fallbackRun.hasSessionModelOverride;
delete fallbackRun.hasAutoFallbackProvenance;
}
if (hasEntryModelOverride && params.entry?.modelOverrideSource) fallbackRun.modelOverrideSource = params.entry.modelOverrideSource;
else delete fallbackRun.modelOverrideSource;
if (hasEntryModelOverride && authProfileId) {
fallbackRun.authProfileId = authProfileId;
const authProfileIdSource = resolveCollapsedSessionAuthPinSource(params.entry);
if (authProfileIdSource) fallbackRun.authProfileIdSource = authProfileIdSource;
else delete fallbackRun.authProfileIdSource;
} else if (hasEntryModelOverride) {
delete fallbackRun.authProfileId;
delete fallbackRun.authProfileIdSource;
}
return fallbackRun;
};
const refreshedProbe = resolveAutoFallbackPrimaryProbe({
entry: params.entry,
sessionKey: params.sessionKey,
primaryProvider: probe.provider,
primaryModel: probe.model
});
if (!refreshedProbe) return resolveEntrySelectionRun();
return {
...params.run,
provider: refreshedProbe.provider,
model: refreshedProbe.model,
requestedRouteResolution: "resolved",
autoFallbackPrimaryProbe: refreshedProbe
};
}
/** Clears a recovered primary probe without overwriting a newer session selection. */
async function clearRecoveredAutoFallbackPrimaryProbeSelection(params) {
if (shouldPreserveUserFacingSessionStateForInputProvenance(params.run.inputProvenance)) return;
const probe = params.run.autoFallbackPrimaryProbe;
if (!probe || params.provider !== probe.provider || params.model !== probe.model) return;
if (!params.sessionKey || !params.activeSessionStore) return;
const cachedSessionEntry = params.activeSessionStore[params.sessionKey];
const activeSessionEntry = cachedSessionEntry ?? params.getActiveSessionEntry();
if (!activeSessionEntry || !entryMatchesAutoFallbackPrimaryProbe(activeSessionEntry, probe)) return;
const activeSessionEntryBeforeUpdate = structuredClone(activeSessionEntry);
if (!params.storePath) {
clearAutoFallbackPrimaryProbeSelection(activeSessionEntry);
params.activeSessionStore[params.sessionKey] = activeSessionEntry;
return;
}
let comparedEntry;
const authoritativeEntry = await updateSessionEntry({
storePath: params.storePath,
sessionKey: params.sessionKey
}, (persistedEntry) => {
comparedEntry = persistedEntry;
if (persistedEntry.sessionId !== activeSessionEntryBeforeUpdate.sessionId || persistedEntry.updatedAt !== activeSessionEntryBeforeUpdate.updatedAt || !entryMatchesAutoFallbackPrimaryProbe(persistedEntry, probe)) return null;
const shouldClearAuthProfile = resolveSessionAuthProfileOverrideSource(persistedEntry) === "auto";
clearAutoFallbackPrimaryProbeSelection(persistedEntry);
return {
providerOverride: void 0,
modelOverride: void 0,
modelOverrideSource: void 0,
modelOverrideRouteResolution: void 0,
modelOverrideFallbackOriginProvider: void 0,
modelOverrideFallbackOriginModel: void 0,
...shouldClearAuthProfile ? {
authProfileOverride: void 0,
authProfileOverrideSource: void 0,
authProfileOverrideCompactionCount: void 0
} : {},
fallbackNotice: void 0,
updatedAt: persistedEntry.updatedAt
};
}) ?? comparedEntry;
const currentCachedEntry = params.activeSessionStore[params.sessionKey];
if (currentCachedEntry !== cachedSessionEntry) return;
const currentEntry = currentCachedEntry ?? (cachedSessionEntry ? void 0 : activeSessionEntry);
if (!currentEntry) return;
if (authoritativeEntry) {
if (sessionEntryMatchesSnapshot(currentEntry, activeSessionEntryBeforeUpdate)) {
params.activeSessionStore[params.sessionKey] = authoritativeEntry;
return;
}
if (currentEntry.sessionId !== activeSessionEntryBeforeUpdate.sessionId || sessionEntryOnlyUpdatedAtChanged(currentEntry, activeSessionEntryBeforeUpdate)) return;
params.activeSessionStore[params.sessionKey] = mergeSessionSnapshotChanges({
initial: activeSessionEntryBeforeUpdate,
next: authoritativeEntry,
current: currentEntry
});
} else if (sessionEntryMatchesSnapshot(currentEntry, activeSessionEntryBeforeUpdate)) delete params.activeSessionStore[params.sessionKey];
}
//#endregion
//#region src/auto-reply/reply/agent-runner-context-recovery.ts
function buildContextOverflowResetHint() {
return "\n\nTry starting a fresh session or using a model with a larger context window.";
}
function resolveAgentHeartbeatModelRaw(params) {
const defaultModel = normalizeOptionalString(params.cfg.agents?.defaults?.heartbeat?.model);
const agentId = normalizeLowercaseStringOrEmpty(params.agentId);
return (agentId ? normalizeOptionalString(resolveAgentConfig(params.cfg, agentId)?.heartbeat?.model) : void 0) ?? defaultModel;
}
function normalizeModelRefForCompare(ref) {
if (!ref) return;
const provider = normalizeLowercaseStringOrEmpty(ref.provider);
const model = normalizeLowercaseStringOrEmpty(ref.model);
return provider && model ? {
provider,
model
} : void 0;
}
function modelRefsEqual(left, right) {
const normalizedLeft = normalizeModelRefForCompare(left);
const normalizedRight = normalizeModelRefForCompare(right);
return normalizedLeft !== void 0 && normalizedRight !== void 0 && normalizedLeft.provider === normalizedRight.provider && normalizedLeft.model === normalizedRight.model;
}
function formatContextWindowLabel(tokens) {
if (tokens >= 1e6) return `${Math.round(tokens / 1e6 * 10) / 10}M`;
return `${Math.round(tokens / 1024)}k`;
}
function normalizePositiveContextTokens(value) {
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return;
return Math.floor(value);
}
function resolveContextWindowForHint(params) {
const sessionContextTokens = normalizePositiveContextTokens(params.activeSessionEntry?.contextTokens);
return resolveContextTokensForModel({
cfg: params.cfg,
provider: params.ref.provider,
model: params.ref.model,
allowAsyncLoad: false
}) ?? sessionContextTokens;
}
function resolveHeartbeatBleedHint(params) {
const primaryProvider = normalizeOptionalString(params.primaryProvider);
const primaryModel = normalizeOptionalString(params.primaryModel);
const runtimeProvider = normalizeOptionalString(params.activeSessionEntry?.modelProvider);
const runtimeModel = normalizeOptionalString(params.activeSessionEntry?.model);
if (!primaryProvider || !primaryModel || !runtimeProvider || !runtimeModel) return;
const primaryRef = {
provider: primaryProvider,
model: primaryModel
};
const runtimeRef = {
provider: runtimeProvider,
model: runtimeModel
};
if (modelRefsEqual(primaryRef, runtimeRef)) return;
const heartbeatModelRaw = resolveAgentHeartbeatModelRaw({
cfg: params.cfg,
agentId: params.agentId
});
if (!modelRefsEqual(runtimeRef, heartbeatModelRaw ? resolveModelRefFromString({
cfg: params.cfg,
raw: heartbeatModelRaw,
defaultProvider: primaryProvider
})?.ref : void 0)) return;
const runtimeWindow = resolveContextWindowForHint({
cfg: params.cfg,
agentId: params.agentId,
ref: runtimeRef,
activeSessionEntry: params.activeSessionEntry
});
const primaryWindow = resolveContextWindowForHint({
cfg: params.cfg,
agentId: params.agentId,
ref: primaryRef
});
if (typeof runtimeWindow === "number" && typeof primaryWindow === "number" && runtimeWindow >= primaryWindow) return;
return `\n\nThe previous heartbeat turn left this session on ${runtimeProvider}/${runtimeModel}${typeof runtimeWindow === "number" && runtimeWindow > 0 ? ` (${formatContextWindowLabel(runtimeWindow)} context)` : ""} instead of ${primaryProvider}/${primaryModel}. This matches the configured \`heartbeat.model\`, so the overflow is likely heartbeat model bleed rather than a compaction-buffer problem. Set \`heartbeat.isolatedSession: true\`, enable \`heartbeat.lightContext: true\`, or use a heartbeat model with a larger context window.`;
}
/** Builds recovery instructions for context-overflow failures. */
function buildContextOverflowRecoveryText(params) {
return (params.preserveSessionMapping ? "⚠️ Auto-compaction could not recover this turn. I kept this conversation mapped to the current session. Please try again, use /compact, or use /new to start a fresh session." : params.duringCompaction ? "⚠️ Context limit exceeded during compaction. I've reset our conversation to start fresh - please try again." : "⚠️ Context limit exceeded. I've reset our conversation to start fresh - please try again.") + ((!params.runtimeProvider || !params.runtimeModel || params.runtimeProvider === params.activeSessionEntry?.modelProvider && params.runtimeModel === params.activeSessionEntry?.model ? resolveHeartbeatBleedHint({
cfg: params.cfg,
agentId: params.agentId,
primaryProvider: params.primaryProvider,
primaryModel: params.primaryModel,
activeSessionEntry: params.activeSessionEntry
}) : void 0) ?? buildContextOverflowResetHint());
}
//#endregion
//#region src/auto-reply/reply/agent-runner-error-handler.ts
const MAX_LIVE_SWITCH_RETRIES = 2;
async function handleAgentExecutionError(params) {
const turn = params.turn;
const err = params.error;
const postCompactionModelFailure = params.state.postCompactionModelAttempted && params.state.pendingLifecycleTerminal ? true : void 0;
const takePendingLifecycleTerminal = () => {
const terminal = params.state.pendingLifecycleTerminal?.backstop ?? createAgentLifecycleTerminalBackstop({
runId: params.runId,
sessionKey: turn.sessionKey,
startedAt: params.state.turnStartedAtMs,
getLifecycleGeneration: () => params.state.lifecycleGeneration,
resolveTerminationFields: (error) => resolveReplyOperationTerminationFields(error, turn.replyOperation?.abortSignal ?? turn.opts?.abortSignal, turn.replyOperation)
});
params.state.pendingLifecycleTerminal = void 0;
return terminal;
};
const settleFailure = async (payload) => {
takePendingLifecycleTerminal().emit("error", err);
turn.replyOperation?.fail("run_failed", err);
await params.modelPatch.fail(err);
return {
kind: "final",
payload: markAgentRunFailureReplyPayload(payload),
postCompactionModelFailure
};
};
const resolveReplyOperationAbortAction = (abortError) => {
const reason = resolveReplyOperationAbortReason(turn.replyOperation, abortError);
if (!reason) return;
const terminalMetadata = reason === "user" ? void 0 : {
aborted: true,
stopReason: reason
};
takePendingLifecycleTerminal().emit(reason === "restart" ? "end" : "error", abortError, terminalMetadata);
return {
kind: "aborted",
reason
};
};
const replyOperationAbortAction = resolveReplyOperationAbortAction(err);
if (replyOperationAbortAction) return replyOperationAbortAction;
if (err instanceof LiveSessionModelSwitchError) {
if (params.liveModelSwitchRetries <= MAX_LIVE_SWITCH_RETRIES) {
params.state.pendingLifecycleTerminal = void 0;
return {
kind: "retry",
liveModelSwitchError: err
};
}
const visibleReplyDelivered = await turn.resolveVisibleReplyDelivery?.();
defaultRuntime.error(`Live model switch failed after ${MAX_LIVE_SWITCH_RETRIES} retries (${sanitizeForLog(err.provider)}/${sanitizeForLog(err.model)}). The requested model may be unavailable.`);
takePendingLifecycleTerminal().emit("error", err);
const switchErrorText = params.shouldSurfaceToControlUi ? renderControlUiAgentFailureCopy("model switch could not be completed. The requested model may be temporarily unavailable.") : isVerboseFailureDetailEnabled(turn.resolvedVerboseLevel) ? "⚠️ Agent failed before reply: model switch could not be completed. The requested model may be temporarily unavailable. Please try again shortly." : "⚠️ Model switch could not be completed. The requested model may be temporarily unavailable. Please try again shortly.";
turn.replyOperation?.fail("run_failed", err);
await params.modelPatch.fail(err);
return {
kind: "final",
payload: markAgentRunFailureReplyPayload({ text: resolveExternalRunFailureTextForConversation({
text: switchErrorText,
visibleReplyDelivered,
sessionCtx: turn.sessionCtx,
isGenericRunnerFailure: !params.shouldSurfaceToControlUi,
cfg: turn.followupRun.run.config
}) })
};
}
const message = formatErrorMessage(err);
params.timing.logIfSlow({
runId: params.runId,
sessionId: turn.followupRun.run.sessionId,
sessionKey: turn.sessionKey,
outcome: "error",
error: message
});
if (isAgentHarnessPreflightError(err)) {
const externalReply = buildExternalRunFailureReply({
message,
error: err
}, {
includeDetails: isVerboseFailureDetailEnabled(turn.resolvedVerboseLevel),
isHeartbeat: turn.isHeartbeat
});
return await settleFailure({ text: resolveExternalRunFailureTextForConversation({
text: params.shouldSurfaceToControlUi ? renderControlUiAgentFailureCopy(message) : externalReply.text,
visibleReplyDelivered: await turn.resolveVisibleReplyDelivery?.(),
sessionCtx: turn.sessionCtx,
isGenericRunnerFailure: externalReply.isGenericRunnerFailure,
cfg: turn.followupRun.run.config
}) });
}
const failoverFacts = resolveReplyFailoverFacts(err, message);
const fallbackAttempts = isFailoverError(err) ? err.attempts : void 0;
const hasFallbackAttempts = Boolean(fallbackAttempts?.length);
const isPureOverloadSummary = hasFallbackAttempts && fallbackAttempts?.every((attempt) => attempt.reason === "overloaded");
const failoverReason = failoverFacts.reason;
const isOverloaded = hasFallbackAttempts ? isPureOverloadSummary : failoverReason === "overloaded";
const isBilling = hasFallbackAttempts ? fallbackAttempts?.some((attempt) => attempt.reason === "billing") : failoverReason === "billing";
const isContextOverflow = !isBilling && (failoverReason === "context_overflow" || isLikelyContextOverflowError(message));
const isCompactionFailure = !isBilling && isCompactionFailureError(message);
const oauthRefreshFailure = classifyOAuthRefreshFailureError(err) ?? classifyOAuthRefreshFailure(message);
const hasAuthProfileFailoverFailure = buildAuthProfileFailoverFailureText(err) !== null;
const providerRequestError = !isBilling && !oauthRefreshFailure && !hasAuthProfileFailoverFailure && !params.shouldSurfaceToControlUi ? failoverFacts.providerRequestError : void 0;
const restartLifecycleError = resolveRestartLifecycleError(err);
if (restartLifecycleError instanceof GatewayDrainingError || restartLifecycleError instanceof CommandLaneClearedError) {
takePendingLifecycleTerminal().emit("error", restartLifecycleError);
turn.replyOperation?.fail(restartLifecycleError instanceof GatewayDrainingError ? "gateway_draining" : "command_lane_cleared", restartLifecycleError);
return {
kind: "final",
payload: markAgentRunFailureReplyPayload({ text: buildRestartLifecycleReplyText() })
};
}
if (isCompactionFailure) {
takePendingLifecycleTerminal().emit("error", err);
defaultRuntime.error(`Auto-compaction failed (${message}). Preserving existing session mapping for ${turn.sessionKey ?? turn.followupRun.run.sessionId}.`);
turn.replyOperation?.fail("run_failed", err);
return {
kind: "final",
payload: markAgentRunFailureReplyPayload({ text: buildContextOverflowRecoveryText({
duringCompaction: true,
preserveSessionMapping: true,
cfg: params.runtimeConfig,
agentId: turn.followupRun.run.agentId,
primaryProvider: turn.followupRun.run.provider,
primaryModel: turn.followupRun.run.model,
runtimeProvider: params.state.attemptedRuntimeProvider,
runtimeModel: params.state.attemptedRuntimeModel,
activeSessionEntry: turn.getActiveSessionEntry()
}) })
};
}
const replayPrevented = findCliTimeoutError(err)?.cliTimeout.observedActivity === true;
if (providerRequestError) return await settleFailure({ text: providerRequestError.userMessage });
defaultRuntime.error(`Embedded agent failed before reply: ${message}`);
const isPureTransientSummary = Boolean(hasFallbackAttempts && fallbackAttempts?.every((attempt) => attempt.reason === "rate_limit" || attempt.reason === "overloaded"));
const isRateLimit = hasFallbackAttempts ? isPureTransientSummary : failoverReason === "rate_limit" || failoverReason === "overloaded";
const rateLimitOrOverloadedCopy = !hasFallbackAttempts && (failoverReason === "rate_limit" || failoverReason === "overloaded") || isPureTransientSummary ? renderRateLimitOrOverloadedCopy({
reason: isOverloaded ? "overloaded" : "rate_limit",
raw: message
}) : void 0;
const userFacingMessage = message;
const externalRunFailureCandidate = !isBilling && !(isRateLimit && !isOverloaded) && !rateLimitOrOverloadedCopy && !isContextOverflow ? buildExternalRunFailureReply({
message,
error: err
}, {
includeAuthProfileId: !isNonDirectConversationContext(turn.sessionCtx),
includeDetails: isVerboseFailureDetailEnabled(turn.resolvedVerboseLevel),
isHeartbeat: turn.isHeartbeat,
replayPrevented,
failoverFacts
}) : void 0;
const externalRunFailureReply = !params.shouldSurfaceToControlUi || externalRunFailureCandidate?.presentation || renderFailoverCodeUserCopy(failoverFacts.code) ? externalRunFailureCandidate : void 0;
const fallbackText = isBilling ? renderBillingReplyCopy({
attempts: fallbackAttempts,
...isFailoverError(err) ? {
provider: err.provider,
model: err.model,
authMode: err.authMode
} : {}
}) : isRateLimit && !isOverloaded ? renderRateLimitReplyCopy({
message,
reason: failoverReason,
attempts: fallbackAttempts,
provider: isFailoverError(err) ? err.provider : void 0,
cooldownExpiry: isFailoverError(err) ? err.soonestCooldownExpiry : void 0,
sanitizeText: (text) => sanitizeUserFacingText(text, { errorContext: true })
}) : rateLimitOrOverloadedCopy ? rateLimitOrOverloadedCopy : isContextOverflow ? "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model." : externalRunFailureReply?.text ?? (params.shouldSurfaceToControlUi ? renderControlUiAgentFailureCopy(userFacingMessage) : turn.isHeartbeat ? HEARTBEAT_EXTERNAL_RUN_FAILURE_TEXT : "⚠️ Something went wrong while processing your request. Please try again, or use /new to start a fresh session.");
return await settleFailure({
text: resolveExternalRunFailureTextForConversation({
text: fallbackText,
visibleReplyDelivered: await turn.resolveVisibleReplyDelivery?.(),
sessionCtx: turn.sessionCtx,
isGenericRunnerFailure: externalRunFailureReply?.isGenericRunnerFailure ?? false,
cfg: turn.followupRun.run.config
}),
...externalRunFailureReply?.presentation ? { presentation: externalRunFailureReply.presentation } : {}
});
}
//#endregion
//#region src/infra/message-tool-run-outcome-store.ts
const MESSAGE_TOOL_RUN_OUTCOME_MAX_ROWS = 1e4;
/** Records one bounded completion fact for a message-tool-only run. */
function recordMessageToolRunOutcome(params) {
const values = {
run_id: params.runId,
session_key: params.sessionKey,
agent_id: params.agentId,
provider: params.provider,
model: params.model,
outcome: params.outcome,
run_status: params.runStatus,
occurred_at: params.occurredAt
};
const databaseOptions = toDatabaseOptions(resolveSqliteScope(params));
ensureMessageToolRunOutcomeSchema(openOpenClawAgentDatabase(databaseOptions).db);
runOpenClawAgentWriteTransaction(({ db }) => {
const agentDb = getNodeSqliteKysely(db);
executeSqliteQuerySync(db, agentDb.insertInto("message_tool_run_outcomes").values(values));
executeSqliteQuerySync(db, agentDb.deleteFrom("message_tool_run_outcomes").where("id", "in", agentDb.selectFrom("message_tool_run_outcomes").select("id").orderBy("occurred_at", "desc").orderBy("id", "desc").limit(2147483647).offset(MESSAGE_TOOL_RUN_OUTCOME_MAX_ROWS)));
}, databaseOptions, { operationLabel: "message-tool.run-outcome.record" });
}
//#endregion
//#region src/auto-reply/reply/agent-runner-execution-outcome.ts
const messageToolOutcomeLog = createSubsystemLogger("auto-reply/message-tool-outcome");
function recordAgentTurnExecutionOutcome(params, result) {
const executionStatus = resolveAgentTurnExecutionStatus(result?.outcome);
if (executionStatus !== "cancelled") params.opts?.onAgentRunTerminalOutcome?.(executionStatus === "ok" ? "completed" : "failed");
if ((params.followupRun.run.sourceReplyDeliveryMode ?? params.opts?.sourceReplyDeliveryMode) !== "message_tool_only") return;
const sessionKey = params.sessionKey ?? params.followupRun.run.sessionKey;
if (!sessionKey) {
messageToolOutcomeLog.warn("message-tool-only run outcome missing session key", {
runId: result?.runId ?? params.opts?.runId,
agentId: params.followupRun.run.agentId
});
return;
}
const outcome = result?.outcome;
const resolved = outcome?.kind === "settled" || outcome?.kind === "rejected" ? outcome.resolved : void 0;
const runStatus = executionStatus === "ok" ? "completed" : executionStatus === "failed" ? "errored" : "aborted";
const toolDelivered = outcome?.kind === "settled" && hasCompletedSourceReplyDeliveryEvidence(outcome.result);
const values = {
runId: result?.runId ?? params.opts?.runId ?? "unknown",
sessionKey,
agentId: params.followupRun.run.agentId,
provider: resolved?.provider ?? params.followupRun.run.provider,
model: resolved?.model ?? params.followupRun.run.model,
outcome: toolDelivered ? "tool_delivered" : "mute",
runStatus,
occurredAt: Date.now(),
storePath: params.storePath
};
try {
recordMessageToolRunOutcome(values);
messageToolOutcomeLog.info("recorded message-tool-only run outcome", values);
} catch (error) {
messageToolOutcomeLog.warn("failed to record message-tool-only run outcome", {
...values,
error: formatErrorMessage(error)
});
}
}
//#endregion
//#region src/auto-reply/reply/agent-event-bridge.ts
function createAgentEventDeliveryStartOrder() {
let startTail = Promise.resolve();
return { schedule: async (deliver) => {
const previousStart = startTail;
let releaseStart;
startTail = new Promise((resolve) => {
releaseStart = resolve;
});
await previousStart;
let delivery;
try {
delivery = deliver();
} finally {
releaseStart?.();
}
await delivery;
} };
}
function createAgentEventBridge(params) {
const deliver = params.deliver;
if (!deliver) return {
unsubscribe: () => void 0,
drain: async () => void 0
};
let unsubscribed = false;
let delivery = Promise.resolve();
const rawUnsubscribe = onAgentEventForRun(params.runId, (evt) => {
if (evt.runId !== params.runId) return;
if (params.suppressed) return;
const payload = params.read(evt);
if (payload === void 0) return;
if (!params.startOrder) {
delivery = delivery.then(() => deliver(payload)).catch(() => void 0);
return;
}
const scheduled = params.startOrder.schedule(() => deliver(payload)).catch(() => void 0);
delivery = Promise.all([delivery, scheduled]).then(() => void 0);
});
return {
unsubscribe() {
if (unsubscribed) return;
unsubscribed = true;
rawUnsubscribe();
},
async drain() {
await delivery;
}
};
}
//#endregion
//#region src/auto-reply/reply/agent-runner-cli-dispatch.ts
async function stopAgentEventBridges(bridges) {
for (const bridge of bridges) bridge.unsubscribe();
for (const bridge of bridges) await bridge.drain();
}
function createAssistantTextBridge(params) {
let lastText;
return createAgentEventBridge({
runId: params.runId,
suppressed: params.suppressed,
deliver: params.deliver,
startOrder: params.startOrder,
read: (evt) => {
if (evt.stream !== "assistant") return;
const text = typeof evt.data.text === "string" ? evt.data.text : void 0;
if (text === void 0 || text === lastText) return;
lastText = text;
return text;
}
});
}
function createCliReasoningStreamBridge(onReasoningStream) {
if (!onReasoningStream) return;
return async ({ text, isReasoningSnapshot }) => {
await onReasoningStream({
text,
...isReasoningSnapshot ? { isReasoningSnapshot } : {},
requiresReasoningProgressOptIn: true
});
};
}
function createReasoningTextBridge(params) {
let lastText;
return createAgentEventBridge({
runId: params.runId,
suppressed: params.suppressed,
deliver: params.deliver,
startOrder: params.startOrder,
read: (evt) => {
if (evt.stream !== "thinking") return;
const text = typeof evt.data.text === "string" ? evt.data.text : void 0;
if (text === void 0 || text === lastText) return;
lastText = text;
return {
text,
...evt.data.isReasoningSnapshot === true ? { isReasoningSnapshot: true } : {}
};
}
});
}
function createReasoningProgressBridge(params) {
let lastProgressTokens;
return createAgentEventBridge({
runId: params.runId,
suppressed: params.suppressed,
deliver: params.deliver,
startOrder: params.startOrder,
read: (evt) => {
if (evt.stream !== "thinking") return;
const progressTokens = evt.data.progressTokens;
if (typeof progressTokens !== "number" || !Number.isFinite(progressTokens) || progressTokens <= 0 || progressTokens === lastProgressTokens) return;
lastProgressTokens = progressTokens;
return { progressTokens };
}
});
}
function readCommentaryTextPayload(evt) {
if (evt.stream !== "item" || evt.data.kind !== "preamble") return;
const text = typeof evt.data.progressText === "string" ? evt.data.progressText.trim() : "";
if (!text) return;
return {
text,
...typeof evt.data.itemId === "string" ? { itemId: evt.data.itemId } : {}
};
}
function keepCliSessionBindingOnlyWhenReused(params) {
const existingSessionId = normalizeOptionalString(params.existingSessionId);
const agentMeta = params.result.meta.agentMeta;
const returnedSessionId = normalizeOptionalString(agentMeta?.cliSessionBinding?.sessionId);
const shouldClearStoredSession = agentMeta?.clearCliSessionBinding === true;
if (agentMeta === void 0 || !shouldClearStoredSession && existingSessionId === void 0 || returnedSessionId === existingSessionId) return params.result;
if (returnedSessionId || shouldClearStoredSession) params.onDroppedReplacement?.();
return {
...params.result,
meta: {
...params.result.meta,
agentMeta: {
...agentMeta,
sessionId: "",
cliSessionBinding: void 0,
clearCliSessionBinding: void 0
}
}
};
}
function createToolEventBridge(params) {
return createAgentEventBridge({
runId: params.runId,
suppressed: params.suppressed,
deliver: params.deliver,
startOrder: params.startOrder,
read: (evt) => {
if (evt.stream !== "tool") return;
const phaseValue = evt.data.phase;
if (phaseValue !== "start" && phaseValue !== "update" && phaseValue !== "result") return;
const phase = phaseValue === "start" ? "start" : phaseValue === "update" ? "update" : "result";
return {
name: typeof evt.data.name === "string" ? evt.data.name : void 0,
phase,
args: isRecord(evt.data.args) ? evt.data.args : void 0,
toolCallId: typeof evt.data.toolCallId === "string" ? evt.data.toolCallId : void 0,
...phase === "result" ? {
isError: evt.data.isError === true,
result: evt.data.result
} : {}
};
}
});
}
/**
* Tracks CLI tool start/result events and renders the same durable tool
* summaries the embedded runner emits: a formatToolAggregate line per result
* (args-derived meta captured at start), plus the output block under full
* verbose. Keeps CLI runs at tool-summary parity with embedded runs.
*/
function createCliToolSummaryTracker(params) {
const toolByCallId = /* @__PURE__ */ new Map();
return { noteToolEvent: async (payload) => {
if (payload.phase === "start") {
if (payload.toolCallId && payload.name) toolByCallId.set(payload.toolCallId, {
meta: inferToolMetaFromArgsCore(payload.name, payload.args, { detailMode: params.detailMode ?? "explain" }),
commandBearing: isCommandBearingToolCall(payload.name, payload.args)
});
return false;
}
if (payload.phase !== "result") return false;
const storedTool = payload.toolCallId ? toolByCallId.get(payload.toolCallId) : void 0;
const meta = params.commandDetailsVisible || !storedTool?.commandBearing ? storedTool?.meta : void 0;
if (payload.toolCallId) toolByCallId.delete(payload.toolCallId);
if (!params.shouldEmitToolResult()) return storedTool?.commandBearing === true;
const aggregate = formatToolAggregate(payload.name, meta ? [meta] : void 0, { markdown: true });
let text = aggregate;
if (params.shouldEmitToolOutput()) {
const output = extractToolResultText(payload.result)?.trim();
if (output) text = `${aggregate}\n\`\`\`txt\n${output}\n\`\`\``;
}
if (!text.trim()) return storedTool?.commandBearing === true;
await params.deliver({
text,
...payload.isError === true ? { isError: true } : {}
});
return storedTool?.commandBearing === true;
} };
}
function createCommentaryEventBridge(params) {
return createAgentEventBridge({
runId: params.runId,
suppressed: params.suppressed,
deliver: params.deliver,
startOrder: params.startOrder,
read: readCommentaryTextPayload
});
}
function createPlanUpdateBridge(params) {
const deliver = params.deliver;
return createAgentEventBridge({
runId: params.runId,
suppressed: params.suppressed,
deliver: deliver ? async (payload) => {
await deliver(payload);
} : void 0,
startOrder: params.startOrder,
read: (evt) => {
if (evt.stream !== "plan") return;
return {
phase: normalizeOptionalString(evt.data.phase),
title: normalizeOptionalString(evt.data.title),
explanation: normalizeOptionalString(evt.data.explanation),
steps: normalizeAgentPlanSteps(evt.data.steps),
source: normalizeOptionalString(evt.data.source)
};
}
});
}
function createToolBoundaryBridge(params) {
return createAgentEventBridge({
runId: params.runId,
suppressed: params.suppressed,
deliver: params.deliver,
read: (evt) => {
if (evt.stream !== "tool") return;
const phase = typeof evt.data.phase === "string" ? evt.data.phase : "";
return [
"completed",
"end",
"error",
"result"
].includes(phase) ? true : void 0;
}
});
}
function runCliAgentWithLifecycle(params) {
if (!params.lifecycleGeneration) return runCliAgentWithLifecycleInternal(params);
return withAgentRunLifecycleGeneration(params.lifecycleGeneration, () => runCliAgentWithLifecycleInternal(params));
}
async function runCliAgentWithLifecycleInternal(params) {
const startedAt = params.startedAt ?? Date.now();
const fastModeStartedAtMs = params.runParams.fastModeStartedAtMs ?? startedAt;
const fastModeAutoOnSeconds = params.runParams.fastModeAutoOnSeconds ?? 60;
const fastModeAutoProgressState = params.runParams.fastModeAutoProgressState ?? {
offAnnounced: false,
resetAnnounced: false
};
const emitFastModeAutoProgress = async (payload) => {
const summary = formatFastModeAutoProgressText(payload);
emitAgentEvent({
runId: params.runId,
stream: "item",
data: {
kind: "status",
title: "Fast",
phase: "update",
summary
},
...params.runParams.sessionKey ? { sessionKey: params.runParams.sessionKey } : {}
});
try {
await params.onFastModeAutoProgress?.({
text: summary,
channelData: { openclawProgressKind: FAST_MODE_AUTO_PROGRESS_KIND }
});
} catch {}
};
const maybeAnnounceFastModeAutoOff = async () => {
if (params.runParams.fastMode !== "auto" || fastModeAutoProgressState.offAnnounced) return;
const next = resolveFastModeForElapsed({
mode: "auto",
startedAtMs: fastModeStartedAtMs,
fastAutoOnSeconds: fastModeAutoOnSeconds
});
if (next.enabled) return;
fastModeAutoProgressState.offAnnounced = true;
await emitFastModeAutoProgress(next);
};
const maybeEmitFastModeAutoReset = async () => {
if (params.runParams.fastMode !== "auto" || !fastModeAutoProgressState.offAnnounced || fastModeAutoProgressState.resetAnnounced) return;
fastModeAutoProgressState.resetAnnounced = true;
await emitFastModeAutoProgress({
enabled: true,
elapsedSeconds: 0,
fastAutoOnSeconds: fastModeAutoOnSeconds
});
};
const emitLifecycleTerminal = params.emitLifecycleTerminal ?? true;
params.onAgentRunStart?.();
emitAgentEvent({
runId: params.runId,
...params.runParams.agentId ? { agentId: params.runParams.agentId } : {},
...params.runParams.sessionKey ? { sessionKey: params.runParams.sessionKey } : {},
...params.runParams.sessionId ? { sessionId: params.runParams.sessionId } : {},
...params.lifecycleGeneration ? { lifecycleGeneration: params.lifecycleGeneration } : {},
stream: "lifecycle",
data: {
phase: "start",
startedAt
}
});
const activityBridge = params.onActivity ? createAgentEventBridge({
runId: params.runId,
read: () => ({}),
deliver: async () => {
params.onActivity?.();
}
}) : void 0;
const progressStartOrder = params.preserveProgressCallbackStartOrder ? createAgentEventDeliveryStartOrder() : void 0;
const assistantBridge = createAssistantTextBridge({
runId: params.runId,
suppressed: params.suppressAssistantBridge,
deliver: params.onAssistantText,
startOrder: progressStartOrder
});
let finalReasoningText;
const bridges = [
activityBridge,
assistantBridge,
createReasoningTextBridge({
runId: params.runId,
suppressed: params.suppressAssistantBridge,
startOrder: progressStartOrder,
deliver: async (payload) => {
finalReasoningText = normalizeOptionalString(payload.text);
await params.onReasoningText?.(payload);
}
}),
createReasoningProgressBridge({
runId: params.runId,
suppressed: params.suppressAssistantBridge,
deliver: params.onReasoningProgress,
startOrder: progressStartOrder
}),
createAgentEventBridge({
runId: params.runId,
suppressed: params.suppressAssistantBridge,
startOrder: progressStartOrder,
deliver: async (event) => {
if (event.phase === "start") await params.onCompactionStart?.();
else await params.onCompactionEnd?.({ completed: event.completed });
},
read: (evt) => {
if (evt.stream !== "compaction") return;
if (evt.data.phase === "start") return { phase: "start" };
return evt.data.phase === "end" ? {
phase: "end",
completed: evt.data.completed === true
} : void 0;
}
}),
createToolEventBridge({
runId: params.runId,
suppressed: params.suppressAssistantBridge,
deliver: params.onToolEvent,
startOrder: progressStartOrder
}),
createCommentaryEventBridge({
runId: params.runId,
suppressed: params.suppressAssistantBridge,
deliver: params.onCommentaryText,
startOrder: progressStartOrder
}),
createPlanUpdateBridge({
runId: params.runId,
suppressed: params.suppressAssistantBridge,
deliver: params.onPlanUpdate,
startOrder: progressStartOrder
}),
createToolBoundaryBridge({
runId: params.runId,
suppressed: params.suppressAssistantBridge,
deliver: maybeAnnounceFastModeAutoOff
})
].filter((bridge) => bridge !== void 0);
let lifecycleTerminalEmitted = false;
try {
const rawResult = await runCliAgent({
...params.runParams,
emitCommentaryText: params.runParams.emitCommentaryText ?? Boolean(params.onCommentaryText)
});
const restartAbortReason = params.runParams.abortSignal?.reason;
if (isAgentRunRestartAbortReason(restartAbortReason)) throw restartAbortReason;
const result = params.transformResult?.(rawResult) ?? rawResult;
await stopAgentEventBridges(bridges);
const cliText = normalizeOptionalString(result.payloads?.[0]?.text);
const durableReasoningText = normalizeOptionalString(finalReasoningText);
const resultWithReasoning = durableReasoningText ? {
...result,
payloads: [{
text: durableReasoningText,
isReasoning: true
}, ...result.payloads ?? []]
} : result;
if (cliText) emitAgentEvent({
runId: params.runId,
stream: "assistant",
data: { text: cliText }
});
if (emitLifecycleTerminal) {
emitAgentEvent({
runId: params.runId,
...params.runParams.agentId ? { agentId: params.runParams.agentId } : {},
...params.runParams.sessionKey ? { sessionKey: params.runParams.sessionKey } : {},
...params.runParams.sessionId ? { sessionId: params.runParams.sessionId } : {},
...params.lifecycleGeneration ? { lifecycleGeneration: params.lifecycleGeneration } : {},
stream: "lifecycle",
data: {
phase: "end",
startedAt,
endedAt: Date.now(),
...resolveAgentLifecycleTerminalMetadata(result.meta),
...resolveAgentRunAbortLifecycleFields(params.runParams.abortSignal)
}
});
lifecycleTerminalEmitted = true;
}
return resultWithReasoning;
} catch (err) {
await stopAgentEventBridges(bridges);
await params.onErrorBeforeLifecycle?.(err);
if (emitLifecycleTerminal) {
emitAgentEvent({
runId: params.runId,
...params.runParams.agentId ? { agentId: params.runParams.agentId } : {},
...params.runParams.sessionKey ? { sessionKey: params.runParams.sessionKey } : {},
...params.runParams.sessionId ? { sessionId: params.runParams.sessionId } : {},
...params.lifecycleGeneration ? { lifecycleGeneration: params.lifecycleGeneration } : {},
stream: "lifecycle",
data: {
phase: "error",
startedAt,
endedAt: Date.now(),
error: String(err),
...resolveAgentRunErrorLifecycleFields(err, params.runParams.abortSignal)
}
});
lifecycleTerminalEmitted = true;
}
throw err;
} finally {
for (const bridge of bridges) bridge.unsubscribe();
if (params.runParams.isFinalFallbackAttempt !== false) await maybeEmitFastModeAutoReset();
if (emitLifecycleTerminal && !lifecycleTerminalEmitted) emitAgentEvent({
runId: params.runId,
...params.runParams.agentId ? { agentId: params.runParams.agentId } : {},
...params.runParams.sessionKey ? { sessionKey: params.runParams.sessionKey } : {},
...params.runParams.sessionId ? { sessionId: params.runParams.sessionId } : {},
...params.lifecycleGeneration ? { lifecycleGeneration: params.lifecycleGeneration } : {},
stream: "lifecycle",
data: {
phase: "error",
startedAt,
endedAt: Date.now(),
error: "CLI run completed without lifecycle terminal event",
...resolveAgentRunAbortLifecycleFields(params.runParams.abortSignal)
}
});
}
}
//#endregion
//#region src/auto-reply/reply/agent-runner-command-output.ts
/**
* CLI backends report a tool result as its raw content: a string, or the text
* blocks the harness streamed. Structured runners send a record instead, so the
* command projection has to read both or every CLI command result is dropped.
*/
function readToolResultText(value) {
const direct = readStringValue(value);
if (direct !== void 0) return direct;
if (!Array.isArray(value)) return;
return value.map((block) => readStringValue(asOptionalRecord(block)?.text)).filter((part) => part !== void 0).join("\n").trim() || void 0;
}
function readNullableNumberValue(value) {
if (value === null) return null;
return asFiniteNumber(value);
}
function isCommandToolName(name) {
const normalized = name?.trim().toLowerCase();
return normalized === "exec" || normalized === "bash" || normalized === "shell";
}
/** Projects a completed command-tool event into the channel command-output contract. */
function buildCommandOutputFromToolResultEvent(evt) {
if (evt.stream !== "tool" || evt.data.phase !== "result") return;
const name = evt.data.name;
const commandBearing = evt.data.commandBearing === true;
if (!name || !commandBearing && !isCommandToolName(name)) return;
const result = asOptionalRecord(evt.data.result);
const details = asOptionalRecord(result?.details);
const output = evt.data.output ?? readStringValue(result?.output) ?? readStringValue(details?.output) ?? readToolResultText(evt.data.result);
const explicitStatus = evt.data.status ?? readStringValue(result?.status) ?? readStringValue(details?.status);
const exitCode = readNullableNumberValue(result?.exitCode ?? details?.exitCode ?? evt.data.exitCode);
const durationMs = asFiniteNumber(result?.durationMs ?? details?.durationMs ?? evt.data.durationMs);
const cwd = evt.data.cwd;
const errorStatus = evt.data.isError === true ? "failed" : evt.data.isError === false ? "completed" : void 0;
if (!(output !== void 0 || explicitStatus !== void 0 || exitCode !== void 0 || durationMs !== void 0 || cwd !== void 0 || commandBearing && typeof evt.data.isError === "boolean" || result !== void 0 && Object.keys(result).length > 0)) return;
const args = evt.data.args;
const title = evt.data.title ?? (args ? inferToolMetaFromArgsCore(name, args, { detailMode: "explain" }) : void 0);
return {
itemId: evt.data.itemId,
phase: "end",
title,
toolCallId: evt.data.toolCallId,
name,
output,
status: explicitStatus ?? errorStatus,
exitCode,
durationMs,
cwd
};
}
//#endregion
//#region src/auto-reply/reply/agent-runner-cli-candidate.ts
async function runCliFallbackCandidate(params) {
const turn = params.turn;
const expectedLifecycleRevision = turn.getActiveSessionEntry()?.lifecycleRevision;
const selectedModelEntry = findModelInCatalog(params.candidateRun.thinkingCatalog ?? [], params.provider, params.model);
const modelHasVision = await resolveRunModelHasVision({
run: params.candidateRun,
provider: params.provider,
model: params.model
});
const sessionKey = turn.sessionKey ?? turn.followupRun.run.sessionKey;
const sessionTarget = sessionKey && turn.storePath ? {
agentId: turn.followupRun.run.agentId,
sessionId: turn.followupRun.run.sessionId,
sessionKey,
storePath: turn.storePath
} : void 0;
const cliLifecycleStartedAt = Date.now();
const lifecycleBackstop = createAgentLifecycleTerminalBackstop({
runId: params.runId,
sessionKey: turn.sessionKey,
startedAt: cliLifecycleStartedAt,
getLifecycleGeneration: () => params.lifecycleGeneration,
resolveTerminationFields: (error) => resolveReplyOperationTerminationFields(error, params.runAbortSignal, turn.replyOperation)
});
params.onLifecycleBackstop(lifecycleBackstop);
const authProfile = resolveRunAuthProfile(params.candidateRun, params.cliExecutionProvider, { config: params.runtimeConfig });
const hookMessageProvider = resolveOriginMessageProvider({
originatingChannel: turn.followupRun.originatingChannel,
provider: turn.sessionCtx.Provider
});
const cliCurrentThreadId = turn.followupRun.originatingThreadId ?? turn.sessionCtx.MessageThreadId;
const cliCurrentMessageId = turn.sessionCtx.InputProvenance?.kind === "internal_system" && turn.sessionCtx.InputProvenance.sourceTool === "restart-sentinel" ? turn.sessionCtx.ReplyToId : turn.sessionCtx.MessageSidFull ?? turn.sessionCtx.MessageSid;
const commandDetailsVisible = turn.resolvedVerboseLevel === "full";
const cliToolSummaryTracker = createCliToolSummaryTracker({
detailMode: turn.toolProgressDetail,
commandDetailsVisible,
shouldEmitToolResult: turn.shouldEmitToolResult,
shouldEmitToolOutput: turn.shouldEmitToolOutput,
deliver: async (payload) => {
await turn.opts?.onToolResult?.(payload);
}
});
const deliverCliCommandOutcome = async (payload, commandBearing) => {
const onCommandOutput = turn.opts?.onCommandOutput;
if (!onCommandOutput) return;
const commandOutput = buildCommandOutputFromToolResultEvent({
stream: "tool",
data: {
...payload,
commandBearing
}
});
if (commandOutput) await onCommandOutput(commandOutput);
};
const bridgeCliPreambleProgress = Boolean(turn.opts?.onItemEvent) && shouldBridgeCliPreambleEvents(turn.opts);
const bridgeCliDurableCommentary = Boolean(params.presentation.blockReplyHandler) && (turn.blockStreamingEnabled || turn.opts?.commentaryPayloadsEnabled === true);
const toolAuthorityRoute = {
provider: params.provider,
model: params.model
};
const toolAuthorityFingerprint = turn.replyOperation?.bindToolAuthorityRoute(toolAuthorityRoute);
const result = await params.timing.measure("cli_run", () => withLocalSessionPlacementTurnSettlement({
sessionId: turn.followupRun.run.sessionId,
sessionKey,
agentId: turn.followupRun.run.agentId,
runId: params.runId
}, async (assertSettlementCurrent) => {
const sessionEntry = sessionTarget ? loadSessionEntry({
...sessionTarget,
readConsistency: "latest"
}) : turn.getActiveSessionEntry();
if (sessionTarget && (sessionEntry?.sessionId !== sessionTarget.sessionId || sessionEntry.lifecycleRevision !== expectedLifecycleRevision)) throw createAgentRunSupersededAbortError();
const diagnosticOwner = params.deferredLifecycle.handoffToCli();
const cliSessionBinding = getCliSessionBinding(sessionEntry, params.cliExecutionProvider);
const mediaTaskIdsBefore = getGeneratedMediaTaskIdsForSessionKey(turn.sessionKey);
let droppedCliSessionReplacement = false;
const candidateResult = await runCliAgentWithLifecycle({
runId: params.runId,
lifecycleGeneration: params.lifecycleGeneration,
provider: params.cliExecutionProvider,
startedAt: cliLifecycleStartedAt,
emitLifecycleTerminal: false,
onAgentRunStart: params.notifyAgentRunStart,
suppressAssistantBridge: turn.followupRun.run.silentExpected,
onActivity: () => turn.replyOperation?.recordActivity(),
onErrorBeforeLifecycle: params.cliExecutionProvider === "claude-cli" && cliSessionBinding?.sessionId ? async (error) => {
if (!shouldClearFailedCliSessionBinding({
error,
binding: cliSessionBinding,
hasNewGeneratedMediaTask: hasNewGeneratedMediaTaskForSessionKey(turn.sessionKey, mediaTaskIdsBefore)
})) return;
await clearCliSessionInStore({
provider: params.cliExecutionProvider,
expectedCliSessionId: cliSessionBinding.sessionId,
expectedSessionId: sessionEntry?.sessionId,
assertCommitAllowed: assertSettlementCurrent,
sessionKey: turn.sessionKey,
sessionStore: turn.activeSessionStore,
storePath: turn.storePath,
activeSessionEntry: sessionEntry
});
} : void 0,
preserveProgressCallbackStartOrder: params.preserveProgressCallbackStartOrder,
onAssistantText: async (text) => {
const classified = params.presentation.classifyStreamingPartial({ text });
if (classified.skip || !classified.text) return;
const textForTyping = classified.text;
const sanitized = params.presentation.sanitizeStreamingText(textForTyping, false);
const onPartialReply = turn.opts?.onPartialReply;
return await params.presentation.presentWithTyping(turn.typingSignals.signalTextDelta(textForTyping), () => sanitized.skip || !sanitized.text || !onPartialReply ? false : onPartialReply({ text: sanitized.text }));
},
onReasoningText: createCliReasoningStreamBridge(turn.opts?.onReasoningStream),
onPlanUpdate: turn.opts?.onPlanUpdate,
onReasoningProgress: async (payload) => {
await turn.opts?.onReasoningProgress?.(payload);
},
onCompactionStart: turn.opts?.onCompactionStart,
onCompactionEnd: turn.opts?.onCompactionEnd,
onToolEvent: async (payload) => {
if (!params.preserveProgressCallbackStartOrder) {
const commandBearing = await cliToolSummaryTracker.noteToolEvent(payload);
if (payload.phase === "result") {
await deliverCliCommandOutcome(payload, commandBearing);
return;
}
const { name, phase, args, toolCallId } = payload;
await Promise.all([turn.typingSignals.signalToolStart(), turn.opts?.onToolStart?.({
...toolCallId ? { toolCallId } : {},
name,
phase,
args,
detailMode: turn.toolProgressDetail
})]);
return;
}
const summaryPromise = cliToolSummaryTracker.noteToolEvent(payload);
if (payload.phase === "result") {
const commandBearing = await summaryPromise;
await deliverCliCommandOutcome(payload, commandBearing);
return;
}
const { name, phase, args, toolCallId } = payload;
await Promise.all([summaryPromise, params.presentation.presentWithTyping(turn.typingSignals.signalToolStart(), async () => {
await turn.opts?.onToolStart?.({
...toolCallId ? { toolCallId } : {},
name,
phase,
args,
detailMode: turn.toolProgressDetail
});
})]);
},
onCommentaryText: bridgeCliPreambleProgress || bridgeCliDurableCommentary ? async (payload) => {
const deliveries = [];
if (bridgeCliPreambleProgress) deliveries.push(turn.opts?.onItemEvent?.({
itemId: payload.itemId,
kind: "preamble",
progressText: payload.text,
...bridgeCliDurableCommentary ? { suppressDurableProgress: true } : {}
}));
if (bridgeCliDurableCommentary) deliveries.push(params.presentation.blockReplyHandler?.({
text: payload.text,
...turn.blockStreamingEnabled ? {} : { isCommentary: true }
}));
await Promise.all(deliveries);
} : void 0,
onFastModeAutoProgress: async (payload) => {
await turn.opts?.onToolResult?.(payload);
},
transformResult: turn.followupRun.currentInboundEventKind === "room_event" ? (resultLocal) => keepCliSessionBindingOnlyWhenReused({
result: resultLocal,
existingSessionId: cliSessionBinding?.sessionId,
onDroppedReplacement: () => {
droppedCliSessionReplacement = true;
}
}) : void 0,
runParams: {
preparedRunAdmission: params.preparedRunAdmission,
diagnosticOwner,
sessionId: turn.followupRun.run.sessionId,
sessionKey,
sessionTarget,
sessionEntry,
chatType: normalizeChatType(turn.followupRun.originatingChatType) ?? normalizeChatType(turn.sessionCtx.ChatType) ?? params.candidateRun.chatType,
runtimePolicySessionKey: turn.followupRun.run.runtimePolicySessionKey ?? turn.runtimePolicySessionKey,
agentId: turn.followupRun.run.agentId,
trigger: turn.isHeartbeat ? "heartbeat" : "user",
sessionFile: turn.followupRun.run.sessionFile,
workspaceDir: turn.followupRun.run.workspaceDir,
cwd: turn.followupRun.run.cwd,
config: params.runtimeConfig,
toolOverrides: turn.followupRun.run.toolOverrides,
prompt: turn.commandBody,
transcriptPrompt: turn.transcriptCommandBody,
media: turn.followupRun.media,
suppressNextUserMessagePersistence: params.suppressQueuedUserPersistenceForCandidate,
userTurnTranscriptRecorder: params.userTurnTranscriptRecorder,
contextEngineLogicalTurnLease: params.contextEngineLogicalTurnLease,
onContextEngineTurnCandidate: params.onContextEngineTurnCandidate,
onUserMessagePersisted: params.notifyUserMessagePersisted,
prepareAssistantTranscriptMessage: turn.opts?.prepareAssistantTranscriptMessage,
persistAssistantTranscript: turn.followupRun.currentInboundEventKind !== "room_event" && turn.followupRun.run.suppressTranscriptOnlyAssistantPersistence !== true,
storePath: turn.storePath,
currentInboundEventKind: turn.followupRun.currentInboundEventKind,
currentInboundContext: turn.followupRun.currentInboundContext,
inputProvenance: turn.followupRun.run.inputProvenance,
...buildCliMcpDelegationCapabilityBinding(resolveDelegationCapability({
fallbackActive: params.isFallbackRetry,
inputProvenance: turn.followupRun.run.inputProvenance,
disableTools: turn.opts?.disableTools,
toolsAllow: turn.opts?.toolsAllow
})),
modelProvider: params.provider,
modelHasVision,
modelContextWindow: selectedModelEntry?.contextWindow,
modelContextTokens: selectedModelEntry?.contextTokens,
contextWindow: sessionEntry?.contextWindow,
provider: params.cliExecutionProvider,
execOverrides: turn.followupRun.run.execOverrides,
bashElevated: turn.followupRun.run.bashElevated,
model: params.model,
thinkLevel: params.candidateThinkLevel,
fastMode: params.candidateFastMode.fastMode,
fastModeStartedAtMs: params.fastModeStartedAtMs,
fastModeAutoOnSeconds: params.candidateFastMode.fastModeAutoOnSeconds,
fastModeAutoProgressState: params.fastModeAutoProgressState,
isFinalFallbackAttempt: params.isFinalFallbackAttempt,
timeoutMs: turn.followupRun.run.timeoutMs,
runTimeoutOverrideMs: turn.followupRun.run.runTimeoutOverrideMs,
runId: params.runId,
lane: params.runLane,
extraSystemPrompt: turn.followupRun.run.extraSystemPrompt,
sourceReplyDeliveryMode: turn.followupRun.run.sourceReplyDeliveryMode,
taskSuggestionDeliveryMode: turn.followupRun.run.taskSuggestionDeliveryMode,
...turn.isHeartbeat ? { requireExplicitMessageTarget: true } : {},
silentReplyPromptMode: turn.followupRun.run.silentReplyPromptMode,
allowEmptyAssistantReplyAsSilent: turn.followupRun.run.allowEmptyAssistantReplyAsSilent,
extraSystemPromptStatic: turn.followupRun.run.extraSystemPromptStatic,
cliSessionBindingFacts: turn.followupRun.run.cliSessionBindingFacts,
ownerNumbers: turn.followupRun.run.ownerNumbers,
cliSessionId: cliSessionBinding?.sessionId,
cliSessionBinding,
authProfileId: authProfile.authProfileId,
bootstrapContextMode: turn.opts?.bootstrapContextMode,
bootstrapContextRunKind: params.bootstrapContextRunKind,
bootstrapPromptWarningSignaturesSeen: params.bootstrapPromptWarningSignaturesSeen,
bootstrapPromptWarningSignature: params.bootstrapPromptWarningSignaturesSeen[params.bootstrapPromptWarningSignaturesSeen.length - 1],
images: params.currentTurnImages.images,
imageOrder: params.currentTurnImages.imageOrder,
mediaImageLayout: params.currentTurnImages.mediaImageLayout,
skillsSnapshot: turn.followupRun.run.skillsSnapshot,
messageChannel: turn.followupRun.originatingChannel ?? void 0,
messageProvider: hookMessageProvider,
clientCaps: turn.followupRun.run.clientCaps,
currentChannelId: turn.followupRun.originatingTo ?? turn.sessionCtx.OriginatingTo ?? turn.sessionCtx.To,
senderId: turn.followupRun.run.senderId,
senderName: turn.followupRun.run.senderName,
senderUsername: turn.followupRun.run.senderUsername,
senderE164: turn.followupRun.run.senderE164,
groupId: turn.followupRun.run.groupId,
groupChannel: turn.followupRun.run.groupChannel,
groupSpace: turn.followupRun.run.groupSpace,
spawnedBy: turn.followupRun.run.spawnedBy,
chatId: turn.followupRun.originatingChatId,
channelContext: turn.followupRun.run.channelContext,
currentThreadTs: cliCurrentThreadId != null ? String(cliCurrentThreadId) : void 0,
currentMessageId: cliCurrentMessageId,
replyToMode: turn.followupRun.originatingReplyToMode ?? turn.sessionCtx.ReplyToMode,
currentInboundAudio: hasInboundAudio(turn.sessionCtx),
agentAccountId: turn.followupRun.run.agentAccountId,
senderIsOwner: turn.followupRun.run.senderIsOwner,
approvalReviewerDeviceId: turn.followupRun.run.approvalReviewerDeviceId,
toolsAllow: turn.opts?.toolsAllow,
skillWorkshopProposalRevision: params.candidateRun.skillWorkshopProposalRevision,
skillLibraryAuthoring: params.candidateRun.skillLibraryAuthoring,
disableTools: turn.opts?.disableTools,
toolAuthorityFingerprint,
abortSignal: params.runAbortSignal,
onBlockReply: turn.opts?.onBlockReply,
onPartialReply: turn.opts?.onPartialReply,
onExecutionPhase: params.signalExecutionPhaseForTyping,
replyOperation: turn.replyOperation
}
});
if (droppedCliSessionReplacement) return await settleCliSessionResult(candidateResult, async () => {
await clearCliSessionInStore({
provider: params.cliExecutionProvider,
expectedCliSessionId: cliSessionBinding?.sessionId,
expectedSessionId: sessionEntry?.sessionId,
assertCommitAllowed: assertSettlementCurrent,
sessionKey: turn.sessionKey,
sessionStore: turn.activeSessionStore,
storePath: turn.storePath,
activeSessionEntry: sessionEntry
});
params.classifyResult(candidateResult);
});
if ((!params.classifyResult(candidateResult) || candidateResult.meta.agentMeta?.clearCliSessionBinding === true) && !shouldPreserveUserFacingSessionStateForInputProvenance(turn.followupRun.run.inputProvenance)) return await persistCliSessionBindingResult({
provider: params.cliExecutionProvider,
result: candidateResult,
sessionKey,
storePath: turn.storePath,
sessionStore: turn.activeSessionStore,
expectedSession: sessionEntry,
assertSettlementCurrent,
abortSignal: params.runAbortSignal
});
return candidateResult;
}, {
lifecycleGeneration: params.lifecycleGeneration,
abortSignal: params.runAbortSignal,
trigger: turn.isHeartbeat ? "heartbeat" : "user",
inputProvenance: turn.followupRun.run.inputProvenance
}));
return {
result,
bootstrapPromptWarningSignaturesSeen: resolveBootstrapWarningSignaturesSeen(result.meta?.systemPromptReport)
};
}
//#endregion
//#region src/auto-reply/reply/compaction-notice.ts
const COMPACTION_NOTICE_TEXT = {
start: "🧹 Compacting context...",
end: "🧹 Compaction complete",
incomplete: "🧹 Compaction incomplete",
skipped: "🧹 Compaction not needed",
memory_flush_degraded: "⚠️ Memory maintenance temporarily failed; continuing your reply."
};
function formatCompactionModelRef(provider, model) {
const normalizedProvider = normalizeOptionalString(provider);
const normalizedModel = normalizeOptionalString(model);
if (normalizedProvider && normalizedModel) return `${sanitizeForLog(normalizedProvider)}/${sanitizeForLog(normalizedModel)}`;
if (normalizedProvider) return sanitizeForLog(normalizedProvider);
if (normalizedModel) return sanitizeForLog(normalizedModel);
return "unknown model";
}
function shouldNotifyUserAboutCompaction(cfg) {
return cfg?.agents?.defaults?.compaction?.notifyUser === true;
}
function createCompactionNoticePayload(params) {
const payload = {
text: params.text ?? COMPACTION_NOTICE_TEXT[params.phase],
...params.currentMessageId ? { replyToId: params.currentMessageId } : {},
replyToCurrent: true,
isCompactionNotice: true
};
return params.applyReplyToMode ? params.applyReplyToMode(payload) : payload;
}
function readCompactionHookMessages(value) {
if (!Array.isArray(value)) return [];
return value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
}
function createCompactionHookNoticePayload(params) {
if (params.messages.length === 0) return;
const payload = {
text: params.messages.join("\n\n"),
...params.currentMessageId ? { replyToId: params.currentMessageId } : {},
replyToCurrent: true,
isCompactionNotice: true
};
return params.applyReplyToMode ? params.applyReplyToMode(payload) : payload;
}
//#endregion
//#region src/auto-reply/reply/agent-runner-event-handler.ts
const agentCompactionLog = createSubsystemLogger("auto-reply/compaction");
const CODEX_APP_SERVER_COMPACTION_BACKEND = "codex-app-server";
function readApprovalScopeValue(value) {
return value === "turn" || value === "session" ? value : void 0;
}
/** Bridges embedded-agent events into channel progress and compaction notices. */
function createAgentRunEventHandler(params) {
const shouldSuppressProgressAfterMessageToolDelivery = () => params.sourceRepliesAreToolOnly && params.messageToolDeliveryState.completed && params.turn.opts?.allowProgressCallbacksWhenSourceDeliverySuppressed !== true;
const currentMessageId = params.turn.sessionCtx.MessageSidFull ?? params.turn.sessionCtx.MessageSid;
const deliverCompactionNoticePayload = async (noticePayload, label) => {
const deliver = params.turn.opts?.onBlockReply ?? params.turn.onCompactionNoticePayload;
if (!deliver) return;
try {
await deliver(noticePayload);
} catch (err) {
logVerbose(`compaction ${label} notice delivery failed (non-fatal): ${String(err)}`);
}
};
const sendCompactionNotice = async (phase) => {
await deliverCompactionNoticePayload(createCompactionNoticePayload({
phase,
currentMessageId,
applyReplyToMode: params.turn.applyReplyToMode
}), phase);
};
const sendCompactionHookMessages = async (messages) => {
const noticePayload = createCompactionHookNoticePayload({
messages,
currentMessageId,
applyReplyToMode: params.turn.applyReplyToMode
});
if (noticePayload) await deliverCompactionNoticePayload(noticePayload, "hook");
};
return async (evt) => {
params.turn.replyOperation?.recordActivity();
params.lifecycleBackstop.note(evt);
const hasLifecyclePhase = evt.stream === "lifecycle" && typeof evt.data.phase === "string";
if (evt.stream !== "lifecycle" || hasLifecyclePhase) params.notifyAgentRunStart();
if (evt.stream === "tool" && evt.data.hideFromChannelProgress !== true) {
const phase = readStringValue(evt.data.phase) ?? "";
const name = readStringValue(evt.data.name);
const toolCallId = readStringValue(evt.data.toolCallId) ?? "";
const args = evt.data.args && typeof evt.data.args === "object" ? evt.data.args : void 0;
if (params.sourceRepliesAreToolOnly && toolCallId && name && (phase === "start" || phase === "update") && args && isMessagingToolSendAction(name, args)) params.messageToolDeliveryState.toolCallIds.add(toolCallId);
if (shouldSuppressProgressAfterMessageToolDelivery()) return;
if (phase === "start" || phase === "update") {
const toolStartProgressPromise = params.turn.opts?.onToolStart?.({
itemId: readStringValue(evt.data.itemId),
toolCallId: readStringValue(evt.data.toolCallId),
name,
phase,
args,
detailMode: params.turn.toolProgressDetail
});
await Promise.all([params.turn.typingSignals.signalToolStart(), toolStartProgressPromise]);
}
const commandOutput = buildCommandOutputFromToolResultEvent(evt);
if (commandOutput) await params.turn.opts?.onCommandOutput?.(commandOutput);
}
const suppressItemChannelProgress = evt.stream === "item" && evt.data.suppressChannelProgress === true && Boolean(params.turn.opts?.onToolStart);
const hideItemFromChannelProgress = evt.stream === "item" && evt.data.hideFromChannelProgress === true;
const itemPhase = evt.stream === "item" ? readStringValue(evt.data.phase) : "";
const itemName = evt.stream === "item" ? readStringValue(evt.data.name) : "";
const itemStatus = evt.stream === "item" ? readStringValue(evt.data.status) : "";
const itemToolCallId = evt.stream === "item" ? readStringValue(evt.data.toolCallId) ?? "" : "";
const completedMessageToolDelivery = params.sourceRepliesAreToolOnly && itemPhase === "end" && itemStatus === "completed" && itemToolCallId.length > 0 && params.messageToolDeliveryState.toolCallIds.has(itemToolCallId);
const suppressProgressAfterMessageToolDelivery = shouldSuppressProgressAfterMessageToolDelivery();
if (completedMessageToolDelivery) {
params.messageToolDeliveryState.toolCallIds.delete(itemToolCallId);
params.messageToolDeliveryState.completed = true;
}
if (evt.stream === "item" && !hideItemFromChannelProgress && !suppressItemChannelProgress && (!suppressProgressAfterMessageToolDelivery || completedMessageToolDelivery)) {
const itemSummary = readStringValue(evt.data.summary);
const itemProgressText = readStringValue(evt.data.progressText);
const itemMeta = readStringValue(evt.data.meta);
const itemCommandBearing = typeof evt.data.commandBearing === "boolean" ? evt.data.commandBearing : void 0;
const itemApprovalId = readStringValue(evt.data.approvalId);
const itemApprovalSlug = readStringValue(evt.data.approvalSlug);
await params.turn.opts?.onItemEvent?.({
itemId: readStringValue(evt.data.itemId),
kind: readStringValue(evt.data.kind),
title: readStringValue(evt.data.title),
phase: itemPhase,
status: itemStatus,
...itemToolCallId ? { toolCallId: itemToolCallId } : {},
...itemName ? { name: itemName } : {},
...itemSummary !== void 0 ? { summary: itemSummary } : {},
...itemProgressText !== void 0 ? { progressText: itemProgressText } : {},
...itemMeta !== void 0 ? { meta: itemMeta } : {},
...itemCommandBearing !== void 0 ? { commandBearing: itemCommandBearing } : {},
...itemApprovalId !== void 0 ? { approvalId: itemApprovalId } : {},
...itemApprovalSlug !== void 0 ? { approvalSlug: itemApprovalSlug } : {}
});
}
if (evt.stream === "plan" && !shouldSuppressProgressAfterMessageToolDelivery()) await params.turn.opts?.onPlanUpdate?.({
phase: readStringValue(evt.data.phase),
title: readStringValue(evt.data.title),
explanation: readStringValue(evt.data.explanation),
steps: normalizeAgentPlanSteps(evt.data.steps),
source: readStringValue(evt.data.source)
});
if (evt.stream === "approval" && !shouldSuppressProgressAfterMessageToolDelivery()) await params.turn.opts?.onApprovalEvent?.({
phase: readStringValue(evt.data.phase),
kind: readStringValue(evt.data.kind),
status: readStringValue(evt.data.status),
title: readStringValue(evt.data.title),
itemId: readStringValue(evt.data.itemId),
toolCallId: readStringValue(evt.data.toolCallId),
approvalId: readStringValue(evt.data.approvalId),
approvalSlug: readStringValue(evt.data.approvalSlug),
command: readStringValue(evt.data.command),
host: readStringValue(evt.data.host),
reason: readStringValue(evt.data.reason),
scope: readApprovalScopeValue(evt.data.scope),
message: readStringValue(evt.data.message)
});
if (evt.stream === "command_output" && !shouldSuppressProgressAfterMessageToolDelivery()) await params.turn.opts?.onCommandOutput?.({
itemId: readStringValue(evt.data.itemId),
phase: readStringValue(evt.data.phase),
title: readStringValue(evt.data.title),
toolCallId: readStringValue(evt.data.toolCallId),
name: readStringValue(evt.data.name),
output: readStringValue(evt.data.output),
status: readStringValue(evt.data.status),
exitCode: typeof evt.data.exitCode === "number" || evt.data.exitCode === null ? evt.data.exitCode : void 0,
durationMs: typeof evt.data.durationMs === "number" ? evt.data.durationMs : void 0,
cwd: readStringValue(evt.data.cwd)
});
if (evt.stream === "patch" && !shouldSuppressProgressAfterMessageToolDelivery()) await params.turn.opts?.onPatchSummary?.({
itemId: readStringValue(evt.data.itemId),
phase: readStringValue(evt.data.phase),
title: readStringValue(evt.data.title),
toolCallId: readStringValue(evt.data.toolCallId),
name: readStringValue(evt.data.name),
added: Array.isArray(evt.data.added) ? evt.data.added.filter((entry) => typeof entry === "string") : void 0,
modified: Array.isArray(evt.data.modified) ? evt.data.modified.filter((entry) => typeof entry === "string") : void 0,
deleted: Array.isArray(evt.data.deleted) ? evt.data.deleted.filter((entry) => typeof entry === "string") : void 0,
summary: readStringValue(evt.data.summary)
});
if (evt.stream !== "compaction") return;
const phase = readStringValue(evt.data.phase) ?? "";
const backend = readStringValue(evt.data.backend);
const hookMessages = readCompactionHookMessages(evt.data.messages);
const sendCompactionUserNotices = async (noticePhase) => {
if (hookMessages.length > 0) await sendCompactionHookMessages(hookMessages);
if (params.notifyUserAboutCompaction) await sendCompactionNotice(noticePhase);
};
if (phase === "start") {
await params.turn.opts?.onCompactionStart?.();
await sendCompactionUserNotices("start");
return;
}
if (phase !== "end") return;
if (evt.data.completed !== true) {
await params.turn.opts?.onCompactionEnd?.({ completed: false });
await sendCompactionUserNotices("incomplete");
return;
}
const compactionCount = params.onCompactionCompleted();
if (backend === CODEX_APP_SERVER_COMPACTION_BACKEND) {
const consoleMessage = `codex app-server auto-compaction succeeded for ${formatCompactionModelRef(params.provider, params.model)}; refreshed session context`;
agentCompactionLog.info("codex app-server auto-compaction succeeded", {
event: "codex_app_server_compaction_succeeded",
backend,
provider: params.provider,
model: params.model,
sessionKey: params.turn.sessionKey,
sessionId: params.effectiveSessionId,
threadId: readStringValue(evt.data.threadId),
turnId: readStringValue(evt.data.turnId),
itemId: readStringValue(evt.data.itemId),
compactionCount,
consoleMessage
});
}
await params.turn.opts?.onCompactionEnd?.({ completed: true });
await sendCompactionUserNotices("end");
};
}
//#endregion
//#region src/auto-reply/reply/agent-runner-embedded-candidate.ts
async function runEmbeddedFallbackCandidate(params) {
const turn = params.turn;
const sourceReplyDeliveryRuntime = readSourceReplyDeliveryRuntime(params.candidateRun);
const candidateRun = {
...params.candidateRun,
...params.candidateFastMode,
thinkLevel: params.candidateThinkLevel
};
const { embeddedContext, senderContext, runBaseParams } = await buildEmbeddedRunExecutionParams({
run: candidateRun,
replyRoute: turn.followupRun,
sessionCtx: turn.sessionCtx,
hasRepliedRef: turn.opts?.hasRepliedRef,
provider: params.provider,
runId: params.runId,
promptCacheKey: turn.opts?.promptCacheKey,
allowTransientCooldownProbe: params.allowTransientCooldownProbe,
model: params.model
});
if (sourceReplyDeliveryRuntime) bindSourceReplyDeliveryRuntime(runBaseParams, sourceReplyDeliveryRuntime);
const agentHarnessPolicy = params.sessionRuntimeOverride ? {
runtime: params.sessionRuntimeOverride,
runtimeSource: "model"
} : resolveAgentHarnessPolicy({
provider: params.provider,
modelId: params.model,
config: params.runtimeConfig,
agentId: turn.followupRun.run.agentId,
sessionKey: turn.followupRun.run.runtimePolicySessionKey ?? turn.sessionKey
});
const embeddedRunProvider = resolveOpenAIRuntimeProvider({
provider: params.provider,
harnessRuntime: agentHarnessPolicy.runtime,
authProfileProvider: runBaseParams.authProfileId?.split(":", 1)[0],
authProfileId: runBaseParams.authProfileId,
config: params.runtimeConfig,
workspaceDir: turn.followupRun.run.workspaceDir
});
const embeddedRunHarnessOverride = params.sessionRuntimeOverride ?? (agentHarnessPolicy.runtime === "openclaw" && embeddedRunProvider !== params.provider ? "openclaw" : void 0);
const messageActionCapabilitySessionKey = turn.runtimePolicySessionKey ?? embeddedContext.sessionKey;
const messageActionTurnCapability = isTrustedMessageActionTurnIngress(turn.sessionCtx.Provider) && !turn.isHeartbeat && embeddedContext.agentId && messageActionCapabilitySessionKey && embeddedContext.messageProvider && embeddedContext.currentChannelId ? mintMessageActionTurnCapability({
agentId: embeddedContext.agentId,
runId: params.runId,
sessionKey: messageActionCapabilitySessionKey,
sourceReplySessionKey: embeddedContext.sessionKey,
sessionId: embeddedContext.sessionId,
requesterAccountId: embeddedContext.agentAccountId,
requesterSenderId: senderContext.senderId,
requesterSenderName: senderContext.senderName,
requesterSenderUsername: senderContext.senderUsername,
requesterSenderE164: senderContext.senderE164,
toolContext: {
currentChannelId: embeddedContext.currentChannelId,
currentChatType: embeddedContext.chatType,
currentMessagingTarget: embeddedContext.currentMessagingTarget,
currentGraphChannelId: embeddedContext.currentGraphChannelId,
currentChannelProvider: embeddedContext.currentChannelProvider,
currentThreadTs: embeddedContext.currentThreadTs,
currentMessageId: embeddedContext.currentMessageId,
currentSourceTurnId: embeddedContext.currentSourceTurnId,
replyToMode: embeddedContext.replyToMode,
hasRepliedRef: embeddedContext.hasRepliedRef,
sameChannelThreadRequired: embeddedContext.sameChannelThreadRequired
},
...resolveMessageActionTurnCapabilityLifetime(runBaseParams.timeoutMs)
}) : void 0;
let attemptCompactionCount = 0;
let postCompactionModelAttempted = false;
let compactionAccounting;
const lifecycleBackstop = createAgentLifecycleTerminalBackstop({
runId: params.runId,
sessionKey: turn.sessionKey,
getLifecycleGeneration: params.getLifecycleGeneration,
resolveTerminationFields: (error) => resolveReplyOperationTerminationFields(error, params.runAbortSignal, turn.replyOperation)
});
params.onLifecycleBackstop(lifecycleBackstop);
try {
params.timing.logMilestoneIfSlow({
runId: params.runId,
sessionId: turn.followupRun.run.sessionId,
sessionKey: turn.sessionKey,
milestone: "before_embedded_run"
});
let eventHandler;
const result = await params.timing.measure("embedded_run", () => {
const embeddedRunParams = {
preparedRunAdmission: params.preparedRunAdmission,
githubPublicationAvailable: params.githubPublicationAvailable,
...embeddedContext,
messageActionTurnCapability,
lifecycleGeneration: params.getLifecycleGeneration(),
allowGatewaySubagentBinding: true,
trigger: turn.isHeartbeat ? "heartbeat" : "user",
cronCreatorAuthorityCapability: turn.opts?.cronCreatorAuthorityCapability,
cronCreatorAuthorityUnavailableReason: turn.opts?.turnAdoptionLifecycle?.cronCreatorAuthorityUnavailable,
groupId: resolveGroupSessionKey(turn.sessionCtx)?.id,
groupChannel: normalizeOptionalString(turn.sessionCtx.GroupChannel) ?? normalizeOptionalString(turn.sessionCtx.GroupSubject),
groupSpace: normalizeOptionalString(turn.sessionCtx.GroupSpace),
...senderContext,
...runBaseParams,
contextWindow: turn.getActiveSessionEntry()?.contextWindow,
lane: params.runLane,
provider: embeddedRunProvider,
agentHarnessId: resolveSessionPinnedHarnessId(turn.getActiveSessionEntry()),
agentHarnessRuntimeOverride: embeddedRunHarnessOverride,
agentHarnessRuntimePreparationHint: agentHarnessPolicy.runtimeSource !== "implicit" ? agentHarnessPolicy.runtime : void 0,
fastModeStartedAtMs: params.fastModeStartedAtMs,
fastModeAutoProgressState: params.fastModeAutoProgressState,
isFinalFallbackAttempt: params.isFinalFallbackAttempt,
sandboxSessionKey: turn.runtimePolicySessionKey,
prompt: turn.commandBody,
transcriptPrompt: turn.transcriptCommandBody,
media: turn.followupRun.media,
userTurnTranscriptRecorder: params.userTurnTranscriptRecorder,
contextEngineLogicalTurnLease: params.contextEngineLogicalTurnLease,
onContextEngineTurnCandidate: params.onContextEngineTurnCandidate,
currentInboundEventKind: turn.followupRun.currentInboundEventKind,
currentInboundContext: turn.followupRun.currentInboundContext,
explicitSkillSelections: turn.followupRun.explicitSkillSelections,
extraSystemPrompt: turn.followupRun.run.extraSystemPrompt,
sourceReplyDeliveryMode: turn.followupRun.run.sourceReplyDeliveryMode,
forceMessageTool: turn.followupRun.run.sourceReplyDeliveryMode === "message_tool_only",
...turn.isHeartbeat ? { requireExplicitMessageTarget: true } : {},
silentReplyPromptMode: turn.followupRun.run.silentReplyPromptMode,
suppressNextUserMessagePersistence: params.suppressQueuedUserPersistenceForCandidate,
onUserMessagePersisted: params.notifyUserMessagePersisted,
suppressTranscriptOnlyAssistantPersistence: turn.followupRun.run.suppressTranscriptOnlyAssistantPersistence,
suppressAssistantErrorPersistence: params.suppressAssistantErrorPersistenceForCandidate,
onAssistantErrorMessagePersisted: params.onAssistantErrorMessagePersisted,
prepareAssistantTranscriptMessage: turn.opts?.prepareAssistantTranscriptMessage,
onAutoCompactionSucceeded: (count) => {
attemptCompactionCount = Math.max(attemptCompactionCount, count);
},
toolResultFormat: (() => {
const channel = resolveMessageChannel(turn.sessionCtx.Surface, turn.sessionCtx.Provider);
return !channel || isMarkdownCapableMessageChannel(channel) ? "markdown" : "plain";
})(),
toolProgressDetail: turn.toolProgressDetail,
toolsAllow: turn.opts?.toolsAllow,
disableTools: turn.opts?.disableTools,
toolAuthorityFingerprint: turn.replyOperation?.toolAuthorityFingerprint,
enableHeartbeatTool: turn.opts?.enableHeartbeatTool,
forceHeartbeatTool: turn.opts?.forceHeartbeatTool,
bootstrapContextMode: turn.opts?.bootstrapContextMode,
bootstrapContextRunKind: params.bootstrapContextRunKind,
images: params.currentTurnImages.images,
imageOrder: params.currentTurnImages.imageOrder,
abortSignal: params.runAbortSignal,
replyOperation: turn.replyOperation,
deferTerminalLifecycle: true,
onCompactionAccounting: (fact) => {
compactionAccounting = fact;
},
onDeferredLifecycleOwner: params.deferredLifecycle.adopt,
onDeferredLifecycleAbort: params.deferredLifecycle.abort,
onExecutionStarted: (info) => {
if (info?.lifecycleGeneration) params.onLifecycleGeneration(info.lifecycleGeneration);
},
onExecutionPhase: (info) => {
if (info.phase === "model_call_started" && attemptCompactionCount > 0) postCompactionModelAttempted = true;
params.signalExecutionPhaseForTyping(info);
},
onLaneWait: ({ waiting }) => {
const replyOperation = turn.replyOperation;
if (waiting && replyOperation) markReplyOperationGlobalLaneWaitProgress(replyOperation);
},
blockReplyBreak: turn.resolvedBlockStreamingBreak,
blockReplyChunking: turn.blockReplyChunking,
onPartialReply: async (payload) => {
const classified = params.presentation.classifyStreamingPartial(payload);
if (classified.skip || !classified.text) return false;
const textForTyping = classified.text;
let didMaterialize = false;
let materializedText;
const partialPayload = {
get text() {
if (!didMaterialize) {
const sanitized = params.presentation.sanitizeStreamingText(textForTyping, false);
materializedText = sanitized.skip ? void 0 : sanitized.text;
didMaterialize = true;
}
return materializedText;
},
mediaUrls: payload.mediaUrls
};
const onPartialReply = turn.opts?.onPartialReply;
return await params.presentation.presentWithTyping(turn.typingSignals.signalTextDelta(textForTyping), () => onPartialReply ? onPartialReply(partialPayload) : false);
},
onAssistantMessageStart: async () => {
await params.presentation.presentWithTyping(turn.typingSignals.signalMessageStart(), () => turn.opts?.onAssistantMessageStart?.());
},
onReasoningStream: turn.typingSignals.shouldStartOnReasoning || turn.opts?.onReasoningStream ? async (payload) => {
if (turn.followupRun.run.silentExpected) return;
await params.presentation.presentWithTyping(turn.typingSignals.signalReasoningDelta(), () => turn.opts?.onReasoningStream?.({
text: payload.text,
mediaUrls: payload.mediaUrls,
isReasoningSnapshot: payload.isReasoningSnapshot,
requiresReasoningProgressOptIn: payload.requiresReasoningProgressOptIn
}));
} : void 0,
streamReasoningInNonStreamModes: turn.opts?.streamReasoningInNonStreamModes,
onReasoningEnd: turn.opts?.onReasoningEnd ? async () => {
await turn.opts?.onReasoningEnd?.();
} : void 0,
onAgentEvent: (event) => {
eventHandler ??= createAgentRunEventHandler({
turn,
lifecycleBackstop,
notifyAgentRunStart: params.notifyAgentRunStart,
sourceRepliesAreToolOnly: (sourceReplyDeliveryRuntime?.currentMode ?? turn.followupRun.run.sourceReplyDeliveryMode) === "message_tool_only",
messageToolDeliveryState: params.messageToolDeliveryState,
provider: params.provider,
model: params.model,
runId: params.runId,
effectiveSessionId: params.effectiveRun.sessionId,
notifyUserAboutCompaction: params.notifyUserAboutCompaction,
onCompactionCompleted: () => {
attemptCompactionCount += 1;
return attemptCompactionCount;
}
});
return eventHandler(event);
},
onBlockReply: params.presentation.blockReplyHandler,
onBlockReplyFlush: turn.blockStreamingEnabled && turn.blockReplyPipeline ? async () => {
await turn.blockReplyPipeline?.flush({ force: true });
} : void 0,
shouldEmitToolResult: turn.shouldEmitToolResult,
shouldEmitToolOutput: turn.shouldEmitToolOutput,
bootstrapPromptWarningSignaturesSeen: params.bootstrapPromptWarningSignaturesSeen,
bootstrapPromptWarningSignature: params.bootstrapPromptWarningSignaturesSeen[params.bootstrapPromptWarningSignaturesSeen.length - 1],
onToolResult: turn.opts?.onToolResult ? (() => {
let toolResultChain = Promise.resolve();
return (payload) => {
const delivery = toolResultChain.then(async () => {
turn.replyOperation?.recordActivity();
const { text, skip } = params.presentation.normalizeStreamingText(payload);
if (skip) return;
if (text !== void 0) await turn.typingSignals.signalTextDelta(text);
await turn.opts?.onToolResult?.({
...payload,
text
});
});
toolResultChain = delivery.catch((err) => {
logVerbose(`tool result delivery failed: ${String(err)}`);
});
const task = toolResultChain.finally(() => {
turn.pendingToolTasks.delete(task);
});
turn.pendingToolTasks.add(task);
return delivery;
};
})() : void 0
};
return runEmbeddedAgent(embeddedRunParams);
});
const resultCompactionCount = Math.max(0, result.meta?.agentMeta?.compactionCount ?? 0);
attemptCompactionCount = Math.max(attemptCompactionCount, resultCompactionCount);
return {
result,
bootstrapPromptWarningSignaturesSeen: resolveBootstrapWarningSignaturesSeen(result.meta?.systemPromptReport)
};
} finally {
const accounting = compactionAccounting ?? (attemptCompactionCount > 0 ? {
kind: "presentation-only",
count: attemptCompactionCount,
currentContextSnapshot: { tokens: void 0 }
} : void 0);
params.onCompactionFacts({
accounting,
postCompactionModelAttempted
});
revokeMessageActionTurnCapability(messageActionTurnCapability);
}
}
//#endregion
//#region src/auto-reply/reply/agent-runner-model-fallback-lifecycle.ts
function emitModelFallbackStepLifecycle(params) {
emitAgentEvent({
runId: params.runId,
...params.sessionKey ? { sessionKey: params.sessionKey } : {},
stream: "lifecycle",
data: {
phase: "fallback_step",
...params.step
}
});
}
//#endregion
//#region src/auto-reply/reply/agent-runner-fallback-candidate.ts
/** Runs the provider/model fallback candidates while preserving cross-candidate delivery state. */
async function runAgentFallbackCandidates(params) {
const turn = params.turn;
const sourceReplyDeliveryRuntimeOptions = turn.opts;
const sourceReplyDeliveryRuntime = readSourceReplyDeliveryRuntime(turn.followupRun.run) ?? createSourceReplyDeliveryRuntime({
origin: sourceReplyDeliveryRuntimeOptions?.sourceReplyDeliveryModeOrigin ?? "stable_policy",
initialMode: turn.followupRun.run.sourceReplyDeliveryMode ?? "automatic",
projections: [turn.followupRun.run, ...turn.opts ? [turn.opts] : []],
promptComponentByMode: {
automatic: "",
message_tool_only: ""
},
promptComponentOffset: void 0,
onModeResolved: sourceReplyDeliveryRuntimeOptions?.onSourceReplyDeliveryModeResolved
});
sourceReplyDeliveryRuntime.track(turn.followupRun.run);
if (turn.opts) sourceReplyDeliveryRuntime.track(turn.opts);
bindSourceReplyDeliveryRuntime(turn.followupRun.run, sourceReplyDeliveryRuntime);
const sourceReplyDeliveryModeOrigin = sourceReplyDeliveryRuntime.origin;
const preserveProgressCallbackStartOrder = turn.opts?.preserveProgressCallbackStartOrder === true;
const runLane = turn.isHeartbeat ? "cron-nested" : "main";
let queuedUserMessagePersistedAcrossFallback = false;
let assistantErrorPersistedAcrossFallback = false;
const messageToolDeliveryState = {
toolCallIds: /* @__PURE__ */ new Set(),
completed: false
};
const userTurnTranscriptRecorder = turn.followupRun.userTurnTranscriptRecorder ?? turn.opts?.userTurnTranscriptRecorder;
const fastModeStartedAtMs = Date.now();
const fastModeAutoProgressState = {
offAnnounced: false,
resetAnnounced: false
};
const bootstrapContextRunKind = turn.opts?.isHeartbeat ? "heartbeat" : "default";
let githubPublicationAvailability;
params.timing.logMilestoneIfSlow({
runId: params.runId,
sessionId: turn.followupRun.run.sessionId,
sessionKey: turn.sessionKey,
milestone: "before_model_fallback"
});
const selection = resolveModelFallbackOptions(params.effectiveRun, params.runtimeConfig);
const resolveCandidateRuntime = (provider, model) => {
const candidateRun = resolveFallbackCandidateRun(params.effectiveRun, provider, model);
const activeEntry = params.liveModelSwitchRuntimeEntry ?? turn.getActiveSessionEntry();
const sessionRuntimeOverride = resolveSessionRuntimeOverrideForProvider({
provider,
entry: activeEntry,
cfg: params.runtimeConfig
});
const pinnedHarnessId = resolveSessionPinnedHarnessId(activeEntry);
const locksPersistedHarness = pinnedHarnessId !== void 0 && pinnedHarnessId === sessionRuntimeOverride;
const selectedAuthProfile = resolveRunAuthProfile(candidateRun, provider, { config: params.runtimeConfig });
const pinnedCliRuntime = !locksPersistedHarness && sessionRuntimeOverride && isCliProvider(sessionRuntimeOverride, params.runtimeConfig) ? sessionRuntimeOverride : void 0;
const cliExecutionProvider = pinnedCliRuntime ?? (sessionRuntimeOverride ? provider : resolveCliRuntimeExecutionProvider({
provider,
cfg: params.runtimeConfig,
agentId: turn.followupRun.run.agentId,
modelId: model,
authProfileId: selectedAuthProfile.authProfileId
}) ?? provider);
return {
candidateRun,
sessionRuntimeOverride,
cliExecutionProvider,
useCliExecution: pinnedCliRuntime !== void 0 || !sessionRuntimeOverride && isCliProvider(cliExecutionProvider, params.runtimeConfig)
};
};
return params.timing.measure("model_fallback", () => runEmbeddedAgentEntry({
selection: {
cfg: selection.cfg,
provider: selection.provider,
model: selection.model,
requestedRouteResolution: selection.requestedRouteResolution,
agentDir: selection.agentDir,
fallbacksOverride: selection.fallbacksOverride,
userLockedAuthProfileId: turn.followupRun.run.authProfileIdSource === "user" ? turn.followupRun.run.authProfileId : void 0
},
identity: {
runId: params.runId,
agentId: turn.followupRun.run.agentId,
sessionId: turn.followupRun.run.sessionId,
sessionKey: selection.sessionKey,
lane: runLane
},
harness: {
workspaceDir: turn.followupRun.run.workspaceDir,
sessionKey: turn.followupRun.run.runtimePolicySessionKey ?? turn.sessionKey,
preparation: {
kind: "measured",
run: (prepare) => params.timing.measure("fallback_prepare_harness", prepare)
},
resolveRuntimeOverride: (provider) => resolveSessionRuntimeOverrideForProvider({
provider,
entry: params.liveModelSwitchRuntimeEntry ?? turn.getActiveSessionEntry(),
cfg: params.runtimeConfig
}),
resolveContextEngineHost: (provider, model) => {
const runtime = resolveCandidateRuntime(provider, model);
if (!runtime.useCliExecution) return;
const backend = resolveCliBackendConfig(runtime.cliExecutionProvider, params.runtimeConfig, { agentId: turn.followupRun.run.agentId });
return buildGenericCliContextEngineHostSupport({
backendId: backend?.id ?? runtime.cliExecutionProvider,
...backend?.contextEngineHostCapabilities ? { capabilities: backend.contextEngineHostCapabilities } : {}
});
}
},
behavior: {
kind: "channel-delivery",
readDeliveryEvidence: () => ({
hasDirectlySentBlockReply: params.directlySentBlockKeys.size > 0,
hasBlockReplyPipelineOutput: Boolean(turn.blockReplyPipeline?.hasBuffered() || turn.blockReplyPipeline?.didStream())
})
},
sessionOverride: {
kind: "reconcile-completed",
reconcile: params.clearRecoveredAutoFallbackPrimaryProbe
},
onAcceptedTerminal: () => {
params.commitTerminalOutcome();
return turn.replyOperation ? beginReplyOperationFinalizationWork(turn.replyOperation, RUN_STALE_TAKEOVER_MS) : void 0;
},
abortSignal: params.runAbortSignal,
onFallbackStep: (step) => {
emitModelFallbackStepLifecycle({
runId: params.runId,
sessionKey: turn.sessionKey,
step
});
},
runCandidate: async (provider, model, runOptions) => {
invalidateTurnCompactionContext(params.state.compaction);
params.state.attemptedRuntimeProvider = provider;
params.state.attemptedRuntimeModel = model;
const runtime = params.timing.measureSync("fallback_resolve_runtime", () => resolveCandidateRuntime(provider, model));
const candidateRun = runtime.candidateRun;
bindSourceReplyDeliveryRuntime(candidateRun, sourceReplyDeliveryRuntime);
const candidateSourceReplyDeliveryMode = sourceReplyDeliveryModeOrigin === "runtime_default" && runtime.useCliExecution ? candidateRun.cliSessionBindingFacts?.sourceReplyDeliveryMode ?? "automatic" : sourceReplyDeliveryRuntime.currentMode;
const applySourceReplyDeliveryModeBeforeInvocation = sourceReplyDeliveryModeOrigin !== "runtime_default" || runtime.useCliExecution;
if (candidateSourceReplyDeliveryMode && applySourceReplyDeliveryModeBeforeInvocation) sourceReplyDeliveryRuntime.applyMode(candidateRun, candidateSourceReplyDeliveryMode);
const candidateThinkLevel = resolveRunThinkingLevelForFallbackCandidate({
cfg: params.runtimeConfig,
provider,
modelId: model,
run: turn.followupRun.run,
catalog: turn.followupRun.run.thinkingCatalog,
agentId: turn.followupRun.run.agentId,
sessionKey: turn.followupRun.run.runtimePolicySessionKey ?? turn.sessionKey,
sessionEntry: turn.getActiveSessionEntry()
});
const candidateFastMode = resolveRunFastModeForFallbackCandidate({
run: candidateRun,
config: params.runtimeConfig,
provider,
model,
sessionEntry: turn.getActiveSessionEntry()
});
const activeProbe = params.effectiveRun.autoFallbackPrimaryProbe;
if (activeProbe && provider === activeProbe.provider && model === activeProbe.model) markAutoFallbackPrimaryProbe({
probe: activeProbe,
sessionKey: turn.sessionKey
});
turn.opts?.onModelSelected?.({
provider,
model,
thinkLevel: candidateThinkLevel
});
const signalExecutionPhaseForCandidate = (info) => {
if (params.state.compaction.count > 0 && (info.phase === "model_call_started" || info.phase === "process_spawned")) params.state.postCompactionModelAttempted = true;
params.signalExecutionPhaseForTyping(info);
};
const common = {
preparedRunAdmission: params.preparedRunAdmission,
turn,
candidateRun,
runtimeConfig: params.runtimeConfig,
provider,
model,
candidateThinkLevel,
candidateFastMode,
runId: params.runId,
runAbortSignal: params.runAbortSignal,
runLane,
isFallbackRetry: runOptions.isFallbackRetry,
isFinalFallbackAttempt: runOptions?.isFinalFallbackAttempt,
suppressQueuedUserPersistenceForCandidate: (turn.followupRun.run.suppressNextUserMessagePersistence ?? false) || queuedUserMessagePersistedAcrossFallback,
userTurnTranscriptRecorder,
contextEngineLogicalTurnLease: runOptions.contextEngineLogicalTurnLease,
onContextEngineTurnCandidate: runOptions.onContextEngineTurnCandidate,
notifyUserMessagePersisted: () => {
queuedUserMessagePersistedAcrossFallback = true;
},
fastModeStartedAtMs,
fastModeAutoProgressState,
bootstrapContextRunKind,
bootstrapPromptWarningSignaturesSeen: params.state.bootstrapPromptWarningSignaturesSeen,
currentTurnImages: params.currentTurnImages,
signalExecutionPhaseForTyping: signalExecutionPhaseForCandidate,
notifyAgentRunStart: params.notifyAgentRunStart,
preserveProgressCallbackStartOrder,
presentation: params.presentation,
timing: params.timing,
onLifecycleBackstop: (backstop) => {
params.state.pendingLifecycleTerminal = {
provider,
model,
backstop
};
},
deferredLifecycle: params.state.deferredLifecycle
};
if (runtime.useCliExecution) {
const candidate = await runCliFallbackCandidate({
...common,
cliExecutionProvider: runtime.cliExecutionProvider,
classifyResult: runOptions.classifyResult,
lifecycleGeneration: params.state.lifecycleGeneration
});
params.state.bootstrapPromptWarningSignaturesSeen = candidate.bootstrapPromptWarningSignaturesSeen;
return candidate.result;
}
const candidate = await runEmbeddedFallbackCandidate({
...common,
githubPublicationAvailable: await (githubPublicationAvailability ??= turn.sessionKey && params.effectiveRun.agentId ? prepareGitHubPublicationAvailability({
sessionId: turn.followupRun.run.sessionId,
sessionKey: turn.sessionKey,
agentId: params.effectiveRun.agentId
}) : Promise.resolve(false)),
effectiveRun: params.effectiveRun,
sessionRuntimeOverride: runtime.sessionRuntimeOverride,
getLifecycleGeneration: () => params.state.lifecycleGeneration,
onLifecycleGeneration: (generation) => {
params.state.lifecycleGeneration = generation;
},
allowTransientCooldownProbe: runOptions?.allowTransientCooldownProbe,
suppressAssistantErrorPersistenceForCandidate: assistantErrorPersistedAcrossFallback,
onAssistantErrorMessagePersisted: () => {
assistantErrorPersistedAcrossFallback = true;
},
notifyUserAboutCompaction: params.notifyUserAboutCompaction,
messageToolDeliveryState,
onCompactionFacts: ({ accounting, postCompactionModelAttempted }) => {
if (accounting) recordTurnCompaction(params.state.compaction, accounting);
params.state.postCompactionModelAttempted ||= postCompactionModelAttempted;
}
});
params.state.bootstrapPromptWarningSignaturesSeen = candidate.bootstrapPromptWarningSignaturesSeen;
return candidate.result;
}
}));
}
//#endregion
//#region src/auto-reply/reply/pending-tool-task-drain.ts
/** Waits for asynchronous tool tasks before final reply delivery. */
const DEFAULT_PENDING_TOOL_DRAIN_IDLE_TIMEOUT_MS = 3e4;
function createIdleTimeoutPromise(timeoutMs) {
let timeoutId;
return {
promise: new Promise((resolve) => {
timeoutId = setTimeout(() => resolve("timeout"), timeoutMs);
timeoutId.unref?.();
}),
clear: () => {
if (timeoutId) clearTimeout(timeoutId);
}
};
}
/** Waits for pending tool tasks to settle or times out to avoid session deadlock. */
async function drainPendingToolTasks({ tasks, idleTimeoutMs = DEFAULT_PENDING_TOOL_DRAIN_IDLE_TIMEOUT_MS, onTimeout }) {
if (tasks.size === 0) return { kind: "settled" };
if (idleTimeoutMs <= 0) return {
kind: "timeout",
remaining: tasks.size
};
while (tasks.size > 0) {
const snapshot = [...tasks];
const timeout = createIdleTimeoutPromise(idleTimeoutMs);
const outcome = await Promise.race([timeout.promise, ...snapshot.map((task) => task.then(() => ({
kind: "settled",
task
}), () => ({
kind: "settled",
task
})))]);
timeout.clear();
if (outcome === "timeout") {
const remaining = tasks.size;
onTimeout?.(`pending tool tasks made no progress within ${idleTimeoutMs}ms; proceeding with ${remaining} task(s) still pending to avoid session deadlock`);
return {
kind: "timeout",
remaining
};
}
tasks.delete(outcome.task);
}
return { kind: "settled" };
}
//#endregion
//#region src/auto-reply/reply/private-message-tool-final.ts
/** Detects and logs long private finals when message-tool-only delivery was expected. */
const privateFinalReplyLogger = createSubsystemLogger("source-reply/private-final");
const LONG_PRIVATE_FINAL_MIN_CHARS = 280;
const MULTI_SENTENCE_PRIVATE_FINAL_MIN_CHARS = 120;
const MULTI_SENTENCE_TERMINATOR_MIN_COUNT = 2;
const SENTENCE_TERMINATOR_REGEX = /[.!?]+(?:\s|$)|[。!?.。]+/gu;
/** Returns whether a private final can represent an expected source reply that was not delivered. */
function shouldClassifyPrivateMessageToolFinal(params) {
return !(params.isHeartbeat || params.isRoomEvent || params.sourceReplyDeliveryMode !== "message_tool_only" || params.sendPolicyDenied || params.successfulSourceReplyDelivery);
}
/** Classifies private final text after message-tool-only source delivery settles. */
function classifyPrivateMessageToolFinal(params) {
if (!shouldClassifyPrivateMessageToolFinal(params)) return "none";
const trimmed = params.finalText.trim();
if (!trimmed || isSilentReplyText(trimmed)) return "none";
const estimatedChars = estimateStringChars(trimmed);
return estimatedChars >= LONG_PRIVATE_FINAL_MIN_CHARS || estimatedChars >= MULTI_SENTENCE_PRIVATE_FINAL_MIN_CHARS && countSentenceLikeTerminators(trimmed) >= MULTI_SENTENCE_TERMINATOR_MIN_COUNT ? "substantive" : "short";
}
/**
* Emit metadata-only operator signal. The body is intentionally omitted:
* `message_tool_only` keeps normal final text private by design.
*/
function warnPrivateMessageToolFinal(params) {
privateFinalReplyLogger.warn("agent produced a long private final reply without calling the configured delivery tool (message_tool_only); response kept private and not delivered to the source channel", {
sessionKey: params.sessionKey,
channel: params.channel,
chars: params.finalTextLength
});
}
function countSentenceLikeTerminators(text) {
return Array.from(text.matchAll(SENTENCE_TERMINATOR_REGEX)).length;
}
//#endregion
//#region src/auto-reply/reply/agent-runner-fallback-settlement.ts
/** Settles abort, lifecycle, and terminal failure state after fallback execution. */
async function settleAgentFallbackCycle(params) {
const { cycle, fallbackResult } = params;
const turn = cycle.turn;
const runResult = fallbackResult.result;
const fallbackProvider = fallbackResult.provider;
const fallbackModel = fallbackResult.model;
const fallbackExhausted = fallbackResult.outcome === "exhausted";
const terminalMetadata = fallbackResult.terminal.metadata;
const terminalOutcome = fallbackResult.terminal.outcome;
const settledLifecycleTerminal = cycle.state.pendingLifecycleTerminal?.provider === fallbackProvider && cycle.state.pendingLifecycleTerminal.model === fallbackModel ? cycle.state.pendingLifecycleTerminal.backstop : void 0;
cycle.state.pendingLifecycleTerminal = void 0;
if (turn.isRestartRecoveryArmed?.()) turn.replyOperation?.abortForRestart();
const abortReason = resolveReplyOperationAbortReason(turn.replyOperation);
if (abortReason) {
settledLifecycleTerminal?.emit("end", runResult, terminalMetadata);
await drainPendingToolTasks({
tasks: turn.pendingToolTasks,
onTimeout: logVerbose
});
return {
kind: "aborted",
reason: abortReason
};
}
cycle.commitTerminalOutcome();
const fallbackAttempts = Array.isArray(fallbackResult.attempts) ? fallbackResult.attempts.map((attempt) => ({
provider: attempt.provider,
model: attempt.model,
error: attempt.error,
reason: attempt.reason ?? "unknown",
status: typeof attempt.status === "number" ? attempt.status : void 0,
code: attempt.code || void 0
})) : [];
if (!fallbackExhausted) await fallbackResult.settleSessionOverride();
const embeddedError = runResult.meta?.error;
const deferredLifecycleError = settledLifecycleTerminal?.getDeferredError();
const userFacingErrorPayload = runResult.payloads?.find((payload) => payload.isError === true && typeof payload.text === "string")?.text;
const terminalErrorMessage = deferredLifecycleError ?? (terminalOutcome.status === "timeout" ? terminalOutcome.error : userFacingErrorPayload) ?? (embeddedError ? "Agent run failed" : void 0);
const emitSettledLifecycleError = (error, extraData) => {
if (settledLifecycleTerminal) {
settledLifecycleTerminal.emit("error", error, extraData);
return;
}
emitAgentEvent({
runId: cycle.runId,
lifecycleGeneration: cycle.state.lifecycleGeneration,
...turn.sessionKey ? { sessionKey: turn.sessionKey } : {},
stream: "lifecycle",
data: {
phase: "error",
error: error.message,
endedAt: Date.now(),
...extraData,
executionSettled: true
}
});
};
if (embeddedError && isContextOverflowError(embeddedError.message)) {
emitSettledLifecycleError(new Error(terminalErrorMessage ?? "Agent run failed"));
defaultRuntime.error(`Auto-compaction failed (${embeddedError.message}). Preserving existing session mapping for ${turn.sessionKey ?? turn.followupRun.run.sessionId}.`);
turn.replyOperation?.fail("run_failed", embeddedError);
return {
kind: "final",
payload: markAgentRunFailureReplyPayload({ text: buildContextOverflowRecoveryText({
preserveSessionMapping: true,
cfg: cycle.runtimeConfig,
agentId: turn.followupRun.run.agentId,
primaryProvider: turn.followupRun.run.provider,
primaryModel: turn.followupRun.run.model,
runtimeProvider: cycle.state.attemptedRuntimeProvider,
runtimeModel: cycle.state.attemptedRuntimeModel,
activeSessionEntry: turn.getActiveSessionEntry()
}) }),
postCompactionModelFailure: cycle.state.postCompactionModelAttempted || void 0
};
}
if (embeddedError?.kind === "role_ordering") {
emitSettledLifecycleError(new Error(terminalErrorMessage ?? "Agent run failed"));
turn.replyOperation?.fail("run_failed", embeddedError);
const embeddedErrorText = formatErrorMessage(embeddedError);
return {
kind: "final",
payload: markAgentRunFailureReplyPayload({ text: cycle.shouldSurfaceToControlUi ? renderControlUiAgentFailureCopy(embeddedErrorText) : PROVIDER_CONVERSATION_STATE_ERROR_USER_MESSAGE }),
postCompactionModelFailure: cycle.state.postCompactionModelAttempted || void 0
};
}
const sourceReplyPolicy = turn.sessionKey ? resolveSourceReplyPolicy({
cfg: cycle.runtimeConfig,
sessionCtx: turn.sessionCtx,
sessionEntry: turn.getActiveSessionEntry(),
sessionKey: turn.sessionKey,
runtimePolicySessionKey: turn.runtimePolicySessionKey,
opts: turn.opts
}) : void 0;
const finalText = runResult.meta?.finalAssistantVisibleText?.trim() ?? "";
const successfulSourceReplyDelivery = hasCompletedSourceReplyDeliveryEvidence(runResult);
const privateFinalTerminalReply = !(runResult.meta?.yielded === true || (runResult.meta?.pendingToolCalls?.length ?? 0) > 0) && classifyPrivateMessageToolFinal({
sourceReplyDeliveryMode: sourceReplyPolicy?.sourceReplyDeliveryMode,
sendPolicyDenied: sourceReplyPolicy?.sendPolicyDenied === true,
successfulSourceReplyDelivery,
isHeartbeat: turn.isHeartbeat,
isRoomEvent: turn.sessionCtx.InboundEventKind === "room_event",
finalText
}) === "short" ? {
disposition: "empty",
code: "message-tool-not-called"
} : void 0;
let terminalRunFailed = false;
if (fallbackExhausted) {
const exhaustionError = new Error(terminalErrorMessage ?? "All model fallback candidates failed");
terminalRunFailed = true;
if (cycle.modelPatch.captureFallbackFailure(fallbackAttempts) === void 0) cycle.modelPatch.captureFailure(embeddedError ?? exhaustionError);
emitSettledLifecycleError(exhaustionError, terminalMetadata);
turn.replyOperation?.retainFailureUntilComplete();
turn.replyOperation?.fail("run_failed", exhaustionError);
} else if (deferredLifecycleError || embeddedError || terminalOutcome.status === "timeout") {
const terminalError = new Error(terminalErrorMessage ?? "Agent run failed");
terminalRunFailed = true;
cycle.modelPatch.captureFailure(embeddedError ?? terminalError);
emitSettledLifecycleError(terminalError, terminalMetadata);
turn.replyOperation?.retainFailureUntilComplete();
turn.replyOperation?.fail("run_failed", terminalError);
} else settledLifecycleTerminal?.emit("end", runResult, privateFinalTerminalReply ? {
...terminalMetadata,
terminalReply: privateFinalTerminalReply
} : terminalMetadata);
return {
kind: "completed",
runResult,
fallbackProvider,
fallbackModel,
fallbackExhausted,
fallbackAttempts,
terminalRunFailed
};
}
//#endregion
//#region src/auto-reply/reply/agent-runner-fallback-cycle.ts
/** Runs one fallback chain, then settles its terminal lifecycle state. */
async function executeAgentFallbackCycle(params) {
const fallbackResult = await runAgentFallbackCandidates(params);
params.timing.logIfSlow({
runId: params.runId,
sessionId: params.turn.followupRun.run.sessionId,
sessionKey: params.turn.sessionKey,
outcome: "completed"
});
return settleAgentFallbackCycle({
cycle: params,
fallbackResult
});
}
//#endregion
//#region src/auto-reply/reply/agent-runner-presentation.ts
/** Builds the channel-presentation callbacks shared by CLI and embedded runs. */
function createAgentTurnPresentation(params) {
const classifyStreamingPartial = (payload) => {
let text = payload.text;
const reply = resolveSendableOutboundReplyParts(payload, { text: "" });
if (params.turn.followupRun.run.silentExpected) return { skip: true };
if (!params.turn.isHeartbeat && text?.includes("HEARTBEAT_OK")) {
const stripped = stripHeartbeatToken(text, { mode: "message" });
if (stripped.didStrip && !params.heartbeatState.didLogStrip) {
params.heartbeatState.didLogStrip = true;
logVerbose("Stripped stray HEARTBEAT_OK token from reply");
}
if (stripped.shouldSkip && !reply.hasMedia) return { skip: true };
text = stripped.text;
}
if (isSilentReplyText(text, "NO_REPLY")) return { skip: true };
if (isSilentReplyPrefixText(text, "NO_REPLY") || isSilentReplyPrefixText(text, "HEARTBEAT_OK")) return { skip: true };
if (text && startsWithSilentToken(text, "NO_REPLY")) text = stripLeadingSilentToken(text, SILENT_REPLY_TOKEN);
if (!text) return reply.hasMedia ? {
text: void 0,
skip: false
} : { skip: true };
return {
text,
skip: false
};
};
const sanitizeStreamingText = (text, errorContext) => {
if (!text) return { skip: true };
const conversationContext = params.turn.sessionCtx.agentText ?? params.turn.sessionCtx.BodyForAgent;
const sanitized = errorContext ? renderUserFacingText(text, {
errorContext: true,
conversationContext,
streaming: true
}) : sanitizeUserFacingText(text, {
conversationContext,
streaming: true
});
return sanitized.trim() ? {
text: sanitized,
skip: false
} : { skip: true };
};
const normalizeStreamingText = (payload) => {
const classified = classifyStreamingPartial(payload);
if (classified.skip || !classified.text) return classified;
return sanitizeStreamingText(classified.text, Boolean(payload.isError));
};
const preserveProgressCallbackStartOrder = params.turn.opts?.preserveProgressCallbackStartOrder === true;
const presentWithTyping = async (typingPromise, startPresentation) => {
if (!preserveProgressCallbackStartOrder) {
await typingPromise;
return await startPresentation();
}
let presentationPromise;
try {
presentationPromise = startPresentation();
} catch (err) {
typingPromise.catch(() => void 0);
throw err;
}
const [, result] = await Promise.all([typingPromise, presentationPromise]);
return result;
};
const blockReplyPipeline = params.turn.blockReplyPipeline;
return {
classifyStreamingPartial,
sanitizeStreamingText,
normalizeStreamingText,
presentWithTyping,
blockReplyHandler: params.turn.opts?.onBlockReply ? createBlockReplyDeliveryHandler({
onBlockReply: params.turn.opts.onBlockReply,
currentMessageId: params.turn.sessionCtx.MessageSidFull ?? params.turn.sessionCtx.MessageSid,
replyThreading: params.turn.replyThreading,
normalizeStreamingText,
applyReplyToMode: params.turn.applyReplyToMode,
normalizeMediaPaths: params.replyMediaContext.normalizePayload,
typingSignals: params.turn.typingSignals,
reasoningPayloadsEnabled: params.turn.opts?.reasoningPayloadsEnabled,
commentaryPayloadsEnabled: params.turn.opts?.commentaryPayloadsEnabled,
blockStreamingEnabled: params.turn.blockStreamingEnabled,
blockReplyPipeline,
directlySentBlockKeys: params.directlySentBlockKeys,
directlySentBlockPayloads: params.directlySentBlockPayloads
}) : void 0
};
}
//#endregion
//#region src/auto-reply/reply/agent-runner-turn-timing.ts
const agentTurnTimingLog = createSubsystemLogger("auto-reply/agent-turn-timing");
function createAgentTurnTimingTracker(options = {}) {
const observedExecutionPhases = /* @__PURE__ */ new Set();
const timing = createReplyTimingTracker({
log: { warn(message, details) {
if ((details?.milestone === "assistant_output_started" || details?.milestone === "tool_execution_started") && !options.profilerEnabled) agentTurnTimingLog.info(message, details);
else agentTurnTimingLog.warn(message, details);
} },
enabled: options.profilerEnabled === true,
formatMessage: (params, summary, stages) => {
const identity = `runId=${params.runId} sessionId=${params.sessionId ?? "unknown"} sessionKey=${params.sessionKey ?? "unknown"}`;
return "milestone" in params ? `agent turn milestone ${identity} milestone=${params.milestone} totalMs=${summary.totalMs} stages=${stages}` : `agent turn timings ${identity} outcome=${params.outcome} totalMs=${summary.totalMs} stages=${stages}${params.error ? ` error="${params.error}"` : ""}`;
},
detailKeys: (params) => "milestone" in params ? [
"runId",
"sessionId",
"sessionKey",
"milestone"
] : [
"runId",
"sessionId",
"sessionKey",
"outcome",
"error"
]
});
return {
measure: timing.measure,
measureSync: timing.measureSync,
logIfSlow(params) {
if (!options.profilerEnabled) return;
const { runId, sessionId, sessionKey, outcome, error } = params;
timing.logIfSlow({
runId,
sessionId,
sessionKey,
outcome,
error
});
},
logMilestoneIfSlow(params) {
const { runId, sessionId, sessionKey, milestone } = params;
timing.logIfSlow({
runId,
sessionId,
sessionKey,
milestone
}, { repeat: true });
},
logExecutionPhaseIfSlow(params) {
if (observedExecutionPhases.has(params.phase)) return;
observedExecutionPhases.add(params.phase);
const { runId, sessionId, sessionKey, phase: milestone } = params;
timing.logIfSlow({
runId,
sessionId,
sessionKey,
milestone
}, { repeat: true });
}
};
}
//#endregion
//#region src/auto-reply/reply/agent-runner-execution.ts
/** Agent-runner execution loop, fallback handling, and user-facing failure mapping. */
function resolveRunStartupPhase(phase) {
switch (phase) {
case "runner_entered":
case "workspace":
case "runtime_plugins": return "preparing_workspace";
case "before_agent_reply":
case "model_resolution":
case "auth":
case "context_engine":
case "attempt_dispatch":
case "context_assembled": return "preparing_context";
case "turn_accepted":
case "process_spawned":
case "model_call_started": return "starting_model";
case "tool_execution_started":
case "assistant_output_started": return;
}
}
async function executeAgentTurnInternalLoop(params, commitTerminalOutcome, commitMcpAppModelContext, preparedRunAdmission, admittedRunContext, deferredLifecycle, compaction) {
const heartbeatState = { didLogStrip: false };
const directlySentBlockKeys = /* @__PURE__ */ new Set();
const directlySentBlockPayloads = [];
const runnableRun = resolveRunAfterAutoFallbackPrimaryProbeRecheck({
run: params.followupRun.run,
entry: params.activeSessionStore?.[params.sessionKey ?? ""] ?? params.getActiveSessionEntry(),
sessionKey: params.sessionKey
});
if (runnableRun !== params.followupRun.run) params.followupRun.run = runnableRun;
const runtimeConfig = resolveQueuedReplyRuntimeConfig(runnableRun.config);
const effectiveRun = runtimeConfig === runnableRun.config ? runnableRun : {
...runnableRun,
config: runtimeConfig
};
let liveModelSwitchRuntimeEntry;
const applyLiveModelSwitchToRun = (run, err) => {
run.provider = err.provider;
run.model = err.model;
run.authProfileId = err.authProfileId;
run.authProfileIdSource = err.authProfileId ? err.authProfileIdSource : void 0;
run.autoFallbackPrimaryProbe = void 0;
liveModelSwitchRuntimeEntry = { agentRuntimeOverride: err.agentRuntimeOverride };
};
const runId = params.opts?.runId ?? crypto.randomUUID();
const agentTurnTiming = createAgentTurnTimingTracker({ profilerEnabled: isReplyProfilerEnabled({ config: runtimeConfig }) });
const shouldSurfaceToControlUi = isInternalMessageChannel(params.followupRun.run.messageProvider ?? params.sessionCtx.Surface ?? params.sessionCtx.Provider);
let lifecycleGeneration = captureAgentRunLifecycleGeneration(runId);
if (params.sessionKey) registerAgentRunContext(runId, {
sessionKey: params.sessionKey,
...params.followupRun.run.sessionId ? { sessionId: params.followupRun.run.sessionId } : {},
agentId: params.followupRun.run.agentId,
lifecycleGeneration,
verboseLevel: params.resolvedVerboseLevel,
isHeartbeat: params.isHeartbeat,
isControlUiVisible: shouldSurfaceToControlUi
});
if (isDiagnosticsEnabled(runtimeConfig)) logSessionTurnCreated({
runId,
sessionKey: params.sessionKey,
sessionId: params.followupRun.run.sessionId,
agentId: params.followupRun.run.agentId,
channel: params.followupRun.run.messageProvider ?? params.sessionCtx.Surface ?? params.sessionCtx.Provider,
trigger: params.isHeartbeat ? "heartbeat" : "user"
});
let replyMediaContext;
let currentTurnImages;
try {
replyMediaContext = params.replyMediaContext ?? agentTurnTiming.measureSync("reply_media_context", () => createReplyMediaContext({
cfg: runtimeConfig,
agentId: params.followupRun.run.agentId,
sessionKey: params.sessionKey,
workspaceDir: params.followupRun.run.workspaceDir,
messageProvider: params.followupRun.run.messageProvider,
accountId: params.followupRun.originatingAccountId ?? params.followupRun.run.agentAccountId,
groupId: params.followupRun.run.groupId,
groupChannel: params.followupRun.run.groupChannel,
groupSpace: params.followupRun.run.groupSpace,
requesterSenderId: params.followupRun.run.senderId,
requesterSenderName: params.followupRun.run.senderName,
requesterSenderUsername: params.followupRun.run.senderUsername,
requesterSenderE164: params.followupRun.run.senderE164
}));
const internalFollowupRun = params.followupRun;
currentTurnImages = internalFollowupRun.currentTurnImagesPrepared === true || Object.hasOwn(params.followupRun, "images") || Object.hasOwn(params.followupRun, "imageOrder") ? {
images: params.followupRun.images,
imageOrder: params.followupRun.imageOrder,
mediaImageLayout: internalFollowupRun.mediaImageLayout
} : await agentTurnTiming.measure("current_turn_images", () => resolveCurrentTurnImages({
ctx: params.sessionCtx,
cfg: runtimeConfig,
images: params.opts?.images,
imageOrder: params.opts?.imageOrder
}));
} catch (error) {
clearAgentRunContext(runId, lifecycleGeneration);
throw error;
}
let didNotifyAgentRunStart = false;
let lastRunStartupPhase;
const notifyAgentRunStart = () => {
if (didNotifyAgentRunStart) return;
didNotifyAgentRunStart = true;
if (params.replyOperation) markReplyOperationExecutionStarted(params.replyOperation);
params.opts?.onAgentRunStart?.(runId, admittedRunContext.current?.executionIdentityToken);
};
const signalExecutionPhaseForTyping = (info) => {
agentTurnTiming.logExecutionPhaseIfSlow({
runId,
sessionId: params.followupRun.run.sessionId,
sessionKey: params.sessionKey,
phase: info.phase
});
const startupPhase = resolveRunStartupPhase(info.phase);
if (startupPhase && startupPhase !== lastRunStartupPhase) {
lastRunStartupPhase = startupPhase;
emitAgentRunStatusEvent({
runId,
phase: startupPhase
});
}
if (info.phase === "model_call_started" || info.phase === "process_spawned") commitMcpAppModelContext();
if (!(info.phase === "turn_accepted" || info.phase === "process_spawned" || info.phase === "model_call_started" || info.phase === "tool_execution_started" || info.phase === "assistant_output_started")) return;
notifyAgentRunStart();
(params.typingSignals.signalExecutionActivity?.() ?? params.typingSignals.signalRunStart()).catch((err) => {
logVerbose(`execution phase typing signal failed: ${String(err)}`);
});
};
const notifyUserAboutCompaction = shouldNotifyUserAboutCompaction(runtimeConfig);
let runResult;
let fallbackProvider = params.followupRun.run.provider;
let fallbackModel = params.followupRun.run.model;
let fallbackAttempts = [];
let fallbackExhausted = false;
let terminalRunFailed = false;
const modelPatch = createAgentPatchedSessionModelRunGuard({
cfg: runtimeConfig,
agentId: params.followupRun.run.agentId,
sessionKey: params.sessionKey,
storePath: params.storePath,
onError: (error) => logVerbose(`agent model patch reconciliation failed: ${formatErrorMessage(error)}`)
});
let liveModelSwitchRetries = 0;
const fallbackCycleState = {
deferredLifecycle,
lifecycleGeneration,
turnStartedAtMs: Date.now(),
compaction,
postCompactionModelAttempted: false,
attemptedRuntimeProvider: fallbackProvider,
attemptedRuntimeModel: fallbackModel,
bootstrapPromptWarningSignaturesSeen: resolveBootstrapWarningSignaturesSeen(params.getActiveSessionEntry()?.systemPromptReport)
};
const clearRecoveredAutoFallbackPrimaryProbe = async (paramsForClear) => clearRecoveredAutoFallbackPrimaryProbeSelection({
run: effectiveRun,
...paramsForClear,
sessionKey: params.sessionKey,
activeSessionStore: params.activeSessionStore,
getActiveSessionEntry: params.getActiveSessionEntry,
storePath: params.storePath
});
while (true) try {
const presentation = createAgentTurnPresentation({
turn: params,
replyMediaContext,
directlySentBlockKeys,
directlySentBlockPayloads,
heartbeatState
});
const cycle = await executeAgentFallbackCycle({
preparedRunAdmission,
turn: params,
effectiveRun,
runtimeConfig,
liveModelSwitchRuntimeEntry,
runId,
runAbortSignal: fallbackCycleState.deferredLifecycle.signal,
currentTurnImages,
state: fallbackCycleState,
presentation,
directlySentBlockKeys,
notifyAgentRunStart,
signalExecutionPhaseForTyping,
notifyUserAboutCompaction,
timing: agentTurnTiming,
modelPatch,
shouldSurfaceToControlUi,
commitTerminalOutcome,
clearRecoveredAutoFallbackPrimaryProbe
});
lifecycleGeneration = fallbackCycleState.lifecycleGeneration;
if (cycle.kind === "aborted") return cycle;
if (cycle.kind === "final") return {
...cycle,
resolved: {
provider: fallbackCycleState.attemptedRuntimeProvider,
model: fallbackCycleState.attemptedRuntimeModel
}
};
runResult = cycle.runResult;
fallbackProvider = cycle.fallbackProvider;
fallbackModel = cycle.fallbackModel;
fallbackExhausted = cycle.fallbackExhausted;
fallbackAttempts = cycle.fallbackAttempts;
terminalRunFailed = cycle.terminalRunFailed;
break;
} catch (err) {
if (err instanceof LiveSessionModelSwitchError) liveModelSwitchRetries += 1;
const action = await handleAgentExecutionError({
turn: params,
error: err,
runtimeConfig,
runId,
state: fallbackCycleState,
liveModelSwitchRetries,
shouldSurfaceToControlUi,
timing: agentTurnTiming,
modelPatch
});
if (action.kind === "aborted") return action;
if (action.kind === "final") return {
...action,
resolved: {
provider: fallbackCycleState.attemptedRuntimeProvider,
model: fallbackCycleState.attemptedRuntimeModel
}
};
if (action.liveModelSwitchError) {
const switchError = action.liveModelSwitchError;
applyLiveModelSwitchToRun(params.followupRun.run, switchError);
if (runnableRun !== params.followupRun.run) applyLiveModelSwitchToRun(runnableRun, switchError);
if (effectiveRun !== runnableRun && effectiveRun !== params.followupRun.run) applyLiveModelSwitchToRun(effectiveRun, switchError);
}
continue;
}
const finalEmbeddedError = runResult?.meta?.error;
const hasPayloadText = runResult?.payloads?.some((p) => normalizeOptionalString(p.text));
if (finalEmbeddedError && !hasPayloadText) {
const errorMsg = finalEmbeddedError.message ?? "";
if (isContextOverflowError(errorMsg)) {
params.replyOperation?.fail("run_failed", finalEmbeddedError);
return {
kind: "final",
resolved: {
provider: fallbackProvider,
model: fallbackModel
},
payload: markAgentRunFailureReplyPayload({ text: "⚠️ Context overflow — this conversation is too large for the model. Use /new to start a fresh session." }),
postCompactionModelFailure: fallbackCycleState.postCompactionModelAttempted || void 0
};
}
}
if (runResult) {
if (!runResult.payloads?.some((p) => !p.isError && !p.isReasoning && hasOutboundReplyContent(p, { trimText: true }))) {
const metaErrorMsg = finalEmbeddedError?.message ?? "";
const rawErrorPayloadText = runResult.payloads?.find((p) => p.isError && hasNonEmptyString(p.text) && !p.text.startsWith("⚠️"))?.text ?? "";
const errorCandidate = metaErrorMsg || rawErrorPayloadText;
const candidateReason = errorCandidate ? classifyFailoverReason(errorCandidate) : null;
const formattedErrorCandidate = candidateReason === "rate_limit" || candidateReason === "overloaded" ? renderRateLimitOrOverloadedCopy({
reason: candidateReason,
raw: errorCandidate
}) : void 0;
if (formattedErrorCandidate) runResult.payloads = [markAgentRunFailureReplyPayload({
text: resolveExternalRunFailureTextForConversation({
text: formattedErrorCandidate,
sessionCtx: params.sessionCtx,
isGenericRunnerFailure: false,
cfg: params.followupRun.run.config
}),
isError: true
})];
}
}
const patchedModelNeedsRevert = terminalRunFailed ? false : modelPatch.captureFallbackFailure(fallbackAttempts) ?? false;
await modelPatch.finish(!terminalRunFailed && !patchedModelNeedsRevert);
const terminalFailurePayload = terminalRunFailed ? buildTerminalAgentRunFailureReplyPayload({
isHeartbeat: params.isHeartbeat,
visibleReplyDelivered: await params.resolveVisibleReplyDelivery?.() === true,
sessionCtx: params.sessionCtx,
cfg: params.followupRun.run.config
}) : void 0;
return {
kind: "completed",
result: runResult,
fallbackProvider,
fallbackModel,
...fallbackExhausted ? { fallbackExhausted: true } : {},
fallbackAttempts,
didLogHeartbeatStrip: heartbeatState.didLogStrip,
autoCompactionCount: compaction.count,
directlySentBlockKeys: directlySentBlockKeys.size > 0 ? directlySentBlockKeys : void 0,
directlySentBlockPayloads: directlySentBlockPayloads.filter((payload) => payload !== void 0),
...terminalFailurePayload ? { terminalFailurePayload } : {},
...terminalRunFailed && fallbackCycleState.postCompactionModelAttempted ? { postCompactionModelFailure: true } : {}
};
}
async function executeAgentTurnInternal(params, commitTerminalOutcome, commitMcpAppModelContext, compaction) {
const runId = params.opts?.runId ?? crypto.randomUUID();
const admittedRunContext = {};
const gatewayContextResolver = readChannelContextGatewayContextResolver(params.sessionCtx) ?? getPluginRuntimeGatewayRequestScope()?.resolveGatewayContext;
const preparedRunAdmission = prepareChannelRunAdmission({
cfg: resolveQueuedReplyRuntimeConfig(params.followupRun.run.config),
runId,
agentId: params.followupRun.run.agentId,
ingressKind: "channel",
boundary: "auto-reply.agent-runner",
evidence: params.followupRun.channelAdmissionEvidence,
onAdmitted: (context) => {
bindGatewayContextResolver(context, gatewayContextResolver);
admittedRunContext.current = context;
params.followupRun.run.skillLibraryAuthoring?.bind(context);
}
});
const deferredLifecycle = createDeferredEmbeddedRunLifecycleManager({
runId,
agentId: params.followupRun.run.agentId,
sessionId: params.followupRun.run.sessionId,
sessionKey: params.sessionKey,
sessionFile: params.followupRun.run.sessionFile,
abortSignal: params.replyOperation?.abortSignal ?? params.opts?.abortSignal
});
try {
return await executeAgentTurnInternalLoop(params, commitTerminalOutcome, commitMcpAppModelContext, preparedRunAdmission, admittedRunContext, deferredLifecycle, compaction);
} finally {
await deferredLifecycle.complete();
preparedRunAdmission.close();
}
}
/** Runs the agent turn with provider/model fallback, retry, and closed settlement. */
async function executeAgentTurnOutcome(params) {
const runId = params.opts?.runId ?? crypto.randomUUID();
const executionParams = params.opts?.runId === runId ? params : {
...params,
opts: {
...params.opts,
runId
}
};
const runtime = executionParams.isHeartbeat ? void 0 : peekSessionMcpRuntime({
sessionId: executionParams.followupRun.run.sessionId,
sessionKey: executionParams.sessionKey ?? executionParams.followupRun.run.sessionKey
});
const modelContextLease = runtime ? leaseMcpAppModelContextForTurn({
runtime,
prompt: executionParams.commandBody,
transcriptPrompt: executionParams.transcriptCommandBody
}) : void 0;
const turnParams = modelContextLease ? {
...executionParams,
commandBody: modelContextLease.prompt,
transcriptCommandBody: modelContextLease.transcriptPrompt
} : executionParams;
const compaction = {
count: 0,
durable: []
};
const completedCompaction = () => compaction.count > 0 ? { compaction: {
count: compaction.count,
durable: [...compaction.durable]
} } : {};
let terminalOutcomeCommitted = false;
const commitTerminalOutcome = () => {
if (terminalOutcomeCommitted) return;
terminalOutcomeCommitted = true;
executionParams.replyOperation?.freezeAbort();
};
const lifecycleGeneration = captureAgentRunLifecycleGeneration(runId);
try {
const internal = await withAgentRunLifecycleGeneration(lifecycleGeneration, async () => {
try {
return await executeAgentTurnInternal(turnParams, commitTerminalOutcome, modelContextLease?.commit ?? (() => void 0), compaction);
} finally {
modelContextLease?.rollback();
commitTerminalOutcome();
}
});
if (internal.kind === "aborted") return {
runId,
outcome: {
...internal,
...completedCompaction()
}
};
const abortReason = resolveReplyOperationAbortReason(executionParams.replyOperation);
if (abortReason) return {
runId,
outcome: {
kind: "aborted",
reason: abortReason,
...completedCompaction()
}
};
if (internal.kind === "final") return {
runId,
outcome: {
kind: "rejected",
payload: internal.payload,
resolved: internal.resolved,
...internal.postCompactionModelFailure ? { postCompactionModelFailure: internal.postCompactionModelFailure } : {},
...completedCompaction()
}
};
const provider = internal.fallbackProvider ?? internal.result.meta?.agentMeta?.provider ?? executionParams.followupRun.run.provider;
const model = internal.fallbackModel ?? internal.result.meta?.agentMeta?.model ?? executionParams.followupRun.run.model;
return {
runId,
outcome: {
kind: "settled",
...internal.terminalFailurePayload ? {
status: "failed",
terminalFailurePayload: internal.terminalFailurePayload,
...internal.postCompactionModelFailure ? { postCompactionModelFailure: internal.postCompactionModelFailure } : {}
} : { status: "ok" },
result: internal.result,
resolved: {
provider,
model
},
fallback: {
exhausted: internal.fallbackExhausted === true,
attempts: internal.fallbackAttempts
},
autoCompactionCount: internal.autoCompactionCount,
...completedCompaction(),
didLogHeartbeatStrip: internal.didLogHeartbeatStrip,
directlySentBlockKeys: internal.directlySentBlockKeys,
directlySentBlockPayloads: internal.directlySentBlockPayloads
}
};
} catch (error) {
const abortReason = resolveReplyOperationAbortReason(executionParams.replyOperation, error);
if (abortReason) return {
runId,
outcome: {
kind: "aborted",
reason: abortReason,
...completedCompaction()
}
};
throw error;
}
}
/** Runs the agent turn and records its execution and message-tool delivery outcomes. */
async function executeAgentTurn(params) {
params.opts?.onRunVerbosityResolved?.({
verboseLevelOverride: params.followupRun.run.verboseLevelOverride,
resolvedVerboseLevel: params.resolvedVerboseLevel
});
if (params.replyOperation) retainReplyOperationUntilComplete(params.replyOperation);
const runId = params.opts?.runId ?? crypto.randomUUID();
const executionParams = params.opts?.runId === runId ? params : {
...params,
opts: {
...params.opts,
runId
}
};
try {
const result = await executeAgentTurnOutcome(executionParams);
recordAgentTurnExecutionOutcome(executionParams, result);
return result;
} catch (error) {
recordAgentTurnExecutionOutcome(executionParams, void 0);
throw error;
}
}
//#endregion
//#region src/auto-reply/reply/agent-runner-trace.ts
function formatRawTraceBlock(title, value) {
return `🔎 ${title}:\n~~~text\n${value?.trim() ? escapeTraceFence(value) : "<empty>"}\n~~~`;
}
function escapeTraceFence(value) {
return value.replace(/^~~~/gm, "\\~~~");
}
function hasTraceUsageFields(usage) {
if (!usage) return false;
return [
"input",
"output",
"cacheRead",
"cacheWrite",
"total"
].some((key) => {
const value = usage[key];
return typeof value === "number" && Number.isFinite(value);
});
}
function formatTraceUsageLine(label, value) {
return `${label}=${typeof value === "number" && Number.isFinite(value) ? `${value.toLocaleString()} tok (${formatTokenCount(value)})` : "n/a"}`;
}
function formatUsageTraceBlock(title, usage) {
if (!hasTraceUsageFields(usage)) return;
return `🔎 ${title}:\n~~~text\n${[
formatTraceUsageLine("input", usage?.input),
formatTraceUsageLine("output", usage?.output),
formatTraceUsageLine("cacheRead", usage?.cacheRead),
formatTraceUsageLine("cacheWrite", usage?.cacheWrite),
formatTraceUsageLine("total", usage?.total)
].join("\n")}\n~~~`;
}
function formatTraceScalar(value) {
if (typeof value === "boolean") return value ? "yes" : "no";
if (typeof value === "number") return Number.isFinite(value) ? value.toLocaleString() : void 0;
return normalizeOptionalString(value) ?? void 0;
}
function formatKeyValueTraceBlock(title, fields) {
const lines = fields.flatMap(([key, rawValue]) => {
const value = formatTraceScalar(rawValue);
return value ? [`${key}=${value}`] : [];
});
if (lines.length === 0) return;
return `🔎 ${title}:\n~~~text\n${lines.join("\n")}\n~~~`;
}
function formatExecutionResultTraceBlock(executionTrace) {
if (!executionTrace?.winnerProvider && !executionTrace?.winnerModel) return;
return formatKeyValueTraceBlock("Execution Result", [
["winner", executionTrace.winnerProvider && executionTrace.winnerModel ? `${executionTrace.winnerProvider}/${executionTrace.winnerModel}` : void 0],
["fallbackUsed", executionTrace.fallbackUsed],
["attempts", executionTrace.attempts?.length],
["runner", executionTrace.runner]
]);
}
function formatFallbackChainTraceBlock(executionTrace) {
const attempts = executionTrace?.attempts ?? [];
if (attempts.length <= 1) return;
return `🔎 Fallback Chain:\n~~~text\n${attempts.map((attempt, index) => [
`${index + 1}. ${attempt.provider}/${attempt.model}`,
` result=${attempt.result}`,
...attempt.reason ? [` reason=${attempt.reason}`] : [],
...attempt.stage ? [` stage=${attempt.stage}`] : [],
...typeof attempt.elapsedMs === "number" ? [` elapsed=${(attempt.elapsedMs / 1e3).toFixed(1)}s`] : [],
...typeof attempt.status === "number" ? [` status=${attempt.status}`] : []
].join("\n")).join("\n\n")}\n~~~`;
}
function toSnakeCase(value) {
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
}
function resolveMetadataSegmentKey(label) {
const normalized = toSnakeCase(label);
if (normalized === "conversation_info") return "conversation_metadata";
if (normalized === "sender") return "sender_metadata";
return normalized.endsWith("_metadata") ? normalized : `${normalized}_metadata`;
}
function derivePromptSegments(prompt) {
const text = prompt ?? "";
if (!text.trim()) return;
const lines = text.split("\n");
const segments = /* @__PURE__ */ new Map();
let userChars = 0;
const addChars = (key, chars) => {
if (!chars || chars <= 0) return;
segments.set(key, (segments.get(key) ?? 0) + chars);
};
let index = 0;
while (index < lines.length) {
const line = lines[index] ?? "";
if (line === "Context:") {
const tagMatch = (lines[index + 1] ?? "").trim().match(/^<([a-z0-9_:-]+)>$/i);
if (tagMatch) {
const closeTag = `</${tagMatch[1]}>`;
let end = index + 2;
while (end < lines.length && lines[end]?.trim() !== closeTag) end += 1;
if (end < lines.length) {
addChars(expectDefined(tagMatch[1], "tag match capture group 1"), lines.slice(index, end + 1).join("\n").length);
index = end + 1;
while (index < lines.length && lines[index] === "") index += 1;
continue;
}
}
}
const metadataHeaderLine = line.trim().endsWith("⟦openclaw:ctx⟧") ? line : null;
if (metadataHeaderLine) {
const start = index;
if ((lines[index + 1] ?? "").trim() === "```json") {
let end = index + 2;
while (end < lines.length && !(lines[end] ?? "").startsWith("```")) end += 1;
if (end < lines.length) {
addChars(resolveMetadataSegmentKey(metadataHeaderLine.trim().slice(0, -14).trim() || "metadata"), lines.slice(start, end + 1).join("\n").length);
index = end + 1;
while (index < lines.length && lines[index] === "") index += 1;
continue;
}
}
}
if (line.trim()) userChars += line.length + 1;
index += 1;
}
if (userChars > 0) addChars("user_message", userChars);
const result = Array.from(segments.entries()).map(([key, chars]) => ({
key,
chars
}));
return result.length > 0 ? result : void 0;
}
function formatPromptSegmentsTraceBlock(segments, totalPromptText) {
if (!segments?.length && !totalPromptText?.length) return;
const lines = (segments ?? []).map((segment) => `${segment.key}=${segment.chars.toLocaleString()} chars`);
if (typeof totalPromptText === "string" && totalPromptText.length > 0) lines.push(`totalPromptText=${totalPromptText.length.toLocaleString()} chars`);
return lines.length > 0 ? `🔎 Prompt Segments:\n~~~text\n${lines.join("\n")}\n~~~` : void 0;
}
function formatToolSummaryTraceBlock(toolSummary) {
if (!toolSummary || toolSummary.calls <= 0) return;
return formatKeyValueTraceBlock("Tool Summary", [
["calls", toolSummary.calls],
["tools", toolSummary.tools.length > 0 ? toolSummary.tools.join(", ") : void 0],
["failures", toolSummary.failures],
["totalToolTimeMs", toolSummary.totalToolTimeMs]
]);
}
function formatCompletionTraceBlock(completion) {
if (!completion) return;
return formatKeyValueTraceBlock("Completion", [
["finishReason", completion.finishReason],
["stopReason", completion.stopReason],
["refusal", completion.refusal]
]);
}
function formatContextManagementTraceBlock(contextManagement) {
if (!contextManagement) return;
return formatKeyValueTraceBlock("Context Management", [
["sessionCompactions", contextManagement.sessionCompactions],
["lastTurnCompactions", contextManagement.lastTurnCompactions],
["preflightCompactionApplied", contextManagement.preflightCompactionApplied],
["postCompactionContextInjected", contextManagement.postCompactionContextInjected]
]);
}
async function accumulateSessionUsageFromTranscript(params) {
const sessionId = normalizeOptionalString(params.sessionId);
if (!sessionId) return;
try {
const artifactFile = params.sessionFile?.trim();
const useArtifactFile = Boolean(artifactFile && path.isAbsolute(artifactFile) && artifactFile.endsWith(".jsonl"));
const usage = await readLatestSessionUsageFromTranscriptAsync({
agentId: params.agentId,
sessionId,
sessionKey: useArtifactFile ? void 0 : params.sessionKey,
storePath: params.storePath,
sessionFile: params.sessionFile
});
if (!usage) return;
return {
input: usage.inputTokens,
output: usage.outputTokens,
cacheRead: usage.cacheRead,
cacheWrite: usage.cacheWrite,
total: usage.totalTokens
};
} catch {
return;
}
}
function formatRequestContextTraceBlock(params) {
const limit = params.contextLimit;
const used = params.promptTokens;
if ((typeof limit !== "number" || !Number.isFinite(limit) || limit <= 0) && (typeof used !== "number" || !Number.isFinite(used) || used <= 0) && !params.provider && !params.model) return;
const headroom = typeof limit === "number" && Number.isFinite(limit) && typeof used === "number" && Number.isFinite(used) ? Math.max(0, limit - used) : void 0;
const percent = typeof limit === "number" && Number.isFinite(limit) && limit > 0 && typeof used === "number" && Number.isFinite(used) ? Math.round(used / limit * 100) : void 0;
return `🔎 Context Window (Last Model Request):\n~~~text\n${[
`provider=${params.provider ?? "n/a"}`,
`model=${params.model ?? "n/a"}`,
`used=${typeof used === "number" && Number.isFinite(used) ? `${used.toLocaleString()} tok (${formatTokenCount(used)})` : "n/a"}`,
`limit=${typeof limit === "number" && Number.isFinite(limit) ? `${limit.toLocaleString()} tok (${formatTokenCount(limit)})` : "n/a"}`,
`headroom=${typeof headroom === "number" ? `${headroom.toLocaleString()} tok (${formatTokenCount(headroom)})` : "n/a"}`,
`usage=${typeof percent === "number" ? `${percent}%` : "n/a"}`
].join("\n")}\n~~~`;
}
function formatSummaryPromptValue(params) {
const used = params.promptTokens;
const limit = params.contextLimit;
if (typeof used !== "number" || !Number.isFinite(used) || used <= 0 || typeof limit !== "number" || !Number.isFinite(limit) || limit <= 0) return;
return `${formatTokenCount(used)}/${formatTokenCount(limit)}`;
}
function formatRawTraceSummaryLine(params) {
const thinking = normalizeOptionalString(params.requestShaping?.thinking);
const fields = [
params.executionTrace?.winnerModel ? `winner=${params.executionTrace.winnerModel}${thinking ? ` 🧠 ${thinking}` : ""}` : void 0,
typeof params.executionTrace?.fallbackUsed === "boolean" ? `fallback=${params.executionTrace.fallbackUsed ? "yes" : "no"}` : void 0,
typeof params.executionTrace?.attempts?.length === "number" ? `attempts=${params.executionTrace.attempts.length.toLocaleString()}` : void 0,
params.completion?.stopReason ? `stop=${params.completion.stopReason}` : void 0,
(() => {
const prompt = formatSummaryPromptValue({
contextLimit: params.contextLimit,
promptTokens: params.promptTokens
});
return prompt ? `prompt=${prompt}` : void 0;
})(),
typeof params.usage?.input === "number" && params.usage.input > 0 ? `⬇️ ${formatTokenCount(params.usage.input)}` : void 0,
typeof params.usage?.output === "number" && params.usage.output > 0 ? `⬆️ ${formatTokenCount(params.usage.output)}` : void 0,
typeof params.usage?.cacheRead === "number" && params.usage.cacheRead > 0 ? `♻️ ${formatTokenCount(params.usage.cacheRead)}` : void 0,
typeof params.usage?.cacheWrite === "number" && params.usage.cacheWrite > 0 ? `🆕 ${formatTokenCount(params.usage.cacheWrite)}` : void 0,
typeof params.usage?.total === "number" && params.usage.total > 0 ? `🔢 ${formatTokenCount(params.usage.total)}` : void 0,
typeof params.toolSummary?.calls === "number" && params.toolSummary.calls > 0 ? `tools=${params.toolSummary.calls.toLocaleString()}` : void 0,
typeof params.contextManagement?.lastTurnCompactions === "number" && params.contextManagement.lastTurnCompactions > 0 ? `compactions=${params.contextManagement.lastTurnCompactions.toLocaleString()}` : void 0
].filter((value) => Boolean(value));
return fields.length > 0 ? `Summary: ${fields.join(" ")}` : void 0;
}
function buildInlineRawTracePayload(params) {
const resolvedPromptTokens = deriveContextPromptTokens({
lastCallUsage: params.lastCallUsage,
promptTokens: params.promptTokens,
usage: params.usage
});
const requestContextBlock = formatRequestContextTraceBlock({
provider: params.provider,
model: params.model,
contextLimit: params.contextLimit,
promptTokens: resolvedPromptTokens
});
return { text: [
...[
formatUsageTraceBlock("Usage (Session Total)", params.sessionUsage),
formatUsageTraceBlock("Usage (Last Turn Total)", params.usage),
requestContextBlock,
formatExecutionResultTraceBlock(params.executionTrace),
formatFallbackChainTraceBlock(params.executionTrace),
formatKeyValueTraceBlock("Request Shaping", [
["provider", params.provider],
["model", params.model],
["auth", params.requestShaping?.authMode],
["thinking", params.requestShaping?.thinking],
["reasoning", params.requestShaping?.reasoning],
["verbose", params.requestShaping?.verbose],
["trace", params.requestShaping?.trace],
["fallbackEligible", params.requestShaping?.fallbackEligible],
["blockStreaming", params.requestShaping?.blockStreaming]
]),
formatPromptSegmentsTraceBlock(params.promptSegments, params.rawUserText),
formatToolSummaryTraceBlock(params.toolSummary),
formatCompletionTraceBlock(params.completion),
formatContextManagementTraceBlock(params.contextManagement)
].filter((value) => Boolean(value)),
formatRawTraceBlock("Model Input (User Role)", params.rawUserText),
formatRawTraceBlock("Model Output (Assistant Role)", params.rawAssistantText),
formatRawTraceSummaryLine({
executionTrace: params.executionTrace,
completion: params.completion,
contextLimit: params.contextLimit,
promptTokens: resolvedPromptTokens,
usage: params.usage,
toolSummary: params.toolSummary,
contextManagement: params.contextManagement,
requestShaping: params.requestShaping
})
].join("\n\n\n") };
}
//#endregion
//#region src/auto-reply/reply/agent-runner-result-diagnostics.ts
/** Builds the same authorized diagnostic supplement for immediate and queued replies. */
async function buildReplyDiagnosticsPayload(params) {
const { activeSessionEntry, followupRun, accounting, cfg, storePath, userText, resolvedVerboseLevel, resolvedBlockStreamingBreak, preflightCompactionApplied } = params;
const { runResult, providerUsed, modelUsed, contextTokensUsed, promptTokens } = accounting;
const verboseEnabled = (normalizeVerboseLevel(followupRun.run.verboseLevelOverride ?? activeSessionEntry?.verboseLevel ?? resolvedVerboseLevel ?? followupRun.run.verboseLevel) ?? "off") !== "off";
const traceAuthorized = followupRun.run.traceAuthorized === true;
const traceLevel = followupRun.run.traceLevelOverride ?? activeSessionEntry?.traceLevel;
const traceEnabled = traceAuthorized && (traceLevel === "on" || traceLevel === "raw");
if (!verboseEnabled && !traceEnabled) return;
let diagnosticsPayload = buildInlinePluginStatusPayload({
entry: activeSessionEntry,
includeStatusLines: verboseEnabled,
includeTraceLines: traceEnabled
});
if (traceAuthorized && traceLevel === "raw") {
const isHookBlockedRun = runResult.meta?.error?.kind === "hook_block";
const rawUserText = isHookBlockedRun ? runResult.meta?.finalPromptText : runResult.meta?.finalPromptText ?? userText;
const rawAssistantText = isHookBlockedRun ? void 0 : runResult.meta?.finalAssistantRawText ?? runResult.meta?.finalAssistantVisibleText;
const executionTrace = runResult.meta?.executionTrace;
const requestShaping = {
authMode: runResult.meta?.requestShaping?.authMode ?? (cfg?.models?.providers && providerUsed in cfg.models.providers ? resolveModelAuthMode(providerUsed, cfg, void 0, { workspaceDir: followupRun.run.workspaceDir }) ?? void 0 : void 0),
thinking: runResult.meta?.requestShaping?.thinking ?? normalizeOptionalString(followupRun.run.thinkLevel),
reasoning: runResult.meta?.requestShaping?.reasoning ?? normalizeOptionalString(followupRun.run.reasoningLevel),
verbose: runResult.meta?.requestShaping?.verbose ?? normalizeOptionalString(resolvedVerboseLevel),
trace: followupRun.run.traceLevelOverride ?? runResult.meta?.requestShaping?.trace ?? normalizeOptionalString(activeSessionEntry?.traceLevel),
fallbackEligible: runResult.meta?.requestShaping?.fallbackEligible ?? resolveModelFallbackAvailability({
cfg: cfg ?? {},
agentId: followupRun.run.agentId,
sessionKey: followupRun.run.sessionKey,
hasSessionModelOverride: followupRun.run.hasSessionModelOverride === true,
modelOverrideSource: followupRun.run.modelOverrideSource,
hasAutoFallbackProvenance: followupRun.run.hasAutoFallbackProvenance === true,
modelSelectionLocked: followupRun.run.modelSelectionLocked
}).kind === "active",
blockStreaming: runResult.meta?.requestShaping?.blockStreaming ?? normalizeOptionalString(resolvedBlockStreamingBreak)
};
const promptSegments = runResult.meta?.promptSegments ?? derivePromptSegments(rawUserText);
const toolSummary = runResult.meta?.toolSummary;
const completion = runResult.meta?.completion ?? (runResult.meta?.stopReason ? {
stopReason: runResult.meta.stopReason,
finishReason: runResult.meta.stopReason,
...runResult.meta.stopReason.toLowerCase().includes("refusal") ? { refusal: true } : {}
} : void 0);
const contextManagement = {
...typeof activeSessionEntry?.compactionCount === "number" ? { sessionCompactions: activeSessionEntry.compactionCount } : {},
...typeof runResult.meta?.contextManagement?.lastTurnCompactions === "number" ? { lastTurnCompactions: runResult.meta.contextManagement.lastTurnCompactions } : typeof runResult.meta?.agentMeta?.compactionCount === "number" ? { lastTurnCompactions: runResult.meta.agentMeta.compactionCount } : {},
...runResult.meta?.contextManagement && typeof runResult.meta.contextManagement.preflightCompactionApplied === "boolean" ? { preflightCompactionApplied: runResult.meta.contextManagement.preflightCompactionApplied } : preflightCompactionApplied ? { preflightCompactionApplied } : {},
...runResult.meta?.contextManagement && typeof runResult.meta.contextManagement.postCompactionContextInjected === "boolean" ? { postCompactionContextInjected: runResult.meta.contextManagement.postCompactionContextInjected } : {}
};
const rawTracePayload = buildInlineRawTracePayload({
rawUserText,
rawAssistantText,
sessionUsage: await accumulateSessionUsageFromTranscript({
agentId: followupRun.run.agentId,
sessionId: runResult.meta?.agentMeta?.sessionId ?? followupRun.run.sessionId,
sessionKey: followupRun.run.sessionKey,
storePath,
sessionFile: followupRun.run.sessionFile
}),
usage: runResult.meta?.agentMeta?.usage,
lastCallUsage: runResult.meta?.agentMeta?.lastCallUsage,
provider: providerUsed,
model: modelUsed,
contextLimit: contextTokensUsed,
promptTokens,
executionTrace,
requestShaping,
promptSegments,
toolSummary,
completion,
contextManagement
});
diagnosticsPayload = diagnosticsPayload ? { text: `${diagnosticsPayload.text}\n\n${rawTracePayload.text}` } : rawTracePayload;
}
return diagnosticsPayload ? {
...diagnosticsPayload,
isStatusNotice: true
} : void 0;
}
//#endregion
//#region src/auto-reply/reply/session-usage.ts
/** Persists usage, cost, and model metadata after reply runs. */
function applyCliSessionClearToSessionPatch(params, entry, patch) {
const cliProvider = params.providerUsed ?? entry.modelProvider;
if (!cliProvider) return patch;
if (params.clearCliSessionBinding === true) {
const nextEntry = {
...entry,
...patch
};
clearCliSession(nextEntry, cliProvider);
return {
...patch,
cliSessionIds: nextEntry.cliSessionIds,
cliSessionBindings: nextEntry.cliSessionBindings,
claudeCliSessionId: nextEntry.claudeCliSessionId
};
}
return patch;
}
function resolveNonNegativeTokenCount(value) {
const resolved = asNonNegativeFiniteNumber(value);
return resolved === void 0 ? void 0 : Math.floor(resolved);
}
function estimateSessionRunCostUsd(params) {
if (!hasBillableUsage(params.usage)) return;
return asNonNegativeFiniteNumber(estimateAggregateUsageCost({
usage: params.usage,
provider: params.providerUsed,
model: params.modelUsed,
config: params.cfg,
agentDir: params.agentDir
}));
}
/** Persists usage accounting and selected runtime metadata to the session store. */
async function persistSessionUsageUpdate(params) {
const { agentId, storePath, sessionKey, sessionStore, authorize } = params;
if (!storePath || !sessionKey) return;
const expectedSession = params.expectedSession ? { ...params.expectedSession } : void 0;
const label = params.logLabel ? `${params.logLabel} ` : "";
const cfg = params.cfg ?? getRuntimeConfig();
const agentHarnessId = normalizeOptionalString(params.agentHarnessId);
const modelSelection = params.runtimeModelSelection ?? {
provider: params.providerUsed,
model: params.modelUsed
};
const hasUsage = hasNonzeroUsage(params.usage);
const hasBilling = hasBillableUsage(params.usage);
const hasPromptTokens = typeof params.promptTokens === "number" && Number.isFinite(params.promptTokens) && params.promptTokens > 0;
const hasFreshContextSnapshot = Boolean(params.lastCallUsage) && params.lastCallUsage?.contextUsage?.state !== "unavailable" || hasPromptTokens;
const hasCurrentContextSnapshot = params.currentContextSnapshot !== void 0;
const currentContextTokens = resolveNonNegativeTokenCount(params.currentContextSnapshot?.tokens);
const hasContextUpdate = hasUsage || hasFreshContextSnapshot || hasCurrentContextSnapshot || Boolean(modelSelection.model || params.contextTokensUsed);
if (hasBilling || hasContextUpdate) try {
await patchSessionEntryCore({
agentId,
storePath,
sessionKey
}, (entry) => {
if (!(authorize?.() ?? true) || expectedSession && (entry.sessionId !== expectedSession.sessionId || entry.lifecycleRevision !== expectedSession.lifecycleRevision || Object.hasOwn(expectedSession, "activeWriterRunId") && entry.activeWriterRunId !== expectedSession.activeWriterRunId)) return null;
const updatedAt = Date.now();
const preserveSessionModelState = params.isHeartbeat === true || params.preserveRuntimeModel === true || params.preserveUserFacingSessionModelState === true;
const preserveUserFacingRunState = params.preserveUserFacingSessionModelState === true;
const resolvedContextTokens = preserveSessionModelState ? entry.contextTokens : params.contextTokensUsed ?? entry.contextTokens;
const totalTokens = hasCurrentContextSnapshot ? currentContextTokens : hasFreshContextSnapshot ? deriveSessionTotalTokens({
lastCallUsage: params.lastCallUsage,
contextTokens: resolvedContextTokens,
promptTokens: params.promptTokens
}) : void 0;
const runEstimatedCostUsd = preserveUserFacingRunState ? void 0 : estimateSessionRunCostUsd({
cfg,
agentDir: params.agentDir,
usage: params.usage,
providerUsed: params.providerUsed ?? entry.modelProvider,
modelUsed: params.modelUsed ?? entry.model
});
const patch = {
modelProvider: preserveSessionModelState ? entry.modelProvider : modelSelection.provider ?? entry.modelProvider,
model: preserveSessionModelState ? entry.model : modelSelection.model ?? entry.model,
...!preserveSessionModelState ? {
agentHarnessId,
contextTokensSource: params.contextTokensSource,
contextBudgetStatus: params.contextBudgetStatus
} : {},
...resolvedContextTokens !== void 0 ? { contextTokens: resolvedContextTokens } : {},
systemPromptReport: preserveUserFacingRunState ? entry.systemPromptReport : params.systemPromptReport ?? entry.systemPromptReport,
updatedAt
};
if (hasUsage && !preserveUserFacingRunState) {
patch.inputTokens = params.usage?.input ?? 0;
patch.outputTokens = params.usage?.output ?? 0;
const cacheUsage = params.lastCallUsage ?? params.usage;
patch.cacheRead = cacheUsage?.cacheRead ?? 0;
patch.cacheWrite = cacheUsage?.cacheWrite ?? 0;
}
if (hasBilling && !preserveUserFacingRunState) patch.estimatedCostUsd = runEstimatedCostUsd;
if (totalTokens !== void 0 && !preserveUserFacingRunState) {
patch.totalTokens = totalTokens;
patch.totalTokensFresh = true;
patch.totalTokensVersion = 1;
const accountedGoal = resolveSessionGoalDisplayState({
...entry,
...patch
}, updatedAt);
if (accountedGoal) patch.goal = accountedGoal;
} else if (!preserveUserFacingRunState && hasContextUpdate && (hasCurrentContextSnapshot || params.preserveFreshTotalTokensOnStaleUsage !== true || entry.totalTokensFresh !== true)) {
patch.totalTokensFresh = false;
patch.totalTokensVersion = void 0;
}
return preserveUserFacingRunState ? patch : applyCliSessionClearToSessionPatch(params, entry, patch);
}, {
skipMaintenance: true,
...sessionStore ? { onCommitted: (entry) => {
sessionStore[sessionKey] = entry;
} } : {},
...authorize ? { assertCommitAllowed: () => {
if (!authorize()) throw new Error("session usage accounting authority revoked");
} } : {}
});
} catch (err) {
logVerbose(`failed to persist ${label}usage update: ${String(err)}`);
}
}
//#endregion
//#region src/auto-reply/reply/agent-runner-result-accounting.ts
/** Persists only host-bound facts while the exact logical turn still owns accounting. */
async function accountAgentTurnCompaction(params) {
const operation = params.replyOperation;
if (!operation) return;
const authorize = () => replyRunRegistry.get(operation.key) === operation;
let count;
for (const fact of params.compaction?.durable ?? []) {
const persistedCount = await incrementCompactionCount({
agentId: fact.target.agentId,
sessionStore: params.sessionStore,
sessionKey: fact.target.sessionKey,
storePath: fact.target.storePath,
expectedSession: fact.target,
amount: fact.count,
tokensAfter: fact.currentContextSnapshot?.tokens,
authorize
});
if (persistedCount !== void 0) count = persistedCount;
}
return count;
}
async function accountAgentTurn(context) {
const { activeSessionStore, blockReplyPipeline, cfg, defaultModel, followupRun, isHeartbeat, pendingToolTasks, preflightCompactionApplied, resolvedVerboseLevel, execution, runId, runStartedAt, sessionKey, sessionCtx, shouldInjectGroupIntro, storePath } = context;
let { activeSessionEntry } = context;
const latestCompaction = execution.compaction?.durable.at(-1);
const currentContextSnapshot = execution.compaction ? latestCompaction?.currentContextSnapshot ?? { tokens: void 0 } : void 0;
const expectedSession = latestCompaction?.target ?? {
sessionId: activeSessionEntry?.sessionId ?? followupRun.run.sessionId,
lifecycleRevision: activeSessionEntry?.lifecycleRevision
};
const operation = context.replyOperation;
const authorize = latestCompaction ? () => operation !== void 0 && replyRunRegistry.get(operation.key) === operation : void 0;
const runResult = execution.result;
const fallbackProvider = execution.resolved.provider;
const fallbackModel = execution.resolved.model;
const fallbackExhausted = execution.fallback.exhausted;
const fallbackAttempts = execution.fallback.attempts;
const directlySentBlockKeys = execution.directlySentBlockKeys;
const directlySentBlockPayloads = execution.directlySentBlockPayloads;
const terminalFailurePayload = execution.terminalFailurePayload;
const { autoCompactionCount, didLogHeartbeatStrip } = execution;
if (shouldInjectGroupIntro && activeSessionEntry && activeSessionStore && sessionKey && activeSessionEntry.groupActivationNeedsSystemIntro) {
const updatedAt = Date.now();
activeSessionEntry.groupActivationNeedsSystemIntro = false;
activeSessionEntry.updatedAt = updatedAt;
activeSessionStore[sessionKey] = activeSessionEntry;
if (storePath) await updateSessionEntry({
storePath,
sessionKey
}, () => ({
groupActivationNeedsSystemIntro: false,
updatedAt
}), {
skipMaintenance: true,
takeCacheOwnership: true
});
}
const payloadArray = runResult.payloads ?? [];
if (blockReplyPipeline) {
await blockReplyPipeline.flush({ force: true });
blockReplyPipeline.stop();
}
if (pendingToolTasks.size > 0) await drainPendingToolTasks({
tasks: pendingToolTasks,
onTimeout: logVerbose
});
const usage = runResult.meta?.agentMeta?.usage;
const promptTokens = runResult.meta?.agentMeta?.promptTokens;
const modelUsed = runResult.meta?.agentMeta?.model ?? fallbackModel ?? defaultModel;
const providerUsed = runResult.meta?.agentMeta?.provider ?? fallbackProvider ?? followupRun.run.provider;
const runtimeModelSelection = runResult.meta?.agentMeta?.runtimeModelSelection;
const sessionModel = runtimeModelSelection ?? {
provider: providerUsed,
model: modelUsed
};
const winnerProvider = fallbackExhausted ? void 0 : runResult.meta?.executionTrace?.winnerProvider ?? providerUsed;
const winnerModel = fallbackExhausted ? void 0 : runResult.meta?.executionTrace?.winnerModel ?? modelUsed;
const ctxTokens = runResult.meta?.agentMeta?.contextTokens;
const compactions = runResult.meta?.agentMeta?.compactionCount;
const lastCallUsage = runResult.meta?.agentMeta?.lastCallUsage;
const replyUsageState = buildReplyUsageState({
config: cfg,
agentDir: followupRun.run.agentDir,
provider: providerUsed,
model: modelUsed,
fallbackExhausted,
winnerProvider,
winnerModel,
reasoningEffort: typeof followupRun.run.thinkLevel === "string" ? followupRun.run.thinkLevel : void 0,
fastMode: resolveFastModeState({
cfg,
provider: providerUsed ?? "",
model: modelUsed ?? "",
agentId: followupRun.run.agentId,
sessionEntry: activeSessionEntry
}).enabled,
fallbackUsed: runResult.meta?.executionTrace?.fallbackUsed === true,
agentId: followupRun.run.agentId,
sessionId: followupRun.run.sessionId,
chatType: typeof sessionCtx.ChatType === "string" ? sessionCtx.ChatType : void 0,
authMode: runResult.meta?.requestShaping?.authMode ?? void 0,
overrideSource: activeSessionEntry?.modelOverrideSource ?? void 0,
requestedProvider: followupRun.run.provider,
requestedModel: followupRun.run.model,
durationMs: Date.now() - runStartedAt,
compactionCount: typeof compactions === "number" ? compactions : void 0,
contextTokenBudget: typeof ctxTokens === "number" && Number.isFinite(ctxTokens) ? ctxTokens : void 0,
contextUsedTokens: typeof promptTokens === "number" && Number.isFinite(promptTokens) ? promptTokens : void 0,
promptTokens,
usage,
lastCallUsage
});
recordReplyUsageState(runId, replyUsageState);
const verboseEnabled = resolvedVerboseLevel !== "off";
const preserveUserFacingSessionState = shouldPreserveUserFacingSessionStateForInputProvenance(followupRun.run.inputProvenance);
const fallbackStateEntry = activeSessionEntry ?? (sessionKey ? activeSessionStore?.[sessionKey] : void 0);
const configuredFallbackModel = resolveFallbackOriginModel({
run: followupRun.run,
fallbackStateEntry,
runtimeModelSelection
});
const selectedProvider = configuredFallbackModel.provider;
const selectedModel = configuredFallbackModel.model;
const fallbackTransition = resolveFallbackTransition({
selectedProvider,
selectedModel,
activeProvider: sessionModel.provider,
activeModel: sessionModel.model,
attempts: fallbackAttempts,
state: fallbackStateEntry,
cfg
});
if (fallbackTransition.stateChanged && !fallbackExhausted && !preserveUserFacingSessionState) {
const fallbackNotice = fallbackTransition.nextState.selectedModel ? {
kind: "active",
selectedModel: fallbackTransition.nextState.selectedModel,
activeModel: fallbackTransition.nextState.activeModel,
...fallbackTransition.nextState.reason ? { reason: fallbackTransition.nextState.reason } : {}
} : void 0;
if (fallbackStateEntry) {
fallbackStateEntry.fallbackNotice = fallbackNotice;
fallbackStateEntry.updatedAt = Date.now();
activeSessionEntry = fallbackStateEntry;
}
if (sessionKey && fallbackStateEntry && activeSessionStore) activeSessionStore[sessionKey] = fallbackStateEntry;
if (sessionKey && storePath) await updateSessionEntry({
storePath,
sessionKey
}, () => ({ fallbackNotice }), {
skipMaintenance: true,
takeCacheOwnership: true
});
}
const runtimeContextTokens = typeof runResult.meta?.agentMeta?.contextTokens === "number" && Number.isFinite(runResult.meta.agentMeta.contextTokens) && runResult.meta.agentMeta.contextTokens > 0 ? Math.floor(runResult.meta.agentMeta.contextTokens) : void 0;
const resolvedContextTokens = runtimeContextTokens === void 0 ? resolveContextTokensForModel({
cfg,
provider: sessionModel.provider,
model: sessionModel.model,
allowAsyncLoad: false
}) : void 0;
const contextTokensUsed = runtimeContextTokens ?? resolvedContextTokens ?? activeSessionEntry?.contextTokens ?? 2e5;
const contextTokensSource = runResult.meta?.agentMeta?.contextTokensSource ?? (runtimeContextTokens !== void 0 ? "runtime" : resolvedContextTokens !== void 0 ? "resolved-v1" : void 0);
const compactionCount = await accountAgentTurnCompaction({
compaction: execution.compaction,
sessionStore: activeSessionStore,
replyOperation: operation
});
await persistSessionUsageUpdate({
agentId: latestCompaction?.target.agentId ?? followupRun.run.agentId,
sessionStore: activeSessionStore,
storePath: latestCompaction?.target.storePath ?? storePath,
sessionKey: latestCompaction?.target.sessionKey ?? sessionKey,
expectedSession,
authorize,
cfg,
agentDir: followupRun.run.agentDir,
usage,
lastCallUsage: runResult.meta?.agentMeta?.lastCallUsage,
currentContextSnapshot,
promptTokens,
isHeartbeat,
preserveRuntimeModel: fallbackExhausted || fallbackTransition.nextState.selectedModel !== void 0,
preserveUserFacingSessionModelState: preserveUserFacingSessionState,
modelUsed,
providerUsed,
runtimeModelSelection,
contextTokensUsed,
contextTokensSource,
contextBudgetStatus: compactionCount === void 0 ? runResult.meta?.agentMeta?.contextBudgetStatus : void 0,
systemPromptReport: runResult.meta?.systemPromptReport,
preserveFreshTotalTokensOnStaleUsage: preflightCompactionApplied,
agentHarnessId: runResult.meta?.agentMeta?.agentHarnessId
});
if (!isHeartbeat && !preserveUserFacingSessionState && !fallbackExhausted) await consolidateLiveModelSwitchAfterRun({
cfg,
sessionKey,
agentId: followupRun.run.agentId,
providerUsed: sessionModel.provider,
modelUsed: sessionModel.model
});
if (compactionCount !== void 0 && sessionKey) activeSessionEntry = activeSessionStore?.[sessionKey] ?? activeSessionEntry;
return {
activeSessionEntry,
autoCompactionCount,
compactionCount,
expectedSession,
configuredFallbackModel,
contextTokensUsed,
didLogHeartbeatStrip,
directlySentBlockKeys,
directlySentBlockPayloads,
fallbackAttempts,
fallbackExhausted,
fallbackTransition,
modelUsed,
payloadArray,
preserveUserFacingSessionState,
promptTokens,
providerUsed,
replyUsageState,
runId,
runResult,
selectedModel,
selectedProvider,
sessionModel,
terminalFailurePayload,
usage,
verboseEnabled
};
}
/** Applies common accounting plus the queue/session projection owned by follow-up turns. */
async function accountFollowupTurn(params) {
const { turn, defaults, execution } = params;
const settled = execution.execution.outcome;
const sessionKey = turn.session.kind === "session" ? turn.session.key : void 0;
if (settled.kind !== "settled") {
await accountAgentTurnCompaction({
compaction: settled.compaction,
sessionStore: turn.sessionStore,
replyOperation: turn.operation
});
return;
}
const resolvedVerboseLevel = normalizeVerboseLevel(turn.queued.run.verboseLevelOverride ?? turn.session.current()?.verboseLevel ?? turn.queued.run.verboseLevel) ?? "off";
const accounting = await accountAgentTurn({
activeSessionEntry: turn.session.current(),
activeSessionStore: turn.sessionStore,
blockReplyPipeline: null,
cfg: turn.config,
defaultModel: defaults.defaultModel,
followupRun: turn.queued,
isHeartbeat: defaults.opts?.isHeartbeat === true,
pendingToolTasks: execution.pendingToolTasks,
replyOperation: turn.operation,
preflightCompactionApplied: turn.preflightCompactionApplied,
resolvedVerboseLevel,
execution: settled,
runId: execution.execution.runId,
runStartedAt: execution.runStartedAt,
sessionCtx: execution.sessionCtx,
sessionKey,
shouldInjectGroupIntro: false,
storePath: turn.session.kind === "session" ? turn.session.storePath : void 0
});
turn.session.publish(accounting.activeSessionEntry);
const queueKey = turn.queued.run.sessionKey ?? defaults.sessionKey ?? sessionKey;
if (queueKey && accounting.fallbackTransition.stateChanged && !accounting.fallbackExhausted && !accounting.preserveUserFacingSessionState) {
const entry = turn.session.current();
refreshQueuedFollowupSession({
key: queueKey,
previousSessionId: turn.queued.run.sessionId,
nextSessionId: entry?.sessionId ?? turn.queued.run.sessionId,
nextSessionFile: queueKey,
nextProvider: accounting.sessionModel.provider,
nextModel: accounting.sessionModel.model,
nextModelOverrideSource: entry?.modelOverrideSource,
nextAuthProfileId: entry?.authProfileOverride,
nextAuthProfileIdSource: resolveCollapsedSessionAuthPinSource(entry)
});
}
let compactionNotice;
if (accounting.autoCompactionCount > 0) {
const previousSessionId = turn.queued.run.sessionId;
const count = accounting.compactionCount;
const refreshed = turn.session.current();
if (refreshed) {
turn.session.publish(refreshed);
refreshQueuedFollowupSession({
key: queueKey ?? "",
previousSessionId,
nextSessionId: refreshed.sessionId,
nextSessionFile: queueKey ?? sessionKey
});
}
if (accounting.verboseEnabled) compactionNotice = { text: `🧹 Auto-compaction complete${typeof count === "number" ? ` (count ${count})` : ""}.` };
}
if (turn.queued.run.verboseLevelOverride !== "off" || turn.queued.run.traceAuthorized === true) turn.session.publish(refreshSessionEntryFromStore({
storePath: turn.session.kind === "session" ? turn.session.storePath : void 0,
sessionKey,
fallbackEntry: turn.session.current(),
expectedGeneration: accounting.expectedSession
}));
const diagnosticsPayload = await buildReplyDiagnosticsPayload({
activeSessionEntry: turn.session.current(),
followupRun: turn.queued,
accounting,
cfg: turn.config,
storePath: turn.session.kind === "session" ? turn.session.storePath : void 0,
userText: turn.queued.prompt,
resolvedVerboseLevel,
resolvedBlockStreamingBreak: turn.queued.run.blockReplyBreak,
preflightCompactionApplied: turn.preflightCompactionApplied
});
return {
...accounting,
compactionNotice,
diagnosticsPayload
};
}
//#endregion
//#region src/auto-reply/usage-bar/contract.ts
function buildUsageContract(state, surface) {
const usage = state.usage ?? {};
const input = usage.input;
const output = usage.output;
const cacheRead = usage.cacheRead;
const cacheWrite = usage.cacheWrite;
const total = usage.total;
const hasSplitTokens = input !== void 0 || output !== void 0;
const hasTotalOnlyTokens = !hasSplitTokens && total !== void 0;
const hasTokens = hasSplitTokens || cacheRead !== void 0 || cacheWrite !== void 0 || total !== void 0;
const promptTotal = (cacheRead ?? 0) + (cacheWrite ?? 0) + (input ?? 0);
const cacheHitPct = promptTotal > 0 ? Math.round((cacheRead ?? 0) / promptTotal * 100) : void 0;
const last = state.lastUsage;
const lastPromptTotal = last ? (last.cacheRead ?? 0) + (last.cacheWrite ?? 0) + (last.input ?? 0) : 0;
const lastCacheHitPct = last && lastPromptTotal > 0 ? Math.round((last.cacheRead ?? 0) / lastPromptTotal * 100) : void 0;
const maxTokens = state.contextTokenBudget;
const usedTokens = typeof state.contextUsedTokens === "number" && state.contextUsedTokens > 0 ? state.contextUsedTokens : promptTotal > 0 ? promptTotal : void 0;
const pctUsed = maxTokens && usedTokens !== void 0 ? Math.round(usedTokens / maxTokens * 100) : void 0;
const overrideSource = state.overrideSource ?? null;
const isOverride = typeof state.overrideSource === "string" && state.overrideSource !== "" && state.overrideSource !== "auto";
return {
schema: "openclaw.usageLine.v1",
surface: surface ?? null,
agentId: state.agentId ?? null,
chat_type: state.chatType ?? null,
model: {
id: state.model ?? null,
display_name: state.model ?? null,
provider: state.provider ?? null,
reasoning: state.reasoningEffort ?? null,
actual: state.resolvedRef ?? null,
resolved_ref: state.resolvedRef ?? null,
requested: state.requested ?? null,
is_fallback: state.fallbackUsed === true,
is_override: isOverride,
override_source: overrideSource,
auth_mode: state.authMode ?? null
},
state: {
fast_mode: typeof state.fastMode === "boolean" ? state.fastMode : null,
compactions: typeof state.compactionCount === "number" ? state.compactionCount : null
},
usage: {
input_tokens: input,
output_tokens: output,
cache_read_tokens: cacheRead,
cache_write_tokens: cacheWrite,
total_tokens: total,
cache_hit_pct: cacheHitPct,
has_tokens: hasTokens,
has_split_tokens: hasSplitTokens,
has_total_only_tokens: hasTotalOnlyTokens,
last: last ? {
input_tokens: last.input,
output_tokens: last.output,
cache_read_tokens: last.cacheRead,
cache_write_tokens: last.cacheWrite,
total_tokens: last.total,
cache_hit_pct: lastCacheHitPct
} : void 0
},
context: {
used_tokens: usedTokens,
max_tokens: maxTokens,
pct_used: pctUsed
},
cost: {
turn_usd: typeof state.turnUsd === "number" ? state.turnUsd : null,
available: typeof state.turnUsd === "number"
},
timing: { duration_ms: typeof state.durationMs === "number" ? state.durationMs : null },
identity: {
name: state.identity?.name ?? null,
emoji: state.identity?.emoji ?? null,
avatar: state.identity?.avatar ?? null
},
session: { id: state.sessionId ?? null }
};
}
//#endregion
//#region src/auto-reply/usage-bar/default-template.ts
const DEFAULT_USAGE_BAR_TEMPLATE = {
schema: "openclaw.usageBar.v1",
scales: {
braille: "⠐⡀⡄⡆⡇⣇⣧⣷⣿",
block: "░▏▎▍▌▋▊▉█",
shade: "░▒▓█",
moon: "🌑🌘🌗🌖🌕",
level: "▁▂▃▄▅▆▇█",
weather: [
"🥶",
"☁️",
"🌥",
"⛅️",
"🌤",
"☀️"
],
plants: [
"",
"🍂",
"🌱",
"☘️",
"🍀",
"🌿"
],
moons6: [
"🌑",
"🌚",
"🌘",
"🌗",
"🌖",
"🌝"
]
},
aliases: {
models: {
"claude-opus-4-6": "opus46",
"claude-opus-4-8": "opus48",
"claude-sonnet-4-6": "sonnet46",
"claude-haiku-4-5": "haiku45",
"gpt-5.5": "gpt5.5"
},
reasoning: {
off: "🌑",
minimal: "🌚",
low: "🌘",
medium: "🌗",
high: "🌕",
xhigh: "🌝"
}
},
output: {
sep: "",
default: [
{ text: "{model.provider}{identity.emoji|🤖}{model.display_name|alias:models}" },
{
map: "model.is_fallback",
cases: { true: "🔄" }
},
{
map: "model.is_override",
cases: { true: "📌" }
},
{
when: "model.reasoning",
text: "{model.reasoning|alias:reasoning}"
},
{
map: "state.fast_mode",
cases: {
true: "⚡️",
false: "🐌"
}
},
{
when: "context.max_tokens",
text: "\xA0| 📚[{context.pct_used|meter:5:braille}]{context.max_tokens|num}"
},
{
when: "cost.turn_usd",
text: "\xA0💰{cost.turn_usd|fixed:4}"
}
],
surfaces: { discord: [
{ text: "-# -\n" },
{ text: "-# {model.provider}{identity.emoji|🤖}{model.display_name|alias:models}" },
{
map: "model.is_fallback",
cases: { true: "🔄" }
},
{
map: "model.is_override",
cases: { true: "📌" }
},
{
when: "model.reasoning",
text: "{model.reasoning|alias:reasoning}"
},
{
map: "state.fast_mode",
cases: {
true: "⚡️",
false: "🐌"
}
},
{
when: "context.max_tokens",
text: "\xA0| 📚[{context.pct_used|meter:5:braille}]{context.max_tokens|num}"
},
{
when: "cost.turn_usd",
text: "\xA0💰{cost.turn_usd|fixed:4}"
}
] }
}
};
//#endregion
//#region src/auto-reply/usage-bar/template.ts
const fileCache = /* @__PURE__ */ new Map();
/** Maximum number of template file paths to cache concurrently. */
const MAX_CACHED_TEMPLATE_FILES = 64;
const warnedTemplateOverrides = createDedupeCache({
maxSize: 256,
ttlMs: 0
});
const usageTemplateLog = createSubsystemLogger("usage-template");
function expandPath(p) {
if (!p.startsWith("~")) return resolve(p);
return resolve(expandHomePrefix(p, { home: homedir() }));
}
function hasPieces(value) {
return Array.isArray(value) && value.some(isRecord);
}
function hasOutputPieces(output) {
if (!isRecord(output)) return false;
if (hasPieces(output.default)) return true;
const surfaces = output.surfaces;
return isRecord(surfaces) && Object.values(surfaces).some((surfacePieces) => hasPieces(surfacePieces));
}
function isEmptyTemplate(value) {
if (!isRecord(value)) return false;
if (Object.keys(value).length === 0) return true;
if ("segments" in value && Array.isArray(value.segments)) return value.segments.length === 0;
const output = value.output;
return isRecord(output) && !hasOutputPieces(output);
}
function isUsableTemplate(value) {
if (!isRecord(value)) return false;
if (hasOutputPieces(value.output) || hasPieces(value.segments)) return true;
const surfaces = value.surfaces;
return isRecord(surfaces) && Object.values(surfaces).some((surface) => isRecord(surface) && hasPieces(surface.segments));
}
function getErrorCode(error) {
if (typeof error !== "object" || error === null || !("code" in error)) return;
const code = error.code;
return typeof code === "string" ? code : void 0;
}
function warnInvalidUsageTemplate(source, reason, path) {
const key = `${source}:${reason}:${path ?? ""}`;
if (warnedTemplateOverrides.check(key)) return;
usageTemplateLog.warn("configured usage template could not be used; using built-in footer", {
source,
reason,
...path ? { path } : {}
});
}
function parseTemplate(value) {
if (isUsableTemplate(value)) return { template: value };
return isEmptyTemplate(value) ? {} : { reason: "unsupported-shape" };
}
function readTemplateFile(path) {
let raw;
try {
raw = readFileSync(path, "utf8");
} catch (error) {
return getErrorCode(error) === "ENOENT" ? {} : { reason: "unreadable" };
}
if (raw.trim().length === 0) return {};
try {
return parseTemplate(JSON.parse(raw));
} catch {
return { reason: "invalid-json" };
}
}
function cacheTemplateFile(path) {
const result = readTemplateFile(path);
if (result.reason) warnInvalidUsageTemplate("file", result.reason, path);
if (!fileCache.has(path) && fileCache.size >= MAX_CACHED_TEMPLATE_FILES) {
const oldestKey = fileCache.keys().next().value;
if (oldestKey !== void 0) {
fileCache.get(oldestKey)?.watcher?.close();
fileCache.delete(oldestKey);
}
}
const entry = { template: result.template };
if (entry.template) try {
const watcher = watch(path, { persistent: false }, () => {
const next = readTemplateFile(path);
if (next.reason) warnInvalidUsageTemplate("file", next.reason, path);
entry.template = next.template;
});
watcher.on("error", () => {
watcher.close();
entry.watcher = void 0;
entry.template = void 0;
});
entry.watcher = watcher;
} catch {}
fileCache.set(path, entry);
return entry.template;
}
function loadUsageBarTemplate(configured) {
if (!configured) return DEFAULT_USAGE_BAR_TEMPLATE;
if (typeof configured === "object") {
const result = parseTemplate(configured);
if (result.reason) warnInvalidUsageTemplate("inline", result.reason);
return result.template ?? DEFAULT_USAGE_BAR_TEMPLATE;
}
const path = expandPath(configured);
const cached = fileCache.get(path);
return (cached ? cached.template ?? (cached.watcher ? void 0 : cacheTemplateFile(path)) : cacheTemplateFile(path)) ?? DEFAULT_USAGE_BAR_TEMPLATE;
}
function clearUsageBarTemplateCacheForTest() {
for (const entry of fileCache.values()) entry.watcher?.close();
fileCache.clear();
warnedTemplateOverrides.clear();
}
if (process.env.VITEST || false) globalThis[Symbol.for("openclaw.usageBarTemplateTestApi")] = { clearUsageBarTemplateCacheForTest };
//#endregion
//#region src/auto-reply/usage-bar/translator.ts
function toGlyphs(scale) {
if (Array.isArray(scale)) return scale.filter((g) => typeof g === "string");
if (typeof scale === "string") return Array.from(scale);
return [];
}
function num(value) {
if (value === null || value === void 0 || value === "") return "";
const n = Number(value);
if (!Number.isFinite(n)) return "";
if (Math.abs(n) >= 1e3) {
const v = n / 1e3;
return Math.abs(v) < 10 ? `${v.toFixed(1)}k` : `${Math.round(v)}k`;
}
return String(Math.trunc(n));
}
function fixed(value, digits) {
if (value === null || value === void 0 || value === "") return "";
const n = Number(value);
if (!Number.isFinite(n)) return "";
return n.toFixed(digits);
}
function dur(value) {
if (value === null || value === void 0 || value === "") return "";
const raw = Number(value);
if (!Number.isFinite(raw)) return "";
const s = Math.max(0, Math.trunc(raw));
if (s >= 86400) return `${(s / 86400).toFixed(1)}d`;
if (s >= 3600) {
const m = Math.floor(s % 3600 / 60);
return `${Math.floor(s / 3600)}h${String(m).padStart(2, "0")}m`;
}
return `${Math.floor(s / 60)}m`;
}
function pct(value) {
if (value === null || value === void 0 || value === "") return "";
const n = Number(value);
return Number.isFinite(n) ? `${Math.round(n)}%` : "";
}
function inv(value) {
if (value === null || value === void 0 || value === "") return value;
const n = Number(value);
if (!Number.isFinite(n)) return value;
return 100 - Math.max(0, Math.min(100, n));
}
function norm(value) {
const n = Number(value);
if (value === null || value === void 0 || !Number.isFinite(n)) return 0;
return Math.max(0, Math.min(100, n)) / 100;
}
function meter(value, width, scale) {
const glyphs = toGlyphs(scale);
if (glyphs.length < 2 || width < 1) return "";
const empty = expectDefined(glyphs[0], "glyphs entry at 0");
const full = expectDefined(glyphs[glyphs.length - 1], "glyphs entry at glyphs.length 1");
const total = norm(value) * width;
const fullc = Math.trunc(total);
const cells = [];
for (let i = 0; i < Math.min(fullc, width); i++) cells.push(full);
if (cells.length < width) cells.push(expectDefined(glyphs[Math.round((total - fullc) * (glyphs.length - 1))], "glyphs entry at math.round((total fullc) * (glyphs.length 1))"));
while (cells.length < width) cells.push(empty);
return cells.slice(0, width).join("");
}
const VERB_NAMES = /* @__PURE__ */ new Set([
"num",
"fixed",
"dur",
"pct",
"inv",
"alias",
"meter"
]);
function parseBoundedIntegerArg(raw, options) {
const value = raw === void 0 ? options.defaultValue : parseStrictInteger(raw);
return asSafeIntegerInRange(value, options);
}
function applyVerb(name, args, value, vocab) {
switch (name) {
case "num": return num(value);
case "fixed": {
const digits = parseBoundedIntegerArg(args[0], {
defaultValue: 2,
min: 0,
max: 100
});
return digits === void 0 ? "" : fixed(value, digits);
}
case "dur": return dur(value);
case "pct": return pct(value);
case "inv": return inv(value);
case "alias": {
const aliases = isRecord(vocab["_aliases"]) ? vocab["_aliases"] : {};
const table = args[0] && isRecord(aliases[args[0]]) ? aliases[args[0]] : {};
const key = String(value);
if (Object.hasOwn(table, key)) return table[key];
const lower = key.toLowerCase();
return Object.hasOwn(table, lower) ? table[lower] : value;
}
case "meter": {
const width = parseBoundedIntegerArg(args[0]?.trim() ? args[0] : void 0, {
defaultValue: 5,
min: 1,
max: 100
});
const scale = args.length > 1 ? vocab[expectDefined(args[1], "args entry at 1")] : void 0;
return width === void 0 ? "" : meter(value, width, scale);
}
default: return String(value);
}
}
function getPath(ctx, path) {
let cur = ctx;
for (const part of path.split(".")) {
if (!isRecord(cur)) return;
cur = cur[part];
if (cur === null || cur === void 0) return;
}
return cur;
}
const TOKEN = /\{([^}]+)\}/g;
function interp(text, ctx, vocab) {
return text.replace(TOKEN, (_match, body) => {
const parts = body.split("|");
let val = getPath(ctx, (parts[0] ?? "").trim());
const ops = [];
let fallback;
for (const segRaw of parts.slice(1)) {
const seg = segRaw.trim();
const name = expectDefined(seg.split(":")[0], "seg.split(\":\") entry at 0");
if (VERB_NAMES.has(name)) ops.push({
name,
args: seg.split(":").slice(1)
});
else fallback = seg;
}
if (val === null || val === void 0 || val === "") return fallback ?? "";
for (const op of ops) val = applyVerb(op.name, op.args, val, vocab);
return String(val);
});
}
function renderSegment(seg, ctx, vocab) {
if ("when" in seg) {
const v = getPath(ctx, String(seg.when));
if (v === null || v === void 0 || v === false || v === "") return null;
}
if ("map" in seg) {
const v = getPath(ctx, String(seg.map));
const key = typeof v === "boolean" ? String(v) : String(v);
const cases = isRecord(seg.cases) ? seg.cases : {};
const hit = Object.hasOwn(cases, key) ? cases[key] : cases["_default"];
return typeof hit === "string" ? hit : null;
}
if ("each" in seg) {
const arr = getPath(ctx, String(seg.each));
const items = Array.isArray(arr) ? arr : [];
const itemTpl = typeof seg.item === "string" ? seg.item : "";
const names = Array.isArray(seg.item_scales) ? seg.item_scales : void 0;
const parts = [];
items.forEach((el, i) => {
let iv = vocab;
if (names && names.length > 0) iv = {
...vocab,
"*": vocab[expectDefined(names[Math.min(i, names.length - 1)], "names entry at math.min(i, names.length 1)")]
};
const r = interp(itemTpl, el, iv);
if (r) parts.push(r);
});
const join = typeof seg.join === "string" ? seg.join : " ";
const body = parts.join(join);
if (!body) return null;
const prefix = typeof seg.text === "string" ? seg.text : "";
return prefix ? `${prefix} ${body}` : body;
}
if ("text" in seg) return interp(String(seg.text), ctx, vocab) || null;
return null;
}
function resolveLayout(template, surface) {
const output = template.output;
if (isRecord(output)) {
const surfaces = isRecord(output.surfaces) ? output.surfaces : {};
let pieces = typeof surface === "string" ? surfaces[surface] : void 0;
if (pieces === void 0) pieces = output.default;
return {
sep: typeof output.sep === "string" ? output.sep : "",
pieces: Array.isArray(pieces) ? pieces : []
};
}
const ov = typeof surface === "string" && isRecord(template.surfaces) && isRecord(template.surfaces[surface]) ? template.surfaces[surface] : {};
return {
sep: typeof ov.sep === "string" ? ov.sep : typeof template.sep === "string" ? template.sep : " ",
pieces: Array.isArray(ov.segments) ? ov.segments : Array.isArray(template.segments) ? template.segments : []
};
}
function renderUsageBar(template, contract) {
try {
const { sep, pieces } = resolveLayout(template, contract.surface);
const vocab = {
...isRecord(template.ramps) ? template.ramps : {},
...isRecord(template.series) ? template.series : {},
...isRecord(template.scales) ? template.scales : {}
};
vocab["_aliases"] = isRecord(template.aliases) ? template.aliases : {};
const out = [];
for (const piece of pieces) if (isRecord(piece)) {
const r = renderSegment(piece, contract, vocab);
if (r) out.push(r);
}
return out.join(sep);
} catch {
return "";
}
}
//#endregion
//#region src/auto-reply/reply/agent-runner-usage-line.ts
const formatResponseUsageLine = (params) => {
const usage = params.usage;
if (!usage) return null;
const input = usage.input;
const output = usage.output;
const inputLabel = typeof input === "number" ? formatTokenCount(input) : "?";
const outputLabel = typeof output === "number" ? formatTokenCount(output) : "?";
const cacheRead = typeof usage.cacheRead === "number" ? usage.cacheRead : void 0;
const cacheWrite = typeof usage.cacheWrite === "number" ? usage.cacheWrite : void 0;
const canPriceUsage = usage.cost !== void 0 || typeof input === "number" && typeof output === "number";
const cost = params.showCost && canPriceUsage ? estimateAggregateUsageCost(params) : void 0;
const costLabel = params.showCost ? formatUsd(cost) : void 0;
if (typeof input !== "number" && typeof output !== "number" && !costLabel) return null;
return `Usage: ${inputLabel} in / ${outputLabel} out${typeof cacheRead === "number" && cacheRead > 0 || typeof cacheWrite === "number" && cacheWrite > 0 ? ` · cache ${formatTokenCount(cacheRead ?? 0)} cached / ${formatTokenCount(cacheWrite ?? 0)} new` : ""}${costLabel ? ` · est ${costLabel}` : ""}`;
};
const resolveResponseUsageLine = (params) => {
const responseUsageMode = resolveEffectiveResponseUsage(params.sessionRaw, params.config.messages?.responseUsage, params.channel);
const showCost = responseUsageMode === "full";
const hasUsage = showCost ? hasBillableUsage(params.usage) : hasNonzeroUsage(params.usage);
if (responseUsageMode === "off" || !hasUsage || params.preserveUserFacingSessionState === true) return;
const formatted = formatResponseUsageLine({
usage: params.usage,
showCost,
provider: params.provider,
model: params.model,
config: params.config,
agentDir: params.agentDir,
allowPluginNormalization: false
});
const usageTemplate = responseUsageMode === "full" && params.replyUsageState ? loadUsageBarTemplate(params.config.messages?.usageTemplate) : void 0;
const rendered = usageTemplate && params.replyUsageState ? renderUsageBar(usageTemplate, buildUsageContract(params.replyUsageState, params.channel)) : void 0;
if (rendered) return rendered;
return formatted ?? void 0;
};
const appendUsageLine = (payloads, line) => {
let index = -1;
for (let i = payloads.length - 1; i >= 0; i -= 1) if (payloads[i]?.text) {
index = i;
break;
}
if (index === -1) return [...payloads, {
text: line,
isStatusNotice: true
}];
const existing = expectDefined(payloads[index], "payloads entry at index");
const existingText = existing.text ?? "";
const separator = existingText.endsWith("\n") ? "" : "\n";
const next = {
...existing,
text: `${existingText}${separator}${line}`
};
const metadata = getReplyPayloadMetadata(existing);
const nextWithMetadata = metadata ? setReplyPayloadMetadata(next, {
...metadata,
...metadata.sourceReplyTranscriptMirror ? { sourceReplyTranscriptMirror: {
...metadata.sourceReplyTranscriptMirror,
text: next.text
} } : {}
}) : next;
const updated = payloads.slice();
updated[index] = nextWithMetadata;
return updated;
};
//#endregion
//#region src/auto-reply/reply/stranded-reply-recovery.ts
const STRANDED_REPLY_RETRY_MARKER = "stranded-reply-retry";
const STRANDED_REPLY_DELIVERY_FAILURE_TEXT = "I generated a reply but could not deliver it to this chat. Please try again.";
function buildStrandedReplyDeliveryFailurePayload() {
return markReplyPayloadForSourceSuppressionDelivery({
text: STRANDED_REPLY_DELIVERY_FAILURE_TEXT,
isError: true,
isStatusNotice: true
});
}
/** Resolve the one allowed recovery action for a final that missed source delivery. */
function resolveStrandedReplyRecovery(params) {
if (!shouldClassifyPrivateMessageToolFinal(params)) return { kind: "none" };
const classification = classifyPrivateMessageToolFinal(params);
if (params.base.strandedReplyRetry === true) return {
kind: "diagnostic",
payload: buildStrandedReplyDeliveryFailurePayload(),
warn: classification === "substantive"
};
if (classification !== "substantive") return { kind: "none" };
return {
kind: "retry",
run: buildStrandedReplyRetryFollowupRun(params.base, {
finalText: params.finalText,
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode
})
};
}
function buildStrandedReplyRetryPrompt(finalText) {
return formatSystemTurnPrompt(`Your previous reply was not delivered to the conversation because you did not call message(action=send). Your reply text was:
"${finalText}"\n\nPlease deliver this reply now by calling message(action=send). Do not add any extra commentary; just deliver the original reply.`);
}
/** Build the one-shot recovery followup that re-prompts message(action=send). */
function buildStrandedReplyRetryFollowupRun(base, params) {
return {
...base,
prompt: buildStrandedReplyRetryPrompt(params.finalText),
summaryLine: STRANDED_REPLY_RETRY_MARKER,
strandedReplyRetry: true,
disableCollectBatching: true,
transcriptPrompt: void 0,
userTurnTranscriptRecorder: void 0,
currentInboundContext: void 0,
turnAdoptionLifecycle: void 0,
replyOperationRunStates: void 0,
run: {
...base.run,
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
suppressNextUserMessagePersistence: true
}
};
}
//#endregion
//#region src/auto-reply/reply/agent-runner-result-complete.ts
async function completeReplyAgentRun(input) {
const { context, accounting, prepared } = input;
const { activeIsNewSession, activeSessionStore, cfg, followupRun, isHeartbeat, opts, preflightCompactionApplied, queueKey, resolvedBlockStreamingBreak, resolvedQueue, resolvedVerboseLevel, returnWithQueuedFollowupDrain, runFollowupTurn, runtimePolicySessionKey, sessionCtx, sessionKey, storePath } = context;
const { autoCompactionCount, runResult, verboseEnabled } = accounting;
const { completedSourceReplyDelivery, guardedReplyPayloads, responseUsageLine } = prepared;
let { activeSessionEntry } = prepared;
let finalPayloads = guardedReplyPayloads;
const prefixNotices = [];
if (verboseEnabled && activeIsNewSession) prefixNotices.push({ text: `🧭 New session: ${followupRun.run.sessionId}` });
if (autoCompactionCount > 0) {
const previousSessionId = accounting.expectedSession.sessionId;
const count = accounting.compactionCount;
const refreshedSessionEntry = sessionKey && activeSessionStore ? activeSessionStore[sessionKey] : void 0;
if (refreshedSessionEntry) {
activeSessionEntry = refreshedSessionEntry;
refreshQueuedFollowupSession({
key: queueKey,
previousSessionId,
nextSessionId: refreshedSessionEntry.sessionId,
nextSessionFile: queueKey
});
}
if (sessionKey) {
const contextContent = await readPostCompactionContext(followupRun.run.workspaceDir, {
cfg,
agentId: followupRun.run.agentId
});
if (contextContent) enqueueSystemEvent(contextContent, { sessionKey });
}
if (verboseEnabled) {
const suffix = typeof count === "number" ? ` (count ${count})` : "";
prefixNotices.push({ text: `🧹 Auto-compaction complete${suffix}.` });
}
}
const prefixPayloads = [...prefixNotices];
const trailingPluginStatusPayload = await buildReplyDiagnosticsPayload({
activeSessionEntry,
followupRun,
accounting,
cfg,
storePath,
userText: sessionCtx.commandText || sessionCtx.agentText,
resolvedVerboseLevel,
resolvedBlockStreamingBreak,
preflightCompactionApplied
});
const isHookBlockedRun = runResult.meta?.error?.kind === "hook_block";
const rawAssistantText = isHookBlockedRun ? void 0 : runResult.meta?.finalAssistantRawText ?? runResult.meta?.finalAssistantVisibleText;
if (prefixPayloads.length > 0) finalPayloads = [...prefixPayloads, ...finalPayloads];
if (trailingPluginStatusPayload) finalPayloads = [...finalPayloads, trailingPluginStatusPayload];
if (responseUsageLine) finalPayloads = appendUsageLine(finalPayloads, responseUsageLine);
if (isHookBlockedRun) finalPayloads = markBeforeAgentRunBlockedPayloads(finalPayloads);
const isStrandedReplyRetryRun = followupRun.strandedReplyRetry === true;
if (sessionKey && storePath && (finalPayloads.length > 0 || isStrandedReplyRetryRun)) {
const sourceReplyPolicy = resolveSourceReplyPolicy({
cfg,
sessionCtx,
sessionEntry: activeSessionEntry,
sessionKey,
runtimePolicySessionKey,
opts
});
const assistantFinalText = normalizeAssistantFinalDeliveryText(typeof runResult.meta?.finalAssistantVisibleText === "string" ? runResult.meta.finalAssistantVisibleText : rawAssistantText ?? "");
const recovery = resolveStrandedReplyRecovery({
base: followupRun,
finalText: assistantFinalText,
sourceReplyDeliveryMode: sourceReplyPolicy.sourceReplyDeliveryMode,
sendPolicyDenied: sourceReplyPolicy.sendPolicyDenied,
successfulSourceReplyDelivery: completedSourceReplyDelivery,
isHeartbeat,
isRoomEvent: sessionCtx.InboundEventKind === "room_event"
});
if (recovery.kind === "retry" || recovery.kind === "diagnostic" && recovery.warn) warnPrivateMessageToolFinal({
sessionKey,
channel: sessionCtx.OriginatingChannel ?? sessionCtx.Surface ?? sessionCtx.Provider ?? sessionDeliveryChannel(activeSessionEntry),
finalTextLength: assistantFinalText.trim().length
});
if (recovery.kind === "diagnostic") finalPayloads = [...finalPayloads, recovery.payload];
else if (recovery.kind === "retry") {
if (!enqueueFollowupRun(queueKey, recovery.run, resolvedQueue, "none", runFollowupTurn, false, { position: "front" })) finalPayloads = [...finalPayloads, buildStrandedReplyDeliveryFailurePayload()];
}
const recoverablePendingFinalText = buildRecoverablePendingFinalDeliveryText(normalizePendingFinalRecoveryPayloads(finalPayloads));
const pendingText = sourceReplyPolicy.suppressDelivery ? "" : recoverablePendingFinalText ?? "";
const heartbeatAckMaxChars = 300;
const resolvedPendingText = isHeartbeat ? (() => {
const stripped = stripHeartbeatToken(pendingText, {
mode: "heartbeat",
maxAckChars: heartbeatAckMaxChars
});
return stripped.shouldSkip ? "" : stripped.text || pendingText;
})() : pendingText;
const sendableFinalPayloads = sourceReplyPolicy.suppressDelivery ? [] : finalPayloads.filter((payload) => normalizePendingFinalDeliveryPayloads([payload]).length > 0);
if (sendableFinalPayloads.length > 0) {
const pendingFinalDeliveryIntentId = crypto.randomUUID();
const expectedSessionId = activeSessionEntry?.sessionId ?? followupRun.run.sessionId;
const pendingFinalDeliveries = sendableFinalPayloads.map((payload) => {
const deliveryId = crypto.randomUUID();
setReplyPayloadMetadata(payload, { pendingFinalDeliveryCompletion: {
deliveryId,
intentId: pendingFinalDeliveryIntentId,
...activeSessionEntry?.restartRecoveryDeliveryRunId ? { recoveryRunId: activeSessionEntry.restartRecoveryDeliveryRunId } : {},
sessionId: expectedSessionId,
sessionKey,
storePath
} });
return {
id: deliveryId,
state: "prepared"
};
});
const pendingFinalDeliveryContext = resolveReplyRunDeliveryContext({
cfg,
sessionCtx,
sessionEntry: activeSessionEntry,
sessionKey,
runtimePolicySessionKey,
opts
});
const persistedPendingFinalDelivery = await updateSessionEntry({
storePath,
sessionKey
}, (entry) => entry.sessionId === expectedSessionId ? {
pendingFinalDelivery: {
...resolvedPendingText ? {
kind: "replayable",
text: resolvedPendingText
} : { kind: "transport-only" },
intentId: pendingFinalDeliveryIntentId,
deliveries: pendingFinalDeliveries,
context: pendingFinalDeliveryContext,
createdAt: Date.now()
},
updatedAt: Date.now()
} : null, {
skipMaintenance: true,
takeCacheOwnership: true
});
if (persistedPendingFinalDelivery?.sessionId !== expectedSessionId || persistedPendingFinalDelivery.pendingFinalDelivery?.intentId !== pendingFinalDeliveryIntentId) throw new Error("pending final delivery session changed or was deleted");
}
}
return returnWithQueuedFollowupDrain(finalPayloads.length === 1 ? finalPayloads[0] : finalPayloads);
}
//#endregion
//#region src/auto-reply/reply/agent-runner-helpers.ts
/** Helper predicates and gates used while streaming agent-runner payloads. */
const hasAudioMedia = (urls) => Boolean(urls?.some((url) => isAudioFileName(url)));
/** Returns true when a payload carries audio media. */
const isAudioPayload = (payload) => hasAudioMedia(resolveSendableOutboundReplyParts(payload).mediaUrls);
const VERBOSE_GATE_SESSION_REFRESH_MS = 250;
function readCurrentVerboseLevel(params) {
if (!params.sessionKey || !params.storePath) return;
try {
const entry = loadSessionEntryReadOnly({
storePath: params.storePath,
sessionKey: params.sessionKey,
clone: false
});
return typeof entry?.verboseLevel === "string" ? normalizeVerboseLevel(entry.verboseLevel) : void 0;
} catch {
return;
}
}
function createCurrentVerboseLevelResolver(params) {
let cachedLevel;
let cachedAtMs = Number.NEGATIVE_INFINITY;
return () => {
if (!params.sessionKey || !params.storePath) return;
const now = Date.now();
if (now - cachedAtMs < VERBOSE_GATE_SESSION_REFRESH_MS) return cachedLevel;
cachedLevel = readCurrentVerboseLevel(params);
cachedAtMs = now;
return cachedLevel;
};
}
function createVerboseGate(params, shouldEmit) {
const resolveCurrentVerboseLevel = createCurrentVerboseLevelResolver(params);
return () => shouldEmit(params.verboseLevelOverride ?? resolveCurrentVerboseLevel() ?? params.resolvedVerboseLevel);
}
/** Creates the visibility gate for tool result summaries. */
const createShouldEmitToolResult = (params) => createVerboseGate(params, (level) => level !== "off");
/** Creates the visibility gate for command/tool output streams. */
const createShouldEmitToolOutput = (params) => createVerboseGate(params, (level) => level === "full");
/** Sends typing signals for visible text payloads when typing is enabled. */
const signalTypingIfNeeded = async (payloads, typingSignals) => {
if (payloads.some((payload) => hasOutboundReplyContent(payload, { trimText: true }))) await typingSignals.signalRunStart();
};
//#endregion
//#region src/auto-reply/reply/agent-runner-payloads.ts
/** Builds final reply payloads after sanitization, media normalization, and dedupe. */
const replyPayloadsDedupeRuntimeLoader = createLazyImportLoader(() => import("./reply-payloads-dedupe.runtime.js"));
function loadReplyPayloadsDedupeRuntime() {
return replyPayloadsDedupeRuntimeLoader.load();
}
async function normalizeReplyPayloadMedia(params) {
if (!params.normalizeMediaPaths || !resolveSendableOutboundReplyParts(params.payload).hasMedia) return params.payload;
const normalized = await params.normalizeMediaPaths(params.payload);
return copyReplyPayloadMetadata(params.payload, normalized);
}
async function normalizeSentMediaUrlsForDedupe(params) {
if (params.sentMediaUrls.length === 0 || !params.normalizeMediaPaths) return [...params.sentMediaUrls];
const normalizedUrls = [];
const seen = /* @__PURE__ */ new Set();
for (const raw of params.sentMediaUrls) {
const trimmed = raw.trim();
if (!trimmed) continue;
if (!seen.has(trimmed)) {
seen.add(trimmed);
normalizedUrls.push(trimmed);
}
try {
const normalized = await params.normalizeMediaPaths({
mediaUrl: trimmed,
mediaUrls: [trimmed]
});
const normalizedMediaUrls = resolveSendableOutboundReplyParts(normalized).mediaUrls;
for (const mediaUrl of normalizedMediaUrls) {
const candidate = mediaUrl.trim();
if (!candidate || seen.has(candidate)) continue;
seen.add(candidate);
normalizedUrls.push(candidate);
}
} catch (err) {
logVerbose(`messaging tool sent-media normalization failed: ${String(err)}`);
}
}
return normalizedUrls;
}
function shouldKeepPayloadDuringSilentTurn(payload) {
if (payload.isError) return true;
return payload.audioAsVoice === true && resolveSendableOutboundReplyParts(payload).hasMedia;
}
function sanitizeFinalReplyText(payload, text, conversationContext) {
if (!text) return text;
return payload.isError ? renderUserFacingText(text, {
errorContext: true,
conversationContext
}) : sanitizeUserFacingText(text, { conversationContext });
}
function sanitizeHeartbeatPayload(payload, conversationContext) {
const text = payload.text;
if (!text) return payload;
const withoutLegacyBlocks = stripLegacyBracketToolCallBlocks(text);
const cleaned = sanitizeFinalReplyText(payload, withoutLegacyBlocks, conversationContext);
if (cleaned === text) return payload;
if (withoutLegacyBlocks !== text) logVerbose("Stripped legacy tool-call block from heartbeat reply");
return copyPayloadWithSanitizedText(payload, cleaned, conversationContext);
}
function copyPayloadWithSanitizedText(payload, text, conversationContext) {
const sanitizedText = sanitizeFinalReplyText(payload, text, conversationContext);
const next = copyReplyPayloadMetadata(payload, {
...payload,
text: sanitizedText
});
const mirror = getReplyPayloadMetadata(payload)?.sourceReplyTranscriptMirror;
if (!mirror?.text) return next;
setReplyPayloadMetadata(next, { sourceReplyTranscriptMirror: {
...mirror,
text: sanitizeFinalReplyText(payload, mirror.text, conversationContext) || void 0
} });
return next;
}
/** Builds final outbound payloads from agent output and message-tool delivery evidence. */
async function buildReplyPayloads(params) {
let didLogHeartbeatStrip = params.didLogHeartbeatStrip;
const sanitizedPayloads = [];
if (params.isHeartbeat) for (const payload of params.payloads) sanitizedPayloads.push(sanitizeHeartbeatPayload(payload, params.conversationContext));
else for (const payload of params.payloads) {
let text = payload.text;
if (payload.isError && text && isBunFetchSocketError(text)) text = formatBunFetchSocketError(text);
if (!text || !text.includes("HEARTBEAT_OK")) {
sanitizedPayloads.push(copyPayloadWithSanitizedText(payload, text, params.conversationContext));
continue;
}
const stripped = stripHeartbeatToken(text, { mode: "message" });
if (stripped.didStrip && !didLogHeartbeatStrip) {
didLogHeartbeatStrip = true;
logVerbose("Stripped stray HEARTBEAT_OK token from reply");
}
const hasMedia = resolveSendableOutboundReplyParts(payload).hasMedia;
if (stripped.shouldSkip && !hasMedia) continue;
sanitizedPayloads.push(copyPayloadWithSanitizedText(payload, stripped.text, params.conversationContext));
}
const messageProvider = resolveOriginMessageProvider({
originatingChannel: params.originatingChannel,
provider: params.messageProvider
});
const accountId = params.accountId;
const replyDelivery = createReplyDeliveryContext(params.replyToMode, params.originatingChatType);
const replyDeliverySource = messageProvider ? {
channel: messageProvider,
...accountId ? { accountId } : {}
} : void 0;
const resolveThreading = params.applyReplyToMode ? resolveReplyThreadingPayloads : applyReplyThreading;
const replyTaggedPayloads = (await Promise.all(resolveThreading({
payloads: sanitizedPayloads,
replyToMode: params.replyToMode,
replyToChannel: params.replyToChannel,
currentMessageId: params.currentMessageId,
replyThreading: params.replyThreading
}).map(async (payload) => {
const parsed = normalizeReplyPayloadDirectives({
payload,
currentMessageId: params.currentMessageId,
silentToken: SILENT_REPLY_TOKEN,
parseMode: "always",
extractMarkdownImages: params.extractMarkdownImages
});
const mediaNormalizedPayload = await normalizeReplyPayloadMedia({
payload: parsed.payload,
normalizeMediaPaths: params.normalizeMediaPaths
});
if (parsed.isSilent) mediaNormalizedPayload.text = void 0;
return setReplyPayloadMetadata(mediaNormalizedPayload, {
replyDelivery,
...replyDeliverySource ? { replyDeliverySource } : {}
});
}))).filter(isRenderablePayload);
const silentFilteredPayloads = params.silentExpected ? replyTaggedPayloads.filter(shouldKeepPayloadDuringSilentTurn) : replyTaggedPayloads;
const threadedPayloads = params.applyReplyToMode ? silentFilteredPayloads.map(params.applyReplyToMode) : silentFilteredPayloads;
const shouldDropFinalPayloads = params.blockStreamingEnabled && Boolean(params.blockReplyPipeline?.didStream()) && !params.blockReplyPipeline?.isAborted();
const messagingToolSentTexts = params.messagingToolSentTexts ?? [];
const messagingToolSentTargets = params.messagingToolSentTargets ?? [];
const shouldCheckMessagingToolDedupe = messagingToolSentTexts.length > 0 || (params.messagingToolSentMediaUrls?.length ?? 0) > 0 || messagingToolSentTargets.length > 0;
let dedupedPayloads = threadedPayloads;
if (shouldCheckMessagingToolDedupe) {
const dedupeRuntime = await loadReplyPayloadsDedupeRuntime();
const originatingTo = params.originatingTo;
dedupedPayloads = [];
for (const payload of threadedPayloads) {
if (getReplyPayloadMetadata(payload)?.sourceReplyTranscriptMirror) {
dedupedPayloads.push(payload);
continue;
}
dedupedPayloads.push(...await dedupeRuntime.filterMessagingToolReplyPayload({
payload,
config: params.config,
messageProvider,
messagingToolSentTargets,
originatingTo,
originatingThreadId: params.originatingThreadId,
accountId,
sentMediaUrls: params.messagingToolSentMediaUrls,
sentTexts: messagingToolSentTexts,
normalizeSentMediaUrls: (sentMediaUrls) => normalizeSentMediaUrlsForDedupe({
sentMediaUrls,
normalizeMediaPaths: params.normalizeMediaPaths
})
}));
}
}
const directlySentTextFragmentsByAssistantMessage = /* @__PURE__ */ new Map();
for (const sentPayload of params.directlySentBlockPayloads ?? []) {
const sentText = sentPayload.text ?? resolveSendableOutboundReplyParts(sentPayload).trimmedText;
if (!sentText) continue;
const assistantMessageIndex = getReplyPayloadMetadata(sentPayload)?.assistantMessageIndex;
const fragments = directlySentTextFragmentsByAssistantMessage.get(assistantMessageIndex);
if (fragments) fragments.push(sentText);
else directlySentTextFragmentsByAssistantMessage.set(assistantMessageIndex, [sentText]);
}
const isDirectlySentBlockPayload = (payload) => {
const contentKey = createBlockReplyContentKey(payload);
if (!params.directlySentBlockKeys?.has(contentKey)) return false;
const assistantMessageIndex = getReplyPayloadMetadata(payload)?.assistantMessageIndex;
return assistantMessageIndex === void 0 || !params.directlySentBlockPayloads?.length || params.directlySentBlockPayloads.some((sentPayload) => getReplyPayloadMetadata(sentPayload)?.assistantMessageIndex === assistantMessageIndex && createBlockReplyContentKey(sentPayload) === contentKey);
};
const hasDirectlySentText = (payload) => {
if (isDirectlySentBlockPayload(payload)) return true;
const text = resolveSendableOutboundReplyParts(payload).trimmedText;
if (!text || !params.directlySentBlockPayloads?.length) return false;
const normalizedText = text.trim();
const assistantMessageIndex = getReplyPayloadMetadata(payload)?.assistantMessageIndex;
const applicableFragments = directlySentTextFragmentsByAssistantMessage.get(assistantMessageIndex);
return applicableFragments ? applicableFragments.join("").trim() === normalizedText : false;
};
const preserveUnsentMediaAfterBlockSend = (payload) => {
if (payload.isError || payload.isFallbackNotice) return payload;
const reply = resolveSendableOutboundReplyParts(payload);
if (!reply.hasMedia) {
if (hasOutboundReplyContent({
...payload,
text: void 0,
mediaUrl: void 0,
mediaUrls: void 0
}, { trimText: true }) ? params.blockReplyPipeline?.hasSentExactPayload?.(payload) : params.blockReplyPipeline?.hasSentPayload(payload)) return null;
return payload;
}
if (!reply.trimmedText) return payload;
const textOnlyPayload = copyReplyPayloadMetadata(payload, {
...payload,
mediaUrl: void 0,
mediaUrls: void 0,
audioAsVoice: void 0
});
if (!(params.blockReplyPipeline?.hasSentPayload(textOnlyPayload) ? true : hasDirectlySentText(textOnlyPayload))) return payload;
return copyReplyPayloadMetadata(payload, {
...payload,
text: void 0,
audioAsVoice: payload.audioAsVoice || void 0
});
};
const preserveDirectlyUnsentPayload = (payload) => {
const reply = resolveSendableOutboundReplyParts(payload);
if (!reply.hasMedia || !reply.trimmedText) return payload;
return preserveUnsentMediaAfterBlockSend(payload);
};
const contentSuppressedPayloads = shouldDropFinalPayloads ? dedupedPayloads.flatMap((payload) => preserveUnsentMediaAfterBlockSend(payload) ?? []) : params.blockStreamingEnabled ? dedupedPayloads.flatMap((payload) => params.blockReplyPipeline?.hasSentPayload(payload) || isDirectlySentBlockPayload(payload) ? [] : preserveDirectlyUnsentPayload(payload) ?? []) : params.directlySentBlockKeys?.size ? dedupedPayloads.flatMap((payload) => isDirectlySentBlockPayload(payload) ? [] : preserveDirectlyUnsentPayload(payload) ?? []) : dedupedPayloads;
const blockSentMediaUrls = await normalizeSentMediaUrlsForDedupe({
sentMediaUrls: [...params.blockStreamingEnabled ? params.blockReplyPipeline?.getSentMediaUrls() ?? [] : [], ...(params.directlySentBlockPayloads ?? []).flatMap((payload) => resolveSendableOutboundReplyParts(payload).mediaUrls)],
normalizeMediaPaths: params.normalizeMediaPaths
});
return {
replyPayloads: (blockSentMediaUrls.length > 0 ? (await loadReplyPayloadsDedupeRuntime()).filterMessagingToolMediaDuplicates({
payloads: contentSuppressedPayloads,
sentMediaUrls: blockSentMediaUrls
}) : contentSuppressedPayloads).filter(isRenderablePayload),
didLogHeartbeatStrip
};
}
//#endregion
//#region src/auto-reply/reply/agent-runner-reminder-guard.ts
/** Detects reminder commitments that were not backed by scheduled cron jobs. */
const UNSCHEDULED_REMINDER_NOTE = "Note: I did not schedule a reminder in this turn, so this will not trigger automatically.";
const REMINDER_COMMITMENT_PATTERNS = [
/\b(?:i\s*['’]?ll|i will)\s+(?:make sure to\s+)?(?:remind|ping|follow up|follow-up|check (?:back|on)|circle back)\b/i,
/\b(?:i\s*['’]?ll|i will)\s+(?:make sure to\s+)?remember\s+to\s+(?:(?:remind|ping|follow up|follow-up|check (?:back|on)|circle back)\b|(?:set|create|schedule)\s+(?:a\s+)?reminder\b)/i,
/\b(?:i\s*['’]?ll|i will)\s+(?:make sure to\s+)?remember\b[^.!?]{0,160}?(?:\s+and(?:\s+then)?|,\s*(?:(?:and\s+)?then)?)\s+(?:(?:i\s*['’]?ll|i will|will)\s+)?(?:make sure to\s+)?(?:remind|ping|follow up|follow-up|check (?:back|on)|circle back|(?:set|create|schedule)\s+(?:a\s+)?reminder)\b/i,
/\b(?:i\s*['’]?ll|i will)\s+(?:set|create|schedule)\s+(?:a\s+)?reminder\b/i
];
/** Returns true when text promises a reminder/follow-up without the guard note. */
function hasUnbackedReminderCommitment(text) {
const normalized = normalizeLowercaseStringOrEmpty(text);
if (!normalized.trim()) return false;
if (normalized.includes(normalizeLowercaseStringOrEmpty(UNSCHEDULED_REMINDER_NOTE))) return false;
return REMINDER_COMMITMENT_PATTERNS.some((pattern) => pattern.test(text));
}
/**
* Returns true when the cron store has at least one enabled job that shares the
* current session key. Used to suppress the "no reminder scheduled" guard note
* when an existing cron (created in a prior turn) already covers the commitment.
*/
async function hasSessionRelatedCronJobs(params) {
try {
const storePath = resolveCronJobsStorePath(params.cronStorePath);
const store = await loadCronJobsStore(storePath);
if (store.jobs.length === 0) return false;
if (params.sessionKey) return store.jobs.some((job) => job.enabled && job.sessionKey === params.sessionKey);
return false;
} catch {
return false;
}
}
/** Appends the unscheduled-reminder note to the first payload that needs it. */
function appendUnscheduledReminderNote(payloads) {
let appended = false;
return payloads.map((payload) => {
if (appended || payload.isError || typeof payload.text !== "string") return payload;
if (!hasUnbackedReminderCommitment(payload.text)) return payload;
appended = true;
const trimmed = payload.text.trimEnd();
return copyReplyPayloadMetadata(payload, {
...payload,
text: `${trimmed}\n\n${UNSCHEDULED_REMINDER_NOTE}`
});
});
}
//#endregion
//#region src/gateway/mcp-app-channel-action.ts
/** Mint one short-lived launch action only after the final reply route is known. */
function materializeMcpAppChannelPresentation(params) {
const origin = getTailscalePublishedOrigin();
if (!origin) return;
const runtime = peekSessionMcpRuntime({ sessionKey: params.sessionKey });
if (!runtime || runtime.mcpAppsEnabled !== true) return;
const nowMs = params.nowMs ?? Date.now();
const view = getMcpAppViewLease(params.view.viewId, runtime);
if (!view || view.expiresAtMs <= nowMs) return;
const ticket = createMcpAppStandaloneTicket({
sessionKey: params.sessionKey,
view,
nowMs
});
if (!ticket) return;
return { blocks: [{
type: "buttons",
buttons: [{
label: "Open app",
action: {
type: "web-app",
url: new URL(ticket.url, origin.origin).href
}
}]
}] };
}
//#endregion
//#region src/auto-reply/reply/mcp-app-channel-action.ts
function isEligibleTerminalPayload$1(payload) {
return Boolean(payload.text?.trim() && payload.isError !== true && isReplyPayloadTerminalContent(payload));
}
/** Attach one late-minted portable action to the final visible channel reply. */
function attachMcpAppChannelAction(params) {
if (!params.channel || params.channel === "webchat" || !params.sessionKey || !params.view) return params.payloads;
const index = params.payloads.findLastIndex(isEligibleTerminalPayload$1);
if (index < 0) return params.payloads;
const presentation = materializeMcpAppChannelPresentation({
sessionKey: params.sessionKey,
view: params.view
});
if (!presentation) return params.payloads;
const payloads = params.payloads.slice();
const payload = payloads[index];
payloads[index] = {
...payload,
presentation: payload.presentation ? {
...payload.presentation,
blocks: [...payload.presentation.blocks, ...presentation.blocks]
} : presentation
};
return payloads;
}
//#endregion
//#region src/auto-reply/reply/mcp-connect-channel-action.ts
function isEligibleTerminalPayload(payload) {
return Boolean(payload.text?.trim() && payload.isError !== true && isReplyPayloadTerminalContent(payload));
}
function attachMcpConnectChannelAction(params) {
if (!params.action) return params.payloads;
const index = params.payloads.findLastIndex(isEligibleTerminalPayload);
if (index < 0) return params.payloads;
const block = {
type: "buttons",
buttons: [{
label: `Connect ${params.action.serverName}`,
action: {
type: "url",
url: params.action.authorizationUrl
}
}]
};
const payloads = params.payloads.slice();
const payload = payloads[index];
payloads[index] = {
...payload,
presentation: payload.presentation ? {
...payload.presentation,
blocks: [...payload.presentation.blocks, block]
} : { blocks: [block] }
};
return payloads;
}
//#endregion
//#region src/auto-reply/reply/sessions-yield-acknowledgment.ts
function buildSessionsYieldAcknowledgmentPayload(params) {
const text = params.yieldAcknowledgment?.trim();
if (!params.yielded || !text || !params.isInteractive || params.isHeartbeat === true || params.silentExpected === true || params.isSubagentSession || params.hasExplicitSilentReply || params.hasVisibleMessageDelivery) return;
return markReplyPayloadForSourceSuppressionDelivery({ text });
}
//#endregion
//#region src/auto-reply/reply/agent-runner-result-payloads.ts
async function prepareReplyAgentPayloads(state) {
const { context, accounting } = state;
const { activeSessionStore, blockReplyPipeline, blockStreamingEnabled, cfg, followupRun, isHeartbeat, opts, replyMediaContext, replyOperation, replyRouteThreadId, replyThreadingOverride, replyToChannel, replyToMode, returnWithQueuedFollowupDrain, runStartedAt, runtimePolicySessionKey, sessionCtx, sessionKey, storePath, typingSignals } = context;
const { configuredFallbackModel, contextTokensUsed, directlySentBlockKeys, directlySentBlockPayloads, fallbackAttempts, fallbackExhausted, fallbackTransition, modelUsed, payloadArray: rawPayloadArray, preserveUserFacingSessionState, promptTokens, providerUsed, replyUsageState, runId, runResult, selectedModel, selectedProvider, sessionModel, terminalFailurePayload, usage } = accounting;
let { activeSessionEntry, didLogHeartbeatStrip } = accounting;
const deliberateSilentTerminalReply = hasDeliberateSilentTerminalReply(runResult);
if (deliberateSilentTerminalReply) opts?.onDeliberateSilentTerminalReply?.();
const implicitContinuation = runResult.meta?.continuationPending === true;
const pendingContinuation = runResult.meta?.yielded === true || implicitContinuation || (runResult.meta?.pendingToolCalls?.length ?? 0) > 0;
if (pendingContinuation && !implicitContinuation) opts?.onPendingContinuation?.();
let payloadArray = rawPayloadArray;
if (implicitContinuation && payloadArray[0]) payloadArray = [setReplyPayloadMetadata(markReplyPayloadForSourceSuppressionDelivery(payloadArray[0]), { continuationStatus: true }), ...payloadArray.slice(1)];
const successfulSourceReplyDelivery = hasSuccessfulSourceReplyDelivery({
blockReplyPipeline,
directlySentBlockKeys,
messagingToolSentTexts: runResult.messagingToolSentTexts,
messagingToolSentMediaUrls: runResult.messagingToolSentMediaUrls,
messagingToolSentTargets: runResult.messagingToolSentTargets
});
const committedMessagingToolSourceReplyDelivery = hasCommittedSourceReplyDeliveryEvidence(runResult);
const completedSourceReplyDelivery = hasCompletedSourceReplyDeliveryEvidence(runResult);
const visibleOutboundDelivery = hasVisibleOutboundDeliveryEvidence(runResult);
const successfulSideEffectDelivery = successfulSourceReplyDelivery || committedMessagingToolSourceReplyDelivery || visibleOutboundDelivery || runResult.didSendDeterministicApprovalPrompt === true;
const successfulTerminalDelivery = hasSuccessfulTerminalSourceReplyDelivery({
blockReplyPipeline,
directlySentBlockPayloads
}) || hasCompletedTerminalDeliveryEvidence(runResult);
const shouldDeliverTerminalFailure = Boolean(terminalFailurePayload && !successfulTerminalDelivery);
const fallbackFailureKnown = fallbackAttempts.length > 0 || configuredFallbackModel.persistedAutoFallback;
const hasSpecificFallbackFailure = fallbackTransition.fallbackActive && fallbackFailureKnown;
const isInteractive = followupRun.currentInboundEventKind !== "room_event" && (followupRun.run.inputProvenance?.kind === void 0 || followupRun.run.inputProvenance.kind === "external_user");
const yieldAcknowledgmentPayload = terminalFailurePayload ? void 0 : buildSessionsYieldAcknowledgmentPayload({
yielded: runResult.meta?.yielded === true,
yieldAcknowledgment: runResult.meta?.yieldAcknowledgment,
isInteractive,
isHeartbeat,
silentExpected: followupRun.run.silentExpected,
isSubagentSession: isSubagentSessionKey(sessionKey ?? followupRun.run.sessionKey),
hasExplicitSilentReply: deliberateSilentTerminalReply,
hasVisibleMessageDelivery: successfulSourceReplyDelivery || committedMessagingToolSourceReplyDelivery || runResult.didSendDeterministicApprovalPrompt === true
});
const emptyInteractiveReplyPayload = terminalFailurePayload ? void 0 : buildEmptyInteractiveReplyPayload({
isInteractive,
isHeartbeat,
silentExpected: followupRun.run.silentExpected,
allowEmptyAssistantReplyAsSilent: followupRun.run.allowEmptyAssistantReplyAsSilent,
hasPendingContinuation: pendingContinuation,
hasExplicitSilentReply: deliberateSilentTerminalReply,
hasCommittedDelivery: successfulTerminalDelivery,
hasIntentionalTerminalCompletion: hasIntentionalTerminalCompletion(runResult),
sessionCtx,
cfg
});
const buildStrandedRetryMissingDeliveryDiagnostic = () => {
if (!sessionKey || !storePath || followupRun.strandedReplyRetry !== true) return;
if (sessionCtx.InboundEventKind === "room_event" || completedSourceReplyDelivery) return;
const sourceReplyPolicy = resolveSourceReplyPolicy({
cfg,
sessionCtx,
sessionEntry: activeSessionEntry,
sessionKey,
runtimePolicySessionKey,
opts
});
const recovery = resolveStrandedReplyRecovery({
base: followupRun,
finalText: "",
sourceReplyDeliveryMode: sourceReplyPolicy.sourceReplyDeliveryMode,
sendPolicyDenied: sourceReplyPolicy.sendPolicyDenied,
successfulSourceReplyDelivery: completedSourceReplyDelivery,
isHeartbeat,
isRoomEvent: false
});
return recovery.kind === "diagnostic" ? recovery.payload : void 0;
};
if (completedSourceReplyDelivery || (runResult.messagingToolSentTargets?.length ?? 0) > 0 && (await loadReplyPayloadsDedupeRuntime()).hasSourceRoutedMessagingToolDelivery({
config: cfg,
messageProvider: followupRun.run.messageProvider,
messagingToolSentTargets: runResult.messagingToolSentTargets,
messagingToolSentTexts: runResult.messagingToolSentTexts,
messagingToolSentMediaUrls: runResult.messagingToolSentMediaUrls,
originatingTo: sessionCtx.OriginatingTo ?? sessionCtx.To,
originatingThreadId: replyRouteThreadId,
accountId: sessionCtx.AccountId
})) await opts?.onObservedReplyDelivery?.();
const currentMessageId = sessionCtx.MessageSidFull ?? sessionCtx.MessageSid;
const applyDeliveredReplyToMode = createReplyToModeFilterForChannel(replyToMode, replyToChannel);
const isGeneratedToolWarning = (payload) => getReplyPayloadMetadata(payload)?.toolErrorWarning !== void 0;
const applyFinalReplyToMode = (payload) => {
const isDisabledReasoningLane = payload.isReasoning === true && opts?.reasoningPayloadsEnabled !== true;
const isDisabledCommentaryLane = payload.isCommentary === true && opts?.commentaryPayloadsEnabled !== true;
const isFilteredPayload = normalizeReplyPayload(payload, { applyChannelTransforms: false }) === null;
const shouldDeferToolWarning = yieldAcknowledgmentPayload && isGeneratedToolWarning(payload);
return isDisabledReasoningLane || isDisabledCommentaryLane || isFilteredPayload || shouldDeferToolWarning ? payload : applyDeliveredReplyToMode(payload);
};
const buildFinalPayloads = (payloads) => buildReplyPayloads({
config: cfg,
payloads,
conversationContext: sessionCtx.agentText ?? sessionCtx.BodyForAgent,
isHeartbeat,
didLogHeartbeatStrip,
silentExpected: followupRun.run.silentExpected,
blockStreamingEnabled,
blockReplyPipeline,
directlySentBlockKeys,
directlySentBlockPayloads,
replyToMode,
replyToChannel,
currentMessageId,
replyThreading: replyThreadingOverride ?? sessionCtx.ReplyThreading,
applyReplyToMode: applyFinalReplyToMode,
messageProvider: followupRun.run.messageProvider,
messagingToolSentTexts: runResult.messagingToolSentTexts,
messagingToolSentMediaUrls: runResult.messagingToolSentMediaUrls,
messagingToolSentTargets: runResult.messagingToolSentTargets,
originatingChannel: sessionCtx.OriginatingChannel,
originatingChatType: sessionCtx.ChatType,
originatingTo: sessionCtx.OriginatingTo ?? sessionCtx.To,
originatingThreadId: replyRouteThreadId,
accountId: sessionCtx.AccountId,
normalizeMediaPaths: replyMediaContext.normalizePayload
});
const returnPreparedFallbackPayload = async (payload) => {
const result = await buildFinalPayloads([payload]);
didLogHeartbeatStrip = result.didLogHeartbeatStrip;
const preparedPayload = result.replyPayloads[0];
if (!preparedPayload) return;
await signalTypingIfNeeded([preparedPayload], typingSignals);
return returnWithQueuedFollowupDrain(preparedPayload);
};
const returnSilentFallbackFailureIfNeeded = async () => {
const silentFallbackFailurePayload = buildSilentFallbackFailurePayload({
fallbackTransition,
fallbackFailureKnown,
isHeartbeat,
hasSuccessfulTerminalDelivery: successfulTerminalDelivery,
allowEmptyAssistantReplyAsSilent: followupRun.run.allowEmptyAssistantReplyAsSilent,
silentExpected: followupRun.run.silentExpected,
hasExplicitSilentReply: deliberateSilentTerminalReply
});
if (!silentFallbackFailurePayload) return;
replyOperation.fail("run_failed", /* @__PURE__ */ new Error(`configured model backend ${fallbackTransition.selectedModelRef} failed and fallback ${fallbackTransition.activeModelRef} produced no visible reply`));
opts?.onAgentRunTerminalOutcome?.("failed");
return returnPreparedFallbackPayload(silentFallbackFailurePayload);
};
const fallbackNoticeChanged = !fallbackExhausted && !preserveUserFacingSessionState && (fallbackTransition.fallbackTransitioned || fallbackTransition.fallbackCleared);
const fallbackNoticeChatType = fallbackNoticeChanged ? normalizeChatType(sessionCtx.ChatType) : void 0;
const shouldDeliverFallbackNotice = fallbackNoticeChatType !== "group" && fallbackNoticeChatType !== "channel";
let fallbackNoticeText = null;
if (fallbackNoticeChanged && fallbackTransition.fallbackTransitioned) {
emitAgentEvent({
runId,
sessionKey,
stream: "lifecycle",
data: {
phase: "fallback",
selectedProvider,
selectedModel,
activeProvider: sessionModel.provider,
activeModel: sessionModel.model,
reasonSummary: fallbackTransition.reasonSummary,
attemptSummaries: fallbackTransition.attemptSummaries,
attempts: fallbackAttempts
}
});
if (shouldDeliverFallbackNotice) fallbackNoticeText = buildFallbackNotice({
selectedProvider,
selectedModel,
activeProvider: sessionModel.provider,
activeModel: sessionModel.model,
attempts: fallbackAttempts,
cfg
});
}
if (fallbackNoticeChanged && fallbackTransition.fallbackCleared) {
emitAgentEvent({
runId,
sessionKey,
stream: "lifecycle",
data: {
phase: "fallback_cleared",
selectedProvider,
selectedModel,
activeProvider: sessionModel.provider,
activeModel: sessionModel.model,
previousActiveModel: fallbackTransition.previousState.activeModel
}
});
if (shouldDeliverFallbackNotice) fallbackNoticeText = buildFallbackClearedNotice({
selectedProvider,
selectedModel,
previousActiveModel: fallbackTransition.previousState.activeModel
});
}
const fallbackNoticePayloads = fallbackNoticeText ? [markReplyPayloadForSourceSuppressionDelivery({
text: fallbackNoticeText,
isFallbackNotice: true
})] : [];
if (payloadArray.length === 0 && fallbackNoticePayloads.length === 0 && !shouldDeliverTerminalFailure && !yieldAcknowledgmentPayload && (!emptyInteractiveReplyPayload || hasSpecificFallbackFailure)) {
const silentFallbackFailurePayload = await returnSilentFallbackFailureIfNeeded();
if (silentFallbackFailurePayload) return {
kind: "return",
value: silentFallbackFailurePayload
};
const strandedRetryDiagnostic = buildStrandedRetryMissingDeliveryDiagnostic();
if (strandedRetryDiagnostic) return {
kind: "return",
value: returnWithQueuedFollowupDrain(strandedRetryDiagnostic)
};
return {
kind: "return",
value: returnWithQueuedFollowupDrain(void 0)
};
}
const payloadResult = await buildFinalPayloads((fallbackNoticePayloads.length > 0 ? [...fallbackNoticePayloads, ...payloadArray] : payloadArray).filter((payload) => (payload.isReasoning !== true || opts?.reasoningPayloadsEnabled === true) && (payload.isCommentary !== true || opts?.commentaryPayloadsEnabled === true)));
let { replyPayloads } = payloadResult;
didLogHeartbeatStrip = payloadResult.didLogHeartbeatStrip;
const replyPayloadsWithoutToolWarnings = yieldAcknowledgmentPayload ? replyPayloads.filter((payload) => !isGeneratedToolWarning(payload)) : replyPayloads;
const hasTerminalReplyPayload = replyPayloadsWithoutToolWarnings.some((payload) => isReplyPayloadTerminalContent(payload) && normalizeReplyPayload(payload, { applyChannelTransforms: false }) !== null);
if (yieldAcknowledgmentPayload && hasTerminalReplyPayload) replyPayloads = replyPayloadsWithoutToolWarnings;
if (shouldDeliverTerminalFailure && !hasTerminalReplyPayload && terminalFailurePayload) {
const terminalPayloadResult = await buildFinalPayloads([terminalFailurePayload]);
replyPayloads = [...replyPayloads, ...terminalPayloadResult.replyPayloads];
didLogHeartbeatStrip = terminalPayloadResult.didLogHeartbeatStrip;
} else if (yieldAcknowledgmentPayload && !hasTerminalReplyPayload) {
const acknowledgmentResult = await buildFinalPayloads([yieldAcknowledgmentPayload]);
replyPayloads = acknowledgmentResult.replyPayloads.length > 0 ? [...replyPayloadsWithoutToolWarnings, ...acknowledgmentResult.replyPayloads] : replyPayloads.map((payload) => isGeneratedToolWarning(payload) ? applyFinalReplyToMode(payload) : payload);
didLogHeartbeatStrip = acknowledgmentResult.didLogHeartbeatStrip;
} else if (hasSpecificFallbackFailure && !hasTerminalReplyPayload) {
const silentFallbackFailurePayload = await returnSilentFallbackFailureIfNeeded();
if (silentFallbackFailurePayload) return {
kind: "return",
value: silentFallbackFailurePayload
};
} else if (emptyInteractiveReplyPayload && !hasTerminalReplyPayload) {
const emptyPayloadResult = await buildFinalPayloads([emptyInteractiveReplyPayload]);
replyPayloads = [...replyPayloads, ...emptyPayloadResult.replyPayloads];
didLogHeartbeatStrip = emptyPayloadResult.didLogHeartbeatStrip;
if (emptyPayloadResult.replyPayloads.length > 0) {
replyOperation.retainFailureUntilComplete();
replyOperation.fail("run_failed", /* @__PURE__ */ new Error("interactive agent run completed without a visible reply"));
opts?.onAgentRunTerminalOutcome?.("failed");
}
}
replyPayloads = attachMcpAppChannelAction({
payloads: replyPayloads,
channel: replyToChannel,
sessionKey,
view: runResult.latestMcpAppChannelView
});
replyPayloads = attachMcpConnectChannelAction({
payloads: replyPayloads,
action: runResult.latestMcpConnectAction
});
const hasVisibleReplyPayload = replyPayloads.some((payload) => !isReplyPayloadStatusNotice(payload) && (payload.isReasoning !== true || opts?.reasoningPayloadsEnabled === true) && (payload.isCommentary !== true || opts?.commentaryPayloadsEnabled === true) && normalizeReplyPayload(payload, { applyChannelTransforms: false }) !== null);
const canDeliverStandaloneFallbackNotice = Boolean(blockReplyPipeline?.didStream() && !blockReplyPipeline.isAborted()) || successfulSideEffectDelivery;
if (replyPayloads.length === 0 || !hasVisibleReplyPayload && !canDeliverStandaloneFallbackNotice) {
const silentFallbackFailurePayload = await returnSilentFallbackFailureIfNeeded();
if (silentFallbackFailurePayload) return {
kind: "return",
value: silentFallbackFailurePayload
};
const strandedRetryDiagnostic = buildStrandedRetryMissingDeliveryDiagnostic();
if (strandedRetryDiagnostic) return {
kind: "return",
value: returnWithQueuedFollowupDrain(strandedRetryDiagnostic)
};
return {
kind: "return",
value: returnWithQueuedFollowupDrain(void 0)
};
}
const successfulCronAdds = runResult.successfulCronAdds ?? 0;
const hasReminderCommitment = replyPayloads.some((payload) => !payload.isError && !isReplyPayloadStatusNotice(payload) && typeof payload.text === "string" && hasUnbackedReminderCommitment(payload.text));
const coveredByExistingCron = hasReminderCommitment && successfulCronAdds === 0 ? await hasSessionRelatedCronJobs({
cronStorePath: void 0,
sessionKey
}) : false;
const guardedReplyPayloads = hasReminderCommitment && successfulCronAdds === 0 && !coveredByExistingCron ? appendUnscheduledReminderNote(replyPayloads) : replyPayloads;
if (implicitContinuation) {
const statusPayload = guardedReplyPayloads.find((payload) => getReplyPayloadMetadata(payload)?.continuationStatus === true);
const acceptedSessionSpawns = runResult.acceptedSessionSpawns;
if (!sessionKey || !acceptedSessionSpawns?.length || !statusPayload) throw new Error("accepted continuation status could not be prepared for delivery");
opts?.onPendingContinuation?.({ settle: async (statusDelivered) => {
const { settleRequesterAfterSessionSpawns } = await import("./subagent-registry-Dns8wAWm.js");
if (!settleRequesterAfterSessionSpawns({
requesterSessionKey: sessionKey,
requesterAgentId: followupRun.run.agentId,
requesterTurnRunId: runId,
requesterYielded: statusDelivered,
acceptedSessionSpawns
})) throw new Error("accepted continuation children could not transfer terminal delivery");
} });
}
await signalTypingIfNeeded(guardedReplyPayloads, typingSignals);
const diagnosticUsage = runResult.meta?.agentMeta?.diagnosticUsage ?? usage;
if (isDiagnosticsEnabled(cfg) && hasBillableUsage(diagnosticUsage)) {
const input = diagnosticUsage.input ?? 0;
const output = diagnosticUsage.output ?? 0;
const cacheRead = diagnosticUsage.cacheRead ?? 0;
const cacheWrite = diagnosticUsage.cacheWrite ?? 0;
const usagePromptTokens = input + cacheRead + cacheWrite;
const totalTokens = diagnosticUsage.total ?? usagePromptTokens + output;
const contextUsedTokens = deriveContextPromptTokens({
lastCallUsage: runResult.meta?.agentMeta?.lastCallUsage,
promptTokens,
usage
});
const costUsd = estimateAggregateUsageCost({
usage: diagnosticUsage,
provider: providerUsed,
model: modelUsed,
config: cfg,
agentDir: followupRun.run.agentDir
});
emitTrustedDiagnosticEvent({
type: "model.usage",
...runResult.diagnosticTrace ? { trace: freezeDiagnosticTraceContext(createChildDiagnosticTraceContext(runResult.diagnosticTrace)) } : {},
sessionKey,
sessionId: followupRun.run.sessionId,
channel: replyToChannel,
agentId: followupRun.run.agentId,
provider: providerUsed,
model: modelUsed,
usage: {
input,
output,
cacheRead,
cacheWrite,
promptTokens: usagePromptTokens,
total: totalTokens
},
lastCallUsage: runResult.meta?.agentMeta?.lastCallUsage,
context: {
limit: contextTokensUsed,
...contextUsedTokens !== void 0 ? { used: contextUsedTokens } : {}
},
costUsd,
durationMs: Date.now() - runStartedAt
});
}
const responseUsageSessionRaw = activeSessionEntry?.responseUsage ?? (sessionKey ? activeSessionStore?.[sessionKey]?.responseUsage : void 0);
const responseUsageLine = resolveResponseUsageLine({
config: cfg,
agentDir: followupRun.run.agentDir,
sessionRaw: responseUsageSessionRaw,
channel: replyToChannel,
usage,
provider: providerUsed,
model: modelUsed,
preserveUserFacingSessionState,
replyUsageState
});
if (followupRun.run.verboseLevelOverride !== "off" || followupRun.run.traceAuthorized === true) activeSessionEntry = refreshSessionEntryFromStore({
storePath,
sessionKey,
fallbackEntry: activeSessionEntry,
activeSessionStore,
expectedGeneration: accounting.expectedSession
});
return {
kind: "continue",
activeSessionEntry,
completedSourceReplyDelivery,
didLogHeartbeatStrip,
guardedReplyPayloads,
responseUsageLine
};
}
//#endregion
//#region src/auto-reply/reply/agent-runner-result.ts
async function finalizeReplyAgentRun(context) {
const accounting = await accountAgentTurn(context);
const prepared = await prepareReplyAgentPayloads({
context,
accounting
});
if (prepared.kind === "return") return prepared.value;
return await completeReplyAgentRun({
context,
accounting,
prepared
});
}
//#endregion
//#region src/auto-reply/reply/followup-delivery-payloads.ts
/** Strips empty/heartbeat payloads, applies threading, and dedupes message-tool sends. */
function resolveFollowupDeliveryPayloads(params) {
const replyMessageProvider = resolveOriginMessageProvider({
originatingChannel: params.originatingChannel,
provider: params.messageProvider
});
const replyToChannel = replyMessageProvider;
const replyToMode = params.originatingReplyToMode ?? resolveReplyToMode(params.cfg, replyToChannel, params.originatingAccountId, params.originatingChatType);
const accountId = params.originatingAccountId;
const replyDelivery = createReplyDeliveryContext(replyToMode, params.originatingChatType);
const replyDeliverySource = replyMessageProvider ? {
channel: replyMessageProvider,
...accountId ? { accountId } : {}
} : void 0;
const deliverablePayloads = params.payloads.filter((payload) => !(payload.isReasoning === true && params.reasoningPayloadsEnabled !== true) && !(payload.isCommentary === true && params.commentaryPayloadsEnabled !== true));
const sanitizedPayloads = [];
for (const payload of deliverablePayloads) {
const text = payload.text;
const sanitized = text?.includes("HEARTBEAT_OK") === true ? copyReplyPayloadMetadata(payload, {
...payload,
text: stripHeartbeatToken(text, { mode: "message" }).text
}) : payload;
if (hasOutboundReplyContent(sanitized, { trimText: true })) sanitizedPayloads.push(sanitized);
}
const originatingTo = params.originatingTo;
const applyReplyToMode = createReplyToModeFilterForChannel(replyToMode, replyToChannel);
return sanitizedPayloads.flatMap((payload) => filterMessagingToolReplyPayload({
payload: applyReplyToMode.preview(setReplyPayloadMetadata(applyReplyTagsToPayload(payload), {
replyDelivery,
...replyDeliverySource ? { replyDeliverySource } : {}
})),
config: params.cfg,
messageProvider: replyMessageProvider,
messagingToolSentTargets: params.sentTargets,
originatingTo,
originatingThreadId: params.originatingThreadId,
accountId,
sentMediaUrls: params.sentMediaUrls,
sentTexts: params.sentTexts
}).filter(isRenderablePayload).map(applyReplyToMode));
}
//#endregion
//#region src/auto-reply/reply/followup-delivery.ts
/** Prepares queued follow-up payloads for source-channel delivery. */
/** Resolves one final queued delivery action without performing transport I/O. */
function resolveFollowupDeliveryDecision(params) {
const { turn, execution, accounting, opts } = params;
if (turn.sendPolicy === "deny") return {
kind: "suppress",
reason: "send-policy"
};
if (turn.queued.currentInboundEventKind === "room_event" && !isInternalMessageChannel(turn.queued.originatingChannel)) return {
kind: "suppress",
reason: "room-event"
};
if (execution.outcome.kind === "aborted") return {
kind: "suppress",
reason: "aborted"
};
const postCompactionModelFailure = execution.outcome.postCompactionModelFailure;
const renderFailurePayloads = (payloads) => payloads.map((payload) => renderPostCompactionModelFailurePayload(markPostCompactionModelFailurePayload(postCompactionModelFailure, payload)));
const sourcePolicy = resolveSourceReplyVisibilityPolicy({
cfg: turn.config,
ctx: {
ChatType: turn.queued.originatingChatType ?? turn.queued.run.chatType,
InboundEventKind: turn.queued.currentInboundEventKind,
Provider: turn.queued.originatingChannel ?? turn.queued.run.messageProvider,
Surface: turn.queued.originatingChannel ?? turn.queued.run.messageProvider
},
requested: turn.queued.run.sourceReplyDeliveryMode ?? opts?.sourceReplyDeliveryMode,
sendPolicy: turn.sendPolicy
});
const isInteractive = Boolean(isRoutableChannel(turn.queued.originatingChannel) && turn.queued.originatingTo || opts?.onBlockReply) && (turn.queued.run.inputProvenance?.kind === "external_user" || turn.queued.run.inputProvenance?.kind === void 0 && !isInternalMessageChannel(turn.queued.originatingChannel ?? turn.queued.run.messageProvider));
const deliveryContext = {
cfg: turn.config,
messageProvider: turn.queued.run.messageProvider,
originatingAccountId: turn.queued.originatingAccountId ?? turn.queued.run.agentAccountId,
originatingChannel: turn.queued.originatingChannel,
originatingChatType: turn.queued.originatingChatType,
originatingReplyToMode: turn.queued.originatingReplyToMode,
originatingTo: turn.queued.originatingTo,
originatingThreadId: turn.queued.originatingThreadId
};
if (execution.outcome.kind === "rejected") {
if (!isInteractive) return {
kind: "suppress",
reason: "silent"
};
if (sourcePolicy.sourceReplyDeliveryMode === "message_tool_only" && getReplyPayloadMetadata(execution.outcome.payload)?.deliverDespiteSourceReplySuppression !== true) return {
kind: "suppress",
reason: "message-tool-only"
};
const payloads = renderFailurePayloads(resolveFollowupDeliveryPayloads({
...deliveryContext,
payloads: [execution.outcome.payload],
reasoningPayloadsEnabled: opts?.reasoningPayloadsEnabled === true,
commentaryPayloadsEnabled: opts?.commentaryPayloadsEnabled === true
}));
return payloads.length > 0 ? {
kind: "deliver",
payloads,
resolved: execution.outcome.resolved
} : {
kind: "suppress",
reason: "silent"
};
}
if (!accounting) return {
kind: "suppress",
reason: "silent"
};
const runtimeResolved = {
provider: accounting.providerUsed,
model: accounting.modelUsed
};
const result = execution.outcome.result;
const completedSourceDelivery = hasCompletedSourceReplyDeliveryEvidence(result);
const assistantFinalText = normalizeAssistantFinalDeliveryText(typeof result.meta?.finalAssistantVisibleText === "string" ? result.meta.finalAssistantVisibleText : "");
let payloads = resolveFollowupDeliveryPayloads({
...deliveryContext,
payloads: accounting.payloadArray,
reasoningPayloadsEnabled: opts?.reasoningPayloadsEnabled === true,
commentaryPayloadsEnabled: opts?.commentaryPayloadsEnabled === true,
sentMediaUrls: result.messagingToolSentMediaUrls,
sentTargets: result.messagingToolSentTargets,
sentTexts: result.messagingToolSentTexts
});
const recovery = payloads.some((payload) => getReplyPayloadMetadata(payload)?.deliverDespiteSourceReplySuppression === true) || accounting.terminalFailurePayload ? { kind: "none" } : resolveStrandedReplyRecovery({
base: turn.queued,
finalText: assistantFinalText,
sourceReplyDeliveryMode: sourcePolicy.sourceReplyDeliveryMode,
sendPolicyDenied: sourcePolicy.sendPolicyDenied,
successfulSourceReplyDelivery: completedSourceDelivery,
isHeartbeat: opts?.isHeartbeat === true,
isRoomEvent: false
});
if (recovery.kind === "retry") return {
kind: "retry-source-delivery",
run: recovery.run,
finalTextLength: assistantFinalText.trim().length,
resolved: runtimeResolved
};
if (recovery.kind === "diagnostic") {
const [payload] = resolveFollowupDeliveryPayloads({
...deliveryContext,
payloads: [recovery.payload]
});
if (!payload) return {
kind: "suppress",
reason: "silent"
};
return {
kind: "deliver-diagnostic",
payload,
resolved: runtimeResolved
};
}
const fallbackPayload = accounting.terminalFailurePayload ? isInteractive && !hasCompletedTerminalDeliveryEvidence(result) ? sourcePolicy.sourceReplyDeliveryMode === "message_tool_only" ? markReplyPayloadForSourceSuppressionDelivery(accounting.terminalFailurePayload) : accounting.terminalFailurePayload : void 0 : buildSessionsYieldAcknowledgmentPayload({
yielded: result.meta?.yielded === true,
yieldAcknowledgment: result.meta?.yieldAcknowledgment,
isInteractive,
isHeartbeat: opts?.isHeartbeat,
silentExpected: turn.queued.run.silentExpected,
isSubagentSession: turn.session.kind === "session" && isSubagentSessionKey(turn.session.key),
hasExplicitSilentReply: hasDeliberateSilentTerminalReply(result),
hasVisibleMessageDelivery: hasCommittedSourceReplyDeliveryEvidence(result) || hasVisibleCommittedMessagingToolDeliveryEvidence(result) || result.didSendDeterministicApprovalPrompt === true
}) ?? buildEmptyInteractiveReplyPayload({
isInteractive,
isHeartbeat: opts?.isHeartbeat,
silentExpected: turn.queued.run.silentExpected,
allowEmptyAssistantReplyAsSilent: turn.queued.run.allowEmptyAssistantReplyAsSilent,
hasPendingContinuation: result.meta?.yielded === true || (result.meta?.pendingToolCalls?.length ?? 0) > 0,
hasExplicitSilentReply: hasDeliberateSilentTerminalReply(result),
hasCommittedDelivery: hasCompletedTerminalDeliveryEvidence(result),
hasIntentionalTerminalCompletion: hasIntentionalTerminalCompletion(result),
sessionCtx: {
ChatType: turn.queued.originatingChatType,
Provider: turn.queued.run.messageProvider,
SessionKey: turn.session.kind === "session" ? turn.session.key : void 0,
Surface: turn.queued.originatingChannel
},
cfg: turn.config
});
if (!payloads.some((payload) => isReplyPayloadTerminalContent(payload) && (sourcePolicy.sourceReplyDeliveryMode !== "message_tool_only" || getReplyPayloadMetadata(payload)?.deliverDespiteSourceReplySuppression === true)) && fallbackPayload) payloads = [...payloads, ...resolveFollowupDeliveryPayloads({
...deliveryContext,
payloads: [fallbackPayload]
})];
if (accounting.compactionNotice) payloads = [...resolveFollowupDeliveryPayloads({
...deliveryContext,
payloads: [accounting.compactionNotice]
}), ...payloads];
if (accounting.diagnosticsPayload && payloads.length > 0) payloads = [...payloads, ...resolveFollowupDeliveryPayloads({
...deliveryContext,
payloads: [accounting.diagnosticsPayload]
})];
const responseUsageLine = resolveResponseUsageLine({
config: turn.config,
agentDir: turn.queued.run.agentDir,
sessionRaw: turn.session.current()?.responseUsage,
channel: resolveOriginMessageProvider({
originatingChannel: turn.queued.originatingChannel,
provider: turn.queued.run.messageProvider
}),
usage: accounting.usage,
provider: accounting.providerUsed,
model: accounting.modelUsed,
preserveUserFacingSessionState: accounting.preserveUserFacingSessionState,
replyUsageState: accounting.replyUsageState
});
if (responseUsageLine) payloads = appendUsageLine(payloads, responseUsageLine);
payloads = renderFailurePayloads(payloads);
if (sourcePolicy.sourceReplyDeliveryMode === "message_tool_only") {
const explicitlyDeliverable = payloads.filter((payload) => getReplyPayloadMetadata(payload)?.deliverDespiteSourceReplySuppression === true);
return explicitlyDeliverable.length > 0 ? {
kind: "deliver",
payloads: explicitlyDeliverable,
resolved: runtimeResolved
} : {
kind: "suppress",
reason: "message-tool-only"
};
}
return payloads.length > 0 ? {
kind: "deliver",
payloads,
resolved: runtimeResolved
} : {
kind: "suppress",
reason: "silent"
};
}
async function sendFollowupPayloads(params) {
const { turn, defaults } = params;
const { originatingChannel, originatingTo } = turn.queued;
const originRoutable = Boolean(isRoutableChannel(originatingChannel) && originatingTo);
const deliveryPlan = buildAgentRuntimeDeliveryPlan({
provider: params.resolved?.provider ?? turn.queued.run.provider,
modelId: params.resolved?.model ?? turn.queued.run.model,
config: turn.config,
workspaceDir: turn.queued.run.workspaceDir,
agentDir: turn.queued.run.agentDir
});
const payloads = params.payloads.filter((payload) => hasOutboundReplyContent(payload) && (!deliveryPlan.isSilentPayload(payload) || getReplyPayloadMetadata(payload)?.deliverDespiteSourceReplySuppression === true));
if (payloads.length === 0) return;
const sourceDisposition = turn.queued.queuedFollowupReplyDisposition;
if (sourceDisposition?.kind === "drop") {
logVerbose(`followup queue: source delivery dropped (${sourceDisposition.reason})`);
return;
}
const deliverQueuedBatch = sourceDisposition?.deliver;
const fallbackDispatcher = sourceDisposition ? void 0 : defaults.opts?.onBlockReply;
const dispatcherAvailable = Boolean(deliverQueuedBatch || fallbackDispatcher);
if (!originRoutable && !dispatcherAvailable) {
defaultRuntime.error?.("followup queue: completed with payloads but no origin route or visible dispatcher is available");
return;
}
const typing = createTypingSignaler({
typing: defaults.typing,
mode: defaults.typingMode,
isHeartbeat: defaults.opts?.isHeartbeat === true
});
const crossChannelFailures = [];
const queuedPayloads = [];
const dispatchPayload = async (payload) => {
if (deliverQueuedBatch) queuedPayloads.push(payload);
else await fallbackDispatcher?.(payload);
};
let deliveredCrossChannelOrigin = false;
const provider = resolveOriginMessageProvider({ provider: turn.queued.run.messageProvider });
const origin = resolveOriginMessageProvider({ originatingChannel });
const sameChannelOrigin = Boolean(origin && origin === provider);
const crossChannelOrigin = Boolean(origin && provider && origin !== provider);
for (const payload of payloads) {
const providerRoute = deliveryPlan.resolveFollowupRoute({
payload,
originatingChannel,
originatingTo,
originRoutable,
dispatcherAvailable
});
if (providerRoute?.route === "drop") continue;
const route = providerRoute?.route === "origin" && originRoutable ? "origin" : providerRoute?.route === "dispatcher" && dispatcherAvailable ? "dispatcher" : originRoutable ? "origin" : "dispatcher";
await typing.signalTextDelta(payload.text);
if (route !== "origin") await dispatchPayload(payload);
else if (isRoutableChannel(originatingChannel) && originatingTo) {
const metadata = getReplyPayloadMetadata(payload);
const result = await routeReply({
payload,
channel: originatingChannel,
to: originatingTo,
agentId: turn.queued.run.agentId,
sessionKey: turn.queued.run.sessionKey,
accountId: turn.queued.originatingAccountId,
requesterSenderId: turn.queued.run.senderId,
requesterSenderName: turn.queued.run.senderName,
requesterSenderUsername: turn.queued.run.senderUsername,
requesterSenderE164: turn.queued.run.senderE164,
threadId: turn.queued.originatingThreadId,
cfg: turn.config,
mirror: metadata?.assistantMessageIndex !== void 0 || metadata?.assistantTranscriptOwned === true ? false : params.mirror,
replyKind: params.kind,
runId: params.runId
});
if (!result.delivered && !result.suppressed) {
const routeError = result.error ?? "no visible delivery";
logVerbose(`followup queue: route-reply failed: ${routeError}`);
if (sameChannelOrigin && dispatcherAvailable) await dispatchPayload(payload);
else if (dispatcherAvailable) crossChannelFailures.push(payload);
else defaultRuntime.error?.(`followup queue: route-reply failed: ${routeError}`);
} else if (result.delivered) {
if (!result.ok) logVerbose(`followup queue: route-reply partially failed after delivery: ${result.error ?? "unknown error"}`);
deliveredCrossChannelOrigin ||= crossChannelOrigin;
}
}
}
if ((crossChannelFailures.some(isReplyPayloadTerminalContent) || crossChannelFailures.length > 0 && !deliveredCrossChannelOrigin) && dispatcherAvailable) await dispatchPayload({
text: "Follow-up completed, but OpenClaw could not deliver it to the originating channel. The reply content was not forwarded to this channel to avoid cross-channel misdelivery.",
isError: true
});
if (queuedPayloads.length > 0) await deliverQueuedBatch?.({
kind: "queued-followup",
runId: params.runId,
originatingChannel,
payloads: queuedPayloads
});
}
/** Performs the already-resolved follow-up delivery action. */
async function deliverFollowupDecision(params) {
const { decision, turn, defaults } = params;
if (decision.kind === "suppress") {
logVerbose(`followup queue: delivery suppressed (${decision.reason})`);
return;
}
if (decision.kind === "retry-source-delivery") {
warnPrivateMessageToolFinal({
sessionKey: turn.session.kind === "session" ? turn.session.key : void 0,
channel: turn.queued.originatingChannel ?? turn.queued.run.messageProvider ?? sessionDeliveryChannel(turn.session.current()),
finalTextLength: decision.finalTextLength
});
const key = turn.session.kind === "session" ? turn.session.key : turn.queued.run.sessionKey;
if (key && enqueueFollowupRun(key, decision.run, resolveQueueSettings({
cfg: turn.config,
channel: turn.queued.originatingChannel ?? turn.queued.run.messageProvider,
sessionEntry: turn.session.current()
}), "none", params.runFollowup, false, { position: "front" })) return;
await sendFollowupPayloads({
payloads: resolveFollowupDeliveryPayloads({
cfg: turn.config,
payloads: [buildStrandedReplyDeliveryFailurePayload()],
messageProvider: turn.queued.run.messageProvider,
originatingAccountId: turn.queued.originatingAccountId ?? turn.queued.run.agentAccountId,
originatingChannel: turn.queued.originatingChannel,
originatingChatType: turn.queued.originatingChatType,
originatingReplyToMode: turn.queued.originatingReplyToMode,
originatingTo: turn.queued.originatingTo,
originatingThreadId: turn.queued.originatingThreadId
}),
turn,
defaults,
runId: params.runId,
kind: params.kind ?? "final",
resolved: decision.resolved
});
return;
}
await sendFollowupPayloads({
payloads: decision.kind === "deliver" ? decision.payloads : [decision.payload],
turn,
defaults,
runId: params.runId,
kind: params.kind ?? "final",
mirror: params.kind && params.kind !== "final" ? false : void 0,
resolved: decision.resolved
});
}
//#endregion
//#region src/auto-reply/reply/followup-turn-admission.ts
async function settleQueuedFollowupPresentation(defaults) {
try {
await defaults.opts?.onQueuedFollowupSettled?.();
} catch (error) {
defaultRuntime.error?.(`followup queue: queued presentation cleanup failed: ${formatErrorMessage(error)}`);
}
}
function resolveFollowupCurrentMessageId(queued) {
return queued.run.inputProvenance?.kind === "internal_system" && queued.run.inputProvenance.sourceTool === "restart-sentinel" ? queued.originatingReplyToId : queued.messageId;
}
function isSameSessionGeneration(left, right) {
return Boolean(left && right && left.sessionId === right.sessionId && left.lifecycleRevision === right.lifecycleRevision);
}
/** Resolves one queued item into an immutable admitted turn. */
async function admitFollowupTurn(params) {
const resolvedConfig = await resolveQueuedReplyExecutionConfig(params.queued.run.config, {
originatingChannel: params.queued.originatingChannel,
messageProvider: params.queued.run.messageProvider,
originatingAccountId: params.queued.originatingAccountId,
agentAccountId: params.queued.run.agentAccountId
});
const config = resolveQueuedReplyRuntimeConfig(resolvedConfig);
const replySessionKey = params.queued.run.sessionKey ?? params.defaults.sessionKey;
const initialStoredEntry = replySessionKey ? params.defaults.sessionStore?.[replySessionKey] : void 0;
const initialEntry = initialStoredEntry ?? (replySessionKey === params.defaults.sessionKey ? params.defaults.sessionEntry : void 0);
let run = {
...params.queued.run,
config
};
const resolveRunSessionFile = (source, sessionId) => resolveAdmittedRunSessionFile({
agentId: source.agentId,
sessionId,
sessionKey: replySessionKey,
storePath: params.defaults.storePath
}) ?? source.sessionFile;
const admission = await admitReplyTurn({
resolveGatewayContext: params.defaults.resolveGatewayContext,
sessionId: params.queued.admissionSessionId ?? run.sessionId,
sessionKey: replySessionKey ?? "",
expectedSessionId: initialEntry?.sessionId,
storePath: params.defaults.storePath,
kind: "queued_followup",
resetTriggered: false,
routeThreadId: params.queued.originatingThreadId,
originatingLeafEntryId: params.queued.turnAdoptionLifecycle?.originatingLeafEntryId,
upstreamAbortSignal: resolveFollowupAbortSignal(params.queued)
});
if (admission.status === "skipped") {
if (admission.reason !== "active-run") return {
kind: "skipped",
reason: admission.reason
};
params.queued.turnAdoptionLifecycle?.onDeferredHeartbeat?.();
return {
kind: "deferred",
reason: "active-run"
};
}
const operation = admission.operation;
operation.retainFailureUntilComplete();
let queuedFollowupAdmitted = false;
try {
await admitFollowupRunLifecycle(params.queued);
if (isFollowupRunAborted(params.queued)) return {
kind: "skipped",
reason: "aborted",
operation
};
queuedFollowupAdmitted = true;
await params.defaults.opts?.onQueuedFollowupAdmitted?.();
if (operation.sessionId !== run.sessionId) run = {
...run,
sessionId: operation.sessionId,
sessionFile: resolveRunSessionFile(run, operation.sessionId),
cliSessionBindingFacts: void 0,
autoFallbackPrimaryProbe: void 0,
modelSelectionLocked: false
};
const admittedEntry = replySessionKey ? params.defaults.storePath ? loadSessionEntry({
storePath: params.defaults.storePath,
sessionKey: replySessionKey
}) : params.defaults.sessionStore?.[replySessionKey] : void 0;
const expectedPersistedEntry = admission.sessionEntry?.sessionId === operation.sessionId ? admission.sessionEntry : initialEntry?.sessionId === operation.sessionId ? initialEntry : void 0;
const assertPersistedGeneration = (entry) => {
const matchesExpectedGeneration = isSameSessionGeneration(entry, expectedPersistedEntry);
if ((Boolean(params.defaults.storePath) || entry !== initialStoredEntry) && (expectedPersistedEntry && !matchesExpectedGeneration || !expectedPersistedEntry && entry && entry.sessionId !== operation.sessionId)) throw new ReplySessionGenerationInvalidatedError("Follow-up session generation changed after reply admission");
};
assertPersistedGeneration(admittedEntry);
const admissionEntry = admission.sessionEntry?.sessionId === operation.sessionId ? admission.sessionEntry : void 0;
const reloadedEntry = admittedEntry?.sessionId === operation.sessionId ? admittedEntry : void 0;
let activeEntry = (reloadedEntry && admissionEntry ? reloadedEntry.updatedAt >= admissionEntry.updatedAt ? reloadedEntry : admissionEntry : reloadedEntry ?? admissionEntry) ?? (admittedEntry === void 0 && initialEntry?.sessionId === operation.sessionId ? initialEntry : void 0);
const lifecycleRevisionChanged = operation.sessionId === params.queued.run.sessionId && activeEntry?.sessionId === operation.sessionId && activeEntry.lifecycleRevision !== (initialEntry?.sessionId === operation.sessionId ? initialEntry.lifecycleRevision : void 0);
if (activeEntry?.sessionId === operation.sessionId) run = {
...run,
sessionFile: resolveRunSessionFile(run, operation.sessionId),
modelSelectionLocked: activeEntry.modelSelectionLocked === true,
...lifecycleRevisionChanged ? {
cliSessionBindingFacts: void 0,
autoFallbackPrimaryProbe: void 0
} : {}
};
run = resolveRunAfterAutoFallbackPrimaryProbeRecheck({
run,
entry: activeEntry,
sessionKey: replySessionKey
});
const queued = {
...params.queued,
run
};
const sessionEntryHandle = createReplySessionEntryHandle({
sessionEntry: activeEntry,
sessionKey: replySessionKey,
sessionStore: params.defaults.sessionStore,
generationFence: {
sessionId: operation.sessionId,
expectedStoreEntry: initialStoredEntry
}
});
const session = {
...replySessionKey ? {
kind: "session",
key: replySessionKey,
storePath: params.defaults.storePath
} : { kind: "detached" },
current: () => sessionEntryHandle.getCurrent(),
publish: (entry) => entry && sessionEntryHandle.replaceCurrent(entry),
adopt: (entry) => sessionEntryHandle.adoptCurrent(entry)
};
const sessionStore = replySessionKey ? sessionEntryHandle.toCompatSessionStore() : params.defaults.sessionStore;
const resolveTurnSendPolicy = (entry, source = queued) => resolveSendPolicy({
cfg: config,
entry,
sessionKey: source.run.runtimePolicySessionKey ?? replySessionKey,
channel: source.originatingChannel ?? source.run.messageProvider ?? sessionDeliveryChannel(entry),
chatType: normalizeChatType(source.originatingChatType ?? source.run.chatType ?? entry?.chatType)
});
const currentInboundContext = params.defaults.opts?.isHeartbeat === true ? queued.currentInboundContext : refreshActiveGoalContext(queued.currentInboundContext, activeEntry);
const turn = {
runId: crypto.randomUUID(),
queued: {
...queued,
currentInboundContext
},
operation,
config,
session,
sessionStore,
currentInboundContext,
sendPolicy: resolveTurnSendPolicy(activeEntry),
preflightCompactionApplied: false
};
const refreshTurnSessionState = (entry) => {
const refreshedInboundContext = params.defaults.opts?.isHeartbeat === true ? params.queued.currentInboundContext : refreshActiveGoalContext(params.queued.currentInboundContext, entry);
turn.sendPolicy = resolveTurnSendPolicy(entry, turn.queued);
turn.currentInboundContext = refreshedInboundContext;
turn.queued = {
...turn.queued,
currentInboundContext: refreshedInboundContext
};
};
const readTurnSessionEntry = () => replySessionKey && params.defaults.storePath ? loadSessionEntry({
storePath: params.defaults.storePath,
sessionKey: replySessionKey
}) : replySessionKey && params.defaults.sessionStore ? params.defaults.sessionStore[replySessionKey] : session.current();
const synchronizeTurnGeneration = (entry, previousEntry) => {
const generationRotated = Boolean(entry && !isSameSessionGeneration(entry, previousEntry));
if (entry && generationRotated) {
operation.updateSessionId(entry.sessionId);
turn.queued = {
...turn.queued,
run: {
...turn.queued.run,
sessionId: entry.sessionId,
sessionFile: resolveRunSessionFile(turn.queued.run, entry.sessionId),
cliSessionBindingFacts: void 0,
autoFallbackPrimaryProbe: void 0,
modelSelectionLocked: entry.modelSelectionLocked === true
}
};
}
return generationRotated;
};
const previousCompactionCount = activeEntry?.compactionCount ?? 0;
let pendingTerminalCompactionNotice;
let compactionNoticeGenerationInvalidated = false;
const notifyPreflightCompaction = turn.sendPolicy === "allow" && queued.currentInboundEventKind !== "room_event" && shouldNotifyUserAboutCompaction(config) ? async (phase, text) => {
if (phase !== "start") {
pendingTerminalCompactionNotice = {
phase,
text
};
return;
}
const noticeEntry = readTurnSessionEntry();
try {
assertPersistedGeneration(noticeEntry);
} catch (error) {
if (error instanceof ReplySessionGenerationInvalidatedError) {
compactionNoticeGenerationInvalidated = true;
operation.abortForRestart();
throw error;
}
throw error;
}
if (resolveTurnSendPolicy(noticeEntry, turn.queued) === "deny") return;
await params.onCompactionNoticePayload?.(createCompactionNoticePayload({
phase,
currentMessageId: resolveFollowupCurrentMessageId(queued)
}), turn);
} : void 0;
const preflightEntry = session.current();
try {
activeEntry = await runSessionCompactionIfNeeded({
cfg: config,
followupRun: turn.queued,
promptForEstimate: turn.queued.prompt,
defaultModel: params.defaults.defaultModel,
sessionEntry: activeEntry,
sessionStore,
sessionKey: replySessionKey,
storePath: params.defaults.storePath,
isHeartbeat: params.defaults.opts?.isHeartbeat === true,
abortSignal: operation.abortSignal,
onCompactionStart: () => operation.setPhase("preflight_compacting"),
onSessionIdChanged: (sessionId) => operation.updateSessionId(sessionId),
onCompactionNotice: notifyPreflightCompaction
});
if (compactionNoticeGenerationInvalidated) throw new ReplySessionGenerationInvalidatedError("Follow-up session generation changed during preflight notice delivery");
if (replySessionKey && params.defaults.storePath) {
const persistedEntry = readTurnSessionEntry();
if (!persistedEntry && preflightEntry || persistedEntry && !isSameSessionGeneration(persistedEntry, preflightEntry) && !isSameSessionGeneration(persistedEntry, activeEntry)) throw new ReplySessionGenerationInvalidatedError("Follow-up session generation changed during preflight");
if (persistedEntry && (!activeEntry || isSameSessionGeneration(persistedEntry, activeEntry) && persistedEntry.updatedAt >= activeEntry.updatedAt)) activeEntry = persistedEntry;
}
if (activeEntry) {
session.adopt(activeEntry);
activeEntry = session.current() ?? activeEntry;
}
const generationRotated = synchronizeTurnGeneration(activeEntry, preflightEntry);
refreshTurnSessionState(activeEntry);
turn.preflightCompactionApplied = generationRotated || (activeEntry?.compactionCount ?? 0) > previousCompactionCount;
} catch (error) {
const failureEntry = readTurnSessionEntry();
if (!isSameSessionGeneration(failureEntry, session.current())) assertPersistedGeneration(failureEntry);
if (failureEntry) {
session.adopt(failureEntry);
activeEntry = session.current() ?? failureEntry;
}
synchronizeTurnGeneration(activeEntry, preflightEntry);
refreshTurnSessionState(activeEntry);
if (compactionNoticeGenerationInvalidated) throw new ReplySessionGenerationInvalidatedError("Follow-up session generation changed during preflight notice delivery");
if (error instanceof ReplySessionGenerationInvalidatedError) throw error;
operation.fail("run_failed", error);
const admittedVerboseLevel = turn.queued.run.verboseLevelOverride ?? session.current()?.verboseLevel ?? turn.queued.run.verboseLevel;
const text = buildPreflightCompactionFailureText(formatErrorMessage(error), { includeDetails: admittedVerboseLevel === "on" || admittedVerboseLevel === "full" });
if (!text) turn.preflightError = error;
else turn.preflightFailurePayload = markReplyPayloadForSourceSuppressionDelivery({ text });
}
if (pendingTerminalCompactionNotice && turn.sendPolicy === "allow" && turn.queued.currentInboundEventKind !== "room_event") await params.onCompactionNoticePayload?.(createCompactionNoticePayload({
phase: pendingTerminalCompactionNotice.phase,
text: pendingTerminalCompactionNotice.text,
currentMessageId: resolveFollowupCurrentMessageId(turn.queued)
}), turn);
return {
kind: "admitted",
turn
};
} catch (error) {
if (queuedFollowupAdmitted) await settleQueuedFollowupPresentation(params.defaults);
operation.complete();
throw error instanceof Error ? error : new Error(formatErrorMessage(error));
}
}
//#endregion
//#region src/auto-reply/reply/agent-runner-session-reset.ts
const deps = {
generateSecureUuid,
persistSessionResetLifecycle,
refreshQueuedFollowupSession,
resetRegisteredAgentHarnessSessions,
error: (message) => defaultRuntime.error(message)
};
function setAgentRunnerSessionResetTestDeps(overrides) {
Object.assign(deps, {
generateSecureUuid,
persistSessionResetLifecycle,
refreshQueuedFollowupSession,
resetRegisteredAgentHarnessSessions,
error: (message) => defaultRuntime.error(message),
...overrides
});
}
if (process.env.VITEST || false) globalThis[Symbol.for("openclaw.agentRunnerSessionResetTestApi")] = { setAgentRunnerSessionResetTestDeps };
async function resetReplyRunSession(params) {
if (!params.sessionKey || !params.activeSessionStore || !params.storePath) return false;
const prevEntry = params.activeSessionStore[params.sessionKey] ?? params.activeSessionEntry;
if (!prevEntry) return false;
if (isModelSelectionLocked(prevEntry)) throw new ModelSelectionLockedError(MODEL_SELECTION_LOCKED_RESET_MESSAGE);
const nextSessionId = prevEntry.sessionId;
const now = Date.now();
const nextEntry = {
...prevEntry,
sessionId: nextSessionId,
previousSessionId: void 0,
lifecycleRevision: deps.generateSecureUuid(),
updatedAt: now,
sessionStartedAt: now,
lastInteractionAt: now,
systemSent: false,
abortedLastRun: false,
lifecycleRunId: void 0,
lastRunId: void 0,
modelProvider: void 0,
model: void 0,
inputTokens: void 0,
outputTokens: void 0,
totalTokens: void 0,
totalTokensFresh: false,
totalTokensVersion: void 0,
estimatedCostUsd: void 0,
cacheRead: void 0,
cacheWrite: void 0,
contextTokens: void 0,
contextTokensSource: void 0,
contextBudgetStatus: void 0,
systemPromptReport: void 0,
fallbackNotice: void 0,
sessionDiffBaseline: void 0,
sessionDiffBaselineCapture: prevEntry.execNode ? void 0 : createSessionDiffBaselineCaptureClaim(),
compactionCount: 0,
transcriptByteCompactionLatch: void 0,
memoryFlush: void 0
};
clearAllCliSessions(nextEntry);
nextEntry.agentHarnessId = void 0;
transitionMainSessionRecovery(nextEntry, { kind: "clear" });
const agentId = params.followupRun.run.agentId;
const nextSessionFile = params.sessionKey;
params.activeSessionStore[params.sessionKey] = nextEntry;
try {
await deps.persistSessionResetLifecycle({
agentId,
nextEntry,
nextSessionFile,
previousEntry: prevEntry,
workspaceDir: params.followupRun.run.workspaceDir,
sessionKey: params.sessionKey,
storePath: params.storePath
});
} catch (err) {
params.activeSessionStore[params.sessionKey] = prevEntry;
deps.error(`Failed to persist session reset after ${params.options.failureLabel} (${params.sessionKey}): ${String(err)}`);
throw err;
}
let settledEntry = nextEntry;
try {
if (nextEntry.sessionDiffBaselineCapture) settledEntry = await ensureSessionDiffBaseline({
cwd: nextEntry.spawnedCwd ?? nextEntry.spawnedWorkspaceDir ?? params.followupRun.run.workspaceDir,
entry: nextEntry,
isNewSession: false,
sessionKey: params.sessionKey,
storePath: params.storePath
});
} catch (error) {
const authoritative = loadSessionEntry({
sessionKey: params.sessionKey,
storePath: params.storePath
});
if (authoritative) params.activeSessionStore[params.sessionKey] = authoritative;
else delete params.activeSessionStore[params.sessionKey];
throw error;
}
params.activeSessionStore[params.sessionKey] = settledEntry;
clearBootstrapSnapshotOnSessionBoundary({
boundaryAppended: true,
sessionKey: params.sessionKey
});
await deps.resetRegisteredAgentHarnessSessions({
agentId,
sessionId: nextSessionId,
sessionKey: params.sessionKey,
sessionFile: nextSessionFile,
reason: "reset"
});
params.followupRun.run.sessionId = nextSessionId;
params.followupRun.run.sessionFile = nextSessionFile;
deps.refreshQueuedFollowupSession({
key: params.queueKey,
previousSessionId: prevEntry.sessionId,
nextSessionId,
nextSessionFile
});
params.onActiveSessionEntry(settledEntry);
params.onNewSession(nextSessionId, nextSessionFile);
deps.error(params.options.buildLogMessage(nextSessionId));
return true;
}
//#endregion
//#region src/auto-reply/reply/followup-turn-execution.ts
function buildFollowupTemplateContext(turn) {
const queued = turn.queued;
const run = queued.run;
const surface = queued.originatingChannel ?? run.messageProvider;
const sessionKey = turn.session.kind === "session" ? turn.session.key : run.sessionKey;
const currentMessageId = run.inputProvenance?.kind === "internal_system" && run.inputProvenance.sourceTool === "restart-sentinel" ? queued.originatingReplyToId : queued.messageId;
return {
Provider: run.messageProvider,
Surface: surface,
OriginatingChannel: queued.originatingChannel,
OriginatingTo: queued.originatingTo,
To: queued.originatingTo,
AccountId: queued.originatingAccountId ?? run.agentAccountId,
ChatType: queued.originatingChatType ?? run.chatType,
SessionKey: sessionKey,
RuntimePolicySessionKey: run.runtimePolicySessionKey ?? sessionKey,
MessageSid: currentMessageId,
MessageSidFull: currentMessageId,
MessageThreadId: queued.originatingThreadId,
ReplyToId: queued.originatingReplyToId,
SenderId: run.senderId,
MemberRoleIds: run.memberRoleIds,
ChannelContext: run.channelContext,
SenderName: run.senderName,
SenderUsername: run.senderUsername,
SenderE164: run.senderE164,
GroupChannel: run.groupChannel,
GroupSpace: run.groupSpace,
InputProvenance: run.inputProvenance,
InboundEventKind: queued.currentInboundEventKind,
media: queued.media
};
}
/** Adapts an admitted queued turn to the canonical agent execution owner. */
async function executeFollowupTurn(params) {
const { turn, defaults } = params;
const sourceOpts = defaults.opts;
const roomEvent = turn.queued.currentInboundEventKind === "room_event";
const progressAllowed = () => turn.sendPolicy === "allow" && !roomEvent;
const currentVerboseLevel = () => {
if (turn.queued.run.verboseLevelOverride !== void 0) return turn.queued.run.verboseLevelOverride;
const session = turn.session;
if (session.kind === "session" && session.storePath) try {
const loadedEntry = loadSessionEntryReadOnly({
storePath: session.storePath,
sessionKey: session.key
});
const ownedEntry = session.current();
if (loadedEntry !== void 0 && ownedEntry !== void 0 && loadedEntry.sessionId === ownedEntry.sessionId && loadedEntry.lifecycleRevision === ownedEntry.lifecycleRevision && loadedEntry.updatedAt >= ownedEntry.updatedAt) {
const level = loadedEntry.verboseLevel;
if (level === "off" || level === "on" || level === "full") return level;
}
} catch {}
const level = session.current()?.verboseLevel ?? turn.queued.run.verboseLevel;
return level === "on" || level === "full" ? level : "off";
};
const forceToolResultProgress = sourceOpts?.forceToolResultProgress === true;
const channelToolResultProgress = forceToolResultProgress ? sourceOpts.onToolResult : void 0;
const shouldEmitVerboseToolResult = () => {
const level = currentVerboseLevel();
return level === "on" || level === "full";
};
const shouldEmitToolResult = () => progressAllowed() && (forceToolResultProgress || shouldEmitVerboseToolResult());
const shouldEmitToolOutput = () => progressAllowed() && currentVerboseLevel() === "full";
const shouldEmitToolLifecycle = () => progressAllowed() && (shouldEmitToolResult() || defaults.opts?.allowToolLifecycleWhenProgressHidden === true);
const { commentaryPayloadsEnabled, draftOwnsCommentaryProgress } = resolveTurnCommentaryProgressOwner({
commentaryPayloadsEnabled: sourceOpts?.commentaryPayloadsEnabled === true,
options: sourceOpts,
resolveVerboseProgressVisibility: () => progressAllowed() && shouldEmitVerboseToolResult()
});
let progressChain = Promise.resolve();
let pendingProgressTaskFailure;
const pendingWorkTasks = /* @__PURE__ */ new Set();
const enqueueProgress = (deliver) => {
const deliveryTask = progressChain.then(deliver);
progressChain = deliveryTask.catch(() => void 0);
const trackedTask = deliveryTask.catch((error) => {
pendingProgressTaskFailure ??= error;
throw error;
}).finally(() => pendingWorkTasks.delete(trackedTask));
trackedTask.catch(() => void 0);
pendingWorkTasks.add(trackedTask);
return progressChain;
};
const enqueueProgressResult = async (deliver) => {
let completed = false;
let result = false;
await enqueueProgress(async () => {
result = await deliver();
completed = true;
});
return completed ? result : false;
};
const wrap = (callback, allowed = progressAllowed) => callback ? (value) => enqueueProgress(async () => {
if (allowed()) await callback(value);
}) : void 0;
const wrapVisibility = (callback, allowed = progressAllowed) => callback ? (value) => enqueueProgressResult(async () => {
if (!allowed()) return false;
return (await settleProgressVisibilityCallbackResult(callback(value))).visible;
}) : void 0;
const baseTypingSignals = createTypingSignaler({
typing: defaults.typing,
mode: progressAllowed() ? defaults.typingMode : "never",
isHeartbeat: defaults.opts?.isHeartbeat === true
});
const typingSignals = {
...baseTypingSignals,
signalRunStart: () => enqueueProgress(baseTypingSignals.signalRunStart),
signalMessageStart: () => enqueueProgress(baseTypingSignals.signalMessageStart),
signalTextDelta: (text) => enqueueProgress(() => baseTypingSignals.signalTextDelta(text)),
signalReasoningDelta: () => enqueueProgress(baseTypingSignals.signalReasoningDelta),
signalToolStart: () => enqueueProgress(baseTypingSignals.signalToolStart),
signalExecutionActivity: () => enqueueProgress(baseTypingSignals.signalExecutionActivity ?? baseTypingSignals.signalRunStart)
};
const progressOpts = {
...sourceOpts,
toolsAllow: turn.queued.toolsAllow,
disableTools: turn.queued.disableTools,
commentaryPayloadsEnabled,
runId: turn.runId,
onBlockReply: void 0,
onPartialReply: void 0,
onAssistantMessageStart: void 0,
onToolStart: wrapVisibility(sourceOpts?.onToolStart, shouldEmitToolLifecycle),
onCommandOutput: sourceOpts?.onCommandOutput ? (output) => enqueueProgressResult(async () => {
if (!shouldEmitToolResult()) return false;
return (await settleProgressVisibilityCallbackResult(sourceOpts.onCommandOutput(output))).visible;
}) : void 0,
onItemEvent: sourceOpts?.onItemEvent ? (item) => enqueueProgressResult(async () => {
if (!(progressAllowed() && item.kind === "preamble" && draftOwnsCommentaryProgress) && !shouldEmitToolResult()) return false;
return (await settleProgressVisibilityCallbackResult(sourceOpts.onItemEvent(item))).visible;
}) : void 0,
onNarrationUpdate: wrap(sourceOpts?.onNarrationUpdate),
onPlanUpdate: wrapVisibility(sourceOpts?.onPlanUpdate),
onApprovalEvent: wrapVisibility(sourceOpts?.onApprovalEvent, shouldEmitToolResult),
onPatchSummary: wrapVisibility(sourceOpts?.onPatchSummary, shouldEmitToolResult),
onCompactionStart: sourceOpts?.onCompactionStart ? () => enqueueProgressResult(async () => progressAllowed() ? (await settleProgressVisibilityCallbackResult(sourceOpts.onCompactionStart())).visible : false) : void 0,
onCompactionEnd: sourceOpts?.onCompactionEnd ? (payload) => enqueueProgressResult(async () => progressAllowed() ? (await settleProgressVisibilityCallbackResult(sourceOpts.onCompactionEnd(payload))).visible : false) : void 0,
onReasoningStream: wrapVisibility(sourceOpts?.onReasoningStream),
onReasoningProgress: wrap(sourceOpts?.onReasoningProgress),
onReasoningEnd: sourceOpts?.onReasoningEnd ? () => enqueueProgressResult(async () => progressAllowed() ? (await settleProgressVisibilityCallbackResult(sourceOpts.onReasoningEnd())).visible : false) : void 0,
onToolResult: async (payload) => {
return await enqueueProgressResult(async () => {
if (!progressAllowed()) return false;
const requiresDurableToolResult = requiresDurableToolResultDelivery(payload);
const verboseToolResult = !requiresDurableToolResult && shouldEmitVerboseToolResult();
const transientToolResultProgress = requiresDurableToolResult ? void 0 : channelToolResultProgress;
const toolResultDeliveryAvailable = Boolean(transientToolResultProgress) || verboseToolResult || requiresDurableToolResult;
if (turn.queued.run.sourceReplyDeliveryMode === "message_tool_only" && !toolResultDeliveryAvailable) return false;
return transientToolResultProgress && !verboseToolResult ? (await settleProgressVisibilityCallbackResult(transientToolResultProgress(payload))).visible : await params.onToolResult(payload, { runId: turn.runId }).then(() => true);
});
}
};
let pendingToolTaskFailure;
const pendingToolTasks = new class extends Set {
add(task) {
const watcher = task.catch((error) => {
pendingToolTaskFailure ??= error;
throw error;
}).finally(() => pendingWorkTasks.delete(watcher));
watcher.catch(() => void 0);
pendingWorkTasks.add(watcher);
return super.add(task);
}
}();
let pendingWorkDrain;
const drainPendingWork = () => {
pendingWorkDrain ??= (async () => {
await drainPendingToolTasks({
tasks: pendingWorkTasks,
onTimeout: logVerbose
});
pendingToolTasks.clear();
})();
return pendingWorkDrain;
};
const sessionCtx = buildFollowupTemplateContext(turn);
if (turn.preflightError) throw turn.preflightError instanceof Error ? turn.preflightError : new Error(formatErrorMessage(turn.preflightError));
let execution;
const runStartedAt = Date.now();
if (turn.preflightFailurePayload) execution = {
runId: turn.runId,
outcome: {
kind: "rejected",
payload: turn.preflightFailurePayload
}
};
else try {
const execute = () => executeAgentTurn({
commandBody: turn.queued.prompt,
transcriptCommandBody: turn.queued.transcriptPrompt,
followupRun: turn.queued,
sessionCtx,
replyOperation: turn.operation,
opts: progressOpts,
typingSignals,
blockReplyPipeline: null,
blockStreamingEnabled: false,
resolvedBlockStreamingBreak: turn.queued.run.blockReplyBreak,
applyReplyToMode: (payload) => payload,
shouldEmitToolResult,
shouldEmitToolOutput,
pendingToolTasks,
resetSessionAfterRoleOrderingConflict: async (reason) => {
const session = turn.session;
if (session.kind !== "session") return false;
return await resetReplyRunSession({
options: {
failureLabel: "role ordering conflict",
buildLogMessage: (nextSessionId) => `Role ordering conflict (${reason}). Restarting session ${session.key} -> ${nextSessionId}.`,
cleanupTranscripts: true
},
sessionKey: session.key,
queueKey: session.key,
activeSessionEntry: session.current(),
activeSessionStore: turn.sessionStore,
storePath: session.storePath,
followupRun: turn.queued,
onActiveSessionEntry: (entry) => {
session.adopt(entry);
turn.operation.updateSessionId(entry.sessionId);
},
onNewSession: () => void 0
});
},
isHeartbeat: sourceOpts?.isHeartbeat === true,
sessionKey: turn.session.kind === "session" ? turn.session.key : void 0,
runtimePolicySessionKey: turn.queued.run.runtimePolicySessionKey,
getActiveSessionEntry: turn.session.current,
activeSessionStore: turn.sessionStore,
storePath: turn.session.kind === "session" ? turn.session.storePath : void 0,
resolvedVerboseLevel: currentVerboseLevel() ?? "off",
toolProgressDetail: defaults.toolProgressDetail,
onCompactionNoticePayload: (payload) => enqueueProgress(() => progressAllowed() ? params.onCompactionNoticePayload(payload, { runId: turn.runId }) : void 0)
});
const recorder = turn.queued.userTurnTranscriptRecorder;
await recorder?.resolveMessage();
turn.operation.abortSignal.throwIfAborted();
execution = await (recorder?.withPendingInput ? recorder.withPendingInput(execute) : execute());
} catch (error) {
await drainPendingWork();
if (!hasReplyOperationExecutionStarted(turn.operation)) throw error;
turn.operation.fail("run_failed", error);
execution = {
runId: turn.runId,
outcome: {
kind: "rejected",
payload: buildTerminalAgentRunFailureReplyPayload({
isHeartbeat: sourceOpts?.isHeartbeat,
visibleReplyDelivered: false,
sessionCtx,
cfg: turn.config
})
}
};
}
recordReplyOperationAgentTurn(turn.queued.replyOperationRunStates, turn.operation, execution.outcome);
return {
commentaryPayloadsEnabled,
execution,
runStartedAt,
sessionCtx,
pendingToolTasks,
progress: { drain: async () => {
await drainPendingWork();
const firstFailure = pendingProgressTaskFailure ?? pendingToolTaskFailure;
if (firstFailure !== void 0) throw firstFailure instanceof Error ? firstFailure : new Error(formatErrorMessage(firstFailure));
} }
};
}
//#endregion
//#region src/auto-reply/reply/followup-runner.ts
/** Composes queued admission, canonical execution, accounting, and delivery. */
/** Creates the function that drains one queued follow-up run. */
function createFollowupRunner(initialDefaults) {
const resolveGatewayContext = Object.hasOwn(initialDefaults, "resolveGatewayContext") ? initialDefaults.resolveGatewayContext : getPluginRuntimeGatewayRequestScope()?.resolveGatewayContext;
const defaults = {
...initialDefaults,
resolveGatewayContext
};
const runFollowup = (queued) => withPluginRuntimeGatewayContextResolver(resolveGatewayContext, () => executeFollowup(queued), { inheritRequestScope: false });
const executeFollowup = async (queued) => {
let disposition = {
kind: "retry",
error: void 0
};
let operation;
let admittedRunId;
let queuedFollowupAdmitted = false;
const initiallyAborted = queued.abortSignal?.aborted === true || queued.queueAbortSignal?.aborted === true;
const endDeliveryCorrelations = initiallyAborted ? [] : (queued.deliveryCorrelations ?? []).map((correlation) => correlation.begin()).filter((end) => typeof end === "function");
try {
if (initiallyAborted) {
disposition = { kind: "consumed" };
return;
}
const admission = await admitFollowupTurn({
queued,
defaults,
onCompactionNoticePayload: async (payload, turn) => {
await deliverFollowupDecision({
decision: {
kind: "deliver",
payloads: [payload]
},
turn,
defaults,
runId: turn.runId,
runFollowup,
kind: "block"
});
}
});
switch (admission.kind) {
case "deferred": throw new FollowupRunDeferredError(`Follow-up reply lane is still active (${admission.reason})`);
case "skipped":
operation = admission.operation;
disposition = { kind: "consumed" };
return;
}
const turn = admission.turn;
admittedRunId = turn.runId;
operation = turn.operation;
queuedFollowupAdmitted = true;
const execution = await executeFollowupTurn({
turn,
defaults,
onToolResult: async (payload, identity) => {
await deliverFollowupDecision({
decision: {
kind: "deliver",
payloads: [payload]
},
turn,
defaults,
runId: identity.runId,
runFollowup,
kind: "tool"
});
},
onCompactionNoticePayload: async (payload, identity) => {
await deliverFollowupDecision({
decision: {
kind: "deliver",
payloads: [payload]
},
turn,
defaults,
runId: identity.runId,
runFollowup,
kind: "block"
});
}
});
disposition = { kind: "consumed" };
try {
await execution.progress.drain();
} catch (error) {
defaultRuntime.error?.(`followup queue: progress presentation failed after execution: ${formatErrorMessage(error)}`);
operation.fail("run_failed", error);
}
if (execution.execution.outcome.kind === "settled" && hasCompletedSourceReplyDeliveryEvidence(execution.execution.outcome.result)) await defaults.opts?.onObservedReplyDelivery?.();
const accounting = await accountFollowupTurn({
turn,
defaults,
execution
});
const deliveryOpts = {
...defaults.opts,
commentaryPayloadsEnabled: execution.commentaryPayloadsEnabled
};
await deliverFollowupDecision({
decision: resolveFollowupDeliveryDecision({
turn,
execution: execution.execution,
accounting,
opts: deliveryOpts
}),
turn,
defaults,
runId: execution.execution.runId,
runFollowup
});
} catch (error) {
if (error instanceof FollowupRunDeferredError) disposition = {
kind: "deferred",
reason: error.message
};
else if (operation?.result?.kind === "aborted" && operation.result.code === "aborted_by_user") disposition = { kind: "consumed" };
else if (disposition.kind === "consumed") {
defaultRuntime.error?.(`followup queue: terminal handling failed after execution; refusing replay: ${formatErrorMessage(error)}`);
operation?.fail("run_failed", error);
} else disposition = {
kind: "retry",
error
};
} finally {
if (queuedFollowupAdmitted) await settleQueuedFollowupPresentation(defaults);
for (const end of endDeliveryCorrelations.toReversed()) try {
end();
} catch (error) {
defaultRuntime.error?.(`followup queue: delivery correlation cleanup failed: ${formatErrorMessage(error)}`);
}
if (disposition.kind === "consumed") {
completeFollowupRunLifecycle(queued);
if (admittedRunId) clearAgentRunContext(admittedRunId);
} else if (disposition.kind === "retry" && admittedRunId) clearAgentRunContext(admittedRunId);
operation?.complete();
defaults.typing.markRunComplete();
defaults.typing.markDispatchIdle();
}
if (disposition.kind === "deferred") throw new FollowupRunDeferredError(`Follow-up reply lane is still active (${disposition.reason})`);
if (disposition.kind === "retry") throw disposition.error;
};
return runFollowup;
}
//#endregion
//#region src/auto-reply/reply/agent-runner-execute.ts
function markPostCompactionFailureResult(result, postCompactionModelFailure) {
if (Array.isArray(result)) return result.map((payload) => markPostCompactionModelFailurePayload(postCompactionModelFailure, payload));
return result ? markPostCompactionModelFailurePayload(postCompactionModelFailure, result) : result;
}
async function executePreparedReplyAgentRun(context) {
const { activeSessionStore, admitUserTurn: admitUserTurnWithRecovery, applyReplyToMode, beginBeforeAgentReply: beginBeforeAgentReplyWithRecovery, blockReplyChunking, blockReplyPipeline, blockStreamingEnabled, cfg, checkpointBeforeAgentReply: checkpointBeforeAgentReplyWithRecovery, commandBody, defaultModel, followupRun, getActiveIsNewSession, getActiveSessionEntry, isHeartbeat, isRestartRecoveryArmed, opts, pendingToolTasks, queueKey, replyMediaContext, replyOperation, replyRouteThreadId, replyThreadingOverride, replyToChannel, replyToMode, resetSessionAfterRoleOrderingConflict, resolvedBlockStreamingBreak, resolvedQueue, resolvedVerboseLevel, returnWithQueuedFollowupDrain, runtimePolicySessionKey, sendDirectCompactionNotice, sessionCtx, sessionKey, setActiveSessionEntry, setRunFollowupTurn, shouldEmitToolOutput, shouldEmitToolResult, shouldInjectGroupIntro, storePath, toolProgressDetail, traceAgentPhase, transcriptCommandBody, turnAdoptionLifecycle, typing, typingMode, typingSignals } = context;
let activeSessionEntry = getActiveSessionEntry();
const admitUserTurn = async (...args) => {
const result = await admitUserTurnWithRecovery(...args);
activeSessionEntry = getActiveSessionEntry();
return result;
};
const beginBeforeAgentReply = async (...args) => {
const result = await beginBeforeAgentReplyWithRecovery(...args);
activeSessionEntry = getActiveSessionEntry();
return result;
};
const checkpointBeforeAgentReply = async (...args) => {
const result = await checkpointBeforeAgentReplyWithRecovery(...args);
activeSessionEntry = getActiveSessionEntry();
return result;
};
await typingSignals.signalRunStart();
const memoryFlushResult = await traceAgentPhase("reply.memory_flush", () => runMemoryFlushIfNeeded({
cfg,
followupRun,
promptForEstimate: followupRun.prompt,
sessionCtx,
opts,
defaultModel,
resolvedVerboseLevel,
sessionEntry: activeSessionEntry,
sessionStore: activeSessionStore,
sessionKey,
runtimePolicySessionKey,
storePath,
isHeartbeat,
replyOperation,
onVisibleErrorPayloads: (payloads) => {
logVerbose(`memory flush produced ${payloads.length} visible maintenance error payload(s); continuing user reply`);
}
}));
activeSessionEntry = memoryFlushResult.sessionEntry;
setActiveSessionEntry(activeSessionEntry);
if (replyOperation.result?.kind === "aborted") throw replyOperation.abortSignal.reason ?? /* @__PURE__ */ new Error("reply operation aborted");
const prePreflightCompactionCount = activeSessionEntry?.compactionCount ?? 0;
activeSessionEntry = await traceAgentPhase("reply.preflight_compaction", () => runSessionCompactionIfNeeded({
cfg,
followupRun,
promptForEstimate: followupRun.prompt,
defaultModel,
sessionEntry: activeSessionEntry,
sessionStore: activeSessionStore,
sessionKey,
runtimePolicySessionKey,
storePath,
isHeartbeat,
abortSignal: replyOperation.abortSignal,
onCompactionStart: () => replyOperation.setPhase("preflight_compacting"),
onSessionIdChanged: (sessionId) => replyOperation.updateSessionId(sessionId),
onCompactionNotice: sendDirectCompactionNotice
}));
setActiveSessionEntry(activeSessionEntry);
const preflightCompactionApplied = (activeSessionEntry?.compactionCount ?? 0) > prePreflightCompactionCount;
if (memoryFlushResult.outcome === "exhausted") await sendDirectCompactionNotice?.("memory_flush_degraded");
const runFollowupTurn = createFollowupRunner({
resolveGatewayContext: getGatewayContextResolver(replyOperation),
opts,
typing,
typingMode,
sessionEntry: activeSessionEntry,
sessionStore: activeSessionStore,
sessionKey,
storePath,
defaultModel,
toolProgressDetail
});
setRunFollowupTurn(runFollowupTurn);
replyOperation.setPhase("running");
const runStartedAt = Date.now();
if (await admitUserTurn(followupRun.userTurnTranscriptRecorder) === "duplicate-source") return returnWithQueuedFollowupDrain(void 0);
await turnAdoptionLifecycle?.onAdopted();
const runOutcome = await withBeforeAgentReplyObserver({
beforeDispatch: async () => {
return await beginBeforeAgentReply();
},
afterDispatch: async (hookResult) => {
if (!hookResult?.handled) {
await checkpointBeforeAgentReply({ state: void 0 });
return hookResult;
}
const hookReply = hookResult.reply ?? { text: "NO_REPLY" };
const hookFinalDeliveryText = buildRecoverablePendingFinalDeliveryText([hookReply]);
const normalizedHookReplies = normalizePendingFinalDeliveryPayloads([hookReply]);
let hookCheckpoint = { state: normalizedHookReplies.length === 0 ? "handled-silent" : "pending" };
if (sessionKey && storePath && normalizedHookReplies.length > 0) {
if (!resolveSourceReplyPolicy({
cfg,
sessionCtx,
sessionEntry: activeSessionEntry,
sessionKey,
runtimePolicySessionKey,
opts
}).suppressDelivery) {
const pendingFinalDeliveryIntentId = crypto.randomUUID();
const pendingFinalDeliveryDeliveryId = crypto.randomUUID();
setReplyPayloadMetadata(hookReply, { pendingFinalDeliveryCompletion: {
deliveryId: pendingFinalDeliveryDeliveryId,
intentId: pendingFinalDeliveryIntentId,
...activeSessionEntry?.restartRecoveryDeliveryRunId ? { recoveryRunId: activeSessionEntry.restartRecoveryDeliveryRunId } : {},
sessionId: replyOperation.sessionId,
sessionKey,
storePath
} });
hookCheckpoint = {
state: "handled-reply",
pendingFinalDelivery: {
text: hookFinalDeliveryText ?? "",
intentId: pendingFinalDeliveryIntentId,
deliveries: [{
id: pendingFinalDeliveryDeliveryId,
state: "prepared"
}],
context: resolveReplyRunDeliveryContext({
cfg,
sessionCtx,
sessionEntry: activeSessionEntry,
sessionKey,
runtimePolicySessionKey,
opts
})
}
};
} else hookCheckpoint = { state: "handled-silent" };
}
await checkpointBeforeAgentReply(hookCheckpoint);
return {
...hookResult,
reply: hookReply
};
}
}, () => {
const gitCoauthorAttribution = prepareGitCoauthorAttribution({
agentId: followupRun.run.agentId,
config: cfg,
currentProfileId: readSessionInputProfileId(sessionCtx),
sessionKey,
storePath
});
const agentTurnOpts = gitCoauthorAttribution ? {
...opts,
gitCoauthorAttribution
} : opts;
return traceAgentPhase("reply.run_agent_turn", () => executeAgentTurn({
commandBody,
transcriptCommandBody,
followupRun,
sessionCtx,
replyThreading: replyThreadingOverride ?? sessionCtx.ReplyThreading,
replyOperation,
opts: agentTurnOpts,
resolveVisibleReplyDelivery: context.resolveVisibleReplyDelivery,
typingSignals,
blockReplyPipeline,
blockStreamingEnabled,
blockReplyChunking,
resolvedBlockStreamingBreak,
applyReplyToMode,
shouldEmitToolResult,
shouldEmitToolOutput,
pendingToolTasks,
resetSessionAfterRoleOrderingConflict,
isHeartbeat,
sessionKey,
runtimePolicySessionKey,
getActiveSessionEntry,
activeSessionStore,
storePath,
resolvedVerboseLevel,
toolProgressDetail,
replyMediaContext,
isRestartRecoveryArmed
}));
});
const operationSuperseded = isReplyOperationSuperseded(replyOperation);
recordReplyOperationAgentTurn(followupRun.replyOperationRunStates, replyOperation, runOutcome.outcome);
activeSessionEntry = getActiveSessionEntry();
const activeIsNewSession = getActiveIsNewSession();
if (runOutcome.outcome.kind !== "settled") await accountAgentTurnCompaction({
compaction: runOutcome.outcome.compaction,
sessionStore: activeSessionStore,
replyOperation
});
if (operationSuperseded) return { text: SILENT_REPLY_TOKEN };
if (runOutcome.outcome.kind !== "settled") {
if (runOutcome.outcome.kind === "rejected" && !replyOperation.result) replyOperation.fail("run_failed", /* @__PURE__ */ new Error("reply operation exited with final payload"));
return returnWithQueuedFollowupDrain(runOutcome.outcome.kind === "rejected" ? markPostCompactionModelFailurePayload(runOutcome.outcome.postCompactionModelFailure, runOutcome.outcome.payload) : { text: SILENT_REPLY_TOKEN });
}
return markPostCompactionFailureResult(await finalizeReplyAgentRun({
activeIsNewSession,
activeSessionEntry,
activeSessionStore,
blockReplyPipeline,
blockStreamingEnabled,
cfg,
commandBody,
defaultModel,
followupRun,
isHeartbeat,
opts,
pendingToolTasks,
preflightCompactionApplied,
queueKey,
replyMediaContext,
replyOperation,
replyRouteThreadId,
replyThreadingOverride,
replyToChannel,
replyToMode,
resolvedBlockStreamingBreak,
resolvedQueue,
resolvedVerboseLevel,
returnWithQueuedFollowupDrain,
runFollowupTurn,
execution: runOutcome.outcome,
runId: runOutcome.runId,
runStartedAt,
runtimePolicySessionKey,
sessionCtx,
sessionKey,
shouldInjectGroupIntro,
storePath,
typingSignals
}), runOutcome.outcome.postCompactionModelFailure);
}
function createReplyAgentRestartRecoveryController(context) {
const { activeSessionStore, cfg, followupRun, getActiveSessionEntry, opts, replyOperation, restartRecoverySourceTurnId, runtimePolicySessionKey, sessionCtx, sessionKey, setActiveSessionEntry, storePath } = context;
const restartRecoverySameChannelThreadRequired = restartRecoverySourceTurnId ? buildThreadingToolContext({
sessionCtx,
config: cfg,
hasRepliedRef: void 0
}).sameChannelThreadRequired : void 0;
const { admitUserTurn, beginBeforeAgentReply, checkpointBeforeAgentReply, clear: clearRestartRecoveryDeliveryClaim, isArmed: isRestartRecoveryArmed } = createReplyRestartRecoveryClaimController({
lifecycleGeneration: replyOperation.lifecycleGeneration,
admissionRunId: normalizeOptionalString(sessionCtx.MessageSid) ?? normalizeOptionalString(sessionCtx.MessageSidFull),
getEntry: () => sessionKey ? activeSessionStore?.[sessionKey] ?? getActiveSessionEntry() : getActiveSessionEntry(),
getSessionId: () => replyOperation.sessionId,
isRestartAbort: () => replyOperation.result?.kind === "aborted" && replyOperation.result.code === "aborted_for_restart",
resolveDeliveryContext: (entry) => sessionKey ? resolveReplyRunDeliveryContext({
cfg,
sessionCtx,
sessionEntry: entry,
sessionKey,
runtimePolicySessionKey,
opts
}) : void 0,
requesterAccountId: followupRun.originatingAccountId ?? sessionCtx.AccountId ?? followupRun.run.agentAccountId,
requesterSenderId: sessionCtx.SenderId,
resolveUserTurnTarget: ({ entry, sessionId, sessionKey: targetSessionKey, storePath: targetStorePath }) => ({
sessionId,
sessionKey: targetSessionKey,
sessionEntry: entry,
...activeSessionStore ? { sessionStore: activeSessionStore } : {},
storePath: targetStorePath,
agentId: followupRun.run.agentId,
cwd: followupRun.run.workspaceDir,
config: cfg
}),
...sessionKey ? { sessionKey } : {},
setEntry: (entry) => {
setActiveSessionEntry(entry);
if (activeSessionStore && sessionKey) activeSessionStore[sessionKey] = entry;
},
sameChannelThreadRequired: restartRecoverySameChannelThreadRequired,
sourceTurnId: restartRecoverySourceTurnId,
sourceReplyDeliveryMode: sessionKey ? resolveSourceReplyPolicy({
cfg,
sessionCtx,
sessionEntry: getActiveSessionEntry(),
sessionKey,
runtimePolicySessionKey,
opts
}).sourceReplyDeliveryMode : opts?.sourceReplyDeliveryMode,
...storePath ? { storePath } : {}
});
return {
admitUserTurn,
beginBeforeAgentReply,
checkpointBeforeAgentReply,
clear: clearRestartRecoveryDeliveryClaim,
isArmed: isRestartRecoveryArmed
};
}
//#endregion
//#region src/auto-reply/reply/agent-runner-question-input.ts
/** Question-only runtimes accept answers without exposing ordinary steering. */
async function runReplyQuestionInput(params) {
const { followupRun, opts, sessionKey } = params;
const external = followupRun.run.inputProvenance === void 0 || followupRun.run.inputProvenance.kind === "external_user";
const text = (params.transcriptCommandBody ?? followupRun.transcriptPrompt ?? params.commandBody).trim();
if (!sessionKey || opts?.isHeartbeat || params.resetTriggered || opts?.messageInjectionDisposition === "accepted" || !external || !text || followupRun.images?.length || followupRun.media?.length) return { handled: false };
const caller = resolveInboundReplyToolAuthorityOverlay({
ctx: params.sessionCtx,
sessionEntry: params.sessionEntry,
senderIsOwner: followupRun.run.senderIsOwner === true,
toolsAllow: followupRun.toolsAllow,
disableTools: followupRun.disableTools === true
});
const sourceAbort = opts?.abortSignal;
const queuedAbort = resolveFollowupAbortSignal(followupRun);
const assertSourceCurrent = () => {
sourceAbort?.throwIfAborted();
queuedAbort?.throwIfAborted();
};
const state = resolveReplyOperationRunState(opts);
let outcome;
try {
if (!await claimPendingAgentQuestionAnswerFromCaller({
sessionKey,
text,
caller,
assertSourceCurrent,
persist: followupRun.userTurnTranscriptRecorder ? async () => {
await followupRun.userTurnTranscriptRecorder?.persistApproved();
} : void 0
})) return { handled: false };
outcome = { status: "answered" };
} catch (error) {
if (error instanceof QuestionDispatchRefusedError) {
if (state) state.admission = {
status: "skipped",
reason: "question-response-refused"
};
return {
handled: true,
payload: markReplyPayloadForSourceSuppressionDelivery({
text: `The answer was not sent: ${error.message}. Use the question controls in the Control UI, or check the active run and your permissions before retrying.`,
isError: true
})
};
}
if (!(error instanceof QuestionAnswerUnconfirmedError)) throw error;
outcome = {
status: "indeterminate",
errorMessage: error.message
};
}
if (state) state.admission = outcome.status === "indeterminate" ? {
status: "skipped",
reason: "question-response-indeterminate"
} : {
status: "accepted",
mode: "steer"
};
try {
await admitFollowupRunLifecycle(followupRun);
} catch (error) {
logVerbose(`question input adoption failed after custody transferred: ${String(error)}`);
} finally {
completeFollowupRunLifecycle(followupRun, "consumed");
}
return {
handled: true,
payload: outcome.status === "indeterminate" ? markReplyPayloadForSourceSuppressionDelivery({
text: outcome.errorMessage,
isError: true
}) : void 0
};
}
//#endregion
//#region src/auto-reply/reply/reply-run-typing.ts
const typingByReplyOperation = /* @__PURE__ */ new WeakMap();
/** Keep one feedback controller attached to the task that owns a reply run. */
function bindReplyOperationTyping(operation, typing) {
if (typingByReplyOperation.has(operation)) return;
typingByReplyOperation.set(operation, typing);
runAfterReplyOperationClear(operation, () => {
if (typingByReplyOperation.get(operation) !== typing) return;
typingByReplyOperation.delete(operation);
typing.cleanup();
});
}
/** Refresh the continuing task's feedback after it adopts another inbound turn. */
async function refreshReplyOperationTyping(operation, options) {
const typing = typingByReplyOperation.get(operation);
if (!typing || operation.result || !options.startIfIdle && !typing.isActive()) return;
await typing.startTypingLoop();
}
//#endregion
//#region src/auto-reply/reply/agent-runner-steer-adoption.ts
function resolveAcceptedSteerRunId(params) {
const { followupRun, sessionCtx } = params;
return expectDefined(params.restartRecoverySourceTurnId ?? buildChannelSourceTurnId({
provider: followupRun.originatingChannel ?? followupRun.run.messageProvider ?? sessionCtx.Provider,
accountId: followupRun.originatingAccountId ?? followupRun.run.agentAccountId ?? sessionCtx.AccountId,
conversationId: followupRun.originatingTo ?? followupRun.originatingChatId ?? params.sessionKey ?? followupRun.run.sessionKey,
messageId: followupRun.messageId ?? sessionCtx.MessageSidFull ?? sessionCtx.MessageSid
}) ?? normalizeOptionalString(params.opts?.runId), "steered turn id");
}
async function runActiveReplySteer(params) {
const { followupRun, queueKey, releaseAdmissionTicket, replyOperationRunState, resolvedQueue, runFollowup, sessionKey, touchActiveSessionEntry, typing, typingSignals } = params;
const registeredReplyOperation = sessionKey ? replyRunRegistry.get(sessionKey) : void 0;
const activeReplyOperation = params.providedReplyOperation?.key === sessionKey ? params.providedReplyOperation : registeredReplyOperation ?? params.providedReplyOperation;
const steerSessionId = activeReplyOperation?.sessionId ?? followupRun.run.sessionId;
const injectionTarget = replyRunRegistry.resolveCurrentMessageInjectionTarget(activeReplyOperation?.key ?? queueKey);
const parked = parkSteerCandidate(queueKey, followupRun, resolvedQueue, runFollowup);
if (!parked) {
releaseAdmissionTicket();
typing.cleanup();
return "handled";
}
const scheduleParkedFallback = () => {
const owner = replyRunRegistry.get(queueKey);
if (owner) scheduleFollowupDrainAfterReplyOperationClear({
operation: owner,
queueKey,
runFollowup
});
else scheduleFollowupDrain(queueKey, runFollowup);
};
scheduleParkedFallback();
releaseAdmissionTicket();
const fallback = async (reason) => {
parked.fallback();
if (replyOperationRunState) replyOperationRunState.admission = {
status: "accepted",
mode: "followup"
};
if (reason) logVerbose(`queue: active session ${steerSessionId} rejected steering (${reason})`);
await touchActiveSessionEntry();
typing.cleanup();
return "handled";
};
try {
const admission = await parked.admit();
if (admission === "cancelled") {
parked.consume();
typing.cleanup();
return "handled";
}
if (admission === "fallback") return await fallback();
if (!injectionTarget) return await fallback("no injectable reply operation");
const injectionAttempt = beginReplyMessageInjectionTarget(injectionTarget, followupRun.prompt, {
steeringMode: "all",
isInboundUserMessage: true,
toolAuthorityFingerprint: params.toolAuthorityFingerprint,
...params.pendingInputAuthorityFingerprint ? { pendingInputAuthorityFingerprint: params.pendingInputAuthorityFingerprint } : {},
...followupRun.images?.length ? { images: followupRun.images } : {},
...followupRun.imageOrder?.length ? { imageOrder: followupRun.imageOrder } : {},
...followupRun.media?.length ? { media: followupRun.media } : {},
waitForTranscriptCommit: true,
queueIdentity: resolveAcceptedSteerRunId(params),
abortSignal: resolveFollowupAbortSignal(followupRun),
onQueueAccepted: parked.accepted,
...resolvedQueue.debounceMs !== void 0 ? { debounceMs: resolvedQueue.debounceMs } : {},
...followupRun.run.sourceReplyDeliveryMode ? { sourceReplyDeliveryMode: followupRun.run.sourceReplyDeliveryMode } : {},
taskSuggestionDeliveryMode: followupRun.run.taskSuggestionDeliveryMode,
...followupRun.userTurnTranscriptRecorder ? { userTurnTranscriptRecorder: followupRun.userTurnTranscriptRecorder } : {}
});
const finalization = await finalizeReplyMessageInjectionAttempt({
attempt: injectionAttempt,
target: injectionTarget,
inboundAudio: followupRun.currentInboundAudio === true,
onOutcome: (outcome) => {
if (replyOperationRunState) replyOperationRunState.admission = outcome === "indeterminate" ? {
status: "skipped",
reason: "question-response-indeterminate"
} : {
status: "accepted",
mode: "steer"
};
},
onAdopted: () => admitFollowupRunLifecycle(followupRun),
shouldAbortOnAdoptionError: isIngressAdoptionLostError
});
if (finalization.status === "rejected") return await fallback(finalization.outcome.reason);
parked.consume("consumed");
if (finalization.status === "indeterminate") {
typing.cleanup();
return markReplyPayloadForSourceSuppressionDelivery({
text: finalization.outcome.errorMessage,
isError: true
});
}
const transcriptCommitUnconfirmed = finalization.outcome.result?.transcriptCommit === "unconfirmed";
if (finalization.aborted) {
if (replyOperationRunState) replyOperationRunState.messageInjectionAborted = true;
const reason = transcriptCommitUnconfirmed ? finalization.outcome.result?.errorMessage ?? "transcript commitment unconfirmed" : `adoption lost: ${formatErrorMessage(finalization.adoptionError)}`;
logVerbose(`queue: active session ${steerSessionId} aborted exact steered target without replay (${reason})`);
typing.cleanup();
return "handled";
}
if (finalization.adoptionError) logVerbose(`queue: active session ${steerSessionId} adoption finalizer failed: ${formatErrorMessage(finalization.adoptionError)}`);
if (activeReplyOperation) await refreshReplyOperationTyping(activeReplyOperation, { startIfIdle: typingSignals.shouldStartImmediately });
await touchActiveSessionEntry();
typing.cleanup();
return "handled";
} catch (error) {
if (resolveFollowupAbortSignal(followupRun)?.aborted) parked.consume();
else parked.fallback();
throw error;
} finally {
if (followupRun.steerPending) {
if (resolveFollowupAbortSignal(followupRun)?.aborted) parked.consume();
else parked.fallback();
}
}
}
//#endregion
//#region src/auto-reply/reply/agent-runner-run.ts
async function runReplyAgent(params) {
const { commandBody, transcriptCommandBody, followupRun, queueKey, resolvedQueue, shouldSteer, shouldFollowup, queueAdmissionState = "empty", isActive, isRunActive, opts, typing, sessionEntry, sessionStore, sessionKey, runtimePolicySessionKey, storePath, defaultModel, resolvedVerboseLevel, toolProgressDetail, isNewSession, blockStreamingEnabled, blockReplyChunking, resolvedBlockStreamingBreak, sessionCtx, shouldInjectGroupIntro, typingMode, resetTriggered, replyThreadingOverride, replyOperation: providedReplyOperation } = params;
const resolveGatewayContext = providedReplyOperation ? getGatewayContextResolver(providedReplyOperation) : readChannelContextGatewayContextResolver(sessionCtx) ?? getPluginRuntimeGatewayRequestScope()?.resolveGatewayContext;
const turnAdoptionLifecycle = opts?.turnAdoptionLifecycle;
const releaseAdmissionTicket = () => opts?.[REPLY_ADMISSION_TICKET]?.release();
let activeSessionEntry = sessionEntry;
const activeSessionStore = sessionStore;
let activeIsNewSession = isNewSession;
const effectiveResetTriggered = resetTriggered === true;
const activeRunQueueMode = effectiveResetTriggered ? "interrupt" : resolvedQueue.mode;
const isHeartbeat = opts?.isHeartbeat === true;
let didDeliverVisiblePartialReply = false;
const onPartialReply = opts?.onPartialReply;
const runOpts = onPartialReply ? {
...opts,
onPartialReply: async (payload) => {
const observed = await settleProgressVisibilityCallbackResult(onPartialReply(payload));
if (observed.visible && hasOutboundReplyContent(payload, { trimText: true })) didDeliverVisiblePartialReply = true;
return observed.result;
}
} : opts;
const replyOperationRunState = resolveReplyOperationRunState(opts);
followupRun.replyOperationRunStates = replyOperationRunState ? [replyOperationRunState] : void 0;
const traceAttributes = {
provider: followupRun.run.provider,
hasSessionKey: Boolean(sessionKey ?? followupRun.run.sessionKey),
isHeartbeat,
queueMode: resolvedQueue.mode,
isActive,
blockStreamingEnabled
};
const traceAgentPhase = (name, run) => measureDiagnosticsTimelineSpan(name, run, {
phase: "agent-turn",
config: followupRun.run.config,
attributes: traceAttributes
});
const effectiveShouldSteer = !isHeartbeat && !effectiveResetTriggered && shouldSteer;
const effectiveShouldFollowup = !effectiveResetTriggered && shouldFollowup;
const messageInjectionDisposition = opts?.messageInjectionDisposition ?? "none";
const incomingToolAuthorityFingerprint = resolveFollowupRunToolAuthorityFingerprint(followupRun);
const activeReplyOperation = sessionKey ? replyRunRegistry.get(sessionKey) ?? providedReplyOperation : providedReplyOperation;
const activeToolAuthorityFingerprint = activeReplyOperation?.toolAuthorityFingerprint;
const incomingAuthorityAtActiveRoute = activeReplyOperation?.toolAuthorityRoute ? resolveFollowupRunToolAuthorityFingerprint(followupRun, activeReplyOperation.toolAuthorityRoute) : void 0;
const hasAuthorityMismatch = activeReplyOperation !== void 0 && activeToolAuthorityFingerprint !== incomingToolAuthorityFingerprint;
const hasRouteOnlyAuthorityMismatch = hasAuthorityMismatch && activeToolAuthorityFingerprint !== void 0 && incomingAuthorityAtActiveRoute === activeToolAuthorityFingerprint;
const shouldQueueAuthorityMismatch = effectiveShouldSteer && isActive && hasAuthorityMismatch && !hasRouteOnlyAuthorityMismatch;
if (shouldQueueAuthorityMismatch) logVerbose(`queue: active session ${activeReplyOperation?.sessionId ?? followupRun.run.sessionId} has different or unknown tool authority; queuing instead of steering`);
const typingSignals = createTypingSignaler({
typing,
mode: typingMode,
isHeartbeat
});
const restartRecoverySourceTurnId = readChannelSourceTurnId(sessionCtx);
const restartRecoveryEntry = sessionKey && storePath ? loadSessionEntry({
storePath,
sessionKey,
clone: false,
hydrateSkillPromptRefs: false
}) ?? activeSessionEntry : activeSessionEntry;
if (restartRecoverySourceTurnId && isDuplicateRestartRecoverySource(restartRecoveryEntry, restartRecoverySourceTurnId)) {
if (restartRecoveryEntry?.status !== "running" && sessionKey && storePath && hasRestartRecoverySourceClaim(restartRecoveryEntry, restartRecoverySourceTurnId)) {
const retired = await retireTerminalRestartRecoverySourceClaim({
sessionId: restartRecoveryEntry.sessionId,
sessionKey,
sourceTurnId: restartRecoverySourceTurnId,
storePath
});
if (retired) {
activeSessionEntry = retired;
if (activeSessionStore) activeSessionStore[sessionKey] = retired;
}
}
releaseAdmissionTicket();
typing.cleanup();
return;
}
const questionInput = await runReplyQuestionInput(params);
if (questionInput.handled) {
releaseAdmissionTicket();
typing.cleanup();
return questionInput.payload;
}
const baseShouldEmitToolResult = createShouldEmitToolResult({
sessionKey,
storePath,
resolvedVerboseLevel,
verboseLevelOverride: followupRun.run.verboseLevelOverride
});
const channelProgressCanConsumeToolResults = Boolean(opts?.forceToolResultProgress) && Boolean(opts?.onToolResult);
const shouldEmitToolResult = () => channelProgressCanConsumeToolResults || baseShouldEmitToolResult();
const shouldEmitToolOutput = createShouldEmitToolOutput({
sessionKey,
storePath,
resolvedVerboseLevel,
verboseLevelOverride: followupRun.run.verboseLevelOverride
});
const pendingToolTasks = /* @__PURE__ */ new Set();
const blockReplyTimeoutMs = opts?.blockReplyTimeoutMs ?? 15e3;
const touchActiveSessionEntry = async () => {
if (!activeSessionEntry || !activeSessionStore || !sessionKey) return;
const updatedAt = activeSessionEntry.updatedAt === 0 ? 0 : Date.now();
activeSessionEntry.updatedAt = updatedAt;
activeSessionStore[sessionKey] = activeSessionEntry;
if (storePath) await updateSessionEntry({
storePath,
sessionKey
}, () => ({ updatedAt }), {
skipMaintenance: true,
takeCacheOwnership: true
});
};
const queuedRunFollowupTurn = createFollowupRunner({
resolveGatewayContext,
opts,
typing,
typingMode,
sessionEntry: activeSessionEntry,
sessionStore: activeSessionStore,
sessionKey,
storePath,
defaultModel,
toolProgressDetail
});
if (messageInjectionDisposition === "accepted") {
if (replyOperationRunState) replyOperationRunState.admission = {
status: "accepted",
mode: "steer"
};
releaseAdmissionTicket();
typing.cleanup();
return;
}
const bindQueueDisposition = () => {
const observe = followupRun.onQueueDisposition;
followupRun.onQueueDisposition = (disposition) => {
observe?.(disposition);
if (replyOperationRunState && disposition !== "queue-cap-old") replyOperationRunState.admission = {
status: "skipped",
reason: "queue-cap"
};
};
};
if (effectiveShouldSteer && isActive && !shouldQueueAuthorityMismatch && messageInjectionDisposition === "none") {
bindQueueDisposition();
const result = await runActiveReplySteer({
followupRun,
opts,
providedReplyOperation,
queueKey,
releaseAdmissionTicket,
replyOperationRunState,
resolvedQueue,
restartRecoverySourceTurnId,
runFollowup: queuedRunFollowupTurn,
sessionCtx,
sessionKey,
touchActiveSessionEntry,
typing,
typingSignals,
toolAuthorityFingerprint: incomingToolAuthorityFingerprint,
pendingInputAuthorityFingerprint: hasRouteOnlyAuthorityMismatch ? activeToolAuthorityFingerprint : void 0
});
return result === "handled" ? void 0 : result;
}
const activeRunQueueAction = resolveActiveRunQueueAction({
queueAdmissionState,
isActive,
isHeartbeat,
shouldFollowup: effectiveShouldFollowup || shouldQueueAuthorityMismatch,
queueMode: activeRunQueueMode,
resetTriggered: effectiveResetTriggered
});
if (activeRunQueueAction === "drop") {
if (replyOperationRunState) replyOperationRunState.admission = {
status: "skipped",
reason: "active-run"
};
releaseAdmissionTicket();
typing.cleanup();
return;
}
if (activeRunQueueAction === "enqueue-followup") {
bindQueueDisposition();
if (!enqueueFollowupRun(queueKey, followupRun, resolvedQueue, "message-id", queuedRunFollowupTurn, false)) {
releaseAdmissionTicket();
typing.cleanup();
return;
}
if (replyOperationRunState) replyOperationRunState.admission = {
status: "accepted",
mode: "followup"
};
const queuedOperationOwner = replyRunRegistry.get(queueKey) ?? activeReplyOperation;
if (queuedOperationOwner) scheduleFollowupDrainAfterReplyOperationClear({
operation: queuedOperationOwner,
queueKey,
runFollowup: queuedRunFollowupTurn
});
else scheduleFollowupDrain(queueKey, queuedRunFollowupTurn);
releaseAdmissionTicket();
const queuedBehindActiveRun = isRunActive?.() === true;
await touchActiveSessionEntry();
if (queuedBehindActiveRun) await typingSignals.signalToolStart();
else typing.cleanup();
return;
}
followupRun.run.config = await resolveQueuedReplyExecutionConfig(followupRun.run.config, {
originatingChannel: sessionCtx.OriginatingChannel,
messageProvider: followupRun.run.messageProvider,
originatingAccountId: followupRun.originatingAccountId,
agentAccountId: followupRun.run.agentAccountId
});
followupRun.run.agentId ??= resolveDefaultAgentId(followupRun.run.config);
const replyToChannel = resolveOriginMessageProvider({
originatingChannel: sessionCtx.OriginatingChannel,
provider: sessionCtx.Surface ?? sessionCtx.Provider
});
const replyToMode = followupRun.originatingReplyToMode ?? resolveReplyToMode(followupRun.run.config, replyToChannel, sessionCtx.AccountId, sessionCtx.ChatType);
const applyReplyToMode = createReplyToModeFilterForChannel(replyToMode, replyToChannel);
const cfg = followupRun.run.config;
const replyMediaContext = createReplyMediaContext({
cfg,
agentId: followupRun.run.agentId,
sessionKey,
workspaceDir: followupRun.run.workspaceDir,
messageProvider: followupRun.run.messageProvider,
accountId: followupRun.originatingAccountId ?? followupRun.run.agentAccountId,
groupId: followupRun.run.groupId,
groupChannel: followupRun.run.groupChannel,
groupSpace: followupRun.run.groupSpace,
requesterSenderId: followupRun.run.senderId,
requesterSenderName: followupRun.run.senderName,
requesterSenderUsername: followupRun.run.senderUsername,
requesterSenderE164: followupRun.run.senderE164
});
const compactionNoticeMessageId = sessionCtx.MessageSidFull ?? sessionCtx.MessageSid;
const sendDirectCompactionNotice = shouldNotifyUserAboutCompaction(cfg) ? async (phase, text) => {
if (!opts?.onBlockReply) return;
const noticePayload = createCompactionNoticePayload({
phase,
text,
currentMessageId: compactionNoticeMessageId,
applyReplyToMode
});
try {
await opts.onBlockReply(noticePayload);
} catch (err) {
logVerbose(`context maintenance notice delivery failed: ${String(err)}`);
}
} : void 0;
const blockReplyCoalescing = blockStreamingEnabled && opts?.onBlockReply ? resolveEffectiveBlockStreamingConfig({
cfg,
provider: sessionCtx.Provider,
accountId: sessionCtx.AccountId,
chunking: blockReplyChunking
}).coalescing : void 0;
const blockReplyPipeline = blockStreamingEnabled && opts?.onBlockReply ? createBlockReplyPipeline({
onBlockReply: opts.onBlockReply,
timeoutMs: blockReplyTimeoutMs,
coalescing: blockReplyCoalescing,
buffer: createAudioAsVoiceBuffer({ isAudioPayload })
}) : null;
const resolveVisibleReplyDelivery = async () => {
try {
await blockReplyPipeline?.flush({ force: true });
} catch (flushError) {
logVerbose(`failed to flush streamed reply blocks before surfacing run failure: ${String(flushError)}`);
}
return didDeliverVisiblePartialReply || hasSuccessfulTerminalSourceReplyDelivery({ blockReplyPipeline });
};
const replySessionKey = sessionKey ?? followupRun.run.sessionKey;
const replyRouteThreadId = resolveRoutedDeliveryThreadId({
ctx: sessionCtx,
sessionKey: replySessionKey
});
let replyOperation;
if (providedReplyOperation) {
replyOperation = providedReplyOperation;
if (replyOperationRunState) replyOperationRunState.admission = { status: "owned" };
releaseAdmissionTicket();
} else {
const replyTurnKind = resolveReplyTurnKind(opts);
const admission = await admitReplyTurn({
resolveGatewayContext,
sessionId: followupRun.run.sessionId,
sessionKey: replySessionKey ?? "",
expectedSessionId: activeSessionEntry?.sessionId,
storePath,
kind: replyTurnKind,
resetTriggered: effectiveResetTriggered,
routeThreadId: replyRouteThreadId,
originatingLeafEntryId: turnAdoptionLifecycle?.originatingLeafEntryId,
upstreamAbortSignal: opts?.abortSignal
});
if (replyOperationRunState) replyOperationRunState.admission = admission.status === "owned" ? { status: "owned" } : {
status: "skipped",
reason: admission.reason
};
if (admission.status === "skipped") {
releaseAdmissionTicket();
typing.cleanup();
if (admission.reason !== "active-run" || replyTurnKind !== "visible") return;
return markReplyPayloadForSourceSuppressionDelivery({ text: REPLY_RUN_STILL_SHUTTING_DOWN_TEXT });
}
replyOperation = admission.operation;
releaseAdmissionTicket();
const previousRunSessionId = followupRun.run.sessionId;
followupRun.run.sessionId = replyOperation.sessionId;
if (replyOperation.sessionId !== previousRunSessionId) {
const admittedSessionEntry = refreshSessionEntryFromStore({
storePath,
sessionKey: replySessionKey,
fallbackEntry: replySessionKey ? activeSessionStore?.[replySessionKey] ?? activeSessionEntry : activeSessionEntry,
activeSessionStore
});
if (admittedSessionEntry?.sessionId === replyOperation.sessionId) {
activeSessionEntry = admittedSessionEntry;
const admittedSessionFile = resolveAdmittedRunSessionFile({
agentId: followupRun.run.agentId,
sessionId: replyOperation.sessionId,
sessionFile: void 0,
sessionKey: replySessionKey,
storePath
});
if (admittedSessionFile) followupRun.run.sessionFile = admittedSessionFile;
}
}
}
replyOperation.bindToolAuthoritySnapshot(prepareReplyToolAuthority(followupRun));
bindReplyOperationTyping(replyOperation, typing);
let runFollowupTurn = queuedRunFollowupTurn;
let shouldDrainQueuedFollowupsAfterClear = false;
const returnWithQueuedFollowupDrain = (value) => {
shouldDrainQueuedFollowupsAfterClear = true;
return value;
};
const { admitUserTurn, beginBeforeAgentReply, checkpointBeforeAgentReply, clear: clearRestartRecoveryDeliveryClaim, isArmed: isRestartRecoveryArmed } = createReplyAgentRestartRecoveryController({
activeSessionStore,
cfg,
followupRun,
getActiveSessionEntry: () => activeSessionEntry,
opts,
replyOperation,
restartRecoverySourceTurnId,
runtimePolicySessionKey,
sessionCtx,
sessionKey,
setActiveSessionEntry: (entry) => {
activeSessionEntry = entry;
},
storePath
});
const resetSessionAfterRoleOrderingConflict = async (reason) => await resetReplyRunSession({
options: {
failureLabel: "role ordering conflict",
buildLogMessage: (nextSessionId) => `Role ordering conflict (${reason}). Restarting session ${sessionKey} -> ${nextSessionId}.`,
cleanupTranscripts: true
},
sessionKey,
queueKey,
activeSessionEntry,
activeSessionStore,
storePath,
followupRun,
onActiveSessionEntry: (nextEntry) => {
activeSessionEntry = nextEntry;
},
onNewSession: () => {
activeIsNewSession = true;
}
});
try {
return await executePreparedReplyAgentRun({
activeSessionStore,
admitUserTurn,
applyReplyToMode,
beginBeforeAgentReply,
blockReplyChunking,
blockReplyPipeline,
blockStreamingEnabled,
cfg,
checkpointBeforeAgentReply,
commandBody,
defaultModel,
resolveVisibleReplyDelivery,
followupRun,
getActiveIsNewSession: () => activeIsNewSession,
getActiveSessionEntry: () => activeSessionEntry,
isHeartbeat,
isRestartRecoveryArmed,
opts: runOpts,
pendingToolTasks,
queueKey,
replyMediaContext,
replyOperation,
replyRouteThreadId,
replyThreadingOverride,
replyToChannel,
replyToMode,
resetSessionAfterRoleOrderingConflict,
resolvedBlockStreamingBreak,
resolvedQueue,
resolvedVerboseLevel,
returnWithQueuedFollowupDrain,
runFollowupTurn,
runtimePolicySessionKey,
sendDirectCompactionNotice,
sessionCtx,
sessionKey,
setActiveSessionEntry: (entry) => {
activeSessionEntry = entry;
},
setRunFollowupTurn: (runner) => {
runFollowupTurn = runner;
},
shouldEmitToolOutput,
shouldEmitToolResult,
shouldInjectGroupIntro,
storePath,
toolProgressDetail,
traceAgentPhase,
transcriptCommandBody,
turnAdoptionLifecycle,
typing,
typingMode,
typingSignals
});
} catch (error) {
recordReplyOperationAgentTurn(followupRun.replyOperationRunStates, replyOperation);
return await handleReplyAgentRunError(error, {
cfg,
resolveVisibleReplyDelivery,
isHeartbeat,
isRestartRecoveryArmed,
replyOperation,
resolvedVerboseLevel,
returnWithQueuedFollowupDrain,
sessionCtx
});
} finally {
await cleanupReplyAgentRun({
blockReplyPipeline,
clearRestartRecoveryDeliveryClaim,
providedReplyOperation,
queueKey,
replyOperation,
runFollowupTurn,
sessionKey,
shouldDrainQueuedFollowupsAfterClear,
typing
});
}
}
//#endregion
export { runReplyAgent };