update openfang fork to upstream v0.6.9#1
Merged
Conversation
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2 to 3. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](softprops/action-gh-release@v2...v3) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
The WASM sandbox host_net_fetch() had its own SSRF implementation (is_ssrf_target) that was incomplete compared to the canonical check_ssrf() in web_fetch.rs: - Missing 6 blocked hostnames (ip6-localhost, Alibaba/Azure IMDS, 0.0.0.0, ::1, [::1]) - Missing cloud metadata IP detection (is_metadata_ip) - Missing IPv6 bracket notation support - Ignoring ssrf_allowed_hosts from config.toml entirely - Duplicate is_private_ip() and extract_host_from_url() functions This meant a WASM agent could bypass SSRF protections that the builtin web_fetch tool correctly enforced. Changes: - Remove duplicated is_ssrf_target(), is_private_ip(), and extract_host_from_url() from host_functions.rs - Delegate to web_fetch::check_ssrf() which has the complete implementation with allowlist, CIDR matching, and metadata IP detection - Add ssrf_allowed_hosts to SandboxConfig and GuestState so the config propagates to WASM host calls - Make extract_host() pub(crate) for reuse - Update tests to exercise the unified code path, including new coverage for IPv6 and cloud metadata endpoints All 908 runtime tests pass. Zero clippy warnings.
When using Lark international (open.larksuite.com) with WebSocket mode, the adapter was hardcoding the Chinese Feishu endpoint URL and also ignoring the configured region entirely when constructing the adapter. Two bugs fixed: 1. FEISHU_WS_ENDPOINT_URL was hardcoded to open.feishu.cn — international Lark apps could not authenticate because their credentials are only valid on open.larksuite.com. Changed to FEISHU_WS_ENDPOINT_PATH and compute the full URL using self.region.domain() at call time. 2. new_websocket() in FeishuAdapter always set region = FeishuRegion::Cn. Added new_websocket_with_region() that accepts an explicit region, and updated the call site in channel_bridge.rs to pass the parsed region. Fixes RightNow-AI#1081
…positives Fixes RightNow-AI#1089 run_agent_loop_streaming skipped the touch_agent() call that the non-streaming run_agent_loop performs before every LLM request. On slow local inference (e.g. Ollama qwen3.5:35b, multi-minute generations), last_active went stale and the heartbeat monitor flagged the agent as unresponsive, triggering crash recovery mid-stream. With multiple agents sharing one Ollama instance, queued agents appeared frozen while the active one generated. Mirror the non-streaming behavior: stamp last_active immediately before stream_with_retry so the heartbeat window covers the full LLM call. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t mod - Replace retain(|_| true) no-op with a size-capped clear: when threaded_message_ids exceeds MAX_DEDUP_MSG_IDS (2000) it is cleared. MESSAGE_UPDATE embed events arrive within seconds so old entries are always safe to discard; prevents unbounded growth on busy servers. - Add #[cfg(test)] to mod tests so empty_threads() helper is only compiled in test mode — removes the need for #[allow(dead_code)].
Node/npx-backed stdio MCP servers (Gmail, AgentMail, Exa, etc.) need a usable HOME directory for npm cache and temp-file scratch space. Without it, npm errors with EACCES on /nonexistent or silently falls over when trying to write cache entries. Previously these three variables were only passed on Windows. Linux and macOS hosts launching stdio MCP servers through npx would get an empty env for HOME/TMP/TEMP, breaking most community MCP servers. Move the HOME/TMP/TEMP passthrough above the cfg!(windows) block so it applies to every platform. Remove the now-redundant entries from the Windows-only list.
Fixes RightNow-AI#1088 - scheduled task results now appear in web UI without page refresh.
The six outbound helpers in the Telegram adapter (sendMessage, sendPhoto, sendDocument, sendDocument_upload, sendVoice, sendLocation) previously logged a `warn!` on HTTP non-success and still returned `Ok(())`. Callers interpreted that as successful delivery and told the agent "Message sent" even when Telegram had rejected the request (e.g. 400 Bad Request from malformed HTML entities with parse_mode=HTML). The agent recorded phantom success in its session history, corrupting subsequent behavior. The fix returns `Err(format!(...).into())` on HTTP non-success in all six helpers, matching the error-handling convention documented in CONTRIBUTING.md. `api_send_message` is slightly different because it splits long messages into chunks via `split_message(4096)`. Naively returning `Err` on any chunk failure would create a partial-delivery-then-error regression — worse than the original silent success. The function now tracks `delivered_any` across chunks: - First-chunk failure (nothing delivered yet) → return `Err` to surface the failure. This is where the motivating HTML-parse-error bug lives, so the fix is fully effective. - Subsequent-chunk failure (user already received preceding chunks) → log `warn!` and continue with best-effort delivery, matching the convention used by every other adapter in the crate that calls `split_message` (Discord, Gitter, Mattermost, Nextcloud, Twitch, Pumble, etc.). Tests: 4 new tests using a small in-crate stub server (axum on an ephemeral port, reached via the existing `api_url` constructor seam — zero new dependencies). 41 telegram tests pass (37 existing + 4 new).
…, emoji) `fire_reaction` calls `setMessageReaction` fire-and-forget on every agent lifecycle event. When Telegram returns a terminal error like `REACTION_INVALID` (emoji not in the bot's free-reaction allowlist), `REACTION_NOT_AVAILABLE` (chat admin restricted this emoji), or `REACTION_TOO_MANY` (per-message cap), retrying on every subsequent turn is pointless log spam and wasted API quota. This adds a per-bot-instance `HashSet<(i64, String)>` keyed by `(chat_id, emoji)` that records terminal rejections and short-circuits future calls for the same pair. Keyed by chat, not just emoji, because `Chat.available_reactions` varies across chats and is admin-mutable (https://core.telegram.org/bots/api#setmessagereaction) — an emoji rejected in chat A may still be valid in chat B. Cache is per-process; on restart it rebuilds naturally, which handles any runtime allowlist change without needing persistence. The terminal-error match uses a small private helper `is_terminal_reaction_error` that substring-matches the three permanent errors. Transient errors (429, 5xx, `MESSAGE_NOT_MODIFIED`, unrelated 400s) are deliberately NOT cached. Concurrency: the cache uses `std::sync::Mutex` — critical section is two `HashSet` ops (contains + insert), never held across `.await`. Endorsed by the Tokio shared-state tutorial (https://tokio.rs/tokio/tutorial/shared-state) for exactly this shape. Two concurrent `fire_reaction` calls for the same (chat, emoji) can both pass the cache check before either rejection lands, producing up to N duplicate API calls on the first rejection; the duplicate `insert` is idempotent so this is benign and self-limits on the second turn. Documented in-code. Tests: 6 new tests covering terminal-error matching, cache insertion, per-chat key isolation, and non-caching of transient and successful responses. Total 47 telegram tests pass (41 existing + 6 new). No new clippy warnings.
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.23.0 to 1.23.1. - [Release notes](https://github.com/uuid-rs/uuid/releases) - [Commits](uuid-rs/uuid@v1.23.0...v1.23.1) --- updated-dependencies: - dependency-name: uuid dependency-version: 1.23.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [rustls](https://github.com/rustls/rustls) from 0.23.37 to 0.23.39. - [Release notes](https://github.com/rustls/rustls/releases) - [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md) - [Commits](rustls/rustls@v/0.23.37...v/0.23.39) --- updated-dependencies: - dependency-name: rustls dependency-version: 0.23.39 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.183 to 0.2.185. - [Release notes](https://github.com/rust-lang/libc/releases) - [Changelog](https://github.com/rust-lang/libc/blob/0.2.185/CHANGELOG.md) - [Commits](rust-lang/libc@0.2.183...0.2.185) --- updated-dependencies: - dependency-name: libc dependency-version: 0.2.185 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [lettre](https://github.com/lettre/lettre) from 0.11.20 to 0.11.21. - [Release notes](https://github.com/lettre/lettre/releases) - [Changelog](https://github.com/lettre/lettre/blob/master/CHANGELOG.md) - [Commits](lettre/lettre@v0.11.20...v0.11.21) --- updated-dependencies: - dependency-name: lettre dependency-version: 0.11.21 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Discord and Slack adapters set sender.platform_id to the channel/conversation ID (needed for the send path), so router.resolve(channel, sender.platform_id, ..) was matching peer_id bindings against the channel ID and never finding the user-keyed binding. The sender_user_id() helper already existed but only the rate-limit/authz paths used it; the routing reads did not. Read-path fix: - discord.rs / slack.rs: stash author/user ID in metadata["sender_user_id"] - bridge.rs: route the text and audio paths through sender_user_id(message) - bridge.rs: thread user_id through handle_command() so the 6 CLI slash-command resolves (/new, /compact, /model, /stop, /usage, /think) also key on user. Tests updated for the new signature. Write-path follow-up (set_user_default + broadcast routing) deferred to a separate commit so this change can be validated in isolation.
Completes the router keying fix started in 6a90aa0. The read path resolves on user_id, but four write sites and the broadcast lookup were still keyed on sender.platform_id (channel ID on Discord/Slack), producing the split-keying state that surfaced in GAP-008. - 4 x set_user_default writes (text/audio fallback, /agent existing, /agent spawned) now key on sender_user_id(message) - 2 x broadcast lookups (has_broadcast / resolve_broadcast) switched to sender_user_id(message), matching the upstream test's intent (router.rs:521-547 keys on "vip_user", not a channel) - boot-time log warning on Discord/Slack adapter start: any pre-existing /agent default may need to be re-run once - new test test_handle_command_agent_select_keys_on_user_id_not_ platform_id locks in the round-trip
The claude-code driver hardcodes its per-message turn timeout inside
ClaudeCodeDriver and exposed no operator-facing knob, so long-running
CC subprocess turns (large prompt-caches, deep tool chains) hit the
internal default with no escape hatch. Adds a public config surface,
honored today only by the claude-code driver, designed so future
subprocess drivers can opt in without re-shaping the API.
Public surface
- DriverConfig.subprocess_timeout_secs: Option<u64> (llm_driver.rs)
- OPENFANG_SUBPROCESS_TIMEOUT_SECS env var (drivers/mod.rs)
- Precedence in create_driver(): env var > config field > driver default
Naming rationale
- Field/env are scope-flavored, not semantic, on purpose: the name
telegraphs that HTTP providers (default/Anthropic, openai, bedrock,
qwen-code) accept-but-silently-ignore the field today. A semantic
name (message_timeout_secs) would have invited the same silent-no-op
footgun on those providers.
- Driver-internal field in claude_code.rs intentionally kept as
message_timeout_secs — it's not on the public boundary and the
semantic name accurately describes what it stores.
Tests (drivers/mod.rs)
- default_when_unset: no env, no config -> driver default
- config_set: config field flows through
- env_overrides_config: env var wins over config (construction-only
assertion; trait-object opacity prevents reading the value back)
- malformed_env_falls_through: unparseable env silently falls through
to config, matching the .parse::<u64>().ok() chain in production
- All four tests scrub OPENFANG_SUBPROCESS_TIMEOUT_SECS pre/post to
avoid cross-test pollution
Mechanical pass-throughs
- 12 x DriverConfig { .. } test fixtures in drivers/mod.rs gain
subprocess_timeout_secs: None
- routes.rs (1), kernel.rs (6), agent_loop.rs (2): same pass-through
fills in DriverConfig literals; no logic touched
Forward-compat note
- A NOTE block in drivers/mod.rs flags the scope-vs-implementation
gap so the next contributor adding a subprocess driver knows
exactly where to wire the config in.
Validated end-to-end against a live daemon: dry-run + full deploy
(deploy-local.sh, all 7 phases) + post-swap agent_send round-trip
through the claude-code dispatch path.
Follow-up to 79aa34c. The previous commit added the public surface (DriverConfig field + OPENFANG_SUBPROCESS_TIMEOUT_SECS env var) but left every DriverConfig construction site hardcoded to None — so the struct field was wired but had no on-disk source feeding it. The env var was the only operator-facing knob. This commit plumbs the missing layer: the timeout is now deserializable from config.toml on both the primary and global-fallback providers. Public surface - DefaultModelConfig.subprocess_timeout_secs: Option<u64> - FallbackProviderConfig.subprocess_timeout_secs: Option<u64> - Both fields are #[serde(default)] — existing config.toml files without the field deserialize cleanly to None (no breaking change). Placement rationale - Per-provider on each config struct, not a top-level field or a new [driver] section. This matches the existing per-provider config shape and lets operators set different timeouts for primary vs. fallback (e.g. tighter timeout on a fast fallback to fail over sooner). If a second driver-level setting ever lands, refactoring two struct fields into a [driver] section is cheap; we don't pre-pay for it now. Wiring (kernel.rs) - L663 primary driver ........ pulls config.default_model.subprocess_timeout_secs - L687 auto-detect path ...... inherits default_model intent (the swap is replacing the *provider*, not the timeout policy) - L736 global fallback loop .. pulls fb.subprocess_timeout_secs - L5031 agent primary ......... inherits effective_default's value when agent_provider == default_provider; None for cross-provider overrides - L5108 agent manifest fallback inherits dm's value when the manifest fallback resolves to "default" (matching the existing fb.provider sentinel logic); None for explicit cross-provider entries - L5139 global fallback (per-agent loop) — pulls fb.subprocess_timeout_secs Sites kept as None (intentional) - agent_loop.rs:1146, 1330: ModelNotFound recovery iterates over the agent manifest's fallback_models (FallbackModel, not the config-toml type) — no per-provider config in scope. - routes.rs:7701: provider connectivity test endpoint; no config source. - routes.rs:7529: dashboard hot-update path constructs a fresh DM with defaults (None) — operator sets timeout via config.toml, not via the set-key flow. Tests - test_subprocess_timeout_secs_in_toml: round-trips a TOML doc with default_model.subprocess_timeout_secs = 600 and one fallback at 180, one fallback omitted; asserts each value (or None) reaches the parsed config struct. - test_subprocess_timeout_secs_omitted_defaults_to_none: asserts a legacy-shaped config.toml (no timeout fields) parses cleanly with both fields = None — backward-compat guard. - 4 existing claude_code driver timeout tests still pass. Mechanical pass-throughs - 8 test fixtures across openfang-kernel/tests and openfang-api/tests gain subprocess_timeout_secs: None on their DefaultModelConfig literals. - 1 production literal in routes.rs gains the same field. - The existing FallbackProviderConfig serde-roundtrip test gains subprocess_timeout_secs: None plus an assertion. Precedence comment in drivers/mod.rs::create_driver updated to reflect that the config-field path is now real, with explicit pointers to the kernel.rs wiring sites for future contributors. Validated: cargo check --workspace --tests is clean; openfang-types (362), openfang-runtime (933), and openfang-kernel (260) lib tests all pass.
…per (RightNow-AI#1124) Adds an optional `audio_base_url` field to `MediaConfig` that overrides the hardcoded provider URLs in `media_understanding::transcribe_audio`, allowing the same OpenAI-compatible multipart wire format to be sent to a local Whisper service (speaches, faster-whisper-server, LM Studio, etc.) instead of api.openai.com / api.groq.com. Closes RightNow-AI#1051. ## Why Self-hosted, sovereignty-conscious, or rate-limited deployments often need to route audio transcription to a local Whisper backend while keeping `media_transcribe` / `speech_to_text` working as native tools (no helper scripts, no shell_exec workarounds). Today the URLs in `media_understanding.rs:118-128` are literal `&'static str` so neither `OPENAI_BASE_URL` nor `provider_urls` (which the LLM drivers do respect) is read for audio. The same problem existed for embeddings and was already addressable via `provider_urls`, so this change keeps the pattern symmetric for media at the simplest possible surface area. ## Wire format The endpoint shape and Authorization header remain identical: POST <audio_base_url>/v1/audio/transcriptions Authorization: Bearer $<provider>_API_KEY Content-Type: multipart/form-data fields: file (binary), model, response_format=text This means **any OpenAI-compatible Whisper server is drop-in** (Speaches, faster-whisper-server, LM Studio's Whisper server, etc.). Local servers typically accept any non-empty bearer string, so users can keep `OPENAI_API_KEY=anything` for the auth header. ## Configuration ```toml [media] audio_provider = "openai" audio_base_url = "http://127.0.0.1:8000" # → POST http://127.0.0.1:8000/v1/audio/transcriptions ``` Or for Groq-compatible local servers: ```toml [media] audio_provider = "groq" audio_base_url = "http://127.0.0.1:9000" # → POST http://127.0.0.1:9000/v1/audio/transcriptions ``` Trailing slash on the user-supplied base is stripped to avoid double slashes in the final URL. ## Backward compatibility - `MediaConfig` already uses `#[serde(default)]`, so existing configs without `audio_base_url` deserialize as `None` and behave exactly as before (cloud provider URLs). - `Default` impl extended; `audio_base_url: None`. - `parakeet-mlx` provider path unaffected (it's a separate code branch). - No new dependencies, no breaking changes to public API. ## Tests - `test_media_config_default` extended to assert `audio_base_url.is_none()`. - `test_media_config_audio_base_url_serde_roundtrip` — set + JSON roundtrip. - `test_media_config_backward_compat_no_audio_base_url` — legacy JSON parses with the new field as None. - `test_audio_base_url_override_logic` — pure-function test that exercises the URL building branch (default URLs preserved when unset, override applied for both providers, trailing-slash strip). The runtime branch in `transcribe_audio` was kept as a straight `if Some/else default` rather than a helper function to minimize the diff and keep the patch obviously safe to review. ## Operational note This change does not affect anyone running the cloud provider URLs out of the box. The override is opt-in via a single optional config field. Useful for users like myself running a local Speaches container behind a reverse proxy and a chat-only LLM key (z.ai Coding Plan) that can't satisfy openai.com's audio endpoint. Linked: RightNow-AI#1051 (Configurable STT/TTS/image URLs and local backends). Co-authored-by: Miguel Guerrero <kortux@gmail.com>
…na-appindicator runtime closure (RightNow-AI#1063) Closes RightNow-AI#1092 Four fixes that together make `nix build .#openfang-cli` and `nix build .#openfang-desktop` work on NixOS: 1. perl / clang / pkg-config moved to nativeBuildInputs (fixes openssl-src build failure — this is the bug filed in RightNow-AI#1092) 2. openfang-desktop nativeBuildInputs gets pkg-config + wrapGAppsHook3 (GTK runtime wrappers + webkit2gtk-4.1 .pc discovery) 3. libayatana-appindicator added to desktop buildInputs (tray.rs dlopen at runtime) 4. preFixup hook prefixes LD_LIBRARY_PATH so the dlopen-only library actually ends up in the runtime closure Authored by @Aypex. Supersedes RightNow-AI#1086 (which only fixed item 1).
fix: expose MiniMax in openfang init
…rk-websocket-region fix(feishu): respect region setting for WebSocket endpoint URL
…-heartbeat-touch Stamp last_active in streaming agent loop to prevent heartbeat false-positives
RightNow-AI#1123) * fix(channels): key router on user, not channel (Discord/Slack) Discord and Slack adapters set sender.platform_id to the channel/conversation ID (needed for the send path), so router.resolve(channel, sender.platform_id, ..) was matching peer_id bindings against the channel ID and never finding the user-keyed binding. The sender_user_id() helper already existed but only the rate-limit/authz paths used it; the routing reads did not. Read-path fix: - discord.rs / slack.rs: stash author/user ID in metadata["sender_user_id"] - bridge.rs: route the text and audio paths through sender_user_id(message) - bridge.rs: thread user_id through handle_command() so the 6 CLI slash-command resolves (/new, /compact, /model, /stop, /usage, /think) also key on user. Tests updated for the new signature. Write-path follow-up (set_user_default + broadcast routing) deferred to a separate commit so this change can be validated in isolation. * fix(channels): close write-path keying gap; broadcast user-scoped Completes the router keying fix started in 6a90aa0. The read path resolves on user_id, but four write sites and the broadcast lookup were still keyed on sender.platform_id (channel ID on Discord/Slack), producing the split-keying state that surfaced in GAP-008. - 4 x set_user_default writes (text/audio fallback, /agent existing, /agent spawned) now key on sender_user_id(message) - 2 x broadcast lookups (has_broadcast / resolve_broadcast) switched to sender_user_id(message), matching the upstream test's intent (router.rs:521-547 keys on "vip_user", not a channel) - boot-time log warning on Discord/Slack adapter start: any pre-existing /agent default may need to be re-run once - new test test_handle_command_agent_select_keys_on_user_id_not_ platform_id locks in the round-trip
fix(kernel): avoid crashing idle reactive agents
…cket-scheduled fix(ws): broadcast cron job results to WebSocket clients in real-time
…thread feat(discord): smart auto-thread mode (true/false/smart)
…tem-prompt fix: system prompt and identity handling, and config form hydration
v0.6.9 — security patches Fixed RUSTSEC advisories that broke v0.6.8 CI: - rustls-webpki 0.103.10 -> 0.103.13: CRL parsing panic (RUSTSEC-2026-0104), URI name constraints accepted (RUSTSEC-2026-0098), wildcard name constraints accepted (RUSTSEC-2026-0099) - wasmtime 43.0.1 -> 43.0.2: table allocation panic (RUSTSEC-2026-0114) Plus: - cargo fmt across workspace (CI green) - All v0.6.8 fixes carried forward (RightNow-AI#1097, RightNow-AI#1085, RightNow-AI#1038, RightNow-AI#995, RightNow-AI#1154, RightNow-AI#1170, RightNow-AI#1174, RightNow-AI#1172, RightNow-AI#780, all 4 Codex installer findings)
Upstream v0.6.9 added test fixtures that construct TokenUsage and ApiUsage with input_tokens/output_tokens only, missing the cache_creation_input_tokens and cache_read_input_tokens fields that fork-commit 57e1bba introduced for Anthropic prompt caching. Auto-merge could not detect the shape mismatch because the fixtures were new on the upstream side. Sites fixed: - compactor.rs:897 (TokenUsage in mock CompletionResponse) - anthropic.rs:1099 (ApiUsage in convert_response test) - anthropic.rs:1243 (ApiUsage in convert_response test, encrypted path) Test values are 0 since these fixtures do not exercise the cache path. All 2705 workspace tests pass after the fix. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
yog-dharaskar-ai
pushed a commit
that referenced
this pull request
May 28, 2026
cargo fmt --all caught one inline-array reformatting in crates/openfang-runtime/src/drivers/anthropic.rs:945 (test fixture added in PR #1 test-fixture follow-up commit 801d1fd). This was the sole reason the Format CI job failed on main after the merge. Verified: cargo fmt --all -- --check passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
RightNow-AI/openfang(v0.6.0 → v0.6.9,acf2587e).57e1bba(Anthropic prompt caching + retry-after on 429).cache_*_input_tokensfields to three upstream test fixtures that constructedTokenUsage/ApiUsagewith the pre-cache shape (compactor.rs:897, anthropic.rs:1099, anthropic.rs:1243).Validation (per docs/openfang-update-plan.md §6a–b)
cargo build --release -p openfang-cli— green (7m33s)cargo test --workspace --no-fail-fast— 2705/2705 pass, 0 failurescargo clippy --workspace --all-targets -- -D warnings— green (1m18s)/api/health→{"status":"ok","version":"0.6.9"},/api/agentslists 30 default agents,/api/budgetreturns valid JSON.57e1bbasurvived the auto-merge:cache_controlon system message + last tool entry (anthropic.rs:713, 731)retry_after_mshelper at anthropic.rs:692, used on both 429 pathscache_creation_input_tokens+cache_read_input_tokensonTokenUsage(message.rs:333,337) andApiUsageWhat's deferred (needs SSH to july EC2 host + Anthropic creds)
$OPENFANG_BEARERfrom the EC2 secrets store and access toBrainGnosis/Julyconfigs/pm-agent/agent.toml.cache_read_input_tokens > 0verification — needs a realANTHROPIC_API_KEY.scp+systemctl stop/start openfang).Test plan
agent.tomlreferences (mcp_notion_api_*,memory_read,memory_write,agent_message).julyhost, smoke/hooks/agentand/hooks/wakeagainsthttps://july.linkton.ai.