fix: Update multiple modules for i18n and drive monitoring
Some checks failed
BotServer CI / build (push) Failing after 6m22s

- Update auto_task modules (app_generator, designer_ai, intent_classifier)
- Refactor use_tool.rs for better structure
- Update bot core and website crawler
- Improve drive_monitor and local_file_monitor
- Update bootstrap module

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Rodrigo Rodriguez 2026-02-14 22:06:57 +00:00
parent d1d591ddcb
commit e34848507d
9 changed files with 63 additions and 59 deletions

View file

@ -1552,7 +1552,7 @@ impl AppGenerator {
); );
for (idx, tool) in llm_app.tools.iter().enumerate() { for (idx, tool) in llm_app.tools.iter().enumerate() {
let tool_path = format!(".gbdialog/tools/{}", tool.filename); let tool_path = format!("{}.gbdialog/tools/{}", llm_app.name, tool.filename);
self.files_written.push(format!("tools/{}", tool.filename)); self.files_written.push(format!("tools/{}", tool.filename));
self.bytes_generated += tool.content.len() as u64; self.bytes_generated += tool.content.len() as u64;
@ -1624,7 +1624,7 @@ impl AppGenerator {
); );
for (idx, scheduler) in llm_app.schedulers.iter().enumerate() { for (idx, scheduler) in llm_app.schedulers.iter().enumerate() {
let scheduler_path = format!(".gbdialog/schedulers/{}", scheduler.filename); let scheduler_path = format!("{}.gbdialog/schedulers/{}", llm_app.name, scheduler.filename);
self.files_written self.files_written
.push(format!("schedulers/{}", scheduler.filename)); .push(format!("schedulers/{}", scheduler.filename));
self.bytes_generated += scheduler.content.len() as u64; self.bytes_generated += scheduler.content.len() as u64;

View file

@ -330,8 +330,8 @@ Guidelines:
- STYLE: Changes to CSS files (colors, layout, fonts, spacing) - STYLE: Changes to CSS files (colors, layout, fonts, spacing)
- HTML: Changes to HTML structure (forms, buttons, elements) - HTML: Changes to HTML structure (forms, buttons, elements)
- DATABASE: Adding fields to tables.bas or creating new tables - DATABASE: Adding fields to tables.bas or creating new tables
- TOOL: Creating/modifying .gbdialog/tools/*.bas files - TOOL: Creating/modifying {botname}.gbdialog/tools/*.bas files
- SCHEDULER: Creating/modifying .gbdialog/schedulers/*.bas files - SCHEDULER: Creating/modifying {botname}.gbdialog/schedulers/*.bas files
- Require confirmation for: deletions, bulk changes, database schema changes - Require confirmation for: deletions, bulk changes, database schema changes
- Use the current_app and current_page context to determine which files to modify - Use the current_app and current_page context to determine which files to modify
@ -432,7 +432,7 @@ Respond ONLY with valid JSON."#
{ {
( (
ModificationType::Tool, ModificationType::Tool,
".gbdialog/tools/new-tool.bas".to_string(), "{botname}.gbdialog/tools/new-tool.bas".to_string(),
) )
} else if lower.contains("schedule") } else if lower.contains("schedule")
|| lower.contains("every day") || lower.contains("every day")
@ -441,7 +441,7 @@ Respond ONLY with valid JSON."#
{ {
( (
ModificationType::Scheduler, ModificationType::Scheduler,
".gbdialog/schedulers/new-scheduler.bas".to_string(), "{botname}.gbdialog/schedulers/new-scheduler.bas".to_string(),
) )
} else { } else {
(ModificationType::Unknown, "".to_string()) (ModificationType::Unknown, "".to_string())
@ -762,7 +762,7 @@ Respond ONLY with valid JSON."#
session: &UserSession, session: &UserSession,
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
let site_path = self.get_site_path(); let site_path = self.get_site_path();
let tools_path = format!("{}/{}.gbai/.gbdialog/tools", site_path, session.bot_id); let tools_path = format!("{}/{}.gbai/{}.gbdialog/tools", site_path, session.bot_id, session.bot_id);
let mut tools = Vec::new(); let mut tools = Vec::new();
if let Ok(entries) = std::fs::read_dir(&tools_path) { if let Ok(entries) = std::fs::read_dir(&tools_path) {
@ -783,7 +783,7 @@ Respond ONLY with valid JSON."#
session: &UserSession, session: &UserSession,
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
let site_path = self.get_site_path(); let site_path = self.get_site_path();
let schedulers_path = format!("{}/{}.gbai/.gbdialog/schedulers", site_path, session.bot_id); let schedulers_path = format!("{}/{}.gbai/{}.gbdialog/schedulers", site_path, session.bot_id, session.bot_id);
let mut schedulers = Vec::new(); let mut schedulers = Vec::new();
if let Ok(entries) = std::fs::read_dir(&schedulers_path) { if let Ok(entries) = std::fs::read_dir(&schedulers_path) {

View file

@ -677,8 +677,8 @@ END ON
Utc::now().format("%Y-%m-%d %H:%M") Utc::now().format("%Y-%m-%d %H:%M")
); );
// Save to .gbdialog/events/ // Save to {bot_id}.gbdialog/events/
let event_path = format!(".gbdialog/events/{handler_name}"); let event_path = format!("{}.gbdialog/events/{handler_name}", session.bot_id);
self.save_basic_file(session.bot_id, &event_path, &basic_code)?; self.save_basic_file(session.bot_id, &event_path, &basic_code)?;
Ok(IntentResult { Ok(IntentResult {
@ -889,8 +889,8 @@ END SCHEDULE
classification.original_text classification.original_text
); );
// Save to .gbdialog/schedulers/ // Save to {bot_id}.gbdialog/schedulers/
let scheduler_path = format!(".gbdialog/schedulers/{scheduler_file}"); let scheduler_path = format!("{}.gbdialog/schedulers/{scheduler_file}", session.bot_id);
self.save_basic_file(session.bot_id, &scheduler_path, &basic_code)?; self.save_basic_file(session.bot_id, &scheduler_path, &basic_code)?;
let schedule_id = Uuid::new_v4(); let schedule_id = Uuid::new_v4();
@ -962,7 +962,7 @@ END GOAL
// Save to .gbdialog/goals/ // Save to .gbdialog/goals/
let goal_file = format!("{}.bas", goal_name.to_lowercase().replace(' ', "-")); let goal_file = format!("{}.bas", goal_name.to_lowercase().replace(' ', "-"));
let goal_path = format!(".gbdialog/goals/{goal_file}"); let goal_path = format!("{}.gbdialog/goals/{goal_file}", session.bot_id);
self.save_basic_file(session.bot_id, &goal_path, &basic_code)?; self.save_basic_file(session.bot_id, &goal_path, &basic_code)?;
Ok(IntentResult { Ok(IntentResult {
@ -1029,8 +1029,8 @@ END TRIGGER
classification.original_text classification.original_text
); );
// Save to .gbdialog/tools/ // Save to {bot_id}.gbdialog/tools/
let tool_path = format!(".gbdialog/tools/{tool_file}"); let tool_path = format!("{}.gbdialog/tools/{tool_file}", session.bot_id);
self.save_basic_file(session.bot_id, &tool_path, &basic_code)?; self.save_basic_file(session.bot_id, &tool_path, &basic_code)?;
Ok(IntentResult { Ok(IntentResult {

View file

@ -19,12 +19,15 @@ pub fn use_tool_keyword(state: Arc<AppState>, user: UserSession, engine: &mut En
tool_path_str, tool_path_str,
user_clone.id user_clone.id
); );
let tool_name = tool_path_str // Strip {bot_name}.gbdialog/ or .gbdialog/ prefix, and .bas suffix
.strip_prefix(".gbdialog/") let tool_name = if let Some(idx) = tool_path_str.find(".gbdialog/") {
.unwrap_or(&tool_path_str) tool_path_str[idx + 10..] // Skip past ".gbdialog/"
.strip_suffix(".bas") } else {
.unwrap_or(&tool_path_str) &tool_path_str
.to_string(); }
.strip_suffix(".bas")
.unwrap_or_else(|| &tool_path_str)
.to_string();
if tool_name.is_empty() { if tool_name.is_empty() {
return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( return Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
"Invalid tool name".into(), "Invalid tool name".into(),
@ -86,12 +89,15 @@ pub fn use_tool_keyword(state: Arc<AppState>, user: UserSession, engine: &mut En
tool_path_str, tool_path_str,
user_clone2.id user_clone2.id
); );
let tool_name = tool_path_str // Strip {bot_name}.gbdialog/ or .gbdialog/ prefix, and .bas suffix
.strip_prefix(".gbdialog/") let tool_name = if let Some(idx) = tool_path_str.find(".gbdialog/") {
.unwrap_or(&tool_path_str) &tool_path_str[idx + 10..] // Skip past ".gbdialog/"
.strip_suffix(".bas") } else {
.unwrap_or(&tool_path_str) &tool_path_str
.to_string(); }
.strip_suffix(".bas")
.unwrap_or_else(|| &tool_path_str)
.to_string();
if tool_name.is_empty() { if tool_name.is_empty() {
return Dynamic::from("ERROR: Invalid tool name"); return Dynamic::from("ERROR: Invalid tool name");
} }
@ -140,12 +146,15 @@ pub fn use_tool_keyword(state: Arc<AppState>, user: UserSession, engine: &mut En
tool_path_str, tool_path_str,
user_clone3.id user_clone3.id
); );
let tool_name = tool_path_str // Strip {bot_name}.gbdialog/ or .gbdialog/ prefix, and .bas suffix
.strip_prefix(".gbdialog/") let tool_name = if let Some(idx) = tool_path_str.find(".gbdialog/") {
.unwrap_or(&tool_path_str) &tool_path_str[idx + 10..] // Skip past ".gbdialog/"
.strip_suffix(".bas") } else {
.unwrap_or(&tool_path_str) &tool_path_str
.to_string(); }
.strip_suffix(".bas")
.unwrap_or_else(|| &tool_path_str)
.to_string();
if tool_name.is_empty() { if tool_name.is_empty() {
return Dynamic::from("ERROR: Invalid tool name"); return Dynamic::from("ERROR: Invalid tool name");
} }

View file

@ -227,8 +227,7 @@ impl BotOrchestrator {
let mut bots_mounted = 0; let mut bots_mounted = 0;
let mut bots_created = 0; let mut bots_created = 0;
let home_dir = std::env::var("HOME").unwrap_or_else(|_| ".".to_string()); let data_dir = "/opt/gbo/data";
let data_dir = format!("{}/data", home_dir);
let directories_to_scan: Vec<std::path::PathBuf> = vec![ let directories_to_scan: Vec<std::path::PathBuf> = vec![
self.state self.state
@ -293,9 +292,9 @@ impl BotOrchestrator {
*bots_mounted += 1; *bots_mounted += 1;
} }
Ok(false) => { Ok(false) => {
// Auto-create bots found in ~/data // Auto-create bots found in /opt/gbo/data
if dir_path.to_string_lossy().contains("/data") { if dir_path.to_string_lossy().contains("/data") {
info!("Auto-creating bot '{}' from ~/data", bot_name); info!("Auto-creating bot '{}' from /opt/gbo/data", bot_name);
match self.create_bot_simple(bot_name) { match self.create_bot_simple(bot_name) {
Ok(_) => { Ok(_) => {
info!("Bot '{}' created successfully", bot_name); info!("Bot '{}' created successfully", bot_name);
@ -489,8 +488,7 @@ impl BotOrchestrator {
if should_execute_start_bas { if should_execute_start_bas {
// Always execute start.bas for this session (blocking - wait for completion) // Always execute start.bas for this session (blocking - wait for completion)
let home_dir = std::env::var("HOME").unwrap_or_else(|_| ".".to_string()); let data_dir = "/opt/gbo/data";
let data_dir = format!("{}/data", home_dir);
let start_script_path = format!("{}/{}.gbai/{}.gbdialog/start.bas", data_dir, bot_name_for_context, bot_name_for_context); let start_script_path = format!("{}/{}.gbai/{}.gbdialog/start.bas", data_dir, bot_name_for_context, bot_name_for_context);
info!("[START_BAS] Executing start.bas for session {} at: {}", actual_session_id, start_script_path); info!("[START_BAS] Executing start.bas for session {} at: {}", actual_session_id, start_script_path);
@ -1088,8 +1086,7 @@ async fn handle_websocket(
}; };
if should_execute_start_bas { if should_execute_start_bas {
let home_dir = std::env::var("HOME").unwrap_or_else(|_| ".".to_string()); let data_dir = "/opt/gbo/data";
let data_dir = format!("{}/data", home_dir);
let start_script_path = format!("{}/{}.gbai/{}.gbdialog/start.bas", data_dir, bot_name, bot_name); let start_script_path = format!("{}/{}.gbai/{}.gbdialog/start.bas", data_dir, bot_name, bot_name);
info!("Looking for start.bas at: {}", start_script_path); info!("Looking for start.bas at: {}", start_script_path);

View file

@ -352,7 +352,7 @@ impl WebsiteCrawlerService {
Err(_) => continue, // Skip if bot not found Err(_) => continue, // Skip if bot not found
}; };
// Scan .gbdialog directory for .bas files // Scan {bot_name}.gbdialog directory for .bas files
let dialog_dir = path.join(format!("{}.gbdialog", bot_name)); let dialog_dir = path.join(format!("{}.gbdialog", bot_name));
if dialog_dir.exists() { if dialog_dir.exists() {
self.scan_directory_for_websites(&dialog_dir, bot_id, &mut conn)?; self.scan_directory_for_websites(&dialog_dir, bot_id, &mut conn)?;

View file

@ -462,7 +462,7 @@ impl DriveMonitor {
&self, &self,
client: &Client, client: &Client,
) -> Result<(), Box<dyn Error + Send + Sync>> { ) -> Result<(), Box<dyn Error + Send + Sync>> {
let prefix = ".gbdialog/"; // No prefix filter - list all and filter by *.gbdialog pattern below
let mut current_files = HashMap::new(); let mut current_files = HashMap::new();
let mut continuation_token = None; let mut continuation_token = None;
loop { loop {
@ -486,6 +486,7 @@ impl DriveMonitor {
for obj in list_objects.contents.unwrap_or_default() { for obj in list_objects.contents.unwrap_or_default() {
let path = obj.key().unwrap_or_default().to_string(); let path = obj.key().unwrap_or_default().to_string();
let path_parts: Vec<&str> = path.split('/').collect(); let path_parts: Vec<&str> = path.split('/').collect();
// Filter for paths matching *.gbdialog/*.bas pattern
if path_parts.len() < 2 || !path_parts[0].ends_with(".gbdialog") { if path_parts.len() < 2 || !path_parts[0].ends_with(".gbdialog") {
continue; continue;
} }

View file

@ -30,10 +30,8 @@ pub struct LocalFileMonitor {
impl LocalFileMonitor { impl LocalFileMonitor {
pub fn new(state: Arc<AppState>) -> Self { pub fn new(state: Arc<AppState>) -> Self {
// Use ~/data as the base directory // Use /opt/gbo/data as the base directory
let data_dir = PathBuf::from(std::env::var("HOME") let data_dir = PathBuf::from("/opt/gbo/data");
.unwrap_or_else(|_| ".".to_string()))
.join("data");
// Use botserver/work as the work directory for generated files // Use botserver/work as the work directory for generated files
let work_root = PathBuf::from("work"); let work_root = PathBuf::from("work");
@ -50,7 +48,7 @@ impl LocalFileMonitor {
} }
pub async fn start_monitoring(&self) -> Result<(), Box<dyn Error + Send + Sync>> { pub async fn start_monitoring(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
info!("[LOCAL_MONITOR] Starting local file monitor for ~/data/*.gbai directories"); info!("[LOCAL_MONITOR] Starting local file monitor for /opt/gbo/data/*.gbai directories");
// Create data directory if it doesn't exist // Create data directory if it doesn't exist
if let Err(e) = tokio::fs::create_dir_all(&self.data_dir).await { if let Err(e) = tokio::fs::create_dir_all(&self.data_dir).await {
@ -157,7 +155,7 @@ impl LocalFileMonitor {
} }
fn is_gbdialog_file(&self, path: &Path) -> bool { fn is_gbdialog_file(&self, path: &Path) -> bool {
// Check if path is something like ~/data/*.gbai/.gbdialog/*.bas // Check if path is something like /opt/gbo/data/*.gbai/.gbdialog/*.bas
path.extension() path.extension()
.and_then(|e| e.to_str()) .and_then(|e| e.to_str())
.map(|e| e.eq_ignore_ascii_case("bas")) .map(|e| e.eq_ignore_ascii_case("bas"))
@ -167,7 +165,7 @@ impl LocalFileMonitor {
} }
async fn scan_and_compile_all(&self) -> Result<(), Box<dyn Error + Send + Sync>> { async fn scan_and_compile_all(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
debug!("[LOCAL_MONITOR] Scanning ~/data for .gbai directories"); debug!("[LOCAL_MONITOR] Scanning /opt/gbo/data for .gbai directories");
let entries = match tokio::fs::read_dir(&self.data_dir).await { let entries = match tokio::fs::read_dir(&self.data_dir).await {
Ok(e) => e, Ok(e) => e,
@ -252,7 +250,7 @@ impl LocalFileMonitor {
.and_then(|s| s.to_str()) .and_then(|s| s.to_str())
.unwrap_or("unknown"); .unwrap_or("unknown");
// Extract bot name from path like ~/data/cristo.gbai/.gbdialog/file.bas // Extract bot name from path like /opt/gbo/data/cristo.gbai/.gbdialog/file.bas
let bot_name = file_path let bot_name = file_path
.ancestors() .ancestors()
.find(|p| p.extension().and_then(|e| e.to_str()).map(|e| e.eq_ignore_ascii_case("gbai")).unwrap_or(false)) .find(|p| p.extension().and_then(|e| e.to_str()).map(|e| e.eq_ignore_ascii_case("gbai")).unwrap_or(false))

View file

@ -795,29 +795,28 @@ async fn start_drive_monitors(
} }
}); });
// Start local file monitor for ~/data/*.gbai directories // Start local file monitor for /opt/gbo/data/*.gbai directories
let local_monitor_state = app_state.clone(); let local_monitor_state = app_state.clone();
tokio::spawn(async move { tokio::spawn(async move {
register_thread("local-file-monitor", "drive"); register_thread("local-file-monitor", "drive");
trace!("Starting LocalFileMonitor for ~/data/*.gbai directories"); trace!("Starting LocalFileMonitor for /opt/gbo/data/*.gbai directories");
let monitor = crate::drive::local_file_monitor::LocalFileMonitor::new(local_monitor_state); let monitor = crate::drive::local_file_monitor::LocalFileMonitor::new(local_monitor_state);
if let Err(e) = monitor.start_monitoring().await { if let Err(e) = monitor.start_monitoring().await {
error!("LocalFileMonitor failed: {}", e); error!("LocalFileMonitor failed: {}", e);
} else { } else {
info!("LocalFileMonitor started - watching ~/data/*.gbai/.gbdialog/*.bas"); info!("LocalFileMonitor started - watching /opt/gbo/data/*.gbai/*.gbdialog/*.bas");
} }
}); });
// Start config file watcher for ~/data/*.gbai/*.gbot/config.csv // Start config file watcher for /opt/gbo/data/*.gbai/*.gbot/config.csv
let config_watcher_state = app_state.clone(); let config_watcher_state = app_state.clone();
tokio::spawn(async move { tokio::spawn(async move {
register_thread("config-file-watcher", "drive"); register_thread("config-file-watcher", "drive");
trace!("Starting ConfigWatcher for ~/data/*.gbai/*.gbot/config.csv"); trace!("Starting ConfigWatcher for /opt/gbo/data/*.gbai/*.gbot/config.csv");
// Determine data directory // Determine data directory
let data_dir = std::env::var("DATA_DIR") let data_dir = std::env::var("DATA_DIR")
.or_else(|_| std::env::var("HOME").map(|h| format!("{}/data", h))) .unwrap_or_else(|_| "/opt/gbo/data".to_string());
.unwrap_or_else(|_| "./botserver-stack/data".to_string());
let data_dir = std::path::PathBuf::from(data_dir); let data_dir = std::path::PathBuf::from(data_dir);
let watcher = crate::core::config::watcher::ConfigWatcher::new( let watcher = crate::core::config::watcher::ConfigWatcher::new(
@ -826,6 +825,6 @@ async fn start_drive_monitors(
); );
Arc::new(watcher).spawn(); Arc::new(watcher).spawn();
info!("ConfigWatcher started - watching ~/data/*.gbai/*.gbot/config.csv"); info!("ConfigWatcher started - watching /opt/gbo/data/*.gbai/*.gbot/config.csv");
}); });
} }