The classic CLI dock above the status bar painted subagents and background processes only. A standing /goal (and whether it is active, parked or paused) was visible only as a "⊙ goal N/M" status-bar segment, and prompts waiting in /queue could not be seen at all while the agent worked. The dock now paints the goal's status line on its top row and a Queue block (count, first three previews, +N more) on its bottom rows, closest to the input they will be sent from; F7's summary line carries "goal active|parked| paused" and "N queued". Goal state comes only from the GoalManager the CLI already holds for the current session, so the 1 Hz dock refresh never opens state.db and never shows a previous session's goal after /new. /queue now dispatches inline while the agent runs, honouring its busy_policy="dispatch": typed mid-run it was queued as the raw command, so "/queue X" only re-enqueued X after the turn and /queue list|rm|edit could not inspect the queue until it had drained.
41 lines
1.6 KiB
Python
41 lines
1.6 KiB
Python
"""Goal + queued-prompt rows for the classic CLI live-work dock.
|
|
|
|
Sibling of ``cli_process_dock``: the dock paints an active ``/goal`` and the prompts waiting in
|
|
``/queue`` under the subagent and process blocks, so both are visible while the agent works
|
|
instead of only through ``/goal status`` and ``/queue list``.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
# Queued prompts drain one per turn; past this many the dock shows a ``+N more`` line.
|
|
QUEUE_ROWS = 3
|
|
|
|
|
|
def goal_line(cli) -> str:
|
|
"""``GoalManager.status_line()`` for an active or paused goal, else ``''``.
|
|
|
|
Reads only the manager the CLI already holds for its current session: the dock refreshes on
|
|
the spinner thread every second and must never be the thing that opens state.db.
|
|
"""
|
|
mgr = getattr(cli, "_goal_manager", None)
|
|
if mgr is None or mgr.session_id != getattr(cli, "session_id", None) or not mgr.has_goal():
|
|
return ""
|
|
return mgr.status_line()
|
|
|
|
|
|
def _prompt_text(item) -> str:
|
|
if isinstance(item, tuple): # (text, images) from an image-attached submit
|
|
item = item[0]
|
|
# TimelineNotification carries a compact title; voice/seeded sentinels wrap their text.
|
|
text = getattr(item, "display_text", None) or getattr(item, "text", None) or str(item)
|
|
return " ".join(str(text).split())
|
|
|
|
|
|
def queued_prompts(cli) -> list[str]:
|
|
"""One-line previews of the turns waiting in ``_pending_input``, oldest first."""
|
|
pending = getattr(cli, "_pending_input", None)
|
|
if pending is None:
|
|
return []
|
|
with pending.mutex:
|
|
items = list(pending.queue)
|
|
return [text for text in map(_prompt_text, items) if text]
|