BREAKING CHANGE: Convert repository to IronClaw-only package with strict external dependency on globally installed `openclaw` runtime. ### Changes - Remove entire OpenClaw core source from repository (src/agents/*, src/acp/*, src/commands/*, and related modules) - Implement CLI delegation: non-bootstrap commands now delegate to global `openclaw` binary via external contract - Remove local OpenClaw path resolution from web app; always spawn global `openclaw` binary instead of local scripts - Rename package.json scripts: `pnpm openclaw` → `pnpm ironclaw`, `openclaw:rpc` → `ironclaw:rpc` - Update bootstrap flow to verify and install global OpenClaw when missing - Migrate web workspace/profile logic to align with OpenClaw state paths - Add migration contract tests for stream-json, session subscribe, and profile resolution behaviors - Update build/release pipeline for IronClaw-only artifacts - Update documentation for new peer + global installation model ### Architecture IronClaw is now strictly a frontend/UI/bootstrap layer: - `npx ironclaw` bootstraps OpenClaw (if missing), runs guided onboarding - IronClaw UI serves on localhost:3100 - OpenClaw Gateway runs on standard port 18789 - Communication via stable CLI contracts and Gateway WebSocket protocol only ### Migration Users must have `openclaw` installed globally: npm install -g openclaw Existing IronClaw profiles and sessions remain compatible through gateway protocol stability. Refs: bootstrap_dev_testing, ironclaw_frontend_split, strict-external-openclaw
6.0 KiB
| name | overview | todos | isProject | |||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Bootstrap dev testing | Remove local OpenClaw paths from the web app, always use global `openclaw` binary, rename dev scripts to `ironclaw`, and verify bootstrap works standalone. |
|
false |
IronClaw Bootstrap: Clean Separation and Dev Testing
Architecture
IronClaw is a frontend/UI/skills layer. OpenClaw is a separate, globally-installed runtime. IronClaw should NEVER bundle or run a local copy of OpenClaw.
flowchart TD
npx["npx ironclaw (or ironclaw)"] --> entry["openclaw.mjs → dist/entry.js"]
entry --> runMain["run-main.ts: bare ironclaw → bootstrap"]
runMain --> delegate{"primary == bootstrap?"}
delegate -->|yes, keep local| bootstrap["bootstrapCommand()"]
delegate -->|no, delegate| globalOC["spawn openclaw ...args"]
bootstrap --> checkOC{"openclaw on PATH?"}
checkOC -->|yes| onboard
checkOC -->|no| prompt["Prompt: install openclaw globally?"]
prompt -->|yes| npmInstall["npm install -g openclaw"]
npmInstall --> onboard
onboard["openclaw onboard --install-daemon"] --> gatewayStart["Gateway starts + spawns web app"]
gatewayStart --> probe["waitForWebAppPort(3100)"]
probe --> openBrowser["Open http://localhost:3100"]
The bootstrap flow is correctly wired:
- Bare
ironclawrewrites toironclaw bootstrap bootstrapis never delegated to globalopenclawbootstrapCommandcallsensureOpenClawCliAvailablewhich prompts to install- Onboarding sets
gateway.webApp.enabled: true - Gateway starts the Next.js standalone server on port 3100
- Bootstrap probes and opens the browser
Problem 1: Local OpenClaw paths in web app (must remove)
[apps/web/lib/agent-runner.ts](apps/web/lib/agent-runner.ts) has resolveOpenClawLaunch which, when IRONCLAW_USE_LOCAL_OPENCLAW=1, resolves a local scripts/run-node.mjs or openclaw.mjs and spawns it with node. This contradicts the architecture: IronClaw should always spawn the global openclaw binary.
The same pattern exists in [apps/web/lib/subagent-runs.ts](apps/web/lib/subagent-runs.ts) where sendGatewayAbortForSubagent and spawnSubagentMessage hardcode node <local-script> paths.
Fix:
- Remove
IRONCLAW_USE_LOCAL_OPENCLAW,resolveOpenClawLaunch,resolvePackageRoot, andOpenClawLaunchtype fromagent-runner.ts - All spawn calls become
spawn("openclaw", [...args], { env, stdio }) - In
subagent-runs.ts: replacenode <scriptPath> gateway call ...withopenclaw gateway call ... - Remove
resolvePackageRootimport fromsubagent-runs.ts
Problem 2: pnpm openclaw script name is wrong
package.json has "openclaw": "node scripts/run-node.mjs". This repo IS IronClaw, not OpenClaw.
Fix: Rename to "ironclaw": "node scripts/run-node.mjs". Also "openclaw:rpc" to "ironclaw:rpc".
Dev workflow (after fixes)
# Prerequisite: install OpenClaw globally (one-time)
npm install -g openclaw
# Run IronClaw bootstrap (installs/configures everything, opens UI)
pnpm ironclaw
# Or for web UI dev only:
openclaw --profile ironclaw gateway --port 18789 # Terminal 1
pnpm web:dev # Terminal 2
Implementation details
1. Simplify agent-runner.ts spawning
Remove ~40 lines (resolvePackageRoot, OpenClawLaunch, resolveOpenClawLaunch). Both spawnLegacyAgentProcess and spawnLegacyAgentSubscribeProcess become:
function spawnLegacyAgentProcess(message: string, agentSessionId?: string) {
const args = ["agent", "--agent", "main", "--message", message, "--stream-json"];
if (agentSessionId) {
const sessionKey = `agent:main:web:${agentSessionId}`;
args.push("--session-key", sessionKey, "--lane", "web", "--channel", "webchat");
}
const profile = getEffectiveProfile();
const workspace = resolveWorkspaceRoot();
return spawn("openclaw", args, {
env: {
...process.env,
...(profile ? { OPENCLAW_PROFILE: profile } : {}),
...(workspace ? { OPENCLAW_WORKSPACE: workspace } : {}),
},
stdio: ["ignore", "pipe", "pipe"],
});
}
2. Simplify subagent-runs.ts spawning
sendGatewayAbortForSubagent and spawnSubagentMessage both have this pattern:
const root = resolvePackageRoot();
const devScript = join(root, "scripts", "run-node.mjs");
const prodScript = join(root, "openclaw.mjs");
const scriptPath = existsSync(devScript) ? devScript : prodScript;
spawn("node", [scriptPath, "gateway", "call", ...], { cwd: root, ... });
Replace with:
spawn("openclaw", ["gateway", "call", ...], { env: process.env, ... });
3. Update agent-runner.test.ts
- Remove
process.env.IRONCLAW_USE_LOCAL_OPENCLAW = "1"frombeforeEach - Remove entire
resolvePackageRootdescribe block (~5 tests) - The "uses global openclaw by default" test becomes the only spawn behavior test
- Update mock assertions: command is always
"openclaw", noprefixArgs
4. Rename package.json scripts
- "openclaw": "node scripts/run-node.mjs",
- "openclaw:rpc": "node scripts/run-node.mjs agent --mode rpc --json",
+ "ironclaw": "node scripts/run-node.mjs",
+ "ironclaw:rpc": "node scripts/run-node.mjs agent --mode rpc --json",