Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions internal/executor/claude_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ func (c *ClaudeExecutor) BuildCommand(task *db.Task, sessionID, prompt string) s
return cmd
}

// GetSessionContent reads the Claude session for the given workDir and returns a conversation transcript.
func (c *ClaudeExecutor) GetSessionContent(workDir string) string {
return ReadClaudeSessionContent(workDir, DefaultClaudeConfigDir())
}

// ---- Session and Dangerous Mode Support ----

// SupportsSessionResume returns true - Claude supports session resume via --resume.
Expand Down
5 changes: 5 additions & 0 deletions internal/executor/codex_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,11 @@ func (c *CodexExecutor) ResumeSafe(task *db.Task, workDir string) bool {
return c.executor.resumeCodexWithMode(task, workDir, false)
}

// GetSessionContent reads the Codex session for the given workDir and returns a conversation transcript.
func (c *CodexExecutor) GetSessionContent(workDir string) string {
return ReadCodexSessionContent(workDir)
}

// findCodexSessionID discovers the most recent Codex session ID for the given workDir.
// Codex stores sessions in ~/.codex/sessions/ directory.
func findCodexSessionID(workDir string) string {
Expand Down
40 changes: 40 additions & 0 deletions internal/executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,16 @@ func (e *Executor) executeTask(ctx context.Context, task *db.Task) {
return
}

// Check for previous session content from a different executor.
// If this is a fresh start (not retry) and the current executor has no existing session,
// but another executor does, include that session's conversation as context.
if !isRetry {
if handoff := e.GetPreviousSessionContent(taskExecutor, workDir); handoff != "" {
e.logLine(task.ID, "system", "Including previous session context from executor switch")
prompt = handoff + prompt
}
}

// Run the executor
var result execResult
if isRetry {
Expand Down Expand Up @@ -1198,6 +1208,36 @@ func (e *Executor) GetTaskExecutor(task *db.Task) TaskExecutor {
return e.executorFactory.Get(name)
}

// GetPreviousSessionContent checks if a different executor has session content
// for the given workDir. This is used to preserve context when switching executors.
// Returns formatted session handoff text, or empty string if no previous session found.
func (e *Executor) GetPreviousSessionContent(currentExecutor TaskExecutor, workDir string) string {
// If the current executor already has a session, no need for handoff
if currentExecutor.FindSessionID(workDir) != "" {
return ""
}

// Check all other executors for session content
for _, name := range e.executorFactory.All() {
if name == currentExecutor.Name() {
continue
}
other := e.executorFactory.Get(name)
if other == nil {
continue
}
// Check if this executor has a session for the workDir
if other.FindSessionID(workDir) == "" {
continue
}
content := other.GetSessionContent(workDir)
if content != "" {
return FormatSessionHandoff(name, content)
}
}
return ""
}

// AvailableExecutors returns the names of all available executors.
func (e *Executor) AvailableExecutors() []string {
return e.executorFactory.Available()
Expand Down
5 changes: 5 additions & 0 deletions internal/executor/gemini_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,11 @@ func (g *GeminiExecutor) ResumeSafe(task *db.Task, workDir string) bool {
return g.executor.resumeGeminiWithMode(task, workDir, false)
}

// GetSessionContent reads the Gemini session for the given workDir and returns a conversation transcript.
func (g *GeminiExecutor) GetSessionContent(workDir string) string {
return ReadGeminiSessionContent(workDir)
}

// findGeminiSessionID discovers the most recent Gemini session ID for the given workDir.
// Gemini stores sessions in ~/.gemini/tmp/<project_hash>/chats/ directory.
func findGeminiSessionID(workDir string) string {
Expand Down
5 changes: 5 additions & 0 deletions internal/executor/openclaw_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,11 @@ func (o *OpenClawExecutor) SupportsDangerousMode() bool {
return false
}

// GetSessionContent returns empty - OpenClaw sessions are not file-based.
func (o *OpenClawExecutor) GetSessionContent(workDir string) string {
return ""
}

// FindSessionID returns the session key for the given task.
// OpenClaw uses explicit session keys rather than auto-discovering from files.
func (o *OpenClawExecutor) FindSessionID(workDir string) string {
Expand Down
5 changes: 5 additions & 0 deletions internal/executor/opencode_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,11 @@ func (o *OpenCodeExecutor) SupportsDangerousMode() bool {
return false
}

// GetSessionContent returns empty - OpenCode doesn't support session discovery.
func (o *OpenCodeExecutor) GetSessionContent(workDir string) string {
return ""
}

// FindSessionID returns empty - OpenCode doesn't support session discovery.
func (o *OpenCodeExecutor) FindSessionID(workDir string) string {
return ""
Expand Down
5 changes: 5 additions & 0 deletions internal/executor/pi_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ func (p *PiExecutor) ResumeSafe(task *db.Task, workDir string) bool {
return false
}

// GetSessionContent reads the Pi session for the given workDir and returns a conversation transcript.
func (p *PiExecutor) GetSessionContent(workDir string) string {
return ReadPiSessionContent(workDir)
}

// findPiSessionID finds the most recent Pi session ID for a workDir.
// It prioritizes explicit session paths in .task-worktrees/sessions/task-<ID>.jsonl
// but falls back to Pi's internal storage (~/.pi/agent/sessions/...) for backward compatibility.
Expand Down
Loading
Loading