This commit is contained in:
2026-03-06 19:54:52 -05:00
parent 36a1bd4257
commit 0e9f84f026
7 changed files with 565 additions and 307 deletions

View File

@@ -18,6 +18,10 @@ paths = [
] ]
active = "C:\\projects\\manual_slop\\tests\\artifacts\\temp_project.toml" active = "C:\\projects\\manual_slop\\tests\\artifacts\\temp_project.toml"
[gui]
separate_message_panel = false
separate_response_panel = false
[gui.show_windows] [gui.show_windows]
"Context Hub" = true "Context Hub" = true
"Files & Media" = true "Files & Media" = true
@@ -29,6 +33,8 @@ active = "C:\\projects\\manual_slop\\tests\\artifacts\\temp_project.toml"
"Tier 4: QA" = true "Tier 4: QA" = true
"Discussion Hub" = true "Discussion Hub" = true
"Operations Hub" = true "Operations Hub" = true
Message = false
Response = false
Theme = true Theme = true
"Log Management" = true "Log Management" = true
Diagnostics = true Diagnostics = true

View File

@@ -79,7 +79,7 @@ DockId=0x0000000F,2
[Window][Theme] [Window][Theme]
Pos=0,17 Pos=0,17
Size=692,824 Size=705,927
Collapsed=0 Collapsed=0
DockId=0x00000005,1 DockId=0x00000005,1
@@ -89,14 +89,14 @@ Size=900,700
Collapsed=0 Collapsed=0
[Window][Diagnostics] [Window][Diagnostics]
Pos=694,17 Pos=707,17
Size=257,794 Size=1141,657
Collapsed=0 Collapsed=0
DockId=0x00000010,1 DockId=0x0000001A,0
[Window][Context Hub] [Window][Context Hub]
Pos=0,17 Pos=0,17
Size=692,824 Size=705,927
Collapsed=0 Collapsed=0
DockId=0x00000005,0 DockId=0x00000005,0
@@ -107,26 +107,26 @@ Collapsed=0
DockId=0x0000000D,0 DockId=0x0000000D,0
[Window][Discussion Hub] [Window][Discussion Hub]
Pos=953,17 Pos=1850,17
Size=727,1082 Size=1137,1311
Collapsed=0 Collapsed=0
DockId=0x00000012,0 DockId=0x00000013,0
[Window][Operations Hub] [Window][Operations Hub]
Pos=694,17 Pos=707,676
Size=257,794 Size=1141,652
Collapsed=0 Collapsed=0
DockId=0x00000010,0 DockId=0x0000001B,0
[Window][Files & Media] [Window][Files & Media]
Pos=0,843 Pos=0,946
Size=692,357 Size=705,1191
Collapsed=0 Collapsed=0
DockId=0x00000006,1 DockId=0x00000006,1
[Window][AI Settings] [Window][AI Settings]
Pos=0,843 Pos=0,946
Size=692,357 Size=705,1191
Collapsed=0 Collapsed=0
DockId=0x00000006,0 DockId=0x00000006,0
@@ -136,16 +136,16 @@ Size=416,325
Collapsed=0 Collapsed=0
[Window][MMA Dashboard] [Window][MMA Dashboard]
Pos=953,1101 Pos=2989,17
Size=727,99 Size=851,2120
Collapsed=0 Collapsed=0
DockId=0x00000013,0 DockId=0x00000004,0
[Window][Log Management] [Window][Log Management]
Pos=953,17 Pos=2989,17
Size=727,1082 Size=851,2120
Collapsed=0 Collapsed=0
DockId=0x00000012,1 DockId=0x00000004,1
[Window][Track Proposal] [Window][Track Proposal]
Pos=709,326 Pos=709,326
@@ -153,28 +153,28 @@ Size=262,209
Collapsed=0 Collapsed=0
[Window][Tier 1: Strategy] [Window][Tier 1: Strategy]
Pos=953,1101 Pos=707,1330
Size=727,99 Size=463,807
Collapsed=0 Collapsed=0
DockId=0x00000013,1 DockId=0x00000014,0
[Window][Tier 2: Tech Lead] [Window][Tier 2: Tech Lead]
Pos=953,1101 Pos=1172,1330
Size=727,99 Size=730,807
Collapsed=0 Collapsed=0
DockId=0x00000013,2 DockId=0x00000016,0
[Window][Tier 4: QA] [Window][Tier 4: QA]
Pos=694,813 Pos=2453,1330
Size=257,387 Size=534,807
Collapsed=0 Collapsed=0
DockId=0x00000011,1 DockId=0x00000019,0
[Window][Tier 3: Workers] [Window][Tier 3: Workers]
Pos=694,813 Pos=1904,1330
Size=257,387 Size=547,807
Collapsed=0 Collapsed=0
DockId=0x00000011,0 DockId=0x00000018,0
[Window][Approve PowerShell Command] [Window][Approve PowerShell Command]
Pos=649,435 Pos=649,435
@@ -209,26 +209,34 @@ Column 2 Weight=1.0000
Column 3 Weight=1.0000 Column 3 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,17 Size=1680,1183 Split=Y DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,17 Size=3840,2120 Split=Y
DockNode ID=0x0000000C Parent=0xAFC85805 SizeRef=1362,1041 Split=X Selected=0x5D11106F DockNode ID=0x0000000C Parent=0xAFC85805 SizeRef=1362,1041 Split=X Selected=0x5D11106F
DockNode ID=0x00000003 Parent=0x0000000C SizeRef=711,1183 Split=X DockNode ID=0x00000003 Parent=0x0000000C SizeRef=2987,1183 Split=X
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=Y Selected=0xF4139CA2 DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=Y Selected=0xF4139CA2
DockNode ID=0x00000002 Parent=0x0000000B SizeRef=1029,1119 Split=X Selected=0xF4139CA2 DockNode ID=0x00000002 Parent=0x0000000B SizeRef=1029,1119 Split=X Selected=0xF4139CA2
DockNode ID=0x00000007 Parent=0x00000002 SizeRef=452,858 Split=Y Selected=0x8CA2375C DockNode ID=0x00000007 Parent=0x00000002 SizeRef=705,858 Split=Y Selected=0x8CA2375C
DockNode ID=0x00000005 Parent=0x00000007 SizeRef=295,824 Selected=0xF4139CA2 DockNode ID=0x00000005 Parent=0x00000007 SizeRef=295,927 Selected=0xF4139CA2
DockNode ID=0x00000006 Parent=0x00000007 SizeRef=295,995 CentralNode=1 Selected=0x7BD57D6A DockNode ID=0x00000006 Parent=0x00000007 SizeRef=295,1191 CentralNode=1 Selected=0x7BD57D6A
DockNode ID=0x0000000E Parent=0x00000002 SizeRef=257,858 Split=Y Selected=0x418C7449 DockNode ID=0x0000000E Parent=0x00000002 SizeRef=2280,858 Split=Y Selected=0x418C7449
DockNode ID=0x00000010 Parent=0x0000000E SizeRef=868,1065 Selected=0xB4CBF21A DockNode ID=0x00000010 Parent=0x0000000E SizeRef=868,1311 Split=X Selected=0x418C7449
DockNode ID=0x00000011 Parent=0x0000000E SizeRef=868,520 Selected=0x5CDB7A4B DockNode ID=0x00000012 Parent=0x00000010 SizeRef=1141,402 Split=Y Selected=0xB4CBF21A
DockNode ID=0x00000001 Parent=0x0000000B SizeRef=1029,775 Selected=0x8B4EBFA6 DockNode ID=0x0000001A Parent=0x00000012 SizeRef=1141,657 Selected=0xB4CBF21A
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6 DockNode ID=0x0000001B Parent=0x00000012 SizeRef=1141,652 Selected=0x418C7449
DockNode ID=0x00000004 Parent=0x0000000C SizeRef=727,1183 Split=Y Selected=0x2C0206CE DockNode ID=0x00000013 Parent=0x00000010 SizeRef=1137,402 Selected=0x6F2B5B04
DockNode ID=0x00000012 Parent=0x00000004 SizeRef=905,1082 Selected=0x6F2B5B04 DockNode ID=0x00000011 Parent=0x0000000E SizeRef=868,807 Split=X Selected=0x5CDB7A4B
DockNode ID=0x00000013 Parent=0x00000004 SizeRef=905,99 Selected=0xBB346584 DockNode ID=0x00000014 Parent=0x00000011 SizeRef=463,837 Selected=0xBB346584
DockNode ID=0x0000000F Parent=0xAFC85805 SizeRef=1362,451 Selected=0xDD6419BC DockNode ID=0x00000015 Parent=0x00000011 SizeRef=1815,837 Split=X Selected=0x5CDB7A4B
DockNode ID=0x00000016 Parent=0x00000015 SizeRef=730,837 Selected=0x390E7942
DockNode ID=0x00000017 Parent=0x00000015 SizeRef=1083,837 Split=X Selected=0x655BC6E9
DockNode ID=0x00000018 Parent=0x00000017 SizeRef=547,874 Selected=0x655BC6E9
DockNode ID=0x00000019 Parent=0x00000017 SizeRef=534,874 Selected=0x5CDB7A4B
DockNode ID=0x00000001 Parent=0x0000000B SizeRef=1029,775 Selected=0x8B4EBFA6
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
DockNode ID=0x00000004 Parent=0x0000000C SizeRef=851,1183 Selected=0x3AEC3498
DockNode ID=0x0000000F Parent=0xAFC85805 SizeRef=1362,451 Selected=0xDD6419BC
;;;<<<Layout_655921752_Default>>>;;; ;;;<<<Layout_655921752_Default>>>;;;
;;;<<<HelloImGui_Misc>>>;;; ;;;<<<HelloImGui_Misc>>>;;;

