Commit Graph
461 Commits
Author SHA1 Message Date
Jaber JaberandGitHub f52bc53e47 Merge pull request #1109 from RightNow-AI/dependabot/cargo/uuid-1.23.1
build(deps): bump uuid from 1.23.0 to 1.23.1
2026-04-29 16:10:05 +03:00
Jaber JaberandGitHub 10f7ee1885 Merge pull request #1060 from ferr079/fix/unify-ssrf-protection
fix(security): unify SSRF protection for WASM host calls
2026-04-29 16:02:51 +03:00
Jaber JaberandGitHub 7bc6591338 Merge pull request #1058 from lc-soft/fix/trader-dashboard-style
fix(hands): correct trader dashboard style
2026-04-29 15:45:05 +03:00
53f2066945 chore: add health stack and stabilize provider env tests (#1126)
Co-authored-by: jack-wz01 <15474862+jack-wz01@user.noreply.gitee.com>
2026-04-29 15:45:01 +03:00
Jaber JaberandGitHub c69dd84184 Merge pull request #1095 from Streamweaver/fix/mcp-stdio-env-passthrough-linux
fix(runtime): pass HOME/TMP/TEMP to stdio MCP servers on all platforms
2026-04-29 15:44:05 +03:00
Jaber JaberandGitHub c1356fc95d Merge pull request #1100 from Streamweaver/fix/telegram-silent-failures
channels/telegram: propagate send failures and cache terminal reaction errors
2026-04-29 15:43:42 +03:00
jaberjaber23 aabf83b351 bump v0.6.1 v0.6.1 2026-04-29 15:30:46 +03:00
jaberjaber23 da6b567ac3 manifest merge 2026-04-29 15:19:41 +03:00
jaberjaber23 ccdd7943a2 fix clippy 2026-04-29 15:14:42 +03:00
jaberjaber23 7fe87babe6 preserve workspace 2026-04-29 15:08:44 +03:00
jaberjaber23 9c0e1637a5 fmt drift 2026-04-29 15:07:22 +03:00
jaberjaber23 fd450dbfc2 fmt cleanup 2026-04-29 15:06:17 +03:00
Jaber JaberandGitHub 81176dc626 Merge pull request #1130 from benhoverter/fix/message-timeout-config
fix(runtime): add subprocess timeout config for claude-code driver
2026-04-29 15:03:10 +03:00
ef9096f7c5 fix(kernel): sync all agent.toml fields to DB on restart (fixes #1087) (#1118)
The TOML-vs-DB change detection at boot only checked a subset of fields,
causing edits to workspace, schedule, resources, autonomous, and exec_policy
to be silently ignored after a restart.

Add the missing fields to the changed-detection predicate so the kernel
properly reflects all agent.toml edits in the SQL database. The workspace
comparison is intentionally guarded — if the TOML omits workspace (None),
the kernel-assigned default path already stored in the DB is kept rather
than being overwritten with None.

Derive PartialEq on ScheduleMode, AutonomousConfig, ResourceQuota, and
ExecPolicy to enable the comparisons without manual field-by-field expansion.

Co-authored-by: octo-patch <octo-patch@github.com>
2026-04-29 14:50:38 +03:00
Jaber JaberandGitHub fbb5bb1ae9 Merge pull request #1099 from nimitbhardwaj/fix#1088/websocket-scheduled
fix(ws): broadcast cron job results to WebSocket clients in real-time
2026-04-29 14:47:39 +03:00
Jaber JaberandGitHub c435a6adcd Merge pull request #1114 from pandego/fix/1102-idle-heartbeat
fix(kernel): avoid crashing idle reactive agents
2026-04-29 14:46:15 +03:00
Ben HoverterandGitHub f67c4e8754 fix(channels): key router default-agent map on user_id, not channel_id (#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
2026-04-29 14:40:36 +03:00
Jaber JaberandGitHub 3b237ac526 Merge pull request #1090 from chrisyoung2005/fix/streaming-heartbeat-touch
Stamp last_active in streaming agent loop to prevent heartbeat false-positives
2026-04-29 14:39:28 +03:00
Jaber JaberandGitHub 96c572df32 Merge pull request #1082 from octo-patch/fix/issue-1081-lark-websocket-region
fix(feishu): respect region setting for WebSocket endpoint URL
2026-04-29 14:38:12 +03:00
Jaber JaberandGitHub 17e0d519ca Merge pull request #1080 from pandego/fix/1079-minimax-init
fix: expose MiniMax in openfang init
2026-04-29 14:30:05 +03:00
Charles HakesandGitHub 37c233d489 fix(flake): NixOS build — nativeBuildInputs, wrapGAppsHook3, libayatana-appindicator runtime closure (#1063)
Closes #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 #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 #1086 (which only fixed item 1).
2026-04-29 14:29:24 +03:00
92f7e996de feat(media): add audio_base_url override for local OpenAI-compat Whisper (#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 #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: #1051 (Configurable STT/TTS/image URLs and local backends).

Co-authored-by: Miguel Guerrero <kortux@gmail.com>
2026-04-29 14:28:04 +03:00
Ben Hoverter b1c4061247 fix(runtime): wire subprocess_timeout_secs through config.toml
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.
2026-04-27 23:40:12 -07:00
Ben Hoverter 79aa34c77a fix(runtime): add subprocess timeout config for claude-code driver
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.
2026-04-27 23:17:41 -07:00
Ben Hoverter 4ae2961b1c 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
2026-04-26 16:14:17 -07:00
Ben Hoverter 6a90aa08df 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.
2026-04-26 12:41:06 -07:00
pandego 356500bb1e fix(kernel): ignore idle reactive heartbeat silence 2026-04-23 18:42:47 +02:00
dependabot[bot]andGitHub 84d90ad342 build(deps): bump uuid from 1.23.0 to 1.23.1
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](https://github.com/uuid-rs/uuid/compare/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>
2026-04-23 08:23:16 +00:00
Scott Turnbull 9fee63d58c channels/telegram: cache terminal setMessageReaction errors per (chat, 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.
2026-04-21 18:03:23 -04:00
Scott Turnbull 40903cceee channels/telegram: propagate send failures from api_send_* helpers
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).
2026-04-21 18:01:24 -04:00
Nimit Bhardwaj 5a86141677 fix(ws): broadcast cron job results to WebSocket clients in real-time
Fixes #1088 - scheduled task results now appear in web UI without page
refresh.
2026-04-21 22:34:36 +05:30
Scott Turnbull e97eb6fff3 fix(runtime): pass HOME/TMP/TEMP to stdio MCP servers on all platforms
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.
2026-04-20 12:24:33 -04:00
chris-youngandClaude Sonnet 4.6 f2587995a2 Stamp last_active in streaming agent loop to prevent heartbeat false-positives
Fixes #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>
2026-04-19 20:59:47 -07:00
jaberjaber23 e6bab993ae bump v0.6.0 v0.6.0 2026-04-19 22:57:55 +03:00
jaberjaber23 a39a675ba9 skill config ui 2026-04-19 22:27:23 +03:00
jaberjaber23 0ce390e09f cron delivery ui 2026-04-19 21:53:20 +03:00
jaberjaber23 88eeaa6a4d commands ui 2026-04-19 21:42:33 +03:00
jaberjaber23 3db5d3a825 cron delivery 2026-04-19 17:10:23 +03:00
jaberjaber23 5a1f372612 command registry 2026-04-19 17:08:11 +03:00
jaberjaber23 a9f15b2d23 skill config 2026-04-19 16:54:11 +03:00
octo-patch 45e7ea7948 fix(feishu): respect region setting for WebSocket endpoint URL
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 #1081
2026-04-19 11:23:46 +08:00
StephaneandClaude Code de8a692036 fix(security): unify SSRF protection for WASM host calls
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.
2026-04-18 14:22:12 +02:00
pandego 8d3d77dd99 fix: expose MiniMax in init provider lists 2026-04-18 13:26:28 +02:00
jaberjaber23 d3d9fa842d release: v0.5.10
Bump workspace version to 0.5.10 and refresh docs.

Bundles the 7 fixes merged on main since v0.5.9:
- #1034 auth fail-closed (#1071)
- #980  channel agent name prefix (#1072)
- #1043 multimodal text+images (#1073)
- #809  openfang hand config subcommand (#1074)
- #843  context.md re-read per turn (#1075)
- #905  config get default_model.base_url (#1076)
- #1069 scheduler unification and migration (#1077)

README: fix stale 0.3.30 badge and March 2026 header to 0.5.10 and April 2026,
drop em dashes throughout.

CHANGELOG: new 0.5.10 section with the above, plus notes on #818 and #819
which were closed as invalid.
v0.5.10
2026-04-17 22:54:56 +03:00
Jaber JaberandGitHub ff44cfbe87 fix(scheduler): route schedule_* tools and /api/schedules through kernel cron scheduler (#1069) (#1077)
The schedule_create tool, its sibling schedule_list and schedule_delete,
and the matching /api/schedules HTTP routes were all writing to a
shared-memory key that no executor ever read. Jobs registered that way
silently never fired.

Route all three tools and all /api/schedules endpoints through the real
cron scheduler in openfang-kernel. Add a one-shot idempotent migration
at kernel startup that imports legacy __openfang_schedules entries into
the cron scheduler and clears the old key.

Tests:
- Unit tests for sanitize_schedule_name and sanitize_cron_job_name
- Tool wrapper tests using a fake KernelHandle that verify
  schedule_create/list/delete route into cron_create/list/cancel
- Migration tests cover the happy path, idempotency via the marker key,
  and skipping entries whose target agent is not in the registry

Quality gates: cargo check + test + clippy -D warnings + fmt clean on
openfang-kernel, openfang-runtime, openfang-api.

Made-with: Cursor
2026-04-17 22:47:11 +03:00
Jaber JaberandGitHub ce89d05987 fix(runtime): combine text and image blocks in multimodal user messages (#1043) (#1073)
When an upload supplied image content blocks alongside the user's text, the agent loop pushed only the image blocks and dropped the text. The LLM saw images with no accompanying prompt.

Build the user turn through a single helper that combines text and image blocks into one multimodal message when both are present, and keeps the existing single-mode representation when only one is supplied. Both the streaming and non-streaming paths now share the same builder.

Closes #1043

Made-with: Cursor
2026-04-17 22:47:08 +03:00
Jaber JaberandGitHub 00c0ff60de feat(channels): optional agent name prefix on outbound messages (#980) (#1072)
Adds an opt-in per-channel knob prefix_agent_name on ChannelOverrides
with styles Off (default), Bracket ([agent] text) and BoldBracket
(**[agent]** text).

The bridge wraps the final outbound text once in dispatch_message,
dispatch_with_blocks and the auto-reply path. Off is byte-identical
to pre-feature behavior so existing configs are unaffected.

Platform-native identity overrides (Slack per-message username,
Discord embed author field) are intentionally out of scope here and
tracked as a follow-up.

Made-with: Cursor
2026-04-17 22:47:04 +03:00
Jaber JaberandGitHub 07af248a07 fix(cli): config get returns base_url from default_model (#905) (#1076)
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
2026-04-17 22:46:08 +03:00
Jaber JaberandGitHub 6ab07d155e fix(runtime): re-read agent context.md per turn so external updates take effect (#843) (#1075)
When an agent had a context.md file updated externally (e.g. a cron job
refreshing live market data), the updated content never reached the LLM
during an active session. The file was effectively cached for the
lifetime of the conversation.

The runtime now reads context.md from the agent workspace once per turn,
right before the system prompt is built, and injects it as a dedicated
'Live Context' section. Agents that still want the old cache-at-start
behaviour can opt back in with 'cache_context = true' on the manifest.

- new openfang-runtime::agent_context module with a small per-path cache
- if a re-read fails after a previous success, fall back to the cached
  content with a warning instead of dropping context mid-conversation
- new PromptContext.context_md field wired up in both kernel streaming
  and non-streaming paths
- one small disk read per agent turn (not per streaming token); file
  size capped at 32 KB like the other identity files

Made-with: Cursor
2026-04-17 22:46:05 +03:00
Jaber JaberandGitHub e2b0a54720 feat(cli): add hand config subcommand (#809) (#1074)
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
2026-04-17 22:46:01 +03:00