Documentation
¶
Index ¶
- Constants
- Variables
- func DefaultConfigPath() (string, error)
- func RunServe(arguments []string, _ io.Writer, diagnostics io.Writer) error
- func Serve(ctx context.Context, opts Options) error
- func StaticFileSystem() http.FileSystem
- type AgentCatalogEntry
- type AgentProfile
- type AgentProfileInput
- type AgentProfilesView
- type AgentState
- type AppState
- type AuditEntryView
- type AuditFilterInput
- type AuditState
- type AuditSummaryView
- type AuditVerificationIssueView
- type AuditVerificationView
- type AuthIdentity
- type Controller
- func (c *Controller) Close(ctx context.Context) error
- func (c *Controller) DeclineControl(runID, sessionID, connID, operator, toConnID string) (HandView, error)
- func (c *Controller) DeleteRecording(id, operator string) error
- func (c *Controller) ExportAuditReport(format string) (string, error)
- func (c *Controller) ExportRecording(id, operator string) (string, error)
- func (c *Controller) ForceTakeControl(runID, sessionID, connID, operator string) (HandView, error)
- func (c *Controller) GetAgentProfiles() (AgentProfilesView, error)
- func (c *Controller) GetAuditEntries(filter AuditFilterInput) ([]AuditEntryView, error)
- func (c *Controller) GetAuditSummary() (AuditSummaryView, error)
- func (c *Controller) GetFullSettings() (FullSettingsView, error)
- func (c *Controller) GetRecording(id string) (RecordingView, error)
- func (c *Controller) GetState() AppState
- func (c *Controller) GetTelemetrySnapshot() TelemetrySnapshotView
- func (c *Controller) GrantControl(runID, sessionID, connID, operator, toConnID string) (HandView, error)
- func (c *Controller) HandFor(sessionID string) HandView
- func (c *Controller) HoldsHand(sessionID, connID string) bool
- func (c *Controller) ListPresence(sessionID string) (PresenceView, error)
- func (c *Controller) ListRecordings(filter RecordingFilterInput) ([]RecordingView, error)
- func (c *Controller) ObserveSession(connID, sessionID string, observing bool) (PresenceView, error)
- func (c *Controller) ReadRecordingChunk(id string, offset, limit int) (RecordingChunk, error)
- func (c *Controller) RegisterPresence(connID, identity, role string)
- func (c *Controller) ReleaseControl(runID, sessionID, connID, operator string) (HandView, error)
- func (c *Controller) ReleasePresence(connID string)
- func (c *Controller) RequestControl(runID, sessionID, connID, operator string) (HandView, error)
- func (c *Controller) ResizeSession(runID, sessionID string, columns, rows int, connID string) error
- func (c *Controller) RestartSession(runID, sessionID string) error
- func (c *Controller) RunPreflight(ctx context.Context) (PreflightReport, error)
- func (c *Controller) SaveAgentProfiles(runID string, req SaveAgentProfilesRequest) (AgentProfilesView, error)
- func (c *Controller) SaveAgentProfilesAndRestart(req SaveAgentProfilesAndRestartRequest) (LifecycleResult, error)
- func (c *Controller) SaveFullSettings(runID string, req SaveFullSettingsRequest) (FullSettingsView, error)
- func (c *Controller) SendTerminalInput(runID, sessionID string, data []byte, operator, connID string) error
- func (c *Controller) SetInteractiveSession(runID, sessionID string, active bool, operator, connID string) error
- func (c *Controller) Start(ctx context.Context) error
- func (c *Controller) StartSession(runID, sessionID string) error
- func (c *Controller) StopRun(runID string) (AppState, error)
- func (c *Controller) StopSession(runID, sessionID string) error
- func (c *Controller) SubmitAutomaticDecision(runID, sessionID, eventID, decision string, actor supervise.Actor) error
- func (c *Controller) SubmitDecision(runID, sessionID, eventID, value string, actor supervise.Actor) error
- func (c *Controller) SubmitLine(runID, sessionID, line string, actor supervise.Actor) error
- func (c *Controller) Subscribe(listener func(event string, payload any)) func()
- func (c *Controller) TakeControl(sessionID, connID, operator string) (HandView, error)
- func (c *Controller) TestNotification() error
- func (c *Controller) VerifyAuditJournal() (AuditVerificationView, error)
- type DecisionBreakdown
- type FullSettingsView
- type HandView
- type LatencyBucketView
- type LifecycleResult
- type NotificationEvent
- type NotificationSettings
- type NotificationWebhookSetting
- type Options
- type PolicyEvaluation
- type PolicyState
- type PreflightAgent
- type PreflightAudit
- type PreflightCheck
- type PreflightConfiguration
- type PreflightPlatform
- type PreflightReport
- type PreflightTool
- type PresenceMember
- type PresenceView
- type RecordingChunk
- type RecordingEvent
- type RecordingFilterInput
- type RecordingFrameView
- type RecordingHeaderView
- type RecordingView
- type SafeErrorEvent
- type SaveAgentProfilesAndRestartRequest
- type SaveAgentProfilesRequest
- type SaveFullSettingsRequest
- type SecuritySettings
- type SnapshotEvent
- type StatusEvent
- type SupervisionEvent
- type TelemetrySnapshotView
- type ToolCallParamView
- type ToolCallView
- type UserInfo
- type UserRole
Constants ¶
const ( // HandFree means no connection may write to the session's terminal. HandFree = "free" // HandHeld means exactly one connection may write to it. HandHeld = "held" // HandRequested means the hand is held and another operator has asked for it. HandRequested = "requested" )
Variables ¶
var ( // ErrHandHeld is returned instead of silently stealing the terminal from // another operator. The caller is expected to request control. ErrHandHeld = errors.New("another operator holds the terminal") // ErrNotHolder is returned to a connection that tried to write to, resize, // or release a terminal it does not hold. ErrNotHolder = errors.New("this connection does not hold the terminal") // ErrNoPendingRequest is returned when granting or declining a request that // has already expired, been withdrawn, or never existed. ErrNoPendingRequest = errors.New("no pending control request") // ErrTooManyObservers is returned once a session roster is full. ErrTooManyObservers = errors.New("observer limit reached") // ErrForceDisabled is returned when force takeover is requested but the // deployment has not opted into it. ErrForceDisabled = errors.New("force takeover is disabled") // ErrUnknownConnection is returned for a connID the registrar never saw, or // that disconnected between a roster read and this call. ErrUnknownConnection = errors.New("unknown connection") // ErrViewerRole is returned when a read-only connection attempts to take or // request the hand. The RPC dispatcher rejects viewers first; this is the // second, authoritative gate. ErrViewerRole = errors.New("permission denied: viewer role is read-only") )
var DistAssets embed.FS
DistAssets embeds the production single-page application bundle. If empty (e.g. in test environments), the server provides a helpful fallback.
Functions ¶
func DefaultConfigPath ¶
DefaultConfigPath resolves the default config file location.
func StaticFileSystem ¶
func StaticFileSystem() http.FileSystem
StaticFileSystem returns an http.FileSystem rooted at the embedded dist directory, stripping the "dist" prefix so that index.html is served at the root.
Types ¶
type AgentCatalogEntry ¶
type AgentCatalogEntry struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
InstallStatus string `json:"installStatus"`
Installed bool `json:"installed"`
Adapter string `json:"adapter"`
AdapterStatus string `json:"adapterStatus"`
DefaultArgv []string `json:"defaultArgv"`
RequiresCustomArgv bool `json:"requiresCustomArgv"`
MinimumArguments int `json:"minimumArguments"`
ArgumentPrefix []string `json:"argumentPrefix"`
}
type AgentProfile ¶
type AgentProfile struct {
ID string `json:"id"`
Name string `json:"name"`
PresetID string `json:"presetID"`
Cwd string `json:"cwd"`
Backend string `json:"backend"`
Adapter string `json:"adapter"`
Argv []string `json:"argv,omitempty"`
ExecutableLabel string `json:"executableLabel"`
ArgumentCount int `json:"argumentCount"`
Locked bool `json:"locked"`
ReadOnlyReason string `json:"readOnlyReason,omitempty"`
PreserveOnSave bool `json:"preserveOnSave"`
}
type AgentProfileInput ¶
type AgentProfileInput struct {
ID string `json:"id"`
Name string `json:"name"`
PresetID string `json:"presetID,omitempty"`
Cwd string `json:"cwd,omitempty"`
Backend string `json:"backend,omitempty"`
Adapter string `json:"adapter,omitempty"`
Argv []string `json:"argv,omitempty"`
Preserve bool `json:"preserve,omitempty"`
}
AgentProfileInput is one profile a save sends back. Preserve, which the interface sends as "preserve", keeps the existing agent of that ID; the gateway read "preserveOnSave", which no client sends, and ignored it.
type AgentProfilesView ¶
type AgentProfilesView struct {
ConfigPath string `json:"configPath"`
Revision string `json:"revision"`
Catalog []AgentCatalogEntry `json:"catalog"`
Profiles []AgentProfile `json:"profiles"`
MinProfiles int `json:"minProfiles"`
MaxProfiles int `json:"maxProfiles"`
RestartRequired bool `json:"restartRequired"`
Editable bool `json:"editable"`
ReadOnlyReason string `json:"readOnlyReason,omitempty"`
}
type AgentState ¶
type AgentState struct {
SessionID string `json:"sessionID"`
AgentID string `json:"agentID"`
Name string `json:"name"`
DisplayCommand string `json:"displayCommand"`
Backend string `json:"backend"`
Adapter string `json:"adapter"`
Status string `json:"status"`
Output string `json:"output"`
Revision uint64 `json:"revision"`
Running bool `json:"running"`
Attached bool `json:"attached"`
ObserverCount int `json:"observerCount"`
HolderIdentity string `json:"holderIdentity,omitempty"`
InputFrozen bool `json:"inputFrozen"`
Simulated bool `json:"simulated"`
ExitCode *int `json:"exitCode,omitempty"`
InstalledVersion string `json:"installedVersion,omitempty"`
UnverifiedVersion bool `json:"unverifiedVersion,omitempty"`
UnverifiedReason string `json:"unverifiedReason,omitempty"`
}
type AppState ¶
type AppState struct {
RunID string `json:"runID"`
RunStatus string `json:"runStatus"`
StartedAt string `json:"startedAt,omitempty"`
Policy PolicyState `json:"policy"`
Audit AuditState `json:"audit"`
Agents []AgentState `json:"agents"`
PendingEvents []SupervisionEvent `json:"pendingEvents"`
Notices []string `json:"notices"`
}
type AuditEntryView ¶
type AuditEntryView struct {
Sequence uint64 `json:"sequence"`
Timestamp string `json:"timestamp"`
EntryID string `json:"entryID"`
RunID string `json:"runID"`
Kind string `json:"kind"`
SessionID string `json:"sessionID,omitempty"`
AgentID string `json:"agentID,omitempty"`
Backend string `json:"backend,omitempty"`
Adapter string `json:"adapter,omitempty"`
EventType string `json:"eventType,omitempty"`
Risk string `json:"risk,omitempty"`
Rule string `json:"rule,omitempty"`
Decision string `json:"decision,omitempty"`
DecisionBy string `json:"decisionBy,omitempty"`
Operator string `json:"operator,omitempty"`
Outcome string `json:"outcome,omitempty"`
Reason string `json:"reason,omitempty"`
Summary string `json:"summary,omitempty"`
Sensitive bool `json:"sensitive"`
Metadata map[string]string `json:"metadata,omitempty"`
}
type AuditFilterInput ¶
type AuditState ¶
type AuditSummaryView ¶
type AuditSummaryView struct {
Path string `json:"path"`
TotalEntries int `json:"totalEntries"`
RunsCount int `json:"runsCount"`
SessionsCount int `json:"sessionsCount"`
AgentCounts map[string]int `json:"agentCounts"`
KindCounts map[string]int `json:"kindCounts"`
DecisionsCount map[string]int `json:"decisionsCount"`
ActorsCount map[string]int `json:"actorsCount"`
OutcomesCount map[string]int `json:"outcomesCount"`
SensitiveCount int `json:"sensitiveCount"`
FirstTimestamp string `json:"firstTimestamp,omitempty"`
LastTimestamp string `json:"lastTimestamp,omitempty"`
}
type AuditVerificationView ¶
type AuthIdentity ¶ added in v0.6.0
type Controller ¶
type Controller struct {
// contains filtered or unexported fields
}
Controller owns the headless supervisor runtime and provides thread-safe query and mutation methods mirroring the RelayerBridge interface.
c.mu guards the gateway's own state: the run, its agents' output and presence, the hands and the subscribers. What decides whether a byte reaches an agent — its prompts, the policy's decisions, the agents' supervision state and the journal's failure — is the run's supervision core's, which GetState lays over the gateway's own. The core's sink takes c.mu (see gatewaySink): c.mu is held while reading the core or calling SetHolder, RecordAudit and BeginDrain, never while calling an operation that changes the core, or Wait.
lifecycleMu serialises what ends a run, StopRun, "Save and restart" and Close, and is taken before c.mu, never after it. A run ends by draining, which waits for what its core admitted and so never happens under c.mu.
func NewController ¶
func NewController(configPath string, diagnostics io.Writer) (*Controller, error)
NewController creates an unstarted supervisor controller.
func (*Controller) Close ¶
func (c *Controller) Close(ctx context.Context) error
Close gracefully stops the running supervisor and all agent processes. It drains the run first, as StopRun does, and within ctx: what the run's core admitted, an answer, a line or keystrokes being written, reaches its journaled outcome before the runtime and its journal close.
func (*Controller) DeclineControl ¶ added in v0.7.0
func (c *Controller) DeclineControl(runID, sessionID, connID, operator, toConnID string) (HandView, error)
DeclineControl refuses a pending request and leaves the hand where it is. runID is the run the caller shows, and is not checked: declining moves no terminal.
func (*Controller) DeleteRecording ¶ added in v0.7.0
func (c *Controller) DeleteRecording(id, operator string) error
DeleteRecording permanently removes a transcript. A recording still being written is refused by the store rather than deleted underneath its writer.
func (*Controller) ExportAuditReport ¶
func (c *Controller) ExportAuditReport(format string) (string, error)
func (*Controller) ExportRecording ¶ added in v0.7.0
func (c *Controller) ExportRecording(id, operator string) (string, error)
ExportRecording returns a whole transcript as asciicast v2 text. Exporting is audited: it is the moment a transcript leaves the supervising host.
func (*Controller) ForceTakeControl ¶ added in v0.7.0
func (c *Controller) ForceTakeControl(runID, sessionID, connID, operator string) (HandView, error)
ForceTakeControl seizes a held hand without the holder's consent. It is off by default: an operator typing into an agent's terminal can be interrupted mid-command, so the deployment must opt in.
Every attempt by a known connection is journaled as control_forced, refused ones included: trying to seize a colleague's terminal is the event worth finding later, whether or not the deployment allowed it. A seizure names the run it is for (supervise.ErrRunStale otherwise, as RequestControl), and one that takes the terminal is journaled, best effort, before the hand moves (recordControlAuditLocked); a refused one afterwards.
func (*Controller) GetAgentProfiles ¶
func (c *Controller) GetAgentProfiles() (AgentProfilesView, error)
func (*Controller) GetAuditEntries ¶
func (c *Controller) GetAuditEntries(filter AuditFilterInput) ([]AuditEntryView, error)
func (*Controller) GetAuditSummary ¶
func (c *Controller) GetAuditSummary() (AuditSummaryView, error)
func (*Controller) GetFullSettings ¶
func (c *Controller) GetFullSettings() (FullSettingsView, error)
func (*Controller) GetRecording ¶ added in v0.7.0
func (c *Controller) GetRecording(id string) (RecordingView, error)
GetRecording returns one transcript's metadata.
func (*Controller) GetState ¶
func (c *Controller) GetState() AppState
func (*Controller) GetTelemetrySnapshot ¶
func (c *Controller) GetTelemetrySnapshot() TelemetrySnapshotView
func (*Controller) GrantControl ¶ added in v0.7.0
func (c *Controller) GrantControl(runID, sessionID, connID, operator, toConnID string) (HandView, error)
GrantControl transfers the hand to a pending requester. Only the current holder may grant, for the run it names (supervise.ErrRunStale otherwise, as RequestControl). The grant is journaled, best effort, before the hand moves (recordControlAuditLocked).
func (*Controller) HandFor ¶ added in v0.7.0
func (c *Controller) HandFor(sessionID string) HandView
HandFor returns the current write-lock snapshot for one session.
func (*Controller) HoldsHand ¶ added in v0.7.0
func (c *Controller) HoldsHand(sessionID, connID string) bool
HoldsHand reports whether a connection may currently resize a session's terminal. A session nobody has claimed may be resized by any operator: a resize writes nothing the agent reads. Keystrokes are stricter, and need the hand held by the connection that types them (SendTerminalInput).
func (*Controller) ListPresence ¶ added in v0.7.0
func (c *Controller) ListPresence(sessionID string) (PresenceView, error)
ListPresence returns the roster of one session without mutating anything.
func (*Controller) ListRecordings ¶ added in v0.7.0
func (c *Controller) ListRecordings(filter RecordingFilterInput) ([]RecordingView, error)
ListRecordings returns the stored transcripts, newest first.
func (*Controller) ObserveSession ¶ added in v0.7.0
func (c *Controller) ObserveSession(connID, sessionID string, observing bool) (PresenceView, error)
ObserveSession adds a connection to one session's roster. Observing is a read-only act: viewers may observe, and observing never implies the hand.
func (*Controller) ReadRecordingChunk ¶ added in v0.7.0
func (c *Controller) ReadRecordingChunk(id string, offset, limit int) (RecordingChunk, error)
ReadRecordingChunk returns one page of frames plus the header. A recording still being written is readable: the operator watching a session live is the one most likely to want the replay.
func (*Controller) RegisterPresence ¶ added in v0.7.0
func (c *Controller) RegisterPresence(connID, identity, role string)
RegisterPresence records a newly authenticated connection. It is called by the gateway immediately after the websocket is registered.
func (*Controller) ReleaseControl ¶ added in v0.7.0
func (c *Controller) ReleaseControl(runID, sessionID, connID, operator string) (HandView, error)
ReleaseControl frees a hand held by this connection and journals it as control_released. Releasing a hand nobody holds changes nothing and is not journaled. runID is the run the caller shows, and is not checked: letting go of a terminal is always safe, and a connection holds no terminal of a run it never took one in.
func (*Controller) ReleasePresence ¶ added in v0.7.0
func (c *Controller) ReleasePresence(connID string)
ReleasePresence removes a disconnected connection and frees any hand it held. The gateway must call this after releasing its own client lock: broadcasting while the gateway holds that lock deadlocks against the broadcast listener.
A hand freed this way is journaled as control_released by the system, so a journal never shows somebody taking a terminal and then simply stops. A withdrawn or expired request moves no terminal and is not journaled.
func (*Controller) RequestControl ¶ added in v0.7.0
func (c *Controller) RequestControl(runID, sessionID, connID, operator string) (HandView, error)
RequestControl asks the current holder to hand over. A second request from the same connection refreshes the deadline rather than erroring, so a UI that retries is not punished, and is not journaled a second time either.
A request names the run it is for, and one for another run, or none, is refused (supervise.ErrRunStale), checked under the lock the hand changes under. The verb named no run, and a tab left on a run "Save and restart" had replaced took the new run's free terminal with its "Ask for the terminal" button: the tab dropped the new run's hand frame and never knew it held the terminal, nobody else could take it, and the policy answered nothing on that agent any more, since its terminal was held.
A request that takes a free terminal is journaled, best effort, before the hand moves (recordControlAuditLocked): no keystroke is admitted before the record of who took the terminal, and a record the journal refuses freezes the run first, so none is admitted at all.
func (*Controller) ResizeSession ¶
func (c *Controller) ResizeSession(runID, sessionID string, columns, rows int, connID string) error
ResizeSession applies a terminal geometry change requested by the connection holding the hand. A resize from anyone else is a silent no-op: two observers with different window sizes must not fight over the PTY geometry and thrash the agent's rendering.
The resize is a write to the run's backend like any other, admitted by the run's core (AdmitRun) as the desktop's is: a run that ends waits for it before its runtime closes, and takes none once it drains. The gateway resized whatever the run was doing, a runtime being closed included.
func (*Controller) RestartSession ¶
func (c *Controller) RestartSession(runID, sessionID string) error
func (*Controller) RunPreflight ¶
func (c *Controller) RunPreflight(ctx context.Context) (PreflightReport, error)
func (*Controller) SaveAgentProfiles ¶
func (c *Controller) SaveAgentProfiles(runID string, req SaveAgentProfilesRequest) (AgentProfilesView, error)
func (*Controller) SaveAgentProfilesAndRestart ¶
func (c *Controller) SaveAgentProfilesAndRestart(req SaveAgentProfilesAndRestartRequest) (LifecycleResult, error)
SaveAgentProfilesAndRestart writes the whole request in one write, then ends the run the caller names and starts another on the new configuration.
It acts only on the run the caller names, the stopped one after StopRun included: the gateway ignored ExpectedRunID, so a tab left open on a run another tab had already restarted restarted the new run, and the request the settings panel sends with an empty run ID was taken too.
The previous run drains as StopRun's does (endRun), strictly, without c.mu, and the new run keeps nothing of it: a supervision core of its own, with none of the previous run's prompts, and no hand held. The gateway closed the runtime under c.mu without waiting for what its core admitted, and handed the new run the previous run's hands, so a connection typed into a new process it had never attached to. Every client is told the new run's ID with its status, running, or failed when it could not start, so a tab on the previous run can load the new one; a run that failed to start keeps that ID, which a retry names. A previous run whose sessions could not all be confirmed stopped starts nothing, as on the desktop.
func (*Controller) SaveFullSettings ¶
func (c *Controller) SaveFullSettings(runID string, req SaveFullSettingsRequest) (FullSettingsView, error)
func (*Controller) SendTerminalInput ¶ added in v0.6.0
func (c *Controller) SendTerminalInput(runID, sessionID string, data []byte, operator, connID string) error
SendTerminalInput delivers raw terminal input bytes directly to the session backend. Used by the web interactive terminal (full PTY mode) to stream keystrokes and signals.
Keystrokes reach the agent without the policy and are never journaled: the documented bypass of an interactive terminal. They are bounded instead, by the run's supervision core, which admits a write only from the connection that holds the session's hand (Admit): a terminal nobody holds takes no keystrokes, from anybody, and neither does a call that names no connection. Every keystroke therefore falls within a hand whose taking was journaled. The write takes the session's one write slot, the one an answer and a line take: it is refused while either is being written, and while it is admitted they are refused or, for the policy's answers, wait. It is refused as well once the journal has failed, on a session frozen by a write whose outcome is unknown, on one whose process is stopped, stopping or starting, and once the run drains, which waits for every admitted write before its runtime closes.
A terminal nobody held was writable by any operator connection, the binary frames that carry no connection's run included, and the keystrokes went to the terminal whatever else was being written: with the policy answering on its own, a client typing without the hand typed alongside the policy's answer to the same question, and nothing said anybody had been at the terminal. The resize, which writes nothing the agent reads, keeps the rule that a free terminal is anybody's (HoldsHand).
The hand is checked when the write is admitted, and the write happens once it is: at most one already-in-flight keystroke may land just after a release. That window is documented in docs/sharing.md rather than papered over.
func (*Controller) SetInteractiveSession ¶ added in v0.6.0
func (c *Controller) SetInteractiveSession(runID, sessionID string, active bool, operator, connID string) error
SetInteractiveSession acquires or releases the session's write lock. It is the attach verb: taking the hand is what makes keystrokes routable.
Taking a hand another operator holds is refused rather than silently stolen; the caller is expected to request control instead.
Keystrokes are never journaled, so the attach record is what says who was at the terminal, and it is journaled first: through the run's supervision core, under the same lock the hand is taken under, before any client can see the terminal held or its holder type into it. A record the journal refuses leaves the terminal as it was and fails the call, and freezes the run as the core's own entries do; once the journal has failed, no terminal is taken this way. The gateway took the hand first and wrote the record afterwards, dropping its error, so keystrokes could reach the agent before any record of the attach existed, or with none at all. Releasing is journaled after the hand is let go, best effort, as the control records are (recordControlAudit): a release that could not be journaled must still release, and the core freezes the run all the same.
A terminal is taken only for the run the caller names. The verb ignored the run, so a tab left open on a run that "Save and restart" had replaced attached to the new run's terminal, which it had never shown, and its keystrokes, which name no run, went to the new process: a run's hands go with it precisely so that such a tab holds nothing in the next one. Letting go of a terminal needs no run, since it is always safe.
func (*Controller) Start ¶
func (c *Controller) Start(ctx context.Context) error
Start boots the supervisor runtime and begins event processing.
The run's context keeps ctx's values but not its cancellation: a run ends by StopRun, "Save and restart" or Close, which drain it in order, and never because the caller's context ended. Serve passes the context its signals cancel, and an interrupt cancelled the run before Close drained it: an answer being written was cut off and recorded as uncertain, and the exits of the agents Close then stopped were never journaled. A run started by "Save and restart" already had a context of its own.
func (*Controller) StartSession ¶
func (c *Controller) StartSession(runID, sessionID string) error
func (*Controller) StopRun ¶
func (c *Controller) StopRun(runID string) (AppState, error)
StopRun stops the run the caller names, and only that one. It stopped whatever run was current, whatever the caller named, so a tab left open on a replaced run, or a request naming none, stopped every agent of the new one.
The run drains first (endRun): its core admits nothing more, every session is stopped strictly, and what the core admitted reaches its journaled outcome before the runtime and its journal close. None of it holds c.mu, so every client can still read the state meanwhile; the gateway closed the runtime under c.mu without waiting, which cut an answer being written off from its journal and blocked every other call for as long as the close took. Every client is told the run is stopping, then stopped. A stopped run keeps its ID, which "Save and restart" names to start another, and nothing else: its agents and prompts were its core's, and its hands are let go.
A stop whose sessions could not all be confirmed stopped leaves the run failed and the gateway refusing to start another beside processes that may still run.
func (*Controller) StopSession ¶
func (c *Controller) StopSession(runID, sessionID string) error
StopSession, StartSession and RestartSession go through the run's supervision core, which owns whether an agent runs: it takes in only the prompts of a running agent, so a start it did not see left the new process's prompts unsupervised. The core shows the agent stopping or starting while the operation runs, refuses one while an answer or a line is still being written to the session, drops the previous process's prompts when a start begins, freezes a session whose stop failed, and reports a failure by a fixed message rather than the backend's own text.
Each acts only on the run the caller names. The gateway ignored the run, so a tab left open on a run a profile save had replaced, or a request naming none, stopped, started or restarted the agent of the same name in the new run, which the operator had never seen.
func (*Controller) SubmitAutomaticDecision ¶
func (c *Controller) SubmitAutomaticDecision(runID, sessionID, eventID, decision string, actor supervise.Actor) error
SubmitAutomaticDecision sends a chosen answer, allow or deny, which the adapter encodes itself. It is taken only when the prompt offers it. The gateway sent whatever the caller named as typed text, so an answer that was no choice at all reached the agent through the call meant for buttons.
func (*Controller) SubmitDecision ¶
func (c *Controller) SubmitDecision(runID, sessionID, eventID, value string, actor supervise.Actor) error
SubmitDecision sends a person's typed answer to a prompt through the run's supervision core, which journals the decision before it writes it, keeps the prompt pending until the write has an outcome, writes one answer at a time to a session, and freezes the session when a write's outcome is unknown.
The text is always sent as typed and journaled as asked: only the adapter knows what the bytes mean. The gateway read "y", "yes" and "allow" as the adapter's allow and "n", "no" and "deny" as its deny, so typing "y" into a Generic prompt, whose adapter encodes no allow, was refused, and any other text was journaled as a human allowing something. An empty answer is refused before anything is claimed or journaled, and so is a run the caller does not name: the gateway took an empty run ID for the current run, so a tab left open on a run that had since been replaced could answer the new one. actor is who typed it, from which connection; the core refuses one whose role may only watch, as the gateway's list of the calls a viewer may make already does.
func (*Controller) SubmitLine ¶
func (c *Controller) SubmitLine(runID, sessionID, line string, actor supervise.Actor) error
SubmitLine sends one ordinary line to a detached, running session through the core. The core journals the line before it writes it and fails closed, takes the session's one write slot, refuses the line while a prompt waits on the operator, while anybody holds the terminal or while the session is frozen, and freezes the session when the write's outcome is unknown. The gateway wrote the line beside whatever else was being written, with no check at all, journaled it afterwards with the journal's errors ignored, and ignored the run the caller named. The line's entries name the actor's identity, which is all their closed shape holds.
func (*Controller) Subscribe ¶
func (c *Controller) Subscribe(listener func(event string, payload any)) func()
Subscribe registers an event listener called on each broadcast. Returns an unsubscribe function.
func (*Controller) TakeControl ¶ added in v0.7.0
func (c *Controller) TakeControl(sessionID, connID, operator string) (HandView, error)
TakeControl acquires a free hand. It deliberately refuses to steal a held one: the caller is told to request control instead.
It journals nothing itself. No client reaches it: a client takes a terminal through the attach verb, SetInteractiveSession, which takes the hand the same way once attach_started is journaled, so one action never produces two records and no terminal is held that the journal does not say was taken.
func (*Controller) TestNotification ¶
func (c *Controller) TestNotification() error
func (*Controller) VerifyAuditJournal ¶
func (c *Controller) VerifyAuditJournal() (AuditVerificationView, error)
type DecisionBreakdown ¶
type FullSettingsView ¶
type FullSettingsView struct {
AgentProfilesView
Security SecuritySettings `json:"security"`
Notifications NotificationSettings `json:"notifications"`
// SecurityPresets are the values each preset fills in, from the same
// source the configuration loader uses.
SecurityPresets map[string]SecuritySettings `json:"securityPresets"`
}
type HandView ¶ added in v0.7.0
type HandView struct {
RunID string `json:"runID"`
SessionID string `json:"sessionID"`
State string `json:"state"`
HolderConnID string `json:"holderConnID,omitempty"`
HolderIdentity string `json:"holderIdentity,omitempty"`
RequesterConnID string `json:"requesterConnID,omitempty"`
RequesterIdentity string `json:"requesterIdentity,omitempty"`
RequestExpiresAt string `json:"requestExpiresAt,omitempty"`
Since string `json:"since,omitempty"`
}
HandView is a complete snapshot of one session's write lock.
type LatencyBucketView ¶
type LifecycleResult ¶
type LifecycleResult struct {
Outcome string `json:"outcome"`
State AppState `json:"state"`
Profiles AgentProfilesView `json:"profiles"`
}
type NotificationEvent ¶
type NotificationEvent struct {
Title string `json:"title"`
Body string `json:"body"`
AgentName string `json:"agentName,omitempty"`
SessionID string `json:"sessionID,omitempty"`
EventID string `json:"eventID,omitempty"`
Kind string `json:"kind"`
Severity string `json:"severity"`
Reason string `json:"reason,omitempty"`
Timestamp string `json:"timestamp"`
}
NotificationEvent is broadcast to WebSocket clients when an operator alert fires.
type NotificationSettings ¶
type NotificationSettings struct {
Enabled bool `json:"enabled"`
Bell bool `json:"bell"`
Desktop bool `json:"desktop"`
MinSeverity string `json:"minSeverity"`
Webhooks []NotificationWebhookSetting `json:"webhooks"`
}
type NotificationWebhookSetting ¶
type NotificationWebhookSetting struct {
Name string `json:"name"`
URL string `json:"url"`
Format string `json:"format"`
MinSeverity string `json:"minSeverity"`
Timeout string `json:"timeout"`
// HasHeaders says the webhook carries headers, usually a credential. Their
// values never leave the engine, and a save keeps them.
HasHeaders bool `json:"hasHeaders"`
}
type Options ¶
type Options struct {
Bind string
Port int
Token string
ViewerToken string
// ViewerTerminals is "shown", the default, or "hidden": then a viewer
// receives no terminal output, only prompt cards and notifications.
ViewerTerminals string
ConfigPath string
StaticDir string
Diagnostics io.Writer
OnReady func(serverURL string, token string)
}
Options configures the headless Relayer web server.
type PolicyEvaluation ¶
type PolicyState ¶
type PreflightAgent ¶
type PreflightAgent struct {
Ordinal int `json:"ordinal"`
Source string `json:"source"`
Command string `json:"command"`
Installation string `json:"installation"`
Adapter string `json:"adapter,omitempty"`
AdapterMaturity string `json:"adapterMaturity,omitempty"`
Backend string `json:"backend,omitempty"`
}
type PreflightAudit ¶
type PreflightCheck ¶
type PreflightConfiguration ¶
type PreflightPlatform ¶
type PreflightReport ¶
type PreflightReport struct {
SchemaVersion int `json:"schemaVersion"`
Status string `json:"status"`
Platform PreflightPlatform `json:"platform"`
Configuration PreflightConfiguration `json:"configuration"`
Audit PreflightAudit `json:"audit"`
Tools []PreflightTool `json:"tools"`
Agents []PreflightAgent `json:"agents"`
Checks []PreflightCheck `json:"checks"`
}
type PreflightTool ¶
type PresenceMember ¶ added in v0.7.0
type PresenceMember struct {
ConnID string `json:"connID"`
Identity string `json:"identity"`
Role string `json:"role"`
Observing bool `json:"observing"`
HoldsHand bool `json:"holdsHand"`
RequestingHand bool `json:"requestingHand"`
Since string `json:"since"`
}
PresenceMember is one connected client as the other clients of a session see it. Identity is not unique: the same operator may hold several connections, so ConnID is the addressable key everywhere the hand is concerned.
type PresenceView ¶ added in v0.7.0
type PresenceView struct {
RunID string `json:"runID"`
SessionID string `json:"sessionID"`
Members []PresenceMember `json:"members"`
ObserverCount int `json:"observerCount"`
}
PresenceView is a complete roster snapshot for one session. It is never a delta: a dropped broadcast must be self-healing at the next one.
type RecordingChunk ¶ added in v0.7.0
type RecordingChunk struct {
ID string `json:"id"`
Header RecordingHeaderView `json:"header"`
Frames []RecordingFrameView `json:"frames"`
Offset int `json:"offset"`
NextOffset int `json:"nextOffset"`
Complete bool `json:"complete"`
}
RecordingChunk is one page of a transcript. Transcripts are paged rather than returned whole because a websocket frame large enough for a multi-megabyte cast would be dropped by the send queue instead of delivered.
type RecordingEvent ¶ added in v0.7.0
type RecordingEvent struct {
Action string `json:"action"`
Recording RecordingView `json:"recording"`
}
type RecordingFilterInput ¶ added in v0.7.0
type RecordingFrameView ¶ added in v0.7.0
type RecordingHeaderView ¶ added in v0.7.0
type RecordingView ¶ added in v0.7.0
type RecordingView struct {
ID string `json:"id"`
RunID string `json:"runID"`
SessionID string `json:"sessionID"`
AgentID string `json:"agentID,omitempty"`
Name string `json:"name,omitempty"`
Backend string `json:"backend,omitempty"`
Adapter string `json:"adapter,omitempty"`
StartedAt string `json:"startedAt"`
EndedAt string `json:"endedAt,omitempty"`
DurationSeconds float64 `json:"durationSeconds"`
Width int `json:"width"`
Height int `json:"height"`
Bytes int64 `json:"bytes"`
Frames int `json:"frames"`
Truncated bool `json:"truncated"`
DroppedFrames int `json:"droppedFrames"`
InputRecorded bool `json:"inputRecorded"`
Redacted bool `json:"redacted"`
ExitCode *int `json:"exitCode,omitempty"`
Active bool `json:"active"`
}
RecordingView is one session transcript as the web UI lists it. It carries no filesystem path: the store is addressed by id, and a path would tell a browser client where the supervising host keeps its files.
type SafeErrorEvent ¶
type SaveAgentProfilesAndRestartRequest ¶
type SaveAgentProfilesAndRestartRequest struct {
ExpectedRunID string `json:"expectedRunID,omitempty"`
ExpectedRevision string `json:"expectedRevision"`
Profiles []AgentProfileInput `json:"profiles"`
// Security and Notifications, when set, are written in the same atomic
// write as the agents rather than by a separate save beforehand.
Security *SecuritySettings `json:"security,omitempty"`
Notifications *NotificationSettings `json:"notifications,omitempty"`
}
type SaveAgentProfilesRequest ¶
type SaveAgentProfilesRequest struct {
ExpectedRevision string `json:"expectedRevision"`
Profiles []AgentProfileInput `json:"profiles"`
}
type SaveFullSettingsRequest ¶
type SaveFullSettingsRequest struct {
ExpectedRevision string `json:"expectedRevision"`
Profiles []AgentProfileInput `json:"profiles,omitempty"`
Security *SecuritySettings `json:"security,omitempty"`
Notifications *NotificationSettings `json:"notifications,omitempty"`
}
type SecuritySettings ¶
type SecuritySettings struct {
Profile string `json:"profile"`
DefaultAction string `json:"defaultAction"`
DryRun bool `json:"dryRun"`
BlockDestructive bool `json:"blockDestructive"`
BlockExfiltration bool `json:"blockExfiltration"`
BlockSensitivePaths bool `json:"blockSensitivePaths"`
BlockOutsideWorkspace bool `json:"blockOutsideWorkspace"`
WorkspaceRoot string `json:"workspaceRoot"`
RateLimitPerMinute int `json:"rateLimitPerMinute"`
MaxConsecutiveAutoDecisions int `json:"maxConsecutiveAutoDecisions"`
}
type SnapshotEvent ¶
type SnapshotEvent struct {
RunID string `json:"runID"`
SessionID string `json:"sessionID"`
Revision uint64 `json:"revision"`
Output string `json:"output"`
Status string `json:"status"`
Running bool `json:"running"`
Attached bool `json:"attached"`
ObserverCount int `json:"observerCount"`
HolderIdentity string `json:"holderIdentity,omitempty"`
InputFrozen bool `json:"inputFrozen"`
ExitCode *int `json:"exitCode,omitempty"`
}
type StatusEvent ¶
type StatusEvent struct {
RunID string `json:"runID"`
Scope string `json:"scope"`
Status string `json:"status"`
SessionID string `json:"sessionID,omitempty"`
// ClearedBefore, when set, says the backend dropped every prompt of the
// session detected before this time (RFC 3339): the session ended, or a
// new process replaced it. Clients drop the same prompts; a status without
// it, such as a stream error on a live session, leaves them answerable.
ClearedBefore string `json:"clearedBefore,omitempty"`
}
type SupervisionEvent ¶
type SupervisionEvent struct {
RunID string `json:"runID"`
ID string `json:"id"`
SessionID string `json:"sessionID"`
AgentID string `json:"agentID"`
Adapter string `json:"adapter"`
Type string `json:"type"`
Summary string `json:"summary"`
Sensitive bool `json:"sensitive"`
Risk string `json:"risk"`
Timestamp string `json:"timestamp"`
Evaluation PolicyEvaluation `json:"evaluation"`
DeliveryStatus string `json:"deliveryStatus"`
Decisions []string `json:"decisions"`
// Command is the command the prompt asks about, redacted and bounded by the
// core. It is agent text read from the screen, so only an operator receives
// it: a viewer's frames and state carry none (see commandForRole).
Command string `json:"command,omitempty"`
// ToolCall is present only when the prompt is about an MCP tool call.
// Its parameter values are agent-controlled terminal text shown so an
// operator can see what a tool is about to be given; they are display
// data, never instructions, and never reach the audit journal.
ToolCall *ToolCallView `json:"toolCall,omitempty"`
}
type TelemetrySnapshotView ¶
type TelemetrySnapshotView struct {
Timestamp string `json:"timestamp"`
Enabled bool `json:"enabled"`
PrometheusEnabled bool `json:"prometheusEnabled"`
PrometheusAddress string `json:"prometheusAddress,omitempty"`
OTLPEnabled bool `json:"otlpEnabled"`
OTLPEndpoint string `json:"otlpEndpoint,omitempty"`
SessionsActive int64 `json:"sessionsActive"`
EventsPending int64 `json:"eventsPending"`
SessionsTotal int64 `json:"sessionsTotal"`
EventsDetectedTotal int64 `json:"eventsDetectedTotal"`
EventsWithdrawnTotal int64 `json:"eventsWithdrawnTotal"`
DecisionsTotal int64 `json:"decisionsTotal"`
DecisionsBreakdown DecisionBreakdown `json:"decisionsBreakdown"`
OperatorInputsTotal int64 `json:"operatorInputsTotal"`
GuardrailsTotal int64 `json:"guardrailsTotal"`
GuardrailsBreakdown map[string]int64 `json:"guardrailsBreakdown"`
AverageReactionTime float64 `json:"averageReactionTime"`
DecisionDurations []LatencyBucketView `json:"decisionDurations"`
}
type ToolCallParamView ¶ added in v0.8.0
type ToolCallView ¶ added in v0.8.0
type ToolCallView struct {
Server string `json:"server"`
Tool string `json:"tool"`
Risk string `json:"risk"`
Params []ToolCallParamView `json:"params,omitempty"`
ParamsTruncated bool `json:"paramsTruncated,omitempty"`
}
type UserInfo ¶ added in v0.6.0
type UserInfo struct {
Identity string `json:"identity"`
ConnID string `json:"connID"`
Role string `json:"role"`
ReadOnly bool `json:"readOnly"`
// TerminalsHidden tells a viewer's interface that the gateway sends it no
// terminal output, so it shows why rather than an empty terminal.
TerminalsHidden bool `json:"terminalsHidden,omitempty"`
}