probes

package
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 17, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package probes hosts the status-line probe registry (model, cost, context, git, subagents, ...) consumed by the formatter.

Index

Constants

This section is empty.

Variables

View Source
var (
	// Line0Registry holds probes rendered on the first status line (row 0).
	// Order: email, project, quota.
	Line0Registry = []Probe{
		&EmailProbe{},
		&ProjectProbe{},
		&QuotaProbe{},
	}

	// Line1Registry holds probes rendered on the second status line (row 1).
	// Order: model (includes effort icon inline), git, ctx, cost, time.
	Line1Registry = []Probe{
		&ModelProbe{},
		&GitProbe{},
		&CtxProbe{},
		&CostProbe{},
		&TimeProbe{},
	}

	// Line2Registry is empty since Phase 6.9.e: the cache-aggregate row (line 2)
	// was removed in favour of per-row cache columns + TTL suffix in the table
	// (T-13). CacheProbe was deleted in Phase 7 (BL-33).
	Line2Registry = []Probe{}

	// SubagentRegistry holds the subagent status probe, rendered only in the
	// subagent status line code-path.
	SubagentRegistry = []Probe{
		&SubagentProbe{},
	}
)

Probe registries define the display order of probes on each status-line row. Each registry is a fixed-order slice: position in the slice == left-to-right render position for that row.

Row assignment (from Phase 4 concept §A4):

Line0Registry    — row 0 of the multiline output: email, project name, quota indicator
Line1Registry    — row 1 of the multiline output: model, effort, git, ctx, cost, time
Line2Registry    — row 2 of the multiline output: cache aggregate row (CacheProbe)
SubagentRegistry — subagent status probes (one per active subagent)

Functions

This section is empty.

Types

type Config

type Config struct {
	// Per-widget toggles (Phase 6 — from config.Widgets). Default false; set
	// via ToProbesConfig to mirror the user's config. Phase 4-5 callers that
	// do not populate these fields should use Default()-sourced Config to get
	// all-true behaviour.
	ModelEnabled    bool
	EffortEnabled   bool
	CostEnabled     bool
	ProjectEnabled  bool
	EmailEnabled    bool
	TimeEnabled     bool
	CtxEnabled      bool
	CacheEnabled    bool
	QuotaEnabled    bool
	GitEnabled      bool
	SubagentEnabled bool

	// TableRows is the maximum number of per-turn rows shown in the subagent
	// table. Set from config.General.TableRows via ToProbesConfig. When 0 the
	// assembler applies its own default (10). Capped at 40 by SetTableRows.
	TableRows int

	// Per-probe values (Phase 6 — from config.Probes).
	// Email is the override address for the Email probe. When empty the probe
	// reads the address from the CC session JSONL.
	Email string

	// Threshold values (Phase 6 — from config.Thresholds).
	CostBudgetUSD    float64
	CtxNoticeRatio   float64
	CtxWarnRatio     float64
	CtxCriticalRatio float64

	Quota5hNoticeRatio   float64
	Quota5hWarnRatio     float64
	Quota5hCriticalRatio float64
	Quota7dNoticeRatio   float64
	Quota7dWarnRatio     float64
	Quota7dCriticalRatio float64

	OrchTTLMinutes     int
	SubagentGapMinutes int

	// IsSubagentContext indicates that the probe is being rendered in a subagent
	// display context (not the main orchestrator line). When true, TTL is
	// suppressed (T-23). This is a runtime flag, not a config threshold —
	// SubagentGapMinutes is the detection threshold (C3 fix).
	IsSubagentContext bool
}

Config carries per-invocation configuration flags. It is a lightweight struct (not the full internal/config.Config) scoped to what probes actually need. All XEnabled fields default to false; callers must populate from config.ToProbesConfig.

type CostProbe

type CostProbe struct{}

CostProbe renders the total session cost in USD. Always visible (even at $0.00). Priority P2.

func (*CostProbe) MinWidth

func (p *CostProbe) MinWidth() int

func (*CostProbe) Name

func (p *CostProbe) Name() string

func (*CostProbe) Priority

func (p *CostProbe) Priority() int

func (*CostProbe) Render

func (p *CostProbe) Render(d Data, c Config, t renderer.Theme, level Level) string

Render formats the header cost from the official Claude Code meter (d.Stdin.Cost.TotalCostUSD), clipped to this session via the /clear baseline, not our table-driven estimate (Phase 7.46).

Rationale: the header must show the single most authoritative figure, even at a small lag, rather than our reconstructed estimate (which carries a known ~10% reasoning-billing gap). But TotalCostUSD is cumulative across /clear, so we subtract st.BaselineCost — the ccTotal captured on the first Reconcile of this session_id — to show only the current session's spend (a /clear starts a new session_id with a fresh baseline). On a session begun from zero baseline=0, so this equals the raw total. The per-turn table keeps our estimate (labelled "~cost"); a small header↔table mismatch is expected. This does not reintroduce the 7.45 dancing-number bug — that came from redistributing the lagging ccTotal across turns, not from a monotonic session-clipped header.

Budget signal: when c.CostBudgetUSD > 0 and the session cost has reached or exceeded it, the figure is painted bold_red (config-honesty, Phase 7.47). A budget of 0 (the default) disables the check and the figure stays plain.

Full:              "cost: $<value>"
Compact/Minimal:   "$<value>"

func (*CostProbe) Visible

func (p *CostProbe) Visible(d Data, c Config) bool

Visible returns false when CostEnabled is false; otherwise always true (even at $0.00).

type CtxProbe

type CtxProbe struct{}

CtxProbe renders the context window usage as a progress bar.

Used tokens = sum of three input-side keys from CurrentUsage: cache_read_input_tokens + cache_creation_input_tokens + input_tokens. Percentage = used / Size * 100 (clamped to [0, 100]).

Display (AnsiEnabled=false / legacy):

Full:    "ctx <bar10> <usedK>/<sizeK> (<pct>%)"
Compact: "<bar5> <usedK>/<sizeK>"   (no %)
Minimal: "<usedK>/<sizeK>"           (no bar, no %)

Display (AnsiEnabled=true / T-22):

Full:    "ctx <bar10> <coloured-usedK>/<sizeK>"  (no %; usedK colour by fill)
Compact: "<bar5> <usedK>/<sizeK>"               (unchanged)
Minimal: "<usedK>/<sizeK>"                       (no colour markers)

Colour rules for usedK (AnsiEnabled=true, > 95% → bold_red; < 50% → green; otherwise marker from ProgressBarColor threshold):

> 95% → {{color:bold_red}}
< 50% → {{color:green}}
50–69% → {{color:yellow}}
70–89% → {{color:orange}}
≥ 90%  → {{color:red}}

func (*CtxProbe) MinWidth

func (p *CtxProbe) MinWidth() int

func (*CtxProbe) Name

func (p *CtxProbe) Name() string

func (*CtxProbe) Priority

func (p *CtxProbe) Priority() int

func (*CtxProbe) Render

func (p *CtxProbe) Render(d Data, c Config, t renderer.Theme, level Level) string

Render formats the context window usage with a progress bar.

func (*CtxProbe) Visible

func (p *CtxProbe) Visible(d Data, c Config) bool

Visible returns false when CtxEnabled is false or ContextWindow.Size is zero.

type Data

type Data struct {
	Stdin        stdin.Payload        // parsed stdin from CC hook
	Session      *parser.SessionStats // JSONL aggregates; nil = empty session
	Subagents    []parser.SubagentStats
	Git          *parser.GitStatus // nil = not in a git repo or detection failed
	Now          time.Time
	TerminalCols int    // 0 = detect failed; probes should fall back to 80
	SessionID    string // CC session id; "" disables hint state persistence (Phase 4.4)

	// ExtraCacheEvents lets main inject synthetic alerts (e.g. config load
	// errors) that are not derived from session/subagent JSONL data. Phase 6.
	ExtraCacheEvents []parser.CacheEvent

	// Phase 6.8.a: delta-based cost fields.
	// SessionTotal is the cost incurred in this session (ccTotal − BaselineCost).
	// Populated by main after cost.Reconcile; zero when state not yet loaded.
	SessionTotal float64

	// Phase 6.9.a: session duration delta.
	// SessionDurMS is TotalAPIDurationMS − BaselineDurMS (resets on /clear).
	// Populated by main via cost.SessionDuration; zero when state not yet loaded.
	SessionDurMS int64

	// LastRequestCost is the cost of the most recent prompt group
	// (ccTotal − PromptCost[curGroupID]). Zero when not yet computed.
	LastRequestCost float64

	// PerTurnCostFn returns the finalized per-turn USD cost for the given UUID.
	// Returns (0, false) when the turn has no recorded cost yet.
	// Set by main from state.Session.PerTurnCost via cost.PerTurn.
	// nil when state not available (graceful degradation: render "—").
	PerTurnCostFn func(uuid string) (float64, bool)

	// State is the reconciled per-session state. Used by perTurnTable to pass
	// to renderer.RenderUnified for per-turn cost column (C1). nil means state
	// not available; renderer degrades gracefully (shows "—" for all turns).
	State *state.Session

	// CommitBadgeCount is the "✓ N committed" badge count to render this refresh
	// (Phase 6.95.a). Zero means no badge. Set by main via state.CommitBadgeTick;
	// GitProbe renders it (green) in Full/Compact, never in Minimal.
	CommitBadgeCount int

	// ExtraActive reports that paid extra-usage is in effect (a rate-limit window
	// at ≥100% AND hasExtraUsageEnabled). Set by main (Phase 6.95.h) via
	// state.ExtraUsageTick. QuotaProbe renders a red "+$X extra usage" block after
	// the triggering window's reset timer.
	ExtraActive bool

	// ExtraUSD is the estimated overage in USD accrued since a rate-limit window
	// crossed 100% (SessionTotal − OverageBaseline). The extra block is shown only
	// when ExtraActive && ExtraUSD ≥ $0.01.
	ExtraUSD float64

	// HintStart is the rotating-hint starting index for this session, read by main
	// from quota.HintStart on the first render of a new session. The hint widget
	// uses it only on that first render; subsequent renders follow per-session
	// HintRotation. Account-wide so the opening hint shifts by one each session.
	HintStart int

	// UpdateHint is the pre-formatted "update available" hint (#7c, Phase 7.46
	// Wave B / BL-36), or "" when the running version is current or unknown. main
	// builds it from the binary version and the cached price file's latest_version
	// via hint.UpdateText; the assembler forwards it to the hint widget.
	UpdateHint string
}

