| Obě strany předchozí revize Předchozí verze Následující verze | Předchozí verze |
| ai:platformy:claude-code:statusline [07.07.2026 18:08] – Aktualizace článku o Claude Code status line a subagentStatusLine Petr Nosek | ai:platformy:claude-code:statusline [09.08.2026 23:18] (aktuální) – Aktualizace subagent status line pro aktuální chování label Petr Nosek |
|---|
| ====== Status line pro Claude Code ====== | ====== Status line pro Claude Code ====== |
| |
| //Vytvořeno: **** | Aktualizováno: **~~LASTMOD~~**// | //Vytvořeno: **11.04.2026** | Aktualizováno: **~~LASTMOD~~**// |
| |
| [[ai:platformy:claude-code:start|Claude Code]] umí podle [[https://code.claude.com/docs/en/statusline|oficiální dokumentace]] vykreslovat vlastní status line přes externí příkaz. Tohle je moje aktuální konfigurace: hlavní ''statusLine'' ukazuje stav top-level session a samostatná ''subagentStatusLine'' přidává přehled běžících subagentů v agent panelu. | [[ai:platformy:claude-code:start|Claude Code]] umí podle [[https://code.claude.com/docs/en/statusline|oficiální dokumentace]] vykreslovat vlastní status line přes externí příkaz. Tohle je moje aktuální konfigurace: hlavní ''statusLine'' ukazuje stav top-level session a samostatná ''subagentStatusLine'' přidává přehled běžících subagentů v agent panelu. |
| |
| * Hlavní status line ukazuje pracovní adresář, model, session ID, využití context window, 5h a 7d limity a absolutní spotřebu tokenů. | * Hlavní status line ukazuje pracovní adresář, model, session ID, využití context window, 5h a 7d limity a absolutní spotřebu tokenů. |
| * Subagent status line ukazuje každého subagenta jako vlastní řádek: agent, model, krátký popis, stav, tokeny, trend tokenů, rychlost tokenů za sekundu, běh od startu a případně pracovní adresář. | * Subagent status line ukazuje každého subagenta jako vlastní řádek: agent, model, krátký popis dispatche, živou činnost, stav, zaplnění jeho context window, tokeny, trend tokenů, rychlost tokenů za sekundu, běh od startu a případně pracovní adresář. |
| * Model subagenta se do panelu dostává přes konvenci v ''description'', protože Claude Code ho ve vstupních datech pro ''subagentStatusLine'' neposílá jako samostatné pole. | * Model subagenta se bere z pole ''model'', které Claude Code předává do ''subagentStatusLine''. Název ''subagent_type'' v datech samostatně není; pro přehled se proto zapisuje do ''description''. |
| * Popisky subagentů jsou čitelné i při paralelním běhu více agentů. | * Popisky subagentů jsou čitelné i při paralelním běhu více agentů. |
| |
| </code> | </code> |
| |
| V reálných datech se ukázalo, že task obsahuje zejména pole ''cwd'', ''description'', ''id'', ''label'', ''startTime'', ''status'', ''tokenCount'', ''tokenSamples'' a ''type''. Pole ''type'' je jen obecná hodnota typu ''local_agent'', nikoli název použitého subagenta. Samostatné pole pro model ani název subagenta v datech není. | V reálných datech task obsahuje zejména pole ''cwd'', ''description'', ''id'', ''label'', ''startTime'', ''status'', ''tokenCount'', ''tokenSamples'', ''type'', ''model'' a ''contextWindowSize''. Pole ''type'' je jen obecná hodnota typu ''local_agent'', nikoli název použitého subagenta. Pole ''model'' obsahuje skutečně použitý model, například ''claude-haiku-4-5-20251001''. |
| |
| Proto se používá konvence v ''description'': | Pro název subagenta se používá konvence v ''description'': |
| |
| <code> | <code> |
| <subagent_type> · <model> · <short description> | <subagent_type> · <short description> |
| </code> | </code> |
| |
| |
| <code> | <code> |
| elite-prompt-architect · haiku · rešerše pricing | elite-prompt-architect · rešerše pricing |
| </code> | </code> |
| |
| Skript tento popisek rozparsuje a zobrazí ho jako: | Skript model přečte z ''model'' a popisek rozparsuje. Výsledek vypadá například takto: |
| |
| <code> | <code> |
| elite-prompt-architect │ haiku │ rešerše pricing │ running │ tok:8.1k ▁▄█ 579/s │ 14s | elite-prompt-architect │ haiku │ rešerše pricing │ Reading subagent-statusline.sh │ running │ ctx:2% │ tok:8.1k ▁▄█ 579/s │ 14s |
| </code> | </code> |
| |
| ==== Pravidlo do globálního ''CLAUDE.md'' ==== | ==== Pravidlo ve skillu ''calling-subagents'' ==== |
| |
| Aby skript fungoval dobře i v dalších session, je potřeba dát do globálního ''CLAUDE.md'' pravidlo pro pojmenování dispatchů v Claude Code. Bez toho se sice subagent zobrazí, ale nebude zřejmé, který agent a model běží. | Pro pojmenování dispatchů v Claude Code je tato konvence uložená ve skillu ''calling-subagents''. ''description'' přenáší identitu subagenta, protože ''subagent_type'' není samostatnou součástí dat pro panel. Model se do ''description'' nepíše: Claude Code předává autoritativní hodnotu v poli ''model''. |
| |
| <code markdown> | <code markdown> |
| |
| ``` | ``` |
| <subagent_type> · <model> · <short description> | <subagent_type> · <short description> |
| ``` | ``` |
| |
| Example: `elite-prompt-architect · haiku · rešerše pricing` | Example: `elite-prompt-architect · rešerše pricing` |
| |
| Why this exact shape: the `description` is the **only** human-readable string that | Why this exact shape: the task data behind the subagent status-line panel exposes |
| reaches the Claude Code subagent status-line panel — the task data exposes no | `model`, `status`, `tokenCount` and timings on its own, but it does **not** expose |
| `name` or `model` field, and `subagent_type` isn't surfaced there either. Encoding | `subagent_type` — `type` is just the coarse category `local_agent`. So the agent's |
| them into `description` is the only way the panel can show *which agent, on which | identity reaches the panel only through `description`. That visibility matters most |
| model, doing what*. That visibility matters most exactly when several subagents run | exactly when several subagents run in parallel and you need to tell them apart at a |
| in parallel and you need to tell them apart at a glance. | glance. |
| |
| - Separator is a spaced middot ` · ` — the status line splits the label on it. | - Separator is a spaced middot ` · ` — the status line splits the label on it. |
| - `<model>` = whatever you pass in the `model` param (`haiku`/`sonnet`/`opus`/`fable`); | - The panel's `label` field is **not** the dispatch label. Claude Code computes it as |
| the status line renders it as a colored chip. | `progress.summary || description`, and for `local_agent` it fills `progress.summary` |
| | every 30 s with an LLM-generated activity line ("Reading runAgent.ts"). A status-line |
| | script must therefore read identity from `description` and treat `label` as live |
| | activity only — never the other way round. |
| | - **Never write the model into `description`.** Claude Code sends the real model in |
| | the task's `model` field and the status line renders it as a colored chip itself. |
| | Writing it by hand is redundant, and when the `model` param is omitted there is |
| | nothing truthful to write — placeholders like `session` or `inherit` used to leak |
| | into the visible label. |
| - Keep `<short description>` genuinely short — the panel truncates to terminal width. | - Keep `<short description>` genuinely short — the panel truncates to terminal width. |
| | |
| | Legacy `<subagent_type> · <model> · <description>` labels still render correctly: |
| | the status line detects a model-shaped middle segment and drops it in favour of the |
| | real `model` field. No need to rewrite old agent definitions, but write new |
| | dispatches in the two-part form. |
| |
| This is purely a Claude Code convenience: it relies on the `subagentStatusLine` | This is purely a Claude Code convenience: it relies on the `subagentStatusLine` |
| |
| input=$(cat) | input=$(cat) |
| | |
| | # ── DEBUG (volitelne): SUBAGENT_SL_DEBUG_DIR=/nejaka/slozka -> dump stdin ────── |
| | if [ -n "$SUBAGENT_SL_DEBUG_DIR" ] && [ -d "$SUBAGENT_SL_DEBUG_DIR" ]; then |
| | printf '%s\n' "$input" >> "$SUBAGENT_SL_DEBUG_DIR/raw.jsonl" 2>/dev/null |
| | fi |
| |
| # ── ANSI ──────────────────────────────────────────────────────────────────────── | # ── ANSI ──────────────────────────────────────────────────────────────────────── |
| fable*) echo "$BLUE" ;; | fable*) echo "$BLUE" ;; |
| *) echo "" ;; | *) echo "" ;; |
| | esac |
| | } |
| | |
| | # Model z pole `.model` (např. "claude-haiku-4-5-20251001") -> rodina "haiku". |
| | # Toto je autoritativní zdroj; konvence v description je jen fallback. |
| | model_family() { |
| | case "$(echo "$1" | tr '[:upper:]' '[:lower:]')" in |
| | *haiku*) echo "haiku" ;; |
| | *sonnet*) echo "sonnet" ;; |
| | *opus*) echo "opus" ;; |
| | *fable*) echo "fable" ;; |
| | *) echo "" ;; |
| | esac |
| | } |
| | |
| | # Segment v description, který jen duplikuje/zastupuje model → zahodit, |
| | # ať neteče do popisu ("session", "inherit", "dedeno" = model param vynechán). |
| | is_model_slot() { |
| | [ -n "$(model_color "$1")" ] && return 0 |
| | case "$(echo "$1" | tr '[:upper:]' '[:lower:]')" in |
| | session|inherit|dedeno|zdeděno|zdedeno|default) return 0 ;; |
| | *) return 1 ;; |
| esac | esac |
| } | } |
| echo "$input" | jq -c '.tasks[]?' | while read -r task; do | echo "$input" | jq -c '.tasks[]?' | while read -r task; do |
| id=$(echo "$task" | jq -r '.id // empty'); [ -z "$id" ] && continue | id=$(echo "$task" | jq -r '.id // empty'); [ -z "$id" ] && continue |
| # Primární štítek = label|description (subagent_type ve schématu NENÍ). | # Identita agenta se čte VÝHRADNĚ z `description` (konvence |
| primary=$(echo "$task" | jq -r '(.label // "") as $l | (.description // "") as $d | (.name // "") as $n | # "<subagent_type> · <popis>"); `subagent_type` ve schématu není. |
| | if ($l|length)>0 then $l elif ($d|length)>0 then $d elif ($n|length)>0 then $n else "agent" end') | # POZOR: `label` NENÍ štítek dispatche. Claude Code ho počítá jako |
| | # `progress.summary || description` a pro `type == "local_agent"` do |
| | # `progress.summary` průběžně (à 30 s) zapisuje LLM-generované shrnutí |
| | # aktuální činnosti ("Reading runAgent.ts"). Jakmile se první shrnutí |
| | # objeví, `label` přepíše description a jméno agenta by z něj zmizelo. |
| | # Proto: description = identita, label = živá činnost. Nikdy naopak. |
| | descr=$(echo "$task" | jq -r '.description // ""') |
| | label=$(echo "$task" | jq -r '.label // ""') |
| | name=$(echo "$task" | jq -r '.name // ""') |
| | primary="$descr"; [ -z "$primary" ] && primary="$name" |
| | activity="" |
| | if [ -n "$label" ] && [ "$label" != "$descr" ] && [ "$label" != "$name" ]; then |
| | activity="$label" |
| | fi |
| | # Typy bez description (local_bash: label = příkaz) → label slouží jako štítek. |
| | [ -z "$primary" ] && { primary="$activity"; activity=""; } |
| | [ -z "$primary" ] && primary="agent" |
| # `type` je hrubá kategorie ("local_agent"); ukázat jen když nese info. | # `type` je hrubá kategorie ("local_agent"); ukázat jen když nese info. |
| type=$(echo "$task" | jq -r '.type // ""') | type=$(echo "$task" | jq -r '.type // ""') |
| tcwd=$(echo "$task" | jq -r '.cwd // ""') | tcwd=$(echo "$task" | jq -r '.cwd // ""') |
| samples=$(echo "$task" | jq -c '.tokenSamples // []') | samples=$(echo "$task" | jq -c '.tokenSamples // []') |
| | rawmodel=$(echo "$task" | jq -r '.model // ""') |
| | ctxsize=$(echo "$task" | jq -r '.contextWindowSize // ""') |
| |
| sc=$(status_color "$status") | sc=$(status_color "$status") |
| |
| # Konvence "agent · model · popis" (oddělovač " · " nebo " | ") → 3 barevné části. | # Model: primárně z pole `.model` (Claude Code ho posílá), teprve pak z konvence. |
| # Zpětně kompatibilní: 2 díly "model · popis", 1 díl jen popis. | _model=$(model_family "$rawmodel") |
| | |
| | # Konvence "agent · popis" (historicky i "agent · model · popis"; oddělovač |
| | # " · " nebo " | "). Model-slot v popisku se zahodí, když model už známe. |
| agentseg=""; modelseg="" | agentseg=""; modelseg="" |
| _tmp="${primary// · /$US}"; _tmp="${_tmp// | /$US}" | _tmp="${primary// · /$US}"; _tmp="${_tmp// | /$US}" |
| IFS="$US" read -ra _segs <<< "$_tmp" | IFS="$US" read -ra _segs <<< "$_tmp" |
| _agent=""; _model=""; _popis="" | _agent=""; _popis="" |
| _n=${#_segs[@]} | _n=${#_segs[@]} |
| if [ "$_n" -ge 3 ]; then | if [ "$_n" -ge 2 ]; then |
| _agent="${_segs[0]}"; _model="${_segs[1]}"; _popis="${_segs[2]}" | _agent="${_segs[0]}"; _start=1 |
| for ((i=3; i<_n; i++)); do _popis="$_popis · ${_segs[$i]}"; done | # druhý díl je model-slot ("haiku", "session", "inherit"…) → přeskoč ho, |
| elif [ "$_n" -eq 2 ]; then | # ale jen když víme model odjinud nebo je to skutečně platný model |
| if [ -n "$(model_color "${_segs[1]}")" ]; then _agent="${_segs[0]}"; _model="${_segs[1]}" | if is_model_slot "${_segs[1]}"; then |
| elif [ -n "$(model_color "${_segs[0]}")" ]; then _model="${_segs[0]}"; _popis="${_segs[1]}" | [ -z "$_model" ] && _model=$(model_family "${_segs[1]}") |
| else _agent="${_segs[0]}"; _popis="${_segs[1]}"; fi | _start=2 |
| | fi |
| | for ((i=_start; i<_n; i++)); do |
| | _popis="${_popis:+$_popis · }${_segs[$i]}" |
| | done |
| else | else |
| _popis="$primary" | _popis="$primary" |
| fi | fi |
| # model chip jen když je to známý model; jinak segment vrať do popisu | # jednodílný popisek, který je sám o sobě model → ber jako model, ne jako agenta |
| | if [ "$_n" -eq 1 ] && [ -z "$_model" ] && [ -n "$(model_color "$primary")" ]; then |
| | _model=$(model_family "$primary"); _popis="" |
| | fi |
| if [ -n "$_model" ]; then | if [ -n "$_model" ]; then |
| _mcol=$(model_color "$_model") | _mcol=$(model_color "$_model") |
| if [ -n "$_mcol" ]; then | [ -n "$_mcol" ] && modelseg=$(printf "%b%b%s%b" "$_mcol" "$BOLD" "$_model" "$RESET") |
| modelseg=$(printf "%b%b%s%b" "$_mcol" "$BOLD" "$_model" "$RESET") | |
| else | |
| _popis="${_model}${_popis:+ · $_popis}" | |
| fi | |
| fi | fi |
| [ -n "$_agent" ] && agentseg=$(printf "%b%s%b" "$BOLD" "$_agent" "$RESET") | [ -n "$_agent" ] && agentseg=$(printf "%b%s%b" "$BOLD" "$_agent" "$RESET") |
| primary="$_popis" | primary="$_popis" |
| |
| # tokens: count + sparkline + rate | # tokens: count + zaplneni okna + sparkline + rate |
| | # ctx: zaplneni kontextoveho okna subagenta. `tokenCount` je aktualni obsazeni |
| | # okna (ne kumulativni utrata), takze procento je primo pomer k `contextWindowSize`. |
| | # Format i barvy zamerne shodne s hlavni statusLine (ctx:55% / ctx:93%!). |
| | ctxseg=""; ctxpct="" |
| | if [ -n "$tokens" ] && [ "$tokens" != "null" ] \ |
| | && [ -n "$ctxsize" ] && [ "$ctxsize" != "null" ] && [ "$ctxsize" -gt 0 ] 2>/dev/null; then |
| | ctxpct=$(( tokens * 100 / ctxsize )) |
| | if [ "$ctxpct" -ge 90 ]; then ctxseg=$(printf "%b%bctx:%s%%!%b" "$RED" "$BOLD" "$ctxpct" "$RESET") |
| | elif [ "$ctxpct" -ge 70 ]; then ctxseg=$(printf "%bctx:%s%%%b" "$YELLOW" "$ctxpct" "$RESET") |
| | else ctxseg=$(printf "%bctx:%s%%%b" "$GREEN" "$ctxpct" "$RESET") |
| | fi |
| | fi |
| tokseg="" | tokseg="" |
| if [ -n "$tokens" ] && [ "$tokens" != "null" ]; then | if [ -n "$tokens" ] && [ "$tokens" != "null" ]; then |
| disp=$(num_fmt "$tokens") | disp=$(num_fmt "$tokens") |
| if [ "$tokens" -ge 100000 ] 2>/dev/null; then tcol="${RED}${BOLD}"; else tcol="$DIM"; fi | # Varovnou roli nese ctx segment; tok zustava neutralni absolutni cislo. |
| | # Bez `contextWindowSize` (starsi Claude Code) plati puvodni pevny prah. |
| | if [ -n "$ctxpct" ]; then tcol="$DIM" |
| | elif [ "$tokens" -ge 100000 ] 2>/dev/null; then tcol="${RED}${BOLD}" |
| | else tcol="$DIM"; fi |
| IFS=$'\t' read -r spark rate < <(spark_and_rate "$samples") | IFS=$'\t' read -r spark rate < <(spark_and_rate "$samples") |
| # fallback rate: průměr od startu, když samples nenesou čas | # fallback rate: průměr od startu, když samples nenesou čas |
| fi | fi |
| |
| # ořez primárního štítku podle columns (heuristika: fixní část ~ 44 sloupců) | # ořez popisku a živé činnosti podle columns (heuristika: fixní část ~ 55 |
| if [ -n "$primary" ] && [ "$columns" -gt 0 ] 2>/dev/null; then | # sloupců — status + ctx + tok/sparkline/rate + elapsed). Popis dispatche má |
| budget=$(( columns - 44 )); [ "$budget" -lt 12 ] && budget=12 | # přednost (odlišuje paralelní agenty téhož typu), činnost bere zbytek. |
| if [ "${#primary}" -gt "$budget" ]; then primary="${primary:0:$((budget-1))}…"; fi | if [ "$columns" -gt 0 ] 2>/dev/null; then |
| | budget=$(( columns - 55 )); [ "$budget" -lt 12 ] && budget=12 |
| | if [ -n "$activity" ]; then |
| | pbudget=$(( budget * 2 / 5 )); [ "$pbudget" -lt 10 ] && pbudget=10 |
| | else |
| | pbudget="$budget" |
| | fi |
| | if [ -n "$primary" ] && [ "${#primary}" -gt "$pbudget" ]; then primary="${primary:0:$((pbudget-1))}…"; fi |
| | if [ -n "$activity" ]; then |
| | abudget=$(( budget - ${#primary} - 3 )); [ "$abudget" -lt 8 ] && abudget=8 |
| | if [ "${#activity}" -gt "$abudget" ]; then activity="${activity:0:$((abudget-1))}…"; fi |
| | fi |
| fi | fi |
| | actseg="" |
| | [ -n "$activity" ] && actseg=$(printf "%b%s%b" "$WHITE" "$activity" "$RESET") |
| |
| # type badge jen když není běžná lokální kategorie | # type badge jen když není běžná lokální kategorie |
| parts+=("$(printf "%b%b%s%b" "$MAGENTA" "$BOLD" "$primary" "$RESET")") | parts+=("$(printf "%b%b%s%b" "$MAGENTA" "$BOLD" "$primary" "$RESET")") |
| fi | fi |
| | [ -n "$actseg" ] && parts+=("$actseg") |
| [ -n "$status" ] && parts+=("$(printf "%b%s%b" "$sc" "$status" "$RESET")") | [ -n "$status" ] && parts+=("$(printf "%b%s%b" "$sc" "$status" "$RESET")") |
| | [ -n "$ctxseg" ] && parts+=("$ctxseg") |
| [ -n "$tokseg" ] && parts+=("$tokseg") | [ -n "$tokseg" ] && parts+=("$tokseg") |
| [ -n "$el" ] && parts+=("$(printf "%b%s%b" "$DIM" "$el" "$RESET")") | [ -n "$el" ] && parts+=("$(printf "%b%s%b" "$DIM" "$el" "$RESET")") |
| |
| * Hlavní ''statusLine'' a ''subagentStatusLine'' jsou oddělené. Hlavní řádek zůstává pro top-level session, subagenti se zobrazují v agent panelu. | * Hlavní ''statusLine'' a ''subagentStatusLine'' jsou oddělené. Hlavní řádek zůstává pro top-level session, subagenti se zobrazují v agent panelu. |
| * ''subagentStatusLine'' nedostává samostatně model ani skutečný ''subagent_type''. Proto je nutná konvence v ''description''. | * ''subagentStatusLine'' dostává skutečný model v poli ''model'', ale nedostává ''subagent_type''. Proto ''description'' používá dvoudílný tvar ''<subagent_type> · <short description>''. |
| | * V aktuálním Claude Code je ''label'' pro ''local_agent'' živá činnost, ne identita dispatche: odpovídá ''progress.summary || description''. ''AgentSummary'' přibližně každých 30 sekund zapisuje do ''progress.summary'' krátké LLM-generované shrnutí činnosti. Identita se proto vždy čte z ''description'', zatímco ''label'' se vypisuje jen jako samostatný sloupec s činností. |
| | * Starší tříčlenné popisky ''<subagent_type> · <model> · <description>'' zůstávají podporované. Prostřední modelový segment se ignoruje ve prospěch hodnoty z ''model''; nově se ale zapisuje dvoudílný tvar. |
| | * ''contextWindowSize'' spolu s ''tokenCount'' určuje ''ctx'' — procento obsazení context window subagenta. Pod 70 % je zelené, od 70 % žluté a od 90 % červené. ''tokenCount'' je aktuální obsazení okna, ne kumulativní spotřeba. |
| * ''tokenSamples'' se používá pro sparkline trendu tokenů. Když vzorky neobsahují čas, rychlost se dopočítá jako průměr od ''startTime''. | * ''tokenSamples'' se používá pro sparkline trendu tokenů. Když vzorky neobsahují čas, rychlost se dopočítá jako průměr od ''startTime''. |
| * ''columns'' se používá pro ořez popisku, aby se řádky v úzkém terminálu nelámaly. | * ''columns'' se používá pro ořez popisku, aby se řádky v úzkém terminálu nelámaly. |
| * ''cwd'' se zobrazí jen tehdy, když se liší od hlavní session. | * ''cwd'' se zobrazí jen tehdy, když se liší od hlavní session. |
| | * Pro diagnostiku lze nastavit proměnnou prostředí ''SUBAGENT_SL_DEBUG_DIR'' na existující adresář. Skript do něj zapíše zachycené JSON vstupy do ''raw.jsonl''. |
| * Skripty vyžadují ''jq'', ''python3'' a běžné unixové ''date''. | * Skripty vyžadují ''jq'', ''python3'' a běžné unixové ''date''. |
| |