Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/apps/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ def console_batches() -> FileResponse:
return FileResponse(WEB_DIR / "index.html")


@app.get("/console/batches/{batch_id}")
def console_batch_detail(batch_id: str) -> FileResponse:
return FileResponse(WEB_DIR / "batch-detail.html")


@app.get("/console/runs/{run_id}")
def console_run_detail(run_id: str) -> FileResponse:
return FileResponse(WEB_DIR / "run-detail.html")


@app.on_event("startup")
def bootstrap_defaults() -> None:
ensure_builtin_agent_roles()
77 changes: 75 additions & 2 deletions src/apps/api/routers/runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@
from sqlalchemy.orm import Session

from src.apps.api.deps import get_db
from src.packages.core.db.models import ExecutionRunORM, TaskORM
from src.packages.core.schemas import ExecutionRunRead
from src.packages.core.db.models import AgentRoleORM, AssignmentORM, EventLogORM, ExecutionRunORM, TaskORM
from src.packages.core.schemas import (
ExecutionRunRead,
RunDetailRead,
RunDetailTaskRead,
RunRetryHistoryItemRead,
RunRoutingRead,
TaskEventRead,
)

router = APIRouter(tags=["runs"])

Expand All @@ -19,6 +26,72 @@ def get_run(run_id: str, db: Session = Depends(get_db)) -> ExecutionRunRead:
return ExecutionRunRead.model_validate(run)


@router.get("/runs/{run_id}/detail", response_model=RunDetailRead)
def get_run_detail(run_id: str, db: Session = Depends(get_db)) -> RunDetailRead:
run = db.get(ExecutionRunORM, run_id)
if run is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Execution run not found")

task = db.get(TaskORM, run.task_id)
if task is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Task not found")

assignment = db.scalars(
select(AssignmentORM)
.where(AssignmentORM.task_id == task.id)
.order_by(AssignmentORM.assigned_at.desc(), AssignmentORM.id.desc())
).first()

agent_role_name: str | None = task.assigned_agent_role
agent_role_id: str | None = assignment.agent_role_id if assignment is not None else None
if agent_role_id:
agent_role = db.get(AgentRoleORM, agent_role_id)
if agent_role is not None:
agent_role_name = agent_role.role_name

runs = db.scalars(
select(ExecutionRunORM)
.where(ExecutionRunORM.task_id == task.id)
.order_by(ExecutionRunORM.started_at.desc(), ExecutionRunORM.id.desc())
).all()
events = db.scalars(
select(EventLogORM)
.where(EventLogORM.task_id == task.id)
.order_by(EventLogORM.created_at.asc(), EventLogORM.id.asc())
).all()

return RunDetailRead(
run=ExecutionRunRead.model_validate(run),
task=RunDetailTaskRead(
task_id=task.id,
title=task.title,
task_type=task.task_type,
status=task.status,
assigned_agent_role=task.assigned_agent_role,
retry_count=task.retry_count,
batch_id=task.batch_id,
),
routing=RunRoutingRead(
routing_reason=assignment.routing_reason if assignment is not None else None,
agent_role_id=agent_role_id,
agent_role_name=agent_role_name,
),
retry_history=[
RunRetryHistoryItemRead(
run_id=item.id,
run_status=item.run_status,
started_at=item.started_at,
finished_at=item.finished_at,
latency_ms=item.latency_ms,
error_message=item.error_message,
is_current=item.id == run.id,
)
for item in runs
],
events=[TaskEventRead.model_validate(event) for event in events],
)


@router.get("/tasks/{task_id}/runs", response_model=list[ExecutionRunRead])
def list_task_runs(task_id: str, db: Session = Depends(get_db)) -> list[ExecutionRunRead]:
task = db.get(TaskORM, task_id)
Expand Down
1 change: 1 addition & 0 deletions src/apps/api/routers/task_batches.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ def get_task_batch_summary(batch_id: str, db: Session = Depends(get_db)) -> Task
title=task.title,
task_type=task.task_type,
status=task.status,
dependency_ids=task.dependency_ids,
assigned_agent_role=task.assigned_agent_role,
latest_run_id=latest_runs.get(task.id).id if latest_runs.get(task.id) is not None else None,
latest_run_status=latest_runs.get(task.id).run_status if latest_runs.get(task.id) is not None else None,
Expand Down
3 changes: 3 additions & 0 deletions src/apps/web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ function renderBatches(items) {
<p><strong>Created:</strong> ${formatDate(item.created_at)}</p>
<p><strong>Updated:</strong> ${formatDate(item.updated_at)}</p>
</div>
<div class="card-actions">
<a class="detail-link" href="/console/batches/${item.batch_id}">View detail</a>
</div>
</article>
`,
)
Expand Down
Loading
Loading