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