web

package
v0.18.0 Latest Latest
Warning

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

Go to latest
Published: May 22, 2026 License: MIT Imports: 36 Imported by: 0

Documentation

Overview

Package web implements the Hearth 2.0 web UI backend for the forge daemon.

The package serves a small chi-based HTTP server that exposes read-only JSON endpoints mirroring the existing IPC commands (status, queue, workers) plus a bcrypt-validated session login. It is intended to run in-process inside the daemon (no extra socket hop) and is gated by the FORGE_WEB_ENABLED environment variable.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ParseUsers

func ParseUsers(raw string) (map[string]string, error)

ParseUsers parses a FORGE_USERS-formatted string. Exposed separately from ParseUsersFromEnv to make testing simpler.

func ParseUsersFromEnv

func ParseUsersFromEnv() (map[string]string, error)

ParseUsersFromEnv parses the FORGE_USERS environment variable. The format is a comma-separated list of user:bcrypt-hash entries:

FORGE_USERS=alice:$2a$10$abcd...,bob:$2a$10$wxyz...

Whitespace around entries is trimmed. Bcrypt hashes contain ':' inside their cost/salt section, so ParseUsersFromEnv splits on the FIRST ':' per entry and treats everything after it as the hash.

Returns an empty map when the env var is unset or empty. An entry with a blank username or hash returns an error.

func SessionFromContext

func SessionFromContext(ctx context.Context) *state.WebSession

SessionFromContext returns the authenticated session attached to ctx by the auth middleware, or nil when no session is present.

func VerifyCredentials

func VerifyCredentials(users map[string]string, username, password string) error

VerifyCredentials returns nil when the password matches the bcrypt hash stored for the user. Returns errInvalidCredentials for unknown users or password mismatches; this avoids leaking which half of the pair was wrong.

Types

type AnvilDispatchTagLister

type AnvilDispatchTagLister func() map[string]string

AnvilDispatchTagLister returns the per-anvil auto-dispatch tag from forge.yaml (`auto_dispatch_tag`) as a map of name -> tag. Anvils with no tag configured are omitted from the map. Used by the Hearth web UI's one-click "Apply tag" action so the dispatch label is resolved server-side and matches the daemon's runtime config (including hot-reloads). Implementations must be safe to call concurrently.

type AnvilLister

type AnvilLister func() map[string]string

AnvilLister returns the registered anvils as a map of name -> on-disk path. Callers use this for the Beads-Forge bead-emission flow: the web layer passes the names to claude (so it knows which anvils to target) and resolves names to paths when shelling out to bd. Implementations must be safe to call concurrently — the daemon's hot-reload may swap the config underneath.

type BdRunnerFn

type BdRunnerFn = forgechat.BdRunner

BdRunnerFn is a process-spawning function compatible with the materializer in package forgechat. The daemon supplies forgechat.DefaultBdRunner; tests inject a fake to avoid spawning real bd subprocesses.

type CommandHandler

type CommandHandler func(cmd ipc.Command) ipc.Response

CommandHandler is the in-process dispatcher used by the web layer to call daemon command handlers without going through the IPC socket. The daemon passes its own handleIPC method here.

type Config

type Config struct {
	// Addr is the TCP listen address (e.g. ":8080"). Required.
	Addr string

	// Users maps username -> bcrypt hash. May be empty, in which case the
	// /login endpoint always rejects.
	Users map[string]string

	// SessionTTL is the sliding session lifetime. Defaults to 30 days when
	// zero.
	SessionTTL time.Duration

	// CookieName is the session cookie name. Defaults to "forge_session".
	CookieName string

	// CookieSecure forces the Secure cookie attribute. Defaults to false so
	// local development over plain HTTP works; production deployments behind
	// HTTPS should set this to true.
	CookieSecure bool

	// PurgeInterval controls how often expired session rows are purged from
	// the DB. Defaults to 1 hour. Set to a negative value to disable.
	PurgeInterval time.Duration
}

Config holds the runtime configuration for the web server. Most fields are derived from environment variables in NewConfigFromEnv.

type Server

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

Server is the chi-based HTTP server. Construct with New and run with Start.

func New

func New(cfg Config, db *state.DB, handler CommandHandler, logger *slog.Logger) (*Server, error)

