Extract the dotted-key lookup in `openfang config get` into a pure
`lookup_config_value` helper with clear outcomes (scalar value, non-scalar
section, or key not found) so the behaviour is covered by unit tests.
Previously the command only had integration coverage, so regressions where
`config get default_model.base_url` silently returned an empty string could
slip through. Tests now pin down every `[default_model]` scalar, including
`base_url`, plus unset, missing, numeric, boolean, and section cases.
Also distinguishes a section-valued key (e.g. `config get default_model`)
from a scalar instead of printing a debug-style `{}`.
Made-with: Cursor
Docs reference `openfang hand config browser --set headless=true` but
the CLI never shipped that command. Add it as a thin wrapper over the
existing GET/PUT `/api/hands/{id}/settings` routes so the documented
example works.
- `openfang hand config <id>` prints the current settings merged with
schema defaults.
- `--get KEY` prints one value, `--set KEY=VAL` (repeatable) updates
values, `--unset KEY` removes them. Empty keys are rejected up front.
- When no instance is active, the daemon's existing 404 is surfaced
with a hint to run `openfang hand activate <id>` first.
Unit tests cover the KEY=VAL parser (including empty keys, urls with
equals signs, and blank values). Integration test runs the registry
update_config path end-to-end through a persist/load round-trip so
the CLI semantics match what the daemon stores.
Made-with: Cursor
The Copilot LLM driver was broken - it expected users to provide a
GITHUB_TOKEN env var, but no standard token type (PAT, gh CLI token)
works with the Copilot token exchange endpoint.
Changes:
- Full rewrite of copilot.rs with OAuth device flow using Copilot's
client ID (Iv1.b507a08c87ecfe98)
- Three-layer token chain: ghu_ (8h) -> Copilot API token (30min),
with automatic caching and refresh
- Dynamic model fetching from Copilot API on daemon startup and on
model_not_supported error
- Init wizard: TUI auth screen with device code display, live model
picker after authentication
- set-key command: interactive device flow for github-copilot provider
- Doctor: detects Copilot auth via persisted token file
- Removed static Copilot model entries (now fetched dynamically)
- Simplified driver instantiation (no env vars needed)
Tested end-to-end with Copilot Enterprise: auth, token exchange,
43 models fetched, completions working with claude-opus-4.6-1m.
Closes#1014
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- #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.
- #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
- #825: Doctor now surfaces blocked workspace skills count in injection scan
- #828: Skill install detects Git URLs (https://, git@) and clones before install
- #856: Custom model names preserved — user-defined models take priority over builtins
- #770: Dashboard WS streaming now triggers Alpine.js reactivity via splice()
- #774: tool_use.input always normalized to JSON object (fixes Anthropic API errors)
- #851/#808: Global skills loaded for all agents; workspace skills properly override globals
- #785: Gemini streaming SSE parser handles \r\n line endings (fixes empty response loop)
All 2,186 tests passing. Live tested with daemon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- #845: Model fallback chain now retries with fallback_models on ModelNotFound
- #844: Heartbeat skips idle agents that never received a message (no more crash loops)
- #823: Doctor --json outputs clean JSON to stdout, tracing to stderr, BrokenPipe handled
- #767: Workflows page scrollable with flex layout fix
- #802: Model dropdown handles object options (no more [object Object] for Ollama)
- #816: Spawn wizard provider dropdown loads dynamically from /api/providers (43 providers)
All 829+ tests passing. Live tested with daemon.
- #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.
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>
Fix Claude Code provider setup flow: wizard now shows Detect button instead of API key input for keyless providers, TUI wizards include claude-code in provider list, credentials detection checks both .credentials.json paths, subprocess env_clear prevents API key leaks. Fixes#376#303.
Fix tool name mapping so LLM-hallucinated aliases (fs-write, fsRead, writeFile, etc.) normalize to canonical names before capability check and dispatch (#349). Fix provider keys not loading after dashboard save by creating fresh drivers that read current env vars instead of stale boot-time cache (#465, #458, #355). Fix Moonshot/kimi model IDs and provider inference (#428). Add Telegram message reactions for agent lifecycle feedback (#435). Add configurable api_url for Telegram proxy support (#477). Add Discord ignore_bots config option (#403). Fix openfang init EPERM crash with 7-browser fallback on Linux (#389). Add text-based tool call parsing for models without native function calling — [TOOL_CALL], <tool_call>, bare JSON patterns (#354, #332). Fix pre-existing Windows test failures with cross-platform paths. 1948 tests pass, 0 clippy warnings.
Fix 8 issues: empty LLM response after ~4 rounds by re-validating message pairs after history trim (#460), MCP tools permission denied by bypassing ToolInvoke capability filter for extension tools (#352), Telegram photos silently dropped now downloaded and passed as multimodal ContentBlock::Image (#362), workflow visual builder double-click editing and live property updates (#357), Claude Code provider card reflects actual install/auth status (#376), Python 3 detection runs actual command instead of path lookup (#405), hand agent_id persisted for cron job reassignment on restart (#402), CLI sends auth headers on all commands not just stop (#478). 1921 tests pass, 0 clippy warnings.