refactor: Update CMake configurations and enhance TODO management features
- Replaced `${CMAKE_SOURCE_DIR}` with `${PROJECT_SOURCE_DIR}` in CMake files for consistent path handling.
- Introduced new `todo_commands` and `todo_manager` components to manage TODO items, including creation, listing, updating, and deletion functionalities.
- Added detailed implementations for handling TODO commands in the CLI, improving task management capabilities.
- Enhanced the `TodoManager` class to support persistence and execution planning for TODO items.
- Updated CMake configurations to include new source files related to TODO management, ensuring proper integration into the build system.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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++
|
||||
|
||||
@@ -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
|
||||
|
||||
302
src/cli/handlers/agent/todo_commands.cc
Normal file
302
src/cli/handlers/agent/todo_commands.cc
Normal file
@@ -0,0 +1,302 @@
|
||||
#include "cli/handlers/agent/todo_commands.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#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<std::string>& args) {
|
||||
if (args.empty()) {
|
||||
return absl::InvalidArgumentError("Usage: agent todo create <description> [--category=<cat>] [--priority=<n>]");
|
||||
}
|
||||
|
||||
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<std::string>& 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<TodoItem> 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<std::string>& args) {
|
||||
if (args.size() < 2) {
|
||||
return absl::InvalidArgumentError("Usage: agent todo update <id> --status=<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<std::string>& args) {
|
||||
if (args.empty()) {
|
||||
return absl::InvalidArgumentError("Usage: agent todo show <id>");
|
||||
}
|
||||
|
||||
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<std::string>& args) {
|
||||
if (args.empty()) {
|
||||
return absl::InvalidArgumentError("Usage: agent todo delete <id>");
|
||||
}
|
||||
|
||||
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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& args) {
|
||||
if (args.empty()) {
|
||||
std::cerr << "Usage: agent todo <command> [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<std::string> 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
|
||||
32
src/cli/handlers/agent/todo_commands.h
Normal file
32
src/cli/handlers/agent/todo_commands.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef YAZE_CLI_HANDLERS_AGENT_TODO_COMMANDS_H_
|
||||
#define YAZE_CLI_HANDLERS_AGENT_TODO_COMMANDS_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
namespace handlers {
|
||||
|
||||
/**
|
||||
* @brief Handle z3ed agent todo commands
|
||||
*
|
||||
* Commands:
|
||||
* agent todo create <description> [--category=<cat>] [--priority=<n>]
|
||||
* agent todo list [--status=<status>] [--category=<cat>]
|
||||
* agent todo update <id> --status=<status>
|
||||
* agent todo show <id>
|
||||
* agent todo delete <id>
|
||||
* agent todo clear-completed
|
||||
* agent todo next
|
||||
* agent todo plan
|
||||
*/
|
||||
absl::Status HandleTodoCommand(const std::vector<std::string>& args);
|
||||
|
||||
} // namespace handlers
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_CLI_HANDLERS_AGENT_TODO_COMMANDS_H_
|
||||
423
src/cli/service/agent/todo_manager.cc
Normal file
423
src/cli/service/agent/todo_manager.cc
Normal file
@@ -0,0 +1,423 @@
|
||||
#include "cli/service/agent/todo_manager.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
#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<TodoItem> 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<TodoItem> 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<TodoItem> TodoManager::GetAllTodos() const {
|
||||
return todos_;
|
||||
}
|
||||
|
||||
std::vector<TodoItem> TodoManager::GetTodosByStatus(TodoItem::Status status) const {
|
||||
std::vector<TodoItem> result;
|
||||
std::copy_if(todos_.begin(), todos_.end(), std::back_inserter(result),
|
||||
[status](const TodoItem& t) { return t.status == status; });
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<TodoItem> TodoManager::GetTodosByCategory(const std::string& category) const {
|
||||
std::vector<TodoItem> 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<TodoItem> TodoManager::GetNextActionableTodo() const {
|
||||
// Find pending/blocked TODOs
|
||||
std::vector<TodoItem> 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<std::vector<TodoItem>> TodoManager::GenerateExecutionPlan() const {
|
||||
std::vector<TodoItem> plan;
|
||||
std::vector<TodoItem> 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<TodoItem> sorted;
|
||||
std::set<std::string> 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<std::string>{});
|
||||
item.tools_needed = j_item.value("tools_needed", std::vector<std::string>{});
|
||||
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<std::string>{});
|
||||
item.tools_needed = j_item.value("tools_needed", std::vector<std::string>{});
|
||||
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
|
||||
156
src/cli/service/agent/todo_manager.h
Normal file
156
src/cli/service/agent/todo_manager.h
Normal file
@@ -0,0 +1,156 @@
|
||||
#ifndef YAZE_CLI_SERVICE_AGENT_TODO_MANAGER_H_
|
||||
#define YAZE_CLI_SERVICE_AGENT_TODO_MANAGER_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<std::string> dependencies; // IDs of tasks that must complete first
|
||||
std::vector<std::string> 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<TodoItem> 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<TodoItem> GetTodo(const std::string& id) const;
|
||||
|
||||
/**
|
||||
* @brief Get all TODO items
|
||||
*/
|
||||
std::vector<TodoItem> GetAllTodos() const;
|
||||
|
||||
/**
|
||||
* @brief Get TODO items by status
|
||||
*/
|
||||
std::vector<TodoItem> GetTodosByStatus(TodoItem::Status status) const;
|
||||
|
||||
/**
|
||||
* @brief Get TODO items by category
|
||||
*/
|
||||
std::vector<TodoItem> GetTodosByCategory(const std::string& category) const;
|
||||
|
||||
/**
|
||||
* @brief Get the next actionable TODO (respecting dependencies and priority)
|
||||
*/
|
||||
absl::StatusOr<TodoItem> 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<std::vector<TodoItem>> 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<TodoItem> 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_
|
||||
@@ -238,63 +238,20 @@ absl::StatusOr<std::filesystem::path> 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<VisionAnalysisResult> AIGUIController::VerifyActionSuccess(
|
||||
const AIAction& action,
|
||||
const std::filesystem::path& before_screenshot,
|
||||
|
||||
Reference in New Issue
Block a user