Compare commits
2 Commits
9d5b874c66
...
0de50e216b
| Author | SHA1 | Date | |
|---|---|---|---|
| 0de50e216b | |||
| 5a484c9e82 |
@@ -3,24 +3,24 @@
|
||||
> **TEST DEBT FIX:** Due to ongoing test architecture instability (documented in `test_architecture_integrity_audit_20260304`), do NOT write new `live_gui` integration tests for this track. Use purely in-process mocks to verify concurrency logic.
|
||||
|
||||
## Phase 1: Engine Refactoring
|
||||
- [ ] Task: Initialize MMA Environment `activate_skill mma-orchestrator`
|
||||
- [ ] Task: Refactor `mcp_client.py` for async execution
|
||||
- [ ] WHERE: `mcp_client.py`
|
||||
- [ ] WHAT: Convert tool execution wrappers to `async def` or wrap them in thread executors.
|
||||
- [ ] HOW: Use `asyncio.to_thread` for blocking I/O bound tools.
|
||||
- [ ] SAFETY: Ensure thread safety for shared resources.
|
||||
- [ ] Task: Update `ai_client.py` dispatcher
|
||||
- [ ] WHERE: `ai_client.py` (around tool dispatch loop)
|
||||
- [ ] WHAT: Use `asyncio.gather` to execute multiple tool calls concurrently.
|
||||
- [ ] HOW: Await the gathered results before proceeding with the AI loop.
|
||||
- [ ] SAFETY: Handle tool execution exceptions gracefully without crashing the gather group.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 1' (Protocol in workflow.md)
|
||||
- [x] Task: Initialize MMA Environment `activate_skill mma-orchestrator`
|
||||
- [x] Task: Refactor `mcp_client.py` for async execution (60e1dce)
|
||||
- [x] WHERE: `mcp_client.py`
|
||||
- [x] WHAT: Convert tool execution wrappers to `async def` or wrap them in thread executors.
|
||||
- [x] HOW: Use `asyncio.to_thread` for blocking I/O bound tools.
|
||||
- [x] SAFETY: Ensure thread safety for shared resources.
|
||||
- [x] Task: Update `ai_client.py` dispatcher (87dbfc5)
|
||||
- [x] WHERE: `ai_client.py` (around tool dispatch loop)
|
||||
- [x] WHAT: Use `asyncio.gather` to execute multiple tool calls concurrently.
|
||||
- [x] HOW: Await the gathered results before proceeding with the AI loop.
|
||||
- [x] SAFETY: Handle tool execution exceptions gracefully without crashing the gather group.
|
||||
- [x] Task: Conductor - User Manual Verification 'Phase 1' (Protocol in workflow.md)
|
||||
|
||||
## Phase 2: Testing & Validation
|
||||
- [ ] Task: Implement async tool execution tests
|
||||
- [ ] WHERE: `tests/test_async_tools.py`
|
||||
- [ ] WHAT: Write a test verifying that multiple tools run concurrently (e.g., measuring total time vs sum of individual sleep times).
|
||||
- [ ] HOW: Use a mock tool with an explicit sleep delay.
|
||||
- [ ] SAFETY: Standard pytest setup.
|
||||
- [ ] Task: Full Suite Validation
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 2' (Protocol in workflow.md)
|
||||
- [x] Task: Implement async tool execution tests (eddc245)
|
||||
- [x] WHERE: `tests/test_async_tools.py`
|
||||
- [x] WHAT: Write a test verifying that multiple tools run concurrently (e.g., measuring total time vs sum of individual sleep times).
|
||||
- [x] HOW: Use a mock tool with an explicit sleep delay.
|
||||
- [x] SAFETY: Standard pytest setup.
|
||||
- [x] Task: Full Suite Validation (3bc900b)
|
||||
- [x] Task: Conductor - User Manual Verification 'Phase 2' (Protocol in workflow.md)
|
||||
@@ -1,5 +1,5 @@
|
||||
[ai]
|
||||
provider = "gemini_cli"
|
||||
provider = "gemini"
|
||||
model = "gemini-2.5-flash-lite"
|
||||
temperature = 0.0
|
||||
max_tokens = 8192
|
||||
@@ -14,8 +14,9 @@ paths = [
|
||||
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveaisettingssim.toml",
|
||||
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_livetoolssim.toml",
|
||||
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveexecutionsim.toml",
|
||||
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_simproject.toml",
|
||||
]
|
||||
active = "C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveexecutionsim.toml"
|
||||
active = "C:\\projects\\manual_slop\\tests\\artifacts\\temp_simproject.toml"
|
||||
|
||||
[gui.show_windows]
|
||||
"Context Hub" = true
|
||||
|
||||
@@ -79,7 +79,7 @@ DockId=0x0000000F,2
|
||||
|
||||
[Window][Theme]
|
||||
Pos=0,17
|
||||
Size=51,824
|
||||
Size=452,824
|
||||
Collapsed=0
|
||||
DockId=0x00000005,1
|
||||
|
||||
@@ -89,14 +89,14 @@ Size=900,700
|
||||
Collapsed=0
|
||||
|
||||
[Window][Diagnostics]
|
||||
Pos=53,17
|
||||
Size=909,794
|
||||
Pos=454,17
|
||||
Size=268,794
|
||||
Collapsed=0
|
||||
DockId=0x00000010,1
|
||||
|
||||
[Window][Context Hub]
|
||||
Pos=0,17
|
||||
Size=51,824
|
||||
Size=452,824
|
||||
Collapsed=0
|
||||
DockId=0x00000005,0
|
||||
|
||||
@@ -107,26 +107,26 @@ Collapsed=0
|
||||
DockId=0x0000000D,0
|
||||
|
||||
[Window][Discussion Hub]
|
||||
Pos=964,17
|
||||
Pos=724,17
|
||||
Size=716,592
|
||||
Collapsed=0
|
||||
DockId=0x00000012,0
|
||||
|
||||
[Window][Operations Hub]
|
||||
Pos=53,17
|
||||
Size=909,794
|
||||
Pos=454,17
|
||||
Size=268,794
|
||||
Collapsed=0
|
||||
DockId=0x00000010,0
|
||||
|
||||
[Window][Files & Media]
|
||||
Pos=0,843
|
||||
Size=51,357
|
||||
Size=452,357
|
||||
Collapsed=0
|
||||
DockId=0x00000006,1
|
||||
|
||||
[Window][AI Settings]
|
||||
Pos=0,843
|
||||
Size=51,357
|
||||
Size=452,357
|
||||
Collapsed=0
|
||||
DockId=0x00000006,0
|
||||
|
||||
@@ -136,13 +136,13 @@ Size=416,325
|
||||
Collapsed=0
|
||||
|
||||
[Window][MMA Dashboard]
|
||||
Pos=964,611
|
||||
Pos=724,611
|
||||
Size=716,589
|
||||
Collapsed=0
|
||||
DockId=0x00000013,0
|
||||
|
||||
[Window][Log Management]
|
||||
Pos=964,17
|
||||
Pos=724,17
|
||||
Size=716,592
|
||||
Collapsed=0
|
||||
DockId=0x00000012,1
|
||||
@@ -153,26 +153,26 @@ Size=262,209
|
||||
Collapsed=0
|
||||
|
||||
[Window][Tier 1: Strategy]
|
||||
Pos=964,611
|
||||
Pos=724,611
|
||||
Size=716,589
|
||||
Collapsed=0
|
||||
DockId=0x00000013,1
|
||||
|
||||
[Window][Tier 2: Tech Lead]
|
||||
Pos=964,611
|
||||
Pos=724,611
|
||||
Size=716,589
|
||||
Collapsed=0
|
||||
DockId=0x00000013,2
|
||||
|
||||
[Window][Tier 4: QA]
|
||||
Pos=53,813
|
||||
Size=909,387
|
||||
Pos=454,813
|
||||
Size=268,387
|
||||
Collapsed=0
|
||||
DockId=0x00000011,1
|
||||
|
||||
[Window][Tier 3: Workers]
|
||||
Pos=53,813
|
||||
Size=909,387
|
||||
Pos=454,813
|
||||
Size=268,387
|
||||
Collapsed=0
|
||||
DockId=0x00000011,0
|
||||
|
||||
@@ -212,16 +212,16 @@ Column 3 Weight=1.0000
|
||||
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
|
||||
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
|
||||
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=1440,1183 Split=Y
|
||||
DockNode ID=0x0000000C Parent=0xAFC85805 SizeRef=1362,1041 Split=X Selected=0x5D11106F
|
||||
DockNode ID=0x00000003 Parent=0x0000000C SizeRef=1658,1183 Split=X
|
||||
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=0x00000007 Parent=0x00000002 SizeRef=747,858 Split=Y Selected=0x8CA2375C
|
||||
DockNode ID=0x00000007 Parent=0x00000002 SizeRef=452,858 Split=Y Selected=0x8CA2375C
|
||||
DockNode ID=0x00000005 Parent=0x00000007 SizeRef=295,824 Selected=0xF4139CA2
|
||||
DockNode ID=0x00000006 Parent=0x00000007 SizeRef=295,995 CentralNode=1 Selected=0x7BD57D6A
|
||||
DockNode ID=0x0000000E Parent=0x00000002 SizeRef=909,858 Split=Y Selected=0x418C7449
|
||||
DockNode ID=0x00000010 Parent=0x0000000E SizeRef=868,1065 Selected=0xB4CBF21A
|
||||
DockNode ID=0x0000000E Parent=0x00000002 SizeRef=268,858 Split=Y Selected=0x418C7449
|
||||
DockNode ID=0x00000010 Parent=0x0000000E SizeRef=868,1065 Selected=0x418C7449
|
||||
DockNode ID=0x00000011 Parent=0x0000000E SizeRef=868,520 Selected=0x5CDB7A4B
|
||||
DockNode ID=0x00000001 Parent=0x0000000B SizeRef=1029,775 Selected=0x8B4EBFA6
|
||||
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
|
||||
|
||||
69
mock_debug_prompt.txt
Normal file
69
mock_debug_prompt.txt
Normal file
@@ -0,0 +1,69 @@
|
||||
--- 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"}
|
||||
------------------
|
||||
@@ -8,5 +8,5 @@ active = "main"
|
||||
|
||||
[discussions.main]
|
||||
git_commit = ""
|
||||
last_updated = "2026-03-06T12:39:50"
|
||||
last_updated = "2026-03-06T13:23:43"
|
||||
history = []
|
||||
|
||||
@@ -70,9 +70,10 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
|
||||
try:
|
||||
if name == "run_powershell":
|
||||
script = arguments.get("script", "")
|
||||
result = shell_runner.run_powershell(script, os.getcwd())
|
||||
# run_powershell is synchronous, so we run it in a thread to avoid blocking the loop
|
||||
result = await asyncio.to_thread(shell_runner.run_powershell, script, os.getcwd())
|
||||
else:
|
||||
result = mcp_client.dispatch(name, arguments)
|
||||
result = await mcp_client.async_dispatch(name, arguments)
|
||||
return [TextContent(type="text", text=str(result))]
|
||||
except Exception as e:
|
||||
return [TextContent(type="text", text=f"ERROR: {e}")]
|
||||
|
||||
@@ -859,88 +859,82 @@ def get_ui_performance() -> str:
|
||||
|
||||
TOOL_NAMES: set[str] = {"read_file", "list_directory", "search_files", "get_file_summary", "py_get_skeleton", "py_get_code_outline", "py_get_definition", "get_git_diff", "web_search", "fetch_url", "get_ui_performance", "get_file_slice", "set_file_slice", "edit_file", "py_update_definition", "py_get_signature", "py_set_signature", "py_get_class_summary", "py_get_var_declaration", "py_set_var_declaration", "py_find_usages", "py_get_imports", "py_check_syntax", "py_get_hierarchy", "py_get_docstring", "get_tree"}
|
||||
|
||||
async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
|
||||
def dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
|
||||
"""
|
||||
Dispatch an MCP tool call by name asynchronously. Returns the result as a string.
|
||||
Dispatch an MCP tool call by name. Returns the result as a string.
|
||||
"""
|
||||
# Handle aliases
|
||||
path = str(tool_input.get("path", tool_input.get("file_path", tool_input.get("dir_path", ""))))
|
||||
if tool_name == "read_file":
|
||||
return await asyncio.to_thread(read_file, path)
|
||||
return read_file(path)
|
||||
if tool_name == "list_directory":
|
||||
return await asyncio.to_thread(list_directory, path)
|
||||
return list_directory(path)
|
||||
if tool_name == "search_files":
|
||||
return await asyncio.to_thread(search_files, path, str(tool_input.get("pattern", "*")))
|
||||
return search_files(path, str(tool_input.get("pattern", "*")))
|
||||
if tool_name == "get_file_summary":
|
||||
return await asyncio.to_thread(get_file_summary, path)
|
||||
return get_file_summary(path)
|
||||
if tool_name == "py_get_skeleton":
|
||||
return await asyncio.to_thread(py_get_skeleton, path)
|
||||
return py_get_skeleton(path)
|
||||
if tool_name == "py_get_code_outline":
|
||||
return await asyncio.to_thread(py_get_code_outline, path)
|
||||
return py_get_code_outline(path)
|
||||
if tool_name == "py_get_definition":
|
||||
return await asyncio.to_thread(py_get_definition, path, str(tool_input.get("name", "")))
|
||||
return py_get_definition(path, str(tool_input.get("name", "")))
|
||||
if tool_name == "py_update_definition":
|
||||
return await asyncio.to_thread(py_update_definition, path, str(tool_input.get("name", "")), str(tool_input.get("new_content", "")))
|
||||
return py_update_definition(path, str(tool_input.get("name", "")), str(tool_input.get("new_content", "")))
|
||||
if tool_name == "py_get_signature":
|
||||
return await asyncio.to_thread(py_get_signature, path, str(tool_input.get("name", "")))
|
||||
return py_get_signature(path, str(tool_input.get("name", "")))
|
||||
if tool_name == "py_set_signature":
|
||||
return await asyncio.to_thread(py_set_signature, path, str(tool_input.get("name", "")), str(tool_input.get("new_signature", "")))
|
||||
return py_set_signature(path, str(tool_input.get("name", "")), str(tool_input.get("new_signature", "")))
|
||||
if tool_name == "py_get_class_summary":
|
||||
return await asyncio.to_thread(py_get_class_summary, path, str(tool_input.get("name", "")))
|
||||
return py_get_class_summary(path, str(tool_input.get("name", "")))
|
||||
if tool_name == "py_get_var_declaration":
|
||||
return await asyncio.to_thread(py_get_var_declaration, path, str(tool_input.get("name", "")))
|
||||
return py_get_var_declaration(path, str(tool_input.get("name", "")))
|
||||
if tool_name == "py_set_var_declaration":
|
||||
return await asyncio.to_thread(py_set_var_declaration, path, str(tool_input.get("name", "")), str(tool_input.get("new_declaration", "")))
|
||||
return py_set_var_declaration(path, str(tool_input.get("name", "")), str(tool_input.get("new_declaration", "")))
|
||||
if tool_name == "get_file_slice":
|
||||
return await asyncio.to_thread(get_file_slice, path, int(tool_input.get("start_line", 1)), int(tool_input.get("end_line", 1)))
|
||||
return get_file_slice(path, int(tool_input.get("start_line", 1)), int(tool_input.get("end_line", 1)))
|
||||
if tool_name == "set_file_slice":
|
||||
return await asyncio.to_thread(set_file_slice, path, int(tool_input.get("start_line", 1)), int(tool_input.get("end_line", 1)), str(tool_input.get("new_content", "")))
|
||||
return set_file_slice(path, int(tool_input.get("start_line", 1)), int(tool_input.get("end_line", 1)), str(tool_input.get("new_content", "")))
|
||||
if tool_name == "get_git_diff":
|
||||
return await asyncio.to_thread(get_git_diff,
|
||||
return get_git_diff(
|
||||
path,
|
||||
str(tool_input.get("base_rev", "HEAD")),
|
||||
str(tool_input.get("head_rev", ""))
|
||||
)
|
||||
if tool_name == "edit_file":
|
||||
return await asyncio.to_thread(edit_file,
|
||||
return edit_file(
|
||||
path,
|
||||
str(tool_input.get("old_string", "")),
|
||||
str(tool_input.get("new_string", "")),
|
||||
bool(tool_input.get("replace_all", False))
|
||||
)
|
||||
if tool_name == "web_search":
|
||||
return await asyncio.to_thread(web_search, str(tool_input.get("query", "")))
|
||||
return web_search(str(tool_input.get("query", "")))
|
||||
if tool_name == "fetch_url":
|
||||
return await asyncio.to_thread(fetch_url, str(tool_input.get("url", "")))
|
||||
return fetch_url(str(tool_input.get("url", "")))
|
||||
if tool_name == "get_ui_performance":
|
||||
return await asyncio.to_thread(get_ui_performance)
|
||||
return get_ui_performance()
|
||||
if tool_name == "py_find_usages":
|
||||
return await asyncio.to_thread(py_find_usages, path, str(tool_input.get("name", "")))
|
||||
return py_find_usages(path, str(tool_input.get("name", "")))
|
||||
if tool_name == "py_get_imports":
|
||||
return await asyncio.to_thread(py_get_imports, path)
|
||||
return py_get_imports(path)
|
||||
if tool_name == "py_check_syntax":
|
||||
return await asyncio.to_thread(py_check_syntax, path)
|
||||
return py_check_syntax(path)
|
||||
if tool_name == "py_get_hierarchy":
|
||||
return await asyncio.to_thread(py_get_hierarchy, path, str(tool_input.get("class_name", "")))
|
||||
return py_get_hierarchy(path, str(tool_input.get("class_name", "")))
|
||||
if tool_name == "py_get_docstring":
|
||||
return await asyncio.to_thread(py_get_docstring, path, str(tool_input.get("name", "")))
|
||||
return py_get_docstring(path, str(tool_input.get("name", "")))
|
||||
if tool_name == "get_tree":
|
||||
return await asyncio.to_thread(get_tree, path, int(tool_input.get("max_depth", 2)))
|
||||
return get_tree(path, int(tool_input.get("max_depth", 2)))
|
||||
return f"ERROR: unknown MCP tool '{tool_name}'"
|
||||
|
||||
def dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
|
||||
async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
|
||||
"""
|
||||
Dispatch an MCP tool call by name. Returns the result as a string.
|
||||
Dispatch an MCP tool call by name asynchronously. Returns the result as a string.
|
||||
"""
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
# If we are in a running loop, we can't use asyncio.run
|
||||
# But we are in a synchronous function.
|
||||
# This is tricky. If we are in a thread, we might not have a loop.
|
||||
return asyncio.run_coroutine_threadsafe(async_dispatch(tool_name, tool_input), loop).result()
|
||||
except RuntimeError:
|
||||
# No running loop, use asyncio.run
|
||||
return asyncio.run(async_dispatch(tool_name, tool_input))
|
||||
# Run blocking I/O bound tools in a thread to allow parallel execution via asyncio.gather
|
||||
return await asyncio.to_thread(dispatch, tool_name, tool_input)
|
||||
|
||||
|
||||
|
||||
def get_tool_schemas() -> list[dict[str, Any]]:
|
||||
|
||||
Reference in New Issue
Block a user