web

package
v0.15.1 Latest Latest
Warning

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

Go to latest
Published: Feb 18, 2026 License: MIT Imports: 25 Imported by: 0

Documentation

Overview

Package web provides HTTP server and SSE streaming for real-time dashboard output.

Index

Constants

View Source
const DefaultReplayerSize = 10000

DefaultReplayerSize is the maximum number of events to keep for replay to late-joining clients.

View Source
const MaxCompletedSessions = 100

MaxCompletedSessions is the maximum number of completed sessions to retain. active sessions are never evicted. oldest completed sessions are removed when this limit is exceeded to prevent unbounded memory growth.

Variables

This section is empty.

Functions

func IsActive

func IsActive(path string) (bool, error)

IsActive checks if a progress file is locked by another process or the current one. returns true if the file is locked (session is running), false otherwise. uses flock with LOCK_EX|LOCK_NB to test without blocking.

func ResolveWatchDirs

func ResolveWatchDirs(cliDirs, configDirs []string) []string

ResolveWatchDirs determines the directories to watch based on precedence: CLI flags > config file > current directory (default). returns at least one directory (current directory if nothing else specified).

Types

type BroadcastLogger

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

BroadcastLogger wraps a Logger and broadcasts events to SSE clients. implements the decorator pattern - all calls are forwarded to the inner logger while also being converted to events for web streaming.

Thread safety: BroadcastLogger is NOT goroutine-safe. All methods must be called from a single goroutine (typically the main execution loop). The SSE server it writes to handles concurrent access from SSE clients.

func NewBroadcastLogger

func NewBroadcastLogger(inner Logger, session *Session, holder *status.PhaseHolder) *BroadcastLogger

NewBroadcastLogger creates a logger that wraps inner and broadcasts to the session's SSE server. registers an OnChange callback on the holder for phase transition events.

func (*BroadcastLogger) LogAnswer

func (b *BroadcastLogger) LogAnswer(answer string)

LogAnswer logs the user's answer for plan creation mode.

func (*BroadcastLogger) LogDraftReview added in v0.6.0

func (b *BroadcastLogger) LogDraftReview(action, feedback string)

LogDraftReview logs the user's draft review action and optional feedback.

func (*BroadcastLogger) LogQuestion

func (b *BroadcastLogger) LogQuestion(question string, options []string)

LogQuestion logs a question and its options for plan creation mode.

func (*BroadcastLogger) Path

func (b *BroadcastLogger) Path() string

Path returns the progress file path.

func (*BroadcastLogger) Print

func (b *BroadcastLogger) Print(format string, args ...any)

Print writes a timestamped message and broadcasts it.

func (*BroadcastLogger) PrintAligned

func (b *BroadcastLogger) PrintAligned(text string)

PrintAligned writes text with timestamp on each line and broadcasts it.

func (*BroadcastLogger) PrintRaw

func (b *BroadcastLogger) PrintRaw(format string, args ...any)

PrintRaw writes without timestamp and broadcasts it.

func (*BroadcastLogger) PrintSection

func (b *BroadcastLogger) PrintSection(section status.Section)

PrintSection writes a section header and broadcasts it. emits task/iteration boundary events based on section type.

type Checkbox

type Checkbox struct {
	Text    string `json:"text"`
	Checked bool   `json:"checked"`
}

Checkbox represents a single checkbox item in a task.

type Dashboard added in v0.6.0

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

Dashboard manages web server and file watching for progress monitoring.

func NewDashboard added in v0.6.0

func NewDashboard(cfg DashboardConfig, holder *status.PhaseHolder) *Dashboard

NewDashboard creates a new dashboard with the given configuration.

func (*Dashboard) RunWatchOnly added in v0.6.0

func (d *Dashboard) RunWatchOnly(ctx context.Context, dirs []string) error

RunWatchOnly runs the web dashboard in watch-only mode without plan execution. monitors directories for progress files and serves the multi-session dashboard.

func (*Dashboard) Start added in v0.6.0

