Add usage commands, a vendor-neutral usage format and a knowledge base

This commit is contained in:
Ian Gulliver
2026-10-19 15:08:57 -07:00
parent 912225cbd1
commit 8a395d4d6e
17 changed files with 1010 additions and 403 deletions
+29 -42
View File
@@ -46,22 +46,23 @@ type Manager struct {
clients *clients.Registry
terminals *terminal.Terminals
mu sync.Mutex
live map[string]*live
starting map[string]*start
closing bool
rateLimits map[api.AgentSessionVendor]map[string]any
leadAnswered map[string]bool
paneFocus map[string]api.PaneFocus
paneReported map[string]time.Time
paneMoved map[string]time.Time
humanMoved map[string]time.Time
moveRefused map[string]bool
startedTodo map[string]time.Time
startRefused map[string]bool
finishing map[string]bool
handoffModel map[string]string
mergeTurns map[string]mergeTurn
mu sync.Mutex
live map[string]*live
starting map[string]*start
closing bool
rateLimits map[api.AgentSessionVendor]api.Usage
usageCommands map[api.AgentSessionVendor]string
leadAnswered map[string]bool
paneFocus map[string]api.PaneFocus
paneReported map[string]time.Time
paneMoved map[string]time.Time
humanMoved map[string]time.Time
moveRefused map[string]bool
startedTodo map[string]time.Time
startRefused map[string]bool
finishing map[string]bool
handoffModel map[string]string
mergeTurns map[string]mergeTurn
deliverMu sync.Mutex
deliverLocks map[string]*sessionLock
@@ -76,6 +77,8 @@ type Manager struct {
reviewMu sync.Mutex
usageMu map[api.AgentSessionVendor]*sync.Mutex
workflowMu sync.Mutex
bad *badLines
@@ -141,7 +144,9 @@ func New(st *store.Store, worktreeRoot, leadRoot, handsRoot, codexHome, antigrav
live: map[string]*live{},
starting: map[string]*start{},
deliverLocks: map[string]*sessionLock{},
rateLimits: map[api.AgentSessionVendor]map[string]any{},
rateLimits: map[api.AgentSessionVendor]api.Usage{},
usageCommands: map[api.AgentSessionVendor]string{},
usageMu: map[api.AgentSessionVendor]*sync.Mutex{api.Claude: {}, api.Codex: {}, api.Antigravity: {}},
leadAnswered: map[string]bool{},
paneFocus: map[string]api.PaneFocus{},
paneReported: map[string]time.Time{},
@@ -160,8 +165,7 @@ func New(st *store.Store, worktreeRoot, leadRoot, handsRoot, codexHome, antigrav
watchStop: make(chan struct{}),
}
go m.signalLoop()
go m.readCodexRateLimit()
go m.readAgyRateLimit()
go m.loadUsage()
return m
}
@@ -504,13 +508,13 @@ func (m *Manager) LiveCount() int {
type Usage struct {
Running []api.TurnRef
RateLimits map[string]map[string]any
RateLimits map[string]api.Usage
}
func (m *Manager) Usage(ctx context.Context) (Usage, error) {
u := Usage{
Running: []api.TurnRef{},
RateLimits: map[string]map[string]any{},
RateLimits: map[string]api.Usage{},
}
running, err := m.store.SessionsInState(ctx, api.AgentSessionStateRunning)
@@ -738,7 +742,7 @@ func (m *Manager) startLive(ctx context.Context, sess api.AgentSession, task api
if err != nil {
return nil, err
}
tools := append([]mcp.Tool{m.askTool(l), m.cancelQuestionTool(l), m.setMergeTitleTool(l), m.stopMergeTool(l), m.mergeToMainTool(l), m.setTaskSummaryTool(l), m.searchClosedTool(l), m.exposePortTool(l), m.takeSnapshotTool(l), m.terminalReadTool(l), m.terminalTypeTool(l), m.bashTool(l)}, append(m.subtaskTools(l), m.clientTools(l)...)...)
tools := append([]mcp.Tool{m.askTool(l), m.cancelQuestionTool(l), m.setMergeTitleTool(l), m.stopMergeTool(l), m.mergeToMainTool(l), m.setTaskSummaryTool(l), m.searchClosedTool(l), m.exposePortTool(l), m.takeSnapshotTool(l), m.terminalReadTool(l), m.terminalTypeTool(l), m.bashTool(l), m.readKnowledgeTool()}, append(m.subtaskTools(l), m.clientTools(l)...)...)
lead := true
if sess.Role == api.Implementer {
g += longTitleGuidance(task)
@@ -806,7 +810,7 @@ func (m *Manager) startLive(ctx context.Context, sess api.AgentSession, task api
return nil, err
}
l.notes = "\n\n" + notes
tools = m.leadTools(l)
tools = append(m.leadTools(l), m.readKnowledgeTool())
env = nil
}
servers := []*mcp.Server{{Name: "byre", Version: "0.0.0", Tools: tools}}
@@ -1001,14 +1005,12 @@ func (m *Manager) handle(ctx context.Context, l *live, ev claude.Event) error {
return err
}
addClaudeWindowMins(info.RateLimitInfo)
m.mu.Lock()
m.rateLimits[l.vendor] = info.RateLimitInfo
if info.RateLimitInfo["status"] == "rejected" {
l.rateLimited = info.RateLimitInfo
}
m.mu.Unlock()
m.setVendorUsage(l.vendor, claudeUsage(info.RateLimitInfo))
case "system":
if ev.Subtype == "init" {
@@ -1057,6 +1059,7 @@ type endResult struct {
}
func (m *Manager) turnEnded(ctx context.Context, l *live, res endResult) error {
go m.readUsage(l.vendor)
if l.owner != "" {
return m.leadTurnEnded(ctx, l, res)
}
@@ -1339,22 +1342,6 @@ func (m *Manager) event(l *live, actor, kind string, payload map[string]any) api
return ev
}
func addClaudeWindowMins(info map[string]any) {
windows, _ := info["unifiedWindows"].(map[string]any)
for name, w := range windows {
window, ok := w.(map[string]any)
if !ok {
continue
}
switch {
case strings.HasPrefix(name, "five_hour"):
window["windowMins"] = 5 * 60
case strings.HasPrefix(name, "seven_day"):
window["windowMins"] = 7 * 24 * 60
}
}
}
func decode(raw json.RawMessage) (any, error) {
if len(raw) == 0 {
return nil, nil