Live agent todo list: compact scrollable strip above the composer
CI / Gateway plugin tests (push) Successful in 5m5s
CI / Kotlin tests (android host + desktop) (push) Successful in 6m47s

Add a todo.update frame (server->app) carrying the agent's full current
todo list. The gateway emits it whenever the hermes todo tool completes
(the tool result is authoritative even for merge writes) and re-sends a
snapshot right after hello so a reconnecting device re-learns the plan.
Ephemeral: never outboxed.

The app renders it as a compact strip above the composer (max 3 lines,
the rest scrollable) mirroring the hermes desktop composer status stack:
pending = hollow ring, in_progress = spinner, completed = green check,
cancelled = struck through. It auto-scrolls to the current task whenever
the active task changes, and hides itself once the list is empty or fully
resolved.
This commit is contained in:
ARIA committed 2026-08-23 00:23:02 +02:00
1 parent 1ff2ef380c
commit 742916903b
10 files changed
+447 -3

No files matched your search

+32
View File
@@ -48,6 +48,9 @@ TYPE_TOOL_START = "tool.start"
TYPE_TOOL_PROGRESS = "tool.progress"
TYPE_TOOL_END = "tool.end"
# Agent todo list (live planning state; ephemeral, never outboxed)
TYPE_TODO_UPDATE = "todo.update"
# Intermediate assistant beat (M2)
TYPE_COMMENTARY = "commentary"
@@ -486,6 +489,35 @@ def tool_end(
)
# ---------------------------------------------------------------------------
# Todo-update frame (agent planning state)
# ---------------------------------------------------------------------------
def todo_update(
chat_id: str,
todos: list[dict[str, str]],
*,
thread_id: str | None = None,
) -> Frame:
"""The agent's current todo list for a chat/thread lane.
Emitted whenever the ``todo`` tool completes (the tool result is the
authoritative full list — it also covers ``merge`` writes, whose args
carry only the changed items) and, as a snapshot, when a device opens
its event stream. Ephemeral state: never outboxed, so a reconnecting
device learns the current list from the snapshot instead of a replay.
Each item is ``{id, content, status}`` with status one of
``pending | in_progress | completed | cancelled``.
"""
return Frame(
type=TYPE_TODO_UPDATE,
chat_id=chat_id,
thread_id=thread_id,
payload={"todos": todos},
)
# ---------------------------------------------------------------------------
# Commentary frame (M2)
# ---------------------------------------------------------------------------