serve

package
v0.21.1 Latest Latest
Warning

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

Go to latest
Published: Jul 31, 2026 License: MIT Imports: 55 Imported by: 0

Documentation

Overview

Package serve provides an HTTP/WebSocket server for managing multiple agent sessions through a web dashboard.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrScopeInvalid is returned for a scope that isn't session/project/global.
	ErrScopeInvalid = errors.New("invalid scope")
	// ErrProjectUntrusted is returned when a project-scope write is requested for
	// a session whose project config path isn't trusted (409).
	ErrProjectUntrusted = errors.New("project config is untrusted")
)

Errors returned by the MCP toggle path, mapped to HTTP status by the handler.

View Source
var (
	ErrNotFound     = errors.New("session not found")
	ErrBusy         = errors.New("session is busy")
	ErrInvalidCWD   = errors.New("invalid working directory")
	ErrInvalidModel = errors.New("invalid model")
	ErrNoMCP        = errors.New("session has no MCP servers")
)
View Source
var ErrAutomationIndexUnavailable = errors.New("idempotency index unavailable")

ErrAutomationIndexUnavailable reports that the idempotency index could not be rebuilt from disk. Keyed requests are refused while it holds: answering them with an empty index would silently execute a redelivered webhook twice.

View Source
var ErrAutomationInvalidMCP = errors.New("invalid mcp_servers")

ErrAutomationInvalidMCP reports a per-run MCP server the request may not ask for (currently: a name an operator-configured server already owns). It is a 400: the caller has to pick another name, retrying changes nothing.

View Source
var ErrAutomationNotLive = errors.New("session is not loaded")

ErrAutomationNotLive reports that the session exists and belongs to the automation token, but is not currently loaded — so it cannot have a pending interaction to answer.

View Source
var ErrAutomationTooManySessions = errors.New("too many loaded sessions")

ErrAutomationTooManySessions reports that resuming a saved session would push the resident set past maxAutomationLoadedSessions.

View Source
var ErrBadAttachment = errors.New("bad attachment")

ErrBadAttachment wraps any attachment validation failure; the wrapping error message names the offending attachment and is safe to surface as a 400.

Functions

func NewServer

func NewServer(manager *Manager, opts ...ServerOption) http.Handler

NewServer returns an http.Handler wired to the given manager.

Types

type AskData

type AskData struct {
	ID        string            `json:"id"`
	Questions []bus.AskQuestion `json:"questions"`
}

AskData is a pending ask_user request.

type Attachment

type Attachment struct {
	Name string `json:"name"`
	Mime string `json:"mime"`
	Data string `json:"data"` // base64 standard encoding
}

Attachment is a file uploaded inline (base64) with a /send request.

type AttachmentDTO

type AttachmentDTO struct {
	ID     string `json:"id"`
	Name   string `json:"name"`
	Mime   string `json:"mime"`
	Size   int64  `json:"size"`
	Kind   string `json:"kind"`
	Width  int    `json:"width"`
	Height int    `json:"height"`
	URL    string `json:"url"`
}

AttachmentDTO is the public metadata returned for attachments created by a send. The URL is served by the attachment endpoint.

type AutomationCallback added in v0.20.0

type AutomationCallback struct {
	SessionID string `json:"session_id"`
	Status    string `json:"status"` // done | failed | needs_input
	Title     string `json:"title"`
	Summary   string `json:"summary"`
	URL       string `json:"url"`
	Error     string `json:"error,omitempty"`
	// Pending describes what the run is blocked on. Only set for needs_input,
	// so a machine caller can answer it through the scoped interaction
	// endpoints instead of only learning that a human is needed.
	Pending   *CallbackPending `json:"pending,omitempty"`
	Timestamp string           `json:"timestamp"` // RFC3339
}

AutomationCallback is the JSON body POSTed to an automation session's callback_url. It is deliberately small: a status, a hint of what happened and a link back to the session, which holds the full transcript.

type AutomationMCPServer added in v0.20.0

type AutomationMCPServer struct {
	Name    string            `json:"name"`
	URL     string            `json:"url"`
	Headers map[string]string `json:"headers,omitempty"`
	// Command/Args/Env exist only so a caller that sends one gets a 400 instead
	// of a silently url-only server. They never start anything.
	Command json.RawMessage `json:"command,omitempty"`
	Args    json.RawMessage `json:"args,omitempty"`
	Env     json.RawMessage `json:"env,omitempty"`
}

AutomationMCPServer is one per-run MCP server: a remote endpoint the session connects to for the duration of its life.

URL-based ONLY, by design. A command-based entry would let a remote caller execute a local program with the automation token as its only credential — authority far beyond "start a session". Decoding is strict about it: a "command" (or "args"/"env") key is an error, not something quietly ignored.

type AutomationRunRequest added in v0.20.0

type AutomationRunRequest struct {
	Prompt string `json:"prompt"`
	Model  string `json:"model"`
	CWD    string `json:"cwd"`
	Title  string `json:"title"`
	Origin string `json:"origin"`
	// IdempotencyKey deduplicates retries: webhooks redeliver, and a redelivery
	// must not spawn a second session.
	IdempotencyKey string `json:"idempotency_key"`
	// CallbackURL/CallbackSecret configure the outbound completion callback:
	// where to POST when the run settles, and the optional HMAC secret the
	// receiver verifies it with (see callback.go).
	CallbackURL    string `json:"callback_url"`
	CallbackSecret string `json:"callback_secret"`
	// MCPServers attaches session-scoped MCP servers to the run, so the agent
	// can call the caller's own tools. URL-based only — see AutomationMCPServer.
	MCPServers []AutomationMCPServer `json:"mcp_servers"`
}