Data is the common render snapshot assembled once per invocation and passed to every probe. Populated by the parser (Phase 3) and stdin decoder (Phase 4) before any probe runs.

type EffortProbe

type EffortProbe struct{}

EffortProbe renders the effort level as a Unicode circle icon. All three display levels render the same icon (effort is P0; icon is never dropped).

func (*EffortProbe) MinWidth

func (p *EffortProbe) MinWidth() int

func (*EffortProbe) Name

func (p *EffortProbe) Name() string

func (*EffortProbe) Priority

func (p *EffortProbe) Priority() int

func (*EffortProbe) Render

func (p *EffortProbe) Render(d Data, _ Config, t renderer.Theme, level Level) string

Render returns the Unicode icon for the effort level, colour-wrapped per B3 §5 (see effortGlyph). Returns "" for "off" or unrecognised levels.

func (*EffortProbe) Visible

func (p *EffortProbe) Visible(d Data, c Config) bool

Visible returns false when EffortEnabled is false or the effort level is "off" or empty.

type EmailProbe

type EmailProbe struct{}

EmailProbe renders the user's email address. Zero-state: all data comes from Config passed per-call to Visible and Render.

Visible returns false when EmailEnabled=false or Email is empty.

Display:

Full:    full email unchanged.
Compact: middle-truncate to 16 runes.
Minimal: middle-truncate to 12 runes.

func (*EmailProbe) MinWidth

func (p *EmailProbe) MinWidth() int

func (*EmailProbe) Name

func (p *EmailProbe) Name() string

func (*EmailProbe) Priority

func (p *EmailProbe) Priority() int

func (*EmailProbe) Render

func (p *EmailProbe) Render(d Data, c Config, t renderer.Theme, level Level) string

Render formats the email address. When AnsiEnabled, the value is wrapped in {{dim}}…{{reset}} per B3 §5.

Full:    full email (or {{dim}}full email{{reset}} with colour)
Compact: middle-truncate(16)
Minimal: middle-truncate(12)

func (*EmailProbe) Visible

func (p *EmailProbe) Visible(d Data, c Config) bool

Visible returns true only when EmailEnabled=true and Email is non-empty.

type GitProbe

type GitProbe struct{}

GitProbe renders the current git branch and modified-file count. Visible only when d.Git is non-nil (i.e., we are inside a git repository).

Display:

Full/Compact: "⎇ <branch>" (or "⎇ -:<branch>" if Worktree != "") + " ⚠N" if N > 0
Minimal:      middle-truncated branch with "…", at least 8 runes in the result

func (*GitProbe) MinWidth

func (p *GitProbe) MinWidth() int

func (*GitProbe) Name

func (p *GitProbe) Name() string

func (*GitProbe) Priority

func (p *GitProbe) Priority() int

func (*GitProbe) Render

func (p *GitProbe) Render(d Data, c Config, t renderer.Theme, level Level) string

