no gemini
This commit is contained in:
+2
-2
@@ -223,11 +223,11 @@ When Claude Code reports a rate-limit status of `rejected` and the turn then fai
|
|||||||
|
|
||||||
Codex runs with its own home, `codex` under the data directory (`CODEX_HOME`, set by `startCodex`), so the human's `~/.codex/config.toml` (its `notify` program, MCP servers and plugins) doesn't apply and byre's threads stay out of the ChatGPT app's history. That home needs its own login, once: `CODEX_HOME="$HOME/Library/Application Support/byre/codex" codex login`. Until then a Codex turn fails with `401 Unauthorized`, filed as a session error. The `codex` binary comes from `PATH`; see `docs/codex-protocol.md`.
|
Codex runs with its own home, `codex` under the data directory (`CODEX_HOME`, set by `startCodex`), so the human's `~/.codex/config.toml` (its `notify` program, MCP servers and plugins) doesn't apply and byre's threads stay out of the ChatGPT app's history. That home needs its own login, once: `CODEX_HOME="$HOME/Library/Application Support/byre/codex" codex login`. Until then a Codex turn fails with `401 Unauthorized`, filed as a session error. The `codex` binary comes from `PATH`; see `docs/codex-protocol.md`.
|
||||||
|
|
||||||
Antigravity runs `agy` from `PATH` with the user's own Antigravity login and config (`~/.gemini`), signed in once by running `agy` interactively; `agy models` lists the models that login offers. byre's tools, guidance and hooks reach it through a per-session plugin directory, `antigravity/<session-id>` under the data directory, removed when the session's process ends. Its conversations show up in the user's own `agy` history. Antigravity reports no rate limits or context window, so its sessions have no `session.context` and no rate-limit entry. See `docs/antigravity-protocol.md`.
|
Antigravity runs `agy` from `PATH` with the user's own Antigravity login and config (`~/.gemini`), signed in once by running `agy` interactively; `agy models` lists the models that login offers. byre's tools, guidance and hooks reach it through a per-session plugin directory, `antigravity/<session-id>` under the data directory, removed when the session's process ends. Its conversations show up in the user's own `agy` history. Antigravity reports no context window, so its sessions have no `session.context`; its rate limits come from `agy --output-format json --print /usage` (`ReadUsage` in `internal/antigravity/usage.go`), which answers without starting a turn. See `docs/antigravity-protocol.md`.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
`GET /api/admin/status` reports `running_turns` (task sessions now `running`, as session, task and vendor; lead turns aren't listed); `rate_limits`, the latest status each vendor sent since byre started (Claude's `rate_limit_info`, with `windowMins` added to each `unifiedWindows` entry by `addClaudeWindowMins` in `internal/agents/manager.go` from the window's name, since Claude doesn't send a length; Codex's `account/rateLimits/updated` reshaped by `codexRateLimit` in `internal/agents/codex.go` into the same `status`, `rateLimitType` and `unifiedWindows` fields, `windowMins` from its `windowDurationMins`). Claude only reports during its turns; Codex is also asked once at startup (`readCodexRateLimit`, a short-lived `codex app-server` that calls `account/rateLimits/read` without opening a thread), so its entry is there before any Codex turn runs, and every Codex update broadcasts `admin.health`; `agent_processes`, `zombies`, `stuck_after_seconds` and `protocol_errors` (below); `clients`, one entry per command channel byre holds (the `/api/clients/connect` WebSockets in `internal/clients`, see "Client Macs"), oldest first, so it lists exactly the Macs `list_clients` and the `client_*` tools can reach; a Mac whose event streams are open but whose command channel is down isn't listed. `id`, `host` and `commit` come from the `Byre-Client`, `Byre-Host` and `Byre-Commit` headers, which the macOS app sends on every request: a UUID the app makes up each time it starts, the Mac's name (`Host.current().localizedName`, else `ProcessInfo.processInfo.hostName`, the name agents' client tools take) and the `ByreCommit` that `mac/bundle.sh` stamped into its `Info.plist`. To check that a rebuilt app is running, compare each client's `commit` with the commit it was built from. `last_action_at` is when byre last received a request the human made from that client `id`: any request other than a `GET`, except the two the app sends on its own (`PUT /api/me/pane-focus` and `PUT /api/tasks/{taskId}/summary-read`, listed in `automaticRequests` in `internal/server/server.go`), and every binary message on a terminal WebSocket, which includes the replies SwiftTerm sends by itself to queries in the output (cursor position and the like). The `identify` middleware and `terminalInput` record it in the client registry's memory (`Registry.Acted` in `internal/clients`), so it is absent until that client's first action after byre starts. The registry keeps each client by `id` across command-channel drops, so a Mac that sleeps and reconnects keeps its `last_action_at`, and forgets a client 24 hours after it was last connected or acted, since an app that quit never comes back under that `id`. An action at least a second after the last one from that client that broadcast, every `GET /api/events` stream opening or closing, and every command channel connecting or disconnecting broadcasts an unstored `admin.clients` event (`clientsChanged`). And server counts (start time, agent processes, projects, active tasks, SSE clients, database size including WAL, last event seq).
|
`GET /api/admin/status` reports `running_turns` (task sessions now `running`, as session, task and vendor; lead turns aren't listed); `rate_limits`, the latest status each vendor sent since byre started (Claude's `rate_limit_info`, with `windowMins` added to each `unifiedWindows` entry by `addClaudeWindowMins` in `internal/agents/manager.go` from the window's name, since Claude doesn't send a length; Codex's `account/rateLimits/updated` reshaped by `codexRateLimit` in `internal/agents/codex.go` into the same `status`, `rateLimitType` and `unifiedWindows` fields, `windowMins` from its `windowDurationMins`; Antigravity's `/usage` buckets reshaped by `agyRateLimit` in `internal/agents/antigravity.go`, one `unifiedWindows` entry per bucket keyed by its `id`, `utilization` from `1 - remaining_fraction` and `windowMins` from its `window`, `rejected` when a bucket has none left). Claude only reports during its turns; Codex is also asked once at startup (`readCodexRateLimit`, a short-lived `codex app-server` that calls `account/rateLimits/read` without opening a thread), so its entry is there before any Codex turn runs, and every Codex update broadcasts `admin.health`. Antigravity is asked at startup and after every Antigravity turn ends (`readAgyRateLimit`), each read broadcasting `admin.health`; `agent_processes`, `zombies`, `stuck_after_seconds` and `protocol_errors` (below); `clients`, one entry per command channel byre holds (the `/api/clients/connect` WebSockets in `internal/clients`, see "Client Macs"), oldest first, so it lists exactly the Macs `list_clients` and the `client_*` tools can reach; a Mac whose event streams are open but whose command channel is down isn't listed. `id`, `host` and `commit` come from the `Byre-Client`, `Byre-Host` and `Byre-Commit` headers, which the macOS app sends on every request: a UUID the app makes up each time it starts, the Mac's name (`Host.current().localizedName`, else `ProcessInfo.processInfo.hostName`, the name agents' client tools take) and the `ByreCommit` that `mac/bundle.sh` stamped into its `Info.plist`. To check that a rebuilt app is running, compare each client's `commit` with the commit it was built from. `last_action_at` is when byre last received a request the human made from that client `id`: any request other than a `GET`, except the two the app sends on its own (`PUT /api/me/pane-focus` and `PUT /api/tasks/{taskId}/summary-read`, listed in `automaticRequests` in `internal/server/server.go`), and every binary message on a terminal WebSocket, which includes the replies SwiftTerm sends by itself to queries in the output (cursor position and the like). The `identify` middleware and `terminalInput` record it in the client registry's memory (`Registry.Acted` in `internal/clients`), so it is absent until that client's first action after byre starts. The registry keeps each client by `id` across command-channel drops, so a Mac that sleeps and reconnects keeps its `last_action_at`, and forgets a client 24 hours after it was last connected or acted, since an app that quit never comes back under that `id`. An action at least a second after the last one from that client that broadcast, every `GET /api/events` stream opening or closing, and every command channel connecting or disconnecting broadcasts an unstored `admin.clients` event (`clientsChanged`). And server counts (start time, agent processes, projects, active tasks, SSE clients, database size including WAL, last event seq).
|
||||||
|
|
||||||
Agent health is `internal/agents/health.go`:
|
Agent health is `internal/agents/health.go`:
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"g.fc.run/ai/byre/internal/antigravity"
|
"g.fc.run/ai/byre/internal/antigravity"
|
||||||
|
"g.fc.run/ai/byre/internal/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
type agySteps struct {
|
type agySteps struct {
|
||||||
@@ -151,6 +153,7 @@ func (m *Manager) agyTurnEnded(ctx context.Context, l *live, p antigravityProces
|
|||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
final := l.lastText
|
final := l.lastText
|
||||||
m.mu.Unlock()
|
m.mu.Unlock()
|
||||||
|
go m.readAgyRateLimit()
|
||||||
|
|
||||||
success := res.Status == "SUCCESS"
|
success := res.Status == "SUCCESS"
|
||||||
bounce := false
|
bounce := false
|
||||||
@@ -194,6 +197,42 @@ func (m *Manager) agyTurnEnded(ctx context.Context, l *live, p antigravityProces
|
|||||||
return m.turnEnded(ctx, l, r)
|
return m.turnEnded(ctx, l, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var agyWindowMins = map[string]int{"5h": 300, "weekly": 7 * 24 * 60}
|
||||||
|
|
||||||
|
func (m *Manager) readAgyRateLimit() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
groups, err := antigravity.ReadUsage(ctx)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("read antigravity rate limits", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.mu.Lock()
|
||||||
|
m.rateLimits[api.Antigravity] = agyRateLimit(groups)
|
||||||
|
m.mu.Unlock()
|
||||||
|
m.store.Broadcast(api.Event{Kind: "admin.health", Actor: "byre", Payload: map[string]any{}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func agyRateLimit(groups []antigravity.UsageGroup) map[string]any {
|
||||||
|
info := map[string]any{"status": "allowed"}
|
||||||
|
windows := map[string]any{}
|
||||||
|
for _, g := range groups {
|
||||||
|
for _, b := range g.Buckets {
|
||||||
|
window := map[string]any{"utilization": 1 - b.RemainingFraction, "resetsAt": b.ResetTime.Unix()}
|
||||||
|
if mins := agyWindowMins[b.Window]; mins != 0 {
|
||||||
|
window["windowMins"] = mins
|
||||||
|
}
|
||||||
|
windows[b.ID] = window
|
||||||
|
if b.RemainingFraction <= 0 {
|
||||||
|
info["status"] = "rejected"
|
||||||
|
info["rateLimitType"] = b.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info["unifiedWindows"] = windows
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) agyBeforeModel(l *live) antigravity.HookFunc {
|
func (m *Manager) agyBeforeModel(l *live) antigravity.HookFunc {
|
||||||
return func(ctx context.Context, input json.RawMessage) (map[string]any, error) {
|
return func(ctx context.Context, input json.RawMessage) (map[string]any, error) {
|
||||||
in := struct {
|
in := struct {
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ func New(st *store.Store, worktreeRoot, leadRoot, handsRoot, codexHome, antigrav
|
|||||||
}
|
}
|
||||||
go m.signalLoop()
|
go m.signalLoop()
|
||||||
go m.readCodexRateLimit()
|
go m.readCodexRateLimit()
|
||||||
|
go m.readAgyRateLimit()
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package antigravity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UsageBucket struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Window string `json:"window"`
|
||||||
|
RemainingFraction float64 `json:"remaining_fraction"`
|
||||||
|
ResetTime time.Time `json:"reset_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UsageGroup struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Buckets []UsageBucket `json:"buckets"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadUsage(ctx context.Context) ([]UsageGroup, error) {
|
||||||
|
out, err := exec.CommandContext(ctx, "agy", "--output-format", "json", "--print", "/usage").Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("agy /usage: %w", err)
|
||||||
|
}
|
||||||
|
resp := struct {
|
||||||
|
Command struct {
|
||||||
|
Data struct {
|
||||||
|
Groups []UsageGroup `json:"groups"`
|
||||||
|
} `json:"data"`
|
||||||
|
} `json:"command"`
|
||||||
|
}{}
|
||||||
|
err = json.Unmarshal(out, &resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("agy /usage: %w", err)
|
||||||
|
}
|
||||||
|
return resp.Command.Data.Groups, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user