func (d *Dashboard) Start(ctx context.Context) (*BroadcastLogger, error)

Start creates the web server and broadcast logger, starting the server in background. returns the broadcast logger to use for execution, or error if server fails to start. when watchDirs is non-empty, creates multi-session mode with file watching.

type DashboardConfig added in v0.6.0

type DashboardConfig struct {
	BaseLog         Logger           // base progress logger
	Port            int              // web server port
	PlanFile        string           // path to plan file (empty for watch-only mode)
	Branch          string           // current git branch
	WatchDirs       []string         // CLI watch directories
	ConfigWatchDirs []string         // config file watch directories
	Colors          *progress.Colors // colors for output
}

DashboardConfig holds configuration for dashboard initialization.

type DiffStats added in v0.10.0

type DiffStats struct {
	Files     int `json:"files"`
	Additions int `json:"additions"`
	Deletions int `json:"deletions"`
}

DiffStats holds git diff statistics for a session.

type Event

type Event struct {
	Type         EventType    `json:"type"`
	Phase        status.Phase `json:"phase"`
	Section      string       `json:"section,omitempty"`
	Text         string       `json:"text"`
	Timestamp    time.Time    `json:"timestamp"`
	Signal       string       `json:"signal,omitempty"`
	TaskNum      int          `json:"task_num,omitempty"`      // 1-based task index from plan (matches plan.tasks[].number)
	IterationNum int          `json:"iteration_num,omitempty"` // 1-based iteration index for review/codex phases
}

Event represents a single event to be streamed to web clients.

func NewIterationStartEvent

func NewIterationStartEvent(phase status.Phase, iterationNum int, text string) Event

NewIterationStartEvent creates an iteration start event.

func NewOutputEvent

func NewOutputEvent(phase status.Phase, text string) Event

NewOutputEvent creates an output event with current timestamp.

func NewSectionEvent

func NewSectionEvent(phase status.Phase, name string) Event

NewSectionEvent creates a section header event.

func NewSignalEvent

func NewSignalEvent(phase status.Phase, signal string) Event

NewSignalEvent creates a signal event.

func NewTaskEndEvent

func NewTaskEndEvent(phase status.Phase, taskNum int, text string) Event

NewTaskEndEvent creates a task end boundary event.

func NewTaskStartEvent

func NewTaskStartEvent(phase status.Phase, taskNum int, text string) Event

NewTaskStartEvent creates a task start boundary event.

func (Event) JSON deprecated

func (e Event) JSON() ([]byte, error)

JSON returns the event as JSON bytes for SSE streaming.

Deprecated: use json.Marshal(event) instead.

func (Event) MarshalJSON

func (e Event) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler for SSE streaming. this allows Event to be used directly with json.Marshal.

func (Event) ToSSEMessage

func (e Event) ToSSEMessage() *sse.Message

ToSSEMessage converts the event to a go-sse Message for streaming. the event is serialized as JSON in the data field. we don't set the SSE event type because browsers' onmessage handler only catches typeless events (or type "message"). the event type is already in the JSON payload for client-side processing.

type EventType

type EventType string

EventType represents the type of event being streamed.

const (
	EventTypeOutput         EventType = "output"          // regular output line
	EventTypeSection        EventType = "section"         // section header
	EventTypeError          EventType = "error"           // error message
	EventTypeWarn           EventType = "warn"            // warning message
	EventTypeSignal         EventType = "signal"          // completion/failure signal
	EventTypeTaskStart      EventType = "task_start"      // task execution started
	EventTypeTaskEnd        EventType = "task_end"        // task execution ended
	EventTypeIterationStart EventType = "iteration_start" // review/codex iteration started
)

event type constants for SSE streaming.

type Logger added in v0.9.0

type Logger interface {
	Print(format string, args ...any)
	PrintRaw(format string, args ...any)
	PrintSection(section status.Section)
	PrintAligned(text string)
	LogQuestion(question string, options []string)
	LogAnswer(answer string)
	LogDraftReview(action string, feedback string)
	Path() string
}

Logger provides progress logging for web dashboard wrapping.

