177 lines
4.7 KiB
Go
177 lines
4.7 KiB
Go
// Package protocol defines the daemon/client control frames shared by
|
|
// transports. It intentionally contains data shapes only; app behavior stays
|
|
// in internal/app until the headless daemon split is complete.
|
|
package protocol
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// FrameType identifies one protocol message kind.
|
|
type FrameType string
|
|
|
|
const (
|
|
FrameHello FrameType = "hello"
|
|
FrameAuthChallenge FrameType = "auth_challenge"
|
|
FrameAuthOK FrameType = "auth_ok"
|
|
FrameAttach FrameType = "attach"
|
|
FrameDetach FrameType = "detach"
|
|
FrameProjectList FrameType = "project_list"
|
|
FrameChrome FrameType = "chrome"
|
|
FramePaneSnapshot FrameType = "pane_snapshot"
|
|
FramePaneChunk FrameType = "pane_chunk"
|
|
FrameLifecycle FrameType = "lifecycle"
|
|
FrameAttention FrameType = "attention"
|
|
FrameTrustPrompt FrameType = "trust_prompt"
|
|
FrameInput FrameType = "input"
|
|
FrameFocus FrameType = "focus"
|
|
FrameSwitchProject FrameType = "switch_project"
|
|
FrameOpenProject FrameType = "open_project"
|
|
FramePaletteCommand FrameType = "palette_command"
|
|
FrameTrustResponse FrameType = "trust_response"
|
|
FrameResize FrameType = "resize"
|
|
FrameList FrameType = "list"
|
|
FrameStop FrameType = "stop"
|
|
FrameError FrameType = "error"
|
|
)
|
|
|
|
// Frame is the transport envelope. Payload is deliberately raw JSON so
|
|
// network transports can frame without knowing every message type; loopback
|
|
// transports may pass the same bytes without JSON re-encoding.
|
|
type Frame struct {
|
|
Type FrameType `json:"type"`
|
|
RequestID string `json:"request_id,omitempty"`
|
|
Payload json.RawMessage `json:"payload,omitempty"`
|
|
}
|
|
|
|
// NewFrame marshals payload into a protocol frame.
|
|
func NewFrame[T any](typ FrameType, payload T) (Frame, error) {
|
|
b, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return Frame{}, fmt.Errorf("protocol: marshal %s: %w", typ, err)
|
|
}
|
|
return Frame{Type: typ, Payload: b}, nil
|
|
}
|
|
|
|
// Decode unmarshals f.Payload into v.
|
|
func Decode[T any](f Frame) (T, error) {
|
|
var v T
|
|
if len(f.Payload) == 0 {
|
|
return v, nil
|
|
}
|
|
if err := json.Unmarshal(f.Payload, &v); err != nil {
|
|
return v, fmt.Errorf("protocol: decode %s: %w", f.Type, err)
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
type Hello struct {
|
|
Version int `json:"version"`
|
|
DaemonID string `json:"daemon_id,omitempty"`
|
|
ClientID string `json:"client_id,omitempty"`
|
|
ProjectKey string `json:"project_key,omitempty"`
|
|
}
|
|
|
|
type Attach struct {
|
|
Token string `json:"token,omitempty"`
|
|
ProjectKey string `json:"project_key,omitempty"`
|
|
ProjectPath string `json:"project_path,omitempty"`
|
|
TermSize Size `json:"term_size"`
|
|
}
|
|
|
|
type Detach struct {
|
|
ClientID string `json:"client_id,omitempty"`
|
|
}
|
|
|
|
type Size struct {
|
|
Cols uint16 `json:"cols"`
|
|
Rows uint16 `json:"rows"`
|
|
}
|
|
|
|
type Project struct {
|
|
Key string `json:"key"`
|
|
Path string `json:"path"`
|
|
Name string `json:"name"`
|
|
LastActive time.Time `json:"last_active,omitempty"`
|
|
TabCount int `json:"tab_count"`
|
|
}
|
|
|
|
type ProjectList struct {
|
|
Projects []Project `json:"projects"`
|
|
}
|
|
|
|
type Chrome struct {
|
|
ProjectKey string `json:"project_key"`
|
|
Model json.RawMessage `json:"model"`
|
|
}
|
|
|
|
type PaneSnapshot struct {
|
|
PaneID string `json:"pane_id"`
|
|
Bytes []byte `json:"bytes"`
|
|
Size Size `json:"size,omitempty"`
|
|
DisplayOwner bool `json:"display_owner,omitempty"`
|
|
}
|
|
|
|
type PaneChunk struct {
|
|
PaneID string `json:"pane_id"`
|
|
Bytes []byte `json:"bytes"`
|
|
Size Size `json:"size,omitempty"`
|
|
DisplayOwner bool `json:"display_owner,omitempty"`
|
|
}
|
|
|
|
type LifecycleKind string
|
|
|
|
const (
|
|
LifecycleSpawned LifecycleKind = "spawned"
|
|
LifecycleExited LifecycleKind = "exited"
|
|
LifecycleClosed LifecycleKind = "closed"
|
|
LifecycleStateChanged LifecycleKind = "state_changed"
|
|
)
|
|
|
|
type Lifecycle struct {
|
|
Kind LifecycleKind `json:"kind"`
|
|
ProjectKey string `json:"project_key,omitempty"`
|
|
ChildID string `json:"child_id,omitempty"`
|
|
Child json.RawMessage `json:"child,omitempty"`
|
|
State string `json:"state,omitempty"`
|
|
}
|
|
|
|
type Input struct {
|
|
PaneID string `json:"pane_id"`
|
|
Bytes []byte `json:"bytes"`
|
|
}
|
|
|
|
type Focus struct {
|
|
PaneID string `json:"pane_id,omitempty"`
|
|
Pad string `json:"pad,omitempty"`
|
|
}
|
|
|
|
type SwitchProject struct {
|
|
Key string `json:"key"`
|
|
}
|
|
|
|
type OpenProject struct {
|
|
Path string `json:"path"`
|
|
}
|
|
|
|
type PaletteCommand struct {
|
|
Kind string `json:"kind"`
|
|
Data json.RawMessage `json:"data,omitempty"`
|
|
}
|
|
|
|
type TrustResponse struct {
|
|
ProcessID string `json:"process_id"`
|
|
Preset string `json:"preset"`
|
|
Allow bool `json:"allow"`
|
|
}
|
|
|
|
type Resize struct {
|
|
Size Size `json:"size"`
|
|
}
|
|
|
|
type Error struct {
|
|
Message string `json:"message"`
|
|
}
|