feat: queue persistence & recovery, queue management, streaming model/compact, collapsible panel - #335
Open
windli2018 wants to merge 5 commits into
Open
Conversation
pi keeps steer/follow-up queues in memory only; a server restart silently loses them. Mirror every queue mutation to a per-session sidecar (<session>.jsonl.queue.json, atomic tmp+rename) and surface orphaned entries after a restart through a manual recovery dialog — nothing is ever auto-delivered. - lib/queue-store.ts: sidecar persistence + queue entry types - lib/rpc-manager.ts: queueMirror/queueRecovery/pendingQueueHints, queue_update reconciliation, resolve_recovery (keep/discard/continueRun via agent.continue), export_queue, import_queue commands, get_state.pendingRecovery - api/sessions/[id]/queue-recovery: lightweight sidecar read that never creates an AgentSession; live wrapper memory wins when present - QueueRecoveryDialog: checklist + re-queue / re-queue & continue / discard / export .md/.json / import / dismiss; responsive layout (desktop single row, mobile 2x2 grid + short description) - ChatInput queued banner: collapse/expand with first-message preview, export / import with inline success/error feedback, "queued" label on its own row on small screens - session DELETE removes the sidecar - next.config.ts: dev server uses its own build dir (.next-dev) so dev and production can run on separate ports simultaneously
- Queue: delete single item, reorder (drag/arrow/touch), recall-to-edit, requeue-at-position, import when empty, immediate display after import - Model: allow switching while streaming (applies next turn) with badge - Compact: queue when agent running, auto-run on idle, cancel support - Bottom panel: tri-state collapse (full/queueHidden/minimal) with swipe - Recovery: consistency check (skip sidecar restore if pi queue non-empty) - Collapsible queue summary row is now clickable to expand - i18n: add missing keys, replace hardcoded Chinese tooltips
- Importing a queue file now stages entries as pending recovery, popping the recovery dialog so the user chooses re-queue / re-queue & continue / discard (same flow as crash recovery) instead of auto-executing. - Fix "re-queue & continue" not executing: runAgentContinue captured queueMirror AFTER clearQueue (whose queue_update emptied it), so only the first entry ran. Now capture entries before clearQueue and prompt each in series — pi's steer/followUp queues aren't drained by a prompt-bootstrapped loop, so sequential prompts are the robust way to run a batch. - Recovery dialog distinguishes import vs crash-recovery copy (mode prop). - Minimal collapsed bar's "Queued · N" label is now clickable to expand. - Fix reversed swipe-hint chevron (collapse/expand arrows were swapped). - Switching back to the current run's model cancels the pending switch.
…reply (message_end)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
pi keeps steer/follow-up queues in memory only — a server restart silently loses them. This PR mirrors every queue mutation to a per-session sidecar (
<session>.jsonl.queue.json) and surfaces orphaned entries after a restart through a manual recovery dialog. Nothing is ever auto-delivered: the user decides per-entry to re-queue, re-queue & continue, discard, or export.On top of crash recovery, this PR adds a full set of queue management operations (delete/reorder/recall/requeue), imports that pop the same recovery dialog so the user chooses how to run an imported queue, lets users switch models and compact while the agent is running, and ships a collapsible bottom panel for small screens.
How — crash recovery
lib/queue-store.ts(new): atomic sidecar persistence (tmp+rename) + entry types.lib/rpc-manager.ts:queueMirror(live queue w/ images) /queueRecovery(orphans) /pendingQueueHints(FIFO image hints — pi reports queued items by text only and template expansion can rewrite text).queue_updateevents reconcile the mirror. New commands:resolve_recovery {keep, discard, continueRun}— re-queues viainner.steer()/followUp().continueRunruns the whole batch: pi has no public API to resume an idle agent's queue (agent.continue()only works inside an active_runAgentPromptloop, and the internal continuation stalls), sobootstrapQueuedRunprompts each kept entry in series — eachAgentSession.prompt()awaits its_runAgentPrompt, so entries run one turn after another. (Entries are captured beforeclearQueue, whosequeue_updatewould otherwise empty the mirror.)stage_recovery {entries}— stage entries as pending recovery (NOT into the live queue) so the recovery dialog pops up; used by import.export_queue,import_queueget_state.pendingRecoveryapi/sessions/[id]/queue-recovery(new): lightweight sidecar read that never creates an AgentSession; when a wrapper is alive its in-memory recovery list wins.QueueRecoveryDialog(new): checklist with re-queue / re-queue & continue / discard / export.md/.json/ import / dismiss. Amodeprop ("recovery"|"import") swaps the title/description so an import shows import copy instead of crash-recovery copy. Responsive: desktop single-row footer, mobile 2×2 grid + shorter description; matches the project's existing modal conventions (fixed overlay,var(--bg), radius 10).How — queue operations
pi has no single-dequeue / reorder / insert API, so all edits are implemented as clear-all + re-enqueue in the target order (image hints preserved FIFO), mirroring pi's own
clearQueue()semantics (allowed mid-run since the loop only pulls entries between turns). New commands:move_queue {kind, fromIndex, toIndex}— reorder within a queue.recall_queue_item {kind, index}— pull one entry back to the input box for editing (returns its text + images).requeue_at {kind, index, text, images}— insert an edited message back at its original position. Sending from the input while arecalledRefis active re-queues instead of prompting.remove_queue_item {kind, index}— delete a single entry.import_queuereturns the queue snapshot (steering/followUp) so the UI updates immediately without a reload.UI: each queued row has ↑/↓ (reorder), ↩ (recall to edit), 🗑 (delete), plus HTML5 drag on desktop and touch drag on mobile (
pointer: coarseauto-disables native draggable). The queue banner collapses to a first-message preview (… +N) which is clickable to expand; in the minimal collapsed state the "Queued · N" label is also clickable to expand. Export/import buttons are always reachable (incl. when the queue is empty). Importing a file stages entries viastage_recoveryand pops the recovery dialog — the user then picks re-queue / re-queue & continue / discard, rather than the queue auto-executing.How — model switch & compact while running
setModel()only overwritesagent.state.model, so the in-flight request still finishes with the old model and the next turn picks up the new one — i.e. it already "applies next turn". This PR simply removes the UIdisabled={isStreaming}and adds an amber ⏱ "next turn" badge on the model button that auto-clears when the run ends. Switching back to the model the current run started with cancels the pending switch (badge clears) — the run model is captured on theagentRunningtransition and intentionally not overwritten by mid-run switches.compact()rewrites context + the session file, so running it mid-loop is unsafe.handleCompactqueues apendingCompactwhenagentRunningis true (covers streaming and tool-call execution, whereisStreamingis briefly false), and an effect drains it once the agent is truly idle (prompt_donealone is not enough — retries / auto-compaction / queued follow-ups can extend a run). The button shows a three-state cycle: idle → amber "Queued" (click to cancel) → red "Compacting".How — small-screen bottom panel
ChatInput gains a tri-state
bottomMode(full→queueHidden→minimal, persisted inlocalStorage): a Windows-10-style vertical bar button in the toolbar, plus swipe gestures (swipe down to collapse one step, up to expand) with a hint overlay (chevron points in the swipe direction). Queue-row touch events are isolated (stopPropagation) so dragging to reorder never triggers mode switching.How — recovery safety
When a wrapper is (re)created with a non-empty pi queue, sidecar restore is skipped and the mirror is rebuilt from the live queue — preventing double-enqueue if the architecture ever changes so pi-web and pi can restart independently.
Why not auto-restore?
Auto-replaying queued messages after a restart risks double-delivery (messages already delivered but still mirrored) and stale-queue resurrection. Manual recovery keeps it explicit. The same reasoning applies to imports: staging them through the dialog lets the user decide whether to run, rather than auto-executing.
Notes
_steeringMessages(falsycontentText("")) — they may surface as already-delivered in recovery; users discard manually (documented in AGENTS.md).next.config.ts: dev server gets its own build dir (.next-dev) so dev and production can run on separate ports simultaneously.tsconfig.json/.gitignoreupdated for it.