fix(conductor): Apply review suggestions for track 'Add full api/hooks so that gemini cli can test, interact, and manipulate the state of the gui & program backend for automated testing.'
This commit is contained in:
21
api_hooks.py
21
api_hooks.py
@@ -5,11 +5,13 @@ import logging
|
||||
import session_logger
|
||||
|
||||
class HookServerInstance(HTTPServer):
|
||||
"""Custom HTTPServer that carries a reference to the main App instance."""
|
||||
def __init__(self, server_address, RequestHandlerClass, app):
|
||||
super().__init__(server_address, RequestHandlerClass)
|
||||
self.app = app
|
||||
|
||||
class HookHandler(BaseHTTPRequestHandler):
|
||||
"""Handles incoming HTTP requests for the API hooks."""
|
||||
def do_GET(self):
|
||||
app = self.server.app
|
||||
session_logger.log_api_hook("GET", self.path, "")
|
||||
@@ -22,12 +24,15 @@ class HookHandler(BaseHTTPRequestHandler):
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({'project': app.project}).encode('utf-8'))
|
||||
self.wfile.write(
|
||||
json.dumps({'project': app.project}).encode('utf-8'))
|
||||
elif self.path == '/api/session':
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({'session': {'entries': app.disc_entries}}).encode('utf-8'))
|
||||
self.wfile.write(
|
||||
json.dumps({'session': {'entries': app.disc_entries}}).
|
||||
encode('utf-8'))
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
@@ -46,13 +51,16 @@ class HookHandler(BaseHTTPRequestHandler):
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({'status': 'updated'}).encode('utf-8'))
|
||||
self.wfile.write(
|
||||
json.dumps({'status': 'updated'}).encode('utf-8'))
|
||||
elif self.path == '/api/session':
|
||||
app.disc_entries = data.get('session', {}).get('entries', app.disc_entries)
|
||||
app.disc_entries = data.get('session', {}).get(
|
||||
'entries', app.disc_entries)
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({'status': 'updated'}).encode('utf-8'))
|
||||
self.wfile.write(
|
||||
json.dumps({'status': 'updated'}).encode('utf-8'))
|
||||
elif self.path == '/api/gui':
|
||||
if not hasattr(app, '_pending_gui_tasks'):
|
||||
app._pending_gui_tasks = []
|
||||
@@ -65,7 +73,8 @@ class HookHandler(BaseHTTPRequestHandler):
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({'status': 'queued'}).encode('utf-8'))
|
||||
self.wfile.write(
|
||||
json.dumps({'status': 'queued'}).encode('utf-8'))
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
|
||||
12
gui.py
12
gui.py
@@ -24,8 +24,8 @@ from ai_client import ProviderError
|
||||
import shell_runner
|
||||
import session_logger
|
||||
import project_manager
|
||||
import theme
|
||||
import api_hooks
|
||||
import theme
|
||||
|
||||
CONFIG_PATH = Path("config.toml")
|
||||
PROVIDERS = ["gemini", "anthropic"]
|
||||
@@ -396,8 +396,12 @@ def _parse_history_entries(history: list[str], roles: list[str] | None = None) -
|
||||
class App:
|
||||
def __init__(self):
|
||||
self.config = load_config()
|
||||
self.test_hooks_enabled = '--enable-test-hooks' in sys.argv or os.environ.get('SLOP_TEST_HOOKS') == '1'
|
||||
self.hook_server = api_hooks.HookServer(self)
|
||||
# Controls whether API hooks are enabled, based on CLI arg or env var
|
||||
self.test_hooks_enabled: bool = (
|
||||
'--enable-test-hooks' in sys.argv or
|
||||
os.environ.get('SLOP_TEST_HOOKS') == '1')
|
||||
# The API hook server instance
|
||||
self.hook_server: api_hooks.HookServer = api_hooks.HookServer(self)
|
||||
|
||||
# ---- global settings from config.toml ----
|
||||
ai_cfg = self.config.get("ai", {})
|
||||
@@ -474,7 +478,9 @@ class App:
|
||||
self._pending_history_adds_lock = threading.Lock()
|
||||
|
||||
# API GUI Hooks Queue
|
||||
# Tasks (e.g., set_value, click) to be executed on the main DPG thread
|
||||
self._pending_gui_tasks: list[dict] = []
|
||||
# Lock for _pending_gui_tasks to ensure thread safety
|
||||
self._pending_gui_tasks_lock = threading.Lock()
|
||||
|
||||
# Blink state
|
||||
|
||||
@@ -40,7 +40,7 @@ _seq_lock = threading.Lock()
|
||||
|
||||
_comms_fh = None # file handle: logs/comms_<ts>.log
|
||||
_tool_fh = None # file handle: logs/toolcalls_<ts>.log
|
||||
_api_fh = None # file handle: logs/apihooks_<ts>.log
|
||||
_api_fh = None # file handle: logs/apihooks_<ts>.log - API hook calls
|
||||
|
||||
|
||||
def _now_ts() -> str:
|
||||
|
||||
@@ -58,26 +58,41 @@ def test_ipc_server_starts_and_responds():
|
||||
assert "session" in data
|
||||
|
||||
# Test project POST
|
||||
req = urllib.request.Request("http://127.0.0.1:8999/api/project", method="POST", data=json.dumps({"project": {"foo": "bar"}}).encode("utf-8"), headers={'Content-Type': 'application/json'})
|
||||
project_data = {"project": {"foo": "bar"}}
|
||||
req = urllib.request.Request(
|
||||
"http://127.0.0.1:8999/api/project",
|
||||
method="POST",
|
||||
data=json.dumps(project_data).encode("utf-8"),
|
||||
headers={'Content-Type': 'application/json'})
|
||||
with urllib.request.urlopen(req) as response:
|
||||
assert response.status == 200
|
||||
assert app_mock.project == {"foo": "bar"}
|
||||
|
||||
# Test session POST
|
||||
req = urllib.request.Request("http://127.0.0.1:8999/api/session", method="POST", data=json.dumps({"session": {"entries": [{"role": "User", "content": "hi"}]}}).encode("utf-8"), headers={'Content-Type': 'application/json'})
|
||||
session_data = {"session": {"entries": [{"role": "User", "content": "hi"}]}}
|
||||
req = urllib.request.Request(
|
||||
"http://127.0.0.1:8999/api/session",
|
||||
method="POST",
|
||||
data=json.dumps(session_data).encode("utf-8"),
|
||||
headers={'Content-Type': 'application/json'})
|
||||
with urllib.request.urlopen(req) as response:
|
||||
assert response.status == 200
|
||||
assert app_mock.disc_entries == [{"role": "User", "content": "hi"}]
|
||||
|
||||
# Test GUI queue hook
|
||||
req = urllib.request.Request("http://127.0.0.1:8999/api/gui", method="POST", data=json.dumps({"action": "set_value", "item": "test_item", "value": "test_value"}).encode("utf-8"), headers={'Content-Type': 'application/json'})
|
||||
gui_data = {"action": "set_value", "item": "test_item", "value": "test_value"}
|
||||
req = urllib.request.Request(
|
||||
"http://127.0.0.1:8999/api/gui",
|
||||
method="POST",
|
||||
data=json.dumps(gui_data).encode("utf-8"),
|
||||
headers={'Content-Type': 'application/json'})
|
||||
with urllib.request.urlopen(req) as response:
|
||||
assert response.status == 200
|
||||
# Instead of checking DPG (since we aren't running the real main loop in tests),
|
||||
# check if it got queued in app_mock
|
||||
assert hasattr(app_mock, '_pending_gui_tasks')
|
||||
assert len(app_mock._pending_gui_tasks) == 1
|
||||
assert app_mock._pending_gui_tasks[0] == {"action": "set_value", "item": "test_item", "value": "test_value"}
|
||||
assert app_mock._pending_gui_tasks[0] == gui_data
|
||||
|
||||
finally:
|
||||
server.stop()
|
||||
|
||||
Reference in New Issue
Block a user