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