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
|
|
@ -12,11 +12,17 @@ import (
|
|||
// ActionFunc is called when the server signals an action on a task.
|
||||
type ActionFunc func(taskID string)
|
||||
|
||||
// StatusReporter is the interface used by ProgressReporter to send progress updates.
|
||||
// Both *agent.Client and agent.Transport implement this via their ReportStatus/SendProgress methods.
|
||||
type StatusReporter interface {
|
||||
ReportStatus(ctx context.Context, update agent.StatusUpdate) (*agent.StatusResponse, error)
|
||||
}
|
||||
|
||||
// ProgressReporter aggregates progress from downloads and reports to the API.
|
||||
// It batches updates to avoid flooding the server.
|
||||
type ProgressReporter struct {
|
||||
agentClient *agent.Client
|
||||
interval time.Duration
|
||||
reporter StatusReporter
|
||||
interval time.Duration
|
||||
|
||||
onCancel ActionFunc
|
||||
onPause ActionFunc
|
||||
|
|
@ -28,14 +34,33 @@ type ProgressReporter struct {
|
|||
}
|
||||
|
||||
// NewProgressReporter creates a reporter that flushes every interval.
|
||||
// Accepts *agent.Client directly (backwards compatible).
|
||||
func NewProgressReporter(ac *agent.Client, interval time.Duration) *ProgressReporter {
|
||||
return &ProgressReporter{
|
||||
agentClient: ac,
|
||||
interval: interval,
|
||||
latest: make(map[string]*Task),
|
||||
reporter: ac,
|
||||
interval: interval,
|
||||
latest: make(map[string]*Task),
|
||||
}
|
||||
}
|
||||
|
||||
// NewProgressReporterWithTransport creates a reporter using a Transport.
|
||||
func NewProgressReporterWithTransport(t agent.Transport, interval time.Duration) *ProgressReporter {
|
||||
return &ProgressReporter{
|
||||
reporter: &transportStatusAdapter{t: t},
|
||||
interval: interval,
|
||||
latest: make(map[string]*Task),
|
||||
}
|
||||
}
|
||||
|
||||
// transportStatusAdapter adapts agent.Transport to StatusReporter.
|
||||
type transportStatusAdapter struct {
|
||||
t agent.Transport
|
||||
}
|
||||
|
||||
func (a *transportStatusAdapter) ReportStatus(ctx context.Context, update agent.StatusUpdate) (*agent.StatusResponse, error) {
|
||||
return a.t.SendProgress(ctx, update)
|
||||
}
|
||||
|
||||
// SetCancelHandler sets the callback invoked when the server says a task is cancelled.
|
||||
func (r *ProgressReporter) SetCancelHandler(fn ActionFunc) { r.onCancel = fn }
|
||||
|
||||
|
|
@ -95,7 +120,7 @@ func (r *ProgressReporter) flush(ctx context.Context) {
|
|||
}
|
||||
|
||||
update := task.ToStatusUpdate()
|
||||
resp, err := r.agentClient.ReportStatus(ctx, update)
|
||||
resp, err := r.reporter.ReportStatus(ctx, update)
|
||||
if err != nil {
|
||||
log.Printf("[%s] progress report failed: %v", task.ID[:8], err)
|
||||
continue
|
||||
|
|
@ -130,7 +155,7 @@ func (r *ProgressReporter) flush(ctx context.Context) {
|
|||
// ReportFinal sends a final status update for a completed/failed task.
|
||||
func (r *ProgressReporter) ReportFinal(ctx context.Context, task *Task) {
|
||||
update := task.ToStatusUpdate()
|
||||
if _, err := r.agentClient.ReportStatus(ctx, update); err != nil {
|
||||
if _, err := r.reporter.ReportStatus(ctx, update); err != nil {
|
||||
log.Printf("[%s] final report failed: %v", task.ID[:8], err)
|
||||
}
|
||||
r.Untrack(task.ID)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue