mirror of
https://github.com/RightNow-AI/openfang.git
synced 2026-04-25 17:25:11 +02:00
issue fixes
This commit is contained in:
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
56
crates/openfang-cli/src/bundled_agents.rs
Normal file
56
crates/openfang-cli/src/bundled_agents.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user