diff --git a/extensions/qa-lab/src/docker-harness.test.ts b/extensions/qa-lab/src/docker-harness.test.ts index d5823cdc7f4..2eb7e26351b 100644 --- a/extensions/qa-lab/src/docker-harness.test.ts +++ b/extensions/qa-lab/src/docker-harness.test.ts @@ -76,7 +76,8 @@ describe("qa docker harness", () => { const config = await readFile(path.join(outputDir, "state", "openclaw.json"), "utf8"); expect(config).toContain('"allowInsecureAuth": true'); - expect(config).toContain('"enabled": false'); + expect(config).toContain('"pluginToolsMcpBridge": true'); + expect(config).toContain('"openClawToolsMcpBridge": true'); expect(config).toContain("/app/dist/control-ui"); expect(config).toContain("C-3PO QA"); expect(config).toContain('"/tmp/openclaw/workspace"'); diff --git a/extensions/qa-lab/src/qa-gateway-config.test.ts b/extensions/qa-lab/src/qa-gateway-config.test.ts index c70a4d2d1e9..0f6e502535f 100644 --- a/extensions/qa-lab/src/qa-gateway-config.test.ts +++ b/extensions/qa-lab/src/qa-gateway-config.test.ts @@ -59,7 +59,13 @@ describe("buildQaGatewayConfig", () => { expect(cfg.models?.providers?.anthropic?.baseUrl).toBe("http://127.0.0.1:44080"); expect(cfg.models?.providers?.anthropic?.request).toEqual({ allowPrivateNetwork: true }); expect(cfg.plugins?.allow).toEqual(["acpx", "memory-core", "qa-channel"]); - expect(cfg.plugins?.entries?.acpx).toEqual({ enabled: false }); + expect(cfg.plugins?.entries?.acpx).toEqual({ + enabled: true, + config: { + pluginToolsMcpBridge: true, + openClawToolsMcpBridge: true, + }, + }); expect(cfg.plugins?.entries?.["memory-core"]).toEqual({ enabled: true }); expect(cfg.plugins?.entries?.["qa-channel"]).toEqual({ enabled: true }); expect(cfg.plugins?.entries?.openai).toBeUndefined(); diff --git a/extensions/qa-lab/src/qa-gateway-config.ts b/extensions/qa-lab/src/qa-gateway-config.ts index a206071872a..f7a0bab4662 100644 --- a/extensions/qa-lab/src/qa-gateway-config.ts +++ b/extensions/qa-lab/src/qa-gateway-config.ts @@ -118,10 +118,11 @@ export function buildQaGatewayConfig(params: { allow: allowedPlugins, entries: { acpx: { - // The parity gateway stages a clean bundled-plugin tree. Keep the - // runtime backend plugin in the allowlist so this disabled entry is - // not mistaken for stale config when optional bundles are pruned. - enabled: false, + enabled: true, + config: { + pluginToolsMcpBridge: true, + openClawToolsMcpBridge: true, + }, }, "memory-core": { enabled: true, diff --git a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts index 3a02f6dc93f..9bf1b3d54d8 100644 --- a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts +++ b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts @@ -597,9 +597,13 @@ vi.mock("../thinking.js", () => ({ dropThinkingBlocks: (messages: T) => messages, })); -vi.mock("../tool-name-allowlist.js", () => ({ - collectAllowedToolNames: () => undefined, -})); +vi.mock("../tool-name-allowlist.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + collectAllowedToolNames: () => undefined, + }; +}); vi.mock("../tool-split.js", () => ({ splitSdkTools: ({ tools }: { tools: unknown[] }) => ({ @@ -660,6 +664,7 @@ export type MutableSession = { }; }; prompt: (prompt: string, options?: { images?: unknown[] }) => Promise; + setActiveToolsByName: (toolNames: string[]) => void; abort: () => Promise; dispose: () => void; steer: (text: string) => Promise; @@ -772,6 +777,7 @@ export function createDefaultEmbeddedSession(params?: { }, }, }, + setActiveToolsByName: () => {}, prompt: async (prompt, options) => { if (params?.prompt) { await params.prompt(session, prompt, options); diff --git a/src/agents/pi-embedded-runner/run/attempt.tool-allowlist.test.ts b/src/agents/pi-embedded-runner/run/attempt.tool-allowlist.test.ts new file mode 100644 index 00000000000..2aa3d43ebd5 --- /dev/null +++ b/src/agents/pi-embedded-runner/run/attempt.tool-allowlist.test.ts @@ -0,0 +1,39 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { + cleanupTempPaths, + createContextEngineAttemptRunner, + createContextEngineBootstrapAndAssemble, + getHoisted, + resetEmbeddedAttemptHarness, +} from "./attempt.spawn-workspace.test-support.js"; + +const hoisted = getHoisted(); + +describe("runEmbeddedAttempt tool allowlist", () => { + const tempPaths: string[] = []; + + beforeEach(() => { + resetEmbeddedAttemptHarness(); + }); + + afterEach(async () => { + await cleanupTempPaths(tempPaths); + }); + + it("passes OpenClaw-managed custom tool names to Pi's session allowlist", async () => { + await createContextEngineAttemptRunner({ + sessionKey: "agent:qa:repo-contract", + tempPaths, + contextEngine: createContextEngineBootstrapAndAssemble(), + }); + + const options = hoisted.createAgentSessionMock.mock.calls.at(-1)?.[0] as + | { + customTools?: Array<{ name?: string }>; + tools?: string[]; + } + | undefined; + expect(options?.customTools?.map((tool) => tool.name)).toContain("sessions_spawn"); + expect(options?.tools).toContain("sessions_spawn"); + }); +}); diff --git a/src/plugins/runtime/runtime-model-auth.runtime.test.ts b/src/plugins/runtime/runtime-model-auth.runtime.test.ts index 9ecbf68d5fa..c049857cabc 100644 --- a/src/plugins/runtime/runtime-model-auth.runtime.test.ts +++ b/src/plugins/runtime/runtime-model-auth.runtime.test.ts @@ -15,7 +15,9 @@ vi.mock("../provider-runtime.runtime.js", () => ({ prepareProviderRuntimeAuth: hoisted.prepareProviderRuntimeAuth, })); +let getApiKeyForModel: typeof import("./runtime-model-auth.runtime.js").getApiKeyForModel; let getRuntimeAuthForModel: typeof import("./runtime-model-auth.runtime.js").getRuntimeAuthForModel; +let resolveApiKeyForProvider: typeof import("./runtime-model-auth.runtime.js").resolveApiKeyForProvider; const MODEL = { id: "github-copilot/gpt-4o", @@ -26,7 +28,8 @@ const MODEL = { describe("runtime-model-auth.runtime", () => { beforeAll(async () => { - ({ getRuntimeAuthForModel } = await import("./runtime-model-auth.runtime.js")); + ({ getApiKeyForModel, getRuntimeAuthForModel, resolveApiKeyForProvider } = + await import("./runtime-model-auth.runtime.js")); }); beforeEach(() => { @@ -115,4 +118,24 @@ describe("runtime-model-auth.runtime", () => { }); expect(hoisted.prepareProviderRuntimeAuth).not.toHaveBeenCalled(); }); + + it("keeps direct model auth exports available for bundled runtime facades", async () => { + hoisted.getApiKeyForModel.mockResolvedValue({ + apiKey: "model-key", + source: "env:OPENAI_API_KEY", + mode: "api-key", + }); + hoisted.resolveApiKeyForProvider.mockResolvedValue({ + apiKey: "provider-key", + source: "env:OPENAI_API_KEY", + mode: "api-key", + }); + + await expect(getApiKeyForModel({ model: MODEL as never })).resolves.toMatchObject({ + apiKey: "model-key", + }); + await expect(resolveApiKeyForProvider({ provider: "openai" })).resolves.toMatchObject({ + apiKey: "provider-key", + }); + }); }); diff --git a/src/plugins/runtime/runtime-model-auth.runtime.ts b/src/plugins/runtime/runtime-model-auth.runtime.ts index 246b5ed6929..74f740f2365 100644 --- a/src/plugins/runtime/runtime-model-auth.runtime.ts +++ b/src/plugins/runtime/runtime-model-auth.runtime.ts @@ -1,10 +1,23 @@ import type { Api, Model } from "@mariozechner/pi-ai"; -import { getApiKeyForModel, resolveApiKeyForProvider } from "../../agents/model-auth.js"; +import { + getApiKeyForModel as resolveModelApiKey, + resolveApiKeyForProvider as resolveProviderApiKey, +} from "../../agents/model-auth.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { prepareProviderRuntimeAuth } from "../provider-runtime.runtime.js"; import type { ResolvedProviderRuntimeAuth } from "./model-auth-types.js"; -export { getApiKeyForModel, resolveApiKeyForProvider }; +export async function getApiKeyForModel( + params: Parameters[0], +): Promise>> { + return resolveModelApiKey(params); +} + +export async function resolveApiKeyForProvider( + params: Parameters[0], +): Promise>> { + return resolveProviderApiKey(params); +} /** * Resolve request-ready auth for a runtime model, applying any provider-owned @@ -15,7 +28,7 @@ export async function getRuntimeAuthForModel(params: { cfg?: OpenClawConfig; workspaceDir?: string; }): Promise { - const resolvedAuth = await getApiKeyForModel({ + const resolvedAuth = await resolveModelApiKey({ model: params.model, cfg: params.cfg, });