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

147 lines
6.0 KiB
Markdown

---
name: Bootstrap dev testing
overview: Remove local OpenClaw paths from the web app, always use global `openclaw` binary, rename dev scripts to `ironclaw`, and verify bootstrap works standalone.
todos:
- id: remove-local-openclaw-agent-runner
content: Remove resolvePackageRoot, resolveOpenClawLaunch, IRONCLAW_USE_LOCAL_OPENCLAW from agent-runner.ts; spawn global `openclaw` directly
status: completed
- id: remove-local-openclaw-subagent-runs
content: Remove local script paths from subagent-runs.ts (sendGatewayAbortForSubagent, spawnSubagentMessage); use global `openclaw` instead
status: completed
- id: rename-pnpm-scripts
content: Rename `pnpm openclaw` to `pnpm ironclaw` and `openclaw:rpc` to `ironclaw:rpc` in package.json
status: completed
- id: update-agent-runner-tests
content: "Update agent-runner.test.ts: remove resolvePackageRoot tests, IRONCLAW_USE_LOCAL_OPENCLAW, update spawn assertions"
status: completed
- id: verify-builds-pass
content: Verify pnpm build, pnpm web:build, and workspace tests pass after changes
status: completed
isProject: 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.
```mermaid
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)
```bash
# 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:
```typescript
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:
```typescript
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:
```typescript
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
```diff
- "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",
```