feat(agent): add WebSocket transport with HTTP fallback
Add Transport interface abstraction supporting WebSocket (via CF Durable Objects) and HTTP (direct to origin) with automatic failover. - Transport interface: Register, SendHeartbeat, SendProgress, Events() - HTTPTransport: thin adapter over existing Client - WSTransport: gorilla/websocket with auth handshake, readLoop, reconnect - HybridTransport: tries WS first, falls back to HTTP, reconnects in bg - Daemon refactored to always use Transport (no dual-path forks) - ProgressReporter accepts StatusReporter interface - deriveWSURL skips localhost/dev (returns "" → HTTP-only) - API key passed in WS query param for connection auth - Fixed: reconnectOnce race (mutex+bool), authDone double-close (sync.Once) - Fixed: forwardWSEvents goroutine leak (select with stop signal) - 20 transport tests + 2 E2E tests (full lifecycle, hybrid failover)
This commit is contained in:
parent
5e80911501
commit
5f337eebd7
10 changed files with 1646 additions and 64 deletions
50
internal/agent/transport_http.go
Normal file
50
internal/agent/transport_http.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package agent
|
||||
|
||||
import "context"
|
||||
|
||||
// HTTPTransport wraps the existing Client to implement Transport.
|
||||
// This is a thin adapter — no behavioral changes from the current HTTP protocol.
|
||||
type HTTPTransport struct {
|
||||
client *Client
|
||||
events chan ServerEvent
|
||||
}
|
||||
|
||||
// NewHTTPTransport creates a new HTTP-based transport.
|
||||
func NewHTTPTransport(baseURL, apiKey, userAgent string) *HTTPTransport {
|
||||
return &HTTPTransport{
|
||||
client: NewClient(baseURL, apiKey, userAgent),
|
||||
events: make(chan ServerEvent, 10),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *HTTPTransport) Connect(_ context.Context) error { return nil }
|
||||
func (t *HTTPTransport) Close() error { return nil }
|
||||
func (t *HTTPTransport) Mode() string { return "http" }
|
||||
func (t *HTTPTransport) Events() <-chan ServerEvent { return t.events }
|
||||
|
||||
func (t *HTTPTransport) Register(ctx context.Context, req RegisterRequest) (*RegisterResponse, error) {
|
||||
return t.client.Register(ctx, req)
|
||||
}
|
||||
|
||||
func (t *HTTPTransport) SendHeartbeat(ctx context.Context, req HeartbeatRequest) (*HeartbeatResponse, error) {
|
||||
return t.client.Heartbeat(ctx, req)
|
||||
}
|
||||
|
||||
func (t *HTTPTransport) SendProgress(ctx context.Context, update StatusUpdate) (*StatusResponse, error) {
|
||||
return t.client.ReportStatus(ctx, update)
|
||||
}
|
||||
|
||||
func (t *HTTPTransport) ClaimTasks(ctx context.Context, agentID string) (*TasksResponse, error) {
|
||||
return t.client.ClaimTasks(ctx, agentID)
|
||||
}
|
||||
|
||||
func (t *HTTPTransport) Deregister(ctx context.Context, agentID string) error {
|
||||
return t.client.Deregister(ctx, agentID)
|
||||
}
|
||||
|
||||
func (t *HTTPTransport) ReportUpgradeResult(ctx context.Context, result UpgradeResult) error {
|
||||
return t.client.ReportUpgradeResult(ctx, result)
|
||||
}
|
||||
|
||||
// Client returns the underlying HTTP client for direct use if needed.
|
||||
func (t *HTTPTransport) Client() *Client { return t.client }
|
||||
Loading…
Add table
Add a link
Reference in a new issue