web

package
v0.8.0-beta.1 Latest Latest
Warning

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

Go to latest
Published: Sep 24, 2026 License: MIT Imports: 42 Imported by: 0

Documentation

Overview

Package web is the `uam web` service: a detached per-user process that owns web-surface managed sessions, drives providers through agentapi, and serves the browser interface described in docs/adr/0004-web-interface.md.

Provider conversations and turns belong to the Manager, never to an HTTP request or an event-stream connection. Browsers only observe and submit.

Index

Constants

View Source
const (
	StateIdle               = "idle"
	StateStarting           = "starting"
	StateWorking            = "working"
	StateAwaitingPermission = "awaiting_permission"
	StateAwaitingAnswer     = "awaiting_answer"
	StateCompleted          = "completed"
	StateCancelled          = "cancelled"
	StateFailed             = "failed"
	StateInterrupted        = "interrupted"
	StateClosed             = "closed"
)

Session states reported to browsers.

View Source
const (
	SubmissionAccepted  = "accepted"
	SubmissionRejected  = "rejected"
	SubmissionUncertain = "uncertain"
	SubmissionQueued    = "queued"
	SubmissionCancelled = "cancelled"
)

Submission outcomes. A queued prompt is "queued" until it is sent, then takes the send's outcome; one removed from the queue unsent is "cancelled".

View Source
const (
	StageActive   = ""
	StageSettled  = "settled"
	StageArchived = "archived"
)

Task stages. A Task is active until the user settles or archives it; archived is final.

View Source
const (
	ModeSend  = "send"
	ModeQueue = "queue"
	ModeSteer = "steer"
)

Prompt modes. While a turn runs, send is refused, queue holds the prompt until the turn completes, and steer adds it to the running turn. While the Task is idle, all three send it.

View Source
const (
	ScopeSession   = "session"
	ScopeWorkspace = "workspace"
)

Change scopes.

View Source
const (
	// DefaultListen is the loopback address `uam web` binds by default.
	DefaultListen = "127.0.0.1:8260"
)

Variables

This section is empty.

Functions

func LoadOrCreateToken

func LoadOrCreateToken(path string) (string, error)

LoadOrCreateToken returns the access token stored at path, creating an owner-only file with a new random token when none exists.

func NormalizePublicOrigin

func NormalizePublicOrigin(origin string) (string, error)

NormalizePublicOrigin validates a --public-origin value and returns it as scheme://host[:port].

func RunDaemon

func RunDaemon(cfg DaemonConfig) error

RunDaemon is `uam __web`: it serves until SIGTERM or SIGINT, then shuts down gracefully. Startup errors are reported on the readiness pipe.

func Spawn

func Spawn(ctx context.Context, exe string, args []string) error

Spawn starts `uam __web` detached, exactly like a session host: its own session, stdio on /dev/null, readiness reported on fd 3. It returns once the service is serving or with the error the service reported.

func Stop

func Stop(ctx context.Context, dir string) (bool, error)

Stop asks the verified running service to shut down gracefully and waits for it. It reports false when nothing was running.

func TokenPath

func TokenPath() string

TokenPath is the access token file, next to sessions.json.

func ValidateListen

func ValidateListen(addr string) (string, error)

ValidateListen accepts only loopback addresses and returns host:port with an IP literal.

Types

type ChangedFile

type ChangedFile struct {
	Path      string `json:"path"`
	Status    string `json:"status"`
	Additions int    `json:"additions"`
	Deletions int    `json:"deletions"`
}

ChangedFile is one entry of a Changes listing.

type Changes

type Changes struct {
	Scope     string        `json:"scope"`
	Label     string        `json:"label"`
	Supported bool          `json:"supported"`
	Reason    string        `json:"reason"`
	Files     []ChangedFile `json:"files"`
}

Changes lists changed files for one scope. Label states plainly what the scope includes.

type CreateRequest

type CreateRequest struct {
	ProjectID   string `json:"project_id"`
	Provider    string `json:"provider"`
	Model       string `json:"model"`
	Effort      string `json:"effort"`
	ContextSize string `json:"context_size"`
	Name        string `json:"name"`
	Prompt      string `json:"prompt"`
	RequestID   string `json:"request_id"`
	// Mode is safe (also when empty) or yolo.
	Mode string `json:"mode"`
}

CreateRequest is the POST /api/sessions body. Name may be empty; the provider's title is shown until the user names the Task.

type DaemonConfig

type DaemonConfig struct {
	Listen        string
	PublicOrigins []string
	// NoAuth serves every request without authentication.
	NoAuth    bool
	Providers []agentapi.Provider
	Version   string
}

DaemonConfig configures `uam __web`.

type DaemonState

