fork: rename IronClaw → OptimClaw

Full rename of all identifiers, filenames, and references:
  ironclaw → optimclaw
  IronClaw → OptimClaw
  IRONCLAW → OPTIMCLAW
  ironclaw_common → optimclaw_common
  ironclaw_safety → optimclaw_safety

Upstream: nearai/ironclaw
This commit is contained in:
OutBack Dingo
2026-03-29 06:27:52 +07:00
parent 8a320ae9db
commit 6d9dbbb3b9
404 changed files with 2382 additions and 2382 deletions
+11 -11
View File
@@ -1,6 +1,6 @@
# Building WASM Channels
This guide covers how to build WASM channel modules for IronClaw.
This guide covers how to build WASM channel modules for OptimClaw.
## Overview
@@ -19,7 +19,7 @@ channels/ # Or channels-src/
After building, deploy to:
```
~/.ironclaw/channels/
~/.optimclaw/channels/
├── my-channel.wasm
└── my-channel.capabilities.json
```
@@ -31,7 +31,7 @@ After building, deploy to:
name = "my-channel"
version = "0.1.0"
edition = "2021"
description = "My messaging platform channel for IronClaw"
description = "My messaging platform channel for OptimClaw"
[lib]
crate-type = ["cdylib"]
@@ -248,7 +248,7 @@ Create `my-channel.capabilities.json`:
### Supply Chain Security: No Committed Binaries
**Do not commit compiled WASM binaries.** They are a supply chain risk — the binary in a PR may not match the source. IronClaw builds channels from source:
**Do not commit compiled WASM binaries.** They are a supply chain risk — the binary in a PR may not match the source. OptimClaw builds channels from source:
- `cargo build` automatically builds `telegram.wasm` via `build.rs`
- The built binary is in `.gitignore` and is not committed
@@ -270,12 +270,12 @@ rustup target add wasm32-wasip2
# Build Telegram channel
./channels-src/telegram/build.sh
# Install (or use ironclaw onboard to install bundled channel)
mkdir -p ~/.ironclaw/channels
cp channels-src/telegram/telegram.wasm channels-src/telegram/telegram.capabilities.json ~/.ironclaw/channels/
# Install (or use optimclaw onboard to install bundled channel)
mkdir -p ~/.optimclaw/channels
cp channels-src/telegram/telegram.wasm channels-src/telegram/telegram.capabilities.json ~/.optimclaw/channels/
```
**Note**: The main IronClaw binary bundles `telegram.wasm` via `include_bytes!`. When modifying the Telegram channel source, run `./channels-src/telegram/build.sh` **before** building the main crate, so the updated WASM is included.
**Note**: The main OptimClaw binary bundles `telegram.wasm` via `include_bytes!`. When modifying the Telegram channel source, run `./channels-src/telegram/build.sh` **before** building the main crate, so the updated WASM is included.
### Other Channels
@@ -284,9 +284,9 @@ cp channels-src/telegram/telegram.wasm channels-src/telegram/telegram.capabiliti
cd channels-src/my-channel
cargo build --release --target wasm32-wasip2
# Deploy to ~/.ironclaw/channels/
cp target/wasm32-wasip2/release/my_channel.wasm ~/.ironclaw/channels/my-channel.wasm
cp my-channel.capabilities.json ~/.ironclaw/channels/
# Deploy to ~/.optimclaw/channels/
cp target/wasm32-wasip2/release/my_channel.wasm ~/.optimclaw/channels/my-channel.wasm
cp my-channel.capabilities.json ~/.optimclaw/channels/
```
## Host Functions Available
+8 -8
View File
@@ -1,6 +1,6 @@
# LLM Provider Configuration
IronClaw defaults to NEAR AI for model access, but supports any OpenAI-compatible
OptimClaw defaults to NEAR AI for model access, but supports any OpenAI-compatible
endpoint as well as Anthropic, Ollama, and Google Gemini directly. This guide covers
the most common configurations.
@@ -30,8 +30,8 @@ the most common configurations.
## NEAR AI (default)
No additional configuration required. On first run, `ironclaw onboard` opens a browser
for OAuth authentication. Credentials are saved to `~/.ironclaw/session.json`.
No additional configuration required. On first run, `optimclaw onboard` opens a browser
for OAuth authentication. Credentials are saved to `~/.optimclaw/session.json`.
```env
NEARAI_MODEL=claude-3-5-sonnet-20241022
@@ -110,7 +110,7 @@ API (`generativelanguage.googleapis.com`).
## GitHub Copilot
GitHub Copilot exposes chat endpoint at
`https://api.githubcopilot.com`. IronClaw uses that endpoint directly through the
`https://api.githubcopilot.com`. OptimClaw uses that endpoint directly through the
built-in `github_copilot` provider.
```env
@@ -121,14 +121,14 @@ GITHUB_COPILOT_MODEL=gpt-4o
# GITHUB_COPILOT_EXTRA_HEADERS=Copilot-Integration-Id:vscode-chat
```
`ironclaw onboard` can acquire this token for you using GitHub device login. If you
`optimclaw onboard` can acquire this token for you using GitHub device login. If you
already signed into Copilot through VS Code or a JetBrains IDE, you can also reuse
the `oauth_token` stored in `~/.config/github-copilot/apps.json`. If you prefer,
`LLM_BACKEND=github-copilot` also works as an alias.
Popular models vary by subscription, but `gpt-4o` is a safe default. IronClaw keeps
Popular models vary by subscription, but `gpt-4o` is a safe default. OptimClaw keeps
model entry manual for this provider because GitHub Copilot model listing may require
extra integration headers on some clients. IronClaw automatically injects the standard
extra integration headers on some clients. OptimClaw automatically injects the standard
VS Code identity headers (`User-Agent`, `Editor-Version`, `Editor-Plugin-Version`,
`Copilot-Integration-Id`) and lets you override them with
`GITHUB_COPILOT_EXTRA_HEADERS`.
@@ -313,7 +313,7 @@ LLM_MODEL=llama-3.2-3b-instruct-q4_K_M
Instead of editing `.env` manually, run the onboarding wizard:
```bash
ironclaw onboard
optimclaw onboard
```
Select **"OpenAI-compatible"** for OpenRouter, Together AI, Fireworks, vLLM, LiteLLM,
+15 -15
View File
@@ -1,10 +1,10 @@
# Telegram Channel Setup
This guide covers configuring the Telegram channel for IronClaw, including DM pairing for access control.
This guide covers configuring the Telegram channel for OptimClaw, including DM pairing for access control.
## Overview
The Telegram channel lets you interact with IronClaw via Telegram DMs and groups. It supports:
The Telegram channel lets you interact with OptimClaw via Telegram DMs and groups. It supports:
- **Webhook mode** (recommended): Instant delivery via tunnel
- **Polling mode**: No tunnel required; ~30s delay
@@ -13,7 +13,7 @@ The Telegram channel lets you interact with IronClaw via Telegram DMs and groups
## Prerequisites
- IronClaw installed and configured (`ironclaw onboard`)
- OptimClaw installed and configured (`optimclaw onboard`)
- A Telegram bot token from [@BotFather](https://t.me/BotFather)
## Quick Start
@@ -27,7 +27,7 @@ The Telegram channel lets you interact with IronClaw via Telegram DMs and groups
### 2. Configure via Setup Wizard
```bash
ironclaw onboard
optimclaw onboard
```
When prompted, enable the Telegram channel and paste your bot token. The wizard will:
@@ -57,26 +57,26 @@ When an unknown user DMs your bot, they receive a pairing code. You must approve
### Flow
1. Unknown user sends a message to your bot
2. Bot replies: `To pair with this bot, run: ironclaw pairing approve telegram ABC12345`
3. You run: `ironclaw pairing approve telegram ABC12345`
2. Bot replies: `To pair with this bot, run: optimclaw pairing approve telegram ABC12345`
3. You run: `optimclaw pairing approve telegram ABC12345`
4. User is added to the allow list; future messages are delivered
### Commands
```bash
# List pending pairing requests
ironclaw pairing list telegram
optimclaw pairing list telegram
# List as JSON
ironclaw pairing list telegram --json
optimclaw pairing list telegram --json
# Approve a user by code
ironclaw pairing approve telegram ABC12345
optimclaw pairing approve telegram ABC12345
```
### Configuration
Edit `~/.ironclaw/channels/telegram.capabilities.json` (or the config injected by the host):
Edit `~/.optimclaw/channels/telegram.capabilities.json` (or the config injected by the host):
| Option | Values | Default | Description |
|--------|--------|---------|-------------|
@@ -96,8 +96,8 @@ rustup target add wasm32-wasip2
./channels-src/telegram/build.sh
# Install
mkdir -p ~/.ironclaw/channels
cp channels-src/telegram/telegram.wasm channels-src/telegram/telegram.capabilities.json ~/.ironclaw/channels/
mkdir -p ~/.optimclaw/channels
cp channels-src/telegram/telegram.wasm channels-src/telegram/telegram.capabilities.json ~/.optimclaw/channels/
```
## Secrets
@@ -106,7 +106,7 @@ The channel expects a secret named `telegram_bot_token`. Configure via:
- **Setup wizard**: Saves to encrypted secrets store
- **Environment**: `TELEGRAM_BOT_TOKEN=your_token`
- **Secrets store**: `ironclaw` CLI (if available)
- **Secrets store**: `optimclaw` CLI (if available)
## Webhook Secret (Optional)
@@ -126,10 +126,10 @@ For webhook validation, set `telegram_webhook_secret` in secrets. Telegram will
### Group mentions not working
- Set `bot_username` in config to your bot's username (e.g., `MyIronClawBot`)
- Set `bot_username` in config to your bot's username (e.g., `MyOptimClawBot`)
- Ensure the message contains `@YourBot` or starts with `/`
### "Connection refused" when starting
- For webhook mode: Start your tunnel before `ironclaw run`
- For webhook mode: Start your tunnel before `optimclaw run`
- For polling only: No tunnel needed; ignore tunnel-related warnings
+5 -5
View File
@@ -1,6 +1,6 @@
# User Management API
DB-backed user management for multi-tenant IronClaw deployments. Covers admin user CRUD, per-user secrets provisioning, self-service profile, API token management, and usage reporting.
DB-backed user management for multi-tenant OptimClaw deployments. Covers admin user CRUD, per-user secrets provisioning, self-service profile, API token management, and usage reporting.
## Authentication
@@ -211,7 +211,7 @@ Permanently delete a user and all associated data (tokens, jobs, conversations,
## Admin: Per-User Secrets
Provision secrets on behalf of individual users. The primary use case is an application backend (acting as admin) that configures per-user credentials so each user's IronClaw agent can call back to external services.
Provision secrets on behalf of individual users. The primary use case is an application backend (acting as admin) that configures per-user credentials so each user's OptimClaw agent can call back to external services.
Secrets are encrypted at rest with AES-256-GCM using a per-secret HKDF-derived key. Plaintext values are **never returned** by any endpoint — they can only be used by the agent's tool system at runtime.
@@ -260,17 +260,17 @@ Create or update a secret for the specified user. If a secret with the same name
```bash
# Admin creates a user
curl -X POST https://ironclaw.example.com/api/admin/users \
curl -X POST https://optimclaw.example.com/api/admin/users \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{"display_name": "Alice", "role": "member"}'
# Response includes: {"id": "alice-uuid", "token": "alice-bearer-token", ...}
# Admin provisions a per-user callback secret
curl -X PUT https://ironclaw.example.com/api/admin/users/alice-uuid/secrets/app_callback_token \
curl -X PUT https://optimclaw.example.com/api/admin/users/alice-uuid/secrets/app_callback_token \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{"value": "per-user-jwt-for-alice", "provider": "my-app"}'
# Now Alice's IronClaw agent can use the "app_callback_token" secret
# Now Alice's OptimClaw agent can use the "app_callback_token" secret
# when calling tools that need to authenticate back to the app backend.
```
+25 -25
View File
@@ -1,4 +1,4 @@
# Automated QA Plan for IronClaw
# Automated QA Plan for OptimClaw
**Date:** 2026-02-24
**Status:** Draft
@@ -8,7 +8,7 @@
## Motivation
A review of all closed issues and merged bug-fix PRs reveals that most IronClaw bugs fall into a few recurring categories:
A review of all closed issues and merged bug-fix PRs reveals that most OptimClaw bugs fall into a few recurring categories:
| Category | Examples | Root Cause |
|----------|----------|------------|
@@ -54,7 +54,7 @@ fn all_tool_schemas_are_openai_strict_valid() {
}
```
Add the same validation for WASM tools (loaded from `~/.ironclaw/tools/`) and MCP tools (mock a simple MCP manifest and validate the schema it produces).
Add the same validation for WASM tools (loaded from `~/.optimclaw/tools/`) and MCP tools (mock a simple MCP manifest and validate the schema it produces).
**Files:** New `src/tools/schema_validator.rs` (validation logic), test in `tests/tool_schema_validation.rs`
@@ -124,7 +124,7 @@ docker-build:
steps:
- uses: actions/checkout@v6
- name: Build Docker image
run: docker build -t ironclaw-test:ci .
run: docker build -t optimclaw-test:ci .
```
**Files:** Modify `.github/workflows/test.yml`
@@ -318,7 +318,7 @@ async fn context_length_exceeded_triggers_compaction() {
## Tier 3: Computer-Use E2E Testing
**Cost:** High (requires Anthropic computer use API, headless browser, ironclaw running)
**Cost:** High (requires Anthropic computer use API, headless browser, optimclaw running)
**Timeline:** ~2 weeks for infrastructure, then incremental scenario additions
**Bugs this would have caught:** #307, #306, #263, all manual web-ui-test checklist items
@@ -326,7 +326,7 @@ async fn context_length_exceeded_triggers_compaction() {
```
+------------------+ +-----------------+ +------------------+
| Test Runner | | Headless | | IronClaw |
| Test Runner | | Headless | | OptimClaw |
| (Python/TS) |---->| Chromium |---->| (cargo run) |
| | | (Playwright) | | GATEWAY=true |
| Orchestrates | | | | port 3001 |
@@ -345,7 +345,7 @@ async fn context_length_exceeded_triggers_compaction() {
**Components:**
1. **Test runner** -- Python or TypeScript script that orchestrates the flow. Starts ironclaw, waits for readiness, launches Playwright browser, runs scenarios.
1. **Test runner** -- Python or TypeScript script that orchestrates the flow. Starts optimclaw, waits for readiness, launches Playwright browser, runs scenarios.
2. **Playwright browser** -- Headless Chromium. Takes screenshots, executes click/type actions as directed by the computer use agent. Also provides DOM access for structural assertions (element exists, text content matches, no error toasts).
@@ -362,7 +362,7 @@ async fn context_length_exceeded_triggers_compaction() {
```
tests/
e2e/
conftest.py # pytest fixtures: start ironclaw, browser
conftest.py # pytest fixtures: start optimclaw, browser
computer_use.py # Claude computer use client wrapper
assertions.py # DOM + visual assertion helpers
scenarios/
@@ -374,15 +374,15 @@ tests/
test_html_injection.py
test_tool_approval.py
screenshots/ # Reference screenshots (gitignored)
Dockerfile.test # Container for CI: ironclaw + chromium
Dockerfile.test # Container for CI: optimclaw + chromium
```
**Fixture: start ironclaw**
**Fixture: start optimclaw**
```python
@pytest.fixture(scope="session")
async def ironclaw_server():
"""Start ironclaw with gateway enabled, return base URL."""
async def optimclaw_server():
"""Start optimclaw with gateway enabled, return base URL."""
env = {
"CLI_ENABLED": "false",
"GATEWAY_ENABLED": "true",
@@ -409,12 +409,12 @@ async def ironclaw_server():
```python
@pytest.fixture
async def browser_agent(ironclaw_server):
async def browser_agent(optimclaw_server):
"""Playwright browser + Claude computer use agent."""
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page(viewport={"width": 1280, "height": 720})
await page.goto(f"{ironclaw_server}/?token=test-token-e2e")
await page.goto(f"{optimclaw_server}/?token=test-token-e2e")
agent = ComputerUseAgent(page)
yield agent
await browser.close()
@@ -533,7 +533,7 @@ async def test_chat_sends_and_receives(browser_agent):
#### Scenario 3: SSE Reconnect
```python
async def test_sse_reconnect_preserves_history(browser_agent, ironclaw_server):
async def test_sse_reconnect_preserves_history(browser_agent, optimclaw_server):
"""Bug: #307 (no re-sync on SSE reconnect after server restart)"""
page = browser_agent.page
@@ -546,7 +546,7 @@ async def test_sse_reconnect_preserves_history(browser_agent, ironclaw_server):
# Step 2: Kill and restart the server
# (test fixture provides a restart helper)
await restart_ironclaw(ironclaw_server)
await restart_optimclaw(optimclaw_server)
# Step 3: Wait for reconnect
await page.wait_for_selector(".connection-status.connected", timeout=30000)
@@ -623,20 +623,20 @@ async def test_tool_approval_overlay(browser_agent):
#### Scenario 7: Onboarding Wizard (Full Flow)
```python
async def test_onboarding_wizard_completes(tmp_ironclaw_home):
async def test_onboarding_wizard_completes(tmp_optimclaw_home):
"""Bugs: #187, #174, #129, #185 (wizard persistence and re-trigger)"""
# Start ironclaw with a fresh home directory (no prior config)
# Start optimclaw with a fresh home directory (no prior config)
# The wizard runs in TUI mode, so we need a PTY or use the web wizard
# if/when one exists. For now, test the CLI wizard via expect-style automation.
proc = pexpect.spawn(
"cargo run",
env={"IRONCLAW_HOME": str(tmp_ironclaw_home), **base_env},
env={"OPTIMCLAW_HOME": str(tmp_optimclaw_home), **base_env},
timeout=60,
)
# Step through wizard
proc.expect("Welcome to IronClaw")
proc.expect("Welcome to OptimClaw")
proc.expect("LLM Backend")
proc.sendline("1") # Select first option
# ... continue through all 7 steps ...
@@ -646,12 +646,12 @@ async def test_onboarding_wizard_completes(tmp_ironclaw_home):
# Restart and verify wizard does NOT re-trigger
proc2 = pexpect.spawn(
"cargo run",
env={"IRONCLAW_HOME": str(tmp_ironclaw_home), **base_env},
env={"OPTIMCLAW_HOME": str(tmp_optimclaw_home), **base_env},
timeout=30,
)
proc2.expect("Agent ironclaw ready") # Should skip wizard
# Must NOT see "Welcome to IronClaw" again
assert not proc2.match_any(["Welcome to IronClaw"], timeout=5)
proc2.expect("Agent optimclaw ready") # Should skip wizard
# Must NOT see "Welcome to OptimClaw" again
assert not proc2.match_any(["Welcome to OptimClaw"], timeout=5)
proc2.close()
```
@@ -687,7 +687,7 @@ jobs:
image: ollama/ollama:latest
steps:
- uses: actions/checkout@v6
- name: Build ironclaw
- name: Build optimclaw
run: cargo build --features libsql
- name: Install Playwright
run: pip install playwright pytest-playwright && playwright install chromium
@@ -2,7 +2,7 @@
**Date:** 2026-02-24
**Status:** Approved
**Goal:** Deterministic browser-level E2E tests for the IronClaw web gateway using Python + Playwright, with a mock LLM backend for CI reliability.
**Goal:** Deterministic browser-level E2E tests for the OptimClaw web gateway using Python + Playwright, with a mock LLM backend for CI reliability.
---
@@ -25,7 +25,7 @@
|
+----------+-----------+
| |
mock_llm.py ironclaw binary
mock_llm.py optimclaw binary
(canned responses) (cargo build --features libsql)
127.0.0.1:{port} 127.0.0.1:{port}
| |
@@ -39,12 +39,12 @@
**Flow:**
1. pytest session starts
2. Session-scoped fixture builds ironclaw binary (or reuses cached)
2. Session-scoped fixture builds optimclaw binary (or reuses cached)
3. Session-scoped fixture starts mock LLM on OS-assigned port
4. Session-scoped fixture starts ironclaw subprocess pointing to mock LLM, gateway on OS-assigned port, libSQL in-memory
4. Session-scoped fixture starts optimclaw subprocess pointing to mock LLM, gateway on OS-assigned port, libSQL in-memory
5. Function-scoped fixture launches Playwright browser, navigates to gateway with auth token
6. Each test uses Playwright locators + DOM assertions
7. Teardown kills ironclaw and mock LLM
7. Teardown kills optimclaw and mock LLM
---
@@ -52,7 +52,7 @@
```
tests/e2e/
conftest.py # pytest fixtures: build binary, start ironclaw, mock LLM, browser
conftest.py # pytest fixtures: build binary, start optimclaw, mock LLM, browser
mock_llm.py # OpenAI-compat HTTP server with canned responses
helpers.py # Shared utilities (wait_for_ready, selectors)
scenarios/
@@ -76,7 +76,7 @@ A minimal async HTTP server that speaks the OpenAI Chat Completions API.
- Parses the `messages` array from the request body
- Pattern-matches the last user message content to select a canned response
- Returns a well-formed `ChatCompletionResponse` with `id`, `choices[0].message`, `usage`
- Supports `stream: true` by returning SSE chunks with `delta` objects (critical: IronClaw streams responses via SSE to the browser)
- Supports `stream: true` by returning SSE chunks with `delta` objects (critical: OptimClaw streams responses via SSE to the browser)
**Canned response table:**
@@ -109,8 +109,8 @@ data: [DONE]
### Session-scoped (run once per test session)
**`ironclaw_binary`**
- Checks if `./target/debug/ironclaw` exists
**`optimclaw_binary`**
- Checks if `./target/debug/optimclaw` exists
- If missing or stale, runs `cargo build --no-default-features --features libsql`
- Returns the binary path
- Timeout: 300s (first build can be slow)
@@ -122,8 +122,8 @@ data: [DONE]
- Yields `(process, url)`
- Kills process on teardown
**`ironclaw_server(ironclaw_binary, mock_llm_server)`**
- Starts the ironclaw binary with environment:
**`optimclaw_server(optimclaw_binary, mock_llm_server)`**
- Starts the optimclaw binary with environment:
```
GATEWAY_ENABLED=true
@@ -143,14 +143,14 @@ ROUTINES_ENABLED=false
HEARTBEAT_ENABLED=false
```
- Parses actual gateway port from ironclaw stdout (`Gateway listening on 127.0.0.1:XXXX`)
- Parses actual gateway port from optimclaw stdout (`Gateway listening on 127.0.0.1:XXXX`)
- Polls `GET /api/status` until ready (timeout 60s)
- Yields the base URL (`http://127.0.0.1:{port}`)
- Sends SIGTERM on teardown, SIGKILL after 5s grace
### Function-scoped (fresh per test)
**`page(ironclaw_server)`**
**`page(optimclaw_server)`**
- Launches Playwright Chromium (headless)
- Creates new browser context (isolated cookies/storage)
- Creates new page with viewport 1280x720
@@ -248,7 +248,7 @@ test_skills_install_and_remove:
## Port Discovery
IronClaw logs `Gateway listening on 127.0.0.1:XXXX` at startup. The fixture reads stdout line-by-line until it finds this pattern, extracts the port.
OptimClaw logs `Gateway listening on 127.0.0.1:XXXX` at startup. The fixture reads stdout line-by-line until it finds this pattern, extracts the port.
```python
async def wait_for_port(process, pattern=r"Gateway listening on .+:(\d+)", timeout=60):
@@ -260,7 +260,7 @@ async def wait_for_port(process, pattern=r"Gateway listening on .+:(\d+)", timeo
)
if match := re.search(pattern, line.decode()):
return int(match.group(1))
raise TimeoutError("ironclaw did not report listening port")
raise TimeoutError("optimclaw did not report listening port")
```
Same pattern for the mock LLM server.
@@ -272,7 +272,7 @@ Same pattern for the mock LLM server.
```toml
# tests/e2e/pyproject.toml
[project]
name = "ironclaw-e2e"
name = "optimclaw-e2e"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
@@ -316,7 +316,7 @@ jobs:
with:
path: target
key: e2e-${{ hashFiles('Cargo.lock') }}
- name: Build ironclaw
- name: Build optimclaw
run: cargo build --no-default-features --features libsql
- uses: actions/setup-python@v5
with:
@@ -347,7 +347,7 @@ Not in initial scope. Design accommodates it via:
## Success Criteria
1. `pytest tests/e2e/ -v` passes locally with a pre-built ironclaw binary
1. `pytest tests/e2e/ -v` passes locally with a pre-built optimclaw binary
2. All 3 scenarios (connection, chat, skills) exercise real browser interactions
3. Mock LLM provides deterministic responses (no flaky tests from LLM randomness)
4. CI workflow runs on web gateway changes and weekly schedule
+29 -29
View File
@@ -2,9 +2,9 @@
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Build a Python + Playwright E2E testing framework that exercises the IronClaw web gateway through a real browser against the real binary with a mock LLM backend.
**Goal:** Build a Python + Playwright E2E testing framework that exercises the OptimClaw web gateway through a real browser against the real binary with a mock LLM backend.
**Architecture:** pytest session fixtures start a mock OpenAI-compat HTTP server and the ironclaw binary (libSQL in-memory, gateway enabled), then per-test Playwright browser instances navigate to the gateway and make DOM assertions.
**Architecture:** pytest session fixtures start a mock OpenAI-compat HTTP server and the optimclaw binary (libSQL in-memory, gateway enabled), then per-test Playwright browser instances navigate to the gateway and make DOM assertions.
**Tech Stack:** Python 3.11+, pytest, pytest-asyncio, playwright, aiohttp
@@ -22,7 +22,7 @@
```toml
[project]
name = "ironclaw-e2e"
name = "optimclaw-e2e"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
@@ -78,7 +78,7 @@ The server must:
- Handle `POST /v1/chat/completions` with both streaming and non-streaming modes
- Handle `GET /v1/models` for health checks
- Pattern-match the last user message to select canned responses
- Support `stream: true` with proper SSE chunk format (critical for IronClaw's streaming)
- Support `stream: true` with proper SSE chunk format (critical for OptimClaw's streaming)
```python
"""Mock OpenAI-compatible LLM server for E2E tests."""
@@ -340,7 +340,7 @@ git commit -m "feat: E2E helpers with DOM selectors and port discovery"
**Step 1: Write the fixtures**
Key details from codebase research:
- IronClaw logs `Web UI: http://{host}:{port}/` to stdout (main.rs:508) using the config port, not the bound port. So we must use a fixed port, not port 0.
- OptimClaw logs `Web UI: http://{host}:{port}/` to stdout (main.rs:508) using the config port, not the bound port. So we must use a fixed port, not port 0.
- Health endpoint: `GET /api/health` (public, no auth required)
- Auth via `?token=` query parameter for the frontend auto-auth flow
- The frontend hides `#auth-screen` when token is valid and SSE connects
@@ -348,7 +348,7 @@ Key details from codebase research:
```python
"""pytest fixtures for E2E tests.
Session-scoped: build binary, start mock LLM, start ironclaw.
Session-scoped: build binary, start mock LLM, start optimclaw.
Function-scoped: fresh Playwright browser page per test.
"""
@@ -372,11 +372,11 @@ GATEWAY_PORT = 18_200
@pytest.fixture(scope="session")
def ironclaw_binary():
"""Ensure ironclaw binary is built. Returns the binary path."""
binary = ROOT / "target" / "debug" / "ironclaw"
def optimclaw_binary():
"""Ensure optimclaw binary is built. Returns the binary path."""
binary = ROOT / "target" / "debug" / "optimclaw"
if not binary.exists():
print("Building ironclaw (this may take a while)...")
print("Building optimclaw (this may take a while)...")
subprocess.run(
["cargo", "build", "--no-default-features", "--features", "libsql"],
cwd=ROOT,
@@ -418,11 +418,11 @@ async def mock_llm_server():
@pytest.fixture(scope="session")
async def ironclaw_server(ironclaw_binary, mock_llm_server):
"""Start the ironclaw gateway. Yields the base URL."""
async def optimclaw_server(optimclaw_binary, mock_llm_server):
"""Start the optimclaw gateway. Yields the base URL."""
env = {
**os.environ,
"RUST_LOG": "ironclaw=info",
"RUST_LOG": "optimclaw=info",
"GATEWAY_ENABLED": "true",
"GATEWAY_HOST": "127.0.0.1",
"GATEWAY_PORT": str(GATEWAY_PORT),
@@ -443,7 +443,7 @@ async def ironclaw_server(ironclaw_binary, mock_llm_server):
"ONBOARD_COMPLETED": "true",
}
proc = await asyncio.create_subprocess_exec(
ironclaw_binary,
optimclaw_binary,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
env=env,
@@ -461,7 +461,7 @@ async def ironclaw_server(ironclaw_binary, mock_llm_server):
@pytest.fixture
async def page(ironclaw_server):
async def page(optimclaw_server):
"""Fresh Playwright browser page, navigated to the gateway with auth."""
from playwright.async_api import async_playwright
@@ -469,7 +469,7 @@ async def page(ironclaw_server):
browser = await p.chromium.launch(headless=True)
context = await browser.new_context(viewport={"width": 1280, "height": 720})
pg = await context.new_page()
await pg.goto(f"{ironclaw_server}/?token={AUTH_TOKEN}")
await pg.goto(f"{optimclaw_server}/?token={AUTH_TOKEN}")
# Wait for the app to initialize (auth screen hidden, SSE connected)
await pg.wait_for_selector("#auth-screen", state="hidden", timeout=15000)
yield pg
@@ -481,7 +481,7 @@ async def page(ironclaw_server):
```bash
git add tests/e2e/conftest.py
git commit -m "feat: E2E conftest with session fixtures for mock LLM and ironclaw"
git commit -m "feat: E2E conftest with session fixtures for mock LLM and optimclaw"
```
---
@@ -529,23 +529,23 @@ async def test_tab_navigation(page):
await chat_input.wait_for(state="visible", timeout=5000)
async def test_auth_rejection(page, ironclaw_server):
async def test_auth_rejection(page, optimclaw_server):
"""Navigating without a token shows the auth screen."""
# Open a new page without the token
new_page = await page.context.new_page()
await new_page.goto(ironclaw_server)
await new_page.goto(optimclaw_server)
auth_screen = new_page.locator(SEL["auth_screen"])
await auth_screen.wait_for(state="visible", timeout=10000)
await new_page.close()
```
**Step 2: Verify test runs (may fail if ironclaw isn't built yet -- that's OK)**
**Step 2: Verify test runs (may fail if optimclaw isn't built yet -- that's OK)**
```bash
cd tests/e2e && python -m pytest scenarios/test_connection.py -v --timeout=120
```
Expected: Tests pass if ironclaw is built, or skip/fail gracefully if not.
Expected: Tests pass if optimclaw is built, or skip/fail gracefully if not.
**Step 3: Commit**
@@ -789,7 +789,7 @@ jobs:
~/.cargo/registry
key: e2e-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
- name: Build ironclaw (libsql)
- name: Build optimclaw (libsql)
run: cargo build --no-default-features --features libsql
- uses: actions/setup-python@v5
@@ -831,14 +831,14 @@ git commit -m "ci: add weekly E2E test workflow with Playwright"
**Step 1: Write the README**
```markdown
# IronClaw E2E Tests
# OptimClaw E2E Tests
Browser-level end-to-end tests for the IronClaw web gateway using Python + Playwright.
Browser-level end-to-end tests for the OptimClaw web gateway using Python + Playwright.
## Prerequisites
- Python 3.11+
- Rust toolchain (for building ironclaw)
- Rust toolchain (for building optimclaw)
- Chromium (installed via Playwright)
## Setup
@@ -849,9 +849,9 @@ pip install -e .
playwright install chromium
```
## Build ironclaw
## Build optimclaw
The tests need the ironclaw binary built with libsql support:
The tests need the optimclaw binary built with libsql support:
```bash
cargo build --no-default-features --features libsql
@@ -874,7 +874,7 @@ HEADED=1 pytest tests/e2e/scenarios/test_connection.py -v
Tests start two subprocesses:
1. **Mock LLM** (`mock_llm.py`) -- fake OpenAI-compat server with canned responses
2. **IronClaw** -- the real binary with gateway enabled, pointing to the mock LLM
2. **OptimClaw** -- the real binary with gateway enabled, pointing to the mock LLM
Then Playwright drives a headless Chromium browser against the gateway, making DOM assertions.
@@ -905,7 +905,7 @@ git commit -m "docs: E2E test README with setup and usage instructions"
### Task 10: Integration test -- run all scenarios end-to-end
**Step 1: Build ironclaw**
**Step 1: Build optimclaw**
```bash
cargo build --no-default-features --features libsql
+1 -1
View File
@@ -1,4 +1,4 @@
# Smart Model Routing for IronClaw
# Smart Model Routing for OptimClaw
**Status:** Implemented
**Author:** Microwave