New constructs a Server. The cfg is validated; an error is returned when required fields are missing.

func (*Server) SetAnvilDispatchTagLister

func (s *Server) SetAnvilDispatchTagLister(a AnvilDispatchTagLister)

SetAnvilDispatchTagLister installs the per-anvil dispatch tag callback used by the one-click Apply-tag action on the Hearth queue. nil clears the callback, after which apply-dispatch-tag requests are rejected.

func (*Server) SetAnvilLister

func (s *Server) SetAnvilLister(a AnvilLister)

SetAnvilLister installs the registry callback used by the Beads-Forge bead-emission flow. The daemon snapshots its current config on each call so hot-reloads are picked up automatically.

func (*Server) SetBdRunner

func (s *Server) SetBdRunner(r BdRunnerFn)

SetBdRunner installs the bd subprocess shim used for bead materialisation. nil restores the default (forgechat.DefaultBdRunner).

func (*Server) SetChatRunner

func (s *Server) SetChatRunner(r forgechat.Runner)

SetChatRunner installs the AI runner used by the Beads-Forge page. The daemon constructs the runner from its provider chain after web.New returns, so this is wired in via a setter rather than the Config struct. nil clears the runner.

func (*Server) SetTurnTimeout added in v0.17.0

func (s *Server) SetTurnTimeout(d time.Duration)

SetTurnTimeout overrides the hard backstop applied to each async turn goroutine. The default (15m) matches MaxForgeChatTurnTimeout from the config package. Used by tests to exercise the timeout sentinel without waiting fifteen real minutes.

func (*Server) Start

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

Start begins serving HTTP requests. It blocks until ctx is cancelled or the server returns a non-Closed error.

func (*Server) TurnStore added in v0.17.0

func (s *Server) TurnStore() *TurnStore

TurnStore exposes the in-flight turn registry for the SSE / polling endpoints (sibling sub-tasks) and tests. Never nil after New returns.

type TurnEvent added in v0.17.0

type TurnEvent struct {
	Type TurnEventType `json:"type"`
	Data any           `json:"data,omitempty"`
}

TurnEvent is one item delivered on TurnState.Events. Data's concrete type depends on Type — consumers should type-switch on it.

type TurnEventType added in v0.17.0

type TurnEventType string

TurnEventType is the typed kind of an event emitted on TurnState.Events. Mirrors the SSE event names the sibling streaming endpoint will surface to clients.

const (
	TurnEventTextDelta  TurnEventType = "text_delta"
	TurnEventToolUse    TurnEventType = "tool_use"
	TurnEventToolResult TurnEventType = "tool_result"
	// TurnEventMessage carries a persisted assistant message. Data is the
	// state.ForgeSessionMessage row so SSE consumers can replay the chat
	// view in real time.
	TurnEventMessage  TurnEventType = "message"
	TurnEventComplete TurnEventType = "complete"
	TurnEventError    TurnEventType = "error"
)

type TurnSnapshot added in v0.17.0

type TurnSnapshot struct {
	ID             string      `json:"id"`
	SessionID      int64       `json:"session_id"`
	Status         TurnStatus  `json:"status"`
	Text           string      `json:"text,omitempty"`
	ToolEvents     []TurnEvent `json:"tool_events,omitempty"`
	FinalMessageID int64       `json:"final_message_id,omitempty"`
	Error          string      `json:"error,omitempty"`
}

TurnSnapshot is the immutable view of a TurnState returned by Snapshot. The polling endpoint serialises this directly to JSON.

type TurnState added in v0.17.0

type TurnState struct {
	// ID is the UUID assigned to this turn. It is the public handle returned
	// from POST /turn and used in the SSE / polling URLs.
	ID string
	// SessionID is the owning forge_session row.
	SessionID int64

	// Done is closed by the goroutine when the turn reaches a terminal state.
	// Callers that need to wait for completion (tests, polling endpoints) can
	// select on this without consuming events.
	Done chan struct{}
	// contains filtered or unexported fields
}

TurnState holds the in-flight state of a single Beads-Forge AI turn.

It is created by the /turn handler before launching the background goroutine, and read by the SSE / polling endpoints (sibling sub-tasks). Call Subscribe to obtain a per-client channel that receives every event emitted by the runner; Done signals the terminal state.