View File

@@ -159,3 +159,211 @@ PROMPT:
role: tool role: tool
Here are the results: {"content": "done"} 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.
<context>
</context>
[DISCUSSION HISTORY]
## Discussion History
### Discussion Excerpt 1
@2026-03-06T19:34:06
System:
[PERFORMANCE ALERT] Frame time high: 430.6ms. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 2
@2026-03-06T19:34:41
System:
[PERFORMANCE ALERT] Frame time high: 58.2ms. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 3
@2026-03-06T19:38:51
System:
[PERFORMANCE ALERT] Frame time high: 409.3ms. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 4
@2026-03-06T19:40:43
System:
[PERFORMANCE ALERT] Frame time high: 64.5ms. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 5
@2026-03-06T19:41:13
System:
[PERFORMANCE ALERT] CPU usage high: 94.0%. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 6
@2026-03-06T19:41:59
System:
[PERFORMANCE ALERT] Frame time high: 440.6ms. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 7
@2026-03-06T19:43:42
System:
[PERFORMANCE ALERT] Frame time high: 58.3ms. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 8
@2026-03-06T19:45:01
System:
[PERFORMANCE ALERT] Frame time high: 435.0ms. Please consider optimizing recent changes or reducing load.
---
test
------------------
--- MOCK INVOKED ---
ARGS: ['C:\\projects\\manual_slop\\tests\\mock_gemini_cli.py', '-m', 'gemini-2.5-flash-lite', '--prompt', '', '--resume', 'mock-session-default', '--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.
<context>
</context>
nice job
------------------
--- 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.
<context>
</context>
[DISCUSSION HISTORY]
## Discussion History
### Discussion Excerpt 1
@2026-03-06T19:34:06
System:
[PERFORMANCE ALERT] Frame time high: 430.6ms. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 2
@2026-03-06T19:34:41
System:
[PERFORMANCE ALERT] Frame time high: 58.2ms. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 3
@2026-03-06T19:38:51
System:
[PERFORMANCE ALERT] Frame time high: 409.3ms. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 4
@2026-03-06T19:40:43
System:
[PERFORMANCE ALERT] Frame time high: 64.5ms. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 5
@2026-03-06T19:41:13
System:
[PERFORMANCE ALERT] CPU usage high: 94.0%. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 6
@2026-03-06T19:41:59
System:
[PERFORMANCE ALERT] Frame time high: 440.6ms. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 7
@2026-03-06T19:43:42
System:
[PERFORMANCE ALERT] Frame time high: 58.3ms. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 8
@2026-03-06T19:45:01
System:
[PERFORMANCE ALERT] Frame time high: 435.0ms. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 9
@2026-03-06T19:45:31
System:
[PERFORMANCE ALERT] CPU usage high: 114.1%. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 10
@2026-03-06T19:52:00
System:
[PERFORMANCE ALERT] Frame time high: 538.9ms. Please consider optimizing recent changes or reducing load.
---
### Discussion Excerpt 11
@2026-03-06T19:52:08
System:
[PERFORMANCE ALERT] Frame time high: 419.0ms. Please consider optimizing recent changes or reducing load.
---
test
------------------

View File

@@ -24,6 +24,7 @@ import threading
import requests # type: ignore[import-untyped] import requests # type: ignore[import-untyped]
from typing import Optional, Callable, Any, List, Union, cast, Iterable from typing import Optional, Callable, Any, List, Union, cast, Iterable
import os import os
from pathlib import Path
from src import project_manager from src import project_manager
from src import file_cache from src import file_cache
from src import mcp_client from src import mcp_client
@@ -157,8 +158,11 @@ def get_comms_log() -> list[dict[str, Any]]:
def clear_comms_log() -> None: def clear_comms_log() -> None:
_comms_log.clear() _comms_log.clear()
def get_credentials_path() -> Path:
return Path(os.environ.get("SLOP_CREDENTIALS", "credentials.toml"))
def _load_credentials() -> dict[str, Any]: def _load_credentials() -> dict[str, Any]:
cred_path = os.environ.get("SLOP_CREDENTIALS", "credentials.toml") cred_path = get_credentials_path()
try: try:
with open(cred_path, "rb") as f: with open(cred_path, "rb") as f:
return tomllib.load(f) return tomllib.load(f)

View File

@@ -148,10 +148,10 @@ class AppController:
"last_latency": 0.0 "last_latency": 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, "model": "gemini-3.1-pro-preview"}, "Tier 1": {"input": 0, "output": 0, "provider": "gemini", "model": "gemini-3.1-pro-preview"},
"Tier 2": {"input": 0, "output": 0, "model": "gemini-3-flash-preview"}, "Tier 2": {"input": 0, "output": 0, "provider": "gemini", "model": "gemini-3-flash-preview"},
"Tier 3": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"}, "Tier 3": {"input": 0, "output": 0, "provider": "gemini", "model": "gemini-2.5-flash-lite"},
"Tier 4": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"}, "Tier 4": {"input": 0, "output": 0, "provider": "gemini", "model": "gemini-2.5-flash-lite"},
} }
self.perf_monitor: performance_monitor.PerformanceMonitor = performance_monitor.PerformanceMonitor() self.perf_monitor: performance_monitor.PerformanceMonitor = performance_monitor.PerformanceMonitor()
self._pending_gui_tasks: List[Dict[str, Any]] = [] self._pending_gui_tasks: List[Dict[str, Any]] = []
@@ -195,6 +195,8 @@ class AppController:
self.ui_global_system_prompt: str = "" self.ui_global_system_prompt: str = ""
self.ui_agent_tools: Dict[str, bool] = {} self.ui_agent_tools: Dict[str, bool] = {}
self.available_models: List[str] = [] self.available_models: List[str] = []
self.all_available_models: Dict[str, List[str]] = {} # provider -> list of models
self._autofocus_response_tab = False
self.proposed_tracks: List[Dict[str, Any]] = [] self.proposed_tracks: List[Dict[str, Any]] = []
self._show_track_proposal_modal: bool = False self._show_track_proposal_modal: bool = False
self.ai_status: str = 'idle' self.ai_status: str = 'idle'
@@ -401,6 +403,8 @@ class AppController:
self._trigger_blink = True self._trigger_blink = True
if not stream_id: if not stream_id:
self._token_stats_dirty = True self._token_stats_dirty = True
if not is_streaming:
self._autofocus_response_tab = True
# ONLY add to history when turn is complete # ONLY add to history when turn is complete
if self.ui_auto_add_history and not stream_id and not is_streaming: if self.ui_auto_add_history and not stream_id and not is_streaming:
role = payload.get("role", "AI") role = payload.get("role", "AI")
@@ -408,7 +412,7 @@ class AppController:
self._pending_history_adds.append({ self._pending_history_adds.append({
"role": role, "role": role,
"content": self.ai_response, "content": self.ai_response,
"collapsed": False, "collapsed": True,
"ts": project_manager.now_ts() "ts": project_manager.now_ts()
}) })
elif action in ("mma_stream", "mma_stream_append"): elif action in ("mma_stream", "mma_stream_append"):
@@ -429,7 +433,19 @@ class AppController:
payload = task # Fallback to task if payload missing or wrong type payload = task # Fallback to task if payload missing or wrong type
self.mma_status = payload.get("status", "idle") self.mma_status = payload.get("status", "idle")
self.active_tier = payload.get("active_tier") self.active_tier = payload.get("active_tier")
self.mma_tier_usage = payload.get("tier_usage", self.mma_tier_usage)
# Preserve existing model/provider config if not explicitly in payload
new_usage = payload.get("tier_usage", {})
for tier, data in new_usage.items():
if tier in self.mma_tier_usage:
# Update usage counts but keep selected model/provider if not in update
self.mma_tier_usage[tier]["input"] = data.get("input", self.mma_tier_usage[tier]["input"])
self.mma_tier_usage[tier]["output"] = data.get("output", self.mma_tier_usage[tier]["output"])
if "model" in data: self.mma_tier_usage[tier]["model"] = data["model"]
if "provider" in data: self.mma_tier_usage[tier]["provider"] = data["provider"]
else:
self.mma_tier_usage[tier] = data
self.active_tickets = payload.get("tickets", []) self.active_tickets = payload.get("tickets", [])
track_data = payload.get("track") track_data = payload.get("track")
if track_data: if track_data:
@@ -622,6 +638,8 @@ class AppController:
"Tier 4: QA": True, "Tier 4: QA": True,
"Discussion Hub": True, "Discussion Hub": True,
"Operations Hub": True, "Operations Hub": True,
"Message": False,
"Response": False,
"Theme": True, "Theme": True,
"Log Management": False, "Log Management": False,
"Diagnostics": False, "Diagnostics": False,
@@ -725,7 +743,14 @@ class AppController:
def do_fetch() -> None: def do_fetch() -> None:
try: try:
models_list = ai_client.list_models(provider) for p in self.PROVIDERS:
try:
self.all_available_models[p] = ai_client.list_models(p)
except Exception as e:
sys.stderr.write(f"[DEBUG] Error fetching models for {p}: {e}\n")
self.all_available_models[p] = []
models_list = self.all_available_models.get(provider, [])
self.available_models = models_list self.available_models = models_list
if self.current_model not in models_list and models_list: if self.current_model not in models_list and models_list:
self.current_model = models_list[0] self.current_model = models_list[0]
@@ -851,7 +876,7 @@ class AppController:
self._pending_history_adds.append({ self._pending_history_adds.append({
"role": "User", "role": "User",
"content": event.prompt, "content": event.prompt,
"collapsed": False, "collapsed": True,
"ts": project_manager.now_ts() "ts": project_manager.now_ts()
}) })
# Clear response area for new turn # Clear response area for new turn
@@ -896,6 +921,13 @@ class AppController:
entry["local_ts"] = time.time() entry["local_ts"] = time.time()
kind = entry.get("kind") kind = entry.get("kind")
payload = entry.get("payload", {}) payload = entry.get("payload", {})
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 u:
self.session_usage[k] += u.get(k, 0) or 0
if kind in ("tool_result", "tool_call"): if kind in ("tool_result", "tool_call"):
role = "Tool" if kind == "tool_result" else "Vendor API" role = "Tool" if kind == "tool_result" else "Vendor API"
content = "" content = ""
@@ -1173,7 +1205,7 @@ class AppController:
self._pending_history_adds.append({ self._pending_history_adds.append({
"role": "User", "role": "User",
"content": user_msg, "content": user_msg,
"collapsed": False, "collapsed": True,
"ts": project_manager.now_ts() "ts": project_manager.now_ts()
}) })
try: try:
@@ -1183,7 +1215,7 @@ class AppController:
self._pending_history_adds.append({ self._pending_history_adds.append({
"role": "AI", "role": "AI",
"content": resp, "content": resp,
"collapsed": False, "collapsed": True,
"ts": project_manager.now_ts() "ts": project_manager.now_ts()
}) })
self._recalculate_session_usage() self._recalculate_session_usage()
@@ -1669,6 +1701,7 @@ class AppController:
# Save MMA State # Save MMA State
mma_sec = proj.setdefault("mma", {}) mma_sec = proj.setdefault("mma", {})
mma_sec["epic"] = self.ui_epic_input mma_sec["epic"] = self.ui_epic_input
mma_sec["tier_models"] = {t: {"model": d["model"], "provider": d.get("provider", "gemini")} for t, d in self.mma_tier_usage.items()}
if self.active_track: if self.active_track:
mma_sec["active_track"] = asdict(self.active_track) mma_sec["active_track"] = asdict(self.active_track)
else: else:
@@ -1684,7 +1717,11 @@ class AppController:
} }
self.config["ai"]["system_prompt"] = self.ui_global_system_prompt self.config["ai"]["system_prompt"] = self.ui_global_system_prompt
self.config["projects"] = {"paths": self.project_paths, "active": self.active_project_path} self.config["projects"] = {"paths": self.project_paths, "active": self.active_project_path}
self.config["gui"] = {"show_windows": self.show_windows} self.config["gui"] = {
"show_windows": self.show_windows,
"separate_message_panel": getattr(self, "ui_separate_message_panel", False),
"separate_response_panel": getattr(self, "ui_separate_response_panel", False),
}
theme.save_to_config(self.config) theme.save_to_config(self.config)
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]:

