mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 03:45:46 -06:00
fix(models): Web UI provider probe fails for direct-credential providers (#125816)
* fix(models): keep direct-credential gateway probes on isolated runtime generations * fix(models): carry isolated-runtime probe mode on the loader type Drop the call-site cast for the widened runner params: the lazy loader now declares the isolated-read-only capable runEmbeddedAgent shape, keeping the assertion-safety ratchet at its grandfathered baseline for this file.
This commit is contained in:
@@ -13,6 +13,7 @@ import { retainLegacyDefaultAgentId } from "../config/legacy.default-agent-owner
|
||||
import { createEmptyPluginRegistry } from "../plugins/registry-empty.js";
|
||||
import {
|
||||
acquireAgentRunPreparedModelRuntime,
|
||||
acquireReadOnlyPreparedModelRuntime,
|
||||
getPreparedModelRuntimeSnapshot,
|
||||
loadPublishedGatewayReplyDispatchRuntime,
|
||||
loadPreparedModelRuntimeSnapshot,
|
||||
@@ -154,6 +155,25 @@ describe("prepared model runtime owner selection", () => {
|
||||
).rejects.toThrow("prepared model runtime owner was not published");
|
||||
});
|
||||
|
||||
it("keeps a caller-pinned agent dir for isolated read-only leases on an active gateway", async () => {
|
||||
mocks.configuredAgentIds = ["default"];
|
||||
const config = {};
|
||||
await refreshPreparedModelRuntimeSnapshots(config, { gatewayLifecycle: true });
|
||||
|
||||
// Run-provenance leases rebind a pinned agentDir to the committed configured
|
||||
// owner; isolated read-only leases keep the pinned dir so synthetic probe
|
||||
// credentials stay resolvable from that generation's auth store.
|
||||
const lease = await acquireReadOnlyPreparedModelRuntime({
|
||||
agentId: "default",
|
||||
config,
|
||||
agentDir: "/tmp/isolated-probe-agent",
|
||||
inheritedAuthDir: "/tmp/unused-agent",
|
||||
workspaceDir: "/tmp/isolated-probe-workspace",
|
||||
});
|
||||
expect(lease.snapshot.agentDir).toBe("/tmp/isolated-probe-agent");
|
||||
lease.release();
|
||||
});
|
||||
|
||||
it("publishes provider selections kept on the core runtime by request parameters", async () => {
|
||||
mocks.configuredAgentIds = ["default"];
|
||||
const config = {
|
||||
|
||||
@@ -179,6 +179,7 @@ describe("runAuthProbes", () => {
|
||||
authProfileId?: string;
|
||||
authProfileIdSource?: string;
|
||||
config?: OpenClawConfig;
|
||||
preparedModelRuntimeMode?: string;
|
||||
}): Promise<AgentRunResultView> => {
|
||||
if (params.agentHarnessRuntimeOverride !== "openclaw") {
|
||||
throw new Error(
|
||||
@@ -262,6 +263,9 @@ describe("runAuthProbes", () => {
|
||||
authProfileIdSource: "user",
|
||||
}),
|
||||
);
|
||||
// Profile targets reuse the committed configured generation: no isolated
|
||||
// runtime mode is requested.
|
||||
expect(runEmbeddedAgent.mock.calls[0]?.[0].preparedModelRuntimeMode).toBeUndefined();
|
||||
|
||||
runEmbeddedAgent.mockResolvedValueOnce({
|
||||
payloads: [{ text: "LLM request timed out.", isError: true }],
|
||||
@@ -308,6 +312,7 @@ describe("runAuthProbes", () => {
|
||||
authProfileId?: string;
|
||||
authProfileIdSource?: string;
|
||||
config?: OpenClawConfig;
|
||||
preparedModelRuntimeMode?: string;
|
||||
}) => ({ payloads: [{ text: "OK" }] }),
|
||||
);
|
||||
vi.doMock("../../agents/embedded-agent.js", () => ({ runEmbeddedAgent }));
|
||||
@@ -386,6 +391,7 @@ describe("runAuthProbes", () => {
|
||||
);
|
||||
expect(configKeyCall?.[0].agentDir).not.toBe("/tmp/openclaw-probe-agent");
|
||||
expect(configKeyCall?.[0].authProfileIdSource).toBe("user");
|
||||
expect(configKeyCall?.[0].preparedModelRuntimeMode).toBe("isolated-read-only");
|
||||
expect(configKeyCall?.[0].config).toMatchObject({
|
||||
models: {
|
||||
providers: {
|
||||
@@ -421,7 +427,12 @@ describe("runAuthProbes", () => {
|
||||
|
||||
it("isolates marker credentials from stored profiles without pinning a synthetic one", async () => {
|
||||
const runEmbeddedAgent = vi.fn(
|
||||
async (_params: { agentDir?: string; authProfileId?: string; config?: OpenClawConfig }) => ({
|
||||
async (_params: {
|
||||
agentDir?: string;
|
||||
authProfileId?: string;
|
||||
config?: OpenClawConfig;
|
||||
preparedModelRuntimeMode?: string;
|
||||
}) => ({
|
||||
payloads: [{ text: "OK" }],
|
||||
}),
|
||||
);
|
||||
@@ -490,6 +501,7 @@ describe("runAuthProbes", () => {
|
||||
const call = runEmbeddedAgent.mock.calls[0]?.[0];
|
||||
expect(call?.agentDir).not.toBe("/tmp/openclaw-probe-agent");
|
||||
expect(call?.agentDir).toContain("openclaw-auth-probe-");
|
||||
expect(call?.preparedModelRuntimeMode).toBe("isolated-read-only");
|
||||
expect(call?.config?.auth?.order?.openai).toEqual([]);
|
||||
expect(call?.config?.models?.providers?.openai?.apiKey).toBe(
|
||||
cfg.models.providers.openai.apiKey,
|
||||
|
||||
@@ -75,9 +75,22 @@ export function redactAuthProbeError(error: string): string {
|
||||
return redactStatusSecrets(error);
|
||||
}
|
||||
|
||||
const embeddedRunnerModuleLoader = createLazyImportLoader(
|
||||
() => import("../../agents/embedded-agent.js"),
|
||||
);
|
||||
/** Widened runner call shape for isolated auth probe generations (see setup-inference-core). */
|
||||
type ProbeRunEmbeddedAgentParams = Parameters<
|
||||
(typeof import("../../agents/embedded-agent.js"))["runEmbeddedAgent"]
|
||||
>[0] & {
|
||||
preparedModelRuntimeMode?: "isolated-read-only";
|
||||
};
|
||||
|
||||
type ProbeRunEmbeddedAgent = (
|
||||
params: ProbeRunEmbeddedAgentParams,
|
||||
) => ReturnType<(typeof import("../../agents/embedded-agent.js"))["runEmbeddedAgent"]>;
|
||||
|
||||
// The probe only calls runEmbeddedAgent; the widened loader type lets the call
|
||||
// request the isolated-read-only runtime generation without a call-site cast.
|
||||
const embeddedRunnerModuleLoader = createLazyImportLoader<{
|
||||
runEmbeddedAgent: ProbeRunEmbeddedAgent;
|
||||
}>(() => import("../../agents/embedded-agent.js"));
|
||||
|
||||
function loadEmbeddedRunnerModule() {
|
||||
return embeddedRunnerModuleLoader.load();
|
||||
@@ -808,7 +821,10 @@ async function probeTarget(params: {
|
||||
// Any bound-value target runs in an empty agent dir so stored profiles are
|
||||
// absent and cannot satisfy the probe via failover. Direct values pin a
|
||||
// synthetic profile; marker values are resolved by the runtime from the
|
||||
// profile-order-cleared config.
|
||||
// profile-order-cleared config. Inside a Gateway, the isolated-read-only
|
||||
// runtime mode set on the runner call keeps this pinned generation
|
||||
// authoritative: a run-provenance lease would rebind it to the committed
|
||||
// configured owner and lose the synthetic profile.
|
||||
if (target.boundValue || target.useRuntimeAuth) {
|
||||
// Canonicalize so the isolated agent DB registers and unregisters under
|
||||
// one path. os.tmpdir() is a symlink on macOS (/var -> /private/var), and
|
||||
@@ -875,6 +891,10 @@ async function probeTarget(params: {
|
||||
disableTools: true,
|
||||
modelRun: true,
|
||||
cleanupBundleMcpOnRunEnd: true,
|
||||
// Keep the isolated generation outside configured Gateway ownership: a
|
||||
// run-provenance lease rebinds the pinned agentDir to the committed
|
||||
// configured owner, losing the synthetic probe profile below.
|
||||
...(isolatedAgentDir ? { preparedModelRuntimeMode: "isolated-read-only" as const } : {}),
|
||||
abortSignal: params.abortSignal,
|
||||
})) as AgentRunResultView;
|
||||
const terminalError = extractAgentRunTerminalError(runResult);
|
||||
|
||||
Reference in New Issue
Block a user