ws

package
v0.137.0 Latest Latest
Warning

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

Go to latest
Published: Mar 24, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Index

Constants

View Source
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

View Source
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

type BandwidthExceeded struct {
	Type    string `json:"type"`
	Message string `json:"message"`
}

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

func (c *Client) HasPTYSession(sessionID string) bool

HasPTYSession returns true if a goroutine is already handling this session.

func (*Client) PushPTYInput added in v0.108.0

func (c *Client) PushPTYInput(sessionID string, data []byte) bool

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

func (c *Client) Run(ctx context.Context) error

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

func (c *Client) SendAttention(ctx context.Context, sessionID string) error

SendAttention sends a session.attention message to the relay (bell detected).

func (*Client) SendConfig added in v0.37.0

func (c *Client) SendConfig(ctx context.Context) error

SendConfig pushes the wing's current lock state to the relay.

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 ErrorMsg

type ErrorMsg struct {
	Type    string `json:"type"`
	Message string `json:"message"`
}

ErrorMsg is sent by the relay for protocol errors.

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

type PTYAttentionAck struct {
	Type      string `json:"type"`
	SessionID string `json:"session_id"`
}

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 PTYDetach added in v0.7.19

type PTYDetach struct {
	Type      string `json:"type"`
	SessionID string `json:"session_id"`
}

PTYDetach explicitly detaches the browser from a PTY session.

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

type PTYFallback struct {
	Type      string `json:"type"`
	SessionID string `json:"session_id"`
}

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 PTYKill

type PTYKill struct {
	Type      string `json:"type"`
	SessionID string `json:"session_id"`
}

PTYKill requests termination of a PTY session.

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

type PTYMigrated struct {
	Type      string `json:"type"`
	SessionID string `json:"session_id"`
}

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

type PTYWriteFunc func(v any) error

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

func (tc *TunnelClient) DiscoverWing(ctx context.Context, wingID string) (*WingInfo, error)

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

type WingHeartbeat struct {
	Type   string `json:"type"`
	WingID string `json:"wing_id"`
}

WingHeartbeat is sent by the wing every 30s.

type WingInfo added in v0.122.0

type WingInfo struct {
	WingID    string `json:"wing_id"`
	PublicKey string `json:"public_key"`
}

WingInfo holds the minimal info needed to connect to a wing.

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.

Jump to

Keyboard shortcuts

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