Skip to content
This repository was archived by the owner on Jul 19, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/openclaw/plugin/__tests__/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,49 @@ describe("resolveAgentName", () => {
it("falls back to openclaw when sessionKey has empty second segment", () => {
expect(resolveAgentName("agent::main", null)).toBe("openclaw");
});

it("uses pluginConfig agentName instead of the openclaw ghost fallback", async () => {
const api = {
id: "multicorn-shield",
name: "Multicorn Shield",
source: "test",
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
on: vi.fn(),
pluginConfig: { agentName: "configured-agent" },
} as unknown as OpenClawPluginApi;

vi.stubEnv("MULTICORN_API_KEY", "mcs_test_key_123456");
readFileSyncMock.mockImplementation(() => {
throw new Error("ENOENT");
});

void plugin.register?.(api);

findOrRegisterAgentMock.mockResolvedValue({
id: "agent-1",
name: "configured-agent",
});
fetchGrantedScopesMock.mockResolvedValue([{ service: "terminal", permissionLevel: "execute" }]);
checkActionPermissionMock.mockResolvedValue({ status: "approved" });

await beforeToolCall(
makeBeforeEvent("exec"),
makeCtx({ sessionKey: "agent::main", agentId: "" }),
);

expect(findOrRegisterAgentMock).toHaveBeenCalledWith(
"configured-agent",
expect.any(String),
expect.any(String),
expect.anything(),
);
expect(findOrRegisterAgentMock).not.toHaveBeenCalledWith(
"openclaw",
expect.any(String),
expect.any(String),
expect.anything(),
);
});
});

describe("agent name pinning", () => {
Expand Down
79 changes: 79 additions & 0 deletions src/proxy/__tests__/init-wizard-platform-registry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { existsSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { INIT_WIZARD_PLATFORM_REGISTRY } from "../config.js";

const REPO_ROOT = process.cwd();

const NATIVE_PLUGIN_IMPLEMENTATIONS: Readonly<Record<string, readonly string[]>> = {
openclaw: ["src/openclaw/plugin/index.ts"],
"claude-code": [
"plugins/multicorn-shield/hooks/scripts/pre-tool-use.cjs",
"plugins/multicorn-shield/hooks/scripts/post-tool-use.cjs",
],
windsurf: [
"plugins/windsurf/hooks/scripts/pre-action.cjs",
"plugins/windsurf/hooks/scripts/post-action.cjs",
],
cline: [
"plugins/cline/hooks/scripts/pre-tool-use.cjs",
"plugins/cline/hooks/scripts/post-tool-use.cjs",
],
"gemini-cli": [
"plugins/gemini-cli/hooks/scripts/before-tool.cjs",
"plugins/gemini-cli/hooks/scripts/after-tool.cjs",
],
opencode: ["plugins/opencode/multicorn-shield.ts"],
"codex-cli": [
"plugins/codex-cli/hooks/scripts/pre-tool-use.cjs",
"plugins/codex-cli/hooks/scripts/post-tool-use.cjs",
],
};

const EXPECTED_NATIVE_SLUGS = [
"openclaw",
"claude-code",
"windsurf",
"cline",
"gemini-cli",
"opencode",
"codex-cli",
] as const;

const HOSTED_ONLY_SLUGS = [
"cursor",
"claude-desktop",
"github-copilot",
"kilo-code",
"continue-dev",
"goose",
"other-mcp",
] as const;

describe("INIT_WIZARD_PLATFORM_REGISTRY native section", () => {
it("lists exactly the seven native-capable platforms", () => {
const nativeSlugs = INIT_WIZARD_PLATFORM_REGISTRY.filter((e) => e.section === "native").map(
(e) => e.slug,
);
expect(nativeSlugs).toEqual([...EXPECTED_NATIVE_SLUGS]);
});

it("keeps hosted-only slugs out of the native section", () => {
const nativeSlugs = new Set(
INIT_WIZARD_PLATFORM_REGISTRY.filter((e) => e.section === "native").map((e) => e.slug),
);
for (const slug of HOSTED_ONLY_SLUGS) {
expect(nativeSlugs.has(slug)).toBe(false);
}
});

it("maps every native-section slug to an on-disk plugin implementation", () => {
for (const slug of EXPECTED_NATIVE_SLUGS) {
const paths = NATIVE_PLUGIN_IMPLEMENTATIONS[slug];
expect(paths, `missing plugin map for ${slug}`).toBeDefined();
for (const rel of paths ?? []) {
expect(existsSync(join(REPO_ROOT, rel)), `${slug} plugin missing at ${rel}`).toBe(true);
}
}
});
});
4 changes: 4 additions & 0 deletions src/proxy/__tests__/proxy.edge-cases.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2018,6 +2018,10 @@ describe("config file parsing", () => {

expect(result).toBe("updated");
const written = JSON.parse(String(writeFileMock.mock.calls[0]?.[1])) as Record<string, unknown>;
const plugins = written["plugins"] as Record<string, unknown>;
const entries = plugins["entries"] as Record<string, unknown>;
const shield = entries["multicorn-shield"] as Record<string, unknown>;
expect(shield["agentName"]).toBe("my-agent");
const agents = written["agents"] as Record<string, unknown>;
expect(agents["list"]).toEqual([{ id: "my-agent", name: "my-agent" }]);
});
Expand Down
33 changes: 14 additions & 19 deletions src/proxy/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,25 +568,20 @@ export async function updateOpenClawConfigIfPresent(
return "parse-error";
}

let hooks = obj["hooks"] as Record<string, unknown> | undefined;
if (hooks === undefined || typeof hooks !== "object") {
hooks = {};
obj["hooks"] = hooks;
}
let internal = hooks["internal"] as Record<string, unknown> | undefined;
if (internal === undefined || typeof internal !== "object") {
internal = { enabled: true, entries: {} };
hooks["internal"] = internal;
}
let entries = internal["entries"] as Record<string, unknown> | undefined;
if (entries === undefined || typeof entries !== "object") {
entries = {};
internal["entries"] = entries;
}
let shield = entries["multicorn-shield"] as Record<string, unknown> | undefined;
let plugins = obj["plugins"] as Record<string, unknown> | undefined;
if (plugins === undefined || typeof plugins !== "object") {
plugins = {};
obj["plugins"] = plugins;
}
let pluginEntries = plugins["entries"] as Record<string, unknown> | undefined;
if (pluginEntries === undefined || typeof pluginEntries !== "object") {
pluginEntries = {};
plugins["entries"] = pluginEntries;
}
let shield = pluginEntries["multicorn-shield"] as Record<string, unknown> | undefined;
if (shield === undefined || typeof shield !== "object") {
shield = { enabled: true, env: {} };
entries["multicorn-shield"] = shield;
shield = { enabled: true };
pluginEntries["multicorn-shield"] = shield;
}
let env = shield["env"] as Record<string, unknown> | undefined;
if (env === undefined || typeof env !== "object") {
Expand All @@ -596,7 +591,7 @@ export async function updateOpenClawConfigIfPresent(
env["MULTICORN_API_KEY"] = apiKey;
env["MULTICORN_BASE_URL"] = baseUrl;
if (agentName !== undefined) {
env["MULTICORN_AGENT_NAME"] = agentName;
shield["agentName"] = agentName;

const agentsList = obj["agents"] as Record<string, unknown> | undefined;
const list = agentsList?.["list"];
Expand Down
Loading