You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When PM dispatches one or more subagents (via dispatch_specialist / dispatch_parallel, or dispatch_lens_review), the user sees nothing in the parent UI while children run. Long children mean minutes of silent spinner — see screenshot context in [this session's design discussion].
We already compute the data we'd need to show: extension/src/progress.ts:17-40 defines RunningState (role, turns, lastToolName, lastText, usage, elapsed) and spawn.ts updates it on every message_end event via ingestEvent(). After the async pivot (#19/#20), the onProgress callback that used to push this into Pi's onUpdate tool-block is no longer wired — the state is computed and discarded.
Pi's extension API exposes the right primitive: ctx.ui.setStatus(id, text) — a persistent footer status entry, addressable by id, theme-aware. Reference: extension/node_modules/@earendil-works/pi-coding-agent/examples/extensions/status-line.ts:15-31.
Design
A single "dispatch deck" status entry, rendered as multi-line text we own end-to-end. One key ("ensemble:deck") — we control the layout, so ordering and overflow are ours.
Per-row format
⏳ developer · 2m14s · bash (#7) — Running test suite in worktree-A
Icon: ⏳ running, ✓ done, ✗ failed
Role label (with optional tag for lens-review children, e.g. code-review[security])
Elapsed (e.g. 47s, 2m14s)
Last tool name + tool-use count
Truncated lastText (≤60 chars, newlines stripped)
Ordering: insertion order, stable
First-dispatched-first-displayed. Once a row exists, it stays at that index until removed. Backed by Map<jobId, RunningState> iterated in insertion order (JS Maps preserve this natively).
Linger: 0s on completion
On child completion (success OR failure), the row drops immediately. Persistent record lives in lifecycle scrollback entries (separate issue) and in PM's reaction text.
Problem
When PM dispatches one or more subagents (via
dispatch_specialist/dispatch_parallel, ordispatch_lens_review), the user sees nothing in the parent UI while children run. Long children mean minutes of silent spinner — see screenshot context in [this session's design discussion].We already compute the data we'd need to show:
extension/src/progress.ts:17-40definesRunningState(role, turns, lastToolName, lastText, usage, elapsed) andspawn.tsupdates it on everymessage_endevent viaingestEvent(). After the async pivot (#19/#20), theonProgresscallback that used to push this into Pi'sonUpdatetool-block is no longer wired — the state is computed and discarded.Pi's extension API exposes the right primitive:
ctx.ui.setStatus(id, text)— a persistent footer status entry, addressable by id, theme-aware. Reference:extension/node_modules/@earendil-works/pi-coding-agent/examples/extensions/status-line.ts:15-31.Design
A single "dispatch deck" status entry, rendered as multi-line text we own end-to-end. One key (
"ensemble:deck") — we control the layout, so ordering and overflow are ours.Per-row format
⏳running,✓done,✗failedcode-review[security])47s,2m14s)lastText(≤60 chars, newlines stripped)Ordering: insertion order, stable
First-dispatched-first-displayed. Once a row exists, it stays at that index until removed. Backed by
Map<jobId, RunningState>iterated in insertion order (JS Maps preserve this natively).Linger: 0s on completion
On child completion (success OR failure), the row drops immediately. Persistent record lives in lifecycle scrollback entries (separate issue) and in PM's reaction text.
Overflow: 4 rows + summary
Picks the 4 most-recently-active.
Sources
All three feed the same deck via existing
onProgress(state)emissions:dispatch_specialist(single async) —extension/src/spawn.ts:328,398dispatch_parallel(multi async) — sameonProgresspathdispatch_lens_review(6 sync lens children) —extension/src/lens-review.ts:247Opt-out
PI_ENSEMBLE_QUIET_STATUS=1disables the deck entirely.Acceptance criteria
extension/src/dispatch-deck.ts(~120 LoC); exportsupdate(jobId, state),clear(jobId); capturesExtensionContextviasession_startlistenerspawn.tsandlens-review.tsboth feed the deck via their existingonProgresscallbacks (no new event plumbing — just route to the new module)PI_ENSEMBLE_QUIET_STATUS=1opt-out honouredsmoke-tests/test-dispatch-deck.ts) covers: render single child, render parallel batch, ordering stability under interleaved updates, overflow rendering, opt-out env vartest-progress-live.tsor newtest-dispatch-deck-live.ts) — footer renders during real parallel dispatchOut of scope (other issues)
dispatch_peektool (research: PM peek-inside-running-subagent (no transcript bloat) #21) — PM-callable query, builds on the same state maponUpdatefromdispatch_lens_review(separate issue) — once the deck is the single live surfaceReferences
extension/src/progress.ts:17-40onProgressemit sites:extension/src/spawn.ts:328,398,extension/src/lens-review.ts:247ctx.ui.setStatus()— seeexamples/extensions/status-line.ts:15-31extension/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts:780+dispatch_statusinextension/src/dispatch-status.ts