Render formats the git branch status.

Colour markers applied per B3 §5:

  • branch segment "⎇ <name>" → {{color:cyan}}…{{reset}}

  • warning segment "⚠N" (when ModifiedCount > 0) → {{color:yellow}}…{{reset}}

    Full: "⎇ <branch>" (+ " ⚠N" if N>0), no truncation. Compact: "⎇ <branch12>" (+ " ⚠N" if N>0), branch middle-truncated to 12. Minimal: "⎇ <branch8>", no ⚠N.

func (*GitProbe) Visible

func (p *GitProbe) Visible(d Data, c Config) bool

Visible returns false when GitEnabled is false or d.Git is nil.

type Level

type Level int

Level controls how aggressively a probe compresses its output to fit narrow terminals. Probes must handle all three levels independently.

const (
	LevelFull    Level = iota // full form: labels + values + units
	LevelCompact              // compact: drop labels, keep values
	LevelMinimal              // minimal: drop non-critical blocks or abbreviate to bare minimum
)

func (Level) String

func (l Level) String() string

String returns the human-readable name used in slog messages and debug output.

type ModelProbe

type ModelProbe struct{}

ModelProbe renders the canonical short model name (e.g. "opus-4-7"). It is a P0 probe: always visible when Model.ID is non-empty.

func (*ModelProbe) MinWidth

func (p *ModelProbe) MinWidth() int

func (*ModelProbe) Name

func (p *ModelProbe) Name() string

func (*ModelProbe) Priority

func (p *ModelProbe) Priority() int

func (*ModelProbe) Render

func (p *ModelProbe) Render(d Data, _ Config, t renderer.Theme, level Level) string

Render returns the canonical short model name, optionally followed by the effort icon (e.g. "sonnet-4-6 ◑"). Effort is appended without a separator so it reads as one visual unit.

When AnsiEnabled, the model name is wrapped in effortColorMarker(effort.Level) instead of the former {{bold}} marker, so the model name shares the same colour semantics as the effort glyph (T-14). An empty marker means the name is plain. All display levels return the same value.

func (*ModelProbe) Visible

func (p *ModelProbe) Visible(d Data, c Config) bool

Visible returns true when ModelEnabled is set and Stdin.Model.ID is non-empty.

type Probe

type Probe interface {
	// Name returns a unique identifier used in logs and registry comments.
	Name() string

	// Priority returns the display priority: 0 (always visible) to 4 (drop first).
	// Matches §A4 priority table in the concept doc.
	Priority() int

	// Visible reports whether this probe has meaningful data to show.
	// Returning false causes the renderer to skip the probe entirely.
	Visible(d Data, c Config) bool

	// Render produces the display string for the given level.
	// Output must be plain text with no embedded ANSI codes; the renderer
	// wraps it with Theme colours after calling Render.
	Render(d Data, c Config, t renderer.Theme, level Level) string

	// MinWidth returns the minimum character width of the Minimal-level output.
	// Used by the renderer to decide which probes to drop when space is tight.
	MinWidth() int
}

Probe is the single interface every status-line block must implement. Probes are stateless: all state comes from Data and Config.

Render parameter order: (d Data, c Config, t renderer.Theme, level Level) — amendment 2026-05-18, from 3-param to 4-param to pass Config explicitly.

type ProjectProbe

type ProjectProbe struct{}

ProjectProbe renders the basename of the current working directory. Falls back to "?" when Cwd is empty or the basename is empty (e.g. root "/"). Always visible. Priority P2.

func (*ProjectProbe) MinWidth

func (p *ProjectProbe) MinWidth() int

func (*ProjectProbe) Name

func (p *ProjectProbe) Name() string

func (*ProjectProbe) Priority

func (p *ProjectProbe) Priority() int

func (*ProjectProbe) Render

func (p *ProjectProbe) Render(d Data, _ Config, t renderer.Theme, level Level) string

Render returns the project name derived from basename(Cwd). When AnsiEnabled, the value is wrapped in {{dim}}…{{reset}} per B3 §5.

Full:    full basename (or {{dim}}…{{reset}} with colour)
Compact: middle-truncate(12)
Minimal: middle-truncate(8)

func (*ProjectProbe) Visible

func (p *ProjectProbe) Visible(d Data, c Config) bool

Visible returns false when ProjectEnabled is false; otherwise always true (falls back to "?").

type QuotaProbe

type QuotaProbe struct{}

QuotaProbe renders quota usage for the 5-hour and 7-day rate-limit windows. Data is sourced from quota.Freshest() (cross-session persistent file) with d.Stdin.RateLimits as a fallback when no snapshot has been stored yet.

