Documentation
¶
Overview ¶
Package adapterclient is the one place the harness spawns an adapter child (plan 3.4, 4.1, 6.6; brief section 2.2). Every request/response command goes through Client.Call and adapterkit.Spawn; the one long-running `message watch` child goes through Client.StartWatch and the single exec.CommandContext in spawn.go. No other harness package spawns an adapter.
The child environment is built FROM SCRATCH with adapterkit.ChildEnv: inherited BRIGADE_* and CLAUDE_CODE_MESSAGING_* never reach the child, only the allow-listed variables (PATH, HOME, TMPDIR, the proxy variables, SSL_CERT_*, XDG_*, CLAUDE_CONFIG_DIR) plus the four computed BRIGADE_PROFILE/CONFIG_DIR/STATE_DIR/LOG_LEVEL do. The child's stderr is captured to the adapter log, never surfaced, so a returned error carries only the adapter's own safe envelope message or adapterkit.Spawn's fixed classification — never raw adapter stderr (U-24).
A failing envelope the adapter PRODUCED is returned as a *protocol.Error alongside the envelope; an error the adapter did NOT produce (a timeout, a signal death, a missing executable, a runaway or unparseable stdout) is returned by Spawn with a nil envelope, so a caller can tell "the adapter said" from "the adapter broke" (4.6).
Index ¶
- Constants
- type Client
- func (c *Client) Ack(ctx context.Context, sessionID string, req *protocol.AckRequest) (*protocol.AckResult, error)
- func (c *Client) Call(ctx context.Context, group, verb string, flags []string, stdin any) (*protocol.Envelope, error)
- func (c *Client) Close(ctx context.Context, sessionID string) (*CloseResult, error)
- func (c *Client) Describe(ctx context.Context) (*protocol.DescribeResult, error)
- func (c *Client) Heartbeat(ctx context.Context, sessionID string, hb *protocol.HeartbeatRequest) (*protocol.HeartbeatResult, error)
- func (c *Client) ListSessions(ctx context.Context, includeOffline bool) (*ListResult, error)
- func (c *Client) PassThrough(ctx context.Context, group, verb string, args []string, stdin io.Reader, ...) (int, error)
- func (c *Client) Receive(ctx context.Context, sessionID string, limit int) (*ReceiveResult, error)
- func (c *Client) Register(ctx context.Context, reg *protocol.SessionRegistration) (*RegisterResult, error)
- func (c *Client) Send(ctx context.Context, req *protocol.SendRequest) (*protocol.SendResponse, error)
- func (c *Client) StartWatch(ctx context.Context, sessionID string) (*Watch, error)
- func (c *Client) TeamMembers(ctx context.Context) (*MembersResult, error)
- type CloseResult
- type Event
- type EventKind
- type ListResult
- type Member
- type MembersResult
- type ReceiveResult
- type RegisterResult
- type SpawnFunc
- type Watch
Constants ¶
const ( // DefaultTimeout is the 20 s budget for an ordinary request/response // command. DefaultTimeout = 20 * time.Second // RegisterTimeout is the 8 s budget for `session register` at // SessionStart. RegisterTimeout = 8 * time.Second // CloseTimeout is the 1 s budget for `session close` at SessionEnd. CloseTimeout = 1 * time.Second // WatchRequestTimeout is the 3 s budget for a request the watcher // makes. WatchRequestTimeout = 3 * time.Second // DescribeTimeout is the 3 s cap on the cached describe probe. DescribeTimeout = 3 * time.Second )
The 4.1 timeout budgets, exposed so callers wrap ctx with them; Call itself honours whatever deadline ctx already carries.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// Adapter is the resolved argv prefix (config.ResolveAdapter's result).
Adapter config.Adapter
// Profile, ConfigDir and StateDir are the computed BRIGADE_* values the
// child's environment carries.
Profile string
ConfigDir string
StateDir string
// LogLevel is the child's BRIGADE_LOG_LEVEL; "" lets the adapter
// default it.
LogLevel string
// Environ is the parent environment ChildEnv filters. Inherited
// BRIGADE_* and CLAUDE_CODE_MESSAGING_* are dropped from it.
Environ []string
// Logger receives this package's scalar diagnostics; nil discards them.
Logger *slog.Logger
// Spawn is the request/response spawn seam: nil means
// [adapterkit.Spawn], and production never sets it. A caller's test
// injects a recorder here to assert exactly what every child would
// have received — argv, environment, stdin — or that no child was
// spawned at all (U-25), without starting a process. StartWatch is not
// routed through it: the watch child is a real process by nature and
// the fake adapter's dump file is its recorder.
Spawn SpawnFunc
}
A Client spawns one adapter's children for one profile. The zero value is not usable; set Adapter, Profile, ConfigDir and StateDir at least.
func (*Client) Ack ¶
func (c *Client) Ack(ctx context.Context, sessionID string, req *protocol.AckRequest) (*protocol.AckResult, error)
Ack runs `message ack --session <id>`.
func (*Client) Call ¶
func (c *Client) Call(ctx context.Context, group, verb string, flags []string, stdin any) (*protocol.Envelope, error)
Call runs one request/response adapter command and returns its envelope.
- group/verb name the command (verb is "" for `describe`); flags are the argv flags (e.g. --session, --include-offline); stdin is the JSON input document, or nil for a command that takes none.
- On a successful spawn that produced an OK envelope: (envelope, nil).
- On a failing envelope the adapter PRODUCED: (envelope, *protocol.Error) — the code, message, retry_after_ms and details from the wire, with an unrecognised code normalised to `internal`; retryability is the caller's to recompute from the code (4.3).
- On an error the adapter did NOT produce (a timeout, a signal death, a missing executable, a runaway or unparseable stdout): (nil, err) as adapterkit.Spawn returns it.
The deadline is ctx's: wrap it with one of the package timeout constants.
func (*Client) Describe ¶
Describe returns the adapter's describe result, cached per adapter argv, and performs the protocol check: a describe whose protocol_version is not this harness's is protocol_mismatch (exit 10), carrying the adapter's own name and version in details for `whoami`. The cached result serves the capability checks P3-3/P3-5 make.
The deadline is ctx's, like Client.Call: a caller wraps it with DescribeTimeout (the 3 s describe cap of 4.1). Deferring the cap to the caller keeps the one describe per command bounded in production without making a loaded test's spawn race a 3 s stopwatch.
func (*Client) Heartbeat ¶
func (c *Client) Heartbeat(ctx context.Context, sessionID string, hb *protocol.HeartbeatRequest) (*protocol.HeartbeatResult, error)
Heartbeat runs `session heartbeat --session <id>`.
func (*Client) ListSessions ¶
ListSessions runs `session list [--include-offline]`.
func (*Client) PassThrough ¶
func (c *Client) PassThrough(ctx context.Context, group, verb string, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error)
PassThrough runs one adapter command with the CALLER's streams instead of captured ones: the human terminal commands of 6.4 (`brigade team create|join|leave`, `brigade profile …`) hand their stdin, stdout and stderr straight to the adapter, so `--prompt` can read a join secret from the TTY without echo and the adapter's own envelope reaches the human unchanged. Nothing is parsed here: the argv is `<adapter prefix> --profile <p> <group> <verb> <args…>` and the child's environment is the same from-scratch one every other child gets (inherited BRIGADE_* and CLAUDE_CODE_MESSAGING_* never cross). stdin may be nil for a command that takes none; an *os.File is handed to the child as a descriptor, which is what keeps a terminal a terminal.
It returns the child's own exit status, forwarded verbatim (4.6 gives it meaning) with a nil error. The error is non-nil only when the adapter did NOT run to an exit of its own — the executable was not found ( `unavailable`, details.reason adapter_not_found), it died on a signal or ctx ended (`unavailable`), or the start failed (`internal`) — and the status is then -1. It is the one other exec.CommandContext of the harness beside StartWatch, which is why it lives in this file.
func (*Client) Register ¶
func (c *Client) Register(ctx context.Context, reg *protocol.SessionRegistration) (*RegisterResult, error)
Register runs `session register` (SessionStart). The caller sets ctx's deadline (RegisterTimeout).
func (*Client) Send ¶
func (c *Client) Send(ctx context.Context, req *protocol.SendRequest) (*protocol.SendResponse, error)
Send runs `message send` (the sender is a member of the request).
func (*Client) StartWatch ¶
StartWatch spawns `message watch --session <id>` with stdin and stdout pipes and the same from-scratch environment as Client.Call. It returns once the child has started; events arrive on Events.
func (*Client) TeamMembers ¶
func (c *Client) TeamMembers(ctx context.Context) (*MembersResult, error)
TeamMembers runs `team members` (capability team.roster).
type CloseResult ¶
CloseResult is the `session close` result (4.4.3).
type Event ¶
type Event struct {
Kind EventKind
Ready *protocol.WatchReady
Message *protocol.MessageEnvelope
Status *protocol.WatchStatus
Acked *protocol.WatchAcked
HeartbeatOK *protocol.WatchHeartbeatOK
Error *protocol.ErrorObject
}
An Event is one decoded, validated `message watch` event (4.4.9). Exactly one typed member is set, selected by Kind. The supervision, backoff and restart policy of 6.6 is P3-5's; this seam only decodes and delivers.
type ListResult ¶
type ListResult struct {
TeamRef string `json:"team_ref"`
TeamName string `json:"team_name"`
ServerTime time.Time `json:"server_time"`
Sessions []protocol.SessionRecord `json:"sessions"`
Truncated bool `json:"truncated"`
}
ListResult is the `session list` result (4.4.3).
type Member ¶
type Member struct {
PrincipalRef string `json:"principal_ref"`
HumanLabel string `json:"human_label,omitzero"`
Status string `json:"status"`
JoinedAt time.Time `json:"joined_at"`
LastSeenAt *time.Time `json:"last_seen_at"`
SessionCount int `json:"session_count"`
}
Member is one roster entry of the `team members` result (4.4.10).
type MembersResult ¶
type MembersResult struct {
TeamRef string `json:"team_ref"`
TeamName string `json:"team_name"`
ServerTime time.Time `json:"server_time"`
Members []Member `json:"members"`
}
MembersResult is the `team members` result (4.4.10, C-43).
type ReceiveResult ¶
type ReceiveResult struct {
Messages []protocol.MessageEnvelope `json:"messages"`
}
ReceiveResult is the `message receive` result (4.4.5).
type RegisterResult ¶
type RegisterResult struct {
protocol.SessionRecord
Resumed bool `json:"resumed"`
LeaseSeconds int `json:"lease_seconds"`
ServerTime time.Time `json:"server_time"`
}
RegisterResult is the `session register` result (4.4.2): a SessionRecord with the three registration-only members.
type SpawnFunc ¶
type SpawnFunc func(context.Context, adapterkit.SpawnSpec) (*adapterkit.SpawnResult, error)
A SpawnFunc has adapterkit.Spawn's shape.
type Watch ¶
type Watch struct {
// contains filtered or unexported fields
}
A Watch is one running `message watch` child. Events are delivered on Watch.Events until the stream ends; commands are written with Watch.Ack, Watch.Heartbeat and Watch.Close; Watch.Wait reaps the child and reports its exit status. Cancel the ctx passed to StartWatch to stop the child (SIGTERM, then SIGKILL after five seconds).
Delivery is unbuffered: the reader blocks until the caller takes each event. Once ctx is cancelled the reader stops delivering and drains the stream to EOF instead, so the stop sequence — cancel, then Wait — never deadlocks on events the caller no longer reads; a message that was not delivered is not acknowledged and the server redelivers it. A caller that has NOT cancelled ctx must consume Events until it closes before Wait can return, exactly as with an exec.Cmd stdout pipe.
func (*Watch) Events ¶
Events delivers each decoded event. The channel closes when the child's stdout reaches EOF or fails.
func (*Watch) Heartbeat ¶
func (w *Watch) Heartbeat(cmd protocol.WatchCommand) error
Heartbeat writes a `heartbeat` command; Type is set for the caller.
func (*Watch) Wait ¶
Wait waits for the event stream to end (the caller has consumed it, or ctx was cancelled and the reader drained it), reaps the child and returns its exit status. A child that exited by itself — 0 or a 4.6 status, including a 0 after the SIGTERM a cancel sends — reports that status with a nil error. (-1, err) means the child did NOT exit by itself: it was killed by a signal (the SIGKILL that follows a cancel by WaitDelay, or a death of its own) or the spawn failed at wait level. Wait is idempotent, so a test may both call it and register it in a cleanup; it must be called, since it is what reaps the child and closes the adapter log.