From 87d5b375d9529de469a261983ffd1b505e43c5d8 Mon Sep 17 00:00:00 2001 From: Rachelle Date: Mon, 22 Jun 2026 21:15:25 +1000 Subject: [PATCH 1/2] fix date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b585f0..886ebb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `version` in `package.json` before publishing to npm. -## [Unreleased] +## [1.11.1] - 2026-06-22 ### Fixed From 9d5dd52bc3429ae30b39c1e6b8c2a7dc70dea482 Mon Sep 17 00:00:00 2001 From: Rachelle Date: Tue, 23 Jun 2026 21:05:19 +1000 Subject: [PATCH 2/2] Fix OpenClaw plugin config path and guard native wizard membership. Write agentName to plugins.entries.multicorn-shield for init, add registry/plugin on-disk tests, and cover pluginConfig agentName resolution without the openclaw ghost agent. Co-authored-by: Cursor --- src/openclaw/plugin/__tests__/plugin.test.ts | 43 ++++++++++ .../init-wizard-platform-registry.test.ts | 79 +++++++++++++++++++ src/proxy/__tests__/proxy.edge-cases.test.ts | 4 + src/proxy/config.ts | 33 ++++---- 4 files changed, 140 insertions(+), 19 deletions(-) create mode 100644 src/proxy/__tests__/init-wizard-platform-registry.test.ts diff --git a/src/openclaw/plugin/__tests__/plugin.test.ts b/src/openclaw/plugin/__tests__/plugin.test.ts index 145b63e..e7d43da 100644 --- a/src/openclaw/plugin/__tests__/plugin.test.ts +++ b/src/openclaw/plugin/__tests__/plugin.test.ts @@ -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", () => { diff --git a/src/proxy/__tests__/init-wizard-platform-registry.test.ts b/src/proxy/__tests__/init-wizard-platform-registry.test.ts new file mode 100644 index 0000000..b16fc63 --- /dev/null +++ b/src/proxy/__tests__/init-wizard-platform-registry.test.ts @@ -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> = { + 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); + } + } + }); +}); diff --git a/src/proxy/__tests__/proxy.edge-cases.test.ts b/src/proxy/__tests__/proxy.edge-cases.test.ts index 1187748..eb67689 100644 --- a/src/proxy/__tests__/proxy.edge-cases.test.ts +++ b/src/proxy/__tests__/proxy.edge-cases.test.ts @@ -2018,6 +2018,10 @@ describe("config file parsing", () => { expect(result).toBe("updated"); const written = JSON.parse(String(writeFileMock.mock.calls[0]?.[1])) as Record; + const plugins = written["plugins"] as Record; + const entries = plugins["entries"] as Record; + const shield = entries["multicorn-shield"] as Record; + expect(shield["agentName"]).toBe("my-agent"); const agents = written["agents"] as Record; expect(agents["list"]).toEqual([{ id: "my-agent", name: "my-agent" }]); }); diff --git a/src/proxy/config.ts b/src/proxy/config.ts index beb4a17..2f7c88d 100644 --- a/src/proxy/config.ts +++ b/src/proxy/config.ts @@ -568,25 +568,20 @@ export async function updateOpenClawConfigIfPresent( return "parse-error"; } - let hooks = obj["hooks"] as Record | undefined; - if (hooks === undefined || typeof hooks !== "object") { - hooks = {}; - obj["hooks"] = hooks; - } - let internal = hooks["internal"] as Record | undefined; - if (internal === undefined || typeof internal !== "object") { - internal = { enabled: true, entries: {} }; - hooks["internal"] = internal; - } - let entries = internal["entries"] as Record | undefined; - if (entries === undefined || typeof entries !== "object") { - entries = {}; - internal["entries"] = entries; - } - let shield = entries["multicorn-shield"] as Record | undefined; + let plugins = obj["plugins"] as Record | undefined; + if (plugins === undefined || typeof plugins !== "object") { + plugins = {}; + obj["plugins"] = plugins; + } + let pluginEntries = plugins["entries"] as Record | undefined; + if (pluginEntries === undefined || typeof pluginEntries !== "object") { + pluginEntries = {}; + plugins["entries"] = pluginEntries; + } + let shield = pluginEntries["multicorn-shield"] as Record | 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 | undefined; if (env === undefined || typeof env !== "object") { @@ -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 | undefined; const list = agentsList?.["list"];