diff --git a/engine/hermes_client.py b/engine/hermes_client.py index 301e252..e5e7f22 100644 --- a/engine/hermes_client.py +++ b/engine/hermes_client.py @@ -121,6 +121,10 @@ class HermesClient: """Returns {name, path, size, mime_type, data_url} (base64 data URL).""" return await self._json("GET", "/api/files/read", params={"path": path}) + async def task_log(self, task_id: str) -> str: + data = await self._json("GET", f"/api/plugins/kanban/tasks/{task_id}/log") + return data.get("content") or "" + # ------------------------------------------------------------ kanban (= work dispatch) async def create_task(self, title: str, *, body: str, assignee: str, tenant: str, diff --git a/engine/main.py b/engine/main.py index a4dc8bd..6705a10 100644 --- a/engine/main.py +++ b/engine/main.py @@ -422,6 +422,25 @@ async def _set_company_state(slug: str, expect: str, new: str, event: str) -> di return {"slug": slug, "lifecycle_state": new} +@app.get("/companies/{slug}/work-items/{item_id}/log") +async def work_item_log(slug: str, item_id: UUID, tail: int = 6000) -> Response: + """Live worker log (incl. reasoning) for a dispatched work item.""" + item = await db.fetch_one( + "SELECT wi.* FROM work_item wi JOIN company c ON c.id = wi.company_id " + "WHERE wi.id = %s AND c.slug = %s", item_id, slug) + if not item: + raise HTTPException(404, "no such work item") + if not item["hermes_task_id"]: + return Response("(not dispatched yet — no log)", media_type="text/plain") + try: + content = await hermes.task_log(item["hermes_task_id"]) + except HermesError as e: + raise HTTPException(502, f"log fetch failed: {e}") + header = f"work item: {item['title']}\nstatus: {item['status']}\n{'='*60}\n" + return Response(header + content[-max(tail, 500):], + media_type="text/plain; charset=utf-8") + + # ---------------------------------------------------------------- workspace files (demos) diff --git a/engine/static/index.html b/engine/static/index.html index f6dee4c..7f96c59 100644 --- a/engine/static/index.html +++ b/engine/static/index.html @@ -105,7 +105,9 @@ async function refresh() { const rows = items.filter(w => w.type !== 'objective').map(w => { const answer = w.status === 'blocked' ? ` ` : ''; - return `