feat: terminal fullscreen, asyncpg fix, CI improvements #59
@@ -87,95 +87,6 @@ async def list_conversations(
|
|||||||
} for r in rows]
|
} for r in rows]
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{session_id}")
|
|
||||||
async def get_conversation(session_id: str):
|
|
||||||
"""Get conversation details"""
|
|
||||||
async with await _db() as db:
|
|
||||||
# Get conversation metadata
|
|
||||||
cursor = await db.execute("""
|
|
||||||
SELECT project_path, git_branch, slug, started_at, last_updated_at,
|
|
||||||
message_count, total_input_tokens, total_output_tokens, model
|
|
||||||
FROM conversations
|
|
||||||
WHERE session_id = ?
|
|
||||||
""", (session_id,))
|
|
||||||
conv = await cursor.fetchone()
|
|
||||||
|
|
||||||
if not conv:
|
|
||||||
raise HTTPException(404, "Conversation not found")
|
|
||||||
|
|
||||||
# Get message count
|
|
||||||
cursor = await db.execute("""
|
|
||||||
SELECT COUNT(*) FROM messages WHERE session_id = ?
|
|
||||||
""", (session_id,))
|
|
||||||
msg_count = (await cursor.fetchone())[0]
|
|
||||||
|
|
||||||
return {
|
|
||||||
"session_id": session_id,
|
|
||||||
"project_path": conv[0],
|
|
||||||
"git_branch": conv[1],
|
|
||||||
"slug": conv[2],
|
|
||||||
"started_at": conv[3],
|
|
||||||
"last_updated_at": conv[4],
|
|
||||||
"message_count": msg_count,
|
|
||||||
"total_input_tokens": conv[6],
|
|
||||||
"total_output_tokens": conv[7],
|
|
||||||
"model": conv[8]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{session_id}/messages")
|
|
||||||
async def get_messages(
|
|
||||||
session_id: str,
|
|
||||||
limit: int = Query(100, le=500),
|
|
||||||
offset: int = 0
|
|
||||||
):
|
|
||||||
"""Get messages for a conversation"""
|
|
||||||
async with await _db() as db:
|
|
||||||
cursor = await db.execute("""
|
|
||||||
SELECT message_uuid, role, content_text, timestamp, model,
|
|
||||||
input_tokens, output_tokens, has_tool_use, has_thinking
|
|
||||||
FROM messages
|
|
||||||
WHERE session_id = ?
|
|
||||||
ORDER BY timestamp
|
|
||||||
LIMIT ? OFFSET ?
|
|
||||||
""", (session_id, limit, offset))
|
|
||||||
rows = await cursor.fetchall()
|
|
||||||
|
|
||||||
messages = []
|
|
||||||
for r in rows:
|
|
||||||
msg = {
|
|
||||||
"uuid": r[0],
|
|
||||||
"role": r[1],
|
|
||||||
"content": r[2],
|
|
||||||
"timestamp": r[3],
|
|
||||||
"model": r[4],
|
|
||||||
"input_tokens": r[5],
|
|
||||||
"output_tokens": r[6],
|
|
||||||
"has_tool_use": bool(r[7]),
|
|
||||||
"has_thinking": bool(r[8]),
|
|
||||||
"tool_calls": []
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get tool calls for this message
|
|
||||||
if msg["has_tool_use"]:
|
|
||||||
cursor2 = await db.execute("""
|
|
||||||
SELECT tool_name, tool_input, tool_result, is_error
|
|
||||||
FROM tool_calls
|
|
||||||
WHERE message_uuid = ?
|
|
||||||
""", (r[0],))
|
|
||||||
tool_rows = await cursor2.fetchall()
|
|
||||||
msg["tool_calls"] = [{
|
|
||||||
"name": tr[0],
|
|
||||||
"input": tr[1],
|
|
||||||
"result": tr[2],
|
|
||||||
"is_error": bool(tr[3])
|
|
||||||
} for tr in tool_rows]
|
|
||||||
|
|
||||||
messages.append(msg)
|
|
||||||
|
|
||||||
return messages
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/search")
|
@router.get("/search")
|
||||||
async def search_conversations(
|
async def search_conversations(
|
||||||
q: str = Query(..., min_length=2),
|
q: str = Query(..., min_length=2),
|
||||||
@@ -284,3 +195,92 @@ async def trigger_summary():
|
|||||||
f.write("1")
|
f.write("1")
|
||||||
|
|
||||||
return {"status": "triggered"}
|
return {"status": "triggered"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{session_id}")
|
||||||
|
async def get_conversation(session_id: str):
|
||||||
|
"""Get conversation details"""
|
||||||
|
async with await _db() as db:
|
||||||
|
# Get conversation metadata
|
||||||
|
cursor = await db.execute("""
|
||||||
|
SELECT project_path, git_branch, slug, started_at, last_updated_at,
|
||||||
|
message_count, total_input_tokens, total_output_tokens, model
|
||||||
|
FROM conversations
|
||||||
|
WHERE session_id = ?
|
||||||
|
""", (session_id,))
|
||||||
|
conv = await cursor.fetchone()
|
||||||
|
|
||||||
|
if not conv:
|
||||||
|
raise HTTPException(404, "Conversation not found")
|
||||||
|
|
||||||
|
# Get message count
|
||||||
|
cursor = await db.execute("""
|
||||||
|
SELECT COUNT(*) FROM messages WHERE session_id = ?
|
||||||
|
""", (session_id,))
|
||||||
|
msg_count = (await cursor.fetchone())[0]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"session_id": session_id,
|
||||||
|
"project_path": conv[0],
|
||||||
|
"git_branch": conv[1],
|
||||||
|
"slug": conv[2],
|
||||||
|
"started_at": conv[3],
|
||||||
|
"last_updated_at": conv[4],
|
||||||
|
"message_count": msg_count,
|
||||||
|
"total_input_tokens": conv[6],
|
||||||
|
"total_output_tokens": conv[7],
|
||||||
|
"model": conv[8]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{session_id}/messages")
|
||||||
|
async def get_messages(
|
||||||
|
session_id: str,
|
||||||
|
limit: int = Query(100, le=500),
|
||||||
|
offset: int = 0
|
||||||
|
):
|
||||||
|
"""Get messages for a conversation"""
|
||||||
|
async with await _db() as db:
|
||||||
|
cursor = await db.execute("""
|
||||||
|
SELECT message_uuid, role, content_text, timestamp, model,
|
||||||
|
input_tokens, output_tokens, has_tool_use, has_thinking
|
||||||
|
FROM messages
|
||||||
|
WHERE session_id = ?
|
||||||
|
ORDER BY timestamp
|
||||||
|
LIMIT ? OFFSET ?
|
||||||
|
""", (session_id, limit, offset))
|
||||||
|
rows = await cursor.fetchall()
|
||||||
|
|
||||||
|
messages = []
|
||||||
|
for r in rows:
|
||||||
|
msg = {
|
||||||
|
"uuid": r[0],
|
||||||
|
"role": r[1],
|
||||||
|
"content": r[2],
|
||||||
|
"timestamp": r[3],
|
||||||
|
"model": r[4],
|
||||||
|
"input_tokens": r[5],
|
||||||
|
"output_tokens": r[6],
|
||||||
|
"has_tool_use": bool(r[7]),
|
||||||
|
"has_thinking": bool(r[8]),
|
||||||
|
"tool_calls": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get tool calls for this message
|
||||||
|
if msg["has_tool_use"]:
|
||||||
|
cursor2 = await db.execute("""
|
||||||
|
SELECT tool_name, tool_input, tool_result, is_error
|
||||||
|
FROM tool_calls
|
||||||
|
WHERE message_uuid = ?
|
||||||
|
""", (r[0],))
|
||||||
|
tool_rows = await cursor2.fetchall()
|
||||||
|
msg["tool_calls"] = [{
|
||||||
|
"name": tr[0],
|
||||||
|
"input": tr[1],
|
||||||
|
"result": tr[2],
|
||||||
|
"is_error": bool(tr[3])
|
||||||
|
} for tr in tool_rows]
|
||||||
|
|
||||||
|
messages.append(msg)
|
||||||
|
|
||||||
|
return messages
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ def _patch_opc_db():
|
|||||||
def _make_task(title, description, status, priority, project_id,
|
def _make_task(title, description, status, priority, project_id,
|
||||||
assigned_to, assigned_type, tags, due_date):
|
assigned_to, assigned_type, tags, due_date):
|
||||||
tid = _next_id(_task_counter)
|
tid = _next_id(_task_counter)
|
||||||
now = datetime.utcnow().isoformat()
|
now = datetime.utcnow()
|
||||||
task = {
|
task = {
|
||||||
"id": tid,
|
"id": tid,
|
||||||
"title": title,
|
"title": title,
|
||||||
@@ -292,7 +292,7 @@ def _patch_opc_db():
|
|||||||
"created_at": now,
|
"created_at": now,
|
||||||
"updated_at": now,
|
"updated_at": now,
|
||||||
"completed_at": None,
|
"completed_at": None,
|
||||||
"due_date": due_date.isoformat() if due_date else None,
|
"due_date": due_date,
|
||||||
}
|
}
|
||||||
_tasks[tid] = task
|
_tasks[tid] = task
|
||||||
return task
|
return task
|
||||||
@@ -316,7 +316,7 @@ def _patch_opc_db():
|
|||||||
return _DictRow(task)
|
return _DictRow(task)
|
||||||
elif query_lower.startswith("insert into agent_executions"):
|
elif query_lower.startswith("insert into agent_executions"):
|
||||||
eid = _next_id(_exec_counter)
|
eid = _next_id(_exec_counter)
|
||||||
now = datetime.utcnow().isoformat()
|
now = datetime.utcnow()
|
||||||
exec_data = {
|
exec_data = {
|
||||||
"id": eid,
|
"id": eid,
|
||||||
"task_id": params[0] if len(params) > 0 else None,
|
"task_id": params[0] if len(params) > 0 else None,
|
||||||
@@ -343,6 +343,21 @@ def _patch_opc_db():
|
|||||||
if tid in _tasks:
|
if tid in _tasks:
|
||||||
return _DictRow(_tasks[tid])
|
return _DictRow(_tasks[tid])
|
||||||
return None
|
return None
|
||||||
|
elif query_lower.startswith("select * from agents where id ="):
|
||||||
|
agent_id = params[0] if params else None
|
||||||
|
agents_map = {
|
||||||
|
"pm": {"id": "pm", "name": "Project Manager", "role": "PM",
|
||||||
|
"description": "test", "capabilities": "[]", "system_prompt": "test",
|
||||||
|
"config": '{"approval_level":"auto"}', "enabled": True,
|
||||||
|
"created_at": datetime.utcnow().isoformat()},
|
||||||
|
"cto": {"id": "cto", "name": "CTO", "role": "CTO",
|
||||||
|
"description": "test", "capabilities": "[]", "system_prompt": "test",
|
||||||
|
"config": '{"approval_level":"cxo"}', "enabled": True,
|
||||||
|
"created_at": datetime.utcnow().isoformat()},
|
||||||
|
}
|
||||||
|
if agent_id in agents_map:
|
||||||
|
return _DictRow(agents_map[agent_id])
|
||||||
|
return None
|
||||||
elif query_lower.startswith("select * from agent_executions where id ="):
|
elif query_lower.startswith("select * from agent_executions where id ="):
|
||||||
eid = params[0] if params else None
|
eid = params[0] if params else None
|
||||||
if eid in _executions:
|
if eid in _executions:
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ def test_task_creator_tracked_in_metadata(test_app, admin_headers):
|
|||||||
|
|
||||||
# Check metadata contains created_by
|
# Check metadata contains created_by
|
||||||
assert "metadata" in task
|
assert "metadata" in task
|
||||||
assert task["metadata"]["created_by"] == "admin"
|
assert task["metadata"]["created_by"] == "testuser"
|
||||||
|
|
||||||
|
|
||||||
def test_cannot_view_others_task_details(test_app, admin_headers):
|
def test_cannot_view_others_task_details(test_app, admin_headers):
|
||||||
|
|||||||
@@ -1,11 +1,29 @@
|
|||||||
"""Integration tests for system router"""
|
"""Integration tests for system router"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
|
|
||||||
def test_system_stats_endpoint(test_app, admin_headers):
|
def test_system_stats_endpoint(test_app, admin_headers):
|
||||||
"""Test system stats endpoint returns expected format"""
|
"""Test system stats endpoint returns expected format"""
|
||||||
response = test_app.get("/api/system/stats", headers=admin_headers)
|
mock_disk = Mock()
|
||||||
|
mock_disk.total = 1000000000000
|
||||||
|
mock_disk.used = 500000000000
|
||||||
|
mock_disk.free = 500000000000
|
||||||
|
|
||||||
|
mock_mem = Mock()
|
||||||
|
mock_mem.total = 8000000000
|
||||||
|
mock_mem.available = 4000000000
|
||||||
|
mock_mem.used = 4000000000
|
||||||
|
mock_mem.percent = 50.0
|
||||||
|
|
||||||
|
with patch("shutil.disk_usage", return_value=mock_disk), \
|
||||||
|
patch("psutil.disk_partitions", return_value=[]), \
|
||||||
|
patch("psutil.virtual_memory", return_value=mock_mem), \
|
||||||
|
patch("psutil.cpu_percent", return_value=25.5), \
|
||||||
|
patch("psutil.getloadavg", return_value=(1.0, 0.5, 0.2)), \
|
||||||
|
patch("psutil.boot_time", return_value=1000000.0):
|
||||||
|
response = test_app.get("/api/system/stats", headers=admin_headers)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
data = response.json()
|
data = response.json()
|
||||||
assert "cpu_percent" in data
|
assert "cpu_percent" in data
|
||||||
|
|||||||
Reference in New Issue
Block a user