type ParsedLine added in v0.9.0

type ParsedLine struct {
	Type      ParsedLineType
	Text      string       // content text (without timestamp prefix for timestamped lines)
	Section   string       // section name (only for ParsedLineSection)
	Timestamp time.Time    // parsed timestamp (only for ParsedLineTimestamp)
	EventType EventType    // detected event type (output, error, warn, signal)
	Signal    string       // extracted signal name, if any
	Phase     status.Phase // phase derived from section name (only for ParsedLineSection)
}

ParsedLine is the result of parsing a single progress file line. callers convert this to Event objects with their own context (phase, pending section logic, etc.).

type ParsedLineType added in v0.9.0

type ParsedLineType int

ParsedLineType indicates what kind of progress line was parsed.

const (
	ParsedLineSkip      ParsedLineType = iota // header line or separator, should be skipped
	ParsedLineSection                         // section header (--- name ---)
	ParsedLineTimestamp                       // timestamped content line
	ParsedLinePlain                           // plain text without timestamp
)

parsed line type constants.

type Plan

type Plan struct {
	Title string `json:"title"`
	Tasks []Task `json:"tasks"`
}

Plan represents a parsed plan file.

func ParsePlan

func ParsePlan(content string) (*Plan, error)

ParsePlan parses a plan markdown file into a structured Plan.

func ParsePlanFile

func ParsePlanFile(path string) (*Plan, error)

ParsePlanFile reads and parses a plan file from disk.

func (*Plan) JSON

func (p *Plan) JSON() ([]byte, error)

JSON returns the plan as JSON bytes.

type Server

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

Server provides HTTP server for the real-time dashboard.

func NewServer

func NewServer(cfg ServerConfig, session *Session) (*Server, error)

NewServer creates a new web server for single-session mode (direct execution). returns an error if the embedded template fails to parse.

func NewServerWithSessions

func NewServerWithSessions(cfg ServerConfig, sm *SessionManager) (*Server, error)

NewServerWithSessions creates a new web server for multi-session mode (dashboard). returns an error if the embedded template fails to parse.

func (*Server) Session

func (s *Server) Session() *Session

Session returns the server's session (for single-session mode).

func (*Server) Start

func (s *Server) Start(ctx context.Context) error

Start begins listening for HTTP requests. blocks until the server is stopped or an error occurs.

func (*Server) Stop

func (s *Server) Stop() error

Stop gracefully shuts down the server.

type ServerConfig

type ServerConfig struct {
	Port     int    // port to listen on
	PlanName string // plan name to display in dashboard
	Branch   string // git branch name
	PlanFile string // path to plan file for /api/plan endpoint
}

ServerConfig holds configuration for the web server.

type Session

type Session struct {
	ID       string          // unique identifier (derived from progress filename)
	Path     string          // full path to progress file
	Metadata SessionMetadata // parsed header information
	State    SessionState    // current state (active/completed)
	SSE      *sse.Server     // SSE server for this session (handles subscriptions and replay)
	Tailer   *Tailer         // file tailer for reading new content (nil if not tailing)
	// contains filtered or unexported fields
}

Session represents a single ralphex execution instance. each session corresponds to one progress file and maintains its own SSE server.

func NewSession

func NewSession(id, path string) *Session

NewSession creates a new session for the given progress file path. the session starts with an SSE server configured for event replay. metadata should be populated by calling ParseMetadata after creation.

func (*Session) Close

func (s *Session) Close()

Close cleans up session resources including the tailer and SSE server.

func (*Session) GetDiffStats added in v0.10.0

func (s *Session) GetDiffStats() *DiffStats

GetDiffStats returns a copy of the diff stats, or nil if not set.

func (*Session) GetLastModified

func (s *Session) GetLastModified() time.Time

GetLastModified returns the last modified time thread-safely.

func (*Session) GetMetadata

func (s *Session) GetMetadata() SessionMetadata

GetMetadata returns the session's metadata thread-safely.

func (*Session) GetState

func (s *Session) GetState() SessionState

