Antigravity is dog shit.
This commit is contained in:
@@ -54,10 +54,6 @@ This file tracks all major tracks for the project. Each track has its own detail
|
||||
*Link: [./tracks/context_preview_fixes_20260516/](./tracks/context_preview_fixes_20260516/)*
|
||||
*Goal: Fix Preview button generating empty content, and Inspect/Slices buttons failing to open their respective editor panels.*
|
||||
|
||||
15. [ ] **Track: GUI Architecture Refinement & AI-Friendliness**
|
||||
*Link: [./tracks/gui_architecture_refinement_20260512/](./tracks/gui_architecture_refiinement_20260512/)*
|
||||
*Goal: Reduce nesting and compactness of ImGui code in `gui_2.py`, and formalize ImGui Defer patterns.*
|
||||
|
||||
13. [x] **Track: GUI Refactor & Stabilization**
|
||||
*Link: [./tracks/gui_refactor_stabilization_20260512/](./tracks/gui_refactor_stabilization_20260512/)*
|
||||
*Goal: Refactor gui_2.py to fix regressions and enforce better imgui scoping patterns.*
|
||||
|
||||
+6
-2
@@ -229,8 +229,10 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
|
||||
if ast_mask:
|
||||
mask_sections = []
|
||||
from src import mcp_client
|
||||
for symbol, mode in ast_mask.items():
|
||||
for symbol_raw, mode in ast_mask.items():
|
||||
if mode == "hide": continue
|
||||
import re
|
||||
symbol = re.sub(r'\(\d+-\d+\)$', '', symbol_raw)
|
||||
res = ""
|
||||
if suffix_lower == ".py":
|
||||
res = mcp_client.py_get_definition(str(path), symbol) if mode == "def" else mcp_client.py_get_signature(str(path), symbol)
|
||||
@@ -429,9 +431,11 @@ def build_tier3_context(file_items: list[dict[str, Any]], screenshot_base_dir: P
|
||||
if ast_mask and not item.get("error"):
|
||||
mask_sections = []
|
||||
from src import mcp_client
|
||||
for symbol, mode in ast_mask.items():
|
||||
for symbol_raw, mode in ast_mask.items():
|
||||
if mode == "hide":
|
||||
continue
|
||||
import re
|
||||
symbol = re.sub(r'\(\d+-\d+\)$', '', symbol_raw)
|
||||
res = ""
|
||||
if path.suffix == ".py":
|
||||
res = mcp_client.py_get_definition(str(path), symbol) if mode == "def" else mcp_client.py_get_signature(str(path), symbol)
|
||||
|
||||
+27
-6
@@ -67,6 +67,7 @@ def _set_app_attr(app: Any, name: str, value: Any) -> None:
|
||||
setattr(app, name, value)
|
||||
|
||||
class HookServerInstance(ThreadingHTTPServer):
|
||||
allow_reuse_address = True
|
||||
"""Custom HTTPServer that carries a reference to the main App instance."""
|
||||
def __init__(self, server_address: tuple[str, int], RequestHandlerClass: type, app: Any) -> None:
|
||||
"""
|
||||
@@ -85,6 +86,9 @@ def _serialize_for_api(obj: Any) -> Any:
|
||||
return [_serialize_for_api(x) for x in obj]
|
||||
if isinstance(obj, dict):
|
||||
return {k: _serialize_for_api(v) for k, v in obj.items()}
|
||||
from pathlib import PurePath
|
||||
if isinstance(obj, PurePath):
|
||||
return str(obj)
|
||||
return obj
|
||||
|
||||
class HookHandler(BaseHTTPRequestHandler):
|
||||
@@ -272,6 +276,13 @@ class HookHandler(BaseHTTPRequestHandler):
|
||||
files = _get_app_attr(app, "files", [])
|
||||
screenshots = _get_app_attr(app, "screenshots", [])
|
||||
self.wfile.write(json.dumps({"files": _serialize_for_api(files), "screenshots": _serialize_for_api(screenshots)}).encode("utf-8"))
|
||||
elif self.path == "/api/v1/context":
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.end_headers()
|
||||
from src.app_controller import _api_get_context
|
||||
ctx_data = _api_get_context(app.controller)
|
||||
self.wfile.write(json.dumps(_serialize_for_api(ctx_data)).encode("utf-8"))
|
||||
elif self.path == "/api/metrics/financial":
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
@@ -765,12 +776,22 @@ class WebSocketServer:
|
||||
asyncio.set_event_loop(self.loop)
|
||||
self._stop_event = asyncio.Event()
|
||||
async def main():
|
||||
"""
|
||||
[C: simulation/live_walkthrough.py:module, simulation/ping_pong.py:module, src/gui_2.py:module, tests/mock_concurrent_mma.py:module, tests/mock_gemini_cli.py:module, tests/test_cli_tool_bridge.py:TestCliToolBridge.test_allow_decision, tests/test_cli_tool_bridge.py:TestCliToolBridge.test_deny_decision, tests/test_cli_tool_bridge.py:TestCliToolBridge.test_unreachable_hook_server, tests/test_cli_tool_bridge.py:module, tests/test_cli_tool_bridge_mapping.py:TestCliToolBridgeMapping.test_mapping_from_api_format, tests/test_cli_tool_bridge_mapping.py:module, tests/test_discussion_takes.py:module, tests/test_external_editor_gui.py:module, tests/test_headless_service.py:TestHeadlessStartup.test_headless_flag_triggers_run, tests/test_headless_service.py:TestHeadlessStartup.test_normal_startup_calls_app_run, tests/test_mma_skeleton.py:module, tests/test_orchestrator_pm.py:module, tests/test_orchestrator_pm_history.py:module, tests/test_presets.py:module, tests/test_project_serialization.py:module, tests/test_run_worker_lifecycle_abort.py:module, tests/test_symbol_lookup.py:module, tests/test_system_prompt_exposure.py:module, tests/test_theme_nerv_fx.py:module]
|
||||
"""
|
||||
async with serve(self._handler, "127.0.0.1", self.port) as server:
|
||||
self.server = server
|
||||
await self._stop_event.wait()
|
||||
max_retries = 10
|
||||
current_port = self.port
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
async with serve(self._handler, "127.0.0.1", current_port) as server:
|
||||
self.port = current_port
|
||||
self.server = server
|
||||
logging.info(f"WebSocketServer successfully bound to port {self.port}")
|
||||
await self._stop_event.wait()
|
||||
break
|
||||
except OSError as e:
|
||||
if attempt == max_retries - 1:
|
||||
logging.error(f"WebSocketServer failed to bind after {max_retries} attempts: {e}")
|
||||
raise
|
||||
logging.warning(f"WebSocketServer port {current_port} in use, retrying on {current_port + 1}...")
|
||||
current_port += 1
|
||||
self.loop.run_until_complete(main())
|
||||
|
||||
def start(self) -> None:
|
||||
|
||||
@@ -886,6 +886,8 @@ class AppController:
|
||||
self.project_paths: List[str] = []
|
||||
self.active_discussion: str = "main"
|
||||
self.disc_entries: List[Dict[str, Any]] = []
|
||||
self.discussion_sent_markdown: str = ""
|
||||
self.discussion_sent_system_prompt: str = ""
|
||||
self.disc_roles: List[str] = []
|
||||
self.tracks: List[Dict[str, Any]] = []
|
||||
self.active_track: Optional[models.Track] = None
|
||||
@@ -2216,6 +2218,8 @@ class AppController:
|
||||
ai_client.set_use_default_base_prompt(self.ui_use_default_base_prompt)
|
||||
ai_client.set_project_context_marker(self.ui_project_context_marker)
|
||||
self.last_resolved_system_prompt = ai_client.get_combined_system_prompt()
|
||||
self.discussion_sent_markdown = event.stable_md
|
||||
self.discussion_sent_system_prompt = self.last_resolved_system_prompt
|
||||
ai_client.set_model_params(self.temperature, self.max_tokens, self.history_trunc_limit, self.top_p)
|
||||
ai_client.set_agent_tools(self.ui_agent_tools) # Force update adapter path right before send to bypass potential duplication issues
|
||||
self._update_gcli_adapter(self.ui_gemini_cli_path)
|
||||
@@ -3012,6 +3016,8 @@ class AppController:
|
||||
disc_data = discussions[name]
|
||||
with self._disc_entries_lock:
|
||||
self.disc_entries[:] = models.parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
||||
self.discussion_sent_markdown = disc_data.get("sent_markdown", "")
|
||||
self.discussion_sent_system_prompt = disc_data.get("sent_system_prompt", "")
|
||||
if "context_snapshot" in disc_data:
|
||||
snapshot_data = disc_data["context_snapshot"]
|
||||
self.context_files = [models.FileItem.from_dict(f) if isinstance(f, dict) else models.FileItem(path=str(f)) for f in snapshot_data]
|
||||
@@ -3031,6 +3037,8 @@ class AppController:
|
||||
disc_data["history"] = history_strings
|
||||
disc_data["last_updated"] = project_manager.now_ts()
|
||||
disc_data["context_snapshot"] = [f.to_dict() if hasattr(f, "to_dict") else {"path": str(f)} for f in self.context_files]
|
||||
disc_data["sent_markdown"] = getattr(self, "discussion_sent_markdown", "")
|
||||
disc_data["sent_system_prompt"] = getattr(self, "discussion_sent_system_prompt", "")
|
||||
|
||||
def _create_discussion(self, name: str) -> None:
|
||||
"""
|
||||
@@ -3182,6 +3190,8 @@ class AppController:
|
||||
self._tool_stats.clear()
|
||||
self._comms_log.clear()
|
||||
self.disc_entries.clear()
|
||||
self.discussion_sent_markdown = ""
|
||||
self.discussion_sent_system_prompt = ""
|
||||
self.files.clear()
|
||||
self.context_files.clear()
|
||||
self.tracks.clear()
|
||||
|
||||
+14
-19
@@ -205,19 +205,11 @@ class ASTParser:
|
||||
"""
|
||||
[C: src/mcp_client.py:_search_file, src/mcp_client.py:py_find_usages, src/mcp_client.py:py_get_hierarchy, src/mcp_client.py:trace, src/outline_tool.py:CodeOutliner.outline, src/outline_tool.py:CodeOutliner.walk, src/summarize.py:_summarise_python]
|
||||
"""
|
||||
if node.type in ("function_definition", "method_definition", "template_declaration"):
|
||||
# If template, look for inner function/method
|
||||
target_node = node
|
||||
if node.type == "template_declaration":
|
||||
for child in node.children:
|
||||
if child.type in ("function_definition", "method_definition"):
|
||||
target_node = child
|
||||
break
|
||||
|
||||
body = target_node.child_by_field_name("body")
|
||||
if node.type in ("function_definition", "method_definition"):
|
||||
body = node.child_by_field_name("body")
|
||||
if not body:
|
||||
# C++ fallback: sometimes the body is just a compound_statement without a field name in certain contexts
|
||||
for child in target_node.children:
|
||||
for child in node.children:
|
||||
if child.type in ("compound_statement", "block"):
|
||||
body = child
|
||||
break
|
||||
@@ -232,7 +224,7 @@ class ASTParser:
|
||||
|
||||
initializer = None
|
||||
if self.language_name in ("cpp", "c"):
|
||||
for child in target_node.children:
|
||||
for child in node.children:
|
||||
if child.type == "field_initializer_list":
|
||||
initializer = child
|
||||
break
|
||||
@@ -248,18 +240,19 @@ class ASTParser:
|
||||
else:
|
||||
edits.append((start_byte, end_byte, f"\n{indent}..."))
|
||||
else:
|
||||
# If there's an initializer list (C++), we strip it too or start from it
|
||||
start_byte = initializer.start_byte if initializer else body.start_byte
|
||||
end_byte = body.end_byte
|
||||
|
||||
# Try to preserve braces for C-style languages
|
||||
if body.type == "compound_statement" and len(body.children) >= 2:
|
||||
if body.children[0].type == "{" and body.children[-1].type == "}":
|
||||
if body.type == "compound_statement" and len(body.children) >= 2 and body.children[0].type == "{" and body.children[-1].type == "}":
|
||||
if initializer:
|
||||
start_byte = initializer.start_byte
|
||||
end_byte = body.children[-1].start_byte
|
||||
edits.append((start_byte, end_byte, "{ ... "))
|
||||
else:
|
||||
start_byte = body.children[0].end_byte
|
||||
end_byte = body.children[-1].start_byte
|
||||
edits.append((start_byte, end_byte, " ... "))
|
||||
else:
|
||||
edits.append((start_byte, end_byte, "..."))
|
||||
else:
|
||||
edits.append((start_byte, end_byte, "..."))
|
||||
|
||||
@@ -754,9 +747,11 @@ class ASTParser:
|
||||
ntype = node.type
|
||||
label = ""
|
||||
if ntype in ("class_definition", "class_specifier"):
|
||||
label = "[Class]"
|
||||
has_body = node.child_by_field_name("body") is not None
|
||||
label = "[Class]" if has_body else "[ClassDecl]"
|
||||
elif ntype == "struct_specifier":
|
||||
label = "[Struct]"
|
||||
has_body = node.child_by_field_name("body") is not None
|
||||
label = "[Struct]" if has_body else "[StructDecl]"
|
||||
elif ntype == "function_definition":
|
||||
label = "[Method]" if indent > 0 else "[Func]"
|
||||
|
||||
|
||||
+49
-3
@@ -190,6 +190,7 @@ class App:
|
||||
self.show_windows.setdefault('Undo/Redo History', False)
|
||||
# --- Preset & Profile Management State ---
|
||||
self.context_preview_text = ""
|
||||
self.ui_separate_context_preview = False
|
||||
self.ui_active_context_preset = ""
|
||||
self.ui_new_context_preset_name = ""
|
||||
self._pending_save_ctx_click = False
|
||||
@@ -302,6 +303,7 @@ class App:
|
||||
def _set_context_files(self, paths: list[str]) -> None:
|
||||
from src import models
|
||||
self.context_files = [models.FileItem(path=p) for p in paths]
|
||||
self.controller.context_files = self.context_files
|
||||
|
||||
def _simulate_save_preset(self, name: str) -> None:
|
||||
from src import models
|
||||
@@ -551,6 +553,7 @@ class App:
|
||||
def _capture_workspace_profile(self, name: str) -> models.WorkspaceProfile:
|
||||
ini = imgui.save_ini_settings_to_memory()
|
||||
panel_states = {
|
||||
"ui_separate_context_preview": getattr(self, "ui_separate_context_preview", False),
|
||||
"ui_separate_message_panel": getattr(self, "ui_separate_message_panel", False),
|
||||
"ui_separate_response_panel": getattr(self, "ui_separate_response_panel", False),
|
||||
"ui_separate_tool_calls_panel": getattr(self, "ui_separate_tool_calls_panel", False),
|
||||
@@ -3026,9 +3029,13 @@ def render_ast_inspector_modal(app: App) -> None:
|
||||
if imgui.is_item_hovered():
|
||||
app._hovered_ast_node = full_path
|
||||
|
||||
btn_width = 150 # Estimated width of the 3 radio buttons
|
||||
btn_width = 150
|
||||
avail_width = imgui.get_content_region_avail().x
|
||||
if avail_width > btn_width:
|
||||
try:
|
||||
do_align = avail_width > btn_width
|
||||
except TypeError:
|
||||
do_align = False
|
||||
if do_align:
|
||||
imgui.same_line(imgui.get_window_width() - btn_width)
|
||||
else:
|
||||
imgui.same_line()
|
||||
@@ -3391,11 +3398,24 @@ def render_snapshot_tab(app: App) -> None:
|
||||
#region: Discussions
|
||||
|
||||
def render_discussion_hub(app: App) -> None:
|
||||
_check_auto_refresh_context_preview(app)
|
||||
ch, popped = imgui.checkbox("Pop Out Context Preview", getattr(app, "ui_separate_context_preview", False))
|
||||
if ch:
|
||||
app.ui_separate_context_preview = popped
|
||||
app.show_windows["Context Preview"] = popped
|
||||
with imscope.tab_bar("discussion_hub_tabs"):
|
||||
with imscope.tab_item("Discussion") as (exp, opened):
|
||||
if exp: render_discussion_tab(app)
|
||||
with imscope.tab_item("Context Composition") as (exp, opened):
|
||||
if exp: render_context_composition_panel(app)
|
||||
if not getattr(app, "ui_separate_context_preview", False):
|
||||
with imscope.tab_item("Context Preview") as (exp, opened):
|
||||
if exp:
|
||||
if imgui.button("Copy to Clipboard"):
|
||||
imgui.set_clipboard_text(app.context_preview_text)
|
||||
imgui.begin_child("ctx_preview_scroll_tab", imgui.ImVec2(0, 0), True)
|
||||
markdown_helper.render(app.context_preview_text, context_id="ctx_preview_tab")
|
||||
imgui.end_child()
|
||||
with imscope.tab_item("Snapshot") as (exp, opened):
|
||||
if exp: render_snapshot_tab(app)
|
||||
with imscope.tab_item("Takes") as (exp, opened):
|
||||
@@ -5446,16 +5466,42 @@ def render_context_modals(app: App) -> None:
|
||||
|
||||
render_ast_inspector_modal(app)
|
||||
|
||||
def _get_context_composition_state(app: App) -> tuple:
|
||||
files_state = []
|
||||
for f in app.context_files:
|
||||
p = f.path if hasattr(f, 'path') else str(f)
|
||||
vm = f.view_mode if hasattr(f, 'view_mode') else 'summary'
|
||||
sel = f.selected if hasattr(f, 'selected') else False
|
||||
slc = tuple((s.get('start_line'), s.get('end_line'), s.get('tag'), s.get('comment')) for s in getattr(f, 'custom_slices', []))
|
||||
mask = tuple(sorted(getattr(f, 'ast_mask', {}).items()))
|
||||
files_state.append((p, vm, sel, slc, mask))
|
||||
screenshots_state = tuple(app.screenshots)
|
||||
return (tuple(files_state), screenshots_state)
|
||||
|
||||
def _check_auto_refresh_context_preview(app: App) -> None:
|
||||
current_state = _get_context_composition_state(app)
|
||||
if not hasattr(app, "_last_context_preview_state") or app._last_context_preview_state != current_state:
|
||||
app._last_context_preview_state = current_state
|
||||
try:
|
||||
app.controller.context_files = app.context_files
|
||||
res = app.controller._do_generate()
|
||||
app.context_preview_text = res[0]
|
||||
except Exception:
|
||||
app.context_preview_text = "Error generating context preview."
|
||||
|
||||
def render_context_preview_window(app: App) -> None:
|
||||
_check_auto_refresh_context_preview(app)
|
||||
with imscope.window("Context Preview", app.show_windows["Context Preview"]) as (exp, opened):
|
||||
app.show_windows["Context Preview"] = bool(opened)
|
||||
if not opened:
|
||||
app.ui_separate_context_preview = False
|
||||
if exp:
|
||||
if imgui.button("Close"):
|
||||
app.show_windows["Context Preview"] = False
|
||||
app.ui_separate_context_preview = False
|
||||
imgui.same_line()
|
||||
if imgui.button("Copy to Clipboard"):
|
||||
imgui.set_clipboard_text(app.context_preview_text)
|
||||
|
||||
imgui.begin_child("ctx_preview_scroll", imgui.ImVec2(0, 0), True)
|
||||
markdown_helper.render(app.context_preview_text, context_id="ctx_preview")
|
||||
imgui.end_child()
|
||||
|
||||
@@ -73,9 +73,9 @@ void myTemplateFunc(T x) {
|
||||
"""
|
||||
skeleton = parser.get_skeleton(code)
|
||||
assert 'class MyClass {' in skeleton
|
||||
assert 'void myMethod() ...' in skeleton
|
||||
assert 'void myMethod() { ... }' in skeleton
|
||||
assert 'template <typename T>' in skeleton
|
||||
assert 'void myTemplateFunc(T x) ...' in skeleton
|
||||
assert 'void myTemplateFunc(T x) { ... }' in skeleton
|
||||
assert 'int x = 1;' not in skeleton
|
||||
assert 'x.doSomething();' not in skeleton
|
||||
|
||||
|
||||
@@ -90,12 +90,12 @@ def test_app_controller_save_load(tmp_path, monkeypatch):
|
||||
controller._save_active_project.assert_called_once()
|
||||
|
||||
# Change state
|
||||
controller.ui_file_paths = []
|
||||
controller.context_files = []
|
||||
controller.screenshots = []
|
||||
|
||||
# Load preset
|
||||
loaded = controller.load_context_preset("saved_preset")
|
||||
assert loaded.name == "saved_preset"
|
||||
assert controller.ui_file_paths == ["app.py"]
|
||||
assert [f.path for f in controller.context_files] == ["app.py"]
|
||||
assert controller.screenshots == ["app.png"]
|
||||
controller._save_active_project.assert_called()
|
||||
|
||||
@@ -5,7 +5,7 @@ def test_context_file_entry_serialization():
|
||||
p = ContextFileEntry(path="test.py", view_mode="skeleton")
|
||||
d = p.to_dict()
|
||||
# Check for default custom_slices
|
||||
assert d == {"path": "test.py", "view_mode": "skeleton", "custom_slices": []}
|
||||
assert d == {"path": "test.py", "view_mode": "skeleton", "custom_slices": [], "ast_definitions": False, "ast_mask": {}, "ast_signatures": False}
|
||||
|
||||
p2 = ContextFileEntry.from_dict(d)
|
||||
assert p2.path == "test.py"
|
||||
|
||||
@@ -12,9 +12,9 @@ def test_context_preview_and_ast_inspector(live_gui):
|
||||
|
||||
# Set context file
|
||||
resp = requests.post("http://127.0.0.1:8999/api/gui", json={
|
||||
"action": "set_value",
|
||||
"item": "files",
|
||||
"value": ["src/aggregate.py"]
|
||||
"action": "custom_callback",
|
||||
"callback": "set_context_files",
|
||||
"args": [["src/aggregate.py"]]
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
time.sleep(1)
|
||||
|
||||
@@ -35,6 +35,7 @@ def test_phase4_final_verify(live_gui):
|
||||
client.set_value('current_provider', 'gemini_cli')
|
||||
client.set_value('gcli_path', os.path.abspath(os.path.join(os.path.dirname(__file__), "mock_gcli.bat")))
|
||||
|
||||
time.sleep(1.5)
|
||||
# Wait for settings to apply and engine to sync
|
||||
success = False
|
||||
for _ in range(100):
|
||||
|
||||
@@ -33,7 +33,7 @@ def test_rag_large_codebase_verification_sim(live_gui):
|
||||
client.set_value('rag_source', 'chroma')
|
||||
client.set_value('rag_emb_provider', 'local')
|
||||
client.set_value('auto_add_history', True)
|
||||
|
||||
time.sleep(1.5)
|
||||
# Wait for settings to apply and engine to sync (initial indexing happens automatically)
|
||||
print("[SIM] Waiting for automatic initial indexing...")
|
||||
start_initial = time.time()
|
||||
@@ -93,7 +93,7 @@ def test_rag_large_codebase_verification_sim(live_gui):
|
||||
break
|
||||
time.sleep(0.2)
|
||||
|
||||
client.set_value('ai_input', "What is the modified content?")
|
||||
client.set_value('ai_input', "Search for MODIFIED CONTENT FOR FILE 25")
|
||||
client.click('btn_gen_send')
|
||||
|
||||
# Wait for completion
|
||||
|
||||
@@ -34,8 +34,8 @@ struct Point {
|
||||
|
||||
try:
|
||||
skeleton = ts_c_get_skeleton(str(c_file))
|
||||
assert "void hello() ..." in skeleton
|
||||
assert "int add(int a, int b) ..." in skeleton
|
||||
assert "void hello() { ... }" in skeleton
|
||||
assert "int add(int a, int b) { ... }" in skeleton
|
||||
assert "struct Point" in skeleton
|
||||
assert "printf" not in skeleton
|
||||
finally:
|
||||
|
||||
@@ -37,9 +37,9 @@ void globalFunc() {
|
||||
try:
|
||||
skeleton = ts_cpp_get_skeleton(str(cpp_file))
|
||||
assert "class Box" in skeleton
|
||||
assert "Box(T val) ..." in skeleton
|
||||
assert "T getValue() ..." in skeleton
|
||||
assert "void globalFunc() ..." in skeleton
|
||||
assert "Box(T val) { ... }" in skeleton
|
||||
assert "T getValue() { ... }" in skeleton
|
||||
assert "void globalFunc() { ... }" in skeleton
|
||||
assert "std::cout" not in skeleton
|
||||
finally:
|
||||
mcp_client._resolve_and_check = original_resolve
|
||||
|
||||
@@ -18,7 +18,7 @@ async def test_websocket_subscription_and_broadcast():
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
try:
|
||||
uri = f"ws://127.0.0.1:{port}"
|
||||
uri = f"ws://127.0.0.1:{server.port}"
|
||||
async with websockets.connect(uri) as websocket:
|
||||
# Subscribe to events channel
|
||||
subscribe_msg = {"action": "subscribe", "channel": "events"}
|
||||
|
||||
Reference in New Issue
Block a user