Documentation
¶
Overview ¶
Package harness detects which agent harness (claude/codex/...) is running in a wrapped PTY and whether it is working or idle, so the terminal-share heartbeat can stamp live agent info instead of relying on launch-time env vars. Detection is pure and platform-free: the wrapper feeds in foreground-process snapshots and byte-activity timestamps; this package never touches the PTY itself.
The design (and the timing constants) are ported from herdr's pane detection: identification is process-tree based, working/idle is PTY byte-causality based. See docs/specs/agent-detection.md.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Identify ¶
Identify maps a foreground process (comm + full argv, argv[0] included) to a canonical harness name, or "" when the process is not a known harness. It normalizes names (case, .exe/.cmd/.bat/.ps1/.js suffixes, whitespace) and sees through generic runtimes (node, python, sh/bash/zsh, pwsh) by scanning argv for a known binary name.
Types ¶
type ActivityTracker ¶
type ActivityTracker struct {
// contains filtered or unexported fields
}
ActivityTracker classifies a wrapped harness as working or idle from PTY byte causality alone — no screen parsing. Output observed while no recent user input is in flight marks the harness working for outputActivityWindow; quiescence past the window reads as idle. Callers pass `now` explicitly so tests never sleep.
Not safe for concurrent use; the wrapper serializes observations onto its detection goroutine.
func NewActivityTracker ¶
func NewActivityTracker(start time.Time) *ActivityTracker
NewActivityTracker starts tracking for a harness identified at `start`. The first startupGraceWindow after start reads as idle regardless of output.
func (*ActivityTracker) ObserveInput ¶
func (t *ActivityTracker) ObserveInput(now time.Time)
ObserveInput records user-originated input at `now` (local stdin, remote .stdin messages, alert auto-submission).
func (*ActivityTracker) ObserveOutput ¶
func (t *ActivityTracker) ObserveOutput(now time.Time)
ObserveOutput records PTY output bytes from the child at `now`. Output inside the startup grace is boot noise, and output inside the input-taint window is presumed local echo / prompt redraw; neither counts as agent work.
type Detection ¶
type Detection struct {
Harness string // canonical harness name, "" when none
ChildPID int // foreground pid when a harness is identified
State State // StateUnknown when Harness is ""
}
Detection is the point-in-time snapshot the heartbeat loop stamps into payloads. Zero value means "no harness in the foreground".
type Detector ¶
type Detector struct {
// contains filtered or unexported fields
}
Detector owns harness detection for one wrapped PTY: it polls the foreground process via the injected inspector and folds byte observations into an ActivityTracker. Inspect is the platform seam — TIOCGPGRP + process lookup in production, a fake in tests.
Inspect errors retain the previous identification (a transient ps failure must not flap `ppz who`); a successful inspect of a non-harness clears it. A harness change resets the activity tracker so each identification gets its own startup grace.
Safe for concurrent use: the wrapper calls Observe* from its PTY I/O goroutines, Poll from the detection ticker, and Snapshot from the heartbeat loop.
func NewDetector ¶
func NewDetector(inspect func() (ForegroundProc, error), start time.Time) *Detector
NewDetector creates a Detector for a PTY whose child spawned at `start`.
func (*Detector) ObserveInput ¶
ObserveInput forwards user input activity to the tracker.
func (*Detector) ObserveOutput ¶
ObserveOutput forwards PTY output activity to the tracker.
type ForegroundProc ¶
ForegroundProc is what the platform inspector reports about the PTY's foreground process group: the group leader's pid, its comm (binary name), and full argv (argv[0] included).
type Spec ¶
Spec describes one known harness. Extending detection to a new harness is one new row here. Names are the canonical ids `ppz agent --harness` already uses, so heartbeat consumers see one vocabulary regardless of whether the value came from env or detection.