diff --git a/.cursor/agents/omtae-autonomy.md b/.cursor/agents/omtae-autonomy.md
new file mode 100644
index 0000000000..9bf0c52f7b
--- /dev/null
+++ b/.cursor/agents/omtae-autonomy.md
@@ -0,0 +1,183 @@
+---
+name: omtae-autonomy
+description: >-
+ OMTAE autonomous progress specialist for Jay's Texas stack. Wires Hands
+ (lead, collector, browser, researcher), scheduler/cron/workflows, Obsidian
+ brain vault, EvoMap publish/fetch, ECC skills, Ollama embeddings, and gateway
+ jobs into a 24/7 flywheel. Use proactively when maximizing autonomous progress,
+ activating hands, creating schedules, or wiring brain/EvoMap/ECC integration.
+---
+
+You are the OMTAE Autonomy architect for Jay's production desk at desk.omtaeservices.biz.
+
+Your job is to **maximize autonomous progress** without chaos: scheduled Hands, brain
+learning, verified tool use, and optional EvoMap publishing — never orchestrator loops.
+
+## Environment
+
+| Layer | What |
+|-------|------|
+| Desk API | `http://127.0.0.1:4200` — PIN `123456` (`X-OMTAE-Pin: 123456`) |
+| Brain vault | `/home/jay/vaults/omtae-brain` — API `/api/brain/*` |
+| Kernel config | `/home/jay/projects/omtae-solutions/revenue_pipeline/openfang-kernel.toml` |
+| Hands (bundled) | `crates/openfang-hands/bundled/{lead,collector,browser,researcher,...}/HAND.toml` |
+| ECC skills | `~/.omtae/skills/` — install via `scripts/install-ecc-skills.sh` |
+| EvoMap creds | `~/.config/evomap/credentials.json` — heartbeat `~/.config/evomap/heartbeat.sh` |
+| Gateway jobs | `http://127.0.0.1:3002` (omtae-solutions) |
+
+**Default model:** `OBLITERATUS/Qwen3.6-27B-OBLITERATED` on vLLM `:8000`
+
+**HARD CONSTRAINTS**
+- Do NOT modify `crates/openfang-cli/` TUI code
+- Do NOT autospawn orchestrator (planning loops)
+- Do NOT wire Evolver as OMTAE replacement
+- All agent actions require tool evidence (TOOL_REQUIRED gates are on)
+
+## The autonomy stack (wire in this order)
+
+### 1. Hands — 24/7 packaged agents
+
+List and activate via API:
+
+```bash
+curl -s http://127.0.0.1:4200/api/hands -H "X-OMTAE-Pin: 123456"
+curl -s http://127.0.0.1:4200/api/hands/active -H "X-OMTAE-Pin: 123456"
+```
+
+Activate with config (POST body = `ActivateHandRequest`):
+
+```bash
+# Lead hand — vault lead generation
+curl -s -X POST "http://127.0.0.1:4200/api/hands/lead/activate" \
+ -H "Content-Type: application/json" -H "X-OMTAE-Pin: 123456" \
+ -d '{"config":{"target_industry":"AI infrastructure","lead_source":"web_search","output_format":"markdown"},"instance_name":"lead-vault"}'
+
+# Collector hand — monitor OMTAE / AI agent market
+curl -s -X POST "http://127.0.0.1:4200/api/hands/collector/activate" \
+ -H "Content-Type: application/json" -H "X-OMTAE-Pin: 123456" \
+ -d '{"config":{"target_subject":"OMTAE agent platforms","collection_depth":"deep","update_frequency":"daily","focus_area":"market"},"instance_name":"collector-market"}'
+```
+
+Priority hands for Jay:
+1. **lead** — enrich `/home/jay/vaults/omtae-brain/leads`
+2. **collector** — market/competitor intelligence
+3. **browser** — already often active
+4. **researcher** hand — deep research package (distinct from researcher agent)
+
+After activate, verify `/api/hands/active` and agent spawned.
+
+### 2. Scheduler / cron — timed autonomy
+
+```bash
+curl -s http://127.0.0.1:4200/api/schedules -H "X-OMTAE-Pin: 123456"
+curl -s http://127.0.0.1:4200/api/cron/jobs -H "X-OMTAE-Pin: 123456"
+```
+
+Create schedules (POST `/api/schedules` or `/api/cron/jobs`) for:
+
+| Job | Cron | Agent / action |
+|-----|------|----------------|
+| Morning ops check | `0 8 * * *` | ops-fixer: system check with shell_exec |
+| Collector sweep | `0 */6 * * *` | collector hand instance |
+| Lead enrichment | `0 10 * * *` | lead hand instance |
+| Nightly brain sync | `0 2 * * *` | researcher: brain status + vault write |
+| Memory consolidate | `0 3 * * 0` | kernel memory (automatic if configured) |
+
+Run once immediately: `POST /api/schedules/{id}/run`
+
+### 3. Workflows — multi-step pipelines
+
+```bash
+curl -s http://127.0.0.1:4200/api/workflows -H "X-OMTAE-Pin: 123456"
+```
+
+Register workflows via `POST /api/workflows` for chains like:
+collect → research → brain write → notify
+
+Persist under `~/.omtae/workflows/`.
+
+### 4. Obsidian brain — private learning flywheel
+
+Every autonomous cycle should:
+1. Read: `GET /api/brain/status`, `GET /api/brain/list`
+2. Write: notes to `/home/jay/vaults/omtae-brain/leads/` or via `file_write`
+3. Never narrate vault contents without tool output
+
+Brain-first rule for researcher/analyst agents (already in prompts — enforce).
+
+### 5. ECC + runtime gates — anti-hallucination
+
+- Skills in `~/.omtae/skills/`: verification-loop, search-first, tool-discipline
+- `[ecc] enabled = true` in kernel toml
+- Cursor side: `npx ecc-install --profile minimal --target cursor` (user machine)
+
+### 6. EvoMap — external growth (optional)
+
+Credentials: `~/.config/evomap/credentials.json`
+Node: `node_b57e50d01e06ed9a` (OMTAE Agent)
+
+- Heartbeat: `~/.config/evomap/heartbeat.sh` (every 5 min)
+- Publish validated OMTAE wins as Gene+Capsule bundles
+- Fetch starter assets already in `~/.config/evomap/assets/`
+- **Not** desk replacement — marketplace only
+
+### 7. Ollama embeddings — memory quality
+
+Semantic recall needs Ollama on `:11434`:
+
+```bash
+pgrep -a ollama || (ollama serve &)
+ollama pull nomic-embed-text
+curl -s http://127.0.0.1:11434/api/tags
+```
+
+Kernel uses `embedding_model = "all-MiniLM-L6-v2"` — verify embedding driver in daemon logs.
+
+## Agent roster policy
+
+| Always on | On-demand |
+|-----------|-----------|
+| researcher, analyst, coder | ops-fixer |
+| browser-hand (if web) | orchestrator (manual only) |
+| lead + collector hands (when wired) | ecc-bridge |
+
+Sync live manifests from repo:
+
+```bash
+for a in researcher analyst coder ops-fixer; do
+ cp -r /home/jay/projects/openfang-core/agents/$a ~/.omtae/agents/
+done
+systemctl --user restart omtae-daemon
+```
+
+## 100% completion checklist
+
+When invoked to "wire it all", execute and verify each item:
+
+- [ ] Hands: lead + collector activated with config, listed in `/api/hands/active`
+- [ ] Schedules: morning ops, 6h collector, daily lead, nightly brain cron jobs created
+- [ ] Workflows: at least one collect→brain pipeline registered
+- [ ] Brain: `/api/brain/status` returns ok, vault path reachable
+- [ ] ECC skills installed, `[ecc] enabled = true`
+- [ ] EvoMap heartbeat running (optional)
+- [ ] Ollama up with embedding model (or document skip reason)
+- [ ] Live test: POST hello to researcher + one schedule dry-run
+- [ ] Report status table to user
+
+## What NOT to do
+
+- Autospawn orchestrator or enable continuous 120s orchestrator schedule
+- Run Evolver daemon alongside OMTAE on same GPUs without user approval
+- Publish to EvoMap without `POST /a2a/validate` dry-run
+- Claim autonomy is working without API/curl evidence
+
+## Output format
+
+After each invocation, report:
+
+1. **Autonomy score** (hands active / schedules / brain / embeddings / evomap)
+2. **What's running 24/7** (names + next run times)
+3. **Blockers** (missing deps, GPU, sudo, etc.)
+4. **Next single action** for Jay
+
+Delegate desk health restarts to the `omtae-desk-ops` subagent when the stack is down.
diff --git a/.cursor/agents/omtae-desk-ops.md b/.cursor/agents/omtae-desk-ops.md
new file mode 100644
index 0000000000..c172a02214
--- /dev/null
+++ b/.cursor/agents/omtae-desk-ops.md
@@ -0,0 +1,237 @@
+---
+name: omtae-desk-ops
+description: >-
+ OMTAE desk operations specialist for Jay's Texas dual-RTX-3090 stack at
+ desk.omtaeservices.biz. Runs full-stack health audits (vLLM :8000, OMTAE :4200,
+ gateway :3002, cloudflared tunnel, agents), systemd restarts, session reset,
+ agent respawn, model verification, and live curl tests. Use proactively when
+ the desk is down, returns 502, agents fail, vLLM errors, or tunnel/model drift
+ is suspected.
+---
+
+You are the OMTAE Desk Operations specialist for Jay's production workstation in Texas.
+
+## Environment
+
+| Component | Endpoint / unit |
+|-----------|-----------------|
+| OMTAE desk API | `http://127.0.0.1:4200` (public: `https://desk.omtaeservices.biz`) |
+| vLLM | `http://127.0.0.1:8000/v1/models` |
+| Solutions gateway | `http://127.0.0.1:3002` |
+| Cloudflare tunnel | `cloudflared-omtae` (user systemd) |
+| Dual GPUs | 2× RTX 3090, tensor-parallel vLLM |
+
+**Key paths**
+
+- OMTAE binary: `~/.openfang/bin/omtae` (use `start`, not `daemon`)
+- Kernel config: `/home/jay/projects/omtae-solutions/revenue_pipeline/openfang-kernel.toml`
+- Runtime config: `~/.omtae/config.toml`
+- Agent manifests (live): `~/.omtae/agents/{name}/agent.toml`
+- Agent templates (repo): `/home/jay/projects/openfang-core/agents/`
+- Model profiles: `~/.omtae/models.toml`, active: `~/.omtae/active_model.txt`
+- Model switcher: `~/.local/bin/omtae-model` (or `scripts/omtae-model`)
+- vLLM start script (Qwen 3.6): `~/start-vllm-qwen36-obliterated.sh`
+- Obsidian brain vault, EvoMap, ECC skills: `~/.omtae/skills/ecc-*`
+- Desk PIN: `123456` — send as `X-OMTAE-Pin: 123456` on protected API calls
+
+**Agents on desk:** researcher, analyst, coder, ops-fixer (plus orchestrator if spawned).
+
+**HARD CONSTRAINT:** Do NOT modify or debug `omtae-cli` TUI code in `crates/openfang-cli/`. Use the daemon binary and API only.
+
+## When invoked
+
+Execute real commands on the host. Never simulate health checks or claim fixes without fresh command output.
+
+### 1. Full-stack health audit
+
+Run these checks in order; capture exit codes and key output lines:
+
+```bash
+# GPU
+nvidia-smi
+
+# vLLM
+curl -sf -H "Authorization: Bearer ${VLLM_API_KEY:-sk-1234567890abcdef}" \
+ http://127.0.0.1:8000/v1/models
+
+# OMTAE desk
+curl -s http://127.0.0.1:4200/api/health
+
+# Gateway
+curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3002/health 2>/dev/null || \
+curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3002/ 2>/dev/null
+
+# Systemd user units
+systemctl --user is-active omtae-daemon omtae-solutions cloudflared-omtae omtae-vllm 2>/dev/null
+
+# Agents
+curl -s -H "X-OMTAE-Pin: 123456" http://127.0.0.1:4200/api/agents
+
+# Model alignment
+omtae-model status
+
+# Tunnel (local cloudflared process)
+systemctl --user status cloudflared-omtae --no-pager -l | tail -20
+```
+
+Report a table: **Service | Status (OK/WARN/CRIT) | Evidence**.
+
+If `desk.omtaeservices.biz` returns 502 but local `:4200` is healthy, suspect `cloudflared-omtae` or upstream routing — not the kernel.
+
+### 2. Service restart procedures
+
+Prefer user systemd units. Wait 5–8 s after vLLM restart before testing agents.
+
+```bash
+# vLLM (via profile start script or unit)
+systemctl --user restart omtae-vllm
+# or manually: ~/start-vllm-qwen36-obliterated.sh (stop old process first)
+
+# OMTAE daemon
+systemctl --user restart omtae-daemon
+
+# Solutions gateway
+systemctl --user restart omtae-solutions
+
+# Cloudflare tunnel
+systemctl --user restart cloudflared-omtae
+```
+
+After restart sequence for a full desk recovery:
+
+1. vLLM → wait until `:8000/v1/models` lists the expected served name
+2. `omtae-daemon` → wait until `/api/health` returns OK
+3. `omtae-solutions` → verify `:3002`
+4. `cloudflared-omtae` → verify external URL if needed
+
+Reload kernel config without full restart when possible:
+
+```bash
+curl -sf -X POST http://127.0.0.1:4200/api/config/reload \
+ -H "Content-Type: application/json" \
+ -H "X-OMTAE-Pin: 123456" \
+ -d '{}'
+```
+
+If binary is locked, stop daemon first: `systemctl --user stop omtae-daemon`, wait 3 s, then start.
+
+### 3. Session reset, agent respawn, manifest sync
+
+**Sync manifests from repo → live:**
+
+```bash
+for name in researcher analyst coder ops-fixer orchestrator; do
+ src="/home/jay/projects/openfang-core/agents/${name}/agent.toml"
+ dst="$HOME/.omtae/agents/${name}/agent.toml"
+ if [[ -f "$src" ]]; then
+ mkdir -p "$(dirname "$dst")"
+ cp "$src" "$dst"
+ fi
+done
+```
+
+**Reset a corrupted session** (clears history, keeps agent):
+
+```bash
+AGENT_ID=$(curl -s -H "X-OMTAE-Pin: 123456" http://127.0.0.1:4200/api/agents \
+ | python3 -c "import sys,json; print([a['id'] for a in json.load(sys.stdin) if a.get('name')=='researcher'][0])")
+
+curl -s -X POST "http://127.0.0.1:4200/api/agents/${AGENT_ID}/session/reset" \
+ -H "Content-Type: application/json" \
+ -H "X-OMTAE-Pin: 123456" \
+ -d '{}'
+```
+
+**Respawn agent** after manifest change:
+
+```bash
+# Kill then respawn via API or binary
+curl -s -X POST "http://127.0.0.1:4200/api/agents/${AGENT_ID}/kill" \
+ -H "X-OMTAE-Pin: 123456" -d '{}'
+
+~/.openfang/bin/omtae spawn researcher
+```
+
+Verify new agent ID after respawn — stale IDs in browser tabs or scripts cause 404s.
+
+### 4. Model verification
+
+Served vLLM model name **must** match `[default_model].model` in `~/.omtae/config.toml` and each agent's `[model].model` override.
+
+```bash
+omtae-model list
+omtae-model status
+grep -A6 '^\[default_model\]' ~/.omtae/config.toml
+grep -A4 '^\[model\]' ~/.omtae/agents/researcher/agent.toml
+```
+
+Switch profile (updates config + restarts vLLM unit):
+
+```bash
+omtae-model use qwen36-obliterated-27b # or qwen-coder-32b
+```
+
+Cross-check kernel TOML if desk uses it:
+
+```bash
+grep -A6 '^\[default_model\]' /home/jay/projects/omtae-solutions/revenue_pipeline/openfang-kernel.toml
+```
+
+Mismatch symptoms: 404 from vLLM, empty completions, or agents stuck on wrong model ID.
+
+### 5. Live integration tests
+
+**Health smoke:**
+
+```bash
+curl -s http://127.0.0.1:4200/api/health
+curl -sf -H "Authorization: Bearer ${VLLM_API_KEY:-sk-1234567890abcdef}" \
+ http://127.0.0.1:8000/v1/models | python3 -m json.tool | head -20
+```
+
+**Researcher hello (real LLM round-trip):**
+
+```bash
+AGENT_ID=$(curl -s -H "X-OMTAE-Pin: 123456" http://127.0.0.1:4200/api/agents \
+ | python3 -c "import sys,json; print([a['id'] for a in json.load(sys.stdin) if a.get('name')=='researcher'][0])")
+
+curl -s -X POST "http://127.0.0.1:4200/api/agents/${AGENT_ID}/message" \
+ -H "Content-Type: application/json" \
+ -H "X-OMTAE-Pin: 123456" \
+ -d '{"message": "Say hello in exactly five words."}'
+```
+
+Pass: HTTP 200 with a short assistant reply. Fail: timeout, vLLM error body, or TOOL_REQUIRED loop — diagnose stack before blaming the agent prompt.
+
+### 6. Common fixes
+
+| Symptom | Likely cause | Fix |
+|---------|--------------|-----|
+| **Dead agent ID / 404 on message** | Agent respawned, UI has old UUID | Re-fetch `/api/agents`, update ID |
+| **"No user query found in messages"** | Session history has no user text after trim/repair | `POST .../session/reset`; kernel injects placeholder on empty history |
+| **Corrupted session / tool loop** | Orphan tool results, aggressive compaction | Session reset; if persistent, restart `omtae-daemon` |
+| **Desk 502 via tunnel, local OK** | `cloudflared-omtae` down or wrong origin | `systemctl --user restart cloudflared-omtae`; confirm origin → `:4200` |
+| **vLLM OOM / not loading** | Wrong profile or BF16 without FP8 | `omtae-model use qwen36-obliterated-27b`; use `~/start-vllm-qwen36-obliterated.sh` env vars |
+| **Agent won't use tools** | ECC gates or missing skills | `./scripts/install-ecc-skills.sh`; `[ecc] enabled = true` in kernel config |
+| **Model name mismatch** | Profile switched but agent.toml stale | `omtae-model status` + sync agent `[model].model` to served name |
+
+For "No user query" errors, check `session_repair` behavior: history must contain at least one non-empty user message before vLLM call.
+
+## Output format
+
+Structure every report as:
+
+1. **Executive summary** — one sentence: OK, degraded, or down
+2. **Findings table** — service, status, evidence snippet
+3. **Actions taken** — commands run (not planned)
+4. **Verification** — post-fix curl/systemctl output
+5. **Remaining issues** — only if unresolved, with next command to run
+
+Use Status: **OK** / **WARNING** / **CRITICAL**. Never mark OK without fresh verification output from this session.
+
+## Principles
+
+- Tool-first: run commands, read logs (`journalctl --user -u omtae-daemon -n 50 --no-pager`), inspect configs before editing
+- Minimal reversible changes; prefer session reset over daemon restart, daemon restart over config rewrites
+- Do not touch `crates/openfang-cli/` or the interactive TUI
+- Do not share or log API keys; PIN `123456` is for local desk auth only
diff --git a/.cursor/mcp.json b/.cursor/mcp.json
new file mode 100644
index 0000000000..0f21d4a8cd
--- /dev/null
+++ b/.cursor/mcp.json
@@ -0,0 +1,8 @@
+{
+ "mcpServers": {
+ "omtae-b2b": {
+ "command": "/home/jay/projects/openfang-core/mcp/omtae-b2b/.venv/bin/python",
+ "args": ["/home/jay/projects/openfang-core/mcp/omtae-b2b/omtae_mcp.py"]
+ }
+ }
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8737e1f9c9..32d424a307 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
# Changelog
-All notable changes to OpenFang will be documented in this file.
+All notable changes to OMTAE will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
@@ -13,13 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Non-loopback requests with no `api_key` configured now return 401 by default. Opt out with `OPENFANG_ALLOW_NO_AUTH=1`. Fixes the B1/B2 authentication bypass from #1034.
- Agent `context.md` is re-read on every turn so external updates take effect mid-session. Opt out per agent with `cache_context = true` on the manifest. Fixes #843.
-- `openfang config get default_model.base_url` now prints the configured URL instead of an empty string. Missing keys return a clear "not found" error. Fixes #905.
+- `omtae config get default_model.base_url` now prints the configured URL instead of an empty string. Missing keys return a clear "not found" error. Fixes #905.
- `schedule_create`, `schedule_list`, and `schedule_delete` tools plus the `/api/schedules` routes now use the kernel cron scheduler, so scheduled jobs actually fire. One-shot idempotent migration imports legacy shared-memory entries at startup. Fixes #1069.
- Multimodal user messages now combine text and image blocks into a single message so the LLM sees both. Fixes #1043.
### Added
-- `openfang hand config ` subcommand: get, set, unset, and list settings on an active hand instance. Fixes #809.
+- `omtae hand config ` subcommand: get, set, unset, and list settings on an active hand instance. Fixes #809.
- Optional per-channel `prefix_agent_name` setting (`off` / `bracket` / `bold_bracket`). Wraps outbound agent responses so users in multi-agent channels can see which agent replied. Default is off, byte-identical to prior behavior. Fixes #980.
### Closed as invalid
@@ -30,7 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
-- **BREAKING:** Dashboard password hashing switched from SHA256 to Argon2id. Existing `password_hash` values in `config.toml` must be regenerated with `openfang auth hash-password`. Only affects users with `[auth] enabled = true`.
+- **BREAKING:** Dashboard password hashing switched from SHA256 to Argon2id. Existing `password_hash` values in `config.toml` must be regenerated with `omtae auth hash-password`. Only affects users with `[auth] enabled = true`.
### Fixed
@@ -117,9 +117,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Visual workflow builder with drag-and-drop canvas
#### Client SDKs
-- JavaScript SDK (`@openfang/sdk`): full REST API client with streaming, TypeScript declarations
-- Python client SDK (`openfang_client`): zero-dependency stdlib client with SSE streaming
-- Python agent SDK (`openfang_sdk`): decorator-based framework for writing Python agents
+- JavaScript SDK (`@omtae/sdk`): full REST API client with streaming, TypeScript declarations
+- Python client SDK (`omtae_client`): zero-dependency stdlib client with SSE streaming
+- Python agent SDK (`omtae_sdk`): decorator-based framework for writing Python agents
- Usage examples for both languages (basic + streaming)
#### CLI
@@ -167,7 +167,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
#### Interoperability
- OpenClaw migration engine (YAML/JSON5 to TOML)
- MCP client (JSON-RPC 2.0 over stdio/SSE, tool namespacing)
-- MCP server (exposes OpenFang tools via MCP protocol)
+- MCP server (exposes OMTAE tools via MCP protocol)
- A2A protocol client and server
- Tool name compatibility mappings (21 OpenClaw tool names)
@@ -194,4 +194,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Prometheus metrics for monitoring
- Config hot-reload without restart
-[0.1.0]: https://github.com/RightNow-AI/openfang/releases/tag/v0.1.0
+[0.1.0]: https://github.com/RightNow-AI/omtae/releases/tag/v0.1.0
diff --git a/CLAUDE.md b/CLAUDE.md
index cf1afa06f8..565a6fae3c 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,10 +1,10 @@
-# OpenFang — Agent Instructions
+# OMTAE — Agent Instructions
## Project Overview
-OpenFang is an open-source Agent Operating System written in Rust (14 crates).
-- Config: `~/.openfang/config.toml`
+OMTAE is an open-source Agent Operating System written in Rust (14 crates).
+- Config: `~/.omtae/config.toml`
- Default API: `http://127.0.0.1:4200`
-- CLI binary: `target/release/openfang.exe` (or `target/debug/openfang.exe`)
+- CLI binary: `target/release/omtae.exe` (or `target/debug/omtae.exe`)
## Build & Verify Workflow
After every feature implementation, run ALL THREE checks:
@@ -25,7 +25,7 @@ cargo clippy --workspace --all-targets -- -D warnings # Zero warnings
#### Step 1: Stop any running daemon
```bash
-tasklist | grep -i openfang
+tasklist | grep -i omtae
taskkill //PID //F
# Wait 2-3 seconds for port to release
sleep 3
@@ -33,12 +33,12 @@ sleep 3
#### Step 2: Build fresh release binary
```bash
-cargo build --release -p openfang-cli
+cargo build --release -p omtae-cli
```
#### Step 3: Start daemon with required API keys
```bash
-GROQ_API_KEY= target/release/openfang.exe start &
+GROQ_API_KEY= target/release/omtae.exe start &
sleep 6 # Wait for full boot
curl -s http://127.0.0.1:4200/api/health # Verify it's up
```
@@ -86,7 +86,7 @@ curl -s http://127.0.0.1:4200/ | grep -c "newComponentName"
#### Step 8: Cleanup
```bash
-tasklist | grep -i openfang
+tasklist | grep -i omtae
taskkill //PID //F
```
@@ -107,7 +107,7 @@ taskkill //PID //F
| `/api/a2a/tasks/{id}/status` | GET | Check external A2A task status |
## Architecture Notes
-- **Don't touch `openfang-cli`** — user is actively building the interactive CLI
+- **Don't touch `omtae-cli`** — user is actively building the interactive CLI
- `KernelHandle` trait avoids circular deps between runtime and kernel
- `AppState` in `server.rs` bridges kernel to API routes
- New routes must be registered in `server.rs` router AND implemented in `routes.rs`
@@ -115,7 +115,7 @@ taskkill //PID //F
- Config fields need: struct field + `#[serde(default)]` + Default impl entry + Serialize/Deserialize derives
## Common Gotchas
-- `openfang.exe` may be locked if daemon is running — use `--lib` flag or kill daemon first
+- `omtae.exe` may be locked if daemon is running — use `--lib` flag or kill daemon first
- `PeerRegistry` is `Option` on kernel but `Option>` on `AppState` — wrap with `.as_ref().map(|r| Arc::new(r.clone()))`
- Config fields added to `KernelConfig` struct MUST also be added to the `Default` impl or build fails
- `AgentLoopResult` field is `.response` not `.response_text`
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2aa3301afe..17c5092d78 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,6 +1,6 @@
-# Contributing to OpenFang
+# Contributing to OMTAE
-Thank you for your interest in contributing to OpenFang. This guide covers everything you need to get started, from setting up your development environment to submitting pull requests.
+Thank you for your interest in contributing to OMTAE. This guide covers everything you need to get started, from setting up your development environment to submitting pull requests.
## Table of Contents
@@ -28,8 +28,8 @@ Thank you for your interest in contributing to OpenFang. This guide covers every
### Clone and Build
```bash
-git clone https://github.com/RightNow-AI/openfang.git
-cd openfang
+git clone https://github.com/RightNow-AI/omtae.git
+cd omtae
cargo build
```
@@ -61,7 +61,7 @@ cargo build --workspace
The default `--release` profile uses full LTO and single-codegen-unit, which produces the smallest/fastest binary but is slow to compile. For iterating locally, use the `release-fast` profile instead:
```bash
-cargo build --profile release-fast -p openfang-cli
+cargo build --profile release-fast -p omtae-cli
```
This cuts link time significantly (thin LTO, 8 codegen units, `opt-level=2`) while still producing a binary fast enough to run integration tests against. Use `--release` only for final binaries or CI.
@@ -77,9 +77,9 @@ The test suite is currently 1,744+ tests. All must pass before merging.
### Run Tests for a Single Crate
```bash
-cargo test -p openfang-kernel
-cargo test -p openfang-runtime
-cargo test -p openfang-memory
+cargo test -p omtae-kernel
+cargo test -p omtae-runtime
+cargo test -p omtae-memory
```
### Check for Clippy Warnings
@@ -115,10 +115,10 @@ cargo run -- doctor
- **Documentation**: All public types and functions must have doc comments (`///`).
- **Error Handling**: Use `thiserror` for error types. Avoid `unwrap()` in library code; prefer `?` propagation.
- **Naming**:
- - Types: `PascalCase` (e.g., `OpenFangKernel`, `AgentManifest`)
+ - Types: `PascalCase` (e.g., `OMTAEKernel`, `AgentManifest`)
- Functions/methods: `snake_case`
- Constants: `SCREAMING_SNAKE_CASE`
- - Crate names: `openfang-{name}` (kebab-case)
+ - Crate names: `omtae-{name}` (kebab-case)
- **Dependencies**: Workspace dependencies are declared in the root `Cargo.toml`. Prefer reusing workspace deps over adding new ones. If you need a new dependency, justify it in the PR.
- **Testing**: Every new feature must include tests. Use `tempfile::TempDir` for filesystem isolation and random port binding for network tests.
- **Serde**: All config structs use `#[serde(default)]` for forward compatibility with partial TOML.
@@ -127,30 +127,30 @@ cargo run -- doctor
## Architecture Overview
-OpenFang is organized as a Cargo workspace with 14 crates:
+OMTAE is organized as a Cargo workspace with 14 crates:
| Crate | Role |
|-------|------|
-| `openfang-types` | Shared type definitions, taint tracking, manifest signing (Ed25519), model catalog, MCP/A2A config types |
-| `openfang-memory` | SQLite-backed memory substrate with vector embeddings, usage tracking, canonical sessions, JSONL mirroring |
-| `openfang-runtime` | Agent loop, 3 LLM drivers (Anthropic/Gemini/OpenAI-compat), 38 built-in tools, WASM sandbox, MCP client/server, A2A protocol |
-| `openfang-hands` | Hands system (curated autonomous capability packages), 7 bundled hands |
-| `openfang-extensions` | Integration registry (25 bundled MCP templates), AES-256-GCM credential vault, OAuth2 PKCE |
-| `openfang-kernel` | Assembles all subsystems: workflow engine, RBAC auth, heartbeat monitor, cron scheduler, config hot-reload |
-| `openfang-api` | REST/WS/SSE API (Axum 0.8), 76 endpoints, 14-page SPA dashboard, OpenAI-compatible `/v1/chat/completions` |
-| `openfang-channels` | 40 channel adapters (Telegram, Discord, Slack, WhatsApp, and 36 more), formatter, rate limiter |
-| `openfang-wire` | OFP (OpenFang Protocol): TCP P2P networking with HMAC-SHA256 mutual authentication |
-| `openfang-cli` | Clap CLI with daemon auto-detect (HTTP mode vs. in-process fallback), MCP server |
-| `openfang-migrate` | Migration engine for importing from OpenClaw (and future frameworks) |
-| `openfang-skills` | Skill system: 60 bundled skills, FangHub marketplace, OpenClaw compatibility, prompt injection scanning |
-| `openfang-desktop` | Tauri 2.0 native desktop app (WebView + system tray + single-instance + notifications) |
+| `omtae-types` | Shared type definitions, taint tracking, manifest signing (Ed25519), model catalog, MCP/A2A config types |
+| `omtae-memory` | SQLite-backed memory substrate with vector embeddings, usage tracking, canonical sessions, JSONL mirroring |
+| `omtae-runtime` | Agent loop, 3 LLM drivers (Anthropic/Gemini/OpenAI-compat), 38 built-in tools, WASM sandbox, MCP client/server, A2A protocol |
+| `omtae-hands` | Hands system (curated autonomous capability packages), 7 bundled hands |
+| `omtae-extensions` | Integration registry (25 bundled MCP templates), AES-256-GCM credential vault, OAuth2 PKCE |
+| `omtae-kernel` | Assembles all subsystems: workflow engine, RBAC auth, heartbeat monitor, cron scheduler, config hot-reload |
+| `omtae-api` | REST/WS/SSE API (Axum 0.8), 76 endpoints, 14-page SPA dashboard, OpenAI-compatible `/v1/chat/completions` |
+| `omtae-channels` | 40 channel adapters (Telegram, Discord, Slack, WhatsApp, and 36 more), formatter, rate limiter |
+| `omtae-wire` | OFP (OMTAE Protocol): TCP P2P networking with HMAC-SHA256 mutual authentication |
+| `omtae-cli` | Clap CLI with daemon auto-detect (HTTP mode vs. in-process fallback), MCP server |
+| `omtae-migrate` | Migration engine for importing from OpenClaw (and future frameworks) |
+| `omtae-skills` | Skill system: 60 bundled skills, FangHub marketplace, OpenClaw compatibility, prompt injection scanning |
+| `omtae-desktop` | Tauri 2.0 native desktop app (WebView + system tray + single-instance + notifications) |
| `xtask` | Build automation tasks |
### Key Architectural Patterns
-- **`KernelHandle` trait**: Defined in `openfang-runtime`, implemented on `OpenFangKernel` in `openfang-kernel`. This avoids circular crate dependencies while enabling inter-agent tools.
+- **`KernelHandle` trait**: Defined in `omtae-runtime`, implemented on `OMTAEKernel` in `omtae-kernel`. This avoids circular crate dependencies while enabling inter-agent tools.
- **Shared memory**: A fixed UUID (`AgentId(Uuid::from_bytes([0..0, 0x01]))`) provides a cross-agent KV namespace.
-- **Daemon detection**: The CLI checks `~/.openfang/daemon.json` and pings the health endpoint. If a daemon is running, commands use HTTP; otherwise, they boot an in-process kernel.
+- **Daemon detection**: The CLI checks `~/.omtae/daemon.json` and pings the health endpoint. If a daemon is running, commands use HTTP; otherwise, they boot an in-process kernel.
- **Capability-based security**: Every agent operation is checked against the agent's granted capabilities before execution.
---
@@ -173,7 +173,7 @@ agents/my-agent/agent.toml
name = "my-agent"
version = "0.1.0"
description = "A brief description of what this agent does."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["category"]
@@ -205,7 +205,7 @@ You are a specialized agent that...
4. Test by spawning:
```bash
-openfang agent spawn agents/my-agent/agent.toml
+omtae agent spawn agents/my-agent/agent.toml
```
5. Submit a PR with the new template.
@@ -214,11 +214,11 @@ openfang agent spawn agents/my-agent/agent.toml
## How to Add a New Channel Adapter
-Channel adapters live in `crates/openfang-channels/src/`. Each adapter implements the `ChannelAdapter` trait.
+Channel adapters live in `crates/omtae-channels/src/`. Each adapter implements the `ChannelAdapter` trait.
### Steps
-1. Create a new file: `crates/openfang-channels/src/myplatform.rs`
+1. Create a new file: `crates/omtae-channels/src/myplatform.rs`
2. Implement the `ChannelAdapter` trait (defined in `types.rs`):
@@ -252,17 +252,17 @@ impl ChannelAdapter for MyPlatformAdapter {
}
```
-3. Register the module in `crates/openfang-channels/src/lib.rs`:
+3. Register the module in `crates/omtae-channels/src/lib.rs`:
```rust
pub mod myplatform;
```
-4. Wire it up in the channel bridge (`crates/openfang-api/src/channel_bridge.rs`) so the daemon starts it alongside other adapters.
+4. Wire it up in the channel bridge (`crates/omtae-api/src/channel_bridge.rs`) so the daemon starts it alongside other adapters.
-5. Add configuration support in `openfang-types` config structs (add a `[channels.myplatform]` section).
+5. Add configuration support in `omtae-types` config structs (add a `[channels.myplatform]` section).
-6. Add CLI setup wizard instructions in `crates/openfang-cli/src/main.rs` under `cmd_channel_setup`.
+6. Add CLI setup wizard instructions in `crates/omtae-cli/src/main.rs` under `cmd_channel_setup`.
7. Write tests and submit a PR.
@@ -270,7 +270,7 @@ pub mod myplatform;
## How to Add a New Tool
-Built-in tools are defined in `crates/openfang-runtime/src/tool_runner.rs`.
+Built-in tools are defined in `crates/omtae-runtime/src/tool_runner.rs`.
### Steps
@@ -366,6 +366,6 @@ Please report unacceptable behavior to the maintainers.
## Questions?
-- Open a [GitHub Discussion](https://github.com/RightNow-AI/openfang/discussions) for questions.
-- Open a [GitHub Issue](https://github.com/RightNow-AI/openfang/issues) for bugs or feature requests.
+- Open a [GitHub Discussion](https://github.com/RightNow-AI/omtae/discussions) for questions.
+- Open a [GitHub Issue](https://github.com/RightNow-AI/omtae/issues) for bugs or feature requests.
- Check the [docs/](docs/) directory for detailed guides on specific topics.
diff --git a/Cargo.lock b/Cargo.lock
index e880570247..5a2b364837 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3928,37 +3928,7 @@ dependencies = [
]
[[package]]
-name = "once_cell"
-version = "1.21.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
-
-[[package]]
-name = "once_cell_polyfill"
-version = "1.70.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
-
-[[package]]
-name = "opaque-debug"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
-
-[[package]]
-name = "open"
-version = "5.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f3bab717c29a857abf75fcef718d441ec7cb2725f937343c734740a985d37fd"
-dependencies = [
- "dunce",
- "is-wsl",
- "libc",
- "pathdiff",
-]
-
-[[package]]
-name = "openfang-api"
+name = "omtae-api"
version = "0.6.9"
dependencies = [
"argon2",
@@ -3971,16 +3941,16 @@ dependencies = [
"governor",
"hex",
"hmac",
- "openfang-channels",
- "openfang-extensions",
- "openfang-hands",
- "openfang-kernel",
- "openfang-memory",
- "openfang-migrate",
- "openfang-runtime",
- "openfang-skills",
- "openfang-types",
- "openfang-wire",
+ "omtae-channels",
+ "omtae-extensions",
+ "omtae-hands",
+ "omtae-kernel",
+ "omtae-memory",
+ "omtae-migrate",
+ "omtae-runtime",
+ "omtae-skills",
+ "omtae-types",
+ "omtae-wire",
"rand 0.8.5",
"reqwest 0.12.28",
"serde",
@@ -4000,7 +3970,7 @@ dependencies = [
]
[[package]]
-name = "openfang-channels"
+name = "omtae-channels"
version = "0.6.9"
dependencies = [
"aes",
@@ -4018,7 +3988,7 @@ dependencies = [
"lettre",
"mailparse",
"native-tls",
- "openfang-types",
+ "omtae-types",
"prost",
"regex-lite",
"reqwest 0.12.28",
@@ -4039,20 +4009,20 @@ dependencies = [
]
[[package]]
-name = "openfang-cli"
+name = "omtae-cli"
version = "0.6.9"
dependencies = [
"clap",
"clap_complete",
"colored",
"dirs 6.0.0",
- "openfang-api",
- "openfang-extensions",
- "openfang-kernel",
- "openfang-migrate",
- "openfang-runtime",
- "openfang-skills",
- "openfang-types",
+ "omtae-api",
+ "omtae-extensions",
+ "omtae-kernel",
+ "omtae-migrate",
+ "omtae-runtime",
+ "omtae-skills",
+ "omtae-types",
"ratatui",
"reqwest 0.12.28",
"serde",
@@ -4067,14 +4037,14 @@ dependencies = [
]
[[package]]
-name = "openfang-desktop"
+name = "omtae-desktop"
version = "0.6.9"
dependencies = [
"axum",
+ "omtae-api",
+ "omtae-kernel",
+ "omtae-types",
"open",
- "openfang-api",
- "openfang-kernel",
- "openfang-types",
"serde",
"serde_json",
"tauri",
@@ -4093,7 +4063,7 @@ dependencies = [
]
[[package]]
-name = "openfang-extensions"
+name = "omtae-extensions"
version = "0.6.9"
dependencies = [
"aes-gcm",
@@ -4103,7 +4073,7 @@ dependencies = [
"chrono",
"dashmap",
"dirs 6.0.0",
- "openfang-types",
+ "omtae-types",
"rand 0.8.5",
"reqwest 0.12.28",
"serde",
@@ -4121,14 +4091,14 @@ dependencies = [
]
[[package]]
-name = "openfang-hands"
+name = "omtae-hands"
version = "0.6.9"
dependencies = [
"chrono",
"dashmap",
"dirs 6.0.0",
"hex",
- "openfang-types",
+ "omtae-types",
"serde",
"serde_json",
"sha2",
@@ -4141,7 +4111,7 @@ dependencies = [
]
[[package]]
-name = "openfang-kernel"
+name = "omtae-kernel"
version = "0.6.9"
dependencies = [
"async-trait",
@@ -4154,14 +4124,14 @@ dependencies = [
"futures",
"hex",
"libc",
- "openfang-channels",
- "openfang-extensions",
- "openfang-hands",
- "openfang-memory",
- "openfang-runtime",
- "openfang-skills",
- "openfang-types",
- "openfang-wire",
+ "omtae-channels",
+ "omtae-extensions",
+ "omtae-hands",
+ "omtae-memory",
+ "omtae-runtime",
+ "omtae-skills",
+ "omtae-types",
+ "omtae-wire",
"rand 0.8.5",
"reqwest 0.12.28",
"rustls",
@@ -4181,12 +4151,12 @@ dependencies = [
]
[[package]]
-name = "openfang-memory"
+name = "omtae-memory"
version = "0.6.9"
dependencies = [
"async-trait",
"chrono",
- "openfang-types",
+ "omtae-types",
"reqwest 0.12.28",
"rmp-serde",
"rusqlite",
@@ -4201,13 +4171,13 @@ dependencies = [
]
[[package]]
-name = "openfang-migrate"
+name = "omtae-migrate"
version = "0.6.9"
dependencies = [
"chrono",
"dirs 6.0.0",
"json5",
- "openfang-types",
+ "omtae-types",
"serde",
"serde_json",
"serde_yaml",
@@ -4220,7 +4190,7 @@ dependencies = [
]
[[package]]
-name = "openfang-runtime"
+name = "omtae-runtime"
version = "0.6.9"
dependencies = [
"anyhow",
@@ -4232,9 +4202,9 @@ dependencies = [
"futures",
"hex",
"http",
- "openfang-memory",
- "openfang-skills",
- "openfang-types",
+ "omtae-memory",
+ "omtae-skills",
+ "omtae-types",
"regex-lite",
"reqwest 0.12.28",
"rmcp",
@@ -4256,13 +4226,13 @@ dependencies = [
]
[[package]]
-name = "openfang-skills"
+name = "omtae-skills"
version = "0.6.9"
dependencies = [
"chrono",
"ed25519-dalek",
"hex",
- "openfang-types",
+ "omtae-types",
"rand 0.8.5",
"reqwest 0.12.28",
"serde",
@@ -4281,7 +4251,7 @@ dependencies = [
]
[[package]]
-name = "openfang-types"
+name = "omtae-types"
version = "0.6.9"
dependencies = [
"async-trait",
@@ -4295,13 +4265,14 @@ dependencies = [
"serde",
"serde_json",
"sha2",
+ "subtle",
"thiserror 2.0.18",
"toml 0.9.12+spec-1.1.0",
"uuid",
]
[[package]]
-name = "openfang-wire"
+name = "omtae-wire"
version = "0.6.9"
dependencies = [
"async-trait",
@@ -4309,7 +4280,7 @@ dependencies = [
"dashmap",
"hex",
"hmac",
- "openfang-types",
+ "omtae-types",
"rand 0.8.5",
"serde",
"serde_json",
@@ -4322,6 +4293,36 @@ dependencies = [
"uuid",
]
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
+[[package]]
+name = "open"
+version = "5.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f3bab717c29a857abf75fcef718d441ec7cb2725f937343c734740a985d37fd"
+dependencies = [
+ "dunce",
+ "is-wsl",
+ "libc",
+ "pathdiff",
+]
+
[[package]]
name = "openssl"
version = "0.10.76"
diff --git a/Cargo.toml b/Cargo.toml
index 554d9a30e0..3b48c8f64c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,19 +1,19 @@
[workspace]
resolver = "2"
members = [
- "crates/openfang-types",
- "crates/openfang-memory",
- "crates/openfang-runtime",
- "crates/openfang-wire",
- "crates/openfang-api",
- "crates/openfang-kernel",
- "crates/openfang-cli",
- "crates/openfang-channels",
- "crates/openfang-migrate",
- "crates/openfang-skills",
- "crates/openfang-desktop",
- "crates/openfang-hands",
- "crates/openfang-extensions",
+ "crates/omtae-types",
+ "crates/omtae-memory",
+ "crates/omtae-runtime",
+ "crates/omtae-wire",
+ "crates/omtae-api",
+ "crates/omtae-kernel",
+ "crates/omtae-cli",
+ "crates/omtae-channels",
+ "crates/omtae-migrate",
+ "crates/omtae-skills",
+ "crates/omtae-desktop",
+ "crates/omtae-hands",
+ "crates/omtae-extensions",
"xtask",
]
@@ -21,7 +21,7 @@ members = [
version = "0.6.9"
edition = "2021"
license = "Apache-2.0 OR MIT"
-repository = "https://github.com/RightNow-AI/openfang"
+repository = "https://github.com/RightNow-AI/omtae"
rust-version = "1.75"
[workspace.dependencies]
diff --git a/Dockerfile b/Dockerfile
index 1174e95085..293611743e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -13,7 +13,7 @@ ARG LTO=true
ARG CODEGEN_UNITS=1
ENV CARGO_PROFILE_RELEASE_LTO=${LTO} \
CARGO_PROFILE_RELEASE_CODEGEN_UNITS=${CODEGEN_UNITS}
-RUN cargo build --release --bin openfang
+RUN cargo build --release --bin omtae
FROM rust:1-slim-bookworm
RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -25,10 +25,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
npm \
&& rm -rf /var/lib/apt/lists/*
-COPY --from=builder /build/target/release/openfang /usr/local/bin/
-COPY --from=builder /build/agents /opt/openfang/agents
+COPY --from=builder /build/target/release/omtae /usr/local/bin/
+COPY --from=builder /build/agents /opt/omtae/agents
EXPOSE 4200
VOLUME /data
ENV OPENFANG_HOME=/data
-ENTRYPOINT ["openfang"]
+ENTRYPOINT ["omtae"]
CMD ["start"]
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
index 066f64e001..2074595b1d 100644
--- a/LICENSE-APACHE
+++ b/LICENSE-APACHE
@@ -174,7 +174,7 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
END OF TERMS AND CONDITIONS
-Copyright 2024 OpenFang Contributors
+Copyright 2024 OMTAE Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/LICENSE-MIT b/LICENSE-MIT
index f510177b8d..ca1d1cbca5 100644
--- a/LICENSE-MIT
+++ b/LICENSE-MIT
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024 OpenFang Contributors
+Copyright (c) 2024 OMTAE Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/MIGRATION.md b/MIGRATION.md
index 6d3347698f..f71d8db11c 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -1,6 +1,6 @@
-# Migrating to OpenFang
+# Migrating to OMTAE
-This guide covers migrating from OpenClaw (and other frameworks) to OpenFang. The migration engine handles config conversion, agent import, memory transfer, channel re-configuration, and skill scanning.
+This guide covers migrating from OpenClaw (and other frameworks) to OMTAE. The migration engine handles config conversion, agent import, memory transfer, channel re-configuration, and skill scanning.
## Table of Contents
@@ -19,61 +19,61 @@ This guide covers migrating from OpenClaw (and other frameworks) to OpenFang. Th
Run a single command to migrate your entire OpenClaw workspace:
```bash
-openfang migrate --from openclaw
+omtae migrate --from openclaw
```
-This auto-detects your OpenClaw workspace at `~/.openclaw/` and imports everything into `~/.openfang/`.
+This auto-detects your OpenClaw workspace at `~/.openclaw/` and imports everything into `~/.omtae/`.
### Options
```bash
# Specify a custom source directory
-openfang migrate --from openclaw --source-dir /path/to/openclaw/workspace
+omtae migrate --from openclaw --source-dir /path/to/openclaw/workspace
# Dry run -- see what would be imported without making changes
-openfang migrate --from openclaw --dry-run
+omtae migrate --from openclaw --dry-run
```
### Migration Report
-After a successful migration, a `migration_report.md` file is saved to `~/.openfang/` with a summary of everything that was imported, skipped, or needs manual attention.
+After a successful migration, a `migration_report.md` file is saved to `~/.omtae/` with a summary of everything that was imported, skipped, or needs manual attention.
### Other Frameworks
LangChain and AutoGPT migration support is planned:
```bash
-openfang migrate --from langchain # Coming soon
-openfang migrate --from autogpt # Coming soon
+omtae migrate --from langchain # Coming soon
+omtae migrate --from autogpt # Coming soon
```
---
## What Gets Migrated
-| Item | Source (OpenClaw) | Destination (OpenFang) | Status |
+| Item | Source (OpenClaw) | Destination (OMTAE) | Status |
|------|-------------------|------------------------|--------|
-| **Config** | `~/.openclaw/config.yaml` | `~/.openfang/config.toml` | Fully automated |
-| **Agents** | `~/.openclaw/agents/*/agent.yaml` | `~/.openfang/agents/*/agent.toml` | Fully automated |
-| **Memory** | `~/.openclaw/agents/*/MEMORY.md` | `~/.openfang/agents/*/imported_memory.md` | Fully automated |
-| **Channels** | `~/.openclaw/messaging/*.yaml` | `~/.openfang/channels_import.toml` | Automated (manual merge) |
+| **Config** | `~/.openclaw/config.yaml` | `~/.omtae/config.toml` | Fully automated |
+| **Agents** | `~/.openclaw/agents/*/agent.yaml` | `~/.omtae/agents/*/agent.toml` | Fully automated |
+| **Memory** | `~/.openclaw/agents/*/MEMORY.md` | `~/.omtae/agents/*/imported_memory.md` | Fully automated |
+| **Channels** | `~/.openclaw/messaging/*.yaml` | `~/.omtae/channels_import.toml` | Automated (manual merge) |
| **Skills** | `~/.openclaw/skills/` | Scanned and reported | Manual reinstall |
| **Sessions** | `~/.openclaw/agents/*/sessions/` | Not migrated | Fresh start recommended |
| **Workspace files** | `~/.openclaw/agents/*/workspace/` | Not migrated | Copy manually if needed |
### Channel Import Note
-Channel configurations (Telegram, Discord, Slack) are exported to a `channels_import.toml` file. You must manually merge the `[channels]` section into your `~/.openfang/config.toml`.
+Channel configurations (Telegram, Discord, Slack) are exported to a `channels_import.toml` file. You must manually merge the `[channels]` section into your `~/.omtae/config.toml`.
### Skills Note
OpenClaw skills (Node.js) are detected and listed in the migration report but not automatically converted. After migration, reinstall skills using:
```bash
-openfang skill install
+omtae skill install
```
-OpenFang automatically detects OpenClaw-format skills and converts them during installation.
+OMTAE automatically detects OpenClaw-format skills and converts them during installation.
---
@@ -81,13 +81,13 @@ OpenFang automatically detects OpenClaw-format skills and converts them during i
If you prefer migrating by hand (or need to handle edge cases), follow these steps:
-### 1. Initialize OpenFang
+### 1. Initialize OMTAE
```bash
-openfang init
+omtae init
```
-This creates `~/.openfang/` with a default `config.toml`.
+This creates `~/.omtae/` with a default `config.toml`.
### 2. Convert Your Config
@@ -103,7 +103,7 @@ memory:
decay_rate: 0.05
```
-**OpenFang** (`~/.openfang/config.toml`):
+**OMTAE** (`~/.omtae/config.toml`):
```toml
[default_model]
provider = "anthropic"
@@ -136,12 +136,12 @@ tags:
- dev
```
-**OpenFang** (`~/.openfang/agents/coder/agent.toml`):
+**OMTAE** (`~/.omtae/agents/coder/agent.toml`):
```toml
name = "coder"
version = "0.1.0"
description = "A coding assistant"
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["coding", "dev"]
@@ -166,7 +166,7 @@ allowed_users:
- "123456789"
```
-**OpenFang** (add to `~/.openfang/config.toml`):
+**OMTAE** (add to `~/.omtae/config.toml`):
```toml
[channels.telegram]
bot_token_env = "TELEGRAM_BOT_TOKEN"
@@ -176,10 +176,10 @@ allowed_users = ["123456789"]
### 5. Import Memory
-Copy any `MEMORY.md` files from OpenClaw agents to OpenFang agent directories:
+Copy any `MEMORY.md` files from OpenClaw agents to OMTAE agent directories:
```bash
-cp ~/.openclaw/agents/coder/MEMORY.md ~/.openfang/agents/coder/imported_memory.md
+cp ~/.openclaw/agents/coder/MEMORY.md ~/.omtae/agents/coder/imported_memory.md
```
The kernel will ingest these on first boot.
@@ -188,10 +188,10 @@ The kernel will ingest these on first boot.
## Config Format Differences
-| Aspect | OpenClaw | OpenFang |
+| Aspect | OpenClaw | OMTAE |
|--------|----------|----------|
| Format | YAML | TOML |
-| Config location | `~/.openclaw/config.yaml` | `~/.openfang/config.toml` |
+| Config location | `~/.openclaw/config.yaml` | `~/.omtae/config.toml` |
| Agent definition | `agent.yaml` | `agent.toml` |
| Channel config | Separate files per channel | Unified in `config.toml` |
| Tool permissions | Implicit (tool list) | Capability-based (tools, memory, network, shell) |
@@ -205,9 +205,9 @@ The kernel will ingest these on first boot.
## Tool Name Mapping
-Tools were renamed between OpenClaw and OpenFang for consistency. The migration engine handles this automatically.
+Tools were renamed between OpenClaw and OMTAE for consistency. The migration engine handles this automatically.
-| OpenClaw Tool | OpenFang Tool | Notes |
+| OpenClaw Tool | OMTAE Tool | Notes |
|---------------|---------------|-------|
| `read_file` | `file_read` | Noun-first naming |
| `write_file` | `file_write` | |
@@ -225,7 +225,7 @@ Tools were renamed between OpenClaw and OpenFang for consistency. The migration
| `agents_list` | `agent_list` | |
| `agent_list` | `agent_list` | |
-### New Tools in OpenFang
+### New Tools in OMTAE
These tools have no OpenClaw equivalent:
@@ -251,7 +251,7 @@ These tools have no OpenClaw equivalent:
OpenClaw's tool profiles map to explicit tool lists:
-| OpenClaw Profile | OpenFang Tools |
+| OpenClaw Profile | OMTAE Tools |
|------------------|----------------|
| `minimal` | `file_read`, `file_list` |
| `coding` | `file_read`, `file_write`, `file_list`, `shell_exec`, `web_fetch` |
@@ -263,7 +263,7 @@ OpenClaw's tool profiles map to explicit tool lists:
## Provider Mapping
-| OpenClaw Name | OpenFang Name | API Key Env Var |
+| OpenClaw Name | OMTAE Name | API Key Env Var |
|---------------|---------------|-----------------|
| `anthropic` | `anthropic` | `ANTHROPIC_API_KEY` |
| `claude` | `anthropic` | `ANTHROPIC_API_KEY` |
@@ -277,7 +277,7 @@ OpenClaw's tool profiles map to explicit tool lists:
| `mistral` | `mistral` | `MISTRAL_API_KEY` |
| `fireworks` | `fireworks` | `FIREWORKS_API_KEY` |
-### New Providers in OpenFang
+### New Providers in OMTAE
| Provider | Description |
|----------|-------------|
@@ -288,7 +288,7 @@ OpenClaw's tool profiles map to explicit tool lists:
## Feature Comparison
-| Feature | OpenClaw | OpenFang |
+| Feature | OpenClaw | OMTAE |
|---------|----------|----------|
| **Language** | Node.js / TypeScript | Rust |
| **Config format** | YAML | TOML |
@@ -305,7 +305,7 @@ OpenClaw's tool profiles map to explicit tool lists:
| **Event triggers** | None | Pattern-matching event triggers with templated prompts |
| **WASM sandbox** | None | Wasmtime-based sandboxed execution |
| **Python runtime** | None | Subprocess-based Python agent execution |
-| **Networking** | None | OFP (OpenFang Protocol) peer-to-peer |
+| **Networking** | None | OFP (OMTAE Protocol) peer-to-peer |
| **API server** | Basic REST | REST + WebSocket + SSE streaming |
| **WebChat UI** | Separate | Embedded in daemon |
| **Channel adapters** | Telegram, Discord | Telegram, Discord, Slack, WhatsApp, Signal, Matrix, Email |
@@ -324,7 +324,7 @@ OpenClaw's tool profiles map to explicit tool lists:
The migration engine looks for `~/.openclaw/` by default. If your OpenClaw workspace is elsewhere:
```bash
-openfang migrate --from openclaw --source-dir /path/to/your/workspace
+omtae migrate --from openclaw --source-dir /path/to/your/workspace
```
### Agent fails to spawn after migration
@@ -339,7 +339,7 @@ Check the converted `agent.toml` for:
OpenClaw Node.js skills must be reinstalled:
```bash
-openfang skill install /path/to/openclaw/skills/my-skill
+omtae skill install /path/to/openclaw/skills/my-skill
```
The installer auto-detects OpenClaw format and converts the skill manifest.
@@ -349,12 +349,12 @@ The installer auto-detects OpenClaw format and converts the skill manifest.
After migration, channels are exported to `channels_import.toml`. You must merge them into your `config.toml` manually:
```bash
-cat ~/.openfang/channels_import.toml
-# Copy the [channels.*] sections into ~/.openfang/config.toml
+cat ~/.omtae/channels_import.toml
+# Copy the [channels.*] sections into ~/.omtae/config.toml
```
Then restart the daemon:
```bash
-openfang start
+omtae start
```
diff --git a/OMTAE-TUNNEL.md b/OMTAE-TUNNEL.md
new file mode 100644
index 0000000000..17285e5c77
--- /dev/null
+++ b/OMTAE-TUNNEL.md
@@ -0,0 +1,58 @@
+# OMTAE dashboard access over Cloudflare Tunnel
+
+This guide covers exposing the OMTAE web dashboard (default port **4200**) through a personal Cloudflare Tunnel (e.g. `desk.omtaeservices.biz`).
+
+## Security model
+
+**PIN auth is for personal tunnels only.** A 4–6 digit PIN stops casual visitors but is not strong cryptography. Anyone who can reach your tunnel URL can attempt brute force. Combine with:
+
+- Cloudflare Access (recommended for production)
+- A long, unique PIN (not `123456`)
+- Tunnel hostname not shared publicly
+- Rate limiting at the edge where possible
+
+Do **not** rely on PIN-only auth for multi-tenant or internet-facing deployments.
+
+## Configuration
+
+Add to `~/.omtae/config.toml`:
+
+```toml
+[dashboard]
+pin = "your-pin-here" # 4–6 digits — change from any default
+require_pin = true
+```
+
+When `require_pin = true` and `pin` is set:
+
+- The top-level `api_key` / `OPENFANG_API_KEY` **Bearer** requirement is disabled for HTTP/WebSocket (PIN + session cookie instead).
+- `/api/*` requires a valid `omtae_session` cookie (after PIN login) or `X-OMTAE-Pin` header.
+- Public endpoints remain open: `/api/health`, static assets, `/api/auth/login`, `/api/auth/check`.
+
+Restart the daemon after changing config:
+
+```bash
+cargo build --release -p omtae-cli
+sudo systemctl restart omtae-daemon.service
+```
+
+## vLLM API key
+
+Local **vLLM** does not need a wizard API key. Configure the model in `[default_model]` and set `VLLM_API_KEY` in **systemd** (or your vLLM service) if the proxy requires it—not in the dashboard wizard.
+
+## Client behavior
+
+1. Open the tunnel URL in a browser.
+2. Enter the PIN on the unlock screen (mobile-friendly numeric keypad).
+3. The server sets an HttpOnly `omtae_session` cookie (default 7-day TTL via `[auth].session_ttl_hours`).
+4. Optional: scripts can send `X-OMTAE-Pin: ` on API calls instead of Bearer tokens.
+
+## Legacy options
+
+| Method | Use case |
+|--------|----------|
+| `api_key` at root of config.toml | Machine-to-machine / scripts |
+| `[auth]` username + `password_hash` | Stronger dashboard login (`omtae auth hash-password`) |
+| `[dashboard]` PIN | Quick personal tunnel gate |
+
+PIN mode takes precedence over Bearer when `[dashboard]` is active.
diff --git a/README.md b/README.md
index afffa314c3..65d2539a7f 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
-
+
-OpenFang
+OMTAE
The Agent Operating System
@@ -11,9 +11,9 @@
- Documentation •
- Quick Start •
- Twitter / X
+ Documentation •
+ Quick Start •
+ Twitter / X
@@ -22,29 +22,29 @@
-
+
---
> **v0.5.10 (April 2026)**
>
-> OpenFang is feature complete but still pre-1.0. Expect rough edges and breaking changes between minor versions. We ship fast and fix fast. Pin to a specific commit for production use until v1.0. [Report issues here.](https://github.com/RightNow-AI/openfang/issues)
+> OMTAE is feature complete but still pre-1.0. Expect rough edges and breaking changes between minor versions. We ship fast and fix fast. Pin to a specific commit for production use until v1.0. [Report issues here.](https://github.com/RightNow-AI/omtae/issues)
---
-## What is OpenFang?
+## What is OMTAE?
-OpenFang is an **open-source Agent Operating System**. Not a chatbot framework. Not a Python wrapper around an LLM. Not a "multi-agent orchestrator." A full operating system for autonomous agents, built from scratch in Rust.
+OMTAE is an **open-source Agent Operating System**. Not a chatbot framework. Not a Python wrapper around an LLM. Not a "multi-agent orchestrator." A full operating system for autonomous agents, built from scratch in Rust.
-Traditional agent frameworks wait for you to type something. OpenFang runs **autonomous agents that work for you**: on schedules, 24/7, building knowledge graphs, monitoring targets, generating leads, managing your social media, and reporting results to your dashboard.
+Traditional agent frameworks wait for you to type something. OMTAE runs **autonomous agents that work for you**: on schedules, 24/7, building knowledge graphs, monitoring targets, generating leads, managing your social media, and reporting results to your dashboard.
The entire system compiles to a **single ~32MB binary**. One install, one command, your agents are live.
```bash
-curl -fsSL https://openfang.sh/install | sh
-openfang init
-openfang start
+curl -fsSL https://omtae.sh/install | sh
+omtae init
+omtae start
# Dashboard live at http://localhost:4200
```
@@ -52,9 +52,9 @@ openfang start
Windows
```powershell
-irm https://openfang.sh/install.ps1 | iex
-openfang init
-openfang start
+irm https://omtae.sh/install.ps1 | iex
+omtae init
+omtae start
```
@@ -65,7 +65,7 @@ openfang start
"Traditional agents wait for you to type. Hands work for you."
-**Hands** are OpenFang's core innovation. Pre-built autonomous capability packages that run independently, on schedules, without you having to prompt them. This is not a chatbot. This is an agent that wakes up at 6 AM, researches your competitors, builds a knowledge graph, scores the findings, and delivers a report to your Telegram before you've had coffee.
+**Hands** are OMTAE's core innovation. Pre-built autonomous capability packages that run independently, on schedules, without you having to prompt them. This is not a chatbot. This is an agent that wakes up at 6 AM, researches your competitors, builds a knowledge graph, scores the findings, and delivers a report to your Telegram before you've had coffee.
Each Hand bundles:
- **HAND.toml**: manifest declaring tools, settings, requirements, and dashboard metrics.
@@ -89,29 +89,29 @@ All compiled into the binary. No downloading, no pip install, no Docker pull.
```bash
# Activate the Researcher Hand. It starts working immediately.
-openfang hand activate researcher
+omtae hand activate researcher
# Check its progress anytime
-openfang hand status researcher
+omtae hand status researcher
# Activate lead generation on a daily schedule
-openfang hand activate lead
+omtae hand activate lead
# Pause without losing state
-openfang hand pause lead
+omtae hand pause lead
# See all available Hands
-openfang hand list
+omtae hand list
```
**Build your own.** Define a `HAND.toml` with tools, settings, and a system prompt. Publish to FangHub.
---
-## OpenFang vs The Landscape
+## OMTAE vs The Landscape
-
+
### Benchmarks: Measured, Not Marketed
@@ -122,7 +122,7 @@ All data from official documentation and public repositories, February 2026.
```
ZeroClaw ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 10 ms
-OpenFang ██████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 180 ms ★
+OMTAE ██████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 180 ms ★
LangGraph █████████████████░░░░░░░░░░░░░░░░░░░░░░░░░ 2.5 sec
CrewAI ████████████████████░░░░░░░░░░░░░░░░░░░░░░ 3.0 sec
AutoGen ██████████████████████████░░░░░░░░░░░░░░░░░ 4.0 sec
@@ -133,7 +133,7 @@ OpenClaw ███████████████████████
```
ZeroClaw █░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 5 MB
-OpenFang ████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 40 MB ★
+OMTAE ████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 40 MB ★
LangGraph ██████████████████░░░░░░░░░░░░░░░░░░░░░░░░░ 180 MB
CrewAI ████████████████████░░░░░░░░░░░░░░░░░░░░░░░ 200 MB
AutoGen █████████████████████████░░░░░░░░░░░░░░░░░░ 250 MB
@@ -144,7 +144,7 @@ OpenClaw ███████████████████████
```
ZeroClaw █░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 8.8 MB
-OpenFang ███░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 32 MB ★
+OMTAE ███░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 32 MB ★
CrewAI ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 100 MB
LangGraph ████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 150 MB
AutoGen ████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░ 200 MB
@@ -154,7 +154,7 @@ OpenClaw ███████████████████████
#### Security Systems (higher is better)
```
-OpenFang ████████████████████████████████████████████ 16 ★
+OMTAE ████████████████████████████████████████████ 16 ★
ZeroClaw ███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 6
OpenClaw ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 3
AutoGen █████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2
@@ -165,7 +165,7 @@ CrewAI ███░░░░░░░░░░░░░░░░░░░░
#### Channel Adapters (higher is better)
```
-OpenFang ████████████████████████████████████████████ 40 ★
+OMTAE ████████████████████████████████████████████ 40 ★
ZeroClaw ███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 15
OpenClaw █████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 13
CrewAI ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0
@@ -177,7 +177,7 @@ LangGraph ░░░░░░░░░░░░░░░░░░░░░░░
```
ZeroClaw ████████████████████████████████████████████ 28
-OpenFang ██████████████████████████████████████████░░ 27 ★
+OMTAE ██████████████████████████████████████████░░ 27 ★
LangGraph ██████████████████████░░░░░░░░░░░░░░░░░░░░░ 15
CrewAI ██████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 10
OpenClaw ██████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 10
@@ -186,7 +186,7 @@ AutoGen ███████████░░░░░░░░░░░░
### Feature-by-Feature Comparison
-| Feature | OpenFang | OpenClaw | ZeroClaw | CrewAI | AutoGen | LangGraph |
+| Feature | OMTAE | OpenClaw | ZeroClaw | CrewAI | AutoGen | LangGraph |
|---------|----------|----------|----------|--------|---------|-----------|
| **Language** | **Rust** | TypeScript | **Rust** | Python | Python | Python |
| **Autonomous Hands** | **7 built-in** | None | None | None | None | None |
@@ -205,7 +205,7 @@ AutoGen ███████████░░░░░░░░░░░░
## 16 Security Systems: Defense in Depth
-OpenFang doesn't bolt security on after the fact. Every layer is independently testable and operates without a single point of failure.
+OMTAE doesn't bolt security on after the fact. Every layer is independently testable and operates without a single point of failure.
| # | System | What It Does |
|---|--------|-------------|
@@ -233,19 +233,19 @@ OpenFang doesn't bolt security on after the fact. Every layer is independently t
14 Rust crates. 137,728 lines of code. Modular kernel design.
```
-openfang-kernel Orchestration, workflows, metering, RBAC, scheduler, budget tracking
-openfang-runtime Agent loop, 3 LLM drivers, 53 tools, WASM sandbox, MCP, A2A
-openfang-api 140+ REST/WS/SSE endpoints, OpenAI-compatible API, dashboard
-openfang-channels 40 messaging adapters with rate limiting, DM/group policies
-openfang-memory SQLite persistence, vector embeddings, canonical sessions, compaction
-openfang-types Core types, taint tracking, Ed25519 manifest signing, model catalog
-openfang-skills 60 bundled skills, SKILL.md parser, FangHub marketplace
-openfang-hands 7 autonomous Hands, HAND.toml parser, lifecycle management
-openfang-extensions 25 MCP templates, AES-256-GCM credential vault, OAuth2 PKCE
-openfang-wire OFP P2P protocol with HMAC-SHA256 mutual authentication
-openfang-cli CLI with daemon management, TUI dashboard, MCP server mode
-openfang-desktop Tauri 2.0 native app (system tray, notifications, global shortcuts)
-openfang-migrate OpenClaw, LangChain, AutoGPT migration engine
+omtae-kernel Orchestration, workflows, metering, RBAC, scheduler, budget tracking
+omtae-runtime Agent loop, 3 LLM drivers, 53 tools, WASM sandbox, MCP, A2A
+omtae-api 140+ REST/WS/SSE endpoints, OpenAI-compatible API, dashboard
+omtae-channels 40 messaging adapters with rate limiting, DM/group policies
+omtae-memory SQLite persistence, vector embeddings, canonical sessions, compaction
+omtae-types Core types, taint tracking, Ed25519 manifest signing, model catalog
+omtae-skills 60 bundled skills, SKILL.md parser, FangHub marketplace
+omtae-hands 7 autonomous Hands, HAND.toml parser, lifecycle management
+omtae-extensions 25 MCP templates, AES-256-GCM credential vault, OAuth2 PKCE
+omtae-wire OFP P2P protocol with HMAC-SHA256 mutual authentication
+omtae-cli CLI with daemon management, TUI dashboard, MCP server mode
+omtae-desktop Tauri 2.0 native app (system tray, notifications, global shortcuts)
+omtae-migrate OpenClaw, LangChain, AutoGPT migration engine
xtask Build automation
```
@@ -268,12 +268,12 @@ Each adapter supports per-channel model overrides, DM/group policies, rate limit
## WhatsApp Web Gateway (QR Code)
-Connect your personal WhatsApp account to OpenFang via QR code, just like WhatsApp Web. No Meta Business account required.
+Connect your personal WhatsApp account to OMTAE via QR code, just like WhatsApp Web. No Meta Business account required.
### Prerequisites
- **Node.js >= 18** installed ([download](https://nodejs.org/))
-- OpenFang installed and initialized
+- OMTAE installed and initialized
### Setup
@@ -316,10 +316,10 @@ node packages/whatsapp-gateway/index.js
The gateway listens on port `3009` by default. Override with `WHATSAPP_GATEWAY_PORT`.
-**5. Start OpenFang:**
+**5. Start OMTAE:**
```bash
-openfang start
+omtae start
# Dashboard at http://localhost:4200
```
@@ -335,9 +335,9 @@ Once scanned, the status changes to `connected` and incoming messages are routed
| Variable | Description | Default |
|----------|-------------|---------|
-| `WHATSAPP_WEB_GATEWAY_URL` | Gateway URL for OpenFang to connect to | _(empty = disabled)_ |
+| `WHATSAPP_WEB_GATEWAY_URL` | Gateway URL for OMTAE to connect to | _(empty = disabled)_ |
| `WHATSAPP_GATEWAY_PORT` | Port the gateway listens on | `3009` |
-| `OPENFANG_URL` | OpenFang API URL the gateway reports to | `http://127.0.0.1:4200` |
+| `OPENFANG_URL` | OMTAE API URL the gateway reports to | `http://127.0.0.1:4200` |
| `OPENFANG_DEFAULT_AGENT` | Agent that handles incoming messages | `assistant` |
### Gateway API Endpoints
@@ -351,7 +351,7 @@ Once scanned, the status changes to `connected` and incoming messages are routed
### Alternative: WhatsApp Cloud API
-For production workloads, use the [WhatsApp Cloud API](https://developers.facebook.com/docs/whatsapp/cloud-api) with a Meta Business account. See the [Cloud API configuration docs](https://openfang.sh/docs/channels/whatsapp).
+For production workloads, use the [WhatsApp Cloud API](https://developers.facebook.com/docs/whatsapp/cloud-api) with a Meta Business account. See the [Cloud API configuration docs](https://omtae.sh/docs/channels/whatsapp).
@@ -373,22 +373,22 @@ Already running OpenClaw? One command:
```bash
# Migrate everything: agents, memory, skills, configs.
-openfang migrate --from openclaw
+omtae migrate --from openclaw
# Migrate from a specific path
-openfang migrate --from openclaw --path ~/.openclaw
+omtae migrate --from openclaw --path ~/.openclaw
# Dry run first to see what would change
-openfang migrate --from openclaw --dry-run
+omtae migrate --from openclaw --dry-run
```
-The migration engine imports your agents, conversation history, skills, and configuration. OpenFang reads SKILL.md natively and is compatible with the ClawHub marketplace.
+The migration engine imports your agents, conversation history, skills, and configuration. OMTAE reads SKILL.md natively and is compatible with the ClawHub marketplace.
---
## OpenAI-Compatible API
-Drop-in replacement. Point your existing tools at OpenFang:
+Drop-in replacement. Point your existing tools at OMTAE:
```bash
curl -X POST localhost:4200/v1/chat/completions \
@@ -408,34 +408,34 @@ curl -X POST localhost:4200/v1/chat/completions \
```bash
# 1. Install (macOS/Linux)
-curl -fsSL https://openfang.sh/install | sh
+curl -fsSL https://omtae.sh/install | sh
# 2. Initialize. Walks you through provider setup.
-openfang init
+omtae init
# 3. Start the daemon
-openfang start
+omtae start
# 4. Dashboard is live at http://localhost:4200
# 5. Activate a Hand. It starts working for you.
-openfang hand activate researcher
+omtae hand activate researcher
# 6. Chat with an agent
-openfang chat researcher
+omtae chat researcher
> "What are the emerging trends in AI agent frameworks?"
# 7. Spawn a pre-built agent
-openfang agent spawn coder
+omtae agent spawn coder
```
Windows (PowerShell)
```powershell
-irm https://openfang.sh/install.ps1 | iex
-openfang init
-openfang start
+irm https://omtae.sh/install.ps1 | iex
+omtae init
+omtae start
```
@@ -462,11 +462,11 @@ cargo fmt --all -- --check
## Stability Notice
-OpenFang v0.5.10 is pre-1.0. The architecture is solid, the test suite is comprehensive, and the security model is deep. That said:
+OMTAE v0.5.10 is pre-1.0. The architecture is solid, the test suite is comprehensive, and the security model is deep. That said:
- **Breaking changes** may occur between minor versions until v1.0.
- **Some Hands** are more mature than others. Browser and Researcher are the most battle tested.
-- **Edge cases** exist. If you find one, [open an issue](https://github.com/RightNow-AI/openfang/issues).
+- **Edge cases** exist. If you find one, [open an issue](https://github.com/RightNow-AI/omtae/issues).
- **Pin to a specific commit** for production deployments until v1.0.
We ship fast and fix fast. The goal is a rock solid v1.0 by mid 2026.
@@ -487,11 +487,11 @@ MIT. Use it however you want.
## Links
-- [Website & Documentation](https://openfang.sh)
-- [Quick Start Guide](https://openfang.sh/docs/getting-started)
-- [GitHub](https://github.com/RightNow-AI/openfang)
+- [Website & Documentation](https://omtae.sh)
+- [Quick Start Guide](https://omtae.sh/docs/getting-started)
+- [GitHub](https://github.com/RightNow-AI/omtae)
- [Discord](https://discord.gg/sSJqgNnq6X)
-- [Twitter / X](https://x.com/openfangg)
+- [Twitter / X](https://x.com/omtaeg)
---
@@ -504,13 +504,13 @@ MIT. Use it however you want.
- OpenFang is built and maintained by Jaber , Founder of RightNow .
+ OMTAE is built and maintained by Jaber , Founder of RightNow .
Website •
Twitter / X •
-
+
---
diff --git a/SECURITY.md b/SECURITY.md
index 273d2cab00..5fd37d782e 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -8,7 +8,7 @@
## Reporting a Vulnerability
-If you discover a security vulnerability in OpenFang, please report it responsibly.
+If you discover a security vulnerability in OMTAE, please report it responsibly.
**Do NOT open a public GitHub issue for security vulnerabilities.**
@@ -45,7 +45,7 @@ The following are in scope for security reports:
## Security Architecture
-OpenFang implements defense-in-depth with the following security controls:
+OMTAE implements defense-in-depth with the following security controls:
### Access Control
- **Capability-based permissions**: Agents only access resources explicitly granted
diff --git a/agents/analyst/agent.toml b/agents/analyst/agent.toml
index 7d3766fd30..28010f106d 100644
--- a/agents/analyst/agent.toml
+++ b/agents/analyst/agent.toml
@@ -1,8 +1,9 @@
name = "analyst"
version = "0.1.0"
description = "Data analyst. Processes data, generates insights, creates reports."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
+max_history_messages = 8
[model]
provider = "default"
@@ -10,7 +11,13 @@ model = "default"
api_key_env = "GEMINI_API_KEY"
max_tokens = 4096
temperature = 0.4
-system_prompt = """You are Analyst, a data analysis agent running inside the OpenFang Agent OS.
+system_prompt = """You are Analyst, a data analysis agent running inside the OMTAE Agent OS.
+
+TOOLS: Use native function calls only (file_*, shell_exec, web_*, memory_*). NEVER output JSON tool syntax in plain text.
+
+TOOL-FIRST (mandatory):
+- NEVER state file paths, counts, service status, or data values without tool output in this conversation. Runtime enforces TOOL_REQUIRED.
+- On data/status questions: FIRST response MUST include shell_exec or file_read — not planning prose or meta-narration.
ANALYSIS FRAMEWORK:
1. QUESTION — Clarify what question we're answering and what decisions it informs.
@@ -25,6 +32,11 @@ EVIDENCE STANDARDS:
- State confidence levels and sample sizes.
- Flag data quality issues upfront.
+RESEARCHER OUTPUT REVIEW:
+- When synthesizing output from the researcher agent (or any web-based list), flag entries that lack real https:// URLs or appear duplicated.
+- Do not treat unverified business lists as confirmed facts. Mark them "unverified — no cited URL" unless each item has a tool-backed link.
+- If researcher output contains "Website:" without https://, or "Confidence Level: High" without URLs, warn the user explicitly.
+
OUTPUT FORMAT:
- Executive Summary (1-2 sentences)
- Key Findings (numbered, with supporting metrics)
@@ -42,7 +54,7 @@ api_key_env = "GROQ_API_KEY"
max_llm_tokens_per_hour = 150000
[capabilities]
-tools = ["file_read", "file_write", "file_list", "shell_exec", "web_search", "web_fetch", "memory_store", "memory_recall"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
diff --git a/agents/architect/agent.toml b/agents/architect/agent.toml
index 3ed37c9112..19f34e4ed8 100644
--- a/agents/architect/agent.toml
+++ b/agents/architect/agent.toml
@@ -1,7 +1,7 @@
name = "architect"
version = "0.1.0"
description = "System architect. Designs software architectures, evaluates trade-offs, creates technical specifications."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["architecture", "design", "planning"]
@@ -11,7 +11,7 @@ model = "default"
api_key_env = "DEEPSEEK_API_KEY"
max_tokens = 8192
temperature = 0.3
-system_prompt = """You are Architect, a senior software architect running inside the OpenFang Agent OS.
+system_prompt = """You are Architect, a senior software architect running inside the OMTAE Agent OS.
You design systems with these principles:
- Separation of concerns and clean boundaries
@@ -39,7 +39,7 @@ api_key_env = "GROQ_API_KEY"
max_llm_tokens_per_hour = 200000
[capabilities]
-tools = ["file_read", "file_list", "memory_store", "memory_recall", "agent_send"]
+tools = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
agent_message = ["*"]
diff --git a/agents/assistant/agent.toml b/agents/assistant/agent.toml
index 0d9b5469d1..08b20a0744 100644
--- a/agents/assistant/agent.toml
+++ b/agents/assistant/agent.toml
@@ -1,7 +1,7 @@
name = "assistant"
version = "0.1.0"
description = "General-purpose assistant agent. The default OpenClaw agent for everyday tasks, questions, and conversations."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["general", "assistant", "default", "multipurpose", "conversation", "productivity"]
@@ -10,7 +10,7 @@ provider = "default"
model = "default"
max_tokens = 8192
temperature = 0.5
-system_prompt = """You are Assistant, a specialist agent in the OpenFang Agent OS. You are the default general-purpose agent — a versatile, knowledgeable, and helpful companion designed to handle a wide range of everyday tasks, answer questions, and assist with productivity workflows.
+system_prompt = """You are Assistant, a specialist agent in the OMTAE Agent OS. You are the default general-purpose agent — a versatile, knowledgeable, and helpful companion designed to handle a wide range of everyday tasks, answer questions, and assist with productivity workflows.
CORE COMPETENCIES:
@@ -30,7 +30,7 @@ You are a versatile writer who adapts style and tone to the task: professional c
You help users think through problems logically. You apply structured frameworks: define the problem, identify constraints, generate options, evaluate trade-offs, and recommend a course of action. You use first-principles thinking to break complex problems into manageable components. You consider multiple perspectives and anticipate potential objections or risks.
6. Agent Delegation
-As the default entry point to the OpenFang Agent OS, you know when a task would be better handled by a specialist agent. You can list available agents, delegate tasks to specialists, and synthesize their responses. You understand each specialist's strengths and route work accordingly: coding tasks to Coder, research to Researcher, data analysis to Analyst, writing to Writer, and so on. When a task is within your general capabilities, you handle it directly without unnecessary delegation.
+As the default entry point to the OMTAE Agent OS, you know when a task would be better handled by a specialist agent. You can list available agents, delegate tasks to specialists, and synthesize their responses. You understand each specialist's strengths and route work accordingly: coding tasks to Coder, research to Researcher, data analysis to Analyst, writing to Writer, and so on. When a task is within your general capabilities, you handle it directly without unnecessary delegation.
7. Knowledge Management
You help users organize and retrieve information across sessions. You store important context, preferences, and reference material in memory for future conversations. You maintain structured notes, to-do lists, and project summaries. You recall previous conversations and build on established context.
@@ -58,7 +58,7 @@ TOOLS AVAILABLE:
- shell_exec: Run computations, scripts, and system commands
- agent_send / agent_list: Delegate tasks to specialist agents and see available agents
-You are reliable, adaptable, and genuinely helpful. You are the user's trusted first point of contact in the OpenFang Agent OS — capable of handling most tasks directly and smart enough to delegate when a specialist would do it better."""
+You are reliable, adaptable, and genuinely helpful. You are the user's trusted first point of contact in the OMTAE Agent OS — capable of handling most tasks directly and smart enough to delegate when a specialist would do it better."""
[[fallback_models]]
provider = "default"
@@ -70,7 +70,7 @@ max_llm_tokens_per_hour = 300000
max_concurrent_tools = 10
[capabilities]
-tools = ["file_read", "file_write", "file_list", "memory_store", "memory_recall", "web_fetch", "shell_exec", "agent_send", "agent_list"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
diff --git a/agents/code-reviewer/agent.toml b/agents/code-reviewer/agent.toml
index 24bbee3ec0..92ed953fd3 100644
--- a/agents/code-reviewer/agent.toml
+++ b/agents/code-reviewer/agent.toml
@@ -1,7 +1,7 @@
name = "code-reviewer"
version = "0.1.0"
description = "Senior code reviewer. Reviews PRs, identifies issues, suggests improvements with production standards."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["review", "code-quality", "best-practices"]
@@ -11,7 +11,7 @@ model = "default"
api_key_env = "GEMINI_API_KEY"
max_tokens = 4096
temperature = 0.3
-system_prompt = """You are Code Reviewer, a senior engineer running inside the OpenFang Agent OS.
+system_prompt = """You are Code Reviewer, a senior engineer running inside the OMTAE Agent OS.
Review criteria (in priority order):
1. CORRECTNESS: Does it work? Logic errors, edge cases, error handling
@@ -42,7 +42,7 @@ api_key_env = "GROQ_API_KEY"
max_llm_tokens_per_hour = 150000
[capabilities]
-tools = ["file_read", "file_list", "shell_exec", "memory_store", "memory_recall"]
+tools = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
shell = ["cargo clippy *", "cargo fmt *", "git diff *", "git log *"]
diff --git a/agents/coder/agent.toml b/agents/coder/agent.toml
index 0974a7b967..bb8dd31d84 100644
--- a/agents/coder/agent.toml
+++ b/agents/coder/agent.toml
@@ -1,17 +1,20 @@
name = "coder"
version = "0.1.0"
description = "Expert software engineer. Reads, writes, and analyzes code."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
+max_history_messages = 8
tags = ["coding", "implementation", "rust", "python"]
[model]
-provider = "default"
-model = "default"
-api_key_env = "GEMINI_API_KEY"
-max_tokens = 8192
+provider = "vllm"
+model = "OBLITERATUS/Qwen3.6-27B-OBLITERATED"
+api_key_env = "VLLM_API_KEY"
+max_tokens = 4096
temperature = 0.3
-system_prompt = """You are Coder, an expert software engineer agent running inside the OpenFang Agent OS.
+system_prompt = """You are Coder, an expert software engineer agent running inside the OMTAE Agent OS.
+
+TOOLS: Use native function calls only (file_*, shell_exec, web_*, memory_*). NEVER output JSON tool syntax in plain text (e.g. {"name":"coder",...} or {"name":"shell_exec",...}).
METHODOLOGY:
1. READ — Always read the relevant file(s) before making changes. Understand context, conventions, and dependencies.
@@ -31,16 +34,16 @@ RESEARCH:
- Check official documentation before guessing at API usage."""
[[fallback_models]]
-provider = "default"
-model = "default"
-api_key_env = "GROQ_API_KEY"
+provider = "vllm"
+model = "OBLITERATUS/Qwen3.6-27B-OBLITERATED"
+api_key_env = "VLLM_API_KEY"
[resources]
max_llm_tokens_per_hour = 200000
max_concurrent_tools = 10
[capabilities]
-tools = ["file_read", "file_write", "file_list", "shell_exec", "web_search", "web_fetch", "memory_store", "memory_recall"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*"]
diff --git a/agents/customer-support/agent.toml b/agents/customer-support/agent.toml
index b7d0ac2e8d..9c67341555 100644
--- a/agents/customer-support/agent.toml
+++ b/agents/customer-support/agent.toml
@@ -1,7 +1,7 @@
name = "customer-support"
version = "0.1.0"
description = "Customer support agent for ticket handling, issue resolution, and customer communication."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["support", "customer-service", "tickets", "helpdesk", "communication", "resolution"]
@@ -10,7 +10,7 @@ provider = "default"
model = "default"
max_tokens = 4096
temperature = 0.3
-system_prompt = """You are Customer Support, a specialist agent in the OpenFang Agent OS. You are an expert customer service representative who handles support tickets, resolves issues, and communicates with customers professionally and empathetically.
+system_prompt = """You are Customer Support, a specialist agent in the OMTAE Agent OS. You are an expert customer service representative who handles support tickets, resolves issues, and communicates with customers professionally and empathetically.
CORE COMPETENCIES:
@@ -64,7 +64,7 @@ max_llm_tokens_per_hour = 200000
max_concurrent_tools = 5
[capabilities]
-tools = ["file_read", "file_write", "file_list", "memory_store", "memory_recall", "web_fetch"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
diff --git a/agents/data-scientist/agent.toml b/agents/data-scientist/agent.toml
index cb69103cf6..f5c81223c6 100644
--- a/agents/data-scientist/agent.toml
+++ b/agents/data-scientist/agent.toml
@@ -1,7 +1,7 @@
name = "data-scientist"
version = "0.1.0"
description = "Data scientist. Analyzes datasets, builds models, creates visualizations, performs statistical analysis."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
[model]
@@ -10,7 +10,7 @@ model = "default"
api_key_env = "GEMINI_API_KEY"
max_tokens = 4096
temperature = 0.3
-system_prompt = """You are Data Scientist, an analytics expert running inside the OpenFang Agent OS.
+system_prompt = """You are Data Scientist, an analytics expert running inside the OMTAE Agent OS.
Your methodology:
1. UNDERSTAND: What question are we answering?
@@ -44,7 +44,7 @@ api_key_env = "GROQ_API_KEY"
max_llm_tokens_per_hour = 150000
[capabilities]
-tools = ["file_read", "file_write", "file_list", "shell_exec", "web_search", "web_fetch", "memory_store", "memory_recall"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
diff --git a/agents/debugger/agent.toml b/agents/debugger/agent.toml
index 41887d2396..5eb251a469 100644
--- a/agents/debugger/agent.toml
+++ b/agents/debugger/agent.toml
@@ -1,16 +1,16 @@
name = "debugger"
version = "0.1.0"
description = "Expert debugger. Traces bugs, analyzes stack traces, performs root cause analysis."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
[model]
-provider = "default"
-model = "default"
-api_key_env = "GEMINI_API_KEY"
+provider = "vllm"
+model = "OBLITERATUS/Qwen3.6-27B-OBLITERATED"
+api_key_env = "VLLM_API_KEY"
max_tokens = 4096
temperature = 0.2
-system_prompt = """You are Debugger, an expert bug hunter running inside the OpenFang Agent OS.
+system_prompt = """You are Debugger, an expert bug hunter running inside the OMTAE Agent OS.
DEBUGGING METHODOLOGY:
1. REPRODUCE — Understand the exact failure. Get the error message, stack trace, or unexpected behavior.
@@ -37,15 +37,15 @@ OUTPUT FORMAT:
- Prevention: Test or pattern to prevent recurrence"""
[[fallback_models]]
-provider = "default"
-model = "default"
-api_key_env = "GROQ_API_KEY"
+provider = "vllm"
+model = "OBLITERATUS/Qwen3.6-27B-OBLITERATED"
+api_key_env = "VLLM_API_KEY"
[resources]
max_llm_tokens_per_hour = 150000
[capabilities]
-tools = ["file_read", "file_write", "file_list", "shell_exec", "web_search", "web_fetch", "memory_store", "memory_recall"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
diff --git a/agents/devops-lead/agent.toml b/agents/devops-lead/agent.toml
index 46e8c550e3..c869fd47da 100644
--- a/agents/devops-lead/agent.toml
+++ b/agents/devops-lead/agent.toml
@@ -1,7 +1,7 @@
name = "devops-lead"
version = "0.1.0"
description = "DevOps lead. Manages CI/CD, infrastructure, deployments, monitoring, and incident response."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
[model]
@@ -9,7 +9,7 @@ provider = "default"
model = "default"
max_tokens = 4096
temperature = 0.2
-system_prompt = """You are DevOps Lead, a platform engineering expert running inside the OpenFang Agent OS.
+system_prompt = """You are DevOps Lead, a platform engineering expert running inside the OMTAE Agent OS.
Your domains:
- CI/CD pipeline design and optimization
@@ -43,7 +43,7 @@ api_key_env = "GEMINI_API_KEY"
max_llm_tokens_per_hour = 150000
[capabilities]
-tools = ["file_read", "file_write", "file_list", "shell_exec", "memory_store", "memory_recall", "agent_send"]
+tools = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
agent_message = ["*"]
diff --git a/agents/doc-writer/agent.toml b/agents/doc-writer/agent.toml
index 0a8a1c11ea..1b16caa7f0 100644
--- a/agents/doc-writer/agent.toml
+++ b/agents/doc-writer/agent.toml
@@ -1,7 +1,7 @@
name = "doc-writer"
version = "0.1.0"
description = "Technical writer. Creates documentation, README files, API docs, tutorials, and architecture guides."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
[model]
@@ -9,7 +9,7 @@ provider = "default"
model = "default"
max_tokens = 8192
temperature = 0.4
-system_prompt = """You are Doc Writer, a technical documentation specialist running inside the OpenFang Agent OS.
+system_prompt = """You are Doc Writer, a technical documentation specialist running inside the OMTAE Agent OS.
Documentation principles:
- Write for the reader, not the writer
@@ -41,6 +41,6 @@ api_key_env = "GEMINI_API_KEY"
max_llm_tokens_per_hour = 200000
[capabilities]
-tools = ["file_read", "file_write", "file_list", "memory_store", "memory_recall"]
+tools = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
diff --git a/agents/ecc-bridge/agent.toml b/agents/ecc-bridge/agent.toml
new file mode 100644
index 0000000000..a15dcb4b4f
--- /dev/null
+++ b/agents/ecc-bridge/agent.toml
@@ -0,0 +1,32 @@
+name = "ecc-bridge"
+version = "0.1.0"
+description = "ECC verification bridge — tool-first discipline for OMTAE daemon."
+author = "omtae"
+module = "builtin:chat"
+max_history_messages = 8
+tags = ["ecc", "verification", "tool-first"]
+
+[model]
+provider = "default"
+model = "default"
+api_key_env = "GEMINI_API_KEY"
+max_tokens = 4096
+temperature = 0.3
+system_prompt = """You are ECC-Bridge, an OMTAE agent wired for ECC-style verification discipline.
+
+TOOLS: shell_exec, file_list, file_read, web_search, web_fetch, memory_recall, memory_store.
+
+TOOL-FIRST (runtime-enforced):
+- Every factual claim requires tool evidence in this conversation or you get TOOL_REQUIRED.
+- Brain/vault: curl -s -H "X-OMTAE-Pin: 839201" http://127.0.0.1:4200/api/brain/status first.
+- No peer-agent narration without curl /api/agents or agent_list output.
+- No meta-narration ("The user is asking about…"). One planning line max, then tool call.
+
+Use ECC skills if installed: ecc-verification-loop, ecc-search-first, ecc-no-fake-tools."""
+
+[capabilities]
+tools = ["*"]
+network = ["*"]
+memory_read = ["*"]
+memory_write = ["self.*"]
+shell = ["curl *", "ls *", "cat *"]
diff --git a/agents/email-assistant/agent.toml b/agents/email-assistant/agent.toml
index 4fe7915d5c..dbb6519620 100644
--- a/agents/email-assistant/agent.toml
+++ b/agents/email-assistant/agent.toml
@@ -1,7 +1,7 @@
name = "email-assistant"
version = "0.1.0"
description = "Email triage, drafting, scheduling, and inbox management agent."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["email", "communication", "triage", "drafting", "scheduling", "productivity"]
@@ -10,7 +10,7 @@ provider = "default"
model = "default"
max_tokens = 8192
temperature = 0.4
-system_prompt = """You are Email Assistant, a specialist agent in the OpenFang Agent OS. Your purpose is to manage, triage, draft, and schedule emails with expert precision and professionalism.
+system_prompt = """You are Email Assistant, a specialist agent in the OMTAE Agent OS. Your purpose is to manage, triage, draft, and schedule emails with expert precision and professionalism.
CORE COMPETENCIES:
@@ -56,7 +56,7 @@ max_llm_tokens_per_hour = 150000
max_concurrent_tools = 5
[capabilities]
-tools = ["file_read", "file_write", "file_list", "memory_store", "memory_recall", "web_fetch"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
diff --git a/agents/health-tracker/agent.toml b/agents/health-tracker/agent.toml
index 6efa73a846..6efa57820f 100644
--- a/agents/health-tracker/agent.toml
+++ b/agents/health-tracker/agent.toml
@@ -1,7 +1,7 @@
name = "health-tracker"
version = "0.1.0"
description = "Wellness tracking agent for health metrics, medication reminders, fitness goals, and lifestyle habits."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["health", "wellness", "fitness", "medication", "habits", "tracking"]
@@ -10,7 +10,7 @@ provider = "default"
model = "default"
max_tokens = 4096
temperature = 0.3
-system_prompt = """You are Health Tracker, a specialist agent in the OpenFang Agent OS. You are an expert wellness assistant who helps users track health metrics, manage medication schedules, set fitness goals, and build healthy habits. You are NOT a medical professional and you always make this clear.
+system_prompt = """You are Health Tracker, a specialist agent in the OMTAE Agent OS. You are an expert wellness assistant who helps users track health metrics, manage medication schedules, set fitness goals, and build healthy habits. You are NOT a medical professional and you always make this clear.
CORE COMPETENCIES:
@@ -63,6 +63,6 @@ max_llm_tokens_per_hour = 100000
max_concurrent_tools = 5
[capabilities]
-tools = ["file_read", "file_write", "file_list", "memory_store", "memory_recall"]
+tools = ["*"]
memory_read = ["*"]
memory_write = ["self.*"]
diff --git a/agents/hello-world/agent.toml b/agents/hello-world/agent.toml
index c6b007f2ce..2894405891 100644
--- a/agents/hello-world/agent.toml
+++ b/agents/hello-world/agent.toml
@@ -1,7 +1,7 @@
name = "hello-world"
version = "0.1.0"
description = "A friendly greeting agent that can read files, search the web, and answer everyday questions."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
[model]
@@ -9,7 +9,7 @@ provider = "default"
model = "default"
max_tokens = 4096
temperature = 0.6
-system_prompt = """You are Hello World, a friendly and approachable agent in the OpenFang Agent OS.
+system_prompt = """You are Hello World, a friendly and approachable agent in the OMTAE Agent OS.
You are the first agent new users interact with. Be warm, concise, and helpful.
Answer questions directly. If you can look something up to give a better answer, do it.
@@ -22,7 +22,7 @@ Keep responses brief (2-4 paragraphs max) unless the user asks for detail."""
max_llm_tokens_per_hour = 100000
[capabilities]
-tools = ["file_read", "file_list", "web_fetch", "web_search", "memory_store", "memory_recall"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*"]
diff --git a/agents/home-automation/agent.toml b/agents/home-automation/agent.toml
index 25b61213d7..aa2de22677 100644
--- a/agents/home-automation/agent.toml
+++ b/agents/home-automation/agent.toml
@@ -1,7 +1,7 @@
name = "home-automation"
version = "0.1.0"
description = "Smart home control agent for IoT device management, automation rules, and home monitoring."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["smart-home", "iot", "automation", "devices", "monitoring", "home"]
@@ -10,7 +10,7 @@ provider = "default"
model = "default"
max_tokens = 4096
temperature = 0.2
-system_prompt = """You are Home Automation, a specialist agent in the OpenFang Agent OS. You are an expert smart home engineer and IoT integration specialist who helps users manage connected devices, create automation rules, monitor home systems, and optimize their smart home setup.
+system_prompt = """You are Home Automation, a specialist agent in the OMTAE Agent OS. You are an expert smart home engineer and IoT integration specialist who helps users manage connected devices, create automation rules, monitor home systems, and optimize their smart home setup.
CORE COMPETENCIES:
@@ -60,7 +60,7 @@ max_llm_tokens_per_hour = 100000
max_concurrent_tools = 10
[capabilities]
-tools = ["file_read", "file_write", "file_list", "memory_store", "memory_recall", "shell_exec", "web_fetch"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
diff --git a/agents/langchain-code-reviewer/config.example.toml b/agents/langchain-code-reviewer/config.example.toml
index 2aadbd983a..2172c7408a 100644
--- a/agents/langchain-code-reviewer/config.example.toml
+++ b/agents/langchain-code-reviewer/config.example.toml
@@ -1,4 +1,4 @@
-# Add this section to your ~/.openfang/config.toml
+# Add this section to your ~/.omtae/config.toml
# to register the LangChain code review agent.
[a2a]
diff --git a/agents/langchain-code-reviewer/server.py b/agents/langchain-code-reviewer/server.py
index 3a060af6c7..a18357901e 100644
--- a/agents/langchain-code-reviewer/server.py
+++ b/agents/langchain-code-reviewer/server.py
@@ -2,7 +2,7 @@
LangChain Code Review Agent — A2A-compatible server.
Exposes a code review agent via Google's A2A protocol so that
-OpenFang workflows can call it as an external agent.
+OMTAE workflows can call it as an external agent.
Start:
OPENAI_API_KEY=sk-xxx python server.py
diff --git a/agents/langchain-code-reviewer/workflow.json b/agents/langchain-code-reviewer/workflow.json
index ae6db0b83c..a65208d8bf 100644
--- a/agents/langchain-code-reviewer/workflow.json
+++ b/agents/langchain-code-reviewer/workflow.json
@@ -1,7 +1,7 @@
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "langchain-code-review-pipeline",
- "description": "Code review pipeline: uses LangChain external agent for deep review, then OpenFang Writer agent to format the final report.",
+ "description": "Code review pipeline: uses LangChain external agent for deep review, then OMTAE Writer agent to format the final report.",
"created_at": "2026-03-16T00:00:00Z",
"steps": [
{
diff --git a/agents/legal-assistant/agent.toml b/agents/legal-assistant/agent.toml
index ef6552f419..2aa15f7d9a 100644
--- a/agents/legal-assistant/agent.toml
+++ b/agents/legal-assistant/agent.toml
@@ -1,7 +1,7 @@
name = "legal-assistant"
version = "0.1.0"
description = "Legal assistant agent for contract review, legal research, compliance checking, and document drafting."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["legal", "contracts", "compliance", "research", "review", "documents"]
@@ -11,7 +11,7 @@ model = "default"
api_key_env = "GEMINI_API_KEY"
max_tokens = 8192
temperature = 0.2
-system_prompt = """You are Legal Assistant, a specialist agent in the OpenFang Agent OS. You are an expert legal research and document review assistant who helps with contract analysis, legal research, compliance checking, and document preparation. You are NOT a licensed attorney and you always make this clear.
+system_prompt = """You are Legal Assistant, a specialist agent in the OMTAE Agent OS. You are an expert legal research and document review assistant who helps with contract analysis, legal research, compliance checking, and document preparation. You are NOT a licensed attorney and you always make this clear.
CORE COMPETENCIES:
@@ -67,7 +67,7 @@ max_llm_tokens_per_hour = 200000
max_concurrent_tools = 5
[capabilities]
-tools = ["file_read", "file_write", "file_list", "memory_store", "memory_recall", "web_fetch"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
diff --git a/agents/meeting-assistant/agent.toml b/agents/meeting-assistant/agent.toml
index 297dbeb94d..b97f9fa326 100644
--- a/agents/meeting-assistant/agent.toml
+++ b/agents/meeting-assistant/agent.toml
@@ -1,7 +1,7 @@
name = "meeting-assistant"
version = "0.1.0"
description = "Meeting notes, action items, agenda preparation, and follow-up tracking agent."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["meetings", "notes", "action-items", "agenda", "follow-up", "productivity"]
@@ -10,7 +10,7 @@ provider = "default"
model = "default"
max_tokens = 8192
temperature = 0.3
-system_prompt = """You are Meeting Assistant, a specialist agent in the OpenFang Agent OS. You are an expert at preparing agendas, capturing meeting notes, extracting action items, and managing follow-up workflows to ensure nothing falls through the cracks.
+system_prompt = """You are Meeting Assistant, a specialist agent in the OMTAE Agent OS. You are an expert at preparing agendas, capturing meeting notes, extracting action items, and managing follow-up workflows to ensure nothing falls through the cracks.
CORE COMPETENCIES:
@@ -59,6 +59,6 @@ max_llm_tokens_per_hour = 150000
max_concurrent_tools = 5
[capabilities]
-tools = ["file_read", "file_write", "file_list", "memory_store", "memory_recall"]
+tools = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
diff --git a/agents/ops-fixer/agent.toml b/agents/ops-fixer/agent.toml
new file mode 100644
index 0000000000..0b16c8b019
--- /dev/null
+++ b/agents/ops-fixer/agent.toml
@@ -0,0 +1,66 @@
+name = "ops-fixer"
+version = "0.1.0"
+description = "OMTAE desk and stack troubleshooter. Diagnoses vLLM, daemon, configs; fixes with evidence."
+author = "omtae"
+module = "builtin:chat"
+max_history_messages = 8
+tags = ["ops", "debug", "omtae", "vllm"]
+
+[exec_policy]
+mode = "full"
+shell_env_passthrough = ["VLLM_API_KEY", "GROQ_API_KEY", "GEMINI_API_KEY"]
+
+[model]
+provider = "vllm"
+model = "OBLITERATUS/Qwen3.6-27B-OBLITERATED"
+api_key_env = "VLLM_API_KEY"
+max_tokens = 4096
+temperature = 0.2
+system_prompt = """You are Ops-Fixer, an OMTAE infrastructure and desk repair agent.
+
+MISSION: Diagnose and fix OMTAE desk (:4200), vLLM (:8000), ~/.omtae config, systemd units, and agent manifests. You work on the real host — not in simulation.
+
+TOOLS: Use native function calls only (file_read, file_write, shell_exec, web_fetch, web_search). NEVER output fake JSON tool blocks or claim you ran a command without shell_exec evidence in this turn.
+
+ACTION RULES (mandatory — never violate):
+- TOOL-FIRST: every factual claim requires shell_exec/file_list evidence. Runtime enforces TOOL_REQUIRED if you skip tools.
+- On "system check", "status", "health", or similar: your FIRST response MUST include a tool call — shell_exec (nvidia-smi, curl -s http://127.0.0.1:4200/api/health) OR file_list on ~/.omtae/agents. No exceptions.
+- FORBIDDEN: "The user is asking about…" meta-narration, repeating planning sentences, or claiming fixes without fresh verification output.
+- NEVER claim workspace, memory, service, or GPU status without tool output from this conversation.
+- Shell commands must NOT use shell metacharacters (no pipes, redirects, semicolons, or ||). Run one simple command per shell_exec call.
+
+METHODOLOGY:
+1. OBSERVE — curl health endpoints, systemctl status, read configs and logs before changing anything.
+2. HYPOTHESIZE — State the most likely root cause with evidence (exit codes, log lines, HTTP status).
+3. FIX — Minimal, reversible changes. Prefer config/manifest fixes over daemon hacks.
+4. VERIFY — Re-run the same curl/systemctl checks and show output. Never say "fixed" without fresh verification output.
+
+CHECKS (run as needed — one command per shell_exec, no metacharacters):
+- curl -s http://127.0.0.1:4200/api/health
+- curl -s http://127.0.0.1:8000/v1/models
+- nvidia-smi
+- systemctl --user status omtae-vllm.service
+- systemctl --user status openfang.service
+- file_read ~/.omtae/config.toml and agent.toml under ~/.omtae/agents/
+
+CONSTRAINTS:
+- Do not spawn other agents (no agent_spawn).
+- Do not enable continuous or periodic schedules.
+- Report Status: OK / WARNING / CRITICAL with command output excerpts."""
+
+[[fallback_models]]
+provider = "vllm"
+model = "OBLITERATUS/Qwen3.6-27B-OBLITERATED"
+api_key_env = "VLLM_API_KEY"
+
+[resources]
+max_llm_tokens_per_hour = 150000
+max_concurrent_tools = 8
+
+[capabilities]
+tools = ["*"]
+network = ["*"]
+memory_read = ["*"]
+memory_write = ["self.*"]
+agent_spawn = false
+shell = ["curl *", "nvidia-smi", "systemctl *", "journalctl *", "pgrep *", "ss *", "netstat *", "bash *", "cat *", "grep *", "ls *", "tail *", "head *", "python3 *", "omtae-model *"]
diff --git a/agents/ops/agent.toml b/agents/ops/agent.toml
index 1a61b61f0a..270b1cab24 100644
--- a/agents/ops/agent.toml
+++ b/agents/ops/agent.toml
@@ -1,7 +1,7 @@
name = "ops"
version = "0.1.0"
description = "DevOps agent. Monitors systems, runs diagnostics, manages deployments."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
[model]
@@ -9,7 +9,7 @@ provider = "default"
model = "default"
max_tokens = 2048
temperature = 0.2
-system_prompt = """You are Ops, a DevOps and systems operations agent running inside the OpenFang Agent OS.
+system_prompt = """You are Ops, a DevOps and systems operations agent running inside the OMTAE Agent OS.
METHODOLOGY:
1. OBSERVE — Check current state before making changes. Read configs, check logs, verify status.
@@ -35,7 +35,7 @@ periodic = { cron = "every 5m" }
max_llm_tokens_per_hour = 50000
[capabilities]
-tools = ["shell_exec", "file_read", "file_list"]
+tools = ["*"]
memory_read = ["*"]
memory_write = ["self.*"]
shell = ["docker *", "git *", "cargo *", "systemctl *", "ps *", "df *", "free *"]
diff --git a/agents/orchestrator/agent.toml b/agents/orchestrator/agent.toml
index b4c0c54d8a..760042511a 100644
--- a/agents/orchestrator/agent.toml
+++ b/agents/orchestrator/agent.toml
@@ -1,28 +1,35 @@
name = "orchestrator"
version = "0.1.0"
description = "Meta-agent that decomposes complex tasks, delegates to specialist agents, and synthesizes results."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
+# Keep chat history bounded on 8k-context local models (issue #871).
+max_history_messages = 40
[model]
provider = "default"
model = "default"
-api_key_env = "DEEPSEEK_API_KEY"
-max_tokens = 8192
+api_key_env = "VLLM_API_KEY"
+max_tokens = 2048
temperature = 0.3
-system_prompt = """You are Orchestrator, the command center of the OpenFang Agent OS.
+system_prompt = """You are Orchestrator, the command center of the OMTAE Agent OS.
Your role is to decompose complex tasks into subtasks and delegate them to specialist agents.
-AVAILABLE TOOLS:
-- agent_list: See all running agents and their capabilities
-- agent_send: Send a message to a specialist agent and get their response
-- agent_spawn: Create new agents when needed
-- agent_kill: Terminate agents no longer needed
+AVAILABLE TOOLS (use native function calling only — NEVER print JSON like {"name":"researcher",...} in your reply):
+- agent_list: See all running agents and their IDs (UUIDs)
+- agent_send(agent_id, message): Delegate to a specialist — agent_id MUST be the UUID from agent_list
- memory_store: Save results and state to shared memory
- memory_recall: Retrieve shared data from memory
-SPECIALIST AGENTS (spawn or message these):
+HONESTY (mandatory — never violate):
+- Call agent_list once before your first agent_send in a turn; copy agent_id (UUID) from that output.
+- NEVER claim delegation succeeded unless the agent_send tool result starts with "agent_send OK".
+- If agent_send returns "agent_send FAILED", an error, or empty content, tell the user FAILED and quote the exact tool error verbatim.
+- NEVER invent or paraphrase a specialist's reply — only quote text from the agent_send tool result block after "--- Specialist response".
+- If you did not receive agent_send OK with specialist text, you did not hear from that agent.
+
+SPECIALIST AGENTS (use agent_id from agent_list — do NOT spawn duplicates):
- coder: Writes and reviews code
- researcher: Gathers information
- writer: Creates documentation and content
@@ -35,29 +42,45 @@ SPECIALIST AGENTS (spawn or message these):
WORKFLOW:
1. Analyze the user's request
-2. Use agent_list to see available agents
+2. Use agent_list once if you need to see who is running
3. Break the task into subtasks
-4. Delegate each subtask to the most appropriate specialist via agent_send
-5. Synthesize all responses into a coherent final answer
+4. Delegate each subtask via agent_send using agent_id from agent_list (max 5 delegations per user message)
+5. Synthesize only from agent_send OK results — quote specialist wording where it matters
6. Store important results in shared memory for future reference
-Always explain your delegation strategy before executing it.
-Be thorough but efficient — don't delegate trivially simple tasks."""
+RESEARCHER OUTPUT REVIEW:
+- When synthesizing researcher output (business lists, rankings, contacts), flag items without real https:// URLs from the specialist's tool results.
+- Do not pass through duplicate entries or placeholder "Website: " lines as verified facts.
+- If researcher lists lack cited URLs or claim high confidence without links, tell the user those entries are unverified.
+
+STOP RULES (mandatory):
+- Never use agent_send to message yourself or "orchestrator"
+- Never call agent_list repeatedly — once per turn is enough
+- After at most 5 agent_send calls, STOP delegating and write the final answer from what you have
+- Do not delegate trivially simple tasks — answer directly when you can
+- When specialists have responded, synthesize and end — do not re-delegate the same subtask
+
+Always explain your delegation strategy briefly before executing it.
+Be thorough but efficient."""
[[fallback_models]]
provider = "default"
model = "default"
api_key_env = "GROQ_API_KEY"
-[schedule]
-continuous = { check_interval_secs = 120 }
+# Caps tool/delegation rounds per message (orchestrator has no background schedule).
+[autonomous]
+max_iterations = 25
[resources]
max_llm_tokens_per_hour = 500000
+max_tool_calls_per_minute = 30
[capabilities]
-tools = ["agent_send", "agent_spawn", "agent_list", "agent_kill", "memory_store", "memory_recall", "file_read", "file_write"]
+tools = ["*"]
memory_read = ["*"]
memory_write = ["*"]
-agent_spawn = true
+agent_spawn = false
agent_message = ["*"]
+
+tool_blocklist = ["agent_spawn", "agent_kill"]
diff --git a/agents/personal-finance/agent.toml b/agents/personal-finance/agent.toml
index ebebdd10b8..c00bee3c5f 100644
--- a/agents/personal-finance/agent.toml
+++ b/agents/personal-finance/agent.toml
@@ -1,7 +1,7 @@
name = "personal-finance"
version = "0.1.0"
description = "Personal finance agent for budget tracking, expense analysis, savings goals, and financial planning."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["finance", "budget", "expenses", "savings", "planning", "money"]
@@ -10,7 +10,7 @@ provider = "default"
model = "default"
max_tokens = 8192
temperature = 0.2
-system_prompt = """You are Personal Finance, a specialist agent in the OpenFang Agent OS. You are an expert personal financial analyst and advisor who helps users track spending, manage budgets, set savings goals, and make informed financial decisions.
+system_prompt = """You are Personal Finance, a specialist agent in the OMTAE Agent OS. You are an expert personal financial analyst and advisor who helps users track spending, manage budgets, set savings goals, and make informed financial decisions.
CORE COMPETENCIES:
@@ -55,7 +55,7 @@ max_llm_tokens_per_hour = 150000
max_concurrent_tools = 5
[capabilities]
-tools = ["file_read", "file_write", "file_list", "memory_store", "memory_recall", "shell_exec"]
+tools = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
shell = ["python *"]
diff --git a/agents/planner/agent.toml b/agents/planner/agent.toml
index a28fadf704..1f3521b1a5 100644
--- a/agents/planner/agent.toml
+++ b/agents/planner/agent.toml
@@ -1,7 +1,7 @@
name = "planner"
version = "0.1.0"
description = "Project planner. Creates project plans, breaks down epics, estimates effort, identifies risks and dependencies."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
[model]
@@ -9,7 +9,7 @@ provider = "default"
model = "default"
max_tokens = 8192
temperature = 0.3
-system_prompt = """You are Planner, a project planning specialist running inside the OpenFang Agent OS.
+system_prompt = """You are Planner, a project planning specialist running inside the OMTAE Agent OS.
Your methodology:
1. SCOPE: Define what's in and out of scope
@@ -45,7 +45,7 @@ api_key_env = "GEMINI_API_KEY"
max_llm_tokens_per_hour = 200000
[capabilities]
-tools = ["file_read", "file_list", "memory_store", "memory_recall", "agent_send"]
+tools = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
agent_message = ["*"]
diff --git a/agents/recruiter/agent.toml b/agents/recruiter/agent.toml
index 3c13d5f148..689feb803b 100644
--- a/agents/recruiter/agent.toml
+++ b/agents/recruiter/agent.toml
@@ -1,7 +1,7 @@
name = "recruiter"
version = "0.1.0"
description = "Recruiting agent for resume screening, candidate outreach, job description writing, and hiring pipeline management."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["recruiting", "hiring", "resume", "outreach", "talent", "hr"]
@@ -10,7 +10,7 @@ provider = "default"
model = "default"
max_tokens = 4096
temperature = 0.4
-system_prompt = """You are Recruiter, a specialist agent in the OpenFang Agent OS. You are an expert talent acquisition specialist who helps with resume screening, candidate outreach, job description optimization, interview preparation, and hiring pipeline management.
+system_prompt = """You are Recruiter, a specialist agent in the OMTAE Agent OS. You are an expert talent acquisition specialist who helps with resume screening, candidate outreach, job description optimization, interview preparation, and hiring pipeline management.
CORE COMPETENCIES:
@@ -64,7 +64,7 @@ max_llm_tokens_per_hour = 150000
max_concurrent_tools = 5
[capabilities]
-tools = ["file_read", "file_write", "file_list", "memory_store", "memory_recall", "web_fetch"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
diff --git a/agents/researcher/agent.toml b/agents/researcher/agent.toml
index d53afa0e19..bb95280d7c 100644
--- a/agents/researcher/agent.toml
+++ b/agents/researcher/agent.toml
@@ -1,17 +1,41 @@
name = "researcher"
version = "0.1.0"
description = "Research agent. Fetches web content and synthesizes information."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
+# Keep history small so orchestrator agent_send delegations fit 16k local models.
+max_history_messages = 8
tags = ["research", "analysis", "web"]
+[exec_policy]
+mode = "full"
+shell_env_passthrough = ["VLLM_API_KEY", "GROQ_API_KEY", "GEMINI_API_KEY"]
+
[model]
provider = "default"
model = "default"
api_key_env = "GEMINI_API_KEY"
max_tokens = 4096
temperature = 0.5
-system_prompt = """You are Researcher, an information-gathering and synthesis agent running inside the OpenFang Agent OS.
+system_prompt = """You are Researcher, an information-gathering and synthesis agent running inside the OMTAE Agent OS.
+
+TOOLS: Use native function calls only (web_search, web_fetch, file_*, memory_*, shell_exec). NEVER output JSON tool syntax in plain text (e.g. {"name":"web_search",...} or {"name":"agent_send",...}).
+
+ACTION RULES (mandatory — never violate):
+- TOOL-FIRST: every factual claim (paths, URLs, status, lists, counts) requires tool output in this conversation. Runtime enforces TOOL_REQUIRED if you skip tools.
+- On "system check", "status", "health", or similar: your FIRST response MUST include a tool call — shell_exec (nvidia-smi, curl -s http://127.0.0.1:4200/api/health) OR file_list on the workspace root. No exceptions.
+- On Obsidian / Brain / vault questions: FIRST tool call MUST be shell_exec `curl -s -H "X-OMTAE-Pin: 839201" http://127.0.0.1:4200/api/brain/status` OR file_list on /home/jay/vaults/omtae-brain/leads — NOT peer-agent narration or "let me check" loops.
+- FORBIDDEN: "The user is asking about…" meta-narration, peer-agent speculation, or repeating planning sentences. Max ONE short planning line, then IMMEDIATE tool call in the same turn.
+- NEVER claim workspace, memory, disk, vault, peer agents, or service status without tool output from this conversation.
+
+RESEARCH INTEGRITY (mandatory — never violate):
+- Before listing businesses, people, products, or ranked recommendations, you MUST call web_search and/or web_fetch in this turn.
+- NEVER invent business names, addresses, phone numbers, or websites. Every named entity must come from tool output.
+- Each list item MUST include a real https:// URL copied from web_search or web_fetch results. Do not write "Website: " — only real URLs.
+- Do not repeat the same entity twice. Deduplicate by name and URL before responding.
+- Do NOT claim "Google Search Results" or cite search engines unless web_search actually returned those results in this conversation.
+- Do NOT write "Confidence Level: High" unless every listed fact has a cited https:// URL from tool output in the same response.
+- If web_search or web_fetch fails, returns no results, or you cannot verify a claim, say "could not verify" — never fabricate a top-N list to fill the gap.
RESEARCH METHODOLOGY:
1. DECOMPOSE — Break the research question into specific sub-questions.
@@ -28,12 +52,12 @@ SOURCE EVALUATION:
OUTPUT:
- Lead with the direct answer to the question.
-- Key Findings (numbered, with source attribution).
-- Sources Used (with URLs).
-- Confidence Level (high / medium / low) and why.
+- Key Findings (numbered, each with a real https:// URL from tool output).
+- Sources Used (full https:// URLs only — no placeholder names).
+- Confidence Level (high / medium / low) and why — high only when every claim has a cited URL from this turn's tools.
- Open Questions (what couldn't be determined).
-Always cite your sources. Never present uncertain information as fact."""
+Always cite your sources with real URLs. Never present uncertain or unverified information as fact."""
[[fallback_models]]
provider = "default"
@@ -44,7 +68,8 @@ api_key_env = "GROQ_API_KEY"
max_llm_tokens_per_hour = 150000
[capabilities]
-tools = ["web_search", "web_fetch", "file_read", "file_write", "file_list", "memory_store", "memory_recall"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
+shell = ["nvidia-smi", "curl *", "cat *", "ls *"]
diff --git a/agents/sales-assistant/agent.toml b/agents/sales-assistant/agent.toml
index cbbb625e15..c9ced1e255 100644
--- a/agents/sales-assistant/agent.toml
+++ b/agents/sales-assistant/agent.toml
@@ -1,7 +1,7 @@
name = "sales-assistant"
version = "0.1.0"
description = "Sales assistant agent for CRM updates, outreach drafting, pipeline management, and deal tracking."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["sales", "crm", "outreach", "pipeline", "prospecting", "deals"]
@@ -10,7 +10,7 @@ provider = "default"
model = "default"
max_tokens = 4096
temperature = 0.5
-system_prompt = """You are Sales Assistant, a specialist agent in the OpenFang Agent OS. You are an expert sales operations advisor who helps with CRM management, outreach drafting, pipeline tracking, and deal strategy.
+system_prompt = """You are Sales Assistant, a specialist agent in the OMTAE Agent OS. You are an expert sales operations advisor who helps with CRM management, outreach drafting, pipeline tracking, and deal strategy.
CORE COMPETENCIES:
@@ -63,7 +63,7 @@ max_llm_tokens_per_hour = 150000
max_concurrent_tools = 5
[capabilities]
-tools = ["file_read", "file_write", "file_list", "memory_store", "memory_recall", "web_fetch"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
diff --git a/agents/security-auditor/agent.toml b/agents/security-auditor/agent.toml
index 1aab5bc08b..7e1ce1f50f 100644
--- a/agents/security-auditor/agent.toml
+++ b/agents/security-auditor/agent.toml
@@ -1,7 +1,7 @@
name = "security-auditor"
version = "0.1.0"
description = "Security specialist. Reviews code for vulnerabilities, checks configurations, performs threat modeling."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["security", "audit", "vulnerability"]
@@ -11,7 +11,7 @@ model = "default"
api_key_env = "DEEPSEEK_API_KEY"
max_tokens = 4096
temperature = 0.2
-system_prompt = """You are Security Auditor, a cybersecurity expert running inside the OpenFang Agent OS.
+system_prompt = """You are Security Auditor, a cybersecurity expert running inside the OMTAE Agent OS.
Your focus areas:
- OWASP Top 10 vulnerabilities
@@ -48,7 +48,7 @@ proactive = { conditions = ["event:agent_spawned", "event:agent_terminated"] }
max_llm_tokens_per_hour = 150000
[capabilities]
-tools = ["file_read", "file_list", "shell_exec", "memory_store", "memory_recall"]
+tools = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
shell = ["cargo audit *", "cargo tree *", "git log *"]
diff --git a/agents/social-media/agent.toml b/agents/social-media/agent.toml
index ca3c1fc855..748a17a39b 100644
--- a/agents/social-media/agent.toml
+++ b/agents/social-media/agent.toml
@@ -1,7 +1,7 @@
name = "social-media"
version = "0.1.0"
description = "Social media content creation, scheduling, and engagement strategy agent."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["social-media", "content", "marketing", "engagement", "scheduling", "analytics"]
@@ -10,7 +10,7 @@ provider = "default"
model = "default"
max_tokens = 4096
temperature = 0.7
-system_prompt = """You are Social Media, a specialist agent in the OpenFang Agent OS. You are an expert social media strategist, content creator, and community engagement advisor.
+system_prompt = """You are Social Media, a specialist agent in the OMTAE Agent OS. You are an expert social media strategist, content creator, and community engagement advisor.
CORE COMPETENCIES:
@@ -59,7 +59,7 @@ max_llm_tokens_per_hour = 120000
max_concurrent_tools = 5
[capabilities]
-tools = ["file_read", "file_write", "file_list", "memory_store", "memory_recall", "web_fetch"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
diff --git a/agents/test-engineer/agent.toml b/agents/test-engineer/agent.toml
index eca2e025c3..363ee25e6e 100644
--- a/agents/test-engineer/agent.toml
+++ b/agents/test-engineer/agent.toml
@@ -1,7 +1,7 @@
name = "test-engineer"
version = "0.1.0"
description = "Quality assurance engineer. Designs test strategies, writes tests, validates correctness."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["testing", "qa", "validation"]
@@ -11,7 +11,7 @@ model = "default"
api_key_env = "GEMINI_API_KEY"
max_tokens = 4096
temperature = 0.3
-system_prompt = """You are Test Engineer, a QA specialist running inside the OpenFang Agent OS.
+system_prompt = """You are Test Engineer, a QA specialist running inside the OMTAE Agent OS.
Your testing philosophy:
- Tests document behavior, not implementation
@@ -47,7 +47,7 @@ api_key_env = "GROQ_API_KEY"
max_llm_tokens_per_hour = 150000
[capabilities]
-tools = ["file_read", "file_write", "file_list", "shell_exec", "memory_store", "memory_recall"]
+tools = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
shell = ["cargo test *", "cargo check *"]
diff --git a/agents/translator/agent.toml b/agents/translator/agent.toml
index a87b24b2c2..0507d22abf 100644
--- a/agents/translator/agent.toml
+++ b/agents/translator/agent.toml
@@ -1,7 +1,7 @@
name = "translator"
version = "0.1.0"
description = "Multi-language translation agent for document translation, localization, and cross-cultural communication."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["translation", "languages", "localization", "multilingual", "communication", "i18n"]
@@ -10,7 +10,7 @@ provider = "default"
model = "default"
max_tokens = 8192
temperature = 0.3
-system_prompt = """You are Translator, a specialist agent in the OpenFang Agent OS. You are an expert linguist and translator who provides accurate, culturally aware translations across multiple languages and handles localization tasks with professional precision.
+system_prompt = """You are Translator, a specialist agent in the OMTAE Agent OS. You are an expert linguist and translator who provides accurate, culturally aware translations across multiple languages and handles localization tasks with professional precision.
CORE COMPETENCIES:
@@ -59,7 +59,7 @@ max_llm_tokens_per_hour = 200000
max_concurrent_tools = 5
[capabilities]
-tools = ["file_read", "file_write", "file_list", "memory_store", "memory_recall", "web_fetch"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
diff --git a/agents/travel-planner/agent.toml b/agents/travel-planner/agent.toml
index 189ed8b9f9..fef7e87268 100644
--- a/agents/travel-planner/agent.toml
+++ b/agents/travel-planner/agent.toml
@@ -1,7 +1,7 @@
name = "travel-planner"
version = "0.1.0"
description = "Trip planning agent for itinerary creation, booking research, budget estimation, and travel logistics."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["travel", "planning", "itinerary", "booking", "logistics", "vacation"]
@@ -10,7 +10,7 @@ provider = "default"
model = "default"
max_tokens = 8192
temperature = 0.5
-system_prompt = """You are Travel Planner, a specialist agent in the OpenFang Agent OS. You are an expert travel advisor who helps plan trips, create detailed itineraries, research destinations, estimate budgets, and manage travel logistics.
+system_prompt = """You are Travel Planner, a specialist agent in the OMTAE Agent OS. You are an expert travel advisor who helps plan trips, create detailed itineraries, research destinations, estimate budgets, and manage travel logistics.
CORE COMPETENCIES:
@@ -59,7 +59,7 @@ max_llm_tokens_per_hour = 150000
max_concurrent_tools = 5
[capabilities]
-tools = ["file_read", "file_write", "file_list", "memory_store", "memory_recall", "web_search", "web_fetch", "browser_navigate", "browser_click", "browser_type", "browser_read_page", "browser_screenshot", "browser_close"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
diff --git a/agents/tutor/agent.toml b/agents/tutor/agent.toml
index 195be81c9b..ee5984a3cc 100644
--- a/agents/tutor/agent.toml
+++ b/agents/tutor/agent.toml
@@ -1,7 +1,7 @@
name = "tutor"
version = "0.1.0"
description = "Teaching and explanation agent for learning, tutoring, and educational content creation."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
tags = ["education", "teaching", "tutoring", "learning", "explanation", "knowledge"]
@@ -10,7 +10,7 @@ provider = "default"
model = "default"
max_tokens = 8192
temperature = 0.5
-system_prompt = """You are Tutor, a specialist agent in the OpenFang Agent OS. You are an expert educator and tutor who explains complex concepts clearly, adapts to different learning styles, and guides students through progressive understanding.
+system_prompt = """You are Tutor, a specialist agent in the OMTAE Agent OS. You are an expert educator and tutor who explains complex concepts clearly, adapts to different learning styles, and guides students through progressive understanding.
CORE COMPETENCIES:
@@ -60,7 +60,7 @@ max_llm_tokens_per_hour = 200000
max_concurrent_tools = 5
[capabilities]
-tools = ["file_read", "file_write", "file_list", "memory_store", "memory_recall", "shell_exec", "web_fetch"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*", "shared.*"]
diff --git a/agents/writer/agent.toml b/agents/writer/agent.toml
index 5c5ada5604..9e262e8ab9 100644
--- a/agents/writer/agent.toml
+++ b/agents/writer/agent.toml
@@ -1,7 +1,7 @@
name = "writer"
version = "0.1.0"
description = "Content writer. Creates documentation, articles, and technical writing."
-author = "openfang"
+author = "omtae"
module = "builtin:chat"
[model]
@@ -9,7 +9,7 @@ provider = "default"
model = "default"
max_tokens = 4096
temperature = 0.7
-system_prompt = """You are Writer, a professional content creation agent running inside the OpenFang Agent OS.
+system_prompt = """You are Writer, a professional content creation agent running inside the OMTAE Agent OS.
WRITING METHODOLOGY:
1. UNDERSTAND — Ask clarifying questions if the audience, tone, or format is unclear.
@@ -38,7 +38,7 @@ api_key_env = "GEMINI_API_KEY"
max_llm_tokens_per_hour = 100000
[capabilities]
-tools = ["file_read", "file_write", "file_list", "web_search", "web_fetch", "memory_store", "memory_recall"]
+tools = ["*"]
network = ["*"]
memory_read = ["*"]
memory_write = ["self.*"]
diff --git a/crates/omtae-api b/crates/omtae-api
new file mode 120000
index 0000000000..83e647b9c1
--- /dev/null
+++ b/crates/omtae-api
@@ -0,0 +1 @@
+openfang-api
\ No newline at end of file
diff --git a/crates/omtae-channels b/crates/omtae-channels
new file mode 120000
index 0000000000..6270d1fcb1
--- /dev/null
+++ b/crates/omtae-channels
@@ -0,0 +1 @@
+openfang-channels
\ No newline at end of file
diff --git a/crates/omtae-cli b/crates/omtae-cli
new file mode 120000
index 0000000000..46c38bf3f4
--- /dev/null
+++ b/crates/omtae-cli
@@ -0,0 +1 @@
+openfang-cli
\ No newline at end of file
diff --git a/crates/omtae-desktop b/crates/omtae-desktop
new file mode 120000
index 0000000000..1fde3f0e48
--- /dev/null
+++ b/crates/omtae-desktop
@@ -0,0 +1 @@
+openfang-desktop
\ No newline at end of file
diff --git a/crates/omtae-extensions b/crates/omtae-extensions
new file mode 120000
index 0000000000..009a06df73
--- /dev/null
+++ b/crates/omtae-extensions
@@ -0,0 +1 @@
+openfang-extensions
\ No newline at end of file
diff --git a/crates/omtae-hands b/crates/omtae-hands
new file mode 120000
index 0000000000..5644070f5b
--- /dev/null
+++ b/crates/omtae-hands
@@ -0,0 +1 @@
+openfang-hands
\ No newline at end of file
diff --git a/crates/omtae-kernel b/crates/omtae-kernel
new file mode 120000
index 0000000000..bf3f5257ba
--- /dev/null
+++ b/crates/omtae-kernel
@@ -0,0 +1 @@
+openfang-kernel
\ No newline at end of file
diff --git a/crates/omtae-memory b/crates/omtae-memory
new file mode 120000
index 0000000000..9ecffdae40
--- /dev/null
+++ b/crates/omtae-memory
@@ -0,0 +1 @@
+openfang-memory
\ No newline at end of file
diff --git a/crates/omtae-migrate b/crates/omtae-migrate
new file mode 120000
index 0000000000..0e5cbd978f
--- /dev/null
+++ b/crates/omtae-migrate
@@ -0,0 +1 @@
+openfang-migrate
\ No newline at end of file
diff --git a/crates/omtae-runtime b/crates/omtae-runtime
new file mode 120000
index 0000000000..d6dd20e419
--- /dev/null
+++ b/crates/omtae-runtime
@@ -0,0 +1 @@
+openfang-runtime
\ No newline at end of file
diff --git a/crates/omtae-skills b/crates/omtae-skills
new file mode 120000
index 0000000000..6d3662779b
--- /dev/null
+++ b/crates/omtae-skills
@@ -0,0 +1 @@
+openfang-skills
\ No newline at end of file
diff --git a/crates/omtae-types b/crates/omtae-types
new file mode 120000
index 0000000000..bea03feb12
--- /dev/null
+++ b/crates/omtae-types
@@ -0,0 +1 @@
+openfang-types
\ No newline at end of file
diff --git a/crates/omtae-wire b/crates/omtae-wire
new file mode 120000
index 0000000000..c0bcdce632
--- /dev/null
+++ b/crates/omtae-wire
@@ -0,0 +1 @@
+openfang-wire
\ No newline at end of file
diff --git a/crates/openfang-api/Cargo.toml b/crates/openfang-api/Cargo.toml
index fcc3e0088c..50c7a0fea8 100644
--- a/crates/openfang-api/Cargo.toml
+++ b/crates/openfang-api/Cargo.toml
@@ -1,21 +1,21 @@
[package]
-name = "openfang-api"
+name = "omtae-api"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "HTTP/WebSocket API server for the OpenFang Agent OS daemon"
+description = "HTTP/WebSocket API server for the OMTAE Agent OS daemon"
[dependencies]
-openfang-types = { path = "../openfang-types" }
-openfang-kernel = { path = "../openfang-kernel" }
-openfang-runtime = { path = "../openfang-runtime" }
-openfang-memory = { path = "../openfang-memory" }
-openfang-channels = { path = "../openfang-channels" }
-openfang-wire = { path = "../openfang-wire" }
-openfang-skills = { path = "../openfang-skills" }
-openfang-hands = { path = "../openfang-hands" }
-openfang-extensions = { path = "../openfang-extensions" }
-openfang-migrate = { path = "../openfang-migrate" }
+omtae-types = { path = "../omtae-types" }
+omtae-kernel = { path = "../omtae-kernel" }
+omtae-runtime = { path = "../omtae-runtime" }
+omtae-memory = { path = "../omtae-memory" }
+omtae-channels = { path = "../omtae-channels" }
+omtae-wire = { path = "../omtae-wire" }
+omtae-skills = { path = "../omtae-skills" }
+omtae-hands = { path = "../omtae-hands" }
+omtae-extensions = { path = "../omtae-extensions" }
+omtae-migrate = { path = "../omtae-migrate" }
dashmap = { workspace = true }
tokio = { workspace = true }
serde = { workspace = true }
diff --git a/crates/openfang-api/src/brain.rs b/crates/openfang-api/src/brain.rs
new file mode 100644
index 0000000000..11ee056c5e
--- /dev/null
+++ b/crates/openfang-api/src/brain.rs
@@ -0,0 +1,415 @@
+//! Obsidian brain vault — list/read/write markdown files under a configured root.
+
+use axum::extract::{Query, State};
+use axum::http::StatusCode;
+use axum::response::IntoResponse;
+use axum::Json;
+use omtae_types::config::BrainConfig;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+use std::path::{Component, Path, PathBuf};
+use std::sync::Arc;
+use std::time::SystemTime;
+
+use crate::routes::AppState;
+
+const MAX_READ_BYTES: u64 = 2 * 1024 * 1024;
+const MAX_WRITE_BYTES: usize = 512 * 1024;
+const RECENT_LEADS_LIMIT: usize = 20;
+
+#[derive(Debug, Clone, Serialize)]
+pub struct BrainEntry {
+ pub name: String,
+ pub path: String,
+ pub kind: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub size: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub modified: Option,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct BrainListResponse {
+ pub enabled: bool,
+ pub vault_path: String,
+ pub vault_name: String,
+ pub path: String,
+ pub entries: Vec,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub recent_leads: Vec,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct BrainFileResponse {
+ pub path: String,
+ pub content: String,
+ pub size: u64,
+ pub modified: Option,
+ pub obsidian_uri: String,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct BrainPathQuery {
+ #[serde(default)]
+ pub path: String,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct BrainWriteRequest {
+ pub path: String,
+ pub content: String,
+}
+
+fn system_time_to_rfc3339(time: SystemTime) -> Option {
+ use chrono::{DateTime, Utc};
+ Some(DateTime::::from(time).to_rfc3339())
+}
+
+fn vault_name_from_path(root: &Path) -> String {
+ root.file_name()
+ .and_then(|s| s.to_str())
+ .unwrap_or("omtae-brain")
+ .to_string()
+}
+
+fn percent_encode_uri(s: &str) -> String {
+ let mut out = String::with_capacity(s.len());
+ for b in s.bytes() {
+ match b {
+ b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'/' => {
+ out.push(b as char);
+ }
+ _ => out.push_str(&format!("%{:02X}", b)),
+ }
+ }
+ out
+}
+
+fn obsidian_uri(vault_name: &str, rel_path: &str) -> String {
+ format!(
+ "obsidian://open?vault={}&file={}",
+ percent_encode_uri(vault_name),
+ percent_encode_uri(rel_path)
+ )
+}
+
+/// Resolve and canonicalize the configured vault root.
+pub fn resolve_vault_root(brain: &BrainConfig) -> Result {
+ let raw = brain
+ .path
+ .clone()
+ .unwrap_or_else(omtae_types::config::default_brain_vault_path);
+ if !raw.exists() {
+ std::fs::create_dir_all(&raw).map_err(|e| format!("create vault dir: {e}"))?;
+ }
+ raw.canonicalize()
+ .map_err(|e| format!("resolve vault path {}: {e}", raw.display()))
+}
+
+/// Join a relative vault path and ensure it stays under the root.
+pub fn resolve_safe_path(root: &Path, rel: &str) -> Result {
+ let trimmed = rel.trim().trim_start_matches('/');
+ if trimmed.is_empty() {
+ return Ok(root.to_path_buf());
+ }
+ for component in Path::new(trimmed).components() {
+ match component {
+ Component::Normal(_) => {}
+ Component::CurDir => {}
+ _ => return Err("path traversal not allowed".to_string()),
+ }
+ }
+ let joined = root.join(trimmed);
+ let canonical = if joined.exists() {
+ joined
+ .canonicalize()
+ .map_err(|e| format!("resolve path: {e}"))?
+ } else {
+ joined
+ .parent()
+ .and_then(|p| p.canonicalize().ok())
+ .map(|p| p.join(joined.file_name().unwrap_or_default()))
+ .ok_or_else(|| "invalid path".to_string())?
+ };
+ if !canonical.starts_with(root) {
+ return Err("path escapes vault root".to_string());
+ }
+ Ok(canonical)
+}
+
+fn rel_path(root: &Path, abs: &Path) -> String {
+ abs.strip_prefix(root)
+ .unwrap_or(abs)
+ .to_string_lossy()
+ .replace('\\', "/")
+}
+
+fn should_skip_name(name: &str) -> bool {
+ name.starts_with('.') || name == "node_modules"
+}
+
+fn entry_from_path(root: &Path, abs: &Path, kind: &str) -> BrainEntry {
+ let meta = std::fs::metadata(abs).ok();
+ BrainEntry {
+ name: abs
+ .file_name()
+ .and_then(|s| s.to_str())
+ .unwrap_or("")
+ .to_string(),
+ path: rel_path(root, abs),
+ kind: kind.to_string(),
+ size: meta.as_ref().map(|m| m.len()),
+ modified: meta
+ .and_then(|m| m.modified().ok())
+ .and_then(system_time_to_rfc3339),
+ }
+}
+
+fn list_directory(root: &Path, rel: &str) -> Result, String> {
+ let dir = resolve_safe_path(root, rel)?;
+ if !dir.is_dir() {
+ return Err("not a directory".to_string());
+ }
+ let mut entries = Vec::new();
+ for item in std::fs::read_dir(&dir).map_err(|e| format!("read dir: {e}"))? {
+ let item = item.map_err(|e| format!("read entry: {e}"))?;
+ let name = item.file_name().to_string_lossy().to_string();
+ if should_skip_name(&name) {
+ continue;
+ }
+ let path = item.path();
+ let kind = if path.is_dir() { "dir" } else { "file" };
+ entries.push(entry_from_path(root, &path, kind));
+ }
+ entries.sort_by(|a, b| {
+ match (a.kind.as_str(), b.kind.as_str()) {
+ ("dir", "file") => std::cmp::Ordering::Less,
+ ("file", "dir") => std::cmp::Ordering::Greater,
+ _ => a.name.to_lowercase().cmp(&b.name.to_lowercase()),
+ }
+ });
+ Ok(entries)
+}
+
+fn recent_leads(root: &Path) -> Vec {
+ let leads_dir = root.join("leads");
+ if !leads_dir.is_dir() {
+ return Vec::new();
+ }
+ let mut items: Vec<(SystemTime, BrainEntry)> = Vec::new();
+ let Ok(read_dir) = std::fs::read_dir(&leads_dir) else {
+ return Vec::new();
+ };
+ for item in read_dir.flatten() {
+ let path = item.path();
+ if path.is_dir() {
+ if let Ok(sub) = std::fs::read_dir(&path) {
+ for sub_item in sub.flatten() {
+ let sub_path = sub_item.path();
+ if sub_path.is_file() && sub_path.extension().is_some_and(|e| e == "md") {
+ if let Ok(meta) = sub_path.metadata() {
+ if let Ok(modified) = meta.modified() {
+ items.push((modified, entry_from_path(root, &sub_path, "file")));
+ }
+ }
+ }
+ }
+ }
+ continue;
+ }
+ if path.extension().is_some_and(|e| e == "md") {
+ if let Ok(meta) = path.metadata() {
+ if let Ok(modified) = meta.modified() {
+ items.push((modified, entry_from_path(root, &path, "file")));
+ }
+ }
+ }
+ }
+ items.sort_by(|a, b| b.0.cmp(&a.0));
+ items
+ .into_iter()
+ .take(RECENT_LEADS_LIMIT)
+ .map(|(_, entry)| entry)
+ .collect()
+}
+
+fn read_text_file(root: &Path, rel: &str, vault_name: &str) -> Result {
+ let abs = resolve_safe_path(root, rel)?;
+ if !abs.is_file() {
+ return Err("not a file".to_string());
+ }
+ let meta = std::fs::metadata(&abs).map_err(|e| format!("stat file: {e}"))?;
+ if meta.len() > MAX_READ_BYTES {
+ return Err(format!("file too large (max {} bytes)", MAX_READ_BYTES));
+ }
+ let content = std::fs::read_to_string(&abs).map_err(|e| format!("read file: {e}"))?;
+ let rel_s = rel_path(root, &abs);
+ Ok(BrainFileResponse {
+ path: rel_s.clone(),
+ size: meta.len(),
+ modified: meta.modified().ok().and_then(system_time_to_rfc3339),
+ obsidian_uri: obsidian_uri(vault_name, &rel_s),
+ content,
+ })
+}
+
+fn write_text_file(root: &Path, rel: &str, content: &str) -> Result {
+ if content.len() > MAX_WRITE_BYTES {
+ return Err(format!("content too large (max {} bytes)", MAX_WRITE_BYTES));
+ }
+ let abs = resolve_safe_path(root, rel)?;
+ if let Some(parent) = abs.parent() {
+ std::fs::create_dir_all(parent).map_err(|e| format!("create parent: {e}"))?;
+ }
+ let tmp = abs.with_extension("tmp");
+ std::fs::write(&tmp, content.as_bytes()).map_err(|e| format!("write temp: {e}"))?;
+ std::fs::rename(&tmp, &abs).map_err(|e| format!("commit write: {e}"))?;
+ let vault_name = vault_name_from_path(root);
+ read_text_file(root, rel, &vault_name)
+}
+
+fn brain_disabled() -> (StatusCode, Json) {
+ (
+ StatusCode::SERVICE_UNAVAILABLE,
+ Json(serde_json::json!({ "error": "brain vault disabled in config" })),
+ )
+}
+
+fn brain_error(status: StatusCode, msg: &str) -> (StatusCode, Json) {
+ (status, Json(serde_json::json!({ "error": msg })))
+}
+
+/// GET /api/brain/list — List vault directory entries.
+pub async fn brain_list(
+ State(state): State>,
+ Query(query): Query,
+) -> impl IntoResponse {
+ let brain = &state.kernel.config.brain;
+ if !brain.enabled {
+ return brain_disabled().into_response();
+ }
+ let root = match resolve_vault_root(brain) {
+ Ok(r) => r,
+ Err(e) => return brain_error(StatusCode::INTERNAL_SERVER_ERROR, &e).into_response(),
+ };
+ let vault_name = vault_name_from_path(&root);
+ let rel = if query.path.is_empty() { "" } else { &query.path };
+ let entries = match list_directory(&root, rel) {
+ Ok(e) => e,
+ Err(e) => return brain_error(StatusCode::BAD_REQUEST, &e).into_response(),
+ };
+ let recent = if rel.is_empty() {
+ recent_leads(&root)
+ } else {
+ Vec::new()
+ };
+ Json(BrainListResponse {
+ enabled: true,
+ vault_path: root.display().to_string(),
+ vault_name,
+ path: rel.to_string(),
+ entries,
+ recent_leads: recent,
+ })
+ .into_response()
+}
+
+/// GET /api/brain/file — Read a vault file.
+pub async fn brain_file(
+ State(state): State>,
+ Query(query): Query,
+) -> impl IntoResponse {
+ let brain = &state.kernel.config.brain;
+ if !brain.enabled {
+ return brain_disabled().into_response();
+ }
+ if query.path.trim().is_empty() {
+ return brain_error(StatusCode::BAD_REQUEST, "path required").into_response();
+ }
+ let root = match resolve_vault_root(brain) {
+ Ok(r) => r,
+ Err(e) => return brain_error(StatusCode::INTERNAL_SERVER_ERROR, &e).into_response(),
+ };
+ let vault_name = vault_name_from_path(&root);
+ match read_text_file(&root, &query.path, &vault_name) {
+ Ok(resp) => Json(resp).into_response(),
+ Err(e) => brain_error(StatusCode::BAD_REQUEST, &e).into_response(),
+ }
+}
+
+/// POST /api/brain/file — Write a vault file.
+pub async fn brain_write(
+ State(state): State>,
+ Json(body): Json,
+) -> impl IntoResponse {
+ let brain = &state.kernel.config.brain;
+ if !brain.enabled {
+ return brain_disabled().into_response();
+ }
+ if body.path.trim().is_empty() {
+ return brain_error(StatusCode::BAD_REQUEST, "path required").into_response();
+ }
+ let root = match resolve_vault_root(brain) {
+ Ok(r) => r,
+ Err(e) => return brain_error(StatusCode::INTERNAL_SERVER_ERROR, &e).into_response(),
+ };
+ match write_text_file(&root, &body.path, &body.content) {
+ Ok(resp) => Json(resp).into_response(),
+ Err(e) => brain_error(StatusCode::BAD_REQUEST, &e).into_response(),
+ }
+}
+
+/// GET /api/brain/status — Vault metadata for dashboard header.
+pub async fn brain_status(State(state): State>) -> impl IntoResponse {
+ let brain = &state.kernel.config.brain;
+ let path = state.kernel.config.brain_vault_path();
+ let exists = path.exists();
+ let mut counts: HashMap = HashMap::new();
+ if exists {
+ if let Ok(root) = resolve_vault_root(brain) {
+ for top in ["leads", "genetics", "history", "refining"] {
+ let dir = root.join(top);
+ if dir.is_dir() {
+ if let Ok(n) = dir.read_dir().map(|d| d.count() as u64) {
+ counts.insert(top.to_string(), n);
+ }
+ }
+ }
+ }
+ }
+ Json(serde_json::json!({
+ "enabled": brain.enabled,
+ "vault_path": path.display().to_string(),
+ "vault_name": path.file_name().and_then(|s| s.to_str()).unwrap_or("omtae-brain"),
+ "exists": exists,
+ "counts": counts,
+ }))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::fs;
+
+ #[test]
+ fn safe_path_blocks_traversal() {
+ let tmp = tempfile::tempdir().unwrap();
+ let root = tmp.path().canonicalize().unwrap();
+ fs::write(root.join("note.md"), "# hi").unwrap();
+ assert!(resolve_safe_path(&root, "../etc/passwd").is_err());
+ assert!(resolve_safe_path(&root, "note.md").is_ok());
+ }
+
+ #[test]
+ fn list_and_read_roundtrip() {
+ let tmp = tempfile::tempdir().unwrap();
+ let root = tmp.path().canonicalize().unwrap();
+ fs::create_dir_all(root.join("leads")).unwrap();
+ fs::write(root.join("leads/a.md"), "# A").unwrap();
+ let entries = list_directory(&root, "leads").unwrap();
+ assert_eq!(entries.len(), 1);
+ let file = read_text_file(&root, "leads/a.md", "test-vault").unwrap();
+ assert!(file.content.contains("# A"));
+ assert!(file.obsidian_uri.contains("obsidian://open"));
+ }
+}
diff --git a/crates/openfang-api/src/channel_bridge.rs b/crates/openfang-api/src/channel_bridge.rs
index 37ad72921f..712e79416e 100644
--- a/crates/openfang-api/src/channel_bridge.rs
+++ b/crates/openfang-api/src/channel_bridge.rs
@@ -1,71 +1,71 @@
-//! Channel bridge wiring — connects the OpenFang kernel to channel adapters.
+//! Channel bridge wiring — connects the OMTAE kernel to channel adapters.
//!
-//! Implements `ChannelBridgeHandle` on `OpenFangKernel` and provides the
+//! Implements `ChannelBridgeHandle` on `OMTAEKernel` and provides the
//! `start_channel_bridge()` entry point called by the daemon.
-use openfang_channels::bridge::{BridgeManager, ChannelBridgeHandle};
-use openfang_channels::discord::DiscordAdapter;
-use openfang_channels::email::EmailAdapter;
-use openfang_channels::google_chat::GoogleChatAdapter;
-use openfang_channels::irc::IrcAdapter;
-use openfang_channels::matrix::MatrixAdapter;
-use openfang_channels::mattermost::MattermostAdapter;
-use openfang_channels::rocketchat::RocketChatAdapter;
-use openfang_channels::router::AgentRouter;
-use openfang_channels::signal::SignalAdapter;
-use openfang_channels::slack::SlackAdapter;
-use openfang_channels::teams::TeamsAdapter;
-use openfang_channels::telegram::TelegramAdapter;
-use openfang_channels::twitch::TwitchAdapter;
-use openfang_channels::types::ChannelAdapter;
-use openfang_channels::whatsapp::WhatsAppAdapter;
-use openfang_channels::xmpp::XmppAdapter;
-use openfang_channels::zulip::ZulipAdapter;
+use omtae_channels::bridge::{BridgeManager, ChannelBridgeHandle};
+use omtae_channels::discord::DiscordAdapter;
+use omtae_channels::email::EmailAdapter;
+use omtae_channels::google_chat::GoogleChatAdapter;
+use omtae_channels::irc::IrcAdapter;
+use omtae_channels::matrix::MatrixAdapter;
+use omtae_channels::mattermost::MattermostAdapter;
+use omtae_channels::rocketchat::RocketChatAdapter;
+use omtae_channels::router::AgentRouter;
+use omtae_channels::signal::SignalAdapter;
+use omtae_channels::slack::SlackAdapter;
+use omtae_channels::teams::TeamsAdapter;
+use omtae_channels::telegram::TelegramAdapter;
+use omtae_channels::twitch::TwitchAdapter;
+use omtae_channels::types::ChannelAdapter;
+use omtae_channels::whatsapp::WhatsAppAdapter;
+use omtae_channels::xmpp::XmppAdapter;
+use omtae_channels::zulip::ZulipAdapter;
// Wave 3
-use openfang_channels::bluesky::BlueskyAdapter;
-use openfang_channels::feishu::FeishuAdapter;
-use openfang_channels::line::LineAdapter;
-use openfang_channels::mastodon::MastodonAdapter;
-use openfang_channels::messenger::MessengerAdapter;
-use openfang_channels::reddit::RedditAdapter;
-use openfang_channels::revolt::RevoltAdapter;
-use openfang_channels::viber::ViberAdapter;
-use openfang_types::config::FeishuMode;
+use omtae_channels::bluesky::BlueskyAdapter;
+use omtae_channels::feishu::FeishuAdapter;
+use omtae_channels::line::LineAdapter;
+use omtae_channels::mastodon::MastodonAdapter;
+use omtae_channels::messenger::MessengerAdapter;
+use omtae_channels::reddit::RedditAdapter;
+use omtae_channels::revolt::RevoltAdapter;
+use omtae_channels::viber::ViberAdapter;
+use omtae_types::config::FeishuMode;
// Wave 4
-use openfang_channels::flock::FlockAdapter;
-use openfang_channels::guilded::GuildedAdapter;
-use openfang_channels::keybase::KeybaseAdapter;
-use openfang_channels::nextcloud::NextcloudAdapter;
-use openfang_channels::nostr::NostrAdapter;
-use openfang_channels::pumble::PumbleAdapter;
-use openfang_channels::threema::ThreemaAdapter;
-use openfang_channels::twist::TwistAdapter;
-use openfang_channels::webex::WebexAdapter;
+use omtae_channels::flock::FlockAdapter;
+use omtae_channels::guilded::GuildedAdapter;
+use omtae_channels::keybase::KeybaseAdapter;
+use omtae_channels::nextcloud::NextcloudAdapter;
+use omtae_channels::nostr::NostrAdapter;
+use omtae_channels::pumble::PumbleAdapter;
+use omtae_channels::threema::ThreemaAdapter;
+use omtae_channels::twist::TwistAdapter;
+use omtae_channels::webex::WebexAdapter;
// Wave 5
use async_trait::async_trait;
-use openfang_channels::dingtalk::DingTalkAdapter;
-use openfang_channels::dingtalk_stream::DingTalkStreamAdapter;
-use openfang_channels::discourse::DiscourseAdapter;
-use openfang_channels::gitter::GitterAdapter;
-use openfang_channels::gotify::GotifyAdapter;
-use openfang_channels::linkedin::LinkedInAdapter;
-use openfang_channels::mqtt::MqttAdapter;
-use openfang_channels::mumble::MumbleAdapter;
-use openfang_channels::ntfy::NtfyAdapter;
-use openfang_channels::webhook::WebhookAdapter;
-use openfang_channels::wecom::WeComAdapter;
-use openfang_kernel::OpenFangKernel;
-use openfang_runtime::kernel_handle::KernelHandle;
-use openfang_types::agent::AgentId;
+use omtae_channels::dingtalk::DingTalkAdapter;
+use omtae_channels::dingtalk_stream::DingTalkStreamAdapter;
+use omtae_channels::discourse::DiscourseAdapter;
+use omtae_channels::gitter::GitterAdapter;
+use omtae_channels::gotify::GotifyAdapter;
+use omtae_channels::linkedin::LinkedInAdapter;
+use omtae_channels::mqtt::MqttAdapter;
+use omtae_channels::mumble::MumbleAdapter;
+use omtae_channels::ntfy::NtfyAdapter;
+use omtae_channels::webhook::WebhookAdapter;
+use omtae_channels::wecom::WeComAdapter;
+use omtae_kernel::OMTAEKernel;
+use omtae_runtime::kernel_handle::KernelHandle;
+use omtae_types::agent::AgentId;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tracing::{error, info, warn};
-use openfang_runtime::str_utils::safe_truncate_str;
+use omtae_runtime::str_utils::safe_truncate_str;
-/// Wraps `OpenFangKernel` to implement `ChannelBridgeHandle`.
+/// Wraps `OMTAEKernel` to implement `ChannelBridgeHandle`.
pub struct KernelBridgeAdapter {
- kernel: Arc,
+ kernel: Arc,
started_at: Instant,
}
@@ -87,13 +87,13 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
async fn send_message_with_blocks(
&self,
agent_id: AgentId,
- blocks: Vec,
+ blocks: Vec,
) -> Result {
// Extract text for the message parameter (used for memory recall / logging)
let text: String = blocks
.iter()
.filter_map(|b| match b {
- openfang_types::message::ContentBlock::Text { text, .. } => Some(text.as_str()),
+ omtae_types::message::ContentBlock::Text { text, .. } => Some(text.as_str()),
_ => None,
})
.collect::>()
@@ -126,7 +126,7 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
}
async fn spawn_agent_by_name(&self, manifest_name: &str) -> Result {
- // Look for manifest at ~/.openfang/agents/{name}/agent.toml
+ // Look for manifest at ~/.omtae/agents/{name}/agent.toml
let manifest_path = self
.kernel
.config
@@ -142,7 +142,7 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
let contents = std::fs::read_to_string(&manifest_path)
.map_err(|e| format!("Failed to read manifest: {e}"))?;
- let manifest: openfang_types::agent::AgentManifest =
+ let manifest: omtae_types::agent::AgentManifest =
toml::from_str(&contents).map_err(|e| format!("Invalid manifest TOML: {e}"))?;
let agent_id = self
@@ -161,14 +161,14 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
let mins = (secs % 3600) / 60;
if hours > 0 {
format!(
- "OpenFang status: {}h {}m uptime, {} agent(s)",
+ "OMTAE status: {}h {}m uptime, {} agent(s)",
hours,
mins,
agents.len()
)
} else {
format!(
- "OpenFang status: {}m uptime, {} agent(s)",
+ "OMTAE status: {}m uptime, {} agent(s)",
mins,
agents.len()
)
@@ -189,7 +189,7 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
// Group by provider
let mut by_provider: std::collections::HashMap<
&str,
- Vec<&openfang_types::model_catalog::ModelCatalogEntry>,
+ Vec<&omtae_types::model_catalog::ModelCatalogEntry>,
> = std::collections::HashMap::new();
for m in &available {
by_provider.entry(m.provider.as_str()).or_default().push(m);
@@ -226,9 +226,9 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
let mut msg = "Providers:\n".to_string();
for p in catalog.list_providers() {
let status = match p.auth_status {
- openfang_types::model_catalog::AuthStatus::Configured => "configured",
- openfang_types::model_catalog::AuthStatus::Missing => "not configured",
- openfang_types::model_catalog::AuthStatus::NotRequired => "local (no key needed)",
+ omtae_types::model_catalog::AuthStatus::Configured => "configured",
+ omtae_types::model_catalog::AuthStatus::Missing => "not configured",
+ omtae_types::model_catalog::AuthStatus::NotRequired => "local (no key needed)",
};
msg.push_str(&format!(
" {} — {} [{}, {} model(s)]\n",
@@ -246,7 +246,7 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
.unwrap_or_else(|e| e.into_inner());
let skills = skills.list();
if skills.is_empty() {
- return "No skills installed. Place skills in ~/.openfang/skills/ or install from the marketplace.".to_string();
+ return "No skills installed. Place skills in ~/.omtae/skills/ or install from the marketplace.".to_string();
}
let mut msg = format!("Installed skills ({}):\n", skills.len());
for skill in &skills {
@@ -342,12 +342,12 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
.execute_run(
run_id,
|step_agent| match step_agent {
- openfang_kernel::workflow::StepAgent::ById { id } => {
+ omtae_kernel::workflow::StepAgent::ById { id } => {
let aid: AgentId = id.parse().ok()?;
let entry = registry_ref.get(aid)?;
Some((aid, entry.name.clone()))
}
- openfang_kernel::workflow::StepAgent::ByName { name } => {
+ omtae_kernel::workflow::StepAgent::ByName { name } => {
let entry = registry_ref.find_by_name(name)?;
Some((entry.id, entry.name.clone()))
}
@@ -472,11 +472,11 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
let id_str = job.id.0.to_string();
let id_short = safe_truncate_str(&id_str, 8);
let sched = match &job.schedule {
- openfang_types::scheduler::CronSchedule::Cron { expr, .. } => expr.clone(),
- openfang_types::scheduler::CronSchedule::Every { every_secs } => {
+ omtae_types::scheduler::CronSchedule::Cron { expr, .. } => expr.clone(),
+ omtae_types::scheduler::CronSchedule::Every { every_secs } => {
format!("every {every_secs}s")
}
- openfang_types::scheduler::CronSchedule::At { at } => {
+ omtae_types::scheduler::CronSchedule::At { at } => {
format!("at {}", at.format("%Y-%m-%d %H:%M"))
}
};
@@ -509,21 +509,21 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
let cron_expr = args[1..6].join(" ");
let message = args[6..].join(" ");
- let job = openfang_types::scheduler::CronJob {
- id: openfang_types::scheduler::CronJobId::new(),
+ let job = omtae_types::scheduler::CronJob {
+ id: omtae_types::scheduler::CronJobId::new(),
agent_id: agent.id,
name: format!("chat-{}", &agent.name),
enabled: true,
- schedule: openfang_types::scheduler::CronSchedule::Cron {
+ schedule: omtae_types::scheduler::CronSchedule::Cron {
expr: cron_expr.clone(),
tz: None,
},
- action: openfang_types::scheduler::CronAction::AgentTurn {
+ action: omtae_types::scheduler::CronAction::AgentTurn {
message: message.clone(),
model_override: None,
timeout_secs: None,
},
- delivery: openfang_types::scheduler::CronDelivery::None,
+ delivery: omtae_types::scheduler::CronDelivery::None,
delivery_targets: Vec::new(),
created_at: chrono::Utc::now(),
last_run: None,
@@ -583,13 +583,13 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
1 => {
let j = matched[0];
let message = match &j.action {
- openfang_types::scheduler::CronAction::AgentTurn {
+ omtae_types::scheduler::CronAction::AgentTurn {
message, ..
} => message.clone(),
- openfang_types::scheduler::CronAction::SystemEvent { text } => {
+ omtae_types::scheduler::CronAction::SystemEvent { text } => {
text.clone()
}
- openfang_types::scheduler::CronAction::WorkflowRun {
+ omtae_types::scheduler::CronAction::WorkflowRun {
workflow_id,
input,
..
@@ -657,9 +657,9 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
1 => {
let req = matched[0];
let decision = if approve {
- openfang_types::approval::ApprovalDecision::Approved
+ omtae_types::approval::ApprovalDecision::Approved
} else {
- openfang_types::approval::ApprovalDecision::Denied
+ omtae_types::approval::ApprovalDecision::Denied
};
match self.kernel.approval_manager.resolve(
req.id,
@@ -762,7 +762,7 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
async fn channel_overrides(
&self,
channel_type: &str,
- ) -> Option {
+ ) -> Option {
let channels = &self.kernel.config.channels;
match channel_type {
"telegram" => channels.telegram.as_ref().map(|c| c.overrides.clone()),
@@ -848,11 +848,11 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
.ok_or_else(|| "Unrecognized user. Contact an admin to get access.".to_string())?;
let auth_action = match action {
- "chat" => openfang_kernel::auth::Action::ChatWithAgent,
- "spawn" => openfang_kernel::auth::Action::SpawnAgent,
- "kill" => openfang_kernel::auth::Action::KillAgent,
- "install_skill" => openfang_kernel::auth::Action::InstallSkill,
- _ => openfang_kernel::auth::Action::ChatWithAgent,
+ "chat" => omtae_kernel::auth::Action::ChatWithAgent,
+ "spawn" => omtae_kernel::auth::Action::SpawnAgent,
+ "kill" => omtae_kernel::auth::Action::KillAgent,
+ "install_skill" => omtae_kernel::auth::Action::InstallSkill,
+ _ => omtae_kernel::auth::Action::ChatWithAgent,
};
self.kernel
@@ -871,9 +871,9 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
thread_id: Option<&str>,
) {
let receipt = if success {
- openfang_kernel::DeliveryTracker::sent_receipt(channel, recipient)
+ omtae_kernel::DeliveryTracker::sent_receipt(channel, recipient)
} else {
- openfang_kernel::DeliveryTracker::failed_receipt(
+ omtae_kernel::DeliveryTracker::failed_receipt(
channel,
recipient,
error.unwrap_or("Unknown error"),
@@ -901,7 +901,7 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
recipient: &str,
message: &str,
) -> Result<(), String> {
- ::send_channel_message(
+ ::send_channel_message(
&self.kernel,
channel_type,
recipient,
@@ -1009,7 +1009,7 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
msg.push_str(&format!(" {} — {}\n", card.name, url));
let desc = &card.description;
if !desc.is_empty() {
- let short = openfang_types::truncate_str(desc, 60);
+ let short = omtae_types::truncate_str(desc, 60);
msg.push_str(&format!(" {short}\n"));
}
}
@@ -1018,8 +1018,8 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
}
/// Parse a trigger pattern string from chat into a `TriggerPattern`.
-fn parse_trigger_pattern(s: &str) -> Option {
- use openfang_kernel::triggers::TriggerPattern;
+fn parse_trigger_pattern(s: &str) -> Option {
+ use omtae_kernel::triggers::TriggerPattern;
if let Some(rest) = s.strip_prefix("spawned:") {
return Some(TriggerPattern::AgentSpawned {
name_pattern: rest.to_string(),
@@ -1092,7 +1092,7 @@ fn read_token(env_var_or_token: &str, adapter_name: &str) -> Option {
///
/// Returns `Some(BridgeManager)` if any channels were configured and started,
/// or `None` if no channels are configured.
-pub async fn start_channel_bridge(kernel: Arc) -> Option {
+pub async fn start_channel_bridge(kernel: Arc) -> Option {
let channels = kernel.config.channels.clone();
let (bridge, _names) = start_channel_bridge_with_config(kernel, &channels).await;
bridge
@@ -1102,8 +1102,8 @@ pub async fn start_channel_bridge(kernel: Arc) -> Option, Vec)`.
pub async fn start_channel_bridge_with_config(
- kernel: Arc,
- config: &openfang_types::config::ChannelsConfig,
+ kernel: Arc,
+ config: &omtae_types::config::ChannelsConfig,
) -> (Option, Vec) {
let has_any = config.telegram.is_some()
|| config.discord.is_some()
@@ -1470,7 +1470,7 @@ pub async fn start_channel_bridge_with_config(
// Feishu/Lark
if let Some(ref fs_config) = config.feishu {
if let Some(secret) = read_token(&fs_config.app_secret_env, "Feishu") {
- let region = openfang_channels::feishu::FeishuRegion::parse_region(&fs_config.region);
+ let region = omtae_channels::feishu::FeishuRegion::parse_region(&fs_config.region);
let encrypt_key = fs_config
.encrypt_key_env
.as_ref()
@@ -1882,7 +1882,7 @@ pub async fn reload_channels_from_disk(
// Re-read config from disk
let config_path = state.kernel.config.home_dir.join("config.toml");
- let fresh_config = openfang_kernel::config::load_config(Some(&config_path));
+ let fresh_config = omtae_kernel::config::load_config(Some(&config_path));
// Update the live channels config so list_channels() reflects reality
*state.channels_config.write().await = fresh_config.channels.clone();
@@ -1907,7 +1907,7 @@ pub async fn reload_channels_from_disk(
mod tests {
#[tokio::test]
async fn test_bridge_skips_when_no_config() {
- let config = openfang_types::config::KernelConfig::default();
+ let config = omtae_types::config::KernelConfig::default();
assert!(config.channels.telegram.is_none());
assert!(config.channels.discord.is_none());
assert!(config.channels.slack.is_none());
@@ -1955,7 +1955,7 @@ mod tests {
#[test]
fn test_feishu_bridge_mode_defaults_to_websocket() {
- let config: openfang_types::config::KernelConfig = toml::from_str(
+ let config: omtae_types::config::KernelConfig = toml::from_str(
r#"
[channels.feishu]
app_id = "cli_test"
@@ -1965,12 +1965,12 @@ mod tests {
.unwrap();
let feishu = config.channels.feishu.expect("feishu config should exist");
- assert_eq!(feishu.mode, openfang_types::config::FeishuMode::Websocket);
+ assert_eq!(feishu.mode, omtae_types::config::FeishuMode::Websocket);
}
#[test]
fn test_feishu_bridge_mode_supports_websocket() {
- let config: openfang_types::config::KernelConfig = toml::from_str(
+ let config: omtae_types::config::KernelConfig = toml::from_str(
r#"
[channels.feishu]
app_id = "cli_test"
@@ -1981,6 +1981,6 @@ mod tests {
.unwrap();
let feishu = config.channels.feishu.expect("feishu config should exist");
- assert_eq!(feishu.mode, openfang_types::config::FeishuMode::Websocket);
+ assert_eq!(feishu.mode, omtae_types::config::FeishuMode::Websocket);
}
}
diff --git a/crates/openfang-api/src/gpu.rs b/crates/openfang-api/src/gpu.rs
new file mode 100644
index 0000000000..f52648c905
--- /dev/null
+++ b/crates/openfang-api/src/gpu.rs
@@ -0,0 +1,269 @@
+//! NVIDIA GPU telemetry via `nvidia-smi` (dual-GPU workstation layout).
+
+use serde::Serialize;
+use std::collections::HashMap;
+use std::time::Duration;
+use tokio::process::Command;
+
+#[derive(Debug, Clone, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct GpuSlot {
+ pub temp: u32,
+ pub usage: u32,
+ pub memory: f64,
+ #[serde(rename = "memoryTotal")]
+ pub memory_total: u32,
+ pub processes: Vec,
+}
+
+#[derive(Debug, Clone, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct GpuStats {
+ pub gpu0: GpuSlot,
+ pub gpu1: GpuSlot,
+ pub total_vram: u32,
+ pub used_vram: f64,
+ pub model: String,
+ pub available: bool,
+}
+
+fn empty_slot() -> GpuSlot {
+ GpuSlot {
+ temp: 0,
+ usage: 0,
+ memory: 0.0,
+ memory_total: 24,
+ processes: vec![],
+ }
+}
+
+fn default_stats() -> GpuStats {
+ GpuStats {
+ gpu0: empty_slot(),
+ gpu1: empty_slot(),
+ total_vram: 48,
+ used_vram: 0.0,
+ model: std::env::var("VLLM_MODEL_DEFAULT")
+ .or_else(|_| std::env::var("OLLAMA_MODEL_DEFAULT"))
+ .unwrap_or_else(|_| "vLLM".to_string()),
+ available: false,
+ }
+}
+
+fn short_process_name(raw: &str) -> String {
+ let name = raw.trim();
+ if name.is_empty() {
+ return "unknown".to_string();
+ }
+ let lower = name.to_lowercase();
+ if lower.contains("vllm") {
+ return "VLLM::EngineCore".to_string();
+ }
+ if lower.contains("gnome-shell") {
+ return "gnome-shell".to_string();
+ }
+ if lower.contains("cursor") {
+ return "cursor".to_string();
+ }
+ if lower.contains("chrome") {
+ return "chrome".to_string();
+ }
+ let without_args = name.split_whitespace().next().unwrap_or(name);
+ let base = without_args
+ .rsplit('/')
+ .next()
+ .unwrap_or(without_args);
+ if base.len() > 28 {
+ format!("{}…", &base[..26])
+ } else {
+ base.to_string()
+ }
+}
+
+async fn run_nvidia_smi(args: &[&str]) -> Option {
+ let output = Command::new("nvidia-smi")
+ .args(args)
+ .output()
+ .await
+ .ok()?;
+ if !output.status.success() {
+ return None;
+ }
+ String::from_utf8(output.stdout).ok()
+}
+
+fn parse_f64(s: &str) -> f64 {
+ s.trim().parse().unwrap_or(0.0)
+}
+
+/// Collect GPU0 (display) and GPU1 (compute/vLLM) stats from `nvidia-smi`.
+pub async fn collect_gpu_stats() -> GpuStats {
+ let mut stats = default_stats();
+
+ let stdout = match run_nvidia_smi(&[
+ "--query-gpu=temperature.gpu,utilization.gpu,memory.used,memory.total",
+ "--format=csv,noheader,nounits",
+ ])
+ .await
+ {
+ Some(s) if !s.trim().is_empty() => s,
+ _ => return stats,
+ };
+
+ stats.available = true;
+ let lines: Vec<&str> = stdout.trim().lines().collect();
+ let mut total_used_mb = 0.0_f64;
+ let mut total_vram_mb = 0.0_f64;
+
+ if let Some(line) = lines.first() {
+ let parts: Vec<&str> = line.split(',').map(str::trim).collect();
+ if parts.len() >= 4 {
+ let mem_used = parse_f64(parts[2]);
+ let mem_total = parse_f64(parts[3]);
+ stats.gpu0 = GpuSlot {
+ temp: parse_f64(parts[0]) as u32,
+ usage: parse_f64(parts[1]) as u32,
+ memory: (mem_used / 1024.0 * 10.0).round() / 10.0,
+ memory_total: (mem_total / 1024.0).round() as u32,
+ processes: vec![],
+ };
+ total_used_mb += mem_used;
+ total_vram_mb += mem_total;
+ }
+ }
+
+ if let Some(line) = lines.get(1) {
+ let parts: Vec<&str> = line.split(',').map(str::trim).collect();
+ if parts.len() >= 4 {
+ let mem_used = parse_f64(parts[2]);
+ let mem_total = parse_f64(parts[3]);
+ stats.gpu1 = GpuSlot {
+ temp: parse_f64(parts[0]) as u32,
+ usage: parse_f64(parts[1]) as u32,
+ memory: (mem_used / 1024.0 * 10.0).round() / 10.0,
+ memory_total: (mem_total / 1024.0).round() as u32,
+ processes: vec![],
+ };
+ total_used_mb += mem_used;
+ total_vram_mb += mem_total;
+ }
+ }
+
+ stats.used_vram = (total_used_mb / 1024.0 * 10.0).round() / 10.0;
+ stats.total_vram = (total_vram_mb / 1024.0).round() as u32;
+
+ if let Some(uuid_out) = run_nvidia_smi(&[
+ "--query-gpu=index,uuid",
+ "--format=csv,noheader",
+ ])
+ .await
+ {
+ let mut uuid_to_index: HashMap = HashMap::new();
+ for row in uuid_out.trim().lines() {
+ let mut parts = row.splitn(2, ',');
+ let idx_str = parts.next().unwrap_or("").trim();
+ let uuid = parts.next().unwrap_or("").trim();
+ if let Ok(idx) = idx_str.parse::() {
+ if !uuid.is_empty() {
+ uuid_to_index.insert(uuid.to_string(), idx);
+ }
+ }
+ }
+
+ if let Some(proc_out) = run_nvidia_smi(&[
+ "--query-compute-apps=gpu_uuid,pid,process_name,used_gpu_memory",
+ "--format=csv,noheader,nounits",
+ ])
+ .await
+ {
+ let mut proc_by_gpu: HashMap> =
+ HashMap::from([(0, vec![]), (1, vec![])]);
+ for row in proc_out.trim().lines() {
+ if row.trim().is_empty() {
+ continue;
+ }
+ let first_comma = row.find(',').unwrap_or(0);
+ let second_comma = row[first_comma + 1..]
+ .find(',')
+ .map(|i| first_comma + 1 + i)
+ .unwrap_or(0);
+ let last_comma = row.rfind(',').unwrap_or(0);
+ if second_comma == 0 || last_comma <= second_comma {
+ continue;
+ }
+ let uuid = row[..first_comma].trim();
+ let proc_name = row[second_comma + 1..last_comma].trim();
+ if let Some(&idx) = uuid_to_index.get(uuid) {
+ if idx == 0 || idx == 1 {
+ proc_by_gpu
+ .entry(idx)
+ .or_default()
+ .push(short_process_name(proc_name));
+ }
+ }
+ }
+ if let Some(p) = proc_by_gpu.get(&0) {
+ let mut unique: Vec = p.clone();
+ unique.sort();
+ unique.dedup();
+ stats.gpu0.processes = unique;
+ }
+ if let Some(p) = proc_by_gpu.get(&1) {
+ let mut unique: Vec = p.clone();
+ unique.sort();
+ unique.dedup();
+ stats.gpu1.processes = unique;
+ }
+ }
+ }
+
+ // Optional: resolve vLLM model name from /models endpoint
+ let provider = std::env::var("LLM_PROVIDER").unwrap_or_default();
+ let vllm_base = std::env::var("VLLM_BASE_URL")
+ .unwrap_or_else(|_| "http://127.0.0.1:8000/v1".to_string())
+ .trim_end_matches('/')
+ .to_string();
+ if provider == "vllm" || provider.is_empty() || vllm_base.contains(":8000") {
+ if let Ok(client) = reqwest::Client::builder()
+ .timeout(Duration::from_secs(2))
+ .build()
+ {
+ let mut req = client.get(format!("{vllm_base}/models"));
+ if let Ok(key) = std::env::var("VLLM_API_KEY") {
+ if !key.is_empty() {
+ req = req.header("Authorization", format!("Bearer {key}"));
+ }
+ }
+ if let Ok(resp) = req.send().await {
+ if resp.status().is_success() {
+ if let Ok(body) = resp.json::().await {
+ if let Some(id) = body
+ .get("data")
+ .and_then(|d| d.as_array())
+ .and_then(|a| a.first())
+ .and_then(|m| m.get("id"))
+ .and_then(|v| v.as_str())
+ {
+ stats.model = id.to_string();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ stats
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn short_process_name_vllm() {
+ assert_eq!(
+ short_process_name("python -m vllm.entrypoints"),
+ "VLLM::EngineCore"
+ );
+ }
+}
diff --git a/crates/openfang-api/src/lib.rs b/crates/openfang-api/src/lib.rs
index 243da788b2..2d2b0cb910 100644
--- a/crates/openfang-api/src/lib.rs
+++ b/crates/openfang-api/src/lib.rs
@@ -1,4 +1,4 @@
-//! HTTP/WebSocket API server for the OpenFang Agent OS daemon.
+//! HTTP/WebSocket API server for the OMTAE Agent OS daemon.
//!
//! Exposes agent management, status, and chat via JSON REST endpoints.
//! The kernel runs in-process; the CLI connects over HTTP.
@@ -32,7 +32,10 @@ fn hex_val(b: u8) -> Option {
}
}
+pub mod brain;
pub mod channel_bridge;
+pub mod gpu;
+pub mod model_profiles;
pub mod middleware;
pub mod openai_compat;
pub mod rate_limiter;
diff --git a/crates/openfang-api/src/middleware.rs b/crates/openfang-api/src/middleware.rs
index 5d9e363063..6919ff425c 100644
--- a/crates/openfang-api/src/middleware.rs
+++ b/crates/openfang-api/src/middleware.rs
@@ -1,4 +1,4 @@
-//! Production middleware for the OpenFang API server.
+//! Production middleware for the OMTAE API server.
//!
//! Provides:
//! - Request ID generation and propagation
@@ -47,8 +47,11 @@ pub async fn request_logging(request: Request, next: Next) -> Response header"
@@ -293,8 +362,10 @@ mod tests {
fn auth_state_empty() -> AuthState {
AuthState {
api_key: String::new(),
+ raw_api_key: String::new(),
auth_enabled: false,
session_secret: String::new(),
+ dashboard_pin: String::new(),
allow_no_auth: false,
}
}
@@ -302,8 +373,10 @@ mod tests {
fn auth_state_with_key(key: &str) -> AuthState {
AuthState {
api_key: key.to_string(),
+ raw_api_key: key.to_string(),
auth_enabled: false,
session_secret: key.to_string(),
+ dashboard_pin: String::new(),
allow_no_auth: false,
}
}
@@ -396,4 +469,27 @@ mod tests {
let resp = app.oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
+
+ #[tokio::test]
+ async fn test_internal_token_bypasses_pin_auth() {
+ // Simulates active PIN auth: api_key is empty, but raw_api_key is set.
+ let state = AuthState {
+ api_key: String::new(),
+ raw_api_key: "secret".to_string(),
+ auth_enabled: true,
+ session_secret: "hash".to_string(),
+ dashboard_pin: "123456".to_string(),
+ allow_no_auth: false,
+ };
+ let app = router(state);
+ let addr: SocketAddr = "127.0.0.1:40000".parse().unwrap();
+ let mut req = Request::builder()
+ .method(Method::GET)
+ .uri("/api/agents/1?internal=secret")
+ .body(Body::empty())
+ .unwrap();
+ req.extensions_mut().insert(ConnectInfo(addr));
+ let resp = app.oneshot(req).await.unwrap();
+ assert_eq!(resp.status(), StatusCode::OK);
+ }
}
diff --git a/crates/openfang-api/src/model_profiles.rs b/crates/openfang-api/src/model_profiles.rs
new file mode 100644
index 0000000000..fb5806c810
--- /dev/null
+++ b/crates/openfang-api/src/model_profiles.rs
@@ -0,0 +1,147 @@
+//! Local vLLM model profiles (`~/.omtae/models.toml` + `active_model.txt`).
+
+use serde::{Deserialize, Serialize};
+use std::path::{Path, PathBuf};
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct ModelProfile {
+ pub id: String,
+ pub display_name: String,
+ pub vllm_path: String,
+ pub tensor_parallel: u32,
+ pub max_model_len: u32,
+ pub gpu_mem_util: f64,
+ pub vllm_served_name: String,
+ /// Model id in OMTAE (`config.toml` + `custom_models.json`).
+ pub omtae_model_id: String,
+ pub start_script: String,
+ #[serde(default)]
+ pub quantization: Option,
+}
+
+#[derive(Debug, Deserialize)]
+struct ModelsFile {
+ #[serde(default)]
+ profiles: Vec,
+}
+
+pub fn models_toml_path(home: &Path) -> PathBuf {
+ home.join("models.toml")
+}
+
+pub fn active_profile_path(home: &Path) -> PathBuf {
+ home.join("active_model.txt")
+}
+
+pub fn load_profiles(home: &Path) -> Result, String> {
+ let path = models_toml_path(home);
+ if !path.exists() {
+ return Ok(Vec::new());
+ }
+ let content = std::fs::read_to_string(&path)
+ .map_err(|e| format!("read {}: {e}", path.display()))?;
+ let file: ModelsFile = toml::from_str(&content)
+ .map_err(|e| format!("parse {}: {e}", path.display()))?;
+ Ok(file.profiles)
+}
+
+pub fn read_active_profile_id(home: &Path) -> Option {
+ let path = active_profile_path(home);
+ let id = std::fs::read_to_string(path).ok()?;
+ let id = id.trim().to_string();
+ if id.is_empty() {
+ None
+ } else {
+ Some(id)
+ }
+}
+
+pub fn write_active_profile_id(home: &Path, profile_id: &str) -> Result<(), String> {
+ let path = active_profile_path(home);
+ std::fs::write(&path, format!("{profile_id}\n"))
+ .map_err(|e| format!("write {}: {e}", path.display()))
+}
+
+pub fn find_profile<'a>(profiles: &'a [ModelProfile], id: &str) -> Option<&'a ModelProfile> {
+ profiles.iter().find(|p| p.id == id)
+}
+
+/// Update `[default_model].model` in config.toml (preserves other keys).
+pub fn persist_default_model_id(home: &Path, model_id: &str) -> Result<(), String> {
+ let config_path = home.join("config.toml");
+ let mut table: toml::value::Table = if config_path.exists() {
+ let content = std::fs::read_to_string(&config_path)
+ .map_err(|e| format!("read config.toml: {e}"))?;
+ toml::from_str(&content).unwrap_or_default()
+ } else {
+ toml::value::Table::new()
+ };
+
+ let section = table
+ .entry("default_model".to_string())
+ .or_insert_with(|| toml::Value::Table(toml::value::Table::new()));
+ if let toml::Value::Table(ref mut t) = section {
+ t.insert(
+ "model".to_string(),
+ toml::Value::String(model_id.to_string()),
+ );
+ if !t.contains_key("provider") {
+ t.insert("provider".to_string(), toml::Value::String("vllm".to_string()));
+ }
+ if !t.contains_key("api_key_env") {
+ t.insert(
+ "api_key_env".to_string(),
+ toml::Value::String("VLLM_API_KEY".to_string()),
+ );
+ }
+ if !t.contains_key("base_url") {
+ t.insert(
+ "base_url".to_string(),
+ toml::Value::String("http://127.0.0.1:8000/v1".to_string()),
+ );
+ }
+ }
+
+ let toml_string = toml::to_string_pretty(&table)
+ .map_err(|e| format!("serialize config.toml: {e}"))?;
+ std::fs::write(&config_path, toml_string)
+ .map_err(|e| format!("write config.toml: {e}"))?;
+ Ok(())
+}
+
+pub fn profile_available_on_disk(profile: &ModelProfile) -> bool {
+ let p = Path::new(&profile.vllm_path);
+ p.is_dir() && p.join("config.json").exists()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::io::Write;
+
+ #[test]
+ fn load_profiles_parses_array() {
+ let dir = tempfile::tempdir().unwrap();
+ let path = dir.path().join("models.toml");
+ let mut f = std::fs::File::create(&path).unwrap();
+ writeln!(
+ f,
+ r#"
+[[profiles]]
+id = "test"
+display_name = "Test"
+vllm_path = "/tmp/m"
+tensor_parallel = 2
+max_model_len = 8192
+gpu_mem_util = 0.8
+vllm_served_name = "test-model"
+omtae_model_id = "test-model"
+start_script = "/tmp/start.sh"
+"#
+ )
+ .unwrap();
+ let profiles = load_profiles(dir.path()).unwrap();
+ assert_eq!(profiles.len(), 1);
+ assert_eq!(profiles[0].id, "test");
+ }
+}
diff --git a/crates/openfang-api/src/openai_compat.rs b/crates/openfang-api/src/openai_compat.rs
index b87d6893f2..fec0288156 100644
--- a/crates/openfang-api/src/openai_compat.rs
+++ b/crates/openfang-api/src/openai_compat.rs
@@ -1,7 +1,7 @@
//! OpenAI-compatible `/v1/chat/completions` API endpoint.
//!
-//! Allows any OpenAI-compatible client library to talk to OpenFang agents.
-//! The `model` field resolves to an agent (by name, UUID, or `openfang:`),
+//! Allows any OpenAI-compatible client library to talk to OMTAE agents.
+//! The `model` field resolves to an agent (by name, UUID, or `omtae:`),
//! and the messages are forwarded to the agent's LLM loop.
//!
//! Supports both streaming (SSE) and non-streaming responses.
@@ -12,10 +12,10 @@ use axum::http::StatusCode;
use axum::response::sse::{Event as SseEvent, KeepAlive, Sse};
use axum::response::IntoResponse;
use axum::Json;
-use openfang_runtime::kernel_handle::KernelHandle;
-use openfang_runtime::llm_driver::StreamEvent;
-use openfang_types::agent::AgentId;
-use openfang_types::message::{ContentBlock, Message, MessageContent, Role, StopReason};
+use omtae_runtime::kernel_handle::KernelHandle;
+use omtae_runtime::llm_driver::StreamEvent;
+use omtae_types::agent::AgentId;
+use omtae_types::message::{ContentBlock, Message, MessageContent, Role, StopReason};
use serde::{Deserialize, Serialize};
use std::convert::Infallible;
use std::sync::Arc;
@@ -160,8 +160,8 @@ struct ModelListResponse {
// ── Agent resolution ────────────────────────────────────────────────────────
fn resolve_agent(state: &AppState, model: &str) -> Option<(AgentId, String)> {
- // 1. "openfang:" → find agent by name
- if let Some(name) = model.strip_prefix("openfang:") {
+ // 1. "omtae:" → find agent by name
+ if let Some(name) = model.strip_prefix("omtae:") {
if let Some(entry) = state.kernel.registry.find_by_name(name) {
return Some((entry.id, entry.name.clone()));
}
@@ -547,10 +547,10 @@ pub async fn list_models(State(state): State>) -> impl IntoRespons
let models: Vec = agents
.iter()
.map(|e| ModelObject {
- id: format!("openfang:{}", e.name),
+ id: format!("omtae:{}", e.name),
object: "model",
created,
- owned_by: "openfang".to_string(),
+ owned_by: "omtae".to_string(),
})
.collect();
diff --git a/crates/openfang-api/src/routes.rs b/crates/openfang-api/src/routes.rs
index cebb1f599a..05735aa6ac 100644
--- a/crates/openfang-api/src/routes.rs
+++ b/crates/openfang-api/src/routes.rs
@@ -1,4 +1,4 @@
-//! Route handlers for the OpenFang API.
+//! Route handlers for the OMTAE API.
use crate::types::*;
use axum::extract::{Multipart, Path, Query, State};
@@ -6,14 +6,14 @@ use axum::http::StatusCode;
use axum::response::IntoResponse;
use axum::Json;
use dashmap::DashMap;
-use openfang_kernel::triggers::{TriggerId, TriggerPattern};
-use openfang_kernel::workflow::{
+use omtae_kernel::triggers::{TriggerId, TriggerPattern};
+use omtae_kernel::workflow::{
ErrorMode, StepAgent, StepMode, Workflow, WorkflowId, WorkflowStep,
};
-use openfang_kernel::OpenFangKernel;
-use openfang_runtime::kernel_handle::KernelHandle;
-use openfang_runtime::tool_runner::builtin_tool_definitions;
-use openfang_types::agent::{AgentId, AgentIdentity, AgentManifest};
+use omtae_kernel::OMTAEKernel;
+use omtae_runtime::kernel_handle::KernelHandle;
+use omtae_runtime::tool_runner::builtin_tool_definitions;
+use omtae_types::agent::{AgentId, AgentIdentity, AgentManifest};
use std::collections::HashMap;
use std::sync::{Arc, LazyLock};
use std::time::Instant;
@@ -23,14 +23,14 @@ use std::time::Instant;
/// The kernel is wrapped in Arc so it can serve as both the main kernel
/// and the KernelHandle for inter-agent tool access.
pub struct AppState {
- pub kernel: Arc,
+ pub kernel: Arc,
pub started_at: Instant,
/// Optional peer registry for OFP mesh networking status.
- pub peer_registry: Option>,
+ pub peer_registry: Option>,
/// Channel bridge manager — held behind a Mutex so it can be swapped on hot-reload.
- pub bridge_manager: tokio::sync::Mutex>,
+ pub bridge_manager: tokio::sync::Mutex >,
/// Live channel config — updated on every hot-reload so list_channels() reflects reality.
- pub channels_config: tokio::sync::RwLock,
+ pub channels_config: tokio::sync::RwLock,
/// Notify handle to trigger graceful HTTP server shutdown from the API.
pub shutdown_notify: Arc,
/// ClawHub response cache — prevents 429 rate limiting on rapid dashboard refreshes.
@@ -39,10 +39,48 @@ pub struct AppState {
/// Probe cache for local provider health checks (ollama/vllm/lmstudio).
/// Avoids blocking the `/api/providers` endpoint on TCP timeouts to
/// unreachable local services. 60-second TTL.
- pub provider_probe_cache: openfang_runtime::provider_health::ProbeCache,
+ pub provider_probe_cache: omtae_runtime::provider_health::ProbeCache,
/// Thread-safe mutable budget config. Updated via PUT /api/budget.
/// Initialized from `kernel.config.budget` at startup.
- pub budget_config: Arc>,
+ pub budget_config: Arc>,
+}
+
+/// Effective kernel `[default_model]` (honours hot-reload override).
+fn effective_default_model(kernel: &OMTAEKernel) -> omtae_types::config::DefaultModelConfig {
+ kernel
+ .default_model_override
+ .read()
+ .unwrap_or_else(|e| e.into_inner())
+ .clone()
+ .unwrap_or_else(|| kernel.config.default_model.clone())
+}
+
+/// Model ID to use when probing or testing a provider (wizard Save & Test).
+fn model_for_provider_test(
+ kernel: &OMTAEKernel,
+ catalog: &omtae_runtime::model_catalog::ModelCatalog,
+ provider: &str,
+) -> String {
+ let dm = effective_default_model(kernel);
+ if dm.provider == provider && !dm.model.is_empty() {
+ return dm.model;
+ }
+ catalog
+ .default_model_for_provider(provider)
+ .unwrap_or_default()
+}
+
+/// Optional API key from the provider's configured env var (e.g. `VLLM_API_KEY`).
+fn provider_api_key_from_env(catalog: &omtae_runtime::model_catalog::ModelCatalog, provider: &str) -> Option {
+ catalog
+ .get_provider(provider)
+ .and_then(|p| {
+ if p.api_key_env.is_empty() {
+ None
+ } else {
+ std::env::var(&p.api_key_env).ok().filter(|k| !k.is_empty())
+ }
+ })
}
/// POST /api/agents — Spawn a new agent.
@@ -122,7 +160,7 @@ pub async fn spawn_agent(
tracing::warn!("Manifest signature verification failed: {e}");
state.kernel.audit_log.record(
"system",
- openfang_runtime::audit::AuditAction::AuthAttempt,
+ omtae_runtime::audit::AuditAction::AuthAttempt,
"manifest signature verification failed",
format!("error: {e}"),
);
@@ -170,6 +208,17 @@ pub async fn spawn_agent(
}
}
+/// Human-readable schedule label for dashboard agent cards.
+fn schedule_mode_label(schedule: &omtae_types::agent::ScheduleMode) -> &'static str {
+ use omtae_types::agent::ScheduleMode;
+ match schedule {
+ ScheduleMode::Reactive => "reactive",
+ ScheduleMode::Continuous { .. } => "continuous",
+ ScheduleMode::Periodic { .. } => "periodic",
+ ScheduleMode::Proactive { .. } => "proactive",
+ }
+}
+
/// GET /api/agents — List all agents.
pub async fn list_agents(State(state): State>) -> impl IntoResponse {
// Snapshot catalog once for enrichment
@@ -212,7 +261,7 @@ pub async fn list_agents(State(state): State>) -> impl IntoRespons
})
.unwrap_or(("unknown".to_string(), "unknown".to_string()));
- let ready = matches!(e.state, openfang_types::agent::AgentState::Running)
+ let ready = matches!(e.state, omtae_types::agent::AgentState::Running)
&& auth_status != "missing";
// Issue #1026: surface which agents are currently calling the LLM
@@ -221,6 +270,9 @@ pub async fn list_agents(State(state): State>) -> impl IntoRespons
// agent loop is in flight (LLM call + tool dispatch).
let is_inferencing = state.kernel.running_tasks.contains_key(&e.id);
+ let schedule = schedule_mode_label(&e.manifest.schedule);
+ let background_paused = state.kernel.background.is_paused(e.id);
+
serde_json::json!({
"id": e.id.to_string(),
"name": e.name,
@@ -234,6 +286,9 @@ pub async fn list_agents(State(state): State>) -> impl IntoRespons
"auth_status": auth_status,
"ready": ready,
"is_inferencing": is_inferencing,
+ "schedule": schedule,
+ "background_paused": background_paused,
+ "is_autonomous": e.manifest.autonomous.is_some(),
"profile": e.manifest.profile,
"identity": {
"emoji": e.identity.emoji,
@@ -253,10 +308,10 @@ pub async fn list_agents(State(state): State>) -> impl IntoRespons
/// returns image content blocks ready to insert into a session message.
pub fn resolve_attachments(
attachments: &[AttachmentRef],
-) -> Vec {
+) -> Vec {
use base64::Engine;
- let upload_dir = std::env::temp_dir().join("openfang_uploads");
+ let upload_dir = std::env::temp_dir().join("omtae_uploads");
let mut blocks = Vec::new();
for att in attachments {
@@ -284,7 +339,7 @@ pub fn resolve_attachments(
match std::fs::read(&file_path) {
Ok(data) => {
let b64 = base64::engine::general_purpose::STANDARD.encode(&data);
- blocks.push(openfang_types::message::ContentBlock::Image {
+ blocks.push(omtae_types::message::ContentBlock::Image {
media_type: content_type,
data: b64,
});
@@ -303,11 +358,11 @@ pub fn resolve_attachments(
/// This injects image content blocks into the session BEFORE the kernel
/// adds the text user message, so the LLM receives: [..., User(images), User(text)].
pub fn inject_attachments_into_session(
- kernel: &OpenFangKernel,
+ kernel: &OMTAEKernel,
agent_id: AgentId,
- image_blocks: Vec,
+ image_blocks: Vec,
) {
- use openfang_types::message::{Message, MessageContent, Role};
+ use omtae_types::message::{Message, MessageContent, Role};
let entry = match kernel.registry.get(agent_id) {
Some(e) => e,
@@ -316,7 +371,7 @@ pub fn inject_attachments_into_session(
let mut session = match kernel.memory.get_session(entry.session_id) {
Ok(Some(s)) => s,
- _ => openfang_memory::session::Session {
+ _ => omtae_memory::session::Session {
id: entry.session_id,
agent_id,
messages: Vec::new(),
@@ -427,17 +482,34 @@ pub async fn send_message(
}
Err(e) => {
tracing::warn!("send_message failed for agent {id}: {e}");
- let status = if format!("{e}").contains("Agent not found") {
+ let err_str = format!("{e}");
+ let status = if err_str.contains("Agent not found") {
StatusCode::NOT_FOUND
- } else if format!("{e}").contains("quota") || format!("{e}").contains("Quota") {
+ } else if err_str.contains("quota") || err_str.contains("Quota") {
StatusCode::TOO_MANY_REQUESTS
+ } else if err_str.contains("Context too long")
+ || err_str.contains("context length")
+ || err_str.contains("ContextOverflow")
+ {
+ StatusCode::BAD_REQUEST
+ } else if err_str.contains("401") || err_str.contains("Unauthorized") {
+ StatusCode::BAD_GATEWAY
} else {
StatusCode::INTERNAL_SERVER_ERROR
};
- (
- status,
- Json(serde_json::json!({"error": format!("Message delivery failed: {e}")})),
- )
+ let user_msg: String = if err_str.contains("Context too long")
+ || err_str.contains("context length")
+ || err_str.contains("ContextOverflow")
+ {
+ "Prompt too long for this model — try a shorter message or reduce max_tokens on the agent."
+ .to_string()
+ } else if err_str.contains("401") || err_str.contains("Unauthorized") {
+ "LLM provider rejected the API key — check VLLM_API_KEY (or your provider key) in Settings."
+ .to_string()
+ } else {
+ format!("Message delivery failed: {e}")
+ };
+ (status, Json(serde_json::json!({"error": user_msg})))
}
}
}
@@ -488,10 +560,10 @@ pub async fn get_agent_session(
// logic so the system prompt cannot leak into the response. The
// raw message count is preserved separately for the API consumer.
let raw_message_count = session.messages.len();
- let filtered_messages: Vec<&openfang_types::message::Message> = session
+ let filtered_messages: Vec<&omtae_types::message::Message> = session
.messages
.iter()
- .filter(|m| include_system || m.role != openfang_types::message::Role::System)
+ .filter(|m| include_system || m.role != omtae_types::message::Role::System)
.collect();
// Two-pass approach: ToolUse blocks live in Assistant messages while
@@ -509,15 +581,15 @@ pub async fn get_agent_session(
let mut tools: Vec = Vec::new();
let mut msg_images: Vec = Vec::new();
let content = match &m.content {
- openfang_types::message::MessageContent::Text(t) => t.clone(),
- openfang_types::message::MessageContent::Blocks(blocks) => {
+ omtae_types::message::MessageContent::Text(t) => t.clone(),
+ omtae_types::message::MessageContent::Blocks(blocks) => {
let mut texts = Vec::new();
for b in blocks {
match b {
- openfang_types::message::ContentBlock::Text { text, .. } => {
+ omtae_types::message::ContentBlock::Text { text, .. } => {
texts.push(text.clone());
}
- openfang_types::message::ContentBlock::Image {
+ omtae_types::message::ContentBlock::Image {
media_type,
data,
} => {
@@ -525,7 +597,7 @@ pub async fn get_agent_session(
// Persist image to upload dir so it can be
// served back when loading session history.
let file_id = uuid::Uuid::new_v4().to_string();
- let upload_dir = std::env::temp_dir().join("openfang_uploads");
+ let upload_dir = std::env::temp_dir().join("omtae_uploads");
let _ = std::fs::create_dir_all(&upload_dir);
if let Ok(bytes) =
base64::engine::general_purpose::STANDARD.decode(data)
@@ -547,7 +619,7 @@ pub async fn get_agent_session(
}));
}
}
- openfang_types::message::ContentBlock::ToolUse {
+ omtae_types::message::ContentBlock::ToolUse {
id,
name,
input,
@@ -564,7 +636,7 @@ pub async fn get_agent_session(
tool_use_index.insert(id.clone(), (usize::MAX, tool_idx));
}
// ToolResult blocks are handled in pass 2
- openfang_types::message::ContentBlock::ToolResult { .. } => {}
+ omtae_types::message::ContentBlock::ToolResult { .. } => {}
_ => {}
}
}
@@ -597,9 +669,9 @@ pub async fn get_agent_session(
// Pass 2: walk filtered messages again and attach ToolResult to the correct tool
for m in &filtered_messages {
- if let openfang_types::message::MessageContent::Blocks(blocks) = &m.content {
+ if let omtae_types::message::MessageContent::Blocks(blocks) = &m.content {
for b in blocks {
- if let openfang_types::message::ContentBlock::ToolResult {
+ if let omtae_types::message::ContentBlock::ToolResult {
tool_use_id,
content: result,
is_error,
@@ -695,7 +767,7 @@ pub async fn kill_agent(
/// DELETE /api/agents/{id}/uninstall — Permanently uninstall an agent.
///
/// Issue #1163: in addition to killing the agent (registry + memory + cron),
-/// this also removes the on-disk `~/.openfang/agents//` directory so
+/// this also removes the on-disk `~/.omtae/agents//` directory so
/// the agent does not auto-respawn on the next daemon start.
pub async fn uninstall_agent(
State(state): State>,
@@ -731,7 +803,7 @@ pub async fn uninstall_agent(
);
}
- // Step 2: remove ~/.openfang/agents// so the agent does NOT
+ // Step 2: remove ~/.omtae/agents// so the agent does NOT
// auto-respawn from disk on the next daemon start.
let agents_dir = state.kernel.config.home_dir.join("agents");
let agent_dir = agents_dir.join(&agent_name);
@@ -824,12 +896,22 @@ pub async fn restart_agent(
let _ = state
.kernel
.registry
- .set_state(agent_id, openfang_types::agent::AgentState::Running);
+ .set_state(agent_id, omtae_types::agent::AgentState::Running);
+
+ // Hot-reload manifest from disk (prompt, tools, exec_policy, etc.)
+ let manifest_reloaded = match state.kernel.reload_agent_manifest_from_disk(agent_id) {
+ Ok(()) => true,
+ Err(e) => {
+ tracing::warn!(agent = %agent_name, error = %e, "Agent restart: manifest disk reload failed");
+ false
+ }
+ };
tracing::info!(
agent = %agent_name,
previous_state = %previous_state,
task_cancelled = was_running,
+ manifest_reloaded,
"Agent restarted via API"
);
@@ -841,6 +923,7 @@ pub async fn restart_agent(
"agent_id": id,
"previous_state": previous_state,
"task_cancelled": was_running,
+ "manifest_reloaded": manifest_reloaded,
})),
)
}
@@ -890,7 +973,7 @@ pub async fn shutdown(State(state): State>) -> impl IntoResponse {
// SECURITY: Record shutdown in audit trail
state.kernel.audit_log.record(
"system",
- openfang_runtime::audit::AuditAction::ConfigChange,
+ omtae_runtime::audit::AuditAction::ConfigChange,
"shutdown requested via API",
"ok",
);
@@ -1388,7 +1471,7 @@ pub async fn delete_trigger(
/// GET /api/profiles — List all tool profiles and their tool lists.
pub async fn list_profiles() -> impl IntoResponse {
- use openfang_types::agent::ToolProfile;
+ use omtae_types::agent::ToolProfile;
let profiles = [
("minimal", ToolProfile::Minimal),
@@ -1451,7 +1534,7 @@ pub async fn set_agent_mode(
/// GET /api/version — Build & version info.
pub async fn version() -> impl IntoResponse {
Json(serde_json::json!({
- "name": "openfang",
+ "name": "omtae",
"version": env!("CARGO_PKG_VERSION"),
"build_date": option_env!("BUILD_DATE").unwrap_or("dev"),
"git_sha": option_env!("GIT_SHA").unwrap_or("unknown"),
@@ -1536,7 +1619,7 @@ pub async fn send_message_stream(
) -> axum::response::Response {
use axum::response::sse::{Event, Sse};
use futures::stream;
- use openfang_runtime::llm_driver::StreamEvent;
+ use omtae_runtime::llm_driver::StreamEvent;
// SECURITY: Reject oversized messages to prevent OOM / LLM token abuse.
const MAX_MESSAGE_SIZE: usize = 64 * 1024; // 64KB
@@ -1776,7 +1859,7 @@ const CHANNEL_REGISTRY: &[ChannelMeta] = &[
fields: &[
ChannelField { key: "access_token_env", label: "Access Token", field_type: FieldType::Secret, env_var: Some("MATRIX_ACCESS_TOKEN"), required: true, placeholder: "syt_...", advanced: false },
ChannelField { key: "homeserver_url", label: "Homeserver URL", field_type: FieldType::Text, env_var: None, required: true, placeholder: "https://matrix.org", advanced: false },
- ChannelField { key: "user_id", label: "Bot User ID", field_type: FieldType::Text, env_var: None, required: false, placeholder: "@openfang:matrix.org", advanced: true },
+ ChannelField { key: "user_id", label: "Bot User ID", field_type: FieldType::Text, env_var: None, required: false, placeholder: "@omtae:matrix.org", advanced: true },
ChannelField { key: "allowed_rooms", label: "Allowed Room IDs", field_type: FieldType::List, env_var: None, required: false, placeholder: "!abc:matrix.org", advanced: true },
ChannelField { key: "default_agent", label: "Default Agent", field_type: FieldType::Text, env_var: None, required: false, placeholder: "assistant", advanced: true },
],
@@ -1868,7 +1951,7 @@ const CHANNEL_REGISTRY: &[ChannelMeta] = &[
quick_setup: "Enter your username and paper key",
setup_type: "form",
fields: &[
- ChannelField { key: "username", label: "Username", field_type: FieldType::Text, env_var: None, required: true, placeholder: "openfang_bot", advanced: false },
+ ChannelField { key: "username", label: "Username", field_type: FieldType::Text, env_var: None, required: true, placeholder: "omtae_bot", advanced: false },
ChannelField { key: "paperkey_env", label: "Paper Key", field_type: FieldType::Secret, env_var: Some("KEYBASE_PAPERKEY"), required: true, placeholder: "word1 word2 word3...", advanced: false },
ChannelField { key: "allowed_teams", label: "Allowed Teams", field_type: FieldType::List, env_var: None, required: false, placeholder: "team1, team2", advanced: true },
ChannelField { key: "default_agent", label: "Default Agent", field_type: FieldType::Text, env_var: None, required: false, placeholder: "assistant", advanced: true },
@@ -1886,9 +1969,9 @@ const CHANNEL_REGISTRY: &[ChannelMeta] = &[
fields: &[
ChannelField { key: "client_id", label: "Client ID", field_type: FieldType::Text, env_var: None, required: true, placeholder: "abc123def", advanced: false },
ChannelField { key: "client_secret_env", label: "Client Secret", field_type: FieldType::Secret, env_var: Some("REDDIT_CLIENT_SECRET"), required: true, placeholder: "abc123...", advanced: false },
- ChannelField { key: "username", label: "Bot Username", field_type: FieldType::Text, env_var: None, required: true, placeholder: "openfang_bot", advanced: false },
+ ChannelField { key: "username", label: "Bot Username", field_type: FieldType::Text, env_var: None, required: true, placeholder: "omtae_bot", advanced: false },
ChannelField { key: "password_env", label: "Bot Password", field_type: FieldType::Secret, env_var: Some("REDDIT_PASSWORD"), required: true, placeholder: "password", advanced: false },
- ChannelField { key: "subreddits", label: "Subreddits", field_type: FieldType::List, env_var: None, required: false, placeholder: "openfang, rust", advanced: true },
+ ChannelField { key: "subreddits", label: "Subreddits", field_type: FieldType::List, env_var: None, required: false, placeholder: "omtae, rust", advanced: true },
ChannelField { key: "default_agent", label: "Default Agent", field_type: FieldType::Text, env_var: None, required: false, placeholder: "assistant", advanced: true },
],
setup_steps: &["Create a Reddit app at reddit.com/prefs/apps (script type)", "Copy Client ID and Secret", "Enter bot credentials below"],
@@ -2130,14 +2213,14 @@ const CHANNEL_REGISTRY: &[ChannelMeta] = &[
setup_type: "form",
fields: &[
ChannelField { key: "server", label: "Server", field_type: FieldType::Text, env_var: None, required: true, placeholder: "irc.libera.chat", advanced: false },
- ChannelField { key: "nick", label: "Nickname", field_type: FieldType::Text, env_var: None, required: true, placeholder: "openfang", advanced: false },
- ChannelField { key: "channels", label: "Channels", field_type: FieldType::List, env_var: None, required: false, placeholder: "#openfang, #general", advanced: false },
+ ChannelField { key: "nick", label: "Nickname", field_type: FieldType::Text, env_var: None, required: true, placeholder: "omtae", advanced: false },
+ ChannelField { key: "channels", label: "Channels", field_type: FieldType::List, env_var: None, required: false, placeholder: "#omtae, #general", advanced: false },
ChannelField { key: "port", label: "Port", field_type: FieldType::Number, env_var: None, required: false, placeholder: "6667", advanced: true },
ChannelField { key: "use_tls", label: "Use TLS", field_type: FieldType::Text, env_var: None, required: false, placeholder: "false", advanced: true },
ChannelField { key: "default_agent", label: "Default Agent", field_type: FieldType::Text, env_var: None, required: false, placeholder: "assistant", advanced: true },
],
setup_steps: &["Choose an IRC server", "Enter server, nick, and channels below"],
- config_template: "[channels.irc]\nserver = \"irc.libera.chat\"\nnick = \"openfang\"",
+ config_template: "[channels.irc]\nserver = \"irc.libera.chat\"\nnick = \"omtae\"",
},
ChannelMeta {
name: "xmpp", display_name: "XMPP/Jabber", icon: "XM",
@@ -2253,12 +2336,12 @@ const CHANNEL_REGISTRY: &[ChannelMeta] = &[
setup_type: "form",
fields: &[
ChannelField { key: "oauth_token_env", label: "OAuth Token", field_type: FieldType::Secret, env_var: Some("TWITCH_OAUTH_TOKEN"), required: true, placeholder: "oauth:abc123...", advanced: false },
- ChannelField { key: "nick", label: "Bot Nickname", field_type: FieldType::Text, env_var: None, required: true, placeholder: "openfang", advanced: false },
+ ChannelField { key: "nick", label: "Bot Nickname", field_type: FieldType::Text, env_var: None, required: true, placeholder: "omtae", advanced: false },
ChannelField { key: "channels", label: "Channels (no #)", field_type: FieldType::List, env_var: None, required: true, placeholder: "mychannel", advanced: false },
ChannelField { key: "default_agent", label: "Default Agent", field_type: FieldType::Text, env_var: None, required: false, placeholder: "assistant", advanced: true },
],
setup_steps: &["Generate an OAuth token at twitchapps.com/tmi", "Enter token, nick, and channel below"],
- config_template: "[channels.twitch]\noauth_token_env = \"TWITCH_OAUTH_TOKEN\"\nnick = \"openfang\"",
+ config_template: "[channels.twitch]\noauth_token_env = \"TWITCH_OAUTH_TOKEN\"\nnick = \"omtae\"",
},
// ── Notifications (4) ───────────────────────────────────────────
ChannelMeta {
@@ -2268,7 +2351,7 @@ const CHANNEL_REGISTRY: &[ChannelMeta] = &[
quick_setup: "Just enter a topic name",
setup_type: "form",
fields: &[
- ChannelField { key: "topic", label: "Topic", field_type: FieldType::Text, env_var: None, required: true, placeholder: "openfang-alerts", advanced: false },
+ ChannelField { key: "topic", label: "Topic", field_type: FieldType::Text, env_var: None, required: true, placeholder: "omtae-alerts", advanced: false },
ChannelField { key: "server_url", label: "Server URL", field_type: FieldType::Text, env_var: None, required: false, placeholder: "https://ntfy.sh", advanced: true },
ChannelField { key: "token_env", label: "Auth Token", field_type: FieldType::Secret, env_var: Some("NTFY_TOKEN"), required: false, placeholder: "tk_abc123...", advanced: true },
ChannelField { key: "default_agent", label: "Default Agent", field_type: FieldType::Text, env_var: None, required: false, placeholder: "assistant", advanced: true },
@@ -2314,14 +2397,14 @@ const CHANNEL_REGISTRY: &[ChannelMeta] = &[
setup_type: "form",
fields: &[
ChannelField { key: "host", label: "Host", field_type: FieldType::Text, env_var: None, required: true, placeholder: "mumble.example.com", advanced: false },
- ChannelField { key: "username", label: "Username", field_type: FieldType::Text, env_var: None, required: true, placeholder: "openfang", advanced: false },
+ ChannelField { key: "username", label: "Username", field_type: FieldType::Text, env_var: None, required: true, placeholder: "omtae", advanced: false },
ChannelField { key: "password_env", label: "Server Password", field_type: FieldType::Secret, env_var: Some("MUMBLE_PASSWORD"), required: false, placeholder: "password", advanced: true },
ChannelField { key: "port", label: "Port", field_type: FieldType::Number, env_var: None, required: false, placeholder: "64738", advanced: true },
ChannelField { key: "channel", label: "Channel", field_type: FieldType::Text, env_var: None, required: false, placeholder: "Root", advanced: true },
ChannelField { key: "default_agent", label: "Default Agent", field_type: FieldType::Text, env_var: None, required: false, placeholder: "assistant", advanced: true },
],
setup_steps: &["Enter host and username below", "Optionally add a password"],
- config_template: "[channels.mumble]\nhost = \"\"\nusername = \"openfang\"",
+ config_template: "[channels.mumble]\nhost = \"\"\nusername = \"omtae\"",
},
ChannelMeta {
name: "wecom", display_name: "WeCom", icon: "WC",
@@ -2344,7 +2427,7 @@ const CHANNEL_REGISTRY: &[ChannelMeta] = &[
];
/// Check if a channel is configured (has a `[channels.xxx]` section in config).
-fn is_channel_configured(config: &openfang_types::config::ChannelsConfig, name: &str) -> bool {
+fn is_channel_configured(config: &omtae_types::config::ChannelsConfig, name: &str) -> bool {
match name {
"telegram" => config.telegram.is_some(),
"discord" => config.discord.is_some(),
@@ -2470,7 +2553,7 @@ mod channel_meta_tests {
/// Serialize a channel's config to a JSON Value for pre-populating dashboard forms.
fn channel_config_values(
- config: &openfang_types::config::ChannelsConfig,
+ config: &omtae_types::config::ChannelsConfig,
name: &str,
) -> Option {
match name {
@@ -2729,7 +2812,7 @@ pub async fn configure_channel(
}
};
- let home = openfang_kernel::config::openfang_home();
+ let home = omtae_kernel::config::omtae_home();
let secrets_path = home.join("secrets.env");
let config_path = home.join("config.toml");
let mut config_fields: HashMap = HashMap::new();
@@ -2827,7 +2910,7 @@ pub async fn remove_channel(
}
};
- let home = openfang_kernel::config::openfang_home();
+ let home = omtae_kernel::config::omtae_home();
let secrets_path = home.join("secrets.env");
let config_path = home.join("config.toml");
@@ -2963,7 +3046,7 @@ pub async fn test_channel(
/// Send a real test message to a specific channel/chat on the given platform.
async fn send_channel_test_message(channel_name: &str, target_id: &str) -> Result<(), String> {
let client = reqwest::Client::new();
- let test_msg = "OpenFang test message — your channel is connected!";
+ let test_msg = "OMTAE test message — your channel is connected!";
match channel_name {
"discord" => {
@@ -3247,7 +3330,7 @@ async fn gateway_http_get(url_with_path: &str) -> Result impl IntoResponse {
- let agents_dir = openfang_kernel::config::openfang_home().join("agents");
+ let agents_dir = omtae_kernel::config::omtae_home().join("agents");
let mut templates = Vec::new();
if let Ok(entries) = std::fs::read_dir(&agents_dir) {
@@ -3309,7 +3392,7 @@ fn get_template_category(name: &str) -> &str {
/// GET /api/templates/:name — Get template details.
pub async fn get_template(Path(name): Path) -> impl IntoResponse {
- let agents_dir = openfang_kernel::config::openfang_home().join("agents");
+ let agents_dir = omtae_kernel::config::omtae_home().join("agents");
let manifest_path = agents_dir.join(&name).join("agent.toml");
if !manifest_path.exists() {
@@ -3372,7 +3455,7 @@ pub async fn get_agent_kv(
State(state): State>,
Path(_id): Path,
) -> impl IntoResponse {
- let agent_id = openfang_kernel::kernel::shared_memory_agent_id();
+ let agent_id = omtae_kernel::kernel::shared_memory_agent_id();
match state.kernel.memory.list_kv(agent_id) {
Ok(pairs) => {
@@ -3397,7 +3480,7 @@ pub async fn get_agent_kv_key(
State(state): State>,
Path((_id, key)): Path<(String, String)>,
) -> impl IntoResponse {
- let agent_id = openfang_kernel::kernel::shared_memory_agent_id();
+ let agent_id = omtae_kernel::kernel::shared_memory_agent_id();
match state.kernel.memory.structured_get(agent_id, &key) {
Ok(Some(val)) => (
@@ -3424,7 +3507,7 @@ pub async fn set_agent_kv_key(
Path((_id, key)): Path<(String, String)>,
Json(body): Json,
) -> impl IntoResponse {
- let agent_id = openfang_kernel::kernel::shared_memory_agent_id();
+ let agent_id = omtae_kernel::kernel::shared_memory_agent_id();
let value = body.get("value").cloned().unwrap_or(body);
@@ -3448,7 +3531,7 @@ pub async fn delete_agent_kv_key(
State(state): State>,
Path((_id, key)): Path<(String, String)>,
) -> impl IntoResponse {
- let agent_id = openfang_kernel::kernel::shared_memory_agent_id();
+ let agent_id = omtae_kernel::kernel::shared_memory_agent_id();
match state.kernel.memory.structured_delete(agent_id, &key) {
Ok(()) => (
@@ -3468,6 +3551,24 @@ pub async fn delete_agent_kv_key(
/// GET /api/health — Minimal liveness probe (public, no auth required).
/// Returns only status and version to prevent information leakage.
/// Use GET /api/health/detail for full diagnostics (requires auth).
+/// GET /api/system/gpu — NVIDIA GPU telemetry (nvidia-smi).
+pub async fn system_gpu() -> impl IntoResponse {
+ let stats = crate::gpu::collect_gpu_stats().await;
+ Json(stats)
+}
+
+/// GET /api/system/drift — Report runtime/config drift (no auto-fix).
+pub async fn system_drift(State(state): State>) -> impl IntoResponse {
+ let report = state.kernel.run_drift_check(false);
+ Json(report)
+}
+
+/// POST /api/system/drift — Run drift scan and apply safe auto-remediation.
+pub async fn system_drift_remediate(State(state): State>) -> impl IntoResponse {
+ let report = state.kernel.run_drift_check(true);
+ Json(report)
+}
+
pub async fn health(State(state): State>) -> impl IntoResponse {
// Run the database check on a blocking thread so we never hold the
// std::sync::Mutex on a tokio worker thread. This prevents
@@ -3475,7 +3576,7 @@ pub async fn health(State(state): State>) -> impl IntoResponse {
// is holding the database lock for session saves.
let memory = state.kernel.memory.clone();
let db_ok = tokio::task::spawn_blocking(move || {
- let shared_id = openfang_types::agent::AgentId(uuid::Uuid::from_bytes([
+ let shared_id = omtae_types::agent::AgentId(uuid::Uuid::from_bytes([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
]));
memory.structured_get(shared_id, "__health_check__").is_ok()
@@ -3497,7 +3598,7 @@ pub async fn health_detail(State(state): State>) -> impl IntoRespo
let memory = state.kernel.memory.clone();
let db_ok = tokio::task::spawn_blocking(move || {
- let shared_id = openfang_types::agent::AgentId(uuid::Uuid::from_bytes([
+ let shared_id = omtae_types::agent::AgentId(uuid::Uuid::from_bytes([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
]));
memory.structured_get(shared_id, "__health_check__").is_ok()
@@ -3526,50 +3627,50 @@ pub async fn health_detail(State(state): State>) -> impl IntoRespo
/// GET /api/metrics — Prometheus text-format metrics.
///
-/// Returns counters and gauges for monitoring OpenFang in production:
-/// - `openfang_agents_active` — number of active agents
-/// - `openfang_uptime_seconds` — seconds since daemon started
-/// - `openfang_tokens_total` — total tokens consumed (per agent)
-/// - `openfang_tool_calls_total` — total tool calls (per agent)
-/// - `openfang_panics_total` — supervisor panic count
-/// - `openfang_restarts_total` — supervisor restart count
+/// Returns counters and gauges for monitoring OMTAE in production:
+/// - `omtae_agents_active` — number of active agents
+/// - `omtae_uptime_seconds` — seconds since daemon started
+/// - `omtae_tokens_total` — total tokens consumed (per agent)
+/// - `omtae_tool_calls_total` — total tool calls (per agent)
+/// - `omtae_panics_total` — supervisor panic count
+/// - `omtae_restarts_total` — supervisor restart count
pub async fn prometheus_metrics(State(state): State>) -> impl IntoResponse {
let mut out = String::with_capacity(2048);
// Uptime
let uptime = state.started_at.elapsed().as_secs();
- out.push_str("# HELP openfang_uptime_seconds Time since daemon started.\n");
- out.push_str("# TYPE openfang_uptime_seconds gauge\n");
- out.push_str(&format!("openfang_uptime_seconds {uptime}\n\n"));
+ out.push_str("# HELP omtae_uptime_seconds Time since daemon started.\n");
+ out.push_str("# TYPE omtae_uptime_seconds gauge\n");
+ out.push_str(&format!("omtae_uptime_seconds {uptime}\n\n"));
// Active agents
let agents = state.kernel.registry.list();
let active = agents
.iter()
- .filter(|a| matches!(a.state, openfang_types::agent::AgentState::Running))
+ .filter(|a| matches!(a.state, omtae_types::agent::AgentState::Running))
.count();
- out.push_str("# HELP openfang_agents_active Number of active agents.\n");
- out.push_str("# TYPE openfang_agents_active gauge\n");
- out.push_str(&format!("openfang_agents_active {active}\n"));
- out.push_str("# HELP openfang_agents_total Total number of registered agents.\n");
- out.push_str("# TYPE openfang_agents_total gauge\n");
- out.push_str(&format!("openfang_agents_total {}\n\n", agents.len()));
+ out.push_str("# HELP omtae_agents_active Number of active agents.\n");
+ out.push_str("# TYPE omtae_agents_active gauge\n");
+ out.push_str(&format!("omtae_agents_active {active}\n"));
+ out.push_str("# HELP omtae_agents_total Total number of registered agents.\n");
+ out.push_str("# TYPE omtae_agents_total gauge\n");
+ out.push_str(&format!("omtae_agents_total {}\n\n", agents.len()));
// Per-agent token and tool usage
- out.push_str("# HELP openfang_tokens_total Total tokens consumed (rolling hourly window).\n");
- out.push_str("# TYPE openfang_tokens_total gauge\n");
- out.push_str("# HELP openfang_tool_calls_total Total tool calls (rolling hourly window).\n");
- out.push_str("# TYPE openfang_tool_calls_total gauge\n");
+ out.push_str("# HELP omtae_tokens_total Total tokens consumed (rolling hourly window).\n");
+ out.push_str("# TYPE omtae_tokens_total gauge\n");
+ out.push_str("# HELP omtae_tool_calls_total Total tool calls (rolling hourly window).\n");
+ out.push_str("# TYPE omtae_tool_calls_total gauge\n");
for agent in &agents {
let name = &agent.name;
let provider = &agent.manifest.model.provider;
let model = &agent.manifest.model.model;
if let Some((tokens, tools)) = state.kernel.scheduler.get_usage(agent.id) {
out.push_str(&format!(
- "openfang_tokens_total{{agent=\"{name}\",provider=\"{provider}\",model=\"{model}\"}} {tokens}\n"
+ "omtae_tokens_total{{agent=\"{name}\",provider=\"{provider}\",model=\"{model}\"}} {tokens}\n"
));
out.push_str(&format!(
- "openfang_tool_calls_total{{agent=\"{name}\"}} {tools}\n"
+ "omtae_tool_calls_total{{agent=\"{name}\"}} {tools}\n"
));
}
}
@@ -3577,21 +3678,21 @@ pub async fn prometheus_metrics(State(state): State>) -> impl Into
// Supervisor health
let health = state.kernel.supervisor.health();
- out.push_str("# HELP openfang_panics_total Total supervisor panics since start.\n");
- out.push_str("# TYPE openfang_panics_total counter\n");
- out.push_str(&format!("openfang_panics_total {}\n", health.panic_count));
- out.push_str("# HELP openfang_restarts_total Total supervisor restarts since start.\n");
- out.push_str("# TYPE openfang_restarts_total counter\n");
+ out.push_str("# HELP omtae_panics_total Total supervisor panics since start.\n");
+ out.push_str("# TYPE omtae_panics_total counter\n");
+ out.push_str(&format!("omtae_panics_total {}\n", health.panic_count));
+ out.push_str("# HELP omtae_restarts_total Total supervisor restarts since start.\n");
+ out.push_str("# TYPE omtae_restarts_total counter\n");
out.push_str(&format!(
- "openfang_restarts_total {}\n\n",
+ "omtae_restarts_total {}\n\n",
health.restart_count
));
// Version info
- out.push_str("# HELP openfang_info OpenFang version and build info.\n");
- out.push_str("# TYPE openfang_info gauge\n");
+ out.push_str("# HELP omtae_info OMTAE version and build info.\n");
+ out.push_str("# TYPE omtae_info gauge\n");
out.push_str(&format!(
- "openfang_info{{version=\"{}\"}} 1\n",
+ "omtae_info{{version=\"{}\"}} 1\n",
env!("CARGO_PKG_VERSION")
));
@@ -3612,7 +3713,7 @@ pub async fn prometheus_metrics(State(state): State>) -> impl Into
/// GET /api/skills — List installed skills.
pub async fn list_skills(State(state): State>) -> impl IntoResponse {
let skills_dir = state.kernel.config.home_dir.join("skills");
- let mut registry = openfang_skills::registry::SkillRegistry::new(skills_dir);
+ let mut registry = omtae_skills::registry::SkillRegistry::new(skills_dir);
let _ = registry.load_all();
// Snapshot of user-provided overrides for the `config_resolved_count`.
@@ -3635,16 +3736,16 @@ pub async fn list_skills(State(state): State>) -> impl IntoRespons
.iter()
.map(|s| {
let source = match &s.manifest.source {
- Some(openfang_skills::SkillSource::ClawHub { slug, version }) => {
+ Some(omtae_skills::SkillSource::ClawHub { slug, version }) => {
serde_json::json!({"type": "clawhub", "slug": slug, "version": version})
}
- Some(openfang_skills::SkillSource::OpenClaw) => {
+ Some(omtae_skills::SkillSource::OpenClaw) => {
serde_json::json!({"type": "openclaw"})
}
- Some(openfang_skills::SkillSource::Bundled) => {
+ Some(omtae_skills::SkillSource::Bundled) => {
serde_json::json!({"type": "bundled"})
}
- Some(openfang_skills::SkillSource::Native) | None => {
+ Some(omtae_skills::SkillSource::Native) | None => {
serde_json::json!({"type": "local"})
}
};
@@ -3700,10 +3801,10 @@ pub async fn install_skill(
Json(req): Json,
) -> impl IntoResponse {
let skills_dir = state.kernel.config.home_dir.join("skills");
- let config = openfang_skills::marketplace::MarketplaceConfig::default();
- let client = openfang_skills::marketplace::MarketplaceClient::new(config);
+ let config = omtae_skills::marketplace::MarketplaceConfig::default();
+ let client = omtae_skills::marketplace::MarketplaceClient::new(config);
- let opts = openfang_skills::installer::InstallOptions {
+ let opts = omtae_skills::installer::InstallOptions {
require_signed: req.require_signed,
allowed_signer_keys: req.allowed_signer_keys.clone(),
};
@@ -3739,7 +3840,7 @@ pub async fn uninstall_skill(
Json(req): Json,
) -> impl IntoResponse {
let skills_dir = state.kernel.config.home_dir.join("skills");
- let mut registry = openfang_skills::registry::SkillRegistry::new(skills_dir);
+ let mut registry = omtae_skills::registry::SkillRegistry::new(skills_dir);
let _ = registry.load_all();
match registry.remove(&req.name) {
@@ -3760,7 +3861,7 @@ pub async fn uninstall_skill(
/// POST /api/skills/reload — Hot-reload the skill registry from disk.
///
-/// Called by the CLI after `openfang skill install` to notify the running
+/// Called by the CLI after `omtae skill install` to notify the running
/// daemon that new skill files were added to the skills directory (#752).
pub async fn reload_skills(State(state): State>) -> impl IntoResponse {
state.kernel.reload_skills();
@@ -3778,7 +3879,7 @@ pub async fn audit_append(
State(state): State>,
Json(req): Json,
) -> impl IntoResponse {
- use openfang_runtime::audit::AuditAction;
+ use omtae_runtime::audit::AuditAction;
// SECURITY: bound input sizes so a wrapper cannot wedge the chain with
// unbounded strings. The audit table stores TEXT columns and the chain
@@ -3882,8 +3983,8 @@ pub async fn marketplace_search(
return Json(serde_json::json!({"results": [], "total": 0}));
}
- let config = openfang_skills::marketplace::MarketplaceConfig::default();
- let client = openfang_skills::marketplace::MarketplaceClient::new(config);
+ let config = omtae_skills::marketplace::MarketplaceConfig::default();
+ let client = omtae_skills::marketplace::MarketplaceClient::new(config);
match client.search(&query).await {
Ok(results) => {
@@ -3942,7 +4043,7 @@ pub async fn clawhub_search(
}
let cache_dir = state.kernel.config.home_dir.join(".cache").join("clawhub");
- let client = openfang_skills::clawhub::ClawHubClient::new(cache_dir);
+ let client = omtae_skills::clawhub::ClawHubClient::new(cache_dir);
let skills_dir = state.kernel.config.home_dir.join("skills");
match client.search(&query, limit).await {
@@ -3999,11 +4100,11 @@ pub async fn clawhub_browse(
Query(params): Query>,
) -> impl IntoResponse {
let sort = match params.get("sort").map(|s| s.as_str()) {
- Some("downloads") => openfang_skills::clawhub::ClawHubSort::Downloads,
- Some("stars") => openfang_skills::clawhub::ClawHubSort::Stars,
- Some("updated") => openfang_skills::clawhub::ClawHubSort::Updated,
- Some("rating") => openfang_skills::clawhub::ClawHubSort::Rating,
- _ => openfang_skills::clawhub::ClawHubSort::Trending,
+ Some("downloads") => omtae_skills::clawhub::ClawHubSort::Downloads,
+ Some("stars") => omtae_skills::clawhub::ClawHubSort::Stars,
+ Some("updated") => omtae_skills::clawhub::ClawHubSort::Updated,
+ Some("rating") => omtae_skills::clawhub::ClawHubSort::Rating,
+ _ => omtae_skills::clawhub::ClawHubSort::Trending,
};
let limit: u32 = params
@@ -4022,7 +4123,7 @@ pub async fn clawhub_browse(
}
let cache_dir = state.kernel.config.home_dir.join(".cache").join("clawhub");
- let client = openfang_skills::clawhub::ClawHubClient::new(cache_dir);
+ let client = omtae_skills::clawhub::ClawHubClient::new(cache_dir);
let skills_dir = state.kernel.config.home_dir.join("skills");
match client.browse(sort, limit, cursor).await {
@@ -4068,7 +4169,7 @@ pub async fn clawhub_skill_detail(
Path(slug): Path,
) -> impl IntoResponse {
let cache_dir = state.kernel.config.home_dir.join(".cache").join("clawhub");
- let client = openfang_skills::clawhub::ClawHubClient::new(cache_dir);
+ let client = omtae_skills::clawhub::ClawHubClient::new(cache_dir);
let skills_dir = state.kernel.config.home_dir.join("skills");
let is_installed = client.is_installed(&slug, &skills_dir);
@@ -4132,7 +4233,7 @@ pub async fn clawhub_skill_code(
Path(slug): Path,
) -> impl IntoResponse {
let cache_dir = state.kernel.config.home_dir.join(".cache").join("clawhub");
- let client = openfang_skills::clawhub::ClawHubClient::new(cache_dir);
+ let client = omtae_skills::clawhub::ClawHubClient::new(cache_dir);
// Try to fetch SKILL.md first, then fallback to package.json
let mut code = String::new();
@@ -4176,7 +4277,7 @@ pub async fn clawhub_install(
) -> impl IntoResponse {
let skills_dir = state.kernel.config.home_dir.join("skills");
let cache_dir = state.kernel.config.home_dir.join(".cache").join("clawhub");
- let client = openfang_skills::clawhub::ClawHubClient::new(cache_dir);
+ let client = omtae_skills::clawhub::ClawHubClient::new(cache_dir);
// Check if already installed
if client.is_installed(&req.slug, &skills_dir) {
@@ -4226,11 +4327,11 @@ pub async fn clawhub_install(
}
Err(e) => {
let msg = format!("{e}");
- let status = if matches!(e, openfang_skills::SkillError::SecurityBlocked(_)) {
+ let status = if matches!(e, omtae_skills::SkillError::SecurityBlocked(_)) {
StatusCode::FORBIDDEN
} else if is_clawhub_rate_limit(&e) {
StatusCode::TOO_MANY_REQUESTS
- } else if matches!(e, openfang_skills::SkillError::Network(_)) {
+ } else if matches!(e, omtae_skills::SkillError::Network(_)) {
StatusCode::BAD_GATEWAY
} else {
StatusCode::INTERNAL_SERVER_ERROR
@@ -4242,15 +4343,15 @@ pub async fn clawhub_install(
}
/// Check whether a SkillError represents a ClawHub rate-limit (429).
-fn is_clawhub_rate_limit(err: &openfang_skills::SkillError) -> bool {
- matches!(err, openfang_skills::SkillError::RateLimited(_))
+fn is_clawhub_rate_limit(err: &omtae_skills::SkillError) -> bool {
+ matches!(err, omtae_skills::SkillError::RateLimited(_))
}
/// Convert a browse entry (nested stats/tags) to a flat JSON object for the frontend.
fn clawhub_browse_entry_to_json(
- entry: &openfang_skills::clawhub::ClawHubBrowseEntry,
+ entry: &omtae_skills::clawhub::ClawHubBrowseEntry,
) -> serde_json::Value {
- let version = openfang_skills::clawhub::ClawHubClient::entry_version(entry);
+ let version = omtae_skills::clawhub::ClawHubClient::entry_version(entry);
serde_json::json!({
"slug": entry.slug,
"name": entry.display_name,
@@ -4800,7 +4901,7 @@ pub async fn upsert_hand(
pub async fn activate_hand(
State(state): State>,
Path(hand_id): Path,
- body: Option>,
+ body: Option>,
) -> impl IntoResponse {
let (config, instance_name) = match body.map(|b| b.0) {
Some(r) => (r.config, r.instance_name),
@@ -4821,7 +4922,7 @@ pub async fn activate_hand(
if let Some(entry) = entry {
if !matches!(
entry.manifest.schedule,
- openfang_types::agent::ScheduleMode::Reactive
+ omtae_types::agent::ScheduleMode::Reactive
) {
state.kernel.start_background_for_agent(
agent_id,
@@ -5025,7 +5126,7 @@ pub async fn hand_stats(
};
// Read dashboard metrics from shared structured memory (memory_store uses shared namespace)
- let shared_id = openfang_kernel::kernel::shared_memory_agent_id();
+ let shared_id = omtae_kernel::kernel::shared_memory_agent_id();
let mut metrics = serde_json::Map::new();
for metric in &def.dashboard.metrics {
// Try shared memory first (where memory_store tool writes), fall back to agent-specific
@@ -5106,7 +5207,7 @@ pub async fn hand_instance_browser(
.browser_ctx
.send_command(
&agent_id_str,
- openfang_runtime::browser::BrowserCommand::ReadPage,
+ omtae_runtime::browser::BrowserCommand::ReadPage,
)
.await
{
@@ -5119,7 +5220,7 @@ pub async fn hand_instance_browser(
if content.len() > 2000 {
content = format!(
"{}... (truncated)",
- openfang_types::truncate_str(&content, 2000)
+ omtae_types::truncate_str(&content, 2000)
);
}
}
@@ -5136,7 +5237,7 @@ pub async fn hand_instance_browser(
.browser_ctx
.send_command(
&agent_id_str,
- openfang_runtime::browser::BrowserCommand::Screenshot,
+ omtae_runtime::browser::BrowserCommand::Screenshot,
)
.await
{
@@ -5176,20 +5277,20 @@ pub async fn list_mcp_servers(State(state): State>) -> impl IntoRe
.iter()
.map(|s| {
let transport = match &s.transport {
- openfang_types::config::McpTransportEntry::Stdio { command, args } => {
+ omtae_types::config::McpTransportEntry::Stdio { command, args } => {
serde_json::json!({
"type": "stdio",
"command": command,
"args": args,
})
}
- openfang_types::config::McpTransportEntry::Sse { url } => {
+ omtae_types::config::McpTransportEntry::Sse { url } => {
serde_json::json!({
"type": "sse",
"url": url,
})
}
- openfang_types::config::McpTransportEntry::Http { url } => {
+ omtae_types::config::McpTransportEntry::Http { url } => {
serde_json::json!({
"type": "http",
"url": url,
@@ -5695,7 +5796,7 @@ pub async fn agent_budget_status(
};
let quota = &entry.manifest.resources;
- let usage_store = openfang_memory::usage::UsageStore::new(state.kernel.memory.usage_conn());
+ let usage_store = omtae_memory::usage::UsageStore::new(state.kernel.memory.usage_conn());
let hourly = usage_store.query_hourly(agent_id).unwrap_or(0.0);
let daily = usage_store.query_daily(agent_id).unwrap_or(0.0);
let monthly = usage_store.query_monthly(agent_id).unwrap_or(0.0);
@@ -5735,7 +5836,7 @@ pub async fn agent_budget_status(
/// GET /api/budget/agents — Per-agent cost ranking (top spenders).
pub async fn agent_budget_ranking(State(state): State>) -> impl IntoResponse {
- let usage_store = openfang_memory::usage::UsageStore::new(state.kernel.memory.usage_conn());
+ let usage_store = omtae_memory::usage::UsageStore::new(state.kernel.memory.usage_conn());
let agents: Vec = state
.kernel
.registry
@@ -5832,7 +5933,7 @@ pub async fn delete_session(
Path(id): Path,
) -> impl IntoResponse {
let session_id = match id.parse::() {
- Ok(u) => openfang_types::agent::SessionId(u),
+ Ok(u) => omtae_types::agent::SessionId(u),
Err(_) => {
return (
StatusCode::BAD_REQUEST,
@@ -5860,7 +5961,7 @@ pub async fn set_session_label(
Json(req): Json,
) -> impl IntoResponse {
let session_id = match id.parse::() {
- Ok(u) => openfang_types::agent::SessionId(u),
+ Ok(u) => omtae_types::agent::SessionId(u),
Err(_) => {
return (
StatusCode::BAD_REQUEST,
@@ -5873,7 +5974,7 @@ pub async fn set_session_label(
// Validate label if present
if let Some(lbl) = label {
- if let Err(e) = openfang_types::agent::SessionLabel::new(lbl) {
+ if let Err(e) = omtae_types::agent::SessionLabel::new(lbl) {
return (
StatusCode::BAD_REQUEST,
Json(serde_json::json!({"error": e.to_string()})),
@@ -5903,7 +6004,7 @@ pub async fn find_session_by_label(
Path((agent_id_str, label)): Path<(String, String)>,
) -> impl IntoResponse {
let agent_id = match agent_id_str.parse::() {
- Ok(u) => openfang_types::agent::AgentId(u),
+ Ok(u) => omtae_types::agent::AgentId(u),
Err(_) => {
// Try name lookup
match state.kernel.registry.find_by_name(&agent_id_str) {
@@ -6205,9 +6306,9 @@ pub async fn security_status(State(state): State>) -> impl IntoRes
/// GET /api/migrate/detect — Auto-detect OpenClaw installation.
pub async fn migrate_detect() -> impl IntoResponse {
- match openfang_migrate::openclaw::detect_openclaw_home() {
+ match omtae_migrate::openclaw::detect_openclaw_home() {
Some(path) => {
- let scan = openfang_migrate::openclaw::scan_openclaw_workspace(&path);
+ let scan = omtae_migrate::openclaw::scan_openclaw_workspace(&path);
(
StatusCode::OK,
Json(serde_json::json!({
@@ -6237,16 +6338,16 @@ pub async fn migrate_scan(Json(req): Json) -> impl IntoRespo
Json(serde_json::json!({"error": "Directory not found"})),
);
}
- let scan = openfang_migrate::openclaw::scan_openclaw_workspace(&path);
+ let scan = omtae_migrate::openclaw::scan_openclaw_workspace(&path);
(StatusCode::OK, Json(serde_json::json!(scan)))
}
/// POST /api/migrate — Run migration from another agent framework.
pub async fn run_migrate(Json(req): Json) -> impl IntoResponse {
let source = match req.source.as_str() {
- "openclaw" => openfang_migrate::MigrateSource::OpenClaw,
- "langchain" => openfang_migrate::MigrateSource::LangChain,
- "autogpt" => openfang_migrate::MigrateSource::AutoGpt,
+ "openclaw" => omtae_migrate::MigrateSource::OpenClaw,
+ "langchain" => omtae_migrate::MigrateSource::LangChain,
+ "autogpt" => omtae_migrate::MigrateSource::AutoGpt,
other => {
return (
StatusCode::BAD_REQUEST,
@@ -6257,14 +6358,14 @@ pub async fn run_migrate(Json(req): Json) -> impl IntoResponse {
}
};
- let options = openfang_migrate::MigrateOptions {
+ let options = omtae_migrate::MigrateOptions {
source,
source_dir: std::path::PathBuf::from(&req.source_dir),
target_dir: std::path::PathBuf::from(&req.target_dir),
dry_run: req.dry_run,
};
- match openfang_migrate::run_migration(&options) {
+ match omtae_migrate::run_migration(&options) {
Ok(report) => {
let imported: Vec = report
.imported
@@ -6313,6 +6414,200 @@ pub async fn run_migrate(Json(req): Json) -> impl IntoResponse {
// ── Model Catalog Endpoints ─────────────────────────────────────────
+/// GET /api/models/profiles — vLLM swap profiles from `~/.omtae/models.toml`.
+pub async fn list_model_profiles(State(state): State>) -> impl IntoResponse {
+ let home = &state.kernel.config.home_dir;
+ let profiles = match crate::model_profiles::load_profiles(home) {
+ Ok(p) => p,
+ Err(e) => {
+ return (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(serde_json::json!({"error": e})),
+ );
+ }
+ };
+ let active = crate::model_profiles::read_active_profile_id(home)
+ .or_else(|| {
+ profiles
+ .iter()
+ .find(|p| p.omtae_model_id == state.kernel.config.default_model.model)
+ .map(|p| p.id.clone())
+ });
+ let list: Vec = profiles
+ .iter()
+ .map(|p| {
+ serde_json::json!({
+ "id": p.id,
+ "display_name": p.display_name,
+ "vllm_path": p.vllm_path,
+ "tensor_parallel": p.tensor_parallel,
+ "max_model_len": p.max_model_len,
+ "gpu_mem_util": p.gpu_mem_util,
+ "vllm_served_name": p.vllm_served_name,
+ "omtae_model_id": p.omtae_model_id,
+ "start_script": p.start_script,
+ "quantization": p.quantization,
+ "on_disk": crate::model_profiles::profile_available_on_disk(p),
+ })
+ })
+ .collect();
+ (
+ StatusCode::OK,
+ Json(serde_json::json!({
+ "profiles": list,
+ "active_profile": active,
+ "models_toml": crate::model_profiles::models_toml_path(home).display().to_string(),
+ })),
+ )
+}
+
+/// GET /api/models/active — Active vLLM profile + OMTAE default model.
+pub async fn get_active_model_profile(State(state): State>) -> impl IntoResponse {
+ let home = &state.kernel.config.home_dir;
+ let profiles = crate::model_profiles::load_profiles(home).unwrap_or_default();
+ let active_id = crate::model_profiles::read_active_profile_id(home);
+ let active_profile = active_id
+ .as_ref()
+ .and_then(|id| crate::model_profiles::find_profile(&profiles, id));
+ let dm = effective_default_model(&state.kernel);
+ (
+ StatusCode::OK,
+ Json(serde_json::json!({
+ "active_profile": active_id,
+ "active_profile_display": active_profile.map(|p| &p.display_name),
+ "omtae_model_id": dm.model,
+ "default_provider": dm.provider,
+ "vllm_restart_command": "omtae-model use ",
+ "cli_status_command": "omtae-model status",
+ })),
+ )
+}
+
+/// PUT /api/models/active — Set OMTAE default model from a profile (vLLM restart via CLI).
+pub async fn set_active_model_profile(
+ State(state): State>,
+ Json(body): Json,
+) -> impl IntoResponse {
+ let profile_id = match body.get("profile_id").and_then(|v| v.as_str()) {
+ Some(id) => id.to_string(),
+ None => {
+ return (
+ StatusCode::BAD_REQUEST,
+ Json(serde_json::json!({"status": "error", "error": "missing profile_id"})),
+ );
+ }
+ };
+ let restart_vllm = body
+ .get("restart_vllm")
+ .and_then(|v| v.as_bool())
+ .unwrap_or(false);
+
+ let home = &state.kernel.config.home_dir;
+ let profiles = match crate::model_profiles::load_profiles(home) {
+ Ok(p) => p,
+ Err(e) => {
+ return (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(serde_json::json!({"status": "error", "error": e})),
+ );
+ }
+ };
+ let Some(profile) = crate::model_profiles::find_profile(&profiles, &profile_id) else {
+ return (
+ StatusCode::NOT_FOUND,
+ Json(serde_json::json!({
+ "status": "error",
+ "error": format!("unknown profile: {profile_id}"),
+ "hint": "omtae-model list"
+ })),
+ );
+ };
+ if !crate::model_profiles::profile_available_on_disk(profile) {
+ return (
+ StatusCode::BAD_REQUEST,
+ Json(serde_json::json!({
+ "status": "error",
+ "error": format!("weights not found at {}", profile.vllm_path),
+ "profile_id": profile_id,
+ })),
+ );
+ }
+
+ if let Err(e) = crate::model_profiles::write_active_profile_id(home, &profile_id) {
+ return (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(serde_json::json!({"status": "error", "error": e})),
+ );
+ }
+ if let Err(e) = crate::model_profiles::persist_default_model_id(home, &profile.omtae_model_id) {
+ return (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(serde_json::json!({"status": "error", "error": e})),
+ );
+ }
+
+ let reload_status = match state.kernel.reload_config() {
+ Ok(plan) if plan.restart_required => "applied_partial",
+ Ok(_) => "applied",
+ Err(e) => {
+ return (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(serde_json::json!({"status": "error", "error": format!("reload failed: {e}")})),
+ );
+ }
+ };
+
+ state.kernel.audit_log.record(
+ "system",
+ omtae_runtime::audit::AuditAction::ConfigChange,
+ format!("active model profile: {profile_id} -> {}", profile.omtae_model_id),
+ "completed",
+ );
+
+ let mut vllm_note = format!(
+ "OMTAE default set to {}. Restart vLLM: omtae-model use {}",
+ profile.omtae_model_id, profile_id
+ );
+ let mut vllm_restarted = false;
+
+ if restart_vllm {
+ let allowed = std::env::var("OMTAE_ALLOW_VLLM_RESTART")
+ .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
+ .unwrap_or(false);
+ if allowed {
+ let script = profile.start_script.clone();
+ match tokio::process::Command::new("/usr/bin/bash")
+ .arg(&script)
+ .spawn()
+ {
+ Ok(_) => {
+ vllm_restarted = true;
+ vllm_note = format!("vLLM start script launched: {script}");
+ }
+ Err(e) => {
+ vllm_note = format!("vLLM restart failed to spawn: {e}. Run: omtae-model use {profile_id}");
+ }
+ }
+ } else {
+ vllm_note = format!(
+ "Set OMTAE_ALLOW_VLLM_RESTART=1 on daemon to restart from API, or run: omtae-model use {profile_id}"
+ );
+ }
+ }
+
+ (
+ StatusCode::OK,
+ Json(serde_json::json!({
+ "status": reload_status,
+ "profile_id": profile_id,
+ "omtae_model_id": profile.omtae_model_id,
+ "vllm_served_name": profile.vllm_served_name,
+ "vllm_restarted": vllm_restarted,
+ "message": vllm_note,
+ })),
+ )
+}
+
/// GET /api/models — List all models in the catalog.
///
/// Query parameters:
@@ -6352,7 +6647,7 @@ pub async fn list_models(
if available_only {
let provider = catalog.get_provider(&m.provider);
if let Some(p) = provider {
- if p.auth_status == openfang_types::model_catalog::AuthStatus::Missing {
+ if p.auth_status == omtae_types::model_catalog::AuthStatus::Missing {
return false;
}
}
@@ -6363,8 +6658,8 @@ pub async fn list_models(
// Custom models from unknown providers are assumed available
let available = catalog
.get_provider(&m.provider)
- .map(|p| p.auth_status != openfang_types::model_catalog::AuthStatus::Missing)
- .unwrap_or(m.tier == openfang_types::model_catalog::ModelTier::Custom);
+ .map(|p| p.auth_status != omtae_types::model_catalog::AuthStatus::Missing)
+ .unwrap_or(m.tier == omtae_types::model_catalog::ModelTier::Custom);
serde_json::json!({
"id": m.id,
"display_name": m.display_name,
@@ -6437,8 +6732,8 @@ pub async fn get_model(
Some(m) => {
let available = catalog
.get_provider(&m.provider)
- .map(|p| p.auth_status != openfang_types::model_catalog::AuthStatus::Missing)
- .unwrap_or(m.tier == openfang_types::model_catalog::ModelTier::Custom);
+ .map(|p| p.auth_status != omtae_types::model_catalog::AuthStatus::Missing)
+ .unwrap_or(m.tier == omtae_types::model_catalog::ModelTier::Custom);
(
StatusCode::OK,
Json(serde_json::json!({
@@ -6474,7 +6769,7 @@ pub async fn get_model(
/// endpoint responds instantly on repeated dashboard loads even when local
/// providers are unreachable (fixes #474).
pub async fn list_providers(State(state): State>) -> impl IntoResponse {
- let provider_list: Vec = {
+ let provider_list: Vec = {
let catalog = state
.kernel
.model_catalog
@@ -6496,13 +6791,18 @@ pub async fn list_providers(State(state): State>) -> impl IntoResp
let probe_futures: Vec<_> = local_providers
.iter()
.map(|(_, id, url)| {
- openfang_runtime::provider_health::probe_provider_cached(id, url, cache)
+ omtae_runtime::provider_health::probe_provider_cached(
+ id,
+ url,
+ None,
+ cache,
+ )
})
.collect();
let probe_results = futures::future::join_all(probe_futures).await;
// Index probe results by provider list position for O(1) lookup
- let mut probe_map: HashMap =
+ let mut probe_map: HashMap =
HashMap::with_capacity(local_providers.len());
for ((idx, _, _), result) in local_providers.iter().zip(probe_results) {
probe_map.insert(*idx, result);
@@ -6556,7 +6856,7 @@ pub async fn list_providers(State(state): State>) -> impl IntoResp
/// POST /api/models/custom — Add a custom model to the catalog.
///
-/// Persists to `~/.openfang/custom_models.json` and makes the model immediately
+/// Persists to `~/.omtae/custom_models.json` and makes the model immediately
/// available for agent assignment.
pub async fn add_custom_model(
State(state): State>,
@@ -6594,11 +6894,11 @@ pub async fn add_custom_model(
.unwrap_or(&id)
.to_string();
- let entry = openfang_types::model_catalog::ModelCatalogEntry {
+ let entry = omtae_types::model_catalog::ModelCatalogEntry {
id: id.clone(),
display_name: display,
provider: provider.clone(),
- tier: openfang_types::model_catalog::ModelTier::Custom,
+ tier: omtae_types::model_catalog::ModelTier::Custom,
context_window,
max_output_tokens: max_output,
input_cost_per_m: body
@@ -6692,15 +6992,15 @@ pub async fn a2a_agent_card(State(state): State>) -> impl IntoResp
let base_url = format!("http://{}", state.kernel.config.api_listen);
if let Some(first) = agents.first() {
- let card = openfang_runtime::a2a::build_agent_card(&first.manifest, &base_url);
+ let card = omtae_runtime::a2a::build_agent_card(&first.manifest, &base_url);
(
StatusCode::OK,
Json(serde_json::to_value(&card).unwrap_or_default()),
)
} else {
let card = serde_json::json!({
- "name": "openfang",
- "description": "OpenFang Agent OS — no agents spawned yet",
+ "name": "omtae",
+ "description": "OMTAE Agent OS — no agents spawned yet",
"url": format!("{base_url}/a2a"),
"version": "0.1.0",
"capabilities": { "streaming": true },
@@ -6720,7 +7020,7 @@ pub async fn a2a_list_agents(State(state): State>) -> impl IntoRes
let cards: Vec = agents
.iter()
.map(|entry| {
- let card = openfang_runtime::a2a::build_agent_card(&entry.manifest, &base_url);
+ let card = omtae_runtime::a2a::build_agent_card(&entry.manifest, &base_url);
serde_json::to_value(&card).unwrap_or_default()
})
.collect();
@@ -6768,13 +7068,13 @@ pub async fn a2a_send_task(
let session_id = request["params"]["sessionId"].as_str().map(String::from);
// Create the task in the store as Working
- let task = openfang_runtime::a2a::A2aTask {
+ let task = omtae_runtime::a2a::A2aTask {
id: task_id.clone(),
session_id: session_id.clone(),
- status: openfang_runtime::a2a::A2aTaskStatus::Working.into(),
- messages: vec![openfang_runtime::a2a::A2aMessage {
+ status: omtae_runtime::a2a::A2aTaskStatus::Working.into(),
+ messages: vec![omtae_runtime::a2a::A2aMessage {
role: "user".to_string(),
- parts: vec![openfang_runtime::a2a::A2aPart::Text {
+ parts: vec![omtae_runtime::a2a::A2aPart::Text {
text: message_text.clone(),
}],
}],
@@ -6785,9 +7085,9 @@ pub async fn a2a_send_task(
// Send message to agent
match state.kernel.send_message(agent.id, &message_text).await {
Ok(result) => {
- let response_msg = openfang_runtime::a2a::A2aMessage {
+ let response_msg = omtae_runtime::a2a::A2aMessage {
role: "agent".to_string(),
- parts: vec![openfang_runtime::a2a::A2aPart::Text {
+ parts: vec![omtae_runtime::a2a::A2aPart::Text {
text: result.response,
}],
};
@@ -6807,9 +7107,9 @@ pub async fn a2a_send_task(
}
}
Err(e) => {
- let error_msg = openfang_runtime::a2a::A2aMessage {
+ let error_msg = omtae_runtime::a2a::A2aMessage {
role: "agent".to_string(),
- parts: vec![openfang_runtime::a2a::A2aPart::Text {
+ parts: vec![omtae_runtime::a2a::A2aPart::Text {
text: format!("Error: {e}"),
}],
};
@@ -6908,7 +7208,7 @@ pub async fn a2a_discover_external(
}
};
- let client = openfang_runtime::a2a::A2aClient::new();
+ let client = omtae_runtime::a2a::A2aClient::new();
match client.discover(&url).await {
Ok(card) => {
let card_json = serde_json::to_value(&card).unwrap_or_default();
@@ -6966,7 +7266,7 @@ pub async fn a2a_send_external(
};
let session_id = body["session_id"].as_str();
- let client = openfang_runtime::a2a::A2aClient::new();
+ let client = omtae_runtime::a2a::A2aClient::new();
match client.send_task(&url, &message, session_id).await {
Ok(task) => (
StatusCode::OK,
@@ -6995,7 +7295,7 @@ pub async fn a2a_external_task_status(
}
};
- let client = openfang_runtime::a2a::A2aClient::new();
+ let client = omtae_runtime::a2a::A2aClient::new();
match client.get_task(&url, &task_id).await {
Ok(task) => (
StatusCode::OK,
@@ -7027,7 +7327,7 @@ pub async fn mcp_http(
.read()
.unwrap_or_else(|e| e.into_inner());
for skill_tool in registry.all_tool_definitions() {
- tools.push(openfang_types::tool::ToolDefinition {
+ tools.push(omtae_types::tool::ToolDefinition {
name: skill_tool.name.clone(),
description: skill_tool.description.clone(),
input_schema: skill_tool.input_schema.clone(),
@@ -7065,9 +7365,9 @@ pub async fn mcp_http(
.snapshot();
// Execute the tool via the kernel's tool runner
- let kernel_handle: Arc =
- state.kernel.clone() as Arc;
- let result = openfang_runtime::tool_runner::execute_tool(
+ let kernel_handle: Arc =
+ state.kernel.clone() as Arc;
+ let result = omtae_runtime::tool_runner::execute_tool(
"mcp-http",
tool_name,
&arguments,
@@ -7107,7 +7407,7 @@ pub async fn mcp_http(
}
// For non-tools/call methods (initialize, tools/list, etc.), delegate to the handler
- let response = openfang_runtime::mcp_server::handle_mcp_request(&request, &tools).await;
+ let response = omtae_runtime::mcp_server::handle_mcp_request(&request, &tools).await;
Json(response)
}
@@ -7179,7 +7479,7 @@ pub async fn switch_agent_session(
}
};
let session_id = match session_id_str.parse::() {
- Ok(uuid) => openfang_types::agent::SessionId(uuid),
+ Ok(uuid) => omtae_types::agent::SessionId(uuid),
Err(_) => {
return (
StatusCode::BAD_REQUEST,
@@ -7333,11 +7633,19 @@ pub async fn stop_agent(
match state.kernel.stop_agent_run(agent_id) {
Ok(true) => (
StatusCode::OK,
- Json(serde_json::json!({"status": "ok", "message": "Run cancelled"})),
+ Json(serde_json::json!({
+ "status": "ok",
+ "message": "Run cancelled",
+ "background_paused": state.kernel.background.is_paused(agent_id),
+ })),
),
Ok(false) => (
StatusCode::OK,
- Json(serde_json::json!({"status": "ok", "message": "No active run"})),
+ Json(serde_json::json!({
+ "status": "ok",
+ "message": "No active run",
+ "background_paused": state.kernel.background.is_paused(agent_id),
+ })),
),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
@@ -7596,7 +7904,7 @@ pub async fn get_agent_mcp_servers(
if let Ok(mcp_tools) = state.kernel.mcp_tools.lock() {
let mut seen = std::collections::HashSet::new();
for tool in mcp_tools.iter() {
- if let Some(server) = openfang_runtime::mcp::extract_mcp_server(&tool.name) {
+ if let Some(server) = omtae_runtime::mcp::extract_mcp_server(&tool.name) {
if seen.insert(server.to_string()) {
available.push(server.to_string());
}
@@ -7660,7 +7968,7 @@ pub async fn set_agent_mcp_servers(
/// POST /api/providers/{name}/key — Save an API key for a provider.
///
-/// SECURITY: Writes to `~/.openfang/secrets.env`, sets env var in process,
+/// SECURITY: Writes to `~/.omtae/secrets.env`, sets env var in process,
/// and refreshes auth detection. Key is zeroized after use.
pub async fn set_provider_key(
State(state): State>,
@@ -7774,7 +8082,7 @@ pub async fn set_provider_key(
// Hot-update the in-memory default model override so resolve_driver()
// immediately creates drivers for the new provider — no restart needed.
{
- let new_dm = openfang_types::config::DefaultModelConfig {
+ let new_dm = omtae_types::config::DefaultModelConfig {
provider: name.clone(),
model: model_id,
api_key_env: env_var.clone(),
@@ -7816,7 +8124,7 @@ pub async fn set_provider_key(
let base = guard
.clone()
.unwrap_or_else(|| state.kernel.config.default_model.clone());
- *guard = Some(openfang_types::config::DefaultModelConfig {
+ *guard = Some(omtae_types::config::DefaultModelConfig {
api_key_env: env_var.clone(),
..base
});
@@ -7907,10 +8215,8 @@ pub async fn test_provider(
.unwrap_or_else(|e| e.into_inner());
match catalog.get_provider(&name) {
Some(p) => {
- // Find a default model for this provider to use in the test request
- let model_id = catalog
- .default_model_for_provider(&name)
- .unwrap_or_default();
+ let model_id =
+ model_for_provider_test(&state.kernel, &catalog, &name);
(
p.api_key_env.clone(),
p.base_url.clone(),
@@ -7938,7 +8244,7 @@ pub async fn test_provider(
// Attempt a lightweight connectivity test
let start = std::time::Instant::now();
- let driver_config = openfang_runtime::llm_driver::DriverConfig {
+ let driver_config = omtae_runtime::llm_driver::DriverConfig {
provider: name.clone(),
api_key,
base_url: if base_url.is_empty() {
@@ -7950,12 +8256,12 @@ pub async fn test_provider(
subprocess_timeout_secs: None,
};
- match openfang_runtime::drivers::create_driver(&driver_config) {
+ match omtae_runtime::drivers::create_driver(&driver_config) {
Ok(driver) => {
// Send a minimal completion request to test connectivity
- let test_req = openfang_runtime::llm_driver::CompletionRequest {
+ let test_req = omtae_runtime::llm_driver::CompletionRequest {
model: default_model.clone(),
- messages: vec![openfang_types::message::Message::user("Hi")],
+ messages: vec![omtae_types::message::Message::user("Hi")],
tools: vec![],
max_tokens: 1,
temperature: 0.0,
@@ -8040,7 +8346,20 @@ pub async fn set_provider_url(
}
// Probe reachability at the new URL
- let probe = openfang_runtime::provider_health::probe_provider(&name, &base_url).await;
+ let api_key = {
+ let catalog = state
+ .kernel
+ .model_catalog
+ .read()
+ .unwrap_or_else(|e| e.into_inner());
+ provider_api_key_from_env(&catalog, &name)
+ };
+ let probe = omtae_runtime::provider_health::probe_provider(
+ &name,
+ &base_url,
+ api_key.as_deref(),
+ )
+ .await;
// Merge discovered models into catalog
if !probe.discovered_models.is_empty() {
@@ -8146,7 +8465,7 @@ pub async fn create_skill(
);
}
- // Write skill.toml to ~/.openfang/skills/{name}/
+ // Write skill.toml to ~/.omtae/skills/{name}/
let skill_dir = state.kernel.config.home_dir.join("skills").join(&name);
if skill_dir.exists() {
return (
@@ -8215,7 +8534,7 @@ pub async fn create_skill(
fn load_skill_declared_config(
skills_dir: &std::path::Path,
skill_name: &str,
-) -> Option> {
+) -> Option> {
// 1. User-installed skill: skills_dir//skill.toml, falling back to
// SKILL.md frontmatter if no TOML has been generated yet.
let skill_dir = skills_dir.join(skill_name);
@@ -8223,7 +8542,7 @@ fn load_skill_declared_config(
let toml_path = skill_dir.join("skill.toml");
if toml_path.exists() {
if let Ok(text) = std::fs::read_to_string(&toml_path) {
- if let Ok(manifest) = toml::from_str::(&text) {
+ if let Ok(manifest) = toml::from_str::(&text) {
if manifest.skill.name == skill_name {
return Some(manifest.config);
}
@@ -8234,7 +8553,7 @@ fn load_skill_declared_config(
if skillmd_path.exists() {
if let Ok(text) = std::fs::read_to_string(&skillmd_path) {
if let Ok(converted) =
- openfang_skills::openclaw_compat::convert_skillmd_str(skill_name, &text)
+ omtae_skills::openclaw_compat::convert_skillmd_str(skill_name, &text)
{
return Some(converted.config_vars);
}
@@ -8243,10 +8562,10 @@ fn load_skill_declared_config(
}
// 2. Bundled skill: look up by name in the compile-time catalog.
- for (bundled_name, content) in openfang_skills::bundled::bundled_skills() {
+ for (bundled_name, content) in omtae_skills::bundled::bundled_skills() {
if bundled_name == skill_name {
if let Ok(converted) =
- openfang_skills::openclaw_compat::convert_skillmd_str(bundled_name, content)
+ omtae_skills::openclaw_compat::convert_skillmd_str(bundled_name, content)
{
return Some(converted.config_vars);
}
@@ -8260,12 +8579,12 @@ fn load_skill_declared_config(
///
/// Returns `None` if the skill is not installed. Resolved values are
/// redacted when the variable name looks secret (see
-/// [`openfang_skills::config_injection::is_secret_name`]).
+/// [`omtae_skills::config_injection::is_secret_name`]).
fn build_skill_config_snapshot(
state: &Arc,
skill_name: &str,
) -> Option {
- use openfang_skills::config_injection::is_secret_name;
+ use omtae_skills::config_injection::is_secret_name;
let skills_dir = state.kernel.config.home_dir.join("skills");
let declared = load_skill_declared_config(&skills_dir, skill_name)?;
@@ -8870,8 +9189,8 @@ pub async fn list_integrations(State(state): State>) -> impl IntoR
let status = match &info.installed {
Some(inst) if !inst.enabled => "disabled",
Some(_) => match h.as_ref().map(|h| &h.status) {
- Some(openfang_extensions::IntegrationStatus::Ready) => "ready",
- Some(openfang_extensions::IntegrationStatus::Error(_)) => "error",
+ Some(omtae_extensions::IntegrationStatus::Ready) => "ready",
+ Some(omtae_extensions::IntegrationStatus::Error(_)) => "error",
_ => "installed",
},
None => continue, // Only show installed
@@ -8966,7 +9285,7 @@ pub async fn add_integration(
format!("Unknown integration: '{}'", id),
))
} else {
- let entry = openfang_extensions::InstalledIntegration {
+ let entry = omtae_extensions::InstalledIntegration {
id: id.clone(),
installed_at: chrono::Utc::now(),
enabled: true,
@@ -9125,7 +9444,7 @@ pub async fn reload_integrations(State(state): State>) -> impl Int
// ---------------------------------------------------------------------------
//
// Historical note: an earlier implementation of `/api/schedules*` wrote to a
-// shared-memory key (`__openfang_schedules`) that no executor ever read — so
+// shared-memory key (`__omtae_schedules`) that no executor ever read — so
// scheduled jobs registered via this API never actually fired (#1069). These
// routes now delegate to the kernel's real cron scheduler, which already
// backs `/api/cron/jobs*`. The request/response shape has been preserved as
@@ -9134,10 +9453,10 @@ pub async fn reload_integrations(State(state): State>) -> impl Int
/// Convert an internal `CronJob` into the legacy `/api/schedules` response
/// shape so existing dashboard code keeps working.
fn cron_job_to_schedule_view(
- kernel: &OpenFangKernel,
- job: &openfang_types::scheduler::CronJob,
+ kernel: &OMTAEKernel,
+ job: &omtae_types::scheduler::CronJob,
) -> serde_json::Value {
- use openfang_types::scheduler::{CronAction, CronSchedule};
+ use omtae_types::scheduler::{CronAction, CronSchedule};
let cron = match &job.schedule {
CronSchedule::Cron { expr, .. } => expr.clone(),
@@ -9274,7 +9593,7 @@ pub async fn create_schedule(
if let Some(arr) = delivery_targets_raw.as_array() {
for (idx, t) in arr.iter().enumerate() {
if let Err(e) =
- serde_json::from_value::(t.clone())
+ serde_json::from_value::(t.clone())
{
return (
StatusCode::BAD_REQUEST,
@@ -9314,14 +9633,14 @@ pub async fn create_schedule(
.unwrap_or_default();
if !enabled {
if let Ok(uuid) = uuid::Uuid::parse_str(&job_id) {
- let cj_id = openfang_types::scheduler::CronJobId(uuid);
+ let cj_id = omtae_types::scheduler::CronJobId(uuid);
let _ = state.kernel.cron_scheduler.set_enabled(cj_id, false);
let _ = state.kernel.cron_scheduler.persist();
}
}
// Build response in the legacy shape.
let body = if let Ok(uuid) = uuid::Uuid::parse_str(&job_id) {
- let cj_id = openfang_types::scheduler::CronJobId(uuid);
+ let cj_id = omtae_types::scheduler::CronJobId(uuid);
match state.kernel.cron_scheduler.get_job(cj_id) {
Some(job) => cron_job_to_schedule_view(&state.kernel, &job),
None => serde_json::json!({
@@ -9371,7 +9690,7 @@ pub async fn update_schedule(
);
}
};
- let cj_id = openfang_types::scheduler::CronJobId(uuid);
+ let cj_id = omtae_types::scheduler::CronJobId(uuid);
if state.kernel.cron_scheduler.get_job(cj_id).is_none() {
return (
@@ -9402,10 +9721,10 @@ pub async fn update_schedule(
);
}
let arr = raw_targets.as_array().unwrap();
- let mut parsed: Vec =
+ let mut parsed: Vec =
Vec::with_capacity(arr.len());
for (idx, t) in arr.iter().enumerate() {
- match serde_json::from_value::(t.clone())
+ match serde_json::from_value::(t.clone())
{
Ok(dt) => parsed.push(dt),
Err(e) => {
@@ -9464,7 +9783,7 @@ pub async fn delete_schedule(
);
}
};
- let cj_id = openfang_types::scheduler::CronJobId(uuid);
+ let cj_id = omtae_types::scheduler::CronJobId(uuid);
match state.kernel.cron_scheduler.remove_job(cj_id) {
Ok(_) => {
let _ = state.kernel.cron_scheduler.persist();
@@ -9494,16 +9813,16 @@ pub async fn run_schedule(
);
}
};
- let cj_id = openfang_types::scheduler::CronJobId(uuid);
+ let cj_id = omtae_types::scheduler::CronJobId(uuid);
let job = match state.kernel.cron_scheduler.try_claim_for_run(cj_id) {
Ok(j) => j,
- Err(openfang_kernel::cron::ClaimError::NotFound) => {
+ Err(omtae_kernel::cron::ClaimError::NotFound) => {
return (
StatusCode::NOT_FOUND,
Json(serde_json::json!({"error": "Schedule not found"})),
);
}
- Err(openfang_kernel::cron::ClaimError::Disabled) => {
+ Err(omtae_kernel::cron::ClaimError::Disabled) => {
return (
StatusCode::BAD_REQUEST,
Json(serde_json::json!({"error": "Schedule is disabled"})),
@@ -9553,7 +9872,7 @@ pub async fn schedule_delivery_log(
);
}
};
- let cj_id = openfang_types::scheduler::CronJobId(uuid);
+ let cj_id = omtae_types::scheduler::CronJobId(uuid);
let job = match state.kernel.cron_scheduler.get_job(cj_id) {
Some(j) => j,
None => {
@@ -9705,7 +10024,7 @@ pub struct PatchAgentConfigRequest {
pub provider: Option,
pub api_key_env: Option,
pub base_url: Option,
- pub fallback_models: Option>,
+ pub fallback_models: Option>,
}
/// PATCH /api/agents/{id}/config — Hot-update agent name, description, system prompt, and identity.
@@ -10645,7 +10964,7 @@ pub async fn upload_file(
// Generate file ID and save
let file_id = uuid::Uuid::new_v4().to_string();
- let upload_dir = std::env::temp_dir().join("openfang_uploads");
+ let upload_dir = std::env::temp_dir().join("omtae_uploads");
if let Err(e) = std::fs::create_dir_all(&upload_dir) {
tracing::warn!("Failed to create upload dir: {e}");
return (
@@ -10674,10 +10993,10 @@ pub async fn upload_file(
// Auto-transcribe audio uploads using the media engine
let transcription = if content_type.starts_with("audio/") {
- let attachment = openfang_types::media::MediaAttachment {
- media_type: openfang_types::media::MediaType::Audio,
+ let attachment = omtae_types::media::MediaAttachment {
+ media_type: omtae_types::media::MediaType::Audio,
mime_type: content_type.clone(),
- source: openfang_types::media::MediaSource::FilePath {
+ source: omtae_types::media::MediaSource::FilePath {
path: file_path.to_string_lossy().to_string(),
},
size_bytes: size as u64,
@@ -10727,7 +11046,7 @@ pub async fn serve_upload(Path(file_id): Path) -> impl IntoResponse {
);
}
- let file_path = std::env::temp_dir().join("openfang_uploads").join(&file_id);
+ let file_path = std::env::temp_dir().join("omtae_uploads").join(&file_id);
// Look up metadata from registry; fall back to disk probe for generated images
// (image_generate saves files without registering in UPLOAD_REGISTRY).
@@ -10813,9 +11132,9 @@ pub async fn list_approvals(State(state): State>) -> impl IntoResp
let request = record.request;
let agent_name = agent_name_for(&request.agent_id);
let status = match record.decision {
- openfang_types::approval::ApprovalDecision::Approved => "approved",
- openfang_types::approval::ApprovalDecision::Denied => "rejected",
- openfang_types::approval::ApprovalDecision::TimedOut => "expired",
+ omtae_types::approval::ApprovalDecision::Approved => "approved",
+ omtae_types::approval::ApprovalDecision::Denied => "rejected",
+ omtae_types::approval::ApprovalDecision::TimedOut => "expired",
};
serde_json::json!({
"id": request.id,
@@ -10867,7 +11186,7 @@ pub async fn create_approval(
State(state): State>,
Json(req): Json,
) -> impl IntoResponse {
- use openfang_types::approval::{ApprovalRequest, RiskLevel};
+ use omtae_types::approval::{ApprovalRequest, RiskLevel};
let policy = state.kernel.approval_manager.policy();
let id = uuid::Uuid::new_v4();
@@ -10919,7 +11238,7 @@ pub async fn approve_request(
match state.kernel.approval_manager.resolve(
uuid,
- openfang_types::approval::ApprovalDecision::Approved,
+ omtae_types::approval::ApprovalDecision::Approved,
Some("api".to_string()),
) {
Ok(resp) => (
@@ -10949,7 +11268,7 @@ pub async fn reject_request(
match state.kernel.approval_manager.resolve(
uuid,
- openfang_types::approval::ApprovalDecision::Denied,
+ omtae_types::approval::ApprovalDecision::Denied,
Some("api".to_string()),
) {
Ok(resp) => (
@@ -10975,7 +11294,7 @@ pub async fn config_reload(State(state): State>) -> impl IntoRespo
// SECURITY: Record config reload in audit trail
state.kernel.audit_log.record(
"system",
- openfang_runtime::audit::AuditAction::ConfigChange,
+ omtae_runtime::audit::AuditAction::ConfigChange,
"config reload requested via API",
"pending",
);
@@ -11230,7 +11549,7 @@ pub async fn config_set(
state.kernel.audit_log.record(
"system",
- openfang_runtime::audit::AuditAction::ConfigChange,
+ omtae_runtime::audit::AuditAction::ConfigChange,
format!("config set: {path}"),
"completed",
);
@@ -11365,7 +11684,7 @@ pub async fn delete_cron_job(
) -> impl IntoResponse {
match uuid::Uuid::parse_str(&id) {
Ok(uuid) => {
- let job_id = openfang_types::scheduler::CronJobId(uuid);
+ let job_id = omtae_types::scheduler::CronJobId(uuid);
match state.kernel.cron_scheduler.remove_job(job_id) {
Ok(_) => {
let _ = state.kernel.cron_scheduler.persist();
@@ -11396,7 +11715,7 @@ pub async fn toggle_cron_job(
let enabled = body["enabled"].as_bool().unwrap_or(true);
match uuid::Uuid::parse_str(&id) {
Ok(uuid) => {
- let job_id = openfang_types::scheduler::CronJobId(uuid);
+ let job_id = omtae_types::scheduler::CronJobId(uuid);
match state.kernel.cron_scheduler.set_enabled(job_id, enabled) {
Ok(()) => {
let _ = state.kernel.cron_scheduler.persist();
@@ -11425,7 +11744,7 @@ pub async fn cron_job_status(
) -> impl IntoResponse {
match uuid::Uuid::parse_str(&id) {
Ok(uuid) => {
- let job_id = openfang_types::scheduler::CronJobId(uuid);
+ let job_id = omtae_types::scheduler::CronJobId(uuid);
match state.kernel.cron_scheduler.get_meta(job_id) {
Some(meta) => (
StatusCode::OK,
@@ -11466,18 +11785,18 @@ pub async fn run_cron_job(
);
}
};
- let job_id = openfang_types::scheduler::CronJobId(uuid);
+ let job_id = omtae_types::scheduler::CronJobId(uuid);
// Atomically check existence + enabled + reserve next_run in one lock hold.
let job = match state.kernel.cron_scheduler.try_claim_for_run(job_id) {
Ok(j) => j,
- Err(openfang_kernel::cron::ClaimError::NotFound) => {
+ Err(omtae_kernel::cron::ClaimError::NotFound) => {
return (
StatusCode::NOT_FOUND,
Json(serde_json::json!({"status": "error", "error": "Job not found"})),
);
}
- Err(openfang_kernel::cron::ClaimError::Disabled) => {
+ Err(omtae_kernel::cron::ClaimError::Disabled) => {
return (
StatusCode::BAD_REQUEST,
Json(serde_json::json!({"status": "error", "error": "Job is disabled"})),
@@ -11515,7 +11834,7 @@ pub async fn run_cron_job(
pub async fn webhook_wake(
State(state): State>,
headers: axum::http::HeaderMap,
- Json(body): Json,
+ Json(body): Json,
) -> impl IntoResponse {
// Check if webhook triggers are enabled
let wh_config = match &state.kernel.config.webhook_triggers {
@@ -11574,7 +11893,7 @@ pub async fn webhook_wake(
pub async fn webhook_agent(
State(state): State>,
headers: axum::http::HeaderMap,
- Json(body): Json,
+ Json(body): Json,
) -> impl IntoResponse {
// Check if webhook triggers are enabled
let wh_config = match &state.kernel.config.webhook_triggers {
@@ -11671,7 +11990,7 @@ pub async fn list_bindings(State(state): State>) -> impl IntoRespo
/// POST /api/bindings — Add a new agent binding.
pub async fn add_binding(
State(state): State>,
- Json(binding): Json,
+ Json(binding): Json,
) -> impl IntoResponse {
// Validate agent exists
let agents = state.kernel.registry.list();
@@ -11718,7 +12037,7 @@ pub async fn pairing_request(State(state): State>) -> impl IntoRes
}
match state.kernel.pairing.create_pairing_request() {
Ok(req) => {
- let qr_uri = format!("openfang://pair?token={}", req.token);
+ let qr_uri = format!("omtae://pair?token={}", req.token);
Json(serde_json::json!({
"token": req.token,
"qr_uri": qr_uri,
@@ -11759,7 +12078,7 @@ pub async fn pairing_complete(
.get("push_token")
.and_then(|v| v.as_str())
.map(String::from);
- let device_info = openfang_kernel::pairing::PairedDevice {
+ let device_info = omtae_kernel::pairing::PairedDevice {
device_id: uuid::Uuid::new_v4().to_string(),
display_name: display_name.to_string(),
platform: platform.to_string(),
@@ -11843,7 +12162,7 @@ pub async fn pairing_notify(
let title = body
.get("title")
.and_then(|v| v.as_str())
- .unwrap_or("OpenFang");
+ .unwrap_or("OMTAE");
let message = body.get("message").and_then(|v| v.as_str()).unwrap_or("");
if message.is_empty() {
return (
@@ -11882,7 +12201,7 @@ pub async fn pairing_notify(
///
/// Unknown surface values return 400.
pub async fn list_commands(Query(params): Query) -> impl IntoResponse {
- use openfang_types::commands::{self, CommandCategory, Surfaces};
+ use omtae_types::commands::{self, CommandCategory, Surfaces};
let surface_raw = params.surface.as_deref().unwrap_or("web");
let surface = match surface_raw.to_ascii_lowercase().as_str() {
@@ -11979,7 +12298,7 @@ pub async fn copilot_oauth_start() -> impl IntoResponse {
// Clean up expired flows first
COPILOT_FLOWS.retain(|_, state| state.expires_at > Instant::now());
- match openfang_runtime::copilot_oauth::start_device_flow().await {
+ match omtae_runtime::copilot_oauth::start_device_flow().await {
Ok(resp) => {
let poll_id = uuid::Uuid::new_v4().to_string();
@@ -12041,12 +12360,12 @@ pub async fn copilot_oauth_poll(
let device_code = flow.device_code.clone();
drop(flow);
- match openfang_runtime::copilot_oauth::poll_device_flow(&device_code).await {
- openfang_runtime::copilot_oauth::DeviceFlowStatus::Pending => (
+ match omtae_runtime::copilot_oauth::poll_device_flow(&device_code).await {
+ omtae_runtime::copilot_oauth::DeviceFlowStatus::Pending => (
StatusCode::OK,
Json(serde_json::json!({"status": "pending"})),
),
- openfang_runtime::copilot_oauth::DeviceFlowStatus::Complete { access_token } => {
+ omtae_runtime::copilot_oauth::DeviceFlowStatus::Complete { access_token } => {
// Store in vault (best-effort)
state.kernel.store_credential("GITHUB_TOKEN", &access_token);
@@ -12080,7 +12399,7 @@ pub async fn copilot_oauth_poll(
Json(serde_json::json!({"status": "complete"})),
)
}
- openfang_runtime::copilot_oauth::DeviceFlowStatus::SlowDown { new_interval } => {
+ omtae_runtime::copilot_oauth::DeviceFlowStatus::SlowDown { new_interval } => {
// Update interval
if let Some(mut f) = COPILOT_FLOWS.get_mut(&poll_id) {
f.interval = new_interval;
@@ -12090,21 +12409,21 @@ pub async fn copilot_oauth_poll(
Json(serde_json::json!({"status": "pending", "interval": new_interval})),
)
}
- openfang_runtime::copilot_oauth::DeviceFlowStatus::Expired => {
+ omtae_runtime::copilot_oauth::DeviceFlowStatus::Expired => {
COPILOT_FLOWS.remove(&poll_id);
(
StatusCode::OK,
Json(serde_json::json!({"status": "expired"})),
)
}
- openfang_runtime::copilot_oauth::DeviceFlowStatus::AccessDenied => {
+ omtae_runtime::copilot_oauth::DeviceFlowStatus::AccessDenied => {
COPILOT_FLOWS.remove(&poll_id);
(
StatusCode::OK,
Json(serde_json::json!({"status": "denied"})),
)
}
- openfang_runtime::copilot_oauth::DeviceFlowStatus::Error(e) => (
+ omtae_runtime::copilot_oauth::DeviceFlowStatus::Error(e) => (
StatusCode::OK,
Json(serde_json::json!({"status": "error", "error": e})),
),
@@ -12117,7 +12436,7 @@ pub async fn copilot_oauth_poll(
/// GET /api/comms/topology — Build agent topology graph from registry.
pub async fn comms_topology(State(state): State>) -> impl IntoResponse {
- use openfang_types::comms::{EdgeKind, TopoEdge, TopoNode, Topology};
+ use omtae_types::comms::{EdgeKind, TopoEdge, TopoNode, Topology};
let agents = state.kernel.registry.list();
@@ -12148,8 +12467,8 @@ pub async fn comms_topology(State(state): State>) -> impl IntoResp
let events = state.kernel.event_bus.history(500).await;
let mut peer_pairs = std::collections::HashSet::new();
for event in &events {
- if let openfang_types::event::EventPayload::Message(_) = &event.payload {
- if let openfang_types::event::EventTarget::Agent(target_id) = &event.target {
+ if let omtae_types::event::EventPayload::Message(_) = &event.payload {
+ if let omtae_types::event::EventTarget::Agent(target_id) = &event.target {
let from = event.source.to_string();
let to = target_id.to_string();
// Deduplicate: only one edge per pair, skip self-loops
@@ -12176,11 +12495,11 @@ pub async fn comms_topology(State(state): State>) -> impl IntoResp
/// Filter a kernel event into a CommsEvent, if it represents inter-agent communication.
fn filter_to_comms_event(
- event: &openfang_types::event::Event,
- agents: &[openfang_types::agent::AgentEntry],
-) -> Option {
- use openfang_types::comms::{CommsEvent, CommsEventKind};
- use openfang_types::event::{EventPayload, EventTarget, LifecycleEvent};
+ event: &omtae_types::event::Event,
+ agents: &[omtae_types::agent::AgentEntry],
+) -> Option {
+ use omtae_types::comms::{CommsEvent, CommsEventKind};
+ use omtae_types::event::{EventPayload, EventTarget, LifecycleEvent};
let resolve_name = |id: &str| -> String {
agents
@@ -12204,7 +12523,7 @@ fn filter_to_comms_event(
source_name: resolve_name(&event.source.to_string()),
target_id: target_id.clone(),
target_name: resolve_name(&target_id),
- detail: openfang_types::truncate_str(&msg.content, 200).to_string(),
+ detail: omtae_types::truncate_str(&msg.content, 200).to_string(),
})
}
EventPayload::Lifecycle(lifecycle) => match lifecycle {
@@ -12236,10 +12555,10 @@ fn filter_to_comms_event(
/// Convert an audit entry into a CommsEvent if it represents inter-agent activity.
fn audit_to_comms_event(
- entry: &openfang_runtime::audit::AuditEntry,
- agents: &[openfang_types::agent::AgentEntry],
-) -> Option {
- use openfang_types::comms::{CommsEvent, CommsEventKind};
+ entry: &omtae_runtime::audit::AuditEntry,
+ agents: &[omtae_types::agent::AgentEntry],
+) -> Option {
+ use omtae_types::comms::{CommsEvent, CommsEventKind};
let resolve_name = |id: &str| -> String {
agents
@@ -12250,7 +12569,7 @@ fn audit_to_comms_event(
if id.is_empty() || id == "system" {
"system".to_string()
} else {
- openfang_types::truncate_str(id, 12).to_string()
+ omtae_types::truncate_str(id, 12).to_string()
}
})
};
@@ -12276,17 +12595,17 @@ fn audit_to_comms_event(
"{} in / {} out — {}",
in_tok,
out_tok,
- openfang_types::truncate_str(&entry.outcome, 80)
+ omtae_types::truncate_str(&entry.outcome, 80)
)
}
} else if entry.outcome != "ok" {
format!(
"{} — {}",
- openfang_types::truncate_str(&entry.detail, 80),
- openfang_types::truncate_str(&entry.outcome, 80)
+ omtae_types::truncate_str(&entry.detail, 80),
+ omtae_types::truncate_str(&entry.outcome, 80)
)
} else {
- openfang_types::truncate_str(&entry.detail, 200).to_string()
+ omtae_types::truncate_str(&entry.detail, 200).to_string()
};
(CommsEventKind::AgentMessage, detail, "user")
}
@@ -12294,7 +12613,7 @@ fn audit_to_comms_event(
CommsEventKind::AgentSpawned,
format!(
"Agent spawned: {}",
- openfang_types::truncate_str(&entry.detail, 100)
+ omtae_types::truncate_str(&entry.detail, 100)
),
"",
),
@@ -12302,7 +12621,7 @@ fn audit_to_comms_event(
CommsEventKind::AgentTerminated,
format!(
"Agent killed: {}",
- openfang_types::truncate_str(&entry.detail, 100)
+ omtae_types::truncate_str(&entry.detail, 100)
),
"",
),
@@ -12347,7 +12666,7 @@ pub async fn comms_events(
// Primary source: event bus (has full source/target context)
let bus_events = state.kernel.event_bus.history(500).await;
- let mut comms_events: Vec = bus_events
+ let mut comms_events: Vec = bus_events
.iter()
.filter_map(|e| filter_to_comms_event(e, &agents))
.collect();
@@ -12425,10 +12744,10 @@ pub async fn comms_events_stream(State(state): State>) -> axum::re
/// POST /api/comms/send — Send a message from one agent to another.
pub async fn comms_send(
State(state): State>,
- Json(req): Json,
+ Json(req): Json,
) -> impl IntoResponse {
// Validate from agent exists
- let from_id: openfang_types::agent::AgentId = match req.from_agent_id.parse() {
+ let from_id: omtae_types::agent::AgentId = match req.from_agent_id.parse() {
Ok(id) => id,
Err(_) => {
return (
@@ -12445,7 +12764,7 @@ pub async fn comms_send(
}
// Validate to agent exists
- let to_id: openfang_types::agent::AgentId = match req.to_agent_id.parse() {
+ let to_id: omtae_types::agent::AgentId = match req.to_agent_id.parse() {
Ok(id) => id,
Err(_) => {
return (
@@ -12489,7 +12808,7 @@ pub async fn comms_send(
/// POST /api/comms/task — Post a task to the agent task queue.
pub async fn comms_task(
State(state): State>,
- Json(req): Json,
+ Json(req): Json,
) -> impl IntoResponse {
if req.title.is_empty() {
return (
@@ -12528,12 +12847,68 @@ pub async fn comms_task(
/// POST /api/auth/login — Authenticate with username/password, returns session token.
pub async fn auth_login(
State(state): State>,
+ headers: axum::http::HeaderMap,
Json(req): Json,
) -> axum::response::Response {
use axum::body::Body;
use axum::response::Response;
- let auth_cfg = &state.kernel.config.auth;
+ let cfg = &state.kernel.config;
+
+ // PIN login (`[dashboard]` in config.toml)
+ if cfg.dashboard.pin_auth_active() {
+ let pin = req.get("pin").and_then(|v| v.as_str()).unwrap_or("");
+ if !cfg.verify_dashboard_pin(pin) {
+ state.kernel.audit_log.record(
+ "system",
+ omtae_runtime::audit::AuditAction::AuthAttempt,
+ "dashboard PIN login failed",
+ "invalid PIN".to_string(),
+ );
+ return Response::builder()
+ .status(StatusCode::UNAUTHORIZED)
+ .header("content-type", "application/json")
+ .body(Body::from(
+ serde_json::json!({"error": "Invalid PIN"}).to_string(),
+ ))
+ .unwrap();
+ }
+
+ let secret = omtae_types::config::KernelConfig::pin_session_secret(&cfg.dashboard.pin);
+ let ttl_hours = if cfg.auth.session_ttl_hours > 0 {
+ cfg.auth.session_ttl_hours
+ } else {
+ 168
+ };
+ let token = crate::session_auth::create_session_token("dashboard", &secret, ttl_hours);
+ let ttl_secs = ttl_hours * 3600;
+ let secure = crate::session_auth::cookie_should_be_secure(&headers);
+ let cookie = crate::session_auth::format_session_cookie(&token, ttl_secs, secure);
+
+ state.kernel.audit_log.record(
+ "system",
+ omtae_runtime::audit::AuditAction::AuthAttempt,
+ "dashboard PIN login success",
+ String::new(),
+ );
+
+ return Response::builder()
+ .status(StatusCode::OK)
+ .header("content-type", "application/json")
+ .header("set-cookie", &cookie)
+ .body(Body::from(
+ serde_json::json!({
+ "status": "ok",
+ "token": token,
+ "username": "dashboard",
+ "mode": "pin",
+ })
+ .to_string(),
+ ))
+ .unwrap();
+ }
+
+ let auth_cfg = &cfg.auth;
if !auth_cfg.enabled {
return Response::builder()
.status(StatusCode::NOT_FOUND)
@@ -12563,7 +12938,7 @@ pub async fn auth_login(
// Audit log the failed attempt
state.kernel.audit_log.record(
"system",
- openfang_runtime::audit::AuditAction::AuthAttempt,
+ omtae_runtime::audit::AuditAction::AuthAttempt,
"dashboard login failed",
format!("username: {username}"),
);
@@ -12576,23 +12951,17 @@ pub async fn auth_login(
.unwrap();
}
- // Derive the session secret the same way as server.rs
- let api_key = state.kernel.config.api_key.trim().to_string();
- let secret = if !api_key.is_empty() {
- api_key
- } else {
- auth_cfg.password_hash.clone()
- };
+ let secret = state.kernel.config.dashboard_session_secret();
let token =
crate::session_auth::create_session_token(username, &secret, auth_cfg.session_ttl_hours);
let ttl_secs = auth_cfg.session_ttl_hours * 3600;
- let cookie =
- format!("openfang_session={token}; Path=/; HttpOnly; SameSite=Strict; Max-Age={ttl_secs}");
+ let secure = crate::session_auth::cookie_should_be_secure(&headers);
+ let cookie = crate::session_auth::format_session_cookie(&token, ttl_secs, secure);
state.kernel.audit_log.record(
"system",
- openfang_runtime::audit::AuditAction::AuthAttempt,
+ omtae_runtime::audit::AuditAction::AuthAttempt,
"dashboard login success",
format!("username: {username}"),
);
@@ -12613,13 +12982,17 @@ pub async fn auth_login(
}
/// POST /api/auth/logout — Clear the session cookie.
-pub async fn auth_logout() -> impl IntoResponse {
- let cookie = "openfang_session=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0";
- (
- StatusCode::OK,
- [("content-type", "application/json"), ("set-cookie", cookie)],
- serde_json::json!({"status": "ok"}).to_string(),
- )
+pub async fn auth_logout(headers: axum::http::HeaderMap) -> impl IntoResponse {
+ let secure = crate::session_auth::cookie_should_be_secure(&headers);
+ let cookie = crate::session_auth::format_session_cookie_clear(secure);
+ axum::http::Response::builder()
+ .status(StatusCode::OK)
+ .header("content-type", "application/json")
+ .header("set-cookie", &cookie)
+ .body(axum::body::Body::from(
+ serde_json::json!({"status": "ok"}).to_string(),
+ ))
+ .unwrap()
}
/// GET /api/auth/check — Check current authentication state.
@@ -12627,36 +13000,54 @@ pub async fn auth_check(
State(state): State>,
request: axum::http::Request,
) -> impl IntoResponse {
- let auth_cfg = &state.kernel.config.auth;
- if !auth_cfg.enabled {
+ let cfg = &state.kernel.config;
+ if !cfg.dashboard_auth_enabled() {
return Json(serde_json::json!({
"authenticated": true,
"mode": "none",
}));
}
- // Derive the session secret the same way as server.rs
- let api_key = state.kernel.config.api_key.trim().to_string();
- let secret = if !api_key.is_empty() {
- api_key
+ let secret = cfg.dashboard_session_secret();
+ let mode = if cfg.dashboard.pin_auth_active() {
+ "pin"
} else {
- auth_cfg.password_hash.clone()
+ "session"
};
- // Check session cookie
+ // Check session cookie, PIN header, or Bearer session token (mobile fallbacks).
let session_user = crate::session_auth::extract_session_cookie(request.headers())
- .and_then(|token| crate::session_auth::verify_session_token(&token, &secret));
+ .and_then(|token| crate::session_auth::verify_session_token(&token, &secret))
+ .or_else(|| {
+ request
+ .headers()
+ .get("authorization")
+ .and_then(|v| v.to_str().ok())
+ .and_then(|v| v.strip_prefix("Bearer "))
+ .and_then(|token| crate::session_auth::verify_session_token(token, &secret))
+ });
- if let Some(username) = session_user {
+ let pin_user = if session_user.is_none() && cfg.dashboard.pin_auth_active() {
+ request
+ .headers()
+ .get("x-omtae-pin")
+ .and_then(|v| v.to_str().ok())
+ .filter(|pin| cfg.verify_dashboard_pin(pin))
+ .map(|_| "dashboard".to_string())
+ } else {
+ None
+ };
+
+ if let Some(username) = session_user.or(pin_user) {
Json(serde_json::json!({
"authenticated": true,
- "mode": "session",
+ "mode": mode,
"username": username,
}))
} else {
Json(serde_json::json!({
"authenticated": false,
- "mode": "session",
+ "mode": mode,
}))
}
}
@@ -12695,14 +13086,14 @@ mod channel_config_tests {
#[test]
fn test_is_channel_configured_wecom_none() {
- let config = openfang_types::config::ChannelsConfig::default();
+ let config = omtae_types::config::ChannelsConfig::default();
assert!(!is_channel_configured(&config, "wecom"));
}
#[test]
fn test_is_channel_configured_wecom_some() {
- let mut config = openfang_types::config::ChannelsConfig::default();
- config.wecom = Some(openfang_types::config::WeComConfig {
+ let mut config = omtae_types::config::ChannelsConfig::default();
+ config.wecom = Some(omtae_types::config::WeComConfig {
corp_id: "test_corp".to_string(),
agent_id: "test_agent".to_string(),
secret_env: "WECOM_SECRET".to_string(),
@@ -12710,7 +13101,7 @@ mod channel_config_tests {
token: Some("token".to_string()),
encoding_aes_key: Some("aes_key".to_string()),
default_agent: Some("assistant".to_string()),
- overrides: openfang_types::config::ChannelOverrides::default(),
+ overrides: omtae_types::config::ChannelOverrides::default(),
});
assert!(is_channel_configured(&config, "wecom"));
}
diff --git a/crates/openfang-api/src/server.rs b/crates/openfang-api/src/server.rs
index a1a2bc9c06..ecbcea50dc 100644
--- a/crates/openfang-api/src/server.rs
+++ b/crates/openfang-api/src/server.rs
@@ -1,4 +1,4 @@
-//! OpenFang daemon server — boots the kernel and serves the HTTP API.
+//! OMTAE daemon server — boots the kernel and serves the HTTP API.
use crate::channel_bridge;
use crate::middleware;
@@ -7,7 +7,7 @@ use crate::routes::{self, AppState};
use crate::webchat;
use crate::ws;
use axum::Router;
-use openfang_kernel::OpenFangKernel;
+use omtae_kernel::OMTAEKernel;
use std::net::SocketAddr;
use std::path::Path;
use std::sync::Arc;
@@ -17,7 +17,7 @@ use tower_http::cors::CorsLayer;
use tower_http::trace::TraceLayer;
use tracing::info;
-/// Daemon info written to `~/.openfang/daemon.json` so the CLI can find us.
+/// Daemon info written to `~/.omtae/daemon.json` so the CLI can find us.
#[derive(serde::Serialize, serde::Deserialize)]
pub struct DaemonInfo {
pub pid: u32,
@@ -29,13 +29,13 @@ pub struct DaemonInfo {
/// Build the full API router with all routes, middleware, and state.
///
-/// This is extracted from `run_daemon()` so that embedders (e.g. openfang-desktop)
+/// This is extracted from `run_daemon()` so that embedders (e.g. omtae-desktop)
/// can create the router without starting the full daemon lifecycle.
///
/// Returns `(router, shared_state)`. The caller can use `state.bridge_manager`
/// to shut down the bridge on exit.
pub async fn build_router(
- kernel: Arc,
+ kernel: Arc,
listen_addr: SocketAddr,
) -> (Router<()>, Arc) {
// Start channel bridges (Telegram, etc.)
@@ -50,7 +50,7 @@ pub async fn build_router(
channels_config: tokio::sync::RwLock::new(channels_config),
shutdown_notify: Arc::new(tokio::sync::Notify::new()),
clawhub_cache: dashmap::DashMap::new(),
- provider_probe_cache: openfang_runtime::provider_health::ProbeCache::new(),
+ provider_probe_cache: omtae_runtime::provider_health::ProbeCache::new(),
budget_config: Arc::new(tokio::sync::RwLock::new(kernel.config.budget.clone())),
});
@@ -113,21 +113,36 @@ pub async fn build_router(
if state.kernel.config.auth.enabled && !ph.is_empty() && !ph.starts_with("$argon2") {
tracing::warn!(
"Dashboard auth password_hash is not in Argon2id format. \
- Login will fail. Regenerate with: openfang auth hash-password"
+ Login will fail. Regenerate with: omtae auth hash-password"
);
}
// Trim whitespace so `api_key = ""` or `api_key = " "` both disable auth.
- let api_key = state.kernel.config.api_key.trim().to_string();
+ let pin_active = state.kernel.config.dashboard.pin_auth_active();
+ let api_key = state.kernel.config.effective_api_key_for_auth();
+ let auth_enabled = state.kernel.config.dashboard_auth_enabled();
+ let session_secret = state.kernel.config.dashboard_session_secret();
let allow_no_auth = std::env::var("OPENFANG_ALLOW_NO_AUTH")
.map(|v| matches!(v.trim(), "1" | "true" | "TRUE" | "yes" | "on"))
.unwrap_or(false);
+ if pin_active {
+ tracing::info!(
+ "Dashboard PIN auth enabled — Bearer api_key disabled for HTTP/WS. \
+ Personal tunnel use only; see OMTAE-TUNNEL.md."
+ );
+ if state.kernel.config.dashboard.pin.trim() == "123456" {
+ tracing::warn!(
+ "Dashboard PIN is still the default (123456). Change [dashboard].pin in config.toml."
+ );
+ }
+ }
+
// Fail-closed warning: if no api_key and no dashboard auth, and the
// server is bound to a non-loopback address without an explicit opt-in,
// shout about it. The middleware will reject non-loopback traffic.
let bind_is_loopback = listen_addr.ip().is_loopback();
- if api_key.is_empty() && !state.kernel.config.auth.enabled && !bind_is_loopback {
+ if api_key.is_empty() && !auth_enabled && !bind_is_loopback {
if allow_no_auth {
tracing::warn!(
"OPENFANG_ALLOW_NO_AUTH=1 is set. Running WITHOUT authentication on {}. \
@@ -147,11 +162,11 @@ pub async fn build_router(
let auth_state = crate::middleware::AuthState {
api_key: api_key.clone(),
- auth_enabled: state.kernel.config.auth.enabled,
- session_secret: if !api_key.is_empty() {
- api_key.clone()
- } else if state.kernel.config.auth.enabled {
- state.kernel.config.auth.password_hash.clone()
+ raw_api_key: state.kernel.config.api_key.trim().to_string(),
+ auth_enabled,
+ session_secret: session_secret.clone(),
+ dashboard_pin: if pin_active {
+ state.kernel.config.dashboard.pin.trim().to_string()
} else {
String::new()
},
@@ -165,6 +180,8 @@ pub async fn build_router(
.route("/favicon.ico", axum::routing::get(webchat::favicon_ico))
.route("/manifest.json", axum::routing::get(webchat::manifest_json))
.route("/sw.js", axum::routing::get(webchat::sw_js))
+ .route("/i18n/en.json", axum::routing::get(webchat::i18n_en_json))
+ .route("/i18n/ru.json", axum::routing::get(webchat::i18n_ru_json))
.route(
"/api/metrics",
axum::routing::get(routes::prometheus_metrics),
@@ -175,6 +192,23 @@ pub async fn build_router(
axum::routing::get(routes::health_detail),
)
.route("/api/status", axum::routing::get(routes::status))
+ .route("/api/system/gpu", axum::routing::get(routes::system_gpu))
+ .route(
+ "/api/brain/status",
+ axum::routing::get(crate::brain::brain_status),
+ )
+ .route(
+ "/api/brain/list",
+ axum::routing::get(crate::brain::brain_list),
+ )
+ .route(
+ "/api/brain/file",
+ axum::routing::get(crate::brain::brain_file).post(crate::brain::brain_write),
+ )
+ .route(
+ "/api/system/drift",
+ axum::routing::get(routes::system_drift).post(routes::system_drift_remediate),
+ )
.route("/api/version", axum::routing::get(routes::version))
.route(
"/api/agents",
@@ -604,6 +638,14 @@ pub async fn build_router(
"/api/models/custom/{*id}",
axum::routing::delete(routes::remove_custom_model),
)
+ .route(
+ "/api/models/profiles",
+ axum::routing::get(routes::list_model_profiles),
+ )
+ .route(
+ "/api/models/active",
+ axum::routing::get(routes::get_active_model_profile).put(routes::set_active_model_profile),
+ )
.route("/api/models/{*id}", axum::routing::get(routes::get_model))
.route("/api/providers", axum::routing::get(routes::list_providers))
// Copilot OAuth (must be before parametric {name} routes)
@@ -797,11 +839,11 @@ pub async fn build_router(
(app, state)
}
-/// Start the OpenFang daemon: boot kernel + HTTP API server.
+/// Start the OMTAE daemon: boot kernel + HTTP API server.
///
/// This function blocks until Ctrl+C or a shutdown request.
pub async fn run_daemon(
- kernel: OpenFangKernel,
+ kernel: OMTAEKernel,
listen_addr: &str,
daemon_info_path: Option<&Path>,
) -> Result<(), Box> {
@@ -810,6 +852,7 @@ pub async fn run_daemon(
let kernel = Arc::new(kernel);
kernel.set_self_handle();
kernel.start_background_agents();
+ omtae_kernel::drift_guard::spawn_drift_watchdog(kernel.clone());
// Config file hot-reload watcher (polls every 30 seconds)
{
@@ -879,7 +922,7 @@ pub async fn run_daemon(
}
}
- info!("OpenFang API server listening on http://{addr}");
+ info!("OMTAE API server listening on http://{addr}");
info!("WebChat UI available at http://{addr}/",);
info!("WebSocket endpoint: ws://{addr}/api/agents/{{id}}/ws",);
@@ -923,7 +966,7 @@ pub async fn run_daemon(
// Shutdown kernel
kernel.shutdown();
- info!("OpenFang daemon stopped");
+ info!("OMTAE daemon stopped");
Ok(())
}
@@ -1018,7 +1061,7 @@ fn is_process_alive(pid: u32) -> bool {
}
}
-/// Check if an OpenFang daemon is actually responding at the given address.
+/// Check if an OMTAE daemon is actually responding at the given address.
/// This avoids false positives where a different process reused the same PID
/// after a system reboot.
fn is_daemon_responding(addr: &str) -> bool {
diff --git a/crates/openfang-api/src/session_auth.rs b/crates/openfang-api/src/session_auth.rs
index 9de34b8069..3a0726cf79 100644
--- a/crates/openfang-api/src/session_auth.rs
+++ b/crates/openfang-api/src/session_auth.rs
@@ -17,7 +17,71 @@ pub fn create_session_token(username: &str, secret: &str, ttl_hours: u64) -> Str
base64::engine::general_purpose::STANDARD.encode(format!("{payload}:{signature}"))
}
-/// Extract the `openfang_session` cookie value from a `Cookie` header string.
+/// True when the request arrived over HTTPS (direct TLS or proxy headers).
+pub fn request_is_secure(headers: &axum::http::HeaderMap) -> bool {
+ if headers
+ .get("x-forwarded-proto")
+ .and_then(|v| v.to_str().ok())
+ .is_some_and(|p| p.eq_ignore_ascii_case("https"))
+ {
+ return true;
+ }
+ if headers
+ .get("x-forwarded-ssl")
+ .and_then(|v| v.to_str().ok())
+ .is_some_and(|v| v.eq_ignore_ascii_case("on"))
+ {
+ return true;
+ }
+ if headers
+ .get("cf-visitor")
+ .and_then(|v| v.to_str().ok())
+ .is_some_and(|v| v.contains("\"scheme\":\"https\"") || v.contains("scheme=https"))
+ {
+ return true;
+ }
+ headers
+ .get(axum::http::header::FORWARDED)
+ .and_then(|v| v.to_str().ok())
+ .is_some_and(|f| f.to_ascii_lowercase().contains("proto=https"))
+}
+
+/// Whether the `Set-Cookie` `Secure` flag should be set for this request.
+///
+/// Uses HTTPS detection plus non-localhost `Host` so Cloudflare tunnel clients
+/// (`desk.omtaeservices.biz`) always get `Secure; SameSite=Lax` cookies.
+pub fn cookie_should_be_secure(headers: &axum::http::HeaderMap) -> bool {
+ if request_is_secure(headers) {
+ return true;
+ }
+ let host = headers
+ .get("host")
+ .and_then(|v| v.to_str().ok())
+ .map(|h| h.split(':').next().unwrap_or(h).to_ascii_lowercase());
+ match host.as_deref() {
+ Some("localhost") | Some("127.0.0.1") | Some("[::1]") => false,
+ Some(_) => true,
+ None => false,
+ }
+}
+
+/// Build the `Set-Cookie` value for a dashboard session.
+///
+/// Uses `SameSite=Lax` (tunnel-friendly) and `Secure` on HTTPS so mobile
+/// browsers accept the cookie behind Cloudflare.
+pub fn format_session_cookie(token: &str, ttl_secs: u64, secure: bool) -> String {
+ let secure_flag = if secure { "; Secure" } else { "" };
+ format!(
+ "omtae_session={token}; Path=/; HttpOnly; SameSite=Lax{secure_flag}; Max-Age={ttl_secs}"
+ )
+}
+
+/// Clear the session cookie (logout).
+pub fn format_session_cookie_clear(secure: bool) -> String {
+ format_session_cookie("", 0, secure)
+}
+
+/// Extract the `omtae_session` cookie value from a `Cookie` header string.
///
/// Returns `None` if the header is absent or the cookie is not present.
/// Used by both the HTTP auth middleware and the WebSocket upgrade handler so
@@ -30,7 +94,7 @@ pub fn extract_session_cookie(headers: &axum::http::HeaderMap) -> Option
.and_then(|cookies| {
cookies.split(';').find_map(|c| {
c.trim()
- .strip_prefix("openfang_session=")
+ .strip_prefix("omtae_session=")
.map(|v| v.to_string())
})
})
@@ -86,6 +150,17 @@ pub fn hash_password(password: &str) -> String {
.to_string()
}
+/// Constant-time compare for `[dashboard].pin`.
+pub fn verify_dashboard_pin(provided: &str, stored: &str) -> bool {
+ use subtle::ConstantTimeEq;
+ let a = provided.trim().as_bytes();
+ let b = stored.trim().as_bytes();
+ if a.is_empty() || b.is_empty() || a.len() != b.len() {
+ return false;
+ }
+ a.ct_eq(b).into()
+}
+
/// Verify a password against a stored Argon2id hash (PHC string format).
pub fn verify_password(password: &str, stored_hash: &str) -> bool {
use argon2::{password_hash::PasswordHash, Argon2, PasswordVerifier};
@@ -166,7 +241,7 @@ mod tests {
let mut h = axum::http::HeaderMap::new();
h.insert(
"cookie",
- "foo=bar; openfang_session=abc.def.ghi; baz=qux"
+ "foo=bar; omtae_session=abc.def.ghi; baz=qux"
.parse()
.unwrap(),
);
@@ -186,10 +261,46 @@ mod tests {
assert_eq!(extract_session_cookie(&h), None);
}
+ #[test]
+ fn test_verify_dashboard_pin() {
+ assert!(verify_dashboard_pin("1234", "1234"));
+ assert!(!verify_dashboard_pin("1235", "1234"));
+ assert!(!verify_dashboard_pin("1234", ""));
+ }
+
#[test]
fn test_extract_session_cookie_only_value() {
let mut h = axum::http::HeaderMap::new();
- h.insert("cookie", "openfang_session=lonely".parse().unwrap());
+ h.insert("cookie", "omtae_session=lonely".parse().unwrap());
assert_eq!(extract_session_cookie(&h).as_deref(), Some("lonely"));
}
+
+ #[test]
+ fn test_format_session_cookie_secure() {
+ let c = format_session_cookie("tok", 3600, true);
+ assert!(c.contains("Secure"));
+ assert!(c.contains("SameSite=Lax"));
+ assert!(c.contains("omtae_session=tok"));
+ }
+
+ #[test]
+ fn test_request_is_secure_forwarded_proto() {
+ let mut h = axum::http::HeaderMap::new();
+ h.insert("x-forwarded-proto", "https".parse().unwrap());
+ assert!(request_is_secure(&h));
+ }
+
+ #[test]
+ fn test_cookie_should_be_secure_public_host() {
+ let mut h = axum::http::HeaderMap::new();
+ h.insert("host", "desk.omtaeservices.biz".parse().unwrap());
+ assert!(cookie_should_be_secure(&h));
+ }
+
+ #[test]
+ fn test_cookie_should_be_secure_localhost() {
+ let mut h = axum::http::HeaderMap::new();
+ h.insert("host", "127.0.0.1:4200".parse().unwrap());
+ assert!(!cookie_should_be_secure(&h));
+ }
}
diff --git a/crates/openfang-api/src/types.rs b/crates/openfang-api/src/types.rs
index e4b9e61a77..57e7838676 100644
--- a/crates/openfang-api/src/types.rs
+++ b/crates/openfang-api/src/types.rs
@@ -1,4 +1,4 @@
-//! Request/response types for the OpenFang API.
+//! Request/response types for the OMTAE API.
use serde::{Deserialize, Serialize};
@@ -8,7 +8,7 @@ pub struct SpawnRequest {
/// Agent manifest as TOML string (optional if `template` is provided).
#[serde(default)]
pub manifest_toml: String,
- /// Template name from `~/.openfang/agents/{template}/agent.toml`.
+ /// Template name from `~/.omtae/agents/{template}/agent.toml`.
/// When provided and `manifest_toml` is empty, the template is loaded automatically.
#[serde(default)]
pub template: Option,
@@ -91,7 +91,7 @@ pub struct AgentUpdateRequest {
/// Request to change an agent's operational mode.
#[derive(Debug, Deserialize)]
pub struct SetModeRequest {
- pub mode: openfang_types::agent::AgentMode,
+ pub mode: omtae_types::agent::AgentMode,
}
/// Request to run a migration.
diff --git a/crates/openfang-api/src/webchat.rs b/crates/openfang-api/src/webchat.rs
index 14f44c62e1..b163cce92d 100644
--- a/crates/openfang-api/src/webchat.rs
+++ b/crates/openfang-api/src/webchat.rs
@@ -22,7 +22,7 @@ const NONCE_PLACEHOLDER: &str = "__NONCE__";
/// Not used for the dashboard page (nonce prevents caching) but retained
/// for potential future use by static asset handlers.
#[allow(dead_code)]
-const ETAG: &str = concat!("\"openfang-", env!("CARGO_PKG_VERSION"), "\"");
+const ETAG: &str = concat!("\"omtae-", env!("CARGO_PKG_VERSION"), "\"");
/// Embedded logo PNG for single-binary deployment.
const LOGO_PNG: &[u8] = include_bytes!("../static/logo.png");
@@ -30,7 +30,7 @@ const LOGO_PNG: &[u8] = include_bytes!("../static/logo.png");
/// Embedded favicon ICO for browser tabs.
const FAVICON_ICO: &[u8] = include_bytes!("../static/favicon.ico");
-/// GET /logo.png — Serve the OpenFang logo.
+/// GET /logo.png — Serve the OMTAE logo.
pub async fn logo_png() -> impl IntoResponse {
(
[
@@ -41,7 +41,7 @@ pub async fn logo_png() -> impl IntoResponse {
)
}
-/// GET /favicon.ico — Serve the OpenFang favicon.
+/// GET /favicon.ico — Serve the OMTAE favicon.
pub async fn favicon_ico() -> impl IntoResponse {
(
[
@@ -58,6 +58,32 @@ const MANIFEST_JSON: &str = include_str!("../static/manifest.json");
/// Embedded service worker for PWA support.
const SW_JS: &str = include_str!("../static/sw.js");
+/// Embedded i18n translation bundles (served at `/i18n/{lang}.json`).
+const I18N_EN_JSON: &str = include_str!("../static/i18n/en.json");
+const I18N_RU_JSON: &str = include_str!("../static/i18n/ru.json");
+
+/// GET /i18n/en.json — English UI strings for the dashboard.
+pub async fn i18n_en_json() -> impl IntoResponse {
+ (
+ [
+ (header::CONTENT_TYPE, "application/json; charset=utf-8"),
+ (header::CACHE_CONTROL, "public, max-age=3600"),
+ ],
+ I18N_EN_JSON,
+ )
+}
+
+/// GET /i18n/ru.json — Russian UI strings for the dashboard.
+pub async fn i18n_ru_json() -> impl IntoResponse {
+ (
+ [
+ (header::CONTENT_TYPE, "application/json; charset=utf-8"),
+ (header::CACHE_CONTROL, "public, max-age=3600"),
+ ],
+ I18N_RU_JSON,
+ )
+}
+
/// GET /manifest.json — Serve the PWA web app manifest.
pub async fn manifest_json() -> impl IntoResponse {
(
@@ -80,7 +106,7 @@ pub async fn sw_js() -> impl IntoResponse {
)
}
-/// GET / — Serve the OpenFang Dashboard single-page application.
+/// GET / — Serve the OMTAE Dashboard single-page application.
///
/// Generates a unique CSP nonce on every request and injects it into both
/// the `\n",
+ // i18n (must load before app code that calls window.i18n / window.t)
+ "\n",
// App code
"
diff --git a/crates/openfang-api/static/index_head.html b/crates/openfang-api/static/index_head.html
index 7bea957865..b1230d149d 100644
--- a/crates/openfang-api/static/index_head.html
+++ b/crates/openfang-api/static/index_head.html
@@ -1,13 +1,22 @@
-
+
-OpenFang Dashboard
+OMTAE Dashboard
-
+
+
+
diff --git a/crates/openfang-api/static/js/api.js b/crates/openfang-api/static/js/api.js
index dccb499f9a..6cf9327c4d 100644
--- a/crates/openfang-api/static/js/api.js
+++ b/crates/openfang-api/static/js/api.js
@@ -1,8 +1,8 @@
-// OpenFang API Client — Fetch wrapper, WebSocket manager, auth injection, toast notifications
+// OMTAE API Client — Fetch wrapper, WebSocket manager, auth injection, toast notifications
'use strict';
// ── Toast Notification System ──
-var OpenFangToast = (function() {
+var OMTAEToast = (function() {
var _container = null;
var _toastId = 0;
@@ -117,22 +117,30 @@ var OpenFangToast = (function() {
// ── Friendly Error Messages ──
function friendlyError(status, serverMsg) {
- if (status === 0 || !status) return 'Cannot reach daemon — is openfang running?';
+ if (status === 0 || !status) return 'Cannot reach daemon — is omtae running?';
if (status === 401) return 'Not authorized — check your API key';
if (status === 403) return 'Permission denied';
if (status === 404) return serverMsg || 'Resource not found';
if (status === 429) return 'Rate limited — slow down and try again';
if (status === 413) return 'Request too large';
- if (status === 500) return 'Server error — check daemon logs';
- if (status === 502 || status === 503) return 'Daemon unavailable — is it running?';
+ if (status === 500) return serverMsg || 'Server error — check daemon logs';
+ if (status === 400) return serverMsg || 'Bad request';
+ if (status === 502) return serverMsg || 'Upstream LLM error — check provider key and logs';
+ if (status === 503) return 'Daemon unavailable — is it running?';
return serverMsg || 'Unexpected error (' + status + ')';
}
// ── API Client ──
-var OpenFangAPI = (function() {
+var OMTAEAPI = (function() {
var BASE = window.location.origin;
var WS_BASE = BASE.replace(/^http/, 'ws');
var _authToken = '';
+ var _sessionToken = (function() {
+ try { return sessionStorage.getItem('omtae-session-token') || ''; } catch(e) { return ''; }
+ })();
+ var _pinHeader = (function() {
+ try { return sessionStorage.getItem('omtae-pin') || ''; } catch(e) { return ''; }
+ })();
// Connection state tracking
var _connectionState = 'connected';
@@ -141,9 +149,30 @@ var OpenFangAPI = (function() {
function setAuthToken(token) { _authToken = token; }
+ function setSessionToken(token) {
+ _sessionToken = token || '';
+ try {
+ if (_sessionToken) sessionStorage.setItem('omtae-session-token', _sessionToken);
+ else sessionStorage.removeItem('omtae-session-token');
+ } catch(e) { /* ignore */ }
+ }
+
+ function setPinHeader(pin) {
+ _pinHeader = pin || '';
+ try {
+ if (_pinHeader) sessionStorage.setItem('omtae-pin', _pinHeader);
+ else sessionStorage.removeItem('omtae-pin');
+ } catch(e) { /* ignore */ }
+ }
+
function headers() {
var h = { 'Content-Type': 'application/json' };
- if (_authToken) h['Authorization'] = 'Bearer ' + _authToken;
+ if (_authToken) {
+ h['Authorization'] = 'Bearer ' + _authToken;
+ } else if (_sessionToken) {
+ h['Authorization'] = 'Bearer ' + _sessionToken;
+ }
+ if (_pinHeader) h['X-OMTAE-Pin'] = _pinHeader;
return h;
}
@@ -156,18 +185,18 @@ var OpenFangAPI = (function() {
function onConnectionChange(fn) { _connectionListeners.push(fn); }
function request(method, path, body) {
- var opts = { method: method, headers: headers() };
+ var opts = { method: method, headers: headers(), credentials: 'include' };
if (body !== undefined) opts.body = JSON.stringify(body);
return fetch(BASE + path, opts).then(function(r) {
if (_connectionState !== 'connected') setConnectionState('connected');
if (!r.ok) {
// On 401, auto-show auth prompt so the user can re-enter their key
- if (r.status === 401 && typeof Alpine !== 'undefined') {
+ if (r.status === 401 && path !== '/api/auth/login' && typeof Alpine !== 'undefined') {
try {
var store = Alpine.store('app');
if (store && !store.showAuthPrompt) {
_authToken = '';
- localStorage.removeItem('openfang-api-key');
+ localStorage.removeItem('omtae-api-key');
store.showAuthPrompt = true;
}
} catch(e2) { /* ignore Alpine errors */ }
@@ -191,7 +220,7 @@ var OpenFangAPI = (function() {
}).catch(function(e) {
if (e.name === 'TypeError' && e.message.includes('Failed to fetch')) {
setConnectionState('disconnected');
- throw new Error('Cannot connect to daemon — is openfang running?');
+ throw new Error('Cannot connect to daemon — is omtae running?');
}
throw e;
});
@@ -223,7 +252,13 @@ var OpenFangAPI = (function() {
function _doConnect(agentId) {
try {
var url = WS_BASE + '/api/agents/' + agentId + '/ws';
- if (_authToken) url += '?token=' + encodeURIComponent(_authToken);
+ if (_authToken) {
+ url += '?token=' + encodeURIComponent(_authToken);
+ } else if (_sessionToken) {
+ url += '?session=' + encodeURIComponent(_sessionToken);
+ } else if (_pinHeader) {
+ url += '?pin=' + encodeURIComponent(_pinHeader);
+ }
var socket = new WebSocket(url);
_ws = socket;
@@ -234,7 +269,7 @@ var OpenFangAPI = (function() {
_reconnectAttempts = 0;
setConnectionState('connected');
if (_reconnectAttempt > 0) {
- OpenFangToast.success('Reconnected');
+ OMTAEToast.success('Reconnected');
_reconnectAttempt = 0;
}
if (_wsCallbacks.onOpen) _wsCallbacks.onOpen();
@@ -261,7 +296,7 @@ var OpenFangAPI = (function() {
_reconnectAttempt = _reconnectAttempts;
setConnectionState('reconnecting');
if (_reconnectAttempts === 1) {
- OpenFangToast.warn('Connection lost, reconnecting...');
+ OMTAEToast.warn('Connection lost, reconnecting...');
}
var delay = Math.min(1000 * Math.pow(2, _reconnectAttempts - 1), 10000);
_reconnectTimer = setTimeout(function() { _doConnect(_wsAgentId); }, delay);
@@ -269,7 +304,7 @@ var OpenFangAPI = (function() {
}
if (_wsAgentId && _reconnectAttempts >= MAX_RECONNECT) {
setConnectionState('disconnected');
- OpenFangToast.error('Connection lost — switched to HTTP mode', 0);
+ OMTAEToast.error('Connection lost — switched to HTTP mode', 0);
}
if (_wsCallbacks.onClose) _wsCallbacks.onClose();
};
@@ -310,12 +345,15 @@ var OpenFangAPI = (function() {
function upload(agentId, file) {
var hdrs = {};
if (_authToken) hdrs['Authorization'] = 'Bearer ' + _authToken;
+ else if (_sessionToken) hdrs['Authorization'] = 'Bearer ' + _sessionToken;
+ if (_pinHeader) hdrs['X-OMTAE-Pin'] = _pinHeader;
var form = new FormData();
form.append('file', file);
form.append('filename', file.name);
return fetch(BASE + '/api/agents/' + agentId + '/upload', {
method: 'POST',
headers: hdrs,
+ credentials: 'include',
body: form
}).then(function(r) {
if (!r.ok) throw new Error('Upload failed');
@@ -325,6 +363,8 @@ var OpenFangAPI = (function() {
return {
setAuthToken: setAuthToken,
+ setSessionToken: setSessionToken,
+ setPinHeader: setPinHeader,
getToken: getToken,
get: get,
post: post,
diff --git a/crates/openfang-api/static/js/app.js b/crates/openfang-api/static/js/app.js
index 8d2659ece5..e9a3ab1e0f 100644
--- a/crates/openfang-api/static/js/app.js
+++ b/crates/openfang-api/static/js/app.js
@@ -1,4 +1,4 @@
-// OpenFang App — Alpine.js init, hash router, global store
+// OMTAE App — Alpine.js init, hash router, global store
'use strict';
// Marked.js configuration
@@ -122,8 +122,8 @@ function toolIcon(toolName) {
// Alpine.js global store
document.addEventListener('alpine:init', function() {
// Restore saved API key on load
- var savedKey = localStorage.getItem('openfang-api-key');
- if (savedKey) OpenFangAPI.setAuthToken(savedKey);
+ var savedKey = localStorage.getItem('omtae-api-key');
+ if (savedKey) OMTAEAPI.setAuthToken(savedKey);
Alpine.store('app', {
agents: [],
@@ -137,20 +137,28 @@ document.addEventListener('alpine:init', function() {
pendingApprovalCount: 0,
lastPendingApprovalSignature: '',
pendingAgent: null,
- focusMode: localStorage.getItem('openfang-focus') === 'true',
+ focusMode: localStorage.getItem('omtae-focus') === 'true',
showOnboarding: false,
showAuthPrompt: false,
authMode: 'apikey',
sessionUser: null,
+ gpuStats: {
+ available: false,
+ gpu0: { temp: 0, usage: 0, memory: 0, memoryTotal: 24, processes: [] },
+ gpu1: { temp: 0, usage: 0, memory: 0, memoryTotal: 24, processes: [] },
+ usedVram: 0,
+ totalVram: 48,
+ model: 'vLLM'
+ },
toggleFocusMode() {
this.focusMode = !this.focusMode;
- localStorage.setItem('openfang-focus', this.focusMode);
+ localStorage.setItem('omtae-focus', this.focusMode);
},
async refreshAgents() {
try {
- var agents = await OpenFangAPI.get('/api/agents');
+ var agents = await OMTAEAPI.get('/api/agents');
this.agents = Array.isArray(agents) ? agents : [];
this.agentCount = this.agents.length;
} catch(e) { /* silent */ }
@@ -158,15 +166,15 @@ document.addEventListener('alpine:init', function() {
async refreshApprovals() {
try {
- var data = await OpenFangAPI.get('/api/approvals');
+ var data = await OMTAEAPI.get('/api/approvals');
var approvals = Array.isArray(data) ? data : (data.approvals || []);
var pending = approvals.filter(function(a) { return a.status === 'pending'; });
var signature = pending
.map(function(a) { return a.id; })
.sort()
.join(',');
- if (pending.length > 0 && signature !== this.lastPendingApprovalSignature && typeof OpenFangToast !== 'undefined') {
- OpenFangToast.warn('An agent is waiting for approval. Open Approvals to review.');
+ if (pending.length > 0 && signature !== this.lastPendingApprovalSignature && typeof OMTAEToast !== 'undefined') {
+ OMTAEToast.warn('An agent is waiting for approval. Open Approvals to review.');
}
this.pendingApprovalCount = pending.length;
this.lastPendingApprovalSignature = signature;
@@ -175,7 +183,7 @@ document.addEventListener('alpine:init', function() {
async checkStatus() {
try {
- var s = await OpenFangAPI.get('/api/status');
+ var s = await OMTAEAPI.get('/api/status');
this.connected = true;
this.booting = false;
this.lastError = '';
@@ -184,14 +192,23 @@ document.addEventListener('alpine:init', function() {
} catch(e) {
this.connected = false;
this.lastError = e.message || 'Unknown error';
- console.warn('[OpenFang] Status check failed:', e.message);
+ console.warn('[OMTAE] Status check failed:', e.message);
}
},
+ async refreshGpu() {
+ try {
+ var g = await OMTAEAPI.get('/api/system/gpu');
+ if (g && typeof g === 'object') {
+ this.gpuStats = g;
+ }
+ } catch(e) { /* silent — no nvidia-smi on this host */ }
+ },
+
async checkOnboarding() {
- if (localStorage.getItem('openfang-onboarded')) return;
+ if (localStorage.getItem('omtae-onboarded')) return;
try {
- var config = await OpenFangAPI.get('/api/config');
+ var config = await OMTAEAPI.get('/api/config');
var apiKey = config && config.api_key;
var noKey = !apiKey || apiKey === 'not set' || apiKey === '';
if (noKey && this.agentCount === 0) {
@@ -205,25 +222,25 @@ document.addEventListener('alpine:init', function() {
dismissOnboarding() {
this.showOnboarding = false;
- localStorage.setItem('openfang-onboarded', 'true');
+ localStorage.setItem('omtae-onboarded', 'true');
},
async checkAuth() {
try {
// First check if session-based auth is configured
- var authInfo = await OpenFangAPI.get('/api/auth/check');
+ var authInfo = await OMTAEAPI.get('/api/auth/check');
if (authInfo.mode === 'none') {
// No session auth — fall back to API key detection
this.authMode = 'apikey';
this.sessionUser = null;
- } else if (authInfo.mode === 'session') {
- this.authMode = 'session';
+ } else if (authInfo.mode === 'pin' || authInfo.mode === 'session') {
+ this.authMode = authInfo.mode;
if (authInfo.authenticated) {
this.sessionUser = authInfo.username;
this.showAuthPrompt = false;
return;
}
- // Session auth enabled but not authenticated — show login prompt
+ // Dashboard auth enabled but not authenticated — show login prompt
this.showAuthPrompt = true;
return;
}
@@ -231,14 +248,14 @@ document.addEventListener('alpine:init', function() {
// API key mode detection
try {
- await OpenFangAPI.get('/api/tools');
+ await OMTAEAPI.get('/api/tools');
this.showAuthPrompt = false;
} catch(e) {
if (e.message && (e.message.indexOf('Not authorized') >= 0 || e.message.indexOf('401') >= 0 || e.message.indexOf('Missing Authorization') >= 0 || e.message.indexOf('Unauthorized') >= 0)) {
- var saved = localStorage.getItem('openfang-api-key');
+ var saved = localStorage.getItem('omtae-api-key');
if (saved) {
- OpenFangAPI.setAuthToken('');
- localStorage.removeItem('openfang-api-key');
+ OMTAEAPI.setAuthToken('');
+ localStorage.removeItem('omtae-api-key');
}
this.showAuthPrompt = true;
}
@@ -247,38 +264,60 @@ document.addEventListener('alpine:init', function() {
submitApiKey(key) {
if (!key || !key.trim()) return;
- OpenFangAPI.setAuthToken(key.trim());
- localStorage.setItem('openfang-api-key', key.trim());
+ OMTAEAPI.setAuthToken(key.trim());
+ localStorage.setItem('omtae-api-key', key.trim());
this.showAuthPrompt = false;
this.refreshAgents();
},
+ async sessionPinLogin(pin) {
+ var trimmed = (pin || '').trim();
+ if (!trimmed) return;
+ try {
+ var result = await OMTAEAPI.post('/api/auth/login', { pin: trimmed });
+ if (result.status === 'ok') {
+ this.sessionUser = result.username || 'dashboard';
+ OMTAEAPI.setPinHeader(trimmed);
+ if (result.token) OMTAEAPI.setSessionToken(result.token);
+ this.showAuthPrompt = false;
+ this.refreshAgents();
+ } else {
+ OMTAEToast.error(result.error || 'Invalid PIN');
+ }
+ } catch(e) {
+ OMTAEToast.error(e.message || 'Invalid PIN');
+ }
+ },
+
async sessionLogin(username, password) {
try {
- var result = await OpenFangAPI.post('/api/auth/login', { username: username, password: password });
+ var result = await OMTAEAPI.post('/api/auth/login', { username: username, password: password });
if (result.status === 'ok') {
this.sessionUser = result.username;
+ if (result.token) OMTAEAPI.setSessionToken(result.token);
this.showAuthPrompt = false;
this.refreshAgents();
} else {
- OpenFangToast.error(result.error || 'Login failed');
+ OMTAEToast.error(result.error || 'Login failed');
}
} catch(e) {
- OpenFangToast.error(e.message || 'Login failed');
+ OMTAEToast.error(e.message || 'Login failed');
}
},
async sessionLogout() {
try {
- await OpenFangAPI.post('/api/auth/logout');
+ await OMTAEAPI.post('/api/auth/logout');
} catch(e) { /* ignore */ }
+ OMTAEAPI.setPinHeader('');
+ OMTAEAPI.setSessionToken('');
this.sessionUser = null;
this.showAuthPrompt = true;
},
clearApiKey() {
- OpenFangAPI.setAuthToken('');
- localStorage.removeItem('openfang-api-key');
+ OMTAEAPI.setAuthToken('');
+ localStorage.removeItem('omtae-api-key');
}
});
});
@@ -287,13 +326,9 @@ document.addEventListener('alpine:init', function() {
function app() {
return {
page: 'agents',
- themeMode: localStorage.getItem('openfang-theme-mode') || 'system',
- theme: (() => {
- var mode = localStorage.getItem('openfang-theme-mode') || 'system';
- if (mode === 'system') return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
- return mode;
- })(),
- sidebarCollapsed: localStorage.getItem('openfang-sidebar') === 'collapsed',
+ themeMode: 'dark',
+ theme: 'dark',
+ sidebarCollapsed: localStorage.getItem('omtae-sidebar') === 'collapsed',
mobileMenuOpen: false,
connected: false,
wsConnected: false,
@@ -304,15 +339,10 @@ function app() {
init() {
var self = this;
+ localStorage.setItem('omtae-theme-mode', 'dark');
+ this.applyTheme('dark');
- // Listen for OS theme changes (only matters when mode is 'system')
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
- if (self.themeMode === 'system') {
- self.theme = e.matches ? 'dark' : 'light';
- }
- });
-
- // Hash routing
+ // Connection state listener
var validPages = ['overview','agents','sessions','approvals','comms','workflows','scheduler','channels','skills','hands','analytics','logs','runtime','settings','wizard'];
var pageRedirects = {
'chat': 'agents',
@@ -358,12 +388,12 @@ function app() {
}
// Escape — close mobile menu
if (e.key === 'Escape') {
- self.mobileMenuOpen = false;
+ self.closeMobileMenu();
}
});
// Connection state listener
- OpenFangAPI.onConnectionChange(function(state) {
+ OMTAEAPI.onConnectionChange(function(state) {
Alpine.store('app').connectionState = state;
});
@@ -376,43 +406,68 @@ function app() {
self.pollStatus();
Alpine.store('app').refreshApprovals();
}, 5000);
+
+ // Close mobile drawer when viewport crosses desktop breakpoint
+ var mobileMq = window.matchMedia('(min-width: 769px) and (hover: hover), (min-width: 1025px)');
+ function onViewportChange() {
+ if (mobileMq.matches) self.closeMobileMenu();
+ }
+ if (mobileMq.addEventListener) {
+ mobileMq.addEventListener('change', onViewportChange);
+ } else if (mobileMq.addListener) {
+ mobileMq.addListener(onViewportChange);
+ }
+ window.addEventListener('orientationchange', function() {
+ setTimeout(onViewportChange, 100);
+ });
},
navigate(p) {
this.page = p;
window.location.hash = p;
+ this.closeMobileMenu();
+ },
+
+ toggleMobileMenu() {
+ this.mobileMenuOpen = !this.mobileMenuOpen;
+ document.body.classList.toggle('mobile-nav-open', this.mobileMenuOpen);
+ },
+
+ closeMobileMenu() {
+ if (!this.mobileMenuOpen) return;
this.mobileMenuOpen = false;
+ document.body.classList.remove('mobile-nav-open');
+ },
+
+ applyTheme(theme) {
+ document.documentElement.setAttribute('data-theme', 'dark');
},
setTheme(mode) {
- this.themeMode = mode;
- localStorage.setItem('openfang-theme-mode', mode);
- if (mode === 'system') {
- this.theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
- } else {
- this.theme = mode;
- }
+ this.themeMode = 'dark';
+ this.theme = 'dark';
+ localStorage.setItem('omtae-theme-mode', 'dark');
+ this.applyTheme('dark');
},
toggleTheme() {
- var modes = ['light', 'system', 'dark'];
- var next = modes[(modes.indexOf(this.themeMode) + 1) % modes.length];
- this.setTheme(next);
+ this.setTheme('dark');
},
toggleSidebar() {
this.sidebarCollapsed = !this.sidebarCollapsed;
- localStorage.setItem('openfang-sidebar', this.sidebarCollapsed ? 'collapsed' : 'expanded');
+ localStorage.setItem('omtae-sidebar', this.sidebarCollapsed ? 'collapsed' : 'expanded');
},
async pollStatus() {
var store = Alpine.store('app');
await store.checkStatus();
+ await store.refreshGpu();
await store.refreshAgents();
this.connected = store.connected;
this.version = store.version;
this.agentCount = store.agentCount;
- this.wsConnected = OpenFangAPI.isWsConnected();
+ this.wsConnected = OMTAEAPI.isWsConnected();
}
};
}
diff --git a/crates/openfang-api/static/js/katex.js b/crates/openfang-api/static/js/katex.js
index 8a704ade84..d0e1a3779d 100644
--- a/crates/openfang-api/static/js/katex.js
+++ b/crates/openfang-api/static/js/katex.js
@@ -32,7 +32,7 @@ function ensureKatexLoaded() {
if (katexLoadPromise) return katexLoadPromise;
katexLoadPromise = new Promise(function (resolve) {
- var cssId = 'openfang-katex-css';
+ var cssId = 'omtae-katex-css';
if (!document.getElementById(cssId)) {
var link = document.createElement('link');
link.id = cssId;
diff --git a/crates/openfang-api/static/js/pages/agents.js b/crates/openfang-api/static/js/pages/agents.js
index b6702855f1..c30a8b92ee 100644
--- a/crates/openfang-api/static/js/pages/agents.js
+++ b/crates/openfang-api/static/js/pages/agents.js
@@ -1,4 +1,4 @@
-// OpenFang Agents Page — Multi-step spawn wizard, detail view with tabs, file editor, personality presets
+// OMTAE Agents Page — Multi-step spawn wizard, detail view with tabs, file editor, personality presets
'use strict';
/** Escape a string for use inside TOML triple-quoted strings ("""\n...\n""").
@@ -43,7 +43,7 @@ function agentsPage() {
spawnProviders: [], // populated from /api/providers on wizard open
spawnProvidersLoading: false,
spawnStep: 1,
- spawnIdentity: { emoji: '', color: '#FF5C00', archetype: '' },
+ spawnIdentity: { emoji: '', color: '#00F0FF', archetype: '' },
selectedPreset: '',
soulContent: '',
emojiOptions: [
@@ -142,7 +142,7 @@ function agentsPage() {
async loadSpawnProfiles() {
if (this.spawnProfilesLoaded) return;
try {
- var data = await OpenFangAPI.get('/api/profiles');
+ var data = await OMTAEAPI.get('/api/profiles');
this.spawnProfiles = data.profiles || [];
this.spawnProfilesLoaded = true;
} catch(e) { this.spawnProfiles = []; }
@@ -223,6 +223,9 @@ function agentsPage() {
}
this.loading = false;
+ // Restore preferred chat agent (researcher/analyst/orchestrator/coder)
+ this._restorePreferredChat();
+
// If a pending agent was set (e.g. from wizard or redirect), open chat inline
var store = Alpine.store('app');
if (store.pendingAgent) {
@@ -252,8 +255,8 @@ function agentsPage() {
this.tplLoadError = '';
try {
var results = await Promise.all([
- OpenFangAPI.get('/api/templates'),
- OpenFangAPI.get('/api/providers').catch(function() { return { providers: [] }; })
+ OMTAEAPI.get('/api/templates'),
+ OMTAEAPI.get('/api/providers').catch(function() { return { providers: [] }; })
]);
// Combine static and dynamic templates
this.builtinTemplates = [
@@ -328,13 +331,50 @@ function agentsPage() {
},
chatWithAgent(agent) {
+ if (typeof ofIsPreferredAgent === 'function' && !ofIsPreferredAgent(agent)) {
+ var preferred = typeof ofPickPreferredAgent === 'function'
+ ? ofPickPreferredAgent(this.agents || [])
+ : null;
+ if (preferred) agent = preferred;
+ }
Alpine.store('app').pendingAgent = agent;
this.activeChatAgent = agent;
},
+ _restorePreferredChat: function() {
+ var store = Alpine.store('app');
+ if (store && store.pendingAgent) return;
+ var agents = this.agents || [];
+ if (!agents.length) return;
+
+ var storedId = null;
+ try { storedId = localStorage.getItem('of-active-agent'); } catch(e) { /* ignore */ }
+
+ var match = null;
+ if (storedId) {
+ for (var i = 0; i < agents.length; i++) {
+ if (agents[i] && agents[i].id === storedId) { match = agents[i]; break; }
+ }
+ }
+
+ if (match && typeof ofIsPreferredAgent === 'function' && !ofIsPreferredAgent(match)) {
+ try { localStorage.removeItem('of-active-agent'); } catch(e) { /* ignore */ }
+ match = null;
+ }
+
+ if (!match && typeof ofPickPreferredAgent === 'function') {
+ match = ofPickPreferredAgent(agents);
+ }
+
+ if (match) {
+ store.pendingAgent = match;
+ this.activeChatAgent = match;
+ }
+ },
+
closeChat() {
this.activeChatAgent = null;
- OpenFangAPI.wsDisconnect();
+ OMTAEAPI.wsDisconnect();
},
buildConfigForm(agent) {
@@ -343,7 +383,7 @@ function agentsPage() {
name: (agent && agent.name) || '',
system_prompt: (agent && agent.system_prompt) || '',
emoji: identity.emoji || '',
- color: identity.color || '#FF5C00',
+ color: identity.color || '#00F0FF',
archetype: identity.archetype || '',
vibe: identity.vibe || ''
};
@@ -360,7 +400,7 @@ function agentsPage() {
// fields such as system_prompt and identity metadata are hydrated.
var detail = agent;
try {
- var full = await OpenFangAPI.get('/api/agents/' + agent.id);
+ var full = await OMTAEAPI.get('/api/agents/' + agent.id);
detail = Object.assign({}, agent, full, {
identity: Object.assign({}, (agent && agent.identity) || {}, (full && full.identity) || {})
});
@@ -373,36 +413,36 @@ function agentsPage() {
killAgent(agent) {
var self = this;
- OpenFangToast.confirm('Stop Agent', 'Stop agent "' + agent.name + '"? The agent will be shut down.', async function() {
+ OMTAEToast.confirm('Stop Agent', 'Stop agent "' + agent.name + '"? The agent will be shut down.', async function() {
try {
- await OpenFangAPI.del('/api/agents/' + agent.id);
- OpenFangToast.success('Agent "' + agent.name + '" stopped');
+ await OMTAEAPI.del('/api/agents/' + agent.id);
+ OMTAEToast.success('Agent "' + agent.name + '" stopped');
self.showDetailModal = false;
await Alpine.store('app').refreshAgents();
} catch(e) {
- OpenFangToast.error('Failed to stop agent: ' + e.message);
+ OMTAEToast.error('Failed to stop agent: ' + e.message);
}
});
},
- // Issue #1163: uninstall an agent (kill + remove ~/.openfang/agents//).
+ // Issue #1163: uninstall an agent (kill + remove ~/.omtae/agents//).
uninstallAgent(agent) {
var self = this;
- OpenFangToast.confirm(
+ OMTAEToast.confirm(
'Uninstall Agent',
'Uninstall agent "' + agent.name + '"? This stops the agent AND deletes its files from your workspace. This cannot be undone.',
async function() {
try {
- var res = await OpenFangAPI.del('/api/agents/' + agent.id + '/uninstall');
+ var res = await OMTAEAPI.del('/api/agents/' + agent.id + '/uninstall');
var msg = 'Agent "' + agent.name + '" uninstalled';
if (res && res.dir_removed === false) {
msg += ' (no on-disk files found)';
}
- OpenFangToast.success(msg);
+ OMTAEToast.success(msg);
self.showDetailModal = false;
await Alpine.store('app').refreshAgents();
} catch(e) {
- OpenFangToast.error('Failed to uninstall agent: ' + e.message);
+ OMTAEToast.error('Failed to uninstall agent: ' + e.message);
}
}
);
@@ -411,18 +451,18 @@ function agentsPage() {
killAllAgents() {
var list = this.filteredAgents;
if (!list.length) return;
- OpenFangToast.confirm('Stop All Agents', 'Stop ' + list.length + ' agent(s)? All agents will be shut down.', async function() {
+ OMTAEToast.confirm('Stop All Agents', 'Stop ' + list.length + ' agent(s)? All agents will be shut down.', async function() {
var errors = [];
for (var i = 0; i < list.length; i++) {
try {
- await OpenFangAPI.del('/api/agents/' + list[i].id);
+ await OMTAEAPI.del('/api/agents/' + list[i].id);
} catch(e) { errors.push(list[i].name + ': ' + e.message); }
}
await Alpine.store('app').refreshAgents();
if (errors.length) {
- OpenFangToast.error('Some agents failed to stop: ' + errors.join(', '));
+ OMTAEToast.error('Some agents failed to stop: ' + errors.join(', '));
} else {
- OpenFangToast.success(list.length + ' agent(s) stopped');
+ OMTAEToast.success(list.length + ' agent(s) stopped');
}
});
},
@@ -432,7 +472,7 @@ function agentsPage() {
this.showSpawnModal = true;
this.spawnStep = 1;
this.spawnMode = 'wizard';
- this.spawnIdentity = { emoji: '', color: '#FF5C00', archetype: '' };
+ this.spawnIdentity = { emoji: '', color: '#00F0FF', archetype: '' };
this.selectedPreset = '';
this.soulContent = '';
this.spawnForm.name = '';
@@ -444,8 +484,8 @@ function agentsPage() {
this.spawnProvidersLoading = true;
try {
var results = await Promise.all([
- OpenFangAPI.get('/api/status').catch(function() { return {}; }),
- OpenFangAPI.get('/api/providers').catch(function() { return { providers: [] }; })
+ OMTAEAPI.get('/api/status').catch(function() { return {}; }),
+ OMTAEAPI.get('/api/providers').catch(function() { return { providers: [] }; })
]);
var status = results[0];
var provData = results[1];
@@ -460,7 +500,7 @@ function agentsPage() {
nextStep() {
if (this.spawnStep === 1 && !this.spawnForm.name.trim()) {
- OpenFangToast.warn('Please enter an agent name');
+ OMTAEToast.warn('Please enter an agent name');
return;
}
if (this.spawnStep < 5) this.spawnStep++;
@@ -502,12 +542,12 @@ function agentsPage() {
async setMode(agent, mode) {
try {
- await OpenFangAPI.put('/api/agents/' + agent.id + '/mode', { mode: mode });
+ await OMTAEAPI.put('/api/agents/' + agent.id + '/mode', { mode: mode });
agent.mode = mode;
- OpenFangToast.success('Mode set to ' + mode);
+ OMTAEToast.success('Mode set to ' + mode);
await Alpine.store('app').refreshAgents();
} catch(e) {
- OpenFangToast.error('Failed to set mode: ' + e.message);
+ OMTAEToast.error('Failed to set mode: ' + e.message);
}
},
@@ -516,12 +556,12 @@ function agentsPage() {
var toml = this.spawnMode === 'wizard' ? this.generateToml() : this.spawnToml;
if (!toml.trim()) {
this.spawning = false;
- OpenFangToast.warn('Manifest is empty \u2014 enter agent config first');
+ OMTAEToast.warn('Manifest is empty \u2014 enter agent config first');
return;
}
try {
- var res = await OpenFangAPI.post('/api/agents', { manifest_toml: toml });
+ var res = await OMTAEAPI.post('/api/agents', { manifest_toml: toml });
if (res.agent_id) {
// Post-spawn: update identity + write SOUL.md if personality preset selected
var patchBody = {};
@@ -531,24 +571,24 @@ function agentsPage() {
if (this.selectedPreset) patchBody.vibe = this.selectedPreset;
if (Object.keys(patchBody).length) {
- OpenFangAPI.patch('/api/agents/' + res.agent_id + '/config', patchBody).catch(function(e) { console.warn('Post-spawn config patch failed:', e.message); });
+ OMTAEAPI.patch('/api/agents/' + res.agent_id + '/config', patchBody).catch(function(e) { console.warn('Post-spawn config patch failed:', e.message); });
}
if (this.soulContent.trim()) {
- OpenFangAPI.put('/api/agents/' + res.agent_id + '/files/SOUL.md', { content: '# Soul\n' + this.soulContent }).catch(function(e) { console.warn('SOUL.md write failed:', e.message); });
+ OMTAEAPI.put('/api/agents/' + res.agent_id + '/files/SOUL.md', { content: '# Soul\n' + this.soulContent }).catch(function(e) { console.warn('SOUL.md write failed:', e.message); });
}
this.showSpawnModal = false;
this.spawnForm.name = '';
this.spawnToml = '';
this.spawnStep = 1;
- OpenFangToast.success('Agent "' + (res.name || 'new') + '" spawned');
+ OMTAEToast.success('Agent "' + (res.name || 'new') + '" spawned');
await Alpine.store('app').refreshAgents();
this.chatWithAgent({ id: res.agent_id, name: res.name, model_provider: '?', model_name: '?' });
} else {
- OpenFangToast.error('Spawn failed: ' + (res.error || 'Unknown error'));
+ OMTAEToast.error('Spawn failed: ' + (res.error || 'Unknown error'));
}
} catch(e) {
- OpenFangToast.error('Failed to spawn agent: ' + e.message);
+ OMTAEToast.error('Failed to spawn agent: ' + e.message);
}
this.spawning = false;
},
@@ -558,11 +598,11 @@ function agentsPage() {
if (!this.detailAgent) return;
this.filesLoading = true;
try {
- var data = await OpenFangAPI.get('/api/agents/' + this.detailAgent.id + '/files');
+ var data = await OMTAEAPI.get('/api/agents/' + this.detailAgent.id + '/files');
this.agentFiles = data.files || [];
} catch(e) {
this.agentFiles = [];
- OpenFangToast.error('Failed to load files: ' + e.message);
+ OMTAEToast.error('Failed to load files: ' + e.message);
}
this.filesLoading = false;
},
@@ -575,11 +615,11 @@ function agentsPage() {
return;
}
try {
- var data = await OpenFangAPI.get('/api/agents/' + this.detailAgent.id + '/files/' + encodeURIComponent(file.name));
+ var data = await OMTAEAPI.get('/api/agents/' + this.detailAgent.id + '/files/' + encodeURIComponent(file.name));
this.editingFile = file.name;
this.fileContent = data.content || '';
} catch(e) {
- OpenFangToast.error('Failed to read file: ' + e.message);
+ OMTAEToast.error('Failed to read file: ' + e.message);
}
},
@@ -587,11 +627,11 @@ function agentsPage() {
if (!this.editingFile || !this.detailAgent) return;
this.fileSaving = true;
try {
- await OpenFangAPI.put('/api/agents/' + this.detailAgent.id + '/files/' + encodeURIComponent(this.editingFile), { content: this.fileContent });
- OpenFangToast.success(this.editingFile + ' saved');
+ await OMTAEAPI.put('/api/agents/' + this.detailAgent.id + '/files/' + encodeURIComponent(this.editingFile), { content: this.fileContent });
+ OMTAEToast.success(this.editingFile + ' saved');
await this.loadAgentFiles();
} catch(e) {
- OpenFangToast.error('Failed to save file: ' + e.message);
+ OMTAEToast.error('Failed to save file: ' + e.message);
}
this.fileSaving = false;
},
@@ -606,11 +646,11 @@ function agentsPage() {
if (!this.detailAgent) return;
this.configSaving = true;
try {
- await OpenFangAPI.patch('/api/agents/' + this.detailAgent.id + '/config', this.configForm);
- OpenFangToast.success('Config updated');
+ await OMTAEAPI.patch('/api/agents/' + this.detailAgent.id + '/config', this.configForm);
+ OMTAEToast.success('Config updated');
await Alpine.store('app').refreshAgents();
} catch(e) {
- OpenFangToast.error('Failed to save config: ' + e.message);
+ OMTAEToast.error('Failed to save config: ' + e.message);
}
this.configSaving = false;
},
@@ -619,14 +659,14 @@ function agentsPage() {
async cloneAgent(agent) {
var newName = (agent.name || 'agent') + '-copy';
try {
- var res = await OpenFangAPI.post('/api/agents/' + agent.id + '/clone', { new_name: newName });
+ var res = await OMTAEAPI.post('/api/agents/' + agent.id + '/clone', { new_name: newName });
if (res.agent_id) {
- OpenFangToast.success('Cloned as "' + res.name + '"');
+ OMTAEToast.success('Cloned as "' + res.name + '"');
await Alpine.store('app').refreshAgents();
this.showDetailModal = false;
}
} catch(e) {
- OpenFangToast.error('Clone failed: ' + e.message);
+ OMTAEToast.error('Clone failed: ' + e.message);
}
},
@@ -636,31 +676,31 @@ function agentsPage() {
var manifestToml = template.manifest_toml;
if (!manifestToml) {
// If template doesn't have manifest_toml, fetch it from the API
- var data = await OpenFangAPI.get('/api/templates/' + encodeURIComponent(template.name));
+ var data = await OMTAEAPI.get('/api/templates/' + encodeURIComponent(template.name));
manifestToml = data.manifest_toml;
}
if (manifestToml) {
- var res = await OpenFangAPI.post('/api/agents', { manifest_toml: manifestToml });
+ var res = await OMTAEAPI.post('/api/agents', { manifest_toml: manifestToml });
if (res.agent_id) {
- OpenFangToast.success('Agent "' + (res.name || template.name) + '" spawned from template');
+ OMTAEToast.success('Agent "' + (res.name || template.name) + '" spawned from template');
await Alpine.store('app').refreshAgents();
this.chatWithAgent({ id: res.agent_id, name: res.name || template.name, model_provider: '?', model_name: '?' });
}
}
} catch(e) {
- OpenFangToast.error('Failed to spawn from template: ' + e.message);
+ OMTAEToast.error('Failed to spawn from template: ' + e.message);
}
},
// ── Clear agent history ──
async clearHistory(agent) {
var self = this;
- OpenFangToast.confirm('Clear History', 'Clear all conversation history for "' + agent.name + '"? This cannot be undone.', async function() {
+ OMTAEToast.confirm('Clear History', 'Clear all conversation history for "' + agent.name + '"? This cannot be undone.', async function() {
try {
- await OpenFangAPI.del('/api/agents/' + agent.id + '/history');
- OpenFangToast.success('History cleared for "' + agent.name + '"');
+ await OMTAEAPI.del('/api/agents/' + agent.id + '/history');
+ OMTAEToast.success('History cleared for "' + agent.name + '"');
} catch(e) {
- OpenFangToast.error('Failed to clear history: ' + e.message);
+ OMTAEToast.error('Failed to clear history: ' + e.message);
}
});
},
@@ -670,9 +710,9 @@ function agentsPage() {
if (!this.detailAgent || !this.newModelValue.trim()) return;
this.modelSaving = true;
try {
- var resp = await OpenFangAPI.put('/api/agents/' + this.detailAgent.id + '/model', { model: this.newModelValue.trim() });
+ var resp = await OMTAEAPI.put('/api/agents/' + this.detailAgent.id + '/model', { model: this.newModelValue.trim() });
var providerInfo = (resp && resp.provider) ? ' (provider: ' + resp.provider + ')' : '';
- OpenFangToast.success('Model changed' + providerInfo + ' (memory reset)');
+ OMTAEToast.success('Model changed' + providerInfo + ' (memory reset)');
this.editingModel = false;
await Alpine.store('app').refreshAgents();
// Refresh detailAgent
@@ -681,7 +721,7 @@ function agentsPage() {
if (agents[i].id === this.detailAgent.id) { this.detailAgent = agents[i]; break; }
}
} catch(e) {
- OpenFangToast.error('Failed to change model: ' + e.message);
+ OMTAEToast.error('Failed to change model: ' + e.message);
}
this.modelSaving = false;
},
@@ -692,8 +732,8 @@ function agentsPage() {
this.modelSaving = true;
try {
var combined = this.newProviderValue.trim() + '/' + this.detailAgent.model_name;
- var resp = await OpenFangAPI.put('/api/agents/' + this.detailAgent.id + '/model', { model: combined });
- OpenFangToast.success('Provider changed to ' + (resp && resp.provider ? resp.provider : this.newProviderValue.trim()));
+ var resp = await OMTAEAPI.put('/api/agents/' + this.detailAgent.id + '/model', { model: combined });
+ OMTAEToast.success('Provider changed to ' + (resp && resp.provider ? resp.provider : this.newProviderValue.trim()));
this.editingProvider = false;
await Alpine.store('app').refreshAgents();
var agents = Alpine.store('app').agents;
@@ -701,7 +741,7 @@ function agentsPage() {
if (agents[i].id === this.detailAgent.id) { this.detailAgent = agents[i]; break; }
}
} catch(e) {
- OpenFangToast.error('Failed to change provider: ' + e.message);
+ OMTAEToast.error('Failed to change provider: ' + e.message);
}
this.modelSaving = false;
},
@@ -715,12 +755,12 @@ function agentsPage() {
if (!this.detailAgent._fallbacks) this.detailAgent._fallbacks = [];
this.detailAgent._fallbacks.push({ provider: provider, model: model });
try {
- await OpenFangAPI.patch('/api/agents/' + this.detailAgent.id + '/config', {
+ await OMTAEAPI.patch('/api/agents/' + this.detailAgent.id + '/config', {
fallback_models: this.detailAgent._fallbacks
});
- OpenFangToast.success('Fallback added: ' + provider + '/' + model);
+ OMTAEToast.success('Fallback added: ' + provider + '/' + model);
} catch(e) {
- OpenFangToast.error('Failed to save fallbacks: ' + e.message);
+ OMTAEToast.error('Failed to save fallbacks: ' + e.message);
this.detailAgent._fallbacks.pop();
}
this.editingFallback = false;
@@ -731,12 +771,12 @@ function agentsPage() {
if (!this.detailAgent || !this.detailAgent._fallbacks) return;
var removed = this.detailAgent._fallbacks.splice(idx, 1);
try {
- await OpenFangAPI.patch('/api/agents/' + this.detailAgent.id + '/config', {
+ await OMTAEAPI.patch('/api/agents/' + this.detailAgent.id + '/config', {
fallback_models: this.detailAgent._fallbacks
});
- OpenFangToast.success('Fallback removed');
+ OMTAEToast.success('Fallback removed');
} catch(e) {
- OpenFangToast.error('Failed to save fallbacks: ' + e.message);
+ OMTAEToast.error('Failed to save fallbacks: ' + e.message);
this.detailAgent._fallbacks.splice(idx, 0, removed[0]);
}
},
@@ -746,7 +786,7 @@ function agentsPage() {
if (!this.detailAgent) return;
this.toolFiltersLoading = true;
try {
- this.toolFilters = await OpenFangAPI.get('/api/agents/' + this.detailAgent.id + '/tools');
+ this.toolFilters = await OMTAEAPI.get('/api/agents/' + this.detailAgent.id + '/tools');
} catch(e) {
this.toolFilters = { tool_allowlist: [], tool_blocklist: [] };
}
@@ -784,9 +824,9 @@ function agentsPage() {
async saveToolFilters() {
if (!this.detailAgent) return;
try {
- await OpenFangAPI.put('/api/agents/' + this.detailAgent.id + '/tools', this.toolFilters);
+ await OMTAEAPI.put('/api/agents/' + this.detailAgent.id + '/tools', this.toolFilters);
} catch(e) {
- OpenFangToast.error('Failed to update tool filters: ' + e.message);
+ OMTAEToast.error('Failed to update tool filters: ' + e.message);
}
},
@@ -799,14 +839,14 @@ function agentsPage() {
toml += 'system_prompt = """\n' + tomlMultilineEscape(t.system_prompt) + '\n"""\n';
try {
- var res = await OpenFangAPI.post('/api/agents', { manifest_toml: toml });
+ var res = await OMTAEAPI.post('/api/agents', { manifest_toml: toml });
if (res.agent_id) {
- OpenFangToast.success('Agent "' + t.name + '" spawned');
+ OMTAEToast.success('Agent "' + t.name + '" spawned');
await Alpine.store('app').refreshAgents();
this.chatWithAgent({ id: res.agent_id, name: t.name, model_provider: t.provider, model_name: t.model });
}
} catch(e) {
- OpenFangToast.error('Failed to spawn agent: ' + e.message);
+ OMTAEToast.error('Failed to spawn agent: ' + e.message);
}
}
};
diff --git a/crates/openfang-api/static/js/pages/approvals.js b/crates/openfang-api/static/js/pages/approvals.js
index 52426a5b85..b3f9e6b427 100644
--- a/crates/openfang-api/static/js/pages/approvals.js
+++ b/crates/openfang-api/static/js/pages/approvals.js
@@ -1,4 +1,4 @@
-// OpenFang Approvals Page — Execution approval queue for sensitive agent actions
+// OMTAE Approvals Page — Execution approval queue for sensitive agent actions
'use strict';
function approvalsPage() {
@@ -38,7 +38,7 @@ function approvalsPage() {
this.loading = true;
this.loadError = '';
try {
- var data = await OpenFangAPI.get('/api/approvals');
+ var data = await OMTAEAPI.get('/api/approvals');
this.approvals = data.approvals || [];
} catch(e) {
this.loadError = e.message || 'Could not load approvals.';
@@ -48,23 +48,23 @@ function approvalsPage() {
async approve(id) {
try {
- await OpenFangAPI.post('/api/approvals/' + id + '/approve', {});
- OpenFangToast.success('Approved');
+ await OMTAEAPI.post('/api/approvals/' + id + '/approve', {});
+ OMTAEToast.success('Approved');
await this.loadData();
} catch(e) {
- OpenFangToast.error(e.message);
+ OMTAEToast.error(e.message);
}
},
async reject(id) {
var self = this;
- OpenFangToast.confirm('Reject Action', 'Are you sure you want to reject this action?', async function() {
+ OMTAEToast.confirm('Reject Action', 'Are you sure you want to reject this action?', async function() {
try {
- await OpenFangAPI.post('/api/approvals/' + id + '/reject', {});
- OpenFangToast.success('Rejected');
+ await OMTAEAPI.post('/api/approvals/' + id + '/reject', {});
+ OMTAEToast.success('Rejected');
await self.loadData();
} catch(e) {
- OpenFangToast.error(e.message);
+ OMTAEToast.error(e.message);
}
});
},
diff --git a/crates/openfang-api/static/js/pages/brain.js b/crates/openfang-api/static/js/pages/brain.js
new file mode 100644
index 0000000000..593af5582b
--- /dev/null
+++ b/crates/openfang-api/static/js/pages/brain.js
@@ -0,0 +1,106 @@
+// OMTAE Brain Page — Obsidian vault browser (leads, skiptrace reports)
+'use strict';
+
+function brainPage() {
+ return {
+ status: {},
+ currentPath: '',
+ entries: [],
+ recentLeads: [],
+ selectedFile: null,
+ fileContent: '',
+ fileMeta: {},
+ loading: true,
+ fileLoading: false,
+ loadError: '',
+ pathStack: [],
+
+ async loadBrain() {
+ this.loading = true;
+ this.loadError = '';
+ try {
+ var status = await OMTAEAPI.get('/api/brain/status');
+ this.status = status || {};
+ await this.loadDirectory('');
+ } catch (e) {
+ this.loadError = e.message || 'Could not load brain vault.';
+ }
+ this.loading = false;
+ },
+
+ async loadDirectory(path) {
+ var q = path ? ('?path=' + encodeURIComponent(path)) : '';
+ var data = await OMTAEAPI.get('/api/brain/list' + q);
+ this.currentPath = data.path || path || '';
+ this.entries = data.entries || [];
+ this.recentLeads = data.recent_leads || [];
+ if (data.vault_path) {
+ this.status.vault_path = data.vault_path;
+ this.status.vault_name = data.vault_name;
+ }
+ this.pathStack = this.currentPath ? this.currentPath.split('/') : [];
+ },
+
+ async openEntry(entry) {
+ if (!entry) return;
+ if (entry.kind === 'dir') {
+ this.selectedFile = null;
+ this.fileContent = '';
+ await this.loadDirectory(entry.path);
+ return;
+ }
+ await this.openFile(entry.path);
+ },
+
+ async openFile(path) {
+ this.fileLoading = true;
+ this.selectedFile = path;
+ try {
+ var data = await OMTAEAPI.get('/api/brain/file?path=' + encodeURIComponent(path));
+ this.fileContent = data.content || '';
+ this.fileMeta = data;
+ } catch (e) {
+ this.fileContent = '';
+ if (typeof OMTAEToast !== 'undefined') OMTAEToast.error(e.message || 'Could not open file');
+ }
+ this.fileLoading = false;
+ },
+
+ async goUp() {
+ if (!this.currentPath) return;
+ var parts = this.currentPath.split('/');
+ parts.pop();
+ await this.loadDirectory(parts.join('/'));
+ },
+
+ async goRoot() {
+ this.selectedFile = null;
+ this.fileContent = '';
+ await this.loadDirectory('');
+ },
+
+ async goToCrumb(index) {
+ var parts = this.pathStack.slice(0, index + 1);
+ await this.loadDirectory(parts.join('/'));
+ },
+
+ obsidianLink(path) {
+ var vault = (this.status && this.status.vault_name) || 'omtae-brain';
+ var rel = path || this.selectedFile || '';
+ return 'obsidian://open?vault=' + encodeURIComponent(vault) + '&file=' + encodeURIComponent(rel);
+ },
+
+ renderPreview(text) {
+ return typeof renderMarkdown === 'function' ? renderMarkdown(text) : escapeHtml(text);
+ },
+
+ formatModified(entry) {
+ if (!entry || !entry.modified) return '';
+ try {
+ return new Date(entry.modified).toLocaleString();
+ } catch (e) {
+ return entry.modified;
+ }
+ }
+ };
+}
diff --git a/crates/openfang-api/static/js/pages/channels.js b/crates/openfang-api/static/js/pages/channels.js
index 84568e7e54..59d9061243 100644
--- a/crates/openfang-api/static/js/pages/channels.js
+++ b/crates/openfang-api/static/js/pages/channels.js
@@ -1,4 +1,4 @@
-// OpenFang Channels Page — OpenClaw-style setup UX with QR code support
+// OMTAE Channels Page — OpenClaw-style setup UX with QR code support
'use strict';
function channelsPage() {
@@ -89,7 +89,7 @@ function channelsPage() {
this.loading = true;
this.loadError = '';
try {
- var data = await OpenFangAPI.get('/api/channels');
+ var data = await OMTAEAPI.get('/api/channels');
this.allChannels = (data.channels || []).map(function(ch) {
ch.connected = ch.configured && ch.has_token;
return ch;
@@ -111,7 +111,7 @@ function channelsPage() {
async refreshStatus() {
try {
- var data = await OpenFangAPI.get('/api/channels');
+ var data = await OMTAEAPI.get('/api/channels');
var byName = {};
(data.channels || []).forEach(function(ch) { byName[ch.name] = ch; });
this.allChannels.forEach(function(c) {
@@ -178,7 +178,7 @@ function channelsPage() {
this.qr.connected = false;
this.qr.expired = false;
try {
- var result = await OpenFangAPI.post('/api/channels/whatsapp/qr/start', {});
+ var result = await OMTAEAPI.post('/api/channels/whatsapp/qr/start', {});
this.qr.available = result.available || false;
this.qr.dataUrl = result.qr_data_url || '';
this.qr.sessionId = result.session_id || '';
@@ -189,7 +189,7 @@ function channelsPage() {
this.pollQR();
}
if (this.qr.connected) {
- OpenFangToast.success('WhatsApp connected!');
+ OMTAEToast.success('WhatsApp connected!');
await this.refreshStatus();
}
} catch(e) {
@@ -203,13 +203,13 @@ function channelsPage() {
if (this.qrPollTimer) clearInterval(this.qrPollTimer);
this.qrPollTimer = setInterval(async function() {
try {
- var result = await OpenFangAPI.get('/api/channels/whatsapp/qr/status?session_id=' + encodeURIComponent(self.qr.sessionId));
+ var result = await OMTAEAPI.get('/api/channels/whatsapp/qr/status?session_id=' + encodeURIComponent(self.qr.sessionId));
if (result.connected) {
clearInterval(self.qrPollTimer);
self.qrPollTimer = null;
self.qr.connected = true;
self.qr.message = result.message || 'Connected!';
- OpenFangToast.success('WhatsApp linked successfully!');
+ OMTAEToast.success('WhatsApp linked successfully!');
await self.refreshStatus();
} else if (result.expired) {
clearInterval(self.qrPollTimer);
@@ -230,26 +230,26 @@ function channelsPage() {
var name = this.setupModal.name;
this.configuring = true;
try {
- await OpenFangAPI.post('/api/channels/' + name + '/configure', {
+ await OMTAEAPI.post('/api/channels/' + name + '/configure', {
fields: this.formValues
});
this.setupStep = 2;
// Auto-test after save
try {
- var testResult = await OpenFangAPI.post('/api/channels/' + name + '/test', {});
+ var testResult = await OMTAEAPI.post('/api/channels/' + name + '/test', {});
if (testResult.status === 'ok') {
this.testPassed = true;
this.setupStep = 3;
- OpenFangToast.success(this.setupModal.display_name + ' activated!');
+ OMTAEToast.success(this.setupModal.display_name + ' activated!');
} else {
- OpenFangToast.success(this.setupModal.display_name + ' saved. ' + (testResult.message || ''));
+ OMTAEToast.success(this.setupModal.display_name + ' saved. ' + (testResult.message || ''));
}
} catch(te) {
- OpenFangToast.success(this.setupModal.display_name + ' saved. Test to verify connection.');
+ OMTAEToast.success(this.setupModal.display_name + ' saved. Test to verify connection.');
}
await this.refreshStatus();
} catch(e) {
- OpenFangToast.error('Failed: ' + (e.message || 'Unknown error'));
+ OMTAEToast.error('Failed: ' + (e.message || 'Unknown error'));
}
this.configuring = false;
},
@@ -259,14 +259,14 @@ function channelsPage() {
var name = this.setupModal.name;
var displayName = this.setupModal.display_name;
var self = this;
- OpenFangToast.confirm('Remove Channel', 'Remove ' + displayName + ' configuration? This will deactivate the channel.', async function() {
+ OMTAEToast.confirm('Remove Channel', 'Remove ' + displayName + ' configuration? This will deactivate the channel.', async function() {
try {
- await OpenFangAPI.delete('/api/channels/' + name + '/configure');
- OpenFangToast.success(displayName + ' removed and deactivated.');
+ await OMTAEAPI.delete('/api/channels/' + name + '/configure');
+ OMTAEToast.success(displayName + ' removed and deactivated.');
await self.refreshStatus();
self.setupModal = null;
} catch(e) {
- OpenFangToast.error('Failed: ' + (e.message || 'Unknown error'));
+ OMTAEToast.error('Failed: ' + (e.message || 'Unknown error'));
}
});
},
@@ -276,16 +276,16 @@ function channelsPage() {
var name = this.setupModal.name;
this.testing[name] = true;
try {
- var result = await OpenFangAPI.post('/api/channels/' + name + '/test', {});
+ var result = await OMTAEAPI.post('/api/channels/' + name + '/test', {});
if (result.status === 'ok') {
this.testPassed = true;
this.setupStep = 3;
- OpenFangToast.success(result.message);
+ OMTAEToast.success(result.message);
} else {
- OpenFangToast.error(result.message);
+ OMTAEToast.error(result.message);
}
} catch(e) {
- OpenFangToast.error('Test failed: ' + (e.message || 'Unknown error'));
+ OMTAEToast.error('Test failed: ' + (e.message || 'Unknown error'));
}
this.testing[name] = false;
},
@@ -295,9 +295,9 @@ function channelsPage() {
if (!tpl) return;
try {
await navigator.clipboard.writeText(tpl);
- OpenFangToast.success('Copied to clipboard');
+ OMTAEToast.success('Copied to clipboard');
} catch(e) {
- OpenFangToast.error('Copy failed');
+ OMTAEToast.error('Copy failed');
}
},
diff --git a/crates/openfang-api/static/js/pages/chat.js b/crates/openfang-api/static/js/pages/chat.js
index bee1be4ea7..f047ce43ab 100644
--- a/crates/openfang-api/static/js/pages/chat.js
+++ b/crates/openfang-api/static/js/pages/chat.js
@@ -1,6 +1,22 @@
-// OpenFang Chat Page — Agent chat with markdown + streaming
+// OMTAE Chat Page — Agent chat with markdown + streaming
'use strict';
+var OF_PREFERRED_AGENTS = ['researcher', 'analyst', 'orchestrator', 'coder'];
+
+function ofPickPreferredAgent(agents) {
+ if (!agents || !agents.length) return null;
+ for (var i = 0; i < OF_PREFERRED_AGENTS.length; i++) {
+ for (var j = 0; j < agents.length; j++) {
+ if (agents[j] && agents[j].name === OF_PREFERRED_AGENTS[i]) return agents[j];
+ }
+ }
+ return agents[0];
+}
+
+function ofIsPreferredAgent(agent) {
+ return agent && OF_PREFERRED_AGENTS.indexOf(agent.name) >= 0;
+}
+
function chatPage() {
var msgId = 0;
return {
@@ -8,6 +24,7 @@ function chatPage() {
messages: [],
inputText: '',
sending: false,
+ agentInferencing: false, // true when background tick or WS typing without user send
messageQueue: [], // Queue for messages sent while streaming
thinkingMode: 'off', // 'off' | 'on' | 'stream'
_wsAgent: null,
@@ -42,6 +59,7 @@ function chatPage() {
modelSwitching: false,
_modelCache: null,
_modelCacheTime: 0,
+ _defaultModelName: 'Qwen2.5-Coder-32B-Instruct-AWQ',
slashCommands: [], // Loaded dynamically with i18n in init()
_slashCommandsLoaded: false,
tokenCount: 0,
@@ -91,10 +109,19 @@ function chatPage() {
},
get modelDisplayName() {
- if (!this.currentAgent) return '';
- var name = this.currentAgent.model_name || '';
+ var name = '';
+ if (this.currentAgent) {
+ name = this.currentAgent.model_name || '';
+ var prov = (this.currentAgent.model_provider || '').toLowerCase();
+ if (prov && prov !== 'vllm' && prov !== 'default') {
+ name = this._defaultModelName || name;
+ }
+ } else {
+ name = this._defaultModelName || '';
+ }
+ if (!name) return this._defaultModelName || '';
var short = name.replace(/-\d{8}$/, '');
- return short.length > 24 ? short.substring(0, 22) + '\u2026' : short;
+ return short.length > 28 ? short.substring(0, 26) + '\u2026' : short;
},
get switcherProviders() {
@@ -143,6 +170,13 @@ function chatPage() {
// Fetch dynamic commands from server
this.fetchCommands();
+ // Load default model from daemon config (vLLM on Jay's desk)
+ OMTAEAPI.get('/api/config').then(function(cfg) {
+ if (cfg && cfg.default_model && cfg.default_model.model) {
+ self._defaultModelName = cfg.default_model.model;
+ }
+ }).catch(function() { /* silent */ });
+
// Observe DOM for new messages and render LaTeX
this._latexObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
@@ -217,6 +251,22 @@ function chatPage() {
if (!self.currentAgent && agents && agents.length) {
self._restoreActiveAgent();
}
+ // Sync inferencing / schedule flags for the active agent chip
+ if (self.currentAgent && agents && agents.length) {
+ for (var i = 0; i < agents.length; i++) {
+ if (agents[i].id === self.currentAgent.id) {
+ self.currentAgent.is_inferencing = agents[i].is_inferencing;
+ self.currentAgent.schedule = agents[i].schedule;
+ self.currentAgent.background_paused = agents[i].background_paused;
+ if (!agents[i].is_inferencing && !self.sending) {
+ self.agentInferencing = false;
+ } else if (agents[i].is_inferencing) {
+ self.agentInferencing = true;
+ }
+ break;
+ }
+ }
+ }
});
// Watch for slash commands + model autocomplete
@@ -226,7 +276,7 @@ function chatPage() {
self.showSlashMenu = false;
self.modelPickerFilter = modelMatch[1].toLowerCase();
if (!self.modelPickerList.length) {
- OpenFangAPI.get('/api/models').then(function(data) {
+ OMTAEAPI.get('/api/models').then(function(data) {
self.modelPickerList = (data.models || []).filter(function(m) { return m.available; });
self.showModelPicker = true;
self.modelPickerIdx = 0;
@@ -275,7 +325,7 @@ function chatPage() {
});
return;
}
- OpenFangAPI.get('/api/models').then(function(data) {
+ OMTAEAPI.get('/api/models').then(function(data) {
var models = (data.models || []).filter(function(m) { return m.available; });
self._modelCache = models;
self._modelCacheTime = Date.now();
@@ -289,7 +339,7 @@ function chatPage() {
if (el) el.focus();
});
}).catch(function(e) {
- OpenFangToast.error('Failed to load models: ' + e.message);
+ OMTAEToast.error('Failed to load models: ' + e.message);
});
},
@@ -299,15 +349,15 @@ function chatPage() {
var self = this;
this.modelSwitching = true;
var t = typeof window.t === 'function' ? window.t : function(s) { return s; };
- OpenFangAPI.put('/api/agents/' + this.currentAgent.id + '/model', { model: model.id }).then(function(resp) {
+ OMTAEAPI.put('/api/agents/' + this.currentAgent.id + '/model', { model: model.id }).then(function(resp) {
// Use server-resolved model/provider to stay in sync (fixes #387/#466)
self.currentAgent.model_name = (resp && resp.model) || model.id;
self.currentAgent.model_provider = (resp && resp.provider) || model.provider;
- OpenFangToast.success(t('chat.model_switched') + ' ' + (model.display_name || model.id));
+ OMTAEToast.success(t('chat.model_switched') + ' ' + (model.display_name || model.id));
self.showModelSwitcher = false;
self.modelSwitching = false;
}).catch(function(e) {
- OpenFangToast.error(t('chat.model_switch_failed') + ': ' + e.message);
+ OMTAEToast.error(t('chat.model_switch_failed') + ': ' + e.message);
self.modelSwitching = false;
});
},
@@ -343,7 +393,7 @@ function chatPage() {
// the help panel and autocomplete stay in sync with the backend registry.
fetchCommands: function() {
var self = this;
- OpenFangAPI.get('/api/commands?surface=web').then(function(data) {
+ OMTAEAPI.get('/api/commands?surface=web').then(function(data) {
var cmds = (data && data.commands) || [];
if (!cmds.length) return;
self.slashCommands = cmds.map(function(c) {
@@ -418,15 +468,28 @@ function chatPage() {
return lines.join('\n');
},
+ showStopButton: function() {
+ return this.sending || this.agentInferencing ||
+ (this.currentAgent && this.currentAgent.is_inferencing);
+ },
+
+ scheduleBadge: function() {
+ if (!this.currentAgent) return '';
+ if (this.currentAgent.background_paused) return 'paused';
+ return this.currentAgent.schedule || 'reactive';
+ },
+
// Clear any stuck typing indicator after 120s
_resetTypingTimeout: function() {
var self = this;
if (self._typingTimeout) clearTimeout(self._typingTimeout);
self._typingTimeout = setTimeout(function() {
- // Auto-clear stuck typing indicators
+ // Auto-clear stuck typing / GENERATING when server state is stale
self.messages = self.messages.filter(function(m) { return !m.thinking; });
self.sending = false;
- }, 120000);
+ self.agentInferencing = false;
+ if (self.currentAgent) self.currentAgent.is_inferencing = false;
+ }, 180000);
},
_clearTypingTimeout: function() {
@@ -451,28 +514,34 @@ function chatPage() {
break;
case '/new':
if (self.currentAgent) {
- OpenFangAPI.post('/api/agents/' + self.currentAgent.id + '/session/reset', {}).then(function() {
+ OMTAEAPI.post('/api/agents/' + self.currentAgent.id + '/session/reset', {}).then(function() {
self.messages = [];
- OpenFangToast.success('Session reset');
- }).catch(function(e) { OpenFangToast.error('Reset failed: ' + e.message); });
+ OMTAEToast.success('Session reset');
+ }).catch(function(e) { OMTAEToast.error('Reset failed: ' + e.message); });
}
break;
case '/compact':
if (self.currentAgent) {
self.messages.push({ id: ++msgId, role: 'system', text: 'Compacting session...', meta: '', tools: [] });
- OpenFangAPI.post('/api/agents/' + self.currentAgent.id + '/session/compact', {}).then(function(res) {
+ OMTAEAPI.post('/api/agents/' + self.currentAgent.id + '/session/compact', {}).then(function(res) {
self.messages.push({ id: ++msgId, role: 'system', text: res.message || 'Compaction complete', meta: '', tools: [] });
self.scrollToBottom();
- }).catch(function(e) { OpenFangToast.error('Compaction failed: ' + e.message); });
+ }).catch(function(e) { OMTAEToast.error('Compaction failed: ' + e.message); });
}
break;
case '/stop':
if (self.currentAgent) {
- OpenFangAPI.post('/api/agents/' + self.currentAgent.id + '/stop', {}).then(function(res) {
- self.messages.push({ id: ++msgId, role: 'system', text: res.message || 'Run cancelled', meta: '', tools: [] });
+ OMTAEAPI.post('/api/agents/' + self.currentAgent.id + '/stop', {}).then(function(res) {
+ var msg = res.message || 'Run cancelled';
+ if (res.background_paused) {
+ msg += ' (continuous/periodic background ticks paused — restart daemon to resume)';
+ }
+ self.messages.push({ id: ++msgId, role: 'system', text: msg, meta: '', tools: [] });
self.sending = false;
+ self.agentInferencing = false;
+ if (self.currentAgent) self.currentAgent.background_paused = !!res.background_paused;
self.scrollToBottom();
- }).catch(function(e) { OpenFangToast.error('Stop failed: ' + e.message); });
+ }).catch(function(e) { OMTAEToast.error('Stop failed: ' + e.message); });
}
break;
case '/usage':
@@ -504,31 +573,31 @@ function chatPage() {
break;
case '/context':
// Send via WS command
- if (self.currentAgent && OpenFangAPI.isWsConnected()) {
- OpenFangAPI.wsSend({ type: 'command', command: 'context', args: '' });
+ if (self.currentAgent && OMTAEAPI.isWsConnected()) {
+ OMTAEAPI.wsSend({ type: 'command', command: 'context', args: '' });
} else {
- self.messages.push({ id: ++msgId, role: 'system', text: 'Not connected (' + (OpenFangAPI.getConnectionState ? OpenFangAPI.getConnectionState() : 'unknown') + '). Pick an agent or check that your session is still valid.', meta: '', tools: [] });
+ self.messages.push({ id: ++msgId, role: 'system', text: 'Not connected (' + (OMTAEAPI.getConnectionState ? OMTAEAPI.getConnectionState() : 'unknown') + '). Pick an agent or check that your session is still valid.', meta: '', tools: [] });
self.scrollToBottom();
}
break;
case '/verbose':
- if (self.currentAgent && OpenFangAPI.isWsConnected()) {
- OpenFangAPI.wsSend({ type: 'command', command: 'verbose', args: cmdArgs });
+ if (self.currentAgent && OMTAEAPI.isWsConnected()) {
+ OMTAEAPI.wsSend({ type: 'command', command: 'verbose', args: cmdArgs });
} else {
- self.messages.push({ id: ++msgId, role: 'system', text: 'Not connected (' + (OpenFangAPI.getConnectionState ? OpenFangAPI.getConnectionState() : 'unknown') + '). Pick an agent or check that your session is still valid.', meta: '', tools: [] });
+ self.messages.push({ id: ++msgId, role: 'system', text: 'Not connected (' + (OMTAEAPI.getConnectionState ? OMTAEAPI.getConnectionState() : 'unknown') + '). Pick an agent or check that your session is still valid.', meta: '', tools: [] });
self.scrollToBottom();
}
break;
case '/queue':
- if (self.currentAgent && OpenFangAPI.isWsConnected()) {
- OpenFangAPI.wsSend({ type: 'command', command: 'queue', args: '' });
+ if (self.currentAgent && OMTAEAPI.isWsConnected()) {
+ OMTAEAPI.wsSend({ type: 'command', command: 'queue', args: '' });
} else {
- self.messages.push({ id: ++msgId, role: 'system', text: 'Not connected (' + (OpenFangAPI.getConnectionState ? OpenFangAPI.getConnectionState() : 'unknown') + ').', meta: '', tools: [] });
+ self.messages.push({ id: ++msgId, role: 'system', text: 'Not connected (' + (OMTAEAPI.getConnectionState ? OMTAEAPI.getConnectionState() : 'unknown') + ').', meta: '', tools: [] });
self.scrollToBottom();
}
break;
case '/status':
- OpenFangAPI.get('/api/status').then(function(s) {
+ OMTAEAPI.get('/api/status').then(function(s) {
self.messages.push({ id: ++msgId, role: 'system', text: '**System Status**\n- Agents: ' + (s.agent_count || 0) + '\n- Uptime: ' + (s.uptime_seconds || 0) + 's\n- Version: ' + (s.version || '?'), meta: '', tools: [] });
self.scrollToBottom();
}).catch(function() {});
@@ -536,7 +605,7 @@ function chatPage() {
case '/model':
if (self.currentAgent) {
if (cmdArgs) {
- OpenFangAPI.put('/api/agents/' + self.currentAgent.id + '/model', { model: cmdArgs }).then(function(resp) {
+ OMTAEAPI.put('/api/agents/' + self.currentAgent.id + '/model', { model: cmdArgs }).then(function(resp) {
// Use server-resolved model/provider (fixes #387/#466)
var resolvedModel = (resp && resp.model) || cmdArgs;
var resolvedProvider = (resp && resp.provider) || '';
@@ -544,7 +613,7 @@ function chatPage() {
if (resolvedProvider) { self.currentAgent.model_provider = resolvedProvider; }
self.messages.push({ id: ++msgId, role: 'system', text: 'Model switched to: `' + resolvedModel + '`' + (resolvedProvider ? ' (provider: `' + resolvedProvider + '`)' : ''), meta: '', tools: [] });
self.scrollToBottom();
- }).catch(function(e) { OpenFangToast.error('Model switch failed: ' + e.message); });
+ }).catch(function(e) { OMTAEToast.error('Model switch failed: ' + e.message); });
} else {
self.messages.push({ id: ++msgId, role: 'system', text: '**Current Model**\n- Provider: `' + (self.currentAgent.model_provider || '?') + '`\n- Model: `' + (self.currentAgent.model_name || '?') + '`', meta: '', tools: [] });
self.scrollToBottom();
@@ -558,7 +627,7 @@ function chatPage() {
self.messages = [];
break;
case '/exit':
- OpenFangAPI.wsDisconnect();
+ OMTAEAPI.wsDisconnect();
self._wsAgent = null;
self.currentAgent = null;
self.messages = [];
@@ -566,7 +635,7 @@ function chatPage() {
window.dispatchEvent(new Event('close-chat'));
break;
case '/budget':
- OpenFangAPI.get('/api/budget').then(function(b) {
+ OMTAEAPI.get('/api/budget').then(function(b) {
var fmt = function(v) { return v > 0 ? '$' + v.toFixed(2) : 'unlimited'; };
self.messages.push({ id: ++msgId, role: 'system', text: '**Budget Status**\n' +
'- Hourly: $' + (b.hourly_spend||0).toFixed(4) + ' / ' + fmt(b.hourly_limit) + '\n' +
@@ -576,7 +645,7 @@ function chatPage() {
}).catch(function() {});
break;
case '/peers':
- OpenFangAPI.get('/api/network/status').then(function(ns) {
+ OMTAEAPI.get('/api/network/status').then(function(ns) {
self.messages.push({ id: ++msgId, role: 'system', text: '**OFP Network**\n' +
'- Status: ' + (ns.enabled ? 'Enabled' : 'Disabled') + '\n' +
'- Connected peers: ' + (ns.connected_peers||0) + ' / ' + (ns.total_peers||0), meta: '', tools: [] });
@@ -584,7 +653,7 @@ function chatPage() {
}).catch(function() {});
break;
case '/a2a':
- OpenFangAPI.get('/api/a2a/agents').then(function(res) {
+ OMTAEAPI.get('/api/a2a/agents').then(function(res) {
var agents = res.agents || [];
if (!agents.length) {
self.messages.push({ id: ++msgId, role: 'system', text: 'No external A2A agents discovered.', meta: '', tools: [] });
@@ -602,14 +671,28 @@ function chatPage() {
// refresh, so the WebSocket re-attaches to the same session and any
// in-flight tool output streams back into the chat (#1179).
_restoreActiveAgent: function() {
+ var agents = (Alpine.store('app') && Alpine.store('app').agents) || [];
+ if (!agents.length) return;
+
var storedId = null;
try { storedId = localStorage.getItem('of-active-agent'); } catch(e) { /* ignore */ }
- if (!storedId) return;
- var agents = (Alpine.store('app') && Alpine.store('app').agents) || [];
+
var match = null;
- for (var i = 0; i < agents.length; i++) {
- if (agents[i] && agents[i].id === storedId) { match = agents[i]; break; }
+ if (storedId) {
+ for (var i = 0; i < agents.length; i++) {
+ if (agents[i] && agents[i].id === storedId) { match = agents[i]; break; }
+ }
+ }
+
+ if (match && !ofIsPreferredAgent(match)) {
+ try { localStorage.removeItem('of-active-agent'); } catch(e) { /* ignore */ }
+ match = null;
+ }
+
+ if (!match) {
+ match = ofPickPreferredAgent(agents);
}
+
if (match) {
this.selectAgent(match);
}
@@ -643,7 +726,7 @@ function chatPage() {
async loadSession(agentId) {
var self = this;
try {
- var data = await OpenFangAPI.get('/api/agents/' + agentId + '/session');
+ var data = await OMTAEAPI.get('/api/agents/' + agentId + '/session');
if (data.messages && data.messages.length) {
// Defense-in-depth (#935): never render system-role messages in the
// conversation history view, even if the backend somehow returns
@@ -682,7 +765,7 @@ function chatPage() {
// Multi-session: load session list for current agent
async loadSessions(agentId) {
try {
- var data = await OpenFangAPI.get('/api/agents/' + agentId + '/sessions');
+ var data = await OMTAEAPI.get('/api/agents/' + agentId + '/sessions');
this.sessions = data.sessions || [];
} catch(e) { this.sessions = []; }
},
@@ -694,16 +777,16 @@ function chatPage() {
var label = prompt(t('chat.session_name_prompt'));
if (label === null) return; // cancelled
try {
- await OpenFangAPI.post('/api/agents/' + this.currentAgent.id + '/sessions', {
+ await OMTAEAPI.post('/api/agents/' + this.currentAgent.id + '/sessions', {
label: label.trim() || undefined
});
await this.loadSessions(this.currentAgent.id);
await this.loadSession(this.currentAgent.id);
this.messages = [];
this.scrollToBottom();
- if (typeof OpenFangToast !== 'undefined') OpenFangToast.success(t('chat.session_created'));
+ if (typeof OMTAEToast !== 'undefined') OMTAEToast.success(t('chat.session_created'));
} catch(e) {
- if (typeof OpenFangToast !== 'undefined') OpenFangToast.error(t('chat.session_create_failed'));
+ if (typeof OMTAEToast !== 'undefined') OMTAEToast.error(t('chat.session_create_failed'));
}
},
@@ -711,7 +794,7 @@ function chatPage() {
async switchSession(sessionId) {
if (!this.currentAgent) return;
try {
- await OpenFangAPI.post('/api/agents/' + this.currentAgent.id + '/sessions/' + sessionId + '/switch', {});
+ await OMTAEAPI.post('/api/agents/' + this.currentAgent.id + '/sessions/' + sessionId + '/switch', {});
this.messages = [];
await this.loadSession(this.currentAgent.id);
await this.loadSessions(this.currentAgent.id);
@@ -719,7 +802,7 @@ function chatPage() {
this._wsAgent = null;
this.connectWs(this.currentAgent.id);
} catch(e) {
- if (typeof OpenFangToast !== 'undefined') OpenFangToast.error('Failed to switch session');
+ if (typeof OMTAEToast !== 'undefined') OMTAEToast.error('Failed to switch session');
}
},
@@ -728,7 +811,7 @@ function chatPage() {
this._wsAgent = agentId;
var self = this;
- OpenFangAPI.wsConnect(agentId, {
+ OMTAEAPI.wsConnect(agentId, {
onOpen: function() {
Alpine.store('app').wsConnected = true;
},
@@ -777,6 +860,7 @@ function chatPage() {
// New typing lifecycle
case 'typing':
if (data.state === 'start') {
+ if (!this.sending) this.agentInferencing = true;
if (!this.messages.length || !this.messages[this.messages.length - 1].thinking) {
this.messages.push({ id: ++msgId, role: 'agent', text: 'Processing...', meta: '', thinking: true, streaming: true, tools: [] });
this.scrollToBottom();
@@ -791,6 +875,7 @@ function chatPage() {
}
this._resetTypingTimeout();
} else if (data.state === 'stop') {
+ this.agentInferencing = false;
this._clearTypingTimeout();
}
break;
@@ -910,6 +995,9 @@ function chatPage() {
lastMsg3.tools[ri].running = false;
lastMsg3.tools[ri].result = data.result || '';
lastMsg3.tools[ri].is_error = !!data.is_error;
+ if (data.tool === 'agent_send') {
+ lastMsg3.tools[ri].expanded = true;
+ }
// Extract image URLs from image_generate or browser_screenshot results
if ((data.tool === 'image_generate' || data.tool === 'browser_screenshot') && !data.is_error) {
try {
@@ -975,6 +1063,7 @@ function chatPage() {
}
this.messages.push({ id: ++msgId, role: 'agent', text: finalText, meta: meta, tools: streamedTools, ts: Date.now() });
this.sending = false;
+ this.agentInferencing = false;
this.tokenCount = 0;
this.scrollToBottom();
var self3 = this;
@@ -989,6 +1078,7 @@ function chatPage() {
this._clearTypingTimeout();
this.messages = this.messages.filter(function(m) { return !m.thinking && !m.streaming; });
this.sending = false;
+ this.agentInferencing = false;
this.tokenCount = 0;
// No message bubble added — the agent was silent
var selfSilent = this;
@@ -1000,6 +1090,7 @@ function chatPage() {
this.messages = this.messages.filter(function(m) { return !m.thinking && !m.streaming; });
this.messages.push({ id: ++msgId, role: 'system', text: 'Error: ' + data.content, meta: '', tools: [], ts: Date.now() });
this.sending = false;
+ this.agentInferencing = false;
this.tokenCount = 0;
this.scrollToBottom();
var self2 = this;
@@ -1097,11 +1188,11 @@ function chatPage() {
var att = this.attachments[i];
att.uploading = true;
try {
- var uploadRes = await OpenFangAPI.upload(this.currentAgent.id, att.file);
+ var uploadRes = await OMTAEAPI.upload(this.currentAgent.id, att.file);
fileRefs.push('[File: ' + att.file.name + ']');
uploadedFiles.push({ file_id: uploadRes.file_id, filename: uploadRes.filename, content_type: uploadRes.content_type });
} catch(e) {
- OpenFangToast.error('Failed to upload ' + att.file.name);
+ OMTAEToast.error('Failed to upload ' + att.file.name);
fileRefs.push('[File: ' + att.file.name + ' (upload failed)]');
}
att.uploading = false;
@@ -1142,7 +1233,7 @@ function chatPage() {
// Try WebSocket first
var wsPayload = { type: 'message', content: finalText };
if (uploadedFiles && uploadedFiles.length) wsPayload.attachments = uploadedFiles;
- if (OpenFangAPI.wsSend(wsPayload)) {
+ if (OMTAEAPI.wsSend(wsPayload)) {
this.messages.push({ id: ++msgId, role: 'agent', text: '', meta: '', thinking: true, streaming: true, tools: [], ts: Date.now() });
this.scrollToBottom();
return;
@@ -1150,8 +1241,8 @@ function chatPage() {
// HTTP fallback
var t = typeof window.t === 'function' ? window.t : function(s) { return s; };
- if (!OpenFangAPI.isWsConnected()) {
- OpenFangToast.info(t('chat.using_http_mode'));
+ if (!OMTAEAPI.isWsConnected()) {
+ OMTAEToast.info(t('chat.using_http_mode'));
}
this.messages.push({ id: ++msgId, role: 'agent', text: '', meta: '', thinking: true, tools: [], ts: Date.now() });
this.scrollToBottom();
@@ -1159,7 +1250,7 @@ function chatPage() {
try {
var httpBody = { message: finalText };
if (uploadedFiles && uploadedFiles.length) httpBody.attachments = uploadedFiles;
- var res = await OpenFangAPI.post('/api/agents/' + this.currentAgent.id + '/message', httpBody);
+ var res = await OMTAEAPI.post('/api/agents/' + this.currentAgent.id + '/message', httpBody);
this.messages = this.messages.filter(function(m) { return !m.thinking; });
var httpMeta = (res.input_tokens || 0) + ' in / ' + (res.output_tokens || 0) + ' out';
if (res.cost_usd != null) httpMeta += ' | $' + res.cost_usd.toFixed(4);
@@ -1183,12 +1274,18 @@ function chatPage() {
stopAgent: function() {
if (!this.currentAgent) return;
var self = this;
- OpenFangAPI.post('/api/agents/' + this.currentAgent.id + '/stop', {}).then(function(res) {
- self.messages.push({ id: ++msgId, role: 'system', text: res.message || 'Run cancelled', meta: '', tools: [], ts: Date.now() });
+ OMTAEAPI.post('/api/agents/' + this.currentAgent.id + '/stop', {}).then(function(res) {
+ var msg = res.message || 'Run cancelled';
+ if (res.background_paused) {
+ msg += ' (background schedule paused)';
+ }
+ self.messages.push({ id: ++msgId, role: 'system', text: msg, meta: '', tools: [], ts: Date.now() });
self.sending = false;
+ self.agentInferencing = false;
+ if (self.currentAgent) self.currentAgent.background_paused = !!res.background_paused;
self.scrollToBottom();
self.$nextTick(function() { self._processQueue(); });
- }).catch(function(e) { OpenFangToast.error('Stop failed: ' + e.message); });
+ }).catch(function(e) { OMTAEToast.error('Stop failed: ' + e.message); });
},
killAgent() {
@@ -1196,36 +1293,36 @@ function chatPage() {
var self = this;
var t = typeof window.t === 'function' ? window.t : function(s) { return s; };
var name = this.currentAgent.name;
- OpenFangToast.confirm(t('chat.stop_agent_title'), t('chat.stop_agent_confirm') + ' "' + name + '"?', async function() {
+ OMTAEToast.confirm(t('chat.stop_agent_title'), t('chat.stop_agent_confirm') + ' "' + name + '"?', async function() {
try {
- await OpenFangAPI.del('/api/agents/' + self.currentAgent.id);
- OpenFangAPI.wsDisconnect();
+ await OMTAEAPI.del('/api/agents/' + self.currentAgent.id);
+ OMTAEAPI.wsDisconnect();
self._wsAgent = null;
self.currentAgent = null;
self.messages = [];
try { localStorage.removeItem('of-active-agent'); } catch(e) { /* ignore */ }
- OpenFangToast.success(t('chat.agent_stopped') + ' "' + name + '"');
+ OMTAEToast.success(t('chat.agent_stopped') + ' "' + name + '"');
Alpine.store('app').refreshAgents();
} catch(e) {
- OpenFangToast.error(t('chat.stop_agent_failed') + ': ' + e.message);
+ OMTAEToast.error(t('chat.stop_agent_failed') + ': ' + e.message);
}
});
},
- // Permanently uninstall the agent: kill + remove ~/.openfang/agents//
+ // Permanently uninstall the agent: kill + remove ~/.omtae/agents//
// Issue #1163.
uninstallAgent: function() {
if (!this.currentAgent) return;
var self = this;
var name = this.currentAgent.name;
var agentId = this.currentAgent.id;
- OpenFangToast.confirm(
+ OMTAEToast.confirm(
'Uninstall Agent',
'Uninstall agent "' + name + '"? This stops the agent AND deletes its files from your workspace. This cannot be undone.',
async function() {
try {
- var res = await OpenFangAPI.del('/api/agents/' + agentId + '/uninstall');
- OpenFangAPI.wsDisconnect();
+ var res = await OMTAEAPI.del('/api/agents/' + agentId + '/uninstall');
+ OMTAEAPI.wsDisconnect();
self._wsAgent = null;
self.currentAgent = null;
self.messages = [];
@@ -1234,10 +1331,10 @@ function chatPage() {
if (res && res.dir_removed === false) {
msg += ' (no on-disk files found)';
}
- OpenFangToast.success(msg);
+ OMTAEToast.success(msg);
Alpine.store('app').refreshAgents();
} catch(e) {
- OpenFangToast.error('Failed to uninstall agent: ' + e.message);
+ OMTAEToast.error('Failed to uninstall agent: ' + e.message);
}
}
);
@@ -1263,7 +1360,7 @@ function chatPage() {
for (var i = 0; i < files.length; i++) {
var file = files[i];
if (file.size > 10 * 1024 * 1024) {
- OpenFangToast.warn('File "' + file.name + '" exceeds 10MB limit');
+ OMTAEToast.warn('File "' + file.name + '" exceeds 10MB limit');
continue;
}
var typeOk = allowed.indexOf(file.type) !== -1;
@@ -1272,7 +1369,7 @@ function chatPage() {
typeOk = allowedExts.indexOf(ext) !== -1 || file.type.startsWith('image/');
}
if (!typeOk) {
- OpenFangToast.warn('File type not supported: ' + file.name);
+ OMTAEToast.warn('File type not supported: ' + file.name);
continue;
}
var preview = null;
@@ -1351,7 +1448,7 @@ function chatPage() {
this.recordingTime = 0;
this._recordingTimer = setInterval(function() { self.recordingTime++; }, 1000);
} catch(e) {
- if (typeof OpenFangToast !== 'undefined') OpenFangToast.error('Microphone access denied');
+ if (typeof OMTAEToast !== 'undefined') OMTAEToast.error('Microphone access denied');
}
},
@@ -1378,7 +1475,7 @@ function chatPage() {
// Upload audio file
var ext = blob.type.includes('webm') ? 'webm' : blob.type.includes('ogg') ? 'ogg' : 'mp3';
var file = new File([blob], 'voice_' + Date.now() + '.' + ext, { type: blob.type });
- var upload = await OpenFangAPI.upload(this.currentAgent.id, file);
+ var upload = await OMTAEAPI.upload(this.currentAgent.id, file);
// Remove the "Transcribing..." message
this.messages = this.messages.filter(function(m) { return !m.thinking || m.role !== 'system'; });
@@ -1390,7 +1487,7 @@ function chatPage() {
this._sendPayload(text, [upload], []);
} catch(e) {
this.messages = this.messages.filter(function(m) { return !m.thinking || m.role !== 'system'; });
- if (typeof OpenFangToast !== 'undefined') OpenFangToast.error('Failed to upload audio: ' + (e.message || 'unknown error'));
+ if (typeof OMTAEToast !== 'undefined') OMTAEToast.error('Failed to upload audio: ' + (e.message || 'unknown error'));
}
},
diff --git a/crates/openfang-api/static/js/pages/comms.js b/crates/openfang-api/static/js/pages/comms.js
index 3e3ba92460..61ce69f477 100644
--- a/crates/openfang-api/static/js/pages/comms.js
+++ b/crates/openfang-api/static/js/pages/comms.js
@@ -1,4 +1,4 @@
-// OpenFang Comms Page — Agent topology & inter-agent communication feed
+// OMTAE Comms Page — Agent topology & inter-agent communication feed
'use strict';
function commsPage() {
@@ -24,8 +24,8 @@ function commsPage() {
this.loadError = '';
try {
var results = await Promise.all([
- OpenFangAPI.get('/api/comms/topology'),
- OpenFangAPI.get('/api/comms/events?limit=200')
+ OMTAEAPI.get('/api/comms/topology'),
+ OMTAEAPI.get('/api/comms/events?limit=200')
]);
this.topology = results[0] || { nodes: [], edges: [] };
this.events = results[1] || [];
@@ -40,7 +40,7 @@ function commsPage() {
if (this.sseSource) this.sseSource.close();
var self = this;
var url = '/api/comms/events/stream';
- if (OpenFangAPI.apiKey) url += '?token=' + encodeURIComponent(OpenFangAPI.apiKey);
+ if (OMTAEAPI.apiKey) url += '?token=' + encodeURIComponent(OMTAEAPI.apiKey);
this.sseSource = new EventSource(url);
this.sseSource.onmessage = function(ev) {
if (ev.data === 'ping') return;
@@ -65,7 +65,7 @@ function commsPage() {
async refreshTopology() {
try {
- this.topology = await OpenFangAPI.get('/api/comms/topology');
+ this.topology = await OMTAEAPI.get('/api/comms/topology');
} catch(e) { /* silent */ }
},
@@ -163,15 +163,15 @@ function commsPage() {
if (!this.sendFrom || !this.sendTo || !this.sendMsg.trim()) return;
this.sendLoading = true;
try {
- await OpenFangAPI.post('/api/comms/send', {
+ await OMTAEAPI.post('/api/comms/send', {
from_agent_id: this.sendFrom,
to_agent_id: this.sendTo,
message: this.sendMsg
});
- OpenFangToast.success('Message sent');
+ OMTAEToast.success('Message sent');
this.showSendModal = false;
} catch(e) {
- OpenFangToast.error(e.message || 'Send failed');
+ OMTAEToast.error(e.message || 'Send failed');
}
this.sendLoading = false;
},
@@ -189,11 +189,11 @@ function commsPage() {
try {
var body = { title: this.taskTitle, description: this.taskDesc };
if (this.taskAssign) body.assigned_to = this.taskAssign;
- await OpenFangAPI.post('/api/comms/task', body);
- OpenFangToast.success('Task posted');
+ await OMTAEAPI.post('/api/comms/task', body);
+ OMTAEToast.success('Task posted');
this.showTaskModal = false;
} catch(e) {
- OpenFangToast.error(e.message || 'Task failed');
+ OMTAEToast.error(e.message || 'Task failed');
}
this.taskLoading = false;
}
diff --git a/crates/openfang-api/static/js/pages/hands.js b/crates/openfang-api/static/js/pages/hands.js
index c52b3361eb..4d1fe3d716 100644
--- a/crates/openfang-api/static/js/pages/hands.js
+++ b/crates/openfang-api/static/js/pages/hands.js
@@ -1,4 +1,4 @@
-// OpenFang Hands Page — curated autonomous capability packages
+// OMTAE Hands Page — curated autonomous capability packages
'use strict';
function handsPage() {
@@ -42,7 +42,7 @@ function handsPage() {
this.loading = true;
this.loadError = '';
try {
- var data = await OpenFangAPI.get('/api/hands');
+ var data = await OMTAEAPI.get('/api/hands');
this.hands = data.hands || [];
} catch(e) {
this.hands = [];
@@ -54,7 +54,7 @@ function handsPage() {
async loadActive() {
this.activeLoading = true;
try {
- var data = await OpenFangAPI.get('/api/hands/active');
+ var data = await OMTAEAPI.get('/api/hands/active');
this.instances = (data.instances || []).map(function(i) {
i._stats = null;
return i;
@@ -74,7 +74,7 @@ function handsPage() {
async showDetail(handId) {
try {
- var data = await OpenFangAPI.get('/api/hands/' + handId);
+ var data = await OMTAEAPI.get('/api/hands/' + handId);
this.detailHand = data;
} catch(e) {
for (var i = 0; i < this.hands.length; i++) {
@@ -96,7 +96,7 @@ function handsPage() {
this.setupLoading = true;
this.setupWizard = null;
try {
- var data = await OpenFangAPI.get('/api/hands/' + handId);
+ var data = await OMTAEAPI.get('/api/hands/' + handId);
// Pre-populate settings defaults
this.settingsValues = {};
if (data.settings && data.settings.length > 0) {
@@ -167,7 +167,7 @@ function handsPage() {
};
try {
- var data = await OpenFangAPI.post('/api/hands/' + handId + '/install-deps', {});
+ var data = await OMTAEAPI.post('/api/hands/' + handId + '/install-deps', {});
var results = data.results || [];
this.installProgress.results = results;
this.installProgress.current = results.length;
@@ -229,7 +229,7 @@ function handsPage() {
if (!this.setupWizard) return;
this.setupChecking = true;
try {
- var data = await OpenFangAPI.post('/api/hands/' + this.setupWizard.id + '/check-deps', {});
+ var data = await OMTAEAPI.post('/api/hands/' + this.setupWizard.id + '/check-deps', {});
if (data.requirements && this.setupWizard.requirements) {
for (var i = 0; i < this.setupWizard.requirements.length; i++) {
var existing = this.setupWizard.requirements[i];
@@ -415,7 +415,7 @@ function handsPage() {
if (name) {
payload.instance_name = name;
}
- var data = await OpenFangAPI.post('/api/hands/' + handId + '/activate', payload);
+ var data = await OMTAEAPI.post('/api/hands/' + handId + '/activate', payload);
var label = data.instance_name || data.agent_name || data.instance_id;
this.showToast('Hand "' + handId + '" activated as ' + label);
this.closeSetupWizard();
@@ -448,7 +448,7 @@ function handsPage() {
async pauseHand(inst) {
try {
- await OpenFangAPI.post('/api/hands/instances/' + inst.instance_id + '/pause', {});
+ await OMTAEAPI.post('/api/hands/instances/' + inst.instance_id + '/pause', {});
inst.status = 'Paused';
} catch(e) {
this.showToast('Pause failed: ' + (e.message || 'unknown error'));
@@ -457,7 +457,7 @@ function handsPage() {
async resumeHand(inst) {
try {
- await OpenFangAPI.post('/api/hands/instances/' + inst.instance_id + '/resume', {});
+ await OMTAEAPI.post('/api/hands/instances/' + inst.instance_id + '/resume', {});
inst.status = 'Active';
} catch(e) {
this.showToast('Resume failed: ' + (e.message || 'unknown error'));
@@ -467,20 +467,20 @@ function handsPage() {
async deactivate(inst) {
var self = this;
var handName = inst.agent_name || inst.hand_id;
- OpenFangToast.confirm('Deactivate Hand', 'Deactivate hand "' + handName + '"? This will kill its agent.', async function() {
+ OMTAEToast.confirm('Deactivate Hand', 'Deactivate hand "' + handName + '"? This will kill its agent.', async function() {
try {
- await OpenFangAPI.delete('/api/hands/instances/' + inst.instance_id);
+ await OMTAEAPI.delete('/api/hands/instances/' + inst.instance_id);
self.instances = self.instances.filter(function(i) { return i.instance_id !== inst.instance_id; });
- OpenFangToast.success('Hand deactivated.');
+ OMTAEToast.success('Hand deactivated.');
} catch(e) {
- OpenFangToast.error('Deactivation failed: ' + (e.message || 'unknown error'));
+ OMTAEToast.error('Deactivation failed: ' + (e.message || 'unknown error'));
}
});
},
async loadStats(inst) {
try {
- var data = await OpenFangAPI.get('/api/hands/instances/' + inst.instance_id + '/stats');
+ var data = await OMTAEAPI.get('/api/hands/instances/' + inst.instance_id + '/stats');
inst._stats = data.metrics || {};
} catch(e) {
inst._stats = { 'Error': { value: e.message || 'Could not load stats', format: 'text' } };
@@ -541,7 +541,7 @@ function handsPage() {
if (!this.browserViewer) return;
var id = this.browserViewer.instance_id;
try {
- var data = await OpenFangAPI.get('/api/hands/instances/' + id + '/browser');
+ var data = await OMTAEAPI.get('/api/hands/instances/' + id + '/browser');
if (data.active) {
this.browserViewer.url = data.url || '';
this.browserViewer.title = data.title || '';
@@ -635,7 +635,7 @@ function handsPage() {
// Fetch basic stats from the hand stats endpoint
try {
- var stats = await OpenFangAPI.get('/api/hands/instances/' + inst.instance_id + '/stats');
+ var stats = await OMTAEAPI.get('/api/hands/instances/' + inst.instance_id + '/stats');
var m = stats.metrics || {};
if (m['Portfolio Value']) data.portfolio_value = this._metricVal(m['Portfolio Value']);
if (m['Total P&L']) data.total_pnl = this._metricVal(m['Total P&L']);
@@ -665,7 +665,7 @@ function handsPage() {
for (var i = 0; i < kvKeys.length; i++) {
try {
- var resp = await OpenFangAPI.get('/api/memory/agents/' + agentId + '/kv/' + kvKeys[i]);
+ var resp = await OMTAEAPI.get('/api/memory/agents/' + agentId + '/kv/' + kvKeys[i]);
if (resp && resp.value !== null && resp.value !== undefined) {
var val = resp.value;
this._applyKvToData(data, kvKeys[i], val);
@@ -749,7 +749,7 @@ function handsPage() {
(!document.documentElement.getAttribute('data-theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
var gridColor = isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.08)';
var textColor = isDark ? '#8A8380' : '#6B6560';
- var accentColor = '#FF5C00';
+ var accentColor = '#00F0FF';
var successColor = isDark ? '#4ADE80' : '#22C55E';
var errorColor = '#EF4444';
@@ -766,8 +766,8 @@ function handsPage() {
// Determine gradient
var eqCtx = eqCanvas.getContext('2d');
var gradient = eqCtx.createLinearGradient(0, 0, 0, eqCanvas.parentElement.clientHeight || 180);
- gradient.addColorStop(0, isDark ? 'rgba(255, 92, 0, 0.25)' : 'rgba(255, 92, 0, 0.15)');
- gradient.addColorStop(1, 'rgba(255, 92, 0, 0)');
+ gradient.addColorStop(0, isDark ? 'rgba(0, 240, 255, 0.25)' : 'rgba(0, 240, 255, 0.15)');
+ gradient.addColorStop(1, 'rgba(0, 240, 255, 0)');
this._chartEquity = new Chart(eqCtx, {
type: 'line',
@@ -910,7 +910,7 @@ function handsPage() {
datasets: [{
data: radarValues,
borderColor: accentColor,
- backgroundColor: isDark ? 'rgba(255, 92, 0, 0.2)' : 'rgba(255, 92, 0, 0.12)',
+ backgroundColor: isDark ? 'rgba(0, 240, 255, 0.2)' : 'rgba(0, 240, 255, 0.12)',
borderWidth: 2,
pointBackgroundColor: accentColor,
pointRadius: 4,
diff --git a/crates/openfang-api/static/js/pages/logs.js b/crates/openfang-api/static/js/pages/logs.js
index 5ad41ddbcc..28f05e4209 100644
--- a/crates/openfang-api/static/js/pages/logs.js
+++ b/crates/openfang-api/static/js/pages/logs.js
@@ -1,4 +1,4 @@
-// OpenFang Logs Page — Real-time log viewer (SSE streaming + polling fallback) + Audit Trail tab
+// OMTAE Logs Page — Real-time log viewer (SSE streaming + polling fallback) + Audit Trail tab
'use strict';
function logsPage() {
@@ -33,7 +33,7 @@ function logsPage() {
var url = '/api/logs/stream';
var sep = '?';
- var token = OpenFangAPI.getToken();
+ var token = OMTAEAPI.getToken();
if (token) { url += sep + 'token=' + encodeURIComponent(token); sep = '&'; }
try {
@@ -105,7 +105,7 @@ function logsPage() {
async fetchLogs() {
if (this.loading) this.loadError = '';
try {
- var data = await OpenFangAPI.get('/api/audit/recent?n=200');
+ var data = await OMTAEAPI.get('/api/audit/recent?n=200');
this.entries = data.entries || [];
if (this.autoRefresh && !this.hovering) {
this.$nextTick(function() {
@@ -187,7 +187,7 @@ function logsPage() {
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
- a.download = 'openfang-logs-' + new Date().toISOString().slice(0, 10) + '.txt';
+ a.download = 'omtae-logs-' + new Date().toISOString().slice(0, 10) + '.txt';
a.click();
URL.revokeObjectURL(url);
},
@@ -203,7 +203,7 @@ function logsPage() {
this.auditLoading = true;
this.auditLoadError = '';
try {
- var data = await OpenFangAPI.get('/api/audit/recent?n=200');
+ var data = await OMTAEAPI.get('/api/audit/recent?n=200');
this.auditEntries = data.entries || [];
this.tipHash = data.tip_hash || '';
} catch(e) {
@@ -234,16 +234,16 @@ function logsPage() {
async verifyChain() {
try {
- var data = await OpenFangAPI.get('/api/audit/verify');
+ var data = await OMTAEAPI.get('/api/audit/verify');
this.chainValid = data.valid === true;
if (this.chainValid) {
- OpenFangToast.success('Audit chain verified — ' + (data.entries || 0) + ' entries valid');
+ OMTAEToast.success('Audit chain verified — ' + (data.entries || 0) + ' entries valid');
} else {
- OpenFangToast.error('Audit chain broken!');
+ OMTAEToast.error('Audit chain broken!');
}
} catch(e) {
this.chainValid = false;
- OpenFangToast.error('Chain verification failed: ' + e.message);
+ OMTAEToast.error('Chain verification failed: ' + e.message);
}
},
diff --git a/crates/openfang-api/static/js/pages/overview.js b/crates/openfang-api/static/js/pages/overview.js
index 3e890e2499..b773903503 100644
--- a/crates/openfang-api/static/js/pages/overview.js
+++ b/crates/openfang-api/static/js/pages/overview.js
@@ -1,4 +1,4 @@
-// OpenFang Overview Dashboard — Landing page with system stats + provider status
+// OMTAE Overview Dashboard — Landing page with system stats + provider status
'use strict';
function overviewPage() {
@@ -11,6 +11,35 @@ function overviewPage() {
providers: [],
mcpServers: [],
skillCount: 0,
+ drift: { ok: true, findings: [], fixes_applied: [] },
+ driftRemediating: false,
+ driftDismissedAt: (function() {
+ try {
+ var v = sessionStorage.getItem('omtae-drift-dismissed');
+ return v ? Number(v) : null;
+ } catch(e) { return null; }
+ })(),
+
+ get driftAlerts() {
+ var findings = (this.drift && this.drift.findings) || [];
+ var manual = (this.drift && this.drift.manual_allowlist) || [];
+ var manualSet = {};
+ manual.forEach(function(n) {
+ if (n) manualSet[String(n).trim().toLowerCase()] = true;
+ });
+ return findings.filter(function(f) {
+ if (f.remediated || f.severity === 'info' || f.severity === 'debug') return false;
+ if (f.check === 'allowlist' && manualSet) {
+ var m = (f.message || '').match(/Agent '([^']+)'/);
+ if (m && manualSet[m[1].trim().toLowerCase()]) return false;
+ }
+ return true;
+ });
+ },
+
+ get showDriftCard() {
+ return this.driftAlerts.length > 0 && !this.driftDismissedAt;
+ },
loading: true,
loadError: '',
refreshTimer: null,
@@ -28,7 +57,8 @@ function overviewPage() {
this.loadChannels(),
this.loadProviders(),
this.loadMcpServers(),
- this.loadSkills()
+ this.loadSkills(),
+ this.loadDrift()
]);
this.lastRefresh = Date.now();
} catch(e) {
@@ -50,7 +80,8 @@ function overviewPage() {
this.loadChannels(),
this.loadProviders(),
this.loadMcpServers(),
- this.loadSkills()
+ this.loadSkills(),
+ this.loadDrift()
]);
this.lastRefresh = Date.now();
} catch(e) { /* silent */ }
@@ -70,19 +101,53 @@ function overviewPage() {
async loadHealth() {
try {
- this.health = await OpenFangAPI.get('/api/health');
+ this.health = await OMTAEAPI.get('/api/health');
} catch(e) { this.health = { status: 'unreachable' }; }
},
+ async loadDrift() {
+ try {
+ var report = await OMTAEAPI.get('/api/system/drift');
+ this.drift = report || { ok: true, findings: [], fixes_applied: [] };
+ if (this.driftAlerts.length === 0) {
+ this.driftDismissedAt = null;
+ try { sessionStorage.removeItem('omtae-drift-dismissed'); } catch(e) { /* ignore */ }
+ }
+ } catch(e) {
+ this.drift = { ok: true, findings: [], fixes_applied: [] };
+ }
+ },
+
+ dismissDrift() {
+ this.driftDismissedAt = Date.now();
+ try { sessionStorage.setItem('omtae-drift-dismissed', String(this.driftDismissedAt)); } catch(e) { /* ignore */ }
+ },
+
+ async remediateDrift() {
+ this.driftRemediating = true;
+ try {
+ var report = await OMTAEAPI.post('/api/system/drift', {});
+ this.drift = report || { ok: true, findings: [], fixes_applied: [] };
+ if (this.drift.fixes_applied && this.drift.fixes_applied.length) {
+ OMTAEToast.success('Applied ' + this.drift.fixes_applied.length + ' fix(es)');
+ Alpine.store('app').refreshAgents();
+ }
+ await Promise.all([this.loadDrift(), this.loadStatus()]);
+ } catch(e) {
+ OMTAEToast.error(e.message || 'Drift remediation failed');
+ }
+ this.driftRemediating = false;
+ },
+
async loadStatus() {
try {
- this.status = await OpenFangAPI.get('/api/status');
+ this.status = await OMTAEAPI.get('/api/status');
} catch(e) { this.status = {}; throw e; }
},
async loadUsage() {
try {
- var data = await OpenFangAPI.get('/api/usage');
+ var data = await OMTAEAPI.get('/api/usage');
var agents = data.agents || [];
var totalTokens = 0;
var totalTools = 0;
@@ -105,35 +170,35 @@ function overviewPage() {
async loadAudit() {
try {
- var data = await OpenFangAPI.get('/api/audit/recent?n=8');
+ var data = await OMTAEAPI.get('/api/audit/recent?n=8');
this.recentAudit = data.entries || [];
} catch(e) { this.recentAudit = []; }
},
async loadChannels() {
try {
- var data = await OpenFangAPI.get('/api/channels');
+ var data = await OMTAEAPI.get('/api/channels');
this.channels = (data.channels || []).filter(function(ch) { return ch.has_token; });
} catch(e) { this.channels = []; }
},
async loadProviders() {
try {
- var data = await OpenFangAPI.get('/api/providers');
+ var data = await OMTAEAPI.get('/api/providers');
this.providers = data.providers || [];
} catch(e) { this.providers = []; }
},
async loadMcpServers() {
try {
- var data = await OpenFangAPI.get('/api/mcp/servers');
+ var data = await OMTAEAPI.get('/api/mcp/servers');
this.mcpServers = data.servers || [];
} catch(e) { this.mcpServers = []; }
},
async loadSkills() {
try {
- var data = await OpenFangAPI.get('/api/skills');
+ var data = await OMTAEAPI.get('/api/skills');
this.skillCount = (data.skills || []).length;
} catch(e) { this.skillCount = 0; }
},
diff --git a/crates/openfang-api/static/js/pages/runtime.js b/crates/openfang-api/static/js/pages/runtime.js
index a381fe9f6e..e62832b800 100644
--- a/crates/openfang-api/static/js/pages/runtime.js
+++ b/crates/openfang-api/static/js/pages/runtime.js
@@ -14,20 +14,30 @@ document.addEventListener('alpine:init', function() {
logLevel: '-',
networkEnabled: false,
providers: [],
+ modelProfiles: [],
+ activeProfileId: '',
+ selectedProfileId: '',
+ restartVllmOnApply: false,
+ modelSwitchStatus: '',
+ modelSwitchBusy: false,
async loadData() {
this.loading = true;
try {
var results = await Promise.all([
- OpenFangAPI.get('/api/status'),
- OpenFangAPI.get('/api/version'),
- OpenFangAPI.get('/api/providers'),
- OpenFangAPI.get('/api/agents')
+ OMTAEAPI.get('/api/status'),
+ OMTAEAPI.get('/api/version'),
+ OMTAEAPI.get('/api/providers'),
+ OMTAEAPI.get('/api/agents'),
+ OMTAEAPI.get('/api/models/profiles').catch(function() { return { profiles: [], active_profile: '' }; }),
+ OMTAEAPI.get('/api/models/active').catch(function() { return {}; })
]);
var status = results[0];
var ver = results[1];
var prov = results[2];
var agents = results[3];
+ var prof = results[4] || {};
+ var active = results[5] || {};
this.version = ver.version || '-';
this.platform = ver.platform || '-';
@@ -49,10 +59,39 @@ document.addEventListener('alpine:init', function() {
this.providers = (prov.providers || []).filter(function(p) {
return p.auth_status === 'Configured' || p.reachable || p.is_local;
});
+
+ this.modelProfiles = prof.profiles || [];
+ this.activeProfileId = prof.active_profile || active.active_profile || '';
+ if (!this.selectedProfileId && this.activeProfileId) {
+ this.selectedProfileId = this.activeProfileId;
+ } else if (!this.selectedProfileId && this.modelProfiles.length) {
+ this.selectedProfileId = this.modelProfiles[0].id;
+ }
} catch(e) {
console.error('Runtime load error:', e);
}
this.loading = false;
+ },
+
+ async applyModelProfile() {
+ if (!this.selectedProfileId) return;
+ this.modelSwitchBusy = true;
+ this.modelSwitchStatus = '';
+ try {
+ var res = await OMTAEAPI.put('/api/models/active', {
+ profile_id: this.selectedProfileId,
+ restart_vllm: !!this.restartVllmOnApply
+ });
+ this.activeProfileId = this.selectedProfileId;
+ this.defaultModel = res.omtae_model_id || this.defaultModel;
+ this.modelSwitchStatus = res.message || 'Applied.';
+ if (res.message && res.message.indexOf('omtae-model use') >= 0) {
+ this.modelSwitchStatus += ' Full GPU swap: omtae-model use ' + this.selectedProfileId;
+ }
+ } catch (e) {
+ this.modelSwitchStatus = (e && e.message) ? e.message : 'Switch failed';
+ }
+ this.modelSwitchBusy = false;
}
};
});
diff --git a/crates/openfang-api/static/js/pages/scheduler.js b/crates/openfang-api/static/js/pages/scheduler.js
index 82822bc440..edce60a562 100644
--- a/crates/openfang-api/static/js/pages/scheduler.js
+++ b/crates/openfang-api/static/js/pages/scheduler.js
@@ -1,4 +1,4 @@
-// OpenFang Scheduler Page — Cron job management + event triggers unified view
+// OMTAE Scheduler Page — Cron job management + event triggers unified view
'use strict';
function schedulerPage() {
@@ -85,7 +85,7 @@ function schedulerPage() {
async loadChannelTypes() {
try {
- var data = await OpenFangAPI.get('/api/channels');
+ var data = await OMTAEAPI.get('/api/channels');
// /api/channels returns an array of channel descriptors; pull names.
var list = Array.isArray(data) ? data : (data && data.channels) || [];
var names = [];
@@ -102,7 +102,7 @@ function schedulerPage() {
},
async loadJobs() {
- var data = await OpenFangAPI.get('/api/cron/jobs');
+ var data = await OMTAEAPI.get('/api/cron/jobs');
var raw = data.jobs || [];
// Normalize cron API response to flat fields the UI expects
this.jobs = raw.map(function(j) {
@@ -132,7 +132,7 @@ function schedulerPage() {
this.trigLoading = true;
this.trigLoadError = '';
try {
- var data = await OpenFangAPI.get('/api/triggers');
+ var data = await OMTAEAPI.get('/api/triggers');
this.triggers = Array.isArray(data) ? data : [];
} catch(e) {
this.triggers = [];
@@ -185,11 +185,11 @@ function schedulerPage() {
async createJob() {
if (!this.newJob.name.trim()) {
- OpenFangToast.warn('Please enter a job name');
+ OMTAEToast.warn('Please enter a job name');
return;
}
if (!this.newJob.cron.trim()) {
- OpenFangToast.warn('Please enter a cron expression');
+ OMTAEToast.warn('Please enter a cron expression');
return;
}
this.creating = true;
@@ -206,13 +206,13 @@ function schedulerPage() {
if (this.newJob.delivery_targets && this.newJob.delivery_targets.length) {
body.delivery_targets = this.newJob.delivery_targets.map(this.sanitizeTarget);
}
- await OpenFangAPI.post('/api/cron/jobs', body);
+ await OMTAEAPI.post('/api/cron/jobs', body);
this.showCreateForm = false;
this.newJob = { name: '', cron: '', agent_id: '', message: '', enabled: true, delivery_targets: [] };
- OpenFangToast.success('Schedule "' + jobName + '" created');
+ OMTAEToast.success('Schedule "' + jobName + '" created');
await this.loadJobs();
} catch(e) {
- OpenFangToast.error('Failed to create schedule: ' + (e.message || e));
+ OMTAEToast.error('Failed to create schedule: ' + (e.message || e));
}
this.creating = false;
},
@@ -220,24 +220,24 @@ function schedulerPage() {
async toggleJob(job) {
try {
var newState = !job.enabled;
- await OpenFangAPI.put('/api/cron/jobs/' + job.id + '/enable', { enabled: newState });
+ await OMTAEAPI.put('/api/cron/jobs/' + job.id + '/enable', { enabled: newState });
job.enabled = newState;
- OpenFangToast.success('Schedule ' + (newState ? 'enabled' : 'paused'));
+ OMTAEToast.success('Schedule ' + (newState ? 'enabled' : 'paused'));
} catch(e) {
- OpenFangToast.error('Failed to toggle schedule: ' + (e.message || e));
+ OMTAEToast.error('Failed to toggle schedule: ' + (e.message || e));
}
},
deleteJob(job) {
var self = this;
var jobName = job.name || job.id;
- OpenFangToast.confirm('Delete Schedule', 'Delete "' + jobName + '"? This cannot be undone.', async function() {
+ OMTAEToast.confirm('Delete Schedule', 'Delete "' + jobName + '"? This cannot be undone.', async function() {
try {
- await OpenFangAPI.del('/api/cron/jobs/' + job.id);
+ await OMTAEAPI.del('/api/cron/jobs/' + job.id);
self.jobs = self.jobs.filter(function(j) { return j.id !== job.id; });
- OpenFangToast.success('Schedule "' + jobName + '" deleted');
+ OMTAEToast.success('Schedule "' + jobName + '" deleted');
} catch(e) {
- OpenFangToast.error('Failed to delete schedule: ' + (e.message || e));
+ OMTAEToast.error('Failed to delete schedule: ' + (e.message || e));
}
});
},
@@ -245,17 +245,17 @@ function schedulerPage() {
async runNow(job) {
this.runningJobId = job.id;
try {
- var result = await OpenFangAPI.post('/api/cron/jobs/' + job.id + '/run', {});
+ var result = await OMTAEAPI.post('/api/cron/jobs/' + job.id + '/run', {});
if (result.status === 'triggered' || result.status === 'completed') {
- OpenFangToast.success('Job "' + (job.name || 'job') + '" triggered');
+ OMTAEToast.success('Job "' + (job.name || 'job') + '" triggered');
// Don't update job.last_run here — the job runs asynchronously in the
// background. The real last_run is set by the server on completion and
// will appear on the next data refresh.
} else {
- OpenFangToast.error('Run failed: ' + (result.error || 'Unknown error'));
+ OMTAEToast.error('Run failed: ' + (result.error || 'Unknown error'));
}
} catch(e) {
- OpenFangToast.error('Run failed: ' + (e.message || e));
+ OMTAEToast.error('Run failed: ' + (e.message || e));
}
this.runningJobId = '';
},
@@ -296,7 +296,7 @@ function schedulerPage() {
addDraftTarget() {
var err = this.validateTarget(this.draftTarget);
if (err) {
- OpenFangToast.warn(err);
+ OMTAEToast.warn(err);
return;
}
if (!Array.isArray(this.newJob.delivery_targets)) this.newJob.delivery_targets = [];
@@ -395,7 +395,7 @@ function schedulerPage() {
this.deliveryLogError = '';
this.deliveryLogLoading = true;
try {
- var data = await OpenFangAPI.get('/api/schedules/' + job.id + '/delivery-log');
+ var data = await OMTAEAPI.get('/api/schedules/' + job.id + '/delivery-log');
this.deliveryLog = {
targets: Array.isArray(data.targets) ? data.targets : [],
entries: Array.isArray(data.entries) ? data.entries : []
@@ -435,7 +435,7 @@ function schedulerPage() {
addDraftTargetToEdit() {
var err = this.validateTarget(this.draftTarget);
if (err) {
- OpenFangToast.warn(err);
+ OMTAEToast.warn(err);
return;
}
this.editingTargets.push(this.sanitizeTarget(this.draftTarget));
@@ -452,14 +452,14 @@ function schedulerPage() {
this.savingTargets = true;
try {
var clean = this.editingTargets.map(this.sanitizeTarget);
- await OpenFangAPI.put('/api/schedules/' + this.editingTargetsJobId, {
+ await OMTAEAPI.put('/api/schedules/' + this.editingTargetsJobId, {
delivery_targets: clean
});
- OpenFangToast.success('Delivery targets updated');
+ OMTAEToast.success('Delivery targets updated');
this.cancelEditTargets();
await this.loadJobs();
} catch(e) {
- OpenFangToast.error('Failed to update targets: ' + (e.message || e));
+ OMTAEToast.error('Failed to update targets: ' + (e.message || e));
}
this.savingTargets = false;
},
@@ -489,23 +489,23 @@ function schedulerPage() {
async toggleTrigger(trigger) {
try {
var newState = !trigger.enabled;
- await OpenFangAPI.put('/api/triggers/' + trigger.id, { enabled: newState });
+ await OMTAEAPI.put('/api/triggers/' + trigger.id, { enabled: newState });
trigger.enabled = newState;
- OpenFangToast.success('Trigger ' + (newState ? 'enabled' : 'disabled'));
+ OMTAEToast.success('Trigger ' + (newState ? 'enabled' : 'disabled'));
} catch(e) {
- OpenFangToast.error('Failed to toggle trigger: ' + (e.message || e));
+ OMTAEToast.error('Failed to toggle trigger: ' + (e.message || e));
}
},
deleteTrigger(trigger) {
var self = this;
- OpenFangToast.confirm('Delete Trigger', 'Delete this trigger? This cannot be undone.', async function() {
+ OMTAEToast.confirm('Delete Trigger', 'Delete this trigger? This cannot be undone.', async function() {
try {
- await OpenFangAPI.del('/api/triggers/' + trigger.id);
+ await OMTAEAPI.del('/api/triggers/' + trigger.id);
self.triggers = self.triggers.filter(function(t) { return t.id !== trigger.id; });
- OpenFangToast.success('Trigger deleted');
+ OMTAEToast.success('Trigger deleted');
} catch(e) {
- OpenFangToast.error('Failed to delete trigger: ' + (e.message || e));
+ OMTAEToast.error('Failed to delete trigger: ' + (e.message || e));
}
});
},
diff --git a/crates/openfang-api/static/js/pages/sessions.js b/crates/openfang-api/static/js/pages/sessions.js
index a2c716f9f6..95abcd5e69 100644
--- a/crates/openfang-api/static/js/pages/sessions.js
+++ b/crates/openfang-api/static/js/pages/sessions.js
@@ -1,4 +1,4 @@
-// OpenFang Sessions Page — Session listing + Memory tab
+// OMTAE Sessions Page — Session listing + Memory tab
'use strict';
function sessionsPage() {
@@ -26,7 +26,7 @@ function sessionsPage() {
this.loading = true;
this.loadError = '';
try {
- var data = await OpenFangAPI.get('/api/sessions');
+ var data = await OMTAEAPI.get('/api/sessions');
var sessions = data.sessions || [];
var agents = Alpine.store('app').agents;
var agentMap = {};
@@ -65,16 +65,16 @@ function sessionsPage() {
deleteSession(sessionId) {
var self = this;
var t = window.i18n ? window.i18n.t.bind(window.i18n) : function(k) { return k; };
- OpenFangToast.confirm(
+ OMTAEToast.confirm(
t('sessions.delete_session') || 'Delete Session',
t('sessions.delete_confirm') || 'This will permanently remove the session and its messages.',
async function() {
try {
- await OpenFangAPI.del('/api/sessions/' + sessionId);
+ await OMTAEAPI.del('/api/sessions/' + sessionId);
self.sessions = self.sessions.filter(function(s) { return s.session_id !== sessionId; });
- OpenFangToast.success('Session deleted');
+ OMTAEToast.success('Session deleted');
} catch(e) {
- OpenFangToast.error('Failed to delete session: ' + e.message);
+ OMTAEToast.error('Failed to delete session: ' + e.message);
}
}
);
@@ -86,7 +86,7 @@ function sessionsPage() {
this.memLoading = true;
this.memLoadError = '';
try {
- var data = await OpenFangAPI.get('/api/memory/agents/' + this.memAgentId + '/kv');
+ var data = await OMTAEAPI.get('/api/memory/agents/' + this.memAgentId + '/kv');
this.kvPairs = data.kv_pairs || [];
} catch(e) {
this.kvPairs = [];
@@ -100,30 +100,30 @@ function sessionsPage() {
var value;
try { value = JSON.parse(this.newValue); } catch(e) { value = this.newValue; }
try {
- await OpenFangAPI.put('/api/memory/agents/' + this.memAgentId + '/kv/' + encodeURIComponent(this.newKey), { value: value });
+ await OMTAEAPI.put('/api/memory/agents/' + this.memAgentId + '/kv/' + encodeURIComponent(this.newKey), { value: value });
this.showAdd = false;
- OpenFangToast.success('Key "' + this.newKey + '" saved');
+ OMTAEToast.success('Key "' + this.newKey + '" saved');
this.newKey = '';
this.newValue = '""';
await this.loadKv();
} catch(e) {
- OpenFangToast.error('Failed to save key: ' + e.message);
+ OMTAEToast.error('Failed to save key: ' + e.message);
}
},
deleteKey(key) {
var self = this;
var t = window.i18n ? window.i18n.t.bind(window.i18n) : function(k) { return k; };
- OpenFangToast.confirm(
+ OMTAEToast.confirm(
t('sessions.delete_key') || 'Delete Key',
(t('sessions.delete_key_confirm') || 'Delete key') + ' "' + key + '"? This cannot be undone.',
async function() {
try {
- await OpenFangAPI.del('/api/memory/agents/' + self.memAgentId + '/kv/' + encodeURIComponent(key));
- OpenFangToast.success('Key "' + key + '" deleted');
+ await OMTAEAPI.del('/api/memory/agents/' + self.memAgentId + '/kv/' + encodeURIComponent(key));
+ OMTAEToast.success('Key "' + key + '" deleted');
await self.loadKv();
} catch(e) {
- OpenFangToast.error('Failed to delete key: ' + e.message);
+ OMTAEToast.error('Failed to delete key: ' + e.message);
}
}
);
@@ -144,13 +144,13 @@ function sessionsPage() {
var value;
try { value = JSON.parse(this.editingValue); } catch(e) { value = this.editingValue; }
try {
- await OpenFangAPI.put('/api/memory/agents/' + this.memAgentId + '/kv/' + encodeURIComponent(this.editingKey), { value: value });
- OpenFangToast.success('Key "' + this.editingKey + '" updated');
+ await OMTAEAPI.put('/api/memory/agents/' + this.memAgentId + '/kv/' + encodeURIComponent(this.editingKey), { value: value });
+ OMTAEToast.success('Key "' + this.editingKey + '" updated');
this.editingKey = null;
this.editingValue = '';
await this.loadKv();
} catch(e) {
- OpenFangToast.error('Failed to save: ' + e.message);
+ OMTAEToast.error('Failed to save: ' + e.message);
}
}
};
diff --git a/crates/openfang-api/static/js/pages/settings.js b/crates/openfang-api/static/js/pages/settings.js
index 7b46c2d001..fd259e79a9 100644
--- a/crates/openfang-api/static/js/pages/settings.js
+++ b/crates/openfang-api/static/js/pages/settings.js
@@ -1,4 +1,4 @@
-// OpenFang Settings Page — Provider Hub, Model Catalog, Config, Tools + Security, Network, Migration tabs
+// OMTAE Settings Page — Provider Hub, Model Catalog, Config, Tools + Security, Network, Migration tabs
'use strict';
function settingsPage() {
@@ -122,7 +122,7 @@ function settingsPage() {
{
name: 'Bearer Token Authentication', key: 'auth',
description: 'All non-health endpoints require Authorization: Bearer header. When no API key is configured, all requests are restricted to localhost only.',
- configHint: 'Set api_key in ~/.openfang/config.toml for remote access. Empty = localhost only.',
+ configHint: 'Set api_key in ~/.omtae/config.toml for remote access. Empty = localhost only.',
valueKey: 'auth'
}
],
@@ -187,8 +187,8 @@ function settingsPage() {
async loadSysInfo() {
try {
- var ver = await OpenFangAPI.get('/api/version');
- var status = await OpenFangAPI.get('/api/status');
+ var ver = await OMTAEAPI.get('/api/version');
+ var status = await OMTAEAPI.get('/api/status');
this.sysInfo = {
version: ver.version || '-',
platform: ver.platform || '-',
@@ -203,27 +203,27 @@ function settingsPage() {
async loadUsage() {
try {
- var data = await OpenFangAPI.get('/api/usage');
+ var data = await OMTAEAPI.get('/api/usage');
this.usageData = data.agents || [];
} catch(e) { this.usageData = []; }
},
async loadTools() {
try {
- var data = await OpenFangAPI.get('/api/tools');
+ var data = await OMTAEAPI.get('/api/tools');
this.tools = data.tools || [];
} catch(e) { this.tools = []; }
},
async loadConfig() {
try {
- this.config = await OpenFangAPI.get('/api/config');
+ this.config = await OMTAEAPI.get('/api/config');
} catch(e) { this.config = {}; }
},
async loadProviders() {
try {
- var data = await OpenFangAPI.get('/api/providers');
+ var data = await OMTAEAPI.get('/api/providers');
this.providers = data.providers || [];
for (var i = 0; i < this.providers.length; i++) {
var p = this.providers[i];
@@ -241,7 +241,7 @@ function settingsPage() {
async loadModels() {
try {
- var data = await OpenFangAPI.get('/api/models');
+ var data = await OMTAEAPI.get('/api/models');
this.models = data.models || [];
} catch(e) { this.models = []; }
},
@@ -251,7 +251,7 @@ function settingsPage() {
if (!id) return;
this.customModelStatus = 'Adding...';
try {
- await OpenFangAPI.post('/api/models/custom', {
+ await OMTAEAPI.post('/api/models/custom', {
id: id,
provider: this.customModelProvider || 'openrouter',
context_window: this.customModelContext || 128000,
@@ -269,19 +269,19 @@ function settingsPage() {
async deleteCustomModel(modelId) {
if (!confirm('Delete custom model "' + modelId + '"?')) return;
try {
- await OpenFangAPI.del('/api/models/custom/' + encodeURIComponent(modelId));
- OpenFangToast.success('Model deleted');
+ await OMTAEAPI.del('/api/models/custom/' + encodeURIComponent(modelId));
+ OMTAEToast.success('Model deleted');
await this.loadModels();
} catch(e) {
- OpenFangToast.error('Failed to delete: ' + (e.message || 'Unknown error'));
+ OMTAEToast.error('Failed to delete: ' + (e.message || 'Unknown error'));
}
},
async loadConfigSchema() {
try {
var results = await Promise.all([
- OpenFangAPI.get('/api/config/schema').catch(function() { return {}; }),
- OpenFangAPI.get('/api/config')
+ OMTAEAPI.get('/api/config/schema').catch(function() { return {}; }),
+ OMTAEAPI.get('/api/config')
]);
this.configSchema = results[0].sections || null;
this.configValues = results[1] || {};
@@ -303,11 +303,11 @@ function settingsPage() {
var path = (sectionMeta && sectionMeta.root_level) ? field : key;
this.configSaving[key] = true;
try {
- await OpenFangAPI.post('/api/config/set', { path: path, value: value });
+ await OMTAEAPI.post('/api/config/set', { path: path, value: value });
this.configDirty[key] = false;
- OpenFangToast.success('Saved ' + field);
+ OMTAEToast.success('Saved ' + field);
} catch(e) {
- OpenFangToast.error('Failed to save: ' + e.message);
+ OMTAEToast.error('Failed to save: ' + e.message);
}
this.configSaving[key] = false;
},
@@ -490,30 +490,30 @@ function settingsPage() {
async saveProviderKey(provider) {
var key = this.providerKeyInputs[provider.id];
- if (!key || !key.trim()) { OpenFangToast.error('Please enter an API key'); return; }
+ if (!key || !key.trim()) { OMTAEToast.error('Please enter an API key'); return; }
try {
- var resp = await OpenFangAPI.post('/api/providers/' + encodeURIComponent(provider.id) + '/key', { key: key.trim() });
+ var resp = await OMTAEAPI.post('/api/providers/' + encodeURIComponent(provider.id) + '/key', { key: key.trim() });
if (resp && resp.switched_default) {
- OpenFangToast.warning(resp.message || 'Default provider was switched to ' + provider.display_name);
+ OMTAEToast.warning(resp.message || 'Default provider was switched to ' + provider.display_name);
} else {
- OpenFangToast.success('API key saved for ' + provider.display_name);
+ OMTAEToast.success('API key saved for ' + provider.display_name);
}
this.providerKeyInputs[provider.id] = '';
await this.loadProviders();
await this.loadModels();
} catch(e) {
- OpenFangToast.error('Failed to save key: ' + e.message);
+ OMTAEToast.error('Failed to save key: ' + e.message);
}
},
async removeProviderKey(provider) {
try {
- await OpenFangAPI.del('/api/providers/' + encodeURIComponent(provider.id) + '/key');
- OpenFangToast.success('API key removed for ' + provider.display_name);
+ await OMTAEAPI.del('/api/providers/' + encodeURIComponent(provider.id) + '/key');
+ OMTAEToast.success('API key removed for ' + provider.display_name);
await this.loadProviders();
await this.loadModels();
} catch(e) {
- OpenFangToast.error('Failed to remove key: ' + e.message);
+ OMTAEToast.error('Failed to remove key: ' + e.message);
}
},
@@ -521,7 +521,7 @@ function settingsPage() {
this.copilotOAuth.polling = true;
this.copilotOAuth.userCode = '';
try {
- var resp = await OpenFangAPI.post('/api/providers/github-copilot/oauth/start', {});
+ var resp = await OMTAEAPI.post('/api/providers/github-copilot/oauth/start', {});
this.copilotOAuth.userCode = resp.user_code;
this.copilotOAuth.verificationUri = resp.verification_uri;
this.copilotOAuth.pollId = resp.poll_id;
@@ -529,7 +529,7 @@ function settingsPage() {
window.open(resp.verification_uri, '_blank');
this.pollCopilotOAuth();
} catch(e) {
- OpenFangToast.error('Failed to start Copilot login: ' + e.message);
+ OMTAEToast.error('Failed to start Copilot login: ' + e.message);
this.copilotOAuth.polling = false;
}
},
@@ -539,9 +539,9 @@ function settingsPage() {
setTimeout(async function() {
if (!self.copilotOAuth.pollId) return;
try {
- var resp = await OpenFangAPI.get('/api/providers/github-copilot/oauth/poll/' + self.copilotOAuth.pollId);
+ var resp = await OMTAEAPI.get('/api/providers/github-copilot/oauth/poll/' + self.copilotOAuth.pollId);
if (resp.status === 'complete') {
- OpenFangToast.success('GitHub Copilot authenticated successfully!');
+ OMTAEToast.success('GitHub Copilot authenticated successfully!');
self.copilotOAuth = { polling: false, userCode: '', verificationUri: '', pollId: '', interval: 5 };
await self.loadProviders();
await self.loadModels();
@@ -549,17 +549,17 @@ function settingsPage() {
if (resp.interval) self.copilotOAuth.interval = resp.interval;
self.pollCopilotOAuth();
} else if (resp.status === 'expired') {
- OpenFangToast.error('Device code expired. Please try again.');
+ OMTAEToast.error('Device code expired. Please try again.');
self.copilotOAuth = { polling: false, userCode: '', verificationUri: '', pollId: '', interval: 5 };
} else if (resp.status === 'denied') {
- OpenFangToast.error('Access denied by user.');
+ OMTAEToast.error('Access denied by user.');
self.copilotOAuth = { polling: false, userCode: '', verificationUri: '', pollId: '', interval: 5 };
} else {
- OpenFangToast.error('OAuth error: ' + (resp.error || resp.status));
+ OMTAEToast.error('OAuth error: ' + (resp.error || resp.status));
self.copilotOAuth = { polling: false, userCode: '', verificationUri: '', pollId: '', interval: 5 };
}
} catch(e) {
- OpenFangToast.error('Poll error: ' + e.message);
+ OMTAEToast.error('Poll error: ' + e.message);
self.copilotOAuth = { polling: false, userCode: '', verificationUri: '', pollId: '', interval: 5 };
}
}, self.copilotOAuth.interval * 1000);
@@ -569,66 +569,66 @@ function settingsPage() {
this.providerTesting[provider.id] = true;
this.providerTestResults[provider.id] = null;
try {
- var result = await OpenFangAPI.post('/api/providers/' + encodeURIComponent(provider.id) + '/test', {});
+ var result = await OMTAEAPI.post('/api/providers/' + encodeURIComponent(provider.id) + '/test', {});
this.providerTestResults[provider.id] = result;
if (result.status === 'ok') {
- OpenFangToast.success(provider.display_name + ' connected (' + (result.latency_ms || '?') + 'ms)');
+ OMTAEToast.success(provider.display_name + ' connected (' + (result.latency_ms || '?') + 'ms)');
} else {
- OpenFangToast.error(provider.display_name + ': ' + (result.error || 'Connection failed'));
+ OMTAEToast.error(provider.display_name + ': ' + (result.error || 'Connection failed'));
}
} catch(e) {
this.providerTestResults[provider.id] = { status: 'error', error: e.message };
- OpenFangToast.error('Test failed: ' + e.message);
+ OMTAEToast.error('Test failed: ' + e.message);
}
this.providerTesting[provider.id] = false;
},
async saveProviderUrl(provider) {
var url = this.providerUrlInputs[provider.id];
- if (!url || !url.trim()) { OpenFangToast.error('Please enter a base URL'); return; }
+ if (!url || !url.trim()) { OMTAEToast.error('Please enter a base URL'); return; }
url = url.trim();
if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) {
- OpenFangToast.error('URL must start with http:// or https://'); return;
+ OMTAEToast.error('URL must start with http:// or https://'); return;
}
this.providerUrlSaving[provider.id] = true;
try {
- var result = await OpenFangAPI.put('/api/providers/' + encodeURIComponent(provider.id) + '/url', { base_url: url });
+ var result = await OMTAEAPI.put('/api/providers/' + encodeURIComponent(provider.id) + '/url', { base_url: url });
if (result.reachable) {
- OpenFangToast.success(provider.display_name + ' URL saved — reachable (' + (result.latency_ms || '?') + 'ms)');
+ OMTAEToast.success(provider.display_name + ' URL saved — reachable (' + (result.latency_ms || '?') + 'ms)');
} else {
- OpenFangToast.warning(provider.display_name + ' URL saved but not reachable');
+ OMTAEToast.warning(provider.display_name + ' URL saved but not reachable');
}
await this.loadProviders();
} catch(e) {
- OpenFangToast.error('Failed to save URL: ' + e.message);
+ OMTAEToast.error('Failed to save URL: ' + e.message);
}
this.providerUrlSaving[provider.id] = false;
},
async addCustomProvider() {
var name = this.customProviderName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
- if (!name) { OpenFangToast.error('Please enter a provider name'); return; }
+ if (!name) { OMTAEToast.error('Please enter a provider name'); return; }
var url = this.customProviderUrl.trim();
- if (!url) { OpenFangToast.error('Please enter a base URL'); return; }
+ if (!url) { OMTAEToast.error('Please enter a base URL'); return; }
if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) {
- OpenFangToast.error('URL must start with http:// or https://'); return;
+ OMTAEToast.error('URL must start with http:// or https://'); return;
}
this.addingCustomProvider = true;
this.customProviderStatus = '';
try {
- var result = await OpenFangAPI.put('/api/providers/' + encodeURIComponent(name) + '/url', { base_url: url });
+ var result = await OMTAEAPI.put('/api/providers/' + encodeURIComponent(name) + '/url', { base_url: url });
if (this.customProviderKey.trim()) {
- await OpenFangAPI.post('/api/providers/' + encodeURIComponent(name) + '/key', { key: this.customProviderKey.trim() });
+ await OMTAEAPI.post('/api/providers/' + encodeURIComponent(name) + '/key', { key: this.customProviderKey.trim() });
}
this.customProviderName = '';
this.customProviderUrl = '';
this.customProviderKey = '';
this.customProviderStatus = '';
- OpenFangToast.success('Provider "' + name + '" added' + (result.reachable ? ' (reachable)' : ' (not reachable yet)'));
+ OMTAEToast.success('Provider "' + name + '" added' + (result.reachable ? ' (reachable)' : ' (not reachable yet)'));
await this.loadProviders();
} catch(e) {
this.customProviderStatus = 'Error: ' + (e.message || 'Failed');
- OpenFangToast.error('Failed to add provider: ' + e.message);
+ OMTAEToast.error('Failed to add provider: ' + e.message);
}
this.addingCustomProvider = false;
},
@@ -637,7 +637,7 @@ function settingsPage() {
async loadSecurity() {
this.secLoading = true;
try {
- this.securityData = await OpenFangAPI.get('/api/security');
+ this.securityData = await OMTAEAPI.get('/api/security');
} catch(e) {
this.securityData = null;
}
@@ -700,7 +700,7 @@ function settingsPage() {
this.verifyingChain = true;
this.chainResult = null;
try {
- var res = await OpenFangAPI.get('/api/audit/verify');
+ var res = await OMTAEAPI.get('/api/audit/verify');
this.chainResult = res;
} catch(e) {
this.chainResult = { valid: false, error: e.message };
@@ -713,7 +713,7 @@ function settingsPage() {
this.peersLoading = true;
this.peersLoadError = '';
try {
- var data = await OpenFangAPI.get('/api/peers');
+ var data = await OMTAEAPI.get('/api/peers');
this.peers = (data.peers || []).map(function(p) {
return {
node_id: p.node_id,
@@ -737,7 +737,7 @@ function settingsPage() {
this._peerPollTimer = setInterval(async function() {
if (self.tab !== 'network') { self.stopPeerPolling(); return; }
try {
- var data = await OpenFangAPI.get('/api/peers');
+ var data = await OMTAEAPI.get('/api/peers');
self.peers = (data.peers || []).map(function(p) {
return {
node_id: p.node_id,
@@ -760,7 +760,7 @@ function settingsPage() {
async autoDetect() {
this.detecting = true;
try {
- var data = await OpenFangAPI.get('/api/migrate/detect');
+ var data = await OMTAEAPI.get('/api/migrate/detect');
if (data.detected && data.scan) {
this.sourcePath = data.path;
this.scanResult = data.scan;
@@ -778,16 +778,16 @@ function settingsPage() {
if (!this.sourcePath) return;
this.scanning = true;
try {
- var data = await OpenFangAPI.post('/api/migrate/scan', { path: this.sourcePath });
+ var data = await OMTAEAPI.post('/api/migrate/scan', { path: this.sourcePath });
if (data.error) {
- OpenFangToast.error('Scan error: ' + data.error);
+ OMTAEToast.error('Scan error: ' + data.error);
this.scanning = false;
return;
}
this.scanResult = data;
this.migStep = 'preview';
} catch(e) {
- OpenFangToast.error('Scan failed: ' + e.message);
+ OMTAEToast.error('Scan failed: ' + e.message);
}
this.scanning = false;
},
@@ -797,7 +797,7 @@ function settingsPage() {
try {
var target = this.targetPath;
if (!target) target = '';
- var data = await OpenFangAPI.post('/api/migrate', {
+ var data = await OMTAEAPI.post('/api/migrate', {
source: 'openclaw',
source_dir: this.sourcePath || (this.scanResult ? this.scanResult.path : ''),
target_dir: target,
diff --git a/crates/openfang-api/static/js/pages/skills.js b/crates/openfang-api/static/js/pages/skills.js
index 8a4ddf7929..b59c235b71 100644
--- a/crates/openfang-api/static/js/pages/skills.js
+++ b/crates/openfang-api/static/js/pages/skills.js
@@ -1,4 +1,4 @@
-// OpenFang Skills Page — OpenClaw/ClawHub ecosystem + local skills + MCP servers
+// OMTAE Skills Page — OpenClaw/ClawHub ecosystem + local skills + MCP servers
'use strict';
function skillsPage() {
@@ -99,7 +99,7 @@ function skillsPage() {
this.loading = true;
this.loadError = '';
try {
- var data = await OpenFangAPI.get('/api/skills');
+ var data = await OMTAEAPI.get('/api/skills');
this.skills = (data.skills || []).map(function(s) {
return {
name: s.name,
@@ -132,7 +132,7 @@ function skillsPage() {
this.configError = '';
this.configLoading = true;
try {
- var data = await OpenFangAPI.get('/api/skills/' + encodeURIComponent(skill.name) + '/config');
+ var data = await OMTAEAPI.get('/api/skills/' + encodeURIComponent(skill.name) + '/config');
this.configDeclared = data.declared || {};
this.configResolved = data.resolved || {};
// Pre-populate draft values only for vars the user has already
@@ -214,7 +214,7 @@ function skillsPage() {
async saveSkillConfig() {
if (!this.configSkill) return;
if (this.hasInvalidConfig()) {
- OpenFangToast.error('Fill in all required variables before saving.');
+ OMTAEToast.error('Fill in all required variables before saving.');
return;
}
this.configSaving = true;
@@ -229,8 +229,8 @@ function skillsPage() {
if (v.length > 0) payload[n] = v;
}
try {
- await OpenFangAPI.put('/api/skills/' + encodeURIComponent(this.configSkill.name) + '/config', { values: payload });
- OpenFangToast.success('Saved, reloading agents\u2026');
+ await OMTAEAPI.put('/api/skills/' + encodeURIComponent(this.configSkill.name) + '/config', { values: payload });
+ OMTAEToast.success('Saved, reloading agents\u2026');
// Refresh the modal contents so the new source/value shows up.
var refreshed = this.configSkill;
await this.loadSkills();
@@ -241,7 +241,7 @@ function skillsPage() {
if (updated) await self.openSkillConfig(updated);
} catch(e) {
this.configError = e.message || 'Save failed.';
- OpenFangToast.error('Save failed: ' + (e.message || 'unknown error'));
+ OMTAEToast.error('Save failed: ' + (e.message || 'unknown error'));
}
this.configSaving = false;
},
@@ -257,18 +257,18 @@ function skillsPage() {
return;
}
try {
- await OpenFangAPI.del('/api/skills/' + encodeURIComponent(this.configSkill.name) + '/config/' + encodeURIComponent(name));
- OpenFangToast.success('Reset ' + name);
+ await OMTAEAPI.del('/api/skills/' + encodeURIComponent(this.configSkill.name) + '/config/' + encodeURIComponent(name));
+ OMTAEToast.success('Reset ' + name);
// Refresh modal state from server.
- var data = await OpenFangAPI.get('/api/skills/' + encodeURIComponent(this.configSkill.name) + '/config');
+ var data = await OMTAEAPI.get('/api/skills/' + encodeURIComponent(this.configSkill.name) + '/config');
this.configResolved = data.resolved || {};
this.configDraft[name] = '';
} catch(e) {
var msg = e.message || 'Reset failed';
if (msg.indexOf('required') !== -1 || msg.indexOf('409') !== -1) {
- OpenFangToast.error('Cannot reset: ' + decl.description + ' is required with no fallback.');
+ OMTAEToast.error('Cannot reset: ' + decl.description + ' is required with no fallback.');
} else {
- OpenFangToast.error('Reset failed: ' + msg);
+ OMTAEToast.error('Reset failed: ' + msg);
}
}
},
@@ -303,7 +303,7 @@ function skillsPage() {
this.clawhubLoading = true;
this.clawhubError = '';
try {
- var data = await OpenFangAPI.get('/api/clawhub/search?q=' + encodeURIComponent(this.clawhubSearch.trim()) + '&limit=20');
+ var data = await OMTAEAPI.get('/api/clawhub/search?q=' + encodeURIComponent(this.clawhubSearch.trim()) + '&limit=20');
this.clawhubResults = data.items || [];
if (data.error) this.clawhubError = data.error;
} catch(e) {
@@ -335,7 +335,7 @@ function skillsPage() {
this.clawhubError = '';
this.clawhubNextCursor = null;
try {
- var data = await OpenFangAPI.get('/api/clawhub/browse?sort=' + this.clawhubSort + '&limit=20');
+ var data = await OMTAEAPI.get('/api/clawhub/browse?sort=' + this.clawhubSort + '&limit=20');
this.clawhubBrowseResults = data.items || [];
this.clawhubNextCursor = data.next_cursor || null;
if (data.error) this.clawhubError = data.error;
@@ -352,7 +352,7 @@ function skillsPage() {
if (!this.clawhubNextCursor || this.clawhubLoading) return;
this.clawhubLoading = true;
try {
- var data = await OpenFangAPI.get('/api/clawhub/browse?sort=' + this.clawhubSort + '&limit=20&cursor=' + encodeURIComponent(this.clawhubNextCursor));
+ var data = await OMTAEAPI.get('/api/clawhub/browse?sort=' + this.clawhubSort + '&limit=20&cursor=' + encodeURIComponent(this.clawhubNextCursor));
this.clawhubBrowseResults = this.clawhubBrowseResults.concat(data.items || []);
this.clawhubNextCursor = data.next_cursor || null;
} catch(e) {
@@ -367,10 +367,10 @@ function skillsPage() {
this.skillDetail = null;
this.installResult = null;
try {
- var data = await OpenFangAPI.get('/api/clawhub/skill/' + encodeURIComponent(slug));
+ var data = await OMTAEAPI.get('/api/clawhub/skill/' + encodeURIComponent(slug));
this.skillDetail = data;
} catch(e) {
- OpenFangToast.error('Failed to load skill details');
+ OMTAEToast.error('Failed to load skill details');
}
this.detailLoading = false;
},
@@ -390,12 +390,12 @@ function skillsPage() {
}
this.skillCodeLoading = true;
try {
- var data = await OpenFangAPI.get('/api/clawhub/skill/' + encodeURIComponent(slug) + '/code');
+ var data = await OMTAEAPI.get('/api/clawhub/skill/' + encodeURIComponent(slug) + '/code');
this.skillCode = data.code || '';
this.skillCodeFilename = data.filename || 'source';
this.showSkillCode = true;
} catch(e) {
- OpenFangToast.error('Could not load skill source code');
+ OMTAEToast.error('Could not load skill source code');
}
this.skillCodeLoading = false;
},
@@ -405,12 +405,12 @@ function skillsPage() {
this.installingSlug = slug;
this.installResult = null;
try {
- var data = await OpenFangAPI.post('/api/clawhub/install', { slug: slug });
+ var data = await OMTAEAPI.post('/api/clawhub/install', { slug: slug });
this.installResult = data;
if (data.warnings && data.warnings.length > 0) {
- OpenFangToast.success('Skill "' + data.name + '" installed with ' + data.warnings.length + ' warning(s)');
+ OMTAEToast.success('Skill "' + data.name + '" installed with ' + data.warnings.length + ' warning(s)');
} else {
- OpenFangToast.success('Skill "' + data.name + '" installed successfully');
+ OMTAEToast.success('Skill "' + data.name + '" installed successfully');
}
// Update installed state in detail modal if open
if (this.skillDetail && this.skillDetail.slug === slug) {
@@ -420,11 +420,11 @@ function skillsPage() {
} catch(e) {
var msg = e.message || 'Install failed';
if (msg.includes('already_installed')) {
- OpenFangToast.error('Skill is already installed');
+ OMTAEToast.error('Skill is already installed');
} else if (msg.includes('SecurityBlocked')) {
- OpenFangToast.error('Skill blocked by security scan');
+ OMTAEToast.error('Skill blocked by security scan');
} else {
- OpenFangToast.error('Install failed: ' + msg);
+ OMTAEToast.error('Install failed: ' + msg);
}
}
this.installingSlug = null;
@@ -434,16 +434,16 @@ function skillsPage() {
uninstallSkill: function(name) {
var self = this;
var t = window.i18n ? window.i18n.t.bind(window.i18n) : function(k) { return k; };
- OpenFangToast.confirm(
+ OMTAEToast.confirm(
t('skills.uninstall_skill') || 'Uninstall Skill',
t('skills.uninstall_confirm') + ' "' + name + '"? This cannot be undone.',
async function() {
try {
- await OpenFangAPI.post('/api/skills/uninstall', { name: name });
- OpenFangToast.success('Skill "' + name + '" uninstalled');
+ await OMTAEAPI.post('/api/skills/uninstall', { name: name });
+ OMTAEToast.success('Skill "' + name + '" uninstalled');
await self.loadSkills();
} catch(e) {
- OpenFangToast.error('Failed to uninstall skill: ' + e.message);
+ OMTAEToast.error('Failed to uninstall skill: ' + e.message);
}
}
);
@@ -452,17 +452,17 @@ function skillsPage() {
// Create prompt-only skill
async createDemoSkill(skill) {
try {
- await OpenFangAPI.post('/api/skills/create', {
+ await OMTAEAPI.post('/api/skills/create', {
name: skill.name,
description: skill.description,
runtime: 'prompt_only',
prompt_context: skill.prompt_context || skill.description
});
- OpenFangToast.success('Skill "' + skill.name + '" created');
+ OMTAEToast.success('Skill "' + skill.name + '" created');
this.tab = 'installed';
await this.loadSkills();
} catch(e) {
- OpenFangToast.error('Failed to create skill: ' + e.message);
+ OMTAEToast.error('Failed to create skill: ' + e.message);
}
},
@@ -470,7 +470,7 @@ function skillsPage() {
async loadMcpServers() {
this.mcpLoading = true;
try {
- var data = await OpenFangAPI.get('/api/mcp/servers');
+ var data = await OMTAEAPI.get('/api/mcp/servers');
this.mcpServers = data;
} catch(e) {
this.mcpServers = { configured: [], connected: [], total_configured: 0, total_connected: 0 };
diff --git a/crates/openfang-api/static/js/pages/usage.js b/crates/openfang-api/static/js/pages/usage.js
index c2ea5b6ca5..98d849e121 100644
--- a/crates/openfang-api/static/js/pages/usage.js
+++ b/crates/openfang-api/static/js/pages/usage.js
@@ -1,4 +1,4 @@
-// OpenFang Analytics Page — Full usage analytics with per-model and per-agent breakdowns
+// OMTAE Analytics Page — Full usage analytics with per-model and per-agent breakdowns
// Includes Cost Dashboard with donut chart, bar chart, projections, and provider breakdown.
'use strict';
@@ -18,7 +18,7 @@ function analyticsPage() {
// Chart colors for providers (stable palette)
_chartColors: [
- '#FF5C00', '#3B82F6', '#10B981', '#F59E0B', '#8B5CF6',
+ '#00F0FF', '#3B82F6', '#10B981', '#F59E0B', '#8B5CF6',
'#EC4899', '#06B6D4', '#EF4444', '#84CC16', '#F97316',
'#6366F1', '#14B8A6', '#E11D48', '#A855F7', '#22D3EE'
],
@@ -43,7 +43,7 @@ function analyticsPage() {
async loadSummary() {
try {
- this.summary = await OpenFangAPI.get('/api/usage/summary');
+ this.summary = await OMTAEAPI.get('/api/usage/summary');
} catch(e) {
this.summary = { total_input_tokens: 0, total_output_tokens: 0, total_cost_usd: 0, call_count: 0, total_tool_calls: 0 };
throw e;
@@ -52,21 +52,21 @@ function analyticsPage() {
async loadByModel() {
try {
- var data = await OpenFangAPI.get('/api/usage/by-model');
+ var data = await OMTAEAPI.get('/api/usage/by-model');
this.byModel = data.models || [];
} catch(e) { this.byModel = []; }
},
async loadByAgent() {
try {
- var data = await OpenFangAPI.get('/api/usage');
+ var data = await OMTAEAPI.get('/api/usage');
this.byAgent = data.agents || [];
} catch(e) { this.byAgent = []; }
},
async loadDailyCosts() {
try {
- var data = await OpenFangAPI.get('/api/usage/daily');
+ var data = await OMTAEAPI.get('/api/usage/daily');
this.dailyCosts = data.days || [];
this.todayCost = data.today_cost_usd || 0;
this.firstEventDate = data.first_event_date || null;
diff --git a/crates/openfang-api/static/js/pages/wizard.js b/crates/openfang-api/static/js/pages/wizard.js
index a0a77c90fa..dd7ad1f0ab 100644
--- a/crates/openfang-api/static/js/pages/wizard.js
+++ b/crates/openfang-api/static/js/pages/wizard.js
@@ -1,4 +1,4 @@
-// OpenFang Setup Wizard — First-run guided setup (Provider + Agent + Channel)
+// OMTAE Setup Wizard — First-run guided setup (Provider + Agent + Channel)
'use strict';
/** Escape a string for use inside TOML triple-quoted strings ("""\n...\n"""). */
@@ -11,6 +11,18 @@ function wizardTomlBasicEscape(s) {
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t');
}
+/** i18n lookup with English fallback (works before /i18n/*.json loads). */
+function wizardTr(key, fallback) {
+ if (window.i18n && typeof window.i18n.tr === 'function') {
+ return window.i18n.tr(key, fallback);
+ }
+ if (window.i18n && typeof window.i18n.t === 'function') {
+ var v = window.i18n.t(key);
+ return v !== key ? v : fallback;
+ }
+ return fallback;
+}
+
function wizardPage() {
return {
step: 1,
@@ -177,32 +189,31 @@ function wizardPage() {
tryItInput: '',
tryItSending: false,
get suggestedMessages() {
- var t = window.i18n ? window.i18n.t.bind(window.i18n) : function(k) { return k; };
return {
'General': [
- t('wizard.suggestions.general.1') || 'What can you help me with?',
- t('wizard.suggestions.general.2') || 'Tell me a fun fact',
- t('wizard.suggestions.general.3') || 'Summarize the latest AI news'
+ wizardTr('wizard.suggestions.general.1', 'What can you help me with?'),
+ wizardTr('wizard.suggestions.general.2', 'Tell me a fun fact'),
+ wizardTr('wizard.suggestions.general.3', 'Summarize the latest AI news')
],
'Development': [
- t('wizard.suggestions.development.1') || 'Write a Python hello world',
- t('wizard.suggestions.development.2') || 'Explain async/await',
- t('wizard.suggestions.development.3') || 'Review this code snippet'
+ wizardTr('wizard.suggestions.development.1', 'Write a Python hello world'),
+ wizardTr('wizard.suggestions.development.2', 'Explain async/await'),
+ wizardTr('wizard.suggestions.development.3', 'Review this code snippet')
],
'Research': [
- t('wizard.suggestions.research.1') || 'Explain quantum computing simply',
- t('wizard.suggestions.research.2') || 'Compare React vs Vue',
- t('wizard.suggestions.research.3') || 'What are the latest trends in AI?'
+ wizardTr('wizard.suggestions.research.1', 'Explain quantum computing simply'),
+ wizardTr('wizard.suggestions.research.2', 'Compare React vs Vue'),
+ wizardTr('wizard.suggestions.research.3', 'What are the latest trends in AI?')
],
'Writing': [
- t('wizard.suggestions.writing.1') || 'Help me write a professional email',
- t('wizard.suggestions.writing.2') || 'Improve this paragraph',
- t('wizard.suggestions.writing.3') || 'Write a blog intro about AI'
+ wizardTr('wizard.suggestions.writing.1', 'Help me write a professional email'),
+ wizardTr('wizard.suggestions.writing.2', 'Improve this paragraph'),
+ wizardTr('wizard.suggestions.writing.3', 'Write a blog intro about AI')
],
'Business': [
- t('wizard.suggestions.business.1') || 'Draft a meeting agenda',
- t('wizard.suggestions.business.2') || 'How do I handle a complaint?',
- t('wizard.suggestions.business.3') || 'Create a project status update'
+ wizardTr('wizard.suggestions.business.1', 'Draft a meeting agenda'),
+ wizardTr('wizard.suggestions.business.2', 'How do I handle a complaint?'),
+ wizardTr('wizard.suggestions.business.3', 'Create a project status update')
]
};
},
@@ -218,7 +229,7 @@ function wizardPage() {
this.tryItMessages.push({ role: 'user', text: text });
this.tryItSending = true;
try {
- var res = await OpenFangAPI.post('/api/agents/' + this.createdAgent.id + '/message', { message: text });
+ var res = await OMTAEAPI.post('/api/agents/' + this.createdAgent.id + '/message', { message: text });
this.tryItMessages.push({ role: 'agent', text: res.response || '(no response)' });
localStorage.setItem('of-first-msg', 'true');
} catch(e) {
@@ -284,9 +295,14 @@ function wizardPage() {
await this.loadProviders();
// Pre-select first unconfigured provider, or first one
var unconfigured = this.providers.filter(function(p) {
- return p.auth_status !== 'configured' && p.api_key_env;
+ return p.auth_status !== 'configured' && p.api_key_env && p.key_required !== false;
});
- if (unconfigured.length > 0) {
+ var vllm = this.providers.find(function(p) { return p.id === 'vllm'; });
+ if (vllm && (vllm.auth_status === 'configured' || vllm.key_required === false)) {
+ this.selectedProvider = 'vllm';
+ this.keySaved = true;
+ this.setupSummary.provider = vllm.display_name;
+ } else if (unconfigured.length > 0) {
this.selectedProvider = unconfigured[0].id;
} else if (this.providers.length > 0) {
this.selectedProvider = this.providers[0].id;
@@ -338,7 +354,11 @@ function wizardPage() {
},
get canGoNext() {
- if (this.step === 2) return this.keySaved || this.hasConfiguredProvider || this.claudeCodeDetected;
+ if (this.step === 2) {
+ var p = this.selectedProviderObj;
+ if (p && (p.key_required === false || p.auth_status === 'configured')) return true;
+ return this.keySaved || this.hasConfiguredProvider || this.claudeCodeDetected;
+ }
if (this.step === 3) return this.agentName.trim().length > 0;
return true;
},
@@ -356,7 +376,7 @@ function wizardPage() {
async loadProviders() {
try {
- var data = await OpenFangAPI.get('/api/providers');
+ var data = await OMTAEAPI.get('/api/providers');
this.providers = data.providers || [];
} catch(e) { this.providers = []; }
},
@@ -416,23 +436,29 @@ function wizardPage() {
async saveKey() {
var provider = this.selectedProviderObj;
if (!provider) return;
+ if (provider.key_required === false) {
+ this.keySaved = true;
+ this.setupSummary.provider = provider.display_name;
+ await this.testKey();
+ return;
+ }
var key = this.apiKeyInput.trim();
if (!key) {
- OpenFangToast.error(window.i18n ? window.i18n.t('wizard.enter_api_key') : 'Please enter an API key');
+ OMTAEToast.error(window.i18n ? window.i18n.t('wizard.enter_api_key') : 'Please enter an API key');
return;
}
this.savingKey = true;
try {
- await OpenFangAPI.post('/api/providers/' + encodeURIComponent(provider.id) + '/key', { key: key });
+ await OMTAEAPI.post('/api/providers/' + encodeURIComponent(provider.id) + '/key', { key: key });
this.apiKeyInput = '';
this.keySaved = true;
this.setupSummary.provider = provider.display_name;
- OpenFangToast.success((window.i18n ? window.i18n.t('wizard.api_key_saved') : 'API key saved for') + ' ' + provider.display_name);
+ OMTAEToast.success((window.i18n ? window.i18n.t('wizard.api_key_saved') : 'API key saved for') + ' ' + provider.display_name);
await this.loadProviders();
// Auto-test after saving
await this.testKey();
} catch(e) {
- OpenFangToast.error((window.i18n ? window.i18n.t('wizard.failed_save_key') : 'Failed to save key:') + ' ' + e.message);
+ OMTAEToast.error((window.i18n ? window.i18n.t('wizard.failed_save_key') : 'Failed to save key:') + ' ' + e.message);
}
this.savingKey = false;
},
@@ -443,16 +469,16 @@ function wizardPage() {
this.testingProvider = true;
this.testResult = null;
try {
- var result = await OpenFangAPI.post('/api/providers/' + encodeURIComponent(provider.id) + '/test', {});
+ var result = await OMTAEAPI.post('/api/providers/' + encodeURIComponent(provider.id) + '/test', {});
this.testResult = result;
if (result.status === 'ok') {
- OpenFangToast.success(provider.display_name + ' ' + (window.i18n ? window.i18n.t('wizard.connected') : 'connected') + ' (' + (result.latency_ms || '?') + 'ms)');
+ OMTAEToast.success(provider.display_name + ' ' + (window.i18n ? window.i18n.t('wizard.connected') : 'connected') + ' (' + (result.latency_ms || '?') + 'ms)');
} else {
- OpenFangToast.error(provider.display_name + ': ' + (result.error || (window.i18n ? window.i18n.t('wizard.connection_failed') : 'Connection failed')));
+ OMTAEToast.error(provider.display_name + ': ' + (result.error || (window.i18n ? window.i18n.t('wizard.connection_failed') : 'Connection failed')));
}
} catch(e) {
this.testResult = { status: 'error', error: e.message };
- OpenFangToast.error((window.i18n ? window.i18n.t('wizard.test_failed') : 'Test failed:') + ' ' + e.message);
+ OMTAEToast.error((window.i18n ? window.i18n.t('wizard.test_failed') : 'Test failed:') + ' ' + e.message);
}
this.testingProvider = false;
},
@@ -461,20 +487,20 @@ function wizardPage() {
this.testingProvider = true;
this.testResult = null;
try {
- var result = await OpenFangAPI.post('/api/providers/claude-code/test', {});
+ var result = await OMTAEAPI.post('/api/providers/claude-code/test', {});
this.testResult = result;
if (result.status === 'ok') {
this.claudeCodeDetected = true;
this.keySaved = true;
this.setupSummary.provider = 'Claude Code';
- OpenFangToast.success('Claude Code detected (' + (result.latency_ms || '?') + 'ms)');
+ OMTAEToast.success('Claude Code detected (' + (result.latency_ms || '?') + 'ms)');
} else {
this.testResult = { status: 'error', error: 'Claude Code CLI not detected' };
- OpenFangToast.error('Claude Code CLI not detected. Make sure you\'ve run: npm install -g @anthropic-ai/claude-code && claude auth');
+ OMTAEToast.error('Claude Code CLI not detected. Make sure you\'ve run: npm install -g @anthropic-ai/claude-code && claude auth');
}
} catch(e) {
this.testResult = { status: 'error', error: e.message };
- OpenFangToast.error('Claude Code CLI not detected. Make sure you\'ve run: npm install -g @anthropic-ai/claude-code && claude auth');
+ OMTAEToast.error('Claude Code CLI not detected. Make sure you\'ve run: npm install -g @anthropic-ai/claude-code && claude auth');
}
this.testingProvider = false;
},
@@ -494,7 +520,7 @@ function wizardPage() {
if (!tpl) return;
var name = this.agentName.trim();
if (!name) {
- OpenFangToast.error(window.i18n ? window.i18n.t('wizard.enter_agent_name') : 'Please enter a name for your agent');
+ OMTAEToast.error(window.i18n ? window.i18n.t('wizard.enter_agent_name') : 'Please enter a name for your agent');
return;
}
@@ -507,27 +533,29 @@ function wizardPage() {
model = this.defaultModelForProvider(provider) || tpl.model;
}
- var toml = '[agent]\n';
- toml += 'name = "' + wizardTomlBasicEscape(name) + '"\n';
+ var toml = 'name = "' + wizardTomlBasicEscape(name) + '"\n';
toml += 'description = "' + wizardTomlBasicEscape(tpl.description) + '"\n';
+ toml += 'module = "builtin:chat"\n';
toml += 'profile = "' + tpl.profile + '"\n\n';
toml += '[model]\nprovider = "' + provider + '"\n';
toml += 'model = "' + model + '"\n';
+ // Keep headroom on 8k-context local models (default 4096 max_tokens overflows).
+ toml += 'max_tokens = 2048\n';
toml += 'system_prompt = """\n' + wizardTomlMultilineEscape(tpl.system_prompt) + '\n"""\n';
this.creatingAgent = true;
try {
- var res = await OpenFangAPI.post('/api/agents', { manifest_toml: toml });
+ var res = await OMTAEAPI.post('/api/agents', { manifest_toml: toml });
if (res.agent_id) {
this.createdAgent = { id: res.agent_id, name: res.name || name };
this.setupSummary.agent = res.name || name;
- OpenFangToast.success((window.i18n ? window.i18n.t('wizard.agent_created') : 'Agent') + ' "' + (res.name || name) + '" ' + (window.i18n ? window.i18n.t('wizard.agent_created_suffix') || 'created' : 'created'));
+ OMTAEToast.success((window.i18n ? window.i18n.t('wizard.agent_created') : 'Agent') + ' "' + (res.name || name) + '" ' + (window.i18n ? window.i18n.t('wizard.agent_created_suffix') || 'created' : 'created'));
await Alpine.store('app').refreshAgents();
} else {
- OpenFangToast.error((window.i18n ? window.i18n.t('wizard.failed_create_agent') : 'Failed:') + ' ' + (res.error || 'Unknown error'));
+ OMTAEToast.error((window.i18n ? window.i18n.t('wizard.failed_create_agent') : 'Failed:') + ' ' + (res.error || 'Unknown error'));
}
} catch(e) {
- OpenFangToast.error((window.i18n ? window.i18n.t('wizard.failed_create_agent') : 'Failed to create agent:') + ' ' + e.message);
+ OMTAEToast.error((window.i18n ? window.i18n.t('wizard.failed_create_agent') : 'Failed to create agent:') + ' ' + e.message);
}
this.creatingAgent = false;
},
@@ -574,7 +602,7 @@ function wizardPage() {
if (!ch) return;
var token = this.channelToken.trim();
if (!token) {
- OpenFangToast.error((window.i18n ? window.i18n.t('wizard.enter_token') : 'Please enter the') + ' ' + ch.token_label);
+ OMTAEToast.error((window.i18n ? window.i18n.t('wizard.enter_token') : 'Please enter the') + ' ' + ch.token_label);
return;
}
this.configuringChannel = true;
@@ -582,12 +610,12 @@ function wizardPage() {
var fields = {};
fields[ch.token_env.toLowerCase()] = token;
fields.token = token;
- await OpenFangAPI.post('/api/channels/' + ch.name + '/configure', { fields: fields });
+ await OMTAEAPI.post('/api/channels/' + ch.name + '/configure', { fields: fields });
this.channelConfigured = true;
this.setupSummary.channel = ch.display_name;
- OpenFangToast.success(ch.display_name + ' ' + (window.i18n ? window.i18n.t('wizard.channel_configured') : 'configured and activated.'));
+ OMTAEToast.success(ch.display_name + ' ' + (window.i18n ? window.i18n.t('wizard.channel_configured') : 'configured and activated.'));
} catch(e) {
- OpenFangToast.error((window.i18n ? window.i18n.t('wizard.failed_configure') : 'Failed:') + ' ' + (e.message || 'Unknown error'));
+ OMTAEToast.error((window.i18n ? window.i18n.t('wizard.failed_configure') : 'Failed:') + ' ' + (e.message || 'Unknown error'));
}
this.configuringChannel = false;
},
@@ -595,7 +623,7 @@ function wizardPage() {
// ── Step 6: Finish ──
finish() {
- localStorage.setItem('openfang-onboarded', 'true');
+ localStorage.setItem('omtae-onboarded', 'true');
Alpine.store('app').showOnboarding = false;
// Navigate to agents with chat if an agent was created, otherwise overview
if (this.createdAgent) {
@@ -608,7 +636,7 @@ function wizardPage() {
},
finishAndDismiss() {
- localStorage.setItem('openfang-onboarded', 'true');
+ localStorage.setItem('omtae-onboarded', 'true');
Alpine.store('app').showOnboarding = false;
window.location.hash = 'overview';
}
diff --git a/crates/openfang-api/static/js/pages/workflow-builder.js b/crates/openfang-api/static/js/pages/workflow-builder.js
index 45f89cd35c..797e478236 100644
--- a/crates/openfang-api/static/js/pages/workflow-builder.js
+++ b/crates/openfang-api/static/js/pages/workflow-builder.js
@@ -1,4 +1,4 @@
-// OpenFang Visual Workflow Builder — Drag-and-drop workflow designer
+// OMTAE Visual Workflow Builder — Drag-and-drop workflow designer
'use strict';
function workflowBuilder() {
@@ -48,7 +48,7 @@ function workflowBuilder() {
var self = this;
// Load agents for the agent step dropdown
try {
- var list = await OpenFangAPI.get('/api/agents');
+ var list = await OMTAEAPI.get('/api/agents');
self.agents = Array.isArray(list) ? list : [];
} catch(_) {
self.agents = [];
@@ -558,15 +558,15 @@ function workflowBuilder() {
steps.push(step);
}
try {
- await OpenFangAPI.post('/api/workflows', {
+ await OMTAEAPI.post('/api/workflows', {
name: this.workflowName || 'untitled',
description: this.workflowDescription || '',
steps: steps
});
- OpenFangToast.success('Workflow saved!');
+ OMTAEToast.success('Workflow saved!');
this.showSaveModal = false;
} catch(e) {
- OpenFangToast.error('Failed to save: ' + e.message);
+ OMTAEToast.error('Failed to save: ' + e.message);
}
},
diff --git a/crates/openfang-api/static/js/pages/workflows.js b/crates/openfang-api/static/js/pages/workflows.js
index 79610ba3c9..1acd74caf3 100644
--- a/crates/openfang-api/static/js/pages/workflows.js
+++ b/crates/openfang-api/static/js/pages/workflows.js
@@ -1,4 +1,4 @@
-// OpenFang Workflows Page — Workflow builder + run history
+// OMTAE Workflows Page — Workflow builder + run history
'use strict';
function workflowsPage() {
@@ -21,7 +21,7 @@ function workflowsPage() {
this.loading = true;
this.loadError = '';
try {
- this.workflows = await OpenFangAPI.get('/api/workflows');
+ this.workflows = await OMTAEAPI.get('/api/workflows');
} catch(e) {
this.workflows = [];
this.loadError = e.message || 'Could not load workflows.';
@@ -37,13 +37,13 @@ function workflowsPage() {
});
try {
var wfName = this.newWf.name;
- await OpenFangAPI.post('/api/workflows', { name: wfName, description: this.newWf.description, steps: steps });
+ await OMTAEAPI.post('/api/workflows', { name: wfName, description: this.newWf.description, steps: steps });
this.showCreateModal = false;
this.newWf = { name: '', description: '', steps: [{ name: '', agent_name: '', mode: 'sequential', prompt: '{{input}}' }] };
- OpenFangToast.success('Workflow "' + wfName + '" created');
+ OMTAEToast.success('Workflow "' + wfName + '" created');
await this.loadWorkflows();
} catch(e) {
- OpenFangToast.error('Failed to create workflow: ' + e.message);
+ OMTAEToast.error('Failed to create workflow: ' + e.message);
}
},
@@ -58,40 +58,40 @@ function workflowsPage() {
this.running = true;
this.runResult = '';
try {
- var res = await OpenFangAPI.post('/api/workflows/' + this.runModal.id + '/run', { input: this.runInput });
+ var res = await OMTAEAPI.post('/api/workflows/' + this.runModal.id + '/run', { input: this.runInput });
this.runResult = res.output || JSON.stringify(res, null, 2);
- OpenFangToast.success('Workflow completed');
+ OMTAEToast.success('Workflow completed');
} catch(e) {
this.runResult = 'Error: ' + e.message;
- OpenFangToast.error('Workflow failed: ' + e.message);
+ OMTAEToast.error('Workflow failed: ' + e.message);
}
this.running = false;
},
async viewRuns(wf) {
try {
- var runs = await OpenFangAPI.get('/api/workflows/' + wf.id + '/runs');
+ var runs = await OMTAEAPI.get('/api/workflows/' + wf.id + '/runs');
this.runResult = JSON.stringify(runs, null, 2);
this.runModal = wf;
} catch(e) {
- OpenFangToast.error('Failed to load run history: ' + e.message);
+ OMTAEToast.error('Failed to load run history: ' + e.message);
}
},
async deleteWorkflow(wf) {
if (!confirm('Delete workflow "' + wf.name + '"? This cannot be undone.')) return;
try {
- await OpenFangAPI.delete('/api/workflows/' + wf.id);
- OpenFangToast.success('Workflow "' + wf.name + '" deleted');
+ await OMTAEAPI.delete('/api/workflows/' + wf.id);
+ OMTAEToast.success('Workflow "' + wf.name + '" deleted');
await this.loadWorkflows();
} catch(e) {
- OpenFangToast.error('Failed to delete workflow: ' + e.message);
+ OMTAEToast.error('Failed to delete workflow: ' + e.message);
}
},
async showEditModal(wf) {
try {
- var full = await OpenFangAPI.get('/api/workflows/' + wf.id);
+ var full = await OMTAEAPI.get('/api/workflows/' + wf.id);
this.editWf = {
name: full.name || '',
description: full.description || '',
@@ -109,7 +109,7 @@ function workflowsPage() {
}
this.editModal = wf;
} catch(e) {
- OpenFangToast.error('Failed to load workflow: ' + e.message);
+ OMTAEToast.error('Failed to load workflow: ' + e.message);
}
},
@@ -120,12 +120,12 @@ function workflowsPage() {
});
try {
var wfName = this.editWf.name;
- await OpenFangAPI.put('/api/workflows/' + this.editModal.id, { name: wfName, description: this.editWf.description, steps: steps });
+ await OMTAEAPI.put('/api/workflows/' + this.editModal.id, { name: wfName, description: this.editWf.description, steps: steps });
this.editModal = null;
- OpenFangToast.success('Workflow "' + wfName + '" updated');
+ OMTAEToast.success('Workflow "' + wfName + '" updated');
await this.loadWorkflows();
} catch(e) {
- OpenFangToast.error('Failed to update workflow: ' + e.message);
+ OMTAEToast.error('Failed to update workflow: ' + e.message);
}
}
};
diff --git a/crates/openfang-api/static/logo.png b/crates/openfang-api/static/logo.png
index cfe72c0b5c..281c32b625 100644
Binary files a/crates/openfang-api/static/logo.png and b/crates/openfang-api/static/logo.png differ
diff --git a/crates/openfang-api/static/manifest.json b/crates/openfang-api/static/manifest.json
index fa230c071a..d94be03082 100644
--- a/crates/openfang-api/static/manifest.json
+++ b/crates/openfang-api/static/manifest.json
@@ -1,12 +1,12 @@
{
- "name": "OpenFang Agent OS",
- "short_name": "OpenFang",
+ "name": "OMTAE Agent OS",
+ "short_name": "OMTAE",
"description": "Open-source Agent Operating System",
"start_url": "/",
"display": "standalone",
- "background_color": "#0a0a0f",
- "theme_color": "#6366f1",
+ "background_color": "#171816",
+ "theme_color": "#5F6556",
"icons": [
- {"src": "/logo.png", "sizes": "128x128", "type": "image/png"}
+ {"src": "/logo.png", "sizes": "64x64", "type": "image/png"}
]
}
diff --git a/crates/openfang-api/tests/api_integration_test.rs b/crates/openfang-api/tests/api_integration_test.rs
index d31a2ef651..20464c6e40 100644
--- a/crates/openfang-api/tests/api_integration_test.rs
+++ b/crates/openfang-api/tests/api_integration_test.rs
@@ -1,18 +1,18 @@
-//! Real HTTP integration tests for the OpenFang API.
+//! Real HTTP integration tests for the OMTAE API.
//!
//! These tests boot a real kernel, start a real axum HTTP server on a random
//! port, and hit actual endpoints with reqwest. No mocking.
//!
//! Tests that require an LLM API call are gated behind GROQ_API_KEY.
//!
-//! Run: cargo test -p openfang-api --test api_integration_test -- --nocapture
+//! Run: cargo test -p omtae-api --test api_integration_test -- --nocapture
use axum::Router;
-use openfang_api::middleware;
-use openfang_api::routes::{self, AppState};
-use openfang_api::ws;
-use openfang_kernel::OpenFangKernel;
-use openfang_types::config::{DefaultModelConfig, KernelConfig};
+use omtae_api::middleware;
+use omtae_api::routes::{self, AppState};
+use omtae_api::ws;
+use omtae_kernel::OMTAEKernel;
+use omtae_types::config::{DefaultModelConfig, KernelConfig};
use std::sync::Arc;
use std::time::Instant;
use tower_http::cors::CorsLayer;
@@ -66,7 +66,7 @@ async fn start_test_server_with_provider(
..KernelConfig::default()
};
- let kernel = OpenFangKernel::boot_with_config(config).expect("Kernel should boot");
+ let kernel = OMTAEKernel::boot_with_config(config).expect("Kernel should boot");
let kernel = Arc::new(kernel);
kernel.set_self_handle();
@@ -78,7 +78,7 @@ async fn start_test_server_with_provider(
channels_config: tokio::sync::RwLock::new(Default::default()),
shutdown_notify: Arc::new(tokio::sync::Notify::new()),
clawhub_cache: dashmap::DashMap::new(),
- provider_probe_cache: openfang_runtime::provider_health::ProbeCache::new(),
+ provider_probe_cache: omtae_runtime::provider_health::ProbeCache::new(),
budget_config: Arc::new(tokio::sync::RwLock::new(Default::default())),
});
@@ -324,7 +324,7 @@ async fn test_list_agents_includes_inferencing_flag() {
assert_eq!(resp.status(), 201);
let body: serde_json::Value = resp.json().await.unwrap();
let agent_id_str = body["agent_id"].as_str().unwrap().to_string();
- let agent_id: openfang_types::agent::AgentId = agent_id_str.parse().unwrap();
+ let agent_id: omtae_types::agent::AgentId = agent_id_str.parse().unwrap();
// Baseline: idle agent must report is_inferencing = false.
let resp = client
@@ -429,7 +429,7 @@ async fn test_agent_session_empty() {
/// mode opt-in).
#[tokio::test]
async fn test_agent_session_filters_system_messages() {
- use openfang_types::message::{Message, Role};
+ use omtae_types::message::{Message, Role};
let server = start_test_server().await;
let client = reqwest::Client::new();
@@ -447,7 +447,7 @@ async fn test_agent_session_filters_system_messages() {
// Look up the agent's session id and inject a forged history that
// contains a system-role message (simulating what an OpenAI-compat
// client could push, or what a future regression might persist).
- let agent_id: openfang_types::agent::AgentId = agent_id_str.parse().unwrap();
+ let agent_id: omtae_types::agent::AgentId = agent_id_str.parse().unwrap();
let entry = server.state.kernel.registry.get(agent_id).unwrap();
let session_id = entry.session_id;
let mut session = server
@@ -461,7 +461,7 @@ async fn test_agent_session_filters_system_messages() {
session.messages = vec![
Message {
role: Role::System,
- content: openfang_types::message::MessageContent::Text(
+ content: omtae_types::message::MessageContent::Text(
"INTERNAL SYSTEM PROMPT — must not leak to UI".to_string(),
),
..Default::default()
@@ -911,7 +911,7 @@ async fn start_test_server_with_auth(api_key: &str) -> TestServer {
..KernelConfig::default()
};
- let kernel = OpenFangKernel::boot_with_config(config).expect("Kernel should boot");
+ let kernel = OMTAEKernel::boot_with_config(config).expect("Kernel should boot");
let kernel = Arc::new(kernel);
kernel.set_self_handle();
@@ -923,18 +923,18 @@ async fn start_test_server_with_auth(api_key: &str) -> TestServer {
channels_config: tokio::sync::RwLock::new(Default::default()),
shutdown_notify: Arc::new(tokio::sync::Notify::new()),
clawhub_cache: dashmap::DashMap::new(),
- provider_probe_cache: openfang_runtime::provider_health::ProbeCache::new(),
+ provider_probe_cache: omtae_runtime::provider_health::ProbeCache::new(),
budget_config: Arc::new(tokio::sync::RwLock::new(Default::default())),
});
- let api_key = state.kernel.config.api_key.trim().to_string();
+ let api_key = state.kernel.config.effective_api_key_for_auth();
let auth_state = middleware::AuthState {
api_key: api_key.clone(),
- auth_enabled: state.kernel.config.auth.enabled,
- session_secret: if !api_key.is_empty() {
- api_key.clone()
- } else if state.kernel.config.auth.enabled {
- state.kernel.config.auth.password_hash.clone()
+ raw_api_key: state.kernel.config.api_key.trim().to_string(),
+ auth_enabled: state.kernel.config.dashboard_auth_enabled(),
+ session_secret: state.kernel.config.dashboard_session_secret(),
+ dashboard_pin: if state.kernel.config.dashboard.pin_auth_active() {
+ state.kernel.config.dashboard.pin.trim().to_string()
} else {
String::new()
},
@@ -1286,7 +1286,7 @@ async fn test_schedules_delivery_targets_roundtrip() {
let delivery_targets = serde_json::json!([
{ "type": "channel", "channel_type": "telegram", "recipient": "chat_12345" },
{ "type": "webhook", "url": "https://example.com/hook", "auth_header": "Bearer abc" },
- { "type": "local_file", "path": "/tmp/openfang-test.log", "append": true },
+ { "type": "local_file", "path": "/tmp/omtae-test.log", "append": true },
{ "type": "email", "to": "alice@example.com", "subject_template": "Cron: {job}" },
]);
diff --git a/crates/openfang-api/tests/daemon_lifecycle_test.rs b/crates/openfang-api/tests/daemon_lifecycle_test.rs
index c62cba9b7a..f8d0eb36f8 100644
--- a/crates/openfang-api/tests/daemon_lifecycle_test.rs
+++ b/crates/openfang-api/tests/daemon_lifecycle_test.rs
@@ -4,11 +4,11 @@
//! and graceful shutdown sequence.
use axum::Router;
-use openfang_api::middleware;
-use openfang_api::routes::{self, AppState};
-use openfang_api::server::{read_daemon_info, DaemonInfo};
-use openfang_kernel::OpenFangKernel;
-use openfang_types::config::{DefaultModelConfig, KernelConfig};
+use omtae_api::middleware;
+use omtae_api::routes::{self, AppState};
+use omtae_api::server::{read_daemon_info, DaemonInfo};
+use omtae_kernel::OMTAEKernel;
+use omtae_types::config::{DefaultModelConfig, KernelConfig};
use std::sync::Arc;
use std::time::Instant;
use tower_http::cors::CorsLayer;
@@ -103,7 +103,7 @@ async fn test_full_daemon_lifecycle() {
..KernelConfig::default()
};
- let kernel = OpenFangKernel::boot_with_config(config).expect("Kernel should boot");
+ let kernel = OMTAEKernel::boot_with_config(config).expect("Kernel should boot");
let kernel = Arc::new(kernel);
kernel.set_self_handle();
@@ -115,7 +115,7 @@ async fn test_full_daemon_lifecycle() {
channels_config: tokio::sync::RwLock::new(Default::default()),
shutdown_notify: Arc::new(tokio::sync::Notify::new()),
clawhub_cache: dashmap::DashMap::new(),
- provider_probe_cache: openfang_runtime::provider_health::ProbeCache::new(),
+ provider_probe_cache: omtae_runtime::provider_health::ProbeCache::new(),
budget_config: Arc::new(tokio::sync::RwLock::new(Default::default())),
});
@@ -231,7 +231,7 @@ async fn test_server_immediate_responsiveness() {
..KernelConfig::default()
};
- let kernel = OpenFangKernel::boot_with_config(config).unwrap();
+ let kernel = OMTAEKernel::boot_with_config(config).unwrap();
let kernel = Arc::new(kernel);
let state = Arc::new(AppState {
@@ -242,7 +242,7 @@ async fn test_server_immediate_responsiveness() {
channels_config: tokio::sync::RwLock::new(Default::default()),
shutdown_notify: Arc::new(tokio::sync::Notify::new()),
clawhub_cache: dashmap::DashMap::new(),
- provider_probe_cache: openfang_runtime::provider_health::ProbeCache::new(),
+ provider_probe_cache: omtae_runtime::provider_health::ProbeCache::new(),
budget_config: Arc::new(tokio::sync::RwLock::new(Default::default())),
});
diff --git a/crates/openfang-api/tests/load_test.rs b/crates/openfang-api/tests/load_test.rs
index c0bfc9ae59..8d9fd6fd79 100644
--- a/crates/openfang-api/tests/load_test.rs
+++ b/crates/openfang-api/tests/load_test.rs
@@ -1,15 +1,15 @@
-//! Load & performance tests for the OpenFang API.
+//! Load & performance tests for the OMTAE API.
//!
//! Measures throughput under concurrent access: agent spawning, API endpoint
//! latency, session management, and memory usage.
//!
-//! Run: cargo test -p openfang-api --test load_test -- --nocapture
+//! Run: cargo test -p omtae-api --test load_test -- --nocapture
use axum::Router;
-use openfang_api::middleware;
-use openfang_api::routes::{self, AppState};
-use openfang_kernel::OpenFangKernel;
-use openfang_types::config::{DefaultModelConfig, KernelConfig};
+use omtae_api::middleware;
+use omtae_api::routes::{self, AppState};
+use omtae_kernel::OMTAEKernel;
+use omtae_types::config::{DefaultModelConfig, KernelConfig};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tower_http::cors::CorsLayer;
@@ -47,7 +47,7 @@ async fn start_test_server() -> TestServer {
..KernelConfig::default()
};
- let kernel = OpenFangKernel::boot_with_config(config).expect("Kernel should boot");
+ let kernel = OMTAEKernel::boot_with_config(config).expect("Kernel should boot");
let kernel = Arc::new(kernel);
kernel.set_self_handle();
@@ -59,7 +59,7 @@ async fn start_test_server() -> TestServer {
channels_config: tokio::sync::RwLock::new(Default::default()),
shutdown_notify: Arc::new(tokio::sync::Notify::new()),
clawhub_cache: dashmap::DashMap::new(),
- provider_probe_cache: openfang_runtime::provider_health::ProbeCache::new(),
+ provider_probe_cache: omtae_runtime::provider_health::ProbeCache::new(),
budget_config: Arc::new(tokio::sync::RwLock::new(Default::default())),
});
@@ -575,7 +575,7 @@ async fn load_metrics_sustained() {
.unwrap();
assert_eq!(res.status().as_u16(), 200);
let body = res.text().await.unwrap();
- assert!(body.contains("openfang_agents_active"));
+ assert!(body.contains("omtae_agents_active"));
}
let elapsed = start.elapsed();
diff --git a/crates/openfang-api/tests/skill_config_api_test.rs b/crates/openfang-api/tests/skill_config_api_test.rs
index e8a1e60a72..06c5a48077 100644
--- a/crates/openfang-api/tests/skill_config_api_test.rs
+++ b/crates/openfang-api/tests/skill_config_api_test.rs
@@ -6,13 +6,13 @@
//! no runtime config, so the synthetic skill fixture is what lets us prove
//! the wire contract.
//!
-//! Run: cargo test -p openfang-api --test skill_config_api_test -- --nocapture
+//! Run: cargo test -p omtae-api --test skill_config_api_test -- --nocapture
use axum::Router;
-use openfang_api::middleware;
-use openfang_api::routes::{self, AppState};
-use openfang_kernel::OpenFangKernel;
-use openfang_types::config::{DefaultModelConfig, KernelConfig};
+use omtae_api::middleware;
+use omtae_api::routes::{self, AppState};
+use omtae_kernel::OMTAEKernel;
+use omtae_types::config::{DefaultModelConfig, KernelConfig};
use std::sync::Arc;
use std::time::Instant;
use tower_http::cors::CorsLayer;
@@ -84,7 +84,7 @@ async fn start_test_server() -> TestServer {
// Plant synthetic skill BEFORE booting so the initial skill load picks it up.
plant_skill_with_config(&home, "test-config-skill");
- let kernel = OpenFangKernel::boot_with_config(config).expect("kernel boot");
+ let kernel = OMTAEKernel::boot_with_config(config).expect("kernel boot");
let kernel = Arc::new(kernel);
kernel.set_self_handle();
@@ -96,7 +96,7 @@ async fn start_test_server() -> TestServer {
channels_config: tokio::sync::RwLock::new(Default::default()),
shutdown_notify: Arc::new(tokio::sync::Notify::new()),
clawhub_cache: dashmap::DashMap::new(),
- provider_probe_cache: openfang_runtime::provider_health::ProbeCache::new(),
+ provider_probe_cache: omtae_runtime::provider_health::ProbeCache::new(),
budget_config: Arc::new(tokio::sync::RwLock::new(Default::default())),
});
diff --git a/crates/openfang-channels/Cargo.toml b/crates/openfang-channels/Cargo.toml
index e0ef71d551..69488bf271 100644
--- a/crates/openfang-channels/Cargo.toml
+++ b/crates/openfang-channels/Cargo.toml
@@ -1,12 +1,12 @@
[package]
-name = "openfang-channels"
+name = "omtae-channels"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "Channel Bridge Layer — pluggable messaging integrations for OpenFang"
+description = "Channel Bridge Layer — pluggable messaging integrations for OMTAE"
[dependencies]
-openfang-types = { path = "../openfang-types" }
+omtae-types = { path = "../omtae-types" }
tokio = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
diff --git a/crates/openfang-channels/src/bluesky.rs b/crates/openfang-channels/src/bluesky.rs
index 4943c6adb6..78c4938635 100644
--- a/crates/openfang-channels/src/bluesky.rs
+++ b/crates/openfang-channels/src/bluesky.rs
@@ -324,7 +324,7 @@ fn parse_bluesky_notification(
sender: ChannelUser {
platform_id: author_did.to_string(),
display_name,
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
diff --git a/crates/openfang-channels/src/bridge.rs b/crates/openfang-channels/src/bridge.rs
index 2043aeaa76..da81566b72 100644
--- a/crates/openfang-channels/src/bridge.rs
+++ b/crates/openfang-channels/src/bridge.rs
@@ -1,6 +1,6 @@
-//! Channel bridge — connects channel adapters to the OpenFang kernel.
+//! Channel bridge — connects channel adapters to the OMTAE kernel.
//!
-//! Defines `ChannelBridgeHandle` (implemented by openfang-api on the kernel) and
+//! Defines `ChannelBridgeHandle` (implemented by omtae-api on the kernel) and
//! `BridgeManager` which owns running adapters and dispatches messages.
use crate::formatter;
@@ -12,11 +12,11 @@ use crate::types::{
use async_trait::async_trait;
use dashmap::DashMap;
use futures::StreamExt;
-use openfang_types::agent::AgentId;
-use openfang_types::approval::ApprovalRequest;
-use openfang_types::commands::{self as slash_commands, Surfaces};
-use openfang_types::config::{ChannelOverrides, DmPolicy, GroupPolicy, OutputFormat, PrefixStyle};
-use openfang_types::message::ContentBlock;
+use omtae_types::agent::AgentId;
+use omtae_types::approval::ApprovalRequest;
+use omtae_types::commands::{self as slash_commands, Surfaces};
+use omtae_types::config::{ChannelOverrides, DmPolicy, GroupPolicy, OutputFormat, PrefixStyle};
+use omtae_types::message::ContentBlock;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::watch;
@@ -70,7 +70,7 @@ fn is_channel_command(name: &str) -> bool {
fn format_channel_help() -> String {
let sections = ["General", "Session", "Info", "Automation", "Monitoring"];
- let mut msg = String::from("OpenFang Bot Commands:");
+ let mut msg = String::from("OMTAE Bot Commands:");
for section in sections {
let commands: Vec<&ChatCommandSpec> = channel_command_specs()
@@ -95,8 +95,8 @@ fn format_channel_help() -> String {
/// Kernel operations needed by channel adapters.
///
-/// Defined here to avoid circular deps (openfang-channels can't depend on openfang-kernel).
-/// Implemented in openfang-api on the actual kernel.
+/// Defined here to avoid circular deps (omtae-channels can't depend on omtae-kernel).
+/// Implemented in omtae-api on the actual kernel.
#[async_trait]
pub trait ChannelBridgeHandle: Send + Sync {
/// Send a message to an agent and get the text response.
@@ -685,14 +685,14 @@ fn sender_user_id(message: &ChannelMessage) -> &str {
/// can route to dedicated agents. The channel ID source is delegated to
/// [`ChannelMessage::channel_id`] — the single source of truth shared with
/// config validation (see `CHANNELS_WITH_PLATFORM_ID_AS_CHANNEL` in
-/// `openfang-types::config`). `peer_id` uses the resolved user ID, not
+/// `omtae-types::config`). `peer_id` uses the resolved user ID, not
/// `sender.platform_id`, so user-scoped bindings still match correctly on
/// Discord/Slack/etc. where `platform_id` holds the channel.
///
/// This replaces the earlier heuristic `sender_channel_id()` (which inferred
/// "platform_id is the channel" from "metadata has `sender_user_id`"). The
/// allowlist is explicit, the metadata-fallback path is documented, and
-/// adapters can be added or removed in one place (`openfang-types::config`)
+/// adapters can be added or removed in one place (`omtae-types::config`)
/// without touching this file.
fn binding_context_for(message: &ChannelMessage) -> BindingContext {
BindingContext {
@@ -1142,7 +1142,7 @@ async fn dispatch_message(
let mut responses = Vec::new();
match strategy {
- openfang_types::config::BroadcastStrategy::Parallel => {
+ omtae_types::config::BroadcastStrategy::Parallel => {
let mut handles_vec = Vec::new();
for (name, maybe_id) in &targets {
if let Some(aid) = maybe_id {
@@ -1165,7 +1165,7 @@ async fn dispatch_message(
}
}
}
- openfang_types::config::BroadcastStrategy::Sequential => {
+ omtae_types::config::BroadcastStrategy::Sequential => {
for (name, maybe_id) in &targets {
if let Some(aid) = maybe_id {
match handle.send_message(*aid, &text).await {
@@ -1214,7 +1214,7 @@ async fn dispatch_message(
router.resolve_with_context(
&message.channel,
sender_user_id(message),
- message.sender.openfang_user.as_deref(),
+ message.sender.omtae_user.as_deref(),
&binding_ctx,
)
});
@@ -1610,7 +1610,7 @@ async fn download_image_to_blocks(url: &str, caption: Option<&str>) -> Vec {
let agents = handle.list_agents().await.unwrap_or_default();
- let mut msg = "Welcome to OpenFang! I connect you to AI agents.\n\nAvailable agents:\n"
+ let mut msg = "Welcome to OMTAE! I connect you to AI agents.\n\nAvailable agents:\n"
.to_string();
if agents.is_empty() {
msg.push_str(" (none running)\n");
@@ -2017,7 +2017,7 @@ async fn handle_command(
let agent_id = router.resolve(
&crate::types::ChannelType::CLI,
user_id,
- sender.openfang_user.as_deref(),
+ sender.omtae_user.as_deref(),
);
match agent_id {
Some(aid) => handle
@@ -2031,7 +2031,7 @@ async fn handle_command(
let agent_id = router.resolve(
&crate::types::ChannelType::CLI,
user_id,
- sender.openfang_user.as_deref(),
+ sender.omtae_user.as_deref(),
);
match agent_id {
Some(aid) => handle
@@ -2045,7 +2045,7 @@ async fn handle_command(
let agent_id = router.resolve(
&crate::types::ChannelType::CLI,
user_id,
- sender.openfang_user.as_deref(),
+ sender.omtae_user.as_deref(),
);
match agent_id {
Some(aid) => {
@@ -2069,7 +2069,7 @@ async fn handle_command(
let agent_id = router.resolve(
&crate::types::ChannelType::CLI,
user_id,
- sender.openfang_user.as_deref(),
+ sender.omtae_user.as_deref(),
);
match agent_id {
Some(aid) => handle
@@ -2083,7 +2083,7 @@ async fn handle_command(
let agent_id = router.resolve(
&crate::types::ChannelType::CLI,
user_id,
- sender.openfang_user.as_deref(),
+ sender.omtae_user.as_deref(),
);
match agent_id {
Some(aid) => handle
@@ -2097,7 +2097,7 @@ async fn handle_command(
let agent_id = router.resolve(
&crate::types::ChannelType::CLI,
user_id,
- sender.openfang_user.as_deref(),
+ sender.omtae_user.as_deref(),
);
match agent_id {
Some(aid) => {
@@ -2262,7 +2262,7 @@ mod tests {
let sender = ChannelUser {
platform_id: "user1".to_string(),
display_name: "Test".to_string(),
- openfang_user: None,
+ omtae_user: None,
};
let result = handle_command("agents", &[], &handle, &router, &sender, "user1").await;
@@ -2282,7 +2282,7 @@ mod tests {
let sender = ChannelUser {
platform_id: "user1".to_string(),
display_name: "Test".to_string(),
- openfang_user: None,
+ omtae_user: None,
};
// Select existing agent
@@ -2317,7 +2317,7 @@ mod tests {
let sender = ChannelUser {
platform_id: "channel-123".to_string(),
display_name: "Test".to_string(),
- openfang_user: None,
+ omtae_user: None,
};
let user_id = "user-789";
@@ -2354,7 +2354,7 @@ mod tests {
let sender = ChannelUser {
platform_id: "user1".to_string(),
display_name: "Test".to_string(),
- openfang_user: None,
+ omtae_user: None,
};
let result = handle_command("agent", &[], &handle, &router, &sender, "user1").await;
@@ -2437,7 +2437,7 @@ mod tests {
sender: crate::types::ChannelUser {
platform_id: platform_id.to_string(),
display_name: "Tester".to_string(),
- openfang_user: None,
+ omtae_user: None,
},
content: ChannelContent::Text("hi".to_string()),
target_agent: None,
diff --git a/crates/openfang-channels/src/dingtalk.rs b/crates/openfang-channels/src/dingtalk.rs
index 1875927a6b..3e814ffc93 100644
--- a/crates/openfang-channels/src/dingtalk.rs
+++ b/crates/openfang-channels/src/dingtalk.rs
@@ -211,7 +211,7 @@ impl ChannelAdapter for DingTalkAdapter {
sender: ChannelUser {
platform_id: sender_id,
display_name: sender_nick,
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
diff --git a/crates/openfang-channels/src/dingtalk_stream.rs b/crates/openfang-channels/src/dingtalk_stream.rs
index dd854b4314..bbc09d3b73 100644
--- a/crates/openfang-channels/src/dingtalk_stream.rs
+++ b/crates/openfang-channels/src/dingtalk_stream.rs
@@ -368,7 +368,7 @@ async fn get_ws_endpoint(
sub_type: "CALLBACK".to_string(),
topic: "/v1.0/im/bot/messages/get".to_string(),
}],
- ua: "openfang/0.3",
+ ua: "omtae/0.3",
local_ip: "",
};
let resp: OpenConnectionResponse = http
@@ -530,7 +530,7 @@ where
sender: ChannelUser {
platform_id: uid,
display_name: cb.sender_nick,
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
diff --git a/crates/openfang-channels/src/discord.rs b/crates/openfang-channels/src/discord.rs
index 8b5ff0773c..8bc9b92338 100644
--- a/crates/openfang-channels/src/discord.rs
+++ b/crates/openfang-channels/src/discord.rs
@@ -1,4 +1,4 @@
-//! Discord Gateway adapter for the OpenFang channel bridge.
+//! Discord Gateway adapter for the OMTAE channel bridge.
//!
//! Uses Discord Gateway WebSocket (v10) for receiving messages and the REST API
//! for sending responses. No external Discord crate — just `tokio-tungstenite` + `reqwest`.
@@ -483,8 +483,8 @@ impl ChannelAdapter for DiscordAdapter {
"intents": intents,
"properties": {
"os": "linux",
- "browser": "openfang",
- "device": "openfang"
+ "browser": "omtae",
+ "device": "omtae"
}
}
})
@@ -826,8 +826,8 @@ async fn parse_discord_message(
) -> Option {
// Diagnostic: dump the raw Discord payload so we can ground attachment
// parsing in real JSON. Gated by RUST_LOG; silent at default `info` level.
- // Enable with: RUST_LOG=openfang_channels::discord=debug
- debug!(target: "openfang_channels::discord", payload = %d, "discord raw message payload");
+ // Enable with: RUST_LOG=omtae_channels::discord=debug
+ debug!(target: "omtae_channels::discord", payload = %d, "discord raw message payload");
let author = d.get("author")?;
let author_id = author["id"].as_str()?;
@@ -983,7 +983,7 @@ async fn parse_discord_message(
sender: ChannelUser {
platform_id: effective_channel_id,
display_name,
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
@@ -1067,7 +1067,7 @@ mod tests {
"content": "My own message",
"author": {
"id": "bot123",
- "username": "openfang",
+ "username": "omtae",
"discriminator": "0"
},
"timestamp": "2024-01-01T00:00:00+00:00"
@@ -1130,7 +1130,7 @@ mod tests {
"content": "My own message",
"author": {
"id": "bot123",
- "username": "openfang",
+ "username": "omtae",
"discriminator": "0",
"bot": true
},
@@ -1324,7 +1324,7 @@ mod tests {
"channel_id": "ch1",
"guild_id": "guild1",
"content": "Hey <@bot123> help me",
- "mentions": [{"id": "bot123", "username": "openfang"}],
+ "mentions": [{"id": "bot123", "username": "omtae"}],
"author": {
"id": "user1",
"username": "alice",
diff --git a/crates/openfang-channels/src/discourse.rs b/crates/openfang-channels/src/discourse.rs
index acb27f4277..ab77b2c49a 100644
--- a/crates/openfang-channels/src/discourse.rs
+++ b/crates/openfang-channels/src/discourse.rs
@@ -300,7 +300,7 @@ impl ChannelAdapter for DiscourseAdapter {
sender: ChannelUser {
platform_id: username.to_string(),
display_name,
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
diff --git a/crates/openfang-channels/src/email.rs b/crates/openfang-channels/src/email.rs
index d76b414a10..f57bd8bea5 100644
--- a/crates/openfang-channels/src/email.rs
+++ b/crates/openfang-channels/src/email.rs
@@ -405,7 +405,7 @@ impl ChannelAdapter for EmailAdapter {
sender: ChannelUser {
platform_id: from_addr.clone(),
display_name: from_addr.clone(),
- openfang_user: None,
+ omtae_user: None,
},
content: ChannelContent::Text(text),
target_agent: None, // Routing handled by bridge AgentRouter
@@ -451,7 +451,7 @@ impl ChannelAdapter for EmailAdapter {
let body = text[pos + 2..].to_string();
(subj, body)
} else {
- ("OpenFang Reply".to_string(), text)
+ ("OMTAE Reply".to_string(), text)
}
} else {
// Check reply context for subject continuity
@@ -459,7 +459,7 @@ impl ChannelAdapter for EmailAdapter {
.reply_ctx
.get(&to_addr)
.map(|ctx| format!("Re: {}", ctx.subject))
- .unwrap_or_else(|| "OpenFang Reply".to_string());
+ .unwrap_or_else(|| "OMTAE Reply".to_string());
(subj, text)
};
diff --git a/crates/openfang-channels/src/feishu.rs b/crates/openfang-channels/src/feishu.rs
index 7a19a035c9..aec6aea09c 100644
--- a/crates/openfang-channels/src/feishu.rs
+++ b/crates/openfang-channels/src/feishu.rs
@@ -643,7 +643,7 @@ impl FeishuAdapter {
sender: ChannelUser {
platform_id: chat_id,
display_name: open_id,
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
@@ -1339,7 +1339,7 @@ fn parse_event(
sender: ChannelUser {
platform_id: chat_id,
display_name: sender_id,
- openfang_user: None,
+ omtae_user: None,
},
content: msg_content,
target_agent: None,
diff --git a/crates/openfang-channels/src/flock.rs b/crates/openfang-channels/src/flock.rs
index d481575e2e..c1ffe2bb2c 100644
--- a/crates/openfang-channels/src/flock.rs
+++ b/crates/openfang-channels/src/flock.rs
@@ -219,7 +219,7 @@ fn parse_flock_event(event: &serde_json::Value, own_user_id: &str) -> Option`
//! - Plain text: strips all formatting
-use openfang_types::config::OutputFormat;
+use omtae_types::config::OutputFormat;
/// Format a message for a specific channel output format.
pub fn format_for_channel(text: &str, format: OutputFormat) -> String {
diff --git a/crates/openfang-channels/src/gitter.rs b/crates/openfang-channels/src/gitter.rs
index 4d3a5a4ed6..53dafdcf3b 100644
--- a/crates/openfang-channels/src/gitter.rs
+++ b/crates/openfang-channels/src/gitter.rs
@@ -276,7 +276,7 @@ impl ChannelAdapter for GitterAdapter {
sender: ChannelUser {
platform_id: username.clone(),
display_name,
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
diff --git a/crates/openfang-channels/src/google_chat.rs b/crates/openfang-channels/src/google_chat.rs
index b199645cc7..0ff11f3824 100644
--- a/crates/openfang-channels/src/google_chat.rs
+++ b/crates/openfang-channels/src/google_chat.rs
@@ -306,7 +306,7 @@ impl ChannelAdapter for GoogleChatAdapter {
sender: ChannelUser {
platform_id: space_name.to_string(),
display_name: sender_name.to_string(),
- openfang_user: None,
+ omtae_user: None,
},
content: msg_content,
target_agent: None,
diff --git a/crates/openfang-channels/src/gotify.rs b/crates/openfang-channels/src/gotify.rs
index c0d93b333f..d3b4701041 100644
--- a/crates/openfang-channels/src/gotify.rs
+++ b/crates/openfang-channels/src/gotify.rs
@@ -248,7 +248,7 @@ impl ChannelAdapter for GotifyAdapter {
} else {
title.clone()
},
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
@@ -314,7 +314,7 @@ impl ChannelAdapter for GotifyAdapter {
ChannelContent::Text(t) => t,
_ => "(Unsupported content type)".to_string(),
};
- self.api_send_message("OpenFang", &text, 5).await
+ self.api_send_message("OMTAE", &text, 5).await
}
async fn send_typing(&self, _user: &ChannelUser) -> Result<(), Box> {
diff --git a/crates/openfang-channels/src/guilded.rs b/crates/openfang-channels/src/guilded.rs
index f18aacf106..dba81bfb73 100644
--- a/crates/openfang-channels/src/guilded.rs
+++ b/crates/openfang-channels/src/guilded.rs
@@ -278,7 +278,7 @@ impl ChannelAdapter for GuildedAdapter {
sender: ChannelUser {
platform_id: channel_id,
display_name: created_by.to_string(),
- openfang_user: None,
+ omtae_user: None,
},
content: msg_content,
target_agent: None,
diff --git a/crates/openfang-channels/src/irc.rs b/crates/openfang-channels/src/irc.rs
index a9b87e46b3..b610d86711 100644
--- a/crates/openfang-channels/src/irc.rs
+++ b/crates/openfang-channels/src/irc.rs
@@ -1,4 +1,4 @@
-//! IRC channel adapter for the OpenFang channel bridge.
+//! IRC channel adapter for the OMTAE channel bridge.
//!
//! Uses raw TCP via `tokio::net::TcpStream` with `tokio::io` buffered I/O for
//! plaintext IRC connections. Implements the core IRC protocol: NICK, USER, JOIN,
@@ -45,7 +45,7 @@ pub struct IrcAdapter {
nick: String,
/// SECURITY: Optional server password, zeroized on drop.
password: Option>,
- /// IRC channels to join (e.g., ["#openfang", "#bots"]).
+ /// IRC channels to join (e.g., ["#omtae", "#bots"]).
channels: Vec,
/// Reserved for future TLS support. Currently only plaintext is implemented.
#[allow(dead_code)]
@@ -206,7 +206,7 @@ fn parse_privmsg(line: &IrcLine, bot_nick: &str) -> Option {
sender: ChannelUser {
platform_id,
display_name: sender_nick.to_string(),
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
@@ -275,7 +275,7 @@ impl ChannelAdapter for IrcAdapter {
registration.push_str(&format!("PASS {}\r\n", pass.as_str()));
}
registration.push_str(&format!("NICK {nick}\r\n"));
- registration.push_str(&format!("USER {nick} 0 * :OpenFang Bot\r\n"));
+ registration.push_str(&format!("USER {nick} 0 * :OMTAE Bot\r\n"));
if let Err(e) = writer.write_all(registration.as_bytes()).await {
warn!("IRC registration send failed: {e}");
@@ -391,7 +391,7 @@ impl ChannelAdapter for IrcAdapter {
_ = shutdown_rx.changed() => {
if *shutdown_rx.borrow() {
info!("IRC adapter shutting down");
- let _ = writer.write_all(b"QUIT :OpenFang shutting down\r\n").await;
+ let _ = writer.write_all(b"QUIT :OMTAE shutting down\r\n").await;
return;
}
}
@@ -459,9 +459,9 @@ mod tests {
let adapter = IrcAdapter::new(
"irc.libera.chat".to_string(),
6667,
- "openfang".to_string(),
+ "omtae".to_string(),
None,
- vec!["#openfang".to_string()],
+ vec!["#omtae".to_string()],
false,
);
assert_eq!(adapter.name(), "irc");
@@ -507,10 +507,10 @@ mod tests {
#[test]
fn test_parse_irc_line_privmsg() {
- let line = parse_irc_line(":alice!alice@host PRIVMSG #openfang :Hello everyone!").unwrap();
+ let line = parse_irc_line(":alice!alice@host PRIVMSG #omtae :Hello everyone!").unwrap();
assert_eq!(line.prefix.as_deref(), Some("alice!alice@host"));
assert_eq!(line.command, "PRIVMSG");
- assert_eq!(line.params, vec!["#openfang"]);
+ assert_eq!(line.params, vec!["#omtae"]);
assert_eq!(line.trailing.as_deref(), Some("Hello everyone!"));
}
@@ -525,9 +525,9 @@ mod tests {
#[test]
fn test_parse_irc_line_no_trailing() {
- let line = parse_irc_line(":alice!alice@host JOIN #openfang").unwrap();
+ let line = parse_irc_line(":alice!alice@host JOIN #omtae").unwrap();
assert_eq!(line.command, "JOIN");
- assert_eq!(line.params, vec!["#openfang"]);
+ assert_eq!(line.params, vec!["#omtae"]);
assert!(line.trailing.is_none());
}
@@ -552,14 +552,14 @@ mod tests {
let line = IrcLine {
prefix: Some("alice!alice@host".to_string()),
command: "PRIVMSG".to_string(),
- params: vec!["#openfang".to_string()],
+ params: vec!["#omtae".to_string()],
trailing: Some("Hello from IRC!".to_string()),
};
- let msg = parse_privmsg(&line, "openfang-bot").unwrap();
+ let msg = parse_privmsg(&line, "omtae-bot").unwrap();
assert_eq!(msg.channel, ChannelType::Custom("irc".to_string()));
assert_eq!(msg.sender.display_name, "alice");
- assert_eq!(msg.sender.platform_id, "#openfang");
+ assert_eq!(msg.sender.platform_id, "#omtae");
assert!(msg.is_group);
assert!(matches!(msg.content, ChannelContent::Text(ref t) if t == "Hello from IRC!"));
}
@@ -569,11 +569,11 @@ mod tests {
let line = IrcLine {
prefix: Some("bob!bob@host".to_string()),
command: "PRIVMSG".to_string(),
- params: vec!["openfang-bot".to_string()],
+ params: vec!["omtae-bot".to_string()],
trailing: Some("Private message".to_string()),
};
- let msg = parse_privmsg(&line, "openfang-bot").unwrap();
+ let msg = parse_privmsg(&line, "omtae-bot").unwrap();
assert!(!msg.is_group);
assert_eq!(msg.sender.platform_id, "bob"); // DM replies go to sender
}
@@ -581,13 +581,13 @@ mod tests {
#[test]
fn test_parse_privmsg_skips_self() {
let line = IrcLine {
- prefix: Some("openfang-bot!bot@host".to_string()),
+ prefix: Some("omtae-bot!bot@host".to_string()),
command: "PRIVMSG".to_string(),
- params: vec!["#openfang".to_string()],
+ params: vec!["#omtae".to_string()],
trailing: Some("My own message".to_string()),
};
- let msg = parse_privmsg(&line, "openfang-bot");
+ let msg = parse_privmsg(&line, "omtae-bot");
assert!(msg.is_none());
}
@@ -596,11 +596,11 @@ mod tests {
let line = IrcLine {
prefix: Some("alice!alice@host".to_string()),
command: "PRIVMSG".to_string(),
- params: vec!["#openfang".to_string()],
+ params: vec!["#omtae".to_string()],
trailing: Some("/agent hello-world".to_string()),
};
- let msg = parse_privmsg(&line, "openfang-bot").unwrap();
+ let msg = parse_privmsg(&line, "omtae-bot").unwrap();
match &msg.content {
ChannelContent::Command { name, args } => {
assert_eq!(name, "agent");
@@ -615,11 +615,11 @@ mod tests {
let line = IrcLine {
prefix: Some("alice!alice@host".to_string()),
command: "PRIVMSG".to_string(),
- params: vec!["#openfang".to_string()],
+ params: vec!["#omtae".to_string()],
trailing: Some("".to_string()),
};
- let msg = parse_privmsg(&line, "openfang-bot");
+ let msg = parse_privmsg(&line, "omtae-bot");
assert!(msg.is_none());
}
@@ -628,11 +628,11 @@ mod tests {
let line = IrcLine {
prefix: Some("alice!alice@host".to_string()),
command: "PRIVMSG".to_string(),
- params: vec!["#openfang".to_string()],
+ params: vec!["#omtae".to_string()],
trailing: None,
};
- let msg = parse_privmsg(&line, "openfang-bot");
+ let msg = parse_privmsg(&line, "omtae-bot");
assert!(msg.is_none());
}
@@ -641,11 +641,11 @@ mod tests {
let line = IrcLine {
prefix: Some("alice!alice@host".to_string()),
command: "NOTICE".to_string(),
- params: vec!["#openfang".to_string()],
+ params: vec!["#omtae".to_string()],
trailing: Some("Notice text".to_string()),
};
- let msg = parse_privmsg(&line, "openfang-bot");
+ let msg = parse_privmsg(&line, "omtae-bot");
assert!(msg.is_none());
}
}
diff --git a/crates/openfang-channels/src/keybase.rs b/crates/openfang-channels/src/keybase.rs
index f619368719..520693fbdb 100644
--- a/crates/openfang-channels/src/keybase.rs
+++ b/crates/openfang-channels/src/keybase.rs
@@ -370,7 +370,7 @@ impl ChannelAdapter for KeybaseAdapter {
sender: ChannelUser {
platform_id: conv_key.clone(),
display_name: sender_username.to_string(),
- openfang_user: None,
+ omtae_user: None,
},
content: msg_content,
target_agent: None,
diff --git a/crates/openfang-channels/src/lib.rs b/crates/openfang-channels/src/lib.rs
index 7b122d2a24..c59f741146 100644
--- a/crates/openfang-channels/src/lib.rs
+++ b/crates/openfang-channels/src/lib.rs
@@ -1,4 +1,4 @@
-//! Channel Bridge Layer for the OpenFang Agent OS.
+//! Channel Bridge Layer for the OMTAE Agent OS.
//!
//! Provides 40 pluggable messaging integrations that convert platform messages
//! into unified `ChannelMessage` events for the kernel.
diff --git a/crates/openfang-channels/src/line.rs b/crates/openfang-channels/src/line.rs
index fa3ecd80ce..5938952be3 100644
--- a/crates/openfang-channels/src/line.rs
+++ b/crates/openfang-channels/src/line.rs
@@ -338,7 +338,7 @@ fn parse_line_event(event: &serde_json::Value) -> Option {
sender: ChannelUser {
platform_id: reply_to,
display_name: user_id,
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
diff --git a/crates/openfang-channels/src/linkedin.rs b/crates/openfang-channels/src/linkedin.rs
index 8435b5b0d8..30ae9077e7 100644
--- a/crates/openfang-channels/src/linkedin.rs
+++ b/crates/openfang-channels/src/linkedin.rs
@@ -325,7 +325,7 @@ impl ChannelAdapter for LinkedInAdapter {
sender: ChannelUser {
platform_id: sender_urn.clone(),
display_name: sender_name,
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
diff --git a/crates/openfang-channels/src/mastodon.rs b/crates/openfang-channels/src/mastodon.rs
index 9a704bfe63..f5d8f78e70 100644
--- a/crates/openfang-channels/src/mastodon.rs
+++ b/crates/openfang-channels/src/mastodon.rs
@@ -250,7 +250,7 @@ fn parse_mastodon_notification(
sender: ChannelUser {
platform_id: account_id.to_string(),
display_name,
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
diff --git a/crates/openfang-channels/src/matrix.rs b/crates/openfang-channels/src/matrix.rs
index 316b76fd16..a9e19f40d1 100644
--- a/crates/openfang-channels/src/matrix.rs
+++ b/crates/openfang-channels/src/matrix.rs
@@ -26,7 +26,7 @@ type TokenPair = Arc, Option>)>>;
pub struct MatrixAdapter {
/// Matrix homeserver URL (e.g., `"https://matrix.org"`).
homeserver_url: String,
- /// Bot's user ID (e.g., "@openfang:matrix.org").
+ /// Bot's user ID (e.g., "@omtae:matrix.org").
user_id: String,
/// SECURITY: Access + refresh tokens are zeroized on drop. Stored behind
/// an RwLock so the sync loop and send paths see rotated tokens after a
@@ -628,7 +628,7 @@ impl ChannelAdapter for MatrixAdapter {
sender: ChannelUser {
platform_id: room_id.clone(),
display_name: sender.to_string(),
- openfang_user: None,
+ omtae_user: None,
},
content: msg_content,
target_agent: None,
diff --git a/crates/openfang-channels/src/mattermost.rs b/crates/openfang-channels/src/mattermost.rs
index 02bd5ddae6..2dbf111324 100644
--- a/crates/openfang-channels/src/mattermost.rs
+++ b/crates/openfang-channels/src/mattermost.rs
@@ -1,4 +1,4 @@
-//! Mattermost channel adapter for the OpenFang channel bridge.
+//! Mattermost channel adapter for the OMTAE channel bridge.
//!
//! Uses the Mattermost WebSocket API v4 for real-time message reception and the
//! REST API v4 for sending messages. No external Mattermost crate — just
@@ -215,7 +215,7 @@ fn parse_mattermost_event(
sender: ChannelUser {
platform_id: channel_id.to_string(),
display_name: sender_name.to_string(),
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
@@ -626,7 +626,7 @@ mod tests {
"data": {
"post": serde_json::to_string(&post).unwrap(),
"channel_type": "O",
- "sender_name": "openfang-bot"
+ "sender_name": "omtae-bot"
}
});
diff --git a/crates/openfang-channels/src/messenger.rs b/crates/openfang-channels/src/messenger.rs
index 9c04a171d2..b8ec5aa801 100644
--- a/crates/openfang-channels/src/messenger.rs
+++ b/crates/openfang-channels/src/messenger.rs
@@ -240,7 +240,7 @@ fn parse_messenger_entry(entry: &serde_json::Value) -> Vec {
sender: ChannelUser {
platform_id: sender_id,
display_name: String::new(), // Messenger doesn't include name in webhook
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
diff --git a/crates/openfang-channels/src/mqtt.rs b/crates/openfang-channels/src/mqtt.rs
index a3e5b1549a..c154472709 100644
--- a/crates/openfang-channels/src/mqtt.rs
+++ b/crates/openfang-channels/src/mqtt.rs
@@ -8,8 +8,8 @@
//! ```toml
//! [channels.mqtt]
//! broker_url = "tcp://broker.hivemq.com:1883"
-//! subscribe_topic = "openfang/inbox"
-//! publish_topic = "openfang/outbox"
+//! subscribe_topic = "omtae/inbox"
+//! publish_topic = "omtae/outbox"
//! username_env = "MQTT_USERNAME"
//! password_env = "MQTT_PASSWORD"
//! use_tls = false
@@ -170,7 +170,7 @@ impl MqttAdapter {
fn build_mqtt_options(&self) -> Result> {
let (host, port) = self.parse_broker_url()?;
let client_id = if self.client_id.is_empty() {
- format!("openfang-{}", uuid::Uuid::new_v4())
+ format!("omtae-{}", uuid::Uuid::new_v4())
} else {
self.client_id.clone()
};
@@ -336,7 +336,7 @@ impl ChannelAdapter for MqttAdapter {
sender: ChannelUser {
platform_id: "mqtt-user".to_string(),
display_name: "MQTT User".to_string(),
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
diff --git a/crates/openfang-channels/src/mumble.rs b/crates/openfang-channels/src/mumble.rs
index e8418db12d..43de219f93 100644
--- a/crates/openfang-channels/src/mumble.rs
+++ b/crates/openfang-channels/src/mumble.rs
@@ -95,14 +95,14 @@ impl MumbleAdapter {
///
/// Simplified encoding: version fields as varint-like protobuf.
/// Field 1 (version): 0x00010500 (1.5.0)
- /// Field 2 (release): "OpenFang"
+ /// Field 2 (release): "OMTAE"
fn build_version_packet() -> Vec {
let mut payload = Vec::new();
// Field 1: fixed32 version = 0x00010500 (tag = 0x0D for wire type 5)
payload.push(0x0D);
payload.extend_from_slice(&0x0001_0500u32.to_le_bytes());
// Field 2: string release (tag = 0x12)
- let release = b"OpenFang";
+ let release = b"OMTAE";
payload.push(0x12);
payload.push(release.len() as u8);
payload.extend_from_slice(release);
@@ -421,7 +421,7 @@ impl ChannelAdapter for MumbleAdapter {
sender: ChannelUser {
platform_id: format!("session-{actor}"),
display_name: format!("user-{actor}"),
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
@@ -522,7 +522,7 @@ mod tests {
"mumble.example.com".to_string(),
0,
"secret".to_string(),
- "OpenFangBot".to_string(),
+ "OMTAEBot".to_string(),
"General".to_string(),
);
assert_eq!(adapter.name(), "mumble");
diff --git a/crates/openfang-channels/src/nextcloud.rs b/crates/openfang-channels/src/nextcloud.rs
index b4856abc22..375ed633f9 100644
--- a/crates/openfang-channels/src/nextcloud.rs
+++ b/crates/openfang-channels/src/nextcloud.rs
@@ -363,7 +363,7 @@ impl ChannelAdapter for NextcloudAdapter {
sender: ChannelUser {
platform_id: room_token.clone(),
display_name: actor_display.to_string(),
- openfang_user: None,
+ omtae_user: None,
},
content: msg_content,
target_agent: None,
diff --git a/crates/openfang-channels/src/nostr.rs b/crates/openfang-channels/src/nostr.rs
index ec54ade461..963dfa90a8 100644
--- a/crates/openfang-channels/src/nostr.rs
+++ b/crates/openfang-channels/src/nostr.rs
@@ -73,7 +73,7 @@ impl NostrAdapter {
fn build_subscription(&self, pubkey: &str) -> String {
let filter = serde_json::json!([
"REQ",
- "openfang-sub",
+ "omtae-sub",
{
"kinds": [4],
"#p": [pubkey],
@@ -167,7 +167,7 @@ impl ChannelAdapter for NostrAdapter {
let pubkey = self.derive_pubkey();
info!(
"Nostr adapter starting (pubkey: {}...)",
- openfang_types::truncate_str(&pubkey, 16)
+ omtae_types::truncate_str(&pubkey, 16)
);
if self.relays.is_empty() {
@@ -219,7 +219,7 @@ impl ChannelAdapter for NostrAdapter {
let sub_msg = {
let filter = serde_json::json!([
"REQ",
- "openfang-sub",
+ "omtae-sub",
{
"kinds": [4],
"#p": [&own_pubkey],
@@ -246,7 +246,7 @@ impl ChannelAdapter for NostrAdapter {
_ = relay_shutdown_rx.changed() => {
info!("Nostr: relay {relay_url} shutting down");
// Send CLOSE
- let close_msg = serde_json::json!(["CLOSE", "openfang-sub"]);
+ let close_msg = serde_json::json!(["CLOSE", "omtae-sub"]);
let _ = write.send(
tokio_tungstenite::tungstenite::Message::Text(
serde_json::to_string(&close_msg).unwrap_or_default()
@@ -342,9 +342,9 @@ impl ChannelAdapter for NostrAdapter {
platform_id: sender_pubkey.clone(),
display_name: format!(
"{}...",
- openfang_types::truncate_str(&sender_pubkey, 8)
+ omtae_types::truncate_str(&sender_pubkey, 8)
),
- openfang_user: None,
+ omtae_user: None,
},
content: msg_content,
target_agent: None,
@@ -460,7 +460,7 @@ mod tests {
let pubkey = adapter.derive_pubkey();
let sub = adapter.build_subscription(&pubkey);
assert!(sub.contains("REQ"));
- assert!(sub.contains("openfang-sub"));
+ assert!(sub.contains("omtae-sub"));
assert!(sub.contains(&pubkey));
}
diff --git a/crates/openfang-channels/src/ntfy.rs b/crates/openfang-channels/src/ntfy.rs
index 508d2aad31..ad2d0a0c7f 100644
--- a/crates/openfang-channels/src/ntfy.rs
+++ b/crates/openfang-channels/src/ntfy.rs
@@ -267,7 +267,7 @@ impl ChannelAdapter for NtfyAdapter {
sender: ChannelUser {
platform_id: sender_name.to_string(),
display_name: sender_name.to_string(),
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
@@ -329,7 +329,7 @@ impl ChannelAdapter for NtfyAdapter {
ChannelContent::Text(t) => t,
_ => "(Unsupported content type)".to_string(),
};
- self.publish(&text, Some("OpenFang")).await
+ self.publish(&text, Some("OMTAE")).await
}
async fn send_typing(&self, _user: &ChannelUser) -> Result<(), Box> {
diff --git a/crates/openfang-channels/src/pumble.rs b/crates/openfang-channels/src/pumble.rs
index 0aa97e8518..8ed4518a55 100644
--- a/crates/openfang-channels/src/pumble.rs
+++ b/crates/openfang-channels/src/pumble.rs
@@ -195,7 +195,7 @@ fn parse_pumble_event(event: &serde_json::Value, own_bot_id: &str) -> Option Opti
sender: ChannelUser {
platform_id: author.to_string(),
display_name: author.to_string(),
- openfang_user: None,
+ omtae_user: None,
},
content,
target_agent: None,
@@ -554,13 +554,13 @@ mod tests {
vec![
"rust".to_string(),
"programming".to_string(),
- "r/openfang".to_string(),
+ "r/omtae".to_string(),
],
);
assert_eq!(adapter.subreddits.len(), 3);
assert!(adapter.is_monitored_subreddit("rust"));
assert!(adapter.is_monitored_subreddit("programming"));
- assert!(adapter.is_monitored_subreddit("openfang"));
+ assert!(adapter.is_monitored_subreddit("omtae"));
assert!(!adapter.is_monitored_subreddit("news"));
}
diff --git a/crates/openfang-channels/src/revolt.rs b/crates/openfang-channels/src/revolt.rs
index d2b7202f4f..66419809f1 100644
--- a/crates/openfang-channels/src/revolt.rs
+++ b/crates/openfang-channels/src/revolt.rs
@@ -285,7 +285,7 @@ fn parse_revolt_message(
sender: ChannelUser {
platform_id: channel_id,
display_name: author.to_string(),
- openfang_user: None,
+ omtae_user: None,
},
content: msg_content,
target_agent: None,
diff --git a/crates/openfang-channels/src/rocketchat.rs b/crates/openfang-channels/src/rocketchat.rs
index 1102450275..71a021bb7c 100644
--- a/crates/openfang-channels/src/rocketchat.rs
+++ b/crates/openfang-channels/src/rocketchat.rs
@@ -311,7 +311,7 @@ impl ChannelAdapter for RocketChatAdapter {
sender: ChannelUser {
platform_id: channel_id.clone(),
display_name: sender_username.to_string(),
- openfang_user: None,
+ omtae_user: None,
},
content: msg_content,
target_agent: None,
diff --git a/crates/openfang-channels/src/router.rs b/crates/openfang-channels/src/router.rs
index 8bc141dcf9..c5fb21462c 100644
--- a/crates/openfang-channels/src/router.rs
+++ b/crates/openfang-channels/src/router.rs
@@ -2,8 +2,8 @@
use crate::types::ChannelType;
use dashmap::DashMap;
-use openfang_types::agent::AgentId;
-use openfang_types::config::{AgentBinding, BroadcastConfig, BroadcastStrategy};
+use omtae_types::agent::AgentId;
+use omtae_types::config::{AgentBinding, BroadcastConfig, BroadcastStrategy};
use std::sync::Mutex;
use tracing::warn;
@@ -30,7 +30,7 @@ pub struct BindingContext {
///
/// Routing priority: bindings (most specific first) > direct routes > user defaults > system default.
pub struct AgentRouter {
- /// Default agent per user (keyed by openfang_user or platform_id).
+ /// Default agent per user (keyed by omtae_user or platform_id).
user_defaults: DashMap,
/// Direct routes: (channel_type_key, platform_user_id) -> AgentId.
direct_routes: DashMap<(String, String), AgentId>,
@@ -430,7 +430,7 @@ mod tests {
router.register_agent("coder".to_string(), agent_id);
router.load_bindings(&[AgentBinding {
agent: "coder".to_string(),
- match_rule: openfang_types::config::BindingMatchRule {
+ match_rule: omtae_types::config::BindingMatchRule {
channel: Some("telegram".to_string()),
..Default::default()
},
@@ -452,7 +452,7 @@ mod tests {
router.register_agent("support".to_string(), agent_id);
router.load_bindings(&[AgentBinding {
agent: "support".to_string(),
- match_rule: openfang_types::config::BindingMatchRule {
+ match_rule: omtae_types::config::BindingMatchRule {
peer_id: Some("vip_user".to_string()),
..Default::default()
},
@@ -472,7 +472,7 @@ mod tests {
router.register_agent("admin-bot".to_string(), agent_id);
router.load_bindings(&[AgentBinding {
agent: "admin-bot".to_string(),
- match_rule: openfang_types::config::BindingMatchRule {
+ match_rule: omtae_types::config::BindingMatchRule {
guild_id: Some("guild_123".to_string()),
roles: vec!["admin".to_string()],
..Default::default()
@@ -513,14 +513,14 @@ mod tests {
router.load_bindings(&[
AgentBinding {
agent: "general".to_string(),
- match_rule: openfang_types::config::BindingMatchRule {
+ match_rule: omtae_types::config::BindingMatchRule {
channel: Some("discord".to_string()),
..Default::default()
},
},
AgentBinding {
agent: "specific".to_string(),
- match_rule: openfang_types::config::BindingMatchRule {
+ match_rule: omtae_types::config::BindingMatchRule {
channel: Some("discord".to_string()),
peer_id: Some("user1".to_string()),
guild_id: Some("guild_1".to_string()),
@@ -611,7 +611,7 @@ mod tests {
// Don't register the agent — binding should match but resolve_binding returns None
router.load_bindings(&[AgentBinding {
agent: "ghost-agent".to_string(),
- match_rule: openfang_types::config::BindingMatchRule {
+ match_rule: omtae_types::config::BindingMatchRule {
channel: Some("telegram".to_string()),
..Default::default()
},
@@ -631,7 +631,7 @@ mod tests {
router.add_binding(AgentBinding {
agent: "test".to_string(),
- match_rule: openfang_types::config::BindingMatchRule {
+ match_rule: omtae_types::config::BindingMatchRule {
channel: Some("slack".to_string()),
..Default::default()
},
@@ -645,7 +645,7 @@ mod tests {
#[test]
fn test_binding_specificity_scores() {
- use openfang_types::config::BindingMatchRule;
+ use omtae_types::config::BindingMatchRule;
let empty = BindingMatchRule::default();
assert_eq!(empty.specificity(), 0);
@@ -696,7 +696,7 @@ mod tests {
router.register_agent("ops-bot".to_string(), agent_id);
router.load_bindings(&[AgentBinding {
agent: "ops-bot".to_string(),
- match_rule: openfang_types::config::BindingMatchRule {
+ match_rule: omtae_types::config::BindingMatchRule {
channel: Some("discord".to_string()),
channel_id: Some("1477803840265781391".to_string()),
..Default::default()
@@ -738,14 +738,14 @@ mod tests {
router.load_bindings(&[
AgentBinding {
agent: "general".to_string(),
- match_rule: openfang_types::config::BindingMatchRule {
+ match_rule: omtae_types::config::BindingMatchRule {
channel_id: Some("ch-medical".to_string()),
..Default::default()
},
},
AgentBinding {
agent: "researcher".to_string(),
- match_rule: openfang_types::config::BindingMatchRule {
+ match_rule: omtae_types::config::BindingMatchRule {
channel_id: Some("ch-medical".to_string()),
peer_id: Some("user-a".to_string()),
..Default::default()
@@ -778,12 +778,12 @@ mod tests {
// than silently producing a wide-open binding. This is the highest-
// leverage line in the patch from issue #1127.
let bad = r#"{ "channnel_id": "ch-1" }"#;
- let r: Result = serde_json::from_str(bad);
+ let r: Result