chore(conductor): Add new tracks for Phase 7
- Add 'structural_file_editor_20260601' track. - Add 'discussion_metrics_and_compression_20260601' track.
This commit is contained in:
+11
-1
@@ -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/)*
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Track discussion_metrics_and_compression_20260601 Context
|
||||
|
||||
- [Specification](./spec.md)
|
||||
- [Implementation Plan](./plan.md)
|
||||
- [Metadata](./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"
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Track structural_file_editor_20260601 Context
|
||||
|
||||
- [Specification](./spec.md)
|
||||
- [Implementation Plan](./plan.md)
|
||||
- [Metadata](./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"
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
+66
-104
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user