All mutating helpers take the mutex internally; readers should call Snapshot rather than touching the unexported fields directly.

func (*TurnState) AppendText added in v0.17.0

func (t *TurnState) AppendText(chunk string)

AppendText accumulates streamed assistant text. The full accumulated text is available via Snapshot.Text.

func (*TurnState) Emit added in v0.17.0

func (t *TurnState) Emit(ev TurnEvent)

Emit broadcasts ev to all per-client subscriber channels without blocking. Events are dropped for any subscriber whose buffer is full; the SSE endpoint replays missed state via Snapshot on initial connect.

func (*TurnState) Err added in v0.17.0

func (t *TurnState) Err() error

Err returns the recorded error, if any.

func (*TurnState) FinalMessageID added in v0.17.0

func (t *TurnState) FinalMessageID() int64

FinalMessageID returns the id of the last persisted assistant message. Zero before the turn completes.

func (*TurnState) RecordToolEvent added in v0.17.0

func (t *TurnState) RecordToolEvent(ev TurnEvent)

RecordToolEvent appends a tool_use / tool_result event to the persisted log so the polling endpoint can replay tool activity from the snapshot. Callers that also need fanout should call Emit separately.

func (*TurnState) SetError added in v0.17.0

func (t *TurnState) SetError(err error)

SetError records the terminal error and transitions status to error.

func (*TurnState) SetFinalMessageID added in v0.17.0

func (t *TurnState) SetFinalMessageID(id int64)

SetFinalMessageID records the ID of the last assistant message persisted during this turn. SSE clients use this on the `complete` event to fetch the canonical row from the messages list.

func (*TurnState) Snapshot added in v0.17.0

func (t *TurnState) Snapshot() TurnSnapshot

Snapshot returns a point-in-time copy of the state. Safe to call from any goroutine.

func (*TurnState) Status added in v0.17.0

func (t *TurnState) Status() TurnStatus

Status returns the current status.

func (*TurnState) Subscribe added in v0.17.0

func (t *TurnState) Subscribe(ctx context.Context) <-chan TurnEvent

Subscribe returns a dedicated per-client channel that receives every event the runner emits. The channel is closed when the turn ends. When the turn has already ended the returned channel is immediately closed so consumers can detect that without missing a terminal notification.

The goroutine launched here unsubscribes when ctx is cancelled (typically when the HTTP request ends), keeping the broadcaster map tidy.

func (*TurnState) Text added in v0.17.0

func (t *TurnState) Text() string

Text returns the accumulated assistant text so far.

type TurnStatus added in v0.17.0

type TurnStatus string

TurnStatus is the lifecycle phase of an in-flight Beads-Forge turn.

const (
	// TurnStatusPending is the initial state, set when the handler creates the
	// store entry but the goroutine has not yet begun.
	TurnStatusPending TurnStatus = "pending"
	// TurnStatusRunning is set when the goroutine has started invoking the AI
	// runner.
	TurnStatusRunning TurnStatus = "running"
	// TurnStatusComplete is the terminal success state.
	TurnStatusComplete TurnStatus = "complete"
	// TurnStatusError is the terminal failure state. State.Err holds the
	// underlying error.
	TurnStatusError TurnStatus = "error"
)

type TurnStore added in v0.17.0

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

TurnStore is the process-local registry of in-flight and recently-finished turns. Keyed by turn UUID and guarded by an RWMutex so the SSE and polling endpoints can read concurrently with the handler creating new turns.

func NewTurnStore added in v0.17.0

func NewTurnStore() *TurnStore

NewTurnStore constructs an empty store.

func (*TurnStore) Delete added in v0.17.0

func (s *TurnStore) Delete(id string)

Delete removes a turn from the store. Used by future garbage-collection once SSE consumers have caught up; the foundation bead retains everything for the process lifetime.

func (*TurnStore) Get added in v0.17.0

func (s *TurnStore) Get(id string) (*TurnState, bool)

Get looks up a turn by id. ok is false when the id is unknown.

func (*TurnStore) Len added in v0.17.0

func (s *TurnStore) Len() int

Len returns the number of registered turns. Test helper.

func (*TurnStore) New added in v0.17.0

func (s *TurnStore) New(id string, sessionID int64) *TurnState

New creates a fresh TurnState and registers it in the store.

Jump to

Keyboard shortcuts

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