mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:40:44 +00:00
fix(plugins): trust official Codex package commands
This commit is contained in:
@@ -63,6 +63,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Plugins/commands: allow the official ClawHub Codex plugin package to keep reserved `/codex` command ownership, matching the existing npm-managed Codex package behavior. Thanks @vincentkoc.
|
||||
- Plugins/commands: scope QQBot framework slash commands to the QQBot channel so `/bot-*` command handlers and native specs do not leak onto unrelated chat surfaces. Thanks @vincentkoc.
|
||||
- fix: harden backend message action gateway routing [AI]. (#76374) Thanks @pgondhi987.
|
||||
- Gate QQBot streaming command auth [AI]. (#76375) Thanks @pgondhi987.
|
||||
|
||||
@@ -161,6 +161,110 @@ describe("host-hook fixture plugin contract", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("allows the official ClawHub Codex plugin to keep /codex command ownership", () => {
|
||||
const { config, registry } = createPluginRegistryFixture();
|
||||
const codexRoot = path.join("/tmp", ".openclaw", "extensions", "codex");
|
||||
registerTestPlugin({
|
||||
registry,
|
||||
config,
|
||||
record: createPluginRecord({
|
||||
id: "codex",
|
||||
name: "Codex",
|
||||
packageName: "@openclaw/codex",
|
||||
origin: "global",
|
||||
rootDir: codexRoot,
|
||||
source: path.join(codexRoot, "dist", "index.js"),
|
||||
}),
|
||||
register(api) {
|
||||
api.registerCommand({
|
||||
name: "codex",
|
||||
description: "Official ClawHub Codex command",
|
||||
ownership: "reserved",
|
||||
handler: async () => ({ text: "ok" }),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
expect(registry.registry.commands.map((entry) => entry.command.name)).toEqual(["codex"]);
|
||||
expect(registry.registry.diagnostics).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
pluginId: "codex",
|
||||
message: expect.stringContaining("only bundled plugins can claim reserved command"),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects non-official global Codex plugins from /codex command ownership", () => {
|
||||
const { config, registry } = createPluginRegistryFixture();
|
||||
const codexRoot = path.join("/tmp", ".openclaw", "extensions", "codex");
|
||||
registerTestPlugin({
|
||||
registry,
|
||||
config,
|
||||
record: createPluginRecord({
|
||||
id: "codex",
|
||||
name: "Codex",
|
||||
origin: "global",
|
||||
rootDir: codexRoot,
|
||||
source: path.join(codexRoot, "dist", "index.js"),
|
||||
}),
|
||||
register(api) {
|
||||
api.registerCommand({
|
||||
name: "codex",
|
||||
description: "Impostor Codex command",
|
||||
ownership: "reserved",
|
||||
handler: async () => ({ text: "no" }),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
expect(registry.registry.commands).toHaveLength(0);
|
||||
expect(registry.registry.diagnostics).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
pluginId: "codex",
|
||||
message: expect.stringContaining("only bundled plugins can claim reserved command"),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects workspace Codex plugins that spoof the official package name", () => {
|
||||
const { config, registry } = createPluginRegistryFixture();
|
||||
const codexRoot = path.join("/tmp", "workspace", "codex");
|
||||
registerTestPlugin({
|
||||
registry,
|
||||
config,
|
||||
record: createPluginRecord({
|
||||
id: "codex",
|
||||
name: "Codex",
|
||||
packageName: "@openclaw/codex",
|
||||
origin: "workspace",
|
||||
rootDir: codexRoot,
|
||||
source: path.join(codexRoot, "dist", "index.js"),
|
||||
}),
|
||||
register(api) {
|
||||
api.registerCommand({
|
||||
name: "codex",
|
||||
description: "Workspace Codex command",
|
||||
ownership: "reserved",
|
||||
handler: async () => ({ text: "no" }),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
expect(registry.registry.commands).toHaveLength(0);
|
||||
expect(registry.registry.diagnostics).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
pluginId: "codex",
|
||||
message: expect.stringContaining("only bundled plugins can claim reserved command"),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects reserved command ownership for non-reserved bundled command names", () => {
|
||||
const { config, registry } = createPluginRegistryFixture();
|
||||
registerTestPlugin({
|
||||
|
||||
@@ -1731,6 +1731,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
name: manifestRecord.name ?? pluginId,
|
||||
description: manifestRecord.description,
|
||||
version: manifestRecord.version,
|
||||
packageName: manifestRecord.packageName,
|
||||
format: manifestRecord.format,
|
||||
bundleFormat: manifestRecord.bundleFormat,
|
||||
bundleCapabilities: manifestRecord.bundleCapabilities,
|
||||
@@ -1768,6 +1769,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
name: manifestRecord.name ?? pluginId,
|
||||
description: manifestRecord.description,
|
||||
version: manifestRecord.version,
|
||||
packageName: manifestRecord.packageName,
|
||||
format: manifestRecord.format,
|
||||
bundleFormat: manifestRecord.bundleFormat,
|
||||
bundleCapabilities: manifestRecord.bundleCapabilities,
|
||||
@@ -2553,6 +2555,7 @@ export async function loadOpenClawPluginCliRegistry(
|
||||
name: manifestRecord.name ?? pluginId,
|
||||
description: manifestRecord.description,
|
||||
version: manifestRecord.version,
|
||||
packageName: manifestRecord.packageName,
|
||||
format: manifestRecord.format,
|
||||
bundleFormat: manifestRecord.bundleFormat,
|
||||
bundleCapabilities: manifestRecord.bundleCapabilities,
|
||||
@@ -2590,6 +2593,7 @@ export async function loadOpenClawPluginCliRegistry(
|
||||
name: manifestRecord.name ?? pluginId,
|
||||
description: manifestRecord.description,
|
||||
version: manifestRecord.version,
|
||||
packageName: manifestRecord.packageName,
|
||||
format: manifestRecord.format,
|
||||
bundleFormat: manifestRecord.bundleFormat,
|
||||
bundleCapabilities: manifestRecord.bundleCapabilities,
|
||||
|
||||
@@ -327,6 +327,7 @@ export type PluginRecord = {
|
||||
id: string;
|
||||
name: string;
|
||||
version?: string;
|
||||
packageName?: string;
|
||||
description?: string;
|
||||
format?: PluginFormat;
|
||||
bundleFormat?: PluginBundleFormat;
|
||||
|
||||
@@ -271,10 +271,18 @@ export function resolvePluginPath(input: string, rootDir: string | undefined): s
|
||||
return rootDir ? path.resolve(rootDir, trimmed) : resolveUserPath(input);
|
||||
}
|
||||
|
||||
function isOfficialNpmCodexPluginRecord(record: Pick<PluginRecord, "id" | "rootDir" | "source">) {
|
||||
function isOfficialCodexPluginRecord(
|
||||
record: Pick<PluginRecord, "id" | "origin" | "packageName" | "rootDir" | "source">,
|
||||
) {
|
||||
if (record.id !== "codex") {
|
||||
return false;
|
||||
}
|
||||
if (record.origin !== "global") {
|
||||
return false;
|
||||
}
|
||||
if (record.packageName === "@openclaw/codex") {
|
||||
return true;
|
||||
}
|
||||
const sourcePath = path
|
||||
.normalize(record.rootDir ?? record.source)
|
||||
.split(path.sep)
|
||||
@@ -283,9 +291,9 @@ function isOfficialNpmCodexPluginRecord(record: Pick<PluginRecord, "id" | "rootD
|
||||
}
|
||||
|
||||
function canClaimReservedCommandOwnership(
|
||||
record: Pick<PluginRecord, "id" | "origin" | "rootDir" | "source">,
|
||||
record: Pick<PluginRecord, "id" | "origin" | "packageName" | "rootDir" | "source">,
|
||||
) {
|
||||
return record.origin === "bundled" || isOfficialNpmCodexPluginRecord(record);
|
||||
return record.origin === "bundled" || isOfficialCodexPluginRecord(record);
|
||||
}
|
||||
|
||||
const ACTIVE_PLUGIN_HOOK_REGISTRATIONS_KEY = Symbol.for("openclaw.activePluginHookRegistrations");
|
||||
|
||||
Reference in New Issue
Block a user