View File

@@ -103,6 +103,13 @@ class App:
self.node_editor_config = ed.Config() self.node_editor_config = ed.Config()
self.node_editor_ctx = ed.create_editor(self.node_editor_config) self.node_editor_ctx = ed.create_editor(self.node_editor_config)
self.ui_selected_ticket_id: Optional[str] = None self.ui_selected_ticket_id: Optional[str] = None
self._autofocus_response_tab = False
gui_cfg = self.config.get("gui", {})
self.ui_separate_message_panel = gui_cfg.get("separate_message_panel", False)
self.ui_separate_response_panel = gui_cfg.get("separate_response_panel", False)
self._comms_log_cache: list[dict[str, Any]] = []
self._comms_log_dirty: bool = True
self._last_ui_focus_agent: Optional[str] = None
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."""
@@ -149,6 +156,11 @@ class App:
def shutdown(self) -> None: def shutdown(self) -> None:
"""Cleanly shuts down the app's background tasks and saves state.""" """Cleanly shuts down the app's background tasks and saves state."""
try:
if hasattr(self, 'runner_params') and self.runner_params.ini_filename:
imgui.save_ini_settings_to_disk(self.runner_params.ini_filename)
except:
pass
self.controller.shutdown() self.controller.shutdown()
def _test_callback_func_write_to_file(self, data: str) -> None: def _test_callback_func_write_to_file(self, data: str) -> None:
@@ -226,6 +238,7 @@ class App:
def _gui_func(self) -> None: def _gui_func(self) -> None:
try: try:
self.perf_monitor.start_frame() self.perf_monitor.start_frame()
self._autofocus_response_tab = self.controller._autofocus_response_tab
# Process GUI task queue # Process GUI task queue
# DEBUG: Check if tasks exist before processing # DEBUG: Check if tasks exist before processing
if hasattr(self, 'controller') and hasattr(self.controller, '_pending_gui_tasks'): if hasattr(self, 'controller') and hasattr(self.controller, '_pending_gui_tasks'):
@@ -249,11 +262,28 @@ class App:
pass # silent — don't disrupt the GUI loop pass # silent — don't disrupt the GUI loop
# Sync pending comms # Sync pending comms
with self._pending_comms_lock: with self._pending_comms_lock:
if self._pending_comms and self.ui_auto_scroll_comms: if self._pending_comms:
self._scroll_comms_to_bottom = True if self.ui_auto_scroll_comms:
for c in self._pending_comms: self._scroll_comms_to_bottom = True
self._comms_log.append(c) self._comms_log_dirty = True
self._pending_comms.clear() for c in self._pending_comms:
self._comms_log.append(c)
self._pending_comms.clear()
if self.ui_focus_agent != self._last_ui_focus_agent:
self._comms_log_dirty = True
self._last_ui_focus_agent = self.ui_focus_agent
if self._comms_log_dirty:
if self.is_viewing_prior_session:
self._comms_log_cache = self.prior_session_entries
else:
log_raw = list(self._comms_log)
if self.ui_focus_agent:
self._comms_log_cache = [e for e in log_raw if e.get("source_tier") == self.ui_focus_agent]
else:
self._comms_log_cache = log_raw
self._comms_log_dirty = False
with self._pending_tool_calls_lock: with self._pending_tool_calls_lock:
if self._pending_tool_calls and self.ui_auto_scroll_tool_calls: if self._pending_tool_calls and self.ui_auto_scroll_tool_calls:
self._scroll_tool_calls_to_bottom = True self._scroll_tool_calls_to_bottom = True
@@ -281,10 +311,11 @@ class App:
if exp: if exp:
if imgui.collapsing_header("Provider & Model"): if imgui.collapsing_header("Provider & Model"):
self._render_provider_panel() self._render_provider_panel()
if imgui.collapsing_header("System Prompts"):
self._render_system_prompts_panel()
if imgui.collapsing_header("Token Budget"): if imgui.collapsing_header("Token Budget"):
self._render_token_budget_panel() self._render_token_budget_panel()
if imgui.collapsing_header("System Prompts"):
self._render_system_prompts_panel()
imgui.end() imgui.end()
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 = imgui.begin("MMA Dashboard", self.show_windows["MMA Dashboard"])
@@ -327,14 +358,39 @@ class App:
self._render_discussion_panel() self._render_discussion_panel()
imgui.end_child() imgui.end_child()
# Bottom part with tabs for message and response # Bottom part with tabs for message and response
if imgui.begin_tab_bar("MessageResponseTabs"): # Detach controls
if imgui.begin_tab_item("Message")[0]: imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
self._render_message_panel() ch1, self.ui_separate_message_panel = imgui.checkbox("Pop Out Message", self.ui_separate_message_panel)
imgui.end_tab_item() imgui.same_line()
if imgui.begin_tab_item("Response")[0]: ch2, self.ui_separate_response_panel = imgui.checkbox("Pop Out Response", self.ui_separate_response_panel)
self._render_response_panel() if ch1: self.show_windows["Message"] = self.ui_separate_message_panel
imgui.end_tab_item() if ch2: self.show_windows["Response"] = self.ui_separate_response_panel
imgui.end_tab_bar() imgui.pop_style_var()
show_message_tab = not self.ui_separate_message_panel
show_response_tab = not self.ui_separate_response_panel
if show_message_tab or show_response_tab:
if imgui.begin_tab_bar("discussion_tabs"):
# Task: Auto-focus Response tab when response received
tab_flags = imgui.TabItemFlags_.none
if self._autofocus_response_tab:
tab_flags = imgui.TabItemFlags_.set_selected
self._autofocus_response_tab = False
self.controller._autofocus_response_tab = False
if show_message_tab:
if imgui.begin_tab_item("Message", None)[0]:
self._render_message_panel()
imgui.end_tab_item()
if show_response_tab:
if imgui.begin_tab_item("Response", None, tab_flags)[0]:
self._render_response_panel()
imgui.end_tab_item()
imgui.end_tab_bar()
else:
imgui.text_disabled("Message & Response panels are detached.")
imgui.end() imgui.end()
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"]) exp, opened = imgui.begin("Operations Hub", self.show_windows["Operations Hub"])
@@ -354,16 +410,32 @@ class App:
if self.ui_focus_agent: if self.ui_focus_agent:
if imgui.button("x##clear_focus"): if imgui.button("x##clear_focus"):
self.ui_focus_agent = None self.ui_focus_agent = None
imgui.separator() if exp:
if imgui.begin_tab_bar("OperationsTabs"): if imgui.begin_tab_bar("ops_tabs"):
if imgui.begin_tab_item("Tool Calls")[0]: if imgui.begin_tab_item("Comms History")[0]:
self._render_tool_calls_panel() self._render_comms_history_panel()
imgui.end_tab_item() imgui.end_tab_item()
if imgui.begin_tab_item("Comms History")[0]: if imgui.begin_tab_item("Tool Calls")[0]:
self._render_comms_history_panel() self._render_tool_calls_panel()
imgui.end_tab_item() imgui.end_tab_item()
imgui.end_tab_bar() imgui.end_tab_bar()
imgui.end() imgui.end()
if self.ui_separate_message_panel and self.show_windows.get("Message", False):
exp, opened = imgui.begin("Message", self.show_windows["Message"])
self.show_windows["Message"] = bool(opened)
if exp:
self._render_message_panel()
imgui.end()
if self.ui_separate_response_panel and self.show_windows.get("Response", False):
exp, opened = imgui.begin("Response", self.show_windows["Response"])
self.show_windows["Response"] = bool(opened)
if exp:
self._render_response_panel()
imgui.end()
if self.show_windows.get("Log Management", False): if self.show_windows.get("Log Management", False):
self._render_log_management() self._render_log_management()
if self.show_windows["Diagnostics"]: if self.show_windows["Diagnostics"]:
@@ -729,11 +801,6 @@ class App:
ch, val = imgui.checkbox(f"Enable {t_name}", val) ch, val = imgui.checkbox(f"Enable {t_name}", val)
if ch: if ch:
self.ui_agent_tools[t_name] = val self.ui_agent_tools[t_name] = val
imgui.separator()
imgui.text_colored(C_LBL, 'MMA Orchestration')
_, self.ui_epic_input = imgui.input_text_multiline('##epic_input', self.ui_epic_input, imgui.ImVec2(-1, 80))
if imgui.button('Plan Epic (Tier 1)', imgui.ImVec2(-1, 0)):
self._cb_plan_epic()
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:
@@ -917,28 +984,32 @@ class App:
self.prior_session_entries.clear() self.prior_session_entries.clear()
imgui.separator() imgui.separator()
imgui.begin_child("prior_scroll", imgui.ImVec2(0, 0), False) imgui.begin_child("prior_scroll", imgui.ImVec2(0, 0), False)
for idx, entry in enumerate(self.prior_session_entries): clipper = imgui.ListClipper()
imgui.push_id(f"prior_{idx}") clipper.begin(len(self.prior_session_entries))
kind = entry.get("kind", entry.get("type", "")) while clipper.step():
imgui.text_colored(C_LBL, f"#{idx+1}") for idx in range(clipper.display_start, clipper.display_end):
imgui.same_line() entry = self.prior_session_entries[idx]
ts = entry.get("ts", entry.get("timestamp", "")) imgui.push_id(f"prior_{idx}")
if ts: kind = entry.get("kind", entry.get("type", ""))
imgui.text_colored(vec4(160, 160, 160), str(ts)) imgui.text_colored(C_LBL, f"#{idx+1}")
imgui.same_line() imgui.same_line()
imgui.text_colored(C_KEY, str(kind)) ts = entry.get("ts", entry.get("timestamp", ""))
payload = entry.get("payload", entry) if ts:
text = payload.get("text", payload.get("message", payload.get("content", ""))) imgui.text_colored(vec4(160, 160, 160), str(ts))
if text: imgui.same_line()
preview = str(text).replace("\\n", " ")[:200] imgui.text_colored(C_KEY, str(kind))
if self.ui_word_wrap: payload = entry.get("payload", entry)
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x) text = payload.get("text", payload.get("message", payload.get("content", "")))
imgui.text(preview) if text:
imgui.pop_text_wrap_pos() preview = str(text).replace("\n", " ")[:200]
else: if self.ui_word_wrap:
imgui.text(preview) imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
imgui.separator() imgui.text(preview)
imgui.pop_id() imgui.pop_text_wrap_pos()
else:
imgui.text(preview)
imgui.separator()
imgui.pop_id()
imgui.end_child() imgui.end_child()
imgui.pop_style_color() imgui.pop_style_color()
return return
@@ -1000,7 +1071,7 @@ class App:
if not self.is_viewing_prior_session: if not self.is_viewing_prior_session:
imgui.separator() imgui.separator()
if imgui.button("+ Entry"): if imgui.button("+ Entry"):
self.disc_entries.append({"role": self.disc_roles[0] if self.disc_roles else "User", "content": "", "collapsed": False, "ts": project_manager.now_ts()}) self.disc_entries.append({"role": self.disc_roles[0] if self.disc_roles else "User", "content": "", "collapsed": True, "ts": project_manager.now_ts()})
imgui.same_line() imgui.same_line()
if imgui.button("-All"): if imgui.button("-All"):
for e in self.disc_entries: e["collapsed"] = True for e in self.disc_entries: e["collapsed"] = True
@@ -1076,7 +1147,7 @@ class App:
if collapsed: if collapsed:
imgui.same_line() imgui.same_line()
if imgui.button("Ins"): if imgui.button("Ins"):
self.disc_entries.insert(i, {"role": "User", "content": "", "collapsed": False, "ts": project_manager.now_ts()}) self.disc_entries.insert(i, {"role": "User", "content": "", "collapsed": True, "ts": project_manager.now_ts()})
imgui.same_line() imgui.same_line()
self._render_text_viewer(f"Entry #{i+1}", entry["content"]) self._render_text_viewer(f"Entry #{i+1}", entry["content"])
imgui.same_line() imgui.same_line()
@@ -1147,8 +1218,9 @@ class App:
if ch: if ch:
if hasattr(ai_client, "_gemini_cli_adapter") and ai_client._gemini_cli_adapter: if hasattr(ai_client, "_gemini_cli_adapter") and ai_client._gemini_cli_adapter:
ai_client._gemini_cli_adapter.binary_path = self.ui_gemini_cli_path ai_client._gemini_cli_adapter.binary_path = self.ui_gemini_cli_path
imgui.separator()
imgui.text("Telemetry") def _render_token_budget_panel(self) -> None:
imgui.text("Session Telemetry")
usage = self.session_usage usage = self.session_usage
total = usage["input_tokens"] + usage["output_tokens"] total = usage["input_tokens"] + usage["output_tokens"]
if total == 0 and usage.get("total_tokens", 0) > 0: if total == 0 and usage.get("total_tokens", 0) > 0:
@@ -1160,8 +1232,8 @@ class App:
imgui.text_colored(C_LBL, f" Cache Read: {usage['cache_read_input_tokens']:,} Creation: {usage['cache_creation_input_tokens']:,}") imgui.text_colored(C_LBL, f" Cache Read: {usage['cache_read_input_tokens']:,} Creation: {usage['cache_creation_input_tokens']:,}")
if self._gemini_cache_text: if self._gemini_cache_text:
imgui.text_colored(C_SUB, self._gemini_cache_text) imgui.text_colored(C_SUB, self._gemini_cache_text)
imgui.separator()
def _render_token_budget_panel(self) -> None:
if self._token_stats_dirty: if self._token_stats_dirty:
self._token_stats_dirty = False self._token_stats_dirty = False
self._refresh_api_metrics({}, md_content=self._last_stable_md or None) self._refresh_api_metrics({}, md_content=self._last_stable_md or None)
@@ -1316,6 +1388,7 @@ class App:
if imgui.button("Exit Prior Session"): if imgui.button("Exit Prior Session"):
self.is_viewing_prior_session = False self.is_viewing_prior_session = False
self.prior_session_entries.clear() self.prior_session_entries.clear()
self._comms_log_dirty = True
self.ai_status = "idle" self.ai_status = "idle"
imgui.separator() imgui.separator()
imgui.text_colored(vec4(255, 200, 100), "VIEWING PRIOR SESSION") imgui.text_colored(vec4(255, 200, 100), "VIEWING PRIOR SESSION")
@@ -1334,62 +1407,77 @@ class App:
imgui.same_line() imgui.same_line()
imgui.text_colored(C_TR, "tool_result") imgui.text_colored(C_TR, "tool_result")
imgui.separator() imgui.separator()
# Use tinted background for prior session
if self.is_viewing_prior_session: if self.is_viewing_prior_session:
imgui.push_style_color(imgui.Col_.child_bg, vec4(40, 30, 20)) imgui.push_style_color(imgui.Col_.child_bg, vec4(40, 30, 20))
imgui.begin_child("comms_scroll", imgui.ImVec2(0, 0), False, imgui.WindowFlags_.horizontal_scrollbar) imgui.begin_child("comms_scroll", imgui.ImVec2(0, 0), False, imgui.WindowFlags_.horizontal_scrollbar)
log_to_render = self.prior_session_entries if self.is_viewing_prior_session else list(self._comms_log)
if self.ui_focus_agent and not self.is_viewing_prior_session: log_to_render = self._comms_log_cache
log_to_render = [e for e in log_to_render if e.get("source_tier") == self.ui_focus_agent]
clipper = imgui.ListClipper() flags = imgui.TableFlags_.resizable | imgui.TableFlags_.hideable | imgui.TableFlags_.borders_inner_v | imgui.TableFlags_.row_bg | imgui.TableFlags_.scroll_y
clipper.begin(len(log_to_render))
while clipper.step(): if imgui.begin_table("comms_table", 5, flags):
for i_minus_one in range(clipper.display_start, clipper.display_end): imgui.table_setup_column("#", imgui.TableColumnFlags_.width_fixed, 40)
i = i_minus_one + 1 imgui.table_setup_column("Tier", imgui.TableColumnFlags_.width_fixed, 60)
entry = log_to_render[i_minus_one] imgui.table_setup_column("Type", imgui.TableColumnFlags_.width_fixed, 80)
local_ts = entry.get("local_ts", 0) imgui.table_setup_column("!", imgui.TableColumnFlags_.width_fixed, 20)
blink_alpha = 0.0 imgui.table_setup_column("Content", imgui.TableColumnFlags_.width_stretch)
if entry.get("_blinking", False):
elapsed = time.time() - entry.get("_blink_start", 0) clipper = imgui.ListClipper()
if elapsed < 1.5: clipper.begin(len(log_to_render))
blink_alpha = 0.3 + 0.7 * abs(math.sin(elapsed * 8 * math.pi)) while clipper.step():
for i in range(clipper.display_start, clipper.display_end):
entry = log_to_render[i]
imgui.table_next_row()
i_display = i + 1
source = entry.get("source_tier", "main")
msg_type = entry.get("type", "?")
content_text = entry.get("content", "")
if len(content_text) > COMMS_CLAMP_CHARS:
content_text = content_text[:COMMS_CLAMP_CHARS] + "..."
# FG Color based on type
fg = C_VAL
if msg_type == "request": fg = C_REQ
elif msg_type == "response": fg = C_RES
elif msg_type == "tool_call": fg = C_TC
elif msg_type == "tool_result": fg = C_TR
elif msg_type == "error": fg = vec4(255, 80, 80)
imgui.table_next_column()
imgui.text_colored(C_LBL, f"#{i_display}")
imgui.table_next_column()
imgui.text_colored(C_SUB, f"[{source}]")
imgui.table_next_column()
imgui.text_colored(fg, msg_type)
imgui.table_next_column()
elapsed = time.time() - entry.get("local_ts", 0)
if elapsed < 3.0:
blink = (math.sin(elapsed * 15) * 0.5 + 0.5)
imgui.text_colored(vec4(255, 255, 0, blink), "*")
else: else:
entry["_blinking"] = False imgui.text("")
source = entry.get("source_tier", "?")
msg_type = entry.get("type", "?") imgui.table_next_column()
content_text = entry.get("content", "") if self.ui_word_wrap:
if len(content_text) > COMMS_CLAMP_CHARS: imgui.push_text_wrap_pos(0.0)
content_text = content_text[:COMMS_CLAMP_CHARS] + "..." imgui.text_unformatted(content_text)
if msg_type == "request": imgui.pop_text_wrap_pos()
bg = vec4(60, 40, 20, 180) else:
fg = C_REQ imgui.text_unformatted(content_text)
elif msg_type == "response":
bg = vec4(20, 60, 40, 180) imgui.end_table()
fg = C_RES
elif msg_type == "tool_call": if self._scroll_comms_to_bottom:
bg = vec4(40, 40, 80, 180) imgui.set_scroll_here_y(1.0)
fg = C_TC self._scroll_comms_to_bottom = False
elif msg_type == "tool_result":
bg = vec4(80, 40, 40, 180)
fg = C_TR
elif msg_type == "error":
bg = vec4(80, 20, 20, 180)
fg = vec4(1, 0.3, 0.3, 1)
else:
bg = vec4(50, 50, 50, 180)
fg = C_OUT if source == "AI" else C_IN
imgui.push_style_color(imgui.Col_.child_bg, bg)
imgui.push_style_color(imgui.Col_.text, fg)
imgui.text_colored(C_KEY, f"#{i}")
imgui.same_line()
imgui.text_colored(C_LBL, f"[{source}]")
imgui.same_line()
imgui.text_colored(C_LBL, msg_type)
if blink_alpha > 0:
imgui.same_line()
imgui.text_colored(vec4(1, 1, 0, blink_alpha), "")
imgui.same_line()
imgui.text_wrapped(content_text)
imgui.pop_style_color(2)
imgui.end_child() imgui.end_child()
if self.is_viewing_prior_session: if self.is_viewing_prior_session:
imgui.pop_style_color() imgui.pop_style_color()
@@ -1482,6 +1570,13 @@ class App:
elif self.mma_status == "done": status_col = imgui.ImVec4(0, 1, 0, 1) elif self.mma_status == "done": status_col = imgui.ImVec4(0, 1, 0, 1)
elif self.mma_status == "error": status_col = imgui.ImVec4(1, 0, 0, 1) elif self.mma_status == "error": status_col = imgui.ImVec4(1, 0, 0, 1)
imgui.text_colored(status_col, self.mma_status.upper()) imgui.text_colored(status_col, self.mma_status.upper())
imgui.separator()
imgui.text_colored(C_LBL, 'Epic Planning (Tier 1)')
_, self.ui_epic_input = imgui.input_text_multiline('##epic_input', self.ui_epic_input, imgui.ImVec2(-1, 80))
if imgui.button('Plan Epic (Tier 1)', imgui.ImVec2(-1, 0)):
self._cb_plan_epic()
imgui.separator() imgui.separator()
# 0. Conductor Setup # 0. Conductor Setup
if imgui.collapsing_header("Conductor Setup"): if imgui.collapsing_header("Conductor Setup"):
@@ -1631,12 +1726,36 @@ class App:
imgui.text(f"{tier}:") imgui.text(f"{tier}:")
imgui.same_line() imgui.same_line()
current_model = self.mma_tier_usage[tier].get("model", "unknown") current_model = self.mma_tier_usage[tier].get("model", "unknown")
if imgui.begin_combo(f"##combo_{tier}", current_model): current_provider = self.mma_tier_usage[tier].get("provider", "gemini")
for model in self.available_models:
imgui.push_id(f"tier_cfg_{tier}")
# Provider selection
imgui.push_item_width(100)
if imgui.begin_combo("##prov", current_provider):
for p in PROVIDERS:
if imgui.selectable(p, p == current_provider)[0]:
self.mma_tier_usage[tier]["provider"] = p
# Reset model to default for provider
models_list = self.controller.all_available_models.get(p, [])
if models_list:
self.mma_tier_usage[tier]["model"] = models_list[0]
imgui.end_combo()
imgui.pop_item_width()
imgui.same_line()
# Model selection
imgui.push_item_width(-1)
models_list = self.controller.all_available_models.get(current_provider, [])
if imgui.begin_combo("##model", current_model):
for model in models_list:
if imgui.selectable(model, current_model == model)[0]: if imgui.selectable(model, current_model == model)[0]:
self.mma_tier_usage[tier]["model"] = model self.mma_tier_usage[tier]["model"] = model
self.project.setdefault("mma", {}).setdefault("tier_models", {})[tier] = model
imgui.end_combo() imgui.end_combo()
imgui.pop_item_width()
imgui.pop_id()
imgui.separator() imgui.separator()
# 4. Task DAG Visualizer # 4. Task DAG Visualizer
imgui.text("Task DAG") imgui.text("Task DAG")
@@ -1820,137 +1939,6 @@ class App:
pass pass
imgui.end_child() imgui.end_child()
imgui.text_colored(vec4(200, 220, 160), f"Status: {self.ai_status}")
imgui.same_line()
if imgui.button("Clear##comms"):
ai_client.clear_comms_log()
self._comms_log.clear()
imgui.same_line()
if imgui.button("Load Log"):
self.cb_load_prior_log()
if self.is_viewing_prior_session:
imgui.same_line()
if imgui.button("Exit Prior Session"):
self.is_viewing_prior_session = False
self.prior_session_entries.clear()
self.ai_status = "idle"
imgui.separator()
imgui.text_colored(vec4(255, 200, 100), "VIEWING PRIOR SESSION")
imgui.separator()
imgui.text_colored(C_OUT, "OUT")
imgui.same_line()
imgui.text_colored(C_REQ, "request")
imgui.same_line()
imgui.text_colored(C_TC, "tool_call")
imgui.same_line()
imgui.text(" ")
imgui.same_line()
imgui.text_colored(C_IN, "IN")
imgui.same_line()
imgui.text_colored(C_RES, "response")
imgui.same_line()
imgui.text_colored(C_TR, "tool_result")
imgui.separator()
# Use tinted background for prior session
if self.is_viewing_prior_session:
imgui.push_style_color(imgui.Col_.child_bg, vec4(40, 30, 20))
imgui.begin_child("comms_scroll", imgui.ImVec2(0, 0), False, imgui.WindowFlags_.horizontal_scrollbar)
log_to_render = self.prior_session_entries if self.is_viewing_prior_session else list(self._comms_log)
if self.ui_focus_agent and not self.is_viewing_prior_session:
log_to_render = [e for e in log_to_render if e.get("source_tier") == self.ui_focus_agent]
for idx_minus_one, entry in enumerate(log_to_render):
idx = idx_minus_one + 1
local_ts = entry.get("local_ts", 0)
# Blink effect
blink_alpha = 0.0
if local_ts > 0 and not self.is_viewing_prior_session:
elapsed = time.time() - local_ts
if elapsed < 3.0:
blink_alpha = (1.0 - (elapsed / 3.0)) * 0.3 * (math.sin(elapsed * 10) * 0.5 + 0.5)
imgui.push_id(f"comms_{idx}")
if blink_alpha > 0:
# Draw a background highlight for the entry
imgui.get_window_draw_list()
imgui.get_cursor_screen_pos()
# Estimate height or just use a fixed height for the background
# It's better to wrap the entry in a group or just use separators
# For now, let's just use the style color push if we are sure we pop it
imgui.push_style_color(imgui.Col_.child_bg, vec4(0, 255, 0, blink_alpha))
# We still need a child or a group to apply the background to
imgui.begin_group()
d = entry.get("direction", "IN")
k = entry.get("kind", "response")
imgui.text_colored(vec4(160, 160, 160), f"#{idx}")
imgui.same_line()
imgui.text_colored(vec4(160, 160, 160), entry.get("ts", "00:00:00"))
imgui.same_line()
imgui.text_colored(DIR_COLORS.get(d, C_VAL), d)
imgui.same_line()
imgui.text_colored(KIND_COLORS.get(k, C_VAL), k)
imgui.same_line()
imgui.text_colored(C_LBL, f"{entry.get('provider', '?')}/{entry.get('model', '?')}")
imgui.same_line()
tier_label = entry.get("source_tier") or "main"
imgui.text_colored(C_SUB, f"[{tier_label}]")
payload = entry.get("payload", {})
if k == "request":
self._render_heavy_text("message", payload.get("message", ""))
elif k == "response":
imgui.text_colored(C_LBL, "round:")
imgui.same_line()
imgui.text_colored(C_VAL, str(payload.get("round", "")))
imgui.text_colored(C_LBL, "stop_reason:")
imgui.same_line()
imgui.text_colored(vec4(255, 200, 120), str(payload.get("stop_reason", "")))
text = payload.get("text", "")
if text: self._render_heavy_text("text", text)
imgui.text_colored(C_LBL, "tool_calls:")
tcs = payload.get("tool_calls", [])
if not tcs: imgui.text_colored(C_VAL, " (none)")
for tc_i, tc in enumerate(tcs):
imgui.text_colored(C_KEY, f" call[{tc_i}] {tc.get('name', '?')}")
if tc.get("id"):
imgui.text_colored(C_LBL, " id:")
imgui.same_line()
imgui.text_colored(C_VAL, str(tc["id"]))
if "args" in tc or "input" in tc:
self._render_heavy_text(f"call_{tc_i}_args", str(tc.get("args") or tc.get("input")))
elif k == "tool_call":
imgui.text_colored(C_KEY, payload.get("name", "?"))
if payload.get("id"):
imgui.text_colored(C_LBL, " id:")
imgui.same_line()
imgui.text_colored(C_VAL, str(payload["id"]))
if "script" in payload: self._render_heavy_text("script", payload["script"])
if "args" in payload: self._render_heavy_text("args", str(payload["args"]))
elif k == "tool_result":
imgui.text_colored(C_KEY, payload.get("name", "?"))
if payload.get("id"):
imgui.text_colored(C_LBL, " id:")
imgui.same_line()
imgui.text_colored(C_VAL, str(payload["id"]))
if "output" in payload: self._render_heavy_text("output", payload["output"])
if "results" in payload:
for r_i, r in enumerate(payload["results"]):
imgui.text_colored(C_LBL, f" Result[{r_i}]:")
self._render_heavy_text(f"res_{r_i}", str(r))
if "usage" in payload:
u = payload["usage"]
u_str = f"In: {u.get('input_tokens', 0)} Out: {u.get('output_tokens', 0)}"
if u.get("cache_read_input_tokens"): u_str += f" (Cache: {u['cache_read_input_tokens']})"
imgui.text_colored(C_SUB, f" Usage: {u_str}")
imgui.separator()
if blink_alpha > 0:
imgui.end_group()
imgui.pop_style_color()
imgui.pop_id()
if self._scroll_comms_to_bottom:
imgui.set_scroll_here_y(1.0)
self._scroll_comms_to_bottom = False
imgui.end_child()
if self.is_viewing_prior_session:
imgui.pop_style_color()
def _render_system_prompts_panel(self) -> None: def _render_system_prompts_panel(self) -> None:
imgui.text("Global System Prompt (all projects)") imgui.text("Global System Prompt (all projects)")
ch, self.ui_global_system_prompt = imgui.input_text_multiline("##gsp", self.ui_global_system_prompt, imgui.ImVec2(-1, 100)) ch, self.ui_global_system_prompt = imgui.input_text_multiline("##gsp", self.ui_global_system_prompt, imgui.ImVec2(-1, 100))
@@ -1969,6 +1957,13 @@ class App:
if imgui.selectable(p, p == cp)[0]: if imgui.selectable(p, p == cp)[0]:
theme.apply(p) theme.apply(p)
imgui.end_combo() imgui.end_combo()
imgui.separator()
ch1, self.ui_separate_message_panel = imgui.checkbox("Separate Message Panel", self.ui_separate_message_panel)
ch2, self.ui_separate_response_panel = imgui.checkbox("Separate Response Panel", self.ui_separate_response_panel)
if ch1: self.show_windows["Message"] = self.ui_separate_message_panel
if ch2: self.show_windows["Response"] = self.ui_separate_response_panel
imgui.separator() imgui.separator()
imgui.text("Font") imgui.text("Font")
imgui.push_item_width(-150) imgui.push_item_width(-150)

View File

@@ -57,7 +57,7 @@ def parse_history_entries(history_strings: list[str], roles: list[str]) -> list[
content = rest[match.end():].strip() content = rest[match.end():].strip()
else: else:
content = rest content = rest
entries.append({"role": role, "content": content, "collapsed": False, "ts": ts}) entries.append({"role": role, "content": content, "collapsed": True, "ts": ts})
return entries return entries
@dataclass @dataclass