activity

package
v0.0.20 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package activity implements tmux session activity detection using screen-delta hysteresis and tag-based output timestamps. It is extracted from the app god-package to decouple pure detection logic from App state.

Index

Constants

View Source
const (
	OwnerOption     = "@amux_activity_owner"
	HeartbeatOption = "@amux_activity_owner_heartbeat_ms"
	EpochOption     = "@amux_activity_owner_epoch"
	SnapshotOption  = "@amux_activity_active_workspaces"
)

tmux global option names backing the cross-instance activity-scan lease.

View Source
const (
	// OwnerLeaseTTL controls how long an activity-scan owner lease stays valid
	// after its last heartbeat before another instance may claim ownership.
	OwnerLeaseTTL = 7 * time.Second
	// OwnerFutureSkewTolerance caps how far in the future a lease heartbeat may be
	// (clock skew) before it is treated as expired rather than alive.
	OwnerFutureSkewTolerance = 2 * time.Second
	// SnapshotStaleAfter controls how long a shared activity snapshot is trusted
	// after its timestamp before followers ignore it.
	SnapshotStaleAfter = 10 * time.Second
	// SnapshotFutureSkewTolerance caps how far in the future a shared snapshot
	// timestamp may be (clock skew) before it is treated as stale rather than
	// fresh, mirroring OwnerFutureSkewTolerance for the owner lease.
	SnapshotFutureSkewTolerance = 2 * time.Second
)
View Source
const (
	ScoreThreshold = 3 // Score needed to be considered active
	ScoreMax       = 6 // Maximum score (prevents runaway accumulation)

	// OutputWindow is how recently output must have occurred to be "active".
	OutputWindow = 2 * time.Second
	// InputEchoWindow treats output immediately after input as likely local echo.
	InputEchoWindow = 400 * time.Millisecond
	// InputSuppressWindow suppresses fallback capture right after user input.
	InputSuppressWindow = 2 * time.Second

	// CaptureTail is the number of terminal lines captured for activity checks.
	CaptureTail = 50
	// HoldDuration holds an active session state after the last observed change.
	HoldDuration = 6 * time.Second
	// DoneWindow is how long after going quiet a session is reported "done"
	// (recently finished) before decaying to idle.
	DoneWindow = 30 * time.Second
)

Hysteresis / detection constants.

Variables

View Source
var ErrTmuxUnavailable = errors.New("tmux service unavailable")

ErrTmuxUnavailable is returned when the tmux service is nil or missing.

Functions

func ActiveWorkspaceIDsFromTagsWithRemoved added in v0.0.20

func ActiveWorkspaceIDsFromTagsWithRemoved(
	infoBySession map[string]SessionInfo,
	sessions []TaggedSession,
	recentActivityBySession map[string]bool,
	states map[string]*SessionState,
	opts tmux.Options,
	captureFn CaptureFn,
	hashFn HashFn,
) (map[string]bool, map[string]*SessionState, []string)

ActiveWorkspaceIDsFromTagsWithRemoved is the production entry point. It returns the active workspace IDs, updated session states, and the names of session states pruned this scan (unseen beyond pruneAfterScans) so the caller can delete them from its persistent map.

func ClassifyWorkspaceStates added in v0.0.20

func ClassifyWorkspaceStates(
	active map[string]bool,
	updated map[string]*SessionState,
	infoBySession map[string]SessionInfo,
	sessions []TaggedSession,
	now time.Time,
) map[string]AgentState

ClassifyWorkspaceStates maps workspaces to a semantic AgentState. A workspace is Working if it is in `active`; otherwise Done if any of its sessions went quiet within DoneWindow (per ClassifyState); otherwise omitted. `updated` is keyed by session name. Workspace IDs are resolved from local tab metadata and the same tagged tmux session metadata used by active classification.

func DecodeSnapshotWithStates added in v0.0.20

func DecodeSnapshotWithStates(raw string) (map[string]bool, map[string]AgentState, int64, time.Time, bool)

DecodeSnapshotWithStates parses an encoded snapshot and optional semantic states, accepting both current and legacy active-only payloads.

func EncodeSnapshot added in v0.0.20

func EncodeSnapshot(active map[string]bool, epoch int64, now time.Time) string

