Session persistence: denied sessions stay alive for resubmission#770
Open
backnotprop wants to merge 11 commits into
Open
Session persistence: denied sessions stay alive for resubmission#770backnotprop wants to merge 11 commits into
backnotprop wants to merge 11 commits into
Conversation
…ubmission Protocol: add "awaiting-resubmission" status (non-terminal), add "session-revision" event family, bump protocol version to 2. Session store: add suspend() (resolves waiters WITHOUT disposing resources — session stays alive), reactivate() (transitions back to active), and matchKey field on records. Server: skip the 2-second deletion timer on result endpoint for awaiting-resubmission sessions — they need to stay alive for the agent to resubmit.
Replace one-shot resolveDecision with a cycle system — each deny resolves the current cycle and starts a new one. Approve resolves the final cycle. Agent-originated sessions return awaitingResubmission in the deny response. Add updateContent(newPlan) method that updates plan content, saves to version history, resets draft state, and publishes a session-revision event via the WebSocket hub. Add slug and getSnapshot to PlannotatorSession interface so the factory can match resubmissions and serve correct snapshots. Add session-revision to SessionEventFamily type.
Session factory: add sessionRefs registry and findAwaitingSession() matching by matchKey. Plan sessions compute matchKey as plan:project:slug. On resubmission, existing awaiting session is matched and reactivated instead of creating a new one. Add registerPersistentDecision() — loops on waitForDecision(), suspending on deny and completing on approve. Replaces one-shot registerSessionDecision for plan sessions. Fix unhandled promise rejection: add .catch() to disposed promise in both registerSessionDecision and registerPersistentDecision. CLI: accept "awaiting-resubmission" as valid non-error status so denied sessions output feedback and exit 0 instead of failing.
Extend CompletionBanner with 'awaiting' variant — amber spinner with "Feedback sent — waiting for agent to revise..." message and optional cancel button. Plan review deny handler now checks response for awaitingResubmission flag. When true, shows the awaiting banner instead of the completion overlay. Subscribe to session-revision events via daemon WebSocket. When the agent resubmits and the session reactivates, the event carries the new plan content. The UI updates: markdown refreshes, previousPlan updates for diff, all annotations clear, awaiting state resets.
Fix operator precedence in registerPersistentDecision catch handler — was evaluating as (A && B) || C instead of A && (B || C). Extend findAwaitingSession to prune completed/expired/failed/cancelled entries from sessionRefs map, preventing slow memory growth over daemon lifetime.
Extract createDecisionScope helper to eliminate duplication between registerSessionDecision and registerPersistentDecision. Define SessionDecisionResult type for explicit contracts. Annotate server: cycle-based decisions, updateContent for file-based modes, awaitingResubmission in /api/feedback response. Review server: cycle-based decisions, updateContent(rawPatch, gitRef), awaitingResubmission for non-approved feedback. Factory: generalize sessionRefs to PersistableSession interface. Add matchKey computation and matching for annotate (annotate:filepath) and review (review:project:branch or review:prUrl). Wire both with registerPersistentDecision. URL and annotate-last modes keep one-shot behavior (no persistent source to refresh).
Annotate: handleAnnotateFeedback now checks response for awaitingResubmission flag, same pattern as plan deny handler. Session-revision subscription already handles both plan and annotate since they share App.tsx. Code review: handleSendFeedback checks awaitingResubmission response. Add session-revision event subscription that refreshes diff data, clears annotations, and resets awaiting state. CompletionBanner shows awaiting variant for all three surfaces.
Move plan server's inline updateContent closure to a named function handleUpdateContent for readability. Document findAwaitingSession's side effect of pruning terminal sessions during search.
Add createDecisionCycle<T>() and resolveAndCycle() to session-handler.ts. All three servers (plan, annotate, review) now use the shared helper instead of copy-pasting the cycle setup, resolve, and startNewCycle pattern. Deny handlers collapse to a single resolveAndCycle() call. Extract updateContent to named handleUpdateContent functions in all three servers for consistency — inline closures in return blocks replaced with named functions defined above the return.
…, empty content, TTL 1. Register no-op snapshot provider for session-revision in all three servers — prevents WebSocket disconnect when frontend subscribes. 2. Exclude plannotator-frontend from agent origins in resolveAndCycle — dashboard-created sessions complete on deny instead of hanging. 3. Complete session on exit: true in registerPersistentDecision — Exit button now properly terminates instead of suspending. 4. Check revision.plan !== undefined instead of truthiness — empty diffs and empty annotate content no longer ignored. 5. Store ttlMs on session record and restore it on reactivate() — reactivated sessions expire normally if abandoned.
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.
Summary
Denied sessions stay alive instead of dying. When the agent revises and resubmits, the same session updates in place — no new tab, no lost context. Works for all three feedback-capable session types: plan review, code review, and file-based annotate.
Key changes
awaiting-resubmission— non-terminal, session stays routable with HTTP handler alivecreateDecisionCycle<T>()helper replaces one-shot promises in all three servers. Each deny starts a new cycle; approve/exit is final.plan:project:slug,review:project:branch,annotate:filepath)session-revisionWebSocket subscription, content refresh with annotation clearingawaiting-resubmissionas valid non-error status (exit 0 with feedback)What stays the same
Test plan