From f986b6895a131f50e975c7cd32cce27ffea29353 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Tue, 7 Jul 2026 19:02:19 +0800 Subject: [PATCH 1/2] feat(cli): load eval prompts from disk --- src/apps/cli/src/main.rs | 9 + src/apps/cli/src/modes/chat.rs | 9 +- src/apps/cli/src/prompts.rs | 112 +++++++++++ src/apps/cli/src/ui/startup.rs | 4 +- src/crates/assembly/core/Cargo.toml | 3 + .../agents/definitions/modes/agentic.rs | 16 +- .../agentic/agents/definitions/modes/debug.rs | 16 +- .../agents/definitions/modes/multitask.rs | 16 +- .../agentic/agents/definitions/modes/plan.rs | 16 +- .../assembly/core/src/agentic/agents/mod.rs | 40 +++- .../core/src/agentic/agents/prompt_runtime.rs | 174 ++++++++++++++++++ .../agentic/execution/model_exchange_trace.rs | 15 ++ .../core/src/agentic/init_agents_md.rs | 8 +- .../src/agentic/session/session_manager.rs | 5 + 14 files changed, 388 insertions(+), 55 deletions(-) create mode 100644 src/crates/assembly/core/src/agentic/agents/prompt_runtime.rs diff --git a/src/apps/cli/src/main.rs b/src/apps/cli/src/main.rs index 84b4802fa5..680d3d70ab 100644 --- a/src/apps/cli/src/main.rs +++ b/src/apps/cli/src/main.rs @@ -73,6 +73,10 @@ struct Cli { /// Disable file logging (stderr logging will still be used) #[arg(long, global = true)] no_log_file: bool, + + /// Load agent prompt templates from disk instead of only using embedded prompts + #[arg(long, global = true, value_name = "DIR")] + prompts_dir: Option, } #[derive(Subcommand)] @@ -613,6 +617,11 @@ async fn run_cli() -> Result<()> { logging::init_file_logging(file_log_level); } + if let Some(prompts_dir) = cli.prompts_dir.as_ref() { + bitfun_core::agentic::agents::set_runtime_prompt_dir(prompts_dir); + prompts::set_runtime_cli_prompt_dir(prompts_dir); + } + let config = CliConfig::load().unwrap_or_else(|e| { if !is_tui_mode { eprintln!("Warning: Failed to load config: {}", e); diff --git a/src/apps/cli/src/modes/chat.rs b/src/apps/cli/src/modes/chat.rs index 3552d96316..59dc650957 100644 --- a/src/apps/cli/src/modes/chat.rs +++ b/src/apps/cli/src/modes/chat.rs @@ -1712,14 +1712,9 @@ impl ChatMode { "/usage" => { self.show_usage_report(chat_view, chat_state, rt_handle); } - "/init" => match crate::prompts::get_cli_prompt("init") { + "/init" => match crate::prompts::get_cli_prompt_text("init") { Some(prompt) => { - self.send_message_to_agent( - prompt.to_string(), - chat_view, - chat_state, - rt_handle, - ); + self.send_message_to_agent(prompt, chat_view, chat_state, rt_handle); } None => { chat_state.add_system_message( diff --git a/src/apps/cli/src/prompts.rs b/src/apps/cli/src/prompts.rs index 17a1affe94..803c36cfa7 100644 --- a/src/apps/cli/src/prompts.rs +++ b/src/apps/cli/src/prompts.rs @@ -1,3 +1,115 @@ // Embedded CLI prompts (auto-generated from `prompts/` directory at build time) include!(concat!(env!("OUT_DIR"), "/embedded_cli_prompts.rs")); + +use std::path::{Path, PathBuf}; +use std::sync::OnceLock; + +pub const CLI_PROMPTS_ENV: &str = "BITFUN_CLI_PROMPTS_DIR"; +const SHARED_PROMPTS_ENV: &str = "BITFUN_PROMPTS_DIR"; + +static CLI_PROMPT_DIR_OVERRIDE: OnceLock = OnceLock::new(); + +pub fn set_runtime_cli_prompt_dir(path: impl Into) { + let _ = CLI_PROMPT_DIR_OVERRIDE.set(path.into()); +} + +pub fn get_cli_prompt_text(name: &str) -> Option { + get_runtime_cli_prompt(name).or_else(|| get_cli_prompt(name).map(str::to_string)) +} + +fn get_runtime_cli_prompt(name: &str) -> Option { + let relative_path = prompt_relative_path(name)?; + runtime_prompt_roots() + .into_iter() + .find_map(|root| read_prompt_file(&root, &relative_path)) +} + +fn prompt_relative_path(name: &str) -> Option { + let name = name.trim(); + if name.is_empty() + || name.starts_with('/') + || name.starts_with('\\') + || name + .split('/') + .any(|component| component.is_empty() || component == "." || component == "..") + { + return None; + } + + let mut path = PathBuf::new(); + for component in name.split('/') { + path.push(component); + } + Some(path) +} + +fn runtime_prompt_roots() -> Vec { + let mut roots = Vec::new(); + + if let Some(path) = CLI_PROMPT_DIR_OVERRIDE.get() { + add_prompt_roots(path, &mut roots); + } + + if let Some(path) = std::env::var_os(CLI_PROMPTS_ENV) { + add_prompt_roots(Path::new(&path), &mut roots); + } + + if let Some(path) = std::env::var_os(SHARED_PROMPTS_ENV) { + add_prompt_roots(Path::new(&path), &mut roots); + } + + add_prompt_roots(Path::new(env!("CARGO_MANIFEST_DIR")), &mut roots); + roots +} + +fn add_prompt_roots(path: &Path, roots: &mut Vec) { + let candidates = [ + path.to_path_buf(), + path.join("prompts"), + path.join("src").join("apps").join("cli"), + path.join("src").join("apps").join("cli").join("prompts"), + ]; + + for candidate in candidates { + if !candidate.is_dir() { + continue; + } + let normalized = dunce::simplified(&candidate).to_path_buf(); + if !roots.iter().any(|existing| existing == &normalized) { + roots.push(normalized); + } + } +} + +fn read_prompt_file(root: &Path, relative_path: &Path) -> Option { + for extension in ["md", "txt"] { + let path = root.join(relative_path).with_extension(extension); + if path.is_file() { + return std::fs::read_to_string(path).ok(); + } + } + + None +} + +#[cfg(test)] +mod tests { + use super::{prompt_relative_path, read_prompt_file}; + use std::fs; + use std::path::PathBuf; + + #[test] + fn runtime_cli_prompt_reads_markdown_file() { + let tempdir = tempfile::tempdir().expect("tempdir"); + fs::write(tempdir.path().join("init.md"), "runtime init").expect("write prompt"); + + let prompt = read_prompt_file(tempdir.path(), &PathBuf::from("init")).expect("prompt"); + assert_eq!(prompt, "runtime init"); + } + + #[test] + fn runtime_cli_prompt_rejects_path_traversal() { + assert!(prompt_relative_path("../init").is_none()); + } +} diff --git a/src/apps/cli/src/ui/startup.rs b/src/apps/cli/src/ui/startup.rs index 4d136095c7..f64e2f4eca 100644 --- a/src/apps/cli/src/ui/startup.rs +++ b/src/apps/cli/src/ui/startup.rs @@ -1146,10 +1146,10 @@ impl StartupPage { "/usage" => { self.status = Some("No active session for /usage.".to_string()); } - "/init" => match crate::prompts::get_cli_prompt("init") { + "/init" => match crate::prompts::get_cli_prompt_text("init") { Some(prompt) => { return Some(StartupResult::NewSession { - prompt: Some(prompt.to_string()), + prompt: Some(prompt), }); } None => { diff --git a/src/crates/assembly/core/Cargo.toml b/src/crates/assembly/core/Cargo.toml index 5d2ecc9977..3c6df6bf56 100644 --- a/src/crates/assembly/core/Cargo.toml +++ b/src/crates/assembly/core/Cargo.toml @@ -219,3 +219,6 @@ ssh-remote = [ [build-dependencies] sha2 = { workspace = true } + +[dev-dependencies] +tempfile = { workspace = true } diff --git a/src/crates/assembly/core/src/agentic/agents/definitions/modes/agentic.rs b/src/crates/assembly/core/src/agentic/agents/definitions/modes/agentic.rs index cde2c4c8ff..fd2898733e 100644 --- a/src/crates/assembly/core/src/agentic/agents/definitions/modes/agentic.rs +++ b/src/crates/assembly/core/src/agentic/agents/definitions/modes/agentic.rs @@ -7,7 +7,7 @@ //! only the reminder content differs. use crate::agentic::agents::{ - get_embedded_prompt, shared_coding_mode_tool_exposure_overrides, shared_coding_mode_tools, + get_prompt_template, shared_coding_mode_tool_exposure_overrides, shared_coding_mode_tools, shared_coding_mode_user_context_policy, Agent, AgentToolPolicyOverrides, UserContextPolicy, SHARED_CODING_MODE_PROMPT_TEMPLATE, }; @@ -38,14 +38,12 @@ impl AgenticMode { &self, template_name: &str, ) -> crate::util::errors::BitFunResult { - get_embedded_prompt(template_name) - .map(str::to_string) - .ok_or_else(|| { - crate::util::errors::BitFunError::Agent(format!( - "{} not found in embedded files", - template_name - )) - }) + get_prompt_template(template_name).ok_or_else(|| { + crate::util::errors::BitFunError::Agent(format!( + "{} not found in prompt files", + template_name + )) + }) } } diff --git a/src/crates/assembly/core/src/agentic/agents/definitions/modes/debug.rs b/src/crates/assembly/core/src/agentic/agents/definitions/modes/debug.rs index 1afc480e9e..b4ba392974 100644 --- a/src/crates/assembly/core/src/agentic/agents/definitions/modes/debug.rs +++ b/src/crates/assembly/core/src/agentic/agents/definitions/modes/debug.rs @@ -7,7 +7,7 @@ //! only the reminder content differs. use crate::agentic::agents::{ - get_embedded_prompt, shared_coding_mode_tool_exposure_overrides, shared_coding_mode_tools, + get_prompt_template, shared_coding_mode_tool_exposure_overrides, shared_coding_mode_tools, shared_coding_mode_user_context_policy, Agent, AgentToolPolicyOverrides, UserContextPolicy, SHARED_CODING_MODE_PROMPT_TEMPLATE, }; @@ -58,14 +58,12 @@ impl DebugMode { } fn load_reminder_template(&self, template_name: &str) -> BitFunResult { - get_embedded_prompt(template_name) - .map(str::to_string) - .ok_or_else(|| { - crate::util::errors::BitFunError::Agent(format!( - "{} not found in embedded files", - template_name - )) - }) + get_prompt_template(template_name).ok_or_else(|| { + crate::util::errors::BitFunError::Agent(format!( + "{} not found in prompt files", + template_name + )) + }) } const BUILTIN_JS_TEMPLATE: &'static str = r#"fetch('http://127.0.0.1:{PORT}/ingest/{SESSION_ID}',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'{LOCATION}',message:'{MESSAGE}',data:{DATA},timestamp:Date.now(),sessionId:'{SESSION_ID}',hypothesisId:'{HYPOTHESIS_ID}',runId:'{RUN_ID}'})}).catch(()=>{});"#; diff --git a/src/crates/assembly/core/src/agentic/agents/definitions/modes/multitask.rs b/src/crates/assembly/core/src/agentic/agents/definitions/modes/multitask.rs index 093c36e384..e8a9f98293 100644 --- a/src/crates/assembly/core/src/agentic/agents/definitions/modes/multitask.rs +++ b/src/crates/assembly/core/src/agentic/agents/definitions/modes/multitask.rs @@ -7,7 +7,7 @@ //! only the reminder content differs. use crate::agentic::agents::{ - get_embedded_prompt, shared_coding_mode_tool_exposure_overrides, shared_coding_mode_tools, + get_prompt_template, shared_coding_mode_tool_exposure_overrides, shared_coding_mode_tools, shared_coding_mode_user_context_policy, Agent, AgentToolPolicyOverrides, UserContextPolicy, SHARED_CODING_MODE_PROMPT_TEMPLATE, }; @@ -39,14 +39,12 @@ impl MultitaskMode { &self, template_name: &str, ) -> crate::util::errors::BitFunResult { - get_embedded_prompt(template_name) - .map(str::to_string) - .ok_or_else(|| { - crate::util::errors::BitFunError::Agent(format!( - "{} not found in embedded files", - template_name - )) - }) + get_prompt_template(template_name).ok_or_else(|| { + crate::util::errors::BitFunError::Agent(format!( + "{} not found in prompt files", + template_name + )) + }) } } diff --git a/src/crates/assembly/core/src/agentic/agents/definitions/modes/plan.rs b/src/crates/assembly/core/src/agentic/agents/definitions/modes/plan.rs index 12b540115f..1d4d3b96c9 100644 --- a/src/crates/assembly/core/src/agentic/agents/definitions/modes/plan.rs +++ b/src/crates/assembly/core/src/agentic/agents/definitions/modes/plan.rs @@ -7,7 +7,7 @@ //! only the reminder content differs. use crate::agentic::agents::{ - get_embedded_prompt, shared_coding_mode_tool_exposure_overrides, shared_coding_mode_tools, + get_prompt_template, shared_coding_mode_tool_exposure_overrides, shared_coding_mode_tools, shared_coding_mode_user_context_policy, Agent, AgentToolPolicyOverrides, UserContextPolicy, SHARED_CODING_MODE_PROMPT_TEMPLATE, }; @@ -39,14 +39,12 @@ impl PlanMode { &self, template_name: &str, ) -> crate::util::errors::BitFunResult { - get_embedded_prompt(template_name) - .map(str::to_string) - .ok_or_else(|| { - crate::util::errors::BitFunError::Agent(format!( - "{} not found in embedded files", - template_name - )) - }) + get_prompt_template(template_name).ok_or_else(|| { + crate::util::errors::BitFunError::Agent(format!( + "{} not found in prompt files", + template_name + )) + }) } } diff --git a/src/crates/assembly/core/src/agentic/agents/mod.rs b/src/crates/assembly/core/src/agentic/agents/mod.rs index 2cb2b91053..09d512e1ce 100644 --- a/src/crates/assembly/core/src/agentic/agents/mod.rs +++ b/src/crates/assembly/core/src/agentic/agents/mod.rs @@ -4,6 +4,7 @@ mod definitions; mod prompt_builder; +mod prompt_runtime; mod registry; use crate::agentic::session::{SystemPromptCacheIdentity, UserContextCacheIdentity}; @@ -37,6 +38,7 @@ pub use prompt_builder::{ PromptBuilderContext, RemoteExecutionHints, RuntimeContextNeeds, ToolListingSections, UserContextPolicy, UserContextSection, }; +pub use prompt_runtime::{set_runtime_prompt_dir, AGENT_PROMPTS_ENV}; pub use registry::catalog::{builtin_agent_specs, BuiltinAgentSpec}; pub use registry::types::{ subagent_source_from_custom_kind, AgentCategory, AgentInfo, AgentToolPolicy, @@ -108,6 +110,8 @@ pub trait Agent: Send + Sync + 'static { let template_name = self.prompt_template_name(model_name).trim(); let scope_key = if template_name.is_empty() { format!("agent:{}", self.id()) + } else if let Some(fingerprint) = prompt_runtime::cache_fingerprint(template_name) { + format!("template:{}:{}", template_name, fingerprint) } else { format!("template:{}", template_name) }; @@ -129,12 +133,12 @@ pub trait Agent: Send + Sync + 'static { async fn build_prompt(&self, context: &PromptBuilderContext) -> BitFunResult { let prompt_components = PromptBuilder::new(context.clone()); let template_name = self.prompt_template_name(context.model_name.as_deref()); - let system_prompt_template = get_embedded_prompt(template_name).ok_or_else(|| { - BitFunError::Agent(format!("{} not found in embedded files", template_name)) + let system_prompt_template = get_prompt_template(template_name).ok_or_else(|| { + BitFunError::Agent(format!("{} not found in prompt files", template_name)) })?; let prompt = prompt_components - .build_prompt_from_template(system_prompt_template) + .build_prompt_from_template(&system_prompt_template) .await?; Ok(prompt) @@ -166,13 +170,13 @@ pub trait Agent: Send + Sync + 'static { ) -> BitFunResult { if let Some(system_reminder_template_name) = self.system_reminder_template_name() { let system_reminder = - get_embedded_prompt(system_reminder_template_name).ok_or_else(|| { + get_prompt_template(system_reminder_template_name).ok_or_else(|| { BitFunError::Agent(format!( - "{} not found in embedded files", + "{} not found in prompt files", system_reminder_template_name )) })?; - Ok(system_reminder.to_string()) + Ok(system_reminder) } else { Ok("".to_string()) } @@ -194,6 +198,12 @@ pub trait Agent: Send + Sync + 'static { } } +pub fn get_prompt_template(prompt_name: &str) -> Option { + prompt_runtime::get_runtime_prompt(prompt_name) + .map(|prompt| prompt.content) + .or_else(|| get_embedded_prompt(prompt_name).map(str::to_string)) +} + #[cfg(test)] mod tests { use super::{ @@ -268,4 +278,22 @@ mod tests { assert_eq!(plan.tool_exposure_overrides(), &shared_overrides); assert_eq!(debug.tool_exposure_overrides(), &shared_overrides); } + + #[test] + fn runtime_prompt_override_reaches_anthropic_request_body_system_field() { + let tempdir = tempfile::tempdir().expect("tempdir"); + let prompt = "# Runtime Prompt Override Test\n\nRUNTIME_PROMPT_OVERRIDE_ACTIVE\n"; + std::fs::write(tempdir.path().join("agentic_mode.md"), prompt).expect("write prompt"); + + let runtime_prompt = + super::prompt_runtime::get_runtime_prompt_from_root(tempdir.path(), "agentic_mode") + .expect("runtime prompt"); + let request_body = serde_json::json!({ + "model": "test-model", + "messages": [{"role": "user", "content": "hello"}], + "system": runtime_prompt.content, + }); + + assert_eq!(request_body["system"], prompt); + } } diff --git a/src/crates/assembly/core/src/agentic/agents/prompt_runtime.rs b/src/crates/assembly/core/src/agentic/agents/prompt_runtime.rs new file mode 100644 index 0000000000..e6f150b6b8 --- /dev/null +++ b/src/crates/assembly/core/src/agentic/agents/prompt_runtime.rs @@ -0,0 +1,174 @@ +use std::path::{Path, PathBuf}; +use std::sync::OnceLock; +use std::time::UNIX_EPOCH; + +pub const AGENT_PROMPTS_ENV: &str = "BITFUN_PROMPTS_DIR"; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RuntimePrompt { + pub content: String, + pub cache_fingerprint: String, +} + +static PROMPT_DIR_OVERRIDE: OnceLock = OnceLock::new(); + +pub fn set_runtime_prompt_dir(path: impl Into) { + let _ = PROMPT_DIR_OVERRIDE.set(path.into()); +} + +pub fn get_runtime_prompt(name: &str) -> Option { + let relative_path = prompt_relative_path(name)?; + runtime_prompt_roots() + .into_iter() + .find_map(|root| read_runtime_prompt(&root, &relative_path)) +} + +#[cfg(test)] +pub(crate) fn get_runtime_prompt_from_root(root: &Path, name: &str) -> Option { + let relative_path = prompt_relative_path(name)?; + let mut roots = Vec::new(); + add_prompt_roots(root, &mut roots); + roots + .into_iter() + .find_map(|root| read_runtime_prompt(&root, &relative_path)) +} + +pub fn cache_fingerprint(name: &str) -> Option { + get_runtime_prompt(name).map(|prompt| prompt.cache_fingerprint) +} + +fn prompt_relative_path(name: &str) -> Option { + let name = name.trim(); + if name.is_empty() + || name.starts_with('/') + || name.starts_with('\\') + || name + .split('/') + .any(|component| component.is_empty() || component == "." || component == "..") + { + return None; + } + + let mut path = PathBuf::new(); + for component in name.split('/') { + path.push(component); + } + Some(path) +} + +fn runtime_prompt_roots() -> Vec { + let mut roots = Vec::new(); + + if let Some(path) = PROMPT_DIR_OVERRIDE.get() { + add_prompt_roots(path, &mut roots); + } + + if let Some(path) = std::env::var_os(AGENT_PROMPTS_ENV) { + add_prompt_roots(Path::new(&path), &mut roots); + } + + add_prompt_roots(Path::new(env!("CARGO_MANIFEST_DIR")), &mut roots); + roots +} + +fn add_prompt_roots(path: &Path, roots: &mut Vec) { + let candidates = [ + path.to_path_buf(), + path.join("prompts"), + path.join("src") + .join("agentic") + .join("agents") + .join("prompts"), + path.join("src").join("agentic").join("prompts"), + path.join("src") + .join("crates") + .join("assembly") + .join("core"), + path.join("src") + .join("crates") + .join("assembly") + .join("core") + .join("src") + .join("agentic") + .join("agents") + .join("prompts"), + path.join("src") + .join("crates") + .join("assembly") + .join("core") + .join("src") + .join("agentic") + .join("prompts"), + ]; + + for candidate in candidates { + if !candidate.is_dir() { + continue; + } + let normalized = dunce::simplified(&candidate).to_path_buf(); + if !roots.iter().any(|existing| existing == &normalized) { + roots.push(normalized); + } + } +} + +fn read_runtime_prompt(root: &Path, relative_path: &Path) -> Option { + for extension in ["md", "txt"] { + let path = root.join(relative_path).with_extension(extension); + if !path.is_file() { + continue; + } + + let content = std::fs::read_to_string(&path).ok()?; + return Some(RuntimePrompt { + content, + cache_fingerprint: file_cache_fingerprint(&path), + }); + } + + None +} + +fn file_cache_fingerprint(path: &Path) -> String { + let path = dunce::simplified(path); + let metadata = match std::fs::metadata(path) { + Ok(metadata) => metadata, + Err(_) => return format!("runtime:{}", path.display()), + }; + let modified_ms = metadata + .modified() + .ok() + .and_then(|modified| modified.duration_since(UNIX_EPOCH).ok()) + .map(|duration| duration.as_millis()) + .unwrap_or(0); + + format!( + "runtime:{}:{}:{}", + path.display(), + metadata.len(), + modified_ms + ) +} + +#[cfg(test)] +mod tests { + use super::{prompt_relative_path, read_runtime_prompt}; + use std::fs; + use std::path::PathBuf; + + #[test] + fn runtime_prompt_reads_markdown_override() { + let tempdir = tempfile::tempdir().expect("tempdir"); + fs::write(tempdir.path().join("agentic_mode.md"), "runtime prompt").expect("write prompt"); + + let prompt = + read_runtime_prompt(tempdir.path(), &PathBuf::from("agentic_mode")).expect("prompt"); + assert_eq!(prompt.content, "runtime prompt"); + assert!(prompt.cache_fingerprint.contains("agentic_mode.md")); + } + + #[test] + fn runtime_prompt_rejects_path_traversal() { + assert!(prompt_relative_path("../secret").is_none()); + } +} diff --git a/src/crates/assembly/core/src/agentic/execution/model_exchange_trace.rs b/src/crates/assembly/core/src/agentic/execution/model_exchange_trace.rs index efd266c70d..25dfe433ce 100644 --- a/src/crates/assembly/core/src/agentic/execution/model_exchange_trace.rs +++ b/src/crates/assembly/core/src/agentic/execution/model_exchange_trace.rs @@ -417,6 +417,10 @@ pub async fn prepare_model_exchange_trace_for_workspace( } async fn current_model_exchange_trace_policy() -> Option { + if let Some(config) = trace_config_from_env() { + return ModelExchangeTracePolicy::from_config(config); + } + let Ok(config_service) = GlobalConfigManager::get_service().await else { return None; }; @@ -428,6 +432,17 @@ async fn current_model_exchange_trace_policy() -> Option Option { + let value = std::env::var("BITFUN_MODEL_EXCHANGE_TRACE").ok()?; + let mode = match value.trim().to_ascii_lowercase().as_str() { + "full" => ModelExchangeTracingMode::Full, + "usage" | "usage_only" | "usage-only" => ModelExchangeTracingMode::UsageOnly, + "off" | "false" | "0" | "" => ModelExchangeTracingMode::Off, + _ => return None, + }; + Some(ModelExchangeTracingConfig { mode }) +} + async fn detect_last_sequence(session_dir: &Path) -> Result { let mut last_sequence = 0u64; let mut entries = match tokio::fs::read_dir(session_dir).await { diff --git a/src/crates/assembly/core/src/agentic/init_agents_md.rs b/src/crates/assembly/core/src/agentic/init_agents_md.rs index 1781f81e15..a8636b28c9 100644 --- a/src/crates/assembly/core/src/agentic/init_agents_md.rs +++ b/src/crates/assembly/core/src/agentic/init_agents_md.rs @@ -1,4 +1,4 @@ -use crate::agentic::agents::get_embedded_prompt; +use crate::agentic::agents::get_prompt_template; use crate::agentic::core::{InternalReminderKind, Message}; use crate::service::config::get_app_language_code; use crate::util::errors::{BitFunError, BitFunResult}; @@ -14,9 +14,9 @@ fn init_agents_md_user_query(is_chinese: bool) -> &'static str { } pub(crate) async fn build_init_agents_md_user_input() -> BitFunResult<(String, Vec)> { - let prompt = get_embedded_prompt(INIT_AGENTS_MD_PROMPT_NAME).ok_or_else(|| { + let prompt = get_prompt_template(INIT_AGENTS_MD_PROMPT_NAME).ok_or_else(|| { BitFunError::Agent(format!( - "{} not found in embedded files", + "{} not found in prompt files", INIT_AGENTS_MD_PROMPT_NAME )) })?; @@ -26,7 +26,7 @@ pub(crate) async fn build_init_agents_md_user_input() -> BitFunResult<(String, V user_query, vec![Message::internal_reminder( InternalReminderKind::InitAgentsMd, - prompt.to_string(), + prompt, )], )) } diff --git a/src/crates/assembly/core/src/agentic/session/session_manager.rs b/src/crates/assembly/core/src/agentic/session/session_manager.rs index 0cf770bd33..4bc886ad71 100644 --- a/src/crates/assembly/core/src/agentic/session/session_manager.rs +++ b/src/crates/assembly/core/src/agentic/session/session_manager.rs @@ -4533,6 +4533,7 @@ mod tests { ModelRoundData { id: round_id.to_string(), turn_id: turn_id.to_string(), + round_group_id: None, round_index: 0, timestamp: 100, text_items: vec![TextItemData { @@ -4545,6 +4546,8 @@ mod tests { is_subagent_item: None, parent_task_tool_id: None, subagent_session_id: None, + attempt_id: None, + attempt_index: None, status: Some("completed".to_string()), }], tool_items: vec![ToolItemData { @@ -4575,6 +4578,8 @@ mod tests { subagent_session_id: None, subagent_model_id: None, subagent_model_alias: None, + attempt_id: None, + attempt_index: None, status: Some("completed".to_string()), interruption_reason: None, }], From 5ffa708ad9ca60e68bc1057dad0cd330e4fbeb7a Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Tue, 7 Jul 2026 19:52:30 +0800 Subject: [PATCH 2/2] chore(harbor): document runtime prompt mounts --- scripts/harbor-build-musl-container.sh | 38 +++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/scripts/harbor-build-musl-container.sh b/scripts/harbor-build-musl-container.sh index 6c652ee1cf..28c4a4bf47 100755 --- a/scripts/harbor-build-musl-container.sh +++ b/scripts/harbor-build-musl-container.sh @@ -10,6 +10,7 @@ REGISTRY_VOLUME="${BITFUN_HARBOR_MUSL_REGISTRY_VOLUME:-bitfun-harbor-musl-cargo- GIT_VOLUME="${BITFUN_HARBOR_MUSL_GIT_VOLUME:-bitfun-harbor-musl-cargo-git}" TARGET_TRIPLE="x86_64-unknown-linux-musl" BINARY="${ROOT}/target/${TARGET_TRIPLE}/release/bitfun-cli" +PROMPTS_DIR="${BITFUN_HARBOR_PROMPTS_DIR:-}" usage() { cat <&2 + exit 1 + fi + if [[ ! -f "${PROMPTS_DIR}/agentic_mode.md" ]]; then + echo "error: prompt directory must contain agentic_mode.md: ${PROMPTS_DIR}" >&2 + exit 1 + fi + printf '%s\n' \ + -v "${PROMPTS_DIR}:/prompts:ro" \ + -e "BITFUN_PROMPTS_DIR=/prompts" +} + cmd_build_image() { docker build -f "${DOCKERFILE}" -t "${IMAGE}" "${ROOT}" echo "Built image: ${IMAGE}" @@ -128,10 +151,16 @@ cmd_test_binary() { file "${BINARY}" ldd "${BINARY}" || true + mapfile -t prompt_args < <(prompt_docker_args) + if [[ ${#prompt_args[@]} -gt 0 ]]; then + echo "Runtime prompts: ${PROMPTS_DIR} -> /prompts" + fi + echo echo "Ubuntu smoke test:" docker run --rm \ -v "${BINARY}:/usr/local/bin/bitfun-cli:ro" \ + "${prompt_args[@]}" \ ubuntu:22.04 \ /usr/local/bin/bitfun-cli --version @@ -139,6 +168,7 @@ cmd_test_binary() { echo "Alpine smoke test:" docker run --rm \ -v "${BINARY}:/usr/local/bin/bitfun-cli:ro" \ + "${prompt_args[@]}" \ alpine:3.20 \ /usr/local/bin/bitfun-cli --version } @@ -156,6 +186,12 @@ cmd_status() { echo "Volumes:" echo " ${REGISTRY_VOLUME}" echo " ${GIT_VOLUME}" + echo "Runtime prompts:" + if [[ -n "${PROMPTS_DIR}" ]]; then + echo " ${PROMPTS_DIR} -> /prompts (BITFUN_PROMPTS_DIR=/prompts)" + else + echo " (not configured; set BITFUN_HARBOR_PROMPTS_DIR)" + fi echo "Binary:" if [[ -e "${BINARY}" ]]; then ls -lh "${BINARY}"