Panel „co robią agenci”: subagenci sesji ze statusem, REALNYM modelem każdego żądania (turn.step), tokenami i liczbą narzędzi; rozjazd tagu [S5.5] w opisie z…

Mod Claude Code (API „function hooks”, early access; pisany pod 2.1.293, testy zielone też na CLI 2.1.289). M1 z planu modów (2026-10-10). Zamyka lukę kanonu: tag [S5.5MD] w opisie subagenta jest deklaracją, a prawdę niesie message.model w transkrypcie — tu widać ją W TRAKCIE pracy, razem z tokenami, liczbą narzędzi i licznikiem spawnów widzianych przez mod (obok progu hooka kanon-agent-limit, który pyta od 10. próby narzędzia Agent). v0.1.1 = poprawki po recenzji sceptyka [OP5.5HI] 2026-10-10 (5 🟡, 4 🟢, 0 🔴).
[Aktywni] Wszyscy Suma Zamknij
agenci: 3 (2 aktywnych · 1 zakończonych) · spawny (mod): 3 · próg hooka kanon-agent-limit: 10 prób Agent
● [S5.5] czytelnik — „[S5.5MD] Rekonesans katalogu src” · running · 4 żądań · 12 narzędzi · 38k in (31k cache) · 1k out · 2 min
● [OP5.5] recenzent — „[S5.5HI] Recenzja diffu” · running · 1 żądań · 0 narzędzi · 9k in (0k cache) · 0k out · 10 s · ⚠ tag≠model (S5.5→OP5.5)
agent.spawn → start wiersza (opis, typ, model rozstrzygnięty przez silnik); turn.step z agentId → KAŻDE żądanie agenta: model, który odpowiedział (usage.model), tokeny in/out/cache, liczba narzędzi, o które poprosiła odpowiedź (toolUses); turn.complete z agentId → koniec pętli; $.agent.list() → status na żywo przy rysowaniu (pending|running|waiting|idle|completed|failed|killed). Kroki bez agentId liczy osobno jako „główny wątek” (zakładka Suma).OP5.5|OP5|OP4.8|F5.1|F5|S5.5|S5|H5.5|H4.5) porównany z kodem z id modelu z usage (rodzina + wersja Z GRANICĄ: claude-opus-5-5-20260101 → OP5.5, us.anthropic.claude-sonnet-5-5-…-v1:0 → S5.5, claude-opus-5-6 → OP5.6, nie OP5) → ⚠ tag≠model (S5.5→OP5.5) na żółto. Wiersz bez tagu, bez odpowiedzi albo z id spoza wzorca nie ostrzega.agent.spawn (także odrzucone { deny } niżej, także z wnętrza subagentów i z $.agent.spawn innych pluginów). Hook kanon-agent-limit liczy co innego: PRÓBY narzędzia Agent w transkrypcie głównej pętli (odmowy PreToolUse/uprawnień nie docierają do modu). Próg hooka (10) jest w etykiecie jako punkt odniesienia, nie jako wspólny licznik.usage: null — przerwanie, błąd API) nie liczy się jako żądanie i nie ustawia modelu; wiersz bez policzonych kroków pokazuje „bez kroków widzianych przez mod” zamiast zer, wiersz prosto z listy czas „—” (mod nie zna startu agenta).turn.complete znów otwiera wiersz — linia statusu (▶ = bez koniec) i panel (status z listy) liczą zgodnie.$.state) zostaje, bufor nieznanych id startuje od nowa./agenci — otwiera panel (poproszony przez człowieka seatuje się przy dowolnej szerokości) i daje podsumowanie tekstowe; /agenci tekst — tylko tekst; /agenci zamknij — zamyka. Linia statusu: agenci 2▶ 1✓.pending|running|waiting|idle), Wszyscy, Suma (tokeny per kod modelu + główny wątek).tool.check, nie pisze plików, nie używa sieci ani modelu. Hooki na zdarzeniach bramkujących mają try/catch po next — błąd modu nie dotyka tury.Linie walidatora (claude plugin validate, 2.1.289 i 2.1.293):
hooks: session.start, agent.spawn, turn.step, turn.complete, command.run{command=agenci}, ui.render{component=Pane, requestId=agenci}
calls: $.agent.list, $.clock.now, $.command.register, $.state.get, $.state.set, $.ui.close, $.ui.open, $.ui.resolve, $.ui.status
Brak $.fs, $.http, $.process, $.model, tool.call, tool.check — to kryterium „gotowe” z planu. Walidator nie dowodzi zasad sam z siebie (nie pokazuje decyzji tool.check ani ścieżek zapisu) — dowodem są testy i recenzja kodu.
Na stałe: ścieżka modu w env.CLAUDE_CODE_PLUGIN_DIRS w ~/.claude/settings.json (z backupem) — działa od NOWEJ sesji (także desktop); folder jest obserwowany, zapis pliku przeładowuje mod. Jedna sesja z terminala bez env:
claude --plugin-dir ~/Developer/claude-mods/kanon-agenci
Panel otwiera /agenci (nie timer — pane otwarty bez prośby czeka do 144 kolumn). Rysuje się w terminalu i w zakładce Code aplikacji desktop; panel czatu VS Code wykonuje tylko hooki (komenda tekst działa wszędzie).
claude plugin validate ~/Developer/claude-mods/kanon-agenci
claude plugin test ~/Developer/claude-mods/kanon-agenci
16 testów (tests/agenci.test.ts): wiersz po spawn + 2 krokach (terminal i desktop), rozjazd tag≠model, kod modelu z granicą (data, prefiks dostawcy, opus-5-6 → OP5.6, id spoza wzorca bez ⚠), krok bez odpowiedzi → nie liczony, bez ⚠, krok forka poza listą → każdy krok pominięty i jedno pytanie listy na id, krok agenta z listy bez spawnu → wiersz, krok wyprzedza spawn (lista pusta → bufor → wiersz; lista zna → scalenie danych spawnu), agent z listy bez kroków widoczny (status z listy wygrywa; czas „—”; zakładki), turn.complete → completed + linia statusu, agent wznowiony po turn.complete → panel i linia statusu zgodne, Suma per model + główny wątek, /agenci open/close (isPlaced:false → powód w tekście), pusty stan, spawn odrzucony ({ deny }) → licznik rośnie, wiersza brak. Bez testu (driver $.agent.spawn kitu nie niesie workflow): pominięcie agentów workflow — wyjątek jest w kodzie. Cisza na powierzchniach bez panelu: linia walidatora hooks: nie zawiera AbovePrompt ani StatusLine — mod rysuje tylko własny Pane i wpis $.ui.status. Kryteria planu (c)(d)(e) — ręczny przegląd w terminalu i desktopie ze zrzutem — czekają na właściciela.
Lekcje (2026-10-10): hook turn.step to generator — const r = yield* next(e) i return r; w teście hook dna też jest generatorem (async function* (_$, e) { return wynik }), a driver $.turn.step(input) trzeba skonsumować (for await) przed await s.result; on(...) testu nie przyjmuje drugiej rejestracji tego samego zdarzenia („registered twice”) — warianty dna przez parametr; driver $.agent.spawn nie niesie workflow, więc wyjątek dla agentów workflow jest w kodzie, ale bez testu; krok „z dna” spawnu ($.turn.step wywołany w hooku dna agent.spawn, zanim odda wynik) odgrywa wyścig spawn/krok; update($, …) zwraca zapisaną wartość — linia statusu liczy z niej, nie z ponownego read (d.ts: „Every get of one dispatch reads one moment”).
git revert commitu modu, usunięcie ścieżki z CLAUDE_CODE_PLUGIN_DIRS (albo cp backupu ~/.claude/settings.json.bak-<ts>), rm -rf ~/.claude/dev-mods/<session_id>/kanon-agenci.
hooks/register.tsx 319 lines1// kanon-agenci — panel „co robią agenci” (mod Claude Code, API function hooks 2.1.293; early access). v0.1.1 po recenzji sceptyka.
2// Dane z silnika, nie z deklaracji: `agent.spawn` (start, opis, typ, model rozstrzygnięty), `turn.step` (KAŻDE żądanie do modelu
3// z `agentId`, model który odpowiedział i tokeny z `usage`), `turn.complete` (koniec pętli agenta), `$.agent.list()` (status na żywo
4// przy rysowaniu). Zamyka lukę kanonu: tag `[S5.5MD]` w opisie subagenta jest deklaracją, a prawdę niesie `message.model`
5// transkryptu — tu widać ją w trakcie pracy (wiersz z `⚠ tag≠model`).
6// Licznik „spawny (mod)” to spawny, które DOTARŁY do hooka `agent.spawn` (także odrzucone przez `{ deny }` niżej, także z wnętrza
7// subagentów i z `$.agent.spawn` innych pluginów). To NIE jest licznik hooka `kanon-agent-limit`, który liczy PRÓBY użycia narzędzia
8// Agent w transkrypcie głównej pętli (odmowy PreToolUse/uprawnień nie docierają do modu) — próg hooka (10) pokazujemy jako punkt odniesienia.
9// Mod wyłącznie obserwuje: nie zmienia modelu ani promptu agentów, nie wstrzymuje spawnów, nie pisze plików, nie używa sieci,
10// nie wywołuje modelu, nie zatwierdza niczego. Hooki na zdarzeniach bramkujących mają try/catch po `next` i `.catch` → `next(e)`.
11// Czego NIE widać (typy 2.1.293): agentów workflow (bez wiersza i bez licznika — także zdalnych, bez lokalnej pętli) i forków silnika
12// (kompaktacja, pamięć): ich kroki liczy `pominiete`; teammate’a w osobnym panelu terminala. Krok bez odpowiedzi (`usage: null`:
13// przerwanie, błąd API) nie liczy się jako żądanie i nie ustawia modelu. Panel otwiera `/agenci` albo przycisk — nie timer.
14import { atom, read, update } from 'claude-code'
15import type { AgentInfo, EngineInterface, Register, TurnStepResult } from 'claude-code'
16
17import type { Glowny, Wiersz, Zakladka } from '../types'
18
19const PANE = 'agenci'
20const LIMIT_AGENT = 10 // próg hooka kanon-agent-limit (PreToolUse Agent) — punkt odniesienia, nie ten sam licznik
21
22// Skaner walidatora wymaga literałów plugin/key w każdym atomie — stąd powtórzona nazwa.
23const wiersze = atom({ plugin: 'kanon-agenci', key: 'wiersze' } as const, [] as Wiersz[])
24const glowny = atom({ plugin: 'kanon-agenci', key: 'glowny' } as const, {
25 kroki: 0,
26 narzedzia: 0,
27 wejscie: 0,
28 wyjscie: 0,
29 cache: 0,
30 model: null,
31} as Glowny)
32const spawny = atom({ plugin: 'kanon-agenci', key: 'spawny' } as const, 0)
33const pominiete = atom({ plugin: 'kanon-agenci', key: 'pominiete' } as const, 0)
34const zakladka = atom({ plugin: 'kanon-agenci', key: 'zakladka' } as const, 'aktywni' as Zakladka)
35
36// Kod modelu jak tag kanonu `[<MODEL><EFFORT>]` (kanon-tag.sh): rodzina + wersja z id modelu, Z GRANICĄ — dozwolona data `-YYYYMMDD`
37// i sufiks dostawcy `-v1:0`, prefiks dostawcy (`us.anthropic.`) ignorowany. `claude-opus-5-6` → OP5.6 (nie OP5), id spoza wzorca → null.
38const ID_MODELU = /claude-(opus|sonnet|haiku|fable)-(\d{1,2})(?:-(\d{1,2}))?(?:-\d{8})?(?:-v\d+(?::\d+)?)?$/
39const RODZINA: Record<string, string> = { opus: 'OP', sonnet: 'S', haiku: 'H', fable: 'F' }
40const TAG = /\[(OP5\.5|OP5|OP4\.8|F5\.1|F5|S5\.5|S5|H5\.5|H4\.5)(LO|MD|HI|XH|MX)?\]/
41const NAZWY: Record<Zakladka, string> = { aktywni: 'Aktywni', wszyscy: 'Wszyscy', suma: 'Suma' }
42const AKTYWNE = new Set(['pending', 'running', 'waiting', 'idle'])
43
44const kod = (model: string | null): string | null => {
45 const m = model ? ID_MODELU.exec(model) : null
46 return m ? `${RODZINA[m[1]]}${m[2]}${m[3] ? `.${m[3]}` : ''}` : null
47}
48const kodLubId = (model: string | null): string => (model ? (kod(model) ?? model) : '?')
49const tagZ = (opis: string): string | null => TAG.exec(opis)?.[1] ?? null
50const k = (n: number) => (n >= 1_000_000 ? `${(n / 1_000_000).toFixed(1)}M` : `${Math.round(n / 1000)}k`)
51const czas = (ms: number) => (ms < 60_000 ? `${Math.max(0, Math.round(ms / 1000))} s` : `${Math.round(ms / 60_000)} min`)
52const skroc = (s: string, n: number) => (s.length > n ? `${s.slice(0, n - 1)}…` : s)
53
54type Liczniki = { model: string | null; kroki: number; narzedzia: number; wejscie: number; wyjscie: number; cache: number }
55type Krok = { model: string; narzedzia: number; wejscie: number; wyjscie: number; cache: number }
56
57const PUSTE: Liczniki = { model: null, kroki: 0, narzedzia: 0, wejscie: 0, wyjscie: 0, cache: 0 }
58
59/** Liczby z jednego kroku; null, gdy żadna odpowiedź nie przyszła (`usage: null`) — takiego kroku nie liczymy. */
60const krokZ = (r: TurnStepResult): Krok | null => {
61 const u = r.usage
62 if (!u) return null
63 return {
64 model: u.model,
65 narzedzia: r.toolUses.length,
66 wejscie: u.input_tokens + u.cache_creation_input_tokens + u.cache_read_input_tokens,
67 wyjscie: u.output_tokens,
68 cache: u.cache_read_input_tokens,
69 }
70}
71const dolicz = <T extends Liczniki>(w: T, s: Krok): T => ({
72 ...w,
73 model: s.model,
74 kroki: w.kroki + 1,
75 narzedzia: w.narzedzia + s.narzedzia,
76 wejscie: w.wejscie + s.wejscie,
77 wyjscie: w.wyjscie + s.wyjscie,
78 cache: w.cache + s.cache,
79})
80
81const nowyWiersz = (id: string, opis: string, typ: string, start: number | null, zrodlo: Wiersz['zrodlo'], modelZadany: string | null): Wiersz => ({
82 id,
83 opis,
84 typ,
85 tag: tagZ(opis),
86 modelZadany,
87 ...PUSTE,
88 start,
89 koniec: null,
90 zrodlo,
91})
92
93const statusWiersza = (w: Wiersz, zListy: AgentInfo | undefined): string => zListy?.status ?? (w.koniec !== null ? 'completed' : 'running')
94
95const rozjazd = (w: Wiersz): string | null => {
96 if (!w.tag || !w.model) return null
97 const realny = kod(w.model)
98 if (!realny) return null // id spoza wzorca — nie zgadujemy
99 return realny === w.tag ? null : `${w.tag}→${realny}`
100}
101
102const liniaAgenta = (w: Wiersz, status: string, teraz: number): string => {
103 const r = rozjazd(w)
104 const liczniki = w.kroki
105 ? `${w.kroki} żądań · ${w.narzedzia} narzędzi · ${k(w.wejscie)} in (${k(w.cache)} cache) · ${k(w.wyjscie)} out`
106 : 'bez kroków widzianych przez mod'
107 const trwanie = w.start === null ? '—' : czas((w.koniec ?? teraz) - w.start)
108 return `[${kodLubId(w.model ?? w.modelZadany)}] ${w.typ} — „${skroc(w.opis, 44)}” · ${status} · ${liczniki} · ${trwanie}` + (r ? ` · ⚠ tag≠model (${r})` : '')
109}
110const liniaGlownego = (g: Glowny): string =>
111 `główny wątek [${kodLubId(g.model)}]: ${g.kroki} żądań · ${g.narzedzia} narzędzi · ${k(g.wejscie)} in (${k(g.cache)} cache) · ${k(g.wyjscie)} out`
112const licznikSpawnow = (n: number) => `spawny (mod): ${n} · próg hooka kanon-agent-limit: ${LIMIT_AGENT} prób Agent`
113
114// Kroki z id bez wiersza i bez wpisu na liście, zebrane per id (lista pytana raz na id). Zwykle forki silnika i agenci workflow —
115// liczone w `pominiete`. Gdy jednak nadejdzie `agent.spawn` tego id (kolejność spawn/krok nie jest gwarantowana), bufor wchodzi
116// do wiersza, a `pominiete` oddaje te kroki. Pamięć modułu: po reloadzie startuje od nowa.
117const nieznane = new Map<string, Liczniki>()
118
119/** Wiersze do pokazania: stan modu + agenci z listy, których spawnu mod nie widział (np. załadowany po ich starcie). */
120async function zebrane($: EngineInterface): Promise<{ wiersze: Wiersz[]; statusy: Map<string, AgentInfo>; teraz: number }> {
121 const zapisane = await read($, wiersze)
122 let lista: AgentInfo[] = []
123 try {
124 lista = await $.agent.list()
125 } catch {
126 lista = []
127 }
128 const teraz = await $.clock.now()
129 const statusy = new Map(lista.map(a => [a.id, a] as const))
130 const znane = new Set(zapisane.map(w => w.id))
131 const zListy = lista.filter(a => !znane.has(a.id)).map(a => nowyWiersz(a.id, a.description, a.type, null, 'lista', null))
132 return { wiersze: [...zapisane, ...zListy], statusy, teraz }
133}
134
135async function zapiszKrok($: EngineInterface, e: { agentId?: string }, r: TurnStepResult): Promise<void> {
136 const s = krokZ(r)
137 if (!s) return
138
139 if (!e.agentId) {
140 await update($, glowny, g => dolicz(g, s))
141 return
142 }
143
144 const id = e.agentId
145 let nowy: Wiersz | null = null
146 if (!(await read($, wiersze)).some(w => w.id === id)) {
147 const buf = nieznane.get(id)
148 if (buf) {
149 nieznane.set(id, dolicz(buf, s))
150 await update($, pominiete, n => n + 1)
151 return
152 }
153 const info = (await $.agent.list()).find(a => a.id === id)
154 if (!info) {
155 nieznane.set(id, dolicz(PUSTE, s))
156 await update($, pominiete, n => n + 1)
157 return
158 }
159 nowy = nowyWiersz(id, info.description, info.type, await $.clock.now(), 'lista', null) // start = pierwszy widziany krok
160 }
161 // Jedno `update`: dopisanie wiersza (jeśli spawn go tymczasem nie założył) i doliczenie kroku. Krok po `turn.complete`
162 // (agent wznowiony wiadomością, teammate) znów otwiera wiersz — `koniec: null`.
163 const l = await update($, wiersze, l => {
164 const baza = l.some(w => w.id === id) ? l : nowy ? [...l, nowy] : l
165 return baza.map(w => (w.id === id ? { ...dolicz(w, s), koniec: null } : w))
166 })
167 pasekStanu($, l)
168}
169
170/** Wpis w linii statusu: `agenci 2▶ 1✓` (▶ = wiersz bez `koniec`); znika, gdy nie ma żadnego. Liczy z wartości, którą właśnie zapisano. */
171function pasekStanu($: EngineInterface, l: Wiersz[]): void {
172 if (l.length === 0) {
173 void $.ui.status(undefined)
174 return
175 }
176 const biegna = l.filter(w => w.koniec === null).length
177 void $.ui.status(`agenci ${biegna}▶ ${l.length - biegna}✓`)
178}
179
180async function podsumowanie($: EngineInterface): Promise<string> {
181 const { wiersze: l, statusy, teraz } = await zebrane($)
182 const [n, pom, g] = await Promise.all([read($, spawny), read($, pominiete), read($, glowny)])
183 const ze = l.map(w => ({ w, status: statusWiersza(w, statusy.get(w.id)) }))
184 const biegna = ze.filter(x => AKTYWNE.has(x.status)).length
185 const naglowek =
186 `agenci: ${l.length} (${biegna} aktywnych · ${l.length - biegna} zakończonych) · ${licznikSpawnow(n)}` +
187 (pom ? ` · pominięte kroki (workflow, forki silnika): ${pom}` : '')
188 const linie = ze.map(x => `- ${liniaAgenta(x.w, x.status, teraz)}`)
189 return [naglowek, ...(l.length ? linie : ['Brak agentów w tej sesji.']), ...(g.kroki ? [liniaGlownego(g)] : [])].join('\n')
190}
191
192export const register: Register = on => {
193 on('session.start', async ($, e, next) => {
194 await $.command.register({
195 name: 'agenci',
196 description: 'Panel „co robią agenci”: status, realny model, tokeny, narzędzia; „tekst” = tylko podsumowanie, „zamknij” = zamknij panel',
197 argumentHint: '[tekst|zamknij]',
198 })
199 return next(e)
200 })
201
202 on('agent.spawn', async ($, e, next) => {
203 const r = await next(e)
204 try {
205 // Agenci workflow: bez wiersza i bez licznika (zdalny nie ma lokalnej pętli ani wpisu na liście — wiersz wisiałby „running” na zawsze);
206 // ich kroki, jeśli przyjdą, trafiają do `pominiete`.
207 if (!e.workflow) {
208 await update($, spawny, n => n + 1)
209 if (!('deny' in r && r.deny) && 'agentId' in r && r.agentId) {
210 const id = r.agentId
211 const teraz = await $.clock.now()
212 const modelZadany = 'model' in r && typeof r.model === 'string' ? r.model : null
213 const buf = nieznane.get(id) // kroki, które wyprzedziły ten hook
214 if (buf) {
215 nieznane.delete(id)
216 await update($, pominiete, n => Math.max(0, n - buf.kroki))
217 }
218 const l = await update($, wiersze, l => {
219 const i = l.findIndex(w => w.id === id)
220 if (i >= 0) {
221 // wiersz założony z listy przez krok, który wyprzedził spawn — dane spawnu dochodzą, liczniki zostają
222 const w = { ...l[i], opis: e.description, typ: e.subagentType, tag: tagZ(e.description), modelZadany, zrodlo: 'spawn' as const }
223 return l.map((x, j) => (j === i ? w : x))
224 }
225 const w = nowyWiersz(id, e.description, e.subagentType, teraz, 'spawn', modelZadany)
226 return [...l, buf ? { ...w, ...buf } : w]
227 })
228 pasekStanu($, l)
229 }
230 }
231 } catch {
232 // obserwator: błąd zapisu nie dotyka spawnu
233 }
234 return r
235 }).catch(($, e, next) => next(e))
236
237 on('turn.step', async function* ($, e, next) {
238 const r = yield* next(e)
239 try {
240 await zapiszKrok($, e, r)
241 } catch {
242 // obserwator: błąd zapisu nie dotyka kroku
243 }
244 return r
245 })
246
247 on('turn.complete', async ($, e, next) => {
248 const r = await next(e)
249 try {
250 if (e.agentId) {
251 const id = e.agentId
252 const teraz = await $.clock.now()
253 const l = await update($, wiersze, l => l.map(w => (w.id === id ? { ...w, koniec: teraz, model: e.usage?.model ?? w.model } : w)))
254 pasekStanu($, l)
255 }
256 } catch {
257 // obserwator
258 }
259 return r
260 }).catch(($, e, next) => next(e))
261
262 on('command.run', { command: 'agenci' }, async ($, e) => {
263 const arg = e.args.trim().toLowerCase()
264 if (arg === 'zamknij') {
265 await $.ui.close({ id: PANE })
266 return { text: 'Panel agentów zamknięty.' }
267 }
268 const tekst = await podsumowanie($)
269 if (arg === 'tekst') return { text: tekst }
270 const o = await $.ui.open({ id: PANE, title: 'Agenci', closeOnEscape: true })
271 return { text: o.isPlaced ? tekst : `${tekst}\nPanel czeka na szersze okno: ${o.reason}` }
272 })
273
274 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
275 const { Box, Button, Text } = $.ui.resolve(e)
276 const { wiersze: l, statusy, teraz } = await zebrane($)
277 const [n, pom, g, zak] = await Promise.all([read($, spawny), read($, pominiete), read($, glowny), read($, zakladka)])
278 const ze = l.map(w => ({ w, status: statusWiersza(w, statusy.get(w.id)) }))
279 const biegna = ze.filter(x => AKTYWNE.has(x.status)).length
280 const widoczne = zak === 'aktywni' ? ze.filter(x => AKTYWNE.has(x.status)) : ze
281
282 const kolor = (status: string) => (status === 'running' ? 'success' : status === 'failed' || status === 'killed' ? 'error' : 'warning')
283
284 const suma = new Map<string, { agentow: number; wejscie: number; wyjscie: number; cache: number }>()
285 for (const { w } of ze) {
286 const kk = kodLubId(w.model ?? w.modelZadany)
287 const s = suma.get(kk) ?? { agentow: 0, wejscie: 0, wyjscie: 0, cache: 0 }
288 suma.set(kk, { agentow: s.agentow + 1, wejscie: s.wejscie + w.wejscie, wyjscie: s.wyjscie + w.wyjscie, cache: s.cache + w.cache })
289 }
290
291 return (
292 <Box flexDirection="column">
293 <Box gap={1}>
294 {(['aktywni', 'wszyscy', 'suma'] as const).map(z => (
295 <Button key={`zak-${z}`} plain label={z === zak ? `[${NAZWY[z]}]` : ` ${NAZWY[z]} `} onPress={() => update($, zakladka, () => z)} />
296 ))}
297 <Button key="zamknij" plain dimColor label="Zamknij" onPress={() => $.ui.close({ id: PANE })} />
298 </Box>
299 <Text bold={n >= LIMIT_AGENT - 1} color={n >= LIMIT_AGENT ? 'warning' : undefined}>
300 {`agenci: ${l.length} (${biegna} aktywnych · ${l.length - biegna} zakończonych) · ${licznikSpawnow(n)}`}
301 </Text>
302 {pom > 0 && <Text dimColor>{`pominięte kroki (workflow, forki silnika — kompaktacja, pamięć): ${pom}`}</Text>}
303 {zak !== 'suma' && widoczne.length === 0 && <Text dimColor>{l.length === 0 ? 'Brak agentów w tej sesji.' : 'Żaden agent nie biegnie.'}</Text>}
304 {zak !== 'suma' &&
305 widoczne.map(({ w, status }) => (
306 <Text key={w.id} wrap="truncate-end" color={rozjazd(w) ? 'warning' : kolor(status)} dimColor={!AKTYWNE.has(status) && !rozjazd(w)}>
307 {`${AKTYWNE.has(status) ? '●' : '○'} ${liniaAgenta(w, status, teraz)}`}
308 </Text>
309 ))}
310 {zak === 'suma' &&
311 [...suma.entries()].map(([kk, s]) => (
312 <Text key={`suma-${kk}`}>{`${kk}: ${s.agentow} agentów · ${k(s.wejscie)} in (${k(s.cache)} cache) · ${k(s.wyjscie)} out`}</Text>
313 ))}
314 {zak === 'suma' && <Text dimColor>{g.kroki ? liniaGlownego(g) : 'główny wątek: brak kroków od załadowania modu'}</Text>}
315 </Box>
316 )
317 })
318}
319types/index.d.ts 56 lines1// Kontrakt stanu modu kanon-agenci ($.state sesji; po reloadzie modułu wartości zostają, zmienne modułu startują od nowa).
2export type Wiersz = {
3 /** Id pętli agenta: `agent.spawn` → `agentId`, to samo niosą `turn.step`/`tool.call`/`turn.complete` i `$.agent.list()`. */
4 id: string
5 /** Opis zadania z wywołania Agent (`description`) — tu siedzi deklarowany tag `[S5.5MD]`. Z listy, póki spawn nie dopisze swojego. */
6 opis: string
7 /** Typ agenta (`subagentType`): czytelnik, sceptyk, general-purpose, Explore… */
8 typ: string
9 /** Kod modelu z tagu w opisie (S5.5, OP5.5, F5.1…) albo null, gdy opis tagu nie ma. */
10 tag: string | null
11 /** Model, jaki silnik rozstrzygnął przy starcie (`AgentSpawnResult.model`); null, póki spawnu mod nie widział. */
12 modelZadany: string | null
13 /** Model, który ODPOWIEDZIAŁ w ostatnim policzonym kroku (`TurnStepResult.usage.model`); null bez żadnej odpowiedzi. */
14 model: string | null
15 /** Liczba żądań do modelu widzianych przez mod — tylko kroki z odpowiedzią (`usage` nie-null). */
16 kroki: number
17 /** Liczba wywołań narzędzi, o które poprosiły odpowiedzi (`toolUses.length` policzonych kroków). */
18 narzedzia: number
19 /** Tokeny wejścia razem: niecache + zapis cache + odczyt cache. */
20 wejscie: number
21 /** Tokeny wyjścia. */
22 wyjscie: number
23 /** Tokeny wejścia podane z cache (`cache_read_input_tokens`). */
24 cache: number
25 /**
26 * `$.clock.now()` przy `agent.spawn` (zrodlo `spawn`) albo przy PIERWSZYM widzianym kroku (zrodlo `lista`) — nie start agenta;
27 * null dla wiersza rysowanego prosto z listy (czas pokazuje się jako „—”).
28 */
29 start: number | null
30 /** `$.clock.now()` przy `turn.complete` pętli agenta; null, póki biegnie — kolejny krok (agent wznowiony, teammate) znów zeruje. */
31 koniec: number | null
32 /** Skąd wiersz: `spawn` = hook `agent.spawn` (także dopisany później do wiersza z listy), `lista` = pierwszy krok agenta, którego spawnu mod nie widział. */
33 zrodlo: 'spawn' | 'lista'
34}
35
36export type Glowny = { kroki: number; narzedzia: number; wejscie: number; wyjscie: number; cache: number; model: string | null }
37
38export type Zakladka = 'aktywni' | 'wszyscy' | 'suma'
39
40declare module 'claude-code' {
41 interface PluginState {
42 'kanon-agenci': {
43 wiersze: Wiersz[]
44 glowny: Glowny
45 /**
46 * Spawny, które dotarły do hooka `agent.spawn` (bez workflow; z odmowami `{ deny }` niżej). Inny licznik niż hook
47 * `kanon-agent-limit` (próby narzędzia Agent w transkrypcie głównej pętli) — próg hooka (10) służy za punkt odniesienia.
48 */
49 spawny: number
50 /** Kroki z `agentId`, którego nie zna ani spawn, ani `$.agent.list()` (workflow, forki silnika: kompaktacja, pamięć) — liczone, nie rysowane. */
51 pominiete: number
52 zakladka: Zakladka
53 }
54 }
55}
56