controller

package
v0.4.6 Latest Latest
Warning

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

Go to latest
Published: May 12, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package controller manages the lifecycle of a single cliwrap-agent subprocess and its managed child. Each Controller owns one agent connection, tracks state transitions, and translates inbound IPC messages into observable state changes.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ReadHandshakeHello added in v0.3.0

func ReadHandshakeHello(conn net.Conn) (ipc.HelloPayload, error)

ReadHandshakeHello reads exactly one MsgHello frame from conn and decodes the HelloPayload. Used by Manager.Reattach (CW-G4) to verify StartedAt before constructing a Controller.

func ReadHandshakePTYRingDump added in v0.3.0

func ReadHandshakePTYRingDump(conn net.Conn) (ipc.PTYRingDumpPayload, error)

ReadHandshakePTYRingDump reads exactly one MsgTypePTYRingDump frame and decodes the PTYRingDumpPayload. Used by Manager.Reattach (CW-G4) after ReadHandshakeHello.

Types

type Controller

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

Controller manages exactly one agent subprocess and its child.

func NewController

func NewController(opts ControllerOptions) (*Controller, error)

NewController constructs a Controller.

func NewControllerFromReattach added in v0.3.0

func NewControllerFromReattach(opts ReattachOptions) (*Controller, error)

NewControllerFromReattach constructs a Controller around a connection to an already-running persistent agent. Returns the Controller in StateRunning with the initial PTY buffer queued for the next SubscribePTYData subscriber.

CW-G4 Task 14.

func (*Controller) AgentSupportsFrameAck added in v0.4.4

func (c *Controller) AgentSupportsFrameAck() bool

AgentSupportsFrameAck reports whether the connected agent advertised the "frame_ack" feature: it emits MsgAckData in response to any inbound frame whose header has FlagAckRequired set. When set, Controller.Stop uses Conn.SendAndAwaitAck to turn MsgStopChild into a synchronous request/response. When unset, Stop falls back to the historical fire-and-forget path. v0.4.4+.

func (*Controller) AgentSupportsPTY added in v0.3.0

func (c *Controller) AgentSupportsPTY() bool

AgentSupportsPTY reports whether the connected agent advertised the "pty" feature during capability negotiation.

func (*Controller) AgentSupportsPersistence added in v0.3.0

func (c *Controller) AgentSupportsPersistence() bool

AgentSupportsPersistence reports whether the connected agent advertised the "persistence" feature during capability negotiation. Used by processHandle.Start to refuse Persistent=true against pre-CW-G4 agents.

func (*Controller) ChildPID

func (c *Controller) ChildPID() int

ChildPID returns the PID of the running child (0 if none).

func (*Controller) Close

func (c *Controller) Close(ctx context.Context) error

Close tears everything down. Idempotent.

func (*Controller) CloseConnOnly added in v0.3.0

func (c *Controller) CloseConnOnly(ctx context.Context) error

CloseConnOnly closes the IPC connection without sending SIGTERM to the agent. Used by ProcessHandle.Close on persistent sessions (CW-G4) — the agent should remain alive in detached mode, ready for next reattach.

For reattach-derived controllers, c.handle is nil already, so Close and CloseConnOnly are equivalent. The asymmetry only matters for the original spawning host's controller, where c.handle holds the AgentHandle whose Close would SIGTERM the agent.

func (*Controller) CrashSource added in v0.3.0

func (c *Controller) CrashSource() CrashSource

CrashSource returns the source that triggered the last crash transition. The value is only meaningful when State() == StateCrashed.

func (*Controller) Options added in v0.2.0

func (c *Controller) Options() ControllerOptions

Options returns the options the controller was constructed with. Exposed for test inspection; production code should not rely on this.

func (*Controller) SendPTY added in v0.3.0

func (c *Controller) SendPTY(_ context.Context, msgType ipc.MsgType, payload []byte) error

SendPTY sends a raw PTY control frame to the agent. payload must already be encoded (e.g. via ipc.EncodePTYWrite). The context is present for interface symmetry and future flow-control; the send is currently non-blocking relative to ctx.

func (*Controller) SetInitialPTYBuffer added in v0.3.0

func (c *Controller) SetInitialPTYBuffer(b []byte)

SetInitialPTYBuffer stores the ring buffer snapshot received via MsgPTYRingDump on reattach. The buffer is delivered as the first chunk(s) to the next SubscribePTYData subscriber; subsequent subscribers see only live data.

CW-G4. Idempotent (last-write-wins) but typically called once per reattach.

func (*Controller) SetOnStateChange added in v0.4.0

func (c *Controller) SetOnStateChange(cb func(cwtypes.State))

SetOnStateChange registers cb to be called every time the controller's state transitions to a new value. The callback is also invoked when setState is called with the same value as the current state? No — duplicate transitions are filtered: cb fires only when old != new. Pass nil to disable. Replacing an existing callback is supported and atomic with respect to setState.

The callback runs on the goroutine that triggered the transition — usually the IPC dispatch loop or a host-initiated Start/Stop call — so it MUST NOT block. Hosts that need to do heavy work should hand off to a separate goroutine.

func (*Controller) Start

func (c *Controller) Start(ctx context.Context) error

Start spawns the agent and issues START_CHILD.

func (*Controller) State

func (c *Controller) State() cwtypes.State

State returns the current state.

func (*Controller) Stop

func (c *Controller) Stop(ctx context.Context) error

Stop sends MsgStopChild.

