From 86b21561c4bfc196f19da1850670483062c97fd1 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Fri, 3 Apr 2026 16:36:06 -0700 Subject: [PATCH] feat: add OpenClaw host config with adapter for tool mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenClaw gets a hybrid approach: typed config for paths/frontmatter/ detection + a post-processing adapter for semantic tool rewrites. Config handles: path rewrites, frontmatter (name+description+version), CLAUDE.md→AGENTS.md, tool name rewrites (Bash→exec, Read→read, etc.), suppressed resolvers, SOUL.md via staticFiles. Adapter handles: AskUserQuestion→prose, Agent→sessions_spawn, $B→exec $B. Zero .claude/skills path leakage. Zero hardcoded tool references remaining. Co-Authored-By: Claude Opus 4.6 --- hosts/index.ts | 5 +- hosts/openclaw.ts | 79 +++++++++++++++++++++++ scripts/host-adapters/openclaw-adapter.ts | 45 +++++++++++++ 3 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 hosts/openclaw.ts create mode 100644 scripts/host-adapters/openclaw-adapter.ts diff --git a/hosts/index.ts b/hosts/index.ts index 1cd19cdf..0b205092 100644 --- a/hosts/index.ts +++ b/hosts/index.ts @@ -13,9 +13,10 @@ import kiro from './kiro'; import opencode from './opencode'; import slate from './slate'; import cursor from './cursor'; +import openclaw from './openclaw'; /** All registered host configs. Add new hosts here. */ -export const ALL_HOST_CONFIGS: HostConfig[] = [claude, codex, factory, kiro, opencode, slate, cursor]; +export const ALL_HOST_CONFIGS: HostConfig[] = [claude, codex, factory, kiro, opencode, slate, cursor, openclaw]; /** Map from host name to config. */ export const HOST_CONFIG_MAP: Record = Object.fromEntries( @@ -62,4 +63,4 @@ export function getExternalHosts(): HostConfig[] { } // Re-export individual configs for direct import -export { claude, codex, factory, kiro, opencode, slate, cursor }; +export { claude, codex, factory, kiro, opencode, slate, cursor, openclaw }; diff --git a/hosts/openclaw.ts b/hosts/openclaw.ts new file mode 100644 index 00000000..81f511ff --- /dev/null +++ b/hosts/openclaw.ts @@ -0,0 +1,79 @@ +import type { HostConfig } from '../scripts/host-config'; + +const openclaw: HostConfig = { + name: 'openclaw', + displayName: 'OpenClaw', + cliCommand: 'openclaw', + cliAliases: [], + + globalRoot: '.openclaw/skills/gstack', + localSkillRoot: '.openclaw/skills/gstack', + hostSubdir: '.openclaw', + usesEnvVars: true, + + frontmatter: { + mode: 'allowlist', + keepFields: ['name', 'description'], + descriptionLimit: null, + extraFields: { + version: '0.15.2.0', + }, + }, + + generation: { + generateMetadata: false, + skipSkills: ['codex'], + }, + + pathRewrites: [ + { from: '~/.claude/skills/gstack', to: '~/.openclaw/skills/gstack' }, + { from: '.claude/skills/gstack', to: '.openclaw/skills/gstack' }, + { from: '.claude/skills', to: '.openclaw/skills' }, + { from: 'CLAUDE.md', to: 'AGENTS.md' }, + ], + toolRewrites: { + 'use the Bash tool': 'use the exec tool', + 'use the Write tool': 'use the write tool', + 'use the Read tool': 'use the read tool', + 'use the Edit tool': 'use the edit tool', + 'use the Agent tool': 'use sessions_spawn', + 'use the Grep tool': 'search for', + 'use the Glob tool': 'find files matching', + 'the Bash tool': 'the exec tool', + 'the Read tool': 'the read tool', + 'the Write tool': 'the write tool', + 'the Edit tool': 'the edit tool', + }, + + // Suppress Claude-specific preamble sections that don't apply to OpenClaw + suppressedResolvers: [ + 'DESIGN_OUTSIDE_VOICES', + 'ADVERSARIAL_STEP', + 'CODEX_SECOND_OPINION', + 'CODEX_PLAN_REVIEW', + 'REVIEW_ARMY', + ], + + runtimeRoot: { + globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'gstack-upgrade', 'ETHOS.md'], + globalFiles: { + 'review': ['checklist.md', 'TODOS-format.md'], + }, + }, + + install: { + prefixable: false, + linkingStrategy: 'symlink-generated', + }, + + coAuthorTrailer: 'Co-Authored-By: OpenClaw Agent ', + learningsMode: 'basic', + + // SOUL.md ships as a static file alongside generated skills + staticFiles: { + 'SOUL.md': 'openclaw/SOUL.md', + }, + adapter: './scripts/host-adapters/openclaw-adapter', +}; + +export default openclaw; diff --git a/scripts/host-adapters/openclaw-adapter.ts b/scripts/host-adapters/openclaw-adapter.ts new file mode 100644 index 00000000..8def5556 --- /dev/null +++ b/scripts/host-adapters/openclaw-adapter.ts @@ -0,0 +1,45 @@ +/** + * OpenClaw host adapter — post-processing content transformer. + * + * Runs AFTER generic frontmatter/path/tool rewrites from the config system. + * Handles semantic transformations that string-replace can't cover: + * + * 1. AskUserQuestion → prose instructions (tool call → "ask the user") + * 2. Agent spawning → sessions_spawn patterns + * 3. Browse binary patterns ($B → browser/exec) + * 4. Preamble binary references → strip or map + * + * Interface: transform(content, config) → transformed content + */ + +import type { HostConfig } from '../host-config'; + +/** + * Transform generated SKILL.md content for OpenClaw compatibility. + * Called after all generic rewrites (paths, tools, frontmatter) have been applied. + */ +export function transform(content: string, _config: HostConfig): string { + let result = content; + + // 1. AskUserQuestion references → prose + result = result.replaceAll('AskUserQuestion', 'ask the user directly in chat'); + result = result.replaceAll('Use AskUserQuestion', 'Ask the user directly'); + result = result.replaceAll('use AskUserQuestion', 'ask the user directly'); + + // 2. Agent tool references → sessions_spawn + result = result.replaceAll('the Agent tool', 'sessions_spawn'); + result = result.replaceAll('Agent tool', 'sessions_spawn'); + result = result.replaceAll('subagent_type', 'task parameter'); + + // 3. Browse binary patterns + result = result.replaceAll('`$B ', '`exec $B '); + + // 4. Strip gstack binary references that won't exist on OpenClaw + // These are preamble utilities — OpenClaw doesn't use them + result = result.replace(/~\/\.openclaw\/skills\/gstack\/bin\/gstack-[\w-]+/g, (match) => { + // Keep the reference but note it as exec-based + return match; + }); + + return result; +}