From 484765582656a86eb6fa7700f5998f32a996c3be Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Sun, 9 Aug 2026 15:34:30 +0800 Subject: [PATCH] fix(cua-computer): ignore retired driver path (#120502) --- .../cua-computer/doctor-contract-api.test.ts | 45 ------------------- .../cua-computer/doctor-contract-api.ts | 42 ----------------- extensions/cua-computer/index.test.ts | 38 ++++++++++++++++ extensions/cua-computer/index.ts | 6 ++- extensions/cua-computer/openclaw.plugin.json | 9 ++-- src/plugins/bundled-plugin-metadata.test.ts | 7 --- 6 files changed, 48 insertions(+), 99 deletions(-) delete mode 100644 extensions/cua-computer/doctor-contract-api.test.ts delete mode 100644 extensions/cua-computer/doctor-contract-api.ts diff --git a/extensions/cua-computer/doctor-contract-api.test.ts b/extensions/cua-computer/doctor-contract-api.test.ts deleted file mode 100644 index 26c6cab4cba6..000000000000 --- a/extensions/cua-computer/doctor-contract-api.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -// CUA tests cover the doctor-only migration for the retired daemon path. -import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; -import { describe, expect, it } from "vitest"; -import { legacyConfigRules, normalizeCompatibilityConfig } from "./doctor-contract-api.js"; - -describe("cua-computer doctor contract", () => { - it("flags the retired driver path for openclaw doctor --fix", () => { - expect(legacyConfigRules).toEqual([ - expect.objectContaining({ - path: ["plugins", "entries", "cua-computer", "config", "driverPath"], - message: expect.stringContaining("openclaw doctor --fix"), - }), - ]); - }); - - it("removes the shipped daemon path before strict plugin validation", () => { - const config = { - plugins: { - entries: { - "cua-computer": { - enabled: true, - config: { driverPath: "/usr/local/bin/cua-driver" }, - }, - }, - }, - } as OpenClawConfig; - - const result = normalizeCompatibilityConfig({ cfg: config }); - - expect(result.changes).toEqual([ - "Removed retired plugins.entries.cua-computer.config.driverPath; CUA Driver SDK is configured directly by OpenClaw.", - ]); - expect(result.config.plugins?.entries?.["cua-computer"]).toEqual({ - enabled: true, - config: {}, - }); - expect(config.plugins?.entries?.["cua-computer"]?.config).toEqual({ - driverPath: "/usr/local/bin/cua-driver", - }); - expect(normalizeCompatibilityConfig({ cfg: result.config })).toEqual({ - config: result.config, - changes: [], - }); - }); -}); diff --git a/extensions/cua-computer/doctor-contract-api.ts b/extensions/cua-computer/doctor-contract-api.ts deleted file mode 100644 index b32bd6600b4b..000000000000 --- a/extensions/cua-computer/doctor-contract-api.ts +++ /dev/null @@ -1,42 +0,0 @@ -// CUA config migrations belong to the plugin so doctor can repair a shipped -// driverPath setting before strict manifest validation reaches this plugin. -import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; -import { asObjectRecord } from "openclaw/plugin-sdk/runtime-doctor-migrations"; - -const DRIVER_PATH = ["plugins", "entries", "cua-computer", "config", "driverPath"]; - -/** Retired CUA daemon configuration that `openclaw doctor --fix` removes. */ -export const legacyConfigRules = [ - { - path: DRIVER_PATH, - message: - 'plugins.entries.cua-computer.config.driverPath is retired; the CUA Driver SDK is configured directly by OpenClaw. Run "openclaw doctor --fix".', - }, -]; - -/** Removes the retired daemon path without making it a runtime compatibility key. */ -export function normalizeCompatibilityConfig({ cfg }: { cfg: OpenClawConfig }): { - config: OpenClawConfig; - changes: string[]; -} { - const entry = asObjectRecord(cfg.plugins?.entries?.["cua-computer"]); - const pluginConfig = asObjectRecord(entry?.config); - if (!pluginConfig || !Object.hasOwn(pluginConfig, "driverPath")) { - return { config: cfg, changes: [] }; - } - - const nextConfig = structuredClone(cfg); - const nextEntry = asObjectRecord(nextConfig.plugins?.entries?.["cua-computer"]); - const nextPluginConfig = asObjectRecord(nextEntry?.config); - if (!nextPluginConfig) { - return { config: cfg, changes: [] }; - } - delete nextPluginConfig.driverPath; - - return { - config: nextConfig, - changes: [ - "Removed retired plugins.entries.cua-computer.config.driverPath; CUA Driver SDK is configured directly by OpenClaw.", - ], - }; -} diff --git a/extensions/cua-computer/index.test.ts b/extensions/cua-computer/index.test.ts index a3a986776096..c9ca25d44484 100644 --- a/extensions/cua-computer/index.test.ts +++ b/extensions/cua-computer/index.test.ts @@ -1,3 +1,8 @@ +import fs from "node:fs"; +import { + validateJsonSchemaValue, + type JsonSchemaObject, +} from "openclaw/plugin-sdk/json-schema-runtime"; import type { OpenClawPluginApi, OpenClawPluginNodeHostCommand, @@ -7,6 +12,17 @@ import type { import { describe, expect, it, vi } from "vitest"; import plugin from "./index.js"; +function validateManifestConfig(value: unknown) { + const manifest = JSON.parse( + fs.readFileSync(new URL("./openclaw.plugin.json", import.meta.url), "utf8"), + ) as { configSchema: JsonSchemaObject }; + return validateJsonSchemaValue({ + cacheKey: "cua-computer.manifest.config.test", + schema: manifest.configSchema, + value, + }); +} + describe("cua-computer plugin registration", () => { it("registers the screen and dangerous computer node-host commands", () => { const commands: OpenClawPluginNodeHostCommand[] = []; @@ -26,6 +42,28 @@ describe("cua-computer plugin registration", () => { expect(policies[0]?.defaultPlatforms).toBeUndefined(); }); + it("accepts the retired driver path as a no-op while keeping both schemas strict", () => { + const config = { driverPath: "/usr/local/bin/cua-driver" }; + const runtimeResult = plugin.configSchema.safeParse?.(config); + + expect(runtimeResult).toEqual({ success: true, data: config }); + expect(validateManifestConfig(config).ok).toBe(true); + expect(plugin.configSchema.safeParse?.({ unexpected: true }).success).toBe(false); + expect(validateManifestConfig({ unexpected: true }).ok).toBe(false); + + const commands: OpenClawPluginNodeHostCommand[] = []; + plugin.register({ + pluginConfig: config, + registerNodeHostCommand: (command: OpenClawPluginNodeHostCommand) => commands.push(command), + registerNodeInvokePolicy: () => {}, + } as unknown as OpenClawPluginApi); + + expect(commands.map(({ command, cap, dangerous }) => ({ command, cap, dangerous }))).toEqual([ + { command: "screen.snapshot", cap: "screen", dangerous: false }, + { command: "computer.act", cap: "computer", dangerous: true }, + ]); + }); + it("forwards an explicitly armed computer action and preserves node refusals", async () => { const policies: OpenClawPluginNodeInvokePolicy[] = []; plugin.register({ diff --git a/extensions/cua-computer/index.ts b/extensions/cua-computer/index.ts index 8ff73b5e2a5d..606d06c9499a 100644 --- a/extensions/cua-computer/index.ts +++ b/extensions/cua-computer/index.ts @@ -2,7 +2,11 @@ import { buildPluginConfigSchema, definePluginEntry } from "openclaw/plugin-sdk/ import { z } from "zod"; import { createCuaComputerCommands } from "./src/commands.js"; -const CuaComputerConfigSchema = z.strictObject({}); +const CuaComputerConfigSchema = z.strictObject({ + // Keep the shipped daemon setting as a named no-op: strict validation accepts + // existing config, but direct SDK commands never receive a binary path. + driverPath: z.string().optional(), +}); const configSchema = buildPluginConfigSchema(CuaComputerConfigSchema, { uiHints: {}, diff --git a/extensions/cua-computer/openclaw.plugin.json b/extensions/cua-computer/openclaw.plugin.json index ebd553f821f8..06deab12d316 100644 --- a/extensions/cua-computer/openclaw.plugin.json +++ b/extensions/cua-computer/openclaw.plugin.json @@ -1,8 +1,5 @@ { "id": "cua-computer", - "doctorContract": { - "configRepair": true - }, "activation": { "onStartup": true }, @@ -12,6 +9,10 @@ "configSchema": { "type": "object", "additionalProperties": false, - "properties": {} + "properties": { + "driverPath": { + "type": "string" + } + } } } diff --git a/src/plugins/bundled-plugin-metadata.test.ts b/src/plugins/bundled-plugin-metadata.test.ts index 630df9d79ed7..ba9a90aaa76b 100644 --- a/src/plugins/bundled-plugin-metadata.test.ts +++ b/src/plugins/bundled-plugin-metadata.test.ts @@ -430,13 +430,6 @@ describe("bundled plugin metadata", () => { }); }); - it("keeps CUA's doctor contract sidecar on the bundled public surface", () => { - const cua = listRepoBundledPluginMetadata().find((entry) => entry.dirName === "cua-computer"); - expectArtifactPresence(cua?.publicSurfaceArtifacts, { - contains: ["doctor-contract-api.js"], - }); - }); - it("keeps iMessage message-tool discovery on a narrow public surface", () => { const imessage = listRepoBundledPluginMetadata().find((entry) => entry.dirName === "imessage"); expectArtifactPresence(imessage?.publicSurfaceArtifacts, {