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