issue fixes

This commit is contained in:
jaberjaber23
2026-02-26 14:20:41 +03:00
parent a8fae5f711
commit 9728eb1118
11 changed files with 152 additions and 16 deletions

View File

@@ -138,10 +138,10 @@ jobs:
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
os: ubuntu-22.04
archive: tar.gz
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
os: ubuntu-22.04
archive: tar.gz
- target: x86_64-apple-darwin
os: macos-latest

View File

@@ -0,0 +1,56 @@
//! Compile-time embedded agent templates.
//!
//! All 30 bundled agent templates are embedded into the binary via `include_str!`.
//! This ensures `openfang agent new` works immediately after install — no filesystem
//! discovery needed.
/// Returns all bundled agent templates as `(name, toml_content)` pairs.
pub fn bundled_agents() -> Vec<(&'static str, &'static str)> {
vec![
("analyst", include_str!("../../../agents/analyst/agent.toml")),
("architect", include_str!("../../../agents/architect/agent.toml")),
("assistant", include_str!("../../../agents/assistant/agent.toml")),
("coder", include_str!("../../../agents/coder/agent.toml")),
("code-reviewer", include_str!("../../../agents/code-reviewer/agent.toml")),
("customer-support", include_str!("../../../agents/customer-support/agent.toml")),
("data-scientist", include_str!("../../../agents/data-scientist/agent.toml")),
("debugger", include_str!("../../../agents/debugger/agent.toml")),
("devops-lead", include_str!("../../../agents/devops-lead/agent.toml")),
("doc-writer", include_str!("../../../agents/doc-writer/agent.toml")),
("email-assistant", include_str!("../../../agents/email-assistant/agent.toml")),
("health-tracker", include_str!("../../../agents/health-tracker/agent.toml")),
("hello-world", include_str!("../../../agents/hello-world/agent.toml")),
("home-automation", include_str!("../../../agents/home-automation/agent.toml")),
("legal-assistant", include_str!("../../../agents/legal-assistant/agent.toml")),
("meeting-assistant", include_str!("../../../agents/meeting-assistant/agent.toml")),
("ops", include_str!("../../../agents/ops/agent.toml")),
("orchestrator", include_str!("../../../agents/orchestrator/agent.toml")),
("personal-finance", include_str!("../../../agents/personal-finance/agent.toml")),
("planner", include_str!("../../../agents/planner/agent.toml")),
("recruiter", include_str!("../../../agents/recruiter/agent.toml")),
("researcher", include_str!("../../../agents/researcher/agent.toml")),
("sales-assistant", include_str!("../../../agents/sales-assistant/agent.toml")),
("security-auditor", include_str!("../../../agents/security-auditor/agent.toml")),
("social-media", include_str!("../../../agents/social-media/agent.toml")),
("test-engineer", include_str!("../../../agents/test-engineer/agent.toml")),
("translator", include_str!("../../../agents/translator/agent.toml")),
("travel-planner", include_str!("../../../agents/travel-planner/agent.toml")),
("tutor", include_str!("../../../agents/tutor/agent.toml")),
("writer", include_str!("../../../agents/writer/agent.toml")),
]
}
/// Install bundled agent templates to `~/.openfang/agents/`.
/// Skips any template that already exists on disk (user customization preserved).
pub fn install_bundled_agents(agents_dir: &std::path::Path) {
for (name, content) in bundled_agents() {
let dest_dir = agents_dir.join(name);
let dest_file = dest_dir.join("agent.toml");
if dest_file.exists() {
continue; // Preserve user customization
}
if std::fs::create_dir_all(&dest_dir).is_ok() {
let _ = std::fs::write(&dest_file, content);
}
}
}

View File

