Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70bfcc61c2 | |||
| 976a3b63e0 | |||
| 0e0b185290 | |||
| 5b196dccf0 | |||
| 1771c32006 | |||
| 6a39f440d9 | |||
| cecbe2245a | |||
| ad9ffb68f5 | |||
| e9b7875de5 | |||
| 3d5c768598 | |||
| f297e7a3bd | |||
| 4a705a8060 | |||
| 55f3bd87e2 | |||
| e56d4af097 | |||
| 1328bc159b | |||
| 3a0d388502 | |||
| 879e0991c9 | |||
| d96adca67c | |||
| 4b0ebe44ff | |||
| 6b8151235f | |||
| 69107a75d3 | |||
| 89c9f62f0c | |||
| 87e6b5c665 | |||
| 9f8dd48a2e | |||
| 87bd2ae11c | |||
| a57a3c78d4 | |||
| ca01397885 | |||
| c76aba64e4 | |||
| 96de21b2b2 | |||
| 25d7d97455 | |||
| da478191e9 | |||
| 9b79044caa | |||
| 229fbe2b3f | |||
| d69434e85f | |||
| 830bd7b1fb | |||
| 50f98deb74 | |||
| 67ed51056e | |||
| 905ac00e3f | |||
| 836168a2a8 | |||
| 2dbd570d59 | |||
| 5ebce894bb | |||
| 6c4c567ed0 | |||
| 09383960be | |||
| ac4f63b76e | |||
| 356d5f3618 | |||
| b9ca69fbae | |||
| 3f4ae21708 | |||
| 59d7368bd7 | |||
| 02fca1f8ba | |||
| 841e54aa47 | |||
| 815ee55981 | |||
| 4e5ec31876 | |||
| 5f4da366f1 | |||
| 82722999a8 | |||
| ad93a294fb | |||
| b677228a96 | |||
| f2c5ae43d7 | |||
| cf5ee6c0f1 | |||
| 123bcdcb58 | |||
| c8eb340afe | |||
| 414379da4f | |||
| 63015e9523 | |||
| 36b3c33dcc | |||
| 727274728f | |||
| befb480285 | |||
| 5a8a91ecf7 | |||
| 8bc6eae101 | |||
| 1f8bb58219 | |||
| 19e7c94c2e | |||
| 23943443e3 | |||
| 6f1fea85f0 | |||
| d237d3b94d |
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
try:
|
||||||
|
from imgui_bundle import hello_imgui
|
||||||
|
rp = hello_imgui.RunnerParams()
|
||||||
|
print(f"Default borderless: {rp.app_window_params.borderless}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# Implementation Plan: External MCP Server Support
|
||||||
|
|
||||||
|
## Phase 1: Configuration & Data Modeling [checkpoint: 4ba1bd9]
|
||||||
|
- [x] Task: Define the schema for external MCP server configuration. [1c863f0]
|
||||||
|
- [x] Update `src/models.py` to include `MCPServerConfig` and `MCPConfiguration` classes.
|
||||||
|
- [x] Implement logic to load `mcp_config.json` from global and project-specific paths.
|
||||||
|
- [x] Task: Integrate configuration loading into `AppController`. [c09e0f5]
|
||||||
|
- [x] Ensure the MCP config path is correctly resolved from `config.toml` and `manual_slop.toml`.
|
||||||
|
- [x] Task: Write unit tests for configuration loading and validation. [c09e0f5]
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Phase 1: Configuration & Data Modeling' [4ba1bd9]
|
||||||
|
|
||||||
|
## Phase 2: MCP Client Extension [checkpoint: 828fadf]
|
||||||
|
- [x] Task: Implement `ExternalMCPManager` in `src/mcp_client.py`. [828fadf]
|
||||||
|
- [x] Add support for managing multiple MCP server sessions.
|
||||||
|
- [x] Implement the `StdioMCPClient` for local subprocess communication.
|
||||||
|
- [x] Implement the `RemoteMCPClient` for SSE/WebSocket communication (stub).
|
||||||
|
- [x] Task: Update Tool Discovery. [828fadf]
|
||||||
|
- [x] Implement `list_external_tools()` to aggregate tools from all active external servers.
|
||||||
|
- [x] Task: Update Tool Dispatch. [828fadf]
|
||||||
|
- [x] Modify `mcp_client.dispatch()` and `mcp_client.async_dispatch()` to route tool calls to either native tools or the appropriate external server.
|
||||||
|
- [x] Task: Write integration tests for stdio and remote MCP client communication (using mock servers). [828fadf]
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Phase 2: MCP Client Extension' [828fadf]
|
||||||
|
|
||||||
|
## Phase 3: GUI Integration & Lifecycle [checkpoint: 3b2588a]
|
||||||
|
- [x] Task: Update the **Operations** panel in `src/gui_2.py`. [3b2588a]
|
||||||
|
- [x] Create a new "External Tools" section.
|
||||||
|
- [x] List discovered tools from active external servers.
|
||||||
|
- [x] Add a "Refresh External MCPs" button to reload configuration and rediscover tools.
|
||||||
|
- [x] Task: Implement Lifecycle Management. [3b2588a]
|
||||||
|
- [x] Add the "Auto-start on Project Load" logic to start servers when a project is initialized.
|
||||||
|
- [x] Add status indicators (e.g., color-coded dots) for each external server in the GUI.
|
||||||
|
- [x] Task: Write visual regression tests or simulation scripts to verify the updated Operations panel. [3b2588a]
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Phase 3: GUI Integration & Lifecycle' [3b2588a]
|
||||||
|
|
||||||
|
## Phase 4: Agent Integration & HITL [checkpoint: f4c5a0b]
|
||||||
|
- [x] Task: Update AI tool declarations. [f4c5a0b]
|
||||||
|
- [x] Ensure `ai_client.py` includes external tools in the tool definitions sent to Gemini/Anthropic.
|
||||||
|
- [x] Task: Verify HITL Approval Flow. [f4c5a0b]
|
||||||
|
- [x] Ensure that calling an external tool correctly triggers the `ConfirmDialog` modal.
|
||||||
|
- [x] Verify that approved external tool results are correctly returned to the AI.
|
||||||
|
- [x] Task: Perform a final end-to-end verification with a real external MCP server. [f4c5a0b]
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Phase 4: Agent Integration & HITL' [f4c5a0b]
|
||||||
+12
-12
@@ -3,13 +3,13 @@
|
|||||||
## Phase 1: Path Info Display
|
## Phase 1: Path Info Display
|
||||||
Focus: Show current path resolution in GUI
|
Focus: Show current path resolution in GUI
|
||||||
|
|
||||||
- [ ] Task 1.1: Add path info functions to paths.py
|
- [x] Task 1.1: Add path info functions to paths.py [d237d3b]
|
||||||
- WHERE: src/paths.py
|
- WHERE: src/paths.py
|
||||||
- WHAT: Add functions to get path resolution source (default/env/config)
|
- WHAT: Add functions to get path resolution source (default/env/config)
|
||||||
- HOW: Return tuple of (resolved_path, source)
|
- HOW: Return tuple of (resolved_path, source)
|
||||||
- SAFETY: New functions, no modifications
|
- SAFETY: New functions, no modifications
|
||||||
|
|
||||||
- [ ] Task 1.2: Create path display helper
|
- [x] Task 1.2: Create path display helper [d237d3b]
|
||||||
- WHERE: src/paths.py
|
- WHERE: src/paths.py
|
||||||
- WHAT: Function to get all paths with resolution info
|
- WHAT: Function to get all paths with resolution info
|
||||||
- HOW: Returns dict of path_name -> (resolved, source)
|
- HOW: Returns dict of path_name -> (resolved, source)
|
||||||
@@ -18,25 +18,25 @@ Focus: Show current path resolution in GUI
|
|||||||
## Phase 2: Context Hub Panel
|
## Phase 2: Context Hub Panel
|
||||||
Focus: Add Path Configuration panel to GUI
|
Focus: Add Path Configuration panel to GUI
|
||||||
|
|
||||||
- [ ] Task 2.1: Add Paths tab to Context Hub
|
- [x] Task 2.1: Add Paths tab to Context Hub [d237d3b]
|
||||||
- WHERE: src/gui_2.py (Context Hub section)
|
- WHERE: src/gui_2.py (Context Hub section)
|
||||||
- WHAT: New tab/section for path configuration
|
- WHAT: New tab/section for path configuration
|
||||||
- HOW: Add ImGui tab item, follow existing panel patterns
|
- HOW: Add ImGui tab item, follow existing panel patterns
|
||||||
- SAFETY: New panel, no modifications to existing
|
- SAFETY: New panel, no modifications to existing
|
||||||
|
|
||||||
- [ ] Task 2.2: Display current paths
|
- [x] Task 2.2: Display current paths [d237d3b]
|
||||||
- WHERE: src/gui_2.py (new paths panel)
|
- WHERE: src/gui_2.py (new paths panel)
|
||||||
- WHAT: Show resolved paths and their sources
|
- WHAT: Show resolved paths and their sources
|
||||||
- HOW: Call paths.py functions, display in read-only text
|
- HOW: Call paths.py functions, display in read-only text
|
||||||
- SAFETY: New code
|
- SAFETY: New code
|
||||||
|
|
||||||
- [ ] Task 2.3: Add path text inputs
|
- [x] Task 2.3: Add path text inputs [d237d3b]
|
||||||
- WHERE: src/gui_2.py (paths panel)
|
- WHERE: src/gui_2.py (paths panel)
|
||||||
- WHAT: Editable text inputs for each path
|
- WHAT: Editable text inputs for each path
|
||||||
- HOW: ImGui input_text for conductor_dir, logs_dir, scripts_dir
|
- HOW: ImGui input_text for conductor_dir, logs_dir, scripts_dir
|
||||||
- SAFETY: New code
|
- SAFETY: New code
|
||||||
|
|
||||||
- [ ] Task 2.4: Add browse buttons
|
- [x] Task 2.4: Add browse buttons [d237d3b]
|
||||||
- WHERE: src/gui_2.py (paths panel)
|
- WHERE: src/gui_2.py (paths panel)
|
||||||
- WHAT: File dialog buttons to browse for directories
|
- WHAT: File dialog buttons to browse for directories
|
||||||
- HOW: Use existing file dialog patterns in gui_2.py
|
- HOW: Use existing file dialog patterns in gui_2.py
|
||||||
@@ -45,19 +45,19 @@ Focus: Add Path Configuration panel to GUI
|
|||||||
## Phase 3: Persistence
|
## Phase 3: Persistence
|
||||||
Focus: Save path changes to config.toml
|
Focus: Save path changes to config.toml
|
||||||
|
|
||||||
- [ ] Task 3.1: Add config write function
|
- [x] Task 3.1: Add config write function [d237d3b]
|
||||||
- WHERE: src/gui_2.py or new utility
|
- WHERE: src/gui_2.py or new utility
|
||||||
- WHAT: Write [paths] section to config.toml
|
- WHAT: Write [paths] section to config.toml
|
||||||
- HOW: Read existing config, update paths section, write back
|
- HOW: Read existing config, update paths section, write back
|
||||||
- SAFETY: Backup before write, handle errors
|
- SAFETY: Backup before write, handle errors
|
||||||
|
|
||||||
- [ ] Task 3.2: Add Apply button
|
- [x] Task 3.2: Add Apply button [d237d3b]
|
||||||
- WHERE: src/gui_2.py (paths panel)
|
- WHERE: src/gui_2.py (paths panel)
|
||||||
- WHAT: Button to save changes
|
- WHAT: Button to save changes
|
||||||
- HOW: Call config write function, show success/error message
|
- HOW: Call config write function, show success/error message
|
||||||
- SAFETY: Confirmation dialog
|
- SAFETY: Confirmation dialog
|
||||||
|
|
||||||
- [ ] Task 3.3: Add Reset button
|
- [x] Task 3.3: Add Reset button [d237d3b]
|
||||||
- WHERE: src/gui_2.py (paths panel)
|
- WHERE: src/gui_2.py (paths panel)
|
||||||
- WHAT: Reset paths to defaults
|
- WHAT: Reset paths to defaults
|
||||||
- HOW: Clear custom values, show confirmation
|
- HOW: Clear custom values, show confirmation
|
||||||
@@ -66,13 +66,13 @@ Focus: Save path changes to config.toml
|
|||||||
## Phase 4: UX Polish
|
## Phase 4: UX Polish
|
||||||
Focus: Improve user experience
|
Focus: Improve user experience
|
||||||
|
|
||||||
- [ ] Task 4.1: Add restart warning
|
- [x] Task 4.1: Add restart warning [d237d3b]
|
||||||
- WHERE: src/gui_2.py (paths panel)
|
- WHERE: src/gui_2.py (paths panel)
|
||||||
- WHAT: Show warning that changes require restart
|
- WHAT: Show warning that changes require restart
|
||||||
- HOW: Text label after Apply
|
- HOW: Text label after Apply
|
||||||
- SAFETY: New code
|
- SAFETY: New code
|
||||||
|
|
||||||
- [ ] Task 4.2: Add tooltips
|
- [x] Task 4.2: Add tooltips [d237d3b]
|
||||||
- WHERE: src/gui_2.py (paths panel)
|
- WHERE: src/gui_2.py (paths panel)
|
||||||
- WHAT: Explain each path and resolution order
|
- WHAT: Explain each path and resolution order
|
||||||
- HOW: ImGui set_tooltip on hover
|
- HOW: ImGui set_tooltip on hover
|
||||||
@@ -81,7 +81,7 @@ Focus: Improve user experience
|
|||||||
## Phase 5: Tests
|
## Phase 5: Tests
|
||||||
Focus: Verify GUI path configuration
|
Focus: Verify GUI path configuration
|
||||||
|
|
||||||
- [ ] Task 5.1: Test path display
|
- [x] Task 5.1: Test path display [d237d3b]
|
||||||
- WHERE: tests/test_gui_paths.py (new file)
|
- WHERE: tests/test_gui_paths.py (new file)
|
||||||
- WHAT: Verify paths panel shows correct values
|
- WHAT: Verify paths panel shows correct values
|
||||||
- HOW: Mock paths.py, verify display
|
- HOW: Mock paths.py, verify display
|
||||||
+8
-8
@@ -3,13 +3,13 @@
|
|||||||
## Phase 1: Extend paths.py
|
## Phase 1: Extend paths.py
|
||||||
Focus: Add project-specific path resolution
|
Focus: Add project-specific path resolution
|
||||||
|
|
||||||
- [ ] Task 1.1: Add project-aware conductor path functions
|
- [x] Task 1.1: Add project-aware conductor path functions [48e2ed8]
|
||||||
- WHERE: src/paths.py
|
- WHERE: src/paths.py
|
||||||
- WHAT: Add optional project_path parameter to get_conductor_dir, get_tracks_dir, get_track_state_dir
|
- WHAT: Add optional project_path parameter to get_conductor_dir, get_tracks_dir, get_track_state_dir
|
||||||
- HOW: If project_path provided, resolve relative to project root; otherwise use global
|
- HOW: If project_path provided, resolve relative to project root; otherwise use global
|
||||||
- SAFETY: Maintain backward compatibility with no-arg calls
|
- SAFETY: Maintain backward compatibility with no-arg calls
|
||||||
|
|
||||||
- [ ] Task 1.2: Add project conductor path resolution
|
- [x] Task 1.2: Add project conductor path resolution [48e2ed8]
|
||||||
- WHERE: src/paths.py
|
- WHERE: src/paths.py
|
||||||
- WHAT: New function `_resolve_project_conductor_dir(project_path)` that reads from project TOML
|
- WHAT: New function `_resolve_project_conductor_dir(project_path)` that reads from project TOML
|
||||||
- HOW: Load project TOML, check `[conductor].dir` key
|
- HOW: Load project TOML, check `[conductor].dir` key
|
||||||
@@ -18,18 +18,18 @@ Focus: Add project-specific path resolution
|
|||||||
## Phase 2: Update project_manager.py
|
## Phase 2: Update project_manager.py
|
||||||
Focus: Use project-specific paths for track operations
|
Focus: Use project-specific paths for track operations
|
||||||
|
|
||||||
- [ ] Task 2.1: Update save_track_state to use project conductor dir
|
- [x] Task 2.1: Update save_track_state to use project conductor dir [3999e9c]
|
||||||
- WHERE: src/project_manager.py (around line 240)
|
- WHERE: src/project_manager.py (around line 240)
|
||||||
- WHAT: Pass project base_dir to paths.get_track_state_dir()
|
- WHAT: Pass project base_dir to paths.get_track_state_dir()
|
||||||
- HOW: Get base_dir from project_path, call paths with project_path param
|
- HOW: Get base_dir from project_path, call paths with project_path param
|
||||||
- SAFETY: Maintain existing function signature compatibility
|
- SAFETY: Maintain existing function signature compatibility
|
||||||
|
|
||||||
- [ ] Task 2.2: Update load_track_state to use project conductor dir
|
- [x] Task 2.2: Update load_track_state to use project conductor dir [3999e9c]
|
||||||
- WHERE: src/project_manager.py (around line 252)
|
- WHERE: src/project_manager.py (around line 252)
|
||||||
- WHAT: Load track state from project-specific directory
|
- WHAT: Load track state from project-specific directory
|
||||||
- HOW: Same as above
|
- HOW: Same as above
|
||||||
|
|
||||||
- [ ] Task 2.3: Update get_all_tracks to use project conductor dir
|
- [x] Task 2.3: Update get_all_tracks to use project conductor dir [3999e9c]
|
||||||
- WHERE: src/project_manager.py (around line 297)
|
- WHERE: src/project_manager.py (around line 297)
|
||||||
- WHAT: List tracks from project-specific directory
|
- WHAT: List tracks from project-specific directory
|
||||||
- HOW: Accept optional project_path param
|
- HOW: Accept optional project_path param
|
||||||
@@ -37,7 +37,7 @@ Focus: Use project-specific paths for track operations
|
|||||||
## Phase 3: Update app_controller.py
|
## Phase 3: Update app_controller.py
|
||||||
Focus: Pass project path to track operations
|
Focus: Pass project path to track operations
|
||||||
|
|
||||||
- [ ] Task 3.1: Update track creation to use project conductor dir
|
- [x] Task 3.1: Update track creation to use project conductor dir [3999e9c]
|
||||||
- WHERE: src/app_controller.py (around line 1907, 1937)
|
- WHERE: src/app_controller.py (around line 1907, 1937)
|
||||||
- WHAT: Pass active_project_path to track path functions
|
- WHAT: Pass active_project_path to track path functions
|
||||||
- HOW: Get active_project_path, pass to paths.get_tracks_dir()
|
- HOW: Get active_project_path, pass to paths.get_tracks_dir()
|
||||||
@@ -46,13 +46,13 @@ Focus: Pass project path to track operations
|
|||||||
## Phase 4: Tests
|
## Phase 4: Tests
|
||||||
Focus: Verify project-specific behavior
|
Focus: Verify project-specific behavior
|
||||||
|
|
||||||
- [ ] Task 4.1: Write test for project-specific conductor dir
|
- [x] Task 4.1: Write test for project-specific conductor dir [48e2ed8]
|
||||||
- WHERE: tests/test_project_paths.py (new file)
|
- WHERE: tests/test_project_paths.py (new file)
|
||||||
- WHAT: Create mock project with custom conductor dir, verify tracks saved there
|
- WHAT: Create mock project with custom conductor dir, verify tracks saved there
|
||||||
- HOW: Mock project_manager, verify path resolution
|
- HOW: Mock project_manager, verify path resolution
|
||||||
- SAFETY: New test file
|
- SAFETY: New test file
|
||||||
|
|
||||||
- [ ] Task 4.2: Test backward compatibility
|
- [x] Task 4.2: Test backward compatibility [3999e9c]
|
||||||
- WHERE: tests/test_project_paths.py
|
- WHERE: tests/test_project_paths.py
|
||||||
- WHAT: Verify global paths still work without project_path
|
- WHAT: Verify global paths still work without project_path
|
||||||
- HOW: Call functions without project_path, verify defaults
|
- HOW: Call functions without project_path, verify defaults
|
||||||
@@ -33,7 +33,7 @@ For deep implementation details when planning or implementing tracks, consult `d
|
|||||||
- **Track Browser:** Real-time visualization of all implementation tracks with status indicators and progress bars. Includes a dedicated **Active Track Summary** featuring a color-coded progress bar, precise ticket status breakdown (Completed, In Progress, Blocked, Todo), and dynamic **ETA estimation** based on historical completion times.
|
- **Track Browser:** Real-time visualization of all implementation tracks with status indicators and progress bars. Includes a dedicated **Active Track Summary** featuring a color-coded progress bar, precise ticket status breakdown (Completed, In Progress, Blocked, Todo), and dynamic **ETA estimation** based on historical completion times.
|
||||||
- **Visual Task DAG:** An interactive, node-based visualizer for the active track's task dependencies using `imgui-node-editor`. Features color-coded state tracking (Ready, Running, Blocked, Done), drag-and-drop dependency creation, and right-click deletion.
|
- **Visual Task DAG:** An interactive, node-based visualizer for the active track's task dependencies using `imgui-node-editor`. Features color-coded state tracking (Ready, Running, Blocked, Done), drag-and-drop dependency creation, and right-click deletion.
|
||||||
- **Strategy Visualization:** Dedicated real-time output streams for Tier 1 (Strategic Planning) and Tier 2/3 (Execution) agents, allowing the user to follow the agent's reasoning chains alongside the task DAG.
|
- **Strategy Visualization:** Dedicated real-time output streams for Tier 1 (Strategic Planning) and Tier 2/3 (Execution) agents, allowing the user to follow the agent's reasoning chains alongside the task DAG.
|
||||||
- **Track-Scoped State Management:** Segregates discussion history and task progress into per-track state files. Supports **Project-Specific Conductor Directories**, allowing projects to define their own conductor path in `manual_slop.toml` (`[conductor].dir`) for isolated track management. This prevents global context pollution and ensures the Tech Lead session is isolated to the specific track's objective.
|
- **Track-Scoped State Management:** Segregates discussion history and task progress into per-track state files. Supports **Project-Specific Conductor Directories**, defaulting to `./conductor` relative to each project's TOML file. Projects can define their own conductor path override in `manual_slop.toml` (`[conductor].dir`) via the Projects tab for isolated track management. This prevents global context pollution and ensures the Tech Lead session is isolated to the specific track's objective.
|
||||||
**Native DAG Execution Engine:** Employs a Python-based Directed Acyclic Graph (DAG) engine to manage complex task dependencies. Supports automated topological sorting, robust cycle detection, and **transitive blocking propagation** (cascading `blocked` status to downstream dependents to prevent execution stalls).
|
**Native DAG Execution Engine:** Employs a Python-based Directed Acyclic Graph (DAG) engine to manage complex task dependencies. Supports automated topological sorting, robust cycle detection, and **transitive blocking propagation** (cascading `blocked` status to downstream dependents to prevent execution stalls).
|
||||||
|
|
||||||
- **Programmable Execution State machine:** Governing the transition between "Auto-Queue" (autonomous worker spawning) and "Step Mode" (explicit manual approval for each task transition).
|
- **Programmable Execution State machine:** Governing the transition between "Auto-Queue" (autonomous worker spawning) and "Step Mode" (explicit manual approval for each task transition).
|
||||||
@@ -49,7 +49,7 @@ For deep implementation details when planning or implementing tracks, consult `d
|
|||||||
- **Multi-Server Lifecycle Management:** Orchestrates multiple concurrent MCP server sessions (Stdio for local subprocesses and SSE for remote servers).
|
- **Multi-Server Lifecycle Management:** Orchestrates multiple concurrent MCP server sessions (Stdio for local subprocesses and SSE for remote servers).
|
||||||
- **Flexible Configuration:** Supports global (`config.toml`) and project-specific (`manual_slop.toml`) paths for `mcp_config.json` (standard MCP configuration format).
|
- **Flexible Configuration:** Supports global (`config.toml`) and project-specific (`manual_slop.toml`) paths for `mcp_config.json` (standard MCP configuration format).
|
||||||
- **Auto-Start & Discovery:** Automatically initializes configured servers on project load and dynamically aggregates their tools into the agent's capability declarations.
|
- **Auto-Start & Discovery:** Automatically initializes configured servers on project load and dynamically aggregates their tools into the agent's capability declarations.
|
||||||
- **Dedicated Operations UI:** Features a new **External Tools** section within the Operations Hub for monitoring server status (idle, starting, running, error) and browsing discovered tool schemas.
|
- **Dedicated Operations UI:** Features a new **External Tools** section within the Operations Hub for monitoring server status (idle, starting, running, error) and browsing discovered tool schemas. Supports **Pop-Out Panel functionality**, allowing the External Tools interface to be detached into a standalone window for optimized multi-monitor workflows.
|
||||||
- **Strict HITL Safety:** All external tool calls are intercepted and require explicit human-in-the-loop approval via the standard confirmation dialog before execution.
|
- **Strict HITL Safety:** All external tool calls are intercepted and require explicit human-in-the-loop approval via the standard confirmation dialog before execution.
|
||||||
- **High-Fidelity Selectable UI:** Most read-only labels and logs across the interface (including discussion history, comms payloads, tool outputs, and telemetry metrics) are now implemented as selectable text fields. This enables standard OS-level text selection and copying (Ctrl+C) while maintaining a high-density, non-editable aesthetic.
|
- **High-Fidelity Selectable UI:** Most read-only labels and logs across the interface (including discussion history, comms payloads, tool outputs, and telemetry metrics) are now implemented as selectable text fields. This enables standard OS-level text selection and copying (Ctrl+C) while maintaining a high-density, non-editable aesthetic.
|
||||||
- **High-Fidelity UI Rendering:** Employs advanced 3x font oversampling and sub-pixel positioning to ensure crisp, high-clarity text rendering across all resolutions, enhancing readability for dense logs and complex code fragments.
|
- **High-Fidelity UI Rendering:** Employs advanced 3x font oversampling and sub-pixel positioning to ensure crisp, high-clarity text rendering across all resolutions, enhancing readability for dense logs and complex code fragments.
|
||||||
@@ -60,13 +60,13 @@ For deep implementation details when planning or implementing tracks, consult `d
|
|||||||
- **Dedicated Diagnostics Hub:** Consolidates real-time telemetry (FPS, CPU, Frame Time) and transient system warnings into a standalone **Diagnostics panel**, providing deep visibility into application health without polluting the discussion history.
|
- **Dedicated Diagnostics Hub:** Consolidates real-time telemetry (FPS, CPU, Frame Time) and transient system warnings into a standalone **Diagnostics panel**, providing deep visibility into application health without polluting the discussion history.
|
||||||
- **Improved MMA Observability:** Enhances sub-agent logging by injecting precise ticket IDs and descriptive roles into communication metadata, enabling granular filtering and tracking of parallel worker activities within the Comms History.
|
- **Improved MMA Observability:** Enhances sub-agent logging by injecting precise ticket IDs and descriptive roles into communication metadata, enabling granular filtering and tracking of parallel worker activities within the Comms History.
|
||||||
- **In-Depth Toolset Access:** MCP-like file exploration, URL fetching, search, and dynamic context aggregation embedded within a multi-viewport Dear PyGui/ImGui interface.
|
- **In-Depth Toolset Access:** MCP-like file exploration, URL fetching, search, and dynamic context aggregation embedded within a multi-viewport Dear PyGui/ImGui interface.
|
||||||
- **Integrated Workspace:** A consolidated Hub-based layout (Context, AI Settings, Discussion, Operations) designed for expert multi-monitor workflows.
|
- **Integrated Workspace:** A consolidated Hub-based layout (Context, AI Settings, Discussion, Operations) designed for expert multi-monitor workflows. Features **GUI-Based Path Configuration** within the Context Hub, allowing users to view and edit system paths (conductor, logs, scripts) with real-time resolution source tracking (default, env, or config). Changes are applied immediately at runtime without requiring an application restart.
|
||||||
- **Session Analysis:** Ability to load and visualize historical session logs with a dedicated tinted "Prior Session" viewing mode.
|
- **Session Analysis:** Ability to load and visualize historical session logs with a dedicated tinted "Prior Session" viewing mode.
|
||||||
- **Structured Log Taxonomy:** Automated session-based log organization into configurable directories (defaulting to `logs/sessions/`). Includes a dedicated GUI panel for monitoring and manual whitelisting. Features an intelligent heuristic-based pruner that automatically cleans up insignificant logs older than 24 hours while preserving valuable sessions.
|
- **Structured Log Taxonomy:** Automated session-based log organization into configurable directories (defaulting to `logs/sessions/`). Includes a dedicated GUI panel for monitoring and manual whitelisting. Features an intelligent heuristic-based pruner that automatically cleans up insignificant logs older than 24 hours while preserving valuable sessions.
|
||||||
- **Clean Project Root:** Enforces a "Cruft-Free Root" policy by organizing core implementation into a `src/` directory and redirecting all temporary test data, configurations, and AI-generated artifacts to `tests/artifacts/`.
|
- **Clean Project Root:** Enforces a "Cruft-Free Root" policy by organizing core implementation into a `src/` directory and redirecting all temporary test data, configurations, and AI-generated artifacts to `tests/artifacts/`.
|
||||||
- **Performance Diagnostics:** Comprehensive, conditional per-component profiling across the entire application. Features a dedicated **Diagnostics Panel** providing real-time telemetry for FPS, Frame Time, CPU usage, and **Detailed Component Timings** for all GUI panels and background threads, including automated threshold-based latency alerts.
|
- **Performance Diagnostics:** Comprehensive, conditional per-component profiling across the entire application. Features a dedicated **Diagnostics Panel** providing real-time telemetry for FPS, Frame Time, CPU usage, and **Detailed Component Timings** for all GUI panels and background threads, including automated threshold-based latency alerts.
|
||||||
- **Automated UX Verification:** A robust IPC mechanism via API hooks and a modular simulation suite allows for human-like simulation walkthroughs and automated regression testing of the full GUI lifecycle across multiple specialized scenarios.
|
- **Automated UX Verification:** A robust IPC mechanism via API hooks and a modular simulation suite allows for human-like simulation walkthroughs and automated regression testing of the full GUI lifecycle across multiple specialized scenarios.
|
||||||
- **Professional UI Theme & Typography:** Implements a high-fidelity visual system featuring **Inter** and **Maple Mono** fonts for optimal readability. Employs a cohesive "Subtle Rounding" aesthetic across all standard widgets, supported by custom **soft shadow shaders** for modals and popups to provide depth and professional polish. Includes a selectable **NERV UI theme** featuring a "Black Void" palette, zero-rounding geometry, and CRT-style visual effects (scanlines, status flickering).
|
- **Professional UI Theme & Typography:** Implements a high-fidelity visual system featuring **Inter** and **Maple Mono** fonts for optimal readability. Employs a cohesive \"Subtle Rounding\" aesthetic across all standard widgets, supported by custom **soft shadow shaders** for modals and popups, and a high-fidelity **frosted glass (acrylic) background effect** for panels to provide depth and professional polish. Includes a selectable **NERV UI theme** featuring a \"Black Void\" palette, zero-rounding geometry, and CRT-style visual effects (scanlines, status flickering).
|
||||||
- **Rich Text & Syntax Highlighting:** Provides advanced rendering for messages, logs, and tool outputs using a hybrid Markdown system. Supports GitHub-Flavored Markdown (GFM) via `imgui_markdown` and integrates `ImGuiColorTextEdit` for high-performance syntax highlighting of code blocks (Python, JSON, C++, etc.). Includes automated language detection and clickable URL support.
|
- **Rich Text & Syntax Highlighting:** Provides advanced rendering for messages, logs, and tool outputs using a hybrid Markdown system. Supports GitHub-Flavored Markdown (GFM) via `imgui_markdown` and integrates `ImGuiColorTextEdit` for high-performance syntax highlighting of code blocks (Python, JSON, C++, etc.). Includes automated language detection and clickable URL support.
|
||||||
- **Multi-Viewport & Layout Management:** Full support for ImGui Multi-Viewport, allowing users to detach panels into standalone OS windows for complex multi-monitor workflows. Includes a comprehensive **Layout Presets system**, enabling developers to save, name, and instantly restore custom window arrangements, including their Multi-Viewport state.
|
- **Multi-Viewport & Layout Management:** Full support for ImGui Multi-Viewport, allowing users to detach panels into standalone OS windows for complex multi-monitor workflows. Includes a comprehensive **Layout Presets system**, enabling developers to save, name, and instantly restore custom window arrangements, including their Multi-Viewport state.
|
||||||
- **Headless Backend Service & Hook API:** Optional headless mode allowing the core AI and tool execution logic to run as a decoupled service. Features a comprehensive Hook API and WebSocket event streaming for remote orchestration, deep state inspection, and manual worker lifecycle management.
|
- **Headless Backend Service & Hook API:** Optional headless mode allowing the core AI and tool execution logic to run as a decoupled service. Features a comprehensive Hook API and WebSocket event streaming for remote orchestration, deep state inspection, and manual worker lifecycle management.
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
- **ai_style_formatter.py:** Custom Python formatter specifically designed to enforce 1-space indentation and ultra-compact whitespace to minimize token consumption.
|
- **ai_style_formatter.py:** Custom Python formatter specifically designed to enforce 1-space indentation and ultra-compact whitespace to minimize token consumption.
|
||||||
|
|
||||||
- **src/paths.py:** Centralized module for path resolution. Supports project-specific conductor directory overrides via project TOML (`[conductor].dir`), enabling isolated track management per project. All paths are resolved to absolute objects. Path configuration (logs, conductor, scripts) can also be configured via `config.toml` or environment variables, eliminating hardcoded filesystem dependencies.
|
- **src/paths.py:** Centralized module for path resolution. Supports project-specific conductor directory overrides via project TOML (`[conductor].dir`), enabling isolated track management per project. If not specified, conductor paths default to `./conductor` relative to each project's TOML file. All paths are resolved to absolute objects. Provides **Path Resolution Metadata**, exposing the source of each resolved path (default, environment variable, or configuration file) for high-fidelity GUI display. Supports **Runtime Re-Resolution** via `reset_resolved()`, allowing path changes to be applied immediately without an application restart. Path configuration (logs, scripts) can also be configured via `config.toml` or environment variables, eliminating hardcoded filesystem dependencies.
|
||||||
|
|
||||||
- **src/presets.py:** Implements `PresetManager` for high-performance CRUD operations on system prompt presets stored in TOML format (`presets.toml`, `project_presets.toml`). Supports dynamic path resolution and scope-based inheritance.
|
- **src/presets.py:** Implements `PresetManager` for high-performance CRUD operations on system prompt presets stored in TOML format (`presets.toml`, `project_presets.toml`). Supports dynamic path resolution and scope-based inheritance.
|
||||||
|
|
||||||
@@ -52,6 +52,8 @@
|
|||||||
- **LogRegistry & LogPruner:** Custom components for session metadata persistence and automated filesystem cleanup within the `logs/sessions/` taxonomy.
|
- **LogRegistry & LogPruner:** Custom components for session metadata persistence and automated filesystem cleanup within the `logs/sessions/` taxonomy.
|
||||||
- **psutil:** For system and process monitoring (CPU/Memory telemetry).
|
- **psutil:** For system and process monitoring (CPU/Memory telemetry).
|
||||||
- **uv:** An extremely fast Python package and project manager.
|
- **uv:** An extremely fast Python package and project manager.
|
||||||
|
- **PyOpenGL:** For compiling and executing true GLSL shaders (dynamic backgrounds, CRT post-processing) directly on the GPU.
|
||||||
|
- **pywin32:** For custom OS window frame manipulation on Windows (e.g., minimizing, maximizing, closing, and dragging the borderless ImGui window).
|
||||||
- **pytest:** For unit and integration testing, leveraging custom fixtures for live GUI verification.
|
- **pytest:** For unit and integration testing, leveraging custom fixtures for live GUI verification.
|
||||||
- **Taxonomy & Artifacts:** Enforces a clean root by organizing core implementation into a `src/` directory, and redirecting session logs and artifacts to configurable directories (defaulting to `logs/sessions/` and `scripts/generated/`). Temporary test data and test logs are siloed in `tests/artifacts/` and `tests/logs/`.
|
- **Taxonomy & Artifacts:** Enforces a clean root by organizing core implementation into a `src/` directory, and redirecting session logs and artifacts to configurable directories (defaulting to `logs/sessions/` and `scripts/generated/`). Temporary test data and test logs are siloed in `tests/artifacts/` and `tests/logs/`.
|
||||||
- **ApiHookClient:** A dedicated IPC client for automated GUI interaction and state inspection.
|
- **ApiHookClient:** A dedicated IPC client for automated GUI interaction and state inspection.
|
||||||
@@ -68,6 +70,6 @@
|
|||||||
- **Synchronous IPC Approval Flow:** A specialized bridge mechanism that allows headless AI providers (like Gemini CLI) to synchronously request and receive human approval for tool calls via the GUI's REST API hooks.
|
- **Synchronous IPC Approval Flow:** A specialized bridge mechanism that allows headless AI providers (like Gemini CLI) to synchronously request and receive human approval for tool calls via the GUI's REST API hooks.
|
||||||
- **High-Fidelity Selectable Labels:** Implements a pattern for making read-only UI text selectable by wrapping `imgui.input_text` with `imgui.InputTextFlags_.read_only`. Includes a specialized `_render_selectable_label` helper that resets frame backgrounds, borders, and padding to mimic standard labels while enabling OS-level clipboard support (Ctrl+C).
|
- **High-Fidelity Selectable Labels:** Implements a pattern for making read-only UI text selectable by wrapping `imgui.input_text` with `imgui.InputTextFlags_.read_only`. Includes a specialized `_render_selectable_label` helper that resets frame backgrounds, borders, and padding to mimic standard labels while enabling OS-level clipboard support (Ctrl+C).
|
||||||
- **Hybrid Markdown Rendering:** Employs a custom `MarkdownRenderer` that orchestrates `imgui_markdown` for standard text and headers while intercepting code blocks to render them via cached `ImGuiColorTextEdit` instances. This ensures high-performance rich text rendering with robust syntax highlighting and stateful text selection.
|
- **Hybrid Markdown Rendering:** Employs a custom `MarkdownRenderer` that orchestrates `imgui_markdown` for standard text and headers while intercepting code blocks to render them via cached `ImGuiColorTextEdit` instances. This ensures high-performance rich text rendering with robust syntax highlighting and stateful text selection.
|
||||||
- **Faux-Shader Visual Effects:** Utilizes an optimized `ImDrawList`-based batching technique to simulate advanced visual effects such as soft shadows, acrylic glass overlays, and **CRT scanline overlays** without the overhead of heavy GPU-resident shaders or external OpenGL dependencies. Includes support for **dynamic status flickering** and **alert pulsing** integrated into the NERV theme.
|
- **Hybrid Shader Pipeline:** Utilizes an optimized `ImDrawList`-based batching technique to simulate UI effects such as soft shadows without the overhead of heavy GPU-resident shaders. Supplemented by a true GPU shader pipeline using `PyOpenGL` and Framebuffer Objects (FBOs) for complex post-processing (CRT scanlines, bloom), dynamic backgrounds, and high-fidelity **frosted glass (acrylic) blurring** of the GUI panels via multi-pass Gaussian/Kawase filtering.
|
||||||
- **Interface-Driven Development (IDD):** Enforces a "Stub-and-Resolve" pattern where cross-module dependencies are resolved by generating signatures/contracts before implementation.
|
- **Interface-Driven Development (IDD):** Enforces a "Stub-and-Resolve" pattern where cross-module dependencies are resolved by generating signatures/contracts before implementation.
|
||||||
|
|
||||||
|
|||||||
+22
-23
@@ -10,32 +10,34 @@ This file tracks all major tracks for the project. Each track has its own detail
|
|||||||
|
|
||||||
### Architecture & Backend
|
### Architecture & Backend
|
||||||
|
|
||||||
1. [x] **Track: External MCP Server Support**
|
1. [ ] **Track: RAG Support**
|
||||||
*Link: [./tracks/external_mcp_support_20260308/](./tracks/external_mcp_support_20260308/)*
|
|
||||||
*Goal: Add support for external MCP servers (Local Stdio and Remote SSE/WS) with flexible configuration and lifecycle management (including auto-start on project load).*
|
|
||||||
|
|
||||||
2. [ ] **Track: RAG Support**
|
|
||||||
*Link: [./tracks/rag_support_20260308/](./tracks/rag_support_20260308/)*
|
*Link: [./tracks/rag_support_20260308/](./tracks/rag_support_20260308/)*
|
||||||
*Goal: Add support for RAG (Retrieval-Augmented Generation) using local vector stores (Chroma/Qdrant), native vendor retrieval, and external RAG APIs. Implement indexing pipeline and retrieval UI.*
|
*Goal: Add support for RAG (Retrieval-Augmented Generation) using local vector stores (Chroma/Qdrant), native vendor retrieval, and external RAG APIs. Implement indexing pipeline and retrieval UI.*
|
||||||
|
|
||||||
3. [x] **Track: Agent Tool Preference & Bias Tuning**
|
2. [x] **Track: Agent Tool Preference & Bias Tuning**
|
||||||
*Link: [./tracks/tool_bias_tuning_20260308/](./tracks/tool_bias_tuning_20260308/)*
|
*Link: [./tracks/tool_bias_tuning_20260308/](./tracks/tool_bias_tuning_20260308/)*
|
||||||
*Goal: Influence agent tool selection via a weighting system. Implement semantic nudges in tool descriptions and a dynamic "Tooling Strategy" section in the system prompt. Includes GUI badges and sliders for weight adjustment.*
|
*Goal: Influence agent tool selection via a weighting system. Implement semantic nudges in tool descriptions and a dynamic "Tooling Strategy" section in the system prompt. Includes GUI badges and sliders for weight adjustment.*
|
||||||
|
|
||||||
4. [x] **Track: Expanded Hook API & Headless Orchestration**
|
3. [x] **Track: Expanded Hook API & Headless Orchestration**
|
||||||
*Link: [./tracks/hook_api_expansion_20260308/](./tracks/hook_api_expansion_20260308/)*
|
*Link: [./tracks/hook_api_expansion_20260308/](./tracks/hook_api_expansion_20260308/)*
|
||||||
*Goal: Maximize internal state exposure and provide comprehensive control endpoints (worker spawn/kill, pipeline pause/resume, DAG mutation) via the Hook API. Implement WebSocket-based real-time event streaming.*
|
*Goal: Maximize internal state exposure and provide comprehensive control endpoints (worker spawn/kill, pipeline pause/resume, DAG mutation) via the Hook API. Implement WebSocket-based real-time event streaming.*
|
||||||
|
|
||||||
5. [ ] **Track: Codebase Audit and Cleanup**
|
4. [ ] **Track: Codebase Audit and Cleanup**
|
||||||
*Link: [./tracks/codebase_audit_20260308/](./tracks/codebase_audit_20260308/)*
|
*Link: [./tracks/codebase_audit_20260308/](./tracks/codebase_audit_20260308/)*
|
||||||
|
|
||||||
6. [ ] **Track: Expanded Test Coverage and Stress Testing**
|
5. [ ] **Track: Expanded Test Coverage and Stress Testing**
|
||||||
*Link: [./tracks/test_coverage_expansion_20260309/](./tracks/test_coverage_expansion_20260309/)*
|
*Link: [./tracks/test_coverage_expansion_20260309/](./tracks/test_coverage_expansion_20260309/)*
|
||||||
|
|
||||||
7. [ ] **Track: Beads Mode Integration**
|
6. [ ] **Track: Beads Mode Integration**
|
||||||
*Link: [./tracks/beads_mode_20260309/](./tracks/beads_mode_20260309/)*
|
*Link: [./tracks/beads_mode_20260309/](./tracks/beads_mode_20260309/)*
|
||||||
*Goal: Integrate Beads (git-backed graph issue tracker) as an alternative backend for MMA implementation tracks and tickets.*
|
*Goal: Integrate Beads (git-backed graph issue tracker) as an alternative backend for MMA implementation tracks and tickets.*
|
||||||
|
|
||||||
|
7. [ ] **Track: Optimization pass for Data-Oriented Python heuristics**
|
||||||
|
*Link: [./tracks/data_oriented_optimization_20260312/](./tracks/data_oriented_optimization_20260312/)*
|
||||||
|
|
||||||
|
8. [ ] **Track: Rich Thinking Trace Handling**
|
||||||
|
*Link: [./tracks/thinking_trace_handling_20260313/](./tracks/thinking_trace_handling_20260313/)*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### GUI Overhauls & Visualizations
|
### GUI Overhauls & Visualizations
|
||||||
@@ -58,7 +60,7 @@ This file tracks all major tracks for the project. Each track has its own detail
|
|||||||
|
|
||||||
5. [x] **Track: NERV UI Theme Integration** (Archived 2026-03-09)
|
5. [x] **Track: NERV UI Theme Integration** (Archived 2026-03-09)
|
||||||
|
|
||||||
6. [ ] **Track: Custom Shader and Window Frame Support**
|
6. [x] **Track: Custom Shader and Window Frame Support**
|
||||||
*Link: [./tracks/custom_shaders_20260309/](./tracks/custom_shaders_20260309/)*
|
*Link: [./tracks/custom_shaders_20260309/](./tracks/custom_shaders_20260309/)*
|
||||||
|
|
||||||
7. [x] **Track: UI/UX Improvements - Presets and AI Settings**
|
7. [x] **Track: UI/UX Improvements - Presets and AI Settings**
|
||||||
@@ -77,6 +79,12 @@ This file tracks all major tracks for the project. Each track has its own detail
|
|||||||
*Link: [./tracks/undo_redo_history_20260311/](./tracks/undo_redo_history_20260311/)*
|
*Link: [./tracks/undo_redo_history_20260311/](./tracks/undo_redo_history_20260311/)*
|
||||||
*Goal: Robust, non-provider based undo/redo for text inputs, UI controls, discussion mutations, and context management. Includes hotkey support and a history list view.*
|
*Goal: Robust, non-provider based undo/redo for text inputs, UI controls, discussion mutations, and context management. Includes hotkey support and a history list view.*
|
||||||
|
|
||||||
|
11. [ ] **Track: Advanced Text Viewer with Syntax Highlighting**
|
||||||
|
*Link: [./tracks/text_viewer_rich_rendering_20260313/](./tracks/text_viewer_rich_rendering_20260313/)*
|
||||||
|
|
||||||
|
12. [x] **Track: Frosted Glass Background Effect**
|
||||||
|
*Link: [./tracks/frosted_glass_20260313/](./tracks/frosted_glass_20260313/)*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Additional Language Support
|
### Additional Language Support
|
||||||
@@ -103,18 +111,6 @@ This file tracks all major tracks for the project. Each track has its own detail
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Path Configuration
|
|
||||||
|
|
||||||
1. [ ] **Track: Project-Specific Conductor Directory**
|
|
||||||
*Link: [./tracks/project_conductor_dir_20260308/](./tracks/project_conductor_dir_20260308/)*
|
|
||||||
*Goal: Make conductor directory per-project. Each project TOML can specify custom conductor dir for isolated track/state management.*
|
|
||||||
|
|
||||||
2. [ ] **Track: GUI Path Configuration in Context Hub**
|
|
||||||
*Link: [./tracks/gui_path_config_20260308/](./tracks/gui_path_config_20260308/)*
|
|
||||||
*Goal: Add path configuration UI to Context Hub. Allow users to view and edit configurable paths directly from the GUI.*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Manual UX Controls
|
### Manual UX Controls
|
||||||
|
|
||||||
1. [x] **Track: Saved System Prompt Presets**
|
1. [x] **Track: Saved System Prompt Presets**
|
||||||
@@ -168,6 +164,9 @@ This file tracks all major tracks for the project. Each track has its own detail
|
|||||||
|
|
||||||
### Completed / Archived
|
### Completed / Archived
|
||||||
|
|
||||||
|
- [x] **Track: External MCP Server Support** (Archived 2026-03-12)
|
||||||
|
- [x] **Track: Project-Specific Conductor Directory** (Archived 2026-03-12)
|
||||||
|
- [x] **Track: GUI Path Configuration in Context Hub** (Archived 2026-03-12)
|
||||||
- [x] **Track: True Parallel Worker Execution (The DAG Realization)**
|
- [x] **Track: True Parallel Worker Execution (The DAG Realization)**
|
||||||
- [x] **Track: Deep AST-Driven Context Pruning (RAG for Code)**
|
- [x] **Track: Deep AST-Driven Context Pruning (RAG for Code)**
|
||||||
- [x] **Track: Visual DAG & Interactive Ticket Editing**
|
- [x] **Track: Visual DAG & Interactive Ticket Editing**
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
# Implementation Plan: Custom Shader and Window Frame Support
|
# Implementation Plan: Custom Shader and Window Frame Support
|
||||||
|
|
||||||
## Phase 1: Investigation & Architecture Prototyping
|
## Phase 1: Investigation & Architecture Prototyping [checkpoint: 815ee55]
|
||||||
- [ ] Task: Investigate `imgui-bundle` and Dear PyGui capabilities for injecting raw custom shaders (OpenGL/D3D11) vs extending ImDrawList batching.
|
- [x] Task: Investigate imgui-bundle and Dear PyGui capabilities for injecting raw custom shaders (OpenGL/D3D11) vs extending ImDrawList batching. [5f4da36]
|
||||||
- [ ] Task: Investigate Python ecosystem capabilities for overloading OS window frames (e.g., `pywin32` for DWM vs ImGui borderless mode).
|
- [x] Task: Investigate Python ecosystem capabilities for overloading OS window frames (e.g., `pywin32` for DWM vs ImGui borderless mode). [5f4da36]
|
||||||
- [ ] Task: Draft architectural design document (`docs/guide_shaders_and_window.md`) detailing the chosen shader injection method and window frame overloading strategy.
|
- [x] Task: Draft architectural design document (`docs/guide_shaders_and_window.md`) detailing the chosen shader injection method and window frame overloading strategy. [5f4da36]
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Investigation & Architecture Prototyping' (Protocol in workflow.md)
|
- [x] Task: Conductor - User Manual Verification 'Phase 1: Investigation & Architecture Prototyping' (Protocol in workflow.md) [815ee55]
|
||||||
|
|
||||||
## Phase 2: Custom OS Window Frame Implementation
|
## Phase 2: Custom OS Window Frame Implementation [checkpoint: b9ca69f]
|
||||||
- [ ] Task: Write Tests: Verify the application window launches with the custom frame/borderless mode active.
|
- [x] Task: Write Tests: Verify the application window launches with the custom frame/borderless mode active. [02fca1f]
|
||||||
- [ ] Task: Implement: Integrate custom window framing logic into the main GUI loop (`src/gui_2.py` / Dear PyGui setup).
|
- [x] Task: Implement: Integrate custom window framing logic into the main GUI loop (`src/gui_2.py` / Dear PyGui setup). [59d7368]
|
||||||
- [ ] Task: Write Tests: Verify standard window controls (minimize, maximize, close, drag) function correctly with the new frame.
|
- [x] Task: Write Tests: Verify standard window controls (minimize, maximize, close, drag) function correctly with the new frame. [59d7368]
|
||||||
- [ ] Task: Implement: Add custom title bar and window controls matching the application's theme.
|
- [x] Task: Implement: Add custom title bar and window controls matching the application's theme. [59d7368]
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Custom OS Window Frame Implementation' (Protocol in workflow.md)
|
- [x] Task: Conductor - User Manual Verification 'Phase 2: Custom OS Window Frame Implementation' (Protocol in workflow.md) [b9ca69f]
|
||||||
|
|
||||||
## Phase 3: Core Shader Pipeline Integration
|
## Phase 3: Core Shader Pipeline Integration [checkpoint: 5ebce89]
|
||||||
- [ ] Task: Write Tests: Verify the shader manager class initializes without errors and can load a basic shader program.
|
- [x] Task: Write Tests: Verify the shader manager class initializes without errors and can load a basic shader program. [ac4f63b]
|
||||||
- [ ] Task: Implement: Create `src/shader_manager.py` (or extend `src/shaders.py`) to handle loading, compiling, and binding true GPU shaders or advanced Faux-Shaders.
|
- [x] Task: Implement: Create `src/shader_manager.py` (or extend `src/shaders.py`) to handle loading, compiling, and binding true GPU shaders or advanced Faux-Shaders. [ac4f63b]
|
||||||
- [ ] Task: Write Tests: Verify shader uniform data can be updated from Python dictionaries/TOML configurations.
|
- [x] Task: Write Tests: Verify shader uniform data can be updated from Python dictionaries/TOML configurations. [0938396]
|
||||||
- [ ] Task: Implement: Add support for uniform passing (time, resolution, mouse pos) to the shader pipeline.
|
- [x] Task: Implement: Add support for uniform passing (time, resolution, mouse pos) to the shader pipeline. [0938396]
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 3: Core Shader Pipeline Integration' (Protocol in workflow.md)
|
- [x] Task: Conductor - User Manual Verification 'Phase 3: Core Shader Pipeline Integration' (Protocol in workflow.md) [5ebce89]
|
||||||
|
|
||||||
## Phase 4: Specific Shader Implementations (CRT, Post-Process, Backgrounds)
|
## Phase 4: Specific Shader Implementations (CRT, Post-Process, Backgrounds) [checkpoint: 50f98de]
|
||||||
- [ ] Task: Write Tests: Verify background shader logic can render behind the main ImGui layer.
|
- [x] Task: Write Tests: Verify background shader logic can render behind the main ImGui layer. [836168a]
|
||||||
- [ ] Task: Implement: Add "Dynamic Background" shader implementation (e.g., animated noise/gradients).
|
- [x] Task: Implement: Add "Dynamic Background" shader implementation (e.g., animated noise/gradients). [836168a]
|
||||||
- [ ] Task: Write Tests: Verify post-process shader logic can capture the ImGui output and apply an effect over it.
|
- [x] Task: Write Tests: Verify post-process shader logic can capture the ImGui output and apply an effect over it. [905ac00]
|
||||||
- [ ] Task: Implement: Add "CRT / Retro" (NERV theme) and general "Post-Processing" (bloom/blur) shaders.
|
- [x] Task: Implement: Add "CRT / Retro" (NERV theme) and general "Post-Processing" (bloom/blur) shaders. [905ac00]
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Specific Shader Implementations' (Protocol in workflow.md)
|
- [x] Task: Conductor - User Manual Verification 'Phase 4: Specific Shader Implementations' (Protocol in workflow.md) [50f98de]
|
||||||
|
|
||||||
## Phase 5: Configuration and Live Editor UI
|
## Phase 5: Configuration and Live Editor UI [checkpoint: da47819]
|
||||||
- [ ] Task: Write Tests: Verify shader and window frame settings can be parsed from `config.toml`.
|
- [x] Task: Write Tests: Verify shader and window frame settings can be parsed from `config.toml`. [d69434e]
|
||||||
- [ ] Task: Implement: Update `src/theme.py` / `src/project_manager.py` to parse and apply shader/window configurations from TOML.
|
- [x] Task: Implement: Update `src/theme.py` / `src/project_manager.py` to parse and apply shader/window configurations from TOML. [d69434e]
|
||||||
- [ ] Task: Write Tests: Verify the Live UI Editor panel renders and modifying its values updates the shader uniforms.
|
- [x] Task: Write Tests: Verify the Live UI Editor panel renders and modifying its values updates the shader uniforms. [229fbe2]
|
||||||
- [ ] Task: Implement: Create a "Live UI Editor" Dear PyGui/ImGui panel to tweak shader uniforms in real-time.
|
- [x] Task: Implement: Create a "Live UI Editor" Dear PyGui/ImGui panel to tweak shader uniforms in real-time. [229fbe2]
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 5: Configuration and Live Editor UI' (Protocol in workflow.md)
|
- [x] Task: Conductor - User Manual Verification 'Phase 5: Configuration and Live Editor UI' (Protocol in workflow.md) [da47819]
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Track data_oriented_optimization_20260312 Context
|
||||||
|
|
||||||
|
- [Specification](./spec.md)
|
||||||
|
- [Implementation Plan](./plan.md)
|
||||||
|
- [Metadata](./metadata.json)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"track_id": "data_oriented_optimization_20260312",
|
||||||
|
"type": "chore",
|
||||||
|
"status": "new",
|
||||||
|
"created_at": "2026-03-12T00:00:00Z",
|
||||||
|
"updated_at": "2026-03-12T00:00:00Z",
|
||||||
|
"description": "Optimization pass. I want to update the product guidlines to take into account with data-oriented appraoch the more performant way to semantically define procedrual code in python so executes almost entirely heavy operations optimally. I know there is a philosophy of 'the less python does the better' which is problably why the imgui lib is so performant because all python really does is define the ui's DAG via an imgui interface procedurally along with what state the dag may modify within its constraints of interactions the user may do. This problably can be reflected in the way the rest of the codebase is done. I want to go over the ./src and ./simulation to make sure this insight and related herustics are properly enfroced. Worst case I want to identify what code I should consider lower down to C maybe and making python bindings to if there is a significant bottleneck identified via profiling and testing that cannot be resolved otherwise."
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Implementation Plan: Data-Oriented Python Optimization Pass
|
||||||
|
|
||||||
|
## Phase 1: Guidelines and Instrumentation
|
||||||
|
- [ ] Task: Update `conductor/product-guidelines.md` with Data-Oriented Python heuristics and the "less Python does the better" philosophy.
|
||||||
|
- [ ] Task: Review existing profiling instrumentation in `src/performance_monitor.py` or diagnostic hooks.
|
||||||
|
- [ ] Task: Expand profiling instrumentation to capture more detailed execution times for non-GUI data structures/processes if necessary.
|
||||||
|
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Guidelines and Instrumentation' (Protocol in workflow.md)
|
||||||
|
|
||||||
|
## Phase 2: Audit and Profiling (`src/` and `simulation/`)
|
||||||
|
- [ ] Task: Run profiling scenarios (especially utilizing simulations) to generate baseline metrics.
|
||||||
|
- [ ] Task: Audit `src/` (e.g., `dag_engine.py`, `multi_agent_conductor.py`, `aggregate.py`) against the new guidelines, cross-referencing with profiling data to identify bottlenecks.
|
||||||
|
- [ ] Task: Audit `simulation/` files against the new guidelines to ensure the test harness is performant and non-blocking.
|
||||||
|
- [ ] Task: Compile a list of identified bottleneck targets to refactor.
|
||||||
|
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Audit and Profiling (`src/` and `simulation/`)' (Protocol in workflow.md)
|
||||||
|
|
||||||
|
## Phase 3: Targeted Optimization and Refactoring
|
||||||
|
- [ ] Task: Write/update tests for the first identified bottleneck to establish a performance or structural baseline (Red Phase).
|
||||||
|
- [ ] Task: Refactor the first identified bottleneck to align with data-oriented guidelines (Green Phase).
|
||||||
|
- [ ] Task: Write/update tests for remaining identified bottlenecks.
|
||||||
|
- [ ] Task: Refactor remaining identified bottlenecks.
|
||||||
|
- [ ] Task: Conductor - User Manual Verification 'Phase 3: Targeted Optimization and Refactoring' (Protocol in workflow.md)
|
||||||
|
|
||||||
|
## Phase 4: Final Evaluation and Documentation
|
||||||
|
- [ ] Task: Re-run all profiling scenarios to compare against the baseline metrics.
|
||||||
|
- [ ] Task: Analyze remaining bottlenecks that did not reach performance thresholds and document them as candidates for C/C++ bindings (Last Resort).
|
||||||
|
- [ ] Task: Generate a final summary report of the optimizations applied and the C extension evaluation.
|
||||||
|
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Final Evaluation and Documentation' (Protocol in workflow.md)
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Specification: Data-Oriented Python Optimization Pass
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Perform an optimization pass and audit across the codebase (`./src` and `./simulation`), aligning the implementation with the Data-Oriented Design philosophy and the "less Python does the better" heuristic. Update the `product-guidelines.md` to formally document this approach for procedural Python code.
|
||||||
|
|
||||||
|
## Functional Requirements
|
||||||
|
1. **Update Product Guidelines:**
|
||||||
|
- Formalize the heuristic that Python should act primarily as a procedural semantic definer (similar to how ImGui defines a UI DAG), delegating heavy lifting.
|
||||||
|
- Enforce data-oriented guidelines for Python code structure, focusing on minimizing Python JIT overhead.
|
||||||
|
2. **Codebase Audit (`./src` and `./simulation`):**
|
||||||
|
- Review global `src/` files and simulation logic against the new guidelines.
|
||||||
|
- Identify bottlenecks that violate these heuristics (e.g., heavy procedural state manipulation in Python).
|
||||||
|
3. **Profiling & Instrumentation Expansion:**
|
||||||
|
- Expand existing profiling instrumentation (e.g., `performance_monitor.py` or diagnostic hooks) if currently insufficient for identifying real structural bottlenecks.
|
||||||
|
4. **Optimization Execution:**
|
||||||
|
- Refactor identified bottlenecks to align with the new data-oriented Python heuristics.
|
||||||
|
- Re-evaluate performance post-refactor.
|
||||||
|
5. **C Extension Evaluation (Last Resort):**
|
||||||
|
- If Python optimizations fail to meet performance thresholds, specifically identify and document routines that must be lowered to C/C++ with Python bindings. Only proceed with bindings if absolutely necessary.
|
||||||
|
|
||||||
|
## Non-Functional Requirements
|
||||||
|
- Maintain existing test coverage and strict type-hinting requirements.
|
||||||
|
- Ensure 1-space indentation and ultra-compact style rules are not violated during refactoring.
|
||||||
|
- Ensure the main GUI rendering thread is never blocked.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
- `product-guidelines.md` is updated with data-oriented procedural Python guidelines.
|
||||||
|
- `src/` and `simulation/` undergo a documented profiling audit.
|
||||||
|
- Identified bottlenecks are refactored to reduce Python overhead.
|
||||||
|
- No regressions in automated simulation or unit tests.
|
||||||
|
- A final report is provided detailing optimizations made and any candidates for future C extension porting.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
- Actually implementing C/C++ bindings in this track (this track only identifies/evaluates them as a last resort; if needed, they get a separate track).
|
||||||
|
- Major UI visual theme changes.
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# Implementation Plan: External MCP Server Support
|
|
||||||
|
|
||||||
## Phase 1: Configuration & Data Modeling
|
|
||||||
- [x] Task: Define the schema for external MCP server configuration. [1c863f0]
|
|
||||||
- [x] Update `src/models.py` to include `MCPServerConfig` and `MCPConfiguration` classes.
|
|
||||||
- [x] Implement logic to load `mcp_config.json` from global and project-specific paths.
|
|
||||||
- [x] Task: Integrate configuration loading into `AppController`. [c09e0f5]
|
|
||||||
- [x] Ensure the MCP config path is correctly resolved from `config.toml` and `manual_slop.toml`.
|
|
||||||
- [x] Task: Write unit tests for configuration loading and validation. [c09e0f5]
|
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Configuration & Data Modeling' (Protocol in workflow.md)
|
|
||||||
|
|
||||||
## Phase 2: MCP Client Extension
|
|
||||||
- [ ] Task: Implement `ExternalMCPManager` in `src/mcp_client.py`.
|
|
||||||
- [ ] Add support for managing multiple MCP server sessions.
|
|
||||||
- [ ] Implement the `StdioMCPClient` for local subprocess communication.
|
|
||||||
- [ ] Implement the `RemoteMCPClient` for SSE/WebSocket communication.
|
|
||||||
- [ ] Task: Update Tool Discovery.
|
|
||||||
- [ ] Implement `list_external_tools()` to aggregate tools from all active external servers.
|
|
||||||
- [ ] Task: Update Tool Dispatch.
|
|
||||||
- [ ] Modify `mcp_client.dispatch()` and `mcp_client.async_dispatch()` to route tool calls to either native tools or the appropriate external server.
|
|
||||||
- [ ] Task: Write integration tests for stdio and remote MCP client communication (using mock servers).
|
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 2: MCP Client Extension' (Protocol in workflow.md)
|
|
||||||
|
|
||||||
## Phase 3: GUI Integration & Lifecycle
|
|
||||||
- [ ] Task: Update the **Operations** panel in `src/gui_2.py`.
|
|
||||||
- [ ] Create a new "External Tools" section.
|
|
||||||
- [ ] List discovered tools from active external servers.
|
|
||||||
- [ ] Add a "Refresh External MCPs" button to reload configuration and rediscover tools.
|
|
||||||
- [ ] Task: Implement Lifecycle Management.
|
|
||||||
- [ ] Add the "Auto-start on Project Load" logic to start servers when a project is initialized.
|
|
||||||
- [ ] Add status indicators (e.g., color-coded dots) for each external server in the GUI.
|
|
||||||
- [ ] Task: Write visual regression tests or simulation scripts to verify the updated Operations panel.
|
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 3: GUI Integration & Lifecycle' (Protocol in workflow.md)
|
|
||||||
|
|
||||||
## Phase 4: Agent Integration & HITL
|
|
||||||
- [ ] Task: Update AI tool declarations.
|
|
||||||
- [ ] Ensure `ai_client.py` includes external tools in the tool definitions sent to Gemini/Anthropic.
|
|
||||||
- [ ] Task: Verify HITL Approval Flow.
|
|
||||||
- [ ] Ensure that calling an external tool correctly triggers the `ConfirmDialog` modal.
|
|
||||||
- [ ] Verify that approved external tool results are correctly returned to the AI.
|
|
||||||
- [ ] Task: Perform a final end-to-end verification with a real external MCP server.
|
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Agent Integration & HITL' (Protocol in workflow.md)
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Track frosted_glass_20260313 Context
|
||||||
|
|
||||||
|
- [Specification](./spec.md)
|
||||||
|
- [Implementation Plan](./plan.md)
|
||||||
|
- [Metadata](./metadata.json)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"track_id": "frosted_glass_20260313",
|
||||||
|
"type": "feature",
|
||||||
|
"status": "new",
|
||||||
|
"created_at": "2026-03-13T14:39:00Z",
|
||||||
|
"updated_at": "2026-03-13T14:39:00Z",
|
||||||
|
"description": "Add 'frosted glass' bg for transparency on panels and popups. This blurring effect will allow drop downs and other elements of these panels to not get hard to discern from background text or elements behind the panel."
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Implementation Plan: Frosted Glass Background Effect
|
||||||
|
|
||||||
|
## Phase 1: Shader Development & Integration [checkpoint: 55f3bd8]
|
||||||
|
- [x] Task: Audit `src/shader_manager.py` to identify existing background/post-process integration points. [1328bc1]
|
||||||
|
- [x] Task: Write Tests: Verify `ShaderManager` can compile and bind a multi-pass blur shader. [1328bc1]
|
||||||
|
- [x] Task: Implement: Add `FrostedGlassShader` (GLSL) to `src/shader_manager.py`. [1328bc1]
|
||||||
|
- [x] Task: Implement: Integrate the blur shader into the `ShaderManager` lifecycle. [1328bc1]
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Phase 1: Shader Development & Integration' (Protocol in workflow.md) [55f3bd8]
|
||||||
|
|
||||||
|
## Phase 2: Framebuffer Capture Pipeline [checkpoint: e9b7875]
|
||||||
|
- [x] Task: Write Tests: Verify the FBO capture mechanism correctly samples the back buffer and stores it in a texture. [f297e7a]
|
||||||
|
- [x] Task: Implement: Update `src/shader_manager.py` or `src/gui_2.py` to handle "pre-rendering" of the background into a texture for blurring. [f297e7a]
|
||||||
|
- [x] Task: Implement: Ensure the blurred texture is updated every frame or on window move events. [f297e7a]
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Phase 2: Framebuffer Capture Pipeline' (Protocol in workflow.md) [e9b7875]
|
||||||
|
|
||||||
|
## Phase 3: GUI Integration & Rendering [checkpoint: cecbe22]
|
||||||
|
- [x] Task: Write Tests: Verify that a mocked ImGui window successfully calls the frosted glass rendering logic. [cecbe22]
|
||||||
|
- [x] Task: Implement: Create a `_render_frosted_background(self, pos, size)` helper in `src/gui_2.py`. [cecbe22]
|
||||||
|
- [x] Task: Implement: Update panel rendering loops (e.g. `_gui_func`) to inject the frosted background before calling `imgui.begin()` for major panels. [cecbe22]
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Phase 3: GUI Integration & Rendering' (Protocol in workflow.md) [cecbe22]
|
||||||
|
|
||||||
|
## Phase 4: UI Controls & Configuration [checkpoint: cecbe22]
|
||||||
|
- [x] Task: Write Tests: Verify that modifying blur uniforms via the Live Editor updates the shader state. [cecbe22]
|
||||||
|
- [x] Task: Implement: Add "Frosted Glass" sliders (Blur, Tint, Opacity) to the **Shader Editor** in `src/gui_2.py`. [cecbe22]
|
||||||
|
- [x] Task: Implement: Update `src/theme.py` to parse and store frosted glass settings from `config.toml`. [cecbe22]
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Phase 4: UI Controls & Configuration' (Protocol in workflow.md) [cecbe22]
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Specification: Frosted Glass Background Effect
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Implement a high-fidelity "frosted glass" (acrylic) background effect for all GUI panels and popups within the Manual Slop interface. This effect will use a GPU-resident shader to blur the content behind active windows, improving readability and visual depth while preventing background text from clashing with foreground UI elements.
|
||||||
|
|
||||||
|
## Functional Requirements
|
||||||
|
- **GPU-Accelerated Blur:**
|
||||||
|
- Implement a GLSL fragment shader (e.g., Gaussian or Kawase blur) within the existing `ShaderManager` pipeline.
|
||||||
|
- The shader must sample the current frame buffer background and render a blurred version behind the active window's background.
|
||||||
|
- **Global Integration:**
|
||||||
|
- The effect must automatically apply to all standard ImGui panels and popups.
|
||||||
|
- Integrate with `imgui.begin()` and `imgui.begin_popup()` (or via a reusable wrapper helper).
|
||||||
|
- **Real-Time Tuning:**
|
||||||
|
- Add controls to the **Live Shader Editor** to adjust the following parameters:
|
||||||
|
- **Blur Radius:** Control the intensity of the Gaussian blur.
|
||||||
|
- **Tint Intensity:** Control the strength of the "frost" overlay color.
|
||||||
|
- **Base Opacity:** Control the overall transparency of the frosted layer.
|
||||||
|
- **Persistence:**
|
||||||
|
- Save frosted glass parameters to `config.toml` under the `theme` or `shader` section.
|
||||||
|
|
||||||
|
## Technical Implementation
|
||||||
|
- **Shader Pipeline:** Use `PyOpenGL` to manage a dedicated background texture/FBO for sampling.
|
||||||
|
- **Coordinate Mapping:** Ensure the blur shader correctly maps screen coordinates to the region behind the current ImGui window.
|
||||||
|
- **State Integration:** Store tuning parameters in `App.shader_uniforms` and ensure they are updated every frame.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
- [ ] Panels and popups have a distinct, blurred background that clearly separates them from the content behind them.
|
||||||
|
- [ ] Changing the "Blur Radius" slider in the Shader Editor immediately updates the visual frostiness.
|
||||||
|
- [ ] The effect remains stable during window dragging and resizing.
|
||||||
|
- [ ] No significant performance degradation (maintaining target FPS).
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
- Implementing different blur types (e.g., motion blur, radial blur).
|
||||||
|
- Per-panel unique blur settings (initially global only).
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Track text_viewer_rich_rendering_20260313 Context
|
||||||
|
|
||||||
|
- [Specification](./spec.md)
|
||||||
|
- [Implementation Plan](./plan.md)
|
||||||
|
- [Metadata](./metadata.json)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"track_id": "text_viewer_rich_rendering_20260313",
|
||||||
|
"type": "feature",
|
||||||
|
"status": "new",
|
||||||
|
"created_at": "2026-03-13T14:22:00Z",
|
||||||
|
"updated_at": "2026-03-13T14:22:00Z",
|
||||||
|
"description": "Make the text viewer support syntax highlighting and markdown for different text types. Whatever feeds the text viewer new context must specify the type to use otherwise fallback to just regular text visualization without highlighting or markdown rendering."
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Implementation Plan: Advanced Text Viewer with Syntax Highlighting
|
||||||
|
|
||||||
|
## Phase 1: State & Interface Update
|
||||||
|
- [ ] Task: Audit `src/gui_2.py` to ensure all `text_viewer_*` state variables are explicitly initialized in `App.__init__`.
|
||||||
|
- [ ] Task: Implement: Update `App.__init__` to initialize `self.show_text_viewer`, `self.text_viewer_title`, `self.text_viewer_content`, and new `self.text_viewer_type` (defaulting to "text").
|
||||||
|
- [ ] Task: Implement: Update `self.text_viewer_wrap` (defaulting to True) to allow independent word wrap.
|
||||||
|
- [ ] Task: Implement: Update `_render_text_viewer(self, label: str, content: str, text_type: str = "text")` signature and caller usage.
|
||||||
|
- [ ] Task: Conductor - User Manual Verification 'Phase 1: State & Interface Update' (Protocol in workflow.md)
|
||||||
|
|
||||||
|
## Phase 2: Core Rendering Logic (Code & MD)
|
||||||
|
- [ ] Task: Write Tests: Create a simulation test in `tests/test_gui_text_viewer.py` to verify the viewer opens and switches rendering paths based on `text_type`.
|
||||||
|
- [ ] Task: Implement: In `src/gui_2.py`, refactor the text viewer window loop to:
|
||||||
|
- Use `MarkdownRenderer.render` if `text_type == "markdown"`.
|
||||||
|
- Use a cached `ImGuiColorTextEdit.TextEditor` if `text_type` matches a code language.
|
||||||
|
- Fallback to `imgui.input_text_multiline` for plain text.
|
||||||
|
- [ ] Task: Implement: Ensure the `TextEditor` instance is properly cached using a unique key for the text viewer to maintain state.
|
||||||
|
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Core Rendering Logic' (Protocol in workflow.md)
|
||||||
|
|
||||||
|
## Phase 3: UI Features (Copy, Line Numbers, Wrap)
|
||||||
|
- [ ] Task: Write Tests: Update `tests/test_gui_text_viewer.py` to verify the copy-to-clipboard functionality and word wrap toggle.
|
||||||
|
- [ ] Task: Implement: Add a "Copy" button to the text viewer title bar or a small toolbar at the top of the window.
|
||||||
|
- [ ] Task: Implement: Add a "Word Wrap" checkbox inside the text viewer window.
|
||||||
|
- [ ] Task: Implement: Configure the `TextEditor` instance to show line numbers and be read-only.
|
||||||
|
- [ ] Task: Conductor - User Manual Verification 'Phase 3: UI Features' (Protocol in workflow.md)
|
||||||
|
|
||||||
|
## Phase 4: Integration & Rollout
|
||||||
|
- [ ] Task: Implement: Update all existing calls to `_render_text_viewer` in `src/gui_2.py` (e.g., in `_render_files_panel`, `_render_tool_calls_panel`) to pass the correct `text_type` based on file extension or content.
|
||||||
|
- [ ] Task: Implement: Add "Markdown Preview" support for system prompt presets using the new text viewer logic.
|
||||||
|
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Integration & Rollout' (Protocol in workflow.md)
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Specification: Advanced Text Viewer with Syntax Highlighting
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Enhance the existing "Text Viewer" popup panel in the Manual Slop GUI to support rich rendering, including syntax highlighting for various code types and Markdown rendering. The viewer will transition from a basic text/multiline input to a specialized component leveraging the project's hybrid rendering pattern.
|
||||||
|
|
||||||
|
## Functional Requirements
|
||||||
|
- **Rich Rendering Support:**
|
||||||
|
- **Code:** Integration with `ImGuiColorTextEdit` for syntax highlighting (Python, PowerShell, JSON, TOML, etc.).
|
||||||
|
- **Markdown:** Integration with `imgui_markdown` for rendering formatted text and documents.
|
||||||
|
- **Fallback:** Plain text rendering for unknown or unspecified types.
|
||||||
|
- **Explicit Type Specification:**
|
||||||
|
- The component/function triggering the viewer (e.g., `_render_text_viewer`) must provide an explicit `text_type` parameter (e.g., "python", "markdown", "text").
|
||||||
|
- **Enhanced UI Features:**
|
||||||
|
- **Line Numbers:** Display line numbers in the gutter when viewing code.
|
||||||
|
- **Copy Button:** A dedicated button to copy the entire content to the clipboard.
|
||||||
|
- **Independent Word Wrap:** A toggle within the viewer window to enable/disable word wrapping specifically for that instance, overriding the global GUI setting if necessary.
|
||||||
|
- **Persistent Sizing:** The viewer should maintain its size/position via ImGui's standard `.ini` persistence.
|
||||||
|
|
||||||
|
## Technical Implementation
|
||||||
|
- Update `App` state in `src/gui_2.py` to store `text_viewer_type`.
|
||||||
|
- Modify `_render_text_viewer` signature to accept `text_type`.
|
||||||
|
- Update the rendering loop in `_gui_func` to switch between `MarkdownRenderer` logic and `TextEditor` logic based on `text_viewer_type`.
|
||||||
|
- Ensure proper caching of `TextEditor` instances to maintain scroll position and selection state while the viewer is open.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
- [ ] Clicking a preview button for a Python file opens the viewer with syntax highlighting and line numbers.
|
||||||
|
- [ ] Clicking a preview for a `.md` file renders it as formatted Markdown.
|
||||||
|
- [ ] The "Copy" button correctly copies text to the OS clipboard.
|
||||||
|
- [ ] The word wrap toggle works immediately without affecting other panels.
|
||||||
|
- [ ] Unsupported types gracefully fall back to standard plain text.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Track thinking_trace_handling_20260313 Context
|
||||||
|
|
||||||
|
- [Specification](./spec.md)
|
||||||
|
- [Implementation Plan](./plan.md)
|
||||||
|
- [Metadata](./metadata.json)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"track_id": "thinking_trace_handling_20260313",
|
||||||
|
"type": "feature",
|
||||||
|
"status": "new",
|
||||||
|
"created_at": "2026-03-13T13:28:00Z",
|
||||||
|
"updated_at": "2026-03-13T13:28:00Z",
|
||||||
|
"description": "Properly section and handle 'agent thinking' responses from the ai. Right now we just have <thinking> indicators not sure if thats a bodge or if there is a richer way we could be handling this..."
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Implementation Plan: Rich Thinking Trace Handling
|
||||||
|
|
||||||
|
## Phase 1: Core Parsing & Model Update
|
||||||
|
- [ ] Task: Audit `src/models.py` and `src/project_manager.py` to identify current message serialization schemas.
|
||||||
|
- [ ] Task: Write Tests: Verify that raw AI responses with `<thinking>`, `<thought>`, and `Thinking:` markers are correctly parsed into segmented data structures (Thinking vs. Response).
|
||||||
|
- [ ] Task: Implement: Add `ThinkingSegment` model and update `ChatMessage` schema in `src/models.py` to support optional thinking traces.
|
||||||
|
- [ ] Task: Implement: Update parsing logic in `src/ai_client.py` or a dedicated utility to extract segments from raw provider responses.
|
||||||
|
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Core Parsing & Model Update' (Protocol in workflow.md)
|
||||||
|
|
||||||
|
## Phase 2: Persistence & History Integration
|
||||||
|
- [ ] Task: Write Tests: Verify that `ProjectManager` correctly serializes and deserializes messages with thinking segments to/from TOML history files.
|
||||||
|
- [ ] Task: Implement: Update `src/project_manager.py` to handle the new `ChatMessage` schema during session save/load.
|
||||||
|
- [ ] Task: Implement: Ensure `src/aggregate.py` or relevant context builders include thinking traces in the "Discussion History" sent back to the AI.
|
||||||
|
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Persistence & History Integration' (Protocol in workflow.md)
|
||||||
|
|
||||||
|
## Phase 3: GUI Rendering - Comms & Discussion
|
||||||
|
- [ ] Task: Write Tests: Verify the GUI rendering logic correctly handles messages with and without thinking segments.
|
||||||
|
- [ ] Task: Implement: Create a reusable `_render_thinking_trace` helper in `src/gui_2.py` using a collapsible header (e.g., `imgui.collapsing_header`).
|
||||||
|
- [ ] Task: Implement: Integrate the thinking trace renderer into the **Comms History** panel in `src/gui_2.py`.
|
||||||
|
- [ ] Task: Implement: Integrate the thinking trace renderer into the **Discussion Hub** message loop in `src/gui_2.py`.
|
||||||
|
- [ ] Task: Conductor - User Manual Verification 'Phase 3: GUI Rendering - Comms & Discussion' (Protocol in workflow.md)
|
||||||
|
|
||||||
|
## Phase 4: Final Polish & Theming
|
||||||
|
- [ ] Task: Implement: Apply specialized styling (e.g., tinted background or italicized text) to expanded thinking traces to distinguish them from direct responses.
|
||||||
|
- [ ] Task: Implement: Ensure thinking trace headers show a "Calculating..." or "Monologue" indicator while an agent is active.
|
||||||
|
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Final Polish & Theming' (Protocol in workflow.md)
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Specification: Rich Thinking Trace Handling
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Implement a formal system for parsing, storing, and rendering "agent thinking" monologues (chains of thought) within the Manual Slop GUI. Currently, thinking traces are treated as raw text or simple markers. This track will introduce a structured UI pattern to separate internal monologue from direct user responses while preserving both for future context.
|
||||||
|
|
||||||
|
## Functional Requirements
|
||||||
|
- **Multi-Format Parsing:** Support extraction of thinking traces from `<thinking>...</thinking>`, `<thought>...</thought>`, and blocks prefixed with `Thinking:`.
|
||||||
|
- **Integrated UI Rendering:**
|
||||||
|
- In the **Comms History** and **Discussion Hub**, thinking traces must be rendered in a distinct, collapsible section.
|
||||||
|
- The section should be **Collapsed by Default** to minimize visual noise.
|
||||||
|
- Thinking traces must be visually separated from the "visible" response (e.g., using a tinted background, border, or specialized header).
|
||||||
|
- **Persistent State Management:**
|
||||||
|
- Both the thinking monologue and the final response must be saved to the permanent discussion history (`manual_slop_history.toml` or `project_history.toml`).
|
||||||
|
- History entries must be properly tagged/schematized to distinguish between thinking and output.
|
||||||
|
- **Context Recurrence:**
|
||||||
|
- Thinking traces must be included in subsequent AI turns (Full Recurrence) to maintain the model's internal state and logical progression.
|
||||||
|
|
||||||
|
## Non-Functional Requirements
|
||||||
|
- **Performance:** Parsing and rendering of thinking blocks must not introduce visible latency in the GUI thread.
|
||||||
|
- **Accessibility:** All thinking blocks must remain selectable and copyable via the standard high-fidelity selectable UI pattern.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
- [ ] AI responses containing `<thinking>` or similar tags are automatically parsed into separate segments.
|
||||||
|
- [ ] A "Thinking..." header appears in the Discussion Hub for messages with monologues.
|
||||||
|
- [ ] Clicking the header expands the full thinking trace.
|
||||||
|
- [ ] Saving/Loading a project preserves the distinction between thinking and response.
|
||||||
|
- [ ] Subsequent AI calls receive the thinking trace as part of the conversation history.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
- Implementing "Hidden Thinking" (where the user cannot see it but the AI can).
|
||||||
|
- Real-time "Streaming" of thinking into the UI (unless already supported by the active provider).
|
||||||
+28
-16
@@ -1,12 +1,12 @@
|
|||||||
[ai]
|
[ai]
|
||||||
provider = "gemini_cli"
|
provider = "minimax"
|
||||||
model = "gemini-2.5-flash-lite"
|
model = "MiniMax-M2.5"
|
||||||
temperature = 0.85
|
temperature = 0.0
|
||||||
top_p = 1.0
|
top_p = 1.0
|
||||||
max_tokens = 1024
|
max_tokens = 32000
|
||||||
history_trunc_limit = 900000
|
history_trunc_limit = 900000
|
||||||
active_preset = ""
|
active_preset = "Default"
|
||||||
system_prompt = "Overridden Prompt"
|
system_prompt = ""
|
||||||
|
|
||||||
[projects]
|
[projects]
|
||||||
paths = [
|
paths = [
|
||||||
@@ -17,27 +17,28 @@ paths = [
|
|||||||
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveexecutionsim.toml",
|
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveexecutionsim.toml",
|
||||||
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_project.toml",
|
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_project.toml",
|
||||||
]
|
]
|
||||||
active = "C:\\projects\\manual_slop\\tests\\artifacts\\live_gui_workspace\\manual_slop.toml"
|
active = "C:/projects/gencpp/gencpp_sloppy.toml"
|
||||||
|
|
||||||
[gui]
|
[gui]
|
||||||
separate_message_panel = false
|
separate_message_panel = false
|
||||||
separate_response_panel = false
|
separate_response_panel = false
|
||||||
separate_tool_calls_panel = false
|
separate_tool_calls_panel = false
|
||||||
bg_shader_enabled = true
|
bg_shader_enabled = false
|
||||||
crt_filter_enabled = false
|
crt_filter_enabled = false
|
||||||
separate_task_dag = false
|
separate_task_dag = false
|
||||||
separate_usage_analytics = false
|
separate_usage_analytics = true
|
||||||
separate_tier1 = false
|
separate_tier1 = false
|
||||||
separate_tier2 = false
|
separate_tier2 = false
|
||||||
separate_tier3 = false
|
separate_tier3 = false
|
||||||
separate_tier4 = false
|
separate_tier4 = false
|
||||||
|
separate_external_tools = true
|
||||||
|
|
||||||
[gui.show_windows]
|
[gui.show_windows]
|
||||||
"Context Hub" = true
|
"Context Hub" = true
|
||||||
"Files & Media" = true
|
"Files & Media" = true
|
||||||
"AI Settings" = true
|
"AI Settings" = true
|
||||||
"MMA Dashboard" = true
|
"MMA Dashboard" = true
|
||||||
"Task DAG" = true
|
"Task DAG" = false
|
||||||
"Usage Analytics" = true
|
"Usage Analytics" = true
|
||||||
"Tier 1" = false
|
"Tier 1" = false
|
||||||
"Tier 2" = false
|
"Tier 2" = false
|
||||||
@@ -50,22 +51,33 @@ separate_tier4 = false
|
|||||||
"Discussion Hub" = true
|
"Discussion Hub" = true
|
||||||
"Operations Hub" = true
|
"Operations Hub" = true
|
||||||
Message = false
|
Message = false
|
||||||
Response = false
|
Response = true
|
||||||
"Tool Calls" = false
|
"Tool Calls" = true
|
||||||
Theme = true
|
Theme = true
|
||||||
"Log Management" = true
|
"Log Management" = true
|
||||||
Diagnostics = false
|
Diagnostics = false
|
||||||
|
"External Tools" = false
|
||||||
|
"Shader Editor" = true
|
||||||
|
|
||||||
[theme]
|
[theme]
|
||||||
palette = "Nord Dark"
|
palette = "Nord Dark"
|
||||||
font_path = "C:/projects/manual_slop/assets/fonts/Inter-Regular.ttf"
|
font_path = "C:/projects/manual_slop/assets/fonts/MapleMono-Regular.ttf"
|
||||||
font_size = 14.0
|
font_size = 16.0
|
||||||
scale = 1.0
|
scale = 1.0
|
||||||
transparency = 1.0
|
transparency = 0.699999988079071
|
||||||
child_transparency = 1.0
|
child_transparency = 0.6899999976158142
|
||||||
|
frosted_blur_radius = 29.68400001525879
|
||||||
|
frosted_tint_intensity = 0.5659999847412109
|
||||||
|
frosted_opacity = 0.5389999747276306
|
||||||
|
frosted_glass_enabled = true
|
||||||
|
|
||||||
[mma]
|
[mma]
|
||||||
max_workers = 4
|
max_workers = 4
|
||||||
|
|
||||||
[headless]
|
[headless]
|
||||||
api_key = "test-secret-key"
|
api_key = "test-secret-key"
|
||||||
|
|
||||||
|
[paths]
|
||||||
|
conductor_dir = "C:\\projects\\gencpp\\.ai\\conductor"
|
||||||
|
logs_dir = "C:\\projects\\manual_slop\\logs"
|
||||||
|
scripts_dir = "C:\\projects\\manual_slop\\scripts"
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Custom Shaders and Window Frame Architecture
|
||||||
|
|
||||||
|
## 1. Shader Injection Strategy
|
||||||
|
|
||||||
|
### Evaluation
|
||||||
|
* **Dear PyGui (Legacy):** Does not natively support raw GLSL/HLSL shader injection into the UI layer. It relies heavily on fixed-function vertex/fragment shaders compiled into the C++ core. Faux-shaders via DrawList are the only viable path without modifying the DPG source.
|
||||||
|
* **imgui-bundle (Current):** `imgui-bundle` utilizes `hello_imgui` as its application runner, which provides robust lifecycle callbacks (e.g., `callbacks.custom_background`, `callbacks.post_init`). Because `hello_imgui` exposes the underlying OpenGL context, we can use `PyOpenGL` alongside it to execute raw GLSL shaders.
|
||||||
|
|
||||||
|
### Chosen Approach: Hybrid Faux-Shader & PyOpenGL FBO
|
||||||
|
Given the Python environment, we will adopt a hybrid approach:
|
||||||
|
1. **Faux-Shaders (ImDrawList Batching):** Continue using `imgui.ImDrawList` primitives for simple effects like soft shadows, glows, and basic gradients (as seen in `src/shaders.py`). This is highly performant for UI elements and requires no external dependencies.
|
||||||
|
2. **True GPU Shaders (PyOpenGL + FBO):** For complex post-processing (CRT curvature, bloom, dynamic noise backgrounds), we will integrate `PyOpenGL`.
|
||||||
|
* We will compile GLSL shaders during `post_init`.
|
||||||
|
* We will render the effect into a Framebuffer Object (FBO).
|
||||||
|
* We will display the resulting texture ID using `imgui.image()` or inject it into the `custom_background` callback.
|
||||||
|
|
||||||
|
*Note: This approach introduces `PyOpenGL` as a dependency, which is standard for advanced Python graphics.*
|
||||||
|
|
||||||
|
## 2. Custom Window Frame Strategy
|
||||||
|
|
||||||
|
### Evaluation
|
||||||
|
* **Native DWM Overloading (PyWin32):** It is possible to use `pywin32` to subclass the application window, intercept `WM_NCHITTEST`, and return `HTCAPTION` for a custom ImGui-drawn title bar region. This preserves Windows snap layouts and native drop shadows. However, it is strictly Windows-only and can conflict with GLFW/SDL2 event loops used by `hello_imgui`.
|
||||||
|
* **Borderless Window Mode (ImGui/GLFW):** `hello_imgui` allows configuring the main window as borderless/undecorated (`runner_params.app_window_params.borderless = True`). We must then manually draw the title bar, minimize/maximize/close buttons, and handle window dragging by updating the OS window position based on ImGui mouse drag deltas.
|
||||||
|
|
||||||
|
### Chosen Approach: Pure ImGui Borderless Implementation
|
||||||
|
To ensure cross-platform compatibility and avoid brittle Win32 hook collisions with `hello_imgui`, we will use the **Borderless Window Mode** approach.
|
||||||
|
1. **Initialization:** Configure `hello_imgui.RunnerParams` to disable OS window decorations.
|
||||||
|
2. **Title Bar Rendering:** Dedicate the top ~30 pixels of the ImGui workspace to a custom title bar that matches the current theme (e.g., NERV or standard).
|
||||||
|
3. **Window Controls:** Implement custom ImGui buttons for `_`, `[]`, and `X`, which will call native window management functions exposed by `hello_imgui` or `glfw`.
|
||||||
|
4. **Drag Handling:** Detect `imgui.is_mouse_dragging()` on the title bar region and dynamically adjust the application window position.
|
||||||
|
|
||||||
|
## 3. Integration with Event Metrics
|
||||||
|
Both the shader uniforms (time, resolution) and window control events will be hooked into the existing `dag_engine` and `events` systems to ensure minimal performance overhead and centralized configuration via `config.toml`.
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
;;; !!! This configuration is handled by HelloImGui and stores several Ini Files, separated by markers like this:
|
||||||
|
;;;<<<INI_NAME>>>;;;
|
||||||
|
|
||||||
|
;;;<<<ImGui_655921752_Default>>>;;;
|
||||||
|
[Window][Debug##Default]
|
||||||
|
Pos=60,60
|
||||||
|
Size=400,400
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Docking][Data]
|
||||||
|
|
||||||
|
;;;<<<Layout_655921752_Default>>>;;;
|
||||||
|
;;;<<<HelloImGui_Misc>>>;;;
|
||||||
|
[Layout]
|
||||||
|
Name=Default
|
||||||
|
[StatusBar]
|
||||||
|
Show=false
|
||||||
|
ShowFps=true
|
||||||
|
[Theme]
|
||||||
|
Name=DarculaDarker
|
||||||
|
;;;<<<SplitIds>>>;;;
|
||||||
|
{"gImGuiSplitIDs":{}}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
;;; !!! This configuration is handled by HelloImGui and stores several Ini Files, separated by markers like this:
|
||||||
|
;;;<<<INI_NAME>>>;;;
|
||||||
|
|
||||||
|
;;;<<<ImGui_655921752_Default>>>;;;
|
||||||
|
[Window][Debug##Default]
|
||||||
|
Pos=60,60
|
||||||
|
Size=400,400
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Docking][Data]
|
||||||
|
|
||||||
|
;;;<<<Layout_655921752_Default>>>;;;
|
||||||
|
;;;<<<HelloImGui_Misc>>>;;;
|
||||||
|
[Layout]
|
||||||
|
Name=Default
|
||||||
|
[StatusBar]
|
||||||
|
Show=false
|
||||||
|
ShowFps=true
|
||||||
|
[Theme]
|
||||||
|
Name=DarculaDarker
|
||||||
|
;;;<<<SplitIds>>>;;;
|
||||||
|
{"gImGuiSplitIDs":{}}
|
||||||
+121
-90
@@ -12,7 +12,7 @@ ViewportPos=43,95
|
|||||||
ViewportId=0x78C57832
|
ViewportId=0x78C57832
|
||||||
Size=897,649
|
Size=897,649
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000001,0
|
DockId=0x00000005,0
|
||||||
|
|
||||||
[Window][Files]
|
[Window][Files]
|
||||||
ViewportPos=3125,170
|
ViewportPos=3125,170
|
||||||
@@ -33,7 +33,7 @@ DockId=0x0000000A,0
|
|||||||
Pos=0,17
|
Pos=0,17
|
||||||
Size=1680,730
|
Size=1680,730
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000001,0
|
DockId=0x00000005,0
|
||||||
|
|
||||||
[Window][Provider]
|
[Window][Provider]
|
||||||
ViewportPos=43,95
|
ViewportPos=43,95
|
||||||
@@ -41,23 +41,22 @@ ViewportId=0x78C57832
|
|||||||
Pos=0,651
|
Pos=0,651
|
||||||
Size=897,468
|
Size=897,468
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000001,0
|
DockId=0x00000005,0
|
||||||
|
|
||||||
[Window][Message]
|
[Window][Message]
|
||||||
Pos=642,1879
|
Pos=661,1321
|
||||||
Size=1002,242
|
Size=716,455
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Response]
|
[Window][Response]
|
||||||
Pos=1700,1898
|
Pos=2437,925
|
||||||
Size=1111,224
|
Size=1111,773
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Tool Calls]
|
[Window][Tool Calls]
|
||||||
Pos=790,1483
|
Pos=1039,464
|
||||||
Size=876,654
|
Size=587,510
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,0
|
|
||||||
|
|
||||||
[Window][Comms History]
|
[Window][Comms History]
|
||||||
ViewportPos=43,95
|
ViewportPos=43,95
|
||||||
@@ -74,10 +73,10 @@ Collapsed=0
|
|||||||
DockId=0xAFC85805,2
|
DockId=0xAFC85805,2
|
||||||
|
|
||||||
[Window][Theme]
|
[Window][Theme]
|
||||||
Pos=0,1786
|
Pos=2671,24
|
||||||
Size=676,351
|
Size=1169,2136
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,2
|
DockId=0x00000002,1
|
||||||
|
|
||||||
[Window][Text Viewer - Entry #7]
|
[Window][Text Viewer - Entry #7]
|
||||||
Pos=379,324
|
Pos=379,324
|
||||||
@@ -85,16 +84,16 @@ Size=900,700
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Diagnostics]
|
[Window][Diagnostics]
|
||||||
Pos=2641,34
|
Pos=1649,24
|
||||||
Size=1199,2103
|
Size=580,1284
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000010,2
|
DockId=0x00000004,2
|
||||||
|
|
||||||
[Window][Context Hub]
|
[Window][Context Hub]
|
||||||
Pos=0,1786
|
Pos=0,1719
|
||||||
Size=676,351
|
Size=999,441
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,1
|
DockId=0x00000006,0
|
||||||
|
|
||||||
[Window][AI Settings Hub]
|
[Window][AI Settings Hub]
|
||||||
Pos=406,17
|
Pos=406,17
|
||||||
@@ -103,28 +102,28 @@ Collapsed=0
|
|||||||
DockId=0x0000000D,0
|
DockId=0x0000000D,0
|
||||||
|
|
||||||
[Window][Discussion Hub]
|
[Window][Discussion Hub]
|
||||||
Pos=1668,22
|
Pos=1762,24
|
||||||
Size=915,2115
|
Size=907,2136
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000013,0
|
DockId=0x00000011,0
|
||||||
|
|
||||||
[Window][Operations Hub]
|
[Window][Operations Hub]
|
||||||
Pos=678,22
|
Pos=1001,24
|
||||||
Size=988,2115
|
Size=759,2136
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000005,0
|
DockId=0x00000010,0
|
||||||
|
|
||||||
[Window][Files & Media]
|
[Window][Files & Media]
|
||||||
Pos=0,1786
|
Pos=0,1719
|
||||||
Size=676,351
|
Size=999,441
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,0
|
DockId=0x00000006,1
|
||||||
|
|
||||||
[Window][AI Settings]
|
[Window][AI Settings]
|
||||||
Pos=0,22
|
Pos=0,24
|
||||||
Size=676,1762
|
Size=999,1693
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000001,0
|
DockId=0x00000005,0
|
||||||
|
|
||||||
[Window][Approve Tool Execution]
|
[Window][Approve Tool Execution]
|
||||||
Pos=3,524
|
Pos=3,524
|
||||||
@@ -132,16 +131,16 @@ Size=416,325
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][MMA Dashboard]
|
[Window][MMA Dashboard]
|
||||||
Pos=2585,22
|
Pos=2671,24
|
||||||
Size=1255,2115
|
Size=1169,2136
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000010,0
|
DockId=0x00000002,0
|
||||||
|
|
||||||
[Window][Log Management]
|
[Window][Log Management]
|
||||||
Pos=2585,22
|
Pos=1931,24
|
||||||
Size=1255,2115
|
Size=629,1416
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000010,1
|
DockId=0x00000002,1
|
||||||
|
|
||||||
[Window][Track Proposal]
|
[Window][Track Proposal]
|
||||||
Pos=709,326
|
Pos=709,326
|
||||||
@@ -152,25 +151,22 @@ Collapsed=0
|
|||||||
Pos=2905,1238
|
Pos=2905,1238
|
||||||
Size=935,899
|
Size=935,899
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000004,0
|
|
||||||
|
|
||||||
[Window][Tier 2: Tech Lead]
|
[Window][Tier 2: Tech Lead]
|
||||||
Pos=2905,1238
|
Pos=2905,1238
|
||||||
Size=935,899
|
Size=935,899
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000004,0
|
|
||||||
|
|
||||||
[Window][Tier 4: QA]
|
[Window][Tier 4: QA]
|
||||||
Pos=2905,1238
|
Pos=2905,1238
|
||||||
Size=935,899
|
Size=935,899
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000004,0
|
|
||||||
|
|
||||||
[Window][Tier 3: Workers]
|
[Window][Tier 3: Workers]
|
||||||
Pos=2641,1719
|
Pos=2822,1717
|
||||||
Size=916,418
|
Size=1018,420
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x0000000C,0
|
DockId=0x00000004,0
|
||||||
|
|
||||||
[Window][Approve PowerShell Command]
|
[Window][Approve PowerShell Command]
|
||||||
Pos=649,435
|
Pos=649,435
|
||||||
@@ -178,7 +174,7 @@ Size=381,329
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Last Script Output]
|
[Window][Last Script Output]
|
||||||
Pos=1005,343
|
Pos=2810,265
|
||||||
Size=800,562
|
Size=800,562
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
@@ -213,7 +209,7 @@ Size=3840,32
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Text Viewer - message]
|
[Window][Text Viewer - message]
|
||||||
Pos=568,1226
|
Pos=562,588
|
||||||
Size=900,700
|
Size=900,700
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
@@ -223,7 +219,7 @@ Size=900,700
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Text Viewer - text]
|
[Window][Text Viewer - text]
|
||||||
Pos=60,60
|
Pos=555,644
|
||||||
Size=900,700
|
Size=900,700
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
@@ -243,7 +239,7 @@ Size=900,700
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Text Viewer - tool_calls]
|
[Window][Text Viewer - tool_calls]
|
||||||
Pos=60,60
|
Pos=589,490
|
||||||
Size=900,700
|
Size=900,700
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
@@ -288,8 +284,8 @@ Size=900,700
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Text Viewer - Tool Call #1 Details]
|
[Window][Text Viewer - Tool Call #1 Details]
|
||||||
Pos=2318,1220
|
Pos=165,1081
|
||||||
Size=900,700
|
Size=727,725
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Text Viewer - Tool Call #10 Details]
|
[Window][Text Viewer - Tool Call #10 Details]
|
||||||
@@ -333,10 +329,9 @@ Size=967,499
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Usage Analytics]
|
[Window][Usage Analytics]
|
||||||
Pos=2641,1719
|
Pos=1702,689
|
||||||
Size=1199,418
|
Size=566,438
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x0000000F,0
|
|
||||||
|
|
||||||
[Window][Tool Preset Manager]
|
[Window][Tool Preset Manager]
|
||||||
Pos=1301,302
|
Pos=1301,302
|
||||||
@@ -353,6 +348,46 @@ Pos=856,546
|
|||||||
Size=1000,800
|
Size=1000,800
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][External Tools]
|
||||||
|
Pos=531,376
|
||||||
|
Size=616,409
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][Text Viewer - Tool Call #2 Details]
|
||||||
|
Pos=60,60
|
||||||
|
Size=900,700
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][Text Viewer - Tool Call #3 Details]
|
||||||
|
Pos=60,60
|
||||||
|
Size=900,700
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][Text Viewer - Entry #4]
|
||||||
|
Pos=828,397
|
||||||
|
Size=900,700
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][Text Viewer - Entry #10]
|
||||||
|
Pos=755,715
|
||||||
|
Size=1593,1240
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][Text Viewer - Entry #5]
|
||||||
|
Pos=60,60
|
||||||
|
Size=900,700
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][Shader Editor]
|
||||||
|
Pos=753,637
|
||||||
|
Size=493,487
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][Text Viewer - list_directory]
|
||||||
|
Pos=60,60
|
||||||
|
Size=900,700
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
[Table][0xFB6E3870,4]
|
[Table][0xFB6E3870,4]
|
||||||
RefScale=13
|
RefScale=13
|
||||||
Column 0 Width=80
|
Column 0 Width=80
|
||||||
@@ -384,11 +419,11 @@ Column 3 Width=20
|
|||||||
Column 4 Weight=1.0000
|
Column 4 Weight=1.0000
|
||||||
|
|
||||||
[Table][0x2A6000B6,4]
|
[Table][0x2A6000B6,4]
|
||||||
RefScale=24
|
RefScale=16
|
||||||
Column 0 Width=72
|
Column 0 Width=48
|
||||||
Column 1 Width=106
|
Column 1 Width=68
|
||||||
Column 2 Weight=1.0000
|
Column 2 Weight=1.0000
|
||||||
Column 3 Width=180
|
Column 3 Width=120
|
||||||
|
|
||||||
[Table][0x8BCC69C7,6]
|
[Table][0x8BCC69C7,6]
|
||||||
RefScale=13
|
RefScale=13
|
||||||
@@ -400,18 +435,18 @@ Column 4 Weight=1.0000
|
|||||||
Column 5 Width=50
|
Column 5 Width=50
|
||||||
|
|
||||||
[Table][0x3751446B,4]
|
[Table][0x3751446B,4]
|
||||||
RefScale=14
|
RefScale=16
|
||||||
Column 0 Width=42
|
Column 0 Width=48
|
||||||
Column 1 Width=63
|
Column 1 Width=72
|
||||||
Column 2 Weight=1.0000
|
Column 2 Weight=1.0000
|
||||||
Column 3 Width=105
|
Column 3 Width=120
|
||||||
|
|
||||||
[Table][0x2C515046,4]
|
[Table][0x2C515046,4]
|
||||||
RefScale=24
|
RefScale=16
|
||||||
Column 0 Width=73
|
Column 0 Width=48
|
||||||
Column 1 Weight=1.0000
|
Column 1 Weight=1.0000
|
||||||
Column 2 Width=181
|
Column 2 Width=117
|
||||||
Column 3 Width=72
|
Column 3 Width=48
|
||||||
|
|
||||||
[Table][0xD99F45C5,4]
|
[Table][0xD99F45C5,4]
|
||||||
Column 0 Sort=0v
|
Column 0 Sort=0v
|
||||||
@@ -432,14 +467,14 @@ Column 1 Width=100
|
|||||||
Column 2 Weight=1.0000
|
Column 2 Weight=1.0000
|
||||||
|
|
||||||
[Table][0xA02D8C87,3]
|
[Table][0xA02D8C87,3]
|
||||||
RefScale=24
|
RefScale=16
|
||||||
Column 0 Width=270
|
Column 0 Width=180
|
||||||
Column 1 Width=180
|
Column 1 Width=120
|
||||||
Column 2 Weight=1.0000
|
Column 2 Weight=1.0000
|
||||||
|
|
||||||
[Table][0xD0277E63,2]
|
[Table][0xD0277E63,2]
|
||||||
RefScale=14
|
RefScale=16
|
||||||
Column 0 Width=116
|
Column 0 Width=132
|
||||||
Column 1 Weight=1.0000
|
Column 1 Weight=1.0000
|
||||||
|
|
||||||
[Table][0x3AAF84D5,2]
|
[Table][0x3AAF84D5,2]
|
||||||
@@ -448,41 +483,37 @@ Column 0 Width=150
|
|||||||
Column 1 Weight=1.0000
|
Column 1 Weight=1.0000
|
||||||
|
|
||||||
[Table][0x8D8494AB,2]
|
[Table][0x8D8494AB,2]
|
||||||
RefScale=14
|
RefScale=16
|
||||||
Column 0 Width=116
|
Column 0 Width=132
|
||||||
Column 1 Weight=1.0000
|
Column 1 Weight=1.0000
|
||||||
|
|
||||||
[Table][0x2C261E6E,2]
|
[Table][0x2C261E6E,2]
|
||||||
RefScale=14
|
RefScale=16
|
||||||
Column 0 Width=87
|
Column 0 Width=99
|
||||||
Column 1 Weight=1.0000
|
Column 1 Weight=1.0000
|
||||||
|
|
||||||
[Table][0x9CB1E6FD,2]
|
[Table][0x9CB1E6FD,2]
|
||||||
RefScale=14
|
RefScale=16
|
||||||
Column 0 Width=164
|
Column 0 Width=187
|
||||||
Column 1 Weight=1.0000
|
Column 1 Weight=1.0000
|
||||||
|
|
||||||
[Docking][Data]
|
[Docking][Data]
|
||||||
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
|
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
|
||||||
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
|
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
|
||||||
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
|
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
|
||||||
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,22 Size=3840,2115 Split=X
|
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,24 Size=3840,2136 Split=X
|
||||||
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2583,1183 Split=X
|
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=1617,1183 Split=X
|
||||||
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
|
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
|
||||||
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=676,858 Split=Y Selected=0x8CA2375C
|
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=999,858 Split=Y Selected=0x7BD57D6A
|
||||||
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=824,1759 CentralNode=1 Selected=0x7BD57D6A
|
DockNode ID=0x00000005 Parent=0x00000007 SizeRef=639,904 CentralNode=1 Selected=0x7BD57D6A
|
||||||
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=824,351 Selected=0x1DCB2623
|
DockNode ID=0x00000006 Parent=0x00000007 SizeRef=639,441 Selected=0x1DCB2623
|
||||||
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1905,858 Split=X Selected=0x418C7449
|
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=2839,858 Split=X Selected=0x418C7449
|
||||||
DockNode ID=0x00000012 Parent=0x0000000E SizeRef=988,402 Split=Y Selected=0x418C7449
|
DockNode ID=0x00000001 Parent=0x0000000E SizeRef=1668,1288 Split=X Selected=0x6F2B5B04
|
||||||
DockNode ID=0x00000005 Parent=0x00000012 SizeRef=876,1455 Selected=0x418C7449
|
DockNode ID=0x00000010 Parent=0x00000001 SizeRef=759,1416 Selected=0x418C7449
|
||||||
DockNode ID=0x00000006 Parent=0x00000012 SizeRef=876,654 Selected=0x1D56B311
|
DockNode ID=0x00000011 Parent=0x00000001 SizeRef=907,1416 Selected=0x6F2B5B04
|
||||||
DockNode ID=0x00000013 Parent=0x0000000E SizeRef=915,402 Selected=0x6F2B5B04
|
DockNode ID=0x00000002 Parent=0x0000000E SizeRef=1169,1288 Selected=0x8CA2375C
|
||||||
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
|
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
|
||||||
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=1255,1183 Split=Y Selected=0x3AEC3498
|
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=511,1183 Selected=0x3AEC3498
|
||||||
DockNode ID=0x00000010 Parent=0x00000004 SizeRef=1199,1689 Selected=0x2C0206CE
|
|
||||||
DockNode ID=0x00000011 Parent=0x00000004 SizeRef=1199,420 Split=X Selected=0xDEB547B6
|
|
||||||
DockNode ID=0x0000000C Parent=0x00000011 SizeRef=916,380 Selected=0x655BC6E9
|
|
||||||
DockNode ID=0x0000000F Parent=0x00000011 SizeRef=281,380 Selected=0xDEB547B6
|
|
||||||
|
|
||||||
;;;<<<Layout_655921752_Default>>>;;;
|
;;;<<<Layout_655921752_Default>>>;;;
|
||||||
;;;<<<HelloImGui_Misc>>>;;;
|
;;;<<<HelloImGui_Misc>>>;;;
|
||||||
|
|||||||
@@ -2416,3 +2416,683 @@ PROMPT:
|
|||||||
role: tool
|
role: tool
|
||||||
Here are the results: {"content": "done"}
|
Here are the results: {"content": "done"}
|
||||||
------------------
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
PATH: Epic Initialization — please produce tracks
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
Please generate the implementation tickets for this track.
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
Please read test.txt
|
||||||
|
You are assigned to Ticket T1.
|
||||||
|
Task Description: do something
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
role: tool
|
||||||
|
Here are the results: {"content": "done"}
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
PATH: Epic Initialization — please produce tracks
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
Please generate the implementation tickets for this track.
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
Please read test.txt
|
||||||
|
You are assigned to Ticket T1.
|
||||||
|
Task Description: do something
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
role: tool
|
||||||
|
Here are the results: {"content": "done"}
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
PATH: Epic Initialization — please produce tracks
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
Please generate the implementation tickets for this track.
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
Please read test.txt
|
||||||
|
You are assigned to Ticket T1.
|
||||||
|
Task Description: do something
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
role: tool
|
||||||
|
Here are the results: {"content": "done"}
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['C:\\projects\\manual_slop\\tests\\mock_gemini_cli.py', '-m', 'gemini-2.5-flash-lite', '--prompt', '', '--output-format', 'stream-json']
|
||||||
|
PROMPT:
|
||||||
|
You are a helpful coding assistant with access to a PowerShell tool (run_powershell) and MCP tools (file access: read_file, list_directory, search_files, get_file_summary, web access: web_search, fetch_url). When calling file/directory tools, always use the 'path' parameter for the target path. When asked to create or edit files, prefer targeted edits over full rewrites. Always explain what you are doing before invoking the tool.
|
||||||
|
|
||||||
|
When writing or rewriting large files (especially those containing quotes, backticks, or special characters), avoid python -c with inline strings. Instead: (1) write a .py helper script to disk using a PS here-string (@'...'@ for literal content), (2) run it with `python <script>`, (3) delete the helper. For small targeted edits, use PowerShell's (Get-Content) / .Replace() / Set-Content or Add-Content directly.
|
||||||
|
|
||||||
|
When making function calls using tools that accept array or object parameters ensure those are structured using JSON. For example:
|
||||||
|
When you need to verify a change, rely on the exit code and stdout/stderr from the tool — the user's context files are automatically refreshed after every tool call, so you do NOT need to re-read files that are already provided in the <context> block.
|
||||||
|
|
||||||
|
[USER SYSTEM PROMPT]
|
||||||
|
|
||||||
|
You are the Tier 1 Orchestrator (Product Manager) for the Manual Slop project.
|
||||||
|
Your role is high-level strategic planning, architecture enforcement, and cross-module delegation.
|
||||||
|
You operate strictly on metadata, summaries, and executive-level directives.
|
||||||
|
NEVER request or attempt to read raw implementation code unless specifically provided in a Macro-Diff.
|
||||||
|
Maintain a "Godot ECS Flat List format" (JSON array of objects) for structural outputs.
|
||||||
|
|
||||||
|
PATH: Epic Initialization (Project Planning)
|
||||||
|
GOAL: Break down a massive feature request into discrete Implementation Tracks.
|
||||||
|
|
||||||
|
CONSTRAINTS:
|
||||||
|
- IGNORE all source code, AST skeletons, and previous micro-task histories.
|
||||||
|
- FOCUS ONLY on the Repository Map and Project Meta-State.
|
||||||
|
|
||||||
|
OUTPUT REQUIREMENT:
|
||||||
|
Return a JSON array of 'Tracks'. Each track object must follow the Godot ECS Flat List format:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "track_unique_id",
|
||||||
|
"type": "Track",
|
||||||
|
"module": "target_module_name",
|
||||||
|
"persona": "required_tech_lead_persona",
|
||||||
|
"severity": "Low|Medium|High",
|
||||||
|
"goal": "Descriptive goal",
|
||||||
|
"acceptance_criteria": ["criteria_1", "criteria_2"]
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
<context>
|
||||||
|
|
||||||
|
</context>
|
||||||
|
|
||||||
|
### USER REQUEST:
|
||||||
|
Add timestamps
|
||||||
|
|
||||||
|
### REPOSITORY MAP:
|
||||||
|
|
||||||
|
|
||||||
|
### TRACK HISTORY:
|
||||||
|
Track: api_hooks_verification_20260223
|
||||||
|
Status: new
|
||||||
|
Overview: This track focuses on integrating the existing, previously implemented API hooks (from track `test_hooks_20260223`) into the Conductor workflow. The primary goal is to automate the verification steps within the "Phase Completion Verification and Checkpointing Protocol", reducing the need for manual user intervention and enabling a more streamlined, automated development process.
|
||||||
|
---
|
||||||
|
Track: api_metrics_20260223
|
||||||
|
Status: new
|
||||||
|
Overview: This track aims to optimize token efficiency and transparency by reviewing and improving how vendor APIs (Gemini and Anthropic) handle conservative context pruning. The primary focus is on extracting, plotting, and exposing deep metrics to the GUI so developers can intuit how close they are to API limits (e.g., token caps, cache counts, history bleed).
|
||||||
|
---
|
||||||
|
Track: api_vendor_alignment_20260223
|
||||||
|
Status: new
|
||||||
|
Overview: This track involves a comprehensive audit of the "Manual Slop" codebase to ensure that the integration with Google Gemini (`google-genai`) and Anthropic Claude (`anthropic`) SDKs aligns perfectly with their latest official documentation and best practices. The goal is to identify discrepancies, performance bottlenecks, or deprecated patterns and implement the necessary fixes.
|
||||||
|
---
|
||||||
|
Track: architecture_boundary_hardening_20260302
|
||||||
|
Status: new
|
||||||
|
Overview: The `manual_slop` project sandbox provides AI meta-tooling (`mma_exec.py`, `tool_call.py`) to orchestrate its own development. When AI agents added advanced AST tools (like `set_file_slice`) to `mcp_client.py` for meta-tooling, they failed to fully integrate them into the application's GUI, config, or HITL (Human-In-The-Loop) safety models. Additionally, meta-tooling scripts are bleeding tokens and rely on non-portable hardcoded machine paths, while the internal application's state machine can deadlock.
|
||||||
|
---
|
||||||
|
Track: cache_analytics_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Gemini cache hit/miss visualization, memory usage, TTL status display. Uses existing `ai_client.get_gemini_cache_stats()` which is implemented but has no GUI representation.
|
||||||
|
---
|
||||||
|
Track: codebase_migration_20260302
|
||||||
|
Status: new
|
||||||
|
Overview: This track focuses on restructuring the codebase to alleviate clutter by moving the main implementation files from the project root into a dedicated `src/` directory. Additionally, files that are completely unused by the current implementation will be automatically identified and removed. A new clean entry point (`sloppy.py`) will be created in the root directory.
|
||||||
|
---
|
||||||
|
Track: comprehensive_gui_ux_20260228
|
||||||
|
Status: completed
|
||||||
|
Overview: This track enhances the existing MMA orchestration GUI from its current functional-but-minimal state to a production-quality control surface. The existing implementation already has a working Track Browser, DAG tree visualizer, epic planning flow, approval dialogs, and token usage table. This track focuses on the **gaps**: dedicated tier stream panels, DAG editing, track-scoped discussions, conductor lifecycle GUI forms, cost tracking, and visual polish.
|
||||||
|
---
|
||||||
|
Track: conductor_workflow_improvements_20260302
|
||||||
|
Status: new
|
||||||
|
Overview: Recent Tier 2 track implementations have resulted in feature bleed, redundant code, unread state variables, and degradation of TDD discipline (e.g., zero-assertion tests).
|
||||||
|
This track updates the Conductor documentation (`workflow.md`) and the Gemini skills for Tiers 2 and 3 to hard-enforce TDD, prevent hallucinated "mock" implementations, and enforce strict codebase auditing before writing code.
|
||||||
|
---
|
||||||
|
Track: consolidate_cruft_and_log_taxonomy_20260228
|
||||||
|
Status: unknown
|
||||||
|
Overview: This track focuses on cleaning up the project root by consolidating temporary and test-related files into a dedicated directory and establishing a structured taxonomy for session logs. This will improve project organization and make manual file exploration easier before a dedicated GUI log viewer is implemented.
|
||||||
|
---
|
||||||
|
Track: context_management_20260223
|
||||||
|
Status: new
|
||||||
|
Overview: This track implements UI improvements and structural changes to Manual Slop to provide explicit visualization of context memory usage and token consumption, fulfilling the "Expert systems level utility" and "Full control" product goals.
|
||||||
|
---
|
||||||
|
Track: context_token_viz_20260301
|
||||||
|
Status: new
|
||||||
|
Overview: product.md lists "Context & Memory Management" as primary use case #2: "Better visualization and management of token usage and context memory, allowing developers to optimize prompt limits manually." The backend already computes everything needed via `ai_client.get_history_bleed_stats()` (ai_client.py:1657-1796, 140 lines). This track builds the UI to expose it.
|
||||||
|
---
|
||||||
|
Track: cost_token_analytics_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: # Implementation Plan: Cost & Token Analytics Panel (cost_token_analytics_20260306)
|
||||||
|
|
||||||
|
> **Reference:** [Spec](./spec.md) | [Architecture Guide](../../../docs/guide_architecture.md)
|
||||||
|
|
||||||
|
## Phase 1: Foundat...
|
||||||
|
---
|
||||||
|
Track: deepseek_support_20260225
|
||||||
|
Status: new
|
||||||
|
Overview: Implement a new AI provider module to support the DeepSeek API within the Manual Slop application. This integration will leverage a dedicated SDK to provide access to high-performance models (DeepSeek-V3 and DeepSeek-R1) with support for streaming, tool calling, and detailed reasoning traces.
|
||||||
|
---
|
||||||
|
Track: deep_ast_context_pruning_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Use tree_sitter to parse target file AST and inject condensed skeletons into worker prompts. Currently workers receive full file context; this track reduces token burn by injecting only relevant function/method signatures.
|
||||||
|
---
|
||||||
|
Track: documentation_refresh_20260224
|
||||||
|
Status: new
|
||||||
|
Overview: This track implements a high-density, expert-level documentation suite for the Manual Slop project. The documentation style is strictly modeled after the **pedagogical and narrative standards** of `gencpp` and `VEFontCache-Odin`. It moves beyond simple "User Guides" to provide a **"USA Graphics Company"** architectural reference: high information density, tactical technical transparency, and a narrative intent that guides a developer from high-level philosophy to low-level implementation.
|
||||||
|
---
|
||||||
|
Track: enhanced_context_control_20260307
|
||||||
|
Status: planned
|
||||||
|
Overview: Give developers granular control over how files are included in the AI context and provide visibility into the active Gemini cache state. This involves moving away from a simple list of files to a structured format with per-file flags (`auto_aggregate`, `force_full`), revamping the UI to display this state, and updating the context builders and API clients to respect and expose these details.
|
||||||
|
---
|
||||||
|
Track: event_driven_metrics_20260223
|
||||||
|
Status: new
|
||||||
|
Overview: Refactor the API metrics update mechanism to be event-driven. Currently, the UI likely polls or recalculates metrics on every frame. This track will implement a signal/event system where `ai_client.py` broadcasts updates only when significant API activities (requests, responses, tool calls, or stream chunks) occur.
|
||||||
|
---
|
||||||
|
Track: feature_bleed_cleanup_20260302
|
||||||
|
Status: new
|
||||||
|
Overview: Multiple tracks added code to `gui_2.py` without removing the old versions, leaving
|
||||||
|
dead duplicate methods, conflicting menu bar designs, and redundant state initializations.
|
||||||
|
This track removes confirmed dead code, resolves the two-menubar conflict, and cleans
|
||||||
|
up the token budget layout regression — restoring a consistent, non-contradictory design state.
|
||||||
|
---
|
||||||
|
Track: gemini_cli_headless_20260224
|
||||||
|
Status: new
|
||||||
|
Overview: This track integrates the `gemini` CLI as a headless backend provider for Manual Slop. This allows users to leverage their Gemini subscription and the CLI's advanced features (e.g., specialized sub-agents like `codebase_investigator`, structured JSON streaming, and robust session management) directly within the Manual Slop GUI.
|
||||||
|
---
|
||||||
|
Track: gemini_cli_parity_20260225
|
||||||
|
Status: new
|
||||||
|
Overview: Achieve full functional and behavioral parity between the Gemini CLI integration (`gemini_cli_adapter.py`, `cli_tool_bridge.py`) and the direct Gemini API implementation (`ai_client.py`). This ensures that users leveraging the Gemini CLI as a headless backend provider experience the same level of capability, reliability, and observability as direct API users.
|
||||||
|
---
|
||||||
|
Track: gui2_feature_parity_20260223
|
||||||
|
Status: new
|
||||||
|
Overview: # Specification: GUIv2 Feature Parity
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
|
||||||
|
This track aims to bring `gui_2.py` (the `imgui-bundle` based UI) to feature parity with the existing `gui.py` (the `dearpygui` based UI). This i...
|
||||||
|
---
|
||||||
|
Track: gui2_parity_20260224
|
||||||
|
Status: new
|
||||||
|
Overview: The project is transitioning from `gui.py` (Dear PyGui-based) to `gui_2.py` (ImGui Bundle-based) to leverage advanced multi-viewport and docking features not natively supported by Dear PyGui. This track focuses on achieving full visual, functional, and performance parity between the two implementations, ultimately enabling the decommissioning of the original `gui.py`.
|
||||||
|
---
|
||||||
|
Track: gui_decoupling_controller_20260302
|
||||||
|
Status: new
|
||||||
|
Overview: `gui_2.py` currently operates as a Monolithic God Object (3,500+ lines). It violates the Data-Oriented Design heuristic by owning complex business logic, orchestrator hooks, and markdown file building. This track extracts the core state machine and lifecycle into a headless `app_controller.py`, turning the GUI into a pure immediate-mode view.
|
||||||
|
---
|
||||||
|
Track: gui_layout_refinement_20260223
|
||||||
|
Status: new
|
||||||
|
Overview: This track focuses on a holistic review and reorganization of the Manual Slop GUI. The goal is to ensure that AI tunings, diagnostic features, context management, and discussion history are logically placed to support an expert-level "Multi-Viewport" workflow. We will strengthen the "Arcade Aesthetics" and "Tactile Density" values while ensuring the layout remains intuitive for power users.
|
||||||
|
---
|
||||||
|
Track: gui_performance_20260223
|
||||||
|
Status: new
|
||||||
|
Overview: This track focuses on identifying and resolving severe frametime performance issues in the Manual Slop GUI. Current observations indicate massive frametime bloat even on idle startup, with performance significantly regressing (target 60 FPS / <16.6ms) since commit `8aa70e287fbf93e669276f9757965d5a56e89b10`.
|
||||||
|
---
|
||||||
|
Track: gui_performance_profiling_20260307
|
||||||
|
Status: unknown
|
||||||
|
Overview: Implement fine-grained performance profiling within the main ImGui rendering loop (`gui_2.py`) to ensure adherence to data-oriented and immediate mode heuristics. This track will provide visual diagnostics for high-overhead UI components, allowing developers to monitor and optimize render frame times.
|
||||||
|
---
|
||||||
|
Track: gui_sim_extension_20260224
|
||||||
|
Status: new
|
||||||
|
Overview: This track aims to expand the test simulation suite by introducing comprehensive, in-breadth tests that cover all facets of the GUI interaction. The original small test simulation will be preserved as a useful baseline. The new extended tests will be structured as multiple focused, modular scripts rather than a single long-running journey, ensuring maintainability and targeted coverage.
|
||||||
|
---
|
||||||
|
Track: history_segregation_20260224
|
||||||
|
Status: new
|
||||||
|
Overview: Currently, `manual_slop.toml` stores both project configuration and the entire discussion history. This leads to redundancy and potential context bloat if the AI agent reads the raw TOML file via its tools. This track will move the discussion history to a dedicated sibling TOML file (`history.toml`) and strictly blacklist it from the AI agent's file tools to ensure it only interacts with the curated context provided in the prompt.
|
||||||
|
---
|
||||||
|
Track: kill_abort_workers_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Add ability to kill/abort a running Tier 3 worker mid-execution. Currently workers run to completion; add cancel button with forced termination option.
|
||||||
|
---
|
||||||
|
Track: live_gui_testing_20260223
|
||||||
|
Status: new
|
||||||
|
Overview: Update the testing suite to ensure all tests (especially GUI-related and integration tests) communicate with a live running instance of `gui.py` started with the `--enable-test-hooks` argument. This ensures that tests can verify the actual application state and metrics via the built-in API hooks.
|
||||||
|
---
|
||||||
|
Track: live_ux_test_20260223
|
||||||
|
Status: new
|
||||||
|
Overview: This track implements a robust, "human-like" interaction test suite for Manual Slop. The suite will simulate a real user's workflow—from project creation to complex AI discussions and history management—using the application's API hooks. It aims to verify the "Integrated Workspace" functionality, tool execution, and history persistence without requiring manual human input, while remaining slow enough for visual audit.
|
||||||
|
---
|
||||||
|
Track: logging_refactor_20260226
|
||||||
|
Status: new
|
||||||
|
Overview: Currently, `gui_2.py` and the test suites generate a large number of log files in a flat `logs/` directory. These logs accumulate quickly, especially during incremental development and testing. This track aims to organize logs into session-based sub-directories and implement a heuristic-based pruning system to keep the log directory clean while preserving valuable sessions.
|
||||||
|
---
|
||||||
|
Track: manual_block_control_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Allow user to manually block or unblock tickets with custom reasons. Currently blocked tickets rely solely on dependency resolution; add manual override capability.
|
||||||
|
---
|
||||||
|
Track: manual_skeleton_injection_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Add UI controls to manually inject file skeletons into discussions. Allow user to preview skeleton content before sending to AI, with option to toggle between skeleton and full file.
|
||||||
|
---
|
||||||
|
Track: manual_slop_headless_20260225
|
||||||
|
Status: new
|
||||||
|
Overview: Transform Manual Slop into a decoupled, container-friendly backend service. This track enables the core AI orchestration and tool execution logic to run without a GUI, exposing its capabilities via a secured REST API optimized for an Unraid Docker environment.
|
||||||
|
---
|
||||||
|
Track: minimax_provider_20260306
|
||||||
|
Status: unknown
|
||||||
|
Overview: Add MiniMax as a new AI provider to Manual Slop. MiniMax provides high-performance text generation models (M2.5, M2.1, M2) with massive context windows (200k+ tokens) and competitive pricing.
|
||||||
|
---
|
||||||
|
Track: mma_agent_focus_ux_20260302
|
||||||
|
Status: new
|
||||||
|
Overview: All MMA observability panels (comms history, tool calls, discussion) display
|
||||||
|
global/session-scoped data. When 4 tiers are running concurrently, their traffic
|
||||||
|
is indistinguishable. This track adds a `source_tier` field to every comms and
|
||||||
|
tool log entry at the point of emission, then adds a "Focus Agent" selector that
|
||||||
|
filters the Operations Hub panels to show only one tier's traffic at a time.
|
||||||
|
|
||||||
|
**Depends on:** `feature_bleed_cleanup_20260302` (Phase 1 removes the dead comms
|
||||||
|
panel duplicate; this track extends the live panel at gui_2.py:~3400).
|
||||||
|
---
|
||||||
|
Track: MMA Core Engine Implementation
|
||||||
|
Status: planning
|
||||||
|
Overview: # Specification: MMA Core Engine Implementation
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
This track consolidates the implementation of the 4-Tier Hierarchical Multi-Model Architecture into the `manual_slop` codebase. The arch...
|
||||||
|
---
|
||||||
|
Track: MMA Dashboard Visualization Overhaul
|
||||||
|
Status: planned
|
||||||
|
Overview: Make the invisible backend operations visible and interactive. The current GUI is too barebones to effectively manage a multi-agent system. This track overhauls the MMA Dashboard to provide real-time insights into tracks, task dependencies, and agent streams.
|
||||||
|
---
|
||||||
|
Track: MMA Data Architecture & DAG Engine
|
||||||
|
Status: planned
|
||||||
|
Overview: Restructure how `manual_slop` stores and executes work. The current implementation relies on global state and linear execution, which does not support the complexity of multi-agent, task-based workflows. This track establishes a robust, data-oriented foundation using track-scoped state and a native Python Directed Acyclic Graph (DAG) engine.
|
||||||
|
---
|
||||||
|
Track: mma_formalization_20260225
|
||||||
|
Status: new
|
||||||
|
Overview: This track aims to formalize and automate the 4-Tier Hierarchical Multi-Model Architecture (MMA) within the Conductor framework. It introduces specialized skills for each tier and a new specialized CLI tool (`mma-exec`) to handle role-specific context gathering and "Context Amnesia" protocols.
|
||||||
|
---
|
||||||
|
Track: mma_implementation_20260224
|
||||||
|
Status: new
|
||||||
|
Overview: # Specification: 4-Tier Architecture Implementation & Conductor Self-Improvement
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
This track encompasses two major phases. Phase 1 focuses on designing a comprehensive, step-by-step imp...
|
||||||
|
---
|
||||||
|
Track: mma_multiworker_viz_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Split-view GUI for parallel worker streams per tier. Visualize multiple concurrent workers with individual status, output tabs, and resource usage. Enable kill/restart per worker.
|
||||||
|
---
|
||||||
|
Track: MMA Orchestrator Integration
|
||||||
|
Status: in-progress
|
||||||
|
Overview: Implement the full hierarchical orchestration loop, connecting Tier 1 (PM) strategic planning with Tier 2 (Tech Lead) tactical ticket generation. This track will enable the GUI to autonomously break down high-level user 'Epics' into actionable tracks and tickets, and manage their execution through the multi-agent system.
|
||||||
|
---
|
||||||
|
Track: mma_pipeline_fix_20260301
|
||||||
|
Status: new
|
||||||
|
Overview: The MMA pipeline has a verified code path from `run_worker_lifecycle` → `_queue_put("response", ...)` → `_process_event_queue` → `_pending_gui_tasks("handle_ai_response")` → `mma_streams[stream_id] = text`. However, the robust_live_simulation track's session compression (2026-02-28) documented that Tier 3 worker output never appears in `mma_streams` during actual GUI operation. The simulation only ever sees `'Tier 1'` in `mma_streams` keys.
|
||||||
|
|
||||||
|
This track diagnoses and fixes the pipeline break, then verifies end-to-end that worker output flows from `ai_client.send()` through to the GUI's `mma_streams` dict.
|
||||||
|
---
|
||||||
|
Track: mma_utilization_refinement_20260226
|
||||||
|
Status: new
|
||||||
|
Overview: Refine the Multi-Model Architecture (MMA) implementation within the Conductor framework to ensure clear role segregation, proper tool permissions, and improved observability for sub-agents.
|
||||||
|
---
|
||||||
|
Track: mma_verification_20260225
|
||||||
|
Status: new
|
||||||
|
Overview: This track aims to review and verify the implementation of the 4-Tier Hierarchical Multi-Model Architecture (MMA) within the Conductor framework. It will confirm that Conductor operates as a Tier 2 Tech Lead/Orchestrator and can successfully delegate tasks to Tier 3 (Workers) and Tier 4 (QA/Utility) sub-agents. A key part of this track is investigating whether this hierarchy should be enforced via a single centralized skill or through separate role-based sub-agent definitions.
|
||||||
|
---
|
||||||
|
Track: mma_verification_mock
|
||||||
|
Status: new
|
||||||
|
Overview: This is a mock track designed to verify the full Tier 2 -> Tier 3 -> Tier 4 delegation flow within the Conductor framework.
|
||||||
|
---
|
||||||
|
Track: native_orchestrator_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Absorb `mma_exec.py` functionality into core application. Manual Slop natively reads/writes plan.md, manages metadata.json, and orchestrates MMA tiers in pure Python without external CLI subprocess calls.
|
||||||
|
---
|
||||||
|
Track: nerv_ui_theme_20260309
|
||||||
|
Status: unknown
|
||||||
|
Overview: This track aims to implement a new "NERV" visual theme for the manual_slop application, inspired by the aesthetic of technical/military consoles (e.g., Evangelion's NERV UI). The theme will be added as a selectable option within the application, allowing users to switch between the existing theme and the new NERV style without altering the core user experience or layout.
|
||||||
|
---
|
||||||
|
Track: on_demand_def_lookup_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Add ability for agent to request specific class/function definitions during discussion. Parse @symbol syntax to trigger lookup and display inline in the discussion.
|
||||||
|
---
|
||||||
|
Track: OpenCode Configuration Overhaul
|
||||||
|
Status: completed
|
||||||
|
Overview: Fix critical gaps in OpenCode agent configuration that cause MMA workflow failures. Remove step limits that prematurely terminate complex tracks, disable automatic context compaction that loses critical session state, raise temperature for better problem-solving, and expand thin command wrappers into full protocol documentation.
|
||||||
|
---
|
||||||
|
Track: per_ticket_model_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Allow user to manually select which model to use for a specific ticket, overriding the default tier model. Useful for forcing smarter model on hard tickets.
|
||||||
|
---
|
||||||
|
Track: pipeline_pause_resume_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Add global pause/resume for entire DAG execution pipeline. Allow user to freeze all worker activity and resume later without losing state.
|
||||||
|
---
|
||||||
|
Track: python_style_refactor_20260227
|
||||||
|
Status: unknown
|
||||||
|
Overview: # Specification: AI-Optimized Python Style Refactor
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
Refactor the Python codebase to a "Single-Space, Ultra-Compact" style specifically designed to minimize token consumption for AI age...
|
||||||
|
---
|
||||||
|
Track: Robust Live Simulation Verification
|
||||||
|
Status: planned
|
||||||
|
Overview: Establish a robust, visual simulation framework to prevent regressions in the complex GUI and asynchronous orchestration layers. This track replaces manual human verification with an automated script that clicks through the GUI and verifies the rendered state.
|
||||||
|
---
|
||||||
|
Track: session_insights_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Token usage over time, cost projections, session summary with efficiency scores. Visualize session_logger data.
|
||||||
|
---
|
||||||
|
Track: simulation_hardening_20260301
|
||||||
|
Status: new
|
||||||
|
Overview: The `robust_live_simulation_verification` track is marked complete but its session compression documents three unresolved issues: (1) brittle mock that triggers the wrong approval popup, (2) popup state desynchronization after "Accept" clicks, (3) Tier 3 output never appearing in `mma_streams` (fixed by `mma_pipeline_fix` track). This track stabilizes the simulation framework so it reliably passes end-to-end.
|
||||||
|
---
|
||||||
|
Track: strict_execution_queue_completed_20260306
|
||||||
|
Status: completed
|
||||||
|
Overview: No overview available.
|
||||||
|
---
|
||||||
|
Track: strict_static_analysis_and_typing_20260302
|
||||||
|
Status: new
|
||||||
|
Overview: The codebase currently suffers from massive type-safety debt (512+ `mypy` errors across 64 files) and lingering `ruff` violations. This track will harden the foundation by resolving all violations, enforcing strict typing (especially in `gui_2.py` and `api_hook_client.py`), and integrating pre-commit checks. This is a prerequisite for safe AI-driven refactoring.
|
||||||
|
---
|
||||||
|
Track: tech_debt_and_test_cleanup_20260302
|
||||||
|
Status: new
|
||||||
|
Overview: Due to rapid iterative development and feature bleed across multiple Tier 2-led tracks, significant tech debt has accumulated in both the testing suite and `gui_2.py`.
|
||||||
|
This track will clean up test fixtures, enforce test assertion integrity, and remove dead codebase remnants.
|
||||||
|
---
|
||||||
|
Track: test_architecture_integrity_audit_20260304
|
||||||
|
Status: unknown
|
||||||
|
Overview: Comprehensive audit of testing infrastructure and simulation framework to identify false positive risks, coverage gaps, and simulation fidelity issues. This analysis was triggered by a request to review how tests and simulations are setup, whether tests can report passing grades when they fail, and if simulations are rigorous enough or are just rough emulators.
|
||||||
|
---
|
||||||
|
Track: test_curation_20260225
|
||||||
|
Status: new
|
||||||
|
Overview: The current test suite for **Manual Slop** and the **Conductor** framework has grown incrementally and lacks a formal organization. This track aims to curate, categorize, and organize existing tests, specifically blacklisting Conductor-specific (MMA) tests from manual_slop's test runs. We will use a central manifest for test management and perform an exhaustive review of all tests to eliminate redundancy.
|
||||||
|
---
|
||||||
|
Track: test_hooks_20260223
|
||||||
|
Status: new
|
||||||
|
Overview: This track introduces a comprehensive suite of API hooks designed specifically for the Gemini CLI and the Conductor framework. These hooks will allow automated agents to manipulate and test the internal state of the application without requiring manual GUI interaction, enabling automated test-driven development and track progression validation.
|
||||||
|
---
|
||||||
|
Track: Test Integrity Audit & Intent Documentation
|
||||||
|
Status: in_progress
|
||||||
|
Overview: Audit and fix tests that have been "simplified" or "dumbed down" by AI agents, restoring their original verification intent through explicit documentation comments. This track addresses the growing problem of AI agents "completing" tasks by weakening test assertions rather than implementing proper functionality.
|
||||||
|
---
|
||||||
|
Track: test_regression_verification_20260307
|
||||||
|
Status: unknown
|
||||||
|
Overview: Verify that all existing tests pass with 0 regressions after recent track implementations (Kill/Abort, Block/Unblock, Pause/Resume, Per-Ticket Model Override).
|
||||||
|
---
|
||||||
|
Track: test_stabilization_20260302
|
||||||
|
Status: new
|
||||||
|
Overview: The goal of this track is to stabilize and unify the project's test suite. This involves resolving pervasive `asyncio` lifecycle errors, consolidating redundant testing paradigms (specifically manual GUI subprocesses), ensuring artifact isolation in `./tests/artifacts/`, implementing functional assertions for currently mocked-out tests, and updating documentation to reflect the finalized verification framework.
|
||||||
|
---
|
||||||
|
Track: ticket_queue_mgmt_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Allow user to manually reorder, prioritize, or requeue tickets in the DAG. Add drag-drop reordering, priority tags, and bulk selection for execute/skip/block operations.
|
||||||
|
---
|
||||||
|
Track: tier4_auto_patching_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Elevate Tier 4 from log summarizer to auto-patcher. When verification tests fail, Tier 4 generates a unified diff patch. GUI displays side-by-side diff; user clicks Apply Patch to resume pipeline.
|
||||||
|
---
|
||||||
|
Track: Tiered Context Scoping & HITL Approval
|
||||||
|
Status: planned
|
||||||
|
Overview: Provide the user with absolute visual control over what the AI sees at every level of the hierarchy. Currently, the system builds a single massive context blob. This track introduces context subsetting based on the target tier and implements a Human-in-the-Loop (HITL) approval gate before any Tier 3/4 worker is spawned.
|
||||||
|
---
|
||||||
|
Track: tool_usage_analytics_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Analytics panel showing most-used tools, average execution time, and failure rates. Uses existing tool execution data from ai_client.
|
||||||
|
---
|
||||||
|
Track: track_progress_viz_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Progress bars and percentage completion for active tracks and tickets. Better visualization of DAG execution state.
|
||||||
|
---
|
||||||
|
Track: true_parallel_worker_execution_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Add worker pool management and configurable concurrency limits to the DAG engine. Currently workers execute in parallel per tick but with no limits or tracking; this track adds max_workers configuration, worker tracking, and proper pool management.
|
||||||
|
---
|
||||||
|
Track: ui_performance_20260223
|
||||||
|
Status: new
|
||||||
|
Overview: This track aims to resolve subpar UI performance (currently perceived below 60 FPS) by implementing a robust performance monitoring system. This system will collect high-resolution telemetry (Frame Time, Input Lag, Thread Usage) and expose it to both the user (via a Diagnostics Panel) and the AI (via API hooks). This ensures that performance degradation is caught early during development and testing.
|
||||||
|
---
|
||||||
|
Track: visual_dag_ticket_editing_20260306
|
||||||
|
Status: planned
|
||||||
|
Overview: Replace linear ticket list with interactive node graph using ImGui Bundle node editor. Users can visually drag dependency lines, split nodes, or delete tasks before execution.
|
||||||
|
---
|
||||||
|
Track: agent_personas_20260309
|
||||||
|
Status: new
|
||||||
|
Overview: Transition the application from fragmented prompt and model settings to a **Unified Persona** model. A Persona consolidates Provider, Model (or a preferred set of models), Parameters (Temp, Top-P, etc.), Prompts (Global, Project, and MMA-specific components), and links to Tool Presets into a single, versionable entity.
|
||||||
|
---
|
||||||
|
Track: beads_mode_20260309
|
||||||
|
Status: new
|
||||||
|
Overview: Introduce "Beads Mode" as a first-class, project-specific alternative to the current markdown-based implementation tracking (Native Mode). By integrating with [Beads](https://github.com/steveyegge/beads), Manual Slop will gain a distributed, git-backed graph issue tracker that allows Implementation Tracks and Tickets to be versioned alongside the codebase using Dolt.
|
||||||
|
---
|
||||||
|
Track: caching_optimization_20260308
|
||||||
|
Status: new
|
||||||
|
Overview: This track aims to verify and optimize the caching strategies across all supported AI providers (Gemini, Anthropic, DeepSeek, MiniMax, and OpenAI). The goal is to minimize token consumption and latency by ensuring that static and recurring context (system prompts, tool definitions, project documents, and conversation history) are effectively cached using each provider's specific mechanisms.
|
||||||
|
---
|
||||||
|
Track: codebase_audit_20260308
|
||||||
|
Status: new
|
||||||
|
Overview: The objective of this track is to audit the `./src` and `./simulation` directories to improve human readability and maintainability. The codebase has matured, and it is necessary to identify and address redundant code paths and state tracking, add missing docstrings to critical paths, and organize declarations/definitions within files.
|
||||||
|
---
|
||||||
|
Track: conductor_path_configurable_20260306
|
||||||
|
Status: unknown
|
||||||
|
Overview: Eliminate all hardcoded paths in the application. Make directory paths configurable via `config.toml` or environment variables, allowing the running app to use different directories from development setup. This is **Phase 0 - Critical Infrastructure** that must be completed before other Phase 3 tracks.
|
||||||
|
---
|
||||||
|
Track: csharp_language_support_tools_20260310
|
||||||
|
Status: new
|
||||||
|
Overview: Expand the Conductor's AI context-gathering and surgical editing capabilities by introducing full Tree-Sitter parsing support for C#. This brings C# to feature-parity with existing Python MCP tools, enabling deep AST-driven structural mapping, documentation extraction, and precise code modification, specifically targeting Unreal Engine build scripts, and Unity/Godot scripting.
|
||||||
|
---
|
||||||
|
Track: custom_shaders_20260309
|
||||||
|
Status: new
|
||||||
|
Overview: Implement proper custom shader support for post-process rendering and backgrounds within the Manual Slop GUI (using Dear PyGui/imgui-bundle). Additionally, investigate and implement a method to overload or replace the default OS window frame to ensure it matches the application's theme.
|
||||||
|
---
|
||||||
|
Track: discussion_takes_branching_20260311
|
||||||
|
Status: new
|
||||||
|
Overview: # Specification: Discussion Takes & Timeline Branching
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
This track introduces non-linear discussion timelines, allowing users to create multiple "takes" (branches) from a shared point i...
|
||||||
|
---
|
||||||
|
Track: external_editor_integration_20260308
|
||||||
|
Status: new
|
||||||
|
Overview: This feature adds the ability to open files modified by AI agents in external text editors (such as VSCode or 10xNotepad) directly from the tool approval popup. This allows users to leverage their preferred editor's native diffing and editing capabilities before confirming an agent's changes.
|
||||||
|
---
|
||||||
|
Track: external_mcp_support_20260308
|
||||||
|
Status: new
|
||||||
|
Overview: This feature adds support for integrating external Model Context Protocol (MCP) servers into Manual Slop. This allows agents to utilize tools from a wide ecosystem of MCP servers (like those for databases, APIs, or specialized utilities) alongside the application's native tools.
|
||||||
|
---
|
||||||
|
Track: gdscript_godot_script_language_support_tools_20260310
|
||||||
|
Status: new
|
||||||
|
Overview: Expand the Conductor's AI context-gathering and surgical editing capabilities by introducing full Tree-Sitter parsing support for GDScript (Godot's native scripting language). This will bring GDScript to feature-parity with the existing Python MCP tools, enabling deep AST-driven structural mapping, documentation extraction, and precise code modification.
|
||||||
|
---
|
||||||
|
Track: Bootstrap gencpp Python Bindings Project
|
||||||
|
Status: pending
|
||||||
|
Overview: Create a new standalone Python project to build CFFI bindings for gencpp (C/C++ staged metaprogramming library). This will eventually provide richer C++ AST understanding than tree-sitter (full type information, operators, specifiers) but is a longer-term effort. This track bootstraps the project structure and initial bindings.
|
||||||
|
---
|
||||||
|
Track: GUI Path Configuration in Context Hub
|
||||||
|
Status: pending
|
||||||
|
Overview: Add path configuration UI to the Context Hub in the GUI. Allow users to view and edit configurable paths (conductor, logs, scripts) directly from the application without manually editing config.toml or environment variables.
|
||||||
|
---
|
||||||
|
Track: hook_api_expansion_20260308
|
||||||
|
Status: new
|
||||||
|
Overview: This track aims to transform the Manual Slop Hook API into a comprehensive control plane for headless use. It focuses on exposing all relevant internal states (worker traces, AST metadata, financial metrics, concurrency telemetry) and providing granular control over the application's lifecycle, MMA pipeline, and context management. Additionally, it introduces a WebSocket-based streaming interface for real-time event delivery.
|
||||||
|
---
|
||||||
|
Track: log_session_overhaul_20260308
|
||||||
|
Status: new
|
||||||
|
Overview: This track focuses on centralizing log management, improving the reliability and scope of session restoration, and optimizing log storage by offloading large data blobs (scripts and tool outputs) to external files. It also aims to "clean" the discussion history by moving transient system warnings to a dedicated diagnostic log.
|
||||||
|
---
|
||||||
|
Track: manual_ux_validation_20260302
|
||||||
|
Status: new
|
||||||
|
Overview: This track is an unusual, highly interactive human-in-the-loop review session. The user will act as the primary QA and Designer, manually using the GUI and observing it during slow-interval simulation runs. The goal is to aggressively iterate on the "feel" of the application: analyzing blinking animations, structural decisions (Tabs vs. Panels vs. Collapsing Headers), knob/control placements, and the efficacy of popups (including adding auto-close timers).
|
||||||
|
---
|
||||||
|
Track: markdown_highlighting_20260308
|
||||||
|
Status: new
|
||||||
|
Overview: This track introduces rich text rendering to the Manual Slop GUI by adding support for GitHub-Flavored Markdown (GFM) in message and response views. It also adds syntax highlighting for code blocks and text content when the language context can be cheaply resolved (e.g., via known metadata or file extensions).
|
||||||
|
---
|
||||||
|
Track: openai_integration_20260308
|
||||||
|
Status: new
|
||||||
|
Overview: This track introduces support for OpenAI as a first-class model provider. It involves implementing a dedicated client in `src/ai_client.py`, updating configuration models, enhancing the GUI for provider selection, and integrating OpenAI into the tiered MMA architecture.
|
||||||
|
---
|
||||||
|
Track: presets_ai_settings_ux_20260311
|
||||||
|
Status: new
|
||||||
|
Overview: # Specification: UI/UX Improvements - Presets and AI Settings
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
This track aims to improve the usability and visual layout of the Preset windows (Personas, Prompts, Tools) and the AI Set...
|
||||||
|
---
|
||||||
|
Track: Project-Specific Conductor Directory
|
||||||
|
Status: pending
|
||||||
|
Overview: Make the conductor directory per-project instead of global. Each project TOML can specify its own `conductor_dir` path, allowing separate track/state management per project. This enables using Manual Slop with multiple independent projects without track/ticket cross-pollution.
|
||||||
|
---
|
||||||
|
Track: rag_support_20260308
|
||||||
|
Status: new
|
||||||
|
Overview: This track introduces Retrieval-Augmented Generation (RAG) capabilities to Manual Slop. It allows agents to search and retrieve relevant information from large local codebases, project documentation, or external knowledge bases, overcoming context window limitations and reducing hallucination.
|
||||||
|
---
|
||||||
|
Track: saved_presets_20260308
|
||||||
|
Status: new
|
||||||
|
Overview: This feature introduces the ability to save, manage, and switch between system prompt presets for both global (application-wide) and project-specific contexts. Presets will include not only the system prompt text but also model-specific parameters like temperature and top_p, effectively allowing for "AI Profiles."
|
||||||
|
---
|
||||||
|
Track: saved_tool_presets_20260308
|
||||||
|
Status: new
|
||||||
|
Overview: This feature adds the ability to create, save, and manage "Tool Presets" for agent roles. These presets define which tools are available to an agent and their respective "auto" vs "ask" approval levels. Tools will be organized into dynamic, TOML-defined categories (e.g., Python, General) and integrated into the global and project-specific AI settings.
|
||||||
|
---
|
||||||
|
Track: selectable_ui_text_20260308
|
||||||
|
Status: new
|
||||||
|
Overview: This track aims to address UI inconveniences by making critical text across the GUI selectable and copyable. This includes discussion history, communication logs, tool outputs, and key metrics. The goal is to provide a standard "Copy to Clipboard" capability via OS-native selection and shortcuts (Ctrl+C).
|
||||||
|
---
|
||||||
|
Track: session_context_snapshots_20260311
|
||||||
|
Status: new
|
||||||
|
Overview: # Specification: Session Context Snapshots & Visibility
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
This track focuses on transitioning from global context management to explicit session-scoped context. It introduces transparent...
|
||||||
|
---
|
||||||
|
Track: test_coverage_expansion_20260309
|
||||||
|
Status: new
|
||||||
|
Overview: Add more unit, simulation, and integration tests to increase coverage and stress test the application. The primary focus will be on critical and complex paths rather than aggressive total coverage percentage.
|
||||||
|
---
|
||||||
|
Track: test_harness_hardening_20260310
|
||||||
|
Status: new
|
||||||
|
Overview: This track focuses on stabilizing the local development and testing environment by hardening the Hook API and its associated `live_gui` test harness. The goal is to eliminate port conflicts, ensure graceful teardowns, and standardize state serialization, laying the groundwork for a future WebSockets implementation.
|
||||||
|
---
|
||||||
|
Track: tool_bias_tuning_20260308
|
||||||
|
Status: new
|
||||||
|
Overview: This track introduces a mechanism to influence AI agent tool selection by implementing a weighting and scoring system at the orchestration layer. Since model APIs do not natively support tool priority, this feature uses semantic nudging (tags in tool descriptions) and explicit system instructions to "bias" the agent toward preferred tools and parameters.
|
||||||
|
---
|
||||||
|
Track: tree_sitter_lua_mcp_tools_20260310
|
||||||
|
Status: new
|
||||||
|
Overview: Expand the Conductor's AI context-gathering and surgical editing capabilities by introducing full Tree-Sitter parsing support for the Lua programming language. This will bring Lua to feature-parity with the existing Python MCP tools, enabling deep AST-driven structural mapping, documentation extraction, and precise code modification.
|
||||||
|
---
|
||||||
|
Track: Tree-Sitter C/C++ MCP Tools
|
||||||
|
Status: pending
|
||||||
|
Overview: Add tree-sitter-based C and C++ parsing support to the MCP client, providing skeleton and outline tools for C/C++ codebases. Tools will be prefixed `ts_c_` and `ts_cpp_` to distinguish from existing Python tools and leave namespace open for future gencpp integration.
|
||||||
|
---
|
||||||
|
Track: ui_theme_overhaul_20260308
|
||||||
|
Status: new
|
||||||
|
Overview: This track aims to modernize the application's appearance by implementing a professional, high-fidelity UI theme using `imgui-bundle`'s native styling and theming capabilities. It focuses on improving typography, visual shapes, and introducing advanced features like custom shaders, multi-viewport support, and user-managed layout presets.
|
||||||
|
---
|
||||||
|
Track: undo_redo_history_20260311
|
||||||
|
Status: new
|
||||||
|
Overview: # Specification: Undo/Redo History Support
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
This track implements a robust, non-provider based Undo/Redo system within the Manual Slop GUI. It allows users to revert and redo common UI ...
|
||||||
|
---
|
||||||
|
Track: ux_sim_test_20260308
|
||||||
|
Status: unknown
|
||||||
|
Overview: No overview available.
|
||||||
|
---
|
||||||
|
Track: ux_sim_test_20260310
|
||||||
|
Status: unknown
|
||||||
|
Overview: No overview available.
|
||||||
|
---
|
||||||
|
Track: workspace_profiles_20260310
|
||||||
|
Status: new
|
||||||
|
Overview: Expand the existing GUI window management to support named "Workspace Profiles." This will allow users to save, manage, and instantly switch between complex multi-window, multi-viewport docking arrangements.
|
||||||
|
---
|
||||||
|
Track: zhipu_integration_20260308
|
||||||
|
Status: new
|
||||||
|
Overview: This track introduces support for Zhipu AI (z.ai) as a first-class model provider. It involves implementing a dedicated client in `src/ai_client.py` for the GLM series of models, updating configuration models, enhancing the GUI for provider selection, and integrating the provider into the tiered MMA architecture.
|
||||||
|
---
|
||||||
|
|
||||||
|
Please generate the implementation tracks for this request.
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
PATH: Epic Initialization — please produce tracks
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
Please generate the implementation tickets for this track.
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
Please read test.txt
|
||||||
|
You are assigned to Ticket T1.
|
||||||
|
Task Description: do something
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
role: tool
|
||||||
|
Here are the results: {"content": "done"}
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
PATH: Epic Initialization — please produce tracks
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
Please generate the implementation tickets for this track.
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
Please read test.txt
|
||||||
|
You are assigned to Ticket T1.
|
||||||
|
Task Description: do something
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
role: tool
|
||||||
|
Here are the results: {"content": "done"}
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
PATH: Epic Initialization — please produce tracks
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
Please generate the implementation tickets for this track.
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
Please read test.txt
|
||||||
|
You are assigned to Ticket T1.
|
||||||
|
Task Description: do something
|
||||||
|
------------------
|
||||||
|
--- MOCK INVOKED ---
|
||||||
|
ARGS: ['tests/mock_gemini_cli.py']
|
||||||
|
PROMPT:
|
||||||
|
role: tool
|
||||||
|
Here are the results: {"content": "done"}
|
||||||
|
------------------
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ active = "main"
|
|||||||
|
|
||||||
[discussions.main]
|
[discussions.main]
|
||||||
git_commit = ""
|
git_commit = ""
|
||||||
last_updated = "2026-03-11T23:45:09"
|
last_updated = "2026-03-12T20:34:43"
|
||||||
history = []
|
history = []
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ dependencies = [
|
|||||||
"tree-sitter-python>=0.25.0",
|
"tree-sitter-python>=0.25.0",
|
||||||
"mcp>=1.0.0",
|
"mcp>=1.0.0",
|
||||||
"pytest-timeout>=2.4.0",
|
"pytest-timeout>=2.4.0",
|
||||||
|
"pyopengl>=3.1.10",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
from imgui_bundle import hello_imgui, imgui
|
||||||
|
|
||||||
|
def on_gui():
|
||||||
|
imgui.text("Hello world")
|
||||||
|
|
||||||
|
params = hello_imgui.RunnerParams()
|
||||||
|
params.app_window_params.borderless = True
|
||||||
|
params.app_window_params.borderless_movable = True
|
||||||
|
params.app_window_params.borderless_resizable = True
|
||||||
|
params.app_window_params.borderless_closable = True
|
||||||
|
|
||||||
|
hello_imgui.run(params)
|
||||||
+142
-23
@@ -180,7 +180,8 @@ class AppController:
|
|||||||
"cache_read_input_tokens": 0,
|
"cache_read_input_tokens": 0,
|
||||||
"cache_creation_input_tokens": 0,
|
"cache_creation_input_tokens": 0,
|
||||||
"total_tokens": 0,
|
"total_tokens": 0,
|
||||||
"last_latency": 0.0
|
"last_latency": 0.0,
|
||||||
|
"percentage": 0.0
|
||||||
}
|
}
|
||||||
self.mma_tier_usage: Dict[str, Dict[str, Any]] = {
|
self.mma_tier_usage: Dict[str, Dict[str, Any]] = {
|
||||||
"Tier 1": {"input": 0, "output": 0, "provider": "gemini", "model": "gemini-3.1-pro-preview", "tool_preset": None},
|
"Tier 1": {"input": 0, "output": 0, "provider": "gemini", "model": "gemini-3.1-pro-preview", "tool_preset": None},
|
||||||
@@ -285,7 +286,9 @@ class AppController:
|
|||||||
self._gemini_cache_text: str = ""
|
self._gemini_cache_text: str = ""
|
||||||
self._last_stable_md: str = ''
|
self._last_stable_md: str = ''
|
||||||
self._token_stats: Dict[str, Any] = {}
|
self._token_stats: Dict[str, Any] = {}
|
||||||
self._token_stats_dirty: bool = False
|
self._comms_log_dirty: bool = True
|
||||||
|
self._tool_log_dirty: bool = True
|
||||||
|
self._token_stats_dirty: bool = True
|
||||||
self.ui_disc_truncate_pairs: int = 2
|
self.ui_disc_truncate_pairs: int = 2
|
||||||
self.ui_auto_scroll_comms: bool = True
|
self.ui_auto_scroll_comms: bool = True
|
||||||
self.ui_auto_scroll_tool_calls: bool = True
|
self.ui_auto_scroll_tool_calls: bool = True
|
||||||
@@ -293,10 +296,14 @@ class AppController:
|
|||||||
self._track_discussion_active: bool = False
|
self._track_discussion_active: bool = False
|
||||||
self._tier_stream_last_len: Dict[str, int] = {}
|
self._tier_stream_last_len: Dict[str, int] = {}
|
||||||
self.is_viewing_prior_session: bool = False
|
self.is_viewing_prior_session: bool = False
|
||||||
|
self._current_session_usage = None
|
||||||
|
self._current_mma_tier_usage = None
|
||||||
self.prior_session_entries: List[Dict[str, Any]] = []
|
self.prior_session_entries: List[Dict[str, Any]] = []
|
||||||
self.prior_tool_calls: List[Dict[str, Any]] = []
|
self.prior_tool_calls: List[Dict[str, Any]] = []
|
||||||
self.prior_disc_entries: List[Dict[str, Any]] = []
|
self.prior_disc_entries: List[Dict[str, Any]] = []
|
||||||
self.prior_mma_dashboard_state: Dict[str, Any] = {}
|
self.prior_mma_dashboard_state = {}
|
||||||
|
self._current_token_history = None
|
||||||
|
self._current_session_start_time = None
|
||||||
self.test_hooks_enabled: bool = ("--enable-test-hooks" in sys.argv) or (os.environ.get("SLOP_TEST_HOOKS") == "1")
|
self.test_hooks_enabled: bool = ("--enable-test-hooks" in sys.argv) or (os.environ.get("SLOP_TEST_HOOKS") == "1")
|
||||||
self.ui_manual_approve: bool = False
|
self.ui_manual_approve: bool = False
|
||||||
# Injection state
|
# Injection state
|
||||||
@@ -443,7 +450,7 @@ class AppController:
|
|||||||
return
|
return
|
||||||
target_path = self._inject_file_path
|
target_path = self._inject_file_path
|
||||||
if not os.path.isabs(target_path):
|
if not os.path.isabs(target_path):
|
||||||
target_path = os.path.join(self.ui_files_base_dir, target_path)
|
target_path = os.path.join(self.active_project_root, target_path)
|
||||||
if not os.path.exists(target_path):
|
if not os.path.exists(target_path):
|
||||||
self._inject_preview = ""
|
self._inject_preview = ""
|
||||||
return
|
return
|
||||||
@@ -530,6 +537,11 @@ class AppController:
|
|||||||
"payload": status
|
"payload": status
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def _trigger_gui_refresh(self):
|
||||||
|
with self._pending_gui_tasks_lock:
|
||||||
|
self._pending_gui_tasks.append({'action': 'set_comms_dirty'})
|
||||||
|
self._pending_gui_tasks.append({'action': 'set_tool_log_dirty'})
|
||||||
|
|
||||||
def _process_pending_gui_tasks(self) -> None:
|
def _process_pending_gui_tasks(self) -> None:
|
||||||
# Periodic telemetry broadcast
|
# Periodic telemetry broadcast
|
||||||
now = time.time()
|
now = time.time()
|
||||||
@@ -556,6 +568,10 @@ class AppController:
|
|||||||
# ...
|
# ...
|
||||||
if action == "refresh_api_metrics":
|
if action == "refresh_api_metrics":
|
||||||
self._refresh_api_metrics(task.get("payload", {}), md_content=self.last_md or None)
|
self._refresh_api_metrics(task.get("payload", {}), md_content=self.last_md or None)
|
||||||
|
elif action == 'set_comms_dirty':
|
||||||
|
self._comms_log_dirty = True
|
||||||
|
elif action == 'set_tool_log_dirty':
|
||||||
|
self._tool_log_dirty = True
|
||||||
elif action == "set_ai_status":
|
elif action == "set_ai_status":
|
||||||
self.ai_status = task.get("payload", "")
|
self.ai_status = task.get("payload", "")
|
||||||
sys.stderr.write(f"[DEBUG] Updated ai_status via task to: {self.ai_status}\n")
|
sys.stderr.write(f"[DEBUG] Updated ai_status via task to: {self.ai_status}\n")
|
||||||
@@ -850,7 +866,11 @@ class AppController:
|
|||||||
self.ui_separate_tier2 = False
|
self.ui_separate_tier2 = False
|
||||||
self.ui_separate_tier3 = False
|
self.ui_separate_tier3 = False
|
||||||
self.ui_separate_tier4 = False
|
self.ui_separate_tier4 = False
|
||||||
|
self.ui_separate_external_tools = False
|
||||||
self.config = models.load_config()
|
self.config = models.load_config()
|
||||||
|
path_info = paths.get_full_path_info()
|
||||||
|
self.ui_logs_dir = str(path_info['logs_dir']['path'])
|
||||||
|
self.ui_scripts_dir = str(path_info['scripts_dir']['path'])
|
||||||
theme.load_from_config(self.config)
|
theme.load_from_config(self.config)
|
||||||
ai_cfg = self.config.get("ai", {})
|
ai_cfg = self.config.get("ai", {})
|
||||||
self._current_provider = ai_cfg.get("provider", "gemini")
|
self._current_provider = ai_cfg.get("provider", "gemini")
|
||||||
@@ -886,6 +906,7 @@ class AppController:
|
|||||||
self.ui_shots_base_dir = self.project.get("screenshots", {}).get("base_dir", ".")
|
self.ui_shots_base_dir = self.project.get("screenshots", {}).get("base_dir", ".")
|
||||||
proj_meta = self.project.get("project", {})
|
proj_meta = self.project.get("project", {})
|
||||||
self.ui_project_git_dir = proj_meta.get("git_dir", "")
|
self.ui_project_git_dir = proj_meta.get("git_dir", "")
|
||||||
|
self.ui_project_conductor_dir = self.project.get('conductor', {}).get('dir', 'conductor')
|
||||||
self.ui_project_main_context = proj_meta.get("main_context", "")
|
self.ui_project_main_context = proj_meta.get("main_context", "")
|
||||||
self.ui_project_system_prompt = proj_meta.get("system_prompt", "")
|
self.ui_project_system_prompt = proj_meta.get("system_prompt", "")
|
||||||
self.ui_gemini_cli_path = self.project.get("gemini_cli", {}).get("binary_path", "gemini")
|
self.ui_gemini_cli_path = self.project.get("gemini_cli", {}).get("binary_path", "gemini")
|
||||||
@@ -958,7 +979,7 @@ class AppController:
|
|||||||
agent_tools_cfg = self.project.get("agent", {}).get("tools", {})
|
agent_tools_cfg = self.project.get("agent", {}).get("tools", {})
|
||||||
self.ui_agent_tools = {t: agent_tools_cfg.get(t, True) for t in models.AGENT_TOOL_NAMES}
|
self.ui_agent_tools = {t: agent_tools_cfg.get(t, True) for t in models.AGENT_TOOL_NAMES}
|
||||||
label = self.project.get("project", {}).get("name", "")
|
label = self.project.get("project", {}).get("name", "")
|
||||||
session_logger.open_session(label=label)
|
session_logger.reset_session(label=label)
|
||||||
# Trigger auto-start of MCP servers
|
# Trigger auto-start of MCP servers
|
||||||
self.event_queue.put('refresh_external_mcps', None)
|
self.event_queue.put('refresh_external_mcps', None)
|
||||||
|
|
||||||
@@ -980,6 +1001,12 @@ class AppController:
|
|||||||
if not path:
|
if not path:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not self.is_viewing_prior_session:
|
||||||
|
self._current_session_usage = copy.deepcopy(self.session_usage)
|
||||||
|
self._current_mma_tier_usage = copy.deepcopy(self.mma_tier_usage)
|
||||||
|
self._current_token_history = copy.deepcopy(self._token_history)
|
||||||
|
self._current_session_start_time = self._session_start_time
|
||||||
|
|
||||||
log_path = Path(path)
|
log_path = Path(path)
|
||||||
if log_path.is_dir():
|
if log_path.is_dir():
|
||||||
log_file = log_path / "comms.log"
|
log_file = log_path / "comms.log"
|
||||||
@@ -1014,6 +1041,15 @@ class AppController:
|
|||||||
|
|
||||||
entries = []
|
entries = []
|
||||||
disc_entries = []
|
disc_entries = []
|
||||||
|
paired_tools = {}
|
||||||
|
final_tool_calls = []
|
||||||
|
new_token_history = []
|
||||||
|
new_usage = {'input_tokens': 0, 'output_tokens': 0, 'cache_read_input_tokens': 0, 'cache_creation_input_tokens': 0, 'total_tokens': 0, 'last_latency': 0.0, 'percentage': 0.0}
|
||||||
|
new_mma_usage = copy.deepcopy(self.mma_tier_usage)
|
||||||
|
for t in new_mma_usage:
|
||||||
|
new_mma_usage[t]['input'] = 0
|
||||||
|
new_mma_usage[t]['output'] = 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(log_file, "r", encoding="utf-8") as f:
|
with open(log_file, "r", encoding="utf-8") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
@@ -1026,6 +1062,47 @@ class AppController:
|
|||||||
payload = entry.get("payload", {})
|
payload = entry.get("payload", {})
|
||||||
ts = entry.get("ts", "")
|
ts = entry.get("ts", "")
|
||||||
|
|
||||||
|
if kind == 'tool_call':
|
||||||
|
tid = payload.get('id') or payload.get('call_id')
|
||||||
|
script = payload.get('script') or json.dumps(payload.get('args', {}), indent=1)
|
||||||
|
script = _resolve_log_ref(script, session_dir)
|
||||||
|
entry_obj = {
|
||||||
|
'source_tier': entry.get('source_tier', 'main'),
|
||||||
|
'script': script,
|
||||||
|
'result': '', # Waiting for result
|
||||||
|
'ts': ts
|
||||||
|
}
|
||||||
|
if tid:
|
||||||
|
paired_tools[tid] = entry_obj
|
||||||
|
final_tool_calls.append(entry_obj)
|
||||||
|
elif kind == 'tool_result':
|
||||||
|
tid = payload.get('id') or payload.get('call_id')
|
||||||
|
output = payload.get('output', payload.get('content', ''))
|
||||||
|
output = _resolve_log_ref(output, session_dir)
|
||||||
|
if tid and tid in paired_tools:
|
||||||
|
paired_tools[tid]['result'] = output
|
||||||
|
else:
|
||||||
|
# Fallback: if no ID, try matching last entry in final_tool_calls that has no result
|
||||||
|
for old_call in reversed(final_tool_calls):
|
||||||
|
if not old_call['result']:
|
||||||
|
old_call['result'] = output
|
||||||
|
break
|
||||||
|
|
||||||
|
if kind == 'response' and 'usage' in payload:
|
||||||
|
u = payload['usage']
|
||||||
|
for k in ['input_tokens', 'output_tokens', 'cache_read_input_tokens', 'cache_creation_input_tokens', 'total_tokens']:
|
||||||
|
if k in new_usage: new_usage[k] += u.get(k, 0) or 0
|
||||||
|
tier = entry.get('source_tier', 'main')
|
||||||
|
if tier in new_mma_usage:
|
||||||
|
new_mma_usage[tier]['input'] += u.get('input_tokens', 0) or 0
|
||||||
|
new_mma_usage[tier]['output'] += u.get('output_tokens', 0) or 0
|
||||||
|
new_token_history.append({
|
||||||
|
'time': ts,
|
||||||
|
'input': u.get('input_tokens', 0) or 0,
|
||||||
|
'output': u.get('output_tokens', 0) or 0,
|
||||||
|
'model': entry.get('model', 'unknown')
|
||||||
|
})
|
||||||
|
|
||||||
if kind == "history_add":
|
if kind == "history_add":
|
||||||
content = payload.get("content", payload.get("text", payload.get("message", "")))
|
content = payload.get("content", payload.get("text", payload.get("message", "")))
|
||||||
content = _resolve_log_ref(content, session_dir)
|
content = _resolve_log_ref(content, session_dir)
|
||||||
@@ -1082,11 +1159,47 @@ class AppController:
|
|||||||
self._set_status(f"log load error: {e}")
|
self._set_status(f"log load error: {e}")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.session_usage = new_usage
|
||||||
|
self.mma_tier_usage = new_mma_usage
|
||||||
|
self._token_history = new_token_history
|
||||||
|
if new_token_history:
|
||||||
|
try:
|
||||||
|
import datetime
|
||||||
|
first_ts = new_token_history[0]['time']
|
||||||
|
dt = datetime.datetime.strptime(first_ts, '%Y-%m-%dT%H:%M:%S')
|
||||||
|
self._session_start_time = dt.timestamp()
|
||||||
|
except:
|
||||||
|
self._session_start_time = time.time()
|
||||||
self.prior_session_entries = entries
|
self.prior_session_entries = entries
|
||||||
self.prior_disc_entries = disc_entries
|
self.prior_disc_entries = disc_entries
|
||||||
|
self.prior_tool_calls = final_tool_calls
|
||||||
self.is_viewing_prior_session = True
|
self.is_viewing_prior_session = True
|
||||||
|
self._trigger_gui_refresh()
|
||||||
self._set_status(f"viewing prior session: {session_dir.name} ({len(entries)} entries)")
|
self._set_status(f"viewing prior session: {session_dir.name} ({len(entries)} entries)")
|
||||||
|
|
||||||
|
|
||||||
|
def cb_exit_prior_session(self):
|
||||||
|
self.is_viewing_prior_session = False
|
||||||
|
if self._current_session_usage:
|
||||||
|
self.session_usage = self._current_session_usage
|
||||||
|
self._current_session_usage = None
|
||||||
|
if self._current_mma_tier_usage:
|
||||||
|
self.mma_tier_usage = self._current_mma_tier_usage
|
||||||
|
self._current_mma_tier_usage = None
|
||||||
|
|
||||||
|
if self._current_token_history is not None:
|
||||||
|
self._token_history = self._current_token_history
|
||||||
|
self._current_token_history = None
|
||||||
|
if self._current_session_start_time is not None:
|
||||||
|
self._session_start_time = self._current_session_start_time
|
||||||
|
self._current_session_start_time = None
|
||||||
|
|
||||||
|
self.prior_session_entries.clear()
|
||||||
|
self.prior_disc_entries.clear()
|
||||||
|
self.prior_tool_calls.clear()
|
||||||
|
self._trigger_gui_refresh()
|
||||||
|
self._set_status('idle')
|
||||||
|
|
||||||
def cb_prune_logs(self) -> None:
|
def cb_prune_logs(self) -> None:
|
||||||
"""Manually triggers the log pruning process with aggressive thresholds."""
|
"""Manually triggers the log pruning process with aggressive thresholds."""
|
||||||
self._set_status("Manual prune started (Age > 0d, Size < 100KB)...")
|
self._set_status("Manual prune started (Age > 0d, Size < 100KB)...")
|
||||||
@@ -1287,6 +1400,7 @@ class AppController:
|
|||||||
|
|
||||||
def _handle_request_event(self, event: events.UserRequestEvent) -> None:
|
def _handle_request_event(self, event: events.UserRequestEvent) -> None:
|
||||||
"""Processes a UserRequestEvent by calling the AI client."""
|
"""Processes a UserRequestEvent by calling the AI client."""
|
||||||
|
self._set_status('sending...')
|
||||||
ai_client.set_current_tier(None) # Ensure main discussion is untagged
|
ai_client.set_current_tier(None) # Ensure main discussion is untagged
|
||||||
# Clear response area for new turn
|
# Clear response area for new turn
|
||||||
self.ai_response = ""
|
self.ai_response = ""
|
||||||
@@ -1675,7 +1789,7 @@ class AppController:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=f"Context aggregation failure: {e}")
|
raise HTTPException(status_code=500, detail=f"Context aggregation failure: {e}")
|
||||||
user_msg = req.prompt
|
user_msg = req.prompt
|
||||||
base_dir = self.ui_files_base_dir
|
base_dir = self.active_project_root
|
||||||
csp = filter(bool, [self.ui_global_system_prompt.strip(), self.ui_project_system_prompt.strip()])
|
csp = filter(bool, [self.ui_global_system_prompt.strip(), self.ui_project_system_prompt.strip()])
|
||||||
ai_client.set_custom_system_prompt("\n\n".join(csp))
|
ai_client.set_custom_system_prompt("\n\n".join(csp))
|
||||||
temp = req.temperature if req.temperature is not None else self.temperature
|
temp = req.temperature if req.temperature is not None else self.temperature
|
||||||
@@ -1783,7 +1897,7 @@ class AppController:
|
|||||||
return {
|
return {
|
||||||
"files": [f.get("path") if isinstance(f, dict) else str(f) for f in file_items],
|
"files": [f.get("path") if isinstance(f, dict) else str(f) for f in file_items],
|
||||||
"screenshots": screenshots,
|
"screenshots": screenshots,
|
||||||
"files_base_dir": self.ui_files_base_dir,
|
"files_base_dir": self.active_project_root,
|
||||||
"markdown": md,
|
"markdown": md,
|
||||||
"discussion": disc_text
|
"discussion": disc_text
|
||||||
}
|
}
|
||||||
@@ -1807,7 +1921,6 @@ class AppController:
|
|||||||
|
|
||||||
def _cb_project_save(self) -> None:
|
def _cb_project_save(self) -> None:
|
||||||
self._flush_to_project()
|
self._flush_to_project()
|
||||||
self._save_active_project()
|
|
||||||
self._flush_to_config()
|
self._flush_to_config()
|
||||||
models.save_config(self.config)
|
models.save_config(self.config)
|
||||||
self._set_status("config saved")
|
self._set_status("config saved")
|
||||||
@@ -1823,7 +1936,6 @@ class AppController:
|
|||||||
self._set_status(f"project file not found: {path}")
|
self._set_status(f"project file not found: {path}")
|
||||||
return
|
return
|
||||||
self._flush_to_project()
|
self._flush_to_project()
|
||||||
self._save_active_project()
|
|
||||||
try:
|
try:
|
||||||
self.project = project_manager.load_project(path)
|
self.project = project_manager.load_project(path)
|
||||||
self.active_project_path = path
|
self.active_project_path = path
|
||||||
@@ -1900,9 +2012,9 @@ class AppController:
|
|||||||
with self._disc_entries_lock:
|
with self._disc_entries_lock:
|
||||||
self.disc_entries = models.parse_history_entries(track_history, self.disc_roles)
|
self.disc_entries = models.parse_history_entries(track_history, self.disc_roles)
|
||||||
|
|
||||||
self.preset_manager.project_root = Path(self.ui_files_base_dir)
|
self.preset_manager.project_root = Path(self.active_project_root)
|
||||||
self.presets = self.preset_manager.load_all()
|
self.presets = self.preset_manager.load_all()
|
||||||
self.tool_preset_manager.project_root = Path(self.ui_files_base_dir)
|
self.tool_preset_manager.project_root = Path(self.active_project_root)
|
||||||
self.tool_presets = self.tool_preset_manager.load_all_presets()
|
self.tool_presets = self.tool_preset_manager.load_all_presets()
|
||||||
self.bias_profiles = self.tool_preset_manager.load_all_bias_profiles()
|
self.bias_profiles = self.tool_preset_manager.load_all_bias_profiles()
|
||||||
|
|
||||||
@@ -2001,7 +2113,8 @@ class AppController:
|
|||||||
def _save_active_project(self) -> None:
|
def _save_active_project(self) -> None:
|
||||||
if self.active_project_path:
|
if self.active_project_path:
|
||||||
try:
|
try:
|
||||||
project_manager.save_project(self.project, self.active_project_path)
|
cleaned = project_manager.clean_nones(self.project)
|
||||||
|
project_manager.save_project(cleaned, self.active_project_path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._set_status(f"save error: {e}")
|
self._set_status(f"save error: {e}")
|
||||||
|
|
||||||
@@ -2028,7 +2141,7 @@ class AppController:
|
|||||||
def _flush_disc_entries_to_project(self) -> None:
|
def _flush_disc_entries_to_project(self) -> None:
|
||||||
history_strings = [project_manager.entry_to_str(e) for e in self.disc_entries]
|
history_strings = [project_manager.entry_to_str(e) for e in self.disc_entries]
|
||||||
if self.active_track and self._track_discussion_active:
|
if self.active_track and self._track_discussion_active:
|
||||||
project_manager.save_track_history(self.active_track.id, history_strings, self.ui_files_base_dir)
|
project_manager.save_track_history(self.active_track.id, history_strings, self.active_project_root)
|
||||||
return
|
return
|
||||||
disc_sec = self.project.setdefault("discussion", {})
|
disc_sec = self.project.setdefault("discussion", {})
|
||||||
discussions = disc_sec.setdefault("discussions", {})
|
discussions = disc_sec.setdefault("discussions", {})
|
||||||
@@ -2198,7 +2311,7 @@ class AppController:
|
|||||||
file_path, definition, line = res
|
file_path, definition, line = res
|
||||||
user_msg += f'\n\n[Definition: {symbol} from {file_path} (line {line})]\n```python\n{definition}\n```'
|
user_msg += f'\n\n[Definition: {symbol} from {file_path} (line {line})]\n```python\n{definition}\n```'
|
||||||
|
|
||||||
base_dir = self.ui_files_base_dir
|
base_dir = self.active_project_root
|
||||||
sys.stderr.write(f"[DEBUG] _do_generate success. Prompt: {user_msg[:50]}...\n")
|
sys.stderr.write(f"[DEBUG] _do_generate success. Prompt: {user_msg[:50]}...\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
# Prepare event payload
|
# Prepare event payload
|
||||||
@@ -2221,7 +2334,7 @@ class AppController:
|
|||||||
threading.Thread(target=worker, daemon=True).start()
|
threading.Thread(target=worker, daemon=True).start()
|
||||||
|
|
||||||
def _recalculate_session_usage(self) -> None:
|
def _recalculate_session_usage(self) -> None:
|
||||||
usage = {"input_tokens": 0, "output_tokens": 0, "cache_read_input_tokens": 0, "cache_creation_input_tokens": 0, "total_tokens": 0, "last_latency": 0.0}
|
usage = {"input_tokens": 0, "output_tokens": 0, "cache_read_input_tokens": 0, "cache_creation_input_tokens": 0, "total_tokens": 0, "last_latency": 0.0, "percentage": self.session_usage.get("percentage", 0.0)}
|
||||||
for entry in ai_client.get_comms_log():
|
for entry in ai_client.get_comms_log():
|
||||||
if entry.get("kind") == "response" and "usage" in entry.get("payload", {}):
|
if entry.get("kind") == "response" and "usage" in entry.get("payload", {}):
|
||||||
u = entry["payload"]["usage"]
|
u = entry["payload"]["usage"]
|
||||||
@@ -2236,6 +2349,8 @@ class AppController:
|
|||||||
def _refresh_api_metrics(self, payload: dict[str, Any], md_content: str | None = None) -> None:
|
def _refresh_api_metrics(self, payload: dict[str, Any], md_content: str | None = None) -> None:
|
||||||
if "latency" in payload:
|
if "latency" in payload:
|
||||||
self.session_usage["last_latency"] = payload["latency"]
|
self.session_usage["last_latency"] = payload["latency"]
|
||||||
|
if "usage" in payload and "percentage" in payload["usage"]:
|
||||||
|
self.session_usage["percentage"] = payload["usage"]["percentage"]
|
||||||
self._recalculate_session_usage()
|
self._recalculate_session_usage()
|
||||||
if md_content is not None:
|
if md_content is not None:
|
||||||
stats = ai_client.get_token_stats(md_content)
|
stats = ai_client.get_token_stats(md_content)
|
||||||
@@ -2291,6 +2406,7 @@ class AppController:
|
|||||||
proj["screenshots"]["paths"] = self.screenshots
|
proj["screenshots"]["paths"] = self.screenshots
|
||||||
proj.setdefault("project", {})
|
proj.setdefault("project", {})
|
||||||
proj["project"]["git_dir"] = self.ui_project_git_dir
|
proj["project"]["git_dir"] = self.ui_project_git_dir
|
||||||
|
proj.setdefault("conductor", {})["dir"] = self.ui_project_conductor_dir
|
||||||
proj["project"]["system_prompt"] = self.ui_project_system_prompt
|
proj["project"]["system_prompt"] = self.ui_project_system_prompt
|
||||||
proj["project"]["main_context"] = self.ui_project_main_context
|
proj["project"]["main_context"] = self.ui_project_main_context
|
||||||
proj["project"]["active_preset"] = self.ui_project_preset_name
|
proj["project"]["active_preset"] = self.ui_project_preset_name
|
||||||
@@ -2316,6 +2432,9 @@ class AppController:
|
|||||||
else:
|
else:
|
||||||
mma_sec["active_track"] = None
|
mma_sec["active_track"] = None
|
||||||
|
|
||||||
|
cleaned_proj = project_manager.clean_nones(proj)
|
||||||
|
project_manager.save_project(cleaned_proj, self.active_project_path)
|
||||||
|
|
||||||
def _flush_to_config(self) -> None:
|
def _flush_to_config(self) -> None:
|
||||||
self.config["ai"] = {
|
self.config["ai"] = {
|
||||||
"provider": self.current_provider,
|
"provider": self.current_provider,
|
||||||
@@ -2336,6 +2455,7 @@ class AppController:
|
|||||||
"separate_message_panel": getattr(self, "ui_separate_message_panel", False),
|
"separate_message_panel": getattr(self, "ui_separate_message_panel", False),
|
||||||
"separate_response_panel": getattr(self, "ui_separate_response_panel", False),
|
"separate_response_panel": getattr(self, "ui_separate_response_panel", False),
|
||||||
"separate_tool_calls_panel": getattr(self, "ui_separate_tool_calls_panel", False),
|
"separate_tool_calls_panel": getattr(self, "ui_separate_tool_calls_panel", False),
|
||||||
|
"separate_external_tools": getattr(self, "ui_separate_external_tools", False),
|
||||||
"separate_task_dag": self.ui_separate_task_dag,
|
"separate_task_dag": self.ui_separate_task_dag,
|
||||||
"separate_usage_analytics": self.ui_separate_usage_analytics,
|
"separate_usage_analytics": self.ui_separate_usage_analytics,
|
||||||
"separate_tier1": self.ui_separate_tier1,
|
"separate_tier1": self.ui_separate_tier1,
|
||||||
@@ -2352,7 +2472,6 @@ class AppController:
|
|||||||
def _do_generate(self) -> tuple[str, Path, list[dict[str, Any]], str, str]:
|
def _do_generate(self) -> tuple[str, Path, list[dict[str, Any]], str, str]:
|
||||||
"""Returns (full_md, output_path, file_items, stable_md, discussion_text)."""
|
"""Returns (full_md, output_path, file_items, stable_md, discussion_text)."""
|
||||||
self._flush_to_project()
|
self._flush_to_project()
|
||||||
self._save_active_project()
|
|
||||||
self._flush_to_config()
|
self._flush_to_config()
|
||||||
models.save_config(self.config)
|
models.save_config(self.config)
|
||||||
track_id = self.active_track.id if self.active_track else None
|
track_id = self.active_track.id if self.active_track else None
|
||||||
@@ -2379,7 +2498,7 @@ class AppController:
|
|||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
proj = project_manager.load_project(self.active_project_path)
|
proj = project_manager.load_project(self.active_project_path)
|
||||||
flat = project_manager.flat_config(self.project)
|
flat = project_manager.flat_config(self.project)
|
||||||
file_items = aggregate.build_file_items(Path(self.ui_files_base_dir), flat.get("files", {}).get("paths", []))
|
file_items = aggregate.build_file_items(Path(self.active_project_root), flat.get("files", {}).get("paths", []))
|
||||||
|
|
||||||
_t1_baseline = len(ai_client.get_comms_log())
|
_t1_baseline = len(ai_client.get_comms_log())
|
||||||
tracks = orchestrator_pm.generate_tracks(self.ui_epic_input, flat, file_items, history_summary=history)
|
tracks = orchestrator_pm.generate_tracks(self.ui_epic_input, flat, file_items, history_summary=history)
|
||||||
@@ -2431,7 +2550,7 @@ class AppController:
|
|||||||
for i, file_path in enumerate(files_to_scan):
|
for i, file_path in enumerate(files_to_scan):
|
||||||
try:
|
try:
|
||||||
self._set_status(f"Phase 2: Scanning files ({i+1}/{len(files_to_scan)})...")
|
self._set_status(f"Phase 2: Scanning files ({i+1}/{len(files_to_scan)})...")
|
||||||
abs_path = Path(self.ui_files_base_dir) / file_path
|
abs_path = Path(self.active_project_root) / file_path
|
||||||
if abs_path.exists() and abs_path.suffix == ".py":
|
if abs_path.exists() and abs_path.suffix == ".py":
|
||||||
with open(abs_path, "r", encoding="utf-8") as f:
|
with open(abs_path, "r", encoding="utf-8") as f:
|
||||||
code = f.read()
|
code = f.read()
|
||||||
@@ -2533,7 +2652,7 @@ class AppController:
|
|||||||
# Initialize track state in the filesystem
|
# Initialize track state in the filesystem
|
||||||
meta = models.Metadata(id=track_id, name=title, status="todo", created_at=datetime.now(), updated_at=datetime.now())
|
meta = models.Metadata(id=track_id, name=title, status="todo", created_at=datetime.now(), updated_at=datetime.now())
|
||||||
state = models.TrackState(metadata=meta, discussion=[], tasks=tickets)
|
state = models.TrackState(metadata=meta, discussion=[], tasks=tickets)
|
||||||
project_manager.save_track_state(track_id, state, self.ui_files_base_dir)
|
project_manager.save_track_state(track_id, state, self.active_project_root)
|
||||||
# Add to memory and notify UI
|
# Add to memory and notify UI
|
||||||
self.tracks.append({"id": track_id, "title": title, "status": "todo"})
|
self.tracks.append({"id": track_id, "title": title, "status": "todo"})
|
||||||
with self._pending_gui_tasks_lock:
|
with self._pending_gui_tasks_lock:
|
||||||
@@ -2598,7 +2717,7 @@ class AppController:
|
|||||||
file_path = data.get("file_path")
|
file_path = data.get("file_path")
|
||||||
if file_path:
|
if file_path:
|
||||||
if not os.path.isabs(file_path):
|
if not os.path.isabs(file_path):
|
||||||
file_path = os.path.relpath(file_path, self.ui_files_base_dir)
|
file_path = os.path.relpath(file_path, self.active_project_root)
|
||||||
existing = next((f for f in self.files if (f.path if hasattr(f, "path") else str(f)) == file_path), None)
|
existing = next((f for f in self.files if (f.path if hasattr(f, "path") else str(f)) == file_path), None)
|
||||||
if not existing:
|
if not existing:
|
||||||
item = models.FileItem(path=file_path)
|
item = models.FileItem(path=file_path)
|
||||||
@@ -2626,7 +2745,7 @@ class AppController:
|
|||||||
self._push_mma_state_update()
|
self._push_mma_state_update()
|
||||||
|
|
||||||
def _cb_run_conductor_setup(self) -> None:
|
def _cb_run_conductor_setup(self) -> None:
|
||||||
base = paths.get_conductor_dir()
|
base = paths.get_conductor_dir(project_path=self.active_project_root)
|
||||||
if not base.exists():
|
if not base.exists():
|
||||||
self.ui_conductor_setup_summary = f"Error: {base}/ directory not found."
|
self.ui_conductor_setup_summary = f"Error: {base}/ directory not found."
|
||||||
return
|
return
|
||||||
@@ -2683,7 +2802,7 @@ class AppController:
|
|||||||
# Sync active_tickets (list of dicts) back to active_track.tickets (list of models.Ticket objects)
|
# Sync active_tickets (list of dicts) back to active_track.tickets (list of models.Ticket objects)
|
||||||
self.active_track.tickets = [models.Ticket.from_dict(t) for t in self.active_tickets]
|
self.active_track.tickets = [models.Ticket.from_dict(t) for t in self.active_tickets]
|
||||||
# Save the state to disk
|
# Save the state to disk
|
||||||
existing = project_manager.load_track_state(self.active_track.id, self.ui_files_base_dir)
|
existing = project_manager.load_track_state(self.active_track.id, self.active_project_root)
|
||||||
meta = models.Metadata(
|
meta = models.Metadata(
|
||||||
id=self.active_track.id,
|
id=self.active_track.id,
|
||||||
name=self.active_track.description,
|
name=self.active_track.description,
|
||||||
@@ -2696,5 +2815,5 @@ class AppController:
|
|||||||
discussion=existing.discussion if existing else [],
|
discussion=existing.discussion if existing else [],
|
||||||
tasks=self.active_track.tickets
|
tasks=self.active_track.tickets
|
||||||
)
|
)
|
||||||
project_manager.save_track_state(self.active_track.id, state, self.ui_files_base_dir)
|
project_manager.save_track_state(self.active_track.id, state, self.active_project_root)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ class BackgroundShader:
|
|||||||
self.ctx: Optional[nvg.Context] = None
|
self.ctx: Optional[nvg.Context] = None
|
||||||
|
|
||||||
def render(self, width: float, height: float):
|
def render(self, width: float, height: float):
|
||||||
if not self.enabled:
|
if not self.enabled or width <= 0 or height <= 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
# In imgui-bundle, hello_imgui handles the background.
|
# In imgui-bundle, hello_imgui handles the background.
|
||||||
|
|||||||
@@ -102,6 +102,15 @@ class AsyncEventQueue:
|
|||||||
"""
|
"""
|
||||||
return self._queue.get()
|
return self._queue.get()
|
||||||
|
|
||||||
|
def empty(self) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if the queue is empty.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if the queue is empty, False otherwise.
|
||||||
|
"""
|
||||||
|
return self._queue.empty()
|
||||||
|
|
||||||
def task_done(self) -> None:
|
def task_done(self) -> None:
|
||||||
"""Signals that a formerly enqueued task is complete."""
|
"""Signals that a formerly enqueued task is complete."""
|
||||||
self._queue.task_done()
|
self._queue.task_done()
|
||||||
|
|||||||
+377
-95
@@ -6,6 +6,7 @@ import math
|
|||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import copy
|
import copy
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tkinter import filedialog, Tk
|
from tkinter import filedialog, Tk
|
||||||
@@ -20,6 +21,7 @@ from src import theme_2 as theme
|
|||||||
from src import theme_nerv_fx as theme_fx
|
from src import theme_nerv_fx as theme_fx
|
||||||
from src import api_hooks
|
from src import api_hooks
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import OpenGL.GL as gl
|
||||||
from src import log_registry
|
from src import log_registry
|
||||||
from src import log_pruner
|
from src import log_pruner
|
||||||
from src import models
|
from src import models
|
||||||
@@ -27,7 +29,15 @@ from src import app_controller
|
|||||||
from src import mcp_client
|
from src import mcp_client
|
||||||
from src import markdown_helper
|
from src import markdown_helper
|
||||||
from src import bg_shader
|
from src import bg_shader
|
||||||
|
from src.shader_manager import ShaderManager
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
|
if sys.platform == "win32":
|
||||||
|
import win32gui
|
||||||
|
import win32con
|
||||||
|
else:
|
||||||
|
win32gui = None
|
||||||
|
win32con = None
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed
|
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed
|
||||||
@@ -177,31 +187,43 @@ class App:
|
|||||||
self.ui_separate_tier2 = gui_cfg.get("separate_tier2", False)
|
self.ui_separate_tier2 = gui_cfg.get("separate_tier2", False)
|
||||||
self.ui_separate_tier3 = gui_cfg.get("separate_tier3", False)
|
self.ui_separate_tier3 = gui_cfg.get("separate_tier3", False)
|
||||||
self.ui_separate_tier4 = gui_cfg.get("separate_tier4", False)
|
self.ui_separate_tier4 = gui_cfg.get("separate_tier4", False)
|
||||||
|
self.ui_separate_external_tools = gui_cfg.get('separate_external_tools', False)
|
||||||
self.show_windows.setdefault("Usage Analytics", False)
|
self.show_windows.setdefault("Usage Analytics", False)
|
||||||
self.show_windows.setdefault("Tier 1: Strategy", False)
|
self.show_windows.setdefault("Tier 1: Strategy", False)
|
||||||
self.show_windows.setdefault("Tier 2: Tech Lead", False)
|
self.show_windows.setdefault("Tier 2: Tech Lead", False)
|
||||||
self.show_windows.setdefault("Tier 3: Workers", False)
|
self.show_windows.setdefault("Tier 3: Workers", False)
|
||||||
self.show_windows.setdefault("Tier 4: QA", False)
|
self.show_windows.setdefault("Tier 4: QA", False)
|
||||||
|
self.show_windows.setdefault('External Tools', False)
|
||||||
|
self.show_windows.setdefault('Shader Editor', False)
|
||||||
self.ui_multi_viewport = gui_cfg.get("multi_viewport", False)
|
self.ui_multi_viewport = gui_cfg.get("multi_viewport", False)
|
||||||
self.layout_presets = self.config.get("layout_presets", {})
|
self.layout_presets = self.config.get("layout_presets", {})
|
||||||
self._new_preset_name = ""
|
self._new_preset_name = ""
|
||||||
self._show_save_preset_modal = False
|
self._show_save_preset_modal = False
|
||||||
self._comms_log_cache: list[dict[str, Any]] = []
|
self._comms_log_cache: list[dict[str, Any]] = []
|
||||||
self._comms_log_dirty: bool = True
|
|
||||||
self._tool_log_cache: list[dict[str, Any]] = []
|
self._tool_log_cache: list[dict[str, Any]] = []
|
||||||
self._tool_log_dirty: bool = True
|
|
||||||
self._last_ui_focus_agent: Optional[str] = None
|
self._last_ui_focus_agent: Optional[str] = None
|
||||||
self._log_registry: Optional[log_registry.LogRegistry] = None
|
self._log_registry: Optional[log_registry.LogRegistry] = None
|
||||||
self.perf_profiling_enabled = False
|
self.perf_profiling_enabled = False
|
||||||
self.perf_show_graphs: dict[str, bool] = {}
|
self.perf_show_graphs: dict[str, bool] = {}
|
||||||
self._token_stats: dict[str, Any] = {}
|
self._token_stats: dict[str, Any] = {}
|
||||||
self._token_stats_dirty: bool = True
|
|
||||||
self.perf_history: dict[str, list] = {"frame_time": [0.0] * 100, "fps": [0.0] * 100}
|
self.perf_history: dict[str, list] = {"frame_time": [0.0] * 100, "fps": [0.0] * 100}
|
||||||
self._nerv_crt = theme_fx.CRTFilter()
|
self._nerv_crt = theme_fx.CRTFilter()
|
||||||
self.ui_crt_filter = True
|
self.ui_crt_filter = True
|
||||||
self._nerv_alert = theme_fx.AlertPulsing()
|
self._nerv_alert = theme_fx.AlertPulsing()
|
||||||
self._nerv_flicker = theme_fx.StatusFlicker()
|
self._nerv_flicker = theme_fx.StatusFlicker()
|
||||||
self.ui_tool_filter_category = "All"
|
self.ui_tool_filter_category = "All"
|
||||||
|
self.ui_discussion_split_h = 300.0
|
||||||
|
self.shader_uniforms = {
|
||||||
|
'crt': 1.0,
|
||||||
|
'scanline': 0.5,
|
||||||
|
'bloom': 0.8,
|
||||||
|
'frosted_blur_radius': theme.get_frosted_blur_radius(),
|
||||||
|
'frosted_tint_intensity': theme.get_frosted_tint_intensity(),
|
||||||
|
'frosted_opacity': theme.get_frosted_opacity()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.shader_manager = ShaderManager()
|
||||||
|
self.start_time = time.time()
|
||||||
|
|
||||||
def _handle_approve_tool(self, user_data=None) -> None:
|
def _handle_approve_tool(self, user_data=None) -> None:
|
||||||
"""UI-level wrapper for approving a pending tool execution ask."""
|
"""UI-level wrapper for approving a pending tool execution ask."""
|
||||||
@@ -331,6 +353,111 @@ class App:
|
|||||||
imgui.pop_style_var(2)
|
imgui.pop_style_var(2)
|
||||||
imgui.pop_id()
|
imgui.pop_id()
|
||||||
|
|
||||||
|
def _render_frosted_background(self, pos: imgui.ImVec2, size: imgui.ImVec2) -> None:
|
||||||
|
if not theme.get_frosted_glass_enabled(): return
|
||||||
|
if size.x <= 0 or size.y <= 0: return
|
||||||
|
try:
|
||||||
|
ws = immapp.get_window_size()
|
||||||
|
display_size = imgui.ImVec2(float(ws[0]), float(ws[1]))
|
||||||
|
except Exception:
|
||||||
|
display_size = imgui.get_io().display_size
|
||||||
|
|
||||||
|
if display_size.x <= 0 or display_size.y <= 0: return
|
||||||
|
|
||||||
|
uv_min = imgui.ImVec2(pos.x / display_size.x, 1.0 - pos.y / display_size.y)
|
||||||
|
uv_max = imgui.ImVec2((pos.x + size.x) / display_size.x, 1.0 - (pos.y + size.y) / display_size.y)
|
||||||
|
|
||||||
|
if self.shader_manager.blur_tex:
|
||||||
|
imgui.get_window_draw_list().add_image(
|
||||||
|
imgui.ImTextureRef(self.shader_manager.blur_tex),
|
||||||
|
pos,
|
||||||
|
imgui.ImVec2(pos.x + size.x, pos.y + size.y),
|
||||||
|
uv_min,
|
||||||
|
uv_max
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Fallback: semi-transparent rectangle to help debug visibility
|
||||||
|
imgui.get_window_draw_list().add_rect_filled(
|
||||||
|
pos,
|
||||||
|
imgui.ImVec2(pos.x + size.x, pos.y + size.y),
|
||||||
|
imgui.get_color_u32(vec4(30, 30, 40, 0.7))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Increase contrast with a 0.2 alpha tint overlay
|
||||||
|
imgui.get_window_draw_list().add_rect_filled(
|
||||||
|
pos,
|
||||||
|
imgui.ImVec2(pos.x + size.x, pos.y + size.y),
|
||||||
|
imgui.get_color_u32(vec4(0, 0, 0, 0.2))
|
||||||
|
)
|
||||||
|
|
||||||
|
def _begin_window(self, name: str, p_open: Any = None, flags: int = 0) -> tuple[bool, Any]:
|
||||||
|
frosted = theme.get_frosted_glass_enabled()
|
||||||
|
if frosted:
|
||||||
|
imgui.push_style_color(imgui.Col_.window_bg, vec4(0, 0, 0, 0))
|
||||||
|
|
||||||
|
expanded, opened = imgui.begin(name, p_open, flags)
|
||||||
|
|
||||||
|
if expanded and frosted:
|
||||||
|
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
|
||||||
|
|
||||||
|
return expanded, opened
|
||||||
|
|
||||||
|
def _end_window(self) -> None:
|
||||||
|
imgui.end()
|
||||||
|
if theme.get_frosted_glass_enabled():
|
||||||
|
imgui.pop_style_color()
|
||||||
|
def _render_operations_hub(self) -> None:
|
||||||
|
exp, opened = self._begin_window("Operations Hub", self.show_windows["Operations Hub"])
|
||||||
|
self.show_windows["Operations Hub"] = bool(opened)
|
||||||
|
if exp:
|
||||||
|
imgui.text("Focus Agent:")
|
||||||
|
imgui.same_line()
|
||||||
|
focus_label = self.ui_focus_agent or "All"
|
||||||
|
if imgui.begin_combo("##focus_agent", focus_label, imgui.ComboFlags_.width_fit_preview):
|
||||||
|
if imgui.selectable("All", self.ui_focus_agent is None)[0]:
|
||||||
|
self.ui_focus_agent = None
|
||||||
|
for tier in ["Tier 2", "Tier 3", "Tier 4"]:
|
||||||
|
if imgui.selectable(tier, self.ui_focus_agent == tier)[0]:
|
||||||
|
self.ui_focus_agent = tier
|
||||||
|
imgui.end_combo()
|
||||||
|
imgui.same_line()
|
||||||
|
if self.ui_focus_agent:
|
||||||
|
if imgui.button("x##clear_focus"):
|
||||||
|
self.ui_focus_agent = None
|
||||||
|
|
||||||
|
imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
|
||||||
|
ch1, self.ui_separate_tool_calls_panel = imgui.checkbox("Pop Out Tool Calls", self.ui_separate_tool_calls_panel)
|
||||||
|
if ch1: self.show_windows["Tool Calls"] = self.ui_separate_tool_calls_panel
|
||||||
|
imgui.same_line()
|
||||||
|
ch2, self.ui_separate_usage_analytics = imgui.checkbox("Pop Out Usage Analytics", self.ui_separate_usage_analytics)
|
||||||
|
if ch2: self.show_windows["Usage Analytics"] = self.ui_separate_usage_analytics
|
||||||
|
imgui.same_line()
|
||||||
|
ch3, self.ui_separate_external_tools = imgui.checkbox('Pop Out External Tools', self.ui_separate_external_tools)
|
||||||
|
if ch3: self.show_windows['External Tools'] = self.ui_separate_external_tools
|
||||||
|
imgui.pop_style_var()
|
||||||
|
|
||||||
|
show_tc_tab = not self.ui_separate_tool_calls_panel
|
||||||
|
show_usage_tab = not self.ui_separate_usage_analytics
|
||||||
|
|
||||||
|
if imgui.begin_tab_bar("ops_tabs"):
|
||||||
|
if imgui.begin_tab_item("Comms History")[0]:
|
||||||
|
self._render_comms_history_panel()
|
||||||
|
imgui.end_tab_item()
|
||||||
|
if show_tc_tab:
|
||||||
|
if imgui.begin_tab_item("Tool Calls")[0]:
|
||||||
|
self._render_tool_calls_panel()
|
||||||
|
imgui.end_tab_item()
|
||||||
|
if show_usage_tab:
|
||||||
|
if imgui.begin_tab_item("Usage Analytics")[0]:
|
||||||
|
self._render_usage_analytics_panel()
|
||||||
|
imgui.end_tab_item()
|
||||||
|
if not self.ui_separate_external_tools:
|
||||||
|
if imgui.begin_tab_item("External Tools")[0]:
|
||||||
|
self._render_external_tools_panel()
|
||||||
|
imgui.end_tab_item()
|
||||||
|
imgui.end_tab_bar()
|
||||||
|
self._end_window()
|
||||||
|
|
||||||
def _show_menus(self) -> None:
|
def _show_menus(self) -> None:
|
||||||
if imgui.begin_menu("manual slop"):
|
if imgui.begin_menu("manual slop"):
|
||||||
if imgui.menu_item("Quit", "Ctrl+Q", False)[0]:
|
if imgui.menu_item("Quit", "Ctrl+Q", False)[0]:
|
||||||
@@ -343,7 +470,6 @@ class App:
|
|||||||
if imgui.begin_menu("Project"):
|
if imgui.begin_menu("Project"):
|
||||||
if imgui.menu_item("Save All", "", False)[0]:
|
if imgui.menu_item("Save All", "", False)[0]:
|
||||||
self._flush_to_project()
|
self._flush_to_project()
|
||||||
self._save_active_project()
|
|
||||||
self._flush_to_config()
|
self._flush_to_config()
|
||||||
models.save_config(self.config)
|
models.save_config(self.config)
|
||||||
self.ai_status = "config saved"
|
self.ai_status = "config saved"
|
||||||
@@ -364,20 +490,122 @@ class App:
|
|||||||
self.ai_status = f"error: {e}"
|
self.ai_status = f"error: {e}"
|
||||||
imgui.end_menu()
|
imgui.end_menu()
|
||||||
|
|
||||||
|
# Draw right-aligned window controls directly in the menu bar (Win32 only)
|
||||||
|
if sys.platform == "win32":
|
||||||
|
try:
|
||||||
|
import ctypes
|
||||||
|
ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
|
||||||
|
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object, ctypes.c_char_p]
|
||||||
|
hwnd_capsule = imgui.get_main_viewport().platform_handle_raw
|
||||||
|
hwnd = ctypes.pythonapi.PyCapsule_GetPointer(hwnd_capsule, b"nb_handle")
|
||||||
|
except Exception:
|
||||||
|
hwnd = 0
|
||||||
|
|
||||||
|
if hwnd:
|
||||||
|
btn_w = 40
|
||||||
|
display_w = imgui.get_io().display_size.x
|
||||||
|
right_x = display_w - (btn_w * 3)
|
||||||
|
|
||||||
|
# Drag area check using an explicit invisible button spanning the empty space
|
||||||
|
curr_x = imgui.get_cursor_pos_x()
|
||||||
|
drag_w = right_x - curr_x
|
||||||
|
if drag_w > 0:
|
||||||
|
# Use a small positive height to satisfy IM_ASSERT(size_arg.y != 0.0f)
|
||||||
|
# The menu bar naturally constrains the hit box height anyway.
|
||||||
|
imgui.invisible_button("##drag_area", (drag_w, 20.0))
|
||||||
|
if imgui.is_item_active() and imgui.is_mouse_dragging(0):
|
||||||
|
# CRITICAL: We must reset ImGui's mouse_down state BEFORE passing control to Windows.
|
||||||
|
# Otherwise, the Windows modal drag loop swallows the WM_LBUTTONUP event,
|
||||||
|
# and ImGui thinks the mouse is permanently held down, causing "sticky" dragging.
|
||||||
|
imgui.get_io().mouse_down[0] = False
|
||||||
|
win32gui.ReleaseCapture()
|
||||||
|
win32gui.SendMessage(hwnd, win32con.WM_NCLBUTTONDOWN, win32con.HTCAPTION, 0)
|
||||||
|
|
||||||
|
imgui.push_style_color(imgui.Col_.button, vec4(0, 0, 0, 0))
|
||||||
|
|
||||||
|
try:
|
||||||
|
is_max = win32gui.GetWindowPlacement(hwnd)[1] == win32con.SW_SHOWMAXIMIZED
|
||||||
|
except Exception:
|
||||||
|
is_max = False
|
||||||
|
|
||||||
|
imgui.set_cursor_pos_x(right_x)
|
||||||
|
if imgui.button("_", (btn_w, 0)):
|
||||||
|
win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE)
|
||||||
|
|
||||||
|
imgui.set_cursor_pos_x(right_x + btn_w)
|
||||||
|
if imgui.button("[=]" if is_max else "[]", (btn_w, 0)):
|
||||||
|
win32gui.ShowWindow(hwnd, win32con.SW_RESTORE if is_max else win32con.SW_MAXIMIZE)
|
||||||
|
|
||||||
|
imgui.set_cursor_pos_x(right_x + btn_w * 2)
|
||||||
|
imgui.push_style_color(imgui.Col_.button_hovered, vec4(200, 50, 50, 255))
|
||||||
|
if imgui.button("X", (btn_w, 0)):
|
||||||
|
win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
|
||||||
|
imgui.pop_style_color()
|
||||||
|
|
||||||
|
imgui.pop_style_color()
|
||||||
|
|
||||||
|
def _render_custom_title_bar(self) -> None:
|
||||||
|
# Obsolete, removed since it renders behind the full screen dock space.
|
||||||
|
# Controls are now embedded in _show_menus.
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _render_shader_live_editor(self) -> None:
|
||||||
|
if self.show_windows.get('Shader Editor', False):
|
||||||
|
exp, opened = imgui.begin('Shader Editor', self.show_windows['Shader Editor'])
|
||||||
|
self.show_windows['Shader Editor'] = bool(opened)
|
||||||
|
if exp:
|
||||||
|
changed_crt, self.shader_uniforms['crt'] = imgui.slider_float('CRT Curvature', self.shader_uniforms['crt'], 0.0, 2.0)
|
||||||
|
changed_scan, self.shader_uniforms['scanline'] = imgui.slider_float('Scanline Intensity', self.shader_uniforms['scanline'], 0.0, 1.0)
|
||||||
|
changed_bloom, self.shader_uniforms['bloom'] = imgui.slider_float('Bloom Threshold', self.shader_uniforms['bloom'], 0.0, 1.0)
|
||||||
|
|
||||||
|
imgui.separator()
|
||||||
|
imgui.text("Frosted Glass")
|
||||||
|
changed_fbr, self.shader_uniforms['frosted_blur_radius'] = imgui.slider_float('Blur Radius', self.shader_uniforms['frosted_blur_radius'], 0.0, 64.0)
|
||||||
|
if changed_fbr: theme.set_frosted_blur_radius(self.shader_uniforms['frosted_blur_radius'])
|
||||||
|
|
||||||
|
changed_fti, self.shader_uniforms['frosted_tint_intensity'] = imgui.slider_float('Tint Intensity', self.shader_uniforms['frosted_tint_intensity'], 0.0, 1.0)
|
||||||
|
if changed_fti: theme.set_frosted_tint_intensity(self.shader_uniforms['frosted_tint_intensity'])
|
||||||
|
|
||||||
|
changed_fo, self.shader_uniforms['frosted_opacity'] = imgui.slider_float('Frosted Opacity', self.shader_uniforms['frosted_opacity'], 0.0, 1.0)
|
||||||
|
if changed_fo: theme.set_frosted_opacity(self.shader_uniforms['frosted_opacity'])
|
||||||
|
imgui.end()
|
||||||
|
|
||||||
def _gui_func(self) -> None:
|
def _gui_func(self) -> None:
|
||||||
|
self._render_custom_title_bar()
|
||||||
|
self._render_shader_live_editor()
|
||||||
pushed_prior_tint = False
|
pushed_prior_tint = False
|
||||||
|
pushed_frosted_style = False
|
||||||
|
|
||||||
# Render background shader
|
# Render background shader
|
||||||
bg = bg_shader.get_bg()
|
bg = bg_shader.get_bg()
|
||||||
if bg.enabled:
|
frosted_enabled = theme.get_frosted_glass_enabled()
|
||||||
|
if bg.enabled and not frosted_enabled:
|
||||||
ws = imgui.get_io().display_size
|
ws = imgui.get_io().display_size
|
||||||
bg.render(ws.x, ws.y)
|
bg.render(ws.x, ws.y)
|
||||||
|
|
||||||
|
if frosted_enabled:
|
||||||
|
ws = imgui.get_io().display_size
|
||||||
|
self.shader_manager.prepare_global_blur(
|
||||||
|
int(ws.x), int(ws.y),
|
||||||
|
self.shader_uniforms['frosted_blur_radius'],
|
||||||
|
self.shader_uniforms['frosted_tint_intensity'],
|
||||||
|
self.shader_uniforms['frosted_opacity'],
|
||||||
|
time.time()
|
||||||
|
)
|
||||||
|
# Draw background texture
|
||||||
|
dl = imgui.get_background_draw_list()
|
||||||
|
dl.add_image(imgui.ImTextureRef(self.shader_manager.scene_tex), (0, 0), (ws.x, ws.y))
|
||||||
|
|
||||||
|
imgui.push_style_color(imgui.Col_.window_bg, vec4(0, 0, 0, 0))
|
||||||
|
pushed_frosted_style = True
|
||||||
|
|
||||||
if theme.is_nerv_active():
|
if theme.is_nerv_active():
|
||||||
ws = imgui.get_io().display_size
|
ws = imgui.get_io().display_size
|
||||||
self._nerv_alert.update(self.ai_status)
|
self._nerv_alert.update(self.ai_status)
|
||||||
self._nerv_alert.render(ws.x, ws.y)
|
self._nerv_alert.render(ws.x, ws.y)
|
||||||
self._nerv_crt.enabled = self.ui_crt_filter
|
self._nerv_crt.enabled = self.ui_crt_filter
|
||||||
self._nerv_crt.render(ws.x, ws.y)
|
self._nerv_crt.render(ws.x, ws.y)
|
||||||
|
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func")
|
if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func")
|
||||||
if self.is_viewing_prior_session:
|
if self.is_viewing_prior_session:
|
||||||
imgui.push_style_color(imgui.Col_.window_bg, vec4(50, 40, 20))
|
imgui.push_style_color(imgui.Col_.window_bg, vec4(50, 40, 20))
|
||||||
@@ -408,7 +636,6 @@ class App:
|
|||||||
self._last_autosave = now
|
self._last_autosave = now
|
||||||
try:
|
try:
|
||||||
self._flush_to_project()
|
self._flush_to_project()
|
||||||
self._save_active_project()
|
|
||||||
self._flush_to_config()
|
self._flush_to_config()
|
||||||
models.save_config(self.config)
|
models.save_config(self.config)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -440,6 +667,9 @@ class App:
|
|||||||
self._comms_log_dirty = False
|
self._comms_log_dirty = False
|
||||||
|
|
||||||
if self._tool_log_dirty:
|
if self._tool_log_dirty:
|
||||||
|
if self.is_viewing_prior_session:
|
||||||
|
self._tool_log_cache = self.prior_tool_calls
|
||||||
|
else:
|
||||||
log_raw = list(self._tool_log)
|
log_raw = list(self._tool_log)
|
||||||
if self.ui_focus_agent:
|
if self.ui_focus_agent:
|
||||||
self._tool_log_cache = [e for e in log_raw if e.get("source_tier", "").startswith(self.ui_focus_agent)]
|
self._tool_log_cache = [e for e in log_raw if e.get("source_tier", "").startswith(self.ui_focus_agent)]
|
||||||
@@ -448,22 +678,29 @@ class App:
|
|||||||
self._tool_log_dirty = False
|
self._tool_log_dirty = False
|
||||||
|
|
||||||
if self.show_windows.get("Context Hub", False):
|
if self.show_windows.get("Context Hub", False):
|
||||||
exp, opened = imgui.begin("Context Hub", self.show_windows["Context Hub"])
|
exp, opened = self._begin_window("Context Hub", self.show_windows["Context Hub"])
|
||||||
self.show_windows["Context Hub"] = bool(opened)
|
self.show_windows["Context Hub"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
|
if imgui.begin_tab_bar('context_hub_tabs'):
|
||||||
|
if imgui.begin_tab_item('Projects')[0]:
|
||||||
self._render_projects_panel()
|
self._render_projects_panel()
|
||||||
imgui.end()
|
imgui.end_tab_item()
|
||||||
|
if imgui.begin_tab_item('Paths')[0]:
|
||||||
|
self._render_paths_panel()
|
||||||
|
imgui.end_tab_item()
|
||||||
|
imgui.end_tab_bar()
|
||||||
|
self._end_window()
|
||||||
if self.show_windows.get("Files & Media", False):
|
if self.show_windows.get("Files & Media", False):
|
||||||
exp, opened = imgui.begin("Files & Media", self.show_windows["Files & Media"])
|
exp, opened = self._begin_window("Files & Media", self.show_windows["Files & Media"])
|
||||||
self.show_windows["Files & Media"] = bool(opened)
|
self.show_windows["Files & Media"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
if imgui.collapsing_header("Files"):
|
if imgui.collapsing_header("Files"):
|
||||||
self._render_files_panel()
|
self._render_files_panel()
|
||||||
if imgui.collapsing_header("Screenshots"):
|
if imgui.collapsing_header("Screenshots"):
|
||||||
self._render_screenshots_panel()
|
self._render_screenshots_panel()
|
||||||
imgui.end()
|
self._end_window()
|
||||||
if self.show_windows.get("AI Settings", False):
|
if self.show_windows.get("AI Settings", False):
|
||||||
exp, opened = imgui.begin("AI Settings", self.show_windows["AI Settings"])
|
exp, opened = self._begin_window("AI Settings", self.show_windows["AI Settings"])
|
||||||
self.show_windows["AI Settings"] = bool(opened)
|
self.show_windows["AI Settings"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_persona_selector_panel()
|
self._render_persona_selector_panel()
|
||||||
@@ -473,64 +710,68 @@ class App:
|
|||||||
self._render_system_prompts_panel()
|
self._render_system_prompts_panel()
|
||||||
self._render_agent_tools_panel()
|
self._render_agent_tools_panel()
|
||||||
|
|
||||||
imgui.end()
|
self._end_window()
|
||||||
if self.ui_separate_usage_analytics and self.show_windows.get("Usage Analytics", False):
|
if self.ui_separate_usage_analytics and self.show_windows.get("Usage Analytics", False):
|
||||||
exp, opened = imgui.begin("Usage Analytics", self.show_windows["Usage Analytics"])
|
exp, opened = self._begin_window("Usage Analytics", self.show_windows["Usage Analytics"])
|
||||||
self.show_windows["Usage Analytics"] = bool(opened)
|
self.show_windows["Usage Analytics"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_usage_analytics_panel()
|
self._render_usage_analytics_panel()
|
||||||
imgui.end()
|
self._end_window()
|
||||||
if self.show_windows.get("MMA Dashboard", False):
|
if self.show_windows.get("MMA Dashboard", False):
|
||||||
exp, opened = imgui.begin("MMA Dashboard", self.show_windows["MMA Dashboard"])
|
exp, opened = self._begin_window("MMA Dashboard", self.show_windows["MMA Dashboard"])
|
||||||
self.show_windows["MMA Dashboard"] = bool(opened)
|
self.show_windows["MMA Dashboard"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard")
|
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard")
|
||||||
self._render_mma_dashboard()
|
self._render_mma_dashboard()
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard")
|
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard")
|
||||||
imgui.end()
|
self._end_window()
|
||||||
|
|
||||||
if self.ui_separate_task_dag and self.show_windows.get("Task DAG", False):
|
if self.ui_separate_task_dag and self.show_windows.get("Task DAG", False):
|
||||||
exp, opened = imgui.begin("Task DAG", self.show_windows["Task DAG"])
|
exp, opened = self._begin_window("Task DAG", self.show_windows["Task DAG"])
|
||||||
self.show_windows["Task DAG"] = bool(opened)
|
self.show_windows["Task DAG"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_task_dag_panel()
|
self._render_task_dag_panel()
|
||||||
imgui.end()
|
self._end_window()
|
||||||
if self.ui_separate_tier1 and self.show_windows.get("Tier 1: Strategy", False):
|
if self.ui_separate_tier1 and self.show_windows.get("Tier 1: Strategy", False):
|
||||||
exp, opened = imgui.begin("Tier 1: Strategy", self.show_windows["Tier 1: Strategy"])
|
exp, opened = self._begin_window("Tier 1: Strategy", self.show_windows["Tier 1: Strategy"])
|
||||||
self.show_windows["Tier 1: Strategy"] = bool(opened)
|
self.show_windows["Tier 1: Strategy"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_tier_stream_panel("Tier 1", "Tier 1")
|
self._render_tier_stream_panel("Tier 1", "Tier 1")
|
||||||
imgui.end()
|
self._end_window()
|
||||||
if self.ui_separate_tier2 and self.show_windows.get("Tier 2: Tech Lead", False):
|
if self.ui_separate_tier2 and self.show_windows.get("Tier 2: Tech Lead", False):
|
||||||
exp, opened = imgui.begin("Tier 2: Tech Lead", self.show_windows["Tier 2: Tech Lead"])
|
exp, opened = self._begin_window("Tier 2: Tech Lead", self.show_windows["Tier 2: Tech Lead"])
|
||||||
self.show_windows["Tier 2: Tech Lead"] = bool(opened)
|
self.show_windows["Tier 2: Tech Lead"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_tier_stream_panel("Tier 2", "Tier 2 (Tech Lead)")
|
self._render_tier_stream_panel("Tier 2", "Tier 2 (Tech Lead)")
|
||||||
imgui.end()
|
self._end_window()
|
||||||
if self.ui_separate_tier3 and self.show_windows.get("Tier 3: Workers", False):
|
if self.ui_separate_tier3 and self.show_windows.get("Tier 3: Workers", False):
|
||||||
exp, opened = imgui.begin("Tier 3: Workers", self.show_windows["Tier 3: Workers"])
|
exp, opened = self._begin_window("Tier 3: Workers", self.show_windows["Tier 3: Workers"])
|
||||||
self.show_windows["Tier 3: Workers"] = bool(opened)
|
self.show_windows["Tier 3: Workers"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_tier_stream_panel("Tier 3", None)
|
self._render_tier_stream_panel("Tier 3", None)
|
||||||
imgui.end()
|
self._end_window()
|
||||||
if self.ui_separate_tier4 and self.show_windows.get("Tier 4: QA", False):
|
if self.ui_separate_tier4 and self.show_windows.get("Tier 4: QA", False):
|
||||||
exp, opened = imgui.begin("Tier 4: QA", self.show_windows["Tier 4: QA"])
|
exp, opened = self._begin_window("Tier 4: QA", self.show_windows["Tier 4: QA"])
|
||||||
self.show_windows["Tier 4: QA"] = bool(opened)
|
self.show_windows["Tier 4: QA"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_tier_stream_panel("Tier 4", "Tier 4 (QA)")
|
self._render_tier_stream_panel("Tier 4", "Tier 4 (QA)")
|
||||||
imgui.end()
|
self._end_window()
|
||||||
if self.show_windows.get("Theme", False):
|
if self.show_windows.get("Theme", False):
|
||||||
self._render_theme_panel()
|
self._render_theme_panel()
|
||||||
if self.show_windows.get("Discussion Hub", False):
|
if self.show_windows.get("Discussion Hub", False):
|
||||||
exp, opened = imgui.begin("Discussion Hub", self.show_windows["Discussion Hub"])
|
exp, opened = self._begin_window("Discussion Hub", self.show_windows["Discussion Hub"])
|
||||||
self.show_windows["Discussion Hub"] = bool(opened)
|
self.show_windows["Discussion Hub"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
# Top part for the history
|
# Top part for the history
|
||||||
imgui.begin_child("HistoryChild", size=(0, -200))
|
imgui.begin_child("HistoryChild", size=(0, -self.ui_discussion_split_h))
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel")
|
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel")
|
||||||
self._render_discussion_panel()
|
self._render_discussion_panel()
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_discussion_panel")
|
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_discussion_panel")
|
||||||
imgui.end_child()
|
imgui.end_child()
|
||||||
|
# Splitter
|
||||||
|
imgui.button("###discussion_splitter", imgui.ImVec2(-1, 4))
|
||||||
|
if imgui.is_item_active():
|
||||||
|
self.ui_discussion_split_h = max(150.0, min(imgui.get_window_height() - 150.0, self.ui_discussion_split_h - imgui.get_io().mouse_delta.y))
|
||||||
# Bottom part with tabs for message and response
|
# Bottom part with tabs for message and response
|
||||||
# Detach controls
|
# Detach controls
|
||||||
imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
|
imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
|
||||||
@@ -565,75 +806,37 @@ class App:
|
|||||||
else:
|
else:
|
||||||
imgui.text_disabled("Message & Response panels are detached.")
|
imgui.text_disabled("Message & Response panels are detached.")
|
||||||
|
|
||||||
imgui.end()
|
self._end_window()
|
||||||
if self.show_windows.get("Operations Hub", False):
|
if self.show_windows.get("Operations Hub", False):
|
||||||
exp, opened = imgui.begin("Operations Hub", self.show_windows["Operations Hub"])
|
self._render_operations_hub()
|
||||||
self.show_windows["Operations Hub"] = bool(opened)
|
|
||||||
if exp:
|
|
||||||
imgui.text("Focus Agent:")
|
|
||||||
imgui.same_line()
|
|
||||||
focus_label = self.ui_focus_agent or "All"
|
|
||||||
if imgui.begin_combo("##focus_agent", focus_label, imgui.ComboFlags_.width_fit_preview):
|
|
||||||
if imgui.selectable("All", self.ui_focus_agent is None)[0]:
|
|
||||||
self.ui_focus_agent = None
|
|
||||||
for tier in ["Tier 2", "Tier 3", "Tier 4"]:
|
|
||||||
if imgui.selectable(tier, self.ui_focus_agent == tier)[0]:
|
|
||||||
self.ui_focus_agent = tier
|
|
||||||
imgui.end_combo()
|
|
||||||
imgui.same_line()
|
|
||||||
if self.ui_focus_agent:
|
|
||||||
if imgui.button("x##clear_focus"):
|
|
||||||
self.ui_focus_agent = None
|
|
||||||
if exp:
|
|
||||||
imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
|
|
||||||
ch1, self.ui_separate_tool_calls_panel = imgui.checkbox("Pop Out Tool Calls", self.ui_separate_tool_calls_panel)
|
|
||||||
if ch1: self.show_windows["Tool Calls"] = self.ui_separate_tool_calls_panel
|
|
||||||
imgui.same_line()
|
|
||||||
ch2, self.ui_separate_usage_analytics = imgui.checkbox("Pop Out Usage Analytics", self.ui_separate_usage_analytics)
|
|
||||||
if ch2: self.show_windows["Usage Analytics"] = self.ui_separate_usage_analytics
|
|
||||||
imgui.pop_style_var()
|
|
||||||
|
|
||||||
show_tc_tab = not self.ui_separate_tool_calls_panel
|
|
||||||
show_usage_tab = not self.ui_separate_usage_analytics
|
|
||||||
|
|
||||||
if imgui.begin_tab_bar("ops_tabs"):
|
|
||||||
if imgui.begin_tab_item("Comms History")[0]:
|
|
||||||
self._render_comms_history_panel()
|
|
||||||
imgui.end_tab_item()
|
|
||||||
if show_tc_tab:
|
|
||||||
if imgui.begin_tab_item("Tool Calls")[0]:
|
|
||||||
self._render_tool_calls_panel()
|
|
||||||
imgui.end_tab_item()
|
|
||||||
if show_usage_tab:
|
|
||||||
if imgui.begin_tab_item("Usage Analytics")[0]:
|
|
||||||
self._render_usage_analytics_panel()
|
|
||||||
imgui.end_tab_item()
|
|
||||||
if imgui.begin_tab_item("External Tools")[0]:
|
|
||||||
self._render_external_tools_panel()
|
|
||||||
imgui.end_tab_item()
|
|
||||||
imgui.end_tab_bar()
|
|
||||||
imgui.end()
|
|
||||||
|
|
||||||
if self.ui_separate_message_panel and self.show_windows.get("Message", False):
|
if self.ui_separate_message_panel and self.show_windows.get("Message", False):
|
||||||
exp, opened = imgui.begin("Message", self.show_windows["Message"])
|
exp, opened = self._begin_window("Message", self.show_windows["Message"])
|
||||||
self.show_windows["Message"] = bool(opened)
|
self.show_windows["Message"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_message_panel()
|
self._render_message_panel()
|
||||||
imgui.end()
|
self._end_window()
|
||||||
|
|
||||||
if self.ui_separate_response_panel and self.show_windows.get("Response", False):
|
if self.ui_separate_response_panel and self.show_windows.get("Response", False):
|
||||||
exp, opened = imgui.begin("Response", self.show_windows["Response"])
|
exp, opened = self._begin_window("Response", self.show_windows["Response"])
|
||||||
self.show_windows["Response"] = bool(opened)
|
self.show_windows["Response"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_response_panel()
|
self._render_response_panel()
|
||||||
imgui.end()
|
self._end_window()
|
||||||
|
|
||||||
if self.ui_separate_tool_calls_panel and self.show_windows.get("Tool Calls", False):
|
if self.ui_separate_tool_calls_panel and self.show_windows.get("Tool Calls", False):
|
||||||
exp, opened = imgui.begin("Tool Calls", self.show_windows["Tool Calls"])
|
exp, opened = self._begin_window("Tool Calls", self.show_windows["Tool Calls"])
|
||||||
self.show_windows["Tool Calls"] = bool(opened)
|
self.show_windows["Tool Calls"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_tool_calls_panel()
|
self._render_tool_calls_panel()
|
||||||
imgui.end()
|
self._end_window()
|
||||||
|
|
||||||
|
if self.ui_separate_external_tools and self.show_windows.get('External Tools', False):
|
||||||
|
exp, opened = self._begin_window('External Tools', self.show_windows['External Tools'])
|
||||||
|
self.show_windows['External Tools'] = bool(opened)
|
||||||
|
if exp:
|
||||||
|
self._render_external_tools_panel()
|
||||||
|
self._end_window()
|
||||||
|
|
||||||
if self.show_windows.get("Log Management", False):
|
if self.show_windows.get("Log Management", False):
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_log_management")
|
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_log_management")
|
||||||
@@ -824,6 +1027,7 @@ class App:
|
|||||||
expanded, opened = imgui.begin("Last Script Output", self.show_script_output)
|
expanded, opened = imgui.begin("Last Script Output", self.show_script_output)
|
||||||
self.show_script_output = bool(opened)
|
self.show_script_output = bool(opened)
|
||||||
if expanded:
|
if expanded:
|
||||||
|
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
|
||||||
imgui.text("Script:")
|
imgui.text("Script:")
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
self._render_text_viewer("Last Script", self.ui_last_script_text)
|
self._render_text_viewer("Last Script", self.ui_last_script_text)
|
||||||
@@ -855,6 +1059,7 @@ class App:
|
|||||||
expanded, opened = imgui.begin(f"Text Viewer - {self.text_viewer_title}", self.show_text_viewer)
|
expanded, opened = imgui.begin(f"Text Viewer - {self.text_viewer_title}", self.show_text_viewer)
|
||||||
self.show_text_viewer = bool(opened)
|
self.show_text_viewer = bool(opened)
|
||||||
if expanded:
|
if expanded:
|
||||||
|
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
|
||||||
if self.ui_word_wrap:
|
if self.ui_word_wrap:
|
||||||
imgui.begin_child("tv_wrap", imgui.ImVec2(-1, -1), False)
|
imgui.begin_child("tv_wrap", imgui.ImVec2(-1, -1), False)
|
||||||
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
|
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
|
||||||
@@ -914,6 +1119,8 @@ class App:
|
|||||||
|
|
||||||
if pushed_prior_tint:
|
if pushed_prior_tint:
|
||||||
imgui.pop_style_color()
|
imgui.pop_style_color()
|
||||||
|
if pushed_frosted_style:
|
||||||
|
imgui.pop_style_color()
|
||||||
|
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_gui_func")
|
if self.perf_profiling_enabled: self.perf_monitor.end_component("_gui_func")
|
||||||
|
|
||||||
@@ -1411,6 +1618,15 @@ class App:
|
|||||||
r.destroy()
|
r.destroy()
|
||||||
if d: self.ui_output_dir = d
|
if d: self.ui_output_dir = d
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
|
imgui.text("Conductor Directory")
|
||||||
|
ch, self.ui_project_conductor_dir = imgui.input_text("##cond_dir", self.ui_project_conductor_dir)
|
||||||
|
imgui.same_line()
|
||||||
|
if imgui.button("Browse##cond"):
|
||||||
|
r = hide_tk_root()
|
||||||
|
d = filedialog.askdirectory(title="Select Conductor Directory")
|
||||||
|
r.destroy()
|
||||||
|
if d: self.ui_project_conductor_dir = d
|
||||||
|
imgui.separator()
|
||||||
imgui.text("Project Files")
|
imgui.text("Project Files")
|
||||||
imgui.begin_child("proj_files", imgui.ImVec2(0, 150), True)
|
imgui.begin_child("proj_files", imgui.ImVec2(0, 150), True)
|
||||||
for i, pp in enumerate(self.project_paths):
|
for i, pp in enumerate(self.project_paths):
|
||||||
@@ -1453,7 +1669,6 @@ class App:
|
|||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if imgui.button("Save All"):
|
if imgui.button("Save All"):
|
||||||
self._flush_to_project()
|
self._flush_to_project()
|
||||||
self._save_active_project()
|
|
||||||
self._flush_to_config()
|
self._flush_to_config()
|
||||||
models.save_config(self.config)
|
models.save_config(self.config)
|
||||||
self.ai_status = "config saved"
|
self.ai_status = "config saved"
|
||||||
@@ -1462,6 +1677,57 @@ class App:
|
|||||||
ch, self.ui_auto_scroll_comms = imgui.checkbox("Auto-scroll Comms History", self.ui_auto_scroll_comms)
|
ch, self.ui_auto_scroll_comms = imgui.checkbox("Auto-scroll Comms History", self.ui_auto_scroll_comms)
|
||||||
ch, self.ui_auto_scroll_tool_calls = imgui.checkbox("Auto-scroll Tool History", self.ui_auto_scroll_tool_calls)
|
ch, self.ui_auto_scroll_tool_calls = imgui.checkbox("Auto-scroll Tool History", self.ui_auto_scroll_tool_calls)
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_projects_panel")
|
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_projects_panel")
|
||||||
|
|
||||||
|
def _save_paths(self):
|
||||||
|
self.config["paths"] = {
|
||||||
|
"logs_dir": self.ui_logs_dir,
|
||||||
|
"scripts_dir": self.ui_scripts_dir
|
||||||
|
}
|
||||||
|
cfg_path = paths.get_config_path()
|
||||||
|
if cfg_path.exists():
|
||||||
|
shutil.copy(cfg_path, str(cfg_path) + ".bak")
|
||||||
|
models.save_config(self.config)
|
||||||
|
paths.reset_resolved()
|
||||||
|
self.init_state()
|
||||||
|
self.ai_status = 'paths applied and session reset'
|
||||||
|
|
||||||
|
def _render_paths_panel(self) -> None:
|
||||||
|
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_paths_panel")
|
||||||
|
path_info = paths.get_full_path_info()
|
||||||
|
|
||||||
|
imgui.text_colored(C_IN, "System Path Configuration")
|
||||||
|
imgui.separator()
|
||||||
|
|
||||||
|
def render_path_field(label: str, attr: str, key: str, tooltip: str):
|
||||||
|
info = path_info.get(key, {'source': 'unknown'})
|
||||||
|
imgui.text(label)
|
||||||
|
if imgui.is_item_hovered(): imgui.set_tooltip(tooltip)
|
||||||
|
imgui.same_line()
|
||||||
|
imgui.text_disabled(f"(Source: {info['source']})")
|
||||||
|
|
||||||
|
val = getattr(self, attr)
|
||||||
|
changed, new_val = imgui.input_text(f"##{key}", val)
|
||||||
|
if imgui.is_item_hovered(): imgui.set_tooltip(tooltip)
|
||||||
|
if changed: setattr(self, attr, new_val)
|
||||||
|
imgui.same_line()
|
||||||
|
if imgui.button(f"Browse##{key}"):
|
||||||
|
r = hide_tk_root()
|
||||||
|
d = filedialog.askdirectory(title=f"Select {label}")
|
||||||
|
r.destroy()
|
||||||
|
if d: setattr(self, attr, d)
|
||||||
|
|
||||||
|
render_path_field("Logs Directory", "ui_logs_dir", "logs_dir", "Directory where session JSON-L logs and artifacts are stored.")
|
||||||
|
render_path_field("Scripts Directory", "ui_scripts_dir", "scripts_dir", "Directory for AI-generated PowerShell scripts.")
|
||||||
|
|
||||||
|
imgui.separator()
|
||||||
|
if imgui.button("Apply", imgui.ImVec2(120, 0)):
|
||||||
|
self._save_paths()
|
||||||
|
imgui.same_line()
|
||||||
|
if imgui.button("Reset", imgui.ImVec2(120, 0)):
|
||||||
|
self.init_state()
|
||||||
|
self.ai_status = "paths reset to defaults"
|
||||||
|
|
||||||
|
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_paths_panel")
|
||||||
def _render_track_proposal_modal(self) -> None:
|
def _render_track_proposal_modal(self) -> None:
|
||||||
if self._show_track_proposal_modal:
|
if self._show_track_proposal_modal:
|
||||||
imgui.open_popup("Track Proposal")
|
imgui.open_popup("Track Proposal")
|
||||||
@@ -1741,7 +2007,6 @@ class App:
|
|||||||
imgui.text(f"History: {key}")
|
imgui.text(f"History: {key}")
|
||||||
hist_data = self.perf_monitor.get_history(key)
|
hist_data = self.perf_monitor.get_history(key)
|
||||||
if hist_data:
|
if hist_data:
|
||||||
import numpy as np
|
|
||||||
imgui.plot_lines(f"##plot_{key}", np.array(hist_data, dtype=np.float32), graph_size=imgui.ImVec2(-1, 60))
|
imgui.plot_lines(f"##plot_{key}", np.array(hist_data, dtype=np.float32), graph_size=imgui.ImVec2(-1, 60))
|
||||||
else:
|
else:
|
||||||
imgui.text_disabled(f"(no history data for {key})")
|
imgui.text_disabled(f"(no history data for {key})")
|
||||||
@@ -1889,9 +2154,10 @@ def hello():
|
|||||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_screenshots_panel")
|
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_screenshots_panel")
|
||||||
|
|
||||||
def _render_discussion_panel(self) -> None:
|
def _render_discussion_panel(self) -> None:
|
||||||
|
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel")
|
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel")
|
||||||
# THINKING indicator
|
# THINKING indicator
|
||||||
is_thinking = self.ai_status in ["sending..."]
|
is_thinking = self.ai_status in ['sending...', 'streaming...', 'running powershell...']
|
||||||
if is_thinking:
|
if is_thinking:
|
||||||
val = math.sin(time.time() * 10 * math.pi)
|
val = math.sin(time.time() * 10 * math.pi)
|
||||||
alpha = 1.0 if val > 0 else 0.0
|
alpha = 1.0 if val > 0 else 0.0
|
||||||
@@ -1906,9 +2172,7 @@ def hello():
|
|||||||
imgui.text_colored(vec4(255, 200, 100), "VIEWING PRIOR SESSION")
|
imgui.text_colored(vec4(255, 200, 100), "VIEWING PRIOR SESSION")
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if imgui.button("Exit Prior Session"):
|
if imgui.button("Exit Prior Session"):
|
||||||
self.is_viewing_prior_session = False
|
self.controller.cb_exit_prior_session()
|
||||||
self.prior_session_entries.clear()
|
|
||||||
self.prior_disc_entries.clear()
|
|
||||||
self._comms_log_dirty = True
|
self._comms_log_dirty = True
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
imgui.begin_child("prior_scroll", imgui.ImVec2(0, 0), False)
|
imgui.begin_child("prior_scroll", imgui.ImVec2(0, 0), False)
|
||||||
@@ -1962,7 +2226,7 @@ def hello():
|
|||||||
if changed:
|
if changed:
|
||||||
if self._track_discussion_active:
|
if self._track_discussion_active:
|
||||||
self._flush_disc_entries_to_project()
|
self._flush_disc_entries_to_project()
|
||||||
history_strings = project_manager.load_track_history(self.active_track.id, self.ui_files_base_dir)
|
history_strings = project_manager.load_track_history(self.active_track.id, self.active_project_root)
|
||||||
with self._disc_entries_lock:
|
with self._disc_entries_lock:
|
||||||
self.disc_entries = models.parse_history_entries(history_strings, self.disc_roles)
|
self.disc_entries = models.parse_history_entries(history_strings, self.disc_roles)
|
||||||
self.ai_status = f"track discussion: {self.active_track.id}"
|
self.ai_status = f"track discussion: {self.active_track.id}"
|
||||||
@@ -2017,7 +2281,6 @@ def hello():
|
|||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if imgui.button("Save"):
|
if imgui.button("Save"):
|
||||||
self._flush_to_project()
|
self._flush_to_project()
|
||||||
self._save_active_project()
|
|
||||||
self._flush_to_config()
|
self._flush_to_config()
|
||||||
models.save_config(self.config)
|
models.save_config(self.config)
|
||||||
self.ai_status = "discussion saved"
|
self.ai_status = "discussion saved"
|
||||||
@@ -2512,17 +2775,23 @@ def hello():
|
|||||||
ch, self.ui_ai_input = imgui.input_text_multiline("##ai_in", self.ui_ai_input, imgui.ImVec2(-1, -40))
|
ch, self.ui_ai_input = imgui.input_text_multiline("##ai_in", self.ui_ai_input, imgui.ImVec2(-1, -40))
|
||||||
# Keyboard shortcuts
|
# Keyboard shortcuts
|
||||||
io = imgui.get_io()
|
io = imgui.get_io()
|
||||||
ctrl_enter = io.key_ctrl and imgui.is_key_pressed(imgui.Key.enter)
|
|
||||||
ctrl_l = io.key_ctrl and imgui.is_key_pressed(imgui.Key.l)
|
ctrl_l = io.key_ctrl and imgui.is_key_pressed(imgui.Key.l)
|
||||||
if ctrl_l:
|
if ctrl_l:
|
||||||
self.ui_ai_input = ""
|
self.ui_ai_input = ""
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
|
is_busy = self.ai_status in ['sending...', 'streaming...']
|
||||||
send_busy = False
|
send_busy = False
|
||||||
with self._send_thread_lock:
|
with self._send_thread_lock:
|
||||||
if self.send_thread and self.send_thread.is_alive():
|
if self.send_thread and self.send_thread.is_alive():
|
||||||
send_busy = True
|
send_busy = True
|
||||||
if (imgui.button("Gen + Send") or ctrl_enter) and not send_busy:
|
if is_busy: send_busy = True
|
||||||
|
|
||||||
|
imgui.begin_disabled(send_busy)
|
||||||
|
ctrl_enter = io.key_ctrl and imgui.is_key_pressed(imgui.Key.enter)
|
||||||
|
label = "Gen + Send (Busy)" if send_busy else "Gen + Send"
|
||||||
|
if (imgui.button(label) or ctrl_enter) and not send_busy:
|
||||||
self._handle_generate_send()
|
self._handle_generate_send()
|
||||||
|
imgui.end_disabled()
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if imgui.button("MD Only"):
|
if imgui.button("MD Only"):
|
||||||
self._handle_md_only()
|
self._handle_md_only()
|
||||||
@@ -2638,10 +2907,8 @@ def hello():
|
|||||||
if self.is_viewing_prior_session:
|
if self.is_viewing_prior_session:
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if imgui.button("Exit Prior Session"):
|
if imgui.button("Exit Prior Session"):
|
||||||
self.is_viewing_prior_session = False
|
self.controller.cb_exit_prior_session()
|
||||||
self.prior_session_entries.clear()
|
|
||||||
self._comms_log_dirty = True
|
self._comms_log_dirty = True
|
||||||
self.ai_status = "idle"
|
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
|
|
||||||
imgui.text_colored(C_OUT, "OUT")
|
imgui.text_colored(C_OUT, "OUT")
|
||||||
@@ -3711,6 +3978,7 @@ def hello():
|
|||||||
exp, opened = imgui.begin("Theme", self.show_windows["Theme"])
|
exp, opened = imgui.begin("Theme", self.show_windows["Theme"])
|
||||||
self.show_windows["Theme"] = bool(opened)
|
self.show_windows["Theme"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
|
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
|
||||||
imgui.text("Palette")
|
imgui.text("Palette")
|
||||||
cp = theme.get_current_palette()
|
cp = theme.get_current_palette()
|
||||||
if imgui.begin_combo("##pal", cp):
|
if imgui.begin_combo("##pal", cp):
|
||||||
@@ -3784,6 +4052,10 @@ def hello():
|
|||||||
gui_cfg["crt_filter_enabled"] = self.ui_crt_filter
|
gui_cfg["crt_filter_enabled"] = self.ui_crt_filter
|
||||||
self._flush_to_config()
|
self._flush_to_config()
|
||||||
models.save_config(self.config)
|
models.save_config(self.config)
|
||||||
|
|
||||||
|
ch_fg, fg_val = imgui.checkbox("Frosted Glass Effect", theme.get_frosted_glass_enabled())
|
||||||
|
if ch_fg:
|
||||||
|
theme.set_frosted_glass_enabled(fg_val)
|
||||||
self._flush_to_config()
|
self._flush_to_config()
|
||||||
models.save_config(self.config)
|
models.save_config(self.config)
|
||||||
imgui.end()
|
imgui.end()
|
||||||
@@ -3829,6 +4101,8 @@ def hello():
|
|||||||
|
|
||||||
def _post_init(self) -> None:
|
def _post_init(self) -> None:
|
||||||
theme.apply_current()
|
theme.apply_current()
|
||||||
|
self.shader_manager.setup_background_shader()
|
||||||
|
self.shader_manager.setup_frosted_glass_shader()
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""Initializes the ImGui runner and starts the main application loop."""
|
"""Initializes the ImGui runner and starts the main application loop."""
|
||||||
@@ -3844,6 +4118,13 @@ def hello():
|
|||||||
theme.load_from_config(self.config)
|
theme.load_from_config(self.config)
|
||||||
self.runner_params = hello_imgui.RunnerParams()
|
self.runner_params = hello_imgui.RunnerParams()
|
||||||
self.runner_params.app_window_params.window_title = "manual slop"
|
self.runner_params.app_window_params.window_title = "manual slop"
|
||||||
|
|
||||||
|
if sys.platform == "win32":
|
||||||
|
self.runner_params.app_window_params.borderless = True
|
||||||
|
self.runner_params.app_window_params.borderless_closable = False
|
||||||
|
self.runner_params.app_window_params.borderless_movable = False
|
||||||
|
self.runner_params.app_window_params.borderless_resizable = True
|
||||||
|
|
||||||
self.runner_params.app_window_params.window_geometry.size = (1680, 1200)
|
self.runner_params.app_window_params.window_geometry.size = (1680, 1200)
|
||||||
self.runner_params.imgui_window_params.enable_viewports = getattr(self, "ui_multi_viewport", False)
|
self.runner_params.imgui_window_params.enable_viewports = getattr(self, "ui_multi_viewport", False)
|
||||||
self.runner_params.imgui_window_params.remember_theme = True
|
self.runner_params.imgui_window_params.remember_theme = True
|
||||||
@@ -3854,8 +4135,9 @@ def hello():
|
|||||||
user_scale = theme.get_current_scale()
|
user_scale = theme.get_current_scale()
|
||||||
self.runner_params.dpi_aware_params.dpi_window_size_factor = user_scale
|
self.runner_params.dpi_aware_params.dpi_window_size_factor = user_scale
|
||||||
|
|
||||||
# Detect Monitor Refresh Rate for capping
|
# Detect Monitor Refresh Rate for capping (Win32 only)
|
||||||
fps_cap = 60.0
|
fps_cap = 60.0
|
||||||
|
if sys.platform == "win32":
|
||||||
try:
|
try:
|
||||||
# Use PowerShell to get max refresh rate across all controllers
|
# Use PowerShell to get max refresh rate across all controllers
|
||||||
cmd = "powershell -NoProfile -Command \"Get-CimInstance -ClassName Win32_VideoController | Select-Object -ExpandProperty CurrentRefreshRate\""
|
cmd = "powershell -NoProfile -Command \"Get-CimInstance -ClassName Win32_VideoController | Select-Object -ExpandProperty CurrentRefreshRate\""
|
||||||
|
|||||||
@@ -48,6 +48,13 @@ from src.paths import get_config_path
|
|||||||
|
|
||||||
CONFIG_PATH = get_config_path()
|
CONFIG_PATH = get_config_path()
|
||||||
|
|
||||||
|
def _clean_nones(data: Any) -> Any:
|
||||||
|
if isinstance(data, dict):
|
||||||
|
return {k: _clean_nones(v) for k, v in data.items() if v is not None}
|
||||||
|
elif isinstance(data, list):
|
||||||
|
return [_clean_nones(v) for v in data if v is not None]
|
||||||
|
return data
|
||||||
|
|
||||||
def load_config() -> dict[str, Any]:
|
def load_config() -> dict[str, Any]:
|
||||||
with open(CONFIG_PATH, "rb") as f:
|
with open(CONFIG_PATH, "rb") as f:
|
||||||
return tomllib.load(f)
|
return tomllib.load(f)
|
||||||
@@ -55,6 +62,7 @@ def load_config() -> dict[str, Any]:
|
|||||||
def save_config(config: dict[str, Any]) -> None:
|
def save_config(config: dict[str, Any]) -> None:
|
||||||
import tomli_w
|
import tomli_w
|
||||||
import sys
|
import sys
|
||||||
|
config = _clean_nones(config)
|
||||||
sys.stderr.write(f"[DEBUG] Saving config. Theme: {config.get('theme')}\n")
|
sys.stderr.write(f"[DEBUG] Saving config. Theme: {config.get('theme')}\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
with open(CONFIG_PATH, "wb") as f:
|
with open(CONFIG_PATH, "wb") as f:
|
||||||
|
|||||||
+32
-12
@@ -6,13 +6,11 @@ This module provides centralized path resolution for all configurable paths in t
|
|||||||
|
|
||||||
Environment Variables:
|
Environment Variables:
|
||||||
SLOP_CONFIG: Path to config.toml
|
SLOP_CONFIG: Path to config.toml
|
||||||
SLOP_CONDUCTOR_DIR: Path to conductor directory
|
|
||||||
SLOP_LOGS_DIR: Path to logs directory
|
SLOP_LOGS_DIR: Path to logs directory
|
||||||
SLOP_SCRIPTS_DIR: Path to generated scripts directory
|
SLOP_SCRIPTS_DIR: Path to generated scripts directory
|
||||||
|
|
||||||
Configuration (config.toml):
|
Configuration (config.toml):
|
||||||
[paths]
|
[paths]
|
||||||
conductor_dir = "conductor"
|
|
||||||
logs_dir = "logs/sessions"
|
logs_dir = "logs/sessions"
|
||||||
scripts_dir = "scripts/generated"
|
scripts_dir = "scripts/generated"
|
||||||
|
|
||||||
@@ -27,8 +25,8 @@ Path Functions:
|
|||||||
|
|
||||||
Resolution Order:
|
Resolution Order:
|
||||||
1. Check project-specific manual_slop.toml (for conductor paths)
|
1. Check project-specific manual_slop.toml (for conductor paths)
|
||||||
2. Check environment variable
|
2. Check environment variable (for logs/scripts)
|
||||||
3. Check config.toml [paths] section
|
3. Check config.toml [paths] section (for logs/scripts)
|
||||||
4. Fall back to default
|
4. Fall back to default
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@@ -45,7 +43,7 @@ See Also:
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
import os
|
||||||
import tomllib
|
import tomllib
|
||||||
from typing import Optional
|
from typing import Optional, Any
|
||||||
|
|
||||||
_RESOLVED: dict[str, Path] = {}
|
_RESOLVED: dict[str, Path] = {}
|
||||||
|
|
||||||
@@ -105,20 +103,20 @@ def _get_project_conductor_dir_from_toml(project_root: Path) -> Optional[Path]:
|
|||||||
if c_dir:
|
if c_dir:
|
||||||
p = Path(c_dir)
|
p = Path(c_dir)
|
||||||
if not p.is_absolute(): p = project_root / p
|
if not p.is_absolute(): p = project_root / p
|
||||||
return p
|
return p.resolve()
|
||||||
except: pass
|
except: pass
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_conductor_dir(project_path: Optional[str] = None) -> Path:
|
def get_conductor_dir(project_path: Optional[str] = None) -> Path:
|
||||||
if project_path:
|
if not project_path:
|
||||||
project_root = Path(project_path)
|
# Fallback for legacy/tests, but we should avoid this
|
||||||
|
return Path('conductor').resolve()
|
||||||
|
|
||||||
|
project_root = Path(project_path).resolve()
|
||||||
p = _get_project_conductor_dir_from_toml(project_root)
|
p = _get_project_conductor_dir_from_toml(project_root)
|
||||||
if p: return p
|
if p: return p
|
||||||
return project_root / 'conductor'
|
|
||||||
|
|
||||||
if "conductor_dir" not in _RESOLVED:
|
return (project_root / "conductor").resolve()
|
||||||
_RESOLVED["conductor_dir"] = _resolve_path("SLOP_CONDUCTOR_DIR", "conductor_dir", "conductor")
|
|
||||||
return _RESOLVED["conductor_dir"]
|
|
||||||
|
|
||||||
def get_logs_dir() -> Path:
|
def get_logs_dir() -> Path:
|
||||||
if "logs_dir" not in _RESOLVED:
|
if "logs_dir" not in _RESOLVED:
|
||||||
@@ -139,6 +137,28 @@ def get_track_state_dir(track_id: str, project_path: Optional[str] = None) -> Pa
|
|||||||
def get_archive_dir(project_path: Optional[str] = None) -> Path:
|
def get_archive_dir(project_path: Optional[str] = None) -> Path:
|
||||||
return get_conductor_dir(project_path) / "archive"
|
return get_conductor_dir(project_path) / "archive"
|
||||||
|
|
||||||
|
def _resolve_path_info(env_var: str, config_key: str, default: str) -> dict[str, Any]:
|
||||||
|
if env_var in os.environ:
|
||||||
|
return {'path': Path(os.environ[env_var]).resolve(), 'source': f'env:{env_var}'}
|
||||||
|
try:
|
||||||
|
with open(get_config_path(), 'rb') as f:
|
||||||
|
cfg = tomllib.load(f)
|
||||||
|
if 'paths' in cfg and config_key in cfg['paths']:
|
||||||
|
p = Path(cfg['paths'][config_key])
|
||||||
|
if not p.is_absolute():
|
||||||
|
p = (Path(__file__).resolve().parent.parent / p).resolve()
|
||||||
|
return {'path': p, 'source': 'config.toml'}
|
||||||
|
except: pass
|
||||||
|
root_dir = Path(__file__).resolve().parent.parent
|
||||||
|
p = (root_dir / default).resolve()
|
||||||
|
return {'path': p, 'source': 'default'}
|
||||||
|
|
||||||
|
def get_full_path_info() -> dict[str, dict[str, Any]]:
|
||||||
|
return {
|
||||||
|
'logs_dir': _resolve_path_info('SLOP_LOGS_DIR', 'logs_dir', 'logs/sessions'),
|
||||||
|
'scripts_dir': _resolve_path_info('SLOP_SCRIPTS_DIR', 'scripts_dir', 'scripts/generated')
|
||||||
|
}
|
||||||
|
|
||||||
def reset_resolved() -> None:
|
def reset_resolved() -> None:
|
||||||
"""For testing only - clear cached resolutions."""
|
"""For testing only - clear cached resolutions."""
|
||||||
_RESOLVED.clear()
|
_RESOLVED.clear()
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ def save_project(proj: dict[str, Any], path: Union[str, Path], disc_data: Option
|
|||||||
disc_data = proj["discussion"]
|
disc_data = proj["discussion"]
|
||||||
proj = dict(proj)
|
proj = dict(proj)
|
||||||
del proj["discussion"]
|
del proj["discussion"]
|
||||||
|
proj = clean_nones(proj)
|
||||||
with open(path, "wb") as f:
|
with open(path, "wb") as f:
|
||||||
tomli_w.dump(proj, f)
|
tomli_w.dump(proj, f)
|
||||||
if disc_data:
|
if disc_data:
|
||||||
|
|||||||
@@ -112,6 +112,11 @@ def close_session() -> None:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Warning: Could not update auto-whitelist on close: {e}")
|
print(f"Warning: Could not update auto-whitelist on close: {e}")
|
||||||
|
|
||||||
|
def reset_session(label: Optional[str] = None) -> None:
|
||||||
|
"""Closes the current session and opens a new one with the given label."""
|
||||||
|
close_session()
|
||||||
|
open_session(label)
|
||||||
|
|
||||||
def log_api_hook(method: str, path: str, payload: str) -> None:
|
def log_api_hook(method: str, path: str, payload: str) -> None:
|
||||||
"""Log an API hook invocation."""
|
"""Log an API hook invocation."""
|
||||||
if _api_fh is None:
|
if _api_fh is None:
|
||||||
|
|||||||
@@ -0,0 +1,404 @@
|
|||||||
|
import OpenGL.GL as gl
|
||||||
|
|
||||||
|
class ShaderManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.program = None
|
||||||
|
self.bg_program = None
|
||||||
|
self.pp_program = None
|
||||||
|
self.blur_h_program = None
|
||||||
|
self.blur_v_program = None
|
||||||
|
self.blur_fbo = None
|
||||||
|
self.scene_fbo = None
|
||||||
|
self.temp_fbo = None
|
||||||
|
self.scene_tex = None
|
||||||
|
self.blur_tex = None
|
||||||
|
self.temp_tex = None
|
||||||
|
self.fbo_width = 0
|
||||||
|
self.fbo_height = 0
|
||||||
|
self._vao = None
|
||||||
|
|
||||||
|
def _ensure_vao(self):
|
||||||
|
if self._vao is None:
|
||||||
|
try:
|
||||||
|
import sys
|
||||||
|
if sys.platform == "win32":
|
||||||
|
self._vao = gl.glGenVertexArrays(1)
|
||||||
|
else:
|
||||||
|
# Some non-win32 environments might not support VAOs or need different handling
|
||||||
|
self._vao = gl.glGenVertexArrays(1)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if self._vao is not None:
|
||||||
|
gl.glBindVertexArray(self._vao)
|
||||||
|
|
||||||
|
def setup_capture_fbo(self, width, height):
|
||||||
|
if self.blur_fbo is not None:
|
||||||
|
gl.glDeleteFramebuffers(1, [self.blur_fbo])
|
||||||
|
if self.scene_fbo is not None:
|
||||||
|
gl.glDeleteFramebuffers(1, [self.scene_fbo])
|
||||||
|
if self.temp_fbo is not None:
|
||||||
|
gl.glDeleteFramebuffers(1, [self.temp_fbo])
|
||||||
|
if self.scene_tex is not None:
|
||||||
|
gl.glDeleteTextures(1, [self.scene_tex])
|
||||||
|
if self.blur_tex is not None:
|
||||||
|
gl.glDeleteTextures(1, [self.blur_tex])
|
||||||
|
if self.temp_tex is not None:
|
||||||
|
gl.glDeleteTextures(1, [self.temp_tex])
|
||||||
|
|
||||||
|
self.scene_tex = gl.glGenTextures(1)
|
||||||
|
gl.glBindTexture(gl.GL_TEXTURE_2D, self.scene_tex)
|
||||||
|
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, width, height, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, None)
|
||||||
|
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
|
||||||
|
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
|
||||||
|
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
|
||||||
|
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
|
||||||
|
|
||||||
|
self.scene_fbo = gl.glGenFramebuffers(1)
|
||||||
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.scene_fbo)
|
||||||
|
gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, self.scene_tex, 0)
|
||||||
|
if gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) != gl.GL_FRAMEBUFFER_COMPLETE:
|
||||||
|
raise RuntimeError("Scene Framebuffer not complete")
|
||||||
|
|
||||||
|
self.temp_tex = gl.glGenTextures(1)
|
||||||
|
gl.glBindTexture(gl.GL_TEXTURE_2D, self.temp_tex)
|
||||||
|
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, width, height, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, None)
|
||||||
|
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
|
||||||
|
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
|
||||||
|
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
|
||||||
|
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
|
||||||
|
|
||||||
|
self.temp_fbo = gl.glGenFramebuffers(1)
|
||||||
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.temp_fbo)
|
||||||
|
gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, self.temp_tex, 0)
|
||||||
|
if gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) != gl.GL_FRAMEBUFFER_COMPLETE:
|
||||||
|
raise RuntimeError("Temp Framebuffer not complete")
|
||||||
|
|
||||||
|
self.blur_tex = gl.glGenTextures(1)
|
||||||
|
gl.glBindTexture(gl.GL_TEXTURE_2D, self.blur_tex)
|
||||||
|
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, width, height, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, None)
|
||||||
|
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
|
||||||
|
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
|
||||||
|
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
|
||||||
|
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
|
||||||
|
|
||||||
|
self.blur_fbo = gl.glGenFramebuffers(1)
|
||||||
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo)
|
||||||
|
gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, self.blur_tex, 0)
|
||||||
|
if gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) != gl.GL_FRAMEBUFFER_COMPLETE:
|
||||||
|
raise RuntimeError("Blur Framebuffer not complete")
|
||||||
|
|
||||||
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
|
||||||
|
self.fbo_width = width
|
||||||
|
self.fbo_height = height
|
||||||
|
|
||||||
|
def render_background_to_fbo(self, width, height, time):
|
||||||
|
if self.scene_fbo is None or self.fbo_width != width or self.fbo_height != height:
|
||||||
|
self.setup_capture_fbo(width, height)
|
||||||
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.scene_fbo)
|
||||||
|
gl.glViewport(0, 0, width, height)
|
||||||
|
self.render_background(width, height, time)
|
||||||
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
|
||||||
|
|
||||||
|
def prepare_global_blur(self, width, height, radius, tint, opacity, time):
|
||||||
|
self.render_background_to_fbo(width, height, time)
|
||||||
|
self.render_blur(self.scene_tex, width, height, radius, tint, opacity)
|
||||||
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
|
||||||
|
|
||||||
|
def capture_begin(self, width, height):
|
||||||
|
if self.blur_fbo is None or self.fbo_width != width or self.fbo_height != height:
|
||||||
|
self.setup_capture_fbo(width, height)
|
||||||
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo)
|
||||||
|
gl.glViewport(0, 0, width, height)
|
||||||
|
|
||||||
|
def capture_end(self):
|
||||||
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
|
||||||
|
|
||||||
|
def compile_shader(self, vertex_src: str, fragment_src: str) -> int:
|
||||||
|
program = gl.glCreateProgram()
|
||||||
|
|
||||||
|
def _compile(src, shader_type):
|
||||||
|
shader = gl.glCreateShader(shader_type)
|
||||||
|
gl.glShaderSource(shader, src)
|
||||||
|
gl.glCompileShader(shader)
|
||||||
|
|
||||||
|
if not gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS):
|
||||||
|
info_log = gl.glGetShaderInfoLog(shader)
|
||||||
|
if hasattr(info_log, "decode"):
|
||||||
|
info_log = info_log.decode()
|
||||||
|
raise RuntimeError(f"Shader compilation failed: {info_log}")
|
||||||
|
return shader
|
||||||
|
|
||||||
|
vert_shader = _compile(vertex_src, gl.GL_VERTEX_SHADER)
|
||||||
|
frag_shader = _compile(fragment_src, gl.GL_FRAGMENT_SHADER)
|
||||||
|
|
||||||
|
gl.glAttachShader(program, vert_shader)
|
||||||
|
gl.glAttachShader(program, frag_shader)
|
||||||
|
gl.glLinkProgram(program)
|
||||||
|
|
||||||
|
if not gl.glGetProgramiv(program, gl.GL_LINK_STATUS):
|
||||||
|
info_log = gl.glGetProgramInfoLog(program)
|
||||||
|
if hasattr(info_log, "decode"):
|
||||||
|
info_log = info_log.decode()
|
||||||
|
raise RuntimeError(f"Program linking failed: {info_log}")
|
||||||
|
|
||||||
|
gl.glDeleteShader(vert_shader)
|
||||||
|
gl.glDeleteShader(frag_shader)
|
||||||
|
|
||||||
|
self.program = program
|
||||||
|
return program
|
||||||
|
|
||||||
|
def update_uniforms(self, uniforms: dict):
|
||||||
|
if self.program is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
for name, value in uniforms.items():
|
||||||
|
loc = gl.glGetUniformLocation(self.program, name)
|
||||||
|
if loc == -1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(value, float):
|
||||||
|
gl.glUniform1f(loc, value)
|
||||||
|
elif isinstance(value, int):
|
||||||
|
gl.glUniform1i(loc, value)
|
||||||
|
elif isinstance(value, (list, tuple)):
|
||||||
|
if len(value) == 2:
|
||||||
|
gl.glUniform2f(loc, value[0], value[1])
|
||||||
|
elif len(value) == 3:
|
||||||
|
gl.glUniform3f(loc, value[0], value[1], value[2])
|
||||||
|
elif len(value) == 4:
|
||||||
|
gl.glUniform4f(loc, value[0], value[1], value[2], value[3])
|
||||||
|
|
||||||
|
def setup_background_shader(self):
|
||||||
|
vertex_src = """
|
||||||
|
#version 330 core
|
||||||
|
const vec2 positions[4] = vec2[](
|
||||||
|
vec2(-1.0, -1.0),
|
||||||
|
vec2( 1.0, -1.0),
|
||||||
|
vec2(-1.0, 1.0),
|
||||||
|
vec2( 1.0, 1.0)
|
||||||
|
);
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
fragment_src = """
|
||||||
|
#version 330 core
|
||||||
|
uniform float u_time;
|
||||||
|
uniform vec2 u_resolution;
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
float hash(vec2 p) {
|
||||||
|
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
|
||||||
|
}
|
||||||
|
|
||||||
|
float noise(vec2 p) {
|
||||||
|
vec2 i = floor(p);
|
||||||
|
vec2 f = fract(p);
|
||||||
|
vec2 u = f * f * (3.0 - 2.0 * f);
|
||||||
|
return mix(mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x),
|
||||||
|
mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), u.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
|
||||||
|
vec2 p = uv * 2.0 - 1.0;
|
||||||
|
p.x *= u_resolution.x / u_resolution.y;
|
||||||
|
|
||||||
|
// Deep sea background gradient (dark blue)
|
||||||
|
vec3 col = mix(vec3(0.01, 0.03, 0.08), vec3(0.0, 0.08, 0.15), uv.y);
|
||||||
|
|
||||||
|
// Moving blobs / caustics
|
||||||
|
float n = 0.0;
|
||||||
|
float t = u_time * 0.15;
|
||||||
|
n += noise(p * 1.2 + vec2(t * 0.8, t * 0.5)) * 0.4;
|
||||||
|
n += noise(p * 2.5 - vec2(t * 0.4, t * 0.9)) * 0.2;
|
||||||
|
|
||||||
|
col += vec3(0.05, 0.12, 0.22) * n;
|
||||||
|
|
||||||
|
// Bright highlights (caustics approximation)
|
||||||
|
float c = 0.0;
|
||||||
|
for(int i=0; i<3; i++) {
|
||||||
|
vec2 p2 = p * (float(i) + 1.0) * 0.4;
|
||||||
|
p2 += vec2(sin(t + p2.y * 1.5), cos(t + p2.x * 1.5));
|
||||||
|
c += abs(0.015 / (length(p2) - 0.4));
|
||||||
|
}
|
||||||
|
col += vec3(0.1, 0.25, 0.45) * c * 0.12;
|
||||||
|
|
||||||
|
FragColor = vec4(col, 1.0);
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
self.bg_program = self.compile_shader(vertex_src, fragment_src)
|
||||||
|
|
||||||
|
def render_background(self, width, height, time):
|
||||||
|
if not self.bg_program:
|
||||||
|
return
|
||||||
|
gl.glUseProgram(self.bg_program)
|
||||||
|
u_time_loc = gl.glGetUniformLocation(self.bg_program, "u_time")
|
||||||
|
if u_time_loc != -1:
|
||||||
|
gl.glUniform1f(u_time_loc, float(time))
|
||||||
|
u_res_loc = gl.glGetUniformLocation(self.bg_program, "u_resolution")
|
||||||
|
if u_res_loc != -1:
|
||||||
|
gl.glUniform2f(u_res_loc, float(width), float(height))
|
||||||
|
self._ensure_vao()
|
||||||
|
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
||||||
|
gl.glUseProgram(0)
|
||||||
|
|
||||||
|
def setup_post_process_shader(self):
|
||||||
|
vertex_src = """
|
||||||
|
#version 330 core
|
||||||
|
const vec2 positions[4] = vec2[](
|
||||||
|
vec2(-1.0, -1.0),
|
||||||
|
vec2( 1.0, -1.0),
|
||||||
|
vec2(-1.0, 1.0),
|
||||||
|
vec2( 1.0, 1.0)
|
||||||
|
);
|
||||||
|
const vec2 uvs[4] = vec2[](
|
||||||
|
vec2(0.0, 0.0),
|
||||||
|
vec2(1.0, 0.0),
|
||||||
|
vec2(0.0, 1.0),
|
||||||
|
vec2(1.0, 1.0)
|
||||||
|
);
|
||||||
|
out vec2 v_uv;
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
|
||||||
|
v_uv = uvs[gl_VertexID];
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
fragment_src = """
|
||||||
|
#version 330 core
|
||||||
|
in vec2 v_uv;
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_time;
|
||||||
|
out vec4 FragColor;
|
||||||
|
void main() {
|
||||||
|
vec4 color = texture(u_texture, v_uv);
|
||||||
|
float scanline = sin(v_uv.y * 800.0 + u_time * 2.0) * 0.04;
|
||||||
|
color.rgb -= scanline;
|
||||||
|
FragColor = color;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
self.pp_program = self.compile_shader(vertex_src, fragment_src)
|
||||||
|
|
||||||
|
def render_post_process(self, texture_id, width, height, time):
|
||||||
|
if not self.pp_program:
|
||||||
|
return
|
||||||
|
gl.glUseProgram(self.pp_program)
|
||||||
|
gl.glActiveTexture(gl.GL_TEXTURE0)
|
||||||
|
gl.glBindTexture(gl.GL_TEXTURE_2D, texture_id)
|
||||||
|
u_tex_loc = gl.glGetUniformLocation(self.pp_program, "u_texture")
|
||||||
|
if u_tex_loc != -1:
|
||||||
|
gl.glUniform1i(u_tex_loc, 0)
|
||||||
|
u_time_loc = gl.glGetUniformLocation(self.pp_program, "u_time")
|
||||||
|
if u_time_loc != -1:
|
||||||
|
gl.glUniform1f(u_time_loc, float(time))
|
||||||
|
self._ensure_vao()
|
||||||
|
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
||||||
|
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
|
||||||
|
gl.glUseProgram(0)
|
||||||
|
|
||||||
|
def setup_frosted_glass_shader(self):
|
||||||
|
vertex_src = """
|
||||||
|
#version 330 core
|
||||||
|
const vec2 positions[4] = vec2[](
|
||||||
|
vec2(-1.0, -1.0),
|
||||||
|
vec2( 1.0, -1.0),
|
||||||
|
vec2(-1.0, 1.0),
|
||||||
|
vec2( 1.0, 1.0)
|
||||||
|
);
|
||||||
|
const vec2 uvs[4] = vec2[](
|
||||||
|
vec2(0.0, 0.0),
|
||||||
|
vec2(1.0, 0.0),
|
||||||
|
vec2(0.0, 1.0),
|
||||||
|
vec2(1.0, 1.0)
|
||||||
|
);
|
||||||
|
out vec2 v_uv;
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
|
||||||
|
v_uv = uvs[gl_VertexID];
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
fragment_src_h = """
|
||||||
|
#version 330 core
|
||||||
|
in vec2 v_uv;
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_blur_radius;
|
||||||
|
uniform vec2 u_direction;
|
||||||
|
out vec4 FragColor;
|
||||||
|
void main() {
|
||||||
|
float weight[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
|
||||||
|
vec2 res = vec2(textureSize(u_texture, 0));
|
||||||
|
vec2 tex_offset = (u_blur_radius / res) * u_direction * 2.5; // Multiplied by 2.5 for milky effect
|
||||||
|
vec4 result = texture(u_texture, v_uv) * weight[0];
|
||||||
|
for(int i = 1; i < 5; ++i) {
|
||||||
|
result += texture(u_texture, v_uv + tex_offset * float(i)) * weight[i];
|
||||||
|
result += texture(u_texture, v_uv - tex_offset * float(i)) * weight[i];
|
||||||
|
}
|
||||||
|
FragColor = result;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
fragment_src_v = """
|
||||||
|
#version 330 core
|
||||||
|
in vec2 v_uv;
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_blur_radius;
|
||||||
|
uniform vec2 u_direction;
|
||||||
|
uniform float u_tint_intensity;
|
||||||
|
uniform float u_opacity;
|
||||||
|
out vec4 FragColor;
|
||||||
|
void main() {
|
||||||
|
float weight[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
|
||||||
|
vec2 res = vec2(textureSize(u_texture, 0));
|
||||||
|
vec2 tex_offset = (u_blur_radius / res) * u_direction * 2.5; // Multiplied by 2.5 for milky effect
|
||||||
|
vec4 result = texture(u_texture, v_uv) * weight[0];
|
||||||
|
for(int i = 1; i < 5; ++i) {
|
||||||
|
result += texture(u_texture, v_uv + tex_offset * float(i)) * weight[i];
|
||||||
|
result += texture(u_texture, v_uv - tex_offset * float(i)) * weight[i];
|
||||||
|
}
|
||||||
|
vec3 tint_color = vec3(0.05, 0.07, 0.12); // Slightly deeper tint
|
||||||
|
vec3 tinted = mix(result.rgb, tint_color, u_tint_intensity);
|
||||||
|
FragColor = vec4(tinted, result.a * u_opacity);
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
self.blur_h_program = self.compile_shader(vertex_src, fragment_src_h)
|
||||||
|
self.blur_v_program = self.compile_shader(vertex_src, fragment_src_v)
|
||||||
|
|
||||||
|
def render_blur(self, texture_id, width, height, radius, tint, opacity):
|
||||||
|
if not self.blur_h_program or not self.blur_v_program:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._ensure_vao()
|
||||||
|
|
||||||
|
# Pass 1: Horizontal blur to temp_fbo
|
||||||
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.temp_fbo)
|
||||||
|
gl.glViewport(0, 0, width, height)
|
||||||
|
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
|
||||||
|
|
||||||
|
gl.glUseProgram(self.blur_h_program)
|
||||||
|
gl.glActiveTexture(gl.GL_TEXTURE0)
|
||||||
|
gl.glBindTexture(gl.GL_TEXTURE_2D, texture_id)
|
||||||
|
|
||||||
|
gl.glUniform1i(gl.glGetUniformLocation(self.blur_h_program, "u_texture"), 0)
|
||||||
|
gl.glUniform1f(gl.glGetUniformLocation(self.blur_h_program, "u_blur_radius"), float(radius))
|
||||||
|
gl.glUniform2f(gl.glGetUniformLocation(self.blur_h_program, "u_direction"), 1.0, 0.0)
|
||||||
|
|
||||||
|
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
||||||
|
|
||||||
|
# Pass 2: Vertical blur to blur_fbo
|
||||||
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo)
|
||||||
|
gl.glViewport(0, 0, width, height)
|
||||||
|
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
|
||||||
|
|
||||||
|
gl.glUseProgram(self.blur_v_program)
|
||||||
|
gl.glActiveTexture(gl.GL_TEXTURE0)
|
||||||
|
gl.glBindTexture(gl.GL_TEXTURE_2D, self.temp_tex)
|
||||||
|
|
||||||
|
gl.glUniform1i(gl.glGetUniformLocation(self.blur_v_program, "u_texture"), 0)
|
||||||
|
gl.glUniform1f(gl.glGetUniformLocation(self.blur_v_program, "u_blur_radius"), float(radius))
|
||||||
|
gl.glUniform2f(gl.glGetUniformLocation(self.blur_v_program, "u_direction"), 0.0, 1.0)
|
||||||
|
gl.glUniform1f(gl.glGetUniformLocation(self.blur_v_program, "u_tint_intensity"), float(tint))
|
||||||
|
gl.glUniform1f(gl.glGetUniformLocation(self.blur_v_program, "u_opacity"), float(opacity))
|
||||||
|
|
||||||
|
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
||||||
|
|
||||||
|
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
|
||||||
|
gl.glUseProgram(0)
|
||||||
@@ -268,6 +268,12 @@ _current_palette: str = "DPG Default"
|
|||||||
_current_font_path: str = ""
|
_current_font_path: str = ""
|
||||||
_current_font_size: float = 14.0
|
_current_font_size: float = 14.0
|
||||||
_current_scale: float = 1.0
|
_current_scale: float = 1.0
|
||||||
|
_shader_config: dict[str, Any] = {
|
||||||
|
"crt": False,
|
||||||
|
"bloom": False,
|
||||||
|
"bg": "none",
|
||||||
|
"custom_window_frame": False,
|
||||||
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------ public API
|
# ------------------------------------------------------------------ public API
|
||||||
|
|
||||||
@@ -286,6 +292,14 @@ def get_current_font_size() -> float:
|
|||||||
def get_current_scale() -> float:
|
def get_current_scale() -> float:
|
||||||
return _current_scale
|
return _current_scale
|
||||||
|
|
||||||
|
def get_shader_config(key: str) -> Any:
|
||||||
|
"""Get a specific shader configuration value."""
|
||||||
|
return _shader_config.get(key)
|
||||||
|
|
||||||
|
def get_window_frame_config() -> bool:
|
||||||
|
"""Get the window frame configuration."""
|
||||||
|
return _shader_config.get("custom_window_frame", False)
|
||||||
|
|
||||||
def get_palette_colours(name: str) -> dict[str, Any]:
|
def get_palette_colours(name: str) -> dict[str, Any]:
|
||||||
"""Return a copy of the colour dict for the named palette."""
|
"""Return a copy of the colour dict for the named palette."""
|
||||||
return dict(_PALETTES.get(name, {}))
|
return dict(_PALETTES.get(name, {}))
|
||||||
@@ -388,4 +402,9 @@ def load_from_config(config: dict[str, Any]) -> None:
|
|||||||
if font_path:
|
if font_path:
|
||||||
apply_font(font_path, font_size)
|
apply_font(font_path, font_size)
|
||||||
set_scale(scale)
|
set_scale(scale)
|
||||||
|
global _shader_config
|
||||||
|
_shader_config["crt"] = t.get("shader_crt", False)
|
||||||
|
_shader_config["bloom"] = t.get("shader_bloom", False)
|
||||||
|
_shader_config["bg"] = t.get("shader_bg", "none")
|
||||||
|
_shader_config["custom_window_frame"] = t.get("custom_window_frame", False)
|
||||||
|
|
||||||
|
|||||||
+41
-1
@@ -235,6 +235,10 @@ _current_font_size: float = 16.0
|
|||||||
_current_scale: float = 1.0
|
_current_scale: float = 1.0
|
||||||
_transparency: float = 1.0
|
_transparency: float = 1.0
|
||||||
_child_transparency: float = 1.0
|
_child_transparency: float = 1.0
|
||||||
|
_frosted_glass_enabled: bool = False
|
||||||
|
_frosted_blur_radius: float = 8.0
|
||||||
|
_frosted_tint_intensity: float = 0.1
|
||||||
|
_frosted_opacity: float = 1.0
|
||||||
|
|
||||||
# ------------------------------------------------------------------ public API
|
# ------------------------------------------------------------------ public API
|
||||||
|
|
||||||
@@ -269,6 +273,34 @@ def set_child_transparency(val: float) -> None:
|
|||||||
_child_transparency = val
|
_child_transparency = val
|
||||||
apply(_current_palette)
|
apply(_current_palette)
|
||||||
|
|
||||||
|
def get_frosted_glass_enabled() -> bool:
|
||||||
|
return _frosted_glass_enabled
|
||||||
|
|
||||||
|
def set_frosted_glass_enabled(val: bool) -> None:
|
||||||
|
global _frosted_glass_enabled
|
||||||
|
_frosted_glass_enabled = val
|
||||||
|
|
||||||
|
def get_frosted_blur_radius() -> float:
|
||||||
|
return _frosted_blur_radius
|
||||||
|
|
||||||
|
def set_frosted_blur_radius(val: float) -> None:
|
||||||
|
global _frosted_blur_radius
|
||||||
|
_frosted_blur_radius = val
|
||||||
|
|
||||||
|
def get_frosted_tint_intensity() -> float:
|
||||||
|
return _frosted_tint_intensity
|
||||||
|
|
||||||
|
def set_frosted_tint_intensity(val: float) -> None:
|
||||||
|
global _frosted_tint_intensity
|
||||||
|
_frosted_tint_intensity = val
|
||||||
|
|
||||||
|
def get_frosted_opacity() -> float:
|
||||||
|
return _frosted_opacity
|
||||||
|
|
||||||
|
def set_frosted_opacity(val: float) -> None:
|
||||||
|
global _frosted_opacity
|
||||||
|
_frosted_opacity = val
|
||||||
|
|
||||||
def apply(palette_name: str) -> None:
|
def apply(palette_name: str) -> None:
|
||||||
"""
|
"""
|
||||||
Apply a named palette by setting all ImGui style colors and applying global professional styling.
|
Apply a named palette by setting all ImGui style colors and applying global professional styling.
|
||||||
@@ -350,13 +382,17 @@ def save_to_config(config: dict) -> None:
|
|||||||
config["theme"]["scale"] = _current_scale
|
config["theme"]["scale"] = _current_scale
|
||||||
config["theme"]["transparency"] = _transparency
|
config["theme"]["transparency"] = _transparency
|
||||||
config["theme"]["child_transparency"] = _child_transparency
|
config["theme"]["child_transparency"] = _child_transparency
|
||||||
|
config["theme"]["frosted_glass_enabled"] = _frosted_glass_enabled
|
||||||
|
config["theme"]["frosted_blur_radius"] = _frosted_blur_radius
|
||||||
|
config["theme"]["frosted_tint_intensity"] = _frosted_tint_intensity
|
||||||
|
config["theme"]["frosted_opacity"] = _frosted_opacity
|
||||||
sys.stderr.write(f"[DEBUG theme_2] save_to_config: palette={_current_palette}, transparency={_transparency}\n")
|
sys.stderr.write(f"[DEBUG theme_2] save_to_config: palette={_current_palette}, transparency={_transparency}\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
|
|
||||||
def load_from_config(config: dict) -> None:
|
def load_from_config(config: dict) -> None:
|
||||||
"""Read [theme] from config. Font is handled separately at startup."""
|
"""Read [theme] from config. Font is handled separately at startup."""
|
||||||
import sys
|
import sys
|
||||||
global _current_font_path, _current_font_size, _current_scale, _current_palette, _transparency, _child_transparency
|
global _current_font_path, _current_font_size, _current_scale, _current_palette, _transparency, _child_transparency, _frosted_glass_enabled, _frosted_blur_radius, _frosted_tint_intensity, _frosted_opacity
|
||||||
t = config.get("theme", {})
|
t = config.get("theme", {})
|
||||||
sys.stderr.write(f"[DEBUG theme_2] load_from_config raw: {t}\n")
|
sys.stderr.write(f"[DEBUG theme_2] load_from_config raw: {t}\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
@@ -369,6 +405,10 @@ def load_from_config(config: dict) -> None:
|
|||||||
_current_scale = float(t.get("scale", 1.0))
|
_current_scale = float(t.get("scale", 1.0))
|
||||||
_transparency = float(t.get("transparency", 1.0))
|
_transparency = float(t.get("transparency", 1.0))
|
||||||
_child_transparency = float(t.get("child_transparency", 1.0))
|
_child_transparency = float(t.get("child_transparency", 1.0))
|
||||||
|
_frosted_glass_enabled = bool(t.get("frosted_glass_enabled", False))
|
||||||
|
_frosted_blur_radius = float(t.get("frosted_blur_radius", 8.0))
|
||||||
|
_frosted_tint_intensity = float(t.get("frosted_tint_intensity", 0.1))
|
||||||
|
_frosted_opacity = float(t.get("frosted_opacity", 1.0))
|
||||||
sys.stderr.write(f"[DEBUG theme_2] load_from_config effective: palette={_current_palette}, transparency={_transparency}\n")
|
sys.stderr.write(f"[DEBUG theme_2] load_from_config effective: palette={_current_palette}, transparency={_transparency}\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
|||||||
+18
-3
@@ -200,14 +200,29 @@ def live_gui() -> Generator[tuple[subprocess.Popen, str], None, None]:
|
|||||||
temp_workspace.mkdir(parents=True, exist_ok=True)
|
temp_workspace.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# Create minimal project files to avoid cluttering root
|
# Create minimal project files to avoid cluttering root
|
||||||
(temp_workspace / "manual_slop.toml").write_text("[project]\nname = 'TestProject'\n", encoding="utf-8")
|
(temp_workspace / "manual_slop.toml").write_text("[project]\nname = 'TestProject'\n\n[conductor]\ndir = 'conductor'\n", encoding="utf-8")
|
||||||
(temp_workspace / "conductor" / "tracks").mkdir(parents=True, exist_ok=True)
|
(temp_workspace / "conductor" / "tracks").mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Create a local config.toml in temp_workspace
|
||||||
|
config_content = {
|
||||||
|
'ai': {'provider': 'gemini', 'model': 'gemini-2.5-flash-lite'},
|
||||||
|
'projects': {
|
||||||
|
'paths': [str((temp_workspace / 'manual_slop.toml').absolute())],
|
||||||
|
'active': str((temp_workspace / 'manual_slop.toml').absolute())
|
||||||
|
},
|
||||||
|
'paths': {
|
||||||
|
'logs_dir': str((temp_workspace / "logs").absolute()),
|
||||||
|
'scripts_dir': str((temp_workspace / "scripts" / "generated").absolute())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
import tomli_w
|
||||||
|
with open(temp_workspace / 'config.toml', 'wb') as f:
|
||||||
|
tomli_w.dump(config_content, f)
|
||||||
|
|
||||||
# Resolve absolute paths for shared resources
|
# Resolve absolute paths for shared resources
|
||||||
project_root = Path(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
project_root = Path(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||||
config_file = temp_workspace / "config.toml"
|
config_file = temp_workspace / "config.toml"
|
||||||
if not config_file.exists():
|
|
||||||
config_file = project_root / "config.toml"
|
|
||||||
cred_file = project_root / "credentials.toml"
|
cred_file = project_root / "credentials.toml"
|
||||||
mcp_file = project_root / "mcp_env.toml"
|
mcp_file = project_root / "mcp_env.toml"
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ def test_app_processes_new_actions() -> None:
|
|||||||
with patch('src.models.load_config', return_value={}), \
|
with patch('src.models.load_config', return_value={}), \
|
||||||
patch('src.performance_monitor.PerformanceMonitor'), \
|
patch('src.performance_monitor.PerformanceMonitor'), \
|
||||||
patch('src.session_logger.open_session'), \
|
patch('src.session_logger.open_session'), \
|
||||||
|
patch('src.session_logger.reset_session'), \
|
||||||
patch('src.app_controller.AppController._prune_old_logs'), \
|
patch('src.app_controller.AppController._prune_old_logs'), \
|
||||||
patch('src.app_controller.AppController._load_active_project'):
|
patch('src.app_controller.AppController._load_active_project'):
|
||||||
app = gui_2.App()
|
app = gui_2.App()
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class TestArchBoundaryPhase2(unittest.TestCase):
|
|||||||
with patch('src.models.load_config', return_value={}), \
|
with patch('src.models.load_config', return_value={}), \
|
||||||
patch('src.performance_monitor.PerformanceMonitor'), \
|
patch('src.performance_monitor.PerformanceMonitor'), \
|
||||||
patch('src.session_logger.open_session'), \
|
patch('src.session_logger.open_session'), \
|
||||||
|
patch('src.session_logger.reset_session'), \
|
||||||
patch('src.app_controller.AppController._prune_old_logs'), \
|
patch('src.app_controller.AppController._prune_old_logs'), \
|
||||||
patch('src.app_controller.AppController._init_ai_and_hooks'):
|
patch('src.app_controller.AppController._init_ai_and_hooks'):
|
||||||
controller = AppController()
|
controller = AppController()
|
||||||
@@ -69,6 +70,7 @@ class TestArchBoundaryPhase2(unittest.TestCase):
|
|||||||
with patch('src.models.load_config', return_value={}), \
|
with patch('src.models.load_config', return_value={}), \
|
||||||
patch('src.performance_monitor.PerformanceMonitor'), \
|
patch('src.performance_monitor.PerformanceMonitor'), \
|
||||||
patch('src.session_logger.open_session'), \
|
patch('src.session_logger.open_session'), \
|
||||||
|
patch('src.session_logger.reset_session'), \
|
||||||
patch('src.app_controller.AppController._prune_old_logs'), \
|
patch('src.app_controller.AppController._prune_old_logs'), \
|
||||||
patch('src.app_controller.AppController._init_ai_and_hooks'):
|
patch('src.app_controller.AppController._init_ai_and_hooks'):
|
||||||
controller = AppController()
|
controller = AppController()
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import pytest
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
def test_dynamic_background_rendering():
|
||||||
|
# Mock OpenGL before importing
|
||||||
|
with patch("src.shader_manager.gl") as mock_gl:
|
||||||
|
from src.shader_manager import ShaderManager
|
||||||
|
|
||||||
|
# Setup mock return values
|
||||||
|
mock_gl.glCreateProgram.return_value = 1
|
||||||
|
mock_gl.glCreateShader.return_value = 2
|
||||||
|
mock_gl.glGetShaderiv.return_value = 1 # GL_TRUE
|
||||||
|
mock_gl.glGetProgramiv.return_value = 1 # GL_TRUE
|
||||||
|
mock_gl.glGetUniformLocation.return_value = 10
|
||||||
|
|
||||||
|
manager = ShaderManager()
|
||||||
|
manager.setup_background_shader()
|
||||||
|
|
||||||
|
# Verify background program was created
|
||||||
|
assert manager.bg_program == 1
|
||||||
|
assert mock_gl.glCreateProgram.called
|
||||||
|
|
||||||
|
# Render background
|
||||||
|
manager.render_background(800, 600, 1.0)
|
||||||
|
|
||||||
|
# Verify OpenGL calls
|
||||||
|
mock_gl.glUseProgram.assert_any_call(1)
|
||||||
|
mock_gl.glDrawArrays.assert_called_with(mock_gl.GL_TRIANGLE_STRIP, 0, 4)
|
||||||
|
mock_gl.glUseProgram.assert_any_call(0)
|
||||||
|
|
||||||
|
# Verify uniforms were updated
|
||||||
|
mock_gl.glUniform1f.assert_called()
|
||||||
|
mock_gl.glUniform2f.assert_called()
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import pytest
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
import OpenGL.GL as gl
|
||||||
|
|
||||||
|
def test_shader_manager_fbo_initialization():
|
||||||
|
with patch("src.shader_manager.gl") as mock_gl:
|
||||||
|
mock_gl.glGenFramebuffers.side_effect = [1, 2]
|
||||||
|
mock_gl.glGenTextures.side_effect = [3, 4]
|
||||||
|
mock_gl.glCheckFramebufferStatus.return_value = mock_gl.GL_FRAMEBUFFER_COMPLETE
|
||||||
|
|
||||||
|
from src.shader_manager import ShaderManager
|
||||||
|
manager = ShaderManager()
|
||||||
|
|
||||||
|
manager.setup_capture_fbo(800, 600)
|
||||||
|
|
||||||
|
assert manager.scene_fbo == 1
|
||||||
|
assert manager.blur_fbo == 2
|
||||||
|
assert manager.scene_tex == 3
|
||||||
|
assert manager.blur_tex == 4
|
||||||
|
assert mock_gl.glGenFramebuffers.call_count == 2
|
||||||
|
assert mock_gl.glGenTextures.call_count == 2
|
||||||
|
assert mock_gl.glCheckFramebufferStatus.call_count == 2
|
||||||
|
|
||||||
|
def test_shader_manager_capture_lifecycle():
|
||||||
|
with patch("src.shader_manager.gl") as mock_gl:
|
||||||
|
mock_gl.glCheckFramebufferStatus.return_value = mock_gl.GL_FRAMEBUFFER_COMPLETE
|
||||||
|
mock_gl.glGenFramebuffers.side_effect = [1, 2]
|
||||||
|
mock_gl.glGenTextures.side_effect = [3, 4]
|
||||||
|
from src.shader_manager import ShaderManager
|
||||||
|
manager = ShaderManager()
|
||||||
|
|
||||||
|
# Ensure setup is called on first capture
|
||||||
|
manager.capture_begin(1024, 768)
|
||||||
|
assert manager.fbo_width == 1024
|
||||||
|
assert manager.fbo_height == 768
|
||||||
|
# Should bind the blur FBO
|
||||||
|
mock_gl.glBindFramebuffer.assert_any_call(mock_gl.GL_FRAMEBUFFER, manager.blur_fbo)
|
||||||
|
|
||||||
|
mock_gl.glBindFramebuffer.reset_mock()
|
||||||
|
manager.capture_end()
|
||||||
|
# Verify unbind (glBindFramebuffer(..., 0))
|
||||||
|
mock_gl.glBindFramebuffer.assert_called_with(mock_gl.GL_FRAMEBUFFER, 0)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import pytest
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
def test_shader_manager_frosted_glass_compilation():
|
||||||
|
# Mock OpenGL before importing ShaderManager
|
||||||
|
with patch("src.shader_manager.gl") as mock_gl:
|
||||||
|
mock_gl.glCreateProgram.return_value = 1
|
||||||
|
mock_gl.glCreateShader.return_value = 2
|
||||||
|
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
|
||||||
|
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
|
||||||
|
|
||||||
|
from src.shader_manager import ShaderManager
|
||||||
|
manager = ShaderManager()
|
||||||
|
|
||||||
|
# This should fail initially because the method doesn't exist
|
||||||
|
manager.setup_frosted_glass_shader()
|
||||||
|
|
||||||
|
assert manager.blur_program is not None
|
||||||
|
assert mock_gl.glCreateProgram.called
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import pytest
|
||||||
|
from unittest.mock import patch
|
||||||
|
from src.gui_2 import App
|
||||||
|
|
||||||
|
@patch("src.gui_2.immapp.run")
|
||||||
|
@patch("src.gui_2.session_logger.close_session")
|
||||||
|
@patch("src.gui_2.imgui.save_ini_settings_to_disk")
|
||||||
|
@patch("sys.argv", ["gui_2.py"])
|
||||||
|
def test_app_window_is_borderless(mock_save_ini, mock_close, mock_run):
|
||||||
|
app = App()
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
assert app.runner_params is not None
|
||||||
|
# This assertion will fail initially because we haven't implemented it yet
|
||||||
|
assert getattr(app.runner_params.app_window_params, 'borderless', False) is True, "Window should be borderless"
|
||||||
@@ -15,6 +15,7 @@ def mock_gui() -> App:
|
|||||||
patch('src.project_manager.migrate_from_legacy_config', return_value={}),
|
patch('src.project_manager.migrate_from_legacy_config', return_value={}),
|
||||||
patch('src.project_manager.save_project'),
|
patch('src.project_manager.save_project'),
|
||||||
patch('src.session_logger.open_session'),
|
patch('src.session_logger.open_session'),
|
||||||
|
patch('src.session_logger.reset_session'),
|
||||||
patch('src.app_controller.AppController._init_ai_and_hooks'),
|
patch('src.app_controller.AppController._init_ai_and_hooks'),
|
||||||
patch('src.app_controller.AppController._fetch_models')
|
patch('src.app_controller.AppController._fetch_models')
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import pytest
|
||||||
|
import time
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
def test_gui_frosted_background_call():
|
||||||
|
# Mock ShaderManager and OpenGL functions
|
||||||
|
with patch("src.gui_2.ShaderManager") as mock_sm_class, \
|
||||||
|
patch("src.gui_2.gl") as mock_gl, \
|
||||||
|
patch("src.gui_2.imgui") as mock_imgui, \
|
||||||
|
patch("src.gui_2.theme") as mock_theme:
|
||||||
|
|
||||||
|
mock_sm = mock_sm_class.return_value
|
||||||
|
mock_sm.blur_tex = 2
|
||||||
|
mock_theme.get_frosted_glass_enabled.return_value = True
|
||||||
|
|
||||||
|
mock_imgui.get_io().display_size = MagicMock(x=1920, y=1080)
|
||||||
|
|
||||||
|
from src.gui_2 import App
|
||||||
|
with patch.object(App, '__init__', return_value=None):
|
||||||
|
app = App()
|
||||||
|
app.shader_manager = mock_sm
|
||||||
|
|
||||||
|
# Simulate frame
|
||||||
|
app._render_frosted_background(pos=MagicMock(x=10, y=10), size=MagicMock(x=100, y=100))
|
||||||
|
|
||||||
|
# Now it should only call add_image
|
||||||
|
assert mock_imgui.get_window_draw_list().add_image.called
|
||||||
|
# It no longer calls these
|
||||||
|
assert not mock_sm.setup_capture_fbo.called
|
||||||
|
assert not mock_sm.render_blur.called
|
||||||
|
assert not mock_sm.capture_begin.called
|
||||||
|
assert not mock_sm.capture_end.called
|
||||||
|
|
||||||
|
def test_gui_global_blur_call():
|
||||||
|
with patch("src.gui_2.ShaderManager") as mock_sm_class, \
|
||||||
|
patch("src.gui_2.imgui") as mock_imgui, \
|
||||||
|
patch("src.gui_2.theme") as mock_theme, \
|
||||||
|
patch("src.gui_2.bg_shader") as mock_bg:
|
||||||
|
|
||||||
|
mock_sm = mock_sm_class.return_value
|
||||||
|
mock_theme.get_frosted_glass_enabled.return_value = True
|
||||||
|
mock_theme.is_nerv_active.return_value = False
|
||||||
|
mock_imgui.get_io().display_size = MagicMock(x=1920, y=1080)
|
||||||
|
mock_bg.get_bg.return_value = MagicMock(enabled=False)
|
||||||
|
|
||||||
|
from src.gui_2 import App
|
||||||
|
with patch.object(App, '__init__', return_value=None):
|
||||||
|
app = App()
|
||||||
|
app.shader_manager = mock_sm
|
||||||
|
app.shader_uniforms = {
|
||||||
|
'frosted_blur_radius': 10.0,
|
||||||
|
'frosted_tint_intensity': 0.5,
|
||||||
|
'frosted_opacity': 0.8
|
||||||
|
}
|
||||||
|
app.ai_status = "idle"
|
||||||
|
app.ui_crt_filter = False
|
||||||
|
app.controller = MagicMock()
|
||||||
|
app.perf_profiling_enabled = False
|
||||||
|
app.is_viewing_prior_session = False
|
||||||
|
app.config = {}
|
||||||
|
app.project = {}
|
||||||
|
app.show_windows = {}
|
||||||
|
app.ui_auto_scroll_comms = False
|
||||||
|
app._comms_log_dirty = False
|
||||||
|
app._tool_log_dirty = False
|
||||||
|
app._pending_comms_lock = MagicMock()
|
||||||
|
app._pending_comms = []
|
||||||
|
app.ui_focus_agent = None
|
||||||
|
app._last_ui_focus_agent = None
|
||||||
|
app.perf_monitor = MagicMock()
|
||||||
|
app._process_pending_gui_tasks = MagicMock()
|
||||||
|
app._process_pending_history_adds = MagicMock()
|
||||||
|
app._render_track_proposal_modal = MagicMock()
|
||||||
|
app._render_patch_modal = MagicMock()
|
||||||
|
app._render_save_preset_modal = MagicMock()
|
||||||
|
app._render_preset_manager_window = MagicMock()
|
||||||
|
app._render_tool_preset_manager_window = MagicMock()
|
||||||
|
app._render_persona_editor_window = MagicMock()
|
||||||
|
app._last_autosave = time.time()
|
||||||
|
app._autosave_interval = 60
|
||||||
|
app._render_custom_title_bar = MagicMock()
|
||||||
|
app._render_shader_live_editor = MagicMock()
|
||||||
|
|
||||||
|
try:
|
||||||
|
app._gui_func()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert mock_sm.prepare_global_blur.called
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import pytest
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
from src import paths
|
||||||
|
|
||||||
|
# We mock App to avoid the heavy initialization logic
|
||||||
|
class MockApp:
|
||||||
|
def __init__(self):
|
||||||
|
self.ui_conductor_dir = '/mock/conductor'
|
||||||
|
self.ui_logs_dir = '/mock/logs'
|
||||||
|
self.ui_scripts_dir = '/mock/scripts'
|
||||||
|
self.config = {"paths": {}}
|
||||||
|
self.ai_status = ""
|
||||||
|
|
||||||
|
def init_state(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
from src.gui_2 import App
|
||||||
|
_save_paths = App._save_paths
|
||||||
|
|
||||||
|
def test_save_paths():
|
||||||
|
mock_app = MockApp()
|
||||||
|
|
||||||
|
with patch('src.models.save_config') as mock_save, \
|
||||||
|
patch('shutil.copy') as mock_copy, \
|
||||||
|
patch('src.paths.get_config_path') as mock_get_cfg, \
|
||||||
|
patch('src.paths.reset_resolved') as mock_reset, \
|
||||||
|
patch.object(MockApp, 'init_state') as mock_init:
|
||||||
|
|
||||||
|
mock_get_cfg.return_value = MagicMock()
|
||||||
|
mock_get_cfg.return_value.exists.return_value = True
|
||||||
|
|
||||||
|
mock_app.ui_conductor_dir = '/new/conductor'
|
||||||
|
mock_app._save_paths()
|
||||||
|
|
||||||
|
# Verify config update
|
||||||
|
assert 'conductor_dir' not in mock_app.config['paths']
|
||||||
|
mock_save.assert_called_once()
|
||||||
|
mock_copy.assert_called_once()
|
||||||
|
assert 'applied' in mock_app.ai_status
|
||||||
|
mock_reset.assert_called_once()
|
||||||
|
mock_init.assert_called_once()
|
||||||
@@ -6,6 +6,7 @@ import os
|
|||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
from src import paths
|
||||||
|
|
||||||
|
|
||||||
def test_track_proposal_editing(app_instance):
|
def test_track_proposal_editing(app_instance):
|
||||||
@@ -30,7 +31,7 @@ def test_track_proposal_editing(app_instance):
|
|||||||
assert app_instance.proposed_tracks[0]['title'] == "New Title"
|
assert app_instance.proposed_tracks[0]['title'] == "New Title"
|
||||||
|
|
||||||
|
|
||||||
def test_conductor_setup_scan(app_instance, tmp_path):
|
def test_conductor_setup_scan(app_instance, tmp_path, monkeypatch):
|
||||||
"""
|
"""
|
||||||
Verifies that the conductor setup scan properly iterates through the conductor directory,
|
Verifies that the conductor setup scan properly iterates through the conductor directory,
|
||||||
counts files and lines, and identifies active tracks.
|
counts files and lines, and identifies active tracks.
|
||||||
@@ -44,6 +45,9 @@ def test_conductor_setup_scan(app_instance, tmp_path):
|
|||||||
(cond_dir / "tracks").mkdir(exist_ok=True)
|
(cond_dir / "tracks").mkdir(exist_ok=True)
|
||||||
(cond_dir / "tracks" / "track1").mkdir(exist_ok=True)
|
(cond_dir / "tracks" / "track1").mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
monkeypatch.setenv('SLOP_CONDUCTOR_DIR', str((tmp_path / 'conductor').resolve()))
|
||||||
|
paths.reset_resolved()
|
||||||
|
|
||||||
app_instance._cb_run_conductor_setup()
|
app_instance._cb_run_conductor_setup()
|
||||||
|
|
||||||
# ANTI-SIMPLIFICATION: Assert that the summary output correctly counts files/lines/tracks
|
# ANTI-SIMPLIFICATION: Assert that the summary output correctly counts files/lines/tracks
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ def test_gui_updates_on_event(app_instance: App) -> None:
|
|||||||
mock_stats = {"percentage": 50.0, "current": 500, "limit": 1000}
|
mock_stats = {"percentage": 50.0, "current": 500, "limit": 1000}
|
||||||
app_instance.last_md = "mock_md"
|
app_instance.last_md = "mock_md"
|
||||||
with patch('src.ai_client.get_token_stats', return_value=mock_stats):
|
with patch('src.ai_client.get_token_stats', return_value=mock_stats):
|
||||||
|
# Drain the queue
|
||||||
|
while not app_instance.event_queue.empty():
|
||||||
|
app_instance.event_queue.get()
|
||||||
# Simulate receiving an event from the API client thread
|
# Simulate receiving an event from the API client thread
|
||||||
app_instance._on_api_event(payload={"text": "test"})
|
app_instance._on_api_event(payload={"text": "test"})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import pytest
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
from imgui_bundle import imgui
|
||||||
|
|
||||||
|
def test_gui_window_controls_minimize_maximize_close():
|
||||||
|
# We will test the logic of the title bar controls that we are about to implement.
|
||||||
|
from src.gui_2 import App
|
||||||
|
app = App()
|
||||||
|
|
||||||
|
with patch("src.gui_2.win32gui") as mock_win32gui, \
|
||||||
|
patch("src.gui_2.win32con") as mock_win32con, \
|
||||||
|
patch("src.gui_2.imgui") as mock_imgui, \
|
||||||
|
patch("ctypes.pythonapi.PyCapsule_GetPointer") as mock_get_pointer:
|
||||||
|
|
||||||
|
# Setup mock for HWND
|
||||||
|
mock_viewport = MagicMock()
|
||||||
|
mock_viewport.platform_handle_raw = "mock_capsule"
|
||||||
|
mock_imgui.get_main_viewport.return_value = mock_viewport
|
||||||
|
mock_get_pointer.return_value = 12345
|
||||||
|
mock_imgui.get_window_width.return_value = 800.0
|
||||||
|
mock_imgui.get_cursor_pos_x.return_value = 100.0
|
||||||
|
mock_imgui.get_io().display_size.x = 800.0
|
||||||
|
mock_style = MagicMock()
|
||||||
|
mock_style.item_spacing.x = 4.0
|
||||||
|
mock_imgui.get_style.return_value = mock_style
|
||||||
|
# Setup mock for buttons to simulate clicks
|
||||||
|
# Let's say _render_custom_title_bar uses imgui.button
|
||||||
|
# We will test the close button logic
|
||||||
|
# Since it's UI code, we just simulate the conditions
|
||||||
|
mock_imgui.button.return_value = True # Simulate all buttons being clicked
|
||||||
|
|
||||||
|
# Avoid hitting actual menu logic that requires real runner_params
|
||||||
|
mock_imgui.begin_menu.return_value = False
|
||||||
|
|
||||||
|
app.runner_params = MagicMock()
|
||||||
|
|
||||||
|
# Call the method (now in _show_menus)
|
||||||
|
app._show_menus()
|
||||||
|
|
||||||
|
# Verify that win32gui calls are made for minimize, maximize, close
|
||||||
|
# Since all buttons returned True, all actions should be triggered in this dummy test
|
||||||
|
assert mock_win32gui.ShowWindow.called
|
||||||
|
assert mock_win32gui.PostMessage.called
|
||||||
@@ -13,6 +13,7 @@ class TestHeadlessAPI(unittest.TestCase):
|
|||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
with patch('src.models.load_config', return_value={'ai': {'provider': 'gemini', 'model': 'gemini-2.5-flash-lite'}, 'projects': {}, 'gui': {'show_windows': {}}}), \
|
with patch('src.models.load_config', return_value={'ai': {'provider': 'gemini', 'model': 'gemini-2.5-flash-lite'}, 'projects': {}, 'gui': {'show_windows': {}}}), \
|
||||||
patch('src.session_logger.open_session'), \
|
patch('src.session_logger.open_session'), \
|
||||||
|
patch('src.session_logger.reset_session'), \
|
||||||
patch('src.ai_client.set_provider'), \
|
patch('src.ai_client.set_provider'), \
|
||||||
patch('src.performance_monitor.PerformanceMonitor'), \
|
patch('src.performance_monitor.PerformanceMonitor'), \
|
||||||
patch('src.session_logger.close_session'), \
|
patch('src.session_logger.close_session'), \
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ def test_hooks_enabled_via_cli(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||||||
with patch('src.models.load_config', return_value={}), \
|
with patch('src.models.load_config', return_value={}), \
|
||||||
patch('src.performance_monitor.PerformanceMonitor'), \
|
patch('src.performance_monitor.PerformanceMonitor'), \
|
||||||
patch('src.session_logger.open_session'), \
|
patch('src.session_logger.open_session'), \
|
||||||
|
patch('src.session_logger.reset_session'), \
|
||||||
patch('src.app_controller.AppController._prune_old_logs'), \
|
patch('src.app_controller.AppController._prune_old_logs'), \
|
||||||
patch('src.app_controller.AppController._init_ai_and_hooks'):
|
patch('src.app_controller.AppController._init_ai_and_hooks'):
|
||||||
app = App()
|
app = App()
|
||||||
@@ -25,6 +26,7 @@ def test_hooks_disabled_by_default() -> None:
|
|||||||
with patch('src.models.load_config', return_value={}), \
|
with patch('src.models.load_config', return_value={}), \
|
||||||
patch('src.performance_monitor.PerformanceMonitor'), \
|
patch('src.performance_monitor.PerformanceMonitor'), \
|
||||||
patch('src.session_logger.open_session'), \
|
patch('src.session_logger.open_session'), \
|
||||||
|
patch('src.session_logger.reset_session'), \
|
||||||
patch('src.app_controller.AppController._prune_old_logs'), \
|
patch('src.app_controller.AppController._prune_old_logs'), \
|
||||||
patch('src.app_controller.AppController._init_ai_and_hooks'):
|
patch('src.app_controller.AppController._init_ai_and_hooks'):
|
||||||
app = App()
|
app = App()
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ def test_user_request_integration_flow(mock_app: App) -> None:
|
|||||||
disc_text="History",
|
disc_text="History",
|
||||||
base_dir="."
|
base_dir="."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
while not app.controller.event_queue.empty():
|
||||||
|
app.controller.event_queue.get()
|
||||||
|
|
||||||
# 2. Call the handler directly since start_services is mocked (no event loop thread)
|
# 2. Call the handler directly since start_services is mocked (no event loop thread)
|
||||||
# But _handle_request_event itself puts a 'response' event in the queue.
|
# But _handle_request_event itself puts a 'response' event in the queue.
|
||||||
# Our mock_app fixture mocks start_services, so _process_event_queue is NOT running.
|
# Our mock_app fixture mocks start_services, so _process_event_queue is NOT running.
|
||||||
@@ -87,6 +91,10 @@ def test_user_request_error_handling(mock_app: App) -> None:
|
|||||||
disc_text="",
|
disc_text="",
|
||||||
base_dir="."
|
base_dir="."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
while not app.controller.event_queue.empty():
|
||||||
|
app.controller.event_queue.get()
|
||||||
|
|
||||||
app.controller._handle_request_event(event)
|
app.controller._handle_request_event(event)
|
||||||
|
|
||||||
# Manually consume from queue
|
# Manually consume from queue
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ history = []
|
|||||||
def app_instance(mock_config: Path, mock_project: Path, monkeypatch: pytest.MonkeyPatch) -> App:
|
def app_instance(mock_config: Path, mock_project: Path, monkeypatch: pytest.MonkeyPatch) -> App:
|
||||||
monkeypatch.setattr("src.models.CONFIG_PATH", mock_config)
|
monkeypatch.setattr("src.models.CONFIG_PATH", mock_config)
|
||||||
with patch("src.project_manager.load_project") as mock_load, \
|
with patch("src.project_manager.load_project") as mock_load, \
|
||||||
patch("src.session_logger.open_session"):
|
patch("src.session_logger.open_session"), \
|
||||||
|
patch("src.session_logger.reset_session"):
|
||||||
mock_load.return_value = {
|
mock_load.return_value = {
|
||||||
"project": {"name": "test"},
|
"project": {"name": "test"},
|
||||||
"discussion": {"roles": ["User", "AI"], "active": "main", "discussions": {"main": {"history": []}}},
|
"discussion": {"roles": ["User", "AI"], "active": "main", "discussions": {"main": {"history": []}}},
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ def test_mma_dashboard_refresh(app_instance: Any) -> None:
|
|||||||
assert len(app.tracks) == 2
|
assert len(app.tracks) == 2
|
||||||
assert app.tracks[0]["id"] == "track_1"
|
assert app.tracks[0]["id"] == "track_1"
|
||||||
assert app.tracks[1]["id"] == "track_2"
|
assert app.tracks[1]["id"] == "track_2"
|
||||||
mock_pm.get_all_tracks.assert_called_with(app.ui_files_base_dir)
|
mock_pm.get_all_tracks.assert_called_with(app.active_project_root)
|
||||||
|
|
||||||
|
|
||||||
def test_mma_dashboard_initialization_refresh(app_instance: Any) -> None:
|
def test_mma_dashboard_initialization_refresh(app_instance: Any) -> None:
|
||||||
|
|||||||
+27
-35
@@ -9,61 +9,53 @@ def reset_paths():
|
|||||||
yield
|
yield
|
||||||
paths.reset_resolved()
|
paths.reset_resolved()
|
||||||
|
|
||||||
def test_default_paths():
|
def test_default_paths(tmp_path, monkeypatch):
|
||||||
assert paths.get_conductor_dir() == Path("conductor")
|
monkeypatch.setenv("SLOP_CONFIG", str(tmp_path / "non_existent.toml"))
|
||||||
assert paths.get_logs_dir() == Path("logs/sessions")
|
|
||||||
assert paths.get_scripts_dir() == Path("scripts/generated")
|
|
||||||
# config path should now be an absolute path relative to src/paths.py
|
|
||||||
root_dir = Path(paths.__file__).resolve().parent.parent
|
root_dir = Path(paths.__file__).resolve().parent.parent
|
||||||
assert paths.get_config_path() == root_dir / "config.toml"
|
assert paths.get_logs_dir() == root_dir / "logs/sessions"
|
||||||
assert paths.get_tracks_dir() == Path("conductor/tracks")
|
assert paths.get_scripts_dir() == root_dir / "scripts/generated"
|
||||||
assert paths.get_archive_dir() == Path("conductor/archive")
|
# config path should be what we set in env
|
||||||
|
assert paths.get_config_path() == tmp_path / "non_existent.toml"
|
||||||
|
|
||||||
def test_env_var_overrides(monkeypatch):
|
def test_env_var_overrides(tmp_path, monkeypatch):
|
||||||
monkeypatch.setenv("SLOP_CONDUCTOR_DIR", "custom_conductor")
|
# Absolute env var
|
||||||
monkeypatch.setenv("SLOP_LOGS_DIR", "custom_logs")
|
abs_logs = (tmp_path / "abs_logs").resolve()
|
||||||
monkeypatch.setenv("SLOP_SCRIPTS_DIR", "custom_scripts")
|
monkeypatch.setenv("SLOP_LOGS_DIR", str(abs_logs))
|
||||||
|
assert paths.get_logs_dir() == abs_logs
|
||||||
assert paths.get_conductor_dir() == Path("custom_conductor")
|
|
||||||
assert paths.get_logs_dir() == Path("custom_logs")
|
|
||||||
assert paths.get_scripts_dir() == Path("custom_scripts")
|
|
||||||
assert paths.get_tracks_dir() == Path("custom_conductor/tracks")
|
|
||||||
|
|
||||||
def test_config_overrides(tmp_path, monkeypatch):
|
def test_config_overrides(tmp_path, monkeypatch):
|
||||||
|
root_dir = Path(paths.__file__).resolve().parent.parent
|
||||||
config_file = tmp_path / "custom_config.toml"
|
config_file = tmp_path / "custom_config.toml"
|
||||||
content = """
|
content = """
|
||||||
[paths]
|
[paths]
|
||||||
conductor_dir = "cfg_conductor"
|
|
||||||
logs_dir = "cfg_logs"
|
logs_dir = "cfg_logs"
|
||||||
scripts_dir = "cfg_scripts"
|
scripts_dir = "cfg_scripts"
|
||||||
"""
|
"""
|
||||||
config_file.write_text(content)
|
config_file.write_text(content)
|
||||||
monkeypatch.setenv("SLOP_CONFIG", str(config_file))
|
monkeypatch.setenv("SLOP_CONFIG", str(config_file))
|
||||||
|
|
||||||
# Need to update the _CONFIG_PATH in paths.py since it's set at import
|
assert paths.get_logs_dir() == root_dir / "cfg_logs"
|
||||||
# Actually, the get_config_path() uses _CONFIG_PATH which is Path(os.environ.get("SLOP_CONFIG", "config.toml"))
|
assert paths.get_scripts_dir() == root_dir / "cfg_scripts"
|
||||||
# But it's defined at module level. Let's see if we can reload it or if monkeypatching early enough works.
|
|
||||||
# In src/paths.py: _CONFIG_PATH: Path = Path(os.environ.get("SLOP_CONFIG", "config.toml"))
|
|
||||||
# This is set when src.paths is first imported.
|
|
||||||
|
|
||||||
# For the test to work, we might need to manually set paths._CONFIG_PATH or reload the module.
|
|
||||||
# paths._CONFIG_PATH = config_file # No longer needed
|
|
||||||
|
|
||||||
assert paths.get_conductor_dir() == Path("cfg_conductor")
|
|
||||||
assert paths.get_logs_dir() == Path("cfg_logs")
|
|
||||||
assert paths.get_scripts_dir() == Path("cfg_scripts")
|
|
||||||
|
|
||||||
def test_precedence(tmp_path, monkeypatch):
|
def test_precedence(tmp_path, monkeypatch):
|
||||||
|
root_dir = Path(paths.__file__).resolve().parent.parent
|
||||||
config_file = tmp_path / "custom_config.toml"
|
config_file = tmp_path / "custom_config.toml"
|
||||||
content = """
|
content = """
|
||||||
[paths]
|
[paths]
|
||||||
conductor_dir = "cfg_conductor"
|
logs_dir = "cfg_logs"
|
||||||
"""
|
"""
|
||||||
config_file.write_text(content)
|
config_file.write_text(content)
|
||||||
monkeypatch.setenv("SLOP_CONFIG", str(config_file))
|
monkeypatch.setenv("SLOP_CONFIG", str(config_file))
|
||||||
monkeypatch.setenv("SLOP_CONDUCTOR_DIR", "env_conductor")
|
monkeypatch.setenv("SLOP_LOGS_DIR", "env_logs")
|
||||||
|
|
||||||
# paths._CONFIG_PATH = config_file # No longer needed
|
|
||||||
|
|
||||||
# Env var should take precedence over config
|
# Env var should take precedence over config
|
||||||
assert paths.get_conductor_dir() == Path("env_conductor")
|
assert paths.get_logs_dir() == (root_dir / "env_logs").resolve()
|
||||||
|
|
||||||
|
def test_conductor_dir_project_relative(tmp_path):
|
||||||
|
# Should default to tmp_path/conductor
|
||||||
|
project_path = str(tmp_path)
|
||||||
|
base = (tmp_path / 'conductor').resolve()
|
||||||
|
assert paths.get_conductor_dir(project_path) == base
|
||||||
|
assert paths.get_tracks_dir(project_path) == base / "tracks"
|
||||||
|
assert paths.get_archive_dir(project_path) == base / "archive"
|
||||||
|
assert paths.get_track_state_dir("test_track", project_path) == base / "tracks" / "test_track"
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ def test_per_tier_model_persistence():
|
|||||||
patch("src.gui_2.ai_client.list_models", return_value=["gpt-4", "claude-3"]),
|
patch("src.gui_2.ai_client.list_models", return_value=["gpt-4", "claude-3"]),
|
||||||
patch("src.performance_monitor.PerformanceMonitor"),
|
patch("src.performance_monitor.PerformanceMonitor"),
|
||||||
patch("src.gui_2.api_hooks.HookServer"),
|
patch("src.gui_2.api_hooks.HookServer"),
|
||||||
patch("src.gui_2.session_logger.open_session")
|
patch("src.gui_2.session_logger.open_session"),
|
||||||
|
patch("src.gui_2.session_logger.reset_session")
|
||||||
):
|
):
|
||||||
|
|
||||||
app = App()
|
app = App()
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import unittest
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Mock OpenGL.GL before importing ShaderManager
|
||||||
|
gl_mock = MagicMock()
|
||||||
|
# Setup some constants
|
||||||
|
gl_mock.GL_VERTEX_SHADER = 0x8B31
|
||||||
|
gl_mock.GL_FRAGMENT_SHADER = 0x8B30
|
||||||
|
gl_mock.GL_COMPILE_STATUS = 0x8B81
|
||||||
|
gl_mock.GL_LINK_STATUS = 0x8B82
|
||||||
|
gl_mock.GL_TEXTURE0 = 0x84C0
|
||||||
|
gl_mock.GL_TEXTURE_2D = 0x0DE1
|
||||||
|
gl_mock.GL_TRIANGLE_STRIP = 0x0005
|
||||||
|
|
||||||
|
opengl_mock = MagicMock()
|
||||||
|
sys.modules['OpenGL'] = opengl_mock
|
||||||
|
sys.modules['OpenGL.GL'] = gl_mock
|
||||||
|
opengl_mock.GL = gl_mock
|
||||||
|
|
||||||
|
from src.shader_manager import ShaderManager
|
||||||
|
|
||||||
|
class TestPostProcess(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
gl_mock.reset_mock()
|
||||||
|
# Mock return values for shader compilation
|
||||||
|
gl_mock.glCreateProgram.return_value = 1
|
||||||
|
gl_mock.glCreateShader.return_value = 2
|
||||||
|
gl_mock.glGetShaderiv.return_value = 1 # GL_TRUE
|
||||||
|
gl_mock.glGetProgramiv.return_value = 1 # GL_TRUE
|
||||||
|
gl_mock.glGetUniformLocation.return_value = 10
|
||||||
|
|
||||||
|
def test_setup_post_process_shader(self):
|
||||||
|
sm = ShaderManager()
|
||||||
|
sm.setup_post_process_shader()
|
||||||
|
self.assertEqual(sm.pp_program, 1)
|
||||||
|
gl_mock.glCreateProgram.assert_called()
|
||||||
|
gl_mock.glLinkProgram.assert_called_with(1)
|
||||||
|
|
||||||
|
def test_render_post_process(self):
|
||||||
|
sm = ShaderManager()
|
||||||
|
sm.pp_program = 1
|
||||||
|
sm.render_post_process(texture_id=5, width=800, height=600, time=1.0)
|
||||||
|
|
||||||
|
gl_mock.glUseProgram.assert_any_call(1)
|
||||||
|
gl_mock.glActiveTexture.assert_called_with(gl_mock.GL_TEXTURE0)
|
||||||
|
gl_mock.glBindTexture.assert_any_call(gl_mock.GL_TEXTURE_2D, 5)
|
||||||
|
gl_mock.glUniform1f.assert_called()
|
||||||
|
gl_mock.glDrawArrays.assert_called_with(gl_mock.GL_TRIANGLE_STRIP, 0, 4)
|
||||||
|
gl_mock.glUseProgram.assert_any_call(0)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
+24
-20
@@ -1,25 +1,17 @@
|
|||||||
import os
|
import os
|
||||||
import pytest
|
import pytest
|
||||||
import tomllib
|
import json
|
||||||
import tomli_w
|
import tomli_w
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from src import paths
|
from src import paths
|
||||||
|
from src import project_manager
|
||||||
|
|
||||||
def test_get_conductor_dir_default():
|
def test_get_conductor_dir_default():
|
||||||
paths.reset_resolved()
|
paths.reset_resolved()
|
||||||
# Should return default "conductor" relative to root
|
# Should return absolute path to "conductor" in project root
|
||||||
expected = Path(__file__).resolve().parent.parent / "conductor"
|
expected = Path(__file__).resolve().parent.parent / "conductor"
|
||||||
assert paths.get_conductor_dir() == expected
|
assert paths.get_conductor_dir() == expected
|
||||||
|
|
||||||
def test_get_conductor_dir_project_specific_no_toml(tmp_path):
|
|
||||||
paths.reset_resolved()
|
|
||||||
project_root = tmp_path / "my_project"
|
|
||||||
project_root.mkdir()
|
|
||||||
|
|
||||||
# Should default to project_root / "conductor" if no manual_slop.toml
|
|
||||||
res = paths.get_conductor_dir(project_path=str(project_root))
|
|
||||||
assert res == project_root / "conductor"
|
|
||||||
|
|
||||||
def test_get_conductor_dir_project_specific_with_toml(tmp_path):
|
def test_get_conductor_dir_project_specific_with_toml(tmp_path):
|
||||||
paths.reset_resolved()
|
paths.reset_resolved()
|
||||||
project_root = tmp_path / "my_project"
|
project_root = tmp_path / "my_project"
|
||||||
@@ -38,18 +30,30 @@ def test_get_conductor_dir_project_specific_with_toml(tmp_path):
|
|||||||
res = paths.get_conductor_dir(project_path=str(project_root))
|
res = paths.get_conductor_dir(project_path=str(project_root))
|
||||||
assert res == project_root / "custom_tracks"
|
assert res == project_root / "custom_tracks"
|
||||||
|
|
||||||
def test_get_tracks_dir_project_specific(tmp_path):
|
def test_get_all_tracks_project_specific(tmp_path):
|
||||||
paths.reset_resolved()
|
paths.reset_resolved()
|
||||||
project_root = tmp_path / "my_project"
|
project_root = tmp_path / "my_project"
|
||||||
project_root.mkdir()
|
project_root.mkdir()
|
||||||
|
|
||||||
res = paths.get_tracks_dir(project_path=str(project_root))
|
# Custom conductor dir
|
||||||
assert res == project_root / "conductor" / "tracks"
|
custom_dir = project_root / "my_conductor"
|
||||||
|
custom_dir.mkdir()
|
||||||
|
tracks_dir = custom_dir / "tracks"
|
||||||
|
tracks_dir.mkdir()
|
||||||
|
|
||||||
def test_get_track_state_dir_project_specific(tmp_path):
|
# Create a dummy track
|
||||||
paths.reset_resolved()
|
track_dir = tracks_dir / "test_track_20260312"
|
||||||
project_root = tmp_path / "my_project"
|
track_dir.mkdir()
|
||||||
project_root.mkdir()
|
with open(track_dir / "metadata.json", "w") as f:
|
||||||
|
json.dump({"id": "test_track", "title": "Test Track"}, f)
|
||||||
|
|
||||||
res = paths.get_track_state_dir("track-123", project_path=str(project_root))
|
# Setup manual_slop.toml
|
||||||
assert res == project_root / "conductor" / "tracks" / "track-123"
|
toml_path = project_root / "manual_slop.toml"
|
||||||
|
config = {"conductor": {"dir": "my_conductor"}}
|
||||||
|
with open(toml_path, "wb") as f:
|
||||||
|
f.write(tomli_w.dumps(config).encode())
|
||||||
|
|
||||||
|
# project_manager.get_all_tracks(base_dir) should now find it
|
||||||
|
tracks = project_manager.get_all_tracks(str(project_root))
|
||||||
|
assert len(tracks) == 1
|
||||||
|
assert tracks[0]["title"] == "Test Track"
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import pytest
|
||||||
|
import tomllib
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Generator
|
||||||
|
from src import session_logger
|
||||||
|
import time
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def temp_logs(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Generator[Path, None, None]:
|
||||||
|
# Ensure closed before starting
|
||||||
|
session_logger.close_session()
|
||||||
|
monkeypatch.setattr(session_logger, "_comms_fh", None)
|
||||||
|
|
||||||
|
log_dir = tmp_path / "logs"
|
||||||
|
scripts_dir = tmp_path / "scripts" / "generated"
|
||||||
|
log_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
scripts_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
from src import paths
|
||||||
|
monkeypatch.setattr(paths, "get_logs_dir", lambda: log_dir)
|
||||||
|
monkeypatch.setattr(paths, "get_scripts_dir", lambda: scripts_dir)
|
||||||
|
|
||||||
|
yield log_dir
|
||||||
|
# Cleanup: Close handles if open
|
||||||
|
session_logger.close_session()
|
||||||
|
|
||||||
|
def test_reset_session(temp_logs: Path) -> None:
|
||||||
|
# 1. Open first session
|
||||||
|
label1 = "label1"
|
||||||
|
session_logger.open_session(label=label1)
|
||||||
|
|
||||||
|
subdirs = [d for d in temp_logs.iterdir() if d.is_dir()]
|
||||||
|
assert len(subdirs) == 1
|
||||||
|
session1_dir = subdirs[0]
|
||||||
|
assert session1_dir.name.endswith(f"_{label1}")
|
||||||
|
assert session_logger._comms_fh is not None
|
||||||
|
|
||||||
|
# 2. Reset to second session
|
||||||
|
time.sleep(1.1)
|
||||||
|
|
||||||
|
label2 = "label2"
|
||||||
|
session_logger.reset_session(label=label2)
|
||||||
|
|
||||||
|
subdirs = sorted([d for d in temp_logs.iterdir() if d.is_dir()], key=lambda x: x.name)
|
||||||
|
assert len(subdirs) == 2
|
||||||
|
session2_dir = subdirs[1]
|
||||||
|
assert session2_dir.name.endswith(f"_{label2}")
|
||||||
|
assert session_logger._comms_fh is not None
|
||||||
|
|
||||||
|
# Verify new handle points to new dir
|
||||||
|
assert str(session2_dir) in str(Path(session_logger._comms_fh.name).resolve())
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import pytest
|
||||||
|
from unittest.mock import patch
|
||||||
|
from src import theme
|
||||||
|
|
||||||
|
def test_shader_config_parsing():
|
||||||
|
config = {
|
||||||
|
"theme": {
|
||||||
|
"shader_crt": True,
|
||||||
|
"shader_bloom": False,
|
||||||
|
"shader_bg": "noise",
|
||||||
|
"custom_window_frame": True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch("src.theme.apply"), \
|
||||||
|
patch("src.theme.apply_font"), \
|
||||||
|
patch("src.theme.set_scale"):
|
||||||
|
theme.load_from_config(config)
|
||||||
|
|
||||||
|
assert theme.get_shader_config("crt") is True
|
||||||
|
assert theme.get_shader_config("bloom") is False
|
||||||
|
assert theme.get_shader_config("bg") == "noise"
|
||||||
|
assert theme.get_window_frame_config() is True
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import pytest
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
def test_shader_live_editor_renders():
|
||||||
|
from src.gui_2 import App
|
||||||
|
app = App()
|
||||||
|
app.show_windows["Shader Editor"] = True
|
||||||
|
|
||||||
|
with patch("src.gui_2.imgui") as mock_imgui:
|
||||||
|
mock_imgui.begin.return_value = (True, True)
|
||||||
|
mock_imgui.slider_float.return_value = (False, 1.0)
|
||||||
|
app._render_shader_live_editor()
|
||||||
|
assert mock_imgui.begin.called
|
||||||
|
assert mock_imgui.end.called
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import pytest
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
def test_shader_manager_initialization_and_compilation():
|
||||||
|
# Import inside test to allow patching OpenGL before import if needed
|
||||||
|
# In this case, we patch the OpenGL.GL functions used by ShaderManager
|
||||||
|
with patch("src.shader_manager.gl") as mock_gl:
|
||||||
|
mock_gl.glCreateProgram.return_value = 1
|
||||||
|
mock_gl.glCreateShader.return_value = 2
|
||||||
|
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
|
||||||
|
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
|
||||||
|
|
||||||
|
from src.shader_manager import ShaderManager
|
||||||
|
|
||||||
|
manager = ShaderManager()
|
||||||
|
|
||||||
|
# Basic vertex and fragment shader source
|
||||||
|
vert_src = "void main() {}"
|
||||||
|
frag_src = "void main() {}"
|
||||||
|
|
||||||
|
program_id = manager.compile_shader(vert_src, frag_src)
|
||||||
|
|
||||||
|
assert program_id == 1
|
||||||
|
assert mock_gl.glCreateProgram.called
|
||||||
|
assert mock_gl.glCreateShader.called
|
||||||
|
|
||||||
|
def test_shader_manager_uniform_update():
|
||||||
|
# Mock OpenGL.GL functions
|
||||||
|
with patch("src.shader_manager.gl") as mock_gl:
|
||||||
|
from src.shader_manager import ShaderManager
|
||||||
|
manager = ShaderManager()
|
||||||
|
# Set a mock program ID
|
||||||
|
manager.program = 1
|
||||||
|
|
||||||
|
# Mock glGetUniformLocation to return some valid locations
|
||||||
|
# u_time -> 10, u_resolution -> 20
|
||||||
|
def mock_get_loc(prog, name):
|
||||||
|
if name == "u_time": return 10
|
||||||
|
if name == "u_resolution": return 20
|
||||||
|
return -1
|
||||||
|
|
||||||
|
mock_gl.glGetUniformLocation.side_effect = mock_get_loc
|
||||||
|
|
||||||
|
# Call the method
|
||||||
|
manager.update_uniforms({"u_time": 1.5, "u_resolution": (800, 600)})
|
||||||
|
|
||||||
|
# Assert calls
|
||||||
|
mock_gl.glGetUniformLocation.assert_any_call(1, "u_time")
|
||||||
|
mock_gl.glGetUniformLocation.assert_any_call(1, "u_resolution")
|
||||||
|
mock_gl.glUniform1f.assert_called_once_with(10, 1.5)
|
||||||
|
mock_gl.glUniform2f.assert_called_once_with(20, 800, 600)
|
||||||
@@ -16,6 +16,7 @@ def controller():
|
|||||||
patch('src.project_manager.migrate_from_legacy_config', return_value={}),
|
patch('src.project_manager.migrate_from_legacy_config', return_value={}),
|
||||||
patch('src.project_manager.save_project'),
|
patch('src.project_manager.save_project'),
|
||||||
patch('src.session_logger.open_session'),
|
patch('src.session_logger.open_session'),
|
||||||
|
patch('src.session_logger.reset_session'),
|
||||||
patch('src.app_controller.AppController._init_ai_and_hooks'),
|
patch('src.app_controller.AppController._init_ai_and_hooks'),
|
||||||
patch('src.app_controller.AppController._fetch_models')
|
patch('src.app_controller.AppController._fetch_models')
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -33,3 +33,18 @@ def test_theme_apply_sets_rounding_and_padding(monkeypatch):
|
|||||||
assert mock_style.item_spacing == (8.0, 4.0)
|
assert mock_style.item_spacing == (8.0, 4.0)
|
||||||
assert mock_style.item_inner_spacing == (4.0, 4.0)
|
assert mock_style.item_inner_spacing == (4.0, 4.0)
|
||||||
assert mock_style.scrollbar_size == 14.0
|
assert mock_style.scrollbar_size == 14.0
|
||||||
|
|
||||||
|
def test_frosted_glass_enabled_toggle():
|
||||||
|
theme.set_frosted_glass_enabled(True)
|
||||||
|
assert theme.get_frosted_glass_enabled() is True
|
||||||
|
theme.set_frosted_glass_enabled(False)
|
||||||
|
assert theme.get_frosted_glass_enabled() is False
|
||||||
|
|
||||||
|
def test_theme_config_frosted_glass():
|
||||||
|
config = {"theme": {"frosted_glass_enabled": True}}
|
||||||
|
theme.load_from_config(config)
|
||||||
|
assert theme.get_frosted_glass_enabled() is True
|
||||||
|
|
||||||
|
new_config = {}
|
||||||
|
theme.save_to_config(new_config)
|
||||||
|
assert new_config["theme"]["frosted_glass_enabled"] is True
|
||||||
|
|||||||
Reference in New Issue
Block a user