diff --git a/src/cli/handlers/agent/conversation_test.cc b/src/cli/handlers/agent/conversation_test.cc index 918a6874..0cc966d7 100644 --- a/src/cli/handlers/agent/conversation_test.cc +++ b/src/cli/handlers/agent/conversation_test.cc @@ -1,5 +1,8 @@ #include "cli/handlers/agent/commands.h" +#include "app/rom.h" +#include "absl/flags/declare.h" +#include "absl/flags/flag.h" #include #include #include @@ -11,12 +14,35 @@ #include "cli/service/agent/conversational_agent_service.h" #include "nlohmann/json.hpp" +ABSL_DECLARE_FLAG(std::string, rom); + namespace yaze { namespace cli { namespace agent { namespace { +absl::Status LoadRomForAgent(Rom& rom) { + if (rom.is_loaded()) { + return ::absl::OkStatus(); + } + + std::string rom_path = ::absl::GetFlag(FLAGS_rom); + if (rom_path.empty()) { + return ::absl::InvalidArgumentError( + "No ROM loaded. Pass --rom= to z3ed agent test-conversation."); + } + + auto status = rom.LoadFromFile(rom_path); + if (!status.ok()) { + return ::absl::FailedPreconditionError( + ::absl::StrCat("Failed to load ROM from '", rom_path, + "': ", status.message())); + } + + return ::absl::OkStatus(); +} + struct ConversationTestCase { std::string name; std::string description; @@ -85,9 +111,13 @@ void PrintUserPrompt(const std::string& prompt) { std::cout << "๐Ÿ‘ค User: " << prompt << "\n\n"; } -void PrintAgentResponse(const ChatMessage& response) { +void PrintAgentResponse(const ChatMessage& response, bool verbose) { std::cout << "๐Ÿค– Agent: " << response.message << "\n\n"; - + + if (verbose && response.json_pretty.has_value()) { + std::cout << "๐Ÿงพ JSON Output:\n" << *response.json_pretty << "\n\n"; + } + if (response.table_data.has_value()) { std::cout << "๐Ÿ“Š Table Output:\n"; const auto& table = response.table_data.value(); @@ -122,16 +152,29 @@ void PrintAgentResponse(const ChatMessage& response) { std::cout << "\n"; } - if (table.rows.size() > max_rows) { + if (!verbose && table.rows.size() > max_rows) { std::cout << " ... (" << (table.rows.size() - max_rows) << " more rows)\n"; } + + if (verbose && table.rows.size() > max_rows) { + for (size_t i = max_rows; i < table.rows.size(); ++i) { + std::cout << " "; + for (size_t j = 0; j < table.rows[i].size(); ++j) { + std::cout << table.rows[i][j]; + if (j < table.rows[i].size() - 1) { + std::cout << " | "; + } + } + std::cout << "\n"; + } + } std::cout << "\n"; } } bool ValidateResponse(const ChatMessage& response, - const ConversationTestCase& test_case) { + const ConversationTestCase& test_case) { bool passed = true; // Check for expected keywords @@ -162,10 +205,13 @@ bool ValidateResponse(const ChatMessage& response, } absl::Status RunTestCase(const ConversationTestCase& test_case, - ConversationalAgentService& service) { + ConversationalAgentService& service, + bool verbose) { PrintTestHeader(test_case); bool all_passed = true; + + service.ResetConversation(); for (const auto& prompt : test_case.user_prompts) { PrintUserPrompt(prompt); @@ -178,20 +224,33 @@ absl::Status RunTestCase(const ConversationTestCase& test_case, } const auto& response = response_or.value(); - PrintAgentResponse(response); + PrintAgentResponse(response, verbose); if (!ValidateResponse(response, test_case)) { all_passed = false; } } + if (verbose) { + const auto& history = service.GetHistory(); + std::cout << "๐Ÿ—‚ Conversation Summary (" << history.size() + << " message" << (history.size() == 1 ? "" : "s") << ")\n"; + for (const auto& message : history) { + const char* sender = + message.sender == ChatMessage::Sender::kUser ? "User" : "Agent"; + std::cout << " [" << sender << "] " << message.message << "\n"; + } + std::cout << "\n"; + } + if (all_passed) { std::cout << "โœ… Test PASSED: " << test_case.name << "\n"; - } else { - std::cout << "โš ๏ธ Test completed with warnings: " << test_case.name << "\n"; + return absl::OkStatus(); } - - return absl::OkStatus(); + + std::cout << "โš ๏ธ Test completed with warnings: " << test_case.name << "\n"; + return absl::InternalError( + absl::StrCat("Conversation test failed validation: ", test_case.name)); } absl::Status LoadTestCasesFromFile(const std::string& file_path, @@ -299,7 +358,7 @@ absl::Status HandleTestConversationCommand( int failed = 0; for (const auto& test_case : test_cases) { - auto status = RunTestCase(test_case, service); + auto status = RunTestCase(test_case, service, verbose); if (status.ok()) { ++passed; } else { @@ -323,7 +382,12 @@ absl::Status HandleTestConversationCommand( std::cout << "\nโš ๏ธ Some tests failed\n"; } - return absl::OkStatus(); + if (failed == 0) { + return absl::OkStatus(); + } + + return absl::InternalError( + absl::StrCat(failed, " conversation test(s) reported failures")); } } // namespace agent diff --git a/src/cli/modern_cli.cc b/src/cli/modern_cli.cc index 3f3dad77..9174e302 100644 --- a/src/cli/modern_cli.cc +++ b/src/cli/modern_cli.cc @@ -60,12 +60,13 @@ void ModernCLI::SetupCommands() { commands_["agent"] = { .name = "agent", .description = "Interact with the AI agent", - .usage = "z3ed agent [options]\n" + .usage = "z3ed agent [options]\n" " test run: --prompt \"\" [--host ] [--port ] [--timeout ]\n" " test status: status --test-id [--follow] [--host ] [--port ]\n" " test list: list [--category ] [--status ] [--limit ] [--host ] [--port ]\n" " test results: results --test-id [--include-logs] [--format yaml|json] [--host ] [--port ]\n" " test suite: suite [options]\n" + " test-conversation: [--file ] [--verbose] [--rom ]\n" " gui discover: discover [--window ] [--type ] [--path-prefix ]\n" " [--include-invisible] [--include-disabled] [--format table|json] [--limit ]\n" " describe options: [--resource ] [--format json|yaml] [--output ]\n" diff --git a/src/cli/service/agent/conversational_agent_service.cc b/src/cli/service/agent/conversational_agent_service.cc index b46cc13c..cd230789 100644 --- a/src/cli/service/agent/conversational_agent_service.cc +++ b/src/cli/service/agent/conversational_agent_service.cc @@ -157,6 +157,10 @@ void ConversationalAgentService::SetRomContext(Rom* rom) { } } +void ConversationalAgentService::ResetConversation() { + history_.clear(); +} + absl::StatusOr ConversationalAgentService::SendMessage( const std::string& message) { if (message.empty() && history_.empty()) { diff --git a/src/cli/service/agent/conversational_agent_service.h b/src/cli/service/agent/conversational_agent_service.h index a35299bc..a9ea8b35 100644 --- a/src/cli/service/agent/conversational_agent_service.h +++ b/src/cli/service/agent/conversational_agent_service.h @@ -42,6 +42,9 @@ class ConversationalAgentService { // Provide the service with a ROM context for tool execution. void SetRomContext(Rom* rom); + // Clear the current conversation history, preserving ROM/tool context. + void ResetConversation(); + private: std::vector history_; std::unique_ptr ai_service_; diff --git a/src/cli/service/ai/prompt_builder.cc b/src/cli/service/ai/prompt_builder.cc index 5337dacd..726a7f4c 100644 --- a/src/cli/service/ai/prompt_builder.cc +++ b/src/cli/service/ai/prompt_builder.cc @@ -7,6 +7,7 @@ #include #include +#include "absl/strings/ascii.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" #include "nlohmann/json.hpp" @@ -19,14 +20,31 @@ namespace { namespace fs = std::filesystem; +bool IsYamlBool(const std::string& value) { + const std::string lower = absl::AsciiStrToLower(value); + return lower == "true" || lower == "false" || lower == "yes" || + lower == "no" || lower == "on" || lower == "off"; +} + nlohmann::json YamlToJson(const YAML::Node& node) { if (!node) { return nlohmann::json(); } switch (node.Type()) { - case YAML::NodeType::Scalar: - return node.as(""); + case YAML::NodeType::Scalar: { + const std::string scalar = node.as(""); + + if (IsYamlBool(scalar)) { + return node.as(); + } + + if (scalar == "~" || absl::AsciiStrToLower(scalar) == "null") { + return nlohmann::json(); + } + + return scalar; + } case YAML::NodeType::Sequence: { nlohmann::json array = nlohmann::json::array(); for (const auto& item : node) {