AutomationRunRequest is the body of POST /api/automation/runs: create a session and send it a first prompt in one call.

type AutomationRunResponse added in v0.20.0

type AutomationRunResponse struct {
	SessionID string `json:"session_id"`
	URL       string `json:"url"`
	// Created is false when an idempotency key matched an existing session.
	Created bool `json:"created"`
}

AutomationRunResponse identifies the session the run landed in.

type BashCompleteData

type BashCompleteData struct {
	JobID        string `json:"job_id"`
	OwnerAgentID string `json:"owner_agent_id,omitempty"`
	Command      string `json:"command"`
	Status       string `json:"status"`
	Text         string `json:"text"`
}

BashCompleteData is sent when an async background bash job finishes and its formatted result is reinjected into the conversation.

type BashJobEndData

type BashJobEndData struct {
	JobID        string `json:"job_id"`
	OwnerAgentID string `json:"owner_agent_id,omitempty"`
	Status       string `json:"status"`
	Output       string `json:"output"`
}

type BashJobInitData

type BashJobInitData struct {
	JobID        string `json:"job_id"`
	OwnerAgentID string `json:"owner_agent_id,omitempty"`
	Command      string `json:"command"`
	CWD          string `json:"cwd"`
	Status       string `json:"status"`
	Output       string `json:"output"`
}

BashJobInitData restores a live/recent background command after reconnect.

type BashJobOutputData

type BashJobOutputData struct {
	JobID        string `json:"job_id"`
	OwnerAgentID string `json:"owner_agent_id,omitempty"`
	Delta        string `json:"delta"`
}

type BashJobStartData

type BashJobStartData struct {
	JobID        string `json:"job_id"`
	OwnerAgentID string `json:"owner_agent_id,omitempty"`
	Command      string `json:"command"`
	CWD          string `json:"cwd"`
}

type CallbackPending added in v0.20.0

type CallbackPending struct {
	Kind      string            `json:"kind"` // question | permission
	ID        string            `json:"id"`
	Questions []bus.AskQuestion `json:"questions,omitempty"`
	Tool      string            `json:"tool,omitempty"`
	Summary   string            `json:"summary,omitempty"`
}

CallbackPending is the interaction a needs_input callback is blocked on. It mirrors the bus event that raised it: a question carries its questions, a permission carries the tool and a human-readable summary of its arguments.

type CommandData

type CommandData struct {
	Command          string              `json:"command"`
	Messages         []core.AgentMessage `json:"messages,omitempty"` // compact sends updated messages
	HistoryTruncated bool                `json:"history_truncated,omitempty"`
}

CommandData is sent when a slash command is executed.

type CommandDequeuedData

type CommandDequeuedData struct {
	ID       string `json:"id"`
	Raw      string `json:"raw"`
	Executed bool   `json:"executed"`
	Err      string `json:"err,omitempty"`
}

CommandDequeuedData is sent when a queued command barrier leaves the queue (after execution, cancellation, or a permanent failure). The client removes the matching chip by ID. Err is set when it left because execution failed.

type CommandQueuedData

type CommandQueuedData struct {
	ID  string `json:"id"`
	Raw string `json:"raw"`
}

CommandQueuedData is sent when a slash command is enqueued as a barrier in the unified queue rail (issued while the session was busy). The client renders a queued command chip keyed by ID, distinct from a queued message chip.

type CommandResult

type CommandResult struct {
	OK           bool   `json:"ok"`
	Message      string `json:"message"`
	NewSessionID string `json:"newSessionId,omitempty"`
	// Queued is true when the command was not executed now but enqueued as a
	// barrier in the unified queue rail (issued while the session was busy). ID
	// is then the queued chip's authoritative ID, so the client reconciles its
	// optimistic command chip by ID.
	Queued bool   `json:"queued,omitempty"`
	ID     string `json:"id,omitempty"`
}

CommandResult is the response from executing a slash command.

type ConfigChangeData

type ConfigChangeData struct {
	Model          string `json:"model,omitempty"`
	Provider       string `json:"provider,omitempty"`
	Thinking       string `json:"thinking,omitempty"`
	PermissionMode string `json:"permission_mode,omitempty"`
	PathScope      string `json:"path_scope,omitempty"`
	// CompactAt carries a compaction-threshold change only. Pointer because 0
	// ("compact at the model window") is a real setting, not "unchanged".
	CompactAt *int `json:"compact_at,omitempty"`
	// ContextWindow carries a model switch's new input window, the denominator
	// for every context percentage the client shows.
	ContextWindow int `json:"context_window,omitempty"`
}

ConfigChangeData is sent when model/thinking/permissions/path scope change.

type ContextUpdateData

type ContextUpdateData struct {
	ContextPercent int `json:"context_percent"`
}

ContextUpdateData carries the current context usage percentage.

type ConversationMessage

