Finished encapsualte track.
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
file_path = Path('src/app_controller.py')
|
||||
code = file_path.read_text(encoding='utf-8')
|
||||
|
||||
# Remove the garbage tail
|
||||
code = re.sub(r'\n= \[\]\s*$', '', code)
|
||||
code = code.strip() + "\n"
|
||||
|
||||
file_path.write_text(code, encoding='utf-8')
|
||||
print("Tail cleanup complete.")
|
||||
@@ -0,0 +1,29 @@
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
file_path = Path('src/app_controller.py')
|
||||
code = file_path.read_text(encoding='utf-8')
|
||||
|
||||
# 1. Inject show_windows
|
||||
code = re.sub(
|
||||
r'(self\._pending_gui_tasks_lock: threading\.Lock = threading\.Lock\(\))',
|
||||
r'\1\n self.show_windows: Dict[str, bool] = {}',
|
||||
code
|
||||
)
|
||||
|
||||
# 2. Inject ui_active_persona and ui_active_bias_profile
|
||||
code = re.sub(
|
||||
r'(self\.ui_ai_input: str = "")',
|
||||
r'self.ui_active_persona: str = ""\n self.ui_active_bias_profile: str | None = None\n \1',
|
||||
code
|
||||
)
|
||||
|
||||
# 3. Add ui_active_persona to _settable_fields
|
||||
code = re.sub(
|
||||
r"('disc_new_role_input': 'ui_disc_new_role_input',)",
|
||||
r"\1\n 'active_persona': 'ui_active_persona',",
|
||||
code
|
||||
)
|
||||
|
||||
file_path.write_text(code, encoding='utf-8')
|
||||
print("Precision injection complete.")
|
||||
@@ -0,0 +1,62 @@
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
# Fix AppController
|
||||
file_ac = Path('src/app_controller.py')
|
||||
code_ac = file_ac.read_text(encoding='utf-8')
|
||||
# Robust non-recursive __getattr__
|
||||
new_getattr_ac = """ def __getattr__(self, name: str) -> Any:
|
||||
if name == '_app':
|
||||
raise AttributeError(name)
|
||||
# Check if _app exists in self without triggering __getattr__
|
||||
try:
|
||||
app = object.__getattribute__(self, '_app')
|
||||
except AttributeError:
|
||||
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
|
||||
|
||||
if app is not None and name in app.__dict__:
|
||||
return getattr(app, name)
|
||||
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
|
||||
"""
|
||||
# Replace all instances of __getattr__ in AppController (in case of duplication)
|
||||
code_ac = re.sub(r'def __getattr__\(self, name: str\) -> Any:.*?raise AttributeError\(.*?\)', new_getattr_ac, code_ac, flags=re.DOTALL)
|
||||
file_ac.write_text(code_ac, encoding='utf-8')
|
||||
|
||||
# Fix App
|
||||
file_gui = Path('src/gui_2.py')
|
||||
code_gui = file_gui.read_text(encoding='utf-8')
|
||||
|
||||
new_getattr_gui = """ def __getattr__(self, name: str) -> Any:
|
||||
if name == 'controller':
|
||||
raise AttributeError(name)
|
||||
try:
|
||||
ctrl = object.__getattribute__(self, 'controller')
|
||||
except AttributeError:
|
||||
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
|
||||
|
||||
if ctrl is not None and name in ctrl.__dict__:
|
||||
return getattr(ctrl, name)
|
||||
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
|
||||
"""
|
||||
code_gui = re.sub(r'def __getattr__\(self, name: str\) -> Any:.*?raise AttributeError\(.*?\)', new_getattr_gui, code_gui, flags=re.DOTALL)
|
||||
|
||||
new_setattr_gui = """ def __setattr__(self, name: str, value: Any) -> None:
|
||||
if name == 'controller':
|
||||
object.__setattr__(self, name, value)
|
||||
return
|
||||
|
||||
try:
|
||||
ctrl = object.__getattribute__(self, 'controller')
|
||||
except AttributeError:
|
||||
ctrl = None
|
||||
|
||||
if ctrl is not None and name in ctrl.__dict__:
|
||||
setattr(ctrl, name, value)
|
||||
else:
|
||||
object.__setattr__(self, name, value)
|
||||
"""
|
||||
code_gui = re.sub(r'def __setattr__\(self, name: str, value: Any\) -> None:.*?object\.__setattr__\(self, name, value\)', new_setattr_gui, code_gui, flags=re.DOTALL)
|
||||
|
||||
file_gui.write_text(code_gui, encoding='utf-8')
|
||||
|
||||
print('Delegation fix complete.')
|
||||
@@ -0,0 +1,28 @@
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
file_path = Path('src/app_controller.py')
|
||||
lines = file_path.read_text(encoding='utf-8').splitlines()
|
||||
|
||||
# We know the issue starts around line 1405 where `def cb_exit_prior_session(self):` is indented with 2 spaces.
|
||||
# We will unindent everything that has 2 or more spaces back by 1 space,
|
||||
# until we hit a line that is correctly 1-space indented (like `def start_services` or similar, if it exists).
|
||||
# Actually, let's just use a state machine: find `def cb_exit_prior_session` and unindent everything until `def start_services` or end of file.
|
||||
|
||||
in_bad_block = False
|
||||
for i, line in enumerate(lines):
|
||||
if re.match(r'^ def cb_exit_prior_session\(self\):', line):
|
||||
in_bad_block = True
|
||||
|
||||
if in_bad_block:
|
||||
if line.startswith(' '):
|
||||
lines[i] = line[1:]
|
||||
elif line.startswith(' '):
|
||||
pass # Keep it if it's already 1 space
|
||||
|
||||
# Stop if we hit a properly indented class method
|
||||
if in_bad_block and re.match(r'^ def start_services', line):
|
||||
in_bad_block = False
|
||||
|
||||
file_path.write_text('\n'.join(lines) + '\n', encoding='utf-8')
|
||||
print("Scope repair complete.")
|
||||
@@ -0,0 +1,65 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
project_root = os.path.abspath(os.getcwd())
|
||||
sys.path.append(project_root)
|
||||
sys.path.append(os.path.join(project_root, 'src'))
|
||||
|
||||
from src import app_controller, gui_2, models, project_manager, events
|
||||
|
||||
def test_repro():
|
||||
print("Initializing App...")
|
||||
app = gui_2.App()
|
||||
ctrl = app.controller
|
||||
|
||||
# 1. Scaffold a fake project
|
||||
proj_path = os.path.abspath("tests/artifacts/repro_project.toml")
|
||||
if os.path.exists(proj_path): os.remove(proj_path)
|
||||
|
||||
print(f"Scaffolding project at {proj_path}...")
|
||||
proj = project_manager.default_project("Repro")
|
||||
project_manager.save_project(proj, proj_path)
|
||||
|
||||
# 2. Load the project
|
||||
ctrl.active_project_path = proj_path
|
||||
ctrl.init_state()
|
||||
|
||||
# 3. Enable history
|
||||
ctrl.ui_auto_add_history = True
|
||||
print(f"ui_auto_add_history set to: {ctrl.ui_auto_add_history}")
|
||||
|
||||
# 4. Simulate a request event
|
||||
print("Triggering user_request event...")
|
||||
event_payload = events.UserRequestEvent(prompt="Hello Test", stable_md="Context", file_items=[], disc_text="", base_dir=".")
|
||||
# The event is processed by _handle_request_event which calls ai_client.send
|
||||
# But ai_client.send emits events via its global emitter.
|
||||
|
||||
from src import ai_client
|
||||
# Simulate ai_client emitting the "request_start" event which should trigger _on_api_event
|
||||
print("Emitting request_start event...")
|
||||
ai_client.events.emit("request_start", payload={
|
||||
"provider": "repro",
|
||||
"model": "repro-model",
|
||||
"message": "Hello Test"
|
||||
}, kind="request")
|
||||
|
||||
# 5. Wait for history processor
|
||||
print("Waiting for history processing...")
|
||||
# Headless mode would call it in queue_fallback, but we can call it manually
|
||||
time.sleep(0.5)
|
||||
ctrl._process_pending_history_adds()
|
||||
|
||||
print(f"History entries: {len(ctrl.disc_entries)}")
|
||||
for e in ctrl.disc_entries:
|
||||
print(f" - {e.get('role')}: {e.get('content')}")
|
||||
|
||||
if len(ctrl.disc_entries) == 0:
|
||||
print("REPRODUCED: History is empty!")
|
||||
else:
|
||||
print("SUCCESS: History updated.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_repro()
|
||||
Reference in New Issue
Block a user