Documentation
¶
Overview ¶
Package agentbridge implements the live-session agent control surface for GWC apps: the gwc.agentbridge wire protocol spoken between a running js/wasm app (which dials out to the agent hub) and the hub that relays commands from MCP tools.
This file set is layered like hotreload: the protocol and any other pure-Go logic are tag-free so they build, vet, and unit-test on both native and js/wasm targets; browser-coupled transport lives in _wasm.go files with _native.go stubs.
Design rule for every command this protocol carries: mutations target the inputs the fiber tree is derived from (atoms, hook slots, events, route, mount roots) - never fibers directly, which the reconciler would overwrite.
Index ¶
- Constants
- func EnableAgentBridge()
- func FormatEnvelopeJSON(parseEnvelope Envelope) (string, error)
- func IsAgentModeActive() bool
- func ListAgentCommands() []string
- func ListMountComponents() []string
- func RecordAgentMutation(parseCommand string, parseSummary string, parseUndo func() *EnvelopeError) uint64
- func RegisterAgentCommand(parseName string, parseHandler AgentCommandHandler)
- func RegisterControlCommands()
- func RegisterMountComponent(parseName string, parseFactory MountComponentFactory)
- func RegisterReadCommands()
- func RegisterRenderCommands()
- func RegisterReplayCommands()
- func RegisterSafetyCommands()
- func RegisterWriteCommands()
- func SetAgentModeActive(parseActive bool)
- type AgentAuditEntry
- type AgentCommandHandler
- type AgentSocket
- type BridgeClient
- type Envelope
- func BuildAckEnvelope(parseSeq uint64, parseSession string, parseAckSeq uint64, ...) Envelopedeprecated
- func BuildCommandEnvelope(parseSeq uint64, parseSession string, parseName string, ...) Envelopedeprecated
- func BuildErrorAckEnvelope(parseSeq uint64, parseSession string, parseAckSeq uint64, parseCode string, ...) Envelopedeprecated
- func BuildEventEnvelope(parseSeq uint64, parseSession string, parseName string, ...) Envelopedeprecated
- func BuildHelloEnvelope(parseSeq uint64, parsePayload json.RawMessage) Envelopedeprecated
- func NewAckEnvelope(parseSeq uint64, parseSession string, parseAckSeq uint64, ...) Envelope
- func NewCommandEnvelope(parseSeq uint64, parseSession string, parseName string, ...) Envelope
- func NewErrorAckEnvelope(parseSeq uint64, parseSession string, parseAckSeq uint64, parseCode string, ...) Envelope
- func NewEventEnvelope(parseSeq uint64, parseSession string, parseName string, ...) Envelope
- func NewHelloEnvelope(parseSeq uint64, parsePayload json.RawMessage) Envelope
- func ParseEnvelope(parsePayload string) (Envelope, error)
- type EnvelopeError
- type MountComponentFactory
Constants ¶
const ( // KindHello is the first frame an app sends after connecting: app id, // build id, and capabilities ride in the payload. KindHello = "hello" // KindCommand is a hub-to-app instruction (snapshot, query, set-atom, ...). KindCommand = "command" // KindAck is the app's reply to exactly one command, matched by AckSeq. KindAck = "ack" // KindEvent is an unsolicited app-to-hub push (log, diagnostic, lifecycle). KindEvent = "event" )
Envelope kinds. Every frame on the wire is exactly one of these.
const ( // ErrorCodeStaleRef means a node ref no longer resolves; the agent should // re-query instead of retrying the same ref. ErrorCodeStaleRef = "stale-ref" // ErrorCodeUnknownCommand means the app build does not implement the // requested command name. ErrorCodeUnknownCommand = "unknown-command" // ErrorCodeBadPayload means the command payload failed validation; state // was not touched. ErrorCodeBadPayload = "bad-payload" // ErrorCodeForbidden means the app is not in agent mode or the caller // lacks the session write lease. ErrorCodeForbidden = "forbidden" // ErrorCodeTimeout means a wait command reached its deadline before the // requested runtime condition became true. ErrorCodeTimeout = "timeout" )
Command-layer error codes carried in EnvelopeError.Code. These are part of the wire contract: agents branch on them, so they are stable strings.
const ProtocolName = "gwc.agentbridge"
ProtocolName identifies the agent bridge wire protocol in every envelope.
const ProtocolVersion = 1
ProtocolVersion is the newest envelope schema version this build speaks. Parsers reject newer versions loudly instead of guessing.
Variables ¶
This section is empty.
Functions ¶
func EnableAgentBridge ¶
func EnableAgentBridge()
EnableAgentBridge is a no-op stub for non-browser or non-agent builds. Applications call EnableAgentBridge unconditionally at startup; this stub compiles it out on all builds that do not have the js, wasm, and gwcagent build tags simultaneously active so release builds carry zero overhead.
func FormatEnvelopeJSON ¶
FormatEnvelopeJSON validates an envelope and serializes it for the wire.
func IsAgentModeActive ¶
func IsAgentModeActive() bool
IsAgentModeActive reports whether the bridge client enabled agent mode.
func ListAgentCommands ¶
func ListAgentCommands() []string
ListAgentCommands returns the sorted names of every registered command.
func ListMountComponents ¶
func ListMountComponents() []string
ListMountComponents returns the sorted names of every component registered for bridge.mount via RegisterMountComponent. It is the discovery surface an agent uses to learn what bridge.mount accepts, instead of guessing component names.
func RecordAgentMutation ¶
func RecordAgentMutation(parseCommand string, parseSummary string, parseUndo func() *EnvelopeError) uint64
RecordAgentMutation appends an audit entry for an applied write command and, when parseUndo is non-nil, pushes a reversible op onto the undo stack. It returns the assigned audit sequence number. parseUndo must restore the exact prior state and is safe to call once.
func RegisterAgentCommand ¶
func RegisterAgentCommand(parseName string, parseHandler AgentCommandHandler)
RegisterAgentCommand registers a bridge command handler by wire name. Later registrations replace earlier ones (last writer wins) so tests can stub commands; production registration happens once at bridge install.
A blank name or nil handler is silently ignored (no registration, no panic), so guard against accidentally passing either if you rely on the command being present.
func RegisterControlCommands ¶
func RegisterControlCommands()
RegisterControlCommands installs bridge.wait-for and bridge.describe.
func RegisterMountComponent ¶
func RegisterMountComponent(parseName string, parseFactory MountComponentFactory)
RegisterMountComponent registers a component factory for bridge.mount.
func RegisterReadCommands ¶
func RegisterReadCommands()
RegisterReadCommands installs the bridge.snapshot and bridge.query command handlers into the process-wide bridge command registry via RegisterAgentCommand. It should be called once, typically from application bootstrap or from an init() guarded by an appropriate build tag. Calling it multiple times is safe (last registration wins per RegisterAgentCommand semantics).
Both commands are read-only and work regardless of whether agent mode is active (reads are always allowed in agent builds).
func RegisterRenderCommands ¶
func RegisterRenderCommands()
RegisterRenderCommands installs bridge.render-tree, which interprets a JSON element tree into a live DOM subtree using the runtime element builders and mounts it into a selector. This is structure-only: it injects arbitrary markup (tags, attributes, text, nesting) at runtime without compiling any Go code. It carries no behavior — event handlers and hooks are Go closures that cannot be shipped over the wire, so on* attributes are dropped.
func RegisterReplayCommands ¶
func RegisterReplayCommands()
RegisterReplayCommands installs bridge.replay, the agent-facing wrapper over the runtime's deterministic update recorder. An agent records a window of scheduling updates, then replays them to reproduce a bug deterministically — the local stand-in for the prod→plan loop.
func RegisterSafetyCommands ¶
func RegisterSafetyCommands()
RegisterSafetyCommands installs bridge.audit and bridge.undo. bridge.audit is read-only; bridge.undo reverses the most recent reversible mutation and is itself gated by agent mode.
func RegisterWriteCommands ¶
func RegisterWriteCommands()
RegisterWriteCommands installs the four input-level mutation commands on the agent bridge:
- bridge.set-atom — set one atom by id from a JSON value
- bridge.emit — invoke a fiber's event handler by node ref + event name
- bridge.publish — publish to the in-app events bus
- bridge.navigate — navigate the router to a new path
Every handler first checks IsAgentModeActive() and refuses with ErrorCodeForbidden when the app has not been launched in agent mode.
RegisterWriteCommands must be called once at bridge install time (typically inside EnableAgentBridge), before any commands are dispatched.
func SetAgentModeActive ¶
func SetAgentModeActive(parseActive bool)
SetAgentModeActive records whether agent mode is enabled. The bridge client sets it after its gating checks pass; mutating command handlers refuse with the forbidden wire code while it is false.
Types ¶
type AgentAuditEntry ¶
type AgentAuditEntry struct {
Seq uint64 `json:"seq"`
Command string `json:"command"`
Summary string `json:"summary"`
Reversible bool `json:"reversible"`
}
AgentAuditEntry is one recorded write the bridge applied, for the bridge.audit trail an agent (or a human reviewing an agent) reads to see what changed.
func AgentAuditEntries ¶
func AgentAuditEntries(parseMax int) []AgentAuditEntry
AgentAuditEntries returns up to parseMax most-recent audit entries, oldest first. A non-positive parseMax returns the whole retained ring.
type AgentCommandHandler ¶
type AgentCommandHandler func(parsePayload json.RawMessage) (json.RawMessage, *EnvelopeError)
AgentCommandHandler executes one bridge command. It returns the ack payload on success, or a structured error (with a stable wire code) on failure. Handlers own their scheduler-locking discipline: anything touching the fiber tree or hook state must take the runtime's scheduler lock the same way Inspect does.
type AgentSocket ¶
type AgentSocket interface {
// ReadFrame blocks until a text frame arrives or the connection closes.
// It returns ("", io.EOF) or any non-nil error when the socket is done.
ReadFrame() (string, error)
// WriteFrame sends one text frame synchronously. An error means the
// connection is broken; the client will stop the loop.
WriteFrame(parseFrame string) error
// Close terminates the connection. Safe to call more than once.
Close() error
}
AgentSocket abstracts one bidirectional frame-oriented transport. The bridge client calls ReadFrame and WriteFrame from a single goroutine; Close may be called from any goroutine and must be idempotent.
type BridgeClient ¶
type BridgeClient struct {
// contains filtered or unexported fields
}
BridgeClient is the transport-agnostic client loop. Create one with NewBridgeClient; the zero value is not valid.
func NewBridgeClient ¶
func NewBridgeClient(parseAppID string, parseBuildID string) *BridgeClient
NewBridgeClient constructs a BridgeClient for the given app and build identifiers. Neither string is validated; empty strings are legal for tests.
func (*BridgeClient) RunLoop ¶
func (parseC *BridgeClient) RunLoop(parseSock AgentSocket)
RunLoop runs the client loop on the given socket. It sends the hello frame, then reads, parses, and dispatches frames until the socket closes or WriteFrame returns an error. RunLoop blocks; run it in a goroutine. The socket is closed before RunLoop returns.
func (*BridgeClient) SendEvent ¶
func (parseC *BridgeClient) SendEvent(parseSession string, parseName string, parsePayload json.RawMessage) error
SendEvent pushes an unsolicited event frame on the current socket, if one is active. It returns an error if no socket is connected or the write fails. SendEvent is safe to call from any goroutine.
type Envelope ¶
type Envelope struct {
// Protocol is the wire-protocol identifier (always ProtocolName) — receivers reject mismatches.
Protocol string `json:"protocol"`
// Version is the protocol version (always ProtocolVersion) for compatibility negotiation.
Version int `json:"version"`
// Kind is the frame type: KindHello, KindCommand, KindAck, or KindEvent.
Kind string `json:"kind"`
// Seq is this sender's monotonic frame number, starting at 1.
Seq uint64 `json:"seq"`
// Session names the app session a frame belongs to. It is empty on the
// app's hello (the hub assigns the session id in its reply) and may be
// empty on app<->hub frames where the connection implies the session.
Session string `json:"session,omitempty"`
// Name is the command name for KindCommand and the topic for KindEvent.
Name string `json:"name,omitempty"`
// Payload is the kind-specific body, left raw so the protocol layer stays
// decoupled from individual command schemas.
Payload json.RawMessage `json:"payload,omitempty"`
// AckSeq is the Seq of the command a KindAck frame answers.
AckSeq uint64 `json:"ackSeq,omitempty"`
// OK reports command success on a KindAck frame. It is a pointer so acks
// are explicit on the wire and other kinds omit it entirely.
OK *bool `json:"ok,omitempty"`
// Error carries the structured failure when OK is false.
Error *EnvelopeError `json:"error,omitempty"`
// StateVersion is the app's post-commit state version on a successful
// ack, giving callers read-your-writes ordering without polling.
StateVersion uint64 `json:"stateVersion,omitempty"`
}
Envelope is one frame of the gwc.agentbridge protocol. Each side numbers its own outbound frames with a monotonic Seq starting at 1; an ack points back at the command it answers via AckSeq.
func BuildAckEnvelope
deprecated
func BuildCommandEnvelope
deprecated
func BuildEventEnvelope
deprecated
func BuildHelloEnvelope
deprecated
func BuildHelloEnvelope(parseSeq uint64, parsePayload json.RawMessage) Envelope
BuildHelloEnvelope builds the first frame an app sends after connecting.
Deprecated: use NewHelloEnvelope (New* matches the module-wide constructor convention).
func NewAckEnvelope ¶
func NewAckEnvelope(parseSeq uint64, parseSession string, parseAckSeq uint64, parseStateVersion uint64, parsePayload json.RawMessage) Envelope
NewAckEnvelope builds a successful reply to the command numbered parseAckSeq.
func NewCommandEnvelope ¶
func NewCommandEnvelope(parseSeq uint64, parseSession string, parseName string, parsePayload json.RawMessage) Envelope
NewCommandEnvelope builds a hub-to-app command frame.
func NewErrorAckEnvelope ¶
func NewErrorAckEnvelope(parseSeq uint64, parseSession string, parseAckSeq uint64, parseCode string, parseMessage string) Envelope
NewErrorAckEnvelope builds a failed reply to the command numbered parseAckSeq, carrying a stable error code the agent can branch on.
func NewEventEnvelope ¶
func NewEventEnvelope(parseSeq uint64, parseSession string, parseName string, parsePayload json.RawMessage) Envelope
NewEventEnvelope builds an unsolicited app-to-hub push frame.
func NewHelloEnvelope ¶
func NewHelloEnvelope(parseSeq uint64, parsePayload json.RawMessage) Envelope
NewHelloEnvelope builds the first frame an app sends after connecting.
func ParseEnvelope ¶
ParseEnvelope decodes one wire frame and rejects anything this build does not speak: wrong protocol, future version, unknown kind, or a frame whose kind-specific required fields are missing.
type EnvelopeError ¶
EnvelopeError is the structured failure attached to a non-ok ack.
func ExecuteAgentCommand ¶
func ExecuteAgentCommand(parseName string, parsePayload json.RawMessage) (parseResult json.RawMessage, parseErr *EnvelopeError)
ExecuteAgentCommand runs a registered command by name. Unknown names fail with the stable unknown-command wire code so agents can branch on it. A handler that panics is contained into a bad-payload error so a single bad command can never kill the client's dispatch goroutine (which would strand the app in agent mode with no listener).