From 797f283f448921e32992aeacbbf47763d1133398 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Tue, 2 Jun 2026 00:25:10 -0400 Subject: [PATCH] chore(conductor): Add new tracks for Phase 7 - Add 'structural_file_editor_20260601' track. - Add 'discussion_metrics_and_compression_20260601' track. --- conductor/tracks.md | 12 +- .../index.md | 5 + .../metadata.json | 8 + .../plan.md | 19 ++ .../spec.md | 21 +++ .../structural_file_editor_20260601/index.md | 5 + .../metadata.json | 8 + .../structural_file_editor_20260601/plan.md | 17 ++ .../structural_file_editor_20260601/spec.md | 18 ++ src/gui_2.py | 170 +++++++----------- 10 files changed, 178 insertions(+), 105 deletions(-) create mode 100644 conductor/tracks/discussion_metrics_and_compression_20260601/index.md create mode 100644 conductor/tracks/discussion_metrics_and_compression_20260601/metadata.json create mode 100644 conductor/tracks/discussion_metrics_and_compression_20260601/plan.md create mode 100644 conductor/tracks/discussion_metrics_and_compression_20260601/spec.md create mode 100644 conductor/tracks/structural_file_editor_20260601/index.md create mode 100644 conductor/tracks/structural_file_editor_20260601/metadata.json create mode 100644 conductor/tracks/structural_file_editor_20260601/plan.md create mode 100644 conductor/tracks/structural_file_editor_20260601/spec.md diff --git a/conductor/tracks.md b/conductor/tracks.md index f0974a1b..5a7d7bae 100644 --- a/conductor/tracks.md +++ b/conductor/tracks.md @@ -298,5 +298,15 @@ This file tracks all major tracks for the project. Each track has its own detail --- -- [ ] **Track: UX Refinements for Context Composition and Discussion Entries** +- [~] **Track: UX Refinements for Context Composition and Discussion Entries** *Link: [./tracks/context_composition_ux_20260601/](./tracks/context_composition_ux_20260601/)* + +--- + +- [ ] **Track: Combine AST Inspector and Slices Editor into a unified Structural File Editor** +*Link: [./tracks/structural_file_editor_20260601/](./tracks/structural_file_editor_20260601/)* + +--- + +- [ ] **Track: Add per-response token metrics and AI-assisted history compression** +*Link: [./tracks/discussion_metrics_and_compression_20260601/](./tracks/discussion_metrics_and_compression_20260601/)* diff --git a/conductor/tracks/discussion_metrics_and_compression_20260601/index.md b/conductor/tracks/discussion_metrics_and_compression_20260601/index.md new file mode 100644 index 00000000..841f74a6 --- /dev/null +++ b/conductor/tracks/discussion_metrics_and_compression_20260601/index.md @@ -0,0 +1,5 @@ +# Track discussion_metrics_and_compression_20260601 Context + +- [Specification](./spec.md) +- [Implementation Plan](./plan.md) +- [Metadata](./metadata.json) \ No newline at end of file diff --git a/conductor/tracks/discussion_metrics_and_compression_20260601/metadata.json b/conductor/tracks/discussion_metrics_and_compression_20260601/metadata.json new file mode 100644 index 00000000..8c8a9bc1 --- /dev/null +++ b/conductor/tracks/discussion_metrics_and_compression_20260601/metadata.json @@ -0,0 +1,8 @@ +{ + "track_id": "discussion_metrics_and_compression_20260601", + "type": "feature", + "status": "new", + "created_at": "2026-06-01T00:00:00Z", + "updated_at": "2026-06-01T00:00:00Z", + "description": "Add per-response token metrics and AI-assisted history compression" +} \ No newline at end of file diff --git a/conductor/tracks/discussion_metrics_and_compression_20260601/plan.md b/conductor/tracks/discussion_metrics_and_compression_20260601/plan.md new file mode 100644 index 00000000..ef1010a1 --- /dev/null +++ b/conductor/tracks/discussion_metrics_and_compression_20260601/plan.md @@ -0,0 +1,19 @@ +# Implementation Plan: Discussion Metrics and Compression + +## Phase 1: Metrics Visibility +- [ ] Task: Update UI for Token Metrics + - [ ] Modify `_render_comms_history_panel` and `render_discussion_entry` in `src/gui_2.py` to extract and prominently display `usage` stats (input, output, cache) from the entry payloads. + +## Phase 2: Compression Helper Agent +- [ ] Task: Implement Compression Agent + - [ ] Create a new agent definition or function in `src/ai_client.py` (or a dedicated module) capable of receiving a discussion history and a system prompt instructing it to summarize and compact the history. +- [ ] Task: Implement UI Triggers + - [ ] Add a "Compress Discussion" button to the Discussion Hub UI. + - [ ] Wire the button to dispatch the compression task to the background executor and display a loading indicator. + - [ ] Upon completion, replace the older entries with the generated summary block. + +## Phase 3: Verification +- [ ] Task: Verification + - [ ] Verify token metrics are visible per response. + - [ ] Run the "Compress Discussion" tool on a heavy discussion and verify the history is successfully summarized without losing core context. +- [ ] Task: Conductor - User Manual Verification 'Phase 3: Verification' (Protocol in workflow.md) \ No newline at end of file diff --git a/conductor/tracks/discussion_metrics_and_compression_20260601/spec.md b/conductor/tracks/discussion_metrics_and_compression_20260601/spec.md new file mode 100644 index 00000000..4b88f6c7 --- /dev/null +++ b/conductor/tracks/discussion_metrics_and_compression_20260601/spec.md @@ -0,0 +1,21 @@ +# Specification: Discussion Metrics and Compression + +## 1. Overview +The user requested better visibility into token usage on a per-response and per-discussion basis, rather than just session-wide totals. Additionally, the current history truncation is deemed too naive. The user wants a smarter compression strategy utilizing a helper agent to summarize, categorize, and compact discussion entries (especially tool/vendor logs) when the token limit is approached. + +## 2. Functional Requirements +* **Per-Response Metrics:** Update the Comms History and Discussion Hub panels to display token usage (input/output/cache) and remaining quota for each specific response. +* **Discussion-Level Metrics:** Add a summary of total token usage for the active discussion. +* **Helper Agent Compression:** + * Implement an asynchronous helper agent capable of analyzing a `discussion`'s history. + * The agent should categorize entries (e.g., tool calls, user prompts, AI thinking) and generate a compacted summary. + * Provide UI options to trigger this compression manually or set thresholds for automatic suggestions. +* **New Session Prompt:** Allow the AI to generate a "summary prompt" and "target prompt" to effortlessly transition a heavy, exhausted discussion into a fresh session with adjusted context. + +## 3. Non-Functional Requirements +* **Performance:** The helper agent must run asynchronously to avoid blocking the main GUI thread. + +## 4. Acceptance Criteria +* Token metrics are clearly visible per entry in the Comms History. +* A new "Compress Discussion" button triggers the helper agent, which successfully produces a compacted history. +* The system can transition an exhausted discussion into a new one using an AI-generated summary. \ No newline at end of file diff --git a/conductor/tracks/structural_file_editor_20260601/index.md b/conductor/tracks/structural_file_editor_20260601/index.md new file mode 100644 index 00000000..4fa43efd --- /dev/null +++ b/conductor/tracks/structural_file_editor_20260601/index.md @@ -0,0 +1,5 @@ +# Track structural_file_editor_20260601 Context + +- [Specification](./spec.md) +- [Implementation Plan](./plan.md) +- [Metadata](./metadata.json) \ No newline at end of file diff --git a/conductor/tracks/structural_file_editor_20260601/metadata.json b/conductor/tracks/structural_file_editor_20260601/metadata.json new file mode 100644 index 00000000..f52199bd --- /dev/null +++ b/conductor/tracks/structural_file_editor_20260601/metadata.json @@ -0,0 +1,8 @@ +{ + "track_id": "structural_file_editor_20260601", + "type": "feature", + "status": "new", + "created_at": "2026-06-01T00:00:00Z", + "updated_at": "2026-06-01T00:00:00Z", + "description": "Combine AST Inspector and Slices Editor into a unified Structural File Editor" +} \ No newline at end of file diff --git a/conductor/tracks/structural_file_editor_20260601/plan.md b/conductor/tracks/structural_file_editor_20260601/plan.md new file mode 100644 index 00000000..23b873ce --- /dev/null +++ b/conductor/tracks/structural_file_editor_20260601/plan.md @@ -0,0 +1,17 @@ +# Implementation Plan: Structural File Editor + +## Phase 1: Unification +- [ ] Task: Create Structural File Editor Modal + - [ ] Add `app.show_structural_editor_modal = False` to `App.__init__`. + - [ ] Create `render_structural_file_editor_modal(app: App)` in `src/gui_2.py`. + - [ ] Port the tree rendering logic from `render_ast_inspector_modal` into this new modal. + - [ ] Port the custom slice management UI from `render_slices_editor_modal` into this new modal, positioning it logically alongside or above the AST tree. +- [ ] Task: Update File Row UI + - [ ] In `render_context_files_table`, replace the separate "AST" and "Slices" buttons with a single "Structure" button that sets `app.show_structural_editor_modal = True` and sets the target file. + +## Phase 2: Verification +- [ ] Task: Verification + - [ ] Open the Structural File Editor for a complex Python file. + - [ ] Verify both AST nodes and custom slices are visible and interactive. + - [ ] Verify that adding a custom slice works correctly within the unified interface. +- [ ] Task: Conductor - User Manual Verification 'Phase 2: Verification' (Protocol in workflow.md) \ No newline at end of file diff --git a/conductor/tracks/structural_file_editor_20260601/spec.md b/conductor/tracks/structural_file_editor_20260601/spec.md new file mode 100644 index 00000000..5f984e5d --- /dev/null +++ b/conductor/tracks/structural_file_editor_20260601/spec.md @@ -0,0 +1,18 @@ +# Specification: Structural File Editor + +## 1. Overview +The user wants to combine the current AST Inspector and Slices Editor into a single, unified "Structural File Editor". This modal will provide a comprehensive visualization of both auto-detected AST elements and user-defined custom slices, allowing users to precisely tailor the context sent to the AI, especially in combination with "skeleton" or other filtered view modes. + +## 2. Functional Requirements +* **Unified Interface:** Merge the UI elements of `render_ast_inspector_modal` and `render_slices_editor_modal` into a new `render_structural_file_editor_modal`. +* **Visualization:** Display a unified graph or list showing the file's structure. This includes AST nodes (classes, functions, etc.) and custom slices seamlessly integrated. +* **Custom Slices:** Allow users to define arbitrary text ranges as "custom slices" that exist independently of the AST graph but are visualized alongside it. +* **Filtering:** The editor must clearly indicate which parts of the file will be included based on the current `view_mode` (e.g., if `view_mode='skeleton'`, only signatures and explicitly selected custom slices/AST nodes are included). + +## 3. Non-Functional Requirements +* **Performance:** The unified editor must remain responsive even for large files with complex ASTs. + +## 4. Acceptance Criteria +* Clicking a "Structure" or "Edit Slices" button on a file opens the new unified modal. +* The modal clearly displays both AST elements and custom slices. +* Users can add, modify, and delete custom slices within this single interface. \ No newline at end of file diff --git a/src/gui_2.py b/src/gui_2.py index 0d166511..52fa2e20 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -2642,87 +2642,45 @@ def render_files_and_media(app: App) -> None: with imscope.child("Files_child", -1, split_y, True): if not hasattr(app, 'files_last_selected'): app.files_last_selected = -1 - with imscope.table("files_table", 5, imgui.TableFlags_.resizable | imgui.TableFlags_.borders): - imgui.table_setup_column("", imgui.TableColumnFlags_.width_fixed, 20) + with imscope.table("files_table", 3, imgui.TableFlags_.resizable | imgui.TableFlags_.borders): + imgui.table_setup_column("", imgui.TableColumnFlags_.width_fixed, 25) imgui.table_setup_column("Path", imgui.TableColumnFlags_.width_stretch) - imgui.table_setup_column("Agg", imgui.TableColumnFlags_.width_fixed, 0) - imgui.table_setup_column("Full", imgui.TableColumnFlags_.width_fixed, 0) - imgui.table_setup_column("Cache", imgui.TableColumnFlags_.width_fixed, 0) + imgui.table_setup_column("Status", imgui.TableColumnFlags_.width_fixed, 60) imgui.table_headers_row() - + app.files.sort(key=lambda f: f.path.lower() if hasattr(f, 'path') else str(f).lower()) for i, f_item in enumerate(app.files): imgui.table_next_row(); imgui.table_set_column_index(0) - clicked, f_item.selected = imgui.checkbox(f"##{i}", f_item.selected) - if clicked: - if (imgui.is_key_down(imgui.Key.left_shift) or imgui.is_key_down(imgui.Key.right_shift)) and app.files_last_selected >= 0: - start_i = min(app.files_last_selected, i) - end_i = max(app.files_last_selected, i) - for j in range(start_i, end_i + 1): app.files[j].selected = True - app.files_last_selected = i - imgui.table_set_column_index(1); imgui.text(f_item.path if hasattr(f_item, 'path') else str(f_item)) - imgui.table_set_column_index(2) - if f_item.auto_aggregate: imgui.text_colored(imgui.ImVec4(0.3, 0.8, 1, 1), "A") - else: imgui.text_disabled(" ") - - imgui.same_line(spacing=1) - if imgui.invisible_button(f"agg{i}", imgui.ImVec2(15, 15)): - f_item.auto_aggregate = not f_item.auto_aggregate - if f_item.auto_aggregate: f_item.force_full = False - - imgui.table_set_column_index(3) - if f_item.force_full: imgui.text_colored(imgui.ImVec4(1, 0.6, 0.3, 1), "F") - else: imgui.text_disabled(" ") - - imgui.same_line(spacing=1) - if imgui.invisible_button(f"full{i}", imgui.ImVec2(15, 15)): - f_item.force_full = not f_item.force_full - if f_item.force_full: f_item.auto_aggregate = False - - imgui.table_set_column_index(4) fpath = f_item.path if hasattr(f_item, 'path') else str(f_item) - is_cached = any(fpath in c for c in getattr(app, '_cached_files', [])) + in_context = any((cf.path if hasattr(cf, 'path') else str(cf)) == fpath for cf in app.context_files) + + if imgui.button(f"+##add_f_{i}"): + if not in_context: + from src import models + new_item = models.FileItem(path=fpath) + app.context_files.append(new_item) + app._populate_auto_slices(new_item) + + imgui.table_set_column_index(1); imgui.text(fpath) + imgui.table_set_column_index(2) + if in_context: + imgui.text_colored(imgui.ImVec4(0.3, 0.8, 0.3, 1), "Active") + else: + imgui.text_disabled(" - ") if is_cached: imgui.text_colored(imgui.ImVec4(0, 1, 0, 1), "Y") else: imgui.text_colored(imgui.ImVec4(0.5, 0.5, 0.5, 1), "-") - if imgui.button("Add Files##addf"): + if imgui.button("Add Files to Inventory"): r = hide_tk_root(); paths = filedialog.askopenfilenames(); r.destroy() for p in paths: if p not in [f.path if hasattr(f, 'path') else f for f in app.files]: app.files.append(models.FileItem(path=p)) imgui.same_line() - if imgui.button("Sel All##selall"): - for f in app.files: - f.selected = True + if imgui.button("Clear Selection##inv"): + for f in app.files: f.selected = False imgui.same_line() - if imgui.button("Unsel##unselall"): - for f in app.files: - f.selected = False - - imgui.same_line() - if imgui.button("None##nonesel"): - for f in app.files: - if f.selected: - f.auto_aggregate = False - f.force_full = False - - imgui.same_line() - if imgui.button("Agg##aggsel"): - for f in app.files: - if f.selected: - f.auto_aggregate = True - f.force_full = False - - imgui.same_line() - if imgui.button("Full##fullsel"): - for f in app.files: - if f.selected: - f.force_full = True - f.auto_aggregate = False - - imgui.same_line() - if imgui.button("Del##dels"): + if imgui.button("Remove from Inventory"): app.files = [f for f in app.files if not f.selected] imgui.separator() @@ -3440,9 +3398,16 @@ def render_discussion_hub(app: App) -> None: def render_discussion_entry(app: App, entry: dict, index: int) -> None: with imscope.id(f"disc_{index}"): - collapsed, read_mode = entry.get("collapsed", False), entry.get("read_mode", False) - if imgui.button("+" if collapsed else "-"): entry["collapsed"] = not collapsed - imgui.same_line(); render_text_viewer(app, f"Entry #{index+1}", entry["content"], id_suffix=f"disc_btn_{index}"); imgui.same_line(); imgui.set_next_item_width(120) + role = entry.get("role", "User") + bg_col = vec4(20, 20, 20, 255) # default + if role == "User": bg_col = vec4(30, 40, 55, 200) + elif role == "AI": bg_col = vec4(35, 50, 40, 200) + elif role == "Vendor API": bg_col = vec4(45, 40, 30, 200) + + with imscope.style_color(imgui.Col_.child_bg, bg_col): + collapsed, read_mode = entry.get("collapsed", False), entry.get("read_mode", False) + if imgui.button("+" if collapsed else "-"): entry["collapsed"] = not collapsed + imgui.same_line(); render_text_viewer(app, f"Entry #{index+1}", entry["content"], id_suffix=f"disc_btn_{index}"); imgui.same_line(); imgui.set_next_item_width(120) if imgui.begin_combo("##role", entry["role"]): for r in app.disc_roles: if imgui.selectable(r, r == entry["role"])[0]: entry["role"] = r @@ -4117,43 +4082,39 @@ def render_tool_calls_panel(app: App) -> None: imgui.table_setup_column("Tier", imgui.TableColumnFlags_.width_fixed, 60) imgui.table_setup_column("Script", imgui.TableColumnFlags_.width_stretch) imgui.table_setup_column("Result", imgui.TableColumnFlags_.width_fixed, 100) - + imgui.table_headers_row() - - clipper = imgui.ListClipper() - clipper.begin(len(log_to_render)) - while clipper.step(): - for i in range(clipper.display_start, clipper.display_end): - entry = log_to_render[i] - imgui.table_next_row() - - script = entry.get("script", "") - res = entry.get("result", "") - combined = f"**COMMAND:**\n```powershell\n{script}\n```\n\n---\n**OUTPUT:**\n```text\n{res}\n```" - - imgui.table_next_column() - # Use selectable for the entire row trigger - opened_details = imgui.selectable(f"#{i+1}##row_{i}", False, imgui.SelectableFlags_.span_all_columns)[0] - if opened_details: - app.text_viewer_title = f"Tool Call #{i+1} Details" - app.text_viewer_content = combined - app.text_viewer_type = 'markdown' - app.show_windows["Text Viewer"] = True - - imgui.table_next_column() - imgui.text_colored(C_SUB, f"[{entry.get('source_tier', 'main')}]") - - imgui.table_next_column() - script_preview = script.replace("\n", " ")[:150] - if len(script) > 150: script_preview += "..." - render_selectable_label(app, f'tc_script_{i}', script_preview, width=-1) - - imgui.table_next_column() - res_preview = res.replace("\n", " ")[:30] - if len(res) > 30: res_preview += "..." - render_selectable_label(app, f'tc_res_{i}', res_preview, width=-1) - - imgui.end_table() + + for i, entry in enumerate(log_to_render): + imgui.table_next_row() + + script = entry.get("script", "") + res = entry.get("result", "") + combined = f"**COMMAND:**\n```powershell\n{script}\n```\n\n---\n**OUTPUT:**\n```text\n{res}\n```" + + imgui.table_next_column() + # Use selectable for the entire row trigger + opened_details = imgui.selectable(f"#{i+1}##row_{i}", False, imgui.SelectableFlags_.span_all_columns)[0] + if opened_details: + app.text_viewer_title = f"Tool Call #{i+1} Details" + app.text_viewer_content = combined + app.text_viewer_type = 'markdown' + app.show_windows["Text Viewer"] = True + + imgui.table_next_column() + imgui.text_colored(C_SUB, f"[{entry.get('source_tier', 'main')}]") + + imgui.table_next_column() + script_preview = script.replace("\n", " ")[:150] + if len(script) > 150: script_preview += "..." + render_selectable_label(app, f'tc_script_{i}', script_preview, width=-1) + + imgui.table_next_column() + res_preview = res.replace("\n", " ")[:30] + if len(res) > 30: res_preview += "..." + render_selectable_label(app, f'tc_res_{i}', res_preview, width=-1) + + imgui.end_table() if app._scroll_tool_calls_to_bottom: imgui.set_scroll_here_y(1.0) @@ -5453,6 +5414,7 @@ def render_empty_context_modal(app: App) -> None: def render_context_modals(app: App) -> None: render_empty_context_modal(app) + render_add_context_files_modal(app) if app.show_missing_files_modal: imgui.open_popup("Missing Files Warning")