The GLM/Zhipu provider was using the generic OpenAI-compatible provider,
which failed because:
- Zhipu requires JWT authentication (HS256 with sign_type: SIGN header),
not raw Bearer tokens
- The endpoint uses /v4/chat/completions, not /v1/
- default_model_for_provider() had no GLM case, silently defaulting to
a Claude model
Changes:
- Add src/providers/glm.rs with JWT token generation, caching, and
correct Z.AI international endpoint
- Wire GLM provider into factory (mod.rs) replacing the broken
OpenAI-compatible shim
- Add ring dependency for HMAC-SHA256 signing
- Add GLM-4.7 and GLM-4.7-Flash to onboarding wizard model list
- Fix default_model_for_provider() to return glm-4.7 for GLM provider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests that set/remove the same environment variables can race when
cargo test runs them in parallel. Merges each racing pair into a
single test function.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests that set/remove the same environment variables can race when
cargo test runs them in parallel. Merges each racing pair into a
single test function.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests that set/remove the same environment variables can race when
cargo test runs them in parallel. Merges each racing pair into a
single test function.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extends the temperature test fix to also cover provider, api_key, port,
and host env-var tests that had the same race condition.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests that set/remove the same environment variables can race when
cargo test runs them in parallel. Merges each racing pair into a
single test function.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests that set/remove the same environment variables (PROVIDER,
PORT, HOST, TEMPERATURE, API_KEY) can race when cargo test runs
them in parallel. Merges each racing pair into a single test function.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add open-skills auto-clone/pull/sync support in skills loader
- Clone https://github.com/besoeasy/open-skills to ~/open-skills
- Weekly sync via .zeroclaw-open-skills-sync marker
- Env controls: ZEROCLAW_OPEN_SKILLS_ENABLED, ZEROCLAW_OPEN_SKILLS_DIR
- Load open-skills markdown files before workspace skills
- Track Skill.location for accurate prompt rendering
- Update system prompt to render skill.location with fallback
- Use actual file path when available
- Maintain backward compatibility with workspace SKILL.md path
- Fix clippy warnings across tests and supporting files
- Readable timestamp literals
- Remove underscore bindings in tests
- Use struct update syntax for Config::default() patterns
- Fix module inception, duplicate attributes, manual strip
- Clean raw string hashes and empty string construction
Resolves: #77
The `is_multiple_of` method is unstable before Rust 1.87, breaking Docker
builds that use rust:1.83-slim. Also merges the two temperature env-var
tests into one to eliminate the race condition when tests run in parallel.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Docker image uses rust:1.83-slim where is_multiple_of is unstable.
Also regenerates Cargo.lock to include the sha2 dependency.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address review feedback from @coderabbitai and @gemini-code-assist:
- Missing API key is now a silent no-op instead of returning an error
- Network/TLS errors are now propagated via `?` instead of silently
discarded, so they surface as non-fatal warnings in the caller's log
- Added `error_for_status()` to catch HTTP-level failures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The first API request after daemon startup consistently timed out (120s)
when using channels (Telegram, Discord, etc.), requiring a retry before
succeeding. This happened because the reqwest HTTP client's connection
pool was cold — no TLS handshake, DNS resolution, or HTTP/2 negotiation
had occurred yet.
The fix adds a `warmup()` method to the Provider trait that establishes
the connection pool on startup by hitting a lightweight endpoint
(`/api/v1/auth/key` for OpenRouter). The channel server calls this
immediately after creating the provider, before entering the message
processing loop.
Tested on Raspberry Pi 5 (aarch64) with OpenRouter + DeepSeek v3.2 via
Telegram channel. Before: first message took 2-7 minutes (120s timeout +
retries). After: first message responds in <30s with no retries.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use bitwise & instead of && to avoid short-circuit timing leak
- Use get().unwrap_or(&0) instead of if/else for branchless byte access
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Check for empty USERNAME env var before running icacls to avoid a
doomed invocation with ":F" grant argument
- Log a clear warning when USERNAME is empty
- Add tracing::debug on successful permission set
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace manual hex encoding loop with `format!("{:x}", Sha256::digest(...))`,
which is more idiomatic and concise.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add missing assertion for variant_match (byte[8] UUID v4 variant bits)
which was computed but never checked.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- use rusqlite with SQLITE_OPEN_READ_ONLY | SQLITE_OPEN_NO_MUTEX
- run sync sqlite reads via spawn_blocking
- bind since_rowid with ?1 parameter to avoid SQL interpolation
- add comprehensive edge-case tests for message fetch and rowid helpers
Fixes#50
Add tower-http TimeoutLayer with the existing REQUEST_TIMEOUT_SECS (30s)
constant and 408 Request Timeout status code. Previously, the constant
was defined but no timeout middleware was applied, allowing slow
requests to hold connections indefinitely (slow-loris risk).
Closes#60
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove the early return on length mismatch that leaked length
information via timing. Now iterates over max(a.len(), b.len()),
padding the shorter input with zeros, and checks both byte-level
differences and length equality at the end.
Closes#57
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace silently discarded icacls result with proper error handling
that logs a tracing::warn! on failure. Previously, if icacls failed
(binary not found, permission denied), the key file would remain
world-readable on Windows with no indication of the problem.
Closes#56
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>