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
52 changes: 52 additions & 0 deletions cmd/entire/cli/agent/opencode/cli_commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package opencode

import (
"context"
"fmt"
"os/exec"
"strings"
"time"
)

// openCodeCommandTimeout is the maximum time to wait for opencode CLI commands.
const openCodeCommandTimeout = 30 * time.Second

// runOpenCodeSessionDelete runs `opencode session delete <sessionID>` to remove
// a session from OpenCode's database. Treats "Session not found" as success
// (nothing to delete).
func runOpenCodeSessionDelete(sessionID string) error {
ctx, cancel := context.WithTimeout(context.Background(), openCodeCommandTimeout)
defer cancel()

cmd := exec.CommandContext(ctx, "opencode", "session", "delete", sessionID)
output, err := cmd.CombinedOutput()
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
return fmt.Errorf("opencode session delete timed out after %s", openCodeCommandTimeout)
}
// Treat "Session not found" as success — nothing to delete.
if strings.Contains(string(output), "Session not found") {
return nil
}
return fmt.Errorf("opencode session delete failed: %w (output: %s)", err, string(output))
}
return nil
}

// runOpenCodeImport runs `opencode import <file>` to import a session into
// OpenCode's database. The import preserves the original session ID
// from the export file.
func runOpenCodeImport(exportFilePath string) error {
ctx, cancel := context.WithTimeout(context.Background(), openCodeCommandTimeout)
defer cancel()

cmd := exec.CommandContext(ctx, "opencode", "import", exportFilePath)
if output, err := cmd.CombinedOutput(); err != nil {
if ctx.Err() == context.DeadlineExceeded {
return fmt.Errorf("opencode import timed out after %s", openCodeCommandTimeout)
}
return fmt.Errorf("opencode import failed: %w (output: %s)", err, string(output))
}

return nil
}
27 changes: 13 additions & 14 deletions cmd/entire/cli/agent/opencode/opencode.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,33 +144,32 @@ func (a *OpenCodeAgent) WriteSession(session *agent.AgentSession) error {
return fmt.Errorf("failed to write session data: %w", err)
}

// 2. If we have export data, import the session into OpenCode's SQLite.
// 2. If we have export data, import the session into OpenCode.
// This enables `opencode -s <id>` for both resume and rewind.
if len(session.ExportData) == 0 {
return nil // No export data — skip SQLite import (graceful degradation)
return nil // No export data — skip import (graceful degradation)
}

if err := a.importSessionIntoSQLite(session.SessionID, session.ExportData); err != nil {
// Non-fatal: SQLite import is best-effort. The JSONL file is written,
if err := a.importSessionIntoOpenCode(session.SessionID, session.ExportData); err != nil {
// Non-fatal: import is best-effort. The JSONL file is written,
// and the user can always run `opencode import <file>` manually.
fmt.Fprintf(os.Stderr, "warning: could not import session into OpenCode: %v\n", err)
}

return nil
}

// importSessionIntoSQLite writes the export JSON to a temp file and runs
// `opencode import` to restore the session into OpenCode's SQLite database.
// For rewind (session already exists), messages are deleted first so the
// reimport replaces them with the checkpoint-state messages.
func (a *OpenCodeAgent) importSessionIntoSQLite(sessionID string, exportData []byte) error {
// If the session already exists in SQLite, delete its messages first.
// importSessionIntoOpenCode writes the export JSON to a temp file and runs
// `opencode import` to restore the session into OpenCode's database.
// For rewind (session already exists), the session is deleted first so the
// reimport replaces it with the checkpoint-state messages.
func (a *OpenCodeAgent) importSessionIntoOpenCode(sessionID string, exportData []byte) error {
// Delete the session first so reimport replaces it cleanly.
// opencode import uses ON CONFLICT DO NOTHING, so existing messages
// would be skipped without this step (breaking rewind).
if sessionExistsInSQLite(sessionID) {
if err := deleteMessagesFromSQLite(sessionID); err != nil {
return fmt.Errorf("failed to clear existing messages: %w", err)
}
// runOpenCodeSessionDelete treats "not found" as success.
if err := runOpenCodeSessionDelete(sessionID); err != nil {
return fmt.Errorf("failed to delete existing session: %w", err)
}

// Write export JSON to a temp file for opencode import
Expand Down
110 changes: 0 additions & 110 deletions cmd/entire/cli/agent/opencode/sqlite.go

This file was deleted.