90 lines
2.7 KiB
Go
90 lines
2.7 KiB
Go
package herdr
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
type Adapter interface {
|
|
Lease(context.Context, string, string) (Session, error)
|
|
Bootstrap(context.Context, Session, string) error
|
|
Release(context.Context, Session) (string, error)
|
|
Kill(context.Context, Session) error
|
|
Occupancy(Session) (float64, error)
|
|
}
|
|
|
|
// TurnBoundary is optional so older herdr deployments remain usable. A true
|
|
// result means the current harness turn has ended and handoff is safe.
|
|
type TurnBoundary interface {
|
|
AtTurnBoundary(context.Context, Session) (bool, error)
|
|
}
|
|
type RotationSignal interface {
|
|
RotationSignal(context.Context, Session) (string, error)
|
|
}
|
|
type CLIAdapter struct {
|
|
Client *Client
|
|
Harness string
|
|
Window int64
|
|
Usage func(string) (Usage, error)
|
|
}
|
|
|
|
func (a CLIAdapter) Lease(ctx context.Context, task, worktree string) (Session, error) {
|
|
if a.Client == nil {
|
|
return Session{}, fmt.Errorf("adapter: client required")
|
|
}
|
|
var s Session
|
|
e := a.Client.Call(ctx, "pane.create", map[string]string{"harness": a.Harness, "task_id": task, "worktree": worktree}, &s)
|
|
s.Harness = a.Harness
|
|
s.Worktree = worktree
|
|
return s, e
|
|
}
|
|
func (a CLIAdapter) Bootstrap(ctx context.Context, s Session, ref string) error {
|
|
return a.Client.Prompt(ctx, s.PaneID, fmt.Sprintf("Read handoff %s, validate the anchor and TASK.md, then continue.", ref), time.Minute)
|
|
}
|
|
func (a CLIAdapter) Release(ctx context.Context, s Session) (string, error) {
|
|
var r struct {
|
|
Ref string `json:"handoff_ref"`
|
|
}
|
|
e := a.Client.Call(ctx, "pane.release", s, &r)
|
|
return r.Ref, e
|
|
}
|
|
func (a CLIAdapter) Kill(ctx context.Context, s Session) error {
|
|
return a.Client.Call(ctx, "pane.kill", s, nil)
|
|
}
|
|
func (a CLIAdapter) AtTurnBoundary(ctx context.Context, s Session) (bool, error) {
|
|
var r struct {
|
|
Status string `json:"status"`
|
|
}
|
|
if err := a.Client.Call(ctx, "pane.status", s, &r); err != nil {
|
|
return false, err
|
|
}
|
|
return !IsBusy(r.Status), nil
|
|
}
|
|
func (a CLIAdapter) RotationSignal(ctx context.Context, s Session) (string, error) {
|
|
var r struct {
|
|
Reason string `json:"reason"`
|
|
}
|
|
if err := a.Client.Call(ctx, "pane.rotation_signal", s, &r); err != nil {
|
|
return "", err
|
|
}
|
|
return r.Reason, nil
|
|
}
|
|
func (a CLIAdapter) Occupancy(s Session) (float64, error) {
|
|
if a.Usage == nil {
|
|
return 0, fmt.Errorf("adapter: usage reader required")
|
|
}
|
|
u, e := a.Usage(s.PaneID)
|
|
return Fraction(u, a.Window), e
|
|
}
|
|
|
|
var Claude = func(c *Client, w int64) CLIAdapter {
|
|
return CLIAdapter{Client: c, Harness: "claude", Window: w, Usage: ClaudeUsage}
|
|
}
|
|
var Codex = func(c *Client, w int64) CLIAdapter {
|
|
return CLIAdapter{Client: c, Harness: "codex", Window: w, Usage: CodexUsage}
|
|
}
|
|
var OpenCode = func(c *Client, w int64) CLIAdapter {
|
|
return CLIAdapter{Client: c, Harness: "opencode", Window: w, Usage: OpenCodeUsage}
|
|
}
|