type ConversationMessage struct {
	ID            string          `json:"id"`
	Role          string          `json:"role"`
	Timestamp     time.Time       `json:"timestamp,omitempty"`
	Text          string          `json:"text,omitempty"`
	Truncated     bool            `json:"truncated,omitempty"`
	Omitted       bool            `json:"omitted,omitempty"`
	OmittedBlocks int             `json:"omitted_blocks,omitempty"`
	Tool          string          `json:"tool,omitempty"`
	Action        string          `json:"action,omitempty"`
	Target        string          `json:"target,omitempty"`
	Status        string          `json:"status,omitempty"`
	Attachments   []AttachmentDTO `json:"attachments,omitempty"`
}

ConversationMessage is the owner-facing transcript DTO. Tool activity is projected into role=tool items; tool result output remains available only through the explicit detail query.

type CreateOpts

type CreateOpts struct {
	Model string `json:"model"`
	Title string `json:"title"`
	CWD   string `json:"cwd"`
	// Origin records who created the session ("user" when empty). Free-form so
	// automation callers can label their integration, e.g. "linear-webhook".
	Origin string `json:"origin"`
	// contains filtered or unexported fields
}

CreateOpts configures a new session.

type DeltaData

type DeltaData struct {
	Delta string `json:"delta"`
}

DeltaData carries a streaming text delta.

type Event

type Event struct {
	Type string `json:"type"`
	Data any    `json:"data,omitempty"`
	Seq  uint64 `json:"seq,omitempty"`
}

Event is a JSON-serializable event sent to WebSocket clients.

type GoalChangeData

type GoalChangeData struct {
	Active    bool   `json:"active"`
	Objective string `json:"objective,omitempty"`
	WorkDir   string `json:"work_dir,omitempty"`
	Iteration int    `json:"iteration"`
	Stalled   int    `json:"stalled"`
}

GoalChangeData is sent when goal mode activates or deactivates.

type GoalEndData

type GoalEndData struct {
	Reason string `json:"reason"`
}

GoalEndData is sent when a goal loop ends.

type GoalIterationData

type GoalIterationData struct {
	Iteration int    `json:"iteration"`
	Satisfied bool   `json:"satisfied"`
	Feedback  string `json:"feedback,omitempty"`
}

GoalIterationData is sent after the verifier judges a goal iteration.

type InitData

type InitData struct {
	Messages          []core.AgentMessage `json:"messages"`
	State             string              `json:"state"`
	ContextPercent    int                 `json:"context_percent"`
	ContextWindow     int                 `json:"context_window,omitempty"`
	CompactAt         int                 `json:"compact_at,omitempty"`
	CompactAtMin      int                 `json:"compact_at_min,omitempty"`
	PermissionMode    string              `json:"permission_mode"`
	PathScope         string              `json:"path_scope,omitempty"`
	PendingPermission *PermissionData     `json:"pending_permission,omitempty"`
	PendingAsk        *AskData            `json:"pending_ask,omitempty"`
	Tasks             any                 `json:"tasks,omitempty"`
	PlanMode          string              `json:"plan_mode,omitempty"`
	PlanFile          string              `json:"plan_file,omitempty"`
	GoalActive        bool                `json:"goal_active,omitempty"`
	GoalObjective     string              `json:"goal_objective,omitempty"`
	GoalWorkDir       string              `json:"goal_work_dir,omitempty"`
	GoalIteration     int                 `json:"goal_iteration,omitempty"`
	GoalStalled       int                 `json:"goal_stalled,omitempty"`
	GoalVerifying     bool                `json:"goal_verifying,omitempty"`
	Compacting        bool                `json:"compacting,omitempty"`
	StreamingText     string              `json:"streaming_text,omitempty"`
	StreamingThinking string              `json:"streaming_thinking,omitempty"`
	RunTokensUp       int                 `json:"run_tokens_up"`
	RunTokensDown     int                 `json:"run_tokens_down"`
	RunStartedAtMs    int64               `json:"run_started_at_ms,omitempty"`
	PendingSteers     []PendingSteerData  `json:"pending_steers,omitempty"`
	CostUSD           float64             `json:"cost_usd,omitempty"`
	Subagents         []SubagentInitData  `json:"subagents,omitempty"`
	BashJobs          []BashJobInitData   `json:"bash_jobs,omitempty"`
	LastSeq           uint64              `json:"last_seq,omitempty"`
	HistoryTruncated  bool                `json:"history_truncated,omitempty"`
}

InitData is sent on WebSocket connect with the full session state.

type MCPChangeData

type MCPChangeData struct {
	Total     int `json:"total"`
	Ready     int `json:"ready"`
	Disabled  int `json:"disabled"`
	Unhealthy int `json:"unhealthy"`
	Pending   int `json:"pending"`
}

MCPChangeData carries the rolled-up MCP summary counts for the status-line indicator, matching the MCPSummary fields. An open panel re-fetches full per-server detail from GET /api/sessions/{id}/mcp when this arrives.

type MCPSummary

type MCPSummary struct {
	Total     int `json:"total"`
	Ready     int `json:"ready"`
	Disabled  int `json:"disabled"`
	Unhealthy int `json:"unhealthy"`
	Pending   int `json:"pending"`
}

MCPSummary is the glanceable MCP health for the status line. Total counts all configured servers (including disabled ones); Disabled is the count in the intentionally-off state (neutral, not an alarm); Unhealthy counts only servers that are enabled yet failed/exited (the alert color); Pending counts servers mid-transition (desired differs from applied). The indicator shows whenever Total > 0 and turns to an alert color only when Unhealthy > 0.

type ManagedSession

