diff --git a/src/auto-reply/reply/agent-runner-cli-candidate.ts b/src/auto-reply/reply/agent-runner-cli-candidate.ts index ff1b4fa9ff12..33c252498cf8 100644 --- a/src/auto-reply/reply/agent-runner-cli-candidate.ts +++ b/src/auto-reply/reply/agent-runner-cli-candidate.ts @@ -1,27 +1,16 @@ import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; -import type { PreparedAgentRunAdmission } from "../../agents/admitted-run-context.js"; import { resolveBootstrapWarningSignaturesSeen } from "../../agents/bootstrap-budget.js"; -import type { BootstrapContextRunKind } from "../../agents/bootstrap-mode.js"; -import type { RunCliAgentParams } from "../../agents/cli-runner/types.js"; import { getCliSessionBinding, shouldClearFailedCliSessionBinding, } from "../../agents/cli-session.js"; -import type { RunEmbeddedAgentParams } from "../../agents/embedded-agent-runner/run/params.js"; -import type { FastModeAutoProgressState } from "../../agents/fast-mode.js"; -import type { ContextEngineLogicalTurnLease } from "../../agents/harness/context-engine-logical-turn.js"; import { withLocalSessionPlacementTurnAdmission } from "../../agents/session-placement-admission.js"; import { normalizeChatType } from "../../channels/chat-type.js"; -import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { getGeneratedMediaTaskIdsForSessionKey, hasNewGeneratedMediaTaskForSessionKey, } from "../../tasks/task-status-access.js"; -import type { ThinkLevel } from "../thinking.js"; -import { - createAgentLifecycleTerminalBackstop, - type AgentLifecycleTerminalBackstop, -} from "./agent-lifecycle-terminal.js"; +import { createAgentLifecycleTerminalBackstop } from "./agent-lifecycle-terminal.js"; import { resolveRunAuthProfile } from "./agent-runner-auth-profile.js"; import { clearCliSessionBindingForRun, @@ -31,58 +20,19 @@ import { runCliAgentWithLifecycle, } from "./agent-runner-cli-dispatch.js"; import { buildCommandOutputFromToolResultEvent } from "./agent-runner-command-output.js"; -import type { AgentTurnParams } from "./agent-runner-execution.types.js"; -import type { createAgentTurnPresentation } from "./agent-runner-presentation.js"; -import type { AgentTurnTimingTracker } from "./agent-runner-turn-timing.js"; +import type { AgentFallbackCandidateCommonParams } from "./agent-runner-fallback-cycle.types.js"; import { shouldBridgeCliPreambleEvents } from "./get-reply.types.js"; import { hasInboundAudio } from "./inbound-media.js"; import { resolveOriginMessageProvider } from "./origin-routing.js"; -import type { FollowupRun } from "./queue.js"; import { resolveReplyOperationTerminationFields } from "./reply-operation-abort.js"; import { resolveFollowupRunToolAuthorityFingerprint } from "./reply-tool-authority.js"; -type CliPresentation = Pick< - ReturnType, - | "blockReplyHandler" - | "classifyStreamingPartial" - | "sanitizeStreamingText" - | "startPresentationWhileTyping" ->; - -export async function runCliFallbackCandidate(params: { - preparedRunAdmission: PreparedAgentRunAdmission; - turn: AgentTurnParams; - candidateRun: FollowupRun["run"]; - runtimeConfig: OpenClawConfig; - provider: string; - model: string; - cliExecutionProvider: string; - candidateThinkLevel?: ThinkLevel; - candidateFastMode: Pick; - runId: string; - lifecycleGeneration: string; - runAbortSignal?: AbortSignal; - runLane: RunCliAgentParams["lane"]; - isFinalFallbackAttempt?: boolean; - suppressQueuedUserPersistenceForCandidate: boolean; - userTurnTranscriptRecorder: RunCliAgentParams["userTurnTranscriptRecorder"]; - contextEngineLogicalTurnLease: ContextEngineLogicalTurnLease; - onContextEngineTurnCandidate: RunCliAgentParams["onContextEngineTurnCandidate"]; - notifyUserMessagePersisted: () => void; - fastModeStartedAtMs: number; - fastModeAutoProgressState: FastModeAutoProgressState; - bootstrapContextRunKind: BootstrapContextRunKind; - bootstrapPromptWarningSignaturesSeen: string[]; - currentTurnImages: Awaited< - ReturnType - >; - signalExecutionPhaseForTyping: NonNullable; - notifyAgentRunStart: () => void; - preserveProgressCallbackStartOrder: boolean; - presentation: CliPresentation; - timing: AgentTurnTimingTracker; - onLifecycleBackstop: (backstop: AgentLifecycleTerminalBackstop) => void; -}): Promise<{ +export async function runCliFallbackCandidate( + params: AgentFallbackCandidateCommonParams & { + cliExecutionProvider: string; + lifecycleGeneration: string; + }, +): Promise<{ result: Awaited>; bootstrapPromptWarningSignaturesSeen: string[]; }> { @@ -103,10 +53,6 @@ export async function runCliFallbackCandidate(params: { storePath: turn.storePath, } : undefined; - const cliSessionBinding = getCliSessionBinding( - turn.getActiveSessionEntry(), - params.cliExecutionProvider, - ); const cliLifecycleStartedAt = Date.now(); const lifecycleBackstop = createAgentLifecycleTerminalBackstop({ runId: params.runId, @@ -120,7 +66,6 @@ export async function runCliFallbackCandidate(params: { const authProfile = resolveRunAuthProfile(params.candidateRun, params.cliExecutionProvider, { config: params.runtimeConfig, }); - let droppedCliSessionReplacement = false; const hookMessageProvider = resolveOriginMessageProvider({ originatingChannel: turn.followupRun.originatingChannel, provider: turn.sessionCtx.Provider, @@ -186,10 +131,13 @@ export async function runCliFallbackCandidate(params: { runId: params.runId, }, async () => { - // Admission may wait behind another turn that starts detached media. - // Snapshot only after this turn owns the session placement. + // Placement admission may wait behind an older turn. Snapshot placement, + // permission, and native resume identity only after this turn owns it. + const sessionEntry = turn.getActiveSessionEntry(); + const cliSessionBinding = getCliSessionBinding(sessionEntry, params.cliExecutionProvider); const mediaTaskIdsBefore = getGeneratedMediaTaskIdsForSessionKey(turn.sessionKey); - return await runCliAgentWithLifecycle({ + let droppedCliSessionReplacement = false; + const candidateResult = await runCliAgentWithLifecycle({ runId: params.runId, lifecycleGeneration: params.lifecycleGeneration, provider: params.cliExecutionProvider, @@ -349,6 +297,7 @@ export async function runCliFallbackCandidate(params: { sessionId: turn.followupRun.run.sessionId, sessionKey: turn.sessionKey, sessionTarget, + sessionEntry, chatType: normalizeChatType(turn.followupRun.originatingChatType) ?? normalizeChatType(turn.sessionCtx.ChatType) ?? @@ -381,7 +330,7 @@ export async function runCliFallbackCandidate(params: { modelHasVision, modelContextWindow: selectedModelEntry?.contextWindow, modelContextTokens: selectedModelEntry?.contextTokens, - contextWindow: turn.getActiveSessionEntry()?.contextWindow, + contextWindow: sessionEntry?.contextWindow, provider: params.cliExecutionProvider, execOverrides: turn.followupRun.run.execOverrides, bashElevated: turn.followupRun.run.bashElevated, @@ -454,19 +403,20 @@ export async function runCliFallbackCandidate(params: { replyOperation: turn.replyOperation, }, }); + if (droppedCliSessionReplacement) { + await clearCliSessionBindingForRun({ + provider: params.cliExecutionProvider, + expectedSessionId: cliSessionBinding?.sessionId, + sessionKey: turn.sessionKey, + sessionStore: turn.activeSessionStore, + storePath: turn.storePath, + activeSessionEntry: turn.getActiveSessionEntry(), + }); + } + return candidateResult; }, ), ); - if (droppedCliSessionReplacement) { - await clearCliSessionBindingForRun({ - provider: params.cliExecutionProvider, - expectedSessionId: cliSessionBinding?.sessionId, - sessionKey: turn.sessionKey, - sessionStore: turn.activeSessionStore, - storePath: turn.storePath, - activeSessionEntry: turn.getActiveSessionEntry(), - }); - } return { result, bootstrapPromptWarningSignaturesSeen: resolveBootstrapWarningSignaturesSeen( diff --git a/src/auto-reply/reply/agent-runner-embedded-candidate.ts b/src/auto-reply/reply/agent-runner-embedded-candidate.ts index 7a4945e2a37a..93214ccf8d6e 100644 --- a/src/auto-reply/reply/agent-runner-embedded-candidate.ts +++ b/src/auto-reply/reply/agent-runner-embedded-candidate.ts @@ -1,16 +1,10 @@ import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import type { PreparedAgentRunAdmission } from "../../agents/admitted-run-context.js"; import { resolveBootstrapWarningSignaturesSeen } from "../../agents/bootstrap-budget.js"; -import type { BootstrapContextRunKind } from "../../agents/bootstrap-mode.js"; import type { RunEmbeddedAgentInternalParams } from "../../agents/embedded-agent-runner/run/internal-params.js"; -import type { RunEmbeddedAgentParams } from "../../agents/embedded-agent-runner/run/params.js"; import { runEmbeddedAgent } from "../../agents/embedded-agent.js"; -import type { FastModeAutoProgressState } from "../../agents/fast-mode.js"; -import type { ContextEngineLogicalTurnLease } from "../../agents/harness/context-engine-logical-turn.js"; import { resolveAgentHarnessPolicy } from "../../agents/harness/policy.js"; import { resolveOpenAIRuntimeProvider } from "../../agents/openai-routing.js"; import { resolveGroupSessionKey } from "../../config/sessions.js"; -import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { isTrustedMessageActionTurnIngress, mintMessageActionTurnCapability, @@ -23,21 +17,14 @@ import { resolveMessageChannel, } from "../../utils/message-channel.js"; import type { PartialReplyPayload } from "../get-reply-options.types.js"; -import type { ThinkLevel } from "../thinking.js"; import type { ReplyPayload } from "../types.js"; -import { - createAgentLifecycleTerminalBackstop, - type AgentLifecycleTerminalBackstop, -} from "./agent-lifecycle-terminal.js"; +import { createAgentLifecycleTerminalBackstop } from "./agent-lifecycle-terminal.js"; import { createAgentRunEventHandler, type MessageToolDeliveryState, } from "./agent-runner-event-handler.js"; -import type { AgentTurnParams } from "./agent-runner-execution.types.js"; -import type { createAgentTurnPresentation } from "./agent-runner-presentation.js"; -import type { AgentTurnTimingTracker } from "./agent-runner-turn-timing.js"; +import type { AgentFallbackCandidateCommonParams } from "./agent-runner-fallback-cycle.types.js"; import { buildEmbeddedRunExecutionParams } from "./agent-runner-utils.js"; -import type { FollowupRun } from "./queue.js"; import { resolveReplyOperationTerminationFields } from "./reply-operation-abort.js"; import { markReplyOperationGlobalLaneWaitProgress } from "./reply-run-registry.js"; import { resolveFollowupRunToolAuthorityFingerprint } from "./reply-tool-authority.js"; @@ -46,60 +33,21 @@ import { readSourceReplyDeliveryRuntime, } from "./source-reply-delivery-runtime.js"; -type EmbeddedPresentation = Pick< - ReturnType, - | "classifyStreamingPartial" - | "sanitizeStreamingText" - | "normalizeStreamingText" - | "startPresentationWhileTyping" - | "blockReplyHandler" ->; - -export async function runEmbeddedFallbackCandidate(params: { - preparedRunAdmission: PreparedAgentRunAdmission; - turn: AgentTurnParams; - effectiveRun: FollowupRun["run"]; - candidateRun: FollowupRun["run"]; - runtimeConfig: OpenClawConfig; - provider: string; - model: string; - sessionRuntimeOverride?: string; - candidateThinkLevel?: ThinkLevel; - candidateFastMode: Pick; - runLane: RunEmbeddedAgentParams["lane"]; - runId: string; - getLifecycleGeneration: () => string; - onLifecycleGeneration: (generation: string) => void; - runAbortSignal?: AbortSignal; - allowTransientCooldownProbe?: boolean; - isFinalFallbackAttempt?: boolean; - suppressQueuedUserPersistenceForCandidate: boolean; - suppressAssistantErrorPersistenceForCandidate: boolean; - onAssistantErrorMessagePersisted: () => void; - userTurnTranscriptRecorder: NonNullable["userTurnTranscriptRecorder"]; - contextEngineLogicalTurnLease: ContextEngineLogicalTurnLease; - onContextEngineTurnCandidate: RunEmbeddedAgentParams["onContextEngineTurnCandidate"]; - notifyUserMessagePersisted: () => void; - fastModeStartedAtMs: number; - fastModeAutoProgressState: FastModeAutoProgressState; - bootstrapContextRunKind: BootstrapContextRunKind; - bootstrapPromptWarningSignaturesSeen: string[]; - currentTurnImages: Awaited< - ReturnType - >; - signalExecutionPhaseForTyping: NonNullable< - Parameters[0]["onExecutionPhase"] - >; - notifyAgentRunStart: () => void; - notifyUserAboutCompaction: boolean; - messageToolDeliveryState: MessageToolDeliveryState; - preserveProgressCallbackStartOrder: boolean; - githubPublicationAvailable: boolean; - presentation: EmbeddedPresentation; - timing: AgentTurnTimingTracker; - onLifecycleBackstop: (backstop: AgentLifecycleTerminalBackstop) => void; - onCompactionCount: (count: number) => void; -}): Promise<{ +export async function runEmbeddedFallbackCandidate( + params: AgentFallbackCandidateCommonParams & { + effectiveRun: AgentFallbackCandidateCommonParams["candidateRun"]; + sessionRuntimeOverride?: string; + getLifecycleGeneration: () => string; + onLifecycleGeneration: (generation: string) => void; + allowTransientCooldownProbe?: boolean; + suppressAssistantErrorPersistenceForCandidate: boolean; + onAssistantErrorMessagePersisted: () => void; + notifyUserAboutCompaction: boolean; + messageToolDeliveryState: MessageToolDeliveryState; + githubPublicationAvailable: boolean; + onCompactionCount: (count: number) => void; + }, +): Promise<{ result: Awaited>; bootstrapPromptWarningSignaturesSeen: string[]; }> { diff --git a/src/auto-reply/reply/agent-runner-execution-cli-sessions.test.ts b/src/auto-reply/reply/agent-runner-execution-cli-sessions.test.ts index b884f3fa2ab4..f3212fda820a 100644 --- a/src/auto-reply/reply/agent-runner-execution-cli-sessions.test.ts +++ b/src/auto-reply/reply/agent-runner-execution-cli-sessions.test.ts @@ -1,5 +1,9 @@ -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { buildPreparedCliRunContext } from "../../agents/cli-runner.test-helpers.js"; +import { executeDeps } from "../../agents/cli-runner/execute-deps.js"; +import { executePreparedCliRun } from "../../agents/cli-runner/execute.js"; import { prepareCliPromptImagePayload } from "../../agents/cli-runner/helpers.js"; +import { buildCliMcpGrantContext } from "../../agents/cli-runner/mcp-grant-context.js"; import type { RunCliAgentParams } from "../../agents/cli-runner/types.js"; import { detectAndLoadPromptImages } from "../../agents/embedded-agent-runner/run/images.js"; import { FailoverError } from "../../agents/failover-error.js"; @@ -27,6 +31,137 @@ const state = setupAgentRunnerExecutionTestState(); afterEach(resetGeneratedMediaTaskActivityForTests); describe("executeAgentTurn: CLI session routing", () => { + it("carries the admitted session permission and placement into the CLI grant", async () => { + state.isCliProviderMock.mockReturnValue(true); + state.runWithModelFallbackMock.mockImplementationOnce(async (params: FallbackRunnerParams) => ({ + result: await params.run( + "claude-cli", + "claude-sonnet-4-6", + initialFallbackAttemptOptions(params), + ), + provider: "claude-cli", + model: "claude-sonnet-4-6", + attempts: [], + })); + let sessionEntry: SessionEntry = { + sessionId: "session", + updatedAt: 1, + permissionMode: "guarded", + sessionRoot: "/workspace/old", + execHost: "gateway", + cliSessionBindings: { + "claude-cli": { sessionId: "old-native-session", forceReuse: true }, + }, + }; + const admittedSessionEntry: SessionEntry = { + sessionId: "session", + updatedAt: 2, + permissionMode: "read-only", + sessionRoot: "/workspace/project", + execHost: "node", + execNode: "node-a", + execCwd: "/workspace/project/task", + cliSessionBindings: { + "claude-cli": { sessionId: "new-native-session", forceReuse: true }, + }, + }; + state.runCliAgentMock.mockResolvedValueOnce({ payloads: [{ text: "done" }], meta: {} }); + const followupRun = createFollowupRun(); + followupRun.run.provider = "claude-cli"; + followupRun.run.model = "claude-sonnet-4-6"; + const restoreAdmission = installSessionPlacementAdmissionProvider({ + executeLocalTurn: async (_claim, runLocal) => { + sessionEntry = admittedSessionEntry; + return await runLocal(); + }, + executeTurn: async (_claim, _params, runLocal) => await runLocal(), + }); + + try { + const executeAgentTurn = await getExecuteAgentTurnForTest(); + const result = await executeAgentTurn({ + ...createMinimalRunAgentTurnParams({ followupRun }), + getActiveSessionEntry: () => sessionEntry, + }); + + expect(result.kind).toBe("success"); + const run = requireMockCall( + state.runCliAgentMock, + 0, + "CLI run params", + )[0] as RunCliAgentParams; + expect(run.sessionEntry).toBe(admittedSessionEntry); + expect(run.sessionEntry?.sessionRoot).toBe("/workspace/project"); + expect(run.cliSessionId).toBe("new-native-session"); + expect(run.cliSessionBinding).toMatchObject({ + sessionId: "new-native-session", + forceReuse: true, + }); + const observedCliSessionId = run.cliSessionBinding?.sessionId ?? run.cliSessionId; + expect(observedCliSessionId).toBe("new-native-session"); + if (!observedCliSessionId) { + throw new Error("expected admitted CLI session binding"); + } + expect( + buildCliMcpGrantContext({ + run, + config: run.config ?? {}, + requireExplicitMessageTarget: false, + agentId: "main", + modelProvider: "anthropic", + modelId: "claude-sonnet-4-6", + }).execSession, + ).toMatchObject({ + permissionMode: "read-only", + execHost: "node", + execNode: "node-a", + }); + + const nodeInvoke = vi.fn(async (request) => { + expect(request.nodeId).toBe("node-a"); + expect(request.argv).toContain("new-native-session"); + expect(request.argv).not.toContain("old-native-session"); + return { + ok: true, + payloadJSON: JSON.stringify({ exitCode: 0, stderrTail: "", truncated: false }), + }; + }); + const restoreNodeInvoke = executeDeps.invokeNodeClaudeCliRun; + const backend = { + command: "claude", + args: ["-p"], + resumeArgs: ["--resume", "{sessionId}"], + output: "text" as const, + input: "stdin" as const, + serialize: true, + }; + const prepared = buildPreparedCliRunContext({ + provider: "claude-cli", + model: run.model, + runId: run.runId, + workspaceDir: run.workspaceDir, + config: run.config, + backend, + }); + prepared.params = { + ...run, + admittedRunContext: prepared.params.admittedRunContext, + skillsSnapshot: undefined, + }; + prepared.cwd = run.cwd; + prepared.reusableCliSession = { mode: "reuse", sessionId: observedCliSessionId }; + executeDeps.invokeNodeClaudeCliRun = nodeInvoke; + try { + await executePreparedCliRun(prepared, observedCliSessionId); + } finally { + executeDeps.invokeNodeClaudeCliRun = restoreNodeInvoke; + } + expect(nodeInvoke).toHaveBeenCalledOnce(); + } finally { + restoreAdmission(); + } + }); + it("carries prepared model and thread context facts into CLI execution", async () => { state.isCliProviderMock.mockReturnValue(true); state.runWithModelFallbackMock.mockImplementationOnce(async (params: FallbackRunnerParams) => ({ @@ -561,14 +696,30 @@ describe("executeAgentTurn: CLI session routing", () => { }, } as unknown as SessionEntry; const activeSessionStore = { main: sessionEntry }; - - const result = await executeAgentTurn({ - ...createMinimalRunAgentTurnParams({ followupRun }), - activeSessionStore, - getActiveSessionEntry: () => sessionEntry, + let cleanupObservedBeforePlacementRelease = false; + const restoreAdmission = installSessionPlacementAdmissionProvider({ + executeLocalTurn: async (_claim, runLocal) => { + const resultLocal = await runLocal(); + expect(activeSessionStore.main.cliSessionBindings?.["codex-cli"]).toBeUndefined(); + cleanupObservedBeforePlacementRelease = true; + return resultLocal; + }, + executeTurn: async (_claim, _params, runLocal) => await runLocal(), }); + let result: Awaited>; + try { + result = await executeAgentTurn({ + ...createMinimalRunAgentTurnParams({ followupRun }), + activeSessionStore, + getActiveSessionEntry: () => sessionEntry, + }); + } finally { + restoreAdmission(); + } + expect(result.kind).toBe("success"); + expect(cleanupObservedBeforePlacementRelease).toBe(true); expectMockCallArgFields(state.runCliAgentMock, 0, "CLI run params", { currentInboundEventKind: "room_event", cliSessionId: "existing-cli-session", diff --git a/src/auto-reply/reply/agent-runner-fallback-candidate.ts b/src/auto-reply/reply/agent-runner-fallback-candidate.ts index d80ac668085f..979ef1744ce8 100644 --- a/src/auto-reply/reply/agent-runner-fallback-candidate.ts +++ b/src/auto-reply/reply/agent-runner-fallback-candidate.ts @@ -16,7 +16,10 @@ import { runCliFallbackCandidate } from "./agent-runner-cli-candidate.js"; import { runEmbeddedFallbackCandidate } from "./agent-runner-embedded-candidate.js"; import type { MessageToolDeliveryState } from "./agent-runner-event-handler.js"; import type { EmbeddedAgentRunResult } from "./agent-runner-execution.types.js"; -import type { AgentFallbackCycleParams } from "./agent-runner-fallback-cycle.types.js"; +import type { + AgentFallbackCandidateCommonParams, + AgentFallbackCycleParams, +} from "./agent-runner-fallback-cycle.types.js"; import { emitModelFallbackStepLifecycle } from "./agent-runner-model-fallback-lifecycle.js"; import { resolveModelFallbackOptions, @@ -240,6 +243,7 @@ export async function runAgentFallbackCandidates(params: AgentFallbackCycleParam candidateFastMode, runId: params.runId, runAbortSignal: params.runAbortSignal, + runLane, isFinalFallbackAttempt: runOptions?.isFinalFallbackAttempt, suppressQueuedUserPersistenceForCandidate: (turn.followupRun.run.suppressNextUserMessagePersistence ?? false) || @@ -263,13 +267,12 @@ export async function runAgentFallbackCandidates(params: AgentFallbackCycleParam onLifecycleBackstop: (backstop: AgentLifecycleTerminalBackstop) => { params.state.pendingLifecycleTerminal = { provider, model, backstop }; }, - }; + } satisfies AgentFallbackCandidateCommonParams; if (runtime.useCliExecution) { const candidate = await runCliFallbackCandidate({ ...common, cliExecutionProvider: runtime.cliExecutionProvider, lifecycleGeneration: params.state.lifecycleGeneration, - runLane, }); params.state.bootstrapPromptWarningSignaturesSeen = candidate.bootstrapPromptWarningSignaturesSeen; @@ -277,7 +280,6 @@ export async function runAgentFallbackCandidates(params: AgentFallbackCycleParam } const candidate = await runEmbeddedFallbackCandidate({ ...common, - runLane, githubPublicationAvailable: await (githubPublicationAvailability ??= turn.sessionKey && params.effectiveRun.agentId ? prepareGitHubPublicationAvailability({ diff --git a/src/auto-reply/reply/agent-runner-fallback-cycle.types.ts b/src/auto-reply/reply/agent-runner-fallback-cycle.types.ts index 75e9966db194..8da79714fe19 100644 --- a/src/auto-reply/reply/agent-runner-fallback-cycle.types.ts +++ b/src/auto-reply/reply/agent-runner-fallback-cycle.types.ts @@ -1,7 +1,11 @@ import type { PreparedAgentRunAdmission } from "../../agents/admitted-run-context.js"; +import type { BootstrapContextRunKind } from "../../agents/bootstrap-mode.js"; import type { RunEmbeddedAgentParams } from "../../agents/embedded-agent-runner/run/params.js"; +import type { FastModeAutoProgressState } from "../../agents/fast-mode.js"; +import type { ContextEngineLogicalTurnLease } from "../../agents/harness/context-engine-logical-turn.js"; import type { SessionEntry } from "../../config/sessions.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; +import type { ThinkLevel } from "../thinking.js"; import type { AgentLifecycleTerminalBackstop } from "./agent-lifecycle-terminal.js"; import type { AgentTurnInternalResult, @@ -13,6 +17,40 @@ import type { createAgentTurnPresentation } from "./agent-runner-presentation.js import type { AgentTurnTimingTracker } from "./agent-runner-turn-timing.js"; import type { FollowupRun } from "./queue.js"; +/** Inputs prepared once per fallback candidate and consumed by either runtime adapter. */ +export type AgentFallbackCandidateCommonParams = { + preparedRunAdmission: PreparedAgentRunAdmission; + turn: AgentTurnParams; + candidateRun: FollowupRun["run"]; + runtimeConfig: OpenClawConfig; + provider: string; + model: string; + candidateThinkLevel?: ThinkLevel; + candidateFastMode: Pick; + runId: string; + runAbortSignal?: AbortSignal; + runLane: RunEmbeddedAgentParams["lane"]; + isFinalFallbackAttempt?: boolean; + suppressQueuedUserPersistenceForCandidate: boolean; + userTurnTranscriptRecorder: RunEmbeddedAgentParams["userTurnTranscriptRecorder"]; + contextEngineLogicalTurnLease: ContextEngineLogicalTurnLease; + onContextEngineTurnCandidate: RunEmbeddedAgentParams["onContextEngineTurnCandidate"]; + notifyUserMessagePersisted: () => void; + fastModeStartedAtMs: number; + fastModeAutoProgressState: FastModeAutoProgressState; + bootstrapContextRunKind: BootstrapContextRunKind; + bootstrapPromptWarningSignaturesSeen: string[]; + currentTurnImages: Awaited< + ReturnType + >; + signalExecutionPhaseForTyping: NonNullable; + notifyAgentRunStart: () => void; + preserveProgressCallbackStartOrder: boolean; + presentation: ReturnType; + timing: AgentTurnTimingTracker; + onLifecycleBackstop: (backstop: AgentLifecycleTerminalBackstop) => void; +}; + export type AgentFallbackCycleState = { lifecycleGeneration: string; autoCompactionCount: number;