UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

705 lines (704 loc) • 38.8 kB
import { f as normalizeStringifiedEntries, l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-1VuiLaaR.mjs"; import { a as extractErrorCode, c as readErrorCauses, l as readErrorName, r as collectErrorGraphCandidates } from "./error-coercion-W9BtigRm.mjs"; import "./src-BirUqOZp.mjs"; import { t as expectDefined } from "./expect-lbe3Hgrh.mjs"; import { i as asOptionalObjectRecord } from "./record-coerce-TIsquoVR.mjs"; import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.mjs"; import { t as formatErrorMessage } from "./errors-BvOkmQM5.mjs"; import { t as SkillLibraryError } from "./skill-library-error-z-Nwz4n5.mjs"; import { E as isAgentRunStaleLifecycleError, T as createRestartRecoveryClaimChangedError, d as getAgentEventLifecycleGeneration, f as isAgentEventLifecycleGenerationCurrent, t as assertAgentRunLifecycleGenerationCurrent, w as createAgentRunStaleLifecycleError } from "./agent-events-jWdQ_ESE.mjs"; import { a as buildOAuthRefreshFailureLoginCommand, l as formatOAuthRefreshFailureLoginCommandMarkdown, o as classifyOAuthRefreshFailure, s as classifyOAuthRefreshFailureError } from "./oauth-refresh-failure-DW27Dugk.mjs"; import { v as resolveExecutionApprovalFailureMessage } from "./message-patterns-DU8N-ZQt.mjs"; import { s as isFailoverError } from "./error-Cmm-AuUb.mjs"; import { r as SILENT_REPLY_TOKEN, s as isSilentReplyText } from "./tokens-Ljy0DfNV.mjs"; import { C as markReplyPayloadForSourceSuppressionDelivery, D as setReplyPayloadMetadata, c as getReplyPayloadMetadata, o as copyReplyPayloadMetadata, y as isReplyPayloadTerminalContent } from "./reply-payload-BEgsL2xJ.mjs"; import { k as isProviderAuthError } from "./model-auth-provider-config-Cwa51-db.mjs"; import { a as isAgentHarnessPreflightError } from "./errors-BAipjK5G.mjs"; import { M as hasRestartRecoverySourceClaim, N as hasRestartRecoveryTerminalRun, O as buildRestartRecoveryClaimCleanupPatch, P as isMainRestartRecoveryCandidate, V as recordLifecycleFence, k as canReplaceAbortedRestartRecoveryClaim } from "./session-accessor.sqlite-entry-worker-publication-BNeFwCVE.mjs"; import { n as isTerminalSessionStatus } from "./types-BGDhC4_W.mjs"; import { n as buildRestartRecoveryExpectedState } from "./session-transcript-turn-state-Bqkxfhzx.mjs"; import { C as renderRateLimitReplyCopy, S as renderRateLimitOrOverloadedCopy, _ as renderCodexAppServerFailureCopy, a as HEARTBEAT_EXTERNAL_RUN_FAILURE_TEXT, b as renderHeartbeatRunFailureCopy, g as renderCliTimeoutReplyCopy, h as renderBillingReplyCopy, i as GENERIC_EXTERNAL_RUN_FAILURE_TEXT, j as sanitizeUserFacingText, m as renderAuthProfileFailoverCopy, n as renderUserFacingText, t as renderAgentHarnessPreflightUserMessage, x as renderMissingApiKeyReplyCopy, y as renderFailoverCodeUserCopy } from "./user-facing-text-BCPDY-39.mjs"; import { a as renderAssistantRequestFailureCopy, c as renderRuntimeCoordinationFailureCopy } from "./assistant-request-failure-copy-bk0US-bY.mjs"; import { l as patchSessionEntryTarget, n as applySessionEntryTargetOperation } from "./session-accessor.sqlite-entry-DLa7_9ui.mjs"; import "./session-accessor-C3ljJ974.mjs"; import { d as readSessionEntryInWorker } from "./session-entry-read-runtime-BdnQjoL6.mjs"; import { t as createKeyedFifoLeaseRegistry } from "./keyed-fifo-lease-Czc3xgVJ.mjs"; import { a as findCliTerminalStopError, d as isNonProviderRuntimeCoordinationError, o as findCliTimeoutError } from "./failover-error-JM6M2oyF.mjs"; import { i as classifyCompactionReason, t as resolveReplyFailoverFacts } from "./request-error-facts-CtJZTTS5.mjs"; import { t as buildProviderAuthRecoveryHint } from "./provider-auth-recovery-hint-CR9bd50i.mjs"; import { t as SkillResourceDeliveryLimitError } from "./resource-delivery-error-CFKvWByr.mjs"; import { t as buildProviderLoginRecovery } from "./provider-login-recovery-G6GeGSWZ.mjs"; import { t as createRestartRecoveryOperatorSource } from "./operator-run-recovery-source-C5n2jGiN.mjs"; import { t as resolveSessionWorkerPlacementContext } from "./session-worker-placement-context-CvBn6ZZM.mjs"; import { t as formatSystemTurnPrompt } from "./system-turn-prompt-CqPm0DzY.mjs"; import { isDeepStrictEqual } from "node:util"; import { randomUUID } from "node:crypto"; //#region src/auto-reply/reply/reply-admission-ticket.ts const REPLY_ADMISSION_TICKET = Symbol("openclaw.replyAdmissionTicket"); const replyAdmissionTickets = createKeyedFifoLeaseRegistry(Symbol.for("openclaw.replyAdmissionTickets")); /** Briefly orders queue publication across a command's source and target sessions. */ function reserveReplyAdmissionTicket(sessionKeys) { return replyAdmissionTickets.reserve(normalizeStringifiedEntries(sessionKeys)); } //#endregion //#region src/auto-reply/reply/agent-runner-failure-reply.ts function readFallbackAttempts(error) { return isFailoverError(error) && Array.isArray(error.attempts) ? error.attempts : []; } function resolveReplyFailureSummary(params) { const attempts = params.attempts; let kind = params.reason; if (attempts?.length) { if (attempts.some((attempt) => attempt.reason === "billing")) kind = "billing"; else if (attempts.every((attempt) => attempt.reason === "overloaded")) kind = "overloaded"; else kind = attempts.every((attempt) => attempt.reason === "rate_limit" || attempt.reason === "overloaded") ? "rate_limit" : void 0; } if (kind !== "billing" && kind !== "rate_limit" && kind !== "overloaded") return; const failoverError = isFailoverError(params.error) ? params.error : void 0; const text = kind === "billing" ? renderBillingReplyCopy({ attempts, provider: failoverError?.provider, model: failoverError?.model, authMode: failoverError?.authMode }) : kind === "overloaded" ? renderRateLimitOrOverloadedCopy({ reason: kind, raw: params.message }) : renderRateLimitReplyCopy({ message: params.message, reason: params.reason, attempts, provider: failoverError?.provider, cooldownExpiry: failoverError?.soonestCooldownExpiry, sanitizeText: (rawText) => sanitizeUserFacingText(rawText, { errorContext: true }) }); return { kind, text }; } function collapseRepeatedFailureDetail(message) { const parts = message.split(/\s+\|\s+/u).map((part) => part.trim()).filter(Boolean); if (parts.length >= 2 && parts.every((part) => part === parts[0])) return expectDefined(parts[0], "parts entry at 0"); return message.trim(); } const EXTERNAL_RUN_FAILURE_DETAIL_MAX_CHARS = 900; const PREFLIGHT_COMPACTION_FAILURE_PREFIX = "Preflight compaction required but failed:"; function isNonDirectConversationContext(ctx) { const chatType = normalizeLowercaseStringOrEmpty(ctx.ChatType); return chatType === "group" || chatType === "channel"; } function isVerboseFailureDetailEnabled(level) { return level === "on" || level === "full"; } function buildPreflightCompactionFailureText(message, options) { const normalizedMessage = collapseRepeatedFailureDetail(message); if (!normalizedMessage.startsWith(PREFLIGHT_COMPACTION_FAILURE_PREFIX)) return null; const reason = renderUserFacingText(normalizedMessage.slice(41), { errorContext: true }).trim().replace(/\s+/gu, " "); const isTimeout = classifyCompactionReason(reason) === "timeout"; const reasonSuffix = options?.includeDetails && reason && !isTimeout ? ` Reason: ${reason}.` : ""; return `${isTimeout ? "⚠️ This conversation is too long, and shortening it took too long." : "⚠️ This conversation is too long, and OpenClaw couldn't shorten it."}${reasonSuffix} Try again, use /compact, or use /new to start a fresh session.`; } function buildAuthProfileFailoverFailureText(error) { if (!isFailoverError(error) || !error.provider || !error.authProfileFailure) return null; return renderAuthProfileFailoverCopy({ reason: error.reason, provider: error.provider, allInCooldown: error.authProfileFailure.allInCooldown, recoveryHint: buildProviderAuthRecoveryHint({ provider: error.provider }) }); } function resolveExternalRunFailureDetail(message) { const sanitized = message.trim().replace(/^⚠️\s*/u, "").replace(/\s+/gu, " "); return sanitized.length > EXTERNAL_RUN_FAILURE_DETAIL_MAX_CHARS ? `${truncateUtf16Safe(sanitized, 899).trimEnd()}…` : sanitized || void 0; } function formatForwardedExternalRunFailureText(message) { const detail = resolveExternalRunFailureDetail(message); return detail ? `⚠️ Agent failed before reply: ${detail}${/[.!?]$/u.test(detail) ? "" : "."} Please try again, or use /new to start a fresh session.` : GENERIC_EXTERNAL_RUN_FAILURE_TEXT; } function hasLocalWorkerTimeoutCause(error) { let localTimeout = false; for (const candidate of collectErrorGraphCandidates(error, readErrorCauses)) { if (isFailoverError(candidate)) continue; const original = asOptionalObjectRecord(candidate); if (original?.status !== void 0 || original?.statusCode !== void 0) return false; localTimeout ||= readErrorName(candidate) === "WorkerTaskError" && extractErrorCode(candidate) === "timeout"; } return localTimeout; } function buildExternalRunFailureReply(input, options) { const message = typeof input === "string" ? input : input.message; const error = typeof input === "string" ? void 0 : input.error; const normalizedMessage = collapseRepeatedFailureDetail(message); const useHeartbeatFailureCopy = options?.useHeartbeatFailureCopy ?? options?.isHeartbeat === true; const buildUnclassifiedReply = (includeHeartbeatDetails) => { const sanitizedMessage = sanitizeUserFacingText(normalizedMessage, { errorContext: true }); return { text: useHeartbeatFailureCopy ? renderHeartbeatRunFailureCopy(includeHeartbeatDetails ? resolveExternalRunFailureDetail(sanitizedMessage) : void 0) : options?.includeDetails ? formatForwardedExternalRunFailureText(sanitizedMessage) : GENERIC_EXTERNAL_RUN_FAILURE_TEXT, isGenericRunnerFailure: !options?.isHeartbeat }; }; const approvalMessage = resolveExecutionApprovalFailureMessage(normalizedMessage); if (approvalMessage) return { text: `⚠️ ${approvalMessage}`, isGenericRunnerFailure: false }; if (collectErrorGraphCandidates(error, readErrorCauses).some((candidate) => candidate instanceof SkillResourceDeliveryLimitError)) return { text: "⚠️ Selected skill resources exceed the 8 MiB delivery limit. Select fewer skills, then try again.", isGenericRunnerFailure: false }; if (isAgentHarnessPreflightError(error)) { const userMessage = renderAgentHarnessPreflightUserMessage(error); if (userMessage !== void 0) return { text: userMessage, isGenericRunnerFailure: false }; return buildUnclassifiedReply(true); } const failoverFacts = options?.failoverFacts ?? resolveReplyFailoverFacts(error ?? normalizedMessage, normalizedMessage); const failoverCodeCopy = renderFailoverCodeUserCopy(failoverFacts.code); if (failoverCodeCopy) return { text: failoverCodeCopy, isGenericRunnerFailure: false }; const runtimeCoordinationFailure = failoverFacts.code && isNonProviderRuntimeCoordinationError(error) ? renderRuntimeCoordinationFailureCopy(failoverFacts.code) : void 0; if (runtimeCoordinationFailure) return { text: runtimeCoordinationFailure, isGenericRunnerFailure: false }; const oauthRefreshFailure = classifyOAuthRefreshFailureError(error) ?? classifyOAuthRefreshFailure(normalizedMessage); const providerLoginRecovery = buildProviderLoginRecovery({ provider: oauthRefreshFailure ? oauthRefreshFailure.provider ?? void 0 : failoverFacts.provider, oauthReason: oauthRefreshFailure?.reason, failoverReason: failoverFacts.reason, authMode: failoverFacts.authMode }); if (oauthRefreshFailure) { const loginCommand = buildOAuthRefreshFailureLoginCommand(oauthRefreshFailure.provider, { profileId: options?.includeAuthProfileId ? oauthRefreshFailure.profileId : void 0 }); const loginCommandMarkdown = formatOAuthRefreshFailureLoginCommandMarkdown(loginCommand); const providerText = oauthRefreshFailure.provider ? ` for ${oauthRefreshFailure.provider}` : ""; const retryLoginHint = providerLoginRecovery ? "send `/login` from a private chat or Control UI session to choose a provider, or re-auth" : "re-auth"; if (oauthRefreshFailure.reason) return { text: providerLoginRecovery ? `⚠️ ${providerLoginRecovery.hint} You can also re-auth with ${loginCommandMarkdown} on the gateway.` : `⚠️ Model login expired on the gateway${providerText}. Re-auth with ${loginCommandMarkdown} in a terminal, then try again.`, ...providerLoginRecovery ? { presentation: providerLoginRecovery.presentation } : {}, isGenericRunnerFailure: false }; return { text: `⚠️ Model login failed on the gateway${providerText}. Please try again. If this keeps happening, ${retryLoginHint} with ${loginCommandMarkdown} in a terminal.`, isGenericRunnerFailure: false }; } const authProfileFailoverFailure = buildAuthProfileFailoverFailureText(error); if (authProfileFailoverFailure) return { text: providerLoginRecovery ? `${providerLoginRecovery.hint}\n\n${authProfileFailoverFailure}` : authProfileFailoverFailure, ...providerLoginRecovery ? { presentation: providerLoginRecovery.presentation } : {}, isGenericRunnerFailure: false }; const cliTerminalStopError = findCliTerminalStopError(error); if (cliTerminalStopError) return { text: renderUserFacingText(cliTerminalStopError.message, { errorContext: true }), isGenericRunnerFailure: false }; const cliTimeoutError = findCliTimeoutError(error); const cliBackendTimeoutFailure = renderCliTimeoutReplyCopy({ message: normalizedMessage, cliTimeout: cliTimeoutError?.cliTimeout, provider: cliTimeoutError?.provider, replayPrevented: options?.replayPrevented }); if (cliBackendTimeoutFailure) return { text: cliBackendTimeoutFailure, isGenericRunnerFailure: false }; const providerRequestError = failoverFacts.providerRequestError; if (providerRequestError) return { text: providerRequestError.userMessage, isGenericRunnerFailure: false }; const authError = isProviderAuthError(error) ? error : void 0; const missingApiKeyFailure = renderMissingApiKeyReplyCopy(authError ? { provider: authError.provider, providerGuidance: authError.providerGuidance } : void 0); if (missingApiKeyFailure) return { text: missingApiKeyFailure, isGenericRunnerFailure: false }; if (options?.isHeartbeat) return buildUnclassifiedReply(options.includeDetails === true); const codexAppServerFailure = renderCodexAppServerFailureCopy(normalizedMessage); if (codexAppServerFailure) return { text: codexAppServerFailure, isGenericRunnerFailure: false }; if (failoverFacts.reason === "timeout" && hasLocalWorkerTimeoutCause(error)) return { text: "A local worker task timed out. Please try again.", isGenericRunnerFailure: false }; const classifiedFailure = failoverFacts.formatFailureText ?? renderAssistantRequestFailureCopy(failoverFacts); if (classifiedFailure) return { text: classifiedFailure, isGenericRunnerFailure: false }; return { text: options?.includeDetails ? formatForwardedExternalRunFailureText(renderUserFacingText(normalizedMessage, { errorContext: true })) : GENERIC_EXTERNAL_RUN_FAILURE_TEXT, isGenericRunnerFailure: true }; } function markAgentRunFailureReplyPayload(payload) { const marked = markReplyPayloadForSourceSuppressionDelivery(payload); if (!isSilentReplyText(marked.text, "NO_REPLY")) marked.isError = true; return marked; } function markPostCompactionModelFailurePayload(postCompactionModelFailure, payload) { return postCompactionModelFailure === true && payload.isError === true && isReplyPayloadTerminalContent(payload) && typeof payload.text === "string" ? setReplyPayloadMetadata(payload, { postCompactionModelFailure: true }) : payload; } function renderPostCompactionModelFailurePayload(payload) { return getReplyPayloadMetadata(payload)?.postCompactionModelFailure === true && typeof payload.text === "string" ? copyReplyPayloadMetadata(payload, { ...payload, text: `⚠️ Context compaction succeeded, but the later model request still failed. ${payload.text.replace(/^⚠️\s*/u, "")}` }) : payload; } /** Optional silence hides generic boilerplate, not guidance or the outcome of visible work. */ function resolveAgentRunFailureText(params) { return params.replyExpectation === "optional" && params.isGenericRunnerFailure && !params.visibleReplyDelivered ? SILENT_REPLY_TOKEN : params.text; } function buildTerminalAgentRunFailureReplyPayload(params) { const useHeartbeatFailureCopy = params.useHeartbeatFailureCopy ?? params.isHeartbeat === true; return markAgentRunFailureReplyPayload({ text: resolveAgentRunFailureText({ ...params, text: useHeartbeatFailureCopy ? HEARTBEAT_EXTERNAL_RUN_FAILURE_TEXT : GENERIC_EXTERNAL_RUN_FAILURE_TEXT, isGenericRunnerFailure: !params.isHeartbeat }) }); } function buildEmptyInteractiveReplyPayload(params) { if (params.completion.outcome !== "missing") return; return markAgentRunFailureReplyPayload({ text: "I finished the turn, but it did not produce a visible reply. Please try again, or start a new session if this keeps happening." }); } function buildKnownAgentRunFailureReplyPayload(params) { if (isAgentHarnessPreflightError(params.err)) { const reply = buildExternalRunFailureReply({ message: params.err.message, error: params.err }); return reply.isGenericRunnerFailure ? void 0 : markAgentRunFailureReplyPayload({ text: reply.text }); } const message = formatErrorMessage(params.err); const failoverFacts = resolveReplyFailoverFacts(params.err, message); const failureSummary = resolveReplyFailureSummary({ error: params.err, message, reason: failoverFacts.reason, attempts: readFallbackAttempts(params.err) }); const knownFailureText = failureSummary?.kind === "billing" ? failureSummary.text : buildPreflightCompactionFailureText(message, { includeDetails: isVerboseFailureDetailEnabled(params.resolvedVerboseLevel) }) ?? failureSummary?.text; const externalRunFailureReply = knownFailureText ? { text: knownFailureText, isGenericRunnerFailure: false } : buildExternalRunFailureReply({ message, error: params.err }, { includeAuthProfileId: !isNonDirectConversationContext(params.sessionCtx), includeDetails: isVerboseFailureDetailEnabled(params.resolvedVerboseLevel), failoverFacts }); if (externalRunFailureReply.isGenericRunnerFailure) return; return markAgentRunFailureReplyPayload({ text: externalRunFailureReply.text, ...externalRunFailureReply.presentation ? { presentation: externalRunFailureReply.presentation } : {} }); } //#endregion //#region src/auto-reply/reply/restart-recovery-claim.ts /** Provider redelivery guard shared by ingress and the agent admission boundary. */ function isDuplicateRestartRecoverySource(entry, sourceTurnId) { const normalizedSourceTurnId = normalizeOptionalString(sourceTurnId); return Boolean(normalizedSourceTurnId && (hasRestartRecoveryTerminalRun(entry ?? void 0, normalizedSourceTurnId) || hasRestartRecoverySourceClaim(entry ?? void 0, normalizedSourceTurnId))); } async function retireTerminalRestartRecoverySourceClaim(params) { let didRetire = false; const retired = await patchSessionEntryTarget(params.target, (current) => { if (current.sessionId !== params.sessionId || !isTerminalSessionStatus(current.status) || current.status === "interrupted" || current.abortedLastRun === true || current.restartRecoveryDeliveryReceiptState === "terminal-pending" || !hasRestartRecoverySourceClaim(current, params.sourceTurnId)) return null; didRetire = true; return { ...buildRestartRecoveryClaimCleanupPatch({ entry: current, recordTerminalSource: true, terminalSourceRunId: params.sourceTurnId }), updatedAt: Date.now() }; }, { skipMaintenance: true, takeCacheOwnership: true, workerGuard: { source: params.assertCurrent } }); return didRetire ? retired ?? void 0 : void 0; } function createReplyRestartRecoveryClaimController(params) { let recoveryRunId = normalizeOptionalString(params.admissionRunId) ?? randomUUID(); const executionRunId = params.executionRunId ?? recoveryRunId; const executionGeneration = params.lifecycleGeneration ?? getAgentEventLifecycleGeneration(); let recoverySourceRunId; let trackedSessionId; let trackedLifecycleRevision; let tracked = false; let confirmedArmed = false; let readTarget; const recordReadTarget = (target) => { if (readTarget && (readTarget.agentId !== target.agentId || readTarget.storePath !== target.storePath || readTarget.target.canonicalKey !== target.target.canonicalKey || !isDeepStrictEqual(readTarget.readSource, target.readSource))) throw createRestartRecoveryClaimChangedError(); readTarget ??= target; }; const preparedTarget = () => { if (!readTarget) throw new Error("Restart recovery claim has no admitted session target"); return readTarget; }; const isExecutionFence = (run) => run.runId === executionRunId && run.lifecycleGeneration === executionGeneration; const isTrackedClaim = (entry) => entry !== void 0 && entry.sessionId === trackedSessionId && entry.sessionId === params.getSessionId() && (entry.restartRecoveryDeliveryRunId !== void 0 ? entry.restartRecoveryDeliveryRunId === recoveryRunId && normalizeOptionalString(entry.restartRecoveryDeliverySourceRunId) === recoverySourceRunId : entry.restartRecoveryRuns?.some(isExecutionFence) === true); const recordAdmittedClaim = (entry, exactRunId) => { params.setEntry(entry); recoveryRunId = exactRunId ?? recoveryRunId; recoverySourceRunId = normalizeOptionalString(entry.restartRecoveryDeliverySourceRunId); trackedSessionId = entry.sessionId; trackedLifecycleRevision = entry.lifecycleRevision; tracked = exactRunId !== void 0 || isTrackedClaim(entry); }; const assertReadCurrent = () => { if (params.lifecycleGeneration) assertAgentRunLifecycleGenerationCurrent(params.lifecycleGeneration); }; const assertClaimCurrent = (sessionId) => { assertReadCurrent(); if (params.getSessionId() !== sessionId) throw createRestartRecoveryClaimChangedError(); }; const persistAdmissionPatch = async (options) => { const expectedSessionState = buildRestartRecoveryExpectedState(options.entry); if (options.recorder && !options.recorder.hasPersisted()) { const result = await options.recorder.persistApproved({ target: params.resolveUserTurnTarget?.({ entry: options.entry, sessionId: options.sessionId, sessionKey: options.sessionKey, storePath: options.storePath }), expectedSessionId: options.sessionId, expectedSessionState, sessionLifecyclePatch: options.patch }); if (!result?.sessionEntry) throw new Error("session changed before durable user-turn admission"); return result.sessionEntry; } let didCommit = false; const persisted = await applySessionEntryTargetOperation(preparedTarget(), { kind: "restart-admission", sessionId: options.sessionId, expectedSessionState, patch: options.patch }, { onCommitted: () => { didCommit = true; }, workerGuard: { source: params.operatorAuthority?.assertCurrent, assertCurrent: () => assertClaimCurrent(options.sessionId) } }); if (!didCommit || !persisted) throw createRestartRecoveryClaimChangedError(); return persisted; }; const admitUserTurn = async (recorder) => { if (!params.sessionKey || !params.storePath) { await recorder?.persistApproved(); return "admitted"; } const sessionId = params.getSessionId(); assertReadCurrent(); const hasPendingPlacementInput = () => Boolean(recorder?.getPendingInputMessage?.() && !recorder.hasPersisted()); const pendingPlacementInput = hasPendingPlacementInput(); const placementContext = pendingPlacementInput ? resolveSessionWorkerPlacementContext() : void 0; const placementService = placementContext?.workerSessionPlacementService; if (placementService && !placementService.prepareRuntimeRefresh) throw new Error("Worker placement observation service is unavailable"); const placementObservation = placementService?.prepareRuntimeRefresh ? await placementService.prepareRuntimeRefresh(sessionId) : void 0; let entry; let stagedWorkerInput = false; try { const assertAdmissionCurrent = () => { assertReadCurrent(); if (params.getSessionId() !== sessionId) throw new Error("session changed before durable user-turn admission"); if (hasPendingPlacementInput() !== pendingPlacementInput) throw new Error("pending user turn changed before durable user-turn admission"); if (placementContext?.workerSessionPlacementService !== placementService) throw new Error("Worker placement service changed before durable user-turn admission"); placementObservation?.assertCurrent(); }; const current = await readSessionEntryInWorker({ agentId: params.agentId, storePath: params.storePath, sessionKey: params.sessionKey }, assertAdmissionCurrent, void 0, recordReadTarget) ?? params.getEntry(); assertAdmissionCurrent(); if (!current || current.sessionId !== sessionId) throw new Error("session changed before durable user-turn admission"); entry = current; stagedWorkerInput = Boolean(placementObservation?.placement && placementObservation.placement.state !== "local"); } finally { placementObservation?.release(); } const admissionRunId = normalizeOptionalString(params.admissionRunId); const sourceTurnId = normalizeOptionalString(params.sourceTurnId); const activeClaimRunId = normalizeOptionalString(entry.restartRecoveryDeliveryRunId); const isExactRecoveryClaim = admissionRunId && activeClaimRunId === admissionRunId; if (sourceTurnId) { if (hasRestartRecoveryTerminalRun(entry, sourceTurnId)) return "duplicate-source"; if (!isExactRecoveryClaim && hasRestartRecoverySourceClaim(entry, sourceTurnId)) { const retired = await retireTerminalRestartRecoverySourceClaim({ target: preparedTarget(), assertCurrent: assertReadCurrent, sessionId, sourceTurnId }); if (retired) params.setEntry(retired); return "duplicate-source"; } } if (stagedWorkerInput) return "admitted"; if (isExactRecoveryClaim) { if (isTerminalSessionStatus(entry.status) || entry.abortedLastRun === true) throw createRestartRecoveryClaimChangedError(); const preservesTerminalReceipt = entry.restartRecoveryDeliveryReceiptState === "terminal-pending"; const adopted = await persistAdmissionPatch({ entry, patch: { restartRecoveryBeforeAgentReplyState: void 0, ...preservesTerminalReceipt ? {} : { restartRecoveryDeliveryReceiptState: void 0, restartRecoveryDeliveryToolCallId: void 0, restartRecoveryDeliveryRequestFingerprint: void 0 }, restartRecoverySourceIngress: entry.restartRecoverySourceIngress ?? "control-ui", updatedAt: Date.now() }, recorder, sessionId, sessionKey: params.sessionKey, storePath: params.storePath }); recordAdmittedClaim(adopted, admissionRunId); return "admitted"; } const deliveryContext = params.resolveDeliveryContext(entry); const recoverableDeliveryContext = deliveryContext && sourceTurnId ? deliveryContext : void 0; if (recoverableDeliveryContext) { const sourceMessage = recorder?.getPersistedMessage?.() ?? await recorder?.resolveMessage(); const persistedSourceTurnId = normalizeOptionalString(sourceMessage?.idempotencyKey); if (!recorder || persistedSourceTurnId !== sourceTurnId) throw new Error("channel restart recovery requires source-keyed user-turn admission"); } const operatorSource = !recoverableDeliveryContext && !sourceTurnId ? createRestartRecoveryOperatorSource({ authority: params.operatorAuthority, entry, agentId: params.agentId, sessionKey: params.sessionKey, sourceRunId: recoveryRunId, inputProvenance: params.inputProvenance }) : void 0; if (!recoverableDeliveryContext && !activeClaimRunId && (!recorder || recorder.hasPersisted()) && !operatorSource) return "admitted"; const updatedAt = Date.now(); const canReplaceAbortedClaim = canReplaceAbortedRestartRecoveryClaim(entry, params.sessionKey, admissionRunId); if (activeClaimRunId && !canReplaceAbortedClaim && (entry.abortedLastRun === true || !isTerminalSessionStatus(entry.status) || entry.status === "interrupted" || entry.restartRecoveryDeliveryReceiptState === "terminal-pending")) throw createRestartRecoveryClaimChangedError(); const retiredClaim = activeClaimRunId ? buildRestartRecoveryClaimCleanupPatch({ entry, recordTerminalSource: true, terminalSourceRunId: normalizeOptionalString(entry.restartRecoveryDeliverySourceRunId) }) : {}; const transfersControlUiClaim = canReplaceAbortedClaim && entry.restartRecoverySourceIngress === "control-ui" && !recoverableDeliveryContext; const hasDeliveryClaim = Boolean(recoverableDeliveryContext || transfersControlUiClaim || operatorSource); const patch = { ...retiredClaim, abortedLastRun: false, endedAt: void 0, restartRecoveryBeforeAgentReplyState: void 0, restartRecoveryDeliveryReceiptState: void 0, restartRecoveryDeliveryToolCallId: void 0, restartRecoveryDeliveryContext: recoverableDeliveryContext, restartRecoveryDeliveryRequestFingerprint: void 0, restartRecoveryDeliveryRunId: hasDeliveryClaim ? recoveryRunId : void 0, restartRecoveryOperatorSource: operatorSource, restartRecoveryDeliverySourceRunId: transfersControlUiClaim || operatorSource ? recoveryRunId : recoverableDeliveryContext ? sourceTurnId : void 0, restartRecoveryRequesterAccountId: transfersControlUiClaim ? void 0 : normalizeOptionalString(params.requesterAccountId), restartRecoveryRequesterSenderId: transfersControlUiClaim ? void 0 : normalizeOptionalString(params.requesterSenderId), restartRecoverySameChannelThreadRequired: !transfersControlUiClaim && params.sameChannelThreadRequired === true ? true : void 0, restartRecoverySourceIngress: recoverableDeliveryContext ? "channel" : transfersControlUiClaim ? "control-ui" : operatorSource?.snapshot.sourceIngress, restartRecoverySourceReplyDeliveryMode: transfersControlUiClaim ? void 0 : params.sourceReplyDeliveryMode, runtimeMs: void 0, startedAt: updatedAt, status: void 0, lastRunError: void 0, updatedAt }; patch.restartRecoveryRuns = entry.restartRecoveryRuns; if (isMainRestartRecoveryCandidate(entry, params.sessionKey)) recordLifecycleFence(patch, { runId: executionRunId, lifecycleGeneration: executionGeneration }); const persisted = await persistAdmissionPatch({ entry, patch, recorder, sessionId, sessionKey: params.sessionKey, storePath: params.storePath }); recordAdmittedClaim(persisted); return "admitted"; }; const updateBeforeAgentReply = async (expectedState, { state, pendingFinalDelivery }) => { if (!tracked || !params.sessionKey || !params.storePath) return; const updatedAt = Date.now(); const sessionId = params.getSessionId(); const persisted = await patchSessionEntryTarget(preparedTarget(), (current) => isTrackedClaim(current) && current.restartRecoveryBeforeAgentReplyState === expectedState ? { restartRecoveryBeforeAgentReplyState: state, ...pendingFinalDelivery ? { pendingFinalDelivery: { ...pendingFinalDelivery.text ? { kind: "replayable", text: pendingFinalDelivery.text } : { kind: "transport-only" }, createdAt: updatedAt, ...pendingFinalDelivery.intentId ? { intentId: pendingFinalDelivery.intentId } : {}, deliveries: pendingFinalDelivery.deliveries, ...pendingFinalDelivery.context ? { context: pendingFinalDelivery.context } : {} }, restartRecoveryForceSafeTools: true } : {}, updatedAt } : null, { skipMaintenance: true, takeCacheOwnership: true, workerGuard: { assertCurrent: () => assertClaimCurrent(sessionId) } }); if (!persisted) throw new Error(`before_agent_reply ${expectedState === "pending" ? "checkpoint" : "start"} lost restart recovery ownership`); params.setEntry(persisted); }; const clear = async () => { const lifecycleGeneration = params.lifecycleGeneration; if (!tracked || !params.sessionKey || !params.storePath || !lifecycleGeneration || params.isRestartAbort()) return; const sessionId = params.getSessionId(); if (sessionId !== trackedSessionId) return; const expected = { sessionId, lifecycleRevision: trackedLifecycleRevision }; const persisted = await applySessionEntryTargetOperation(preparedTarget(), { kind: "restart-claim-clear", expected, sessionId, recoveryRunId, recoverySourceRunId, executionRunId, executionGeneration }, { workerGuard: { assertCurrent: () => { assertAgentRunLifecycleGenerationCurrent(lifecycleGeneration); if (params.isRestartAbort()) throw createAgentRunStaleLifecycleError(); if (params.getSessionId() !== sessionId) throw createRestartRecoveryClaimChangedError(); } } }); if (persisted?.sessionId === expected.sessionId && persisted.lifecycleRevision === expected.lifecycleRevision) params.setEntry(persisted); }; const isArmed = async () => { if (!tracked || !params.sessionKey || !params.storePath) return false; const isRetiredRestart = () => params.lifecycleGeneration ? params.isRestartAbort() && !isAgentEventLifecycleGenerationCurrent(params.lifecycleGeneration) : false; if (isRetiredRestart()) return confirmedArmed; try { const persisted = await readSessionEntryInWorker({ agentId: params.agentId, sessionKey: params.sessionKey, storePath: params.storePath }, assertReadCurrent, void 0, recordReadTarget); assertReadCurrent(); if (!confirmedArmed) { const current = params.getEntry(); confirmedArmed = isTrackedClaim(persisted) && persisted?.abortedLastRun === true || isTrackedClaim(current) && current?.abortedLastRun === true; } return confirmedArmed; } catch (error) { if (isAgentRunStaleLifecycleError(error) && isRetiredRestart()) return confirmedArmed; throw error; } }; return { admitUserTurn, async beginBeforeAgentReply() { await updateBeforeAgentReply(void 0, { state: "pending" }); return true; }, checkpointBeforeAgentReply: (checkpoint) => updateBeforeAgentReply("pending", checkpoint), clear, isArmed }; } //#endregion //#region src/auto-reply/reply/stalled-turn-recovery.ts /** Last-resort feedback once no continuation can answer a stalled turn. */ const STALLED_TURN_NOTICE_TEXT = "⚠️ This turn was interrupted because it stopped making progress. Please try again."; const STALLED_TURN_GUIDANCE = "Your previous turn stopped making progress and was stopped before you replied, so the user's request from that turn is still unanswered. Answer it now, together with any newer message, from what the transcript already contains. Keep further tool use to a minimum, do not repeat actions that already ran, and briefly say what you could not verify."; /** A stale watchdog expired this operation before it delivered output: the user saw nothing. */ function isReplyOperationStalledBeforeOutput(operation) { return operation?.result?.kind === "failed" && !operation.sourceReplyDelivered && operation.result.code === "run_stalled" && (operation.staleExpiryReason === "no_activity" || operation.staleExpiryReason === "stuck_recovery"); } /** Builds the one recovery run that answers a stalled turn over its persisted transcript. */ function buildStalledTurnRecoveryRun(base) { const source = base.queuedFollowupReplyDisposition; return { ...base, prompt: formatSystemTurnPrompt(STALLED_TURN_GUIDANCE), summaryLine: "stalled-turn-recovery", stalledTurnRecovery: true, disableCollectBatching: true, enqueuedAt: Date.now(), transcriptPrompt: void 0, userTurnTranscriptRecorder: void 0, sourceTurnId: void 0, currentInboundContext: void 0, images: void 0, imageOrder: void 0, media: void 0, abortSignal: void 0, turnAdoptionLifecycle: void 0, replyOperationRunStates: void 0, onQueueDisposition: void 0, queuedFollowupReplyDisposition: source?.kind === "deliver" ? { kind: "deliver", deliver: source.deliver.createSourceRetry?.() ?? source.deliver } : source, run: { ...base.run, inputProvenance: { kind: "internal_system", sourceTool: "stalled-turn-recovery" }, suppressNextUserMessagePersistence: true, skillLibraryAuthoring: base.run.skillLibraryAuthoring && { target: "personal", defaultTarget: "personal", multipleProfiles: base.run.skillLibraryAuthoring.multipleProfiles, bind: () => {}, invoke: async () => { throw new SkillLibraryError("AUTHORITY_EXPIRED", "Skill authoring is unavailable while recovering an interrupted turn. Send a fresh message requesting the change."); } } } }; } //#endregion //#region src/auto-reply/reply/commentary-progress-owner.ts /** Freezes and registers one commentary owner for the current agent turn. */ async function resolveTurnCommentaryProgressOwner(params) { const shouldDeliverCommentaryPayloads = params.commentaryPayloadsEnabled ? params.options?.shouldDeliverCommentaryPayloads : void 0; if (params.options?.onVerboseProgressVisibilityAsync) { const frozen = shouldDeliverCommentaryPayloads ? await params.resolveVerboseProgressVisibilityAsync() : void 0; await params.options.onVerboseProgressVisibilityAsync(frozen === void 0 ? params.resolveVerboseProgressVisibilityAsync : async () => frozen); } else if (params.options?.onVerboseProgressVisibility) { const frozen = shouldDeliverCommentaryPayloads ? params.resolveVerboseProgressVisibility() : void 0; params.options.onVerboseProgressVisibility(frozen === void 0 ? params.resolveVerboseProgressVisibility : () => frozen); } const commentaryPayloadsEnabled = params.commentaryPayloadsEnabled && (shouldDeliverCommentaryPayloads?.() ?? true); return { commentaryPayloadsEnabled, draftOwnsCommentaryProgress: params.commentaryPayloadsEnabled && shouldDeliverCommentaryPayloads !== void 0 && !commentaryPayloadsEnabled }; } //#endregion export { reserveReplyAdmissionTicket as C, REPLY_ADMISSION_TICKET as S, markAgentRunFailureReplyPayload as _, isReplyOperationStalledBeforeOutput as a, resolveAgentRunFailureText as b, retireTerminalRestartRecoverySourceClaim as c, buildExternalRunFailureReply as d, buildKnownAgentRunFailureReplyPayload as f, isVerboseFailureDetailEnabled as g, isNonDirectConversationContext as h, buildStalledTurnRecoveryRun as i, buildAuthProfileFailoverFailureText as l, buildTerminalAgentRunFailureReplyPayload as m, STALLED_TURN_GUIDANCE as n, createReplyRestartRecoveryClaimController as o, buildPreflightCompactionFailureText as p, STALLED_TURN_NOTICE_TEXT as r, isDuplicateRestartRecoverySource as s, resolveTurnCommentaryProgressOwner as t, buildEmptyInteractiveReplyPayload as u, markPostCompactionModelFailurePayload as v, resolveReplyFailureSummary as x, renderPostCompactionModelFailurePayload as y };