mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-21 18:08:05 -06:00
fix(agents): preserve explicit harness runtime selection (#126259)
This commit is contained in:
@@ -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 () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
+107
@@ -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) => ({
|
||||
|
||||
Reference in New Issue
Block a user