activity

package
v1.61.0 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package activity keeps the live answer to one question: in which coder sessions is something happening right now. It is the signal behind the working mark on the terminal icons.

The answer falls through two shelves, best first, and working() below is that fall in code:

  1. The coder's own account of its turn (SetTurn). It is fed by whoever can read or hear that account: the record watcher tailing the coder's transcript or event log, the turn-end hooks and bells the notification center already ingests, and a coder's own plugin events. While any of them has spoken, the screen has no vote: a turn is open even when a quiet tool call paints nothing, and over even while an idle input line keeps blinking.

  2. Movement on the screen (Output/Input), for a session whose account nobody has read yet, a turn already running when the serve process started, or a coder that keeps no readable record. Sustained output that no input explains is somebody working in there, and the mark decays when the screen goes quiet.

One abort leaves no trace on either shelf: interrupting a coder mid streaming writes nothing into its record at all. The interrupt keys are therefore taken as a hint (Interrupt) that the account may answer within a grace, and its silence is what closes the turn; the per-coder Policy.OpenTurnCap is the backstop behind everything else.

The heuristics live here once, but whether one applies to a session is its coder's choice: the feeders read coder.ActivityProfile and call only what the profile allows, and Configure hands the tracker the knobs it needs itself. The tracker knows nothing about coders, transcripts or tmux: it holds session ids, takes the raw facts, and publishes the working set.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Policy

type Policy struct {
	// OpenTurnCap ends an open turn whose account has said nothing for
	// this long; zero or less disables it.
	OpenTurnCap time.Duration
	// MovementStartGrace mutes the movement shelf for at least this long
	// after the session started, and past that until the screen first
	// stands still for bootSettle: a booting TUI paints without anybody
	// working, and a cold machine restoring every session at once paints
	// far past any fixed span. Zero or less mutes nothing.
	MovementStartGrace time.Duration
}

Policy is what a coder's ActivityProfile means to this tracker: the knobs of the shared heuristics that vary per coder. Which feeder calls what is already decided outside (the interrupt hint, the record watch); what remains here is what the sweep has to know.

type Tracker

type Tracker struct {
	// contains filtered or unexported fields
}

Tracker holds the working state per session id. Safe for concurrent use.

func NewTracker

func NewTracker() *Tracker

NewTracker returns an empty tracker. Wire SetOnChange before the feeders start.

func (*Tracker) Configure

func (t *Tracker) Configure(id string, policy Policy, startedAt time.Time)

Configure hands a session its coder's policy, called when the session enters the running set, with when it started: the movement grace counts from the session's own birth, so a serve restart over long-running sessions mutes nothing. Until the policy is here, movement does not count (see Output), so however late this call arrives, no boot paint has marked anything in the meantime.

func (*Tracker) Forget

func (t *Tracker) Forget(id string)

Forget drops a session that is gone. The record watcher calls it when a session leaves the running set, so a dead session's mark cannot outlive its terminal.

func (*Tracker) Input

func (t *Tracker) Input(id string)

Input records that input was sent to a session, which starts the echo window: whatever the terminal paints right after is the input coming back, not the coder working.

func (*Tracker) Interrupt

func (t *Tracker) Interrupt(id string)

Interrupt records an interrupt key, Escape or Ctrl+C, for a coder whose profile chose the hint. It closes nothing itself: any sign of life withdraws it, the record moving on (SetTurn) and the screen painting past the key's own echo alike (Output), because an aborted coder falls truly silent while one whose dialog just closed keeps ticking its status line. Only total silence past interruptGrace lets the sweep close the turn, the abort that writes nothing. A turn inside a tool call ignores the hint entirely: an abort there writes its own marker, while the phase itself paints nothing that could withdraw a wrong hint.

func (*Tracker) Output

func (t *Tracker) Output(id string)

Output records that output flowed in a session: the movement shelf. Output within echoGap of the last input is the echo of that input and counts for nothing; a run of qualifying output that spans minSpan makes the session moving. Marking waits for two things the run itself cannot know: the coder's policy (a session seen before Configure may be a TUI booting under a grace not yet delivered), and the end of the boot, which outlasts its grace until the screen first stands still. The run and the timestamps are tracked throughout, so a turn already painting when the policy arrives marks immediately, and the sweep can drop stale sessions.

func (*Tracker) Run

func (t *Tracker) Run(interval time.Duration)

Run expires movement marks whose screens went quiet and drops sessions that stopped mattering long ago. Blocks; run it in a goroutine.

func (*Tracker) SetOnChange

func (t *Tracker) SetOnChange(listen func(working []string))

SetOnChange installs the listener that hears every change of the working set, called with the full sorted set so a consumer can publish it as a snapshot. Called outside the tracker's lock.

func (*Tracker) SetTurn

func (t *Tracker) SetTurn(id string, open, inTool bool, at time.Time)

SetTurn records the coder's account: a turn opened, inside a tool call or not, or a turn is over. Shelf one; it stands until the account speaks again or Forget drops the session. Every word of the account also withdraws a pending interrupt: the record moving on is the account answering it.

at is when the word was written, not when it arrived: a record entry carries its file's stamp, a signal the moment it was heard. The words reach the tracker through different roads with different delays, so they are ordered by their own time, and an older word never overrules a newer one. The permission ask is the case that needs this: the record's tool call entry says open, the ask signal moments later says the coder now waits on its person, and the signal must win no matter which road was faster.

func (*Tracker) Working

func (t *Tracker) Working() map[string]bool

Working answers the current working set as a copy.

func (*Tracker) WorkingIDs

func (t *Tracker) WorkingIDs() []string

WorkingIDs answers the current working set sorted, the shape the event snapshot carries.

Jump to

Keyboard shortcuts

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