realtime

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Aug 27, 2026 License: AGPL-3.0 Imports: 18 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func JobsTopic added in v0.5.0

func JobsTopic(agentID uuid.UUID) uuid.UUID

JobsTopic derives the private operator topic for an agent's job events.

Types

type BuildEventPublisher

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

BuildEventPublisher adapts PubSub to the builder.EventPublisher interface.

Build events split across two topics: lightweight lifecycle status (agent.build, carrying tasks_done/total for the badge) broadcasts on the AGENT topic so every member's UI updates without opening the Build page; the verbose stream (log lines, live actions, todos) publishes on the per-BUILD topic, which the Build page subscribes to only while open.

func NewBuildEventPublisher

func NewBuildEventPublisher(ps *PubSub, hub *Hub) *BuildEventPublisher

NewBuildEventPublisher creates a BuildEventPublisher.

func (*BuildEventPublisher) PublishBuildEvent

func (p *BuildEventPublisher) PublishBuildEvent(ctx context.Context, agentID, buildID uuid.UUID, status, errMsg, phase string, tasksDone, tasksTotal int32)

PublishBuildEvent publishes an agent build lifecycle event on the agent topic. tasksDone/tasksTotal drive the "Building N/M tasks" badge.

func (*BuildEventPublisher) PublishBuildLogLine

func (p *BuildEventPublisher) PublishBuildLogLine(ctx context.Context, agentID, buildID uuid.UUID, seq int64, stream, line string)

PublishBuildLogLine publishes a single build log line on the per-build topic. seq is a monotonic counter per build (across both sol and docker streams); the frontend uses it to dedupe against a REST snapshot of the persisted log.

func (*BuildEventPublisher) PublishBuildTodos added in v0.4.0

func (p *BuildEventPublisher) PublishBuildTodos(ctx context.Context, buildID uuid.UUID, seq int64, todosJSON []byte)

PublishBuildTodos publishes the agent's full todo list on the per-build topic. todosJSON is the same jsonb blob persisted to agent_builds.todos.

type Conn

type Conn struct {
	ID     string
	UserID uuid.UUID
	Email  string
	// TenantRole is fixed for the lifetime of the connection. The live
	// authorization monitor closes the connection if the user's role changes.
	TenantRole auth.Role
	// SinceSeq is the client's replay cursor from the ?since= connect
	// param: the max Envelope.Seq it has already processed. Set once by
	// the WS accept handler before the Subscribe loop; the hub replays
	// only seq>SinceSeq per topic (0 = fresh connect, no replay).
	SinceSeq uint64
	// contains filtered or unexported fields
}

Conn wraps a WebSocket connection with user identity and a send buffer.

func NewConn

func NewConn(ws *websocket.Conn, userID uuid.UUID, email string, tenantRole auth.Role, disconnect context.CancelFunc, logger *zap.Logger) *Conn

NewConn creates a new Conn for the given WebSocket and user.

func (*Conn) JobsSubscriptions added in v0.5.0

func (c *Conn) JobsSubscriptions() []uuid.UUID

JobsSubscriptions returns a snapshot of dynamically subscribed agent IDs.

func (*Conn) ReadPump

func (c *Conn) ReadPump(ctx context.Context, handler MessageHandler)

ReadPump reads messages from the WebSocket and dispatches to handler. Blocks until the connection closes or ctx is cancelled.

func (*Conn) Send

func (c *Conn) Send(data []byte)

Send enqueues a message for writing. Non-blocking: drops if buffer full.

func (*Conn) SendEnvelope

func (c *Conn) SendEnvelope(env Envelope)

SendEnvelope marshals and enqueues an envelope. Correctness control messages disconnect a slow client instead of being dropped so it reloads state.

func (*Conn) TrackJobsSubscription added in v0.5.0

func (c *Conn) TrackJobsSubscription(agentID uuid.UUID)

TrackJobsSubscription records an authorized dynamic jobs subscription.

func (*Conn) UntrackJobsSubscription added in v0.5.0

func (c *Conn) UntrackJobsSubscription(agentID uuid.UUID)

UntrackJobsSubscription removes a dynamic jobs subscription.

func (*Conn) WritePump

