Add interactive resume picker for stopped/idle sessions · Entire
Add interactive resume picker for stopped/idle sessions
103fcd7→main·
Soph·1mo ago·12 files·+838 added/-240 removed
entire resume with no argument now opens an interactive picker of
resumable sessions across all worktrees, so you don't have to remember
which branch you left work on. Picking a session checks out its branch
and prints the command to continue the agent; if the branch is already
checked out in another worktree, it points you there instead of failing
a checkout. entire resume <branch> is unchanged.
Details:
- Resumable = any session not currently mid-turn (idle + ended), not just
sessions explicitly ended via
session stop. Exiting an agent leaves a session idle (only a real SessionEnd marks it ended), so idle is the common "walked away" case and must be included. - Adds a
Branchfield to session state, captured on each turn start, and surfaced insession list --json. For sessions recorded before the field existed, the branch is derived by matching the session's last checkpoint ID against branch-only commit trailers. Internalentire/refs (metadata branch + shadow branches) are excluded from that index, both to avoid mis-resolving to a non-resumable ref and to keep the scan fast (the index is also built lazily and avoids go-git MergeBase). - Resume now keeps an existing local session log as-is by default; only a
missing log is restored from the checkpoint.
--forceoverwrites. This is driven by file existence, not transcript timestamps.
Tests cover the picker filtering/sorting, labels, branch derivation, internal-ref exclusion, worktree-clash detection, and keep-existing-log behavior. Obsolete overwrite-prompt tests removed; timestamp resume integration tests updated to keep-by-default semantics.
Sessions
Changes
12
MCLAUDE.md+6/-1
cmd/entire/cli
integration_test
Dresume_interactive_test.go-155
Mresume_test.go+44/-38
Mresume.go+26/-27
Aresume_picker.go+344
Aresume_picker_test.go+294
session
Mstate.go+8
Msessions.go+4/-2
strategy
Mmanual_commit_hooks.go+16
Mmanual_commit_rewind.go+34/-17
Mrewind_test.go+55
docs/architecture
Msessions-and-checkpoints.md+7
Code
//go:build integration && unix
package integration
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func (env *TestEnv) RunResumeInteractive(branchName string, respond func(ptyFile *os.File) string) (string, error) {
env.T.Helper()
return env.RunCommandInteractive([]string{"resume", branchName}, respond)
}
func TestResume_LocalLogNewerTimestamp_UserConfirmsOverwrite(t *testing.T) {
t.Parallel()
env := NewFeatureBranchEnv(t)
// Create a session with a specific timestamp
session := env.NewSession()
if err := env.SimulateUserPromptSubmit(session.ID); err != nil {
t.Fatalf("SimulateUserPromptSubmit failed: %v", err)
}
content := "def hello; end"
env.WriteFile("hello.rb", content)
session.CreateTranscript(
"Create hello method",
[]FileChange{{Path: "hello.rb", Content: content}},
)
if err := env.SimulateStop(session.ID, session.TranscriptPath); err != nil {
t.Fatalf("SimulateStop failed: %v", err)
}
// Commit the session's changes (manual-commit requires user to commit)
env.GitCommitWithShadowHooks("Create hello method", "hello.rb")
featureBranch := env.GetCurrentBranch()
// Create a local log with a NEWER timestamp than the checkpoint
if err := os.MkdirAll(env.ClaudeProjectDir, 0o755); err != nil {
t.Fatalf("failed to create Claude project dir: %v", err)
}
existingLog := filepath.Join(env.ClaudeProjectDir, session.ID+".jsonl")
futureTimestamp := time.Now().Add(24 * time.Hour).UTC().Format(time.RFC3339)
newerContent := fmt.Sprintf(`{"type":"human","timestamp":"%s","message":{"content":"newer local work"}}`, futureTimestamp)
if err := os.WriteFile(existingLog, []byte(newerContent), 0o644); err != nil {
t.Fatalf("failed to write existing log: %v", err)
}
// Switch to main
env.GitCheckoutBranch(masterBranch)
// Resume interactively and confirm the overwrite
output, err := env.RunResumeInteractive(featureBranch, func(ptyFile *os.File) string {
out, promptErr := WaitForPromptAndRespond(ptyFile, "[y/N]", "y\n", 10*time.Second)
if promptErr != nil {
t.Logf("Warning: %v", promptErr)
}
return out
})
if err != nil {
t.Fatalf("resume with user confirmation failed: %v\nOutput: %s", err, output)
}
// Verify local log was overwritten with checkpoint content
data, err := os.ReadFile(existingLog)
if err != nil {
t.Fatalf("failed to read log: %v", err)
}
if strings.Contains(string(data), "newer local work") {
t.Errorf("local log should have been overwritten after user confirmed, but still has newer content: %s", string(data))
}
if !strings.Contains(string(data), "Create hello method") {
t.Errorf("restored log should contain checkpoint transcript, got: %s", string(data))
}
}
// Other relevant tests...
Example Outputs
In tests: Ensure logs are preserved and actions taken are in line with user inputs.
Demonstrate branch handling and session resumption logic.