fix: make worker handoff rotation durable

This commit is contained in:
kami
2026-07-30 01:30:59 +04:00
parent ce02c60106
commit 1ff0af2e69
16 changed files with 1488 additions and 1566 deletions
+56
View File
@@ -10,6 +10,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
)
@@ -30,6 +31,7 @@ func Fraction(u Usage, w int64) float64 {
}
return f
}
// ClaudeSessionFile resolves the transcript file for a Claude Code session
// running against worktree, by newest-mtime under Claude Code's encoded
// project directory (~/.claude/projects/<abs-worktree-path-with-/-replaced-
@@ -202,6 +204,60 @@ func OpenCodeUsage(p string) (Usage, error) {
return Usage{x.Tokens.Input, x.Tokens.Cache.Read, x.Tokens.Cache.Write, x.Tokens.Output}, e
}
// OpenCodeSessionID resolves the exact OpenCode session associated with a
// checkout. OpenCode stores usage in its SQLite session table, not in a pane
// transcript. Selecting by directory and persisting the returned id prevents
// a multi-pane worker from attributing another task's newest session to this
// lease.
func OpenCodeSessionID(worktree string) (string, error) {
db := os.Getenv("ORCHESTRA_OPENCODE_DB")
if db == "" {
db = filepath.Join(os.Getenv("HOME"), ".local", "share", "opencode", "opencode.db")
}
abs, err := filepath.Abs(worktree)
if err != nil {
return "", err
}
out, err := exec.Command("sqlite3", "-readonly", "-noheader", db, "select id from session where directory = "+sqliteQuote(abs)+" order by time_updated desc limit 1;").Output()
if err != nil {
return "", fmt.Errorf("opencode session lookup: %w", err)
}
id := strings.TrimSpace(string(out))
if id == "" {
return "", fmt.Errorf("opencode session lookup: no session for %s", abs)
}
return id, nil
}
// OpenCodeSessionUsage reads the token counters for one persisted session.
func OpenCodeSessionUsage(sessionID string) (Usage, error) {
if sessionID == "" {
return Usage{}, fmt.Errorf("opencode session id required")
}
db := os.Getenv("ORCHESTRA_OPENCODE_DB")
if db == "" {
db = filepath.Join(os.Getenv("HOME"), ".local", "share", "opencode", "opencode.db")
}
out, err := exec.Command("sqlite3", "-readonly", "-noheader", "-separator", "|", db, "select tokens_input,tokens_cache_read,tokens_cache_write,tokens_output from session where id = "+sqliteQuote(sessionID)+";").Output()
if err != nil {
return Usage{}, fmt.Errorf("opencode usage lookup: %w", err)
}
parts := strings.Split(strings.TrimSpace(string(out)), "|")
if len(parts) != 4 {
return Usage{}, fmt.Errorf("opencode usage lookup: unknown session %q", sessionID)
}
values := [4]int64{}
for i, part := range parts {
values[i], err = strconv.ParseInt(part, 10, 64)
if err != nil {
return Usage{}, fmt.Errorf("opencode usage lookup: %w", err)
}
}
return Usage{Input: values[0], CacheRead: values[1], CacheWrite: values[2], Output: values[3]}, nil
}
func sqliteQuote(s string) string { return "'" + strings.ReplaceAll(s, "'", "''") + "'" }
// OpenCodeStatus probes the server fast path. Callers can use the returned
// status and fall back to OpenCodeUsage when the SSE/server is unavailable.
func OpenCodeStatus(ctx context.Context, baseURL, sessionID string) (string, error) {