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
27
29 // Draw content without window wrapper (for embedding in RightPanelManager)
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(), ICON_MD_HOURGLASS_TOP " Pending");
206 break;
208 ImGui::TextColored(gui::GetSuccessColor(), ICON_MD_CHECK_CIRCLE " Accepted");
209 break;
211 ImGui::TextColored(gui::GetErrorColor(), ICON_MD_CANCEL " Rejected");
212 break;
213 }
214
215 // Prompt column (truncated)
216 ImGui::TableSetColumnIndex(2);
217 std::string truncated = proposal.prompt;
218 if (truncated.length() > 30) {
219 truncated = truncated.substr(0, 27) + "...";
220 }
221 ImGui::TextWrapped("%s", truncated.c_str());
222 }
223
224 ImGui::EndTable();
225 }
226}
227
230 return;
231
232 const auto& p = *selected_proposal_;
233
234 // Metadata section
235 if (ImGui::CollapsingHeader("Metadata", ImGuiTreeNodeFlags_DefaultOpen)) {
236 ImGui::Text("ID: %s", p.id.c_str());
237 ImGui::Text("Sandbox: %s", p.sandbox_id.c_str());
238 ImGui::Text("Created: %s", absl::FormatTime(p.created_at).c_str());
239 if (p.reviewed_at.has_value()) {
240 ImGui::Text("Reviewed: %s", absl::FormatTime(*p.reviewed_at).c_str());
241 }
242 ImGui::Text("Commands: %d", p.commands_executed);
243 ImGui::Text("Bytes Changed: %d", p.bytes_changed);
244 ImGui::Separator();
245 ImGui::TextWrapped("Prompt: %s", p.prompt.c_str());
246 ImGui::TextWrapped("Description: %s", p.description.c_str());
247 }
248
249 // Diff section
250 if (ImGui::CollapsingHeader("Diff", ImGuiTreeNodeFlags_DefaultOpen)) {
251 if (diff_content_.empty() && std::filesystem::exists(p.diff_path)) {
252 std::ifstream diff_file(p.diff_path);
253 if (diff_file.is_open()) {
254 std::stringstream buffer;
255 buffer << diff_file.rdbuf();
256 diff_content_ = buffer.str();
257 }
258 }
259
260 if (!diff_content_.empty()) {
261 ImGui::BeginChild("DiffContent", ImVec2(0, 150), true,
262 ImGuiWindowFlags_HorizontalScrollbar);
263 ImGui::TextUnformatted(diff_content_.c_str());
264 ImGui::EndChild();
265 } else {
266 ImGui::TextWrapped("No diff available");
267 }
268 }
269
270 // Log section
271 if (ImGui::CollapsingHeader("Execution Log")) {
272 if (log_content_.empty() && std::filesystem::exists(p.log_path)) {
273 std::ifstream log_file(p.log_path);
274 if (log_file.is_open()) {
275 std::stringstream buffer;
276 std::string line;
277 int line_count = 0;
278 while (std::getline(log_file, line) &&
279 line_count < log_display_lines_) {
280 buffer << line << "\n";
281 line_count++;
282 }
283 if (line_count >= log_display_lines_) {
284 buffer << "... (truncated, see " << p.log_path.string() << ")\n";
285 }
286 log_content_ = buffer.str();
287 }
288 }
289
290 if (!log_content_.empty()) {
291 ImGui::BeginChild("LogContent", ImVec2(0, 150), true,
292 ImGuiWindowFlags_HorizontalScrollbar);
293 ImGui::TextUnformatted(log_content_.c_str());
294 ImGui::EndChild();
295 } else {
296 ImGui::TextWrapped("No log available");
297 }
298 }
299
300 // Policy Status section (NEW)
301#ifdef YAZE_ENABLE_POLICY_FRAMEWORK
303#endif
304
305 // Action buttons
306 ImGui::Separator();
308}
309
311 const char* filter_labels[] = {"All", "Pending", "Accepted", "Rejected"};
312 int current_filter = static_cast<int>(status_filter_);
313
314 ImGui::SetNextItemWidth(120.0f);
315 if (ImGui::Combo("Filter", &current_filter, filter_labels, 4)) {
316 status_filter_ = static_cast<StatusFilter>(current_filter);
318 }
319}
320
322#ifdef YAZE_ENABLE_POLICY_FRAMEWORK
324 return;
325
326 const auto& p = *selected_proposal_;
327
328 // Only evaluate policies for pending proposals
330 return;
331 }
332
333 if (ImGui::CollapsingHeader("Policy Status",
334 ImGuiTreeNodeFlags_DefaultOpen)) {
335 auto& policy_eval = cli::PolicyEvaluator::GetInstance();
336
337 if (!policy_eval.IsEnabled()) {
338 ImGui::TextColored(gui::GetDisabledColor(),
339 ICON_MD_INFO " No policies configured");
340 ImGui::TextWrapped(
341 "Create .yaze/policies/agent.yaml to enable policy evaluation");
342 return;
343 }
344
345 // Evaluate proposal against policies
346 auto policy_result = policy_eval.EvaluateProposal(p.id);
347
348 if (!policy_result.ok()) {
349 ImGui::TextColored(gui::GetErrorColor(),
350 ICON_MD_ERROR " Policy evaluation failed");
351 ImGui::TextWrapped("%s", policy_result.status().message().data());
352 return;
353 }
354
355 const auto& result = policy_result.value();
356
357 // Overall status
358 if (result.is_clean()) {
359 ImGui::TextColored(gui::GetSuccessColor(),
360 ICON_MD_VERIFIED " All policies passed");
361 } else if (result.passed) {
362 ImGui::TextColored(gui::GetWarningColor(),
363 ICON_MD_WARNING " Passed with warnings");
364 } else {
365 ImGui::TextColored(gui::GetErrorColor(),
366 ICON_MD_CANCEL " Critical violations found");
367 }
368
369 ImGui::Separator();
370
371 // Show critical violations
372 if (!result.critical_violations.empty()) {
373 ImGui::TextColored(gui::GetErrorColor(),
374 ICON_MD_BLOCK " Critical Violations:");
375 for (const auto& violation : result.critical_violations) {
376 ImGui::Bullet();
377 ImGui::TextWrapped("%s: %s", violation.policy_name.c_str(),
378 violation.message.c_str());
379 if (!violation.details.empty()) {
380 ImGui::Indent();
381 ImGui::TextColored(gui::GetDisabledColor(), "%s",
382 violation.details.c_str());
383 ImGui::Unindent();
384 }
385 }
386 ImGui::Separator();
387 }
388
389 // Show warnings
390 if (!result.warnings.empty()) {
391 ImGui::TextColored(gui::GetWarningColor(),
392 ICON_MD_WARNING " Warnings:");
393 for (const auto& violation : result.warnings) {
394 ImGui::Bullet();
395 ImGui::TextWrapped("%s: %s", violation.policy_name.c_str(),
396 violation.message.c_str());
397 if (!violation.details.empty()) {
398 ImGui::Indent();
399 ImGui::TextColored(gui::GetDisabledColor(), "%s",
400 violation.details.c_str());
401 ImGui::Unindent();
402 }
403 }
404 ImGui::Separator();
405 }
406
407 // Show info messages
408 if (!result.info.empty()) {
409 ImGui::TextColored(gui::GetInfoColor(),
410 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
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:155
const auto & vector() const
Definition rom.h:143
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