Skip to content

Commit 4e9899f

Browse files
fix(openclaw): emit empty configSchema in plugin manifests
OpenClaw rejects generated plugin manifests that omit configSchema, even for tool plugins with no user configuration. Always emit an empty object schema so converted installs boot cleanly.\n\nAdd converter and writer regression coverage for the manifest shape.\n\nFixes #224
1 parent 020eb88 commit 4e9899f

4 files changed

Lines changed: 59 additions & 6 deletions

File tree

src/converters/claude-to-openclaw.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ function buildManifest(plugin: ClaudePlugin, skillDirs: string[]): OpenClawPlugi
6464
id: plugin.manifest.name,
6565
name: formatDisplayName(plugin.manifest.name),
6666
kind: "tool",
67+
configSchema: {
68+
type: "object",
69+
properties: {},
70+
},
6771
skills: skillDirs.map((dir) => `skills/${dir}`),
6872
}
6973
}

src/types/openclaw.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@ export type OpenClawPluginManifest = {
22
id: string
33
name: string
44
kind: "tool"
5-
configSchema?: {
6-
type: "object"
7-
additionalProperties: boolean
8-
properties: Record<string, OpenClawConfigProperty>
9-
required?: string[]
10-
}
5+
configSchema: OpenClawConfigSchema
116
uiHints?: Record<string, OpenClawUiHint>
127
skills?: string[]
138
}
149

10+
export type OpenClawConfigSchema = {
11+
type: "object"
12+
properties: Record<string, OpenClawConfigProperty>
13+
additionalProperties?: boolean
14+
required?: string[]
15+
}
16+
1517
export type OpenClawConfigProperty = {
1618
type: string
1719
description?: string

tests/openclaw-converter.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ describe("convertClaudeToOpenClaw", () => {
108108
expect(bundle.manifest.id).toBe("compound-engineering")
109109
expect(bundle.manifest.name).toBe("Compound Engineering")
110110
expect(bundle.manifest.kind).toBe("tool")
111+
expect(bundle.manifest.configSchema).toEqual({
112+
type: "object",
113+
properties: {},
114+
})
111115
expect(bundle.manifest.skills).toContain("skills/agent-security-reviewer")
112116
expect(bundle.manifest.skills).toContain("skills/cmd-workflows:plan")
113117
expect(bundle.manifest.skills).toContain("skills/existing-skill")

tests/openclaw-writer.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { describe, expect, test } from "bun:test"
2+
import { promises as fs } from "fs"
3+
import os from "os"
4+
import path from "path"
5+
import { writeOpenClawBundle } from "../src/targets/openclaw"
6+
import type { OpenClawBundle } from "../src/types/openclaw"
7+
8+
describe("writeOpenClawBundle", () => {
9+
test("writes openclaw.plugin.json with a configSchema", async () => {
10+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-writer-"))
11+
const bundle: OpenClawBundle = {
12+
manifest: {
13+
id: "compound-engineering",
14+
name: "Compound Engineering",
15+
kind: "tool",
16+
configSchema: {
17+
type: "object",
18+
properties: {},
19+
},
20+
skills: [],
21+
},
22+
packageJson: {
23+
name: "openclaw-compound-engineering",
24+
version: "1.0.0",
25+
},
26+
entryPoint: "export default async function register() {}",
27+
skills: [],
28+
skillDirCopies: [],
29+
commands: [],
30+
}
31+
32+
await writeOpenClawBundle(tempRoot, bundle)
33+
34+
const manifest = JSON.parse(
35+
await fs.readFile(path.join(tempRoot, "openclaw.plugin.json"), "utf8"),
36+
)
37+
38+
expect(manifest.configSchema).toEqual({
39+
type: "object",
40+
properties: {},
41+
})
42+
})
43+
})

0 commit comments

Comments
 (0)