yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
proposal_drawer.cc
Go to the documentation of this file.
2
3#include <filesystem>
4#include <fstream>
5#include <sstream>
6
7#include "absl/strings/str_format.h"
8#include "absl/time/time.h"
11#ifdef Z3ED_AI
13#endif
14#include "imgui/imgui.h"
15
16// Policy evaluation support (optional, only in main yaze build)
17#ifdef YAZE_ENABLE_POLICY_FRAMEWORK
19#endif
20
21namespace yaze {
22namespace editor {
23
26}
27
29 // Draw content without window wrapper (for embedding in RightDrawerManager)
30 if (needs_refresh_) {
32 needs_refresh_ = false;
35 }
36 }
37
38 // Header with refresh button
39 if (ImGui::Button(ICON_MD_REFRESH " Refresh")) {
41 }
42 ImGui::SameLine();
44
45 ImGui::Separator();
46
47 // Split view: proposal list on top, details on bottom
48 float list_height = ImGui::GetContentRegionAvail().y * 0.4f;
49
50 ImGui::BeginChild("ProposalListEmbed", ImVec2(0, list_height), true);
52 ImGui::EndChild();
53
55 ImGui::Separator();
56 ImGui::BeginChild("ProposalDetailEmbed", ImVec2(0, 0), true);
58 ImGui::EndChild();
59 }
60}
61
63 if (!visible_)
64 return;
65
66 // Set drawer position on the right side
67 ImGuiIO& io = ImGui::GetIO();
68 ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x - drawer_width_, 0),
69 ImGuiCond_Always);
70 ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y),
71 ImGuiCond_Always);
72
73 ImGuiWindowFlags flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
74 ImGuiWindowFlags_NoCollapse;
75
76 if (ImGui::Begin("Agent Proposals", &visible_, flags)) {
77 if (needs_refresh_) {
79 needs_refresh_ = false;
82 }
83 }
84
85 // Header with refresh button
86 if (ImGui::Button(ICON_MD_REFRESH " Refresh")) {
88 }
89 ImGui::SameLine();
91
92 ImGui::Separator();
93
94 // Split view: proposal list on top, details on bottom
95 float list_height = ImGui::GetContentRegionAvail().y * 0.4f;
96
97 ImGui::BeginChild("ProposalList", ImVec2(0, list_height), true);
99 ImGui::EndChild();
100
101 if (selected_proposal_) {
102 ImGui::Separator();
103 ImGui::BeginChild("ProposalDetail", ImVec2(0, 0), true);
105 ImGui::EndChild();
106 }
107 }
108 ImGui::End();
109
110 // Confirmation dialog
112 ImGui::OpenPopup("Confirm Action");
113 show_confirm_dialog_ = false;
114 }
115
116 if (ImGui::BeginPopupModal("Confirm Action", nullptr,
117 ImGuiWindowFlags_AlwaysAutoResize)) {
118 ImGui::Text("Are you sure you want to %s this proposal?",
119 confirm_action_.c_str());
120 ImGui::Separator();
121
122 if (ImGui::Button("Yes", ImVec2(120, 0))) {
123 if (confirm_action_ == "accept") {
125 } else if (confirm_action_ == "reject") {
127 } else if (confirm_action_ == "delete") {
129 }
130 ImGui::CloseCurrentPopup();
132 }
133 ImGui::SameLine();
134 if (ImGui::Button("No", ImVec2(120, 0))) {
135 ImGui::CloseCurrentPopup();
136 }
137 ImGui::EndPopup();
138 }
139
140#ifdef YAZE_ENABLE_POLICY_FRAMEWORK
141 // Policy override dialog (NEW)
143 ImGui::OpenPopup("Override Policy");
144 show_override_dialog_ = false;
145 }
146
147 if (ImGui::BeginPopupModal("Override Policy", nullptr,
148 ImGuiWindowFlags_AlwaysAutoResize)) {
149 ImGui::TextColored(gui::GetWarningColor(),
150 ICON_MD_WARNING " Policy Override Required");
151 ImGui::Separator();
152 ImGui::TextWrapped("This proposal has policy warnings.");
153 ImGui::TextWrapped("Do you want to override and accept anyway?");
154 ImGui::Spacing();
155 ImGui::TextColored(gui::GetWarningColor(),
156 "Note: This action will be logged.");
157 ImGui::Separator();
158
159 if (ImGui::Button("Override and Accept", ImVec2(150, 0))) {
160 confirm_action_ = "accept";
162 ImGui::CloseCurrentPopup();
163 }
164 ImGui::SameLine();
165 if (ImGui::Button("Cancel", ImVec2(150, 0))) {
166 ImGui::CloseCurrentPopup();
167 }
168 ImGui::EndPopup();
169 }
170#endif // YAZE_ENABLE_POLICY_FRAMEWORK
171}
172
174 if (proposals_.empty()) {
175 ImGui::TextWrapped("No proposals found.");
176 ImGui::TextWrapped("Run CLI command: z3ed agent run --prompt \"...\"");
177 return;
178 }
179
180 ImGuiTableFlags flags =
181 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY;
182
183 if (ImGui::BeginTable("ProposalsTable", 3, flags)) {
184 ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 60.0f);
185 ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 80.0f);
186 ImGui::TableSetupColumn("Prompt", ImGuiTableColumnFlags_WidthStretch);
187 ImGui::TableSetupScrollFreeze(0, 1);
188 ImGui::TableHeadersRow();
189
190 for (const auto& proposal : proposals_) {
191 ImGui::TableNextRow();
192
193 // ID column
194 ImGui::TableSetColumnIndex(0);
195 bool is_selected = (proposal.id == selected_proposal_id_);
196 if (ImGui::Selectable(proposal.id.c_str(), is_selected,
197 ImGuiSelectableFlags_SpanAllColumns)) {
198 SelectProposal(proposal.id);
199 }
200
201 // Status column
202 ImGui::TableSetColumnIndex(1);
203 switch (proposal.status) {
205 ImGui::TextColored(gui::GetWarningColor(),
206 ICON_MD_HOURGLASS_TOP " Pending");
207 break;
209 ImGui::TextColored(gui::GetSuccessColor(),
210 ICON_MD_CHECK_CIRCLE " Accepted");
211 break;
213 ImGui::TextColored(gui::GetErrorColor(), ICON_MD_CANCEL " Rejected");
214 break;
215 }
216
217 // Prompt column (truncated)
218 ImGui::TableSetColumnIndex(2);
219 std::string truncated = proposal.prompt;
220 if (truncated.length() > 30) {
221 truncated = truncated.substr(0, 27) + "...";
222 }
223 ImGui::TextWrapped("%s", truncated.c_str());
224 }
225
226 ImGui::EndTable();
227 }
228}
229
232 return;
233
234 const auto& p = *selected_proposal_;
235
236 // Metadata section
237 if (ImGui::CollapsingHeader("Metadata", ImGuiTreeNodeFlags_DefaultOpen)) {
238 ImGui::Text("ID: %s", p.id.c_str());
239 ImGui::Text("Sandbox: %s", p.sandbox_id.c_str());
240 ImGui::Text("Created: %s", absl::FormatTime(p.created_at).c_str());
241 if (p.reviewed_at.has_value()) {
242 ImGui::Text("Reviewed: %s", absl::FormatTime(*p.reviewed_at).c_str());
243 }
244 ImGui::Text("Commands: %d", p.commands_executed);
245 ImGui::Text("Bytes Changed: %d", p.bytes_changed);
246 ImGui::Separator();
247 ImGui::TextWrapped("Prompt: %s", p.prompt.c_str());
248 ImGui::TextWrapped("Description: %s", p.description.c_str());
249 }
250
251 // Diff section
252 if (ImGui::CollapsingHeader("Diff", ImGuiTreeNodeFlags_DefaultOpen)) {
253 if (diff_content_.empty() && std::filesystem::exists(p.diff_path)) {
254 std::ifstream diff_file(p.diff_path);
255 if (diff_file.is_open()) {
256 std::stringstream buffer;
257 buffer << diff_file.rdbuf();
258 diff_content_ = buffer.str();
259 }
260 }
261
262 if (!diff_content_.empty()) {
263 ImGui::BeginChild("DiffContent", ImVec2(0, 150), true,
264 ImGuiWindowFlags_HorizontalScrollbar);
265 ImGui::TextUnformatted(diff_content_.c_str());
266 ImGui::EndChild();
267 } else {
268 ImGui::TextWrapped("No diff available");
269 }
270 }
271
272 // Log section
273 if (ImGui::CollapsingHeader("Execution Log")) {
274 if (log_content_.empty() && std::filesystem::exists(p.log_path)) {
275 std::ifstream log_file(p.log_path);
276 if (log_file.is_open()) {
277 std::stringstream buffer;
278 std::string line;
279 int line_count = 0;
280 while (std::getline(log_file, line) &&
281 line_count < log_display_lines_) {
282 buffer << line << "\n";
283 line_count++;
284 }
285 if (line_count >= log_display_lines_) {
286 buffer << "... (truncated, see " << p.log_path.string() << ")\n";
287 }
288 log_content_ = buffer.str();
289 }
290 }
291
292 if (!log_content_.empty()) {
293 ImGui::BeginChild("LogContent", ImVec2(0, 150), true,
294 ImGuiWindowFlags_HorizontalScrollbar);
295 ImGui::TextUnformatted(log_content_.c_str());
296 ImGui::EndChild();
297 } else {
298 ImGui::TextWrapped("No log available");
299 }
300 }
301
302 // Policy Status section (NEW)
303#ifdef YAZE_ENABLE_POLICY_FRAMEWORK
305#endif
306
307 // Action buttons
308 ImGui::Separator();
310}
311
313 const char* filter_labels[] = {"All", "Pending", "Accepted", "Rejected"};
314 int current_filter = static_cast<int>(status_filter_);
315
316 ImGui::SetNextItemWidth(120.0f);
317 if (ImGui::Combo("Filter", &current_filter, filter_labels, 4)) {
318 status_filter_ = static_cast<StatusFilter>(current_filter);
320 }
321}
322
324#ifdef YAZE_ENABLE_POLICY_FRAMEWORK
326 return;
327
328 const auto& p = *selected_proposal_;
329
330 // Only evaluate policies for pending proposals
332 return;
333 }
334
335 if (ImGui::CollapsingHeader("Policy Status",
336 ImGuiTreeNodeFlags_DefaultOpen)) {
337 auto& policy_eval = cli::PolicyEvaluator::GetInstance();
338
339 if (!policy_eval.IsEnabled()) {
340 ImGui::TextColored(gui::GetDisabledColor(),
341 ICON_MD_INFO " No policies configured");
342 ImGui::TextWrapped(
343 "Create .yaze/policies/agent.yaml to enable policy evaluation");
344 return;
345 }
346
347 // Evaluate proposal against policies
348 auto policy_result = policy_eval.EvaluateProposal(p.id);
349
350 if (!policy_result.ok()) {
351 ImGui::TextColored(gui::GetErrorColor(),
352 ICON_MD_ERROR " Policy evaluation failed");
353 ImGui::TextWrapped("%s", policy_result.status().message().data());
354 return;
355 }
356
357 const auto& result = policy_result.value();
358
359 // Overall status
360 if (result.is_clean()) {
361 ImGui::TextColored(gui::GetSuccessColor(),
362 ICON_MD_VERIFIED " All policies passed");
363 } else if (result.passed) {
364 ImGui::TextColored(gui::GetWarningColor(),
365 ICON_MD_WARNING " Passed with warnings");
366 } else {
367 ImGui::TextColored(gui::GetErrorColor(),
368 ICON_MD_CANCEL " Critical violations found");
369 }
370
371 ImGui::Separator();
372
373 // Show critical violations
374 if (!result.critical_violations.empty()) {
375 ImGui::TextColored(gui::GetErrorColor(),
376 ICON_MD_BLOCK " Critical Violations:");
377 for (const auto& violation : result.critical_violations) {
378 ImGui::Bullet();
379 ImGui::TextWrapped("%s: %s", violation.policy_name.c_str(),
380 violation.message.c_str());
381 if (!violation.details.empty()) {
382 ImGui::Indent();
383 ImGui::TextColored(gui::GetDisabledColor(), "%s",
384 violation.details.c_str());
385 ImGui::Unindent();
386 }
387 }
388 ImGui::Separator();
389 }
390
391 // Show warnings
392 if (!result.warnings.empty()) {
393 ImGui::TextColored(gui::GetWarningColor(), ICON_MD_WARNING " Warnings:");
394 for (const auto& violation : result.warnings) {
395 ImGui::Bullet();
396 ImGui::TextWrapped("%s: %s", violation.policy_name.c_str(),
397 violation.message.c_str());
398 if (!violation.details.empty()) {
399 ImGui::Indent();
400 ImGui::TextColored(gui::GetDisabledColor(), "%s",
401 violation.details.c_str());
402 ImGui::Unindent();
403 }
404 }
405 ImGui::Separator();
406 }
407
408 // Show info messages
409 if (!result.info.empty()) {
410 ImGui::TextColored(gui::GetInfoColor(), ICON_MD_INFO " Information:");
411 for (const auto& violation : result.info) {
412 ImGui::Bullet();
413 ImGui::TextWrapped("%s: %s", violation.policy_name.c_str(),
414 violation.message.c_str());
415 }
416 }
417 }
418#endif // YAZE_ENABLE_POLICY_FRAMEWORK
419}
420
423 return;
424
425 const auto& p = *selected_proposal_;
427
428 // Evaluate policies to determine if Accept button should be enabled
429 bool can_accept = true;
430 bool needs_override = false;
431
432#ifdef YAZE_ENABLE_POLICY_FRAMEWORK
433 if (is_pending) {
434 auto& policy_eval = cli::PolicyEvaluator::GetInstance();
435 if (policy_eval.IsEnabled()) {
436 auto policy_result = policy_eval.EvaluateProposal(p.id);
437 if (policy_result.ok()) {
438 const auto& result = policy_result.value();
439 can_accept = !result.has_critical_violations();
440 needs_override = result.can_accept_with_override();
441 }
442 }
443 }
444#endif // YAZE_ENABLE_POLICY_FRAMEWORK
445
446 // Accept button (only for pending proposals, gated by policy)
447 if (is_pending) {
448 if (!can_accept) {
449 ImGui::BeginDisabled();
450 }
451
452 if (ImGui::Button(ICON_MD_CHECK " Accept", ImVec2(-1, 0))) {
453 if (needs_override) {
454 // Show override confirmation dialog
457 } else {
458 // Proceed directly to accept confirmation
459 confirm_action_ = "accept";
462 }
463 }
464
465 if (!can_accept) {
466 ImGui::EndDisabled();
467 ImGui::SameLine();
468 ImGui::TextColored(gui::GetErrorColor(), "(Blocked by policy)");
469 }
470
471 // Reject button (only for pending proposals)
472 if (ImGui::Button(ICON_MD_CLOSE " Reject", ImVec2(-1, 0))) {
473 confirm_action_ = "reject";
476 }
477 }
478
479 // Delete button (for all proposals)
480 if (ImGui::Button(ICON_MD_DELETE " Delete", ImVec2(-1, 0))) {
481 confirm_action_ = "delete";
484 }
485}
486
487void ProposalDrawer::FocusProposal(const std::string& proposal_id) {
488 visible_ = true;
489 selected_proposal_id_ = proposal_id;
490 selected_proposal_ = nullptr;
491 needs_refresh_ = true;
492}
493
495#ifdef Z3ED_AI
496 auto& registry = cli::ProposalRegistry::Instance();
497
498 std::optional<cli::ProposalRegistry::ProposalStatus> filter;
499 switch (status_filter_) {
502 break;
505 break;
508 break;
510 filter = std::nullopt;
511 break;
512 }
513
514 proposals_ = registry.ListProposals(filter);
515
516 // Clear selection if proposal no longer exists
517 if (!selected_proposal_id_.empty()) {
518 bool found = false;
519 for (const auto& p : proposals_) {
520 if (p.id == selected_proposal_id_) {
521 found = true;
522 break;
523 }
524 }
525 if (!found) {
526 selected_proposal_id_.clear();
527 selected_proposal_ = nullptr;
528 diff_content_.clear();
529 log_content_.clear();
530 }
531 }
532#endif
533}
534
535void ProposalDrawer::SelectProposal(const std::string& proposal_id) {
536 selected_proposal_id_ = proposal_id;
537 selected_proposal_ = nullptr;
538 diff_content_.clear();
539 log_content_.clear();
540
541 // Find the proposal in our list
542 for (auto& p : proposals_) {
543 if (p.id == proposal_id) {
545 break;
546 }
547 }
548}
549
550absl::Status ProposalDrawer::AcceptProposal(const std::string& proposal_id) {
551#ifdef Z3ED_AI
552 auto& registry = cli::ProposalRegistry::Instance();
553
554 // Get proposal metadata to find sandbox
555 auto proposal_or = registry.GetProposal(proposal_id);
556 if (!proposal_or.ok()) {
557 return proposal_or.status();
558 }
559
560 const auto& proposal = *proposal_or;
561
562 // Check if ROM is available
563 if (!rom_) {
564 return absl::FailedPreconditionError(
565 "No ROM loaded. Cannot merge proposal changes.");
566 }
567
568 // Find sandbox ROM path using the sandbox_id from the proposal
569 auto& sandbox_mgr = cli::RomSandboxManager::Instance();
570 auto sandboxes = sandbox_mgr.ListSandboxes();
571
572 std::filesystem::path sandbox_rom_path;
573 for (const auto& sandbox : sandboxes) {
574 if (sandbox.id == proposal.sandbox_id) {
575 sandbox_rom_path = sandbox.rom_path;
576 break;
577 }
578 }
579
580 if (sandbox_rom_path.empty()) {
581 return absl::NotFoundError(
582 absl::StrFormat("Sandbox ROM not found for proposal %s (sandbox: %s)",
583 proposal_id, proposal.sandbox_id));
584 }
585
586 // Verify sandbox ROM exists
587 std::error_code ec;
588 if (!std::filesystem::exists(sandbox_rom_path, ec)) {
589 return absl::NotFoundError(absl::StrFormat(
590 "Sandbox ROM file does not exist: %s", sandbox_rom_path.string()));
591 }
592
593 // Load sandbox ROM data
594 Rom sandbox_rom;
595 auto load_status = sandbox_rom.LoadFromFile(sandbox_rom_path.string());
596 if (!load_status.ok()) {
597 return absl::InternalError(absl::StrFormat("Failed to load sandbox ROM: %s",
598 load_status.message()));
599 }
600
601 // Merge sandbox ROM data into main ROM
602 // Copy the entire ROM data vector from sandbox to main ROM
603 const auto& sandbox_data = sandbox_rom.vector();
604 auto merge_status = rom_->WriteVector(0, sandbox_data);
605 if (!merge_status.ok()) {
606 return absl::InternalError(absl::StrFormat(
607 "Failed to merge sandbox ROM data: %s", merge_status.message()));
608 }
609
610 // Update proposal status
611 auto status = registry.UpdateStatus(
613
614 if (status.ok()) {
615 // Mark ROM as dirty so save prompts appear
616 // Note: Rom tracks dirty state internally via Write operations
617 // The WriteVector call above already marked it as dirty
618 }
619
620 needs_refresh_ = true;
621 return status;
622#else
623 return absl::UnimplementedError("AI features disabled");
624#endif
625}
626
627absl::Status ProposalDrawer::RejectProposal(const std::string& proposal_id) {
628#ifdef Z3ED_AI
629 auto& registry = cli::ProposalRegistry::Instance();
630 auto status = registry.UpdateStatus(
632
633 needs_refresh_ = true;
634 return status;
635#else
636 return absl::UnimplementedError("AI features disabled");
637#endif
638}
639
640absl::Status ProposalDrawer::DeleteProposal(const std::string& proposal_id) {
641#ifdef Z3ED_AI
642 auto& registry = cli::ProposalRegistry::Instance();
643 auto status = registry.RemoveProposal(proposal_id);
644
645 if (proposal_id == selected_proposal_id_) {
646 selected_proposal_id_.clear();
647 selected_proposal_ = nullptr;
648 diff_content_.clear();
649 log_content_.clear();
650 }
651
652 needs_refresh_ = true;
653 return status;
654#else
655 return absl::UnimplementedError("AI features disabled");
656#endif
657}
658
659} // namespace editor
660} // namespace yaze
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
Definition rom.cc:548
static PolicyEvaluator & GetInstance()
static ProposalRegistry & Instance()
static RomSandboxManager & Instance()
absl::Status AcceptProposal(const std::string &proposal_id)
absl::Status RejectProposal(const std::string &proposal_id)
cli::ProposalRegistry::ProposalMetadata * selected_proposal_
absl::Status DeleteProposal(const std::string &proposal_id)
void FocusProposal(const std::string &proposal_id)
std::vector< cli::ProposalRegistry::ProposalMetadata > proposals_
void SelectProposal(const std::string &proposal_id)
#define ICON_MD_BLOCK
Definition icons.h:269
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_CANCEL
Definition icons.h:364
#define ICON_MD_WARNING
Definition icons.h:2123
#define ICON_MD_CHECK
Definition icons.h:397
#define ICON_MD_REFRESH
Definition icons.h:1572
#define ICON_MD_VERIFIED
Definition icons.h:2055
#define ICON_MD_ERROR
Definition icons.h:686
#define ICON_MD_HOURGLASS_TOP
Definition icons.h:969
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_DELETE
Definition icons.h:530
#define ICON_MD_CLOSE
Definition icons.h:418
ImVec4 GetSuccessColor()
Definition ui_helpers.cc:48
ImVec4 GetDisabledColor()
Definition ui_helpers.cc:73
ImVec4 GetErrorColor()
Definition ui_helpers.cc:58
ImVec4 GetWarningColor()
Definition ui_helpers.cc:53
ImVec4 GetInfoColor()
Definition ui_helpers.cc:63