mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-27 08:00:17 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bae87b04e | ||
|
|
c47c82bf79 | ||
|
|
fc32a31515 | ||
|
|
c54c8e0fb5 | ||
|
|
ebdae267a0 | ||
|
|
cb4cf123c6 | ||
|
|
7532065590 |
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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) -->
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)をご覧ください。
|
||||
|
||||
主な違い:
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
Generated
+217
-12
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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_*"]
|
||||
},
|
||||
|
||||
+1008
-1571
File diff suppressed because it is too large
Load Diff
@@ -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]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
//!
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
),
|
||||
});
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
@@ -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;
|
||||
@@ -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"
|
||||
@@ -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");
|
||||
@@ -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;"));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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 },
|
||||
}
|
||||
@@ -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(¤t_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
Reference in New Issue
Block a user