type DaemonState struct {
	PID           int       `json:"pid"`
	StartTime     int64     `json:"start_time"`
	Listen        string    `json:"listen"`
	PublicOrigins []string  `json:"public_origins,omitempty"`
	NoAuth        bool      `json:"no_auth,omitempty"`
	Version       string    `json:"version"`
	StartedAt     time.Time `json:"started_at"`
}

DaemonState is web.json: how to find and verify the running service. It never contains the access token.

func ReadRunning

func ReadRunning(dir string) (DaemonState, bool)

ReadRunning returns the running service's state after verifying that its PID still names the same process. A stale file reports not running.

func (DaemonState) URL

func (st DaemonState) URL() string

URL is the loopback address browsers use.

type Error

type Error struct {
	Status  int
	Message string
	// ProjectID names the existing Project when adding a directory that
	// already has one.
	ProjectID string
}

Error is a failure with the HTTP status the server reports for it.

func (*Error) Error

func (e *Error) Error() string

type Manager

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

Manager owns every web session, its provider conversation, and the event fan-out to browsers.

Two locks per session keep provider callbacks non-blocking: session.op serializes operations that call the provider (open, send, close) and may be held across those calls, while Manager.mu guards all observable state and is only ever held for short, allocation-bounded sections. Provider events take only Manager.mu, so a slow provider call or an absent browser never delays event processing.

func NewManager

func NewManager(st *store.Store, providers []agentapi.Provider) *Manager

NewManager builds a manager for providers. Start must run before use.

func (*Manager) AddProject

func (m *Manager) AddProject(dir, name string) (Project, error)

AddProject adds the directory dir as a Project. A directory has at most one Project; adding it again reports the existing one with 409.

func (*Manager) Answer

func (m *Manager) Answer(id, interactionID string, answer agentapi.Answer) (agentapi.Interaction, error)

Answer forwards the user's answer to a pending interaction. The first answer wins. The service answers on the user's behalf only for permission requests of a yolo Task, and never for questions.

func (*Manager) Archive

func (m *Manager) Archive(id string) (SessionSummary, error)

Archive moves an active or settled Task to its final stage; nothing moves it back. An active Task must meet the same conditions as for Settle.

func (*Manager) Cancel

func (m *Manager) Cancel(id string) (SessionSummary, error)

Cancel aborts the running turn. It is distinct from a viewer leaving and from Close: the conversation stays open.

func (*Manager) CancelQueued

func (m *Manager) CancelQueued(id, reqID string) error

CancelQueued removes one prompt from the queue before it is sent. It never contacts the provider. Cancelling it again succeeds; a prompt already sent is refused with 409.

func (*Manager) CancelSubagent

func (m *Manager) CancelSubagent(id, agentID string) (agentapi.Subagent, error)

CancelSubagent stops only the selected agent. Successful repeats never resend, and final status is supplied by the provider's subagent event.

func (*Manager) Changes

func (m *Manager) Changes(ctx context.Context, id, scope string) (Changes, error)

Changes lists changed files for a session in the requested scope.

func (*Manager) ClearQueue

func (m *Manager) ClearQueue(id string) error

ClearQueue removes every queued prompt, as CancelQueued does one.

func (*Manager) Close

func (m *Manager) Close(id string) (SessionSummary, error)

Close disconnects the conversation and keeps the record.

func (*Manager) Create

func (m *Manager) Create(req CreateRequest) (SessionSummary, error)

Create opens a new provider conversation in a Project's directory, records the session (a Task), and, when a prompt is given, submits it through the same path as Submit.

func (*Manager) Delete

func (m *Manager) Delete(id string) error

Delete deletes an archived Task's record. It is refused for any other stage. The conversation is never deleted at the provider.

func (*Manager) Detail

func (m *Manager) Detail(id string) (SessionDetail, error)

Detail returns one session with its retained transcript.

func (*Manager) DropSubscribers

func (m *Manager) DropSubscribers()

DropSubscribers disconnects every event stream, for server shutdown.

func (*Manager) FileChange

func (m *Manager) FileChange(ctx context.Context, id, scope, path string) (agentapi.FileDiff, error)

FileChange returns one file's diff in the requested scope. The path must be one the scope's listing reports.

func (*Manager) List

func (m *Manager) List() []SessionSummary

List returns every web session, newest first.

func (*Manager) Projects

func (m *Manager) Projects() []Project

Projects returns every Project, oldest first.

func (*Manager) Providers

func (m *Manager) Providers() []ProviderInfo

Providers lists every provider with its availability.

func (*Manager) RecentWorkdirs

func (m *Manager) RecentWorkdirs() []string

RecentWorkdirs returns distinct workdirs of stored records of any surface, most recently seen first.

func (*Manager) RefreshModels

func (m *Manager) RefreshModels()

RefreshModels reloads the catalog of every available provider whose copy is older than modelsMaxAge. A failed load keeps the previous catalog and is retried after modelsMaxAge.

func (*Manager) RemoveProject

func (m *Manager) RemoveProject(id string) error

RemoveProject deletes a Project and its Task records. It is refused unless every Task in it is archived. Conversations are never deleted at the provider, and the directory is not touched.

func (*Manager) Rename

func (m *Manager) Rename(id, name string) (SessionSummary, error)

Rename sets the Task's typed name. An empty name shows the provider title again; the provider's conversation is not renamed.

func (*Manager) RenameProject

func (m *Manager) RenameProject(id, name string) (Project, error)

RenameProject renames a Project. An empty name resets it to the directory's base name.

func (*Manager) Reopen

func (m *Manager) Reopen(id string) (SessionSummary, error)

Reopen makes a settled Task active again. Its next prompt reopens the same conversation.

func (*Manager) ResumeQueue

func (m *Manager) ResumeQueue(id string) error

ResumeQueue lets a paused queue drain again: at once when no turn is running, otherwise after the running turn completes.

func (*Manager) SetMode

func (m *Manager) SetMode(id, mode string) (SessionSummary, error)

SetMode sets the Task's permission mode at any time, even while a turn runs. It applies to permission requests raised afterwards; switching to yolo also answers the ones already pending.

func (*Manager) SetModel

func (m *Manager) SetModel(id string, model, effort, contextSize *string) (SessionSummary, error)

SetModel changes the supplied settings together for the Task's next turns. An omitted effort or context size survives a model change when supported. With the conversation open, settings are stored only after provider success; otherwise the next open applies them. Changes during a turn are refused.

func (*Manager) Settle

func (m *Manager) Settle(id string) (SessionSummary, error)

Settle marks an active Task complete and closes its conversation. It is refused while the Task is busy, has queued prompts or waits for an answer.

func (*Manager) Shutdown

func (m *Manager) Shutdown(ctx context.Context) error

Shutdown ends every conversation this service drives: running turns are recorded as interrupted, conversations are closed, and every provider is asked to stop the runtimes it started.

func (*Manager) Start

func (m *Manager) Start(ctx context.Context) error

Start checks providers, loads their model catalogs and the web records, and assigns records from before Projects existed to a Project. A provider whose check fails is listed as unavailable; it is not fatal.

func (*Manager) Subagent

func (m *Manager) Subagent(id, agentID string) (SubagentDetail, error)

Subagent returns one subagent of a session with its retained transcript.

func (*Manager) Submit

func (m *Manager) Submit(id, text, requestID, mode string) (Submission, error)

Submit sends one prompt in mode: ModeSend ("" too), ModeQueue or ModeSteer. A repeated request ID returns the recorded outcome, or "queued" while the prompt waits in the queue, without contacting the provider.

func (*Manager) Subscribe

func (m *Manager) Subscribe(sessionID string) (*Subscriber, []byte, error)

Subscribe registers a subscriber and returns it with its snapshot frame. Registration and snapshot happen under one lock, so the subscriber sees every later event exactly once and nothing between the two.

func (*Manager) Summary

func (m *Manager) Summary(id string) (SessionSummary, error)

Summary returns one session's summary.

func (*Manager) Unsubscribe

func (m *Manager) Unsubscribe(sub *Subscriber)

Unsubscribe removes a subscriber (its viewer left). It has no effect on the provider side.

func (*Manager) View

func (m *Manager) View(ctx context.Context, id string) error

View opens a session's conversation lazily for a viewer and waits a bounded time for it. The open runs on the service, not on ctx: a viewer leaving early does not abort it.

type Meta

type Meta struct {
	Version        string         `json:"version"`
	Providers      []ProviderInfo `json:"providers"`
	RecentWorkdirs []string       `json:"recent_workdirs"`
}

Meta is the /api/meta response.

type Project

type Project struct {
	ID        string    `json:"id"`
	Name      string    `json:"name"`
	Dir       string    `json:"dir"`
	CreatedAt time.Time `json:"created_at"`
}

Project is a directory the user added; its Tasks are web sessions whose project_id is ID.

type ProviderInfo

type ProviderInfo struct {
	Name         string                `json:"name"`
	DisplayName  string                `json:"display_name"`
	Available    bool                  `json:"available"`
	Reason       string                `json:"reason"`
	Capabilities agentapi.Capabilities `json:"capabilities"`
	// Models are the selectable models; empty means the provider default only.
	Models []agentapi.Model `json:"models"`
}

ProviderInfo describes one provider for the create form.

type QueuedPrompt

