mirror of
https://github.com/RightNow-AI/openfang.git
synced 2026-07-30 06:32:17 +00:00
bugfixes batch
This commit is contained in:
Generated
+14
-14
@@ -3866,7 +3866,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-api"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
@@ -3902,7 +3902,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-channels"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
@@ -3933,7 +3933,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-cli"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
@@ -3960,7 +3960,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-desktop"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"open",
|
||||
@@ -3986,7 +3986,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-extensions"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"argon2",
|
||||
@@ -4014,7 +4014,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-hands"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dashmap",
|
||||
@@ -4031,7 +4031,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-kernel"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -4067,7 +4067,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-memory"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -4086,7 +4086,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-migrate"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dirs 6.0.0",
|
||||
@@ -4105,7 +4105,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-runtime"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -4137,7 +4137,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-skills"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
@@ -4160,7 +4160,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-types"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -4179,7 +4179,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-wire"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -8791,7 +8791,7 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.3.6"
|
||||
version = "0.3.7"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0 OR MIT"
|
||||
repository = "https://github.com/RightNow-AI/openfang"
|
||||
|
||||
@@ -1959,7 +1959,7 @@ pub async fn configure_channel(
|
||||
let home = openfang_kernel::config::openfang_home();
|
||||
let secrets_path = home.join("secrets.env");
|
||||
let config_path = home.join("config.toml");
|
||||
let mut config_fields: HashMap<String, String> = HashMap::new();
|
||||
let mut config_fields: HashMap<String, (String, FieldType)> = HashMap::new();
|
||||
|
||||
for field_def in meta.fields {
|
||||
let value = fields
|
||||
@@ -1983,8 +1983,8 @@ pub async fn configure_channel(
|
||||
std::env::set_var(env_var, value);
|
||||
}
|
||||
} else {
|
||||
// Config field — collect for TOML write
|
||||
config_fields.insert(field_def.key.to_string(), value.to_string());
|
||||
// Config field — collect for TOML write with type info
|
||||
config_fields.insert(field_def.key.to_string(), (value.to_string(), field_def.field_type));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2455,19 +2455,14 @@ pub async fn get_template(Path(name): Path<String>) -> impl IntoResponse {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// GET /api/memory/agents/:id/kv — List KV pairs for an agent.
|
||||
///
|
||||
/// Note: memory_store tool writes to a shared namespace, so we read from that
|
||||
/// same namespace regardless of which agent ID is in the URL.
|
||||
pub async fn get_agent_kv(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(id): Path<String>,
|
||||
Path(_id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
let agent_id: AgentId = match id.parse() {
|
||||
Ok(id) => id,
|
||||
Err(_) => {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(serde_json::json!({"error": "Invalid agent ID"})),
|
||||
);
|
||||
}
|
||||
};
|
||||
let agent_id = openfang_kernel::kernel::shared_memory_agent_id();
|
||||
|
||||
match state.kernel.memory.list_kv(agent_id) {
|
||||
Ok(pairs) => {
|
||||
@@ -2478,7 +2473,7 @@ pub async fn get_agent_kv(
|
||||
(StatusCode::OK, Json(serde_json::json!({"kv_pairs": kv})))
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("Memory list_kv failed for agent {id}: {e}");
|
||||
tracing::warn!("Memory list_kv failed: {e}");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(serde_json::json!({"error": "Memory operation failed"})),
|
||||
@@ -2490,17 +2485,9 @@ pub async fn get_agent_kv(
|
||||
/// GET /api/memory/agents/:id/kv/:key — Get a specific KV value.
|
||||
pub async fn get_agent_kv_key(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path((id, key)): Path<(String, String)>,
|
||||
Path((_id, key)): Path<(String, String)>,
|
||||
) -> impl IntoResponse {
|
||||
let agent_id: AgentId = match id.parse() {
|
||||
Ok(id) => id,
|
||||
Err(_) => {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(serde_json::json!({"error": "Invalid agent ID"})),
|
||||
);
|
||||
}
|
||||
};
|
||||
let agent_id = openfang_kernel::kernel::shared_memory_agent_id();
|
||||
|
||||
match state.kernel.memory.structured_get(agent_id, &key) {
|
||||
Ok(Some(val)) => (
|
||||
@@ -2512,7 +2499,7 @@ pub async fn get_agent_kv_key(
|
||||
Json(serde_json::json!({"error": "Key not found"})),
|
||||
),
|
||||
Err(e) => {
|
||||
tracing::warn!("Memory get failed for agent {id}, key '{key}': {e}");
|
||||
tracing::warn!("Memory get failed for key '{key}': {e}");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(serde_json::json!({"error": "Memory operation failed"})),
|
||||
@@ -2524,18 +2511,10 @@ pub async fn get_agent_kv_key(
|
||||
/// PUT /api/memory/agents/:id/kv/:key — Set a KV value.
|
||||
pub async fn set_agent_kv_key(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path((id, key)): Path<(String, String)>,
|
||||
Path((_id, key)): Path<(String, String)>,
|
||||
Json(body): Json<serde_json::Value>,
|
||||
) -> impl IntoResponse {
|
||||
let agent_id: AgentId = match id.parse() {
|
||||
Ok(id) => id,
|
||||
Err(_) => {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(serde_json::json!({"error": "Invalid agent ID"})),
|
||||
);
|
||||
}
|
||||
};
|
||||
let agent_id = openfang_kernel::kernel::shared_memory_agent_id();
|
||||
|
||||
let value = body.get("value").cloned().unwrap_or(body);
|
||||
|
||||
@@ -2545,7 +2524,7 @@ pub async fn set_agent_kv_key(
|
||||
Json(serde_json::json!({"status": "stored", "key": key})),
|
||||
),
|
||||
Err(e) => {
|
||||
tracing::warn!("Memory set failed for agent {id}, key '{key}': {e}");
|
||||
tracing::warn!("Memory set failed for key '{key}': {e}");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(serde_json::json!({"error": "Memory operation failed"})),
|
||||
@@ -2557,17 +2536,9 @@ pub async fn set_agent_kv_key(
|
||||
/// DELETE /api/memory/agents/:id/kv/:key — Delete a KV value.
|
||||
pub async fn delete_agent_kv_key(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path((id, key)): Path<(String, String)>,
|
||||
Path((_id, key)): Path<(String, String)>,
|
||||
) -> impl IntoResponse {
|
||||
let agent_id: AgentId = match id.parse() {
|
||||
Ok(id) => id,
|
||||
Err(_) => {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(serde_json::json!({"error": "Invalid agent ID"})),
|
||||
);
|
||||
}
|
||||
};
|
||||
let agent_id = openfang_kernel::kernel::shared_memory_agent_id();
|
||||
|
||||
match state.kernel.memory.structured_delete(agent_id, &key) {
|
||||
Ok(()) => (
|
||||
@@ -2575,7 +2546,7 @@ pub async fn delete_agent_kv_key(
|
||||
Json(serde_json::json!({"status": "deleted", "key": key})),
|
||||
),
|
||||
Err(e) => {
|
||||
tracing::warn!("Memory delete failed for agent {id}, key '{key}': {e}");
|
||||
tracing::warn!("Memory delete failed for key '{key}': {e}");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(serde_json::json!({"error": "Memory operation failed"})),
|
||||
@@ -6788,7 +6759,7 @@ fn remove_secret_env(path: &std::path::Path, key: &str) -> Result<(), std::io::E
|
||||
fn upsert_channel_config(
|
||||
config_path: &std::path::Path,
|
||||
channel_name: &str,
|
||||
fields: &HashMap<String, String>,
|
||||
fields: &HashMap<String, (String, FieldType)>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let content = if config_path.exists() {
|
||||
std::fs::read_to_string(config_path)?
|
||||
@@ -6816,10 +6787,20 @@ fn upsert_channel_config(
|
||||
.and_then(|v| v.as_table_mut())
|
||||
.ok_or("channels is not a table")?;
|
||||
|
||||
// Build channel sub-table
|
||||
// Build channel sub-table with correct TOML types
|
||||
let mut ch_table = toml::map::Map::new();
|
||||
for (k, v) in fields {
|
||||
ch_table.insert(k.clone(), toml::Value::String(v.clone()));
|
||||
for (k, (v, ft)) in fields {
|
||||
let toml_val = match ft {
|
||||
FieldType::Number => {
|
||||
if let Ok(n) = v.parse::<i64>() {
|
||||
toml::Value::Integer(n)
|
||||
} else {
|
||||
toml::Value::String(v.clone())
|
||||
}
|
||||
}
|
||||
_ => toml::Value::String(v.clone()),
|
||||
};
|
||||
ch_table.insert(k.clone(), toml_val);
|
||||
}
|
||||
channels_table.insert(channel_name.to_string(), toml::Value::Table(ch_table));
|
||||
|
||||
|
||||
@@ -589,6 +589,14 @@
|
||||
|
||||
<!-- Messages area -->
|
||||
<div class="messages" id="messages" @dragover.prevent="dragOver = true" @dragleave="dragOver = false" @drop.prevent="handleDrop($event); dragOver = false">
|
||||
<!-- Empty state: no agent selected -->
|
||||
<template x-if="!currentAgent">
|
||||
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;text-align:center;padding:32px;opacity:0.8">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--text-dim)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="margin-bottom:16px;opacity:0.5"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
<h3 style="margin:0 0 8px;font-size:16px;font-weight:600">Select an agent to start chatting</h3>
|
||||
<p class="text-dim" style="font-size:13px;max-width:320px">Choose an agent from the sidebar or go to the Agents tab to create a new one.</p>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Message list -->
|
||||
<template x-if="currentAgent">
|
||||
<div>
|
||||
@@ -3051,6 +3059,12 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path"]</pre>
|
||||
<span class="text-xs text-dim ml-2" x-text="customModelStatus"></span>
|
||||
</div>
|
||||
<div class="text-xs text-dim mb-2" x-text="filteredModels.length + ' of ' + models.length + ' models'"></div>
|
||||
<div x-show="!filteredModels.length && !settingsLoading" style="text-align:center;padding:32px 16px">
|
||||
<div style="font-size:32px;margin-bottom:8px;opacity:0.5">🤖</div>
|
||||
<h3 style="margin:0 0 4px;font-size:14px" x-text="models.length ? 'No models match your search' : 'No models available'"></h3>
|
||||
<p class="text-xs text-dim" x-text="models.length ? 'Try a different search term or clear filters.' : 'Configure an LLM provider to see available models.'"></p>
|
||||
<button class="btn btn-ghost btn-sm mt-2" x-show="models.length && (modelSearch || modelProviderFilter)" @click="modelSearch=''; modelProviderFilter=''">Clear Filters</button>
|
||||
</div>
|
||||
<div class="table-wrap" x-show="filteredModels.length">
|
||||
<table>
|
||||
<thead><tr><th>Model</th><th>Provider</th><th>Tier</th><th>Context</th><th>Input Cost</th><th>Output Cost</th><th>Status</th></tr></thead>
|
||||
@@ -3078,6 +3092,12 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path"]</pre>
|
||||
<input placeholder="Search tools..." x-model="toolSearch">
|
||||
</div>
|
||||
<div class="text-xs text-dim mb-2" x-text="filteredTools.length + ' of ' + tools.length + ' tools'"></div>
|
||||
<div x-show="!filteredTools.length && !settingsLoading" style="text-align:center;padding:32px 16px">
|
||||
<div style="font-size:32px;margin-bottom:8px;opacity:0.5">🔧</div>
|
||||
<h3 style="margin:0 0 4px;font-size:14px" x-text="tools.length ? 'No tools match your search' : 'No tools available'"></h3>
|
||||
<p class="text-xs text-dim" x-text="tools.length ? 'Try a different search term.' : 'Tools will appear once agents are configured.'"></p>
|
||||
<button class="btn btn-ghost btn-sm mt-2" x-show="tools.length && toolSearch" @click="toolSearch=''">Clear Search</button>
|
||||
</div>
|
||||
<div class="table-wrap" x-show="filteredTools.length">
|
||||
<table>
|
||||
<thead><tr><th>Tool</th><th>Description</th></tr></thead>
|
||||
|
||||
@@ -541,14 +541,14 @@ fn sanitize_telegram_html(text: &str) -> String {
|
||||
];
|
||||
|
||||
let mut result = String::with_capacity(text.len());
|
||||
let bytes = text.as_bytes();
|
||||
let mut i = 0;
|
||||
let mut chars = text.char_indices().peekable();
|
||||
|
||||
while i < bytes.len() {
|
||||
if bytes[i] == b'<' {
|
||||
while let Some(&(i, ch)) = chars.peek() {
|
||||
if ch == '<' {
|
||||
// Try to parse an HTML tag
|
||||
if let Some(end) = text[i..].find('>') {
|
||||
let tag_content = &text[i + 1..i + end]; // content between < and >
|
||||
if let Some(end_offset) = text[i..].find('>') {
|
||||
let tag_end = i + end_offset;
|
||||
let tag_content = &text[i + 1..tag_end]; // content between < and >
|
||||
let tag_name = tag_content
|
||||
.trim_start_matches('/')
|
||||
.split(|c: char| c.is_whitespace() || c == '/' || c == '>')
|
||||
@@ -558,22 +558,28 @@ fn sanitize_telegram_html(text: &str) -> String {
|
||||
|
||||
if !tag_name.is_empty() && ALLOWED.contains(&tag_name.as_str()) {
|
||||
// Allowed tag — keep as-is
|
||||
result.push_str(&text[i..i + end + 1]);
|
||||
result.push_str(&text[i..tag_end + 1]);
|
||||
} else {
|
||||
// Unknown tag — escape both brackets
|
||||
result.push_str("<");
|
||||
result.push_str(&text[i + 1..i + end]);
|
||||
result.push_str(tag_content);
|
||||
result.push_str(">");
|
||||
}
|
||||
i += end + 1;
|
||||
// Advance past the whole tag
|
||||
while let Some(&(j, _)) = chars.peek() {
|
||||
chars.next();
|
||||
if j >= tag_end {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No closing > — escape the lone <
|
||||
result.push_str("<");
|
||||
i += 1;
|
||||
chars.next();
|
||||
}
|
||||
} else {
|
||||
result.push(text[i..].chars().next().unwrap());
|
||||
i += text[i..].chars().next().unwrap().len_utf8();
|
||||
result.push(ch);
|
||||
chars.next();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2099,7 +2099,20 @@ decay_rate = 0.05
|
||||
all_ok = false;
|
||||
}
|
||||
|
||||
// --- Check 4: Port 4200 availability ---
|
||||
// --- Check 4: Port availability ---
|
||||
// Read api_listen from config (default: 127.0.0.1:4200)
|
||||
let api_listen = {
|
||||
let cfg_path = openfang_dir.join("config.toml");
|
||||
if cfg_path.exists() {
|
||||
std::fs::read_to_string(&cfg_path)
|
||||
.ok()
|
||||
.and_then(|s| toml::from_str::<openfang_types::config::KernelConfig>(&s).ok())
|
||||
.map(|c| c.api_listen)
|
||||
.unwrap_or_else(|| "127.0.0.1:4200".to_string())
|
||||
} else {
|
||||
"127.0.0.1:4200".to_string()
|
||||
}
|
||||
};
|
||||
if !json {
|
||||
println!();
|
||||
}
|
||||
@@ -2115,19 +2128,24 @@ decay_rate = 0.05
|
||||
}
|
||||
checks.push(serde_json::json!({"check": "daemon", "status": "warn"}));
|
||||
|
||||
// Check if port 4200 is available
|
||||
match std::net::TcpListener::bind("127.0.0.1:4200") {
|
||||
// Check if the configured port is available
|
||||
let bind_addr = if api_listen.starts_with("0.0.0.0") {
|
||||
api_listen.replacen("0.0.0.0", "127.0.0.1", 1)
|
||||
} else {
|
||||
api_listen.clone()
|
||||
};
|
||||
match std::net::TcpListener::bind(&bind_addr) {
|
||||
Ok(_) => {
|
||||
if !json {
|
||||
ui::check_ok("Port 4200 is available");
|
||||
ui::check_ok(&format!("Port {api_listen} is available"));
|
||||
}
|
||||
checks.push(serde_json::json!({"check": "port_4200", "status": "ok"}));
|
||||
checks.push(serde_json::json!({"check": "port", "status": "ok", "address": api_listen}));
|
||||
}
|
||||
Err(_) => {
|
||||
if !json {
|
||||
ui::check_warn("Port 4200 is in use by another process");
|
||||
ui::check_warn(&format!("Port {api_listen} is in use by another process"));
|
||||
}
|
||||
checks.push(serde_json::json!({"check": "port_4200", "status": "warn"}));
|
||||
checks.push(serde_json::json!({"check": "port", "status": "warn", "address": api_listen}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1017,23 +1017,24 @@ impl OpenFangKernel {
|
||||
);
|
||||
|
||||
// Apply default_model to restored agents (same logic as spawn)
|
||||
if restored_entry.manifest.model.api_key_env.is_none()
|
||||
&& restored_entry.manifest.model.base_url.is_none()
|
||||
{
|
||||
let dm = &kernel.config.default_model;
|
||||
let is_default_provider = restored_entry.manifest.model.provider.is_empty()
|
||||
|| restored_entry.manifest.model.provider == "default";
|
||||
let is_default_model = restored_entry.manifest.model.model.is_empty()
|
||||
|| restored_entry.manifest.model.model == "default";
|
||||
if is_default_provider && is_default_model {
|
||||
let dm = &kernel.config.default_model;
|
||||
if !dm.provider.is_empty() {
|
||||
restored_entry.manifest.model.provider = dm.provider.clone();
|
||||
}
|
||||
if !dm.model.is_empty() {
|
||||
restored_entry.manifest.model.model = dm.model.clone();
|
||||
}
|
||||
if dm.base_url.is_some() {
|
||||
restored_entry.manifest.model.base_url = dm.base_url.clone();
|
||||
if !dm.api_key_env.is_empty() && restored_entry.manifest.model.api_key_env.is_none() {
|
||||
restored_entry.manifest.model.api_key_env = Some(dm.api_key_env.clone());
|
||||
}
|
||||
if dm.base_url.is_some() && restored_entry.manifest.model.base_url.is_none() {
|
||||
restored_entry.manifest.model.base_url.clone_from(&dm.base_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1103,29 +1104,34 @@ impl OpenFangKernel {
|
||||
|
||||
// Overlay kernel default_model onto agent if agent didn't explicitly choose.
|
||||
// Treat empty or "default" as "use the kernel's configured default_model".
|
||||
// This allows bundled agents to defer to the user's configured provider/model.
|
||||
if manifest.model.api_key_env.is_none() && manifest.model.base_url.is_none() {
|
||||
// Check hot-reloaded override first, fall back to boot-time config
|
||||
let override_guard = self
|
||||
.default_model_override
|
||||
.read()
|
||||
.unwrap_or_else(|e: std::sync::PoisonError<_>| e.into_inner());
|
||||
let dm = override_guard
|
||||
.as_ref()
|
||||
.unwrap_or(&self.config.default_model);
|
||||
// This allows bundled agents to defer to the user's configured provider/model,
|
||||
// even if the agent manifest specifies an api_key_env (which is just a hint
|
||||
// about which env var to check, not a hard lock on provider/model).
|
||||
{
|
||||
let is_default_provider =
|
||||
manifest.model.provider.is_empty() || manifest.model.provider == "default";
|
||||
let is_default_model =
|
||||
manifest.model.model.is_empty() || manifest.model.model == "default";
|
||||
if is_default_provider && is_default_model {
|
||||
// Check hot-reloaded override first, fall back to boot-time config
|
||||
let override_guard = self
|
||||
.default_model_override
|
||||
.read()
|
||||
.unwrap_or_else(|e: std::sync::PoisonError<_>| e.into_inner());
|
||||
let dm = override_guard
|
||||
.as_ref()
|
||||
.unwrap_or(&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();
|
||||
}
|
||||
if dm.base_url.is_some() {
|
||||
manifest.model.base_url = dm.base_url.clone();
|
||||
if !dm.api_key_env.is_empty() && manifest.model.api_key_env.is_none() {
|
||||
manifest.model.api_key_env = Some(dm.api_key_env.clone());
|
||||
}
|
||||
if dm.base_url.is_some() && manifest.model.base_url.is_none() {
|
||||
manifest.model.base_url.clone_from(&dm.base_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2889,10 +2895,23 @@ impl OpenFangKernel {
|
||||
manifest.model.system_prompt, resolved.prompt_block
|
||||
);
|
||||
}
|
||||
if !resolved.env_vars.is_empty() {
|
||||
// Collect env vars from settings + from requires (api_key/env_var requirements)
|
||||
let mut allowed_env = resolved.env_vars;
|
||||
for req in &def.requires {
|
||||
match req.requirement_type {
|
||||
openfang_hands::RequirementType::ApiKey
|
||||
| openfang_hands::RequirementType::EnvVar => {
|
||||
if !req.check_value.is_empty() && !allowed_env.contains(&req.check_value) {
|
||||
allowed_env.push(req.check_value.clone());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if !allowed_env.is_empty() {
|
||||
manifest.metadata.insert(
|
||||
"hand_allowed_env".to_string(),
|
||||
serde_json::to_value(&resolved.env_vars).unwrap_or_default(),
|
||||
serde_json::to_value(&allowed_env).unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4641,7 +4660,7 @@ fn infer_provider_from_model(model: &str) -> Option<String> {
|
||||
|
||||
/// A well-known agent ID used for shared memory operations across agents.
|
||||
/// This is a fixed UUID so all agents read/write to the same namespace.
|
||||
fn shared_memory_agent_id() -> AgentId {
|
||||
pub fn shared_memory_agent_id() -> AgentId {
|
||||
AgentId(uuid::Uuid::from_bytes([
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01,
|
||||
|
||||
@@ -91,16 +91,21 @@ impl StructuredStore {
|
||||
let rows = stmt
|
||||
.query_map(rusqlite::params![agent_id.0.to_string()], |row| {
|
||||
let key: String = row.get(0)?;
|
||||
let val_str: String = row.get(1)?;
|
||||
Ok((key, val_str))
|
||||
let blob: Vec<u8> = row.get(1)?;
|
||||
Ok((key, blob))
|
||||
})
|
||||
.map_err(|e| OpenFangError::Memory(e.to_string()))?;
|
||||
|
||||
let mut pairs = Vec::new();
|
||||
for row in rows {
|
||||
let (key, val_str) = row.map_err(|e| OpenFangError::Memory(e.to_string()))?;
|
||||
let value: serde_json::Value =
|
||||
serde_json::from_str(&val_str).unwrap_or(serde_json::Value::String(val_str));
|
||||
let (key, blob) = row.map_err(|e| OpenFangError::Memory(e.to_string()))?;
|
||||
let value: serde_json::Value = serde_json::from_slice(&blob)
|
||||
.unwrap_or_else(|_| {
|
||||
// Fallback: try as UTF-8 string
|
||||
String::from_utf8(blob)
|
||||
.map(serde_json::Value::String)
|
||||
.unwrap_or(serde_json::Value::Null)
|
||||
});
|
||||
pairs.push((key, value));
|
||||
}
|
||||
Ok(pairs)
|
||||
|
||||
Reference in New Issue
Block a user