- #962: WebSocket auth now URL-decodes token before comparison. API keys with +/=/
characters (base64-derived) now work correctly for WS streaming.
- #939: Clippy bool_comparison lint fixed in web_fetch.rs test.
- #983: Dockerfile adds perl and make for openssl-sys compilation on slim-bookworm.
- #987: Nextcloud chat poll endpoint corrected from api/v4/room/{token}/chat to
api/v1/chat/{token}/ matching the send endpoint.
- #970: Moonshot Kimi K2/K2.5 models now redirect to api.moonshot.cn/v1 instead of
api.moonshot.ai/v1. The .ai domain only serves legacy moonshot-v1-* models.
- #882: Closed as resolved by v0.5.7 custom hand persistence fix (#984).
- #926: Verified already fixed (rmcp builder API from previous session).
All tests passing. 8 files changed, 75 insertions.
Allow certain Discord channel IDs to respond without requiring @mention,
similar to Hermes gateway's free_response_channels.
- Add free_response_channels field to DiscordConfig
- Add free_response_channels method to ChannelBridgeHandle trait
- Implement free_response_channels in KernelBridgeAdapter
- Modify dispatch_message to bypass mention_only policy for free channels
- Add test for free_response_channels deserialization
- #771: Fix Qwen tool_calls orphaning after context overflow. Added safe drain boundaries
in compactor and context_overflow to avoid splitting tool pairs. Added missing
validate_and_repair call in streaming loop.
- #811: LINE webhook signature now uses raw request bytes (not re-serialized JSON) for
HMAC. Channel secret is trimmed. Debug logging added for mismatches.
- #752: Local skill install now hot-reloads kernel via POST /api/skills/reload. TUI skill
list fixed to parse wrapper object. ClawHub install also triggers reload.
- #772: exec_policy mode=full now bypasses approval gate for shell_exec tools. Non-shell
tools like file_delete still respect approval settings.
- #661: Closed as resolved by #770 splice() reactivity fix and #836 tool ID fix.
All tests passing. 10 files changed, 436 insertions.
Addresses review feedback:
- Add `openfang auth hash-password` subcommand so users can generate
Argon2id hashes after upgrading (the command referenced in docs).
- Emit a tracing::warn at daemon startup when auth is enabled but the
password_hash is not in Argon2id format, so users know why login fails.
Dashboard passwords were hashed with plain SHA256 (no salt), vulnerable
to rainbow tables and GPU brute force. Switch to Argon2id with random
per-hash salts. Breaking change: existing SHA256 hashes in config.toml
must be regenerated with `openfang auth hash-password`.
- #875: Install script uses robust sed parsing instead of fragile cut for version detection
- #872: Session endpoint returns full tool results (removed 2000-char truncation)
- #867: agent_send/agent_spawn get 600s timeout (was 120s), regular tools keep 120s
- #824: Doctor workspace skills count uses direct return value from load_workspace_skills
- #833: Model switching respects provider via new find_model_for_provider() lookup
- #766: Closed as resolved by combined heartbeat fixes (v0.5.3 + merged PRs)
All tests passing. Live tested with daemon.
- Add missing budget_config field to AppState in all 3 test files
- Fix redundant closures and unwrap_or_else in openfang-memory semantic.rs
- Fix needless_borrow in openfang-api routes.rs (toml::from_str)
- Update parse_researcher_hand test to match new max_iterations = 25
- Update tar to 0.4.45 to fix RUSTSEC-2026-0067 and RUSTSEC-2026-0068
- Apply cargo fmt fixes in ws.rs and feishu.rs
Replace the custom JSON-RPC + stdio/SSE transport layer with the rmcp
SDK (crate 'rmcp'). This gives us spec-compliant Streamable-HTTP
transport, automatic Mcp-Session-Id tracking, SSE stream parsing, and
content-type negotiation out of the box while deleting ~300 lines of
hand-rolled plumbing.
Key changes:
- Add rmcp dependency with transport feature
- Replace McpTransportHandle enum with rmcp RunningService
- Replace manual JSON-RPC send_request/send_notification with rmcp client calls
- Add custom HTTP headers support for authenticated remote MCP servers
- Simplify tool discovery and invocation through rmcp's typed API
Add generic MQTT 3.1.1/5.0 support for IoT and messaging integration:
- MqttConfig with broker_url, TLS, QoS, auth via env vars
- MqttAdapter implementing ChannelAdapter trait
- Support for text and JSON {"text": "..."} payloads
- Command messages via /command args syntax
- Auto-reconnect with exponential backoff
- Message chunking for long responses
Configuration example:
[channels.mqtt]
broker_url = "tcp://broker.hivemq.com:1883"
subscribe_topic = "openfang/inbox"
publish_topic = "openfang/outbox"
- #834: Remove 3 decommissioned Groq models (gemma2-9b-it, llama-3.2-1b/3b-preview)
- #805: Ollama streaming parser now checks both reasoning_content and reasoning fields
- #820: Browser Hand checks python3 before python, fix optional dep logic
- #848: Hand continuous interval changed from 60s to 3600s to prevent credit waste
- #826: Doctor command no longer reports all_ok when provider key is rejected
- #836: WebSocket tool events now include tool call ID for concurrent call correlation
All 825+ tests passing. Verified live with daemon.
The dashboard CSP uses 'unsafe-inline' for script-src, which permits
any inline <script> block to execute — including attacker-injected
scripts if any endpoint reflects user input (agent names, message
content, channel descriptions, etc.).
Replace with a per-request cryptographic nonce (UUID v4):
- webchat_page generates a unique nonce on every request
- All <script> tags embed the nonce at compile time via __NONCE__ placeholder
- CSP becomes: script-src 'self' 'nonce-{nonce}' 'unsafe-eval'
('unsafe-eval' is still required for Alpine.js x-data expressions)
- API endpoints receive a strict default-src 'none'; frame-ancestors 'none'
policy instead of the permissive dashboard policy
This is a standard CSP Level 2 hardening; all modern browsers support nonces.
PUT /api/budget casts &Arc<AppState> to *mut KernelConfig and mutates
the budget fields through a raw pointer. This is unsound: AppState is
shared across Tokio worker threads, so two concurrent PUT /api/budget
requests cause a data race on the same memory location.
Replace with Arc<tokio::sync::RwLock<BudgetConfig>> stored on AppState,
initialized from kernel.config.budget at startup. All readers use
.read().await and all writers use .write().await. No unsafe code remains
in the budget update path.
Fixes: data race / undefined behaviour under concurrent budget updates
- Ensure all templates have manifest_toml field
- Use spawnFromTemplate for templates with manifest_toml
- Fix spawnBuiltin to handle missing fields gracefully
- Update HTML template to call correct spawn method
- Replace hardcoded list of 6 templates with all 30+ available templates
- Add category information to templates
- Combine static and dynamic templates with static templates displayed first
- Add loading and error states for template list
- Fix showDetail method for agent configuration
The previous sequence — get_job (read lock), check enabled, reserve_run
(write lock) — had a TOCTOU window where another request could disable
or delete the job between the check and the reservation.
Replace with CronScheduler::try_claim_for_run() which holds a single
DashMap write lock for the existence check, enabled guard, and next_run
advancement. Returns a typed ClaimError (NotFound | Disabled) so the
route handler maps directly to HTTP status codes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add POST /api/cron/jobs/{id}/run endpoint that triggers a cron job
immediately without waiting for its next scheduled fire time. The job
executes asynchronously in the background and its status can be polled
via the existing /status endpoint.
Key changes:
- Extract per-job execution logic from the inline cron tick loop into
a reusable `cron_run_job()` method on OpenFangKernel, called by both
the background scheduler and the new API endpoint
- Add `reserve_run()` on CronScheduler to pre-advance next_run for
overdue jobs before spawning manual runs, preventing duplicate
execution from the scheduler tick (only advances when next_run <= now
to avoid skipping imminent scheduled runs)
- Fix dashboard scheduler.js to call the correct cron API endpoint
instead of the legacy /api/schedules/ path
- Document all cron/scheduler endpoints in api-reference.md
Partially addresses upstream issue #634.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolve catalog display names and aliases to canonical model IDs when spawning or updating agents, keep provider-specific api_key_env hints in sync when switching providers, and route explicit provider model changes through kernel model normalization instead of directly mutating the registry. This fixes cases like xAI agents carrying stale OpenAI auth hints or UI labels such as 'Grok 4.20' being treated as raw model IDs.
(cherry picked from commit 51ee6a5a927208ab0301a29fd2e40b29c9dfaf7d)
Keep a bounded recent approval history instead of dropping timed-out or resolved requests on the floor, return recent approvals from /api/approvals, and make the dashboard poll and badge pending approvals so shell_exec prompts do not disappear before the user ever sees them.
(cherry picked from commit 78dd9f99cc835e85e452889bf6cfced5137f8a4a)
MatrixAdapter hardcoded `auto_accept_invites: true`, meaning any
Matrix-connected instance would blindly join every room it was invited
to. This is a security concern for public-facing homeservers — a
malicious user could invite the bot into an arbitrary room and interact
with the agent without the operator's consent.
Changes:
- Add `auto_accept_invites: bool` to `MatrixConfig` in openfang-types,
with `#[serde(default)]` defaulting to `false`.
- Thread the field through `MatrixAdapter::new()` instead of hardcoding.
- Wire it in `channel_bridge.rs` from `mx_config.auto_accept_invites`.
- Update tests to pass the new parameter.
Operators who want the old behaviour can set:
```toml
[channels.matrix]
auto_accept_invites = true
```
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add WebSocket long-connection receive mode for the Feishu/Lark adapter
as an alternative to webhook callbacks. WebSocket mode is enabled by
default, requiring no public IP or domain.
- FeishuConnectionMode enum (Webhook/WebSocket) with mode dispatch
- Protobuf binary frame parsing (prost) based on Feishu pbbp2 protocol
- Auto-reconnect, ping/pong heartbeat, ACK, multi-part payload combine
- handle_data_frame reuses parse_event() pipeline (dedup, group filter)
- FeishuMode config enum with bridge-layer adapter creation per mode
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>