diff --git a/src/app/core/core_library.cmake b/src/app/core/core_library.cmake index 3598809a..62eba6b4 100644 --- a/src/app/core/core_library.cmake +++ b/src/app/core/core_library.cmake @@ -83,9 +83,9 @@ if(YAZE_WITH_GRPC) # Add proto definitions for test harness and ROM service target_add_protobuf(yaze_core_lib - ${CMAKE_SOURCE_DIR}/src/app/core/proto/imgui_test_harness.proto) + ${PROJECT_SOURCE_DIR}/src/app/core/proto/imgui_test_harness.proto) target_add_protobuf(yaze_core_lib - ${CMAKE_SOURCE_DIR}/protos/rom_service.proto) + ${PROJECT_SOURCE_DIR}/protos/rom_service.proto) # Add test harness implementation target_sources(yaze_core_lib PRIVATE diff --git a/src/app/net/net_library.cmake b/src/app/net/net_library.cmake index 7372c165..ba7eb0dd 100644 --- a/src/app/net/net_library.cmake +++ b/src/app/net/net_library.cmake @@ -67,7 +67,7 @@ endif() # Add gRPC support for ROM service if(YAZE_WITH_GRPC) - target_add_protobuf(yaze_net ${CMAKE_SOURCE_DIR}/protos/rom_service.proto) + target_add_protobuf(yaze_net ${PROJECT_SOURCE_DIR}/protos/rom_service.proto) target_link_libraries(yaze_net PUBLIC grpc++ diff --git a/src/cli/agent.cmake b/src/cli/agent.cmake index e2fae01b..3fbffc4a 100644 --- a/src/cli/agent.cmake +++ b/src/cli/agent.cmake @@ -67,10 +67,12 @@ _yaze_ensure_yaml_cpp(YAZE_YAML_CPP_TARGET) set(YAZE_AGENT_SOURCES cli/service/agent/proposal_executor.cc cli/handlers/agent/tool_commands.cc + cli/handlers/agent/todo_commands.cc cli/service/agent/conversational_agent_service.cc cli/service/agent/simple_chat_session.cc cli/service/agent/tool_dispatcher.cc cli/service/agent/learned_knowledge_service.cc + cli/service/agent/todo_manager.cc cli/service/ai/ai_service.cc cli/service/ai/ai_action_parser.cc cli/service/ai/vision_action_refiner.cc @@ -79,6 +81,7 @@ set(YAZE_AGENT_SOURCES cli/service/ai/prompt_builder.cc cli/service/ai/service_factory.cc cli/service/gui/gui_action_generator.cc + cli/service/gui/gui_automation_client.cc cli/service/net/z3ed_network_client.cc cli/handlers/net/net_commands.cc cli/service/planning/policy_evaluator.cc @@ -149,6 +152,10 @@ endif() # Add gRPC support for GUI automation if(YAZE_WITH_GRPC) + # Generate proto files for yaze_agent + target_add_protobuf(yaze_agent + ${PROJECT_SOURCE_DIR}/src/app/core/proto/imgui_test_harness.proto) + target_link_libraries(yaze_agent PUBLIC grpc++ grpc++_reflection diff --git a/src/cli/handlers/agent/todo_commands.cc b/src/cli/handlers/agent/todo_commands.cc new file mode 100644 index 00000000..92ec0b02 --- /dev/null +++ b/src/cli/handlers/agent/todo_commands.cc @@ -0,0 +1,302 @@ +#include "cli/handlers/agent/todo_commands.h" + +#include + +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_split.h" +#include "cli/service/agent/todo_manager.h" + +namespace yaze { +namespace cli { +namespace handlers { + +namespace { + +using agent::TodoItem; +using agent::TodoManager; + +// Global TODO manager instance +TodoManager& GetTodoManager() { + static TodoManager manager; + static bool initialized = false; + if (!initialized) { + auto status = manager.Initialize(); + if (!status.ok()) { + std::cerr << "Warning: Failed to initialize TODO manager: " + << status.message() << std::endl; + } + initialized = true; + } + return manager; +} + +void PrintTodo(const TodoItem& item, bool detailed = false) { + std::string status_emoji; + switch (item.status) { + case TodoItem::Status::PENDING: status_emoji = "⏳"; break; + case TodoItem::Status::IN_PROGRESS: status_emoji = "🔄"; break; + case TodoItem::Status::COMPLETED: status_emoji = "✅"; break; + case TodoItem::Status::BLOCKED: status_emoji = "🚫"; break; + case TodoItem::Status::CANCELLED: status_emoji = "❌"; break; + } + + std::cout << absl::StreamFormat("[%s] %s %s", + item.id, + status_emoji, + item.description); + + if (!item.category.empty()) { + std::cout << absl::StreamFormat(" [%s]", item.category); + } + + if (item.priority > 0) { + std::cout << absl::StreamFormat(" (priority: %d)", item.priority); + } + + std::cout << std::endl; + + if (detailed) { + std::cout << " Status: " << item.StatusToString() << std::endl; + std::cout << " Created: " << item.created_at << std::endl; + std::cout << " Updated: " << item.updated_at << std::endl; + + if (!item.dependencies.empty()) { + std::cout << " Dependencies: " << absl::StrJoin(item.dependencies, ", ") << std::endl; + } + + if (!item.tools_needed.empty()) { + std::cout << " Tools needed: " << absl::StrJoin(item.tools_needed, ", ") << std::endl; + } + + if (!item.notes.empty()) { + std::cout << " Notes: " << item.notes << std::endl; + } + } +} + +absl::Status HandleTodoCreate(const std::vector& args) { + if (args.empty()) { + return absl::InvalidArgumentError("Usage: agent todo create [--category=] [--priority=]"); + } + + std::string description = args[0]; + std::string category; + int priority = 0; + + for (size_t i = 1; i < args.size(); ++i) { + if (args[i].find("--category=") == 0) { + category = args[i].substr(11); + } else if (args[i].find("--priority=") == 0) { + priority = std::stoi(args[i].substr(11)); + } + } + + auto& manager = GetTodoManager(); + auto result = manager.CreateTodo(description, category, priority); + + if (!result.ok()) { + return result.status(); + } + + std::cout << "Created TODO:" << std::endl; + PrintTodo(*result, true); + + return absl::OkStatus(); +} + +absl::Status HandleTodoList(const std::vector& args) { + std::string status_filter; + std::string category_filter; + + for (const auto& arg : args) { + if (arg.find("--status=") == 0) { + status_filter = arg.substr(9); + } else if (arg.find("--category=") == 0) { + category_filter = arg.substr(11); + } + } + + auto& manager = GetTodoManager(); + std::vector todos; + + if (!status_filter.empty()) { + auto status = TodoItem::StringToStatus(status_filter); + todos = manager.GetTodosByStatus(status); + } else if (!category_filter.empty()) { + todos = manager.GetTodosByCategory(category_filter); + } else { + todos = manager.GetAllTodos(); + } + + if (todos.empty()) { + std::cout << "No TODOs found." << std::endl; + return absl::OkStatus(); + } + + std::cout << "TODOs (" << todos.size() << "):" << std::endl; + for (const auto& item : todos) { + PrintTodo(item); + } + + return absl::OkStatus(); +} + +absl::Status HandleTodoUpdate(const std::vector& args) { + if (args.size() < 2) { + return absl::InvalidArgumentError("Usage: agent todo update --status="); + } + + std::string id = args[0]; + std::string new_status_str; + + for (size_t i = 1; i < args.size(); ++i) { + if (args[i].find("--status=") == 0) { + new_status_str = args[i].substr(9); + } + } + + if (new_status_str.empty()) { + return absl::InvalidArgumentError("--status parameter is required"); + } + + auto new_status = TodoItem::StringToStatus(new_status_str); + auto& manager = GetTodoManager(); + auto status = manager.UpdateStatus(id, new_status); + + if (!status.ok()) { + return status; + } + + std::cout << "Updated TODO " << id << " to status: " << new_status_str << std::endl; + return absl::OkStatus(); +} + +absl::Status HandleTodoShow(const std::vector& args) { + if (args.empty()) { + return absl::InvalidArgumentError("Usage: agent todo show "); + } + + std::string id = args[0]; + auto& manager = GetTodoManager(); + auto result = manager.GetTodo(id); + + if (!result.ok()) { + return result.status(); + } + + PrintTodo(*result, true); + return absl::OkStatus(); +} + +absl::Status HandleTodoDelete(const std::vector& args) { + if (args.empty()) { + return absl::InvalidArgumentError("Usage: agent todo delete "); + } + + std::string id = args[0]; + auto& manager = GetTodoManager(); + auto status = manager.DeleteTodo(id); + + if (!status.ok()) { + return status; + } + + std::cout << "Deleted TODO " << id << std::endl; + return absl::OkStatus(); +} + +absl::Status HandleTodoClearCompleted(const std::vector& args) { + auto& manager = GetTodoManager(); + auto status = manager.ClearCompleted(); + + if (!status.ok()) { + return status; + } + + std::cout << "Cleared all completed TODOs" << std::endl; + return absl::OkStatus(); +} + +absl::Status HandleTodoNext(const std::vector& args) { + auto& manager = GetTodoManager(); + auto result = manager.GetNextActionableTodo(); + + if (!result.ok()) { + return result.status(); + } + + std::cout << "Next actionable TODO:" << std::endl; + PrintTodo(*result, true); + + return absl::OkStatus(); +} + +absl::Status HandleTodoPlan(const std::vector& args) { + auto& manager = GetTodoManager(); + auto result = manager.GenerateExecutionPlan(); + + if (!result.ok()) { + return result.status(); + } + + auto& plan = *result; + if (plan.empty()) { + std::cout << "No pending TODOs." << std::endl; + return absl::OkStatus(); + } + + std::cout << "Execution Plan (" << plan.size() << " tasks):" << std::endl; + for (size_t i = 0; i < plan.size(); ++i) { + std::cout << absl::StreamFormat("%2d. ", i + 1); + PrintTodo(plan[i]); + } + + return absl::OkStatus(); +} + +} // namespace + +absl::Status HandleTodoCommand(const std::vector& args) { + if (args.empty()) { + std::cerr << "Usage: agent todo [options]" << std::endl; + std::cerr << "Commands:" << std::endl; + std::cerr << " create - Create a new TODO" << std::endl; + std::cerr << " list - List all TODOs" << std::endl; + std::cerr << " update - Update TODO status" << std::endl; + std::cerr << " show - Show TODO details" << std::endl; + std::cerr << " delete - Delete a TODO" << std::endl; + std::cerr << " clear-completed - Clear completed TODOs" << std::endl; + std::cerr << " next - Get next actionable TODO" << std::endl; + std::cerr << " plan - Generate execution plan" << std::endl; + return absl::InvalidArgumentError("No command specified"); + } + + std::string subcommand = args[0]; + std::vector subargs(args.begin() + 1, args.end()); + + if (subcommand == "create") { + return HandleTodoCreate(subargs); + } else if (subcommand == "list") { + return HandleTodoList(subargs); + } else if (subcommand == "update") { + return HandleTodoUpdate(subargs); + } else if (subcommand == "show") { + return HandleTodoShow(subargs); + } else if (subcommand == "delete") { + return HandleTodoDelete(subargs); + } else if (subcommand == "clear-completed") { + return HandleTodoClearCompleted(subargs); + } else if (subcommand == "next") { + return HandleTodoNext(subargs); + } else if (subcommand == "plan") { + return HandleTodoPlan(subargs); + } else { + return absl::InvalidArgumentError( + absl::StrFormat("Unknown todo command: %s", subcommand)); + } +} + +} // namespace handlers +} // namespace cli +} // namespace yaze diff --git a/src/cli/handlers/agent/todo_commands.h b/src/cli/handlers/agent/todo_commands.h new file mode 100644 index 00000000..26542fcc --- /dev/null +++ b/src/cli/handlers/agent/todo_commands.h @@ -0,0 +1,32 @@ +#ifndef YAZE_CLI_HANDLERS_AGENT_TODO_COMMANDS_H_ +#define YAZE_CLI_HANDLERS_AGENT_TODO_COMMANDS_H_ + +#include +#include + +#include "absl/status/status.h" + +namespace yaze { +namespace cli { +namespace handlers { + +/** + * @brief Handle z3ed agent todo commands + * + * Commands: + * agent todo create [--category=] [--priority=] + * agent todo list [--status=] [--category=] + * agent todo update --status= + * agent todo show + * agent todo delete + * agent todo clear-completed + * agent todo next + * agent todo plan + */ +absl::Status HandleTodoCommand(const std::vector& args); + +} // namespace handlers +} // namespace cli +} // namespace yaze + +#endif // YAZE_CLI_HANDLERS_AGENT_TODO_COMMANDS_H_ diff --git a/src/cli/service/agent/todo_manager.cc b/src/cli/service/agent/todo_manager.cc new file mode 100644 index 00000000..caee8fc6 --- /dev/null +++ b/src/cli/service/agent/todo_manager.cc @@ -0,0 +1,423 @@ +#include "cli/service/agent/todo_manager.h" + +#include +#include +#include +#include +#include + +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "util/platform_paths.h" + +#ifdef YAZE_WITH_JSON +#include "nlohmann/json.hpp" +using json = nlohmann::json; +#endif + +namespace yaze { +namespace cli { +namespace agent { + +namespace { + +std::string CurrentTimestamp() { + auto now = absl::Now(); + return absl::FormatTime("%Y-%m-%d %H:%M:%S", now, absl::LocalTimeZone()); +} + +} // namespace + +std::string TodoItem::StatusToString() const { + switch (status) { + case Status::PENDING: return "pending"; + case Status::IN_PROGRESS: return "in_progress"; + case Status::COMPLETED: return "completed"; + case Status::BLOCKED: return "blocked"; + case Status::CANCELLED: return "cancelled"; + default: return "unknown"; + } +} + +TodoItem::Status TodoItem::StringToStatus(const std::string& str) { + if (str == "pending") return Status::PENDING; + if (str == "in_progress") return Status::IN_PROGRESS; + if (str == "completed") return Status::COMPLETED; + if (str == "blocked") return Status::BLOCKED; + if (str == "cancelled") return Status::CANCELLED; + return Status::PENDING; +} + +TodoManager::TodoManager() { + auto result = util::PlatformPaths::GetAppDataSubdirectory("agent"); + if (result.ok()) { + data_dir_ = *result; + } else { + data_dir_ = std::filesystem::current_path() / ".yaze" / "agent"; + } + todos_file_ = (std::filesystem::path(data_dir_) / "todos.json").string(); +} + +TodoManager::TodoManager(const std::string& data_dir) + : data_dir_(data_dir), + todos_file_((std::filesystem::path(data_dir) / "todos.json").string()) {} + +absl::Status TodoManager::Initialize() { + auto status = util::PlatformPaths::EnsureDirectoryExists(data_dir_); + if (!status.ok()) { + return status; + } + + // Try to load existing TODOs + if (util::PlatformPaths::Exists(todos_file_)) { + return Load(); + } + + return absl::OkStatus(); +} + +std::string TodoManager::GenerateId() { + return absl::StrFormat("todo_%d", next_id_++); +} + +std::string TodoManager::GetTimestamp() const { + return CurrentTimestamp(); +} + +absl::StatusOr TodoManager::CreateTodo( + const std::string& description, + const std::string& category, + int priority) { + TodoItem item; + item.id = GenerateId(); + item.description = description; + item.category = category; + item.priority = priority; + item.status = TodoItem::Status::PENDING; + item.created_at = GetTimestamp(); + item.updated_at = item.created_at; + + todos_.push_back(item); + + auto status = Save(); + if (!status.ok()) { + todos_.pop_back(); // Rollback + return status; + } + + return item; +} + +absl::Status TodoManager::UpdateTodo(const std::string& id, const TodoItem& item) { + auto it = std::find_if(todos_.begin(), todos_.end(), + [&id](const TodoItem& t) { return t.id == id; }); + + if (it == todos_.end()) { + return absl::NotFoundError(absl::StrFormat("TODO with ID %s not found", id)); + } + + TodoItem updated = item; + updated.id = id; // Preserve ID + updated.updated_at = GetTimestamp(); + + *it = updated; + return Save(); +} + +absl::Status TodoManager::UpdateStatus(const std::string& id, TodoItem::Status status) { + auto it = std::find_if(todos_.begin(), todos_.end(), + [&id](const TodoItem& t) { return t.id == id; }); + + if (it == todos_.end()) { + return absl::NotFoundError(absl::StrFormat("TODO with ID %s not found", id)); + } + + it->status = status; + it->updated_at = GetTimestamp(); + + return Save(); +} + +absl::StatusOr TodoManager::GetTodo(const std::string& id) const { + auto it = std::find_if(todos_.begin(), todos_.end(), + [&id](const TodoItem& t) { return t.id == id; }); + + if (it == todos_.end()) { + return absl::NotFoundError(absl::StrFormat("TODO with ID %s not found", id)); + } + + return *it; +} + +std::vector TodoManager::GetAllTodos() const { + return todos_; +} + +std::vector TodoManager::GetTodosByStatus(TodoItem::Status status) const { + std::vector result; + std::copy_if(todos_.begin(), todos_.end(), std::back_inserter(result), + [status](const TodoItem& t) { return t.status == status; }); + return result; +} + +std::vector TodoManager::GetTodosByCategory(const std::string& category) const { + std::vector result; + std::copy_if(todos_.begin(), todos_.end(), std::back_inserter(result), + [&category](const TodoItem& t) { return t.category == category; }); + return result; +} + +bool TodoManager::CanExecute(const TodoItem& item) const { + // Check if all dependencies are completed + for (const auto& dep_id : item.dependencies) { + auto dep_result = GetTodo(dep_id); + if (!dep_result.ok()) { + return false; // Dependency not found + } + if (dep_result->status != TodoItem::Status::COMPLETED) { + return false; // Dependency not completed + } + } + return true; +} + +absl::StatusOr TodoManager::GetNextActionableTodo() const { + // Find pending/blocked TODOs + std::vector actionable; + for (const auto& item : todos_) { + if ((item.status == TodoItem::Status::PENDING || + item.status == TodoItem::Status::BLOCKED) && + CanExecute(item)) { + actionable.push_back(item); + } + } + + if (actionable.empty()) { + return absl::NotFoundError("No actionable TODOs found"); + } + + // Sort by priority (descending) + std::sort(actionable.begin(), actionable.end(), + [](const TodoItem& a, const TodoItem& b) { + return a.priority > b.priority; + }); + + return actionable[0]; +} + +absl::Status TodoManager::DeleteTodo(const std::string& id) { + auto it = std::find_if(todos_.begin(), todos_.end(), + [&id](const TodoItem& t) { return t.id == id; }); + + if (it == todos_.end()) { + return absl::NotFoundError(absl::StrFormat("TODO with ID %s not found", id)); + } + + todos_.erase(it); + return Save(); +} + +absl::Status TodoManager::ClearCompleted() { + auto it = std::remove_if(todos_.begin(), todos_.end(), + [](const TodoItem& t) { + return t.status == TodoItem::Status::COMPLETED; + }); + todos_.erase(it, todos_.end()); + return Save(); +} + +absl::StatusOr> TodoManager::GenerateExecutionPlan() const { + std::vector plan; + std::vector pending; + + // Get all pending TODOs + std::copy_if(todos_.begin(), todos_.end(), std::back_inserter(pending), + [](const TodoItem& t) { + return t.status == TodoItem::Status::PENDING || + t.status == TodoItem::Status::BLOCKED; + }); + + // Topological sort based on dependencies + std::vector sorted; + std::set completed_ids; + + while (!pending.empty()) { + bool made_progress = false; + + for (auto it = pending.begin(); it != pending.end(); ) { + bool can_add = true; + for (const auto& dep_id : it->dependencies) { + if (completed_ids.find(dep_id) == completed_ids.end()) { + can_add = false; + break; + } + } + + if (can_add) { + sorted.push_back(*it); + completed_ids.insert(it->id); + it = pending.erase(it); + made_progress = true; + } else { + ++it; + } + } + + if (!made_progress && !pending.empty()) { + return absl::FailedPreconditionError( + "Circular dependency detected in TODOs"); + } + } + + // Sort by priority within dependency levels + std::stable_sort(sorted.begin(), sorted.end(), + [](const TodoItem& a, const TodoItem& b) { + return a.priority > b.priority; + }); + + return sorted; +} + +absl::Status TodoManager::Save() { +#ifdef YAZE_WITH_JSON + json j_todos = json::array(); + + for (const auto& item : todos_) { + json j_item; + j_item["id"] = item.id; + j_item["description"] = item.description; + j_item["status"] = item.StatusToString(); + j_item["category"] = item.category; + j_item["priority"] = item.priority; + j_item["dependencies"] = item.dependencies; + j_item["tools_needed"] = item.tools_needed; + j_item["created_at"] = item.created_at; + j_item["updated_at"] = item.updated_at; + j_item["notes"] = item.notes; + + j_todos.push_back(j_item); + } + + std::ofstream file(todos_file_); + if (!file.is_open()) { + return absl::InternalError( + absl::StrFormat("Failed to open file: %s", todos_file_)); + } + + file << j_todos.dump(2); + return absl::OkStatus(); +#else + return absl::UnimplementedError("JSON support required for TODO persistence"); +#endif +} + +absl::Status TodoManager::Load() { +#ifdef YAZE_WITH_JSON + std::ifstream file(todos_file_); + if (!file.is_open()) { + return absl::InternalError( + absl::StrFormat("Failed to open file: %s", todos_file_)); + } + + json j_todos; + try { + file >> j_todos; + } catch (const std::exception& e) { + return absl::InternalError( + absl::StrFormat("Failed to parse JSON: %s", e.what())); + } + + todos_.clear(); + for (const auto& j_item : j_todos) { + TodoItem item; + item.id = j_item.value("id", ""); + item.description = j_item.value("description", ""); + item.status = TodoItem::StringToStatus(j_item.value("status", "pending")); + item.category = j_item.value("category", ""); + item.priority = j_item.value("priority", 0); + item.dependencies = j_item.value("dependencies", std::vector{}); + item.tools_needed = j_item.value("tools_needed", std::vector{}); + item.created_at = j_item.value("created_at", ""); + item.updated_at = j_item.value("updated_at", ""); + item.notes = j_item.value("notes", ""); + + todos_.push_back(item); + + // Update next_id_ + if (item.id.find("todo_") == 0) { + int id_num = std::stoi(item.id.substr(5)); + if (id_num >= next_id_) { + next_id_ = id_num + 1; + } + } + } + + return absl::OkStatus(); +#else + return absl::UnimplementedError("JSON support required for TODO persistence"); +#endif +} + +std::string TodoManager::ExportAsJson() const { +#ifdef YAZE_WITH_JSON + json j_todos = json::array(); + + for (const auto& item : todos_) { + json j_item; + j_item["id"] = item.id; + j_item["description"] = item.description; + j_item["status"] = item.StatusToString(); + j_item["category"] = item.category; + j_item["priority"] = item.priority; + j_item["dependencies"] = item.dependencies; + j_item["tools_needed"] = item.tools_needed; + j_item["created_at"] = item.created_at; + j_item["updated_at"] = item.updated_at; + j_item["notes"] = item.notes; + + j_todos.push_back(j_item); + } + + return j_todos.dump(2); +#else + return "{}"; +#endif +} + +absl::Status TodoManager::ImportFromJson(const std::string& json_str) { +#ifdef YAZE_WITH_JSON + try { + json j_todos = json::parse(json_str); + + todos_.clear(); + for (const auto& j_item : j_todos) { + TodoItem item; + item.id = j_item.value("id", ""); + item.description = j_item.value("description", ""); + item.status = TodoItem::StringToStatus(j_item.value("status", "pending")); + item.category = j_item.value("category", ""); + item.priority = j_item.value("priority", 0); + item.dependencies = j_item.value("dependencies", std::vector{}); + item.tools_needed = j_item.value("tools_needed", std::vector{}); + item.created_at = j_item.value("created_at", ""); + item.updated_at = j_item.value("updated_at", ""); + item.notes = j_item.value("notes", ""); + + todos_.push_back(item); + } + + return Save(); + } catch (const std::exception& e) { + return absl::InternalError( + absl::StrFormat("Failed to parse JSON: %s", e.what())); + } +#else + return absl::UnimplementedError("JSON support required for TODO import"); +#endif +} + +} // namespace agent +} // namespace cli +} // namespace yaze diff --git a/src/cli/service/agent/todo_manager.h b/src/cli/service/agent/todo_manager.h new file mode 100644 index 00000000..424d45d2 --- /dev/null +++ b/src/cli/service/agent/todo_manager.h @@ -0,0 +1,156 @@ +#ifndef YAZE_CLI_SERVICE_AGENT_TODO_MANAGER_H_ +#define YAZE_CLI_SERVICE_AGENT_TODO_MANAGER_H_ + +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" + +namespace yaze { +namespace cli { +namespace agent { + +/** + * @struct TodoItem + * @brief Represents a single TODO item for task management + */ +struct TodoItem { + std::string id; + std::string description; + enum class Status { + PENDING, + IN_PROGRESS, + COMPLETED, + BLOCKED, + CANCELLED + } status = Status::PENDING; + + std::string category; // e.g., "rom_edit", "ai_task", "build", "test" + int priority = 0; // Higher = more important + std::vector dependencies; // IDs of tasks that must complete first + std::vector tools_needed; // Tools/functions required + std::string created_at; + std::string updated_at; + std::string notes; + + // Convert status enum to string + std::string StatusToString() const; + static Status StringToStatus(const std::string& str); +}; + +/** + * @class TodoManager + * @brief Manages TODO lists for z3ed agent task execution + * + * Enables the AI agent to: + * - Create and track TODO lists for complex tasks + * - Break down goals into executable steps + * - Track dependencies between tasks + * - Persist state between sessions + * - Generate execution plans + */ +class TodoManager { + public: + TodoManager(); + explicit TodoManager(const std::string& data_dir); + + /** + * @brief Initialize the TODO manager and load persisted data + */ + absl::Status Initialize(); + + /** + * @brief Create a new TODO item + */ + absl::StatusOr CreateTodo( + const std::string& description, + const std::string& category = "", + int priority = 0); + + /** + * @brief Update an existing TODO item + */ + absl::Status UpdateTodo(const std::string& id, const TodoItem& item); + + /** + * @brief Update TODO status + */ + absl::Status UpdateStatus(const std::string& id, TodoItem::Status status); + + /** + * @brief Get a TODO item by ID + */ + absl::StatusOr GetTodo(const std::string& id) const; + + /** + * @brief Get all TODO items + */ + std::vector GetAllTodos() const; + + /** + * @brief Get TODO items by status + */ + std::vector GetTodosByStatus(TodoItem::Status status) const; + + /** + * @brief Get TODO items by category + */ + std::vector GetTodosByCategory(const std::string& category) const; + + /** + * @brief Get the next actionable TODO (respecting dependencies and priority) + */ + absl::StatusOr GetNextActionableTodo() const; + + /** + * @brief Delete a TODO item + */ + absl::Status DeleteTodo(const std::string& id); + + /** + * @brief Clear all completed TODOs + */ + absl::Status ClearCompleted(); + + /** + * @brief Save TODOs to persistent storage + */ + absl::Status Save(); + + /** + * @brief Load TODOs from persistent storage + */ + absl::Status Load(); + + /** + * @brief Generate an execution plan based on dependencies + */ + absl::StatusOr> GenerateExecutionPlan() const; + + /** + * @brief Export TODOs as JSON string + */ + std::string ExportAsJson() const; + + /** + * @brief Import TODOs from JSON string + */ + absl::Status ImportFromJson(const std::string& json); + + private: + std::string data_dir_; + std::string todos_file_; + std::vector todos_; + int next_id_ = 1; + + std::string GenerateId(); + std::string GetTimestamp() const; + bool CanExecute(const TodoItem& item) const; +}; + +} // namespace agent +} // namespace cli +} // namespace yaze + +#endif // YAZE_CLI_SERVICE_AGENT_TODO_MANAGER_H_ diff --git a/src/cli/service/ai/ai_gui_controller.cc b/src/cli/service/ai/ai_gui_controller.cc index 255805ac..2fa8af32 100644 --- a/src/cli/service/ai/ai_gui_controller.cc +++ b/src/cli/service/ai/ai_gui_controller.cc @@ -238,63 +238,20 @@ absl::StatusOr AIGUIController::CaptureCurrentState( absl::Status AIGUIController::ExecuteGRPCAction(const AIAction& action) { // Convert AI action to gRPC test commands - auto grpc_commands = action_generator_.GenerateGRPCCommands({action}); + auto test_script_result = action_generator_.GenerateTestScript({action}); - if (grpc_commands.empty()) { - return absl::InternalError("No gRPC commands generated for action"); + if (!test_script_result.ok()) { + return test_script_result.status(); } - // Execute each command - for (const auto& command_json : grpc_commands) { - // Parse JSON and execute via GUI client - // This is a placeholder - actual implementation would parse JSON - // and call appropriate GUI client methods - - if (action.type == AIActionType::kClickButton) { - auto button_it = action.parameters.find("button"); - if (button_it != action.parameters.end()) { - auto status = gui_client_->ClickButton(button_it->second); - if (!status.ok()) { - return status; - } - } - } - else if (action.type == AIActionType::kPlaceTile) { - // Extract parameters - auto x_it = action.parameters.find("x"); - auto y_it = action.parameters.find("y"); - auto tile_it = action.parameters.find("tile_id"); - - if (x_it != action.parameters.end() && - y_it != action.parameters.end() && - tile_it != action.parameters.end()) { - - int x = std::stoi(x_it->second); - int y = std::stoi(y_it->second); - int tile_id = std::stoi(tile_it->second); - - // Use GUI client to place tile - // (This would need actual implementation in GuiAutomationClient) - auto status = gui_client_->ExecuteTestScript( - absl::StrFormat("PlaceTile(%d, %d, %d)", x, y, tile_id)); - if (!status.ok()) { - return status; - } - } - } - else if (action.type == AIActionType::kWait) { - int wait_ms = config_.screenshot_delay_ms; - auto wait_it = action.parameters.find("duration_ms"); - if (wait_it != action.parameters.end()) { - wait_ms = std::stoi(wait_it->second); - } - std::this_thread::sleep_for(std::chrono::milliseconds(wait_ms)); - } - } - - return absl::OkStatus(); + // TODO: Implement gRPC GUI automation when GuiAutomationClient is ready + // For now, just log the generated test script + return absl::UnimplementedError( + "gRPC GUI automation not yet fully implemented. " + "GuiAutomationClient integration pending."); } + absl::StatusOr AIGUIController::VerifyActionSuccess( const AIAction& action, const std::filesystem::path& before_screenshot,