/ src / tasks / pillLabel.ts
pillLabel.ts
 1  import { DIAMOND_FILLED, DIAMOND_OPEN } from '../constants/figures.js'
 2  import { count } from '../utils/array.js'
 3  import type { BackgroundTaskState } from './types.js'
 4  
 5  /**
 6   * Produces the compact footer-pill label for a set of background tasks.
 7   * Used by both the footer pill and the turn-duration transcript line so the
 8   * two surfaces agree on terminology.
 9   */
10  export function getPillLabel(tasks: BackgroundTaskState[]): string {
11    const n = tasks.length
12    const allSameType = tasks.every(t => t.type === tasks[0]!.type)
13  
14    if (allSameType) {
15      switch (tasks[0]!.type) {
16        case 'local_bash': {
17          const monitors = count(
18            tasks,
19            t => t.type === 'local_bash' && t.kind === 'monitor',
20          )
21          const shells = n - monitors
22          const parts: string[] = []
23          if (shells > 0)
24            parts.push(shells === 1 ? '1 shell' : `${shells} shells`)
25          if (monitors > 0)
26            parts.push(monitors === 1 ? '1 monitor' : `${monitors} monitors`)
27          return parts.join(', ')
28        }
29        case 'in_process_teammate': {
30          const teamCount = new Set(
31            tasks.map(t =>
32              t.type === 'in_process_teammate' ? t.identity.teamName : '',
33            ),
34          ).size
35          return teamCount === 1 ? '1 team' : `${teamCount} teams`
36        }
37        case 'local_agent':
38          return n === 1 ? '1 local agent' : `${n} local agents`
39        case 'remote_agent': {
40          const first = tasks[0]!
41          // Per design mockup: โ—‡ open diamond while running/needs-input,
42          // โ—† filled once ExitPlanMode is awaiting approval.
43          if (n === 1 && first.type === 'remote_agent' && first.isUltraplan) {
44            switch (first.ultraplanPhase) {
45              case 'plan_ready':
46                return `${DIAMOND_FILLED} ultraplan ready`
47              case 'needs_input':
48                return `${DIAMOND_OPEN} ultraplan needs your input`
49              default:
50                return `${DIAMOND_OPEN} ultraplan`
51            }
52          }
53          return n === 1
54            ? `${DIAMOND_OPEN} 1 cloud session`
55            : `${DIAMOND_OPEN} ${n} cloud sessions`
56        }
57        case 'local_workflow':
58          return n === 1 ? '1 background workflow' : `${n} background workflows`
59        case 'monitor_mcp':
60          return n === 1 ? '1 monitor' : `${n} monitors`
61        case 'dream':
62          return 'dreaming'
63      }
64    }
65  
66    return `${n} background ${n === 1 ? 'task' : 'tasks'}`
67  }
68  
69  /**
70   * True when the pill should show the dimmed " ยท โ†“ to view" call-to-action.
71   * Per the state diagram: only the two attention states (needs_input,
72   * plan_ready) surface the CTA; plain running shows just the diamond + label.
73   */
74  export function pillNeedsCta(tasks: BackgroundTaskState[]): boolean {
75    if (tasks.length !== 1) return false
76    const t = tasks[0]!
77    return (
78      t.type === 'remote_agent' &&
79      t.isUltraplan === true &&
80      t.ultraplanPhase !== undefined
81    )
82  }