mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-09-01 09:09:19 +00:00
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:
+11
-11
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
# Smart Model Routing for IronClaw
|
||||
# Smart Model Routing for OptimClaw
|
||||
|
||||
**Status:** Implemented
|
||||
**Author:** Microwave
|
||||
|
||||
Reference in New Issue
Block a user