/ tools / AgentTool / agentDisplay.ts
agentDisplay.ts
  1  /**
  2   * Shared utilities for displaying agent information.
  3   * Used by both the CLI `claude agents` handler and the interactive `/agents` command.
  4   */
  5  
  6  import { getDefaultSubagentModel } from '../../utils/model/agent.js'
  7  import {
  8    getSourceDisplayName,
  9    type SettingSource,
 10  } from '../../utils/settings/constants.js'
 11  import type { AgentDefinition } from './loadAgentsDir.js'
 12  
 13  type AgentSource = SettingSource | 'built-in' | 'plugin'
 14  
 15  export type AgentSourceGroup = {
 16    label: string
 17    source: AgentSource
 18  }
 19  
 20  /**
 21   * Ordered list of agent source groups for display.
 22   * Both the CLI and interactive UI should use this to ensure consistent ordering.
 23   */
 24  export const AGENT_SOURCE_GROUPS: AgentSourceGroup[] = [
 25    { label: 'User agents', source: 'userSettings' },
 26    { label: 'Project agents', source: 'projectSettings' },
 27    { label: 'Local agents', source: 'localSettings' },
 28    { label: 'Managed agents', source: 'policySettings' },
 29    { label: 'Plugin agents', source: 'plugin' },
 30    { label: 'CLI arg agents', source: 'flagSettings' },
 31    { label: 'Built-in agents', source: 'built-in' },
 32  ]
 33  
 34  export type ResolvedAgent = AgentDefinition & {
 35    overriddenBy?: AgentSource
 36  }
 37  
 38  /**
 39   * Annotate agents with override information by comparing against the active
 40   * (winning) agent list. An agent is "overridden" when another agent with the
 41   * same type from a higher-priority source takes precedence.
 42   *
 43   * Also deduplicates by (agentType, source) to handle git worktree duplicates
 44   * where the same agent file is loaded from both the worktree and main repo.
 45   */
 46  export function resolveAgentOverrides(
 47    allAgents: AgentDefinition[],
 48    activeAgents: AgentDefinition[],
 49  ): ResolvedAgent[] {
 50    const activeMap = new Map<string, AgentDefinition>()
 51    for (const agent of activeAgents) {
 52      activeMap.set(agent.agentType, agent)
 53    }
 54  
 55    const seen = new Set<string>()
 56    const resolved: ResolvedAgent[] = []
 57  
 58    // Iterate allAgents, annotating each with override info from activeAgents.
 59    // Deduplicate by (agentType, source) to handle git worktree duplicates.
 60    for (const agent of allAgents) {
 61      const key = `${agent.agentType}:${agent.source}`
 62      if (seen.has(key)) continue
 63      seen.add(key)
 64  
 65      const active = activeMap.get(agent.agentType)
 66      const overriddenBy =
 67        active && active.source !== agent.source ? active.source : undefined
 68      resolved.push({ ...agent, overriddenBy })
 69    }
 70  
 71    return resolved
 72  }
 73  
 74  /**
 75   * Resolve the display model string for an agent.
 76   * Returns the model alias or 'inherit' for display purposes.
 77   */
 78  export function resolveAgentModelDisplay(
 79    agent: AgentDefinition,
 80  ): string | undefined {
 81    const model = agent.model || getDefaultSubagentModel()
 82    if (!model) return undefined
 83    return model === 'inherit' ? 'inherit' : model
 84  }
 85  
 86  /**
 87   * Get a human-readable label for the source that overrides an agent.
 88   * Returns lowercase, e.g. "user", "project", "managed".
 89   */
 90  export function getOverrideSourceLabel(source: AgentSource): string {
 91    return getSourceDisplayName(source).toLowerCase()
 92  }
 93  
 94  /**
 95   * Compare agents alphabetically by name (case-insensitive).
 96   */
 97  export function compareAgentsByName(
 98    a: AgentDefinition,
 99    b: AgentDefinition,
100  ): number {
101    return a.agentType.localeCompare(b.agentType, undefined, {
102      sensitivity: 'base',
103    })
104  }