Visible only when Config.QuotaEnabled=true AND either quota.Freshest() returns a snapshot OR d.Stdin.RateLimits != nil.

func (*QuotaProbe) MinWidth

func (p *QuotaProbe) MinWidth() int

func (*QuotaProbe) Name

func (p *QuotaProbe) Name() string

func (*QuotaProbe) Priority

func (p *QuotaProbe) Priority() int

func (*QuotaProbe) Render

func (p *QuotaProbe) Render(d Data, c Config, t renderer.Theme, level Level) string

Render formats the quota blocks.

Data source priority:

  1. quota.Freshest() — cross-session persistent snapshot (account-wide freshest).
  2. d.Stdin.RateLimits — current payload fallback when no snapshot exists.

When the snapshot is older than staleDuration (10m), an "as of Xm ago" suffix is appended to signal freshness decay.

Colour markers applied per B3 §5:

  • progress bars wrapped in ProgressBarColor(pct, th) + bar + Reset
  • ↻ reset countdown wrapped in {{color:yellow}} when time-to-reset < 30m
  • percentage value wrapped in {{color:bold_red}} when pct > 95

Display levels:

Full:    "5h: <bar10_5h> <reset5h> · 7d: <bar10_7d> <reset7d>" [+ age suffix]
Compact: "<bar5_5h> <reset5h> · <bar5_7d> <reset7d>" [+ age suffix]
Minimal: "<pct5h>% · <pct7d>%" [+ age suffix]

func (*QuotaProbe) Visible

func (p *QuotaProbe) Visible(d Data, c Config) bool

Visible returns true only when QuotaEnabled is true and quota data is available (either from the persistent Freshest snapshot or from the current payload).

type SubagentProbe

type SubagentProbe struct{}

SubagentProbe renders one line per active subagent task. It resolves each stdin Task to a SubagentStats entry by matching task.ID to SubagentStats.AgentID. Unmatched tasks produce a fallback line with "?" placeholders and emit slog.Warn.

Visible only when len(d.Stdin.Tasks) > 0.

Display (fields separated by " · "):

Full:    "<name> · <model> · <ctx>/<max> · $<cost> · ⏱<time> · <last_tool>"  (6 fields)
Compact: "<name> · <model> · <ctx>/<max> · $<cost> · ⏱<time>"               (5 fields, last_tool dropped)
Minimal: "<name> · <model> · <ctx>/<max>"                                     (3 fields)

func (*SubagentProbe) MinWidth

func (p *SubagentProbe) MinWidth() int

func (*SubagentProbe) Name

func (p *SubagentProbe) Name() string

func (*SubagentProbe) Priority

func (p *SubagentProbe) Priority() int

func (*SubagentProbe) Render

func (p *SubagentProbe) Render(d Data, c Config, t renderer.Theme, level Level) string

Render produces one output line per task, joined with "\n".

func (*SubagentProbe) Visible

func (p *SubagentProbe) Visible(d Data, c Config) bool

Visible returns false when SubagentEnabled is false or Stdin.Tasks is empty or nil.

type TimeProbe

type TimeProbe struct{}

TimeProbe renders the total API wall-clock duration in MM:SS format. Priority P1. Returns MM:SS at all levels including Minimal.

func (*TimeProbe) MinWidth

func (p *TimeProbe) MinWidth() int

func (*TimeProbe) Name

func (p *TimeProbe) Name() string

func (*TimeProbe) Priority

func (p *TimeProbe) Priority() int

func (*TimeProbe) Render

func (p *TimeProbe) Render(d Data, _ Config, _ renderer.Theme, level Level) string

Render formats the elapsed time relative to the session baseline (phase 6.9.a). Uses d.SessionDurMS (TotalAPIDurationMS − BaselineDurMS) so the counter resets on /clear together with the cost counter.

The fallback to raw TotalAPIDurationMS keys on d.State == nil ("state not yet loaded") rather than on durMS <= 0. After /clear the state IS loaded and SessionDurMS is legitimately 0 (baseline == current) — falling back there would resurrect the stale cumulative total (bug: cost reset to $0.00 but time kept 238:17). Negative deltas (transient ccTotal dip) are clamped to 0.

Full:    "time: MM:SS"
Compact: "MM:SS"
Minimal: "MM:SS"

func (*TimeProbe) Visible

func (p *TimeProbe) Visible(d Data, c Config) bool

Visible returns false when TimeEnabled is false; otherwise always true.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL