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
- func LoadOrCreateToken(path string) (string, error)
- func NormalizePublicOrigin(origin string) (string, error)
- func RunDaemon(cfg DaemonConfig) error
- func Spawn(ctx context.Context, exe string, args []string) error
- func Stop(ctx context.Context, dir string) (bool, error)
- func TokenPath() string
- func ValidateListen(addr string) (string, error)
- type ChangedFile
- type Changes
- type CreateRequest
- type DaemonConfig
- type DaemonState
- type Error
- type Manager
- func (m *Manager) AddProject(dir, name string) (Project, error)
- func (m *Manager) Answer(id, interactionID string, answer agentapi.Answer) (agentapi.Interaction, error)
- func (m *Manager) Archive(id string) (SessionSummary, error)
- func (m *Manager) Cancel(id string) (SessionSummary, error)
- func (m *Manager) CancelQueued(id, reqID string) error
- func (m *Manager) CancelSubagent(id, agentID string) (agentapi.Subagent, error)
- func (m *Manager) Changes(ctx context.Context, id, scope string) (Changes, error)
- func (m *Manager) ClearQueue(id string) error
- func (m *Manager) Close(id string) (SessionSummary, error)
- func (m *Manager) Create(req CreateRequest) (SessionSummary, error)
- func (m *Manager) Delete(id string) error
- func (m *Manager) Detail(id string) (SessionDetail, error)
- func (m *Manager) DropSubscribers()
- func (m *Manager) FileChange(ctx context.Context, id, scope, path string) (agentapi.FileDiff, error)
- func (m *Manager) List() []SessionSummary
- func (m *Manager) Projects() []Project
- func (m *Manager) Providers() []ProviderInfo
- func (m *Manager) RecentWorkdirs() []string
- func (m *Manager) RefreshModels()
- func (m *Manager) RemoveProject(id string) error
- func (m *Manager) Rename(id, name string) (SessionSummary, error)
- func (m *Manager) RenameProject(id, name string) (Project, error)
- func (m *Manager) Reopen(id string) (SessionSummary, error)
- func (m *Manager) ResumeQueue(id string) error
- func (m *Manager) SetMode(id, mode string) (SessionSummary, error)
- func (m *Manager) SetModel(id string, model, effort, contextSize *string) (SessionSummary, error)
- func (m *Manager) Settle(id string) (SessionSummary, error)
- func (m *Manager) Shutdown(ctx context.Context) error
- func (m *Manager) Start(ctx context.Context) error
- func (m *Manager) Subagent(id, agentID string) (SubagentDetail, error)
- func (m *Manager) Submit(id, text, requestID, mode string) (Submission, error)
- func (m *Manager) Subscribe(sessionID string) (*Subscriber, []byte, error)
- func (m *Manager) Summary(id string) (SessionSummary, error)
- func (m *Manager) Unsubscribe(sub *Subscriber)
- func (m *Manager) View(ctx context.Context, id string) error
- type Meta
- type Project
- type ProviderInfo
- type QueuedPrompt
- type Server
- type ServerConfig
- type SessionDetail
- type SessionSummary
- type SubagentDetail
- type Submission
- type Subscriber
Constants ¶
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.
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".
const ( StageActive = "" StageSettled = "settled" StageArchived = "archived" )
Task stages. A Task is active until the user settles or archives it; archived is final.
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.
const ( ScopeSession = "session" ScopeWorkspace = "workspace" )
Change scopes.
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 ¶
LoadOrCreateToken returns the access token stored at path, creating an owner-only file with a new random token when none exists.
func NormalizePublicOrigin ¶
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 ¶
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 ¶
Stop asks the verified running service to shut down gracefully and waits for it. It reports false when nothing was running.
func ValidateListen ¶
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.
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 ¶
NewManager builds a manager for providers. Start must run before use.
func (*Manager) AddProject ¶
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 ¶
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 ¶
CancelSubagent stops only the selected agent. Successful repeats never resend, and final status is supplied by the provider's subagent event.
func (*Manager) ClearQueue ¶
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 ¶
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) Providers ¶
func (m *Manager) Providers() []ProviderInfo
Providers lists every provider with its availability.
func (*Manager) RecentWorkdirs ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
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.
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.