b4fbd6fe9f
Deploy Site / deploy-vercel (push) Has been skipped
Deploy Site / deploy-docs (push) Has been skipped
Build Skills Index / build-index (push) Has been skipped
CI / Deny unrelated histories (push) Has been skipped
CI / Detect affected areas (push) Successful in 27m35s
CI / OSV scan (push) Failing after 4s
CI / Build&Test Docker image (push) Successful in 9s
CI / Supply-chain scan (push) Has been skipped
CI / Lint Docker scripts (push) Failing after 5m13s
CI / Check contributors (push) Failing after 12m8s
CI / Docs Site (push) Failing after 12m8s
CI / TypeScript (push) Failing after 12m8s
CI / Python lints (push) Failing after 12m9s
CI / Python tests (push) Failing after 12m9s
CI / Check uv.lock (push) Failing after 23m22s
CI / CI timing report (push) Has been cancelled
Build Skills Index / trigger-deploy (push) Has been cancelled
CI / All required checks pass (push) Has been cancelled
82 lines
2.8 KiB
Python
82 lines
2.8 KiB
Python
"""Map agent activity → a :class:`PetState`.
|
|
|
|
This is the one place the "what is the agent doing right now?" → "which
|
|
animation row?" decision lives. Each surface feeds it the signals it already
|
|
tracks:
|
|
|
|
- CLI — ``KawaiiSpinner`` waiting/thinking state + tool outcomes.
|
|
- TUI — gateway ``tool.start/complete`` + ``message.delta/complete`` events.
|
|
- Desktop — the ``$busy``/``$awaitingResponse``/tool-event nanostores
|
|
(re-implemented in TS, but mirroring this priority order).
|
|
|
|
Keeping the priority order here (and documenting it) lets the TypeScript
|
|
mirror stay faithful without a second design.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Iterable
|
|
from typing import Any
|
|
|
|
from agent.pet.constants import PetState
|
|
|
|
|
|
def todos_all_done(todos: Iterable[Any] | None) -> bool:
|
|
"""True iff there's ≥1 todo and every one is completed/cancelled.
|
|
|
|
The "celebrate" beat (``JUMP``) fires when a plan finishes; this mirrors
|
|
the TUI's ``isTodoDone`` so the trigger is defined once across surfaces.
|
|
Accepts dicts (``{"status": ...}``) or objects with a ``status`` attr.
|
|
"""
|
|
items = list(todos or [])
|
|
if not items:
|
|
return False
|
|
|
|
def _status(t: Any) -> Any:
|
|
return t.get("status") if isinstance(t, dict) else getattr(t, "status", None)
|
|
|
|
return all(_status(t) in ("completed", "cancelled") for t in items)
|
|
|
|
|
|
def derive_pet_state(
|
|
*,
|
|
busy: bool = False,
|
|
awaiting_input: bool = False,
|
|
error: bool = False,
|
|
celebrate: bool = False,
|
|
just_completed: bool = False,
|
|
tool_running: bool = False,
|
|
reasoning: bool = False,
|
|
) -> PetState:
|
|
"""Resolve the animation state from coarse activity signals.
|
|
|
|
Priority (highest first) — only one row can show at a time, so the most
|
|
salient signal wins:
|
|
|
|
1. ``error`` → ``FAILED`` (a tool/turn just failed)
|
|
2. ``celebrate`` → ``JUMP`` (explicit success beat, e.g. todos done)
|
|
3. ``just_completed`` → ``WAVE`` (turn finished cleanly / greeting)
|
|
4. ``awaiting_input`` → ``WAITING`` (blocked on the user — a clarify/approval
|
|
prompt is open; this outranks the in-flight signals below because the turn
|
|
is paused on *you*, even though a tool is technically mid-call)
|
|
5. ``tool_running`` → ``RUN`` (a tool is executing)
|
|
6. ``reasoning`` → ``REVIEW`` (model is thinking / reading)
|
|
7. ``busy`` → ``RUN`` (turn in flight, unspecified work)
|
|
8. otherwise → ``IDLE``
|
|
"""
|
|
if error:
|
|
return PetState.FAILED
|
|
if celebrate:
|
|
return PetState.JUMP
|
|
if just_completed:
|
|
return PetState.WAVE
|
|
if awaiting_input:
|
|
return PetState.WAITING
|
|
if tool_running:
|
|
return PetState.RUN
|
|
if reasoning:
|
|
return PetState.REVIEW
|
|
if busy:
|
|
return PetState.RUN
|
|
return PetState.IDLE
|