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() {
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.bytes_generated += tool.content.len() as u64;
@ -1624,7 +1624,7 @@ impl AppGenerator {
);
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
.push(format!("schedulers/{}", scheduler.filename));
self.bytes_generated += scheduler.content.len() as u64;

View file

@ -330,8 +330,8 @@ Guidelines:
- STYLE: Changes to CSS files (colors, layout, fonts, spacing)
- HTML: Changes to HTML structure (forms, buttons, elements)
- DATABASE: Adding fields to tables.bas or creating new tables
- TOOL: Creating/modifying .gbdialog/tools/*.bas files
- SCHEDULER: Creating/modifying .gbdialog/schedulers/*.bas files
- TOOL: Creating/modifying {botname}.gbdialog/tools/*.bas files
- SCHEDULER: Creating/modifying {botname}.gbdialog/schedulers/*.bas files
- Require confirmation for: deletions, bulk changes, database schema changes
- 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,
".gbdialog/tools/new-tool.bas".to_string(),
"{botname}.gbdialog/tools/new-tool.bas".to_string(),
)
} else if lower.contains("schedule")
|| lower.contains("every day")
@ -441,7 +441,7 @@ Respond ONLY with valid JSON."#
{
(
ModificationType::Scheduler,
".gbdialog/schedulers/new-scheduler.bas".to_string(),
"{botname}.gbdialog/schedulers/new-scheduler.bas".to_string(),
)
} else {
(ModificationType::Unknown, "".to_string())
@ -762,7 +762,7 @@ Respond ONLY with valid JSON."#
session: &UserSession,
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
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();
if let Ok(entries) = std::fs::read_dir(&tools_path) {
@ -783,7 +783,7 @@ Respond ONLY with valid JSON."#
session: &UserSession,
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
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();
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")
);
// Save to .gbdialog/events/
let event_path = format!(".gbdialog/events/{handler_name}");
// Save to {bot_id}.gbdialog/events/
let event_path = format!("{}.gbdialog/events/{handler_name}", session.bot_id);
self.save_basic_file(session.bot_id, &event_path, &basic_code)?;
Ok(IntentResult {
@ -889,8 +889,8 @@ END SCHEDULE
classification.original_text
);
// Save to .gbdialog/schedulers/
let scheduler_path = format!(".gbdialog/schedulers/{scheduler_file}");
// Save to {bot_id}.gbdialog/schedulers/
let scheduler_path = format!("{}.gbdialog/schedulers/{scheduler_file}", session.bot_id);
self.save_basic_file(session.bot_id, &scheduler_path, &basic_code)?;
let schedule_id = Uuid::new_v4();
@ -962,7 +962,7 @@ END GOAL
// Save to .gbdialog/goals/
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)?;
Ok(IntentResult {
@ -1029,8 +1029,8 @@ END TRIGGER
classification.original_text
);
// Save to .gbdialog/tools/
let tool_path = format!(".gbdialog/tools/{tool_file}");
// Save to {bot_id}.gbdialog/tools/
let tool_path = format!("{}.gbdialog/tools/{tool_file}", session.bot_id);
self.save_basic_file(session.bot_id, &tool_path, &basic_code)?;
Ok(IntentResult {

View file

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

View file

@ -227,8 +227,7 @@ impl BotOrchestrator {
let mut bots_mounted = 0;
let mut bots_created = 0;
let home_dir = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
let data_dir = format!("{}/data", home_dir);
let data_dir = "/opt/gbo/data";
let directories_to_scan: Vec<std::path::PathBuf> = vec![
self.state
@ -293,9 +292,9 @@ impl BotOrchestrator {
*bots_mounted += 1;
}
Ok(false) => {
// Auto-create bots found in ~/data
// Auto-create bots found in /opt/gbo/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) {
Ok(_) => {
info!("Bot '{}' created successfully", bot_name);
@ -489,8 +488,7 @@ impl BotOrchestrator {
if should_execute_start_bas {
// 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 = format!("{}/data", home_dir);
let data_dir = "/opt/gbo/data";
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);
@ -1088,8 +1086,7 @@ async fn handle_websocket(
};
if should_execute_start_bas {
let home_dir = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
let data_dir = format!("{}/data", home_dir);
let data_dir = "/opt/gbo/data";
let start_script_path = format!("{}/{}.gbai/{}.gbdialog/start.bas", data_dir, bot_name, bot_name);
info!("Looking for start.bas at: {}", start_script_path);

View file

@ -352,7 +352,7 @@ impl WebsiteCrawlerService {
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));
if dialog_dir.exists() {
self.scan_directory_for_websites(&dialog_dir, bot_id, &mut conn)?;

View file

@ -462,7 +462,7 @@ impl DriveMonitor {
&self,
client: &Client,
) -> 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 continuation_token = None;
loop {
@ -486,6 +486,7 @@ impl DriveMonitor {
for obj in list_objects.contents.unwrap_or_default() {
let path = obj.key().unwrap_or_default().to_string();
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") {
continue;
}

View file

@ -30,10 +30,8 @@ pub struct LocalFileMonitor {
impl LocalFileMonitor {
pub fn new(state: Arc<AppState>) -> Self {
// Use ~/data as the base directory
let data_dir = PathBuf::from(std::env::var("HOME")
.unwrap_or_else(|_| ".".to_string()))
.join("data");
// Use /opt/gbo/data as the base directory
let data_dir = PathBuf::from("/opt/gbo/data");
// Use botserver/work as the work directory for generated files
let work_root = PathBuf::from("work");
@ -50,7 +48,7 @@ impl LocalFileMonitor {
}
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
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 {
// Check if path is something like ~/data/*.gbai/.gbdialog/*.bas
// Check if path is something like /opt/gbo/data/*.gbai/.gbdialog/*.bas
path.extension()
.and_then(|e| e.to_str())
.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>> {
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 {
Ok(e) => e,
@ -252,7 +250,7 @@ impl LocalFileMonitor {
.and_then(|s| s.to_str())
.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
.ancestors()
.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();
tokio::spawn(async move {
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);
if let Err(e) = monitor.start_monitoring().await {
error!("LocalFileMonitor failed: {}", e);
} 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();
tokio::spawn(async move {
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
let data_dir = std::env::var("DATA_DIR")
.or_else(|_| std::env::var("HOME").map(|h| format!("{}/data", h)))
.unwrap_or_else(|_| "./botserver-stack/data".to_string());
.unwrap_or_else(|_| "/opt/gbo/data".to_string());
let data_dir = std::path::PathBuf::from(data_dir);
let watcher = crate::core::config::watcher::ConfigWatcher::new(
@ -826,6 +825,6 @@ async fn start_drive_monitors(
);
Arc::new(watcher).spawn();
info!("ConfigWatcher started - watching ~/data/*.gbai/*.gbot/config.csv");
info!("ConfigWatcher started - watching /opt/gbo/data/*.gbai/*.gbot/config.csv");
});
}