fix(agents): preserve explicit harness runtime selection (#126259)

This commit is contained in:
Vincent Koc
2026-08-21 06:32:23 +08:00
committed by GitHub
parent 7c959c85f9
commit c19bdb3a1d
7 changed files with 174 additions and 5 deletions
@@ -3776,7 +3776,11 @@ describe("embedded attempt harness pinning", () => {
sessionHasHistory: true,
});
expectMockArgFields(runEmbeddedAgentMock, { agentHarnessId: undefined });
expectMockArgFields(runEmbeddedAgentMock, {
agentHarnessId: undefined,
agentHarnessRuntimeOverride: undefined,
agentHarnessRuntimePreparationHint: "codex",
});
});
it("auto-forwards OpenAI Codex auth profiles to default Codex harness runs", async () => {
+2
View File
@@ -1171,6 +1171,8 @@ export function runAgentAttempt(params: {
agentHarnessId: embeddedAgentHarnessOverride,
modelSelectionLocked: !isRawModelRun && params.sessionEntry?.modelSelectionLocked === true,
agentHarnessRuntimeOverride: embeddedAgentHarnessOverride,
agentHarnessRuntimePreparationHint:
agentHarnessPolicy.runtimeSource !== "implicit" ? agentHarnessPolicy.runtime : undefined,
skillsSnapshot: params.skillsSnapshot,
prompt: embeddedModelPrompt,
transcriptPrompt: embeddedPersistencePrompt,
@@ -222,7 +222,9 @@ async function runEmbeddedAgentInternal(
provider: params.provider,
model: params.model,
});
const requestedHarnessRuntime = params.agentHarnessId ?? params.agentHarnessRuntimeOverride;
const explicitHarnessRuntime = params.agentHarnessId ?? params.agentHarnessRuntimeOverride;
const requestedHarnessRuntime =
explicitHarnessRuntime ?? params.agentHarnessRuntimePreparationHint;
const runtimePluginFallbacksOverride =
params.modelFallbacksOverride ??
resolveRunModelFallbacksOverride({
@@ -245,8 +247,10 @@ async function runEmbeddedAgentInternal(
model: requestedRuntimeSelection.modelId,
requestedRouteResolution: "resolved",
fallbacksOverride: runtimePluginFallbacksOverride,
}).map((candidate) =>
requestedHarnessRuntime
}).map((candidate, index) =>
requestedHarnessRuntime &&
// Preparation hints apply only to the requested route; fallbacks resolve their own policy.
(index === 0 || explicitHarnessRuntime)
? {
provider: candidate.provider,
modelId: candidate.model,
@@ -33,11 +33,13 @@ import { registerAgentHarness } from "../harness/registry.js";
import { makeAttemptResult } from "./run.overflow-compaction.fixture.js";
import {
loadRunOverflowCompactionHarness,
mockedAcquireAgentRunPreparedModelRuntime,
mockedBuildEmbeddedRunPayloads,
mockedGlobalHookRunner,
mockedRunEmbeddedAttempt,
useOpenAIPlatformAuthFixture,
} from "./run.overflow-compaction.harness.js";
import type { RunEmbeddedAgentInternalParams } from "./run/internal-params.js";
import { buildEmbeddedSystemPrompt } from "./system-prompt.js";
const runnerState = setupAgentRunnerExecutionTestState();
@@ -429,4 +431,109 @@ describe("prepared harness source delivery", () => {
);
}
});
it("prepares a Codex primary without pinning a plugin-owned fallback", async () => {
const { runEmbeddedAgent, registerPreparedAgentHarness } =
await loadRunOverflowCompactionHarness();
registerPreparedAgentHarness({
id: "fallback-owner",
label: "Fallback owner",
supports: ({ provider }) =>
provider === "custom" ? { supported: true } : { supported: false },
runAttempt: vi.fn(async () => ({}) as never),
});
mockedGlobalHookRunner.hasHooks.mockReturnValue(false);
mockedBuildEmbeddedRunPayloads.mockReturnValue([{ text: "primary" }]);
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
makeAttemptResult({ assistantTexts: ["primary"] }),
);
useOpenAIPlatformAuthFixture();
const runParams: RunEmbeddedAgentInternalParams = {
agentId: "worker",
sessionId: "runtime-preparation-hint",
workspaceDir: "/tmp/workspace",
prompt: "hello",
runId: "runtime-preparation-hint",
timeoutMs: 30_000,
provider: "openai",
model: "gpt-5.4",
agentHarnessRuntimePreparationHint: "codex",
modelFallbacksOverride: ["fast"],
config: {
agents: {
list: [
{ id: "main", default: true },
{
id: "worker",
models: {
"openai/gpt-5.4": { agentRuntime: { id: "codex" } },
"custom/plugin-fallback": {
alias: "fast",
agentRuntime: { id: "fallback-owner" },
},
},
},
],
defaults: {
models: {
"custom/global-fallback": { alias: "fast" },
},
},
},
},
};
await runEmbeddedAgent(runParams);
expect(mockedAcquireAgentRunPreparedModelRuntime).toHaveBeenCalledWith(
expect.objectContaining({
runtimePluginSelections: [
{ provider: "openai", modelId: "gpt-5.4", runtime: "codex", agentId: "worker" },
{ provider: "custom", modelId: "plugin-fallback", agentId: "worker" },
],
}),
expect.any(Object),
);
});
it.each([
["agentHarnessId", { agentHarnessId: "codex" }],
["agentHarnessRuntimeOverride", { agentHarnessRuntimeOverride: "codex" }],
] as const)("keeps %s authoritative across fallback preparation", async (_label, override) => {
const { runEmbeddedAgent } = await loadRunOverflowCompactionHarness();
mockedGlobalHookRunner.hasHooks.mockReturnValue(false);
mockedBuildEmbeddedRunPayloads.mockReturnValue([{ text: "primary" }]);
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
makeAttemptResult({ assistantTexts: ["primary"] }),
);
useOpenAIPlatformAuthFixture();
await runEmbeddedAgent({
agentId: "worker",
sessionId: `authoritative-${_label}`,
workspaceDir: "/tmp/workspace",
prompt: "hello",
runId: `authoritative-${_label}`,
timeoutMs: 30_000,
provider: "openai",
model: "gpt-5.4",
modelFallbacksOverride: ["custom/plugin-fallback"],
...override,
});
expect(mockedAcquireAgentRunPreparedModelRuntime).toHaveBeenCalledWith(
expect.objectContaining({
runtimePluginSelections: [
{ provider: "openai", modelId: "gpt-5.4", runtime: "codex", agentId: "worker" },
{
provider: "custom",
modelId: "plugin-fallback",
runtime: "codex",
agentId: "worker",
},
],
}),
expect.any(Object),
);
});
});
@@ -6,6 +6,8 @@ import type { RunEmbeddedAgentParams } from "./params.js";
export type RunEmbeddedAgentInternalParams = RunEmbeddedAgentParams & {
onSuccessfulAuthBinding?: (binding: AgentExecutionAuthBinding) => void;
authProfileStateMode?: "read-write" | "read-only";
/** Prepare only the requested candidate with this runtime; fallbacks keep their own policy. */
agentHarnessRuntimePreparationHint?: string;
/** Keep staged setup config and credentials outside configured Gateway ownership. */
preparedModelRuntimeMode?: "isolated-read-only";
/** Ring-zero tool override, supplied only by the OpenClaw orchestrator. */
@@ -2,6 +2,7 @@ import { normalizeOptionalString } from "@openclaw/normalization-core/string-coe
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";
@@ -212,7 +213,7 @@ export async function runEmbeddedFallbackCandidate(params: {
});
let eventHandler: ReturnType<typeof createAgentRunEventHandler> | undefined;
const result = await params.timing.measure("embedded_run", () => {
const embeddedRunParams: Parameters<typeof runEmbeddedAgent>[0] = {
const embeddedRunParams: RunEmbeddedAgentInternalParams = {
preparedRunAdmission: params.preparedRunAdmission,
githubPublicationAvailable: params.githubPublicationAvailable,
...embeddedContext,
@@ -233,6 +234,8 @@ export async function runEmbeddedFallbackCandidate(params: {
provider: embeddedRunProvider,
agentHarnessId: embeddedRunHarnessOverride,
agentHarnessRuntimeOverride: embeddedRunHarnessOverride,
agentHarnessRuntimePreparationHint:
agentHarnessPolicy.runtimeSource !== "implicit" ? agentHarnessPolicy.runtime : undefined,
fastModeStartedAtMs: params.fastModeStartedAtMs,
fastModeAutoProgressState: params.fastModeAutoProgressState,
isFinalFallbackAttempt: params.isFinalFallbackAttempt,
@@ -243,6 +243,53 @@ describe("executeAgentTurn: runtime selection", () => {
});
});
it("forwards model-scoped Codex policy as a worker preparation hint", async () => {
state.runWithModelFallbackMock.mockImplementationOnce(async (params: FallbackRunnerParams) => ({
result: await params.run("openai", "gpt-5.5"),
provider: "openai",
model: "gpt-5.5",
attempts: [],
}));
state.runEmbeddedAgentMock.mockResolvedValueOnce({
payloads: [{ text: "worker" }],
meta: {},
});
const executeAgentTurn = await getExecuteAgentTurnForTest();
const followupRun = createFollowupRun();
followupRun.run.agentId = "worker";
followupRun.run.sessionKey = "agent:worker:main";
followupRun.run.provider = "openai";
followupRun.run.model = "gpt-5.5";
followupRun.run.config = {
agents: {
ownership: "explicit",
entries: {
main: {},
worker: {
models: {
"openai/gpt-5.5": { agentRuntime: { id: "codex" } },
},
},
},
},
};
const result = await executeAgentTurn({
...createMinimalRunAgentTurnParams({ followupRun }),
sessionKey: "agent:worker:main",
});
expect(result.kind).toBe("success");
expectMockCallArgFields(state.runEmbeddedAgentMock, 0, "embedded run params", {
agentId: "worker",
githubPublicationAvailable: false,
agentHarnessId: undefined,
agentHarnessRuntimeOverride: undefined,
agentHarnessRuntimePreparationHint: "codex",
});
});
it("keeps catalog-adopted Codex sessions on Codex during heartbeat model overrides", async () => {
state.isCliProviderMock.mockImplementation((provider: unknown) => provider === "claude-cli");
state.runWithModelFallbackMock.mockImplementationOnce(async (params: FallbackRunnerParams) => ({