feat(beads): integrate Beads Mode backend, MCP tools, and GUI support

This commit is contained in:
2026-05-06 13:48:47 -04:00
parent b1ddaa50f4
commit 2b66f3569b
17 changed files with 525 additions and 77 deletions
+97 -36
View File
@@ -1931,6 +1931,13 @@ class App:
proj_name = self.project.get("project", {}).get("name", Path(self.active_project_path).stem)
imgui.text_colored(C_IN, f"Active: {proj_name}")
imgui.separator()
imgui.text("Execution Mode")
modes = ["native", "beads"]
current_idx = modes.index(self.ui_project_execution_mode) if self.ui_project_execution_mode in modes else 0
ch, new_idx = imgui.combo("##exec_mode", current_idx, modes)
if ch:
self.ui_project_execution_mode = modes[new_idx]
imgui.separator()
imgui.text("Git Directory")
ch, self.ui_project_git_dir = imgui.input_text("##git_dir", self.ui_project_git_dir)
imgui.same_line()
@@ -4066,47 +4073,53 @@ def hello():
return
# Task 5.3: Dense Summary Line
track_name = self.active_track.description if self.active_track else "None"
if getattr(self, "ui_project_execution_mode", "native") == "beads":
track_name = "Beads Graph"
track_stats = {"percentage": 0.0, "completed": 0, "total": 0, "in_progress": 0, "blocked": 0, "todo": 0}
if self.active_track:
track_stats = project_manager.calculate_track_progress(self.active_track.tickets)
elif self.active_tickets:
track_stats = project_manager.calculate_track_progress(self.active_tickets)
total_cost = 0.0
for usage in self.mma_tier_usage.values():
model = usage.get('model', 'unknown')
in_t = usage.get('input', 0)
out_t = usage.get('output', 0)
total_cost += cost_tracker.estimate_cost(model, in_t, out_t)
total_cost = 0.0
for usage in self.mma_tier_usage.values():
model = usage.get('model', 'unknown')
in_t = usage.get('input', 0)
out_t = usage.get('output', 0)
total_cost += cost_tracker.estimate_cost(model, in_t, out_t)
imgui.text("Track:")
imgui.same_line()
imgui.text_colored(C_VAL, track_name)
imgui.same_line()
imgui.text(" | Status:")
imgui.same_line()
if self.mma_status == "paused":
c = imgui.ImVec4(1, 0.5, 0, 1)
if is_nerv: c = vec4(255, 152, 48)
imgui.text_colored(c, "PIPELINE PAUSED")
imgui.text("Track:")
imgui.same_line()
status_col = imgui.ImVec4(1, 1, 1, 1)
if self.mma_status == "idle": status_col = imgui.ImVec4(0.7, 0.7, 0.7, 1)
elif self.mma_status == "running": status_col = imgui.ImVec4(1, 1, 0, 1)
elif self.mma_status == "done": status_col = imgui.ImVec4(0, 1, 0, 1)
elif self.mma_status == "error": status_col = imgui.ImVec4(1, 0, 0, 1)
elif self.mma_status == "paused": status_col = imgui.ImVec4(1, 0.5, 0, 1)
if is_nerv:
if self.mma_status == "running": status_col = vec4(80, 255, 80) # DATA_GREEN
elif self.mma_status == "error": status_col = vec4(255, 72, 64) # ALERT_RED
imgui.text_colored(status_col, self.mma_status.upper())
imgui.same_line()
imgui.text(" | Cost:")
imgui.same_line()
imgui.text_colored(imgui.ImVec4(0, 1, 0, 1), f"${total_cost:,.4f}")
imgui.text_colored(C_VAL, track_name)
imgui.same_line()
imgui.text(" | Status:")
imgui.same_line()
if self.mma_status == "paused":
c = imgui.ImVec4(1, 0.5, 0, 1)
if is_nerv: c = vec4(255, 152, 48)
imgui.text_colored(c, "PIPELINE PAUSED")
imgui.same_line()
status_col = imgui.ImVec4(1, 1, 1, 1)
if self.mma_status == "idle": status_col = imgui.ImVec4(0.7, 0.7, 0.7, 1)
elif self.mma_status == "running": status_col = imgui.ImVec4(1, 1, 0, 1)
elif self.mma_status == "done": status_col = imgui.ImVec4(0, 1, 0, 1)
elif self.mma_status == "error": status_col = imgui.ImVec4(1, 0, 0, 1)
elif self.mma_status == "paused": status_col = imgui.ImVec4(1, 0.5, 0, 1)
if is_nerv:
if self.mma_status == "running": status_col = vec4(80, 255, 80) # DATA_GREEN
elif self.mma_status == "error": status_col = vec4(255, 72, 64) # ALERT_RED
imgui.text_colored(status_col, self.mma_status.upper())
imgui.same_line()
imgui.text(" | Cost:")
imgui.same_line()
imgui.text_colored(imgui.ImVec4(0, 1, 0, 1), f"${total_cost:,.4f}")
# Progress Bar
perc = track_stats["percentage"] / 100.0
p_color = imgui.ImVec2(0.0, 1.0) # WAIT WRONG TYPE
# Progress Bar
perc = track_stats["percentage"] / 100.0
p_color = imgui.ImVec4(0.0, 1.0, 0.0, 1.0)
if track_stats["percentage"] < 33:
p_color = imgui.ImVec4(1.0, 0.0, 0.0, 1.0)
@@ -4448,12 +4461,16 @@ def hello():
else:
imgui.text_disabled("Tier 4 stream is detached.")
imgui.end_tab_item()
if getattr(self, "ui_project_execution_mode", "native") == "beads":
if imgui.begin_tab_item("Beads")[0]:
self._render_beads_tab()
imgui.end_tab_item()
imgui.end_tab_bar()
def _render_task_dag_panel(self) -> None:
# 4. Task DAG Visualizer
imgui.text("Task DAG")
if self.active_track and self.node_editor_ctx:
if (self.active_track or self.active_tickets) and self.node_editor_ctx:
ed.set_current_editor(self.node_editor_ctx)
ed.begin('Visual DAG')
# Selection detection
@@ -4470,6 +4487,9 @@ def hello():
tid = str(t.get('id', '??'))
int_id = abs(hash(tid))
ed.begin_node(ed.NodeId(int_id))
if getattr(self, "ui_project_execution_mode", "native") == "beads":
imgui.text_colored(imgui.ImVec4(0, 1, 1, 1), "[B] ")
imgui.same_line()
imgui.text_colored(C_KEY, f"Ticket: {tid}")
status = t.get('status', 'todo')
s_col = C_VAL
@@ -4590,7 +4610,48 @@ def hello():
self._show_add_ticket_form = False
imgui.end_child()
else:
imgui.text_disabled("No active MMA track.")
imgui.text_disabled("No active MMA track or tickets.")
def _render_beads_tab(self) -> None:
imgui.text("Beads Graph (Dolt-backed)")
if imgui.button("Refresh Beads"):
pass
imgui.separator()
# Check for dolt/bd dependencies
dolt_path = shutil.which("dolt")
bd_path = shutil.which("bd")
if not dolt_path or not bd_path:
missing = []
if not dolt_path: missing.append("'dolt'")
if not bd_path: missing.append("'bd'")
imgui.text_colored(imgui.ImVec4(1, 0.5, 0, 1), f"Warning: {', '.join(missing)} not found in PATH.")
imgui.text_wrapped("Beads mode requires Dolt and the Beads (bd) CLI tools.")
if getattr(self, "ui_project_execution_mode", "native") == "beads":
try:
from src import beads_client
bclient = beads_client.BeadsClient(Path(self.active_project_root))
beads = bclient.list_beads()
if not beads:
imgui.text_disabled("No beads found.")
else:
if imgui.begin_table("beads_table", 3, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable):
imgui.table_setup_column("ID")
imgui.table_setup_column("Status")
imgui.table_setup_column("Title")
imgui.table_headers_row()
for b in beads:
imgui.table_next_row()
imgui.table_next_column()
imgui.text(str(b.id))
imgui.table_next_column()
imgui.text(str(b.status))
imgui.table_next_column()
imgui.text(str(b.title))
imgui.end_table()
except Exception as e:
imgui.text_colored(imgui.ImVec4(1, 0, 0, 1), f"Error loading beads: {e}")
def _render_tier_stream_panel(self, tier_key: str, stream_key: str | None) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_tier_stream_panel")