Documentation
¶
Overview ¶
Package agent contains the logic that runs inside the cliwrap-agent subprocess. It owns the child CLI process and the IPC connection back to the host.
Index ¶
- Constants
- Variables
- func BuildCapabilityReply() ipc.CapabilityReply
- func IsPidAlive(pid int) bool
- func ReadPidFile(dir string) (int, error)
- func Run(ctx context.Context, cfg Config) error
- func WritePersistentMeta(dir string, meta PersistentMeta) error
- func WritePidFile(dir string, pid int) error
- type Config
- type Dispatcher
- func (d *Dispatcher) AdoptConn(newConn *ipc.Conn)
- func (d *Dispatcher) Drain(ctx context.Context) error
- func (d *Dispatcher) Handle(msg ipc.OutboxMessage)
- func (d *Dispatcher) SendControl(t ipc.MsgType, payload any, ackRequired bool) error
- func (d *Dispatcher) SetPersistentSessionDir(dir string)
- func (d *Dispatcher) SetPersistentShutdown(ch chan struct{})
- type PersistentMeta
- type RunResult
- type RunSpec
- type Runner
- func (r *Runner) ResizeActivePTY(cols, rows uint16) error
- func (r *Runner) Run(ctx context.Context, spec RunSpec) (RunResult, error)
- func (r *Runner) SetPersistentRingBuffer(rb *ringBuffer)
- func (r *Runner) SetSender(s chunkSender)
- func (r *Runner) SignalActivePTY(sig syscall.Signal) error
- func (r *Runner) WriteToActivePTY(b []byte) error
Constants ¶
const FeatureFrameAck = "frame_ack"
FeatureFrameAck is the capability token advertised when the agent emits MsgAckData in response to any inbound frame whose header has FlagAckRequired set. Hosts that detect this bit may use the ack- await Conn helpers (SendAndAwaitAck) to turn fire-and-forget control messages into synchronous request/response. Without the bit, hosts must fall back to the historical fire-and-forget behaviour.
const FeaturePTY = "pty"
FeaturePTY is the capability token advertised when the agent supports PTY-mode child processes.
const FeaturePersistence = "persistence"
FeaturePersistence is the capability token advertised when the agent supports CW-G4 persistent sessions (--persistent flag, UNIX listener, reattach handshake).
Variables ¶
var ErrNoActivePTY = errors.New("agent: no active PTY child")
ErrNoActivePTY is returned when a PTY write or control operation is attempted but no PTY child is currently running.
Functions ¶
func BuildCapabilityReply ¶ added in v0.3.0
func BuildCapabilityReply() ipc.CapabilityReply
BuildCapabilityReply constructs the CapabilityReply the agent sends in response to a MsgTypeCapabilityQuery frame.
func IsPidAlive ¶ added in v0.3.0
IsPidAlive returns true if the process exists and is reachable via signal 0.
Caller must additionally compare meta.StartedAt to the agent's reported startup time to defend against PID rollover (handled in Manager.Reattach, CW-G4 Task 14).
func ReadPidFile ¶ added in v0.3.0
ReadPidFile parses the integer in <dir>/pid.
func Run ¶
Run is the main entry point invoked from cmd/cliwrap-agent/main.go. It blocks until the IPC connection closes or ctx is canceled.
func WritePersistentMeta ¶ added in v0.3.0
func WritePersistentMeta(dir string, meta PersistentMeta) error
WritePersistentMeta serializes meta to <dir>/meta.json with mode 0600. Caller is responsible for creating <dir> with appropriate permissions before calling.
func WritePidFile ¶ added in v0.3.0
WritePidFile writes <dir>/pid as a single decimal line, mode 0600.
Types ¶
type Config ¶
type Config struct {
IPCFD int
AgentID string
RuntimeDir string
OutboxCapacity int
WALBytes int64
MaxRecvPayload uint32
// Persistent enables CW-G4 daemon mode: SIGHUP ignored, UNIX listener
// at <CLIWRAP_SESSION_DIR>/sock for reattach, in-memory PTY ring buffer.
// Set by cmd/cliwrap-agent when --persistent argv flag is present.
Persistent bool
}
Config is the runtime configuration passed from the host via env vars and argv. For v1, the fd index is fixed at 3.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns sensible defaults, reading host-supplied env vars when present:
- CLIWRAP_AGENT_ID — overrides AgentID (set by spawner)
- CLIWRAP_AGENT_RUNTIME — overrides RuntimeDir (set by spawner)
- CLIWRAP_AGENT_OUTBOX_CAPACITY — overrides OutboxCapacity when set and parseable as a positive integer. Lets hosts use WithAgentOutboxCapacity to tune the agent's outbox for high- throughput PTY scenarios without recompiling the agent binary.
type Dispatcher ¶
type Dispatcher struct {
// contains filtered or unexported fields
}
Dispatcher routes inbound IPC messages and coordinates child lifecycles.
func NewDispatcher ¶
func NewDispatcher(conn *ipc.Conn) *Dispatcher
NewDispatcher constructs a Dispatcher bound to conn.
func (*Dispatcher) AdoptConn ¶ added in v0.3.0
func (d *Dispatcher) AdoptConn(newConn *ipc.Conn)
AdoptConn replaces the dispatcher's current conn with newConn. Used by the persistent agent's accept-loop on reattach: after sending Hello + PTYRingDump on the freshly accepted socket, the agent rewires d.conn so all subsequent inbound messages route through it and outbound messages (PTY data, child status, etc.) go to the new host.
The previously-adopted conn is closed before the new one is installed. Callers must call newConn.OnMessage(d.Handle) and newConn.Start() before or after AdoptConn — both orderings are safe; OnMessage is atomic.
CW-G4 Task 12e.
func (*Dispatcher) Drain ¶ added in v0.3.0
func (d *Dispatcher) Drain(ctx context.Context) error
Drain blocks until all goroutines registered via markRunnerActive have released their tokens, or until ctx is canceled / its deadline elapses. Returns nil on clean drain, ctx.Err() otherwise.
Intended caller: agent.Run, after the IPC connection has begun shutting down. See CW-G3 spec.
func (*Dispatcher) Handle ¶
func (d *Dispatcher) Handle(msg ipc.OutboxMessage)
Handle processes an inbound message from the host.
If the inbound frame has FlagAckRequired set, an MsgAckData carrying the inbound SeqNo is emitted AFTER the switch-handler completes — the ack promises "synchronous processing done", so it must follow rather than precede the work. Hosts that opted into this flag via SendAndAwaitAck can then turn fire-and-forget control messages (e.g., MsgStopChild) into synchronous request/response without bespoke per-message replies.
func (*Dispatcher) SendControl ¶
SendControl serializes payload and enqueues it as an outbound frame. Uses Conn.SendWithNewSeq so the (seq assignment → outbox enqueue) pair is atomic across concurrent senders; otherwise the receiver's watermark dedup would silently drop the lower-seq frame when a higher-seq one raced ahead. CW-G5.
func (*Dispatcher) SetPersistentSessionDir ¶ added in v0.3.0
func (d *Dispatcher) SetPersistentSessionDir(dir string)
SetPersistentSessionDir enables CW-G4 meta.json updates on StartChild — the full Spec (Command, Args, PTY config, etc.) is recorded so reattach hosts can reconstruct it. Required because initPersistent runs BEFORE StartChild arrives, so the initial meta.json only has the agent ID.
func (*Dispatcher) SetPersistentShutdown ¶ added in v0.3.0
func (d *Dispatcher) SetPersistentShutdown(ch chan struct{})
SetPersistentShutdown wires the persistent-mode shutdown channel. When set (persistent=true), the dispatcher closes shutdownReq after the child terminates (Stop or natural exit), allowing agent.Run to fall through to cleanup before the bootstrap socketpair would EOF.
CW-G4 Task 15+16. Called once at agent.Run startup when in persistent mode; safe to call before or after the dispatcher has begun handling IPC messages.
type PersistentMeta ¶ added in v0.3.0
type PersistentMeta struct {
Version string `json:"version"`
ID string `json:"id"`
Spec cwtypes.Spec `json:"spec"`
AgentPID int `json:"agentPID"`
StartedAt time.Time `json:"startedAt"`
}
PersistentMeta is the on-disk schema for persistent-session metadata. Stored at <sessionDir>/meta.json with mode 0600.
The Version field is for future schema migrations. v1 is "1.0".
func ReadPersistentMeta ¶ added in v0.3.0
func ReadPersistentMeta(dir string) (PersistentMeta, error)
ReadPersistentMeta loads <dir>/meta.json. Returns wrapped err if missing or unparseable; callers should treat any error as "session not loadable".
type RunSpec ¶
type RunSpec struct {
Command string
Args []string
Env map[string]string
WorkDir string
StopTimeout time.Duration
OnStarted func(pid int) // called after cmd.Start() succeeds, before Wait()
PTY *cwtypes.PTYConfig
}
RunSpec describes the child process to fork/exec.
type Runner ¶
type Runner struct {
// StdoutSink, if non-nil, is used to tee the child's stdout bytes.
StdoutSink io.Writer
// StderrSink, if non-nil, is used to tee the child's stderr bytes.
StderrSink io.Writer
// contains filtered or unexported fields
}
Runner starts a single child process and blocks until it exits.
func NewRunner ¶
func NewRunner() *Runner
NewRunner returns a Runner with default sinks set to io.Discard.
func (*Runner) ResizeActivePTY ¶ added in v0.3.0
ResizeActivePTY sends TIOCSWINSZ to the running PTY child with the given cols and rows. Returns ErrNoActivePTY if no PTY child is currently active.
func (*Runner) Run ¶
Run forks/execs the child and blocks until it exits or ctx is canceled. On ctx cancel it sends SIGTERM, waits StopTimeout, then SIGKILL. If spec.PTY is non-nil, the child is started inside a pseudo-terminal and output is emitted as MsgTypePTYData frames via the configured sender.
func (*Runner) SetPersistentRingBuffer ¶ added in v0.3.0
func (r *Runner) SetPersistentRingBuffer(rb *ringBuffer)
SetPersistentRingBuffer wires the per-session ring buffer into the PTY OnData broadcast path. Called once at agent startup when the agent is invoked with --persistent. Nil disables the integration (no-op tee). CW-G4.
func (*Runner) SetSender ¶ added in v0.3.0
func (r *Runner) SetSender(s chunkSender)
SetSender configures the outbound frame sender used in PTY mode. Call this before Run when RunSpec.PTY is non-nil.
func (*Runner) SignalActivePTY ¶ added in v0.3.0
SignalActivePTY delivers sig to the foreground process group of the running PTY child. Returns ErrNoActivePTY if no PTY child is currently active.
func (*Runner) WriteToActivePTY ¶ added in v0.3.0
WriteToActivePTY forwards b to the running PTY child's stdin. Returns ErrNoActivePTY if no PTY child is currently active.