type QueuedPrompt struct {
	RequestID string    `json:"request_id"`
	Text      string    `json:"text"`
	QueuedAt  time.Time `json:"queued_at"`
}

QueuedPrompt is one prompt in a Task's queue.

type Server

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

Server is the HTTP handler for the web interface.

func NewServer

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

NewServer validates cfg and builds the handler.

func (*Server) ServeHTTP

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP applies the security checks every request passes before routing.

type ServerConfig

type ServerConfig struct {
	Manager *Manager
	// Token is the access token browsers present at /api/login.
	Token string
	// PublicOrigins are origins (scheme://host[:port]) of same-host reverse
	// proxies whose Host header is accepted besides loopback.
	PublicOrigins []string
	// NoAuth treats every request as authenticated. The Host, cross-origin,
	// content-type and body checks still apply.
	NoAuth  bool
	Version string
	// Assets overrides the embedded frontend (tests).
	Assets fs.FS
}

ServerConfig configures the HTTP interface.

type SessionDetail

type SessionDetail struct {
	SessionSummary
	Items            []agentapi.Item        `json:"items"`
	Interactions     []agentapi.Interaction `json:"interactions"`
	Subagents        []agentapi.Subagent    `json:"subagents"`
	HistoryTruncated bool                   `json:"history_truncated"`
	LastSubmission   *Submission            `json:"last_submission"`
	// Queue holds the prompts waiting for the running turn, oldest first.
	Queue []QueuedPrompt `json:"queue"`
	// QueuePaused is set while the queue waits for the user to resume or
	// clear it; it is never set with an empty queue.
	QueuePaused bool `json:"queue_paused"`
}

SessionDetail is a summary plus the retained main-agent transcript, interactions and subagents.

type SessionSummary

type SessionSummary struct {
	ID             string    `json:"id"`
	Provider       string    `json:"provider"`
	Name           string    `json:"name"`
	Workdir        string    `json:"workdir"`
	ConversationID string    `json:"conversation_id"`
	State          string    `json:"state"`
	StateDetail    string    `json:"state_detail"`
	Open           bool      `json:"open"`
	Pending        int       `json:"pending"`
	CreatedAt      time.Time `json:"created_at"`
	UpdatedAt      time.Time `json:"updated_at"`
	// Capabilities come from the provider so the browser can hide controls
	// the provider does not really support.
	Capabilities agentapi.Capabilities `json:"capabilities"`
	ProjectID    string                `json:"project_id"`
	// Model is the selected model; empty means the provider default.
	Model       string            `json:"model"`
	Effort      string            `json:"effort"`
	ContextSize string            `json:"context_size"`
	Context     *agentapi.Context `json:"context,omitempty"`
	// Title is the provider-generated title. Name may be empty; browsers
	// display name || title || "New task".
	Title string `json:"title"`
	// LastModel is the model the provider reported for the latest turn that
	// reported one. It is not persisted.
	LastModel string `json:"last_model"`
	// SubagentsRunning counts subagents that have not ended.
	SubagentsRunning int `json:"subagents_running"`
	// Queued counts prompts waiting in the Task's queue.
	Queued int `json:"queued"`
	// Mode is safe or yolo. A yolo Task's permission requests are allowed
	// once without asking; questions still wait for the user.
	Mode string `json:"mode"`
	// Stage is omitted for an active Task, otherwise StageSettled or
	// StageArchived; SettledAt and ArchivedAt say when.
	Stage      string    `json:"stage,omitempty"`
	SettledAt  time.Time `json:"settled_at,omitzero"`
	ArchivedAt time.Time `json:"archived_at,omitzero"`
}

SessionSummary is one web session (a Task) as listed.

type SubagentDetail

type SubagentDetail struct {
	Subagent agentapi.Subagent `json:"subagent"`
	Items    []agentapi.Item   `json:"items"`
}

SubagentDetail is one subagent and its retained transcript.

type Submission

type Submission struct {
	RequestID string    `json:"request_id"`
	Status    string    `json:"status"`
	Error     string    `json:"error"`
	Time      time.Time `json:"time"`
}

Submission is the recorded outcome of one prompt request.

type Subscriber

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

Subscriber is one event-stream connection. It only observes: dropping it never affects providers.

func (*Subscriber) Frames

func (s *Subscriber) Frames() <-chan []byte

Frames returns the queue of encoded events for this subscriber.

func (*Subscriber) Gone

func (s *Subscriber) Gone() <-chan struct{}

Gone is closed when the subscriber was dropped (queue overflow or service shutdown).

func (*Subscriber) Sent

func (s *Subscriber) Sent(frame []byte)

Sent records that one queued frame was written.

Jump to

Keyboard shortcuts

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