func (c *Conn) WritePump(ctx context.Context)

WritePump drains the send channel and writes to the WebSocket. Also sends periodic pings for liveness detection. Blocks until ctx is cancelled or the WebSocket closes.

type Envelope

type Envelope struct {
	Type      string `json:"type"`
	RequestID string `json:"requestId,omitempty"`
	TopicID   string `json:"topicId,omitempty"`
	UserID    string `json:"userId,omitempty"`
	// Seq is a hub-global monotonic sequence stamped at publish. The
	// client keeps the max seq it has processed and presents it as
	// ?since= on (re)connect; the hub replays only seq>since per topic,
	// or sends a `resync` when the gap is wider than the buffer. Opaque
	// to the client ("bigger = newer"); when realtime moves to a shared
	// bus the source changes without touching the wire contract.
	Seq            uint64          `json:"seq,omitempty"`
	ConversationID string          `json:"conversationId,omitempty"`
	Subagent       *SubagentInfo   `json:"subagent,omitempty"`
	Payload        json.RawMessage `json:"payload,omitempty"`
}

Envelope is the JSON wire format for all WebSocket messages.

UserID is the principal who "owns" the event — typically the human at the top of a run chain. Subscribers gate delivery on env.UserID == conn.UserID, so a tenant admin who happens to be a member of agent X does not see live events from runs another user started on X. Pre-existing system-level broadcasts leave it empty and fall through to the "deliver to every subscriber on the topic" behaviour. ConversationID lets the frontend route an event to the correct chat card without payload introspection. Subagent tags an envelope as a sub-run event so the chat store can render it underneath the parent run's tool-call instead of as a top-level message.

func NewEnvelope

func NewEnvelope(eventType, topicID string, payload proto.Message) Envelope

NewEnvelope creates an Envelope, marshaling the payload via protojson. The proto.Message constraint provides compile-time enforcement — passing map[string]any or dbq types is a compile error.

This constructor leaves UserID empty, which means "deliver to every subscriber on the topic" — appropriate for system-level broadcasts (agent.synced, build events). Per-user events should use NewEnvelopeForUser so the WS hub can apply the user-id gate.

func NewEnvelopeForUser added in v0.4.0

func NewEnvelopeForUser(eventType, topicID, userID, conversationID string, payload proto.Message) Envelope

NewEnvelopeForUser is NewEnvelope plus per-user gating + conversation routing. Callers that publish run events (text-delta, tool-call, etc.) use this so subscribers other than the run's owner don't receive the live stream.

func (Envelope) WithSubagent added in v0.4.0

func (e Envelope) WithSubagent(info SubagentInfo) Envelope

WithSubagent tags the envelope as a sub-run event and returns it. Chainable on the constructors above.

type Handler

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

Handler routes dynamic WebSocket subscriptions that require narrower access than the agent topics installed by the WS upgrade handler.

func NewHandler

func NewHandler(database *db.DB, hub *Hub, pubsub *PubSub, logger *zap.Logger) *Handler

NewHandler creates a new inbound message handler.

func (*Handler) HandleMessage

func (h *Handler) HandleMessage(conn *Conn, env Envelope)

HandleMessage routes an inbound client message.

type Hub

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

Hub manages WebSocket connections and topic subscriptions. A single connection can be subscribed to many topics at once — typically every agent the user is a member of, wired up at WS-accept time.

func NewHub

func NewHub(logger *zap.Logger) *Hub

NewHub creates a new Hub.

func (*Hub) BroadcastToTopic

func (h *Hub) BroadcastToTopic(topicID uuid.UUID, env Envelope)

BroadcastToTopic sends an envelope to all connections subscribed to a topic.

User-id gating: if env.UserID is set, only connections whose authenticated user matches receive the event. Empty env.UserID falls through to the historical "deliver to every subscriber" behaviour (used for tenant-wide / system broadcasts: agent.synced, build events, etc).

The replay buffer stores (bytes, userID) pairs so a late subscriber gets the same gate applied on replay — without it, joining late would leak events from other users' runs that happened before the join.

func (*Hub) ClearTopicBuffer

func (h *Hub) ClearTopicBuffer(topicID uuid.UUID)

