Add a Python-based code review agent powered by LangChain that
integrates with OpenFang via the A2A (Agent-to-Agent) protocol.
- agent.py: Core review logic with structured Chinese SYSTEM_PROMPT
covering 6 dimensions (correctness, security, performance,
maintainability, testing, style) and 4 severity levels
- server.py: FastAPI server exposing A2A-compatible endpoints
(/.well-known/agent.json and /a2a JSON-RPC)
- workflow.json: OpenFang workflow definition for the review pipeline
- config.example.toml: Example A2A config for ~/.openfang/config.toml
- Supports OpenAI, DeepSeek, and Ollama backends
Made-with: Cursor
* Feat: Add Nix support
Adding Nix support. Nixos modules may follow...
Run directly with `nix run github:RightNow-AI/openfang`
There are a bunch of flake outputs (based on cargo workspace)
Focus on these:
* openfang-cli (default)
* openfang-desktop
* nix: cmake depdencency was introduced via llama
* Follow upstream style
Merge lark.rs features (dedup, encryption, group filtering, rich text parsing)
into feishu.rs with FeishuRegion toggle (cn/intl). Single [channels.feishu]
config handles both domestic Feishu and international Lark via region field.
- Expand FeishuConfig: region, webhook_path, verification_token, encrypt_key_env, bot_names
- Add FeishuRegion enum with domain switching (open.feishu.cn / open.larksuite.com)
- Add AES-256-CBC event decryption, message/event dedup, group chat filtering
- Update channel_bridge.rs wiring for full config
- Update routes.rs ChannelMeta with new UI fields (region basic, rest advanced)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds a WebSocket-based DingTalk Stream channel adapter as an alternative
to the existing webhook-based DingTalk adapter.
DingTalk Stream Mode uses a long-lived WebSocket connection to the
DingTalk Gateway, eliminating the need for a public webhook endpoint.
Changes:
- `openfang-types`: add `DingTalkStreamConfig` struct and wire into
`ChannelsConfig` alongside the existing `DingTalkConfig`
- `openfang-channels`: implement `DingTalkStreamAdapter` (WebSocket
connection management, ping/pong, token refresh, send via batchSend API)
- `openfang-api`: register `dingtalk_stream` in the channel registry,
`is_channel_configured`, and `channel_config_values`
- `openfang-api`: wire adapter startup in `channel_bridge.rs`
- `openfang-cli`: add `dingtalk_stream` entry to the TUI channels list
Configuration:
```toml
[channels.dingtalk_stream]
app_key_env = "DINGTALK_APP_KEY" # Enterprise Internal App Key
app_secret_env = "DINGTALK_APP_SECRET" # Enterprise Internal App Secret
robot_code_env = "DINGTALK_ROBOT_CODE" # optional, defaults to app_key
```
Requires an Enterprise Internal App in the DingTalk Open Platform with
Stream Mode enabled. No public endpoint needed.
Made-with: Cursor
Co-authored-by: Wang Hanbin <wanghb@best-inc.com>
* feat: heartbeat auto-recovery for crashed agents
Extend the heartbeat monitor to detect and automatically recover crashed
agents, reducing operator intervention for 24/7 autonomous deployments:
- Add RecoveryTracker: per-agent failure count with configurable cooldown
- Heartbeat now monitors both Running and Crashed agents
- Crashed agents auto-recover up to max_recovery_attempts (default 3)
- After exhausting attempts, agents are marked Terminated
- Unresponsive Running agents marked Crashed for next-cycle recovery
- Increase default timeout from 60s to 180s (browser/LLM tasks need time)
- Add HeartbeatStatus.state field for downstream consumers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: claude code driver — PID tracking and message timeout
Add subprocess lifecycle management to prevent hung CLI processes from
blocking agents indefinitely:
- Track active subprocess PIDs in a concurrent DashMap for external monitoring
- Enforce configurable message timeout (default 300s) with automatic process kill
- Return proper LlmError::Api on non-zero exit in streaming mode (was silently ignored)
- Add with_timeout(), active_pids(), pid_map() public methods
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: /restart endpoint — manual per-agent recovery without daemon bounce
POST /api/agents/{id}/restart and /api/agents/{id}/start both:
- Cancel any active task via stop_agent_run()
- Reset agent state to Running (updates last_active)
- Return JSON with previous state and whether a task was cancelled
Enables operators to recover individual crashed/stuck agents through the
API without restarting the entire daemon.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: ZiLLA Dev <dev@zilla.wtf>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When an agent is restarted, its UUID changes but the channel bridge still
holds the old UUID from startup. This causes "Agent not found" errors.
This fix stores the agent *name* alongside the cached UUID at bridge
startup and, on "Agent not found" errors, re-resolves the name to a
fresh UUID via find_agent_by_name(), updates the cache, and retries the
message — all transparently to the end user.
Changes:
- router.rs: add channel_default_names DashMap, set_channel_default_with_name(),
channel_default_name(), update_channel_default()
- channel_bridge.rs: use set_channel_default_with_name() at startup
- bridge.rs: add try_reresolution() helper, integrate retry logic into
dispatch_message() and dispatch_with_blocks() error paths with proper
lifecycle_reactions guards and sanitize_agent_error() usage
Add Shell runtime type to SkillRuntime enum and implement
execute_shell function for running Bash scripts as skills.
This allows skills to use Bash script files, which many
existing skills rely on.
Ref: RightNow-AI/openfang/issues/620
Co-authored-by: TJUEZ <tjuez@email.com>
Chromium refuses to launch without --no-sandbox when the process is
running as UID 0. This causes the browser hand to fail immediately with
'Chromium exited before printing DevTools URL' on any server-based
OpenFang installation that runs as root (the default install).
Added is_running_as_root() which reads /proc/self/status on Linux to
detect UID 0 without requiring a libc dependency, with a fallback to
the HOME env var for other Unix systems. When root is detected,
--no-sandbox is appended to the Chromium launch args automatically.
* Add unfurl_links config for Slack channel
Add unfurl_links: bool (default true) to SlackConfig to control
Slack's automatic URL preview expansion. When set to false, links
in agent messages are not unfurled, keeping output compact.
Applied to SlackAdapter's chat.postMessage payload via unfurl_links
and unfurl_media parameters, affecting both real-time and cron
delivery paths.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Rename test per review: clarify it tests explicit true, not default
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add release-fast Cargo profile for faster dev builds
Introduces a `release-fast` profile that inherits from `release` but
uses thin LTO and 8 codegen units instead of full LTO + 1, cutting
link time significantly while remaining fast enough for integration
testing. Documents usage in CONTRIBUTING.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: allow renaming an agent to its current name
AgentRegistry::update_name was calling name_index.contains_key()
without excluding the agent being renamed. Renaming to the same name
always returned AgentAlreadyExists instead of succeeding silently.
Fix: only error when a *different* agent owns the target name.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: use fixed agent ID for hand agents based on hand_id
This ensures triggers and cron jobs continue to work after daemon restart,
as hand agents now have stable IDs instead of generating a new UUID each time.
Changes:
- Add AgentId::from_string() method for deterministic ID generation
- Modify spawn_agent_with_parent() to accept optional fixed_id
- Use hand_id-based fixed ID in activate_hand()
See: #519
* remove: remove serena local config from commit
* chore: ignore .serena directory
* fix: use async save_session to avoid blocking tokio runtime
save_session() was synchronous, holding a Mutex<Connection> on the
tokio worker thread during SQLite writes. On pods with 1 CPU core
(1 tokio worker thread), this starved the entire runtime — including
health check endpoints — causing K8s to mark the pod not-ready and
return 504 on all subsequent requests.
Add save_session_async() that wraps the SQLite write in
spawn_blocking, matching the pattern already used by other memory
operations (recall, remember, etc.). Update all 12 call sites in
the agent loop.
* fix: move health check DB query to spawn_blocking and add SSE keep_alive
The health endpoint called structured_get() synchronously on the tokio
async runtime, acquiring the shared std::sync::Mutex<Connection> on a
worker thread. When the agent loop held this mutex during session saves,
the health check blocked the tokio thread, starving the SSE stream and
causing Kubernetes probe timeouts.
- Health and health_detail now run the DB check via spawn_blocking
- SSE message/stream endpoint now includes keep_alive to flush periodic
heartbeats even during contention
* feat: add hands upsert API for idempotent hand definition updates
Add upsert_from_content() to HandRegistry that overwrites existing
definitions instead of rejecting duplicates. Exposed as POST
/api/hands/upsert for use by the shard manager to keep hand definitions
up to date across pod restarts.
* fix: websocket streaming delays
* fix: get response immediately
Adds LTO and CODEGEN_UNITS arguments that default to optimized prod settings but can be overridden (e.g., LTO=false, CODEGEN_UNITS=16) by developers for faster iteration.
The polling loop was updating last_notification_id on every iteration,
leaving it set to the oldest (smallest) ID in the batch after the loop
completed. On the next poll, since_id was set to that oldest ID, causing
Mastodon to return all previously seen notifications again.
Re-delivered notifications caused the bot to respond to the same user
mention repeatedly. Combined with api_post_status chaining each response
chunk as a reply to the previous chunk, this produced long self-reply
threads that appeared to be the bot conversing with itself.
Fix: capture the first (newest) notification ID before processing the
batch, so since_id always advances correctly on each poll cycle.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces a `release-fast` profile that inherits from `release` but
uses thin LTO and 8 codegen units instead of full LTO + 1, cutting
link time significantly while remaining fast enough for integration
testing. Documents usage in CONTRIBUTING.md.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>