Painel lateral com subagents em pixel art, bolinhas de status das tarefas e barra do plano acima do prompt

Mods pessoais para Claude Code (terminal e aba Code do app desktop).
| Mod | O que faz |
|---|---|
agentes-pixel | Painel lateral com pixel art de cada subagent (modelo, status, tempo, tokens, resumo), bolinhas de status das tarefas e barra do plano acima do prompt. /agentes reabre o painel. |
polimento-visual | Pill de contexto usado/total abaixo do prompt, borda laranja nas suas mensagens e ícones coloridos por tipo de ferramenta. |
claude plugin marketplace add DiogoHSM/claude-mods
claude plugin install agentes-pixel@diogo-mods --scope user
claude plugin install polimento-visual@diogo-mods --scope user
Ou, no prompt do Claude Code:
/plugin install agentes-pixel --marketplace DiogoHSM/claude-mods
/plugin install polimento-visual --marketplace DiogoHSM/claude-mods
No app desktop: + ao lado do prompt → Plugins → Add marketplace com DiogoHSM/claude-mods → Add plugin.
claude plugin marketplace add ~/Developer/Github/claude-mods
claude plugin install agentes-pixel@diogo-mods --scope user
Depois de editar um mod, rode /reload-plugins.
claude plugin validate agentes-pixel
claude plugin test agentes-pixelhooks/register.tsx 579 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register, Timer } from 'claude-code'
3
4import type { AgentRow, AgentState, TaskDot, TaskState } from '../types'
5
6const PANE = 'agentes-pixel'
7const MAIN = 'main'
8
9const agents = atom({ plugin: 'agentes-pixel', key: 'agents' } as const, [])
10const tasks = atom({ plugin: 'agentes-pixel', key: 'tasks' } as const, {})
11const now = atom({ plugin: 'agentes-pixel', key: 'now' } as const, 0)
12
13const HEX: Record<TaskState, string> = {
14 pending: '#888780',
15 running: '#D97757',
16 done: '#639922',
17 error: '#E24B4A',
18}
19const THEME: Record<TaskState, 'inactive' | 'claude' | 'success' | 'error'> = {
20 pending: 'inactive',
21 running: 'claude',
22 done: 'success',
23 error: 'error',
24}
25const TASK_LABEL: Record<TaskState, string> = {
26 pending: 'planejado',
27 running: 'executando',
28 done: 'concluído',
29 error: 'erro',
30}
31const AGENT_LABEL: Record<AgentState, string> = {
32 running: 'executando',
33 done: 'concluído',
34 error: 'erro',
35}
36const AGENT_TASK: Record<AgentState, TaskState> = {
37 running: 'running',
38 done: 'done',
39 error: 'error',
40}
41
42type Creature = 'owl' | 'fox' | 'bird' | 'cat' | 'bot'
43
44const SPRITES: Record<Creature, { colors: Record<string, string>; grid: string[] }> = {
45 owl: {
46 colors: { a: '#534AB7', b: '#AFA9EC', e: '#FAEEDA', k: '#26215C', y: '#EF9F27' },
47 grid: ['.a....a.', 'aaaaaaaa', 'aeekkeea', 'aeekkeea', 'aaayyaaa', 'abbbbbba', 'abbbbbba', '.a.aa.a.'],
48 },
49 fox: {
50 colors: { a: '#D85A30', b: '#FAECE7', k: '#4A1B0C' },
51 grid: ['a......a', 'aa....aa', 'aaaaaaaa', 'akaaaaka', 'abaaaaba', '.bbkkbb.', '..bbbb..', '...aa...'],
52 },
53 bird: {
54 colors: { a: '#1D9E75', b: '#9FE1CB', k: '#04342C', y: '#EF9F27' },
55 grid: ['........', '..aaaa..', '.aaaaaa.', '.akaaaay', '.aaaaaa.', '.abbbba.', '..bbbb..', '..y..y..'],
56 },
57 cat: {
58 colors: { a: '#D4537E', b: '#FBEAF0', k: '#4B1528' },
59 grid: ['a......a', 'aa....aa', 'aaaaaaaa', 'akaaaaka', 'aaabbaaa', 'abbkkbba', '.aaaaaa.', '.a....a.'],
60 },
61 bot: {
62 colors: { a: '#888780', b: '#D3D1C7', k: '#2C2C2A' },
63 grid: ['...k....', '.aaaaaa.', '.akaaka.', '.aaaaaa.', '..abba..', 'aaaaaaaa', 'a.aaaa.a', '..a..a..'],
64 },
65}
66
67const GLYPH: Record<Creature, string> = { owl: '🦉', fox: '🦊', bird: '🐦', cat: '🐱', bot: '🤖' }
68
69function family(model: string): { name: string; creature: Creature } {
70 const id = model.toLowerCase()
71 if (id.includes('opus')) return { name: 'Opus', creature: 'owl' }
72 if (id.includes('sonnet')) return { name: 'Sonnet', creature: 'fox' }
73 if (id.includes('haiku')) return { name: 'Haiku', creature: 'bird' }
74 if (id.includes('fable')) return { name: 'Fable', creature: 'cat' }
75
76 return { name: model || 'modelo', creature: 'bot' }
77}
78
79function sprite(creature: Creature, state: AgentState): string {
80 const { colors, grid } = SPRITES[creature]
81 let cells = ''
82 grid.forEach((row, y) =>
83 [...row].forEach((ch, x) => {
84 const fill = ch === 'k' && state === 'error' ? '#E24B4A' : colors[ch]
85 if (fill) cells += `<rect x="${x + 1}" y="${y + 1}" width="1" height="1" fill="${fill}"/>`
86 }),
87 )
88 const bob =
89 state === 'running'
90 ? '<animateTransform attributeName="transform" type="translate" values="0 0;0 -0.6" dur="0.8s" calcMode="discrete" repeatCount="indefinite"/>'
91 : ''
92 const badge =
93 state === 'done'
94 ? `<rect x="8" y="8" width="2" height="2" fill="${HEX.done}"/>`
95 : state === 'error'
96 ? `<rect x="8" y="8" width="2" height="2" fill="${HEX.error}"/>`
97 : ''
98
99 return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10" width="36" height="36" shape-rendering="crispEdges"><g>${cells}${bob}</g>${badge}</svg>`
100}
101
102function escape(text: string): string {
103 return text.replace(/[<>&"']/g, ch => `&#${ch.charCodeAt(0)};`)
104}
105
106function elapsed(ms: number): string {
107 const s = Math.max(0, Math.round(ms / 1000))
108 if (s < 60) return `${s}s`
109 const m = Math.floor(s / 60)
110 if (m < 60) return `${m}m ${s % 60}s`
111
112 return `${Math.floor(m / 60)}h ${m % 60}m`
113}
114
115function tokens(n: number): string {
116 if (n < 1000) return `${n}`
117 if (n < 1_000_000) return `${(n / 1000).toFixed(n < 10_000 ? 1 : 0)}k`
118
119 return `${(n / 1_000_000).toFixed(1)}M`
120}
121
122function oneLine(text: string, max = 90): string {
123 const line = text.replace(/\s+/g, ' ').trim()
124
125 return line.length > max ? `${line.slice(0, max - 1)}…` : line
126}
127
128function toTaskState(status: unknown): TaskState {
129 if (status === 'in_progress') return 'running'
130 if (status === 'completed') return 'done'
131
132 return 'pending'
133}
134
135function moveTo(task: TaskDot, state: TaskState, at: number): TaskDot {
136 if (task.state === state) return task
137 const next: TaskDot = { ...task, state }
138 if (state === 'running') next.startedAt = at
139 if (state === 'done' || state === 'error') next.endedAt = at
140
141 return next
142}
143
144function replaceAll(
145 previous: TaskDot[],
146 items: { subject: string; status: unknown; active?: string }[],
147 at: number,
148): TaskDot[] {
149 return items.map((item, i) => {
150 const old = previous.find(one => one.subject === item.subject)
151 const base: TaskDot = old ?? { id: `p${i}`, subject: item.subject, state: 'pending' }
152
153 return moveTo({ ...base, id: `p${i}`, active: item.active ?? base.active }, toTaskState(item.status), at)
154 })
155}
156
157function applyPlan(previous: TaskDot[], e: Record<string, unknown>, at: number): TaskDot[] {
158 let list = previous
159 if (Array.isArray(e.items)) {
160 const items = e.items.map((item: { content?: unknown; status?: unknown }) => ({
161 subject: String(item.content ?? ''),
162 status: item.status,
163 }))
164 list = replaceAll(list, items, at)
165 }
166 if (e.advance === true) {
167 const current = list.findIndex(task => task.state === 'running')
168 const after = list.findIndex((task, i) => i > current && task.state === 'pending')
169 list = list.map((task, i) =>
170 i === current ? moveTo(task, 'done', at) : i === after ? moveTo(task, 'running', at) : task,
171 )
172 }
173 if (Array.isArray(e.done)) {
174 const done = new Set(e.done.map(n => Number(n) - 1))
175 list = list.map((task, i) => (done.has(i) ? moveTo(task, 'done', at) : task))
176 }
177 if (typeof e.start === 'number') {
178 const start = e.start - 1
179 list = list.map((task, i) => (i === start ? moveTo(task, 'running', at) : task))
180 }
181
182 return list
183}
184
185function failRunning(list: TaskDot[] | undefined, at: number): TaskDot[] | undefined {
186 return list?.map(task => (task.state === 'running' ? moveTo(task, 'error', at) : task))
187}
188
189function dotsSvg(list: TaskDot[]): string {
190 const dots = list
191 .map((task, i) => {
192 const cx = 7 + i * 16
193 const title = `<title>${i + 1}. ${escape(task.subject)} — ${TASK_LABEL[task.state]}</title>`
194 const shape =
195 task.state === 'pending'
196 ? `<circle cx="${cx}" cy="8" r="5" fill="none" stroke="${HEX.pending}" stroke-width="1.5"/>`
197 : `<circle cx="${cx}" cy="8" r="5.5" fill="${HEX[task.state]}">${
198 task.state === 'running'
199 ? '<animate attributeName="opacity" values="1;0.35;1" dur="1.2s" repeatCount="indefinite"/>'
200 : ''
201 }</circle>`
202
203 return `<g>${title}${shape}</g>`
204 })
205 .join('')
206
207 return `<svg xmlns="http://www.w3.org/2000/svg" width="${list.length * 16}" height="16">${dots}</svg>`
208}
209
210const PLAN_W = 700
211
212function planSvg(list: TaskDot[]): string {
213 const n = list.length
214 const gap = 6
215 const width = (PLAN_W - gap * (n - 1)) / n
216 const bars = list
217 .map((task, i) => {
218 const fill = task.state === 'pending' ? 'var(--empty)' : HEX[task.state]
219 const pulse =
220 task.state === 'running'
221 ? '<animate attributeName="opacity" values="1;0.45;1" dur="1.4s" repeatCount="indefinite"/>'
222 : ''
223
224 return `<rect x="${i * (width + gap)}" y="2" width="${width}" height="8" rx="4" fill="${fill}">${pulse}</rect>`
225 })
226 .join('')
227
228 return `<svg xmlns="http://www.w3.org/2000/svg" width="10000" height="12" viewBox="0 0 ${PLAN_W} 12" preserveAspectRatio="none"><style>svg{--empty:#D3D1C7}@media (prefers-color-scheme:dark){svg{--empty:#444441}}</style>${bars}</svg>`
229}
230
231function stepLine(task: TaskDot, i: number, n: number, at: number): string {
232 const time =
233 task.state === 'running' && task.startedAt
234 ? ` · há ${elapsed(at - task.startedAt)}`
235 : task.state === 'done' && task.startedAt && task.endedAt
236 ? ` · em ${elapsed(task.endedAt - task.startedAt)}`
237 : ''
238
239 return `${i + 1}/${n} · ${task.subject} — ${TASK_LABEL[task.state]}${time}`
240}
241
242async function showAgent($: EngineInterface, id: string) {
243 const row = (await read($, agents)).find(one => one.id === id)
244 if (row?.isExpanded) {
245 await update($, agents, list => list.map(one => (one.id === id ? { ...one, isExpanded: false } : one)))
246 return
247 }
248 const shown = await $.ui.view({ agentId: id })
249 if (shown.deny === undefined) return
250 if (!row?.result) {
251 $.ui.toast(
252 row?.state === 'running'
253 ? 'O agent ainda está rodando; o resultado aparece aqui quando terminar.'
254 : row
255 ? 'A resposta do agent está chegando; tente de novo em instantes.'
256 : shown.deny,
257 )
258 return
259 }
260 await update($, agents, list => list.map(one => (one.id === id ? { ...one, isExpanded: true } : one)))
261}
262
263let isOpen = false
264let ticker: Timer | undefined
265
266async function openPane($: EngineInterface) {
267 if (isOpen) return
268 isOpen = true
269 await $.ui.open({ id: PANE, title: 'Agentes' })
270}
271
272async function finish($: EngineInterface, ended: Map<string, AgentState>, at: number) {
273 await update($, agents, list =>
274 list.map(row => {
275 const state = ended.get(row.id)
276
277 return state && row.state === 'running' ? { ...row, state, endedAt: at } : row
278 }),
279 )
280 await update($, tasks, plan => {
281 const next = { ...plan }
282 for (const [id, state] of ended) {
283 const failed = state === 'error' ? failRunning(next[id], at) : next[id]
284 if (failed) next[id] = failed
285 }
286
287 return next
288 })
289}
290
291async function tick($: EngineInterface) {
292 const rows = await read($, agents)
293 const plan = await read($, tasks)
294 const isBusy =
295 rows.some(row => row.state === 'running') ||
296 Object.values(plan).some(list => list.some(task => task.state === 'running'))
297 if (!isBusy) return
298 const at = await $.clock.now()
299 await update($, now, () => at)
300 if (!rows.some(row => row.state === 'running')) return
301 const live = await $.agent.list()
302 const ended = new Map<string, AgentState>()
303 for (const info of live) {
304 if (info.status === 'completed') ended.set(info.id, 'done')
305 if (info.status === 'failed' || info.status === 'killed') ended.set(info.id, 'error')
306 }
307 if (ended.size > 0) await finish($, ended, at)
308}
309
310async function setTasks($: EngineInterface, owner: string, fn: (list: TaskDot[], at: number) => TaskDot[]) {
311 const at = await $.clock.now()
312 await update($, tasks, plan => ({ ...plan, [owner]: fn(plan[owner] ?? [], at) }))
313}
314
315export const register: Register = on => {
316 on('session.start', async ($, e, next) => {
317 await $.command.register({ name: 'agentes', description: 'Abre o painel de agentes e tarefas' })
318 ticker?.cancel()
319 ticker = $.clock.every(1000, () => void tick($))
320 if ((await read($, agents)).length > 0) void openPane($)
321
322 return next(e)
323 })
324
325 on('command.run', { command: 'agentes' }, async $ => {
326 isOpen = false
327 await openPane($)
328
329 return { text: 'Painel de agentes aberto.' }
330 })
331
332 on('agent.spawn', async ($, e, next) => {
333 const spawned = await next(e)
334 if (spawned.deny !== undefined || spawned.agentId === undefined) return spawned
335 const row: AgentRow = {
336 id: spawned.agentId,
337 toolUseId: e.tool_use_id,
338 description: e.description,
339 summary: oneLine(e.prompt),
340 type: e.subagentType,
341 model: spawned.model,
342 state: 'running',
343 startedAt: await $.clock.now(),
344 tokens: 0,
345 }
346 await update($, agents, list => [...list.filter(one => one.id !== row.id), row].slice(-30))
347 void openPane($)
348
349 return spawned
350 }).catch(($, e, next) => next(e))
351
352 on('turn.step', async function* ($, e, next) {
353 const step = yield* next(e)
354 const id = e.agentId
355 const usage = step.usage
356 if (id !== undefined && usage) {
357 const spent =
358 usage.input_tokens + usage.output_tokens + usage.cache_creation_input_tokens + usage.cache_read_input_tokens
359 await update($, agents, list =>
360 list.map(row => (row.id === id ? { ...row, tokens: row.tokens + spent, model: usage.model || row.model } : row)),
361 )
362 }
363
364 return step
365 })
366
367 on('turn.complete', async ($, e, next) => {
368 const done = await next(e)
369 const at = await $.clock.now()
370 if (e.agentId !== undefined) {
371 const state: AgentState = e.reason === 'answer' ? 'done' : 'error'
372 await finish($, new Map([[e.agentId, state]]), at)
373 const id = e.agentId
374 if (done.text) await update($, agents, list => list.map(row => (row.id === id ? { ...row, result: done.text } : row)))
375 } else if (e.reason === 'error') {
376 await update($, tasks, plan => ({ ...plan, [MAIN]: failRunning(plan[MAIN], at) ?? [] }))
377 }
378
379 return done
380 })
381
382 on('tool.call', { tool: 'TaskCreate' }, async ($, e, next) => {
383 const ran = await next(e)
384 const created = ran.deny === undefined ? (ran.result as { task?: { id?: string } } | undefined) : undefined
385 const id = created?.task?.id
386 if (id !== undefined) {
387 const task: TaskDot = { id, subject: e.subject, active: e.activeForm, state: 'pending' }
388 await setTasks($, e.agentId ?? MAIN, list => [...(list.every(one => one.state === 'done') ? [] : list), task])
389 }
390
391 return ran
392 }).catch(($, e, next) => next(e))
393
394 on('tool.call', { tool: 'TaskUpdate' }, async ($, e, next) => {
395 const ran = await next(e)
396 if (ran.deny === undefined && !ran.isError) {
397 await setTasks($, e.agentId ?? MAIN, (list, at) =>
398 e.status === 'deleted'
399 ? list.filter(task => task.id !== e.taskId)
400 : list.map(task =>
401 task.id === e.taskId
402 ? moveTo({ ...task, subject: e.subject ?? task.subject, active: e.activeForm ?? task.active }, e.status ? toTaskState(e.status) : task.state, at)
403 : task,
404 ),
405 )
406 }
407
408 return ran
409 }).catch(($, e, next) => next(e))
410
411 on('tool.call', { tool: 'TodoWrite' }, async ($, e, next) => {
412 const ran = await next(e)
413 if (ran.deny === undefined && !ran.isError) {
414 await setTasks($, e.agentId ?? MAIN, (list, at) =>
415 replaceAll(list, e.todos.map(todo => ({ subject: todo.content, status: todo.status, active: todo.activeForm })), at),
416 )
417 }
418
419 return ran
420 }).catch(($, e, next) => next(e))
421
422 on('tool.call', { tool: 'mcp__ccd_turn__update_plan' }, async ($, e, next) => {
423 const ran = await next(e)
424 if (ran.deny === undefined && !ran.isError) {
425 await setTasks($, e.agentId ?? MAIN, (list, at) => applyPlan(list, e, at))
426 }
427
428 return ran
429 }).catch(($, e, next) => next(e))
430
431 on('tool.call', { tool: 'mcp__ccd_turn__wrap_up' }, async ($, e, next) => {
432 const ran = await next(e)
433 if (ran.deny === undefined && !ran.isError) {
434 await setTasks($, e.agentId ?? MAIN, (list, at) =>
435 list.map(task => (task.state === 'done' || task.state === 'error' ? task : moveTo(task, 'done', at))),
436 )
437 }
438
439 return ran
440 }).catch(($, e, next) => next(e))
441
442 on('ui.render', { component: 'ToolUse' }, async ($, e, next) => {
443 if (e.props.tool !== 'mcp__ccd_turn__update_plan' || e.props.isExpanded) return next(e)
444 const { Box } = $.ui.resolve(e)
445
446 return <Box />
447 })
448
449 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
450 const { Box, Text, Button } = $.ui.resolve(e)
451 const Svg = e.surface === 'terminal' ? undefined : $.ui.resolve(e).Svg
452 const rows = await read($, agents)
453 const plan = await read($, tasks)
454 const at = Math.max(await read($, now), ...rows.map(row => row.endedAt ?? row.startedAt))
455 const ordered = [...rows].sort(
456 (a, b) => Number(b.state === 'running') - Number(a.state === 'running') || b.startedAt - a.startedAt,
457 )
458 const owners = Object.entries(plan).filter(([, list]) => list.length > 0)
459 const ownerLabel = (owner: string) => {
460 if (owner === MAIN) return 'Principal'
461 const row = rows.find(one => one.id === owner)
462
463 return row ? `${family(row.model).name} · ${row.description}` : 'Subagent'
464 }
465
466 return (
467 <Box flexDirection="column" gap={1}>
468 <Text dimColor size="caption">
469 Subagents
470 </Text>
471 {ordered.length === 0 && <Text dimColor>Nenhum subagent nesta sessão ainda.</Text>}
472 {ordered.slice(0, 12).map(row => {
473 const { name, creature } = family(row.model)
474 const color = THEME[AGENT_TASK[row.state]]
475
476 return (
477 <Box key={row.id} flexDirection="row" gap={1} borderStyle="round" borderColor={color} paddingX={1}>
478 {Svg ? (
479 <Svg source={sprite(creature, row.state)} alt={`${name}, ${AGENT_LABEL[row.state]}`} width={36} height={36} isInteractive />
480 ) : (
481 <Text>{GLYPH[creature]}</Text>
482 )}
483 <Box flexDirection="column" flexGrow={1}>
484 <Text>
485 <Text bold>{name}</Text> <Text color={color}>● {AGENT_LABEL[row.state]}</Text>{' '}
486 <Text dimColor>{row.type}</Text>
487 </Text>
488 <Text>{row.description}</Text>
489 <Text dimColor>{row.summary}</Text>
490 <Text dimColor tabular>
491 {elapsed((row.endedAt ?? at) - row.startedAt)} · ~{tokens(row.tokens)} tokens
492 </Text>
493 {row.isExpanded && row.result && (
494 <Box borderStyle="round" borderColor="subtle" paddingX={1} marginTop={1}>
495 <Text>{row.result.length > 4000 ? `${row.result.slice(0, 4000)}…` : row.result}</Text>
496 </Box>
497 )}
498 </Box>
499 <Button key={`ver-${row.id}`} label={row.isExpanded ? 'Ocultar' : 'Ver'} plain onPress={() => void showAgent($, row.id)} />
500 </Box>
501 )
502 })}
503 {owners.length > 0 && (
504 <Text dimColor size="caption">
505 Tarefas
506 </Text>
507 )}
508 {owners.map(([owner, list]) => (
509 <Box key={`t-${owner}`} flexDirection="row" gap={1} alignItems="center">
510 <Box width={22}>
511 <Text dimColor>{oneLine(ownerLabel(owner), 22)}</Text>
512 </Box>
513 {Svg ? (
514 <Svg
515 source={dotsSvg(list)}
516 alt={list.map(task => `${task.subject}: ${TASK_LABEL[task.state]}`).join('; ')}
517 height={16}
518 isInteractive
519 />
520 ) : (
521 <Box flexDirection="row">
522 {list.map(task => (
523 <Text color={THEME[task.state]}>{task.state === 'pending' ? '○ ' : '● '}</Text>
524 ))}
525 </Box>
526 )}
527 </Box>
528 ))}
529 </Box>
530 )
531 })
532
533 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
534 const list = (await read($, tasks))[MAIN] ?? []
535 if (e.props.hasSurvey || list.length < 2) return next(e)
536 const isFinished = list.every(task => task.state === 'done' || task.state === 'error')
537 const at = await read($, now)
538 const { Box, Text } = $.ui.resolve(e)
539 const n = list.length
540 const index = Math.max(
541 0,
542 list.findIndex(task => task.state === 'running') >= 0
543 ? list.findIndex(task => task.state === 'running')
544 : list.findIndex(task => task.state !== 'done'),
545 )
546 const head = list[index]
547 const done = list.filter(task => task.state === 'done').length
548 const percent = Math.round((done / n) * 100)
549 const Svg = e.surface === 'terminal' ? undefined : $.ui.resolve(e).Svg
550 const cell = Math.max(2, Math.floor((e.props.bodyColumns + 1) / n) - 1)
551 const bar =
552 !Svg ? (
553 <Box flexDirection="row">
554 {list.map(task => (
555 <Text color={THEME[task.state]}>{`${'━'.repeat(cell)} `}</Text>
556 ))}
557 </Box>
558 ) : (
559 <Svg source={planSvg(list)} alt={head ? `Etapa atual: ${head.subject}` : 'Plano em andamento'} height={12} />
560 )
561
562 return (
563 <Box key="plano" flexDirection="column">
564 {bar}
565 {isFinished ? (
566 <Text dimColor>{`Plano concluído · ${done}/${n} etapas · ${percent}%`}</Text>
567 ) : head && (
568 <Text dimColor>{`Etapa ${index + 1}/${n} · ${percent}% · ${head.state === 'running' && head.active ? head.active : head.subject} — ${TASK_LABEL[head.state]}`}</Text>
569 )}
570 <Box flexDirection="column" display="none" hover={{ display: 'flex' }}>
571 {list.map((task, i) => (
572 <Text key={`etapa-${i}`} color={THEME[task.state]}>{`● ${stepLine(task, i, n, at)}`}</Text>
573 ))}
574 </Box>
575 </Box>
576 )
577 })
578}
579types/index.d.ts 38 lines1export type AgentState = 'running' | 'done' | 'error'
2
3export type AgentRow = {
4 id: string
5 toolUseId: string
6 description: string
7 summary: string
8 type: string
9 model: string
10 state: AgentState
11 startedAt: number
12 endedAt?: number
13 tokens: number
14 result?: string
15 isExpanded?: boolean
16}
17
18export type TaskState = 'pending' | 'running' | 'done' | 'error'
19
20export type TaskDot = {
21 id: string
22 subject: string
23 active?: string
24 state: TaskState
25 startedAt?: number
26 endedAt?: number
27}
28
29declare module 'claude-code' {
30 interface PluginState {
31 'agentes-pixel': {
32 agents: AgentRow[]
33 tasks: Record<string, TaskDot[]>
34 now: number
35 }
36 }
37}
38