ClearTopicBuffer removes the replay buffer for a topic. Called after a terminal event (build complete/failed) since replay is no longer needed.

func (*Hub) ConnCount

func (h *Hub) ConnCount() int

ConnCount returns the number of active connections.

func (*Hub) OnTopicSubscriptionChange

func (h *Hub) OnTopicSubscriptionChange(onFirst func(uuid.UUID), onLast func(uuid.UUID))

OnTopicSubscriptionChange sets callbacks for when the first local connection subscribes to a topic and when the last unsubscribes. Used by PubSub to manage Redis subscriptions.

func (*Hub) Register

func (h *Hub) Register(conn *Conn)

Register adds a connection to the hub.

func (*Hub) ResyncAll added in v0.5.0

func (h *Hub) ResyncAll()

ResyncAll asks every connected client to reload authoritative REST state.

func (*Hub) SendToConnection

func (h *Hub) SendToConnection(connID string, env Envelope)

SendToConnection sends an envelope to a specific connection by ID.

func (*Hub) Subscribe

func (h *Hub) Subscribe(conn *Conn, topicID uuid.UUID)

Subscribe adds the connection to the given topic if not already subscribed. Pre-existing topic subscriptions on this connection are not affected — Subscribe is additive, so the WS accept handler can call it in a loop over the user's member agents without disturbing anything else. Replays any buffered events for the topic to the newly-subscribed connection.

func (*Hub) TopicConnCount

func (h *Hub) TopicConnCount(topicID uuid.UUID) int

TopicConnCount returns the number of connections subscribed to a topic.

func (*Hub) Unregister

func (h *Hub) Unregister(conn *Conn)

Unregister removes a connection and all its topic subscriptions, firing onLastUnsubscribe for any topic that's now empty.

func (*Hub) Unsubscribe added in v0.4.0

func (h *Hub) Unsubscribe(conn *Conn, topicID uuid.UUID)

Unsubscribe removes one topic subscription from a connection (the inverse of a single Subscribe). Used when the client leaves a dynamically-subscribed topic — e.g. the Build page unmounting its per-build subscription — while the connection's other subscriptions stay intact. Idempotent.

type JobEventRelay added in v0.5.0

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

JobEventRelay fans committed PostgreSQL job notifications out to this replica's local WebSocket subscribers.

func NewJobEventRelay added in v0.5.0

func NewJobEventRelay(pool *pgxpool.Pool, hub *Hub, logger *zap.Logger) *JobEventRelay

func (*JobEventRelay) Run added in v0.5.0

func (r *JobEventRelay) Run(ctx context.Context) error

Run maintains a dedicated LISTEN connection until ctx is cancelled.

type MessageHandler

type MessageHandler func(conn *Conn, env Envelope)

MessageHandler processes an inbound WebSocket message from a client.

type PubSub

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

PubSub delivers real-time envelopes to local WebSocket subscribers via the Hub. This is the single-instance implementation. For multi-instance fan-out (e.g. via Redis pub/sub), replace or extend this with a cross-process transport.

func NewPubSub

func NewPubSub(hub *Hub, logger *zap.Logger) *PubSub

NewPubSub creates a PubSub wired to the given Hub.

func (*PubSub) ClearTopicBuffer

func (ps *PubSub) ClearTopicBuffer(topicID uuid.UUID)

ClearTopicBuffer removes the replay buffer for a topic. Call after terminal events (run complete, build complete) so reconnecting clients don't replay stale streaming events.

func (*PubSub) Close

func (ps *PubSub) Close()

Close is a no-op for the local implementation.

func (*PubSub) Publish

func (ps *PubSub) Publish(ctx context.Context, topicID uuid.UUID, env Envelope) error

Publish delivers an envelope to all local subscribers of the topic.

type SubagentInfo added in v0.4.0

type SubagentInfo struct {
	AgentID string `json:"agentId"`
	RunID   string `json:"runId"`
	Slug    string `json:"slug,omitempty"`
}

SubagentInfo identifies a sub-run when an A2A child agent's events are mirrored to the parent's topic. Frontend chat-store reads it to attach the event to the parent run's active tool-call card.

Jump to

Keyboard shortcuts

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