Runtime gateway sessions (Claude CLI, Codex, GPT, Ollama) are fetched via fetch_recent_events for all org gateways,
This commit is contained in:
parent
ea62e387a4
commit
90a4abde30
|
|
@ -7,7 +7,7 @@ import json
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from datetime import UTC, datetime, timedelta
|
from datetime import UTC, datetime, timedelta
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
from uuid import UUID
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
|
||||||
from sqlalchemy import and_, asc, desc, func, or_
|
from sqlalchemy import and_, asc, desc, func, or_
|
||||||
|
|
@ -21,6 +21,7 @@ from app.db.session import async_session_maker, get_session
|
||||||
from app.models.activity_events import ActivityEvent
|
from app.models.activity_events import ActivityEvent
|
||||||
from app.models.agents import Agent
|
from app.models.agents import Agent
|
||||||
from app.models.boards import Board
|
from app.models.boards import Board
|
||||||
|
from app.models.gateways import Gateway
|
||||||
from app.models.tasks import Task
|
from app.models.tasks import Task
|
||||||
from app.schemas.activity_events import ActivityEventRead, ActivityTaskCommentFeedItemRead, ActivityTickerItem
|
from app.schemas.activity_events import ActivityEventRead, ActivityTaskCommentFeedItemRead, ActivityTickerItem
|
||||||
from app.schemas.pagination import DefaultLimitOffsetPage
|
from app.schemas.pagination import DefaultLimitOffsetPage
|
||||||
|
|
@ -249,6 +250,68 @@ def _ticker_source(event: ActivityEvent, agent: Agent | None) -> str:
|
||||||
return " ".join(p.capitalize() for p in parts)
|
return " ".join(p.capitalize() for p in parts)
|
||||||
|
|
||||||
|
|
||||||
|
def _runtime_source(session_key: str, session_label: str | None, model: str | None) -> str:
|
||||||
|
if session_label:
|
||||||
|
return session_label
|
||||||
|
if model:
|
||||||
|
m = model.lower()
|
||||||
|
if "claude" in m:
|
||||||
|
return "Claude"
|
||||||
|
if "codex" in m:
|
||||||
|
return "Codex"
|
||||||
|
if "gpt" in m or "openai" in m:
|
||||||
|
return "GPT"
|
||||||
|
if "gemini" in m or "google" in m:
|
||||||
|
return "Gemini"
|
||||||
|
return model
|
||||||
|
parts = session_key.split(":")
|
||||||
|
if len(parts) >= 2:
|
||||||
|
return parts[1].replace("-", " ").replace("_", " ").title()
|
||||||
|
return "Session"
|
||||||
|
|
||||||
|
|
||||||
|
async def _fetch_runtime_ticker_items(
|
||||||
|
gateways: list[Gateway],
|
||||||
|
cutoff: datetime,
|
||||||
|
) -> list[ActivityTickerItem]:
|
||||||
|
from app.services.openclaw.gateway_rpc import GatewayConfig # noqa: PLC0415
|
||||||
|
from app.services.openclaw.runtime_activity import fetch_recent_events # noqa: PLC0415
|
||||||
|
|
||||||
|
items: list[ActivityTickerItem] = []
|
||||||
|
for gw in gateways:
|
||||||
|
try:
|
||||||
|
config = GatewayConfig(
|
||||||
|
url=gw.url,
|
||||||
|
token=gw.token,
|
||||||
|
allow_insecure_tls=gw.allow_insecure_tls,
|
||||||
|
disable_device_pairing=gw.disable_device_pairing,
|
||||||
|
)
|
||||||
|
events = await asyncio.wait_for(
|
||||||
|
fetch_recent_events(config, max_sessions=10, history_limit=15),
|
||||||
|
timeout=3.0,
|
||||||
|
)
|
||||||
|
for ev in events:
|
||||||
|
if ev.role != "assistant":
|
||||||
|
continue
|
||||||
|
msg = ev.content_preview.strip()
|
||||||
|
if not msg:
|
||||||
|
continue
|
||||||
|
ts = ev.timestamp
|
||||||
|
if ts is None or ts < cutoff:
|
||||||
|
continue
|
||||||
|
items.append(
|
||||||
|
ActivityTickerItem(
|
||||||
|
id=uuid4(),
|
||||||
|
source=_runtime_source(ev.session_key, ev.session_label, ev.model),
|
||||||
|
message=msg[:200],
|
||||||
|
created_at=ts,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
pass
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
@router.get("/ticker", response_model=list[ActivityTickerItem])
|
@router.get("/ticker", response_model=list[ActivityTickerItem])
|
||||||
async def get_activity_ticker(
|
async def get_activity_ticker(
|
||||||
limit: int = Query(default=20, ge=1, le=50),
|
limit: int = Query(default=20, ge=1, le=50),
|
||||||
|
|
@ -265,6 +328,7 @@ async def get_activity_ticker(
|
||||||
.outerjoin(Task, col(ActivityEvent.task_id) == col(Task.id))
|
.outerjoin(Task, col(ActivityEvent.task_id) == col(Task.id))
|
||||||
.where(func.length(func.trim(col(ActivityEvent.message))) > 0)
|
.where(func.length(func.trim(col(ActivityEvent.message))) > 0)
|
||||||
.where(col(ActivityEvent.created_at) >= cutoff)
|
.where(col(ActivityEvent.created_at) >= cutoff)
|
||||||
|
.where(col(ActivityEvent.event_type) != "agent.heartbeat")
|
||||||
.order_by(desc(col(ActivityEvent.created_at)))
|
.order_by(desc(col(ActivityEvent.created_at)))
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
)
|
)
|
||||||
|
|
@ -283,14 +347,14 @@ async def get_activity_ticker(
|
||||||
statement = statement.where(col(ActivityEvent.id).is_(None))
|
statement = statement.where(col(ActivityEvent.id).is_(None))
|
||||||
|
|
||||||
rows = (await session.exec(statement)).all()
|
rows = (await session.exec(statement)).all()
|
||||||
items: list[ActivityTickerItem] = []
|
db_items: list[ActivityTickerItem] = []
|
||||||
for row in rows:
|
for row in rows:
|
||||||
event: ActivityEvent = row[0]
|
event: ActivityEvent = row[0]
|
||||||
agent: Agent | None = row[1]
|
agent: Agent | None = row[1]
|
||||||
msg = (event.message or "").strip()
|
msg = (event.message or "").strip()
|
||||||
if not msg:
|
if not msg:
|
||||||
continue
|
continue
|
||||||
items.append(
|
db_items.append(
|
||||||
ActivityTickerItem(
|
ActivityTickerItem(
|
||||||
id=event.id,
|
id=event.id,
|
||||||
source=_ticker_source(event, agent),
|
source=_ticker_source(event, agent),
|
||||||
|
|
@ -298,7 +362,15 @@ async def get_activity_ticker(
|
||||||
created_at=event.created_at,
|
created_at=event.created_at,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return items
|
|
||||||
|
gw_rows = list((await session.exec(
|
||||||
|
select(Gateway).where(col(Gateway.organization_id) == ctx.organization.id)
|
||||||
|
)).all())
|
||||||
|
runtime_items = await _fetch_runtime_ticker_items(gw_rows, cutoff)
|
||||||
|
|
||||||
|
all_items = db_items + runtime_items
|
||||||
|
all_items.sort(key=lambda x: x.created_at, reverse=True)
|
||||||
|
return all_items[:limit]
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=DefaultLimitOffsetPage[ActivityEventRead])
|
@router.get("", response_model=DefaultLimitOffsetPage[ActivityEventRead])
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ interface TickerItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
function fmtRelative(isoString: string): string {
|
function fmtRelative(isoString: string): string {
|
||||||
const diffMs = Date.now() - new Date(isoString).getTime();
|
const utc = isoString.endsWith("Z") || isoString.includes("+") ? isoString : isoString + "Z";
|
||||||
|
const diffMs = Date.now() - new Date(utc).getTime();
|
||||||
const s = Math.round(diffMs / 1000);
|
const s = Math.round(diffMs / 1000);
|
||||||
if (s < 60) return `${s}s ago`;
|
if (s < 60) return `${s}s ago`;
|
||||||
const m = Math.floor(s / 60);
|
const m = Math.floor(s / 60);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue