You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[HTO-35] Decouple aggregator from api.Client + add runtime capability flags
## Changes
**1. Extract AgentNamer interface (internal/aggregator/provider.go)**
- New `AgentNamer` interface with two methods:
- `GetAgentName(ctx context.Context, agentID string) (string, error)`
- `RefreshAgentCache(ctx context.Context, companyID string) error`
- `NullAgentNamer` no-op implementation for runtimes without API access
- `api.Client` already satisfies this interface (no changes needed to api package)
**2. Update Aggregator struct (internal/aggregator/aggregator.go)**
- Replace `apiClient *api.Client` field with `agentNamer AgentNamer`
- Remove `internal/api` import (zero imports in non-test code now)
- Update `NewAggregator()` and `NewAggregatorWithRuntimes()` signatures
- Replace all `a.apiClient.*()` calls with `a.agentNamer.*()`
- Check `name != ""` when calling GetAgentName to filter out empty responses
**3. Add Runtime field to AgentView (internal/aggregator/aggregator.go)**
- New `Runtime parser.Runtime` field tracks which runtime each agent uses
- Populated in `updateFleetState()` from the parsed `AgentRun.Runtime`
**4. Runtime capability flags (internal/aggregator/capabilities.go)**
- New `RuntimeCapabilities` struct with `CanKill`, `CanPause`, `CostKnown` flags
- `CapabilitiesByRuntime` map:
- Paperclip: all capabilities enabled
- Claude: read-only (no kill/pause)
- Codex: read-only, cost unknown
- `GetCapabilities(runtime)` helper for TUI layer
**5. Update TUI to gate actions (internal/ui/model.go)**
- `K` (kill): Now checks `aggregator.GetCapabilities(selected.Runtime).CanKill`
- `P` (pause): Checks `CanPause` before allowing action
- `R` (resume): Also requires `CanPause` capability
- Updated header comment to document action limitations
**6. Update main.go (cmd/agent-htop/main.go)**
- Create `agentNamer` variable (either `api.Client` or `NullAgentNamer`)
- Pass `agentNamer` to `NewAggregatorWithRuntimes()` instead of `apiClient`
- When no company specified: pass `NullAgentNamer{}`
- When company specified: pass `api.NewClient()` (which implements interface)
**7. Update tests (internal/aggregator/aggregator_test.go)**
- Rename `apiClient` variable to `agentNamer` for clarity
- Tests still use `api.NewClient()` since it satisfies the interface
## Acceptance Criteria - All Met ✓
✓ `internal/aggregator` package has zero imports of `internal/api` (non-test code)
✓ `agent-htop` with no company compiles and runs without creating API client
✓ Kill keybinding (`K`) gated by runtime capability
✓ All existing tests pass
✓ Builds with `go build ./...`
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
0 commit comments