Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
180 lines
5.7 KiB
Python
180 lines
5.7 KiB
Python
"""Office Status API - Star Office style visualization for Jarvis agents."""
|
|
|
|
from datetime import datetime, timedelta
|
|
from typing import Literal
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel
|
|
|
|
router = APIRouter(prefix="/api/office", tags=["office"])
|
|
|
|
# ============================================================================
|
|
# State Definitions (mapped to spaceship areas)
|
|
# ============================================================================
|
|
# idle → Rest Bay (breakroom)
|
|
# writing/researching/executing → Command Console (writing)
|
|
# syncing → Server Room (syncing)
|
|
# error → Repair Bay (error)
|
|
|
|
SHIP_AREAS = {
|
|
"breakroom": {"x": 200, "y": 300}, # Rest Bay - bottom left
|
|
"writing": {"x": 640, "y": 200}, # Command Console - center top
|
|
"server": {"x": 640, "y": 400}, # Server Room - center bottom
|
|
"error": {"x": 1000, "y": 300}, # Repair Bay - right side
|
|
}
|
|
|
|
STATES = {
|
|
"idle": {"name": "待命", "area": "breakroom"},
|
|
"writing": {"name": "执行中", "area": "writing"},
|
|
"researching": {"name": "研究中", "area": "writing"},
|
|
"executing": {"name": "执行中", "area": "writing"},
|
|
"syncing": {"name": "同步中", "area": "server"},
|
|
"error": {"name": "故障中", "area": "error"},
|
|
}
|
|
|
|
|
|
# ============================================================================
|
|
# Data Models
|
|
# ============================================================================
|
|
class AgentState(BaseModel):
|
|
agent_id: str
|
|
name: str
|
|
state: Literal["idle", "writing", "researching", "executing", "syncing", "error"]
|
|
detail: str | None = None
|
|
area: str | None = None
|
|
is_main: bool = False
|
|
auth_status: str = "approved" # approved, pending, rejected, offline
|
|
|
|
|
|
class SetStateRequest(BaseModel):
|
|
state: str
|
|
detail: str | None = None
|
|
|
|
|
|
class OfficeStatus(BaseModel):
|
|
state: str
|
|
detail: str | None = None
|
|
agent_name: str
|
|
timestamp: str
|
|
|
|
|
|
class OfficeMemo(BaseModel):
|
|
success: bool
|
|
date: str
|
|
memo: str
|
|
|
|
|
|
# ============================================================================
|
|
# In-Memory State (in production, this would come from Jarvis's agent state)
|
|
# ============================================================================
|
|
_current_state: dict = {
|
|
"agent_id": "jarvis-main",
|
|
"name": "JARVIS",
|
|
"state": "idle",
|
|
"detail": "战舰启动中...",
|
|
"area": "breakroom",
|
|
"is_main": True,
|
|
"auth_status": "approved",
|
|
}
|
|
|
|
|
|
def normalize_state(state: str | None) -> str:
|
|
"""Normalize various state names to our canonical states."""
|
|
if not state:
|
|
return "idle"
|
|
state = state.lower().strip()
|
|
if state in ("working", "run", "running"):
|
|
return "writing"
|
|
if state in ("sync", "syncing"):
|
|
return "syncing"
|
|
if state in ("research", "researching"):
|
|
return "researching"
|
|
if state in ("execute", "executing"):
|
|
return "executing"
|
|
if state == "error":
|
|
return "error"
|
|
return "idle"
|
|
|
|
|
|
def get_state_info(state: str) -> dict:
|
|
"""Get state info including area mapping."""
|
|
return STATES.get(state, STATES["idle"])
|
|
|
|
|
|
# ============================================================================
|
|
# API Endpoints
|
|
# ============================================================================
|
|
@router.get("/status", response_model=OfficeStatus)
|
|
async def get_status():
|
|
"""Get current agent status."""
|
|
state_info = get_state_info(_current_state["state"])
|
|
return OfficeStatus(
|
|
state=_current_state["state"],
|
|
detail=_current_state.get("detail"),
|
|
agent_name=_current_state["name"],
|
|
timestamp=datetime.now().isoformat(),
|
|
)
|
|
|
|
|
|
@router.get("/yesterday-memo", response_model=OfficeMemo)
|
|
async def get_yesterday_memo():
|
|
"""Return a lightweight public memo for the Star Office viewer."""
|
|
target_date = (datetime.now() - timedelta(days=1)).date().isoformat()
|
|
detail = (_current_state.get("detail") or "No detailed log was recorded.").strip()
|
|
memo = (
|
|
"Yesterday summary\n"
|
|
f"- Last known state: {_current_state['state']}\n"
|
|
f"- Detail: {detail}\n"
|
|
"- Next step: open the command surface and continue from the current work thread."
|
|
)
|
|
return OfficeMemo(success=True, date=target_date, memo=memo)
|
|
|
|
|
|
@router.post("/set_state")
|
|
async def set_state(req: SetStateRequest):
|
|
"""Set the current agent state."""
|
|
normalized = normalize_state(req.state)
|
|
state_info = get_state_info(normalized)
|
|
|
|
_current_state["state"] = normalized
|
|
_current_state["detail"] = req.detail or ""
|
|
_current_state["area"] = state_info["area"]
|
|
|
|
return {
|
|
"success": True,
|
|
"state": normalized,
|
|
"area": state_info["area"],
|
|
"detail": _current_state["detail"],
|
|
}
|
|
|
|
|
|
@router.get("/agents")
|
|
async def get_agents():
|
|
"""Get all agents in the office (for multi-agent support)."""
|
|
# For now, return just the main agent
|
|
# In full implementation, this would query Jarvis's agent registry
|
|
state_info = get_state_info(_current_state["state"])
|
|
return [
|
|
{
|
|
"agentId": _current_state["agent_id"],
|
|
"name": _current_state["name"],
|
|
"state": _current_state["state"],
|
|
"detail": _current_state.get("detail", ""),
|
|
"area": state_info["area"],
|
|
"isMain": _current_state.get("is_main", True),
|
|
"authStatus": _current_state.get("auth_status", "approved"),
|
|
"updated_at": datetime.now().isoformat(),
|
|
}
|
|
]
|
|
|
|
|
|
@router.get("/areas")
|
|
async def get_areas():
|
|
"""Get all spaceship areas with coordinates."""
|
|
return SHIP_AREAS
|
|
|
|
|
|
@router.get("/health")
|
|
async def health():
|
|
"""Health check."""
|
|
return {"status": "ok", "service": "office"}
|