diff --git a/apps/backend/internal/agent/settings/controller/controller.go b/apps/backend/internal/agent/settings/controller/controller.go index 1786d17e07..5d953e5044 100644 --- a/apps/backend/internal/agent/settings/controller/controller.go +++ b/apps/backend/internal/agent/settings/controller/controller.go @@ -56,7 +56,6 @@ type Controller struct { mcpService *mcpconfig.Service modelCache *modelfetcher.Cache hostUtility *hostutility.Manager - hostUsage HostUsageLister jobStore *JobStore hub JobBroadcaster logger *logger.Logger diff --git a/apps/backend/internal/agent/settings/controller/subscription_usage.go b/apps/backend/internal/agent/settings/controller/subscription_usage.go deleted file mode 100644 index feafa324c7..0000000000 --- a/apps/backend/internal/agent/settings/controller/subscription_usage.go +++ /dev/null @@ -1,43 +0,0 @@ -package controller - -import ( - "context" - - "github.com/kandev/kandev/internal/agent/settings/dto" - agentusage "github.com/kandev/kandev/internal/agent/usage" -) - -// HostUsageLister lists subscription utilization for host-installed agent CLIs. -// Implemented by usage.HostService. fresh bypasses the 5-minute cache (still -// bounded by a short server-side clamp). -type HostUsageLister interface { - List(ctx context.Context, fresh bool) []agentusage.HostAgentUsage -} - -// SetHostUsageLister wires in the host subscription-usage service. Optional — -// when unset SubscriptionUsage returns an empty listing. -func (c *Controller) SetHostUsageLister(l HostUsageLister) { - c.hostUsage = l -} - -// SubscriptionUsage returns utilization for host agents authenticated with -// subscription (OAuth) credentials, enriched with registry display names. -func (c *Controller) SubscriptionUsage(ctx context.Context, fresh bool) *dto.AgentSubscriptionUsageResponse { - resp := &dto.AgentSubscriptionUsageResponse{Agents: []dto.AgentSubscriptionUsage{}} - if c.hostUsage == nil { - return resp - } - for _, entry := range c.hostUsage.List(ctx, fresh) { - displayName := entry.AgentID - if ag, ok := c.agentRegistry.Get(entry.AgentID); ok { - displayName = ag.DisplayName() - } - resp.Agents = append(resp.Agents, dto.AgentSubscriptionUsage{ - AgentID: entry.AgentID, - DisplayName: displayName, - Usage: entry.Usage, - Error: entry.Error, - }) - } - return resp -} diff --git a/apps/backend/internal/agent/settings/controller/subscription_usage_test.go b/apps/backend/internal/agent/settings/controller/subscription_usage_test.go deleted file mode 100644 index 2c2cf20cd4..0000000000 --- a/apps/backend/internal/agent/settings/controller/subscription_usage_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package controller - -import ( - "context" - "testing" - "time" - - "github.com/kandev/kandev/internal/agent/agents" - agentusage "github.com/kandev/kandev/internal/agent/usage" -) - -type fakeHostUsageLister struct { - entries []agentusage.HostAgentUsage - lastFresh bool - calls int -} - -func (f *fakeHostUsageLister) List(_ context.Context, fresh bool) []agentusage.HostAgentUsage { - f.calls++ - f.lastFresh = fresh - return f.entries -} - -func TestControllerSubscriptionUsage_NoLister(t *testing.T) { - ctrl := newTestController(map[string]agents.Agent{}) - - resp := ctrl.SubscriptionUsage(context.Background(), false) - - if resp == nil || resp.Agents == nil { - t.Fatalf("expected non-nil response with empty agents, got %+v", resp) - } - if len(resp.Agents) != 0 { - t.Fatalf("expected empty listing without a lister, got %+v", resp.Agents) - } -} - -func TestControllerSubscriptionUsage_MapsEntries(t *testing.T) { - ctrl := newTestController(map[string]agents.Agent{ - "claude-acp": &testAgent{id: "claude-acp", name: "claude-acp", displayName: "Claude", enabled: true}, - }) - now := time.Now() - usage := &agentusage.ProviderUsage{ - Provider: "anthropic", - Plan: "max", - Windows: []agentusage.UtilizationWindow{{Label: "5-hour", UtilizationPct: 42, ResetAt: now}}, - FetchedAt: now, - } - lister := &fakeHostUsageLister{entries: []agentusage.HostAgentUsage{ - {AgentID: "claude-acp", Usage: usage}, - {AgentID: "unknown-acp", Error: "failed to fetch usage from provider"}, - }} - ctrl.SetHostUsageLister(lister) - - resp := ctrl.SubscriptionUsage(context.Background(), false) - - if len(resp.Agents) != 2 { - t.Fatalf("agents = %+v, want 2 entries", resp.Agents) - } - // Registry display-name lookup for known agents. - if resp.Agents[0].AgentID != "claude-acp" || resp.Agents[0].DisplayName != "Claude" { - t.Errorf("entry[0] = %+v", resp.Agents[0]) - } - if resp.Agents[0].Usage != usage || resp.Agents[0].Error != "" { - t.Errorf("entry[0] usage/error = %+v", resp.Agents[0]) - } - // Unknown agents fall back to the agent ID and forward the error. - if resp.Agents[1].DisplayName != "unknown-acp" || resp.Agents[1].Error == "" { - t.Errorf("entry[1] = %+v", resp.Agents[1]) - } - if resp.Agents[1].Usage != nil { - t.Errorf("entry[1] usage = %+v, want nil", resp.Agents[1].Usage) - } -} - -func TestControllerSubscriptionUsage_PropagatesFresh(t *testing.T) { - ctrl := newTestController(map[string]agents.Agent{}) - lister := &fakeHostUsageLister{} - ctrl.SetHostUsageLister(lister) - - _ = ctrl.SubscriptionUsage(context.Background(), true) - if !lister.lastFresh { - t.Error("fresh=true was not propagated to the lister") - } - _ = ctrl.SubscriptionUsage(context.Background(), false) - if lister.lastFresh { - t.Error("fresh=false was not propagated to the lister") - } - if lister.calls != 2 { - t.Errorf("lister calls = %d, want 2", lister.calls) - } -} diff --git a/apps/backend/internal/agent/settings/dto/dto.go b/apps/backend/internal/agent/settings/dto/dto.go index 2eab5480db..a2da14a500 100644 --- a/apps/backend/internal/agent/settings/dto/dto.go +++ b/apps/backend/internal/agent/settings/dto/dto.go @@ -4,23 +4,8 @@ import ( "time" "github.com/kandev/kandev/internal/agent/mcpconfig" - agentusage "github.com/kandev/kandev/internal/agent/usage" ) -// AgentSubscriptionUsage is one subscription-billed host agent's utilization -// entry returned by GET /api/v1/agents/usage. -type AgentSubscriptionUsage struct { - AgentID string `json:"agent_id"` - DisplayName string `json:"display_name"` - Usage *agentusage.ProviderUsage `json:"usage,omitempty"` - Error string `json:"error,omitempty"` -} - -// AgentSubscriptionUsageResponse is the GET /api/v1/agents/usage payload. -type AgentSubscriptionUsageResponse struct { - Agents []AgentSubscriptionUsage `json:"agents"` -} - type AgentProfileDTO struct { ID string `json:"id"` AgentID string `json:"agent_id"` diff --git a/apps/backend/internal/agent/settings/handlers/handlers.go b/apps/backend/internal/agent/settings/handlers/handlers.go index c6c1e8322e..17fa8ef729 100644 --- a/apps/backend/internal/agent/settings/handlers/handlers.go +++ b/apps/backend/internal/agent/settings/handlers/handlers.go @@ -19,8 +19,6 @@ import ( "go.uber.org/zap" ) -const queryValueTrue = "true" - var availableAgentsBroadcastTimeout = 10 * time.Second type Handlers struct { @@ -55,7 +53,6 @@ func (h *Handlers) registerHTTP(router *gin.Engine) { api := router.Group("/api/v1") api.GET("/agents/discovery", h.httpDiscoverAgents) api.GET("/agents/available", h.httpListAvailableAgents) - api.GET("/agents/usage", h.httpAgentSubscriptionUsage) api.GET("/agents", h.httpListAgents) api.POST("/agents", h.interlock, h.httpCreateAgent) api.POST("/agents/tui", h.interlock, h.httpCreateCustomTUIAgent) @@ -87,11 +84,6 @@ func (h *Handlers) httpDiscoverAgents(c *gin.Context) { h.broadcastAvailableAgentsAsync() } -func (h *Handlers) httpAgentSubscriptionUsage(c *gin.Context) { - fresh := c.Query("fresh") == queryValueTrue || c.Query("fresh") == "1" - c.JSON(http.StatusOK, h.controller.SubscriptionUsage(c.Request.Context(), fresh)) -} - func (h *Handlers) httpListAvailableAgents(c *gin.Context) { resp, err := h.controller.ListAvailableAgents(c.Request.Context()) if err != nil { @@ -485,7 +477,7 @@ func (h *Handlers) httpUpdateProfile(c *gin.Context) { } func (h *Handlers) httpDeleteProfile(c *gin.Context) { - force := c.Query("force") == queryValueTrue + force := c.Query("force") == "true" profile, err := h.controller.DeleteProfile(c.Request.Context(), c.Param("id"), force) if err != nil { if err == controller.ErrAgentProfileNotFound { @@ -589,7 +581,7 @@ func (h *Handlers) httpGetAgentModels(c *gin.Context) { } // Check for refresh query parameter - refresh := c.Query("refresh") == queryValueTrue + refresh := c.Query("refresh") == "true" resp, err := h.controller.FetchDynamicModels(c.Request.Context(), agentName, refresh) if err != nil { diff --git a/apps/backend/internal/agent/usage/host.go b/apps/backend/internal/agent/usage/host.go deleted file mode 100644 index 43349d40ca..0000000000 --- a/apps/backend/internal/agent/usage/host.go +++ /dev/null @@ -1,106 +0,0 @@ -package usage - -import ( - "context" - "os" - "path/filepath" - "time" - - "go.uber.org/zap" - - "github.com/kandev/kandev/internal/common/logger" -) - -// freshMaxAge clamps fresh (hover-triggered) fetches: a provider is queried -// again at most every 15 s even when callers keep requesting fresh data. -const freshMaxAge = 15 * time.Second - -// hostUsageFetchError is the sanitized per-agent error surfaced through the -// API. Raw provider errors can contain credential paths and response bodies, -// so those are only logged server-side. -const hostUsageFetchError = "failed to fetch usage from provider" - -// HostAgentUsage is one host-installed subscription agent's usage entry. -type HostAgentUsage struct { - AgentID string `json:"agent_id"` - Usage *ProviderUsage `json:"usage,omitempty"` - Error string `json:"error,omitempty"` -} - -// hostUsageClient is a ProviderUsageClient that can also report whether -// subscription credentials are present on disk. -type hostUsageClient interface { - ProviderUsageClient - HasSubscriptionCredentials() bool -} - -type hostEntry struct { - agentID string - cacheKey string - client hostUsageClient -} - -// HostService lists subscription utilization for agent CLIs installed on the -// kandev host (Claude Code, Codex). Agents without subscription credentials -// are omitted from List results. -type HostService struct { - cache *UsageCache - entries []hostEntry - logger *logger.Logger -} - -// NewHostService builds a HostService reading credentials from the current -// user's home directory. The service is empty when the home dir is unavailable. -func NewHostService(log *logger.Logger) *HostService { - s := &HostService{cache: NewUsageCache(), logger: log} - home, err := os.UserHomeDir() - if err != nil { - return s - } - claudePath := filepath.Join(home, ".claude", ".credentials.json") - codexPath := filepath.Join(home, ".codex", "auth.json") - s.entries = []hostEntry{ - { - agentID: "claude-acp", - cacheKey: CacheKey("anthropic", claudePath), - client: NewClaudeUsageClientWithPath(claudePath), - }, - { - agentID: "codex-acp", - cacheKey: CacheKey("openai", codexPath), - client: NewCodexUsageClientWithPath(codexPath), - }, - } - return s -} - -// List returns usage for every host agent that has subscription credentials. -// With fresh=false cached results up to 5 minutes old are served; fresh=true -// re-queries the providers, bounded by freshMaxAge. Fetch failures are -// reported per-agent via the Error field rather than failing the whole listing. -func (s *HostService) List(ctx context.Context, fresh bool) []HostAgentUsage { - maxAge := cacheTTL - if fresh { - maxAge = freshMaxAge - } - out := make([]HostAgentUsage, 0, len(s.entries)) - for _, e := range s.entries { - if !e.client.HasSubscriptionCredentials() { - continue - } - entry := HostAgentUsage{AgentID: e.agentID} - u, err := s.cache.GetOrFetchWithin(ctx, e.cacheKey, maxAge, e.client.FetchUsage) - switch { - case err != nil: - if s.logger != nil { - s.logger.Warn("subscription usage fetch failed", - zap.String("agent", e.agentID), zap.Error(err)) - } - entry.Error = hostUsageFetchError - default: - entry.Usage = u - } - out = append(out, entry) - } - return out -} diff --git a/apps/backend/internal/agent/usage/host_internal_test.go b/apps/backend/internal/agent/usage/host_internal_test.go deleted file mode 100644 index ee39885121..0000000000 --- a/apps/backend/internal/agent/usage/host_internal_test.go +++ /dev/null @@ -1,240 +0,0 @@ -package usage - -import ( - "context" - "errors" - "sync" - "sync/atomic" - "testing" - "time" -) - -type fakeHostClient struct { - hasCreds bool - usage *ProviderUsage - err error - calls int -} - -func (f *fakeHostClient) FetchUsage(_ context.Context) (*ProviderUsage, error) { - f.calls++ - return f.usage, f.err -} - -func (f *fakeHostClient) HasSubscriptionCredentials() bool { return f.hasCreds } - -func TestHostServiceList(t *testing.T) { - now := time.Now() - okUsage := &ProviderUsage{ - Provider: "anthropic", - Plan: "max", - Windows: []UtilizationWindow{{Label: "5-hour", UtilizationPct: 42, ResetAt: now}}, - FetchedAt: now, - } - noCreds := &fakeHostClient{hasCreds: false} - ok := &fakeHostClient{hasCreds: true, usage: okUsage} - failing := &fakeHostClient{hasCreds: true, err: errors.New("boom")} - - svc := &HostService{ - cache: NewUsageCache(), - entries: []hostEntry{ - {agentID: "claude-acp", cacheKey: "k1", client: noCreds}, - {agentID: "codex-acp", cacheKey: "k2", client: ok}, - {agentID: "other-acp", cacheKey: "k3", client: failing}, - }, - } - - got := svc.List(context.Background(), false) - if len(got) != 2 { - t.Fatalf("List = %+v, want 2 entries", got) - } - if got[0].AgentID != "codex-acp" || got[0].Usage != okUsage || got[0].Error != "" { - t.Errorf("entry[0] = %+v", got[0]) - } - if got[1].AgentID != "other-acp" || got[1].Usage != nil || got[1].Error != hostUsageFetchError { - t.Errorf("entry[1] = %+v", got[1]) - } - if noCreds.calls != 0 { - t.Errorf("client without creds was fetched %d times", noCreds.calls) - } - - // Second List hits the cache for the successful entry. - _ = svc.List(context.Background(), false) - if ok.calls != 1 { - t.Errorf("expected cached fetch, got %d calls", ok.calls) - } -} - -func TestHostServiceList_FreshBypassesStaleCache(t *testing.T) { - now := time.Now() - okUsage := &ProviderUsage{Provider: "anthropic", FetchedAt: now} - ok := &fakeHostClient{hasCreds: true, usage: okUsage} - svc := &HostService{ - cache: NewUsageCache(), - entries: []hostEntry{{agentID: "claude-acp", cacheKey: "k1", client: ok}}, - } - - _ = svc.List(context.Background(), false) - if ok.calls != 1 { - t.Fatalf("initial fetch calls = %d", ok.calls) - } - - // Entry is younger than freshMaxAge: fresh serves the cached value. - _ = svc.List(context.Background(), true) - if ok.calls != 1 { - t.Errorf("fresh within clamp should hit cache, calls = %d", ok.calls) - } - - // Age the entry past freshMaxAge but below the 5-minute TTL: a normal List - // still serves the cache, a fresh List re-queries the provider. - svc.cache.mu.Lock() - svc.cache.entries["k1"].successAt = now.Add(-freshMaxAge - time.Second) - svc.cache.mu.Unlock() - - _ = svc.List(context.Background(), false) - if ok.calls != 1 { - t.Errorf("stale-but-within-TTL cached List should not refetch, calls = %d", ok.calls) - } - _ = svc.List(context.Background(), true) - if ok.calls != 2 { - t.Errorf("fresh List should refetch, calls = %d", ok.calls) - } -} - -func TestNewHostServiceRegistersHostAgents(t *testing.T) { - svc := NewHostService(nil) - if len(svc.entries) != 2 { - t.Fatalf("entries = %d, want 2", len(svc.entries)) - } - if svc.entries[0].agentID != "claude-acp" || svc.entries[1].agentID != "codex-acp" { - t.Errorf("agent IDs = %q, %q", svc.entries[0].agentID, svc.entries[1].agentID) - } -} - -// runCoalescedFetch launches n concurrent GetOrFetchWithin callers whose first -// fetch blocks until every goroutine has been started, maximizing contention -// without sleep-based synchronization. It returns the per-caller results. -func runCoalescedFetch( - t *testing.T, - cache *UsageCache, - n int, - result *ProviderUsage, - fetchErr error, - calls *atomic.Int32, -) { - t.Helper() - release := make(chan struct{}) - fetch := func(_ context.Context) (*ProviderUsage, error) { - calls.Add(1) - <-release - return result, fetchErr - } - - var started, wg sync.WaitGroup - for range n { - started.Add(1) - wg.Add(1) - go func() { - defer wg.Done() - started.Done() - _, err := cache.GetOrFetchWithin(context.Background(), "k", freshMaxAge, fetch) - if fetchErr == nil && err != nil { - t.Errorf("GetOrFetchWithin: %v", err) - } - if fetchErr != nil && err == nil { - t.Error("expected error from coalesced failing fetch") - } - }() - } - started.Wait() - close(release) - wg.Wait() -} - -func TestUsageCacheCoalescesConcurrentFetches(t *testing.T) { - cache := NewUsageCache() - var calls atomic.Int32 - - runCoalescedFetch(t, cache, 8, &ProviderUsage{Provider: "anthropic", FetchedAt: time.Now()}, nil, &calls) - - if got := calls.Load(); got != 1 { - t.Errorf("concurrent misses fetched %d times, want 1", got) - } -} - -func TestUsageCacheCoalescesFailures(t *testing.T) { - cache := NewUsageCache() - var calls atomic.Int32 - - runCoalescedFetch(t, cache, 8, nil, errors.New("boom"), &calls) - - if got := calls.Load(); got != 1 { - t.Errorf("concurrent failing fetches called provider %d times, want 1", got) - } - fetch := func(_ context.Context) (*ProviderUsage, error) { - calls.Add(1) - return nil, errors.New("boom") - } - - // Once the negative entry ages past failureCacheTTL, the provider is retried. - cache.mu.Lock() - cache.entries["k"].errAt = time.Now().Add(-failureCacheTTL - time.Second) - cache.mu.Unlock() - if _, err := cache.GetOrFetchWithin(context.Background(), "k", freshMaxAge, fetch); err == nil { - t.Error("expected error on retry after TTL") - } - if got := calls.Load(); got != 2 { - t.Errorf("expired failure entry should refetch, calls = %d, want 2", got) - } -} - -func TestUsageCacheFailureKeepsStaleSuccess(t *testing.T) { - cache := NewUsageCache() - okUsage := &ProviderUsage{Provider: "anthropic", FetchedAt: time.Now()} - okFetch := func(_ context.Context) (*ProviderUsage, error) { return okUsage, nil } - failFetch := func(_ context.Context) (*ProviderUsage, error) { return nil, errors.New("boom") } - - if _, err := cache.GetOrFetchWithin(context.Background(), "k", freshMaxAge, okFetch); err != nil { - t.Fatalf("seed fetch: %v", err) - } - // Age the success past the fresh clamp but within the regular TTL. - cache.mu.Lock() - cache.entries["k"].successAt = time.Now().Add(-freshMaxAge - time.Second) - cache.mu.Unlock() - - // A failed fresh refresh returns the error to the fresh caller... - if _, err := cache.GetOrFetchWithin(context.Background(), "k", freshMaxAge, failFetch); err == nil { - t.Fatal("expected error from failed fresh fetch") - } - // ...but must not evict the stale-but-valid success for non-fresh callers. - got, err := cache.GetOrFetchWithin(context.Background(), "k", cacheTTL, failFetch) - if err != nil || got != okUsage { - t.Errorf("stale success was evicted: usage=%v err=%v", got, err) - } -} - -func TestUsageCacheDoesNotCacheCancellation(t *testing.T) { - cache := NewUsageCache() - var calls atomic.Int32 - fetch := func(ctx context.Context) (*ProviderUsage, error) { - calls.Add(1) - return nil, ctx.Err() - } - - cancelled, cancel := context.WithCancel(context.Background()) - cancel() - if _, err := cache.GetOrFetchWithin(cancelled, "k", freshMaxAge, fetch); err == nil { - t.Fatal("expected cancellation error") - } - - // The cancellation must not have been recorded as a provider failure. - okUsage := &ProviderUsage{Provider: "anthropic", FetchedAt: time.Now()} - got, err := cache.GetOrFetchWithin(context.Background(), "k", freshMaxAge, - func(_ context.Context) (*ProviderUsage, error) { return okUsage, nil }) - if err != nil || got != okUsage { - t.Errorf("cancelled fetch poisoned the cache: usage=%v err=%v", got, err) - } - if calls.Load() != 1 { - t.Errorf("fetch calls = %d, want 1", calls.Load()) - } -} diff --git a/apps/backend/internal/backendapp/services.go b/apps/backend/internal/backendapp/services.go index 9059702326..05cb19244b 100644 --- a/apps/backend/internal/backendapp/services.go +++ b/apps/backend/internal/backendapp/services.go @@ -13,7 +13,6 @@ import ( "github.com/kandev/kandev/internal/agent/hostutility" "github.com/kandev/kandev/internal/agent/registry" agentsettingscontroller "github.com/kandev/kandev/internal/agent/settings/controller" - agentusage "github.com/kandev/kandev/internal/agent/usage" agentctlutil "github.com/kandev/kandev/internal/agentctl/server/utility" analyticsservice "github.com/kandev/kandev/internal/analytics/service" "github.com/kandev/kandev/internal/automation" @@ -53,7 +52,6 @@ func provideServices(cfg *config.Config, log *logger.Logger, repos *Repositories return nil, nil, err } agentSettingsController := agentsettingscontroller.NewController(repos.AgentSettings, discoveryRegistry, agentRegistry, repos.Task, log) - agentSettingsController.SetHostUsageLister(agentusage.NewHostService(log)) userSvc := userservice.NewService(repos.User, eventBus, log) editorSvc := editorservice.NewService(repos.Editor, repos.Task, userSvc) diff --git a/apps/web/app/office/agents/[id]/components/agent-overview-tab.tsx b/apps/web/app/office/agents/[id]/components/agent-overview-tab.tsx index b0e57880c8..c28e755381 100644 --- a/apps/web/app/office/agents/[id]/components/agent-overview-tab.tsx +++ b/apps/web/app/office/agents/[id]/components/agent-overview-tab.tsx @@ -11,7 +11,7 @@ import { toast } from "sonner"; import { useAppStore } from "@/components/state-provider"; import { updateAgentProfile, getAgentUtilization } from "@/lib/api/domains/office-api"; import type { AgentProfile, AgentRole, ProviderUsage } from "@/lib/state/slices/office/types"; -import { UtilizationBars } from "@/components/usage/utilization-bars"; +import { UtilizationBars } from "@/app/office/components/utilization-bars"; type AgentOverviewTabProps = { agent: AgentProfile; diff --git a/apps/web/components/usage/utilization-bars.tsx b/apps/web/app/office/components/utilization-bars.tsx similarity index 89% rename from apps/web/components/usage/utilization-bars.tsx rename to apps/web/app/office/components/utilization-bars.tsx index a48b3c6a4c..7a814ae85f 100644 --- a/apps/web/components/usage/utilization-bars.tsx +++ b/apps/web/app/office/components/utilization-bars.tsx @@ -1,22 +1,22 @@ "use client"; import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip"; -import type { ProviderUsage } from "@/lib/types/agent-profile"; +import type { ProviderUsage } from "@/lib/state/slices/office/types"; type Props = { usage: ProviderUsage; }; -export function getBarColor(pct: number): string { +function getBarColor(pct: number): string { if (pct >= 90) return "bg-red-500"; if (pct >= 80) return "bg-amber-500"; - return "bg-emerald-500"; + return "bg-blue-500"; } -export function getTextColor(pct: number): string { +function getTextColor(pct: number): string { if (pct >= 90) return "text-red-600 dark:text-red-400"; if (pct >= 80) return "text-amber-600 dark:text-amber-400"; - return "text-emerald-600 dark:text-emerald-400"; + return "text-muted-foreground"; } function formatResetTime(resetAt: string): string { diff --git a/apps/web/app/office/page-client.tsx b/apps/web/app/office/page-client.tsx index 8faa79114a..e7f1b4b617 100644 --- a/apps/web/app/office/page-client.tsx +++ b/apps/web/app/office/page-client.tsx @@ -23,7 +23,7 @@ import { AgentCardsPanel } from "./components/agent-cards-panel"; import { ProviderHealthCard } from "./components/routing/provider-health-card"; import { timeAgo } from "@/lib/utils/time"; -import { UtilizationBars } from "@/components/usage/utilization-bars"; +import { UtilizationBars } from "./components/utilization-bars"; import { formatDollars } from "@/lib/utils"; // formatMonthSpend renders the subcents value from /office dashboard diff --git a/apps/web/app/settings/agents/page.tsx b/apps/web/app/settings/agents/page.tsx index 7e5462e596..1b4669703e 100644 --- a/apps/web/app/settings/agents/page.tsx +++ b/apps/web/app/settings/agents/page.tsx @@ -31,7 +31,6 @@ import { useAgentDiscovery } from "@/hooks/domains/settings/use-agent-discovery" import { useAvailableAgents } from "@/hooks/domains/settings/use-available-agents"; import { AgentLogo } from "@/components/agent-logo"; import { AddTUIAgentDialog } from "@/components/settings/add-tui-agent-dialog"; -import { AgentUsageSection } from "@/components/settings/agent-usage-section"; import { HostShellDialog } from "@/components/settings/host-shell-dialog"; import { InstallAgentCard } from "@/components/settings/install-agent-card"; import { InstalledAgentCard } from "@/components/settings/installed-agent-card"; @@ -563,8 +562,6 @@ export default function AgentsSettingsPage() {
-
-
- Rate-limit utilization for agents signed in with a subscription plan. -
-