openclaw/.cursor/plans/bootstrap_dev_testing_0b5817e5.plan.md
kumarabhirup 52707f471d
refactor!: IronClaw v2.0 - external OpenClaw runtime
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
2026-03-01 16:11:40 -08:00

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.
id content status
remove-local-openclaw-agent-runner Remove resolvePackageRoot, resolveOpenClawLaunch, IRONCLAW_USE_LOCAL_OPENCLAW from agent-runner.ts; spawn global `openclaw` directly completed
id content status
remove-local-openclaw-subagent-runs Remove local script paths from subagent-runs.ts (sendGatewayAbortForSubagent, spawnSubagentMessage); use global `openclaw` instead completed
id content status
rename-pnpm-scripts Rename `pnpm openclaw` to `pnpm ironclaw` and `openclaw:rpc` to `ironclaw:rpc` in package.json completed
id content status
update-agent-runner-tests Update agent-runner.test.ts: remove resolvePackageRoot tests, IRONCLAW_USE_LOCAL_OPENCLAW, update spawn assertions completed
id content status
verify-builds-pass Verify pnpm build, pnpm web:build, and workspace tests pass after changes completed
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 ironclaw rewrites to ironclaw bootstrap
  • bootstrap is never delegated to global openclaw
  • bootstrapCommand calls ensureOpenClawCliAvailable which 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, and OpenClawLaunch type from agent-runner.ts
  • All spawn calls become spawn("openclaw", [...args], { env, stdio })
  • In subagent-runs.ts: replace node <scriptPath> gateway call ... with openclaw gateway call ...
  • Remove resolvePackageRoot import from subagent-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" from beforeEach
  • Remove entire resolvePackageRoot describe block (~5 tests)
  • The "uses global openclaw by default" test becomes the only spawn behavior test
  • Update mock assertions: command is always "openclaw", no prefixArgs

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",