When the connected agent advertises the `frame_ack` capability, Stop blocks (until ctx) on the agent's MsgAckData reply, which the agent emits AFTER `dispatcher.stopChild` has cancelled the runner and awaited its wg. The caller is therefore guaranteed that by the time Stop returns nil, the child is dead and the runner has fully cleaned up. Older agents that lack the capability fall back to fire-and- forget (current historical behaviour) — Stop returns immediately once the frame is enqueued.

func (*Controller) SubscribePTYData added in v0.3.0

func (c *Controller) SubscribePTYData() (ch <-chan PTYData, unsubscribe func())

SubscribePTYData registers a new PTY data subscriber. The returned channel receives PTYData values for every inbound MsgTypePTYData frame. The caller MUST invoke the returned unsubscribe function exactly once when done, typically via defer. Failing to call it leaks the channel.

If the controller is not configured for PTY mode the caller should guard with Spec.PTY != nil before calling this method. SubscribePTYData itself does not validate the spec; it simply registers a receiver.

type ControllerOptions

type ControllerOptions struct {
	Spec       cwtypes.Spec
	Spawner    *supervise.Spawner
	RuntimeDir string

	// PersistentDir is the parent directory for per-session metadata
	// (CW-G4). Used only when Spec.Persistent=true. Caller (Manager)
	// passes Manager.persistentDir.
	PersistentDir string

	// OutboxCapacity overrides the in-memory IPC outbox slot count. Zero
	// uses the legacy default (1024). Larger values reduce the chance of
	// overflow under bursty traffic at the cost of memory.
	OutboxCapacity int

	// DisableWAL skips the disk-backed write-ahead log on the outgoing
	// IPC direction. When true, outbox overflow drops messages rather
	// than fsync-spilling to disk; the caller accepts the rare-drop
	// trade-off in exchange for predictable latency under bursty input.
	// Default (false) preserves the legacy "messages are never lost"
	// guarantee.
	DisableWAL bool

	// OnLogChunk is invoked for every inbound ipc.MsgLogChunk frame.
	// The callback runs on the ipc.Conn dispatch goroutine, so it must
	// not block for long. Callers that need to do heavy work should
	// hand the data off to a separate goroutine. May be nil, in which
	// case log chunks are dropped.
	OnLogChunk func(stream uint8, data []byte)
}

ControllerOptions configures a Controller.

type CrashInfo

type CrashInfo struct {
	Source     CrashSource
	AgentExit  int
	AgentSig   int
	ChildExit  int
	ChildSig   int
	Reason     string
	DetectedAt time.Time
}

CrashInfo unifies information from all four crash-detection layers. The earliest-arriving source wins for the "primary" fields; Layer 4 (OS wait) augments but does not overwrite.

func (*CrashInfo) Record

func (c *CrashInfo) Record(src CrashSource, data CrashInfo)

Record merges data from a detection source. The first call wins for Source/Reason/ChildExit/ChildSig; OS wait data is always merged in.

type CrashSource

type CrashSource int

CrashSource identifies which of the 4 crash-detection layers fired first.

const (
	CrashSourceNone CrashSource = iota
	CrashSourceExplicit
	CrashSourceConnectionLost
	CrashSourceHeartbeat
	CrashSourceOSWait
)

func (CrashSource) String

func (s CrashSource) String() string

String returns the lowercase name of the crash source.

type Heartbeat

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

Heartbeat sends periodic PINGs and fires OnMiss after the configured number of consecutive missing PONGs.

func NewHeartbeat

func NewHeartbeat(opts HeartbeatOptions) *Heartbeat

NewHeartbeat returns a Heartbeat.

func (*Heartbeat) RecordPong

func (h *Heartbeat) RecordPong()

RecordPong resets the miss counter to zero.

func (*Heartbeat) Start

func (h *Heartbeat) Start(ctx context.Context)

Start launches the heartbeat goroutine.

func (*Heartbeat) Wait

func (h *Heartbeat) Wait()

Wait blocks until the loop exits.

type HeartbeatOptions

type HeartbeatOptions struct {
	Sender        PingSender
	Interval      time.Duration
	Timeout       time.Duration
	MissThreshold int
	OnMiss        func(consecutiveMisses int)
}

HeartbeatOptions configures a Heartbeat.

type PTYData added in v0.3.0

type PTYData struct {
	Seq   uint64
	Bytes []byte
}

PTYData carries one chunk of PTY output delivered to a subscriber.

type PingSender

type PingSender interface {
	SendPing() error
}

PingSender is any object that can emit a PING message.

type ReattachOptions added in v0.3.0

type ReattachOptions struct {
	Spec          cwtypes.Spec
	Conn          net.Conn // post-handshake; controller wraps in ipc.Conn
	InitialBuffer []byte   // ring buffer dump from agent
	RuntimeDir    string
	PersistentDir string
	OnLogChunk    func(stream uint8, data []byte)

	// OutboxCapacity / DisableWAL mirror ControllerOptions; passed through
	// to the freshly-constructed ipc.Conn for the reattach connection.
	OutboxCapacity int
	DisableWAL     bool
}

ReattachOptions carries the inputs needed to construct a Controller around an already-handshaked persistent session conn (CW-G4 Task 14).

The caller (Manager.Reattach) has already: 1. Verified the session metadata and pid alive. 2. Dialed the per-session UNIX socket. 3. Read the Hello frame and verified StartedAt against meta.StartedAt. 4. Read the PTYRingDump frame.

NewControllerFromReattach takes ownership of Conn from this point; further reads belong to the IPC layer.

Jump to

Keyboard shortcuts

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