type ManagedSession struct {
	// Immutable after construction.
	ID      string    `json:"id"`
	CWD     string    `json:"cwd"`
	Created time.Time `json:"created"`
	// Origin is who created the session ("user" for a human, or a caller-chosen
	// label for automation). Mirrors session.Session metadata.
	Origin string `json:"origin"`

	Title       string
	TitleSource string // "manual" | "auto" | "" (legacy=auto); see session.TitleSource
	Archived    bool   // closed-but-kept (presentation-only); see session.Session.Archived
	Updated     time.Time
	// contains filtered or unexported fields
}

ManagedSession wraps a bus.SessionRuntime with metadata for the web dashboard.

func (*ManagedSession) History

func (s *ManagedSession) History() []core.AgentMessage

History returns a copy of the session's conversation messages.

func (*ManagedSession) MCPStatus

func (s *ManagedSession) MCPStatus() []mcp.ControllerStatus

MCPStatus returns the policy-decorated health snapshot of this session's MCP servers (empty if none are configured). Each entry carries the applied enabled state, desired-enabled, the scopes that veto it, and any pending action.

func (*ManagedSession) RestartMCPServer

func (s *ManagedSession) RestartMCPServer(name string) (mcp.ServerStatus, error)

RestartMCPServer restarts a single MCP server for this session and re-syncs the tool registry with its (possibly changed) tool set. Other servers are untouched. Returns ErrNoMCP if the session has no MCP manager, mcp.ErrUnknownServer for a name it doesn't manage, mcp.ErrServerDisabled for a disabled server (enable it first), or ErrBusy if the session is running or awaiting a permission decision.

type Manager

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

Manager owns all active sessions.

func NewManager

func NewManager(ctx context.Context, cfg ManagerConfig) *Manager

NewManager creates a Manager. The context controls the lifetime of all agent runs — cancelling it aborts every active session.

func (*Manager) ArchiveSession

func (m *Manager) ArchiveSession(id string, archived bool) error

ArchiveSession sets or clears the archived flag on a session, whether it is currently active in memory or only saved on disk. Archiving is presentation-only: it never unloads an active session or touches Updated.

func (*Manager) Cancel

func (m *Manager) Cancel(sessionID string) error

Cancel aborts the running agent in a session via bus command.

func (*Manager) CancelBashJob

func (m *Manager) CancelBashJob(sessionID, jobID string) error

CancelBashJob cancels a session-scoped background bash job.

func (*Manager) CancelSubagent

func (m *Manager) CancelSubagent(sessionID, jobID string) error

CancelSubagent requests cancellation of a single (async) subagent job belonging to a session, without aborting the parent run.

func (*Manager) CreateAutomationRun added in v0.20.0

func (m *Manager) CreateAutomationRun(req AutomationRunRequest) (sessionID string, created bool, err error)

CreateAutomationRun creates a session for an external caller and sends it the first prompt. It returns the session ID and whether it was created now; a repeated idempotency key resolves to the existing session without creating a duplicate or re-sending the prompt.

The whole check-create-send-record sequence runs under automationMu so two simultaneous retries of the same webhook cannot both pass the check, and so a concurrent Delete cannot drop the index entry we are about to write.

The key is committed after success, never rolled back: it is written to the session metadata (and indexed) only once the first prompt was accepted. A failed send therefore leaves an inert, keyless session — nothing deletes a session another client may already be using, and neither a retry in this process nor an index rebuilt after a restart can resolve the key to it.

func (*Manager) CreateSession

func (m *Manager) CreateSession(opts CreateOpts) (*ManagedSession, error)

CreateSession creates a new agent session.

func (*Manager) Delete

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

Delete aborts any running agent, closes resources, and removes the session.

