Documentation
¶
Index ¶
- Constants
- Variables
- type BandwidthExceeded
- type Client
- func (c *Client) HasPTYSession(sessionID string) bool
- func (c *Client) PushPTYInput(sessionID string, data []byte) bool
- func (c *Client) RegisterPTYSession(ctx context.Context, sessionID string) (write PTYWriteFunc, input <-chan []byte, cleanup func())
- func (c *Client) Run(ctx context.Context) error
- func (c *Client) SendAttention(ctx context.Context, sessionID string) error
- func (c *Client) SendConfig(ctx context.Context) error
- type DirEntry
- type Envelope
- type ErrorMsg
- type PTYAttach
- type PTYAttentionAck
- type PTYBrowserOpen
- type PTYDetach
- type PTYExited
- type PTYFallback
- type PTYHandler
- type PTYInput
- type PTYKill
- type PTYMigrate
- type PTYMigrated
- type PTYOutput
- type PTYPreview
- type PTYResize
- type PTYStart
- type PTYStarted
- type PTYWriteFunc
- type PasskeyChallenge
- type PasskeyRegistered
- type PasskeyResponse
- type RegisteredMsg
- type RelayRestart
- type SessionAttention
- type SessionInfo
- type TunnelClient
- type TunnelHandler
- type TunnelRequest
- type TunnelResponse
- type TunnelStream
- type WingConfig
- type WingHeartbeat
- type WingInfo
- type WingProject
- type WingRegister
Constants ¶
const ( // Wing → Relay TypeWingRegister = "wing.register" TypeWingHeartbeat = "wing.heartbeat" // PTY (bidirectional, already E2E encrypted) TypePTYStart = "pty.start" // browser → relay → wing TypePTYStarted = "pty.started" // wing → relay → browser TypePTYOutput = "pty.output" // wing → relay → browser TypePTYInput = "pty.input" // browser → relay → wing TypePTYResize = "pty.resize" // browser → relay → wing TypePTYExited = "pty.exited" // wing → relay → browser TypePTYAttach = "pty.attach" // browser → relay → wing (reattach) TypePTYKill = "pty.kill" // browser → relay → wing (terminate session) TypePTYDetach = "pty.detach" // browser → relay (explicit detach before disconnect) TypePTYAttentionAck = "pty.attention_ack" // browser → relay → wing (notification seen) TypePTYPreview = "pty.preview" // wing → relay → browser (ephemeral) TypePTYBrowserOpen = "pty.browser_open" // wing → relay → browser (URL open request) TypePTYMigrate = "pty.migrate" // browser → relay → wing (request P2P migration) TypePTYMigrated = "pty.migrated" // wing → relay → browser (P2P migration complete) TypePTYFallback = "pty.fallback" // wing → relay → browser (P2P failed, back to relay) // Encrypted tunnel (browser ↔ wing, relay is opaque forwarder) TypeTunnelRequest = "tunnel.req" // browser → relay → wing TypeTunnelResponse = "tunnel.res" // wing → relay → browser TypeTunnelStream = "tunnel.stream" // wing → relay → browser (streaming) // Wing → Relay (session attention broadcast) TypeSessionAttention = "session.attention" // Relay → Browser/Wing (bandwidth) TypeBandwidthExceeded = "bandwidth.exceeded" // Passkey challenge-response (wing ↔ browser, relay is passthrough) TypePasskeyChallenge = "passkey.challenge" TypePasskeyResponse = "passkey.response" // Relay → Wing (passkey lifecycle event) TypePasskeyRegistered = "passkey.registered" // Wing → Relay (config change) TypeWingConfig = "wing.config" // Relay → Wing (control) TypeRegistered = "registered" TypeRelayRestart = "relay.restart" // relay → all: server shutting down, reconnect TypeWingOffline = "wing.offline" // relay → PTY browsers: wing disconnected TypeError = "error" )
Message types for the relay WebSocket protocol.
Variables ¶
var ErrAuthRejected = errors.New("relay rejected authentication (401)")
ErrAuthRejected is returned when the relay rejects the WebSocket handshake with 401.
Functions ¶
This section is empty.
Types ¶
type BandwidthExceeded ¶ added in v0.23.0
BandwidthExceeded is sent to browser/wing when monthly cap is hit.
type Client ¶
type Client struct {
RoostURL string // e.g. "wss://ws.wingthing.ai/ws/wing"
Token string // device auth token
WingID string
Hostname string // display name (os.Hostname)
Platform string // runtime.GOOS
Version string // build version
Agents []string
Skills []string
Labels []string
Identities []string
Projects []WingProject
OrgSlug string
RootDir string
PublicKey string // X25519 identity key (base64)
Locked bool
AllowedCount int
// RelayPubKey is the relay's EC P-256 public key (base64 DER), received during registration.
// Used for JWT verification in direct mode.
RelayPubKey string
OnPTY PTYHandler
OnTunnel TunnelHandler
OnOrphanKill func(ctx context.Context, sessionID string) // kill egg with no active goroutine
OnReconnect func(ctx context.Context) // called after re-registration with relay
OnPasskeyRegistered func(msg PasskeyRegistered) // called when a user registers a passkey
OnStateChange func(state string, err error) // called on connection state transitions
// contains filtered or unexported fields
}
Client is an outbound WebSocket client that connects a wing to the roost.
func (*Client) HasPTYSession ¶ added in v0.16.1
HasPTYSession returns true if a goroutine is already handling this session.
func (*Client) PushPTYInput ¶ added in v0.108.0
PushPTYInput pushes raw data into a session's input channel from outside the WebSocket read loop. Used by P2P DataChannels to route messages into the session handler. Returns true if the session exists and the message was delivered.
func (*Client) RegisterPTYSession ¶ added in v0.7.35
func (c *Client) RegisterPTYSession(ctx context.Context, sessionID string) (write PTYWriteFunc, input <-chan []byte, cleanup func())
RegisterPTYSession creates an input channel for a reclaimed session so pty.attach/input/resize/kill messages from the relay get routed to it. Returns the input channel and a write function. The caller must start a goroutine to handle the session and clean up when done.
func (*Client) Run ¶
Run connects to the relay and processes tasks until ctx is cancelled. Automatically reconnects on disconnect with exponential backoff. Returns ErrAuthRejected if the relay rejects the token with 401.
func (*Client) SendAttention ¶ added in v0.44.4
SendAttention sends a session.attention message to the relay (bell detected).
type DirEntry ¶ added in v0.6.3
type DirEntry struct {
Name string `json:"name"`
IsDir bool `json:"is_dir"`
Path string `json:"path"`
}
DirEntry is a single entry in a directory listing.
type Envelope ¶
type Envelope struct {
Type string `json:"type"`
}
Envelope wraps every WebSocket message with a type field for routing.
type PTYAttach ¶
type PTYAttach struct {
Type string `json:"type"`
SessionID string `json:"session_id"`
PublicKey string `json:"public_key,omitempty"` // new browser ephemeral key
WingID string `json:"wing_id,omitempty"` // target wing (for relay routing)
AuthToken string `json:"auth_token,omitempty"` // cached passkey auth token
UserID string `json:"user_id,omitempty"` // relay-injected
Cols uint32 `json:"cols,omitempty"` // browser terminal cols (for resize-before-snapshot)
Rows uint32 `json:"rows,omitempty"` // browser terminal rows (for resize-before-snapshot)
Spectate bool `json:"spectate,omitempty"` // read-only spectator mode
ViewerID string `json:"viewer_id,omitempty"` // relay-assigned spectator ID
Email string `json:"email,omitempty"` // relay-injected user email
Passkeys []string `json:"passkeys,omitempty"` // relay-injected passkey attestation
}
PTYAttach requests reattachment to an existing PTY session.
type PTYAttentionAck ¶ added in v0.12.8
PTYAttentionAck acknowledges a notification was seen by the browser.
type PTYBrowserOpen ¶ added in v0.68.0
type PTYBrowserOpen struct {
Type string `json:"type"` // "pty.browser_open"
SessionID string `json:"session_id"`
URL string `json:"url"`
}
PTYBrowserOpen notifies the browser that an agent requested a URL open.
type PTYExited ¶
type PTYExited struct {
Type string `json:"type"`
SessionID string `json:"session_id"`
ExitCode int `json:"exit_code"`
Error string `json:"error,omitempty"` // crash/error info for display
ViewerID string `json:"viewer_id,omitempty"` // spectator viewer ID (for relay routing)
}
PTYExited tells the browser the process exited.
type PTYFallback ¶ added in v0.108.0
PTYFallback notifies the browser that a P2P DataChannel died and I/O is back on the relay.
type PTYHandler ¶
type PTYHandler func(ctx context.Context, start PTYStart, write PTYWriteFunc, input <-chan []byte)
PTYHandler is called when the wing receives a pty.start request. It should spawn the agent in a PTY and manage I/O. The write function sends messages back through the relay to the browser. The input channel receives raw JSON messages (pty.input and pty.resize) from the browser.
type PTYInput ¶
type PTYInput struct {
Type string `json:"type"`
SessionID string `json:"session_id"`
Data string `json:"data"` // base64-encoded
}
PTYInput carries keystrokes from browser to wing.
type PTYMigrate ¶ added in v0.108.0
type PTYMigrate struct {
Type string `json:"type"`
SessionID string `json:"session_id"`
AuthToken string `json:"auth_token,omitempty"` // passkey auth token for re-validation
}
PTYMigrate requests migration of a PTY session to a P2P DataChannel.
type PTYMigrated ¶ added in v0.108.0
PTYMigrated confirms a PTY session has been migrated to a P2P DataChannel.
type PTYOutput ¶
type PTYOutput struct {
Type string `json:"type"`
SessionID string `json:"session_id"`
Data string `json:"data"` // base64-encoded
Compressed bool `json:"compressed,omitempty"` // gzip before encrypt
ViewerID string `json:"viewer_id,omitempty"` // spectator viewer ID (for relay routing)
}
PTYOutput carries raw terminal bytes from wing to browser.
type PTYPreview ¶ added in v0.63.0
type PTYPreview struct {
Type string `json:"type"`
SessionID string `json:"session_id"`
Data string `json:"data"` // base64(AES-GCM encrypted JSON)
ViewerID string `json:"viewer_id,omitempty"` // spectator viewer ID (for relay routing)
}
PTYPreview carries preview panel data (URL or markdown) from wing to browser.
type PTYResize ¶
type PTYResize struct {
Type string `json:"type"`
SessionID string `json:"session_id"`
Cols int `json:"cols"`
Rows int `json:"rows"`
}
PTYResize tells the wing to resize the terminal.
type PTYStart ¶
type PTYStart struct {
Type string `json:"type"`
SessionID string `json:"session_id"`
Agent string `json:"agent"` // "claude", "codex", "ollama"
Cols int `json:"cols"`
Rows int `json:"rows"`
PublicKey string `json:"public_key,omitempty"` // browser's ephemeral X25519 (base64)
CWD string `json:"cwd,omitempty"` // working directory for the agent
WingID string `json:"wing_id,omitempty"` // target wing (picks first if empty)
PasskeyCredentialID string `json:"passkey_credential_id,omitempty"` // base64url credential ID
AuthToken string `json:"auth_token,omitempty"` // cached passkey auth token
UserID string `json:"user_id,omitempty"` // relay-injected creator user ID
Email string `json:"email,omitempty"` // relay-injected user email
DisplayName string `json:"display_name,omitempty"` // relay-injected display name (Google full name, GitHub login)
OrgRole string `json:"org_role,omitempty"` // relay-injected: "owner", "admin", "member", ""
Passkeys []string `json:"passkeys,omitempty"` // relay-injected: base64 raw P-256 public keys
}
PTYStart requests a new interactive terminal session on the wing.
type PTYStarted ¶
type PTYStarted struct {
Type string `json:"type"`
SessionID string `json:"session_id"`
Agent string `json:"agent"`
PublicKey string `json:"public_key,omitempty"` // wing's X25519 (base64)
CWD string `json:"cwd,omitempty"` // resolved working directory
AuthToken string `json:"auth_token,omitempty"` // passkey auth token
ViewerID string `json:"viewer_id,omitempty"` // spectator viewer ID (relay-assigned)
}
PTYStarted confirms the PTY session is running.
type PTYWriteFunc ¶
PTYWriteFunc sends a message back to the relay over the wing's WebSocket.
type PasskeyChallenge ¶ added in v0.34.0
type PasskeyChallenge struct {
Type string `json:"type"`
SessionID string `json:"session_id"`
Challenge string `json:"challenge"` // base64url random 32 bytes
}
PasskeyChallenge is sent from wing to browser requesting passkey verification.
type PasskeyRegistered ¶ added in v0.62.0
type PasskeyRegistered struct {
Type string `json:"type"`
UserID string `json:"user_id"`
Email string `json:"email,omitempty"`
}
PasskeyRegistered is sent from relay to wings when a user registers a new passkey. Lightweight event — actual key data flows through relay-enriched messages at auth time.
type PasskeyResponse ¶ added in v0.34.0
type PasskeyResponse struct {
Type string `json:"type"`
SessionID string `json:"session_id"`
CredentialID string `json:"credential_id"` // base64url
AuthenticatorData string `json:"authenticator_data"` // base64
ClientDataJSON string `json:"client_data_json"` // base64
Signature string `json:"signature"` // base64 (ASN.1 DER)
}
PasskeyResponse is sent from browser to wing with the passkey assertion.
type RegisteredMsg ¶
type RegisteredMsg struct {
Type string `json:"type"`
WingID string `json:"wing_id"`
RelayPubKey string `json:"relay_pub_key,omitempty"` // base64 DER EC P-256 public key for JWT verification
}
RegisteredMsg is the relay's acknowledgment of a successful wing registration.
type RelayRestart ¶ added in v0.23.0
type RelayRestart struct {
Type string `json:"type"`
}
RelayRestart is sent to all connected WebSockets when the server is shutting down.
type SessionAttention ¶ added in v0.44.4
type SessionAttention struct {
Type string `json:"type"`
SessionID string `json:"session_id"`
Agent string `json:"agent,omitempty"`
CWD string `json:"cwd,omitempty"`
Nonce string `json:"nonce,omitempty"` // dedup key: same nonce = same attention episode
}
SessionAttention is sent by the wing when a session needs user attention (bell detected).
type SessionInfo ¶ added in v0.7.32
type SessionInfo struct {
SessionID string `json:"session_id"`
Agent string `json:"agent"`
CWD string `json:"cwd,omitempty"`
EggConfig string `json:"egg_config,omitempty"` // YAML config snapshot
NeedsAttention bool `json:"needs_attention,omitempty"`
Audit bool `json:"audit,omitempty"` // true if session has audit recording
Chat bool `json:"chat,omitempty"` // true if session has chat history
UserID string `json:"user_id,omitempty"`
Email string `json:"email,omitempty"`
}
SessionInfo describes one active session on a wing (used in tunnel sessions.list responses).
type TunnelClient ¶ added in v0.122.0
type TunnelClient struct {
RelayURL string // HTTP base URL (e.g. "https://wingthing.ai")
DeviceToken string // Bearer token for relay auth
PrivKey *ecdh.PrivateKey // wing's own identity key
}
TunnelClient sends encrypted tunnel requests to a wing via the relay.
func (*TunnelClient) DiscoverWing ¶ added in v0.122.0
DiscoverWing finds a wing's public key from the relay API.
func (*TunnelClient) Stream ¶ added in v0.122.0
func (tc *TunnelClient) Stream(ctx context.Context, wingID, wingPubKey string, inner any, onChunk func([]byte) error) error
Stream opens a WebSocket to the relay, sends an encrypted tunnel request, and collects streaming response chunks. The onChunk callback receives decrypted JSON payloads. The stream ends when a chunk with done:true is received.
type TunnelHandler ¶ added in v0.34.0
type TunnelHandler func(ctx context.Context, req TunnelRequest, write PTYWriteFunc)
TunnelHandler is called when the wing receives an encrypted tunnel request.
type TunnelRequest ¶ added in v0.34.0
type TunnelRequest struct {
Type string `json:"type"`
WingID string `json:"wing_id"`
RequestID string `json:"request_id"`
SenderPub string `json:"sender_pub,omitempty"` // browser X25519 identity pubkey
Payload string `json:"payload"` // base64(AES-GCM encrypted)
SenderUserID string `json:"sender_user_id,omitempty"` // relay-injected user ID
SenderOrgRole string `json:"sender_org_role,omitempty"` // relay-injected: "owner", "admin", "member", ""
SenderEmail string `json:"sender_email,omitempty"` // relay-injected user email
SenderPasskeys []string `json:"sender_passkeys,omitempty"` // relay-injected: base64 raw P-256 public keys
}
TunnelRequest is an encrypted request from browser to wing via relay.
type TunnelResponse ¶ added in v0.34.0
type TunnelResponse struct {
Type string `json:"type"`
RequestID string `json:"request_id"`
Payload string `json:"payload"` // base64(AES-GCM encrypted)
}
TunnelResponse is an encrypted response from wing to browser via relay.
type TunnelStream ¶ added in v0.34.0
type TunnelStream struct {
Type string `json:"type"`
RequestID string `json:"request_id"`
Payload string `json:"payload"` // base64(AES-GCM encrypted)
Done bool `json:"done"`
}
TunnelStream is an encrypted streaming chunk from wing to browser via relay.
type WingConfig ¶ added in v0.37.0
type WingConfig struct {
Type string `json:"type"`
WingID string `json:"wing_id"`
Locked bool `json:"locked"`
AllowedCount int `json:"allowed_count"`
}
WingConfig is sent by the wing when lock state changes (e.g. lock/unlock, allow/revoke).
type WingHeartbeat ¶
WingHeartbeat is sent by the wing every 30s.
type WingProject ¶ added in v0.6.0
type WingProject struct {
Name string `json:"name"` // directory name (e.g. "wingthing")
Path string `json:"path"` // absolute path (e.g. "/Users/ehrlich/repos/wingthing")
ModTime int64 `json:"mod_time,omitempty"` // unix timestamp of last modification
}
WingProject is a project directory discovered on the wing.
type WingRegister ¶
type WingRegister struct {
Type string `json:"type"`
WingID string `json:"wing_id"`
Hostname string `json:"hostname,omitempty"`
Platform string `json:"platform,omitempty"` // runtime.GOOS (e.g. "darwin", "linux")
Version string `json:"version,omitempty"` // build version (e.g. "v0.7.35")
Agents []string `json:"agents"`
Skills []string `json:"skills"`
Labels []string `json:"labels"`
Identities []string `json:"identities"`
Projects []WingProject `json:"projects,omitempty"`
OrgSlug string `json:"org_slug,omitempty"`
RootDir string `json:"root_dir,omitempty"`
PublicKey string `json:"public_key,omitempty"` // wing's X25519 identity key (base64)
Locked bool `json:"locked"` // explicit locked flag from wing.yaml
AllowedCount int `json:"allowed_count,omitempty"` // number of allowed keys
}
WingRegister is sent by the wing on connect.