GetState returns the session's state thread-safely.

func (*Session) IsLoaded

func (s *Session) IsLoaded() bool

IsLoaded returns whether historical data has been loaded into the SSE server.

func (*Session) IsTailing

func (s *Session) IsTailing() bool

IsTailing returns whether the session is currently tailing its progress file.

func (*Session) MarkLoadedIfNot

func (s *Session) MarkLoadedIfNot() bool

MarkLoadedIfNot atomically checks if the session is not loaded and marks it as loaded. returns true if the session was successfully marked (was not loaded before), false if it was already loaded. this prevents double-loading race conditions.

func (*Session) Publish

func (s *Session) Publish(event Event) error

Publish sends an event to all connected SSE clients and stores it for replay. returns an error if publishing fails.

func (*Session) SetDiffStats added in v0.10.0

func (s *Session) SetDiffStats(stats DiffStats)

SetDiffStats stores diff stats for the session.

func (*Session) SetLastModified

func (s *Session) SetLastModified(t time.Time)

SetLastModified updates the last modified time thread-safely.

func (*Session) SetMetadata

func (s *Session) SetMetadata(meta SessionMetadata)

SetMetadata updates the session's metadata thread-safely.

func (*Session) SetState

func (s *Session) SetState(state SessionState)

SetState updates the session's state thread-safely.

func (*Session) StartTailing

func (s *Session) StartTailing(fromStart bool) error

StartTailing begins tailing the progress file and feeding events to SSE clients. if fromStart is true, reads from the beginning of the file. does nothing if already tailing.

func (*Session) StopTailing

func (s *Session) StopTailing()

StopTailing stops the tailer and event feeder goroutine.

type SessionInfo

type SessionInfo struct {
	ID    string       `json:"id"`
	State SessionState `json:"state"`
	// dir is the short display name for the project (last path segment of session directory).
	Dir string `json:"dir"`
	// DirPath is the full filesystem path to the session directory (used for grouping and copy-to-clipboard).
	DirPath      string     `json:"dirPath,omitempty"`
	PlanPath     string     `json:"planPath,omitempty"`
	Branch       string     `json:"branch,omitempty"`
	Mode         string     `json:"mode,omitempty"`
	StartTime    time.Time  `json:"startTime"`
	LastModified time.Time  `json:"lastModified"`
	DiffStats    *DiffStats `json:"diffStats,omitempty"`
}

SessionInfo represents session data for the API response.

type SessionManager

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

SessionManager maintains a registry of all discovered sessions. it handles discovery of progress files, state detection via flock, and provides access to sessions by ID. completed sessions are automatically evicted when MaxCompletedSessions is exceeded.

func NewSessionManager

func NewSessionManager() *SessionManager

NewSessionManager creates a new session manager with an empty registry.

func (*SessionManager) All

func (m *SessionManager) All() []*Session

All returns all sessions in the registry.

func (*SessionManager) Close

func (m *SessionManager) Close()

Close closes all sessions and clears the registry.

func (*SessionManager) Discover

func (m *SessionManager) Discover(dir string) ([]string, error)

Discover scans a directory for progress files matching progress-*.txt pattern. for each file found, it creates or updates a session in the registry. returns the list of discovered session IDs.

func (*SessionManager) DiscoverRecursive

func (m *SessionManager) DiscoverRecursive(root string) ([]string, error)

DiscoverRecursive walks a directory tree and discovers all progress files. unlike Discover, this searches subdirectories recursively. returns the list of all discovered session IDs (deduplicated).

func (*SessionManager) Get

func (m *SessionManager) Get(id string) *Session

Get returns a session by ID, or nil if not found.

func (*SessionManager) RefreshStates

func (m *SessionManager) RefreshStates()

RefreshStates checks all sessions for state changes (active->completed). stops tailing for sessions that have completed.

func (*SessionManager) Register

func (m *SessionManager) Register(session *Session)

Register adds an externally-created session to the manager. This is used when a session is created for live execution (BroadcastLogger) and needs to be visible in the multi-session dashboard. The session's ID is derived from its path using sessionIDFromPath.

