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