feat(v0.5.4): SessionStatus display-only helper
Add internal/session.SessionStatus(wsDir) that derives a display-only view of the workspace session lease. Pure read of .ctask/session.json with no tmux invocation, no PID liveness, no lock acquisition, and no mutation of lease state. States: none | active | stale. Mode defaults to "direct" when a pre-v0.5.3 lease lacks the field. Malformed leases surface as stale with a diagnostic so the present-but-broken case stays visible instead of being silently classified as none. Reused for ctask info and ctask list in subsequent commits. Lifecycle code continues to use the existing primitives (ReadLease, IsFresh, InspectLease).
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SessionState is the display-only classification surfaced by SessionStatus.
|
||||
type SessionState string
|
||||
|
||||
const (
|
||||
// SessionStateNone: no lease file is present.
|
||||
SessionStateNone SessionState = "none"
|
||||
// SessionStateActive: lease file is present and fresh (heartbeat within StaleLeaseAfter).
|
||||
SessionStateActive SessionState = "active"
|
||||
// SessionStateStale: lease file is present but stale, or present-and-malformed.
|
||||
SessionStateStale SessionState = "stale"
|
||||
)
|
||||
|
||||
// Status is the derived display-only view of a workspace's session lease.
|
||||
//
|
||||
// It is intentionally narrower than InspectLease/LeaseState: callers that
|
||||
// need behavioral decisions (adoption, dispatch) must keep using the
|
||||
// existing primitives. Status exists for `ctask info` and `ctask list` to
|
||||
// surface session visibility without touching tmux, PID liveness, or any
|
||||
// lock state.
|
||||
type Status struct {
|
||||
State SessionState
|
||||
Mode string // "direct" | "persistent" | "" when malformed
|
||||
PID int
|
||||
Hostname string
|
||||
Diagnostic string // human-readable note for the malformed case; empty otherwise
|
||||
}
|
||||
|
||||
// SessionStatus returns the display-only session summary for wsDir.
|
||||
//
|
||||
// It performs only one file read (.ctask/session.json) and never invokes
|
||||
// tmux, checks PID liveness, modifies lease state, acquires locks, or
|
||||
// otherwise mutates the workspace. PID liveness is intentionally deferred
|
||||
// to v0.6's lazy-cleanup redesign, where it will have behavioral
|
||||
// consequences; building it display-only here would mean building it
|
||||
// twice.
|
||||
//
|
||||
// Display-only contract: do NOT call SessionStatus from lifecycle or
|
||||
// adoption code. The "missing Mode defaults to direct" rule and the
|
||||
// malformed-lease "stale" classification are display choices, not
|
||||
// behavioral truths. Use ReadLease / IsFresh / InspectLease for
|
||||
// lifecycle decisions.
|
||||
func SessionStatus(wsDir string) Status {
|
||||
return statusAt(wsDir, time.Now())
|
||||
}
|
||||
|
||||
// statusAt is the test entry point with an injected clock. Production
|
||||
// code goes through SessionStatus.
|
||||
func statusAt(wsDir string, now time.Time) Status {
|
||||
data, err := os.ReadFile(LeasePath(wsDir))
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return Status{State: SessionStateNone}
|
||||
}
|
||||
if err != nil {
|
||||
return Status{
|
||||
State: SessionStateStale,
|
||||
Diagnostic: "lease exists but could not be read",
|
||||
}
|
||||
}
|
||||
var l Lease
|
||||
if jsonErr := json.Unmarshal(data, &l); jsonErr != nil {
|
||||
return Status{
|
||||
State: SessionStateStale,
|
||||
Diagnostic: "lease exists but could not be read",
|
||||
}
|
||||
}
|
||||
|
||||
state := SessionStateStale
|
||||
if IsFresh(&l, now, StaleLeaseAfter) {
|
||||
state = SessionStateActive
|
||||
}
|
||||
|
||||
// Pre-v0.5.3 leases predate the mode field; treat them as direct so
|
||||
// `info` and `list` render a meaningful value rather than blank.
|
||||
mode := l.Mode
|
||||
if mode == "" {
|
||||
mode = "direct"
|
||||
}
|
||||
|
||||
return Status{
|
||||
State: state,
|
||||
Mode: mode,
|
||||
PID: l.PID,
|
||||
Hostname: l.Hostname,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user