EncodeSnapshot serializes the active-workspace set with epoch and timestamp.

func EncodeSnapshotWithStates added in v0.0.20

func EncodeSnapshotWithStates(active map[string]bool, states map[string]AgentState, epoch int64, now time.Time) string

EncodeSnapshotWithStates serializes the active-workspace set plus optional per-workspace semantic states with epoch and timestamp.

func FetchRecentlyActiveByWindow

func FetchRecentlyActiveByWindow(svc SessionFetcher, window time.Duration, opts tmux.Options) (map[string]bool, error)

FetchRecentlyActiveByWindow returns session names with recent tmux window activity.

func FreshTagVisibleActivity added in v0.0.15

func FreshTagVisibleActivity(
	sessionName string,
	states map[string]*SessionState,
	updated map[string]*SessionState,
	now time.Time,
	opts tmux.Options,
	captureFn CaptureFn,
	hashFn HashFn,
) bool

FreshTagVisibleActivity validates fresh output tags against visible pane changes for initialized sessions. This prevents control-sequence churn from keeping sessions active when captured content is unchanged.

Returns true when the fresh tag should count as active.

func HasRecentUserInput

func HasRecentUserInput(snapshot TaggedSession, now time.Time) bool

HasRecentUserInput returns true if the session has had user input within the suppress window.

func HasRecentWindowActivity added in v0.0.15

func HasRecentWindowActivity(sessionName string, recentActivityBySession map[string]bool) bool

HasRecentWindowActivity reports whether a session has recent tmux window activity. When the prefilter map is nil (data unavailable), it returns true to preserve accuracy by allowing the caller to proceed.

func IsChatSession

func IsChatSession(session tmux.SessionActivity, info SessionInfo, hasInfo bool) bool

IsChatSession determines whether a tmux session represents an active AI agent.

Detection priority:

  1. Known-tab metadata marks chat sessions active even if tmux type is stale.
  2. Session tag (@amux_type == "agent") is authoritative for agent sessions.
  3. For known sessions with no explicit type, fall back to tab metadata.

func IsLikelyUserEcho

func IsLikelyUserEcho(snapshot TaggedSession) bool

IsLikelyUserEcho returns true if the output timestamp is within the echo window after the input timestamp, suggesting the output is local echo rather than agent work.

func IsRunningSession added in v0.0.15

func IsRunningSession(info SessionInfo, hasInfo bool) bool

IsRunningSession reports whether a known session should be considered active-capable based on status metadata from app state.

func OwnerLeaseAlive added in v0.0.20

func OwnerLeaseAlive(lease OwnerLease, now time.Time) bool

OwnerLeaseAlive reports whether lease is a live owner lease at now, tolerating small forward clock skew but expiring stale or far-future heartbeats.

func ParseLastOutputAtTag

func ParseLastOutputAtTag(raw string) (time.Time, bool)

ParseLastOutputAtTag parses a unix timestamp tag value in seconds, millis, or nanos.

func PrepareStaleTagFallbackState

func PrepareStaleTagFallbackState(sessionName string, states map[string]*SessionState)

PrepareStaleTagFallbackState trims stale-tag hysteresis carryover so sessions stop appearing active promptly after output ceases.

func ReadSnapshotWithStates added in v0.0.20

func ReadSnapshotWithStates(opts tmux.Options, now time.Time, expectedEpoch int64) (map[string]bool, map[string]AgentState, bool, error)

ReadSnapshotWithStates reads the shared activity snapshot, including optional semantic agent states for newer owners. Legacy active-only snapshots decode with a nil states map.

func RenewOwnerLeaseHeartbeat added in v0.0.20

func RenewOwnerLeaseHeartbeat(opts tmux.Options, now time.Time) error

RenewOwnerLeaseHeartbeat refreshes only the heartbeat timestamp.

func SeedFreshTagFallbackBaseline

func SeedFreshTagFallbackBaseline(
	sessionName string,
	states map[string]*SessionState,
	updated map[string]*SessionState,
	opts tmux.Options,
	captureFn CaptureFn,
	hashFn HashFn,
)

SeedFreshTagFallbackBaseline initializes hysteresis state for sessions that are currently active via fresh tags, so stale fallback doesn't treat them as brand-new sessions and blip active on unchanged content.

func WorkspaceIDForSession

