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} }