@@ -11,19 +11,32 @@ pub fn env_file_path() -> Option<PathBuf> {
dirs::home_dir().map(|h| h.join(".openfang").join(".env"))
}
/// Load `~/.openfang/.env` into `std::env`.
/// Load `~/.openfang/.env` and `~/.openfang/secrets.env` into `std::env`.
///
/// System env vars take priority — existing vars are NOT overridden.
/// Silently does nothing if the file doesn't exist.
/// `secrets.env` is loaded second so `.env` values take priority over secrets
/// (but both yield to system env vars).
/// Silently does nothing if the files don't exist.
pub fn load_dotenv() {
let path = match env_file_path() {
load_env_file(env_file_path());
// Also load secrets.env (written by dashboard "Set API Key" button)
load_env_file(secrets_env_path());
}
/// Return the path to `~/.openfang/secrets.env`.
pub fn secrets_env_path() -> Option<PathBuf> {
dirs::home_dir().map(|h| h.join(".openfang").join("secrets.env"))
}
fn load_env_file(path: Option<PathBuf>) {
let path = match path {
Some(p) => p,
None => return,
};
let content = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => return, // file doesn't exist or unreadable — that's fine
Err(_) => return,
};
for line in content.lines() {
@@ -33,7 +46,6 @@ pub fn load_dotenv() {
}
if let Some((key, value)) = parse_env_line(trimmed) {
// Only set if not already in environment (system env takes priority)
if std::env::var(&key).is_err() {
std::env::set_var(&key, &value);
}

View File

@@ -3,6 +3,7 @@
//! When a daemon is running (`openfang start`), the CLI talks to it over HTTP.
//! Otherwise, commands boot an in-process kernel (single-shot mode).
mod bundled_agents;
mod dotenv;
mod launcher;
mod mcp;
@@ -1051,6 +1052,9 @@ fn cmd_init(quick: bool) {
}
}
// Install bundled agent templates (skips existing ones to preserve user edits)
bundled_agents::install_bundled_agents(&openfang_dir.join("agents"));
if quick {
cmd_init_quick(&openfang_dir);
} else {

View File

@@ -54,11 +54,12 @@ pub fn discover_template_dirs() -> Vec<PathBuf> {
dirs
}
/// Load all templates from discovered directories.
/// Load all templates from discovered directories, falling back to bundled templates.
pub fn load_all_templates() -> Vec<AgentTemplate> {
let mut templates = Vec::new();
let mut seen_names = std::collections::HashSet::new();
// First: load from filesystem (user-installed or dev repo)
for dir in discover_template_dirs() {
if let Ok(entries) = std::fs::read_dir(&dir) {
for entry in entries.flatten() {
@@ -86,6 +87,18 @@ pub fn load_all_templates() -> Vec<AgentTemplate> {
}
}
// Fallback: load bundled templates for any not found on disk
for (name, content) in crate::bundled_agents::bundled_agents() {
if seen_names.insert(name.to_string()) {
let description = extract_description(content);
templates.push(AgentTemplate {
name: name.to_string(),
description,
content: content.to_string(),
});
}
}
templates.sort_by(|a, b| a.name.cmp(&b.name));
templates
}

View File

@@ -958,6 +958,18 @@ impl OpenFangKernel {
manifest.exec_policy = Some(self.config.exec_policy.clone());
}
// Overlay kernel default_model onto agent if no custom key/url is set.
// This ensures agents respect the user's configured provider from `openfang init`.
if manifest.model.api_key_env.is_none() && manifest.model.base_url.is_none() {
let dm = &self.config.default_model;
if !dm.provider.is_empty() {
manifest.model.provider = dm.provider.clone();
}
if !dm.model.is_empty() {
manifest.model.model = dm.model.clone();
}
}
// Create workspace directory for the agent
let workspace_dir = manifest.workspace.clone().unwrap_or_else(|| {
self.config.effective_workspaces_dir().join(format!(

View File

@@ -14,8 +14,10 @@ use crate::llm_driver::{DriverConfig, LlmDriver, LlmError};
use openfang_types::model_catalog::{
AI21_BASE_URL, ANTHROPIC_BASE_URL, CEREBRAS_BASE_URL, COHERE_BASE_URL, DEEPSEEK_BASE_URL,
FIREWORKS_BASE_URL, GEMINI_BASE_URL, GROQ_BASE_URL, HUGGINGFACE_BASE_URL, LMSTUDIO_BASE_URL,
MISTRAL_BASE_URL, OLLAMA_BASE_URL, OPENAI_BASE_URL, OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL,
MINIMAX_BASE_URL, MISTRAL_BASE_URL, MOONSHOT_BASE_URL, OLLAMA_BASE_URL, OPENAI_BASE_URL,
OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL, QIANFAN_BASE_URL, QWEN_BASE_URL,
REPLICATE_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VLLM_BASE_URL, XAI_BASE_URL,
ZHIPU_BASE_URL,
};
use std::sync::Arc;
@@ -130,6 +132,31 @@ fn provider_defaults(provider: &str) -> Option<ProviderDefaults> {
api_key_env: "GITHUB_TOKEN",
key_required: true,
}),
"moonshot" | "kimi" => Some(ProviderDefaults {
base_url: MOONSHOT_BASE_URL,
api_key_env: "MOONSHOT_API_KEY",
key_required: true,
}),
"qwen" | "dashscope" => Some(ProviderDefaults {
base_url: QWEN_BASE_URL,
api_key_env: "DASHSCOPE_API_KEY",
key_required: true,
}),
"minimax" => Some(ProviderDefaults {
base_url: MINIMAX_BASE_URL,
api_key_env: "MINIMAX_API_KEY",
key_required: true,
}),
"zhipu" | "glm" => Some(ProviderDefaults {
base_url: ZHIPU_BASE_URL,
api_key_env: "ZHIPU_API_KEY",
key_required: true,
}),
"qianfan" | "baidu" => Some(ProviderDefaults {
base_url: QIANFAN_BASE_URL,
api_key_env: "QIANFAN_API_KEY",
key_required: true,
}),
_ => None,
}
}
@@ -286,6 +313,11 @@ pub fn known_providers() -> &'static [&'static str] {
"xai",
"replicate",
"github-copilot",
"moonshot",
"qwen",
"minimax",
"zhipu",
"qianfan",
]
}
@@ -373,7 +405,12 @@ mod tests {
assert!(providers.contains(&"xai"));
assert!(providers.contains(&"replicate"));
assert!(providers.contains(&"github-copilot"));
assert_eq!(providers.len(), 21);
assert!(providers.contains(&"moonshot"));
assert!(providers.contains(&"qwen"));
assert!(providers.contains(&"minimax"));
assert!(providers.contains(&"zhipu"));
assert!(providers.contains(&"qianfan"));
assert_eq!(providers.len(), 26);
}
#[test]

View File

@@ -48,7 +48,6 @@ pub enum SkillError {
#[serde(rename_all = "lowercase")]
pub enum SkillRuntime {
/// Python script executed in subprocess.
#[default]
Python,
/// WASM module executed in sandbox.
Wasm,
@@ -58,6 +57,7 @@ pub enum SkillRuntime {
Builtin,
/// Prompt-only skill: injects context into the LLM system prompt.
/// No executable code — the Markdown body teaches the LLM.
#[default]
PromptOnly,
}
@@ -101,7 +101,8 @@ pub struct SkillRequirements {
pub struct SkillManifest {
/// Skill metadata.
pub skill: SkillMeta,
/// Runtime configuration.
/// Runtime configuration (defaults to PromptOnly if omitted).
#[serde(default)]
pub runtime: SkillRuntimeConfig,
/// Tools provided by this skill.
#[serde(default)]
@@ -144,7 +145,7 @@ fn default_version() -> String {
}
/// Runtime configuration section.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SkillRuntimeConfig {
/// Runtime type.
#[serde(rename = "type", default)]

View File

@@ -929,7 +929,8 @@ pub struct KernelConfig {
pub data_dir: PathBuf,
/// Log level (trace, debug, info, warn, error).
pub log_level: String,
/// gRPC API listen address.
/// API listen address (e.g., "0.0.0.0:4200").
#[serde(alias = "listen_addr")]
pub api_listen: String,
/// Whether to enable the OFP network layer.
pub network_enabled: bool,

View File

@@ -2,7 +2,7 @@ version: "3.8"
services:
openfang:
build: .
image: ghcr.io/RightNow-AI/openfang:latest
image: ghcr.io/rightnow-ai/openfang:latest
ports:
- "4200:4200"
volumes:

View File

@@ -3,7 +3,7 @@
# API server settings
# api_key = "" # Set to enable Bearer auth (recommended)
# listen_addr = "127.0.0.1:3000" # HTTP API bind address
# api_listen = "127.0.0.1:50051" # HTTP API bind address (use 0.0.0.0 for public)
[default_model]
provider = "anthropic" # "anthropic", "gemini", "openai", "groq", "ollama", etc.