mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-11 20:50:43 +00:00
test: add codex npm plugin Docker live proof
This commit is contained in:
@@ -127,6 +127,40 @@ describe("host-hook fixture plugin contract", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("allows the official npm Codex plugin to keep /codex command ownership", () => {
|
||||
const { config, registry } = createPluginRegistryFixture();
|
||||
const codexRoot = path.join("/tmp", ".openclaw", "npm", "node_modules", "@openclaw", "codex");
|
||||
registerTestPlugin({
|
||||
registry,
|
||||
config,
|
||||
record: createPluginRecord({
|
||||
id: "codex",
|
||||
name: "Codex",
|
||||
origin: "global",
|
||||
rootDir: codexRoot,
|
||||
source: path.join(codexRoot, "index.ts"),
|
||||
}),
|
||||
register(api) {
|
||||
api.registerCommand({
|
||||
name: "codex",
|
||||
description: "Official npm 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 reserved command ownership for non-reserved bundled command names", () => {
|
||||
const { config, registry } = createPluginRegistryFixture();
|
||||
registerTestPlugin({
|
||||
|
||||
@@ -601,7 +601,7 @@ function logTrustedSourceLinkedOfficialInstall(params: {
|
||||
targetLabel: string;
|
||||
}) {
|
||||
params.logger.warn?.(
|
||||
`WARNING: ${params.targetLabel} allowed because it is an official source-linked ClawHub package: ${buildCriticalDetails({ findings: params.findings })}`,
|
||||
`WARNING: ${params.targetLabel} allowed because it is an official OpenClaw package: ${buildCriticalDetails({ findings: params.findings })}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -260,6 +260,44 @@ describe("installPluginFromNpmSpec", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("allows the official Codex npm plugin to spawn its managed app-server", async () => {
|
||||
const npmRoot = path.join(suiteTempRootTracker.makeTempDir(), "npm");
|
||||
const warnings: string[] = [];
|
||||
mockNpmViewAndInstall({
|
||||
spec: "@openclaw/codex@beta",
|
||||
packageName: "@openclaw/codex",
|
||||
version: "2026.5.1-beta.1",
|
||||
pluginId: "codex",
|
||||
npmRoot,
|
||||
indexJs: `import { spawn } from "node:child_process";\nspawn("codex", ["app-server"]);`,
|
||||
});
|
||||
|
||||
const result = await installPluginFromNpmSpec({
|
||||
spec: "@openclaw/codex@beta",
|
||||
npmDir: npmRoot,
|
||||
logger: {
|
||||
info: () => {},
|
||||
warn: (msg: string) => warnings.push(msg),
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) {
|
||||
return;
|
||||
}
|
||||
expect(result.pluginId).toBe("codex");
|
||||
expect(
|
||||
warnings.some((warning) =>
|
||||
warning.includes("allowed because it is an official OpenClaw package"),
|
||||
),
|
||||
).toBe(true);
|
||||
expectNpmInstallIntoRoot({
|
||||
calls: runCommandWithTimeoutMock.mock.calls,
|
||||
npmRoot,
|
||||
spec: "@openclaw/codex@beta",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects non-registry npm specs", async () => {
|
||||
const result = await installPluginFromNpmSpec({ spec: "github:evil/evil" });
|
||||
expect(result.ok).toBe(false);
|
||||
|
||||
@@ -773,7 +773,7 @@ describe("installPluginFromArchive", () => {
|
||||
expect(result.ok).toBe(true);
|
||||
expect(
|
||||
warnings.some((warning) =>
|
||||
warning.includes("allowed because it is an official source-linked ClawHub package"),
|
||||
warning.includes("allowed because it is an official OpenClaw package"),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
@@ -1935,7 +1935,7 @@ describe("installPluginFromArchive", () => {
|
||||
expect(result.ok).toBe(true);
|
||||
expect(
|
||||
warnings.some((warning) =>
|
||||
warning.includes("allowed because it is an official source-linked ClawHub package"),
|
||||
warning.includes("allowed because it is an official OpenClaw package"),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
@@ -110,6 +110,7 @@ type PluginInstallPolicyRequest = {
|
||||
};
|
||||
|
||||
const defaultLogger: PluginInstallLogger = {};
|
||||
const TRUSTED_OFFICIAL_NPM_PLUGIN_PACKAGES = new Map([["@openclaw/codex", "codex"]]);
|
||||
|
||||
function ensureOpenClawExtensions(params: { manifest: PackageManifest }):
|
||||
| {
|
||||
@@ -185,6 +186,26 @@ function hasPackageRuntimeDependencies(manifest: PackageManifest): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isTrustedOfficialNpmPluginInstall(params: {
|
||||
installPolicyRequest?: PluginInstallPolicyRequest;
|
||||
packageName: string;
|
||||
pluginId: string;
|
||||
}): boolean {
|
||||
if (params.installPolicyRequest?.kind !== "plugin-npm") {
|
||||
return false;
|
||||
}
|
||||
const requested = parseRegistryNpmSpec(params.installPolicyRequest.requestedSpecifier ?? "");
|
||||
if (!requested) {
|
||||
return false;
|
||||
}
|
||||
const expectedPluginId = TRUSTED_OFFICIAL_NPM_PLUGIN_PACKAGES.get(requested.name);
|
||||
return (
|
||||
expectedPluginId !== undefined &&
|
||||
params.packageName === requested.name &&
|
||||
params.pluginId === expectedPluginId
|
||||
);
|
||||
}
|
||||
|
||||
function buildBlockedInstallResult(params: {
|
||||
blocked: NonNullable<NonNullable<InstallSecurityScanResult>["blocked"]>;
|
||||
}): Extract<InstallPluginResult, { ok: false }> {
|
||||
@@ -697,12 +718,19 @@ async function validatePackagePluginInstallSource(params: {
|
||||
const scanMode = params.resolveEffectiveMode
|
||||
? await params.resolveEffectiveMode(pluginId)
|
||||
: params.mode;
|
||||
const trustedOfficialInstall =
|
||||
params.trustedSourceLinkedOfficialInstall ||
|
||||
isTrustedOfficialNpmPluginInstall({
|
||||
installPolicyRequest: params.installPolicyRequest,
|
||||
packageName: pkgName,
|
||||
pluginId,
|
||||
});
|
||||
const scanResult = await runInstallSourceScan({
|
||||
subject: `Plugin "${pluginId}"`,
|
||||
scan: async () =>
|
||||
await params.runtime.scanPackageInstallSource({
|
||||
dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
|
||||
trustedSourceLinkedOfficialInstall: params.trustedSourceLinkedOfficialInstall,
|
||||
trustedSourceLinkedOfficialInstall: trustedOfficialInstall,
|
||||
packageDir: params.packageDir,
|
||||
pluginId,
|
||||
logger: params.logger,
|
||||
|
||||
@@ -250,6 +250,23 @@ 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">) {
|
||||
if (record.id !== "codex") {
|
||||
return false;
|
||||
}
|
||||
const sourcePath = path
|
||||
.normalize(record.rootDir ?? record.source)
|
||||
.split(path.sep)
|
||||
.join("/");
|
||||
return sourcePath.includes("/node_modules/@openclaw/codex");
|
||||
}
|
||||
|
||||
function canClaimReservedCommandOwnership(
|
||||
record: Pick<PluginRecord, "id" | "origin" | "rootDir" | "source">,
|
||||
) {
|
||||
return record.origin === "bundled" || isOfficialNpmCodexPluginRecord(record);
|
||||
}
|
||||
|
||||
const ACTIVE_PLUGIN_HOOK_REGISTRATIONS_KEY = Symbol.for("openclaw.activePluginHookRegistrations");
|
||||
const activePluginHookRegistrations = resolveGlobalSingleton<
|
||||
Map<string, Array<{ event: string; handler: Parameters<typeof registerInternalHook>[1] }>>
|
||||
@@ -1428,7 +1445,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
return;
|
||||
}
|
||||
const allowReservedCommandNames = command.ownership === "reserved";
|
||||
if (allowReservedCommandNames && record.origin !== "bundled") {
|
||||
if (allowReservedCommandNames && !canClaimReservedCommandOwnership(record)) {
|
||||
pushDiagnostic({
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
|
||||
Reference in New Issue
Block a user