- #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.
- Implement create-with-PATCH-on-conflict push pattern (POST 409 → PATCH)
- Migrate push and delete endpoints from deprecated v3 to v4 API
- Replace workspaceId with projectId in push/delete API calls
Claude CLI ≥2.x emits type=assistant events where the response text is
inside message.content[{"type":"text","text":"..."}] rather than a flat
content string. The old handler only checked event.content, so every
token was silently dropped and streaming always returned an empty response.
The handler now checks the flat content field first (backward-compatible),
then falls back to joining all text blocks from message.content[].
Refs: RightNow-AI/openfang#295
Mirror the same environment fixes applied to complete(): inject HOME so
the CLI locates ~/.claude/credentials when running as a service, and set
stdin to null so the process does not block on interactive input.
Refs: RightNow-AI/openfang#295
When complete() called child.wait() before reading stdout/stderr, large
responses (>64 KB) caused a deadlock: the subprocess blocked on write()
because the OS pipe buffer was full, and wait() never returned.
Fix by spawning two tokio tasks to drain stdout/stderr concurrently with
child.wait(), then collecting after the process exits.
Also inject HOME from home_dir() so the CLI finds ~/.claude/credentials
when OpenFang runs as a service, and set stdin to null so the CLI does
not stall waiting for interactive input.
Refs: RightNow-AI/openfang#295
Newer Claude CLI versions (≥2.x) emit assistant responses inside a nested
`message.content[].text` structure in stream-json events, rather than a
flat `content` string.
Add ClaudeMessageBlock and ClaudeAssistantMessage structs, plus a new
`message` field on ClaudeStreamEvent, so the stream handler can extract
text from both layouts.
Refs: RightNow-AI/openfang#295
Without this attribute, serde treats a missing `result` field as a
deserialization error even though `Option<T>` implies the field is
optional. Some Claude CLI versions emit the response in `content` or
`text` rather than `result`; the silent parse failure caused the
driver to fall through to a plain-text read which could be empty,
triggering the "model returned an empty response" guard in the agent
loop.
Closes#295.
Co-Authored-By: Claude <noreply@anthropic.com>
Add sanitize_gemini_turns() to enforce Gemini's strict turn-ordering
constraints after message history is trimmed. This merges consecutive
same-role turns, drops orphaned functionCall/functionResponse parts,
and removes empty turns. Also adds #[serde(default)] on GeminiContent.parts
and fixes two tests that were missing required ToolResult messages.
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)