Documentation
¶
Overview ¶
Package state provides manifest CRUD, locking, and session discovery.
Package state provides manifest CRUD, locking, and session discovery.
Index ¶
- Constants
- Variables
- func AppendStateEvent(id string, ev StateEvent) error
- func ArmQuestLoop(sessionID string, since time.Time, force bool) error
- func ClearQuest(sessionID string) error
- func ClearQuestLoop(sessionID string) error
- func DisplayColorOptions() []string
- func HasSessionIDPrefix(id string) bool
- func InitStartingState(id string, agentsByRole map[string]string) error
- func IsQuestAttached(questID string) (bool, error)
- func IsValidSessionID(id string) bool
- func NewSessionID(timestamp int64) string
- func NewSessionIDWithSuffix(timestamp, suffix int64) string
- func NormalizeDisplayColor(color string) string
- func NowUTC() string
- func QuestIDForSession(sessionID string) (string, error)
- func SanitizeResumeID(v string) string
- func SaveSessionState(id string, ss *SessionState) error
- func SessionIDFromEnv() string
- func SessionStateDir(root, id string) string
- func SessionStateLockPath(root, id string) string
- func SessionStateLogPath(root, id string) string
- func SessionStatePath(root, id string) string
- func SessionsForQuest(questID string) ([]string, error)
- func SortByMtime(manifests []Manifest, root string)
- func StampQuest(sessionID, questID string) error
- func StateRoot() string
- func TrimSessionIDPrefix(id string) string
- func UpdateQuestLoop(sessionID string, iterations int, verdict string) error
- func UpdateSessionState(id string, mutate func(*SessionState) bool) error
- type AgentManifest
- type DisplayMetadata
- type Manifest
- type PaneState
- type QuestLoopState
- type SessionState
- type StateEvent
- type Store
- func (s *Store) AddWorker(sessionID, workerID string) error
- func (s *Store) Create(m Manifest) error
- func (s *Store) Delete(sessionID string) error
- func (s *Store) DiscoverSessions() ([]Manifest, error)
- func (s *Store) GetWorkers(sessionID string) ([]string, error)
- func (s *Store) Read(sessionID string) (Manifest, error)
- func (s *Store) RemoveWorker(sessionID, workerID string) error
- func (s *Store) Root() string
- func (s *Store) Update(sessionID string, fn func(*Manifest)) error
Constants ¶
const ( // SessionIDPrefix is used for all questmaster session IDs. SessionIDPrefix = "qm-" // SessionEnv is the environment variable for the current session ID. SessionEnv = "QUESTMASTER_SESSION" // StateRootEnv is the environment variable for the state root. StateRootEnv = "QUESTMASTER_STATE_ROOT" )
const DefaultDisplayColor = "blue"
const SchemaVersion = 1
SchemaVersion is the current PaneState/SessionState schema version. Hook payloads that load a SessionState with a different Version overwrite it; readers (the tracker) treat foreign-version state as `unknown`.
const StateJSONLMaxSize = 1 << 20 // 1 MiB
StateJSONLMaxSize is the rotation threshold for state.jsonl. When the file exceeds this size, the writer rotates it to state.jsonl.1 and starts a fresh log. One historical file is kept.
Variables ¶
var ( // ErrManifestExists is returned when Create finds an existing manifest. ErrManifestExists = errors.New("manifest already exists") // ErrManifestNotFound is returned when Delete targets a missing manifest. ErrManifestNotFound = errors.New("manifest not found") )
Sentinel errors for manifest operations.
var ErrQuestLoopArmed = errors.New("quest loop already armed")
ErrQuestLoopArmed means a session already carries an advisory loop marker.
Functions ¶
func AppendStateEvent ¶
func AppendStateEvent(id string, ev StateEvent) error
AppendStateEvent appends to state.jsonl and rotates the file when it crosses StateJSONLMaxSize. The state root must already exist (the session-state directory is created lazily). Rotation drops the previous .1 file, so only one rolled file is retained on disk.
func ArmQuestLoop ¶ added in v0.3.13
ArmQuestLoop sets the advisory marker for an armed foreground loop. With force=true, an existing stale marker is replaced.
func ClearQuest ¶ added in v0.3.12
ClearQuest detaches a session from its quest, returning the quest to the board. A no-op when the session is already unattached.
func ClearQuestLoop ¶ added in v0.3.13
ClearQuestLoop removes the advisory loop marker. It is safe to call even when no loop is currently marked.
func DisplayColorOptions ¶ added in v0.3.4
func DisplayColorOptions() []string
DisplayColorOptions returns the supported named display colors.
func HasSessionIDPrefix ¶ added in v0.2.12
HasSessionIDPrefix reports whether id starts with the session prefix, without validating the full ID shape.
func InitStartingState ¶
InitStartingState seeds state.json with State="starting" / Activity="started" for each provided pane (roleName → agentName, e.g. {"primary": "codex"}). Skips any pane that already has a non-empty State so the agent's first hook (which fires moments later) is never overwritten. This eliminates the brief "unknown" the tracker would otherwise render between spawn and the SessionStart hook.
func IsQuestAttached ¶ added in v0.3.12
IsQuestAttached reports whether any session is on the quest.
func IsValidSessionID ¶ added in v0.2.12
IsValidSessionID reports whether id is a supported questmaster session ID.
func NewSessionID ¶ added in v0.2.12
NewSessionID formats the base ID for a new questmaster session.
func NewSessionIDWithSuffix ¶ added in v0.2.12
NewSessionIDWithSuffix formats a collision-retry ID for a new questmaster session.
func NormalizeDisplayColor ¶ added in v0.3.4
NormalizeDisplayColor returns a supported color name or the default.
func NowUTC ¶
func NowUTC() string
NowUTC returns the current time in the format used by bash manifest helpers.
func QuestIDForSession ¶ added in v0.3.12
QuestIDForSession returns the session's quest_id, or "" when the session is free or has no state.
func SanitizeResumeID ¶
SanitizeResumeID returns v unchanged when it's a safe identifier, or "" when it contains characters that could escape filesystem / glob contexts (path traversal, wildcards, NUL, etc.). Exported so downstream consumers share the single source of truth rather than carrying their own copy of the regex.
func SaveSessionState ¶
func SaveSessionState(id string, ss *SessionState) error
SaveSessionState writes the state.json for a session atomically. The write is serialized via flock on a sibling lockfile.
func SessionIDFromEnv ¶ added in v0.2.12
func SessionIDFromEnv() string
SessionIDFromEnv returns the current session ID from QUESTMASTER_SESSION.
func SessionStateDir ¶
SessionStateDir returns <root>/<id>. Caller must ensure id is valid.
func SessionStateLockPath ¶
SessionStateLockPath returns the flock path. We lock a sibling file rather than state.json itself so the atomic rename of state.json never races a still-held lock fd.
func SessionStateLogPath ¶
SessionStateLogPath returns the state.jsonl path for the session.
func SessionStatePath ¶
SessionStatePath returns the state.json path for the session.
func SessionsForQuest ¶ added in v0.3.12
SessionsForQuest scans every session's state.json and returns the ids stamped with questID, sorted. These are the directly-attached sessions (masters and standalones); a quest with none reads unattached. Worker membership via the session tree is layered on by callers that have the manifest store.
func SortByMtime ¶
SortByMtime sorts manifests by file modification time, newest first.
func StampQuest ¶ added in v0.3.12
StampQuest sets the session's quest_id. The read-modify-write runs inside the per-session flock (UpdateSessionState), so it never clobbers a concurrent hook write. A session with no state.json yet gets a minimal one — attaching is an explicit act.
func StateRoot ¶
func StateRoot() string
StateRoot resolves the directory that holds per-session state. Honors $QUESTMASTER_STATE_ROOT, then falls back to ~/.questmaster-state. Returns the empty string when neither an override nor $HOME is set (caller treats that as a no-op condition).
func TrimSessionIDPrefix ¶ added in v0.2.12
TrimSessionIDPrefix removes the session prefix when present.
func UpdateQuestLoop ¶ added in v0.3.13
UpdateQuestLoop records the latest visible loop progress if a marker is present. It is advisory only; a missing marker is a no-op.
func UpdateSessionState ¶
func UpdateSessionState(id string, mutate func(*SessionState) bool) error
UpdateSessionState performs a locked read-modify-write. mutate runs against the freshly-loaded state inside the critical section. Returning false from mutate aborts the write (used when the freshly-read state no longer satisfies the precondition the caller checked optimistically).
This is the only safe way to apply tracker-side mutations like done → idle: a naive Load → mutate → Save races against hooks because the load happens outside the lock.
Types ¶
type AgentManifest ¶
type AgentManifest struct {
Name string `json:"name"`
Role string `json:"role"`
CLI string `json:"cli"`
ResumeID string `json:"resume_id,omitempty"`
Window int `json:"window"`
}
AgentManifest stores per-agent runtime state in the manifest.
type DisplayMetadata ¶ added in v0.3.4
type DisplayMetadata struct {
Color string `json:"color,omitempty"`
Extra map[string]json.RawMessage `json:"-"`
}
DisplayMetadata stores user-facing presentation hints for a session. Extra preserves unknown nested display keys written by newer versions.
func NewDisplayMetadata ¶ added in v0.3.4
func NewDisplayMetadata(color string) *DisplayMetadata
NewDisplayMetadata creates display metadata with a valid color.
func (DisplayMetadata) IsZero ¶ added in v0.3.4
func (d DisplayMetadata) IsZero() bool
func (DisplayMetadata) MarshalJSON ¶ added in v0.3.4
func (d DisplayMetadata) MarshalJSON() ([]byte, error)
MarshalJSON merges typed display fields with Extra to preserve unknown keys.
func (*DisplayMetadata) UnmarshalJSON ¶ added in v0.3.4
func (d *DisplayMetadata) UnmarshalJSON(data []byte) error
UnmarshalJSON preserves unknown nested display fields in Extra.
type Manifest ¶
type Manifest struct {
SessionID string `json:"session_id"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
Title string `json:"title,omitempty"`
Cwd string `json:"cwd,omitempty"`
WindowName string `json:"window_name,omitempty"`
Agents []AgentManifest `json:"agents,omitempty"`
AgentPath string `json:"agent_path,omitempty"`
SessionType string `json:"session_type,omitempty"`
Workers []string `json:"workers,omitempty"`
Display *DisplayMetadata `json:"display,omitempty"`
// Extra preserves unknown fields written by bash helpers
// (e.g. parent_session, initial_prompt).
Extra map[string]json.RawMessage `json:"-"`
}
Manifest represents a questmaster session's persisted state. Extra holds unknown fields to preserve fields this version does not interpret.
func (Manifest) DisplayColor ¶ added in v0.3.4
DisplayColor returns the manifest's normalized display color, or an empty string when the manifest has no display color metadata.
func (Manifest) ExtraString ¶
ExtraString reads a string value from the manifest's Extra map.
func (Manifest) MarshalJSON ¶
MarshalJSON merges typed fields with Extra to preserve unknown keys.
func (*Manifest) UnmarshalJSON ¶
UnmarshalJSON preserves unknown fields in Extra.
type PaneState ¶
type PaneState struct {
Role string `json:"role"`
Agent string `json:"agent"`
State string `json:"state"`
Activity string `json:"activity"`
Tool string `json:"tool,omitempty"`
Seq int64 `json:"seq"`
LastEvent time.Time `json:"last_event"`
LastKind string `json:"last_kind"`
WorkingSince time.Time `json:"working_since,omitempty"`
Recent []string `json:"recent,omitempty"`
SessionFile string `json:"session_file,omitempty"`
PiSessionID string `json:"pi_session_id,omitempty"`
}
PaneState is the renderer-visible state for one role within a session. WorkingSince timestamps the moment State transitioned to "working" and is preserved across PreToolUse/PostToolUse cycles within the same turn, so the tracker can render an ever-growing "working 2m14s" suffix. Pi-specific carry-through fields are populated only when Agent == "pi".
type QuestLoopState ¶ added in v0.3.13
type QuestLoopState struct {
Since time.Time `json:"since"`
Iterations int `json:"iterations"`
LastVerdict string `json:"last_verdict,omitempty"`
}
QuestLoopState is the renderer-visible marker for an armed quest loop.
type SessionState ¶
type SessionState struct {
SessionID string `json:"session_id"`
Version int `json:"version"`
Panes map[string]PaneState `json:"panes"`
SeenAt time.Time `json:"seen_at"`
// QuestID links a session to an active quest. It is stamped on a master or
// standalone at attach and cleared on detach; workers inherit their
// master's quest via the session tree and carry no own stamp. "Sessions on
// a quest" is derived by scanning this field, never stored on the quest.
// Preserved across hook writes because hooks read-modify-write the whole
// SessionState (UpdateSessionState).
QuestID string `json:"quest_id,omitempty"`
// QuestLoop is an advisory marker written while `qm quest loop` is armed
// for this session. The foreground process is authoritative; this marker
// only drives visibility and double-arm refusal.
QuestLoop *QuestLoopState `json:"quest_loop,omitempty"`
}
SessionState is the authoritative per-session state snapshot written by hooks and read by the tracker. One file per session at <state_root>/<session_id>/state.json.
func LoadSessionState ¶
func LoadSessionState(id string) (*SessionState, error)
LoadSessionState reads the state.json for a session. Returns (nil, nil) if the file does not exist (the renderer should treat this as "unknown"). The state root is resolved from $QUESTMASTER_STATE_ROOT or $HOME.
type StateEvent ¶
type StateEvent struct {
Ts time.Time `json:"ts"`
Agent string `json:"agent"`
Role string `json:"role,omitempty"`
Action string `json:"action"`
State string `json:"state,omitempty"`
Activity string `json:"activity,omitempty"`
Tool string `json:"tool,omitempty"`
Kind string `json:"kind,omitempty"`
Fields map[string]interface{} `json:"fields,omitempty"`
}
StateEvent is one line of state.jsonl. Free-form fields beyond the fixed columns belong in Fields so consumers parsing the log don't have to keep up with a moving schema.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store manages manifest files on disk with flock-based locking.
func OpenStore ¶
OpenStore opens a Store for read-only access without creating the directory. DiscoverSessions and other reads gracefully return empty results if the directory does not exist. Use NewStore for mutating operations that need the directory.
func (*Store) Create ¶
Create writes a new manifest. Returns an error if it already exists. Uses flock to prevent TOCTOU races between concurrent Create calls.
func (*Store) DiscoverSessions ¶
DiscoverSessions returns all supported questmaster session manifests found in the state root. Non-session files, lock files, and corrupt manifests are silently skipped.
func (*Store) GetWorkers ¶
GetWorkers returns the worker IDs from a manifest.
func (*Store) RemoveWorker ¶
RemoveWorker removes a worker ID from the manifest's workers list.