feat: queue persistence & recovery, queue management, streaming model/compact, collapsible panel - #335
Open
windli2018 wants to merge 3 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
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), 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();continueRuncallsagent.continue()directly (AgentSession has no public continue; events/persistence still flow via its subscriptions)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. 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_queuenow returns 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. Export/import buttons are always reachable (incl. when the queue is empty) so history can be re-imported at any time, with inline green/red feedback rendered outside the banner so it shows even with an empty queue.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.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. 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.
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.