diff --git a/CHANGELOG.md b/CHANGELOG.md index bc1bcb6fd19d..76a785d91619 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ Docs: https://docs.openclaw.ai - Security/config parsing: reject unsafe OAuth/token lifetimes, retry-after delays, inbound timestamps, response body sizes, command timeout config, sandbox observer token TTLs, and gateway WebSocket calls after close. - Providers/media: cap local service, model, usage, queue, generated media, TTS, music, workflow polling, and provider OAuth request timers across hosted and local providers. - Release/CI/E2E: bound release candidate reads, beta smoke REST calls, changelog restore, kitchen-sink and bundled plugin readiness probes, secret-provider probes, Vitest routing, and mainline test flakes. (#88127, #88137, #88155, #88160) +- Release/CI/E2E: keep Kitchen Sink live plugin MCP probes resolving source-checkout workspace packages and align the live gauntlet with current Kitchen Sink diagnostics. - Release/CI/E2E: run the secret-provider integration proof through the repo pnpm runner so native macOS and Windows validation use the hydrated package-manager shim. - Release/CI/E2E: run the Telegram desktop proof gateway through the repo pnpm runner so native macOS proof uses the hydrated package-manager shim. - Docs/CI: run Mintlify anchor checks through the repo pnpm runner so docs link validation works when pnpm is only available through the hydrated package-manager shim. diff --git a/extensions/qa-lab/src/scenario-catalog.test.ts b/extensions/qa-lab/src/scenario-catalog.test.ts index 4208f2dc8c33..8388a1d4528e 100644 --- a/extensions/qa-lab/src/scenario-catalog.test.ts +++ b/extensions/qa-lab/src/scenario-catalog.test.ts @@ -405,6 +405,15 @@ describe("qa scenario catalog", () => { expect(config?.expectedAdversarialDiagnostics).toContain( "control UI descriptor registration requires id, surface, label, and valid optional fields", ); + expect(config?.expectedAdversarialDiagnostics).toContain( + "hosted media resolver registration missing resolver", + ); + expect(config?.expectedAdversarialDiagnostics).toContain( + "plugin must declare contracts.embeddingProviders for adapter: kitchen-sink-embedding-provider", + ); + expect(config?.expectedAdversarialDiagnostics).toContain( + "model catalog provider registration missing provider", + ); expect( config?.expectedAdversarialDiagnostics?.every((entry) => typeof entry === "string"), ).toBe(true); diff --git a/extensions/qa-lab/src/suite-runtime-agent-tools.test.ts b/extensions/qa-lab/src/suite-runtime-agent-tools.test.ts index 57c2d7056027..a9cfc0ef1b4a 100644 --- a/extensions/qa-lab/src/suite-runtime-agent-tools.test.ts +++ b/extensions/qa-lab/src/suite-runtime-agent-tools.test.ts @@ -143,7 +143,7 @@ describe("qa suite runtime agent tools helpers", () => { path.join(repoRoot, "src", "mcp", "plugin-tools-serve.ts"), ], stderr: "pipe", - cwd: gatewayTempRoot, + cwd: repoRoot, env: { PATH: "/usr/bin", OPENCLAW_KEY: "1", @@ -225,4 +225,37 @@ describe("qa suite runtime agent tools helpers", () => { expect(message).not.toContain("old stderr"); expect(closeMock).toHaveBeenCalled(); }); + + it("keeps plugin-tools MCP stderr on startup failures", async () => { + connectMock.mockImplementationOnce(async () => { + const stderrListener = stderrOnMock.mock.calls[0]?.[1] as + | ((chunk: unknown) => void) + | undefined; + stderrListener?.( + Buffer.from("Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@openclaw/example'\n"), + ); + throw new Error("MCP error -32000: Connection closed"); + }); + + const error = await callPluginToolsMcp({ + env: { + gateway: { + tempRoot: gatewayTempRoot, + runtimeEnv: { + PATH: "/usr/bin", + }, + }, + repoRoot, + } as never, + toolName: "plugin.echo", + args: { text: "hello" }, + }).catch((value: unknown) => value); + + expect(error).toBeInstanceOf(Error); + const message = error instanceof Error ? error.message : String(error); + expect(message).toContain("MCP error -32000: Connection closed"); + expect(message).toContain("MCP stderr tail:"); + expect(message).toContain("Cannot find package '@openclaw/example'"); + expect(closeMock).toHaveBeenCalled(); + }); }); diff --git a/extensions/qa-lab/src/suite-runtime-agent-tools.ts b/extensions/qa-lab/src/suite-runtime-agent-tools.ts index 873d0b86319a..265da46632d9 100644 --- a/extensions/qa-lab/src/suite-runtime-agent-tools.ts +++ b/extensions/qa-lab/src/suite-runtime-agent-tools.ts @@ -56,7 +56,7 @@ async function callPluginToolsMcp(params: { path.join(params.env.repoRoot, "src/mcp/plugin-tools-serve.ts"), ], stderr: "pipe", - cwd: params.env.gateway.tempRoot, + cwd: params.env.repoRoot, env: transportEnv, }); const stderrTail = createQaChildOutputTail(MCP_STDERR_TAIL_LIMIT); @@ -80,22 +80,20 @@ async function callPluginToolsMcp(params: { `MCP tool missing: ${params.toolName}; available tools: ${availableTools.join(", ") || ""}`, ); } - try { - return await client.callTool( - { - name: params.toolName, - arguments: params.args, - }, - undefined, - { timeout: MCP_REQUEST_TIMEOUT_MS }, - ); - } catch (error) { - const tail = formatQaChildOutputTail(stderrTail, "MCP stderr").trim(); - if (!tail || !(error instanceof Error)) { - throw error; - } - throw new Error(`${error.message}\nMCP stderr tail:\n${tail}`, { cause: error }); + return await client.callTool( + { + name: params.toolName, + arguments: params.args, + }, + undefined, + { timeout: MCP_REQUEST_TIMEOUT_MS }, + ); + } catch (error) { + const tail = formatQaChildOutputTail(stderrTail, "MCP stderr").trim(); + if (!tail || !(error instanceof Error)) { + throw error; } + throw new Error(`${error.message}\nMCP stderr tail:\n${tail}`, { cause: error }); } finally { await client.close().catch(() => {}); } diff --git a/qa/scenarios/plugins/kitchen-sink-live-openai.md b/qa/scenarios/plugins/kitchen-sink-live-openai.md index 01de4d979141..6f28cbb4d7c9 100644 --- a/qa/scenarios/plugins/kitchen-sink-live-openai.md +++ b/qa/scenarios/plugins/kitchen-sink-live-openai.md @@ -95,9 +95,12 @@ execution: - compaction provider "kitchen-sink-compaction-provider" registration missing summarize - context engine registration missing id - control UI descriptor registration requires id, surface, label, and valid optional fields + - hosted media resolver registration missing resolver - "http route registration missing or invalid auth: /kitchen-sink/http-route" + - "plugin must declare contracts.embeddingProviders for adapter: kitchen-sink-embedding-provider" - "plugin must own memory slot or declare contracts.memoryEmbeddingProviders for adapter: kitchen-sink-memory-embedding-provider" - memory prompt supplement registration missing builder + - model catalog provider registration missing provider - node invoke policy registration missing commands - session extension registration requires namespace and description - session scheduler job registration requires unique id, sessionKey, and kind