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 ¶
- func ParseUsers(raw string) (map[string]string, error)
- func ParseUsersFromEnv() (map[string]string, error)
- func SessionFromContext(ctx context.Context) *state.WebSession
- func VerifyCredentials(users map[string]string, username, password string) error
- type AnvilDispatchTagLister
- type AnvilLister
- type BdRunnerFn
- type CommandHandler
- type Config
- type Server
- func (s *Server) SetAnvilDispatchTagLister(a AnvilDispatchTagLister)
- func (s *Server) SetAnvilLister(a AnvilLister)
- func (s *Server) SetAnvilRepoURLs(m map[string]string)
- func (s *Server) SetBdRunner(r BdRunnerFn)
- func (s *Server) SetChatRunner(r forgechat.Runner)
- func (s *Server) SetTurnTimeout(d time.Duration)
- func (s *Server) Start(ctx context.Context) error
- func (s *Server) TurnStore() *TurnStore
- type TurnEvent
- type TurnEventType
- type TurnSnapshot
- type TurnState
- func (t *TurnState) AppendText(chunk string)
- func (t *TurnState) Emit(ev TurnEvent)
- func (t *TurnState) Err() error
- func (t *TurnState) FinalMessageID() int64
- func (t *TurnState) RecordToolEvent(ev TurnEvent)
- func (t *TurnState) SetError(err error)
- func (t *TurnState) SetFinalMessageID(id int64)
- func (t *TurnState) Snapshot() TurnSnapshot
- func (t *TurnState) Status() TurnStatus
- func (t *TurnState) Subscribe(ctx context.Context) <-chan TurnEvent
- func (t *TurnState) Text() string
- type TurnStatus
- type TurnStore
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ParseUsers ¶
ParseUsers parses a FORGE_USERS-formatted string. Exposed separately from ParseUsersFromEnv to make testing simpler.
func ParseUsersFromEnv ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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) SetAnvilRepoURLs ¶ added in v0.21.0
SetAnvilRepoURLs installs the anvil-name -> GitHub repo base URL map used by /api/status to render a worker's pr_number as a clickable PR link.
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 ¶
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
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.
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
AppendText accumulates streamed assistant text. The full accumulated text is available via Snapshot.Text.
func (*TurnState) Emit ¶ added in v0.17.0
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) FinalMessageID ¶ added in v0.17.0
FinalMessageID returns the id of the last persisted assistant message. Zero before the turn completes.
func (*TurnState) RecordToolEvent ¶ added in v0.17.0
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
SetError records the terminal error and transitions status to error.
func (*TurnState) SetFinalMessageID ¶ added in v0.17.0
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
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.
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
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
Get looks up a turn by id. ok is false when the id is unknown.