Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@ ENV CARGO_PROFILE_RELEASE_LTO=${LTO} \
CARGO_PROFILE_RELEASE_CODEGEN_UNITS=${CODEGEN_UNITS}
RUN cargo build --release --bin openfang

FROM rust:1-slim-bookworm
FROM python:3.14-slim-bookworm
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
python3 \
python3-pip \
python3-venv \
nodejs \
npm \
&& rm -rf /var/lib/apt/lists/*
Expand Down
2 changes: 1 addition & 1 deletion crates/openfang-api/src/channel_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ use openfang_channels::discourse::DiscourseAdapter;
use openfang_channels::gitter::GitterAdapter;
use openfang_channels::gotify::GotifyAdapter;
use openfang_channels::linkedin::LinkedInAdapter;
use openfang_channels::mumble::MumbleAdapter;
use openfang_channels::mqtt::MqttAdapter;
use openfang_channels::mumble::MumbleAdapter;
use openfang_channels::ntfy::NtfyAdapter;
use openfang_channels::webhook::WebhookAdapter;
use openfang_channels::wecom::WeComAdapter;
Expand Down
3 changes: 2 additions & 1 deletion crates/openfang-api/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,8 @@ pub async fn get_agent_session(
msg.get_mut("tools").and_then(|v| v.as_array_mut())
{
if let Some(tool_obj) = tools_arr.get_mut(tool_idx) {
tool_obj["result"] = serde_json::Value::String(result.clone());
tool_obj["result"] =
serde_json::Value::String(result.clone());
tool_obj["is_error"] =
serde_json::Value::Bool(*is_error);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/openfang-channels/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ pub mod discourse;
pub mod gitter;
pub mod gotify;
pub mod linkedin;
pub mod mumble;
pub mod mqtt;
pub mod mumble;
pub mod ntfy;
pub mod webhook;
pub mod wecom;
8 changes: 3 additions & 5 deletions crates/openfang-channels/src/line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ impl LineAdapter {
diff |= a ^ b;
}
if diff != 0 {
let computed = base64::engine::general_purpose::STANDARD.encode(&result);
let computed = base64::engine::general_purpose::STANDARD.encode(result);
// Log first/last 4 chars of each signature for debugging without leaking full HMAC
let comp_redacted = format!(
"{}...{}",
Expand Down Expand Up @@ -381,8 +381,7 @@ impl ChannelAdapter for LineAdapter {
axum::routing::post({
let secret = Arc::clone(&channel_secret);
let tx = Arc::clone(&tx);
move |headers: axum::http::HeaderMap,
body: axum::body::Bytes| {
move |headers: axum::http::HeaderMap, body: axum::body::Bytes| {
let secret = Arc::clone(&secret);
let tx = Arc::clone(&tx);
async move {
Expand All @@ -404,8 +403,7 @@ impl ChannelAdapter for LineAdapter {
shutdown_rx: watch::channel(false).1,
};

if !signature.is_empty()
&& !adapter.verify_signature(&body, signature)
if !signature.is_empty() && !adapter.verify_signature(&body, signature)
{
warn!("LINE: invalid webhook signature");
return axum::http::StatusCode::UNAUTHORIZED;
Expand Down
8 changes: 6 additions & 2 deletions crates/openfang-channels/src/mqtt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,10 @@ impl MqttAdapter {
}

/// Parse host:port string.
fn parse_host_port(s: &str, default_port: u16) -> Result<(String, u16), Box<dyn std::error::Error>> {
fn parse_host_port(
s: &str,
default_port: u16,
) -> Result<(String, u16), Box<dyn std::error::Error>> {
let s = s.trim();
if let Some(colon_pos) = s.rfind(':') {
let host = s[..colon_pos].to_string();
Expand Down Expand Up @@ -239,7 +242,8 @@ impl ChannelAdapter for MqttAdapter {

async fn start(
&self,
) -> Result<Pin<Box<dyn Stream<Item = ChannelMessage> + Send>>, Box<dyn std::error::Error>> {
) -> Result<Pin<Box<dyn Stream<Item = ChannelMessage> + Send>>, Box<dyn std::error::Error>>
{
let options = self.build_mqtt_options()?;
let (client, mut eventloop) = AsyncClient::new(options, 10);

Expand Down
10 changes: 8 additions & 2 deletions crates/openfang-kernel/src/heartbeat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,17 @@ pub fn check_agents(registry: &AgentRegistry, config: &HeartbeatConfig) -> Vec<H

let inactive_secs = (now - entry_ref.last_active).num_seconds();

// Determine timeout: use agent's autonomous config if set, else default
// Determine timeout: use agent's autonomous config if set, else default.
// The timeout must be at least as long as the agent's max_tick_duration
// to avoid marking agents as unresponsive during long PTC/execute_code runs.
let timeout_secs = entry_ref
.manifest
.autonomous
.as_ref()
.map(|a| a.heartbeat_interval_secs * UNRESPONSIVE_MULTIPLIER)
.map(|a| {
let heartbeat_timeout = a.heartbeat_interval_secs * UNRESPONSIVE_MULTIPLIER;
heartbeat_timeout.max(a.max_tick_duration_secs)
})
.unwrap_or(config.default_timeout_secs) as i64;

// --- Skip idle agents that have never genuinely processed a message ---
Expand Down Expand Up @@ -335,6 +340,7 @@ mod tests {
exec_policy: None,
tool_allowlist: vec![],
tool_blocklist: vec![],
ptc_enabled: None,
},
state,
mode: AgentMode::default(),
Expand Down
58 changes: 42 additions & 16 deletions crates/openfang-kernel/src/kernel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1903,6 +1903,7 @@ impl OpenFangKernel {
),
sender_id,
sender_name,
ptc_enabled: manifest.ptc_enabled.unwrap_or(self.config.ptc.enabled),
};
manifest.model.system_prompt =
openfang_runtime::prompt_builder::build_system_prompt(&prompt_ctx);
Expand Down Expand Up @@ -2464,6 +2465,7 @@ impl OpenFangKernel {
),
sender_id,
sender_name,
ptc_enabled: manifest.ptc_enabled.unwrap_or(self.config.ptc.enabled),
};
manifest.model.system_prompt =
openfang_runtime::prompt_builder::build_system_prompt(&prompt_ctx);
Expand Down Expand Up @@ -2889,20 +2891,16 @@ impl OpenFangKernel {
model: &str,
explicit_provider: Option<&str>,
) -> KernelResult<()> {
let catalog_entry = self
.model_catalog
.read()
.ok()
.and_then(|catalog| {
// When the caller specifies a provider, use provider-aware lookup
// so we resolve the model on the correct provider — not a builtin
// from a different provider that happens to share the same name (#833).
if let Some(ep) = explicit_provider {
catalog.find_model_for_provider(model, ep).cloned()
} else {
catalog.find_model(model).cloned()
}
});
let catalog_entry = self.model_catalog.read().ok().and_then(|catalog| {
// When the caller specifies a provider, use provider-aware lookup
// so we resolve the model on the correct provider — not a builtin
// from a different provider that happens to share the same name (#833).
if let Some(ep) = explicit_provider {
catalog.find_model_for_provider(model, ep).cloned()
} else {
catalog.find_model(model).cloned()
}
});
let provider = if let Some(ep) = explicit_provider {
// User explicitly set the provider — use it as-is
Some(ep.to_string())
Expand Down Expand Up @@ -3329,6 +3327,7 @@ impl OpenFangKernel {
system_prompt: def.agent.system_prompt.clone(),
api_key_env: def.agent.api_key_env.clone(),
base_url: def.agent.base_url.clone(),
thinking: None,
},
capabilities: ManifestCapabilities {
tools: def.tools.clone(),
Expand Down Expand Up @@ -3369,7 +3368,15 @@ impl OpenFangKernel {
} else {
None
},
// Do NOT set tool_allowlist here — capabilities.tools (line 3223) already
// provides the primary tool filter in available_tools() Step 1.
// Setting tool_allowlist to def.tools would cause Step 4 to strip out
// MCP tools that were correctly added in Step 3 via mcp_servers opt-in,
// because MCP tool names (mcp_github_*, etc.) are dynamic and not in
// the hand's static tool list.
tool_allowlist: Vec::new(),
tool_blocklist: Vec::new(),
ptc_enabled: None,
// Custom profile avoids ToolProfile-based expansion overriding the
// explicit tool list.
profile: if !def.tools.is_empty() {
Expand Down Expand Up @@ -5173,9 +5180,20 @@ impl OpenFangKernel {
.cloned()
.collect()
};
// When an agent explicitly lists MCP servers via `mcp_servers`,
// include all tools from those servers without requiring each
// tool name in the declared tools list. MCP tool names are
// dynamic and change when the upstream server updates, so
// `mcp_servers` acts as the opt-in for the entire server's
// tool set. If the agent does NOT list mcp_servers (empty
// allowlist → all MCP tools are candidates), fall back to the
// declared-tools filter to avoid flooding the context.
let mcp_explicitly_opted_in = !mcp_allowlist.is_empty();
for t in mcp_candidates {
// If agent declares specific tools, only include matching MCP tools
if !tools_unrestricted && !declared_tools.iter().any(|d| d == &t.name) {
if !tools_unrestricted
&& !mcp_explicitly_opted_in
&& !declared_tools.iter().any(|d| d == &t.name)
{
continue;
}
all_tools.push(t);
Expand Down Expand Up @@ -5917,6 +5935,12 @@ impl KernelHandle for OpenFangKernel {
OpenFangKernel::kill_agent(self, id).map_err(|e| format!("Kill failed: {e}"))
}

fn touch_active(&self, agent_id: &str) {
if let Ok(id) = agent_id.parse::<AgentId>() {
let _ = self.registry.set_state(id, AgentState::Running);
}
}

fn memory_store(&self, key: &str, value: serde_json::Value) -> Result<(), String> {
let agent_id = shared_memory_agent_id();
self.memory
Expand Down Expand Up @@ -6631,6 +6655,7 @@ mod tests {
exec_policy: None,
tool_allowlist: vec![],
tool_blocklist: vec![],
ptc_enabled: None,
};
manifest.capabilities.tools = vec!["file_read".to_string(), "web_fetch".to_string()];
manifest.capabilities.agent_spawn = true;
Expand Down Expand Up @@ -6668,6 +6693,7 @@ mod tests {
exec_policy: None,
tool_allowlist: vec![],
tool_blocklist: vec![],
ptc_enabled: None,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/openfang-kernel/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ mod tests {
exec_policy: None,
tool_allowlist: vec![],
tool_blocklist: vec![],
ptc_enabled: None,
},
state: AgentState::Created,
mode: AgentMode::default(),
Expand Down
2 changes: 2 additions & 0 deletions crates/openfang-kernel/src/wizard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ impl SetupWizard {
system_prompt,
api_key_env: None,
base_url: None,
thinking: None,
},
resources: ResourceQuota::default(),
priority: Priority::default(),
Expand All @@ -182,6 +183,7 @@ impl SetupWizard {
exec_policy: None,
tool_allowlist: vec![],
tool_blocklist: vec![],
ptc_enabled: None,
};

let skills_to_install: Vec<String> = intent
Expand Down
Loading
Loading