It runs under automationMu (the same guard as CreateAutomationRun's check-create-send-register sequence) so a delete cannot interleave with a run creation and leave an idempotency key pointing at a session that is already gone. Lock order is automationMu → m.mu, as in CreateAutomationRun.

func (*Manager) ExecCommand

func (m *Manager) ExecCommand(sessionID, rawCommand, id string) (*CommandResult, error)

ExecCommand executes a slash command in a session. id is the client-minted stable ID for the optimistic chip when the command is enqueued as a barrier (busy session, PolicyQueue); it is ignored when the command runs immediately.

func (*Manager) FileScanner

func (m *Manager) FileScanner() *files.Scanner

FileScanner returns the shared file scanner instance.

func (*Manager) Get

func (m *Manager) Get(id string) (*ManagedSession, bool)

Get returns a managed session by ID.

func (*Manager) GetSubagentTranscript

func (m *Manager) GetSubagentTranscript(sessionID, jobID string) (*session.SubagentTranscript, error)

GetSubagentTranscript loads one persisted transcript by jobID.

func (*Manager) InvalidateFileCache

func (m *Manager) InvalidateFileCache(cwd string)

InvalidateFileCache invalidates the file scanner cache for a given CWD. Called after successful file edits to keep file suggestions fresh.

func (*Manager) List

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

List returns info for all sessions, sorted by updated time descending.

func (*Manager) ListSubagentTranscripts

func (m *Manager) ListSubagentTranscripts(sessionID string) ([]session.SubagentTranscript, error)

ListSubagentTranscripts returns the persisted subagent transcripts for a session (newest-finished first). Returns ErrNotFound if the session isn't active or has no persistence.

func (*Manager) PromoteSubagent

func (m *Manager) PromoteSubagent(sessionID, jobID string) error

PromoteSubagent flips a running sync subagent job to async, unblocking its parent's blocking tool call while the child keeps running in the background. Returns ErrNotFound if the session/job doesn't exist, and propagates subagent.ErrNotSync / subagent.ErrNotRunning otherwise so callers can map them to specific responses.

func (*Manager) ReconfigureSession

func (m *Manager) ReconfigureSession(sessionID, modelSpec, thinking string) (map[string]string, error)

ReconfigureSession changes the model and/or thinking level of a session. Only allowed when the session is idle (not running).

func (*Manager) ResumeSession

func (m *Manager) ResumeSession(id string) (*ManagedSession, error)

ResumeSession loads a saved session from disk and creates a full runtime.

func (*Manager) Send

func (m *Manager) Send(sessionID, text string, atts []Attachment, steerID, msgID string) (action, id string, descriptors []attachment.Descriptor, err error)

Send delivers a user message (with optional attachments) to a session. If idle: starts a new agent run via bus. If running/permission: steers the running agent (attachments not allowed there). steerID, when non-empty, is the client-minted stable ID for the queued message: the client shows its optimistic chip under that same ID, so there is no window where a chip lacks an authoritative identity (closes the double-send and cancel-vs-in-flight races). When empty (e.g. CLI), the handler mints one. msgID plays the same role for a direct send: the client mints it for its optimistic echo, and the user message enters the conversation under it, so the UserMessageAppended broadcast dedups against that echo instead of doubling the message on the sending client. A malformed or already-used msgID is replaced by a server-minted one rather than rejected (see validClientID): the prompt must reach the agent either way.

Returns the action taken ("send" or "steer") and the effective ID the message was accepted under — the chip ID for a steer, the message ID for a direct send — so the caller can reconcile its optimistic view by identity even when the server re-minted it.

func (*Manager) SetCompactAt

func (m *Manager) SetCompactAt(sessionID string, tokens int) (int, error)

SetCompactAt changes a session's soft compaction threshold (tokens), so the agent compacts once context passes it rather than waiting for the full model window. 0 restores the window-based default. Like model/thinking this reconfigures the agent, so it is only allowed while the session is idle.

func (*Manager) SetPermissionMode

func (m *Manager) SetPermissionMode(sessionID, modeStr string) (string, error)

SetPermissionMode changes the permission mode for a session via bus command.

func (*Manager) SetTitle

func (m *Manager) SetTitle(sessionID, title string) (string, error)

SetTitle renames a session, marking the title as manually set so background auto-titling won't overwrite it, and persists the change immediately.

func (*Manager) Shutdown

func (m *Manager) Shutdown()

Shutdown synchronously flushes every active session to disk. Call it after the HTTP server has stopped accepting requests and before the process exits, so a turn that finished just before shutdown is persisted even though the async RunEnded→TreeSynced→save chain may not have drained.

A SIGTERM cancels the root context, which cancels each in-flight run. Before flushing we wait — bounded by shutdownDrainBudget across the whole process — for active sessions to leave the running/permission state, so a snapshot captures the complete final turn rather than a partial one. If the budget expires we flush regardless (best effort beats losing the turn entirely).

func (*Manager) SteerSubagent

func (m *Manager) SteerSubagent(sessionID, jobID, text string) (bool, error)

SteerSubagent queues a message for inter-step delivery to the running child agent of a subagent job. Returns ErrNotFound if the session or job doesn't exist. The bool reports whether the message was actually queued (false if the job has no live child agent yet or has already finished).

func (*Manager) ToggleMCPServer

func (m *Manager) ToggleMCPServer(anchor *ManagedSession, params mcpDisableParams, server string) (mcpToggleResult, error)

ToggleMCPServer applies a disable/enable preference for one server in one scope, persisting it (project/global) and fanning it out to every affected open session in the process:

  • session: only the anchor session,
  • project: every open session whose canonical cwd matches the anchor's,
  • global: every open session in the process.

The persisted scope also governs future session startups. Returns how many sessions applied the change now vs deferred it to quiescence. anchor must be a session that has the server configured (the caller validates that via GET).

func (*Manager) Version

func (m *Manager) Version() release.Result

Version returns the build version and last best-effort update result.

type ManagerConfig

type ManagerConfig struct {
	ProviderFactory func(model core.Model) (core.Provider, error)
	Transcriber     core.Transcriber // optional; enables POST /api/transcribe
	UsagePoller     *usage.Poller    // optional; enables GET /api/usage
	PushStore       *push.Store      // optional; enables Web Push
	PushDispatcher  *push.Dispatcher // optional; enables Web Push
	DefaultModel    core.Model
	WorkspaceRoot   string
	MoaCfg          core.MoaConfig
	// ConfigLoader loads configuration for an individual session CWD. When
	// nil, core.LoadMoaConfig preserves the normal global/project lookup.
	ConfigLoader   func(cwd string) core.MoaConfig
	SessionBaseDir string // root for session stores; empty = default
	// SchedulePath overrides the durable schedules file. Empty stores it beside
	// the session base directory.
	SchedulePath       string
	ReleaseInfo        release.Info
	UpdateChecker      *release.Checker
	UpdateCheckEnabled bool
}

ManagerConfig configures a Manager.

type MessageEndData

type MessageEndData struct {
	Text         string `json:"text"`
	MsgID        string `json:"msg_id,omitempty"`
	InputTokens  int    `json:"input_tokens,omitempty"`
	OutputTokens int    `json:"output_tokens,omitempty"`
}

MessageEndData carries the full assistant text on message completion and the provider-reported usage for that message.

type PendingSteerData

type PendingSteerData struct {
	ID      string `json:"id"`
	Text    string `json:"text"`
	Command bool   `json:"command,omitempty"`
	Images  int    `json:"images,omitempty"`
}

PendingSteerData is one queued (not yet delivered) item in the unified queue rail, with its authoritative ID so a reconnecting client reconciles its optimistic chip by ID instead of by text. Command marks a queued slash-command barrier (Text holds its raw command line, e.g. "/compact") so the client renders a command chip; Images is the number of image blocks a queued message carries (0 for a plain-text steer or a command) so the chip can show an attachment badge.

type PermissionData

type PermissionData struct {
	ID           string         `json:"id"`
	ToolName     string         `json:"tool_name"`
	Args         map[string]any `json:"args"`
	AllowPattern string         `json:"allow_pattern,omitempty"`
}

PermissionData is a pending permission request.

type PlanModeData

type PlanModeData struct {
	Mode     string `json:"mode"`
	PlanFile string `json:"plan_file,omitempty"`
}

PlanModeData is sent on plan mode state changes.

type RateLimitData

type RateLimitData struct {
	Status              string `json:"status,omitempty"`
	RepresentativeClaim string `json:"representative_claim,omitempty"`
	OnOverage           bool   `json:"on_overage"`
	FiveHourPct         int    `json:"five_hour_pct"`
	SevenDayPct         int    `json:"seven_day_pct"`
	OveragePct          int    `json:"overage_pct"`
}

RateLimitData carries the provider's per-request rate-limit state: plan-window utilization (as percentages, to match /api/usage) and whether this request was served from extra usage.

type RealtimeAPIKeyFunc

type RealtimeAPIKeyFunc func() (key string, ok bool)

RealtimeAPIKeyFunc returns only a normal OpenAI API key. ok must be false for missing credentials and OAuth credentials.

type RunEndData

type RunEndData struct {
	Text string `json:"text"`
}

RunEndData carries the final assistant text when a run completes.

type RunTokensData

type RunTokensData struct {
	Up   int `json:"up"`
	Down int `json:"down"`
}

RunTokensData carries the current run's estimated logical input/output traffic.

type ServerOption

type ServerOption func(*serverOptions)

ServerOption configures optional NewServer behavior.

func WithAllowedHosts

func WithAllowedHosts(hosts []string) ServerOption

WithAllowedHosts adds extra hostnames accepted by the anti DNS-rebinding Host check (on top of localhost and any IP literal). Use it for named hosts such as a Tailscale MagicDNS name.

func WithAuthToken

func WithAuthToken(token string, secureCookie bool) ServerOption

WithAuthToken enables opt-in shared-token authentication. An empty token leaves the server unauthenticated (current behavior). secureCookie marks the session cookie Secure and should be true only when served over TLS.

func WithAutomationToken added in v0.20.0

func WithAutomationToken(token string) ServerOption

WithAutomationToken enables the Automation API with its own shared secret, separate from the browser token. An empty token leaves the automation routes disabled entirely (they answer 404), so the API is fail-closed even on localhost.

func WithDeviceAuthentication

func WithDeviceAuthentication() ServerOption

WithDeviceAuthentication enables Pulse device pairing and credentials when Serve is embedded without a shared token. The CLI enables it by default; tests and other embedders opt in explicitly.

func WithDeviceStorePath

func WithDeviceStorePath(path string) ServerOption

WithDeviceStorePath overrides the private device credential store. It is primarily useful for embedded deployments and tests; normal Serve uses ~/.config/moa/devices.json (or MOA_CONFIG_DIR/devices.json).

func WithRealtimeClientSecretBroker

func WithRealtimeClientSecretBroker(key RealtimeAPIKeyFunc, client *http.Client) ServerOption

WithRealtimeClientSecretBroker supplies the narrowly scoped capability used to mint OpenAI Realtime client secrets. The key is never retained by Serve.

type SessionCostData

type SessionCostData struct {
	CostUSD float64 `json:"cost_usd"`
}

SessionCostData carries the accumulated session spend (main run + subagents).

type SessionInfo

type SessionInfo struct {
	ID           string       `json:"id"`
	Title        string       `json:"title"`
	Archived     bool         `json:"archived,omitempty"`
	State        SessionState `json:"state"`
	Model        string       `json:"model"`
	Provider     string       `json:"provider"`
	Thinking     string       `json:"thinking"`
	CWD          string       `json:"cwd"`
	Created      time.Time    `json:"created"`
	Updated      time.Time    `json:"updated"`
	Origin       string       `json:"origin,omitempty"` // who created it; omitted for ordinary user sessions
	Error        string       `json:"error,omitempty"`
	UntrustedMCP bool         `json:"untrusted_mcp,omitempty"`
	// MCP summarizes this session's MCP servers for the status line: a count and
	// whether any is unhealthy, so the indicator can appear only when servers
	// exist and turn red when one has failed or exited. The full per-server
	// detail is fetched on demand from GET /api/sessions/{id}/mcp. Omitted when
	// the session has no MCP servers.
	MCP            *MCPSummary `json:"mcp,omitempty"`
	PlanMode       string      `json:"plan_mode,omitempty"`
	PlanFile       string      `json:"plan_file,omitempty"`
	ContextPercent int         `json:"context_percent"` // 0-100, -1 if unknown
	// ContextWindow is the model's usable input window in tokens — the
	// denominator ContextPercent is measured against, and the scale CompactAt
	// is expressed on, so a UI can show the limit as a percentage of the ring.
	ContextWindow int `json:"context_window,omitempty"`
	// CompactAt is the soft compaction threshold in tokens; 0 means the session
	// compacts only when it approaches ContextWindow.
	CompactAt int `json:"compact_at,omitempty"`
	// CompactAtMin is the lowest threshold the engine honors, in tokens. A UI
	// picking a threshold must not offer below it — the engine would raise the
	// value and compact somewhere other than where the control says.
	CompactAtMin   int                        `json:"compact_at_min,omitempty"`
	PermissionMode string                     `json:"permission_mode"` // "yolo", "ask", "auto"
	CostUSD        float64                    `json:"cost_usd"`        // accumulated session spend (main run + subagents)
	Activity       *attention.SessionActivity `json:"activity,omitempty"`
	// CacheExpiresAt is when the Anthropic prompt cache for this session goes
	// cold (last run + cache TTL). Zero/omitted when not applicable (no run yet,
	// or a non-Anthropic model that doesn't use TTL-based prompt caching). The
	// UI warns once this time has passed that a new message pays a cache write.
	CacheExpiresAt time.Time `json:"cache_expires_at,omitzero"`
	// RunStartedAt is when the in-progress run began; zero/omitted when idle.
	// The UI anchors the activity-indicator elapsed counter to it so the counter
	// stays correct across reconnects. Only meaningful while State is running or
	// permission.
	RunStartedAt time.Time `json:"run_started_at,omitzero"`
	// BriefAttempting/BriefProgress are the cheap LLM-generated status prose:
	// what the session is attempting and how it's going. Prose that can age;
	// the actionable state is State/PermissionMode above (derived live), never
	// baked into this prose. BriefUpdated is the freshness stamp. Empty until
	// the first brief is generated. See brief.go.
	BriefAttempting string    `json:"brief_attempting,omitempty"`
	BriefProgress   string    `json:"brief_progress,omitempty"`
	BriefUpdated    time.Time `json:"brief_updated,omitzero"`
}

SessionInfo is the public representation returned by List/Get endpoints.

type SessionState

type SessionState string

SessionState describes the current state of a managed session.

const (
	StateIdle       SessionState = "idle"       // waiting for user input
	StateRunning    SessionState = "running"    // agent is executing
	StatePermission SessionState = "permission" // blocked on permission approval
	StateError      SessionState = "error"      // last run errored (still usable)
	StateSaved      SessionState = "saved"      // on disk but not loaded into memory
)

type StateChangeData

type StateChangeData struct {
	State string `json:"state"`
	Error string `json:"error,omitempty"`
}

StateChangeData is sent when the session state changes.

type SteerData

type SteerData struct {
	ID    string `json:"id,omitempty"`
	MsgID string `json:"msg_id,omitempty"`
	Text  string `json:"text"`
}

SteerData is sent when the user steers a running agent.

type SubagentCompleteData

type SubagentCompleteData struct {
	JobID  string `json:"job_id"`
	Task   string `json:"task"`
	Status string `json:"status"`
	Text   string `json:"text"`
}

SubagentCompleteData is sent when an async subagent finishes.

type SubagentCountData

type SubagentCountData struct {
	Count int `json:"count"`
}

SubagentCountData is sent when async subagent jobs start/finish.

type SubagentEndData

type SubagentEndData struct {
	JobID        string  `json:"job_id"`
	Status       string  `json:"status"`
	InputTokens  int     `json:"input_tokens"`
	OutputTokens int     `json:"output_tokens"`
	CostUSD      float64 `json:"cost_usd"`
}

SubagentEndData is sent when a subagent finishes, carrying its usage/cost.

type SubagentEventData

type SubagentEventData struct {
	JobID string `json:"job_id"`
	Event *Event `json:"event"`
}

SubagentEventData wraps a single translated bus event from a subagent child, namespaced by JobID. Event is produced by re-applying wsEventFromBus to the inner (already-typed) bus event — same shape as a top-level WS event.

type SubagentInitData

type SubagentInitData struct {
	JobID            string              `json:"job_id"`
	OriginToolCallID string              `json:"origin_tool_call_id,omitempty"`
	Task             string              `json:"task"`
	Model            string              `json:"model"`
	Thinking         string              `json:"thinking"`
	Status           string              `json:"status"`
	Async            bool                `json:"async"`
	Messages         []core.AgentMessage `json:"messages"`
	// StartedAtMs is the child's start time as epoch milliseconds (same
	// encoding as InitData.RunStartedAtMs), so a reconnecting client resumes
	// its live elapsed timer instead of restarting it. Omitted when unknown.
	StartedAtMs int64 `json:"started_at_ms,omitempty"`
	// InputTokens/OutputTokens/CostUSD are the child's accumulated usage/cost
	// so far, so live cost doesn't reset to zero after a reconnect. Omitted
	// (zero) until the child has closed at least one message.
	InputTokens  int     `json:"input_tokens,omitempty"`
	OutputTokens int     `json:"output_tokens,omitempty"`
	CostUSD      float64 `json:"cost_usd,omitempty"`
	// ContextPercent restores the child's own context reading after a
	// reconnect (0-100, -1 unknown) — see SubagentUsageData.ContextPercent.
	ContextPercent int `json:"context_percent"`
	// AccentIndex is the subagent's stable per-session creation ordinal, used
	// by the client to derive a deterministic accent color that survives
	// reconnects (instead of one derived from array/map position).
	AccentIndex int `json:"accent_index"`
}

SubagentInitData describes one live subagent job for reconnecting clients (WS init snapshot), so a client that connects mid-run sees the agent tray and its accumulated transcript instead of starting empty.

type SubagentStartData

type SubagentStartData struct {
	JobID            string `json:"job_id"`
	OriginToolCallID string `json:"origin_tool_call_id,omitempty"`
	Task             string `json:"task"`
	Model            string `json:"model"`
	Thinking         string `json:"thinking"`
	Async            bool   `json:"async"`
	// StartedAtMs is the child's start time as epoch milliseconds (same
	// encoding as InitData.RunStartedAtMs), so the client can compute live
	// elapsed time (now - StartedAtMs). Omitted when unknown.
	StartedAtMs int64 `json:"started_at_ms,omitempty"`
	// AccentIndex is the subagent's stable per-session creation ordinal (see
	// SubagentInitData.AccentIndex). Never omitted: 0 is a valid ordinal
	// (the session's first subagent).
	AccentIndex int `json:"accent_index"`
}

SubagentStartData is sent when a subagent (sync or async) begins.

type SubagentSummary

type SubagentSummary struct {
	JobID      string    `json:"job_id"`
	Task       string    `json:"task"`
	Model      string    `json:"model,omitempty"`
	Thinking   string    `json:"thinking,omitempty"`
	Status     string    `json:"status"`
	Async      bool      `json:"async"`
	StartedAt  time.Time `json:"started_at,omitempty"`
	FinishedAt time.Time `json:"finished_at,omitempty"`
	Source     string    `json:"source"`
	// Usage/cost/context of the CHILD, so a client reopening a finished
	// subagent shows the same figures its live view showed. ContextPercent is
	// -1 when unknown; the tokens and cost are omitted when zero.
	InputTokens    int     `json:"input_tokens,omitempty"`
	OutputTokens   int     `json:"output_tokens,omitempty"`
	CostUSD        float64 `json:"cost_usd,omitempty"`
	ContextPercent int     `json:"context_percent"`
}

SubagentSummary describes a subagent available to an owner-authorized client, including the delegated task.

type SubagentUsageData

type SubagentUsageData struct {
	JobID        string  `json:"job_id"`
	InputTokens  int     `json:"input_tokens"`
	OutputTokens int     `json:"output_tokens"`
	CostUSD      float64 `json:"cost_usd"`
	// ContextPercent is how full the CHILD's own window is (0-100), or -1 when
	// its model has no known window. Sent unconditionally (no omitempty): 0 is
	// a real reading, and a client that fell back to the parent's percentage
	// would be showing a number about a different agent.
	ContextPercent int `json:"context_percent"`
}

SubagentUsageData carries a subagent's accumulated usage/cost while it is still running, emitted each time the child closes a message. It lets the client show live tokens/cost before the terminal subagent_end. The cost is computed the same way subagent_end computes its total, so the live value stays consistent with the final one.

type TasksUpdateData

type TasksUpdateData struct {
	Tasks any `json:"tasks"`
}

TasksUpdateData carries the full task list after a change.

type ToolCallDeltaData

type ToolCallDeltaData struct {
	ToolCallID string         `json:"tool_call_id"`
	Args       map[string]any `json:"args"`
}

ToolCallDeltaData carries incrementally-parsed tool call arguments.

type ToolCallStreamingData

type ToolCallStreamingData struct {
	ToolCallID string `json:"tool_call_id"`
	ToolName   string `json:"tool_name"`
}

ToolStartData is sent when a tool execution begins. ToolCallStreamingData is sent when the LLM starts generating a tool call.

type ToolEndData

type ToolEndData struct {
	ToolCallID string `json:"tool_call_id"`
	ToolName   string `json:"tool_name"`
	IsError    bool   `json:"is_error"`
	Rejected   bool   `json:"rejected"`
	Result     string `json:"result"`
}

ToolEndData is sent when a tool execution completes.

type ToolStartData

type ToolStartData struct {
	ToolCallID string         `json:"tool_call_id"`
	ToolName   string         `json:"tool_name"`
	Args       map[string]any `json:"args"`
	// StartLine is the real 1-based file line where an edit's oldText starts,
	// so the frontend diff preview shows real line numbers before the tool
	// result arrives. 0 when unknown (frontend numbers from 1). Edit tool only.
	StartLine int `json:"start_line,omitempty"`
}

type ToolUpdateData

type ToolUpdateData struct {
	ToolCallID string `json:"tool_call_id"`
	Delta      string `json:"delta"`
}

ToolUpdateData carries streaming tool output.

type UserMessageData added in v0.21.0

type UserMessageData struct {
	MsgID   string         `json:"msg_id,omitempty"`
	Text    string         `json:"text,omitempty"`
	Content []core.Content `json:"content,omitempty"`
}

UserMessageData is sent when a user prompt starts a new run, so every connected client (not just the one that issued it) renders the message live. Content carries the message's blocks for a structured send (attachments + text); Text carries a plain-text prompt. Clients dedup by MsgID against their own optimistic echo and against the reconnect snapshot.

Jump to

Keyboard shortcuts

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