fix(cua-computer): ignore retired driver path (#120502)

This commit is contained in:
Dallin Romney
2026-08-09 15:34:30 +08:00
committed by GitHub
parent 266ad72be9
commit 4847655826
6 changed files with 48 additions and 99 deletions
@@ -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: [],
});
});
});
@@ -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.",
],
};
}
+38
View File
@@ -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({
+5 -1
View File
@@ -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: {},
+5 -4
View File
@@ -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"
}
}
}
}
@@ -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, {