func (*SessionManager) Remove

func (m *SessionManager) Remove(id string)

Remove removes a session from the registry and closes its resources.

func (*SessionManager) StartTailingActive

func (m *SessionManager) StartTailingActive()

StartTailingActive starts tailing for all active sessions. for each active session not already tailing, starts tailing from the beginning to populate the buffer with existing content.

type SessionMetadata

type SessionMetadata struct {
	PlanPath  string    // path to plan file (from "Plan:" header line)
	Branch    string    // git branch (from "Branch:" header line)
	Mode      string    // execution mode: full, review, codex-only (from "Mode:" header line)
	StartTime time.Time // start time (from "Started:" header line)
}

SessionMetadata holds parsed information from progress file header.

func ParseProgressHeader

func ParseProgressHeader(path string) (SessionMetadata, error)

ParseProgressHeader reads the header section of a progress file and extracts metadata. the header format is:

# Ralphex Progress Log
Plan: path/to/plan.md
Branch: feature-branch
Mode: full
Started: 2026-01-22 10:30:00
------------------------------------------------------------

type SessionState

type SessionState string

SessionState represents the current state of a session.

const (
	SessionStateActive    SessionState = "active"    // session is running (progress file locked)
	SessionStateCompleted SessionState = "completed" // session finished (no lock held)
)

session state constants.

type Tailer

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

Tailer watches a progress file and emits events for new lines. it parses progress file format (timestamps, sections) into Event structs.

func NewTailer

func NewTailer(path string, config TailerConfig) *Tailer

NewTailer creates a new Tailer for the given progress file. the tailer starts in stopped state; call Start() to begin tailing.

func (*Tailer) Events

func (t *Tailer) Events() <-chan Event

Events returns the channel that emits parsed events. events are emitted in order as lines are read from the file.

func (*Tailer) IsRunning

func (t *Tailer) IsRunning() bool

IsRunning returns whether the tailer is currently active.

func (*Tailer) Start

func (t *Tailer) Start(fromStart bool) error

Start begins tailing the file from the current position. if fromStart is true, reads from the beginning; otherwise reads from current end. note: Tailer is not reusable after Stop() - create a new instance instead.

func (*Tailer) Stop

func (t *Tailer) Stop()

Stop stops the tailer and closes resources. blocks until the tail loop has fully stopped. safe to call multiple times concurrently.

type TailerConfig

type TailerConfig struct {
	PollInterval time.Duration // how often to check for new content (default: 100ms)
	InitialPhase status.Phase  // phase to use for events (default: PhaseTask)
}

TailerConfig holds configuration for the Tailer.

func DefaultTailerConfig

func DefaultTailerConfig() TailerConfig

DefaultTailerConfig returns default configuration.

type Task

type Task struct {
	Number     int        `json:"number"`
	Title      string     `json:"title"`
	Status     TaskStatus `json:"status"`
	Checkboxes []Checkbox `json:"checkboxes"`
}

Task represents a task section in a plan.

type TaskStatus

type TaskStatus string

TaskStatus represents the execution status of a task.

const (
	TaskStatusPending TaskStatus = "pending"
	TaskStatusActive  TaskStatus = "active"
	TaskStatusDone    TaskStatus = "done"
	TaskStatusFailed  TaskStatus = "failed"
)

task status constants.

type Watcher

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

Watcher monitors directories for progress file changes. it uses fsnotify for efficient file system event detection and notifies the SessionManager when new progress files appear.

func NewWatcher

func NewWatcher(dirs []string, sm *SessionManager) (*Watcher, error)

NewWatcher creates a watcher for the specified directories. directories are watched recursively for progress-*.txt files.

func (*Watcher) Close

func (w *Watcher) Close() error

Close stops the watcher and releases resources.

func (*Watcher) Start

func (w *Watcher) Start(ctx context.Context) error

Start begins watching directories for progress file changes. runs until the context is canceled. performs initial discovery before starting the watch loop.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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