mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(cua-computer): ignore retired driver path (#120502)
This commit is contained in:
@@ -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.",
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -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({
|
||||
|
||||
@@ -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: {},
|
||||
|
||||
@@ -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, {
|
||||
|
||||
Reference in New Issue
Block a user