func WorkspaceIDForSession(session tmux.SessionActivity, info SessionInfo, hasInfo bool) string

WorkspaceIDForSession resolves a workspace ID from known tab info, session tags, or session name.

func WorkspaceIDFromSessionName

func WorkspaceIDFromSessionName(name string) string

WorkspaceIDFromSessionName extracts a workspace ID from an amux session name pattern.

func WriteOwnerLease added in v0.0.20

func WriteOwnerLease(opts tmux.Options, ownerID string, epoch int64, now time.Time) error

WriteOwnerLease claims ownership by writing owner/epoch/heartbeat. tmux global options offer no atomic CAS; callers confirm by re-reading and checking epoch.

Types

type AgentState added in v0.0.20

type AgentState int

AgentState is the semantic activity state of an agent session.

const (
	StateIdle    AgentState = iota // quiet, nothing pending
	StateWorking                   // actively producing output
	StateDone                      // recently finished (was working, now quiet)
)

func ClassifyState added in v0.0.20

func ClassifyState(state *SessionState, now time.Time) AgentState

ClassifyState maps a session's hysteresis state to a semantic AgentState as of `now`. Working = currently active; Done = not active but went quiet within DoneWindow; Idle otherwise. This is deterministic — no screen parsing.

The isActive derivation is kept identical to the one in activeWorkspaceIDsWithHysteresisWithSeen so "Working" exactly matches the existing active bit. If the hysteresis logic changes, update both in lockstep (or refactor to a shared predicate — a good Phase 2 cleanup).

func (AgentState) String added in v0.0.20

func (s AgentState) String() string

String returns a human-readable label for the AgentState.

type CaptureFn

type CaptureFn func(sessionName string, lines int, opts tmux.Options) (string, bool)

CaptureFn captures the tail of a tmux pane.

type HashFn

type HashFn func(content string) [16]byte

HashFn hashes pane content for delta detection.

type OwnerLease added in v0.0.20

type OwnerLease struct {
	OwnerID     string
	HeartbeatAt time.Time
	Epoch       int64
}

OwnerLease is the cross-instance activity-scan owner lease stored in tmux global options.

func ReadOwnerLease added in v0.0.20

func ReadOwnerLease(opts tmux.Options) (OwnerLease, error)

ReadOwnerLease reads the current owner lease from tmux global options.

type SessionFetcher

type SessionFetcher interface {
	SessionsWithTags(match map[string]string, keys []string, opts tmux.Options) ([]tmux.SessionTagValues, error)
	ActiveAgentSessionsByActivity(window time.Duration, opts tmux.Options) ([]tmux.SessionActivity, error)
}

SessionFetcher is the subset of tmux operations needed by activity detection.

type SessionInfo

type SessionInfo struct {
	Status      string
	WorkspaceID string
	Assistant   string
	IsChat      bool
}

SessionInfo maps a tmux session name to known tab metadata.

type SessionState

type SessionState struct {
	LastHash     [16]byte  // Hash of last captured pane content
	Score        int       // Activity score (0 to ScoreMax)
	LastActiveAt time.Time // Last time this session was considered active
	Initialized  bool      // Whether we have a baseline hash
	// UnseenScans counts consecutive scans in which this session was not seen.
	// Reset to 0 whenever the session is observed; once it exceeds
	// pruneAfterScans the state is dropped so deleted-workspace sessions do not
	// accumulate as zombie states that are deep-copied every scan forever.
	UnseenScans int
	// LastWorkingAt is the last time this session was classified active; used to
	// derive the transient "done" state after it goes quiet.
	LastWorkingAt time.Time
}

SessionState tracks per-session activity using screen-delta hysteresis.

type TaggedSession

type TaggedSession struct {
	Session       tmux.SessionActivity
	LastOutputAt  time.Time
	HasLastOutput bool
	LastInputAt   time.Time
	HasLastInput  bool
}

TaggedSession pairs a tmux session with parsed tag timestamps.

func FetchTaggedSessions

func FetchTaggedSessions(svc SessionFetcher, infoBySession map[string]SessionInfo, opts tmux.Options) ([]TaggedSession, error)

FetchTaggedSessions retrieves tmux sessions with amux tags and known-tab metadata.

Jump to

Keyboard shortcuts

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