mirror of
https://github.com/RightNow-AI/openfang.git
synced 2026-07-30 06:32:17 +00:00
batch fixes
This commit is contained in:
Generated
+14
-14
@@ -3866,7 +3866,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-api"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
@@ -3902,7 +3902,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-channels"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
@@ -3933,7 +3933,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-cli"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
@@ -3960,7 +3960,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-desktop"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"open",
|
||||
@@ -3986,7 +3986,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-extensions"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"argon2",
|
||||
@@ -4014,7 +4014,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-hands"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dashmap",
|
||||
@@ -4031,7 +4031,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-kernel"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -4067,7 +4067,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-memory"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -4086,7 +4086,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-migrate"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dirs 6.0.0",
|
||||
@@ -4105,7 +4105,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-runtime"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -4137,7 +4137,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-skills"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
@@ -4159,7 +4159,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-types"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -4178,7 +4178,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openfang-wire"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -8790,7 +8790,7 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.2.7"
|
||||
version = "0.2.8"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0 OR MIT"
|
||||
repository = "https://github.com/RightNow-AI/openfang"
|
||||
|
||||
@@ -38,7 +38,7 @@ pub struct DiscordAdapter {
|
||||
/// SECURITY: Bot token is zeroized on drop to prevent memory disclosure.
|
||||
token: Zeroizing<String>,
|
||||
client: reqwest::Client,
|
||||
allowed_guilds: Vec<u64>,
|
||||
allowed_guilds: Vec<String>,
|
||||
intents: u64,
|
||||
shutdown_tx: Arc<watch::Sender<bool>>,
|
||||
shutdown_rx: watch::Receiver<bool>,
|
||||
@@ -51,7 +51,7 @@ pub struct DiscordAdapter {
|
||||
}
|
||||
|
||||
impl DiscordAdapter {
|
||||
pub fn new(token: String, allowed_guilds: Vec<u64>, intents: u64) -> Self {
|
||||
pub fn new(token: String, allowed_guilds: Vec<String>, intents: u64) -> Self {
|
||||
let (shutdown_tx, shutdown_rx) = watch::channel(false);
|
||||
Self {
|
||||
token: Zeroizing::new(token),
|
||||
@@ -422,7 +422,7 @@ impl ChannelAdapter for DiscordAdapter {
|
||||
async fn parse_discord_message(
|
||||
d: &serde_json::Value,
|
||||
bot_user_id: &Arc<RwLock<Option<String>>>,
|
||||
allowed_guilds: &[u64],
|
||||
allowed_guilds: &[String],
|
||||
) -> Option<ChannelMessage> {
|
||||
let author = d.get("author")?;
|
||||
let author_id = author["id"].as_str()?;
|
||||
@@ -442,8 +442,7 @@ async fn parse_discord_message(
|
||||
// Filter by allowed guilds
|
||||
if !allowed_guilds.is_empty() {
|
||||
if let Some(guild_id) = d["guild_id"].as_str() {
|
||||
let gid: u64 = guild_id.parse().unwrap_or(0);
|
||||
if !allowed_guilds.contains(&gid) {
|
||||
if !allowed_guilds.iter().any(|g| g == guild_id) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
@@ -587,11 +586,11 @@ mod tests {
|
||||
});
|
||||
|
||||
// Not in allowed guilds
|
||||
let msg = parse_discord_message(&d, &bot_id, &[111, 222]).await;
|
||||
let msg = parse_discord_message(&d, &bot_id, &["111".into(), "222".into()]).await;
|
||||
assert!(msg.is_none());
|
||||
|
||||
// In allowed guilds
|
||||
let msg = parse_discord_message(&d, &bot_id, &[999]).await;
|
||||
let msg = parse_discord_message(&d, &bot_id, &["999".into()]).await;
|
||||
assert!(msg.is_some());
|
||||
}
|
||||
|
||||
@@ -685,7 +684,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_discord_adapter_creation() {
|
||||
let adapter = DiscordAdapter::new("test-token".to_string(), vec![123, 456], 33280);
|
||||
let adapter = DiscordAdapter::new("test-token".to_string(), vec!["123".to_string(), "456".to_string()], 33280);
|
||||
assert_eq!(adapter.name(), "discord");
|
||||
assert_eq!(adapter.channel_type(), ChannelType::Discord);
|
||||
}
|
||||
|
||||
@@ -924,14 +924,20 @@ impl OpenFangKernel {
|
||||
&& restored_entry.manifest.model.base_url.is_none()
|
||||
{
|
||||
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();
|
||||
let is_default_provider = restored_entry.manifest.model.provider.is_empty()
|
||||
|| restored_entry.manifest.model.provider == "anthropic";
|
||||
let is_default_model = restored_entry.manifest.model.model.is_empty()
|
||||
|| restored_entry.manifest.model.model == "claude-sonnet-4-20250514";
|
||||
if is_default_provider && is_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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -998,18 +1004,26 @@ impl OpenFangKernel {
|
||||
}
|
||||
info!(agent = %name, id = %agent_id, exec_mode = ?manifest.exec_policy.as_ref().map(|p| &p.mode), "Agent exec_policy resolved");
|
||||
|
||||
// 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`.
|
||||
// Overlay kernel default_model onto agent if agent didn't explicitly choose.
|
||||
// Only override provider/model when the agent uses the generic defaults
|
||||
// (empty or the compile-time default "anthropic"/"claude-sonnet-4-20250514").
|
||||
// This preserves explicit model choices like provider="groq", model="llama-3.3-70b".
|
||||
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();
|
||||
}
|
||||
if dm.base_url.is_some() {
|
||||
manifest.model.base_url = dm.base_url.clone();
|
||||
let is_default_provider = manifest.model.provider.is_empty()
|
||||
|| manifest.model.provider == "anthropic";
|
||||
let is_default_model = manifest.model.model.is_empty()
|
||||
|| manifest.model.model == "claude-sonnet-4-20250514";
|
||||
if is_default_provider && is_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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1448,6 +1462,7 @@ impl OpenFangKernel {
|
||||
None
|
||||
},
|
||||
peer_agents,
|
||||
current_date: Some(chrono::Local::now().format("%A, %B %d, %Y (%Y-%m-%d %H:%M %Z)").to_string()),
|
||||
};
|
||||
manifest.model.system_prompt =
|
||||
openfang_runtime::prompt_builder::build_system_prompt(&prompt_ctx);
|
||||
@@ -1916,6 +1931,7 @@ impl OpenFangKernel {
|
||||
None
|
||||
},
|
||||
peer_agents,
|
||||
current_date: Some(chrono::Local::now().format("%A, %B %d, %Y (%Y-%m-%d %H:%M %Z)").to_string()),
|
||||
};
|
||||
manifest.model.system_prompt =
|
||||
openfang_runtime::prompt_builder::build_system_prompt(&prompt_ctx);
|
||||
@@ -3682,7 +3698,9 @@ impl OpenFangKernel {
|
||||
|
||||
// If fallback models are configured, wrap in FallbackDriver
|
||||
if !manifest.fallback_models.is_empty() {
|
||||
let mut chain = vec![primary.clone()];
|
||||
// Primary driver uses the agent's own model name (already set in request)
|
||||
let mut chain: Vec<(std::sync::Arc<dyn openfang_runtime::llm_driver::LlmDriver>, String)> =
|
||||
vec![(primary.clone(), String::new())];
|
||||
for fb in &manifest.fallback_models {
|
||||
let config = DriverConfig {
|
||||
provider: fb.provider.clone(),
|
||||
@@ -3693,7 +3711,7 @@ impl OpenFangKernel {
|
||||
base_url: fb.base_url.clone(),
|
||||
};
|
||||
match drivers::create_driver(&config) {
|
||||
Ok(d) => chain.push(d),
|
||||
Ok(d) => chain.push((d, fb.model.clone())),
|
||||
Err(e) => {
|
||||
warn!("Fallback driver '{}' failed to init: {e}", fb.provider);
|
||||
}
|
||||
@@ -3701,7 +3719,7 @@ impl OpenFangKernel {
|
||||
}
|
||||
if chain.len() > 1 {
|
||||
return Ok(Arc::new(
|
||||
openfang_runtime::drivers::fallback::FallbackDriver::new(chain),
|
||||
openfang_runtime::drivers::fallback::FallbackDriver::with_models(chain),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,14 @@ pub fn apply_context_guard(
|
||||
let mut compacted = 0;
|
||||
for loc in &locations {
|
||||
if loc.char_len > single_max {
|
||||
// Bounds check: indices may be stale if messages were modified concurrently
|
||||
if loc.msg_idx >= messages.len() {
|
||||
continue;
|
||||
}
|
||||
if let MessageContent::Blocks(blocks) = &mut messages[loc.msg_idx].content {
|
||||
if loc.block_idx >= blocks.len() {
|
||||
continue;
|
||||
}
|
||||
if let ContentBlock::ToolResult { content, .. } = &mut blocks[loc.block_idx] {
|
||||
let old_len = content.len();
|
||||
*content = truncate_to(content, single_max);
|
||||
@@ -167,7 +174,13 @@ pub fn apply_context_guard(
|
||||
if loc.char_len <= compact_target {
|
||||
continue;
|
||||
}
|
||||
if loc.msg_idx >= messages.len() {
|
||||
continue;
|
||||
}
|
||||
if let MessageContent::Blocks(blocks) = &mut messages[loc.msg_idx].content {
|
||||
if loc.block_idx >= blocks.len() {
|
||||
continue;
|
||||
}
|
||||
if let ContentBlock::ToolResult { content, .. } = &mut blocks[loc.block_idx] {
|
||||
if content.len() > compact_target {
|
||||
let old_len = content.len();
|
||||
@@ -188,11 +201,32 @@ fn truncate_to(content: &str, max_chars: usize) -> String {
|
||||
if content.len() <= max_chars {
|
||||
return content.to_string();
|
||||
}
|
||||
let keep = max_chars.saturating_sub(80);
|
||||
let keep = max_chars.saturating_sub(80).min(content.len());
|
||||
// Ensure keep is a valid char boundary
|
||||
let keep = if content.is_char_boundary(keep) {
|
||||
keep
|
||||
} else {
|
||||
content[..keep]
|
||||
.char_indices()
|
||||
.next_back()
|
||||
.map(|(i, _)| i)
|
||||
.unwrap_or(0)
|
||||
};
|
||||
let search_start = keep.saturating_sub(100);
|
||||
// Ensure search_start is a valid char boundary
|
||||
let search_start = if content.is_char_boundary(search_start) {
|
||||
search_start
|
||||
} else {
|
||||
content[..search_start]
|
||||
.char_indices()
|
||||
.next_back()
|
||||
.map(|(i, _)| i)
|
||||
.unwrap_or(0)
|
||||
};
|
||||
// Try to break at newline
|
||||
let break_point = content[keep.saturating_sub(100)..keep]
|
||||
let break_point = content[search_start..keep]
|
||||
.rfind('\n')
|
||||
.map(|pos| keep.saturating_sub(100) + pos)
|
||||
.map(|pos| search_start + pos)
|
||||
.unwrap_or(keep);
|
||||
format!(
|
||||
"{}\n\n[COMPACTED: {} → {} chars by context guard]",
|
||||
|
||||
@@ -365,10 +365,10 @@ impl LlmDriver for AnthropicDriver {
|
||||
let mut event_type = String::new();
|
||||
let mut data = String::new();
|
||||
for line in event_text.lines() {
|
||||
if let Some(et) = line.strip_prefix("event: ") {
|
||||
event_type = et.to_string();
|
||||
} else if let Some(d) = line.strip_prefix("data: ") {
|
||||
data = d.to_string();
|
||||
if let Some(et) = line.strip_prefix("event:") {
|
||||
event_type = et.trim_start().to_string();
|
||||
} else if let Some(d) = line.strip_prefix("data:") {
|
||||
data = d.trim_start().to_string();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,15 +12,23 @@ use tracing::warn;
|
||||
///
|
||||
/// On failure (including rate-limit and overload), moves to the next driver.
|
||||
/// Only returns an error when ALL drivers in the chain are exhausted.
|
||||
/// Each driver is paired with the model name it should use.
|
||||
pub struct FallbackDriver {
|
||||
drivers: Vec<Arc<dyn LlmDriver>>,
|
||||
drivers: Vec<(Arc<dyn LlmDriver>, String)>,
|
||||
}
|
||||
|
||||
impl FallbackDriver {
|
||||
/// Create a new fallback driver from an ordered chain of drivers.
|
||||
/// Create a new fallback driver from an ordered chain of (driver, model_name) pairs.
|
||||
///
|
||||
/// The first driver is the primary; subsequent are fallbacks.
|
||||
/// The first entry is the primary; subsequent are fallbacks.
|
||||
pub fn new(drivers: Vec<Arc<dyn LlmDriver>>) -> Self {
|
||||
Self {
|
||||
drivers: drivers.into_iter().map(|d| (d, String::new())).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new fallback driver with explicit model names for each driver.
|
||||
pub fn with_models(drivers: Vec<(Arc<dyn LlmDriver>, String)>) -> Self {
|
||||
Self { drivers }
|
||||
}
|
||||
}
|
||||
@@ -30,12 +38,17 @@ impl LlmDriver for FallbackDriver {
|
||||
async fn complete(&self, request: CompletionRequest) -> Result<CompletionResponse, LlmError> {
|
||||
let mut last_error = None;
|
||||
|
||||
for (i, driver) in self.drivers.iter().enumerate() {
|
||||
match driver.complete(request.clone()).await {
|
||||
for (i, (driver, model_name)) in self.drivers.iter().enumerate() {
|
||||
let mut req = request.clone();
|
||||
if !model_name.is_empty() {
|
||||
req.model = model_name.clone();
|
||||
}
|
||||
match driver.complete(req).await {
|
||||
Ok(response) => return Ok(response),
|
||||
Err(e @ LlmError::RateLimited { .. }) | Err(e @ LlmError::Overloaded { .. }) => {
|
||||
warn!(
|
||||
driver_index = i,
|
||||
model = %model_name,
|
||||
error = %e,
|
||||
"Driver rate-limited/overloaded, trying next fallback"
|
||||
);
|
||||
@@ -44,6 +57,7 @@ impl LlmDriver for FallbackDriver {
|
||||
Err(e) => {
|
||||
warn!(
|
||||
driver_index = i,
|
||||
model = %model_name,
|
||||
error = %e,
|
||||
"Fallback driver failed, trying next"
|
||||
);
|
||||
@@ -65,12 +79,17 @@ impl LlmDriver for FallbackDriver {
|
||||
) -> Result<CompletionResponse, LlmError> {
|
||||
let mut last_error = None;
|
||||
|
||||
for (i, driver) in self.drivers.iter().enumerate() {
|
||||
match driver.stream(request.clone(), tx.clone()).await {
|
||||
for (i, (driver, model_name)) in self.drivers.iter().enumerate() {
|
||||
let mut req = request.clone();
|
||||
if !model_name.is_empty() {
|
||||
req.model = model_name.clone();
|
||||
}
|
||||
match driver.stream(req, tx.clone()).await {
|
||||
Ok(response) => return Ok(response),
|
||||
Err(e @ LlmError::RateLimited { .. }) | Err(e @ LlmError::Overloaded { .. }) => {
|
||||
warn!(
|
||||
driver_index = i,
|
||||
model = %model_name,
|
||||
error = %e,
|
||||
"Driver rate-limited/overloaded (stream), trying next fallback"
|
||||
);
|
||||
@@ -79,6 +98,7 @@ impl LlmDriver for FallbackDriver {
|
||||
Err(e) => {
|
||||
warn!(
|
||||
driver_index = i,
|
||||
model = %model_name,
|
||||
error = %e,
|
||||
"Fallback driver (stream) failed, trying next"
|
||||
);
|
||||
|
||||
@@ -525,10 +525,10 @@ impl LlmDriver for GeminiDriver {
|
||||
let event_text = buffer[..pos].to_string();
|
||||
buffer = buffer[pos + 2..].to_string();
|
||||
|
||||
// Extract the data line
|
||||
// Extract the data line (handle both "data: " and "data:" formats)
|
||||
let data = event_text
|
||||
.lines()
|
||||
.find_map(|line| line.strip_prefix("data: "))
|
||||
.find_map(|line| line.strip_prefix("data:").map(|d| d.trim_start()))
|
||||
.unwrap_or("");
|
||||
|
||||
if data.is_empty() {
|
||||
|
||||
@@ -660,8 +660,8 @@ impl LlmDriver for OpenAIDriver {
|
||||
continue;
|
||||
}
|
||||
|
||||
let data = match line.strip_prefix("data: ") {
|
||||
Some(d) => d,
|
||||
let data = match line.strip_prefix("data:") {
|
||||
Some(d) => d.trim_start(),
|
||||
None => continue,
|
||||
};
|
||||
|
||||
|
||||
@@ -647,8 +647,8 @@ fn builtin_aliases() -> HashMap<String, String> {
|
||||
("gpt4-mini", "gpt-4o-mini"),
|
||||
("gpt5", "gpt-5.2"),
|
||||
("gpt5-mini", "gpt-5-mini"),
|
||||
("flash", "gemini-3-flash"),
|
||||
("gemini-flash", "gemini-3-flash"),
|
||||
("flash", "gemini-2.5-flash"),
|
||||
("gemini-flash", "gemini-2.5-flash"),
|
||||
("gemini-pro", "gemini-3.1-pro"),
|
||||
("deepseek", "deepseek-chat"),
|
||||
("llama", "llama-3.3-70b-versatile"),
|
||||
@@ -1052,20 +1052,7 @@ fn builtin_models() -> Vec<ModelCatalogEntry> {
|
||||
supports_streaming: true,
|
||||
aliases: vec!["gemini-pro".into()],
|
||||
},
|
||||
ModelCatalogEntry {
|
||||
id: "gemini-3-flash".into(),
|
||||
display_name: "Gemini 3 Flash".into(),
|
||||
provider: "gemini".into(),
|
||||
tier: ModelTier::Smart,
|
||||
context_window: 1_048_576,
|
||||
max_output_tokens: 65_536,
|
||||
input_cost_per_m: 0.50,
|
||||
output_cost_per_m: 3.0,
|
||||
supports_tools: true,
|
||||
supports_vision: true,
|
||||
supports_streaming: true,
|
||||
aliases: vec!["flash".into(), "gemini-flash".into()],
|
||||
},
|
||||
// gemini-3-flash removed: model doesn't exist. Use gemini-2.5-flash instead.
|
||||
ModelCatalogEntry {
|
||||
id: "gemini-3-deep-think".into(),
|
||||
display_name: "Gemini 3 Deep Think".into(),
|
||||
|
||||
@@ -53,6 +53,8 @@ pub struct PromptContext {
|
||||
pub heartbeat_md: Option<String>,
|
||||
/// Peer agents visible to this agent: (name, state, model).
|
||||
pub peer_agents: Vec<(String, String, String)>,
|
||||
/// Current date/time string for temporal awareness.
|
||||
pub current_date: Option<String>,
|
||||
}
|
||||
|
||||
/// Build the complete system prompt from a `PromptContext`.
|
||||
@@ -66,6 +68,11 @@ pub fn build_system_prompt(ctx: &PromptContext) -> String {
|
||||
// Section 1 — Agent Identity (always present)
|
||||
sections.push(build_identity_section(ctx));
|
||||
|
||||
// Section 1.5 — Current Date/Time (always present when set)
|
||||
if let Some(ref date) = ctx.current_date {
|
||||
sections.push(format!("## Current Date\nToday is {date}."));
|
||||
}
|
||||
|
||||
// Section 2 — Tool Call Behavior (skip for subagents)
|
||||
if !ctx.is_subagent {
|
||||
sections.push(TOOL_CALL_BEHAVIOR.to_string());
|
||||
|
||||
@@ -746,11 +746,14 @@ impl Default for CanvasConfig {
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ExecSecurityMode {
|
||||
/// Block all shell execution.
|
||||
#[serde(alias = "none", alias = "disabled")]
|
||||
Deny,
|
||||
/// Only allow commands in safe_bins or allowed_commands.
|
||||
#[default]
|
||||
#[serde(alias = "restricted")]
|
||||
Allowlist,
|
||||
/// Allow all commands (unsafe, dev only).
|
||||
#[serde(alias = "allow", alias = "all", alias = "unrestricted")]
|
||||
Full,
|
||||
}
|
||||
|
||||
@@ -1549,7 +1552,8 @@ pub struct DiscordConfig {
|
||||
/// Env var name holding the bot token (NOT the token itself).
|
||||
pub bot_token_env: String,
|
||||
/// Guild (server) IDs allowed to interact (empty = allow all).
|
||||
pub allowed_guilds: Vec<u64>,
|
||||
/// Accepts strings for consistency with other channel configs.
|
||||
pub allowed_guilds: Vec<String>,
|
||||
/// Default agent name to route messages to.
|
||||
pub default_agent: Option<String>,
|
||||
/// Gateway intents bitmask (default: 33280 = GUILD_MESSAGES | MESSAGE_CONTENT).
|
||||
|
||||
Reference in New Issue
Block a user