fix(agents): prepare selected harnesses for setup probes (#123141)

Keep explicitly selected plugin runtimes in isolated read-only generations so gateway setup can run Codex candidates. Improve missing-registration guidance.
This commit is contained in:
Peter Steinberger
2026-08-13 04:09:15 -07:00
committed by GitHub
parent fa18d8d273
commit d5d069a09e
4 changed files with 48 additions and 3 deletions
+14
View File
@@ -62,6 +62,20 @@ describe("harness runtime plugins", () => {
expect(pluginRegistry.agentHarnesses).toHaveLength(1);
});
it("explains how to recover when the selected harness registration is missing", async () => {
await expect(
ensureSelectedAgentHarnessPlugin({
provider: "openai",
modelId: "gpt-5.5",
agentHarnessRuntimeOverride: "codex",
workspaceDir: "/tmp/workspace",
pluginRegistry: createEmptyPluginRegistry(),
}),
).rejects.toThrow(
'Agent harness runtime "codex" is unavailable because its plugin registration is missing from this prepared run. Enable or reinstall the plugin that provides this runtime, restart the Gateway, then retry.',
);
});
it("force-activates a default-disabled harness owner selected for a run", () => {
const plan = resolveAgentRuntimePluginLoadPlan({
config: {},
+3 -1
View File
@@ -131,6 +131,8 @@ export async function ensureSelectedAgentHarnessPlugin(params: {
}
if (!params.pluginRegistry?.agentHarnesses.some((entry) => entry.harness.id === runtime)) {
throw new Error(`Agent harness runtime "${runtime}" is not present in the prepared registry.`);
throw new Error(
`Agent harness runtime "${runtime}" is unavailable because its plugin registration is missing from this prepared run. Enable or reinstall the plugin that provides this runtime, restart the Gateway, then retry.`,
);
}
}
@@ -54,10 +54,12 @@ export function prepareWorkspacePluginRegistries(
runtimePluginRegistry?: PluginRegistry;
inboundPluginRegistry?: PluginRegistry;
} {
if (input.readOnly) {
// Read-only catalog owners stay runtime-free, but setup probes carry an exact harness selection.
// That selected registry must belong to the generation instead of leaking from an outer scope.
if (input.readOnly && !input.runtimePluginSelections) {
return {};
}
const inboundPluginRegistry = loadInboundRegistry?.(input);
const inboundPluginRegistry = input.readOnly ? undefined : loadInboundRegistry?.(input);
const runtimePluginRegistry =
input.runtimePluginSelections || !inboundPluginRegistry
? loadAgentRuntimePluginRegistryHandle({
+27
View File
@@ -48,6 +48,24 @@ describe("prepared model runtime snapshots", () => {
it("keeps an isolated setup probe exact after a gateway replacement", async () => {
mocks.configuredAgentIds = ["default"];
const stagedConfig = { agents: { defaults: { model: "openai/gpt-5.6" } } };
const selectedPluginRegistry = createEmptyPluginRegistry();
selectedPluginRegistry.agentHarnesses.push({
pluginId: "codex",
source: "test",
harness: {
id: "codex",
label: "Codex",
supports: () => ({ supported: true }),
runAttempt: async () => {
throw new Error("unused");
},
},
});
mocks.loadAgentRuntimePluginRegistryHandle.mockImplementation((params) =>
(params as { selections?: unknown }).selections
? selectedPluginRegistry
: createEmptyPluginRegistry(),
);
await refreshPreparedModelRuntimeSnapshots({}, { gatewayLifecycle: true });
markPreparedModelRuntimeSnapshotsStale("test isolated probe replacement", {
waitForReplacement: true,
@@ -58,6 +76,7 @@ describe("prepared model runtime snapshots", () => {
agentDir: "/tmp/setup-probe-agent",
inheritedAuthDir: "/tmp/setup-probe-agent",
workspaceDir: "/tmp/setup-probe-workspace",
runtimePluginSelections: [{ provider: "openai", modelId: "gpt-5.6", runtime: "codex" }],
});
await Promise.resolve();
expect(mocks.ensureOpenClawModelsJson).toHaveBeenCalledTimes(1);
@@ -72,6 +91,14 @@ describe("prepared model runtime snapshots", () => {
agentDir: "/tmp/setup-probe-agent",
workspaceDir: "/tmp/setup-probe-workspace",
});
expect(lease.snapshot.pluginRegistry?.agentHarnesses.map((entry) => entry.harness.id)).toEqual([
"codex",
]);
expect(mocks.loadAgentRuntimePluginRegistryHandle).toHaveBeenCalledWith(
expect.objectContaining({
selections: [{ provider: "openai", modelId: "gpt-5.6", runtime: "codex" }],
}),
);
lease.release();
});