fix: make worker handoff rotation durable
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user