mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:30:45 +00:00
fix(codex): harden computer use setup states
This commit is contained in:
@@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- Codex: add Computer Use setup for Codex-mode agents, including `/codex computer-use status/install`, marketplace discovery, optional auto-install, and fail-closed MCP server checks before Codex-mode turns start. Fixes #72094. (#71842) Thanks @pash-openai.
|
||||
- Matrix/streaming: stream tool-progress updates into live Matrix preview edits by default when preview streaming is active, with `streaming.preview.toolProgress: false` to keep answer previews while hiding interim tool lines. Thanks @gumadeiras.
|
||||
- Plugins/models: wire manifest `modelCatalog.aliases` and `modelCatalog.suppressions` into model-catalog planning and built-in model suppression, with OpenAI stale Spark suppression now declared in the plugin manifest before runtime fallback. Thanks @shakkernerd.
|
||||
- Channels/Yuanbao: register the Tencent Yuanbao external channel plugin (`openclaw-plugin-yuanbao`) in the official channel catalog, contract suites, and community plugin docs, with a new `docs/channels/yuanbao.md` quick-start guide for WebSocket bot DMs and group chats. (#72756) Thanks @loongfay.
|
||||
|
||||
@@ -52,6 +52,27 @@ describe("Codex Computer Use setup", () => {
|
||||
expect(request).not.toHaveBeenCalledWith("plugin/install", expect.anything());
|
||||
});
|
||||
|
||||
it("reports an installed but disabled Computer Use plugin separately", async () => {
|
||||
const request = createComputerUseRequest({ installed: true, enabled: false });
|
||||
|
||||
await expect(
|
||||
readCodexComputerUseStatus({
|
||||
pluginConfig: { computerUse: { enabled: true, marketplaceName: "desktop-tools" } },
|
||||
request,
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
ready: false,
|
||||
installed: true,
|
||||
pluginEnabled: false,
|
||||
mcpServerAvailable: false,
|
||||
message:
|
||||
"Computer Use is installed, but the computer-use plugin is disabled. Run /codex computer-use install or enable computerUse.autoInstall to re-enable it.",
|
||||
}),
|
||||
);
|
||||
expect(request).not.toHaveBeenCalledWith("plugin/install", expect.anything());
|
||||
});
|
||||
|
||||
it("does not register marketplace sources during status checks", async () => {
|
||||
const request = createComputerUseRequest({ installed: true });
|
||||
|
||||
@@ -129,6 +150,28 @@ describe("Codex Computer Use setup", () => {
|
||||
expect(request).toHaveBeenCalledWith("config/mcpServer/reload", undefined);
|
||||
});
|
||||
|
||||
it("re-enables an installed but disabled Computer Use plugin during install", async () => {
|
||||
const request = createComputerUseRequest({ installed: true, enabled: false });
|
||||
|
||||
await expect(
|
||||
installCodexComputerUse({
|
||||
pluginConfig: { computerUse: { marketplaceName: "desktop-tools" } },
|
||||
request,
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
ready: true,
|
||||
installed: true,
|
||||
pluginEnabled: true,
|
||||
message: "Computer Use is ready.",
|
||||
}),
|
||||
);
|
||||
expect(request).toHaveBeenCalledWith("plugin/install", {
|
||||
marketplacePath: "/marketplaces/desktop-tools/.agents/plugins/marketplace.json",
|
||||
pluginName: "computer-use",
|
||||
});
|
||||
});
|
||||
|
||||
it("fails closed when Computer Use is required but not installed", async () => {
|
||||
const request = createComputerUseRequest({ installed: false });
|
||||
|
||||
@@ -240,6 +283,27 @@ describe("Codex Computer Use setup", () => {
|
||||
expect(request).not.toHaveBeenCalledWith("plugin/read", expect.anything());
|
||||
});
|
||||
|
||||
it("fails closed instead of installing from a remote-only Codex marketplace", async () => {
|
||||
const request = createRemoteOnlyComputerUseRequest();
|
||||
|
||||
await expect(
|
||||
installCodexComputerUse({
|
||||
pluginConfig: { computerUse: { marketplaceName: "openai-curated" } },
|
||||
request,
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
status: expect.objectContaining({
|
||||
ready: false,
|
||||
installed: false,
|
||||
pluginEnabled: false,
|
||||
marketplaceName: "openai-curated",
|
||||
message:
|
||||
"Computer Use is available in remote Codex marketplace openai-curated, but Codex app-server does not support remote plugin install yet. Configure computerUse.marketplaceSource or computerUse.marketplacePath for a local marketplace, then run /codex computer-use install.",
|
||||
}),
|
||||
});
|
||||
expect(request).not.toHaveBeenCalledWith("plugin/install", expect.anything());
|
||||
});
|
||||
|
||||
it("waits for the default Codex marketplace during install", async () => {
|
||||
vi.useFakeTimers();
|
||||
const request = createComputerUseRequest({
|
||||
@@ -291,9 +355,11 @@ describe("Codex Computer Use setup", () => {
|
||||
|
||||
function createComputerUseRequest(params: {
|
||||
installed: boolean;
|
||||
enabled?: boolean;
|
||||
marketplaceAvailableAfterListCalls?: number;
|
||||
}): CodexComputerUseRequest {
|
||||
let installed = params.installed;
|
||||
let enabled = params.enabled ?? installed;
|
||||
let pluginListCalls = 0;
|
||||
return vi.fn(async (method: string, requestParams?: unknown) => {
|
||||
if (method === "experimentalFeature/enablement/set") {
|
||||
@@ -317,7 +383,7 @@ function createComputerUseRequest(params: {
|
||||
name: "desktop-tools",
|
||||
path: "/marketplaces/desktop-tools/.agents/plugins/marketplace.json",
|
||||
interface: null,
|
||||
plugins: [pluginSummary(installed)],
|
||||
plugins: [pluginSummary(installed, "desktop-tools", enabled)],
|
||||
},
|
||||
]
|
||||
: [],
|
||||
@@ -335,7 +401,7 @@ function createComputerUseRequest(params: {
|
||||
plugin: {
|
||||
marketplaceName: "desktop-tools",
|
||||
marketplacePath: "/marketplaces/desktop-tools/.agents/plugins/marketplace.json",
|
||||
summary: pluginSummary(installed),
|
||||
summary: pluginSummary(installed, "desktop-tools", enabled),
|
||||
description: "Control desktop apps.",
|
||||
skills: [],
|
||||
apps: [],
|
||||
@@ -345,6 +411,7 @@ function createComputerUseRequest(params: {
|
||||
}
|
||||
if (method === "plugin/install") {
|
||||
installed = true;
|
||||
enabled = true;
|
||||
return { authPolicy: "ON_INSTALL", appsNeedingAuth: [] };
|
||||
}
|
||||
if (method === "config/mcpServer/reload") {
|
||||
@@ -352,22 +419,23 @@ function createComputerUseRequest(params: {
|
||||
}
|
||||
if (method === "mcpServerStatus/list") {
|
||||
return {
|
||||
data: installed
|
||||
? [
|
||||
{
|
||||
name: "computer-use",
|
||||
tools: {
|
||||
list_apps: {
|
||||
name: "list_apps",
|
||||
inputSchema: { type: "object" },
|
||||
data:
|
||||
installed && enabled
|
||||
? [
|
||||
{
|
||||
name: "computer-use",
|
||||
tools: {
|
||||
list_apps: {
|
||||
name: "list_apps",
|
||||
inputSchema: { type: "object" },
|
||||
},
|
||||
},
|
||||
resources: [],
|
||||
resourceTemplates: [],
|
||||
authStatus: "unsupported",
|
||||
},
|
||||
resources: [],
|
||||
resourceTemplates: [],
|
||||
authStatus: "unsupported",
|
||||
},
|
||||
]
|
||||
: [],
|
||||
]
|
||||
: [],
|
||||
nextCursor: null,
|
||||
};
|
||||
}
|
||||
@@ -375,6 +443,46 @@ function createComputerUseRequest(params: {
|
||||
}) as CodexComputerUseRequest;
|
||||
}
|
||||
|
||||
function createRemoteOnlyComputerUseRequest(): CodexComputerUseRequest {
|
||||
return vi.fn(async (method: string, requestParams?: unknown) => {
|
||||
if (method === "experimentalFeature/enablement/set") {
|
||||
return { enablement: { plugins: true } };
|
||||
}
|
||||
if (method === "plugin/list") {
|
||||
return {
|
||||
marketplaces: [
|
||||
{
|
||||
name: "openai-curated",
|
||||
path: null,
|
||||
interface: null,
|
||||
plugins: [pluginSummary(false, "openai-curated", false, "remote")],
|
||||
},
|
||||
],
|
||||
marketplaceLoadErrors: [],
|
||||
featuredPluginIds: [],
|
||||
};
|
||||
}
|
||||
if (method === "plugin/read") {
|
||||
expect(requestParams).toEqual({
|
||||
remoteMarketplaceName: "openai-curated",
|
||||
pluginName: "computer-use",
|
||||
});
|
||||
return {
|
||||
plugin: {
|
||||
marketplaceName: "openai-curated",
|
||||
marketplacePath: null,
|
||||
summary: pluginSummary(false, "openai-curated", false, "remote"),
|
||||
description: "Control desktop apps.",
|
||||
skills: [],
|
||||
apps: [],
|
||||
mcpServers: ["computer-use"],
|
||||
},
|
||||
};
|
||||
}
|
||||
throw new Error(`unexpected request ${method}`);
|
||||
}) as CodexComputerUseRequest;
|
||||
}
|
||||
|
||||
function createAmbiguousComputerUseRequest(): CodexComputerUseRequest {
|
||||
return vi.fn(async (method: string) => {
|
||||
if (method === "plugin/list") {
|
||||
@@ -488,13 +596,21 @@ function marketplaceEntry(marketplaceName: string, installed: boolean) {
|
||||
};
|
||||
}
|
||||
|
||||
function pluginSummary(installed: boolean, marketplaceName = "desktop-tools") {
|
||||
function pluginSummary(
|
||||
installed: boolean,
|
||||
marketplaceName = "desktop-tools",
|
||||
enabled = installed,
|
||||
source: "local" | "remote" = "local",
|
||||
) {
|
||||
return {
|
||||
id: `computer-use@${marketplaceName}`,
|
||||
name: "computer-use",
|
||||
source: { type: "local", path: `/marketplaces/${marketplaceName}/plugins/computer-use` },
|
||||
source:
|
||||
source === "local"
|
||||
? { type: "local", path: `/marketplaces/${marketplaceName}/plugins/computer-use` }
|
||||
: { type: "remote" },
|
||||
installed,
|
||||
enabled: installed,
|
||||
enabled,
|
||||
installPolicy: "AVAILABLE",
|
||||
authPolicy: "ON_INSTALL",
|
||||
interface: null,
|
||||
|
||||
@@ -180,7 +180,15 @@ async function inspectCodexComputerUse(params: {
|
||||
config: params.config,
|
||||
plugin,
|
||||
tools: [],
|
||||
message: `Computer Use is available but not installed. Run /codex computer-use install or enable computerUse.autoInstall.`,
|
||||
message: pluginSetupMessage(params.config, plugin, marketplace.marketplace),
|
||||
});
|
||||
}
|
||||
if (!marketplace.marketplace.path) {
|
||||
return statusFromPlugin({
|
||||
config: params.config,
|
||||
plugin,
|
||||
tools: [],
|
||||
message: remoteInstallUnsupportedMessage(plugin, marketplace.marketplace),
|
||||
});
|
||||
}
|
||||
await request<v2.PluginInstallResponse>(
|
||||
@@ -197,6 +205,14 @@ async function inspectCodexComputerUse(params: {
|
||||
params.config.pluginName,
|
||||
);
|
||||
}
|
||||
if (!plugin.summary.installed || !plugin.summary.enabled) {
|
||||
return statusFromPlugin({
|
||||
config: params.config,
|
||||
plugin,
|
||||
tools: [],
|
||||
message: pluginSetupMessage(params.config, plugin, marketplace.marketplace),
|
||||
});
|
||||
}
|
||||
|
||||
let server = await readMcpServerStatus(request, params.config.mcpServerName);
|
||||
if (!server && params.installPlugin) {
|
||||
@@ -418,6 +434,29 @@ function pluginRequestParams(marketplace: MarketplaceRef, pluginName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
function pluginSetupMessage(
|
||||
config: ResolvedCodexComputerUseConfig,
|
||||
plugin: v2.PluginDetail,
|
||||
marketplace: MarketplaceRef,
|
||||
): string {
|
||||
if (!marketplace.path) {
|
||||
return remoteInstallUnsupportedMessage(plugin, marketplace);
|
||||
}
|
||||
if (!plugin.summary.installed) {
|
||||
return "Computer Use is available but not installed. Run /codex computer-use install or enable computerUse.autoInstall.";
|
||||
}
|
||||
return `Computer Use is installed, but the ${config.pluginName} plugin is disabled. Run /codex computer-use install or enable computerUse.autoInstall to re-enable it.`;
|
||||
}
|
||||
|
||||
function remoteInstallUnsupportedMessage(
|
||||
plugin: v2.PluginDetail,
|
||||
marketplace: MarketplaceRef,
|
||||
): string {
|
||||
const marketplaceName = marketplace.name ?? plugin.marketplaceName;
|
||||
const state = plugin.summary.installed ? "installed but disabled" : "available";
|
||||
return `Computer Use is ${state} in remote Codex marketplace ${marketplaceName}, but Codex app-server does not support remote plugin install yet. Configure computerUse.marketplaceSource or computerUse.marketplacePath for a local marketplace, then run /codex computer-use install.`;
|
||||
}
|
||||
|
||||
function statusFromPlugin(params: {
|
||||
config: ResolvedCodexComputerUseConfig;
|
||||
plugin: v2.PluginDetail;
|
||||
|
||||
Reference in New Issue
Block a user