Compare commits

..
Author SHA1 Message Date
[email protected]andClaude Opus 4.6 3bae87b04e feat(frontend): hash-based URL navigation for page refresh persistence
Navigation state is now encoded in window.location.hash so refreshing
the page (or sharing a URL) restores the current view:

  #/chat                   → chat tab, assistant thread
  #/chat/{threadId}        → specific conversation
  #/memory/{path/to/file}  → memory browser with file open
  #/jobs/{jobId}           → job detail view
  #/routines/{id}          → routine detail view
  #/settings/{subtab}      → settings sub-tab (extensions, etc.)
  #/logs                   → logs tab

Hooked into all navigation functions: switchTab, switchThread,
switchToAssistant, createNewThread, readMemoryFile, openJobDetail,
closeJobDetail, openRoutineDetail, closeRoutineDetail,
switchSettingsSubtab.

Thread restore is deferred until loadThreads() completes (async),
then the pending thread ID is matched against the loaded thread list.

Browser back/forward buttons work via hashchange listener.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-28 14:31:16 -07:00
[email protected]andClaude Opus 4.6 c47c82bf79 feat(frontend): structured data cards + chat renderer API for rich message rendering
Agent responses containing JSON/structured data (like mission results,
status objects) now render as styled cards with labeled fields, status
badges, and monospaced IDs instead of raw text.

Built-in rendering:
- Detects inline JSON objects (including Python-style single quotes)
- Renders as data cards with key-value rows
- Status/state fields get colored badges (success/error/pending)
- UUIDs rendered in monospace

Extensible via widgets:
- IronClaw.registerChatRenderer({ id, match, render, priority })
- First matching renderer wins (priority ordering)
- Renderer gets the content element to mutate in place

Also adds ChatRenderer variant to WidgetSlot enum.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-28 14:04:11 -07:00
[email protected]andClaude Opus 4.6 fc32a31515 fix: address CI failures — license, rust-version, formatting, manifest warnings
- Add license = "MIT OR Apache-2.0" to ironclaw_frontend Cargo.toml (cargo-deny)
- Fix rust-version to 1.92 to match other crates
- Log warning for invalid widget manifests instead of silent skip
- Run cargo fmt across all files

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-28 12:27:41 -07:00
[email protected]andClaude Opus 4.6 c54c8e0fb5 feat(frontend): extract frontend into ironclaw_frontend crate with widget extension system
Moves all frontend static assets (app.js, style.css, index.html, i18n/*,
theme-init.js, favicon.ico) from src/channels/web/static/ into a dedicated
ironclaw_frontend crate. The crate also adds:

- Layout configuration types (branding, tab order, chat features, per-widget config)
- Widget manifest types with named slot system (tab, chat_header, sidebar, etc.)
- CSS scoping utility (auto-prefixes selectors with [data-widget="id"])
- Bundle assembly (injects layout config, widgets, and custom CSS into HTML)
- Frontend API endpoints (GET/PUT layout, list widgets, serve widget files)
- Browser-side IronClaw.registerWidget() API with authenticated fetch,
  event subscription, theme access, and i18n

Widgets are stored in workspace at frontend/widgets/{id}/ and served via
the API. Layout config is stored at frontend/layout.json. The agent can
create/edit both using existing memory_write/memory_read tools.

Gateway handlers now reference ironclaw_frontend::assets constants instead
of include_str!() with local paths, completing the separation.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-28 12:27:41 -07:00
[email protected] ebdae267a0 Merge remote-tracking branch 'origin/staging' into feat/workspace-metadata-versioning-patch
# Conflicts:
#	src/agent/heartbeat.rs
#	src/db/libsql_migrations.rs
2026-03-28 12:27:02 -07:00
[email protected]andClaude Opus 4.6 cb4cf123c6 fix: address review feedback — transaction safety, patch mode, formatting
- Wrap libSQL save_version in a transaction to prevent race condition
  where concurrent writers could allocate the same version number
- Make content optional in memory_write when in patch mode (old_string
  present) — LLM no longer forced to provide unused content param
- Improve metadata update error handling with explicit match arms
- Run cargo fmt across all files

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-28 12:12:41 -07:00
[email protected]andClaude Opus 4.6 7532065590 feat(workspace): metadata-driven indexing/hygiene, document versioning, and patch support
Foundation for the extensible frontend system. Workspace documents now
support metadata flags (skip_indexing, skip_versioning, hygiene config)
via folder-level .config documents and per-file overrides, replacing
hardcoded hygiene targets and indexing behavior.

Key changes:
- DocumentMetadata type with resolution chain (doc → folder .config → defaults)
- Document versioning: auto-saves previous content on write/append/patch
- Workspace patch: search-and-replace editing via memory_write tool
- Hygiene rewrite: discovers cleanup targets from .config metadata
  instead of hardcoded daily/ and conversations/ directories
- memory_read gains version/list_versions params
- memory_write gains metadata/old_string/new_string/replace_all params
- V14 migration adds memory_document_versions table (both PG + libSQL)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-28 00:28:32 -07:00
468 changed files with 7022 additions and 11736 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ argument-hint: <event_name> [description]
model: opus
---
Add a new SSE event called `$ARGUMENTS` to the OptimClaw web gateway. This involves changes across 5 files in a specific order. Follow each step exactly.
Add a new SSE event called `$ARGUMENTS` to the IronClaw web gateway. This involves changes across 5 files in a specific order. Follow each step exactly.
## Step 1: Add `StatusUpdate` variant
+2 -2
View File
@@ -5,7 +5,7 @@ argument-hint: <tool_name> [description]
model: opus
---
Scaffold a new tool called `$ARGUMENTS` for the OptimClaw agent. First, determine the tool type and then follow the appropriate path.
Scaffold a new tool called `$ARGUMENTS` for the IronClaw agent. First, determine the tool type and then follow the appropriate path.
## Step 0: Determine tool type
@@ -43,7 +43,7 @@ Follow this exact pattern (adjust name and description):
name = "<name>-tool"
version = "0.1.0"
edition = "2021"
description = "<Description> tool for OptimClaw (WASM component)"
description = "<Description> tool for IronClaw (WASM component)"
license = "MIT OR Apache-2.0"
publish = false
+2 -2
View File
@@ -62,7 +62,7 @@ Enter planning mode to design the implementation. The plan MUST cover:
- Happy path (expected input produces expected output)
- Error paths (invalid input, missing data, permission denied)
- Edge cases (empty collections, boundary values, concurrent access)
5. **OptimClaw-specific concerns**:
5. **IronClaw-specific concerns**:
- If the change touches persistence, both database backends must be updated (`postgres.rs` and `libsql_backend.rs`)
- New `Database` trait methods need implementations in both backends
- No `.unwrap()` or `.expect()` in production code
@@ -80,7 +80,7 @@ After the plan is approved:
1. Implement each change from the plan.
2. Write all planned tests.
3. Run OptimClaw's full quality gate:
3. Run IronClaw's full quality gate:
- `cargo fmt`
- `cargo clippy --all --benches --tests --examples --all-features` (zero warnings)
- `cargo test --lib` (all tests pass)
+3 -3
View File
@@ -96,7 +96,7 @@ Wait for user confirmation (unless `--fix` flag set), then proceed to Phase 3.
Read EVERY changed file in full (not just diff hunks). For PRs touching >20 files, prioritize: service logic > handlers > types > tests > docs. Batch reads in parallel via Agent tool.
### OptimClaw-specific checks (always)
### IronClaw-specific checks (always)
- No `.unwrap()` or `.expect()` in production code
- Prefer `crate::` for cross-module imports (`super::` OK in tests/intra-module)
- Error types use `thiserror`
@@ -167,7 +167,7 @@ gh pr checkout {number}
1. All approved review comment fixes (from Phase 2a)
2. All approved review findings (from Phase 2b)
Follow OptimClaw conventions:
Follow IronClaw conventions:
- `thiserror` for errors
- `crate::` imports
- No `.unwrap()` in production
@@ -180,7 +180,7 @@ After all fixes implemented, proceed to Phase 4.
## Phase 4: Quality Gate
Run the full OptimClaw shipping checklist:
Run the full IronClaw shipping checklist:
```bash
cargo fmt
+2 -2
View File
@@ -60,7 +60,7 @@ Wait for user confirmation before proceeding to implementation.
After user confirms:
1. Implement each fix in the plan.
2. Run OptimClaw's quality gate to verify nothing breaks:
2. Run IronClaw's quality gate to verify nothing breaks:
- `cargo fmt`
- `cargo clippy --all --benches --tests --examples --all-features`
- `cargo test --lib`
@@ -77,5 +77,5 @@ For each comment addressed, reply on the PR with a short message stating what wa
- Group duplicate comments (same issue reported by multiple bots) and reply to all of them.
- Do not make changes beyond what the review comments ask for. Stay focused.
- If a comment suggests a change you disagree with, present your reasoning to the user during the planning phase rather than silently ignoring it.
- Follow OptimClaw conventions: no `.unwrap()` in production code, use `crate::` imports, `thiserror` errors.
- Follow IronClaw conventions: no `.unwrap()` in production code, use `crate::` imports, `thiserror` errors.
- If changes touch persistence, verify both database backends are updated.
+6 -6
View File
@@ -1,5 +1,5 @@
---
description: Deep audit of the OptimClaw crate for vulnerabilities, bugs, unfinished work, inconsistencies, and oversights
description: Deep audit of the IronClaw crate for vulnerabilities, bugs, unfinished work, inconsistencies, and oversights
disable-model-invocation: true
allowed-tools: Bash(cargo fmt:*), Bash(cargo clippy:*), Bash(cargo test:*), Bash(cargo audit:*), Bash(git diff:*), Bash(git log:*), Bash(git show:*), Bash(wc:*), Read, Grep, Glob, Task
argument-hint: "[path/to/crate]"
@@ -93,7 +93,7 @@ Search for `.unwrap()`, `.expect(`, `panic!`, `unreachable!` in non-test code. F
- Is there a code path that reaches this with None/Err?
- Should it be replaced with proper error handling (`?`, `.ok()`, `.unwrap_or_default()`)?
OptimClaw convention: `.unwrap()` and `.expect()` are banned in production code. Any occurrence outside `#[cfg(test)]` blocks is a **High severity** finding.
IronClaw convention: `.unwrap()` and `.expect()` are banned in production code. Any occurrence outside `#[cfg(test)]` blocks is a **High severity** finding.
### 5c. SQL and injection vectors
@@ -102,7 +102,7 @@ Search for string formatting used in SQL queries, shell commands, or HTML:
- String interpolation in query construction vs parameterized queries
- User input flowing into file paths (`Path::new`, `std::fs::`)
OptimClaw has two database backends (PostgreSQL and libSQL). Check both for injection vectors.
IronClaw has two database backends (PostgreSQL and libSQL). Check both for injection vectors.
### 5d. Cryptographic issues
@@ -125,7 +125,7 @@ If the crate uses crypto:
- Are errors swallowed silently? (`let _ = ...`, `.ok()` discarding errors that matter)
- Do error types carry enough context to debug in production?
- Are there error type mismatches? (returning generic `anyhow::Error` where a typed error would prevent confusion)
- Is `thiserror` used consistently for error types (OptimClaw convention)?
- Is `thiserror` used consistently for error types (IronClaw convention)?
## Step 6: Check for inconsistencies
@@ -156,7 +156,7 @@ Look for:
### 6e. Import style
OptimClaw convention: use `crate::` imports, not `super::`. Flag any `super::` imports in non-test code.
IronClaw convention: use `crate::` imports, not `super::`. Flag any `super::` imports in non-test code.
## Step 7: Inspect for change oversights
@@ -172,7 +172,7 @@ OptimClaw convention: use `crate::` imports, not `super::`. Flag any `super::` i
- Are there `impl` blocks that look incomplete?
- Are `Default` implementations sensible?
OptimClaw key traits: `Database` (~60 methods), `Channel`, `Tool`, `LlmProvider`, `SuccessEvaluator`, `EmbeddingProvider`. If any new methods were added to `Database`, verify both `postgres.rs` and `libsql_backend.rs` implement them.
IronClaw key traits: `Database` (~60 methods), `Channel`, `Tool`, `LlmProvider`, `SuccessEvaluator`, `EmbeddingProvider`. If any new methods were added to `Database`, verify both `postgres.rs` and `libsql_backend.rs` implement them.
### 7c. Test coverage gaps
+2 -2
View File
@@ -49,9 +49,9 @@ If the PR touches more than 20 files, still read all of them, but process in thi
Go through the changes with each of these lenses. For every finding, note the file, line range, severity, and a concrete description.
### OptimClaw-specific checks
### IronClaw-specific checks
In addition to the general lenses below, check OptimClaw conventions (see CLAUDE.md):
In addition to the general lenses below, check IronClaw conventions (see CLAUDE.md):
- No `.unwrap()` or `.expect()` in production code (tests are fine)
- Use `crate::` imports, not `super::`
- Error types use `thiserror` in `error.rs`
+1 -1
View File
@@ -3,7 +3,7 @@ description: Run the full Rust quality gate (fmt, clippy, tests) before shipping
allowed-tools: Bash(cargo fmt:*), Bash(cargo clippy:*), Bash(cargo test:*)
---
Run the OptimClaw shipping checklist. This is the mandatory quality gate before any change is considered done.
Run the IronClaw shipping checklist. This is the mandatory quality gate before any change is considered done.
## Steps
+3 -3
View File
@@ -1,15 +1,15 @@
---
description: Trace a data flow or bug through the OptimClaw codebase end-to-end
description: Trace a data flow or bug through the IronClaw codebase end-to-end
allowed-tools: Read, Glob, Grep, Bash(cargo test:*)
argument-hint: <symptom or feature name>
model: sonnet
---
Trace the flow of `$ARGUMENTS` through the OptimClaw codebase. Your job is to map every file and function involved, identify where data transforms or could break, and report the full chain.
Trace the flow of `$ARGUMENTS` through the IronClaw codebase. Your job is to map every file and function involved, identify where data transforms or could break, and report the full chain.
## Architecture Reference
OptimClaw has three main data flow paths. Identify which one(s) are relevant and trace through them:
IronClaw has three main data flow paths. Identify which one(s) are relevant and trace through them:
### Message Flow (user input to LLM response)
```
+2 -2
View File
@@ -11,8 +11,8 @@ SKILL.md files extend the agent's prompt with domain-specific instructions. Each
| Trust Level | Source | Tool Access |
|-------------|--------|-------------|
| **Trusted** | User-placed in `~/.optimclaw/skills/` or workspace `skills/` | All tools available to the agent |
| **Installed** | Downloaded from ClawHub registry (`~/.optimclaw/installed_skills/`) | Read-only tools only (no shell, file write, HTTP) |
| **Trusted** | User-placed in `~/.ironclaw/skills/` or workspace `skills/` | All tools available to the agent |
| **Installed** | Downloaded from ClawHub registry (`~/.ironclaw/installed_skills/`) | Read-only tools only (no shell, file write, HTTP) |
## SKILL.md Format
+1 -1
View File
@@ -7,7 +7,7 @@ paths:
**Keep tool-specific logic out of the main agent codebase.** The main agent provides generic infrastructure; tools are self-contained units that declare requirements through `<name>.capabilities.json` sidecar files (in dev mode: `tools-src/<name>/<name>-tool.capabilities.json`).
Tools can be WASM (sandboxed, credential-injected, single binary) or MCP servers (ecosystem, any language, no sandbox). Both are first-class via `optimclaw tool install`.
Tools can be WASM (sandboxed, credential-injected, single binary) or MCP servers (ecosystem, any language, no sandbox). Both are first-class via `ironclaw tool install`.
See `src/tools/README.md` for full architecture, adding new tools, auth JSON examples, and WASM vs MCP decision guide.
+17 -18
View File
@@ -1,5 +1,5 @@
# Database Configuration
DATABASE_URL=postgres://localhost/optimclaw
DATABASE_URL=postgres://localhost/ironclaw
DATABASE_POOL_SIZE=10
# LLM Provider
@@ -26,19 +26,19 @@ DATABASE_POOL_SIZE=10
# === GitHub Copilot ===
# Uses the OAuth token from your Copilot IDE sign-in (for example
# ~/.config/github-copilot/apps.json on Linux/macOS), or run `optimclaw onboard`
# ~/.config/github-copilot/apps.json on Linux/macOS), or run `ironclaw onboard`
# and choose the GitHub device login flow.
# LLM_BACKEND=github_copilot
# GITHUB_COPILOT_TOKEN=gho_...
# GITHUB_COPILOT_MODEL=gpt-4o
# OptimClaw injects standard VS Code Copilot headers automatically.
# IronClaw injects standard VS Code Copilot headers automatically.
# Optional advanced headers for custom overrides:
# GITHUB_COPILOT_EXTRA_HEADERS=Copilot-Integration-Id:vscode-chat
# === NEAR AI (Chat Completions API) ===
# Two auth modes:
# 1. Session token (default): Uses browser OAuth (GitHub/Google) on first run.
# Session token stored in ~/.optimclaw/session.json automatically.
# Session token stored in ~/.ironclaw/session.json automatically.
# Base URL defaults to https://private.near.ai
# 2. API key: Set NEARAI_API_KEY to use API key auth from cloud.near.ai.
# Base URL defaults to https://cloud-api.near.ai
@@ -46,7 +46,7 @@ NEARAI_MODEL=Qwen/Qwen3.5-122B-A10B
NEARAI_BASE_URL=https://private.near.ai
NEARAI_AUTH_URL=https://private.near.ai
# NEARAI_SESSION_TOKEN=sess_... # hosting providers: set this
# NEARAI_SESSION_PATH=~/.optimclaw/session.json # optional, default shown
# NEARAI_SESSION_PATH=~/.ironclaw/session.json # optional, default shown
# NEARAI_API_KEY=... # API key from cloud.near.ai
# Local LLM Providers (Ollama, LM Studio, vLLM, LiteLLM)
@@ -63,7 +63,7 @@ NEARAI_AUTH_URL=https://private.near.ai
# LLM_API_KEY=sk-... # optional for local servers
# Custom HTTP headers for OpenAI-compatible providers
# Format: comma-separated key:value pairs
# LLM_EXTRA_HEADERS=HTTP-Referer:https://github.com/nearai/optimclaw,X-Title:optimclaw
# LLM_EXTRA_HEADERS=HTTP-Referer:https://github.com/nearai/ironclaw,X-Title:ironclaw
# === OpenRouter (300+ models via OpenAI-compatible) ===
# LLM_MODEL=anthropic/claude-sonnet-4 # see openrouter.ai/models for IDs
@@ -145,7 +145,7 @@ HTTP_HOST=0.0.0.0
HTTP_PORT=8080
HTTP_WEBHOOK_SECRET=your-webhook-secret
# Webhook authentication uses HMAC-SHA256 signature verification.
# Callers must send an X-OptimClaw-Signature header with format: sha256=<hex_digest>
# Callers must send an X-IronClaw-Signature header with format: sha256=<hex_digest>
# where the digest is HMAC-SHA256(HTTP_WEBHOOK_SECRET, raw_request_body) in lowercase hex.
#
# Example (bash):
@@ -153,7 +153,7 @@ HTTP_WEBHOOK_SECRET=your-webhook-secret
# SIG=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$HTTP_WEBHOOK_SECRET" | cut -d' ' -f2)
# curl -X POST http://localhost:8080/webhook \
# -H "Content-Type: application/json" \
# -H "X-OptimClaw-Signature: sha256=$SIG" \
# -H "X-IronClaw-Signature: sha256=$SIG" \
# -d "$BODY"
#
# DEPRECATED: Passing "secret" in the JSON body still works but will be removed in a future release.
@@ -170,7 +170,7 @@ HTTP_WEBHOOK_SECRET=your-webhook-secret
# SIGNAL_IGNORE_STORIES=true
# Agent Settings
AGENT_NAME=optimclaw
AGENT_NAME=ironclaw
AGENT_MAX_PARALLEL_JOBS=5
AGENT_JOB_TIMEOUT_SECS=3600
AGENT_STUCK_THRESHOLD_SECS=300
@@ -191,10 +191,9 @@ HEARTBEAT_NOTIFY_CHANNEL=cli
HEARTBEAT_NOTIFY_USER=default
# Memory hygiene settings (automatic cleanup of stale workspace documents)
# Runs on each heartbeat tick; identity files (IDENTITY.md, SOUL.md) are never deleted
# Runs on each heartbeat tick; discovers cleanup targets from .config metadata
# MEMORY_HYGIENE_ENABLED=true
# MEMORY_HYGIENE_DAILY_RETENTION_DAYS=30 # delete daily/ docs older than this many days
# MEMORY_HYGIENE_CONVERSATION_RETENTION_DAYS=7 # delete conversations/ docs older than this many days
# MEMORY_HYGIENE_VERSION_KEEP_COUNT=50 # max versions to keep per document
# MEMORY_HYGIENE_CADENCE_HOURS=12 # minimum hours between cleanup passes
# Docker Sandbox
@@ -205,7 +204,7 @@ HEARTBEAT_NOTIFY_USER=default
# # commands directly on the host. Without this
# # set to "true", full_access is downgraded to
# # workspace_write.
# SANDBOX_IMAGE=optimclaw-worker:latest
# SANDBOX_IMAGE=ironclaw-worker:latest
# SANDBOX_TIMEOUT_SECS=120
# SANDBOX_MEMORY_LIMIT_MB=2048
@@ -214,11 +213,11 @@ SAFETY_MAX_OUTPUT_LENGTH=100000
SAFETY_INJECTION_CHECK_ENABLED=true
# Restart Feature (Docker containers only)
# Set OPTIMCLAW_IN_DOCKER=true in the container entrypoint to enable the restart feature.
# Set IRONCLAW_IN_DOCKER=true in the container entrypoint to enable the restart feature.
# Without this, the restart tool and /restart command will be disabled.
# OPTIMCLAW_IN_DOCKER=false
# OPTIMCLAW_RESTART_DELAY=5 # default wait before exit (seconds, range: 1-30)
# OPTIMCLAW_MAX_FAILURES=10 # max consecutive failures before container exits
# IRONCLAW_IN_DOCKER=false
# IRONCLAW_RESTART_DELAY=5 # default wait before exit (seconds, range: 1-30)
# IRONCLAW_MAX_FAILURES=10 # max consecutive failures before container exits
# Logging
RUST_LOG=optimclaw=debug,tower_http=debug
RUST_LOG=ironclaw=debug,tower_http=debug
+5 -12
View File
@@ -6,7 +6,7 @@
## Change Type
<!-- Check all that apply. Refactor-only PRs are for core team or maintainer-requested work. -->
<!-- Check one -->
- [ ] Bug fix
- [ ] New feature
@@ -18,19 +18,16 @@
## Linked Issue
<!-- Closes #N, Fixes #N, Related #N, or "None". New feature PRs must link an approved issue. -->
<!-- Closes #N, or "None" -->
## Validation
<!-- How did you verify this works? -->
- [ ] `cargo fmt --all -- --check`
- [ ] `cargo clippy --all --benches --tests --examples --all-features -- -D warnings`
- [ ] `cargo build`
- [ ] `cargo fmt`
- [ ] `cargo clippy --all --benches --tests --examples --all-features`
- [ ] Relevant tests pass: <!-- list specific tests -->
- [ ] `cargo test --features integration` if database-backed or integration behavior changed
- [ ] Manual testing: <!-- describe what you tested -->
- [ ] If a coding agent was used and supports it, `review-pr` or `pr-shepherd --fix` was run before requesting review
## Security Impact
@@ -48,10 +45,6 @@
<!-- How to revert if this causes problems? For Track C changes, this is mandatory. -->
## Review Follow-Through
<!-- Review conversations are author-owned. Summarize any known follow-up or areas where reviewer judgment is still needed. -->
---
**Review track**: <!-- A (docs/tests/chore) | B (feature/maintainer-requested refactor) | C (security/runtime/DB/CI) -->
**Review track**: <!-- A (docs/tests/chore) | B (feature/refactor) | C (security/runtime/DB/CI) -->
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
allowed_bots: "optimclaw-ci[bot]"
allowed_bots: "ironclaw-ci[bot]"
claude_args: "--max-turns 50 --model claude-haiku-4-5-20251001 --allowedTools 'Read,Glob,Grep,Agent,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(gh issue view:*),Bash(gh issue list:*),Bash(gh search:*),Bash(git blame:*),Bash(git log:*),Bash(git diff:*)'"
prompt: |
Code review this pull request. Follow these steps precisely:
+6 -6
View File
@@ -6,11 +6,11 @@
# What it does:
# - Runs unit and integration tests with coverage instrumentation
# - Runs E2E tests with coverage instrumentation
# - Uploads coverage reports to Codecov (https://codecov.io/gh/nearai/optimclaw)
# - Uploads coverage reports to Codecov (https://codecov.io/gh/nearai/ironclaw)
#
# Viewing coverage reports:
# - PRs automatically get coverage comments showing changes in coverage
# - Visit https://codecov.io/gh/nearai/optimclaw for detailed coverage reports
# - Visit https://codecov.io/gh/nearai/ironclaw for detailed coverage reports
# - Coverage reports are generated for three configurations:
# 1. all-features: Full feature set
# 2. default: Default features
@@ -58,7 +58,7 @@ jobs:
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: optimclaw_test
POSTGRES_DB: ironclaw_test
ports:
- 5432:5432
options: >-
@@ -103,11 +103,11 @@ jobs:
PGHOST: localhost
PGUSER: postgres
PGPASSWORD: postgres
PGDATABASE: optimclaw_test
PGDATABASE: ironclaw_test
- name: Set DATABASE_URL for postgres configs
if: matrix.has_postgres
run: echo "DATABASE_URL=postgres://postgres:postgres@localhost/optimclaw_test" >> "$GITHUB_ENV"
run: echo "DATABASE_URL=postgres://postgres:postgres@localhost/ironclaw_test" >> "$GITHUB_ENV"
- name: Generate coverage
run: cargo llvm-cov ${{ matrix.flags }} --workspace --lcov --output-path lcov.info
@@ -176,7 +176,7 @@ jobs:
run: |
pytest tests/e2e/ -v --timeout=120
env:
RUST_LOG: optimclaw=info
RUST_LOG: ironclaw=info
RUST_BACKTRACE: "1"
- name: Verify profraw files exist
+5 -5
View File
@@ -14,7 +14,7 @@ on:
jobs:
# ── Step 1: compile once ──────────────────────────────────────────────────
build:
name: Build optimclaw (libsql)
name: Build ironclaw (libsql)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
@@ -35,8 +35,8 @@ jobs:
- name: Upload binary
uses: actions/upload-artifact@v4
with:
name: optimclaw-e2e-binary
path: target/debug/optimclaw
name: ironclaw-e2e-binary
path: target/debug/ironclaw
retention-days: 1
# ── Step 2: run test slices in parallel ───────────────────────────────────
@@ -63,11 +63,11 @@ jobs:
- name: Download binary
uses: actions/download-artifact@v4
with:
name: optimclaw-e2e-binary
name: ironclaw-e2e-binary
path: target/debug/
- name: Make binary executable
run: chmod +x target/debug/optimclaw
run: chmod +x target/debug/ironclaw
- uses: actions/setup-python@v5
with:
+1 -1
View File
@@ -56,7 +56,7 @@ jobs:
"src/agent/self_repair.rs"
"src/agent/agentic_loop.rs"
"src/tools/execute.rs"
"crates/optimclaw_safety/src/"
"crates/ironclaw_safety/src/"
)
for pattern in "${HIGH_RISK_PATTERNS[@]}"; do
+2 -2
View File
@@ -166,7 +166,7 @@ jobs:
continue
fi
name=$(echo "$filename" | sed "s/^${kind}-//" | sed 's/-[0-9].*-wasm32-wasip2\.tar\.gz$//')
url="https://github.com/nearai/optimclaw/releases/download/${RELEASE_TAG}/${filename}"
url="https://github.com/nearai/ironclaw/releases/download/${RELEASE_TAG}/${filename}"
manifest="registry/${kind}s/${name}.json"
if [ -f "$manifest" ]; then
@@ -496,7 +496,7 @@ jobs:
continue
fi
name=$(echo "$filename" | sed "s/^${kind}-//" | sed 's/-[0-9].*-wasm32-wasip2\.tar\.gz$//')
url="https://github.com/nearai/optimclaw/releases/download/${RELEASE_TAG}/${filename}"
url="https://github.com/nearai/ironclaw/releases/download/${RELEASE_TAG}/${filename}"
manifest="registry/${kind}s/${name}.json"
if [ -f "$manifest" ]; then
+1 -1
View File
@@ -165,7 +165,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v6
- name: Build Docker image
run: docker build -t optimclaw-test:ci .
run: docker build -t ironclaw-test:ci .
version-check:
name: Version Bump Check
+520 -520
View File
File diff suppressed because it is too large Load Diff
+12 -12
View File
@@ -1,6 +1,6 @@
# OptimClaw Development Guide
# IronClaw Development Guide
**OptimClaw** is a secure personal AI assistant — user-first security, self-expanding tools, defense in depth, multi-channel access with proactive background execution.
**IronClaw** is a secure personal AI assistant — user-first security, self-expanding tools, defense in depth, multi-channel access with proactive background execution.
## Build & Test
@@ -9,7 +9,7 @@ cargo fmt # format
cargo clippy --all --benches --tests --examples --all-features # lint (zero warnings)
cargo test # unit tests
cargo test --features integration # + PostgreSQL tests
RUST_LOG=optimclaw=debug cargo run # run with logging
RUST_LOG=ironclaw=debug cargo run # run with logging
```
E2E tests: see `tests/e2e/CLAUDE.md`.
@@ -35,20 +35,20 @@ All I/O is async with tokio. Use `Arc<T>` for shared state, `RwLock` for concurr
## Extracted Crates
Safety logic lives in `crates/optimclaw_safety/`. The `src/safety/mod.rs` shim re-exports everything for backward compatibility, but **new code should import from `optimclaw_safety` directly** (e.g. `use optimclaw_safety::SafetyLayer`). When touching a file that still uses `crate::safety::*`, migrate its imports to `optimclaw_safety::*`.
Safety logic lives in `crates/ironclaw_safety/`. The `src/safety/mod.rs` shim re-exports everything for backward compatibility, but **new code should import from `ironclaw_safety` directly** (e.g. `use ironclaw_safety::SafetyLayer`). When touching a file that still uses `crate::safety::*`, migrate its imports to `ironclaw_safety::*`.
## Project Structure
```
crates/
└── optimclaw_safety/ # Extracted: prompt injection, validation, leak detection, policy
└── ironclaw_safety/ # Extracted: prompt injection, validation, leak detection, policy
src/
├── lib.rs # Library root, module declarations
├── main.rs # Entry point, CLI args, startup
├── app.rs # App startup orchestration (channel wiring, DB init)
├── bootstrap.rs # Base directory resolution (~/.optimclaw), early .env loading
├── settings.rs # User settings persistence (~/.optimclaw/settings.json)
├── bootstrap.rs # Base directory resolution (~/.ironclaw), early .env loading
├── settings.rs # User settings persistence (~/.ironclaw/settings.json)
├── service.rs # OS service management (launchd/systemd daemon install)
├── tracing_fmt.rs # Custom tracing formatter
├── util.rs # Shared utilities
@@ -111,7 +111,7 @@ src/
│ ├── claude_bridge.rs # Claude Code bridge (spawns claude CLI)
│ └── proxy_llm.rs # LlmProvider that proxies through orchestrator
├── safety/ # Re-export shim for crates/optimclaw_safety (see Extracted Crates)
├── safety/ # Re-export shim for crates/ironclaw_safety (see Extracted Crates)
├── llm/ # Multi-provider LLM integration — see src/llm/CLAUDE.md
@@ -206,7 +206,7 @@ Pending -> InProgress -> Completed -> Submitted -> Accepted
SKILL.md files extend the agent's prompt with domain-specific instructions. See `.claude/rules/skills.md` for full details.
- **Trust model**: Trusted (user-placed in `~/.optimclaw/skills/` or workspace `skills/`, full tool access) vs Installed (registry, read-only tools)
- **Trust model**: Trusted (user-placed in `~/.ironclaw/skills/` or workspace `skills/`, full tool access) vs Installed (registry, read-only tools)
- **Selection pipeline**: gating (check bin/env/config requirements) -> scoring (keywords/patterns/tags) -> budget (fit within `SKILLS_MAX_TOKENS`) -> attenuation (trust-based tool ceiling)
- **Skill tools**: `skill_list`, `skill_search`, `skill_install`, `skill_remove`
@@ -228,9 +228,9 @@ Persistent memory with hybrid search (FTS + vector via RRF). Four tools: `memory
## Debugging
```bash
RUST_LOG=optimclaw=trace cargo run # verbose
RUST_LOG=optimclaw::agent=debug cargo run # agent module only
RUST_LOG=optimclaw=debug,tower_http=debug cargo run # + HTTP request logging
RUST_LOG=ironclaw=trace cargo run # verbose
RUST_LOG=ironclaw::agent=debug cargo run # agent module only
RUST_LOG=ironclaw=debug,tower_http=debug cargo run # + HTTP request logging
```
## Current Limitations
+4 -79
View File
@@ -3,49 +3,13 @@
## Getting Started
```bash
git clone https://github.com/nearai/optimclaw.git
cd optimclaw
git clone https://github.com/nearai/ironclaw.git
cd ironclaw
./scripts/dev-setup.sh
```
This installs the Rust toolchain, WASM targets, git hooks, and runs initial checks.
## How to Contribute
- Bug fixes, docs improvements, and focused cleanup tied to a concrete problem are welcome.
- Search existing issues and PRs before opening a new one to avoid duplicates.
- Keep changes scoped. One bug, one feature, or one documentation improvement per PR.
### Creating Issues
Open an issue when you are reporting a bug, proposing a feature, or documenting a gap in behavior.
For bug reports, include:
- What you expected to happen
- What actually happened
- Clear reproduction steps
- Relevant logs, screenshots, or error output
- Environment details when they matter (OS, database backend, feature flags, commit/branch)
For feature requests:
- Open an issue first before writing code
- Explain the problem being solved, not just the implementation idea
- Wait for maintainer feedback before investing in a large PR
We require an issue for new features so maintainers can prioritize the work and confirm it fits the roadmap before anyone spends time implementing it.
### Fixing Bugs
- Small, targeted bug-fix PRs are welcome
- If there is already an issue, link it in your PR
- If the bug is non-trivial, security-sensitive, or changes behavior across subsystems, open or confirm an issue first so the approach can be aligned before implementation
### Refactor-Only PRs
Refactor-only PRs are not accepted from contributors outside the core team. If a refactor is necessary to land a bug fix or approved feature, keep it minimal and clearly tied to that change.
## Development Workflow
```bash
@@ -55,45 +19,6 @@ cargo test # unit tests
cargo test --features integration # + PostgreSQL tests
```
These commands are for day-to-day iteration while you are developing locally. The pre-submission checks below are intentionally stricter and use CI-style flags so you can catch formatting drift and clippy warnings before requesting review.
## Before You Open a PR
Run the local validation checks required before requesting a review. These are stricter than the commands for iterative development:
```bash
cargo fmt --all -- --check
cargo clippy --all --benches --tests --examples --all-features -- -D warnings
cargo build
cargo test
```
Also run this when your change touches database-backed or integration behavior:
```bash
cargo test --features integration
```
Before asking for review:
- Build and exercise the changed path locally, not just the narrowest unit test
- Keep the PR focused and avoid mixing unrelated concerns
- Fill out the PR template with a clear summary, validation notes, and impact assessment
- If your change affects tracked behavior, update `FEATURE_PARITY.md` in the same branch
- If onboarding or setup behavior changes, update the relevant setup docs in the same branch
- If you are using a coding agent and it supports them, run `review-pr` or `pr-shepherd --fix` before opening or updating the PR
- `codex review --base origin/main` is also encouraged before requesting review
## Review Follow-Through
Review conversations are author-owned.
- Address each review comment with a code change or a clear explanation
- Resolve conversations you have handled; leave them open only when reviewer judgment is still needed
- Do not leave review cleanup for maintainers when the follow-through belongs to the author
If a PR is stale for more than 48 hours after review feedback is posted, maintainers may take over the follow-up work and land the changes needed to accomplish the original PR or issue intent.
## Code Style
- Zero clippy warnings policy
@@ -121,14 +46,14 @@ All PRs follow a risk-based review process:
| Track | Scope | Requirements |
|-------|-------|-------------|
| **A** | Docs, tests, chore, dependency bumps | 1 approval + CI green |
| **B** | Features, maintainer-requested refactors, new tools/channels | 1 approval + CI green + test evidence |
| **B** | Features, refactors, new tools/channels | 1 approval + CI green + test evidence |
| **C** | Security (`src/safety/`, `src/secrets/`), runtime (`src/agent/`, `src/worker/`), database schema, CI workflows | 2 approvals + rollback plan documented |
Select the appropriate track in the PR template based on what your changes touch.
## Database Changes
OptimClaw uses dual-backend persistence (PostgreSQL + libSQL). All new persistence features must support both backends. See `src/db/CLAUDE.md`.
IronClaw uses dual-backend persistence (PostgreSQL + libSQL). All new persistence features must support both backends. See `src/db/CLAUDE.md`.
## Adding Dependencies
+2 -2
View File
@@ -1,6 +1,6 @@
# OptimClaw Coverage Plan: 63.3% to 95%
# IronClaw Coverage Plan: 63.3% to 95%
> Generated 2025-03-06 from [Codecov](https://app.codecov.io/gh/nearai/optimclaw/tree/main/src)
> Generated 2025-03-06 from [Codecov](https://app.codecov.io/gh/nearai/ironclaw/tree/main/src)
## Current State
Generated
+140 -226
View File
@@ -157,7 +157,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -168,7 +168,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -2136,7 +2136,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users 0.5.2",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -2323,7 +2323,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -2887,17 +2887,6 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "hostname"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd"
dependencies = [
"cfg-if",
"libc",
"windows-link",
]
[[package]]
name = "html-escape"
version = "0.2.13"
@@ -3399,6 +3388,136 @@ dependencies = [
"serde",
]
[[package]]
name = "ironclaw"
version = "0.22.0"
dependencies = [
"aes-gcm",
"aho-corasick",
"anyhow",
"async-trait",
"aws-config",
"aws-sdk-bedrockruntime",
"aws-smithy-types",
"axum 0.8.8",
"base64 0.22.1",
"blake3",
"bollard",
"bytes",
"chrono",
"chrono-tz",
"clap",
"clap_complete",
"criterion",
"cron",
"crossterm",
"deadpool-postgres",
"dirs 6.0.0",
"dotenvy",
"ed25519-dalek",
"eventsource-stream",
"flate2",
"fs4",
"futures",
"hex",
"hkdf",
"hmac",
"html-to-markdown-rs",
"http-body-util",
"hyper 1.8.1",
"hyper-util",
"iana-time-zone",
"insta",
"ironclaw_common",
"ironclaw_frontend",
"ironclaw_safety",
"json5",
"libsql",
"lru",
"mime_guess",
"open",
"pdf-extract",
"pgvector",
"postgres-types",
"pretty_assertions",
"pty-process",
"rand 0.8.5",
"readabilityrs",
"refinery",
"regex",
"reqwest",
"rig-core",
"rust_decimal",
"rust_decimal_macros",
"rustls 0.23.37",
"rustls-native-certs 0.8.3",
"rustyline",
"secrecy",
"secret-service",
"security-framework 3.7.0",
"semver",
"serde",
"serde_json",
"serde_yml",
"sha2",
"subtle",
"tar",
"tempfile",
"termimad",
"testcontainers-modules",
"thiserror 2.0.18",
"tokio",
"tokio-postgres",
"tokio-postgres-rustls",
"tokio-stream",
"tokio-test",
"tokio-tungstenite 0.26.2",
"toml",
"tower 0.5.3",
"tower-http 0.6.8",
"tracing",
"tracing-subscriber",
"tracing-test",
"url",
"urlencoding",
"uuid",
"wasmparser 0.220.1",
"wasmtime",
"wasmtime-wasi",
"webpki-roots 0.26.11",
"zbus",
"zip",
]
[[package]]
name = "ironclaw_common"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "ironclaw_frontend"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
"thiserror 2.0.18",
]
[[package]]
name = "ironclaw_safety"
version = "0.2.0"
dependencies = [
"aho-corasick",
"regex",
"serde_json",
"thiserror 2.0.18",
"tracing",
"url",
]
[[package]]
name = "is-docker"
version = "0.2.0"
@@ -4036,7 +4155,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -4214,132 +4333,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "optimclaw"
version = "0.22.0"
dependencies = [
"aes-gcm",
"aho-corasick",
"anyhow",
"async-trait",
"aws-config",
"aws-sdk-bedrockruntime",
"aws-smithy-types",
"axum 0.8.8",
"base64 0.22.1",
"blake3",
"bollard",
"bytes",
"chrono",
"chrono-tz",
"clap",
"clap_complete",
"criterion",
"cron",
"crossterm",
"deadpool-postgres",
"dirs 6.0.0",
"dotenvy",
"ed25519-dalek",
"eventsource-stream",
"flate2",
"fs4",
"futures",
"hex",
"hkdf",
"hmac",
"hostname",
"html-to-markdown-rs",
"http-body-util",
"hyper 1.8.1",
"hyper-util",
"iana-time-zone",
"insta",
"json5",
"libsql",
"lru",
"mime_guess",
"open",
"optimclaw_common",
"optimclaw_safety",
"pdf-extract",
"pgvector",
"postgres-types",
"pqcrypto-kyber",
"pqcrypto-traits",
"pretty_assertions",
"pty-process",
"quinn",
"rand 0.8.5",
"rcgen",
"readabilityrs",
"refinery",
"regex",
"reqwest",
"rig-core",
"rust_decimal",
"rust_decimal_macros",
"rustls 0.23.37",
"rustls-native-certs 0.8.3",
"rustyline",
"secrecy",
"secret-service",
"security-framework 3.7.0",
"semver",
"serde",
"serde_json",
"serde_yml",
"sha2",
"subtle",
"sys-info",
"tar",
"tempfile",
"termimad",
"testcontainers-modules",
"thiserror 2.0.18",
"tokio",
"tokio-postgres",
"tokio-postgres-rustls",
"tokio-stream",
"tokio-test",
"tokio-tungstenite 0.26.2",
"toml",
"tower 0.5.3",
"tower-http 0.6.8",
"tracing",
"tracing-subscriber",
"tracing-test",
"url",
"urlencoding",
"uuid",
"wasmparser 0.220.1",
"wasmtime",
"wasmtime-wasi",
"webpki-roots 0.26.11",
"zbus",
"zip",
]
[[package]]
name = "optimclaw_common"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "optimclaw_safety"
version = "0.2.0"
dependencies = [
"aho-corasick",
"regex",
"serde_json",
"thiserror 2.0.18",
"tracing",
"url",
]
[[package]]
name = "option-ext"
version = "0.2.0"
@@ -4458,16 +4451,6 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "pem"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
dependencies = [
"base64 0.22.1",
"serde_core",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
@@ -4837,37 +4820,6 @@ dependencies = [
"zerocopy 0.8.42",
]
[[package]]
name = "pqcrypto-internals"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4a326caf27cbf2ac291ca7fd56300497ba9e76a8cc6a7d95b7a18b57f22b61d"
dependencies = [
"cc",
"dunce",
"getrandom 0.3.4",
"libc",
]
[[package]]
name = "pqcrypto-kyber"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c00293cf898859d0c771455388054fd69ab712263c73fdc7f287a39b1ba000"
dependencies = [
"cc",
"glob",
"libc",
"pqcrypto-internals",
"pqcrypto-traits",
]
[[package]]
name = "pqcrypto-traits"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94e851c7654eed9e68d7d27164c454961a616cf8c203d500607ef22c737b51bb"
[[package]]
name = "precomputed-hash"
version = "0.1.1"
@@ -5163,19 +5115,6 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "rcgen"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2"
dependencies = [
"pem",
"ring",
"rustls-pki-types",
"time",
"yasna",
]
[[package]]
name = "readabilityrs"
version = "0.1.2"
@@ -5564,7 +5503,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.12.1",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -6246,7 +6185,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -6401,16 +6340,6 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "sys-info"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b3a0d0aba8bf96a0e1ddfdc352fc53b3df7f39318c71854910c3c4b024ae52c"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "system-configuration"
version = "0.7.0"
@@ -6481,7 +6410,7 @@ dependencies = [
"getrandom 0.4.2",
"once_cell",
"rustix 1.1.4",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -6870,11 +6799,7 @@ checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
dependencies = [
"futures-util",
"log",
"rustls 0.23.37",
"rustls-native-certs 0.8.3",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.26.4",
"tungstenite 0.26.2",
]
@@ -7235,8 +7160,6 @@ dependencies = [
"httparse",
"log",
"rand 0.9.2",
"rustls 0.23.37",
"rustls-pki-types",
"sha1",
"thiserror 2.0.18",
"utf-8",
@@ -7288,7 +7211,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
dependencies = [
"memoffset",
"tempfile",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -8138,7 +8061,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.48.0",
]
[[package]]
@@ -8661,15 +8584,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "yasna"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
dependencies = [
"time",
]
[[package]]
name = "yoke"
version = "0.8.1"
+11 -18
View File
@@ -1,5 +1,5 @@
[workspace]
members = [".", "crates/optimclaw_common", "crates/optimclaw_safety"]
members = [".", "crates/ironclaw_common", "crates/ironclaw_safety", "crates/ironclaw_frontend"]
exclude = [
"channels-src/discord",
"channels-src/telegram",
@@ -15,19 +15,19 @@ exclude = [
"tools-src/slack",
"tools-src/telegram",
"fuzz",
"crates/optimclaw_safety/fuzz",
"crates/ironclaw_safety/fuzz",
]
[package]
name = "optimclaw"
name = "ironclaw"
version = "0.22.0"
edition = "2024"
rust-version = "1.92"
description = "Secure personal AI assistant that protects your data and expands its capabilities on the fly"
authors = ["NEAR AI <[email protected]>"]
license = "MIT OR Apache-2.0"
homepage = "https://github.com/nearai/optimclaw"
repository = "https://github.com/nearai/optimclaw"
homepage = "https://github.com/nearai/ironclaw"
repository = "https://github.com/nearai/ironclaw"
[package.metadata.wix]
upgrade-guid = "D0156E61-BA37-451E-8AB9-1A2ECCCFA48F"
@@ -40,7 +40,6 @@ eula = false
tokio = { version = "1", features = ["full"] }
tokio-stream = { version = "0.1", features = ["sync"] }
futures = "0.3"
tokio-tungstenite = { version = "0.26", features = ["rustls-tls-native-roots"] }
eventsource-stream = "0.2"
# HTTP client
@@ -103,10 +102,11 @@ tower-http = { version = "0.6", features = ["trace", "cors", "set-header", "catc
cron = "0.13"
# Shared types
optimclaw_common = { path = "crates/optimclaw_common", version = "0.1.0" }
ironclaw_common = { path = "crates/ironclaw_common", version = "0.1.0" }
# Safety/sanitization
optimclaw_safety = { path = "crates/optimclaw_safety", version = "0.2.0" }
ironclaw_frontend = { path = "crates/ironclaw_frontend", version = "0.1.0" }
ironclaw_safety = { path = "crates/ironclaw_safety", version = "0.2.0" }
regex = "1"
aho-corasick = "1"
@@ -186,14 +186,6 @@ hex = "0.4.3"
# OpenClaw import (feature gated)
json5 = { version = "0.4", optional = true }
# Mesh cluster (feature gated)
pqcrypto-kyber = { version = "0.8", optional = true }
pqcrypto-traits = { version = "0.3", optional = true }
sys-info = { version = "0.9", optional = true }
hostname = { version = "0.4", optional = true }
quinn = { version = "0.11", default-features = false, features = ["runtime-tokio", "rustls-ring"], optional = true }
rcgen = { version = "0.13", optional = true }
# macOS keychain
[target.'cfg(target_os = "macos")'.dependencies]
security-framework = "3"
@@ -210,6 +202,7 @@ zbus = "4"
[dev-dependencies]
tokio-test = "0.4"
tracing-test = "0.2"
tokio-tungstenite = "0.26"
testcontainers-modules = { version = "0.11", features = ["postgres"] }
pretty_assertions = "1"
tempfile = "3"
@@ -245,7 +238,6 @@ integration = []
html-to-markdown = ["dep:html-to-markdown-rs", "dep:readabilityrs"]
bedrock = ["dep:aws-config", "dep:aws-sdk-bedrockruntime", "dep:aws-smithy-types"]
import = ["dep:json5", "libsql"]
cluster = ["dep:pqcrypto-kyber", "dep:pqcrypto-traits", "dep:sys-info", "dep:hostname", "dep:quinn", "dep:rcgen", "dep:rustls"]
[[test]]
name = "e2e_thread_scheduling"
@@ -261,7 +253,8 @@ strip = true # Remove debug symbols from release binaries
# The profile that 'cargo dist' will build with
[profile.dist]
inherits = "release"
lto = "thin"
lto = "fat" # Full cross-crate LTO (slow build, better codegen)
codegen-units = 1 # Single codegen unit for maximum optimization
# Config for 'dist'
[workspace.metadata.dist]
+38 -38
View File
@@ -1,6 +1,6 @@
# OptimClaw ↔ OpenClaw Feature Parity Matrix
# IronClaw ↔ OpenClaw Feature Parity Matrix
This document tracks feature parity between OptimClaw (Rust implementation) and OpenClaw (TypeScript reference implementation). Use this to coordinate work across developers.
This document tracks feature parity between IronClaw (Rust implementation) and OpenClaw (TypeScript reference implementation). Use this to coordinate work across developers.
**Legend:**
@@ -17,7 +17,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## 1. Architecture
| Feature | OpenClaw | OptimClaw | Notes |
| Feature | OpenClaw | IronClaw | Notes |
|---------|----------|----------|-------|
| Hub-and-spoke architecture | ✅ | ✅ | Web gateway as central hub |
| WebSocket control plane | ✅ | ✅ | Gateway with WebSocket + SSE |
@@ -32,7 +32,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## 2. Gateway System
| Feature | OpenClaw | OptimClaw | Notes |
| Feature | OpenClaw | IronClaw | Notes |
|---------|----------|----------|-------|
| Gateway control plane | ✅ | ✅ | Web gateway with 40+ API endpoints |
| HTTP endpoints for Control UI | ✅ | ✅ | Web dashboard with chat, memory, jobs, logs, extensions |
@@ -62,15 +62,15 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## 3. Messaging Channels
| Channel | OpenClaw | OptimClaw | Priority | Notes |
| Channel | OpenClaw | IronClaw | Priority | Notes |
|---------|----------|----------|----------|-------|
| CLI/TUI | ✅ | ✅ | - | Ratatui-based TUI |
| HTTP webhook | ✅ | ✅ | - | axum with secret validation |
| REPL (simple) | ✅ | ✅ | - | For testing |
| WASM channels | ❌ | ✅ | - | OptimClaw innovation; host resolves owner scope vs sender identity |
| WASM channels | ❌ | ✅ | - | IronClaw innovation; host resolves owner scope vs sender identity |
| WhatsApp | ✅ | ❌ | P1 | Baileys (Web), same-phone mode with echo detection |
| Telegram | ✅ | ✅ | - | WASM channel(MTProto), DM pairing, caption, /start, bot_username, DM topics, setup-time owner auto-verification, owner-scoped persistence |
| Discord | ✅ | 🚧 | P2 | Gateway `MESSAGE_CREATE` intake restored via websocket queue + WASM poll; Gateway DMs now respect pairing; thread parent binding inheritance and reply/thread parity still incomplete |
| Discord | ✅ | | P2 | discord.js, thread parent binding inheritance |
| Signal | ✅ | ✅ | P2 | signal-cli daemonPC, SSE listener HTTP/JSON-R, user/group allowlists, DM pairing |
| Slack | ✅ | ✅ | - | WASM tool |
| iMessage | ✅ | ❌ | P3 | BlueBubbles or Linq recommended |
@@ -88,7 +88,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
### Telegram-Specific Features (since Feb 2025)
| Feature | OpenClaw | OptimClaw | Notes |
| Feature | OpenClaw | IronClaw | Notes |
|---------|----------|----------|-------|
| Forum topic creation | ✅ | ❌ | Create topics in forum groups |
| channel_post support | ✅ | ❌ | Bot-to-bot communication |
@@ -100,7 +100,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
### Discord-Specific Features (since Feb 2025)
| Feature | OpenClaw | OptimClaw | Notes |
| Feature | OpenClaw | IronClaw | Notes |
|---------|----------|----------|-------|
| Forwarded attachment downloads | ✅ | ❌ | Fetch media from forwarded messages |
| Faster reaction state machine | ✅ | ❌ | Watchdog + debounce |
@@ -108,7 +108,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
### Slack-Specific Features (since Feb 2025)
| Feature | OpenClaw | OptimClaw | Notes |
| Feature | OpenClaw | IronClaw | Notes |
|---------|----------|----------|-------|
| Streaming draft replies | ✅ | ❌ | Partial replies via draft message updates |
| Configurable stream modes | ✅ | ❌ | Per-channel stream behavior |
@@ -117,23 +117,23 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
### Mattermost-Specific Features (since Mar 2026)
| Feature | OpenClaw | OptimClaw | Notes |
| Feature | OpenClaw | IronClaw | Notes |
|---------|----------|----------|-------|
| Interactive buttons | ✅ | ❌ | Clickable message buttons with signed callback flow |
| Interactive model picker | ✅ | ❌ | In-channel provider/model chooser |
### Feishu/Lark-Specific Features (since Mar 2026)
| Feature | OpenClaw | OptimClaw | Notes |
| Feature | OpenClaw | IronClaw | Notes |
|---------|----------|----------|-------|
| Doc/table actions | ✅ | ❌ | `feishu_doc` supports tables, positional insert, color_text, image upload, and file upload |
| Rich-text embedded media extraction | ✅ | ❌ | Pull video/media attachments from post messages |
### Channel Features
| Feature | OpenClaw | OptimClaw | Notes |
| Feature | OpenClaw | IronClaw | Notes |
|---------|----------|----------|-------|
| DM pairing codes | ✅ | ✅ | `optimclaw pairing list/approve`, host APIs |
| DM pairing codes | ✅ | ✅ | `ironclaw pairing list/approve`, host APIs |
| Allowlist/blocklist | ✅ | 🚧 | `allow_from` + pairing store + hardened command/group allowlists |
| Self-message bypass | ✅ | ❌ | Own messages skip pairing |
| Mention-based activation | ✅ | ✅ | bot_username + respond_to_all_group_messages |
@@ -151,7 +151,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## 4. CLI Commands
| Command | OpenClaw | OptimClaw | Priority | Notes |
| Command | OpenClaw | IronClaw | Priority | Notes |
|---------|----------|----------|----------|-------|
| `run` (agent) | ✅ | ✅ | - | Default command |
| `tool install/list/remove` | ✅ | ✅ | - | WASM tools |
@@ -189,9 +189,9 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## 5. Agent System
| Feature | OpenClaw | OptimClaw | Notes |
| Feature | OpenClaw | IronClaw | Notes |
|---------|----------|----------|-------|
| Pi agent runtime | ✅ | | OptimClaw uses custom runtime |
| Pi agent runtime | ✅ | | IronClaw uses custom runtime |
| RPC-based execution | ✅ | ✅ | Orchestrator/worker pattern |
| Multi-provider failover | ✅ | ✅ | `FailoverProvider` tries providers sequentially on retryable errors |
| Per-sender sessions | ✅ | ✅ | |
@@ -232,7 +232,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## 6. Model & Provider Support
| Provider | OpenClaw | OptimClaw | Priority | Notes |
| Provider | OpenClaw | IronClaw | Priority | Notes |
|----------|----------|----------|----------|-------|
| NEAR AI | ✅ | ✅ | - | Primary provider |
| Anthropic (Claude) | ✅ | 🚧 | - | Via NEAR AI proxy; Opus 4.5, Sonnet 4, Sonnet 4.6, adaptive thinking default |
@@ -245,7 +245,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
| Cloudflare Workers AI | ✅ | ✅ | P3 | Via `cloudflare` adapter |
| NVIDIA API | ✅ | ✅ | P3 | Via `nvidia` adapter and `providers.json` |
| OpenRouter | ✅ | ✅ | - | Via OpenAI-compatible provider (RigAdapter) |
| Tinfoil | ❌ | ✅ | - | Private inference provider (OptimClaw-only) |
| Tinfoil | ❌ | ✅ | - | Private inference provider (IronClaw-only) |
| OpenAI-compatible | ❌ | ✅ | - | Generic OpenAI-compatible endpoint (RigAdapter) |
| GitHub Copilot | ✅ | ✅ | - | Dedicated provider with OAuth token exchange (`GithubCopilotProvider`) |
| Ollama (local) | ✅ | ✅ | - | via `rig::providers::ollama` (full support) |
@@ -257,7 +257,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
### Model Features
| Feature | OpenClaw | OptimClaw | Notes |
| Feature | OpenClaw | IronClaw | Notes |
|---------|----------|----------|-------|
| Auto-discovery | ✅ | ❌ | |
| Failover chains | ✅ | ✅ | `FailoverProvider` with configurable `fallback_model` |
@@ -273,7 +273,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## 7. Media Handling
| Feature | OpenClaw | OptimClaw | Priority | Notes |
| Feature | OpenClaw | IronClaw | Priority | Notes |
|---------|----------|----------|----------|-------|
| Image processing (Sharp) | ✅ | ❌ | P2 | Resize, format convert |
| Configurable image resize dims | ✅ | ❌ | P2 | Per-agent dimension config |
@@ -296,12 +296,12 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## 8. Plugin & Extension System
| Feature | OpenClaw | OptimClaw | Notes |
| Feature | OpenClaw | IronClaw | Notes |
|---------|----------|----------|-------|
| Dynamic loading | ✅ | ✅ | WASM modules |
| Manifest validation | ✅ | ✅ | WASM metadata |
| HTTP path registration | ✅ | ❌ | Plugin routes |
| Workspace-relative install | ✅ | ✅ | ~/.optimclaw/tools/ |
| Workspace-relative install | ✅ | ✅ | ~/.ironclaw/tools/ |
| Channel plugins | ✅ | ✅ | WASM channels |
| Auth plugins | ✅ | ❌ | |
| Memory plugins | ✅ | ❌ | Custom backends + selectable memory slot |
@@ -321,7 +321,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## 9. Configuration System
| Feature | OpenClaw | OptimClaw | Notes |
| Feature | OpenClaw | IronClaw | Notes |
|---------|----------|----------|-------|
| Primary config file | ✅ `~/.openclaw/openclaw.json` | ✅ `.env` | Different formats |
| JSON5 support | ✅ | ❌ | Comments, trailing commas |
@@ -330,7 +330,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
| Config validation/schema | ✅ | ✅ | Type-safe Config struct + `openclaw config validate` |
| Hot-reload | ✅ | ❌ | |
| Legacy migration | ✅ | | |
| State directory | ✅ `~/.openclaw-state/` | ✅ `~/.optimclaw/` | |
| State directory | ✅ `~/.openclaw-state/` | ✅ `~/.ironclaw/` | |
| Credentials directory | ✅ | ✅ | Session files |
| Full model compat fields in schema | ✅ | ❌ | pi-ai model compat exposed in config |
@@ -340,7 +340,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## 10. Memory & Knowledge System
| Feature | OpenClaw | OptimClaw | Notes |
| Feature | OpenClaw | IronClaw | Notes |
|---------|----------|----------|-------|
| Vector memory | ✅ | ✅ | pgvector |
| Session-based memory | ✅ | ✅ | |
@@ -351,7 +351,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
| OpenAI embeddings | ✅ | ✅ | |
| Gemini embeddings | ✅ | ❌ | |
| Local embeddings | ✅ | ❌ | |
| SQLite-vec backend | ✅ | ❌ | OptimClaw uses PostgreSQL |
| SQLite-vec backend | ✅ | ❌ | IronClaw uses PostgreSQL |
| LanceDB backend | ✅ | ❌ | Configurable auto-capture max length |
| QMD backend | ✅ | ❌ | |
| Atomic reindexing | ✅ | ✅ | |
@@ -369,7 +369,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## 11. Mobile Apps
| Feature | OpenClaw | OptimClaw | Priority | Notes |
| Feature | OpenClaw | IronClaw | Priority | Notes |
|---------|----------|----------|----------|-------|
| iOS app (SwiftUI) | ✅ | 🚫 | - | Out of scope initially |
| Android app (Kotlin) | ✅ | 🚫 | - | Out of scope initially |
@@ -390,7 +390,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## 12. macOS App
| Feature | OpenClaw | OptimClaw | Priority | Notes |
| Feature | OpenClaw | IronClaw | Priority | Notes |
|---------|----------|----------|----------|-------|
| SwiftUI native app | ✅ | 🚫 | - | Out of scope |
| Menu bar presence | ✅ | 🚫 | - | Animated menubar icon |
@@ -411,7 +411,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## 13. Web Interface
| Feature | OpenClaw | OptimClaw | Priority | Notes |
| Feature | OpenClaw | IronClaw | Priority | Notes |
|---------|----------|----------|----------|-------|
| Control UI Dashboard | ✅ | ✅ | - | Web gateway with chat, memory, jobs, logs, extensions |
| Channel status view | ✅ | 🚧 | P2 | Gateway status widget, full channel view pending |
@@ -431,7 +431,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## 14. Automation
| Feature | OpenClaw | OptimClaw | Priority | Notes |
| Feature | OpenClaw | IronClaw | Priority | Notes |
|---------|----------|----------|----------|-------|
| Cron jobs | ✅ | ✅ | - | Routines with cron trigger |
| Per-job model fallback override | ✅ | ❌ | P2 | `payload.fallbacks` overrides agent-level fallbacks |
@@ -465,14 +465,14 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## 15. Security Features
| Feature | OpenClaw | OptimClaw | Notes |
| Feature | OpenClaw | IronClaw | Notes |
|---------|----------|----------|-------|
| Gateway token auth | ✅ | ✅ | Bearer token auth on web gateway |
| Device pairing | ✅ | ❌ | |
| Tailscale identity | ✅ | ❌ | |
| Trusted-proxy auth | ✅ | ❌ | Header-based reverse proxy auth |
| OAuth flows | ✅ | 🚧 | NEAR AI OAuth + Gemini OAuth (PKCE, S256) + hosted extension/MCP OAuth broker; external auth-proxy rollout still pending |
| DM pairing verification | ✅ | ✅ | optimclaw pairing approve, host APIs |
| DM pairing verification | ✅ | ✅ | ironclaw pairing approve, host APIs |
| Allowlist/blocklist | ✅ | 🚧 | allow_from + pairing store |
| Per-group tool policies | ✅ | ❌ | |
| Exec approvals | ✅ | ✅ | TUI overlay |
@@ -483,7 +483,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
| Loopback-first | ✅ | 🚧 | HTTP binds 0.0.0.0 |
| Docker sandbox | ✅ | ✅ | Orchestrator/worker containers |
| Podman support | ✅ | ❌ | Alternative to Docker |
| WASM sandbox | ❌ | ✅ | OptimClaw innovation |
| WASM sandbox | ❌ | ✅ | IronClaw innovation |
| Sandbox env sanitization | ✅ | 🚧 | Shell tool scrubs env vars (secret detection); docker container env sanitization partial |
| Tool policies | ✅ | ✅ | |
| Elevated mode | ✅ | ❌ | |
@@ -505,7 +505,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## 16. Development & Build System
| Feature | OpenClaw | OptimClaw | Notes |
| Feature | OpenClaw | IronClaw | Notes |
|---------|----------|----------|-------|
| Primary language | TypeScript | Rust | Different ecosystems |
| Build tool | tsdown | cargo | |
@@ -531,7 +531,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
- ✅ TUI channel with approval overlays
- ✅ HTTP webhook channel
- ✅ DM pairing (optimclaw pairing list/approve, host APIs)
- ✅ DM pairing (ironclaw pairing list/approve, host APIs)
- ✅ WASM tool sandbox
- ✅ Workspace/memory with hybrid search + embeddings batching
- ✅ Prompt injection defense
@@ -605,7 +605,7 @@ This document tracks feature parity between OptimClaw (Rust implementation) and
## Deviations from OpenClaw
OptimClaw intentionally differs from OpenClaw in these ways:
IronClaw intentionally differs from OpenClaw in these ways:
1. **Rust vs TypeScript**: Native performance, memory safety, single binary distribution
2. **WASM sandbox vs Docker**: Lighter weight, faster startup, capability-based security
@@ -613,7 +613,7 @@ OptimClaw intentionally differs from OpenClaw in these ways:
4. **NEAR AI focus**: Primary provider with session-based auth
5. **No mobile/desktop apps**: Focus on server-side and CLI initially
6. **WASM channels**: Novel extension mechanism not in OpenClaw
7. **Tinfoil private inference**: OptimClaw-only provider for private/encrypted inference
7. **Tinfoil private inference**: IronClaw-only provider for private/encrypted inference
8. **GitHub WASM tool**: Native GitHub integration as WASM tool
9. **Prompt-based skills**: Different approach than OpenClaw capability bundles (trust gating, attenuation)
+26 -26
View File
@@ -1,8 +1,8 @@
<p align="center">
<img src="optimclaw.png?v=2" alt="OptimClaw" width="200"/>
<img src="ironclaw.png?v=2" alt="IronClaw" width="200"/>
</p>
<h1 align="center">OptimClaw</h1>
<h1 align="center">IronClaw</h1>
<p align="center">
<strong>あなたの味方になる、安全なパーソナルAIアシスタント</strong>
@@ -10,8 +10,8 @@
<p align="center">
<a href="#license"><img src="https://img.shields.io/badge/license-MIT%20OR%20Apache%202.0-blue.svg" alt="License: MIT OR Apache-2.0" /></a>
<a href="https://t.me/optimclawAI"><img src="https://img.shields.io/badge/Telegram-%40optimclawAI-26A5E4?style=flat&logo=telegram&logoColor=white" alt="Telegram: @optimclawAI" /></a>
<a href="https://www.reddit.com/r/optimclawAI/"><img src="https://img.shields.io/badge/Reddit-r%2FoptimclawAI-FF4500?style=flat&logo=reddit&logoColor=white" alt="Reddit: r/optimclawAI" /></a>
<a href="https://t.me/ironclawAI"><img src="https://img.shields.io/badge/Telegram-%40ironclawAI-26A5E4?style=flat&logo=telegram&logoColor=white" alt="Telegram: @ironclawAI" /></a>
<a href="https://www.reddit.com/r/ironclawAI/"><img src="https://img.shields.io/badge/Reddit-r%2FironclawAI-FF4500?style=flat&logo=reddit&logoColor=white" alt="Reddit: r/ironclawAI" /></a>
</p>
<p align="center">
@@ -34,16 +34,16 @@
## フィロソフィー
OptimClawはシンプルな原則に基づいて構築されています:**あなたのAIアシスタントは、あなたのために働くべきであり、あなたに不利益をもたらすべきではありません。**
IronClawはシンプルな原則に基づいて構築されています:**あなたのAIアシスタントは、あなたのために働くべきであり、あなたに不利益をもたらすべきではありません。**
AIシステムがデータの取り扱いについて不透明になり、企業の利益に沿って調整されることが増えている世界で、OptimClawは異なるアプローチを取ります:
AIシステムがデータの取り扱いについて不透明になり、企業の利益に沿って調整されることが増えている世界で、IronClawは異なるアプローチを取ります:
- **あなたのデータはあなたのもの** - すべての情報はローカルに保存・暗号化され、あなたの管理下から離れることはありません
- **設計段階からの透明性** - オープンソース、監査可能、隠れたテレメトリやデータ収集なし
- **自己拡張する能力** - ベンダーのアップデートを待たずに、新しいツールをその場で構築
- **多層防御** - 複数のセキュリティレイヤーがプロンプトインジェクションやデータ流出から保護
OptimClawは、個人生活にも仕事にも本当に信頼できるAIアシスタントです。
IronClawは、個人生活にも仕事にも本当に信頼できるAIアシスタントです。
## 機能
@@ -66,7 +66,7 @@ OptimClawは、個人生活にも仕事にも本当に信頼できるAIアシス
### 自己拡張
- **動的ツール構築** - 必要なものを説明すると、OptimClawがWASMツールとして構築
- **動的ツール構築** - 必要なものを説明すると、IronClawがWASMツールとして構築
- **MCPプロトコル** - Model Context Protocolサーバーに接続して追加機能を利用
- **プラグインアーキテクチャ** - 再起動なしで新しいWASMツールやチャネルを追加
@@ -86,12 +86,12 @@ OptimClawは、個人生活にも仕事にも本当に信頼できるAIアシス
## ダウンロードまたはビルド
最新のアップデートは[リリースページ](https://github.com/nearai/optimclaw/releases/)をご覧ください。
最新のアップデートは[リリースページ](https://github.com/nearai/ironclaw/releases/)をご覧ください。
<details>
<summary>Windowsインストーラーでインストール(Windows</summary>
[Windowsインストーラー](https://github.com/nearai/optimclaw/releases/latest/download/optimclaw-x86_64-pc-windows-msvc.msi)をダウンロードして実行してください。
[Windowsインストーラー](https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-x86_64-pc-windows-msvc.msi)をダウンロードして実行してください。
</details>
@@ -99,7 +99,7 @@ OptimClawは、個人生活にも仕事にも本当に信頼できるAIアシス
<summary>PowerShellスクリプトでインストール(Windows</summary>
```sh
irm https://github.com/nearai/optimclaw/releases/latest/download/optimclaw-installer.ps1 | iex
irm https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-installer.ps1 | iex
```
</details>
@@ -108,7 +108,7 @@ irm https://github.com/nearai/optimclaw/releases/latest/download/optimclaw-insta
<summary>シェルスクリプトでインストール(macOS、Linux、Windows/WSL</summary>
```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/optimclaw/releases/latest/download/optimclaw-installer.sh | sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-installer.sh | sh
```
</details>
@@ -116,7 +116,7 @@ curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/optimclaw/releas
<summary>Homebrewでインストール(macOS/Linux</summary>
```sh
brew install optimclaw
brew install ironclaw
```
</details>
@@ -128,8 +128,8 @@ brew install optimclaw
```bash
# リポジトリをクローン
git clone https://github.com/nearai/optimclaw.git
cd optimclaw
git clone https://github.com/nearai/ironclaw.git
cd ironclaw
# ビルド
cargo build --release
@@ -146,25 +146,25 @@ cargo test
```bash
# データベースを作成
createdb optimclaw
createdb ironclaw
# pgvectorを有効化
psql optimclaw -c "CREATE EXTENSION IF NOT EXISTS vector;"
psql ironclaw -c "CREATE EXTENSION IF NOT EXISTS vector;"
```
## 設定
セットアップウィザードを実行してOptimClawを設定します:
セットアップウィザードを実行してIronClawを設定します:
```bash
optimclaw onboard
ironclaw onboard
```
ウィザードは、データベース接続、NEAR AI認証(ブラウザOAuth経由)、シークレットの暗号化(システムキーチェーンを使用)を処理します。設定は接続されたデータベースに永続化されます。ブートストラップ変数(例:`DATABASE_URL``LLM_BACKEND`)は、データベース接続前に利用できるよう`~/.optimclaw/.env`に書き込まれます。
ウィザードは、データベース接続、NEAR AI認証(ブラウザOAuth経由)、シークレットの暗号化(システムキーチェーンを使用)を処理します。設定は接続されたデータベースに永続化されます。ブートストラップ変数(例:`DATABASE_URL``LLM_BACKEND`)は、データベース接続前に利用できるよう`~/.ironclaw/.env`に書き込まれます。
### 代替LLMプロバイダー
OptimClawはデフォルトでNEAR AIを使用しますが、多くのLLMプロバイダーをすぐに利用できます。組み込みプロバイダーには**Anthropic**、**OpenAI**、**Google Gemini**、**MiniMax**、**Mistral**、**Ollama**(ローカル)が含まれます。**OpenRouter**300以上のモデル)、**Together AI**、**Fireworks AI**、セルフホストサーバー(**vLLM**、**LiteLLM**)などのOpenAI互換サービスもサポートされています。
IronClawはデフォルトでNEAR AIを使用しますが、多くのLLMプロバイダーをすぐに利用できます。組み込みプロバイダーには**Anthropic**、**OpenAI**、**Google Gemini**、**MiniMax**、**Mistral**、**Ollama**(ローカル)が含まれます。**OpenRouter**300以上のモデル)、**Together AI**、**Fireworks AI**、セルフホストサーバー(**vLLM**、**LiteLLM**)などのOpenAI互換サービスもサポートされています。
ウィザードでプロバイダーを選択するか、環境変数を直接設定してください:
@@ -184,7 +184,7 @@ LLM_MODEL=anthropic/claude-sonnet-4
## セキュリティ
OptimClawは、データを保護し悪用を防ぐために多層防御を実装しています。
IronClawは、データを保護し悪用を防ぐために多層防御を実装しています。
### WASMサンドボックス
@@ -280,13 +280,13 @@ WASM ──► 許可リスト ──► リーク ──► 認証情報 ─
```bash
# 初回セットアップ(データベース、認証などを設定)
optimclaw onboard
ironclaw onboard
# インタラクティブREPLを起動
cargo run
# デバッグログ付き
RUST_LOG=optimclaw=debug cargo run
RUST_LOG=ironclaw=debug cargo run
```
## 開発
@@ -299,7 +299,7 @@ cargo fmt
cargo clippy --all --benches --tests --examples --all-features
# テスト実行
createdb optimclaw_test
createdb ironclaw_test
cargo test
# 特定のテストを実行
@@ -311,7 +311,7 @@ cargo test test_name
## OpenClawの系譜
OptimClawは[OpenClaw](https://github.com/openclaw/openclaw)にインスパイアされたRust再実装です。完全な対応表は[FEATURE_PARITY.md](FEATURE_PARITY.md)をご覧ください。
IronClawは[OpenClaw](https://github.com/openclaw/openclaw)にインスパイアされたRust再実装です。完全な対応表は[FEATURE_PARITY.md](FEATURE_PARITY.md)をご覧ください。
主な違い:
+28 -74
View File
@@ -1,8 +1,8 @@
<p align="center">
<img src="optimclaw.png?v=2" alt="OptimClaw" width="200"/>
<img src="ironclaw.png?v=2" alt="IronClaw" width="200"/>
</p>
<h1 align="center">OptimClaw</h1>
<h1 align="center">IronClaw</h1>
<p align="center">
<strong>Your secure personal AI assistant, always on your side</strong>
@@ -10,10 +10,10 @@
<p align="center">
<a href="#license"><img src="https://img.shields.io/badge/license-MIT%20OR%20Apache%202.0-blue.svg" alt="License: MIT OR Apache-2.0" /></a>
<a href="https://t.me/optimclawAI"><img src="https://img.shields.io/badge/Telegram-%40optimclawAI-26A5E4?style=flat&logo=telegram&logoColor=white" alt="Telegram: @optimclawAI" /></a>
<a href="https://www.reddit.com/r/optimclawAI/"><img src="https://img.shields.io/badge/Reddit-r%2FoptimclawAI-FF4500?style=flat&logo=reddit&logoColor=white" alt="Reddit: r/optimclawAI" /></a>
<a href="https://gitcgr.com/nearai/optimclaw">
<img src="https://gitcgr.com/badge/nearai/optimclaw.svg" alt="gitcgr" />
<a href="https://t.me/ironclawAI"><img src="https://img.shields.io/badge/Telegram-%40ironclawAI-26A5E4?style=flat&logo=telegram&logoColor=white" alt="Telegram: @ironclawAI" /></a>
<a href="https://www.reddit.com/r/ironclawAI/"><img src="https://img.shields.io/badge/Reddit-r%2FironclawAI-FF4500?style=flat&logo=reddit&logoColor=white" alt="Reddit: r/ironclawAI" /></a>
<a href="https://gitcgr.com/nearai/ironclaw">
<img src="https://gitcgr.com/badge/nearai/ironclaw.svg" alt="gitcgr" />
</a>
</p>
@@ -27,8 +27,6 @@
<p align="center">
<a href="#philosophy">Philosophy</a> •
<a href="#features">Features</a> •
<a href="#mesh-cluster">Mesh Cluster</a> •
<a href="#lazy-tools">Lazy Tools</a> •
<a href="#installation">Installation</a> •
<a href="#configuration">Configuration</a> •
<a href="#security">Security</a> •
@@ -39,16 +37,16 @@
## Philosophy
OptimClaw is built on a simple principle: **your AI assistant should work for you, not against you**.
IronClaw is built on a simple principle: **your AI assistant should work for you, not against you**.
In a world where AI systems are increasingly opaque about data handling and aligned with corporate interests, OptimClaw takes a different approach:
In a world where AI systems are increasingly opaque about data handling and aligned with corporate interests, IronClaw takes a different approach:
- **Your data stays yours** - All information is stored locally, encrypted, and never leaves your control
- **Transparency by design** - Open source, auditable, no hidden telemetry or data harvesting
- **Self-expanding capabilities** - Build new tools on the fly without waiting for vendor updates
- **Defense in depth** - Multiple security layers protect against prompt injection and data exfiltration
OptimClaw is the AI assistant you can actually trust with your personal and professional life.
IronClaw is the AI assistant you can actually trust with your personal and professional life.
## Features
@@ -71,7 +69,7 @@ OptimClaw is the AI assistant you can actually trust with your personal and prof
### Self-Expanding
- **Dynamic Tool Building** - Describe what you need, and OptimClaw builds it as a WASM tool
- **Dynamic Tool Building** - Describe what you need, and IronClaw builds it as a WASM tool
- **MCP Protocol** - Connect to Model Context Protocol servers for additional capabilities
- **Plugin Architecture** - Drop in new WASM tools and channels without restarting
@@ -81,50 +79,6 @@ OptimClaw is the AI assistant you can actually trust with your personal and prof
- **Workspace Filesystem** - Flexible path-based storage for notes, logs, and context
- **Identity Files** - Maintain consistent personality and preferences across sessions
## Mesh Cluster
OptimClaw instances can form an **autonomous AI mesh network** where nodes discover each other automatically, coordinate via a gossip protocol, and route tasks intelligently across the cluster.
Key highlights:
- **Zero-config discovery** -- UDP beacon broadcast finds peers on the local network automatically
- **Post-quantum encryption** -- ML-KEM-768 key exchange with AES-256-GCM authenticated encryption protects all inter-node traffic against both classical and quantum adversaries
- **SWIM gossip membership** -- Reliable failure detection and cluster state convergence in O(log N) rounds
- **Intelligent task routing** -- A scoring algorithm balances load, latency, capability match, session affinity, and region locality to pick the best node for each task
- **Graceful degradation** -- Nodes operate independently if connectivity is lost; no split-brain data corruption
Quick start (two nodes on one machine):
```bash
# Terminal 1
export CLUSTER_ENABLED=true CLUSTER_SECRET="your-32-char-secret-here-change-me" CLUSTER_NODE_ID=node-a
cargo run
# Terminal 2
export CLUSTER_ENABLED=true CLUSTER_SECRET="your-32-char-secret-here-change-me" CLUSTER_NODE_ID=node-b CLUSTER_BIND_PORT=9410
cargo run
```
Monitor via `GET /api/mesh/status` and `GET /api/mesh/nodes`.
See [docs/MESH_CLUSTER.md](docs/MESH_CLUSTER.md) for the full guide covering architecture, configuration reference, security model, and troubleshooting.
## Lazy Tools
Lazy tool loading reduces the system prompt from approximately 13,000 tokens to approximately 4,000 tokens by deferring tool schemas that are not immediately needed.
Enable it with:
```bash
export OPTIMCLAW_LAZY_TOOLS=1
```
When enabled, 12 core tools (echo, time, json, http, web_fetch, file_read, file_write, shell, memory_search, memory_write, message, tool_info) are loaded eagerly. All other tools -- including MCP, WASM, and skill tools -- are listed by name only. The LLM calls `tool_info` to load the full schema for any additional tool on demand.
This is recommended for production deployments and cost-sensitive usage with expensive models.
See [docs/LAZY_TOOLS.md](docs/LAZY_TOOLS.md) for the full guide.
## Installation
### Prerequisites
@@ -135,12 +89,12 @@ See [docs/LAZY_TOOLS.md](docs/LAZY_TOOLS.md) for the full guide.
## Download or Build
Visit [Releases page](https://github.com/nearai/optimclaw/releases/) to see the latest updates.
Visit [Releases page](https://github.com/nearai/ironclaw/releases/) to see the latest updates.
<details>
<summary>Install via Windows Installer (Windows)</summary>
Download the [Windows Installer](https://github.com/nearai/optimclaw/releases/latest/download/optimclaw-x86_64-pc-windows-msvc.msi) and run it.
Download the [Windows Installer](https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-x86_64-pc-windows-msvc.msi) and run it.
</details>
@@ -148,7 +102,7 @@ Download the [Windows Installer](https://github.com/nearai/optimclaw/releases/la
<summary>Install via powershell script (Windows)</summary>
```sh
irm https://github.com/nearai/optimclaw/releases/latest/download/optimclaw-installer.ps1 | iex
irm https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-installer.ps1 | iex
```
</details>
@@ -157,7 +111,7 @@ irm https://github.com/nearai/optimclaw/releases/latest/download/optimclaw-insta
<summary>Install via shell script (macOS, Linux, Windows/WSL)</summary>
```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/optimclaw/releases/latest/download/optimclaw-installer.sh | sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-installer.sh | sh
```
</details>
@@ -165,7 +119,7 @@ curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/optimclaw/releas
<summary>Install via Homebrew (macOS/Linux)</summary>
```sh
brew install optimclaw
brew install ironclaw
```
</details>
@@ -177,8 +131,8 @@ Install it with `cargo`, just make sure you have [Rust](https://rustup.rs) insta
```bash
# Clone the repository
git clone https://github.com/nearai/optimclaw.git
cd optimclaw
git clone https://github.com/nearai/ironclaw.git
cd ironclaw
# Build
cargo build --release
@@ -195,28 +149,28 @@ For **full release** (after modifying channel sources), run `./scripts/build-all
```bash
# Create database
createdb optimclaw
createdb ironclaw
# Enable pgvector
psql optimclaw -c "CREATE EXTENSION IF NOT EXISTS vector;"
psql ironclaw -c "CREATE EXTENSION IF NOT EXISTS vector;"
```
## Configuration
Run the setup wizard to configure OptimClaw:
Run the setup wizard to configure IronClaw:
```bash
optimclaw onboard
ironclaw onboard
```
The wizard handles database connection, NEAR AI authentication (via browser OAuth),
and secrets encryption (using your system keychain). Settings are persisted in the
connected database; bootstrap variables (e.g. `DATABASE_URL`, `LLM_BACKEND`) are
written to `~/.optimclaw/.env` so they are available before the database connects.
written to `~/.ironclaw/.env` so they are available before the database connects.
### Alternative LLM Providers
OptimClaw defaults to NEAR AI but supports many LLM providers out of the box.
IronClaw defaults to NEAR AI but supports many LLM providers out of the box.
Built-in providers include **Anthropic**, **OpenAI**, **GitHub Copilot**, **Google Gemini**, **MiniMax**,
**Mistral**, and **Ollama** (local). OpenAI-compatible services like **OpenRouter**
(300+ models), **Together AI**, **Fireworks AI**, and self-hosted servers (**vLLM**,
@@ -240,7 +194,7 @@ See [docs/LLM_PROVIDERS.md](docs/LLM_PROVIDERS.md) for a full provider guide.
## Security
OptimClaw implements defense in depth to protect your data and prevent misuse.
IronClaw implements defense in depth to protect your data and prevent misuse.
### WASM Sandbox
@@ -333,13 +287,13 @@ External content passes through multiple security layers:
```bash
# First-time setup (configures database, auth, etc.)
optimclaw onboard
ironclaw onboard
# Start interactive REPL
cargo run
# With debug logging
RUST_LOG=optimclaw=debug cargo run
RUST_LOG=ironclaw=debug cargo run
```
## Development
@@ -352,7 +306,7 @@ cargo fmt
cargo clippy --all --benches --tests --examples --all-features
# Run tests
createdb optimclaw_test
createdb ironclaw_test
cargo test
# Run specific test
@@ -364,7 +318,7 @@ cargo test test_name
## OpenClaw Heritage
OptimClaw is a Rust reimplementation inspired by [OpenClaw](https://github.com/openclaw/openclaw). See [FEATURE_PARITY.md](FEATURE_PARITY.md) for the complete tracking matrix.
IronClaw is a Rust reimplementation inspired by [OpenClaw](https://github.com/openclaw/openclaw). See [FEATURE_PARITY.md](FEATURE_PARITY.md) for the complete tracking matrix.
Key differences:
+26 -26
View File
@@ -1,8 +1,8 @@
<p align="center">
<img src="optimclaw.png?v=2" alt="OptimClaw" width="200"/>
<img src="ironclaw.png?v=2" alt="IronClaw" width="200"/>
</p>
<h1 align="center">OptimClaw</h1>
<h1 align="center">IronClaw</h1>
<p align="center">
<strong>Ваш защищенный персональный AI-ассистент, всегда на вашей стороне</strong>
@@ -10,8 +10,8 @@
<p align="center">
<a href="#license"><img src="https://img.shields.io/badge/license-MIT%20OR%20Apache%202.0-blue.svg" alt="Лицензия: MIT OR Apache-2.0" /></a>
<a href="https://t.me/optimclawAI"><img src="https://img.shields.io/badge/Telegram-%40optimclawAI-26A5E4?style=flat&logo=telegram&logoColor=white" alt="Telegram: @optimclawAI" /></a>
<a href="https://www.reddit.com/r/optimclawAI/"><img src="https://img.shields.io/badge/Reddit-r%2FoptimclawAI-FF4500?style=flat&logo=reddit&logoColor=white" alt="Reddit: r/optimclawAI" /></a>
<a href="https://t.me/ironclawAI"><img src="https://img.shields.io/badge/Telegram-%40ironclawAI-26A5E4?style=flat&logo=telegram&logoColor=white" alt="Telegram: @ironclawAI" /></a>
<a href="https://www.reddit.com/r/ironclawAI/"><img src="https://img.shields.io/badge/Reddit-r%2FironclawAI-FF4500?style=flat&logo=reddit&logoColor=white" alt="Reddit: r/ironclawAI" /></a>
</p>
<p align="center">
@@ -34,16 +34,16 @@
## Философия
OptimClaw построен на простом принципе: **ваш AI-ассистент должен работать на вас, а не против вас**.
IronClaw построен на простом принципе: **ваш AI-ассистент должен работать на вас, а не против вас**.
В мире, где системы ИИ становятся все более непрозрачными в вопросах обработки данных и ориентируются на корпоративные интересы, OptimClaw выбирает другой путь:
В мире, где системы ИИ становятся все более непрозрачными в вопросах обработки данных и ориентируются на корпоративные интересы, IronClaw выбирает другой путь:
- **Ваши данные остаются вашими** — вся информация хранится локально, зашифрована и никогда не покидает ваш контроль.
- **Прозрачность по умолчанию** — открытый исходный код, возможность аудита, отсутствие скрытой телеметрии или сбора данных.
- **Саморасширяемые возможности** — создавайте новые инструменты «на лету», не дожидаясь обновлений от вендора.
- **Глубокая защита** — несколько уровней безопасности защищают от инъекций промптов и утечки данных.
OptimClaw — это AI-ассистент, которому вы действительно можете доверять в личной и профессиональной жизни.
IronClaw — это AI-ассистент, которому вы действительно можете доверять в личной и профессиональной жизни.
## Возможности
@@ -66,7 +66,7 @@ OptimClaw — это AI-ассистент, которому вы действи
### Саморасширяемый
- **Динамическое создание инструментов** — опишите, что вам нужно, и OptimClaw создаст это как инструмент WASM.
- **Динамическое создание инструментов** — опишите, что вам нужно, и IronClaw создаст это как инструмент WASM.
- **Протокол MCP** — подключайтесь к серверам Model Context Protocol для получения дополнительных возможностей.
- **Плагинная архитектура** — добавляйте новые инструменты WASM и каналы без перезагрузки системы.
@@ -86,12 +86,12 @@ OptimClaw — это AI-ассистент, которому вы действи
## Загрузка и сборка
Посетите [страницу релизов](https://github.com/nearai/optimclaw/releases/), чтобы увидеть последние обновления.
Посетите [страницу релизов](https://github.com/nearai/ironclaw/releases/), чтобы увидеть последние обновления.
<details>
<summary>Установка через установщик Windows (Windows)</summary>
Загрузите [Windows Installer](https://github.com/nearai/optimclaw/releases/latest/download/optimclaw-x86_64-pc-windows-msvc.msi) и запустите его.
Загрузите [Windows Installer](https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-x86_64-pc-windows-msvc.msi) и запустите его.
</details>
@@ -99,7 +99,7 @@ OptimClaw — это AI-ассистент, которому вы действи
<summary>Установка через powershell-скрипт (Windows)</summary>
```sh
irm https://github.com/nearai/optimclaw/releases/latest/download/optimclaw-installer.ps1 | iex
irm https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-installer.ps1 | iex
```
</details>
@@ -108,7 +108,7 @@ irm https://github.com/nearai/optimclaw/releases/latest/download/optimclaw-insta
<summary>Установка через shell-скрипт (macOS, Linux, Windows/WSL)</summary>
```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/optimclaw/releases/latest/download/optimclaw-installer.sh | sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-installer.sh | sh
```
</details>
@@ -116,7 +116,7 @@ curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/optimclaw/releas
<summary>Установка через Homebrew (macOS/Linux)</summary>
```sh
brew install optimclaw
brew install ironclaw
```
</details>
@@ -128,8 +128,8 @@ brew install optimclaw
```bash
# Клонируйте репозиторий
git clone https://github.com/nearai/optimclaw.git
cd optimclaw
git clone https://github.com/nearai/ironclaw.git
cd ironclaw
# Сборка
cargo build --release
@@ -146,25 +146,25 @@ cargo test
```bash
# Создание базы данных
createdb optimclaw
createdb ironclaw
# Включение pgvector
psql optimclaw -c "CREATE EXTENSION IF NOT EXISTS vector;"
psql ironclaw -c "CREATE EXTENSION IF NOT EXISTS vector;"
```
## Конфигурация
Запустите мастер настройки для конфигурации OptimClaw:
Запустите мастер настройки для конфигурации IronClaw:
```bash
optimclaw onboard
ironclaw onboard
```
Мастер настройки поможет установить соединение с базой данных, пройти аутентификацию NEAR AI (через браузер OAuth) и настроить шифрование секретов (используя системную связку ключей). Настройки сохраняются в базе данных; базовые переменные (например, `DATABASE_URL`, `LLM_BACKEND`) записываются в `~/.optimclaw/.env`, чтобы они были доступны до подключения к БД.
Мастер настройки поможет установить соединение с базой данных, пройти аутентификацию NEAR AI (через браузер OAuth) и настроить шифрование секретов (используя системную связку ключей). Настройки сохраняются в базе данных; базовые переменные (например, `DATABASE_URL`, `LLM_BACKEND`) записываются в `~/.ironclaw/.env`, чтобы они были доступны до подключения к БД.
### Альтернативные LLM-провайдеры
OptimClaw по умолчанию использует NEAR AI, но поддерживает множество LLM-провайдеров из коробки.
IronClaw по умолчанию использует NEAR AI, но поддерживает множество LLM-провайдеров из коробки.
Встроенные провайдеры включают **Anthropic**, **OpenAI**, **Google Gemini**, **MiniMax**,
**Mistral** и **Ollama** (локально). Также поддерживаются OpenAI-совместимые сервисы:
**OpenRouter** (300+ моделей), **Together AI**, **Fireworks AI** и собственные серверы
@@ -188,7 +188,7 @@ LLM_MODEL=anthropic/claude-sonnet-4
## Безопасность
OptimClaw реализует эшелонированную защиту для обеспечения безопасности ваших данных и предотвращения злоупотреблений.
IronClaw реализует эшелонированную защиту для обеспечения безопасности ваших данных и предотвращения злоупотреблений.
### Песочница WASM
@@ -282,13 +282,13 @@ WASM ──► Валидатор ──► Сканер ───► Инъек
```bash
# Первоначальная настройка (БД, аутентификация и т.д.)
optimclaw onboard
ironclaw onboard
# Запуск интерактивного REPL
cargo run
# С отладочными логами
RUST_LOG=optimclaw=debug cargo run
RUST_LOG=ironclaw=debug cargo run
```
## Разработка
@@ -301,7 +301,7 @@ cargo fmt
cargo clippy --all --benches --tests --examples --all-features
# Запуск тестов
createdb optimclaw_test
createdb ironclaw_test
cargo test
# Запуск конкретного теста
@@ -313,7 +313,7 @@ cargo test название_теста
## Наследие OpenClaw
OptimClaw — это реализация на Rust, вдохновленная проектом [OpenClaw](https://github.com/openclaw/openclaw). Полную матрицу соответствия функций можно найти в [FEATURE_PARITY.md](FEATURE_PARITY.md).
IronClaw — это реализация на Rust, вдохновленная проектом [OpenClaw](https://github.com/openclaw/openclaw). Полную матрицу соответствия функций можно найти в [FEATURE_PARITY.md](FEATURE_PARITY.md).
Ключевые отличия:
+26 -26
View File
@@ -1,8 +1,8 @@
<p align="center">
<img src="optimclaw.png?v=2" alt="OptimClaw" width="200"/>
<img src="ironclaw.png?v=2" alt="IronClaw" width="200"/>
</p>
<h1 align="center">OptimClaw</h1>
<h1 align="center">IronClaw</h1>
<p align="center">
<strong>安全可靠的个人 AI 助手,始终站在你这边</strong>
@@ -10,8 +10,8 @@
<p align="center">
<a href="#license"><img src="https://img.shields.io/badge/license-MIT%20OR%20Apache%202.0-blue.svg" alt="License: MIT OR Apache-2.0" /></a>
<a href="https://t.me/optimclawAI"><img src="https://img.shields.io/badge/Telegram-%40optimclawAI-26A5E4?style=flat&logo=telegram&logoColor=white" alt="Telegram: @optimclawAI" /></a>
<a href="https://www.reddit.com/r/optimclawAI/"><img src="https://img.shields.io/badge/Reddit-r%2FoptimclawAI-FF4500?style=flat&logo=reddit&logoColor=white" alt="Reddit: r/optimclawAI" /></a>
<a href="https://t.me/ironclawAI"><img src="https://img.shields.io/badge/Telegram-%40ironclawAI-26A5E4?style=flat&logo=telegram&logoColor=white" alt="Telegram: @ironclawAI" /></a>
<a href="https://www.reddit.com/r/ironclawAI/"><img src="https://img.shields.io/badge/Reddit-r%2FironclawAI-FF4500?style=flat&logo=reddit&logoColor=white" alt="Reddit: r/ironclawAI" /></a>
</p>
<p align="center">
@@ -34,16 +34,16 @@
## 设计理念
OptimClaw 基于一个简单的原则:**你的 AI 助手应该为你服务,而不是与你为敌。**
IronClaw 基于一个简单的原则:**你的 AI 助手应该为你服务,而不是与你为敌。**
在 AI 系统对数据处理日益不透明、与企业利益捆绑的今天,OptimClaw 选择了一条不同的路:
在 AI 系统对数据处理日益不透明、与企业利益捆绑的今天,IronClaw 选择了一条不同的路:
- **数据归你所有** — 所有信息存储在本地,加密保护,始终在你掌控之下
- **透明至上** — 完全开源,可审计,没有隐藏的遥测或数据收集
- **自主扩展** — 随时构建新工具,无需等待供应商更新
- **纵深防御** — 多层安全机制抵御提示注入和数据泄露
OptimClaw 是一个你真正可以信赖的 AI 助手,无论是个人生活还是工作。
IronClaw 是一个你真正可以信赖的 AI 助手,无论是个人生活还是工作。
## 功能特性
@@ -66,7 +66,7 @@ OptimClaw 是一个你真正可以信赖的 AI 助手,无论是个人生活还
### 自主扩展
- **动态工具构建** — 描述你的需求,OptimClaw 会将其构建为 WASM 工具
- **动态工具构建** — 描述你的需求,IronClaw 会将其构建为 WASM 工具
- **MCP 协议** — 连接模型上下文协议(Model Context Protocol)服务器以获取额外能力
- **插件架构** — 无需重启即可加载新的 WASM 工具和渠道
@@ -86,12 +86,12 @@ OptimClaw 是一个你真正可以信赖的 AI 助手,无论是个人生活还
## 下载或编译
访问 [Releases 页面](https://github.com/nearai/optimclaw/releases/) 查看最新版本。
访问 [Releases 页面](https://github.com/nearai/ironclaw/releases/) 查看最新版本。
<details>
<summary>通过 Windows 安装程序安装 (Windows)</summary>
下载 [Windows 安装程序](https://github.com/nearai/optimclaw/releases/latest/download/optimclaw-x86_64-pc-windows-msvc.msi) 并运行。
下载 [Windows 安装程序](https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-x86_64-pc-windows-msvc.msi) 并运行。
</details>
@@ -99,7 +99,7 @@ OptimClaw 是一个你真正可以信赖的 AI 助手,无论是个人生活还
<summary>通过 PowerShell 脚本安装 (Windows)</summary>
```sh
irm https://github.com/nearai/optimclaw/releases/latest/download/optimclaw-installer.ps1 | iex
irm https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-installer.ps1 | iex
```
</details>
@@ -108,7 +108,7 @@ irm https://github.com/nearai/optimclaw/releases/latest/download/optimclaw-insta
<summary>通过 Shell 脚本安装 (macOS、Linux、Windows/WSL)</summary>
```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/optimclaw/releases/latest/download/optimclaw-installer.sh | sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-installer.sh | sh
```
</details>
@@ -116,7 +116,7 @@ curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/optimclaw/releas
<summary>通过 Homebrew 安装 (macOS/Linux)</summary>
```sh
brew install optimclaw
brew install ironclaw
```
</details>
@@ -128,8 +128,8 @@ brew install optimclaw
```bash
# 克隆仓库
git clone https://github.com/nearai/optimclaw.git
cd optimclaw
git clone https://github.com/nearai/ironclaw.git
cd ironclaw
# 编译
cargo build --release
@@ -146,25 +146,25 @@ cargo test
```bash
# 创建数据库
createdb optimclaw
createdb ironclaw
# 启用 pgvector 扩展
psql optimclaw -c "CREATE EXTENSION IF NOT EXISTS vector;"
psql ironclaw -c "CREATE EXTENSION IF NOT EXISTS vector;"
```
## 配置
运行设置向导来配置 OptimClaw
运行设置向导来配置 IronClaw
```bash
optimclaw onboard
ironclaw onboard
```
向导将引导你完成数据库连接、NEAR AI 身份验证(通过浏览器 OAuth)和密钥加密(使用系统钥匙串)。设置会保存在数据库中;引导变量(如 `DATABASE_URL``LLM_BACKEND`)写入 `~/.optimclaw/.env`,以便在数据库连接前可用。
向导将引导你完成数据库连接、NEAR AI 身份验证(通过浏览器 OAuth)和密钥加密(使用系统钥匙串)。设置会保存在数据库中;引导变量(如 `DATABASE_URL``LLM_BACKEND`)写入 `~/.ironclaw/.env`,以便在数据库连接前可用。
### 替代 LLM 提供商
OptimClaw 默认使用 NEAR AI,但开箱即用地支持多种 LLM 提供商。
IronClaw 默认使用 NEAR AI,但开箱即用地支持多种 LLM 提供商。
内置提供商包括 **Anthropic**、**OpenAI**、**GitHub Copilot**、**Google Gemini**、**MiniMax**、**Mistral** 和 **Ollama**(本地部署)。同时也支持 OpenAI 兼容服务,如 **OpenRouter**300+ 模型)、**Together AI**、**Fireworks AI** 以及自托管服务器(**vLLM**、**LiteLLM**)。
在向导中选择你的提供商,或直接设置环境变量:
@@ -185,7 +185,7 @@ LLM_MODEL=anthropic/claude-sonnet-4
## 安全机制
OptimClaw 实现了纵深防御策略来保护你的数据并防止滥用。
IronClaw 实现了纵深防御策略来保护你的数据并防止滥用。
### WASM 沙箱
@@ -278,13 +278,13 @@ WASM ──► 白名单 ──► 泄露扫描 ──► 凭据 ──► 执
```bash
# 首次设置(配置数据库、认证等)
optimclaw onboard
ironclaw onboard
# 启动交互式 REPL
cargo run
# 启用调试日志
RUST_LOG=optimclaw=debug cargo run
RUST_LOG=ironclaw=debug cargo run
```
## 开发
@@ -297,7 +297,7 @@ cargo fmt
cargo clippy --all --benches --tests --examples --all-features
# 运行测试
createdb optimclaw_test
createdb ironclaw_test
cargo test
# 运行指定测试
@@ -309,7 +309,7 @@ cargo test test_name
## OpenClaw 传承
OptimClaw 是受 [OpenClaw](https://github.com/openclaw/openclaw) 启发的 Rust 重新实现。参见 [FEATURE_PARITY.md](FEATURE_PARITY.md) 了解完整的功能追踪矩阵。
IronClaw 是受 [OpenClaw](https://github.com/openclaw/openclaw) 启发的 Rust 重新实现。参见 [FEATURE_PARITY.md](FEATURE_PARITY.md) 了解完整的功能追踪矩阵。
主要差异:
+1 -1
View File
@@ -1,5 +1,5 @@
use criterion::{Criterion, black_box, criterion_group, criterion_main};
use optimclaw::safety::{LeakDetector, Sanitizer, Validator};
use ironclaw::safety::{LeakDetector, Sanitizer, Validator};
fn bench_sanitizer(c: &mut Criterion) {
let mut group = c.benchmark_group("sanitizer");
+2 -2
View File
@@ -1,6 +1,6 @@
use criterion::{Criterion, black_box, criterion_group, criterion_main};
use optimclaw::config::SafetyConfig;
use optimclaw::safety::{SafetyLayer, Validator};
use ironclaw::config::SafetyConfig;
use ironclaw::safety::{SafetyLayer, Validator};
fn bench_safety_layer_pipeline(c: &mut Criterion) {
let mut group = c.benchmark_group("safety_pipeline");
+217 -12
View File
@@ -20,12 +20,27 @@ version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "base64ct"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
@@ -33,20 +48,134 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "discord-channel"
version = "0.2.1"
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "curve25519-dalek"
version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [
"cfg-if",
"cpufeatures",
"curve25519-dalek-derive",
"digest",
"fiat-crypto",
"rustc_version",
"subtle",
"zeroize",
]
[[package]]
name = "curve25519-dalek-derive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "der"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid",
"zeroize",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "discord-channel"
version = "0.2.0"
dependencies = [
"ed25519-dalek",
"hex",
"serde",
"serde_json",
"wit-bindgen",
]
[[package]]
name = "ed25519"
version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
"pkcs8",
"signature",
]
[[package]]
name = "ed25519-dalek"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
dependencies = [
"curve25519-dalek",
"ed25519",
"serde",
"sha2",
"subtle",
"zeroize",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fiat-crypto"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
@@ -68,6 +197,12 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "id-arena"
version = "2.3.0"
@@ -88,9 +223,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.18"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "leb128"
@@ -98,6 +233,12 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
[[package]]
name = "libc"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "log"
version = "0.4.29"
@@ -112,9 +253,19 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "once_cell"
version = "1.21.4"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"spki",
]
[[package]]
name = "prettyplease"
@@ -137,13 +288,22 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.45"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "semver"
version = "1.0.27"
@@ -193,6 +353,23 @@ dependencies = [
"zmij",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "signature"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
[[package]]
name = "smallvec"
version = "1.15.1"
@@ -208,6 +385,22 @@ dependencies = [
"smallvec",
]
[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der",
]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.117"
@@ -219,6 +412,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicode-ident"
version = "1.0.24"
@@ -376,24 +575,30 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.47"
version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.47"
version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zmij"
version = "1.0.21"
+4 -2
View File
@@ -1,8 +1,8 @@
[package]
name = "discord-channel"
version = "0.2.1"
version = "0.2.0"
edition = "2021"
description = "Discord channel for OptimClaw"
description = "Discord channel for IronClaw"
license = "MIT OR Apache-2.0"
publish = false
@@ -10,6 +10,8 @@ publish = false
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = "0.36"
ed25519-dalek = { version = "2", default-features = false, features = ["alloc", "fast", "zeroize"] }
hex = "0.4"
[lib]
crate-type = ["cdylib"]
+6 -25
View File
@@ -1,4 +1,4 @@
# Discord Channel for OptimClaw
# Discord Channel for IronClaw
WASM channel for Discord integration - handle slash commands and button interactions via webhooks.
@@ -13,12 +13,12 @@ WASM channel for Discord integration - handle slash commands and button interact
1. Create a Discord Application at <https://discord.com/developers/applications>
2. Create a Bot and get the token
3. Set up Interactions URL to point to your OptimClaw instance
3. Set up Interactions URL to point to your IronClaw instance
4. Copy the Application ID and Public Key
5. Store in OptimClaw secrets:
5. Store in IronClaw secrets:
```bash
optimclaw secret set discord_bot_token YOUR_BOT_TOKEN
ironclaw secret set discord_bot_token YOUR_BOT_TOKEN
```
**Note:** The `discord_bot_token` secret is used for Discord REST API calls.
@@ -51,7 +51,7 @@ curl -X POST \
In your Discord app settings, set:
- Interactions Endpoint URL: `https://your-optimclaw.com/webhook/discord`
- Interactions Endpoint URL: `https://your-ironclaw.com/webhook/discord`
## Usage Examples
@@ -86,24 +86,6 @@ If an internal error occurs (e.g., metadata serialization failure), the tool att
Check the host logs for detailed error information.
## Advanced Usage
### Gateway Mode
The Discord channel now defaults to Discord Gateway transport for inbound message intake.
The bundled identify payload requests intents `4609`, which expands to:
- `GUILDS` (`1`)
- `GUILD_MESSAGES` (`512`)
- `DIRECT_MESSAGES` (`4096`)
Gateway DMs now follow the same pairing policy as webhook DMs. Unpaired users receive a pairing
instruction reply in the DM channel before the message is allowed through to the agent. If you
want stricter access control than pairing, set `owner_id`; that lock still applies to both
webhook and Gateway traffic.
Gateway presence simply reflects a successful authenticated Gateway connection and advertises
`online`. Pairing still controls whether DMs are allowed through to the agent, but it no longer
changes the visible Discord status.
### Mention Polling
The Discord channel can also poll configured channels for `@bot` mentions.
@@ -128,7 +110,6 @@ Example channel config:
- `owner_id`: when set, only that Discord user can interact with the bot.
- `dm_policy`: `open` allows all DMs; `pairing` requires approval.
- `allow_from`: allowlist entries for DM pairing checks (`*`, user id, or username).
- Gateway DMs respect `dm_policy` and pairing just like webhook DMs.
### Embeds
@@ -146,7 +127,7 @@ To send embeds, include an `embeds` array in the `metadata_json` field of the ag
### "401 Unauthorized"
- Check that `discord_bot_token` is set correctly in OptimClaw secrets.
- Check that `discord_bot_token` is set correctly in IronClaw secrets.
- Ensure the bot is added to the server.
### "Interaction Failed"
+2 -17
View File
@@ -1,5 +1,5 @@
{
"version": "0.2.1",
"version": "0.2.0",
"wit_version": "0.3.0",
"type": "channel",
"name": "discord",
@@ -22,8 +22,7 @@
"capabilities": {
"http": {
"allowlist": [
{ "host": "discord.com", "path_prefix": "/api/v10" },
{ "host": "gateway.discord.gg", "path_prefix": "/", "methods": ["GET"] }
{ "host": "discord.com", "path_prefix": "/api/v10" }
],
"credentials": {
"discord_bot_token": {
@@ -37,20 +36,6 @@
"requests_per_hour": 3600
}
},
"websocket": {
"url": "wss://gateway.discord.gg/?v=10&encoding=json",
"connect_on_start": true,
"identify_secret_name": "discord_bot_token",
"identify": {
"_intents_doc": "GUILDS(1) + GUILD_MESSAGES(512) + DIRECT_MESSAGES(4096)",
"intents": 4609,
"properties": {
"os": "linux",
"browser": "ironclaw",
"device": "ironclaw"
}
}
},
"secrets": {
"allowed_names": ["discord_bot_token", "discord_*"]
},
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -2,7 +2,7 @@
name = "feishu-channel"
version = "0.1.0"
edition = "2021"
description = "Feishu/Lark Bot channel for OptimClaw"
description = "Feishu/Lark Bot channel for IronClaw"
license = "MIT OR Apache-2.0"
[lib]
+1 -1
View File
@@ -7,7 +7,7 @@
"auth": {
"secret_name": "feishu_app_id",
"display_name": "Feishu / Lark",
"instructions": "Create a bot at https://open.feishu.cn/app (Feishu) or https://open.larksuite.com/app (Lark). You need the App ID and App Secret. Note: OptimClaw supports Event Subscription webhook delivery, but not Feishu's long-connection websocket mode.",
"instructions": "Create a bot at https://open.feishu.cn/app (Feishu) or https://open.larksuite.com/app (Lark). You need the App ID and App Secret. Note: IronClaw supports Event Subscription webhook delivery, but not Feishu's long-connection websocket mode.",
"setup_url": "https://open.feishu.cn/app",
"token_hint": "App ID looks like cli_XXXX, App Secret is a long alphanumeric string",
"env_var": "FEISHU_APP_ID"
+2 -2
View File
@@ -1,11 +1,11 @@
// Feishu API types have fields reserved for future use.
#![allow(dead_code)]
//! Feishu/Lark Bot channel for OptimClaw.
//! Feishu/Lark Bot channel for IronClaw.
//!
//! This WASM component implements the channel interface for handling Feishu
//! webhooks (Event Subscription v2.0) and sending messages back via the
//! Feishu/Lark Bot API. OptimClaw currently does not connect to Feishu's
//! Feishu/Lark Bot API. IronClaw currently does not connect to Feishu's
//! long-connection websocket subscription mode; use Event Subscription
//! webhooks for this channel.
//!
+1 -1
View File
@@ -2,7 +2,7 @@
name = "slack-channel"
version = "0.2.1"
edition = "2021"
description = "Slack Events API channel for OptimClaw"
description = "Slack Events API channel for IronClaw"
license = "MIT OR Apache-2.0"
[lib]
+2 -2
View File
@@ -1,4 +1,4 @@
//! Slack Events API channel for OptimClaw.
//! Slack Events API channel for IronClaw.
//!
//! This WASM component implements the channel interface for handling Slack
//! webhooks and sending messages back to Slack.
@@ -650,7 +650,7 @@ fn send_pairing_reply(channel_id: &str, code: &str) -> Result<(), String> {
let payload = serde_json::json!({
"channel": channel_id,
"text": format!(
"To pair with this bot, run: `optimclaw pairing approve slack {}`",
"To pair with this bot, run: `ironclaw pairing approve slack {}`",
code
),
});
+1 -1
View File
@@ -2,7 +2,7 @@
name = "telegram-channel"
version = "0.2.1"
edition = "2021"
description = "Telegram Bot API channel for OptimClaw"
description = "Telegram Bot API channel for IronClaw"
license = "MIT OR Apache-2.0"
[lib]
+4 -4
View File
@@ -1,7 +1,7 @@
// Telegram API types have fields reserved for future use (entities, reply threading, etc.)
#![allow(dead_code)]
//! Telegram Bot API channel for OptimClaw.
//! Telegram Bot API channel for IronClaw.
//!
//! This WASM component implements the channel interface for handling Telegram
//! webhooks and sending messages back via the Bot API.
@@ -1170,7 +1170,7 @@ fn send_photo(
);
}
let boundary = format!("optimclaw-{}", channel_host::now_millis());
let boundary = format!("ironclaw-{}", channel_host::now_millis());
let mut body = Vec::new();
write_multipart_field(&mut body, &boundary, "chat_id", &chat_id.to_string());
@@ -1235,7 +1235,7 @@ fn send_document(
) -> Result<(), String> {
let message_thread_id = normalize_thread_id(message_thread_id);
let boundary = format!("optimclaw-{}", channel_host::now_millis());
let boundary = format!("ironclaw-{}", channel_host::now_millis());
let mut body = Vec::new();
write_multipart_field(&mut body, &boundary, "chat_id", &chat_id.to_string());
@@ -1552,7 +1552,7 @@ fn send_pairing_reply(chat_id: i64, code: &str) -> Result<(), String> {
send_message(
chat_id,
&format!(
"To pair with this bot, run: `optimclaw pairing approve telegram {}`",
"To pair with this bot, run: `ironclaw pairing approve telegram {}`",
code
),
None,
+1 -1
View File
@@ -2,7 +2,7 @@
name = "whatsapp-channel"
version = "0.2.0"
edition = "2021"
description = "WhatsApp Cloud API channel for OptimClaw"
description = "WhatsApp Cloud API channel for IronClaw"
[lib]
crate-type = ["cdylib"]
+2 -2
View File
@@ -1,7 +1,7 @@
// WhatsApp API types have fields reserved for future use (contacts, statuses, etc.)
#![allow(dead_code)]
//! WhatsApp Cloud API channel for OptimClaw.
//! WhatsApp Cloud API channel for IronClaw.
//!
//! This WASM component implements the channel interface for handling WhatsApp
//! webhooks and sending messages back via the Cloud API.
@@ -910,7 +910,7 @@ fn send_pairing_reply(
"text": {
"preview_url": false,
"body": format!(
"To pair with this bot, run: optimclaw pairing approve whatsapp {}",
"To pair with this bot, run: ironclaw pairing approve whatsapp {}",
code
)
}
+1 -1
View File
@@ -1,6 +1,6 @@
# Complexity guardrails for AI-assisted development quality.
# These thresholds prevent new violations while preserving existing code.
# See: https://github.com/nearai/optimclaw/issues/338
# See: https://github.com/nearai/ironclaw/issues/338
cognitive-complexity-threshold = 15 # default: 25 (only active when lint is enabled)
too-many-lines-threshold = 100 # default: 100 (only active when lint is enabled)
@@ -1,13 +1,13 @@
[package]
name = "optimclaw_common"
name = "ironclaw_common"
version = "0.1.0"
edition = "2024"
rust-version = "1.92"
description = "Shared types and utilities for the OptimClaw workspace"
description = "Shared types and utilities for the IronClaw workspace"
authors = ["NEAR AI <[email protected]>"]
license = "MIT OR Apache-2.0"
homepage = "https://github.com/nearai/optimclaw"
repository = "https://github.com/nearai/optimclaw"
homepage = "https://github.com/nearai/ironclaw"
repository = "https://github.com/nearai/ironclaw"
[package.metadata.dist]
dist = false
@@ -1,4 +1,4 @@
//! Shared types and utilities for the OptimClaw workspace.
//! Shared types and utilities for the IronClaw workspace.
mod event;
mod util;
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "ironclaw_frontend"
version = "0.1.0"
edition = "2024"
rust-version = "1.92"
description = "Frontend assets, layout configuration, and widget extension system for IronClaw"
license = "MIT OR Apache-2.0"
[package.metadata.dist]
dist = false
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "2"
+37
View File
@@ -0,0 +1,37 @@
//! Embedded static assets for the IronClaw web gateway.
//!
//! All frontend files are compiled into the binary via `include_str!()` /
//! `include_bytes!()`. The web gateway serves these as the default baseline;
//! workspace-stored customizations (layout config, widgets, CSS overrides)
//! are layered on top at runtime.
// ==================== Core Files ====================
/// Main HTML page (SPA shell).
pub const INDEX_HTML: &str = include_str!("../static/index.html");
/// Main application JavaScript.
pub const APP_JS: &str = include_str!("../static/app.js");
/// Base stylesheet.
pub const STYLE_CSS: &str = include_str!("../static/style.css");
/// Theme initialization script (runs synchronously in `<head>` to prevent FOUC).
pub const THEME_INIT_JS: &str = include_str!("../static/theme-init.js");
/// Favicon.
pub const FAVICON_ICO: &[u8] = include_bytes!("../static/favicon.ico");
// ==================== Internationalization ====================
/// i18n core library.
pub const I18N_INDEX_JS: &str = include_str!("../static/i18n/index.js");
/// English translations.
pub const I18N_EN_JS: &str = include_str!("../static/i18n/en.js");
/// Chinese (Simplified) translations.
pub const I18N_ZH_CN_JS: &str = include_str!("../static/i18n/zh-CN.js");
/// i18n integration with the app.
pub const I18N_APP_JS: &str = include_str!("../static/i18n-app.js");
+237
View File
@@ -0,0 +1,237 @@
//! Frontend bundle assembly.
//!
//! Combines the embedded base HTML with workspace customizations (layout
//! config, widgets, CSS overrides) into the final served page.
use crate::layout::LayoutConfig;
use crate::widget::{WidgetManifest, scope_css};
/// A resolved frontend bundle ready for serving.
///
/// Contains the layout configuration, resolved widgets (with their JS/CSS
/// content loaded), and any custom CSS overrides.
#[derive(Debug, Clone, Default)]
pub struct FrontendBundle {
/// Layout configuration (branding, tabs, chat settings).
pub layout: LayoutConfig,
/// Resolved widgets with their source code loaded.
pub widgets: Vec<ResolvedWidget>,
/// Custom CSS to append after the base stylesheet.
pub custom_css: Option<String>,
}
/// A widget with its manifest and source files loaded.
#[derive(Debug, Clone)]
pub struct ResolvedWidget {
/// Widget metadata.
pub manifest: WidgetManifest,
/// JavaScript source code (`index.js`).
pub js: String,
/// Optional CSS source code (`style.css`), auto-scoped.
pub css: Option<String>,
}
/// Inject frontend customizations into the base HTML template.
///
/// Modifications:
///
/// **Before `</head>`:**
/// - Branding CSS custom property overrides
/// - Title override (replaces `<title>` content)
///
/// **Before `</body>`:**
/// - Layout config as `window.__IRONCLAW_LAYOUT__`
/// - Scoped widget `<style>` blocks
/// - Widget `<script type="module">` tags
/// - Custom CSS `<style>` block
pub fn assemble_index(base_html: &str, bundle: &FrontendBundle) -> String {
let mut head_injections = Vec::new();
let mut body_injections = Vec::new();
// --- Head injections ---
// Branding CSS variables
let css_vars = bundle.layout.branding.to_css_vars();
if !css_vars.is_empty() {
head_injections.push(format!("<style>{}</style>", css_vars));
}
// --- Body injections ---
// Layout config as global variable
if let Ok(layout_json) = serde_json::to_string(&bundle.layout) {
body_injections.push(format!(
"<script>window.__IRONCLAW_LAYOUT__ = {};</script>",
layout_json
));
}
// Widget CSS (scoped) and JS
for widget in &bundle.widgets {
if let Some(ref css) = widget.css {
let scoped = scope_css(css, &widget.manifest.id);
if !scoped.trim().is_empty() {
body_injections.push(format!(
"<style data-widget=\"{}\">{}</style>",
widget.manifest.id, scoped
));
}
}
// Widget JS as module script served from API
body_injections.push(format!(
"<script type=\"module\" src=\"/api/frontend/widget/{}/index.js\"></script>",
widget.manifest.id
));
}
// Custom CSS
if let Some(ref custom_css) = bundle.custom_css {
if !custom_css.trim().is_empty() {
body_injections.push(format!("<style data-custom-css>{}</style>", custom_css));
}
}
// --- Assemble ---
let mut result = base_html.to_string();
// Inject before </head>
if !head_injections.is_empty() {
let head_block = head_injections.join("\n");
if let Some(pos) = result.rfind("</head>") {
result.insert_str(pos, &format!("\n{}\n", head_block));
}
}
// Override <title> if branding title is set
if let Some(ref title) = bundle.layout.branding.title {
if let Some(start) = result.find("<title>") {
if let Some(end) = result[start..].find("</title>") {
let end = start + end + "</title>".len();
result.replace_range(start..end, &format!("<title>{}</title>", title));
}
}
}
// Inject before </body>
if !body_injections.is_empty() {
let body_block = body_injections.join("\n");
if let Some(pos) = result.rfind("</body>") {
result.insert_str(pos, &format!("\n{}\n", body_block));
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout::*;
use crate::widget::*;
const MINIMAL_HTML: &str =
"<!DOCTYPE html><html><head><title>IronClaw</title></head><body></body></html>";
#[test]
fn test_assemble_index_no_customizations() {
let bundle = FrontendBundle::default();
let result = assemble_index(MINIMAL_HTML, &bundle);
// Layout config is always injected (even when default/empty)
assert!(result.contains("window.__IRONCLAW_LAYOUT__"));
// No branding overrides or custom CSS
assert!(!result.contains("--color-primary"));
assert!(!result.contains("data-custom-css"));
}
#[test]
fn test_assemble_index_branding_title() {
let bundle = FrontendBundle {
layout: LayoutConfig {
branding: BrandingConfig {
title: Some("Acme AI".to_string()),
..Default::default()
},
..Default::default()
},
..Default::default()
};
let result = assemble_index(MINIMAL_HTML, &bundle);
assert!(result.contains("<title>Acme AI</title>"));
assert!(!result.contains("<title>IronClaw</title>"));
}
#[test]
fn test_assemble_index_branding_colors() {
let bundle = FrontendBundle {
layout: LayoutConfig {
branding: BrandingConfig {
colors: Some(BrandingColors {
primary: Some("#0066cc".to_string()),
accent: None,
}),
..Default::default()
},
..Default::default()
},
..Default::default()
};
let result = assemble_index(MINIMAL_HTML, &bundle);
assert!(result.contains("--color-primary: #0066cc;"));
}
#[test]
fn test_assemble_index_layout_config_injected() {
let bundle = FrontendBundle {
layout: LayoutConfig {
tabs: TabConfig {
hidden: Some(vec!["routines".to_string()]),
..Default::default()
},
..Default::default()
},
..Default::default()
};
let result = assemble_index(MINIMAL_HTML, &bundle);
assert!(result.contains("window.__IRONCLAW_LAYOUT__"));
assert!(result.contains("routines"));
}
#[test]
fn test_assemble_index_widget_script() {
let bundle = FrontendBundle {
widgets: vec![ResolvedWidget {
manifest: WidgetManifest {
id: "dashboard".to_string(),
name: "Dashboard".to_string(),
slot: WidgetSlot::Tab,
icon: None,
position: None,
},
js: "console.log('hello');".to_string(),
css: Some(".panel { color: red; }".to_string()),
}],
..Default::default()
};
let result = assemble_index(MINIMAL_HTML, &bundle);
assert!(result.contains("src=\"/api/frontend/widget/dashboard/index.js\""));
assert!(result.contains("data-widget=\"dashboard\""));
assert!(result.contains("[data-widget=\"dashboard\"] .panel"));
}
#[test]
fn test_assemble_index_custom_css() {
let bundle = FrontendBundle {
custom_css: Some("body { background: #111; }".to_string()),
..Default::default()
};
let result = assemble_index(MINIMAL_HTML, &bundle);
assert!(result.contains("data-custom-css"));
assert!(result.contains("background: #111;"));
}
}
+179
View File
@@ -0,0 +1,179 @@
//! Layout configuration types for frontend customization.
//!
//! A [`LayoutConfig`] is stored as `frontend/layout.json` in the workspace.
//! It controls branding, tab visibility/order, chat features, and per-widget
//! configuration. All fields are optional with sensible defaults.
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
/// Top-level layout configuration.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LayoutConfig {
/// Branding overrides (title, logo, colors).
#[serde(default)]
pub branding: BrandingConfig,
/// Tab bar configuration.
#[serde(default)]
pub tabs: TabConfig,
/// Chat panel configuration.
#[serde(default)]
pub chat: ChatConfig,
/// Per-widget instance configuration (keyed by widget ID).
#[serde(default)]
pub widgets: HashMap<String, WidgetInstanceConfig>,
}
/// Branding overrides for the gateway UI.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BrandingConfig {
/// Page title (replaces default "IronClaw").
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
/// Subtitle shown below the title.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subtitle: Option<String>,
/// URL to a logo image.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub logo_url: Option<String>,
/// URL to a custom favicon.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub favicon_url: Option<String>,
/// Color overrides (injected as CSS custom properties on `:root`).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub colors: Option<BrandingColors>,
}
/// Color overrides for the UI theme.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BrandingColors {
/// Primary brand color (e.g., `"#0066cc"`).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub primary: Option<String>,
/// Accent color.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub accent: Option<String>,
}
/// Tab bar layout configuration.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TabConfig {
/// Ordered list of tab IDs to display (built-in + widget tabs).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub order: Option<Vec<String>>,
/// Tab IDs to hide from the tab bar.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hidden: Option<Vec<String>>,
/// Default tab to show on load.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_tab: Option<String>,
}
/// Chat panel feature flags.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChatConfig {
/// Show suggestion chips below the input.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub suggestions: Option<bool>,
/// Enable image upload in the chat input.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub image_upload: Option<bool>,
}
/// Per-widget instance configuration.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct WidgetInstanceConfig {
/// Whether this widget is enabled.
#[serde(default)]
pub enabled: bool,
/// Arbitrary widget-specific configuration passed to `widget.init()`.
#[serde(default)]
pub config: serde_json::Value,
}
impl BrandingConfig {
/// Generate CSS custom property overrides for injection into `:root`.
pub fn to_css_vars(&self) -> String {
let mut vars = Vec::new();
if let Some(ref colors) = self.colors {
if let Some(ref primary) = colors.primary {
vars.push(format!("--color-primary: {};", primary));
}
if let Some(ref accent) = colors.accent {
vars.push(format!("--color-accent: {};", accent));
}
}
if vars.is_empty() {
String::new()
} else {
format!(":root {{ {} }}", vars.join(" "))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_layout_config_default_is_empty() {
let config = LayoutConfig::default();
assert!(config.branding.title.is_none());
assert!(config.tabs.order.is_none());
assert!(config.widgets.is_empty());
}
#[test]
fn test_layout_config_roundtrip() {
let json = serde_json::json!({
"branding": { "title": "Acme AI", "colors": { "primary": "#0066cc" } },
"tabs": { "order": ["chat", "memory"], "hidden": ["routines"] },
"widgets": { "dashboard": { "enabled": true, "config": { "refresh": 30 } } }
});
let config: LayoutConfig = serde_json::from_value(json).unwrap();
assert_eq!(config.branding.title.as_deref(), Some("Acme AI"));
assert_eq!(config.tabs.hidden.as_ref().map(|h| h.len()), Some(1));
assert!(config.widgets.get("dashboard").is_some_and(|w| w.enabled));
}
#[test]
fn test_branding_css_vars_empty() {
let branding = BrandingConfig::default();
assert!(branding.to_css_vars().is_empty());
}
#[test]
fn test_branding_css_vars_with_colors() {
let branding = BrandingConfig {
colors: Some(BrandingColors {
primary: Some("#0066cc".to_string()),
accent: Some("#ff6b00".to_string()),
}),
..Default::default()
};
let css = branding.to_css_vars();
assert!(css.contains("--color-primary: #0066cc;"));
assert!(css.contains("--color-accent: #ff6b00;"));
}
#[test]
fn test_partial_deserialization() {
let json = serde_json::json!({"branding": {"title": "Test"}});
let config: LayoutConfig = serde_json::from_value(json).unwrap();
assert_eq!(config.branding.title.as_deref(), Some("Test"));
assert!(config.chat.suggestions.is_none());
}
}
+36
View File
@@ -0,0 +1,36 @@
//! IronClaw Frontend — assets, layout configuration, and widget extension system.
//!
//! This crate owns the complete frontend for the IronClaw web gateway:
//!
//! - **Embedded assets** (`assets` module): HTML, JS, CSS, i18n files compiled
//! into the binary for zero-dependency serving.
//! - **Layout configuration** (`layout` module): Branding, tab order, feature
//! flags — customizable per-tenant via workspace.
//! - **Widget system** (`widget` module): Self-contained frontend components
//! that plug into named slots in the UI.
//! - **Bundle assembly** (`bundle` module): Combines base assets with workspace
//! customizations into the final served HTML.
pub mod assets;
mod bundle;
mod layout;
mod widget;
pub use bundle::{FrontendBundle, ResolvedWidget, assemble_index};
pub use layout::{
BrandingColors, BrandingConfig, ChatConfig, LayoutConfig, TabConfig, WidgetInstanceConfig,
};
pub use widget::{WidgetManifest, WidgetSlot, scope_css};
/// Errors from frontend operations.
#[derive(Debug, thiserror::Error)]
pub enum FrontendError {
#[error("Layout configuration is invalid: {reason}")]
InvalidLayout { reason: String },
#[error("Widget '{id}' not found")]
WidgetNotFound { id: String },
#[error("Widget manifest is invalid: {reason}")]
InvalidManifest { reason: String },
}
+178
View File
@@ -0,0 +1,178 @@
//! Widget system types and utilities.
//!
//! Widgets are self-contained frontend components that plug into named
//! [`WidgetSlot`]s in the UI. Each widget has a manifest (`widget.json`)
//! and implementation files (`index.js`, optional `style.css`).
use serde::{Deserialize, Serialize};
/// Widget manifest — metadata about a widget component.
///
/// Stored as `frontend/widgets/{id}/manifest.json` in the workspace.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WidgetManifest {
/// Unique widget identifier (must be a valid HTML attribute value).
pub id: String,
/// Human-readable widget name.
pub name: String,
/// Where this widget is rendered in the UI.
pub slot: WidgetSlot,
/// Optional icon identifier (CSS class or emoji).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub icon: Option<String>,
/// Positioning hint (e.g., `"after:memory"`, `"before:jobs"`).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub position: Option<String>,
}
/// Named insertion points in the UI where widgets can be rendered.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum WidgetSlot {
/// Full tab panel (adds a new tab to the tab bar).
Tab,
/// Banner area above the chat message list.
ChatHeader,
/// Area below the chat input.
ChatFooter,
/// Extra action buttons next to the send button.
ChatActions,
/// Right sidebar panel.
Sidebar,
/// Left side of the status bar.
StatusLeft,
/// Right side of the status bar.
StatusRight,
/// Additional section in the Settings tab.
SettingsSection,
/// Custom inline renderer for structured data in chat messages.
/// Registered via `IronClaw.registerChatRenderer()` on the browser side.
ChatRenderer,
}
/// Prefix every CSS selector with `[data-widget="{widget_id}"]` for style isolation.
///
/// This prevents widget styles from bleeding into the main app or other widgets.
/// The widget container element gets `data-widget="{id}"` set by the runtime.
///
/// # Example
///
/// ```
/// use ironclaw_frontend::scope_css;
///
/// let scoped = scope_css(".title { color: red; }", "my-widget");
/// assert!(scoped.contains("[data-widget=\"my-widget\"] .title"));
/// ```
pub fn scope_css(css: &str, widget_id: &str) -> String {
let prefix = format!("[data-widget=\"{}\"]", widget_id);
let mut result = String::with_capacity(css.len() + css.len() / 4);
let mut chars = css.chars().peekable();
let mut in_block = false;
let mut current_selector = String::new();
while let Some(ch) = chars.next() {
match ch {
'{' if !in_block => {
// Scope each comma-separated selector
let selectors: Vec<&str> = current_selector.split(',').collect();
let scoped: Vec<String> = selectors
.iter()
.map(|s| {
let s = s.trim();
if s.is_empty() || s.starts_with('@') {
s.to_string()
} else {
format!("{} {}", prefix, s)
}
})
.collect();
result.push_str(&scoped.join(", "));
result.push_str(" {");
current_selector.clear();
in_block = true;
}
'}' if in_block => {
result.push('}');
in_block = false;
}
_ if in_block => {
result.push(ch);
}
_ => {
current_selector.push(ch);
}
}
}
// Append any trailing content
if !current_selector.is_empty() {
result.push_str(&current_selector);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_widget_manifest_roundtrip() {
let json = serde_json::json!({
"id": "dashboard",
"name": "Analytics Dashboard",
"slot": "tab",
"icon": "chart-bar",
"position": "after:memory"
});
let manifest: WidgetManifest = serde_json::from_value(json).unwrap();
assert_eq!(manifest.id, "dashboard");
assert_eq!(manifest.slot, WidgetSlot::Tab);
assert_eq!(manifest.icon.as_deref(), Some("chart-bar"));
}
#[test]
fn test_widget_slot_serialization() {
assert_eq!(
serde_json::to_string(&WidgetSlot::ChatHeader).unwrap(),
"\"chat_header\""
);
assert_eq!(
serde_json::to_string(&WidgetSlot::SettingsSection).unwrap(),
"\"settings_section\""
);
}
#[test]
fn test_scope_css_basic() {
let input = ".title { color: red; }";
let result = scope_css(input, "my-widget");
assert!(result.contains("[data-widget=\"my-widget\"] .title"));
assert!(result.contains("color: red;"));
}
#[test]
fn test_scope_css_multiple_selectors() {
let input = ".a, .b { margin: 0; }";
let result = scope_css(input, "w");
assert!(result.contains("[data-widget=\"w\"] .a"));
assert!(result.contains("[data-widget=\"w\"] .b"));
}
#[test]
fn test_scope_css_multiple_rules() {
let input = ".a { color: red; } .b { color: blue; }";
let result = scope_css(input, "w");
assert!(result.contains("[data-widget=\"w\"] .a"));
assert!(result.contains("[data-widget=\"w\"] .b"));
}
#[test]
fn test_scope_css_empty() {
assert_eq!(scope_css("", "w"), "");
}
}
@@ -95,6 +95,118 @@ let authFlowPending = false;
let _ghostSuggestion = '';
let currentSettingsSubtab = 'inference';
// --- Hash-based URL Navigation ---
//
// Encodes navigation state in window.location.hash so refreshing
// the page restores the current tab, thread, memory file, job detail, etc.
//
// Hash format: #/{tab}[/{detail}[/{subtab}]]
// #/chat → chat tab, assistant thread
// #/chat/{threadId} → chat tab, specific thread
// #/memory → memory tab, tree root
// #/memory/{path/to/file} → memory tab, specific file
// #/jobs → jobs list
// #/jobs/{jobId} → job detail
// #/routines → routines list
// #/routines/{id} → routine detail
// #/settings/{subtab} → settings tab with specific sub-tab
// #/logs → logs tab
/** Suppress hash-change handling while we're programmatically updating. */
let _suppressHashChange = false;
/** Update the URL hash to reflect current navigation state. */
function updateHash() {
var parts = [currentTab];
switch (currentTab) {
case 'chat':
if (currentThreadId && currentThreadId !== assistantThreadId) {
parts.push(currentThreadId);
}
break;
case 'memory':
if (typeof currentMemoryPath === 'string' && currentMemoryPath) {
parts.push(currentMemoryPath);
}
break;
case 'jobs':
if (typeof currentJobId !== 'undefined' && currentJobId) {
parts.push(currentJobId);
}
break;
case 'routines':
if (typeof currentRoutineId !== 'undefined' && currentRoutineId) {
parts.push(currentRoutineId);
}
break;
case 'settings':
if (currentSettingsSubtab && currentSettingsSubtab !== 'inference') {
parts.push(currentSettingsSubtab);
}
break;
}
var hash = '#/' + parts.join('/');
_suppressHashChange = true;
if (window.location.hash !== hash) {
window.history.replaceState(null, '', hash);
}
_suppressHashChange = false;
}
/** Parse the current URL hash into navigation state. */
function parseHash() {
var hash = window.location.hash || '';
if (!hash.startsWith('#/')) return null;
var parts = hash.substring(2).split('/');
return {
tab: parts[0] || 'chat',
detail: parts.slice(1).join('/') || null,
};
}
/**
* Restore navigation state from the URL hash.
* Called once after authentication and on hashchange events.
*/
function restoreFromHash() {
var state = parseHash();
if (!state) return;
// Switch tab (without recursively updating hash)
if (state.tab && state.tab !== currentTab) {
switchTab(state.tab);
}
// Restore detail state within the tab
if (state.detail) {
switch (state.tab) {
case 'chat':
// Defer thread switch until threads are loaded
window._pendingThreadRestore = state.detail;
break;
case 'memory':
readMemoryFile(state.detail);
break;
case 'jobs':
openJobDetail(state.detail);
break;
case 'routines':
openRoutineDetail(state.detail);
break;
case 'settings':
switchSettingsSubtab(state.detail);
break;
}
}
}
window.addEventListener('hashchange', function() {
if (_suppressHashChange) return;
restoreFromHash();
});
// --- Streaming Debounce State ---
let _streamBuffer = '';
let _streamDebounceTimer = null;
@@ -197,6 +309,8 @@ function authenticate() {
loadThreads();
loadMemoryTree();
loadJobs();
// Restore navigation state from URL hash (tab, thread, memory file, etc.)
restoreFromHash();
// Apply URL log_level param if present, otherwise just sync the dropdown
if (urlLogLevel) {
setServerLogLevel(urlLogLevel);
@@ -1020,6 +1134,128 @@ function sanitizeRenderedHtml(html) {
return '';
}
// ==================== Structured Data Rendering ====================
//
// Detects JSON objects and key-value data in assistant messages and
// renders them as styled cards instead of raw text. Also supports
// extensible chat renderers via IronClaw.registerChatRenderer().
/**
* Post-process a .message-content element to upgrade structured data into cards.
* Runs registered chat renderers first, then falls back to built-in JSON detection.
*/
function upgradeStructuredData(contentEl) {
// 1. Run registered chat renderers
var renderers = (window.IronClaw && IronClaw._chatRenderers) || [];
for (var i = 0; i < renderers.length; i++) {
try {
if (renderers[i].match(contentEl.textContent, contentEl)) {
renderers[i].render(contentEl, contentEl.textContent);
return; // First matching renderer wins
}
} catch (e) {
console.error('[IronClaw] Chat renderer "' + renderers[i].id + '" failed:', e);
}
}
// 2. Built-in: detect and upgrade inline JSON objects
upgradeInlineJson(contentEl);
}
/**
* Find JSON-like objects in text nodes and replace them with styled cards.
*/
function upgradeInlineJson(contentEl) {
// Walk text content looking for JSON objects: {...} patterns
// Only process <p> and top-level text, not code blocks
var paragraphs = contentEl.querySelectorAll('p');
if (paragraphs.length === 0) {
// No <p> tags — markdown might have produced bare text
paragraphs = [contentEl];
}
paragraphs.forEach(function(p) {
// Skip code blocks
if (p.closest('pre') || p.closest('code')) return;
var html = p.innerHTML;
// Match JSON-like objects: {...} (including Python-style single quotes)
var jsonRegex = /(\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\})/g;
var match;
var replaced = false;
while ((match = jsonRegex.exec(html)) !== null) {
var raw = match[1];
// Normalize Python-style single quotes to double quotes for parsing
var normalized = raw.replace(/'/g, '"');
try {
var obj = JSON.parse(normalized);
if (typeof obj === 'object' && obj !== null && !Array.isArray(obj)) {
var card = buildDataCard(obj);
html = html.substring(0, match.index) + card + html.substring(match.index + match[0].length);
replaced = true;
// Reset regex since we modified the string
jsonRegex.lastIndex = match.index + card.length;
}
} catch (e) {
// Not valid JSON — leave as text
}
}
if (replaced) {
p.innerHTML = html;
}
});
}
/**
* Build an HTML data card from a plain object.
*/
function buildDataCard(obj) {
var keys = Object.keys(obj);
if (keys.length === 0) return '';
var rows = '';
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = obj[key];
var displayKey = key.replace(/_/g, ' ');
var valueClass = 'data-card-value';
var valueHtml;
// Special rendering for known value types
if (key === 'status' || key === 'state') {
var badgeClass = 'status-badge';
var sv = String(value).toLowerCase();
if (sv === 'created' || sv === 'active' || sv === 'success' || sv === 'completed' || sv === 'ok' || sv === 'running') {
badgeClass += ' status-success';
} else if (sv === 'failed' || sv === 'error' || sv === 'cancelled' || sv === 'rejected') {
badgeClass += ' status-error';
} else if (sv === 'pending' || sv === 'waiting' || sv === 'queued') {
badgeClass += ' status-pending';
}
valueHtml = '<span class="' + badgeClass + '">' + escapeHtml(String(value)) + '</span>';
} else if (typeof value === 'object' && value !== null) {
valueHtml = '<code>' + escapeHtml(JSON.stringify(value)) + '</code>';
} else {
// Check if value looks like a UUID or ID
var strVal = String(value);
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(strVal)) {
valueHtml = '<code class="data-card-id">' + escapeHtml(strVal) + '</code>';
} else {
valueHtml = '<span>' + escapeHtml(strVal) + '</span>';
}
}
rows += '<div class="data-card-row">' +
'<span class="data-card-label">' + escapeHtml(displayKey) + '</span>' +
'<span class="' + valueClass + '">' + valueHtml + '</span>' +
'</div>';
}
return '<div class="data-card">' + rows + '</div>';
}
function copyCodeBlock(btn) {
const pre = btn.parentElement;
const code = pre.querySelector('code');
@@ -1824,6 +2060,8 @@ function createMessageElement(role, content) {
} else {
div.setAttribute('data-raw', content);
contentEl.innerHTML = renderMarkdown(content);
// Upgrade structured data (JSON objects, etc.) into styled cards
upgradeStructuredData(contentEl);
// Syntax highlighting for code blocks
if (typeof hljs !== 'undefined') {
requestAnimationFrame(() => {
@@ -2012,6 +2250,19 @@ function loadThreads() {
list.appendChild(item);
}
// Restore thread from URL hash if pending (deferred from restoreFromHash)
if (window._pendingThreadRestore) {
var pendingId = window._pendingThreadRestore;
window._pendingThreadRestore = null;
// Verify the thread exists in the loaded list
var found = (pendingId === assistantThreadId) ||
threads.some(function(t) { return t.id === pendingId; });
if (found) {
switchThread(pendingId);
return;
}
}
// Default to assistant thread on first load if no thread selected
if (!currentThreadId && assistantThreadId) {
switchToAssistant();
@@ -2051,6 +2302,7 @@ function switchToAssistant() {
oldestTimestamp = null;
loadHistory();
loadThreads();
updateHash();
if (window.innerWidth <= 768) {
const sidebar = document.getElementById('thread-sidebar');
sidebar.classList.remove('expanded-mobile');
@@ -2067,6 +2319,7 @@ function switchThread(threadId) {
oldestTimestamp = null;
loadHistory();
loadThreads();
updateHash();
if (window.innerWidth <= 768) {
const sidebar = document.getElementById('thread-sidebar');
sidebar.classList.remove('expanded-mobile');
@@ -2080,6 +2333,7 @@ function createNewThread() {
document.getElementById('chat-messages').innerHTML = '';
showWelcomeCard();
loadThreads();
updateHash();
}).catch((err) => {
showToast('Failed to create thread: ' + err.message, 'error');
});
@@ -2227,6 +2481,7 @@ function switchTab(tab) {
stopPairingPoll();
}
updateTabIndicator();
updateHash();
}
function updateTabIndicator() {
@@ -2372,6 +2627,7 @@ function toggleExpand(node) {
function readMemoryFile(path) {
currentMemoryPath = path;
updateHash();
// Update breadcrumb
document.getElementById('memory-breadcrumb-path').innerHTML = buildBreadcrumb(path);
document.getElementById('memory-edit-btn').style.display = 'inline-block';
@@ -3685,6 +3941,7 @@ function restartJob(jobId) {
function openJobDetail(jobId) {
currentJobId = jobId;
currentJobSubTab = 'activity';
updateHash();
apiFetch('/api/jobs/' + jobId).then((job) => {
renderJobDetail(job);
}).catch((err) => {
@@ -3697,6 +3954,7 @@ function closeJobDetail() {
currentJobId = null;
jobFilesTreeState = null;
loadJobs();
updateHash();
}
function renderJobDetail(job) {
@@ -4193,6 +4451,7 @@ function renderRoutinesList(routines) {
function openRoutineDetail(id) {
currentRoutineId = id;
updateHash();
apiFetch('/api/routines/' + id).then((routine) => {
renderRoutineDetail(routine);
}).catch((err) => {
@@ -4203,6 +4462,7 @@ function openRoutineDetail(id) {
function closeRoutineDetail() {
currentRoutineId = null;
loadRoutines();
updateHash();
}
function renderRoutineDetail(routine) {
@@ -4265,13 +4525,6 @@ function renderRoutineDetail(routine) {
html += '<div class="job-description"><h3>Action</h3>'
+ '<pre class="action-json">' + escapeHtml(JSON.stringify(routine.action, null, 2)) + '</pre></div>';
// Conversation thread link
if (routine.conversation_id) {
html += '<div class="job-description">'
+ '<a href="#" data-action="view-routine-thread" data-id="' + escapeHtml(routine.conversation_id) + '" class="btn-primary" style="display:inline-block;margin:0.5rem 0">'
+ 'View Execution Thread</a></div>';
}
// Recent runs
if (routine.recent_runs && routine.recent_runs.length > 0) {
html += '<div class="job-timeline-section"><h3>Recent Runs</h3>'
@@ -5187,6 +5440,7 @@ function switchSettingsSubtab(subtab) {
document.querySelector('.settings-layout').classList.add('settings-detail-active');
}
loadSettingsSubtab(subtab);
updateHash();
}
function settingsBack() {
@@ -6197,11 +6451,6 @@ document.addEventListener('click', function(e) {
switchTab('jobs');
openJobDetail(el.dataset.id);
break;
case 'view-routine-thread':
e.preventDefault();
switchTab('chat');
switchThread(el.dataset.id);
break;
case 'copy-tee-report':
copyTeeReport();
break;
@@ -6336,3 +6585,177 @@ document.getElementById('settings-search-input').addEventListener('input', funct
activePanel.appendChild(empty);
}
});
// ==================== Widget Extension System ====================
//
// Provides a registration API for frontend widgets. Widgets are self-contained
// components that plug into named slots in the UI (tabs, sidebar, status bar, etc.).
//
// Widget authors call IronClaw.registerWidget({ id, name, slot, init, ... })
// from their module script. The init() function receives a container DOM element
// and the IronClaw.api object for authenticated fetch, event subscription, etc.
window.IronClaw = window.IronClaw || {};
IronClaw.widgets = new Map();
IronClaw._widgetInitQueue = [];
IronClaw._chatRenderers = [];
/**
* Register a widget component.
* @param {Object} def - Widget definition
* @param {string} def.id - Unique widget identifier
* @param {string} def.name - Display name
* @param {string} def.slot - Target slot ('tab', 'chat_header', etc.)
* @param {string} [def.icon] - Icon identifier
* @param {Function} def.init - Called with (container, api) when widget activates
* @param {Function} [def.activate] - Called when widget becomes visible
* @param {Function} [def.deactivate] - Called when widget is hidden
* @param {Function} [def.destroy] - Called when widget is removed
*/
IronClaw.registerWidget = function(def) {
if (!def.id || !def.init) {
console.error('[IronClaw] Widget registration requires id and init:', def);
return;
}
IronClaw.widgets.set(def.id, def);
if (def.slot === 'tab') {
_addWidgetTab(def);
}
};
/**
* Register a chat renderer for custom inline rendering of structured data.
*
* Chat renderers run against each assistant message. The first renderer
* whose `match()` returns true gets to transform the content.
*
* @param {Object} def - Renderer definition
* @param {string} def.id - Unique identifier
* @param {Function} def.match - (textContent, element) => boolean
* @param {Function} def.render - (element, textContent) => void (mutate element in place)
* @param {number} [def.priority=0] - Higher priority runs first
*/
IronClaw.registerChatRenderer = function(def) {
if (!def.id || !def.match || !def.render) {
console.error('[IronClaw] Chat renderer requires id, match, and render:', def);
return;
}
IronClaw._chatRenderers.push(def);
// Sort by priority (higher first)
IronClaw._chatRenderers.sort(function(a, b) {
return (b.priority || 0) - (a.priority || 0);
});
};
/**
* API object exposed to widgets for safe interaction with the app.
*/
IronClaw.api = {
/** Authenticated fetch wrapper — injects the session token. */
fetch: function(path, opts) {
opts = opts || {};
opts.headers = Object.assign({}, opts.headers || {}, {
'Authorization': 'Bearer ' + token
});
return fetch(path, opts);
},
/** Subscribe to an SSE/WebSocket event type. Returns an unsubscribe function. */
subscribe: function(eventType, handler) {
if (!window._widgetEventHandlers) window._widgetEventHandlers = {};
if (!window._widgetEventHandlers[eventType]) window._widgetEventHandlers[eventType] = [];
window._widgetEventHandlers[eventType].push(handler);
return function() {
var handlers = window._widgetEventHandlers[eventType];
if (handlers) {
var idx = handlers.indexOf(handler);
if (idx !== -1) handlers.splice(idx, 1);
}
};
},
/** Current theme information. */
theme: {
get current() { return document.documentElement.dataset.theme || 'dark'; }
},
/** Internationalization helper. */
i18n: {
t: function(key) { return (window.I18n && window.I18n.t) ? window.I18n.t(key) : key; }
},
/** Navigate to a tab by ID. */
navigate: function(tabId) {
if (typeof switchTab === 'function') switchTab(tabId);
}
};
/**
* Add a widget as a new tab in the tab bar.
* @private
*/
function _addWidgetTab(def) {
var tabBar = document.querySelector('.tab-bar');
var tabContent = document.querySelector('.tab-content') || document.getElementById('tab-content');
if (!tabBar || !tabContent) {
// DOM not ready yet — queue for later
IronClaw._widgetInitQueue.push(def);
return;
}
// Create tab button
var btn = document.createElement('button');
btn.className = 'tab-btn';
btn.dataset.tab = def.id;
btn.textContent = def.name;
if (def.icon) {
btn.dataset.icon = def.icon;
}
btn.addEventListener('click', function() {
if (typeof switchTab === 'function') switchTab(def.id);
});
// Insert before the settings tab (last built-in tab) or at the end
var settingsBtn = tabBar.querySelector('[data-tab="settings"]');
if (settingsBtn) {
tabBar.insertBefore(btn, settingsBtn);
} else {
tabBar.appendChild(btn);
}
// Create container panel
var panel = document.createElement('div');
panel.className = 'tab-panel';
panel.dataset.tab = def.id;
panel.dataset.widget = def.id;
panel.style.display = 'none';
tabContent.appendChild(panel);
// Initialize the widget
try {
def.init(panel, IronClaw.api);
} catch (e) {
console.error('[IronClaw] Widget "' + def.id + '" init failed:', e);
panel.innerHTML = '<div style="padding:2rem;color:var(--color-error,red);">Widget "' +
def.id + '" failed to load: ' + (e.message || e) + '</div>';
}
}
// Apply layout config if injected by the server
if (window.__IRONCLAW_LAYOUT__) {
(function() {
var layout = window.__IRONCLAW_LAYOUT__;
// Apply branding title
if (layout.branding && layout.branding.title) {
var titleEl = document.querySelector('.app-title');
if (titleEl) titleEl.textContent = layout.branding.title;
}
// Apply tab visibility
if (layout.tabs && layout.tabs.hidden) {
layout.tabs.hidden.forEach(function(tabId) {
var btn = document.querySelector('.tab-btn[data-tab="' + tabId + '"]');
if (btn) btn.style.display = 'none';
});
}
})();
}

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

@@ -2106,6 +2106,90 @@ body {
background: var(--accent-subtle);
}
/* --- Data Cards (inline structured data) --- */
.data-card {
display: flex;
flex-direction: column;
gap: 0;
margin: 8px 0;
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
border-left: 3px solid var(--accent);
overflow: hidden;
}
.data-card-row {
display: flex;
align-items: baseline;
gap: var(--space-3);
padding: 6px 14px;
border-bottom: 1px solid var(--border);
}
.data-card-row:last-child {
border-bottom: none;
}
.data-card-label {
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
text-transform: capitalize;
min-width: 80px;
flex-shrink: 0;
}
.data-card-value {
font-size: var(--text-sm);
color: var(--text-primary);
word-break: break-word;
}
.data-card-value code {
font-family: var(--font-mono);
font-size: 12px;
padding: 1px 5px;
background: var(--bg-secondary);
border-radius: var(--radius-sm);
}
.data-card-id {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-secondary);
padding: 1px 5px;
background: var(--bg-secondary);
border-radius: var(--radius-sm);
}
/* Status badges */
.status-badge {
display: inline-block;
padding: 2px 10px;
border-radius: 10px;
font-size: 12px;
font-weight: 600;
text-transform: capitalize;
}
.status-success {
background: rgba(52, 211, 153, 0.15);
color: var(--success, #34d399);
}
.status-error {
background: rgba(248, 113, 113, 0.15);
color: var(--error, #f87171);
}
.status-pending {
background: rgba(251, 191, 36, 0.15);
color: var(--warning, #fbbf24);
}
/* Clickable job rows */
.job-row {
cursor: pointer;
@@ -1,13 +1,13 @@
[package]
name = "optimclaw_safety"
name = "ironclaw_safety"
version = "0.2.0"
edition = "2024"
rust-version = "1.92"
description = "Prompt injection defense, input validation, secret leak detection, and safety policy enforcement"
authors = ["NEAR AI <[email protected]>"]
license = "MIT OR Apache-2.0"
homepage = "https://github.com/nearai/optimclaw"
repository = "https://github.com/nearai/optimclaw"
homepage = "https://github.com/nearai/ironclaw"
repository = "https://github.com/nearai/ironclaw"
[package.metadata.dist]
dist = false
@@ -1,5 +1,5 @@
[package]
name = "optimclaw-safety-fuzz"
name = "ironclaw-safety-fuzz"
version = "0.0.0"
publish = false
edition = "2021"
@@ -11,7 +11,7 @@ cargo-fuzz = true
libfuzzer-sys = "0.4"
serde_json = "1"
[dependencies.optimclaw_safety]
[dependencies.ironclaw_safety]
path = ".."
[[bin]]
@@ -1,6 +1,6 @@
# optimclaw_safety Fuzz Targets
# ironclaw_safety Fuzz Targets
Fuzz testing for the `optimclaw_safety` crate using [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) (libFuzzer).
Fuzz testing for the `ironclaw_safety` crate using [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) (libFuzzer).
## Targets
@@ -22,7 +22,7 @@ rustup install nightly
## Running
```bash
cd crates/optimclaw_safety
cd crates/ironclaw_safety
# Run a specific target (runs until stopped or crash found)
cargo +nightly fuzz run fuzz_safety_sanitizer

Some files were not shown because too many files have changed in this diff Show More