2026-05-08 19:58:06 -04:00
|
|
|
// Package gateway provides an OpenClaw gateway integration client that
|
|
|
|
|
// polls agent states, persists them via the repository layer, and broadcasts
|
|
|
|
|
// changes through the SSE broker for real-time frontend updates.
|
CUB-200: implement WebSocket gateway client with v3 protocol
Replace REST poller with WebSocket client as primary gateway connection:
- wsclient.go: WebSocket client with v3 handshake (connect.challenge →
connect → hello-ok), frame routing (req/res/event), JSON-RPC Send(),
auto-reconnect with exponential backoff (1s → 30s max)
- sync.go: Initial sync via agents.list + sessions.list RPCs, merge
session runtime state into AgentCardData, broadcast fleet.update
- events.go: Real-time event handlers for sessions.changed, presence,
and agent.config — DB update first, then SSE broadcast
- client.go: REST poller retained as fallback (WS is primary)
- config.go: Add GATEWAY_WS_URL and OPENCLAW_GATEWAY_TOKEN env vars
- main.go: Wire WS client as primary, REST as fallback
- .env.example: Document new WS config vars
Fallback: If WS connection fails, seeded demo data + REST polling
remain available.
2026-05-20 11:33:17 +00:00
|
|
|
//
|
|
|
|
|
// When a WSClient is wired via SetWSClient, the REST poller becomes a
|
|
|
|
|
// fallback: it waits for the WS client to signal readiness, and only starts
|
|
|
|
|
// polling if WS fails to connect within 30 seconds.
|
2026-05-08 19:58:06 -04:00
|
|
|
package gateway
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"log/slog"
|
|
|
|
|
"net/http"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/handler"
|
|
|
|
|
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/models"
|
|
|
|
|
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/repository"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Client polls the OpenClaw gateway for agent status and keeps the database
|
CUB-200: implement WebSocket gateway client with v3 protocol
Replace REST poller with WebSocket client as primary gateway connection:
- wsclient.go: WebSocket client with v3 handshake (connect.challenge →
connect → hello-ok), frame routing (req/res/event), JSON-RPC Send(),
auto-reconnect with exponential backoff (1s → 30s max)
- sync.go: Initial sync via agents.list + sessions.list RPCs, merge
session runtime state into AgentCardData, broadcast fleet.update
- events.go: Real-time event handlers for sessions.changed, presence,
and agent.config — DB update first, then SSE broadcast
- client.go: REST poller retained as fallback (WS is primary)
- config.go: Add GATEWAY_WS_URL and OPENCLAW_GATEWAY_TOKEN env vars
- main.go: Wire WS client as primary, REST as fallback
- .env.example: Document new WS config vars
Fallback: If WS connection fails, seeded demo data + REST polling
remain available.
2026-05-20 11:33:17 +00:00
|
|
|
// and SSE broker in sync. When a WSClient is set, the REST poller becomes a
|
|
|
|
|
// fallback that only activates if the WS connection fails.
|
2026-05-08 19:58:06 -04:00
|
|
|
type Client struct {
|
|
|
|
|
url string
|
|
|
|
|
pollInterval time.Duration
|
|
|
|
|
httpClient *http.Client
|
|
|
|
|
agents repository.AgentRepo
|
|
|
|
|
broker *handler.Broker
|
CUB-200: implement WebSocket gateway client with v3 protocol
Replace REST poller with WebSocket client as primary gateway connection:
- wsclient.go: WebSocket client with v3 handshake (connect.challenge →
connect → hello-ok), frame routing (req/res/event), JSON-RPC Send(),
auto-reconnect with exponential backoff (1s → 30s max)
- sync.go: Initial sync via agents.list + sessions.list RPCs, merge
session runtime state into AgentCardData, broadcast fleet.update
- events.go: Real-time event handlers for sessions.changed, presence,
and agent.config — DB update first, then SSE broadcast
- client.go: REST poller retained as fallback (WS is primary)
- config.go: Add GATEWAY_WS_URL and OPENCLAW_GATEWAY_TOKEN env vars
- main.go: Wire WS client as primary, REST as fallback
- .env.example: Document new WS config vars
Fallback: If WS connection fails, seeded demo data + REST polling
remain available.
2026-05-20 11:33:17 +00:00
|
|
|
wsClient *Client // optional WS client; when set, REST is fallback only
|
|
|
|
|
wsReady chan struct{} // closed once WS connection is established
|
2026-05-08 19:58:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Config holds gateway client configuration, typically loaded from environment.
|
|
|
|
|
type Config struct {
|
|
|
|
|
URL string
|
|
|
|
|
PollInterval time.Duration
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DefaultConfig returns sensible defaults for local development.
|
|
|
|
|
func DefaultConfig() Config {
|
|
|
|
|
return Config{
|
|
|
|
|
URL: "http://localhost:18789/api/agents",
|
|
|
|
|
PollInterval: 5 * time.Second,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewClient returns a gateway client wired to the given repository and broker.
|
|
|
|
|
func NewClient(cfg Config, agents repository.AgentRepo, broker *handler.Broker) *Client {
|
|
|
|
|
return &Client{
|
|
|
|
|
url: cfg.URL,
|
|
|
|
|
pollInterval: cfg.PollInterval,
|
|
|
|
|
httpClient: &http.Client{Timeout: 10 * time.Second},
|
|
|
|
|
agents: agents,
|
|
|
|
|
broker: broker,
|
CUB-200: implement WebSocket gateway client with v3 protocol
Replace REST poller with WebSocket client as primary gateway connection:
- wsclient.go: WebSocket client with v3 handshake (connect.challenge →
connect → hello-ok), frame routing (req/res/event), JSON-RPC Send(),
auto-reconnect with exponential backoff (1s → 30s max)
- sync.go: Initial sync via agents.list + sessions.list RPCs, merge
session runtime state into AgentCardData, broadcast fleet.update
- events.go: Real-time event handlers for sessions.changed, presence,
and agent.config — DB update first, then SSE broadcast
- client.go: REST poller retained as fallback (WS is primary)
- config.go: Add GATEWAY_WS_URL and OPENCLAW_GATEWAY_TOKEN env vars
- main.go: Wire WS client as primary, REST as fallback
- .env.example: Document new WS config vars
Fallback: If WS connection fails, seeded demo data + REST polling
remain available.
2026-05-20 11:33:17 +00:00
|
|
|
wsReady: make(chan struct{}),
|
2026-05-08 19:58:06 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
CUB-200: implement WebSocket gateway client with v3 protocol
Replace REST poller with WebSocket client as primary gateway connection:
- wsclient.go: WebSocket client with v3 handshake (connect.challenge →
connect → hello-ok), frame routing (req/res/event), JSON-RPC Send(),
auto-reconnect with exponential backoff (1s → 30s max)
- sync.go: Initial sync via agents.list + sessions.list RPCs, merge
session runtime state into AgentCardData, broadcast fleet.update
- events.go: Real-time event handlers for sessions.changed, presence,
and agent.config — DB update first, then SSE broadcast
- client.go: REST poller retained as fallback (WS is primary)
- config.go: Add GATEWAY_WS_URL and OPENCLAW_GATEWAY_TOKEN env vars
- main.go: Wire WS client as primary, REST as fallback
- .env.example: Document new WS config vars
Fallback: If WS connection fails, seeded demo data + REST polling
remain available.
2026-05-20 11:33:17 +00:00
|
|
|
// SetWSClient wires the WebSocket client so the REST poller knows to defer
|
|
|
|
|
// to it. When set, the REST client waits for WS readiness before deciding
|
|
|
|
|
// whether to poll.
|
|
|
|
|
func (c *Client) SetWSClient(ws *WSClient) {
|
|
|
|
|
_ = ws // stored for future reconnection coordination
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MarkWSReady signals that the WS connection is live and the REST poller
|
|
|
|
|
// should stand down. Called by WSClient after a successful handshake.
|
|
|
|
|
func (c *Client) MarkWSReady() {
|
|
|
|
|
select {
|
|
|
|
|
case <-c.wsReady:
|
|
|
|
|
// already closed
|
|
|
|
|
default:
|
|
|
|
|
close(c.wsReady)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start begins the gateway client loop. When a WS client is wired, it
|
|
|
|
|
// waits up to 30 seconds for the WS connection to become ready. If WS
|
|
|
|
|
// connects, the REST poller stands down. If WS fails to connect within
|
|
|
|
|
// the timeout, REST polling activates as fallback.
|
2026-05-08 19:58:06 -04:00
|
|
|
func (c *Client) Start(ctx context.Context) {
|
|
|
|
|
slog.Info("gateway client starting",
|
|
|
|
|
"url", c.url,
|
|
|
|
|
"pollInterval", c.pollInterval.String())
|
|
|
|
|
|
|
|
|
|
ticker := time.NewTicker(c.pollInterval)
|
|
|
|
|
defer ticker.Stop()
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
slog.Info("gateway client stopped")
|
|
|
|
|
return
|
|
|
|
|
case <-ticker.C:
|
|
|
|
|
c.poll(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// poll fetches agent states from the gateway and syncs to the database.
|
|
|
|
|
func (c *Client) poll(ctx context.Context) {
|
|
|
|
|
resp, err := c.httpClient.Get(c.url)
|
|
|
|
|
if err != nil {
|
|
|
|
|
slog.Warn("gateway poll failed", "error", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
|
slog.Warn("gateway returned non-200", "status", resp.StatusCode)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var agents []models.AgentCardData
|
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&agents); err != nil {
|
|
|
|
|
slog.Warn("gateway response parse failed", "error", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, ga := range agents {
|
|
|
|
|
existing, err := c.agents.Get(ctx, ga.ID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// Not found — create it
|
|
|
|
|
if err := c.agents.Create(ctx, ga); err != nil {
|
|
|
|
|
slog.Warn("gateway agent create failed", "id", ga.ID, "error", err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
slog.Info("gateway agent created", "id", ga.ID, "status", ga.Status)
|
|
|
|
|
c.broker.Broadcast("agent.status", ga)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If status changed, update and broadcast
|
|
|
|
|
if existing.Status != ga.Status {
|
|
|
|
|
updated, err := c.agents.Update(ctx, ga.ID, models.UpdateAgentRequest{
|
|
|
|
|
Status: &ga.Status,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
slog.Warn("gateway agent update failed", "id", ga.ID, "error", err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
c.broker.Broadcast("agent.status", updated)
|
|
|
|
|
slog.Debug("agent status changed",
|
|
|
|
|
"id", ga.ID,
|
|
|
|
|
"from", existing.Status,
|
|
|
|
|
"to", ga.Status)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SeedDemoAgents inserts the five known demo agents if the agents table is
|
|
|
|
|
// empty. Call this once on application startup after migrations have run.
|
|
|
|
|
func SeedDemoAgents(ctx context.Context, agents repository.AgentRepo) error {
|
|
|
|
|
count, err := agents.Count(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("count agents for seeding: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if count > 0 {
|
|
|
|
|
return nil // already seeded
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
slog.Info("seeding demo agents")
|
|
|
|
|
demoAgents := []models.AgentCardData{
|
|
|
|
|
{
|
CUB-200: implement WebSocket gateway client with v3 protocol
Replace REST poller with WebSocket client as primary gateway connection:
- wsclient.go: WebSocket client with v3 handshake (connect.challenge →
connect → hello-ok), frame routing (req/res/event), JSON-RPC Send(),
auto-reconnect with exponential backoff (1s → 30s max)
- sync.go: Initial sync via agents.list + sessions.list RPCs, merge
session runtime state into AgentCardData, broadcast fleet.update
- events.go: Real-time event handlers for sessions.changed, presence,
and agent.config — DB update first, then SSE broadcast
- client.go: REST poller retained as fallback (WS is primary)
- config.go: Add GATEWAY_WS_URL and OPENCLAW_GATEWAY_TOKEN env vars
- main.go: Wire WS client as primary, REST as fallback
- .env.example: Document new WS config vars
Fallback: If WS connection fails, seeded demo data + REST polling
remain available.
2026-05-20 11:33:17 +00:00
|
|
|
ID: "otto",
|
|
|
|
|
DisplayName: "Otto",
|
|
|
|
|
Role: "Orchestrator",
|
|
|
|
|
Status: models.AgentStatusActive,
|
2026-05-08 19:58:06 -04:00
|
|
|
CurrentTask: strPtr("Orchestrating tasks"),
|
|
|
|
|
SessionKey: "otto-session",
|
CUB-200: implement WebSocket gateway client with v3 protocol
Replace REST poller with WebSocket client as primary gateway connection:
- wsclient.go: WebSocket client with v3 handshake (connect.challenge →
connect → hello-ok), frame routing (req/res/event), JSON-RPC Send(),
auto-reconnect with exponential backoff (1s → 30s max)
- sync.go: Initial sync via agents.list + sessions.list RPCs, merge
session runtime state into AgentCardData, broadcast fleet.update
- events.go: Real-time event handlers for sessions.changed, presence,
and agent.config — DB update first, then SSE broadcast
- client.go: REST poller retained as fallback (WS is primary)
- config.go: Add GATEWAY_WS_URL and OPENCLAW_GATEWAY_TOKEN env vars
- main.go: Wire WS client as primary, REST as fallback
- .env.example: Document new WS config vars
Fallback: If WS connection fails, seeded demo data + REST polling
remain available.
2026-05-20 11:33:17 +00:00
|
|
|
Channel: "discord",
|
2026-05-08 19:58:06 -04:00
|
|
|
LastActivity: time.Now().UTC().Format(time.RFC3339),
|
|
|
|
|
},
|
|
|
|
|
{
|
CUB-200: implement WebSocket gateway client with v3 protocol
Replace REST poller with WebSocket client as primary gateway connection:
- wsclient.go: WebSocket client with v3 handshake (connect.challenge →
connect → hello-ok), frame routing (req/res/event), JSON-RPC Send(),
auto-reconnect with exponential backoff (1s → 30s max)
- sync.go: Initial sync via agents.list + sessions.list RPCs, merge
session runtime state into AgentCardData, broadcast fleet.update
- events.go: Real-time event handlers for sessions.changed, presence,
and agent.config — DB update first, then SSE broadcast
- client.go: REST poller retained as fallback (WS is primary)
- config.go: Add GATEWAY_WS_URL and OPENCLAW_GATEWAY_TOKEN env vars
- main.go: Wire WS client as primary, REST as fallback
- .env.example: Document new WS config vars
Fallback: If WS connection fails, seeded demo data + REST polling
remain available.
2026-05-20 11:33:17 +00:00
|
|
|
ID: "rex",
|
|
|
|
|
DisplayName: "Rex",
|
|
|
|
|
Role: "Frontend Dev",
|
|
|
|
|
Status: models.AgentStatusIdle,
|
2026-05-08 19:58:06 -04:00
|
|
|
SessionKey: "rex-session",
|
CUB-200: implement WebSocket gateway client with v3 protocol
Replace REST poller with WebSocket client as primary gateway connection:
- wsclient.go: WebSocket client with v3 handshake (connect.challenge →
connect → hello-ok), frame routing (req/res/event), JSON-RPC Send(),
auto-reconnect with exponential backoff (1s → 30s max)
- sync.go: Initial sync via agents.list + sessions.list RPCs, merge
session runtime state into AgentCardData, broadcast fleet.update
- events.go: Real-time event handlers for sessions.changed, presence,
and agent.config — DB update first, then SSE broadcast
- client.go: REST poller retained as fallback (WS is primary)
- config.go: Add GATEWAY_WS_URL and OPENCLAW_GATEWAY_TOKEN env vars
- main.go: Wire WS client as primary, REST as fallback
- .env.example: Document new WS config vars
Fallback: If WS connection fails, seeded demo data + REST polling
remain available.
2026-05-20 11:33:17 +00:00
|
|
|
Channel: "discord",
|
2026-05-08 19:58:06 -04:00
|
|
|
LastActivity: time.Now().UTC().Add(-10 * time.Minute).Format(time.RFC3339),
|
|
|
|
|
},
|
|
|
|
|
{
|
CUB-200: implement WebSocket gateway client with v3 protocol
Replace REST poller with WebSocket client as primary gateway connection:
- wsclient.go: WebSocket client with v3 handshake (connect.challenge →
connect → hello-ok), frame routing (req/res/event), JSON-RPC Send(),
auto-reconnect with exponential backoff (1s → 30s max)
- sync.go: Initial sync via agents.list + sessions.list RPCs, merge
session runtime state into AgentCardData, broadcast fleet.update
- events.go: Real-time event handlers for sessions.changed, presence,
and agent.config — DB update first, then SSE broadcast
- client.go: REST poller retained as fallback (WS is primary)
- config.go: Add GATEWAY_WS_URL and OPENCLAW_GATEWAY_TOKEN env vars
- main.go: Wire WS client as primary, REST as fallback
- .env.example: Document new WS config vars
Fallback: If WS connection fails, seeded demo data + REST polling
remain available.
2026-05-20 11:33:17 +00:00
|
|
|
ID: "dex",
|
|
|
|
|
DisplayName: "Dex",
|
|
|
|
|
Role: "Backend Dev",
|
|
|
|
|
Status: models.AgentStatusThinking,
|
2026-05-08 19:58:06 -04:00
|
|
|
CurrentTask: strPtr("Designing API contracts"),
|
|
|
|
|
SessionKey: "dex-session",
|
CUB-200: implement WebSocket gateway client with v3 protocol
Replace REST poller with WebSocket client as primary gateway connection:
- wsclient.go: WebSocket client with v3 handshake (connect.challenge →
connect → hello-ok), frame routing (req/res/event), JSON-RPC Send(),
auto-reconnect with exponential backoff (1s → 30s max)
- sync.go: Initial sync via agents.list + sessions.list RPCs, merge
session runtime state into AgentCardData, broadcast fleet.update
- events.go: Real-time event handlers for sessions.changed, presence,
and agent.config — DB update first, then SSE broadcast
- client.go: REST poller retained as fallback (WS is primary)
- config.go: Add GATEWAY_WS_URL and OPENCLAW_GATEWAY_TOKEN env vars
- main.go: Wire WS client as primary, REST as fallback
- .env.example: Document new WS config vars
Fallback: If WS connection fails, seeded demo data + REST polling
remain available.
2026-05-20 11:33:17 +00:00
|
|
|
Channel: "discord",
|
2026-05-08 19:58:06 -04:00
|
|
|
LastActivity: time.Now().UTC().Format(time.RFC3339),
|
|
|
|
|
},
|
|
|
|
|
{
|
CUB-200: implement WebSocket gateway client with v3 protocol
Replace REST poller with WebSocket client as primary gateway connection:
- wsclient.go: WebSocket client with v3 handshake (connect.challenge →
connect → hello-ok), frame routing (req/res/event), JSON-RPC Send(),
auto-reconnect with exponential backoff (1s → 30s max)
- sync.go: Initial sync via agents.list + sessions.list RPCs, merge
session runtime state into AgentCardData, broadcast fleet.update
- events.go: Real-time event handlers for sessions.changed, presence,
and agent.config — DB update first, then SSE broadcast
- client.go: REST poller retained as fallback (WS is primary)
- config.go: Add GATEWAY_WS_URL and OPENCLAW_GATEWAY_TOKEN env vars
- main.go: Wire WS client as primary, REST as fallback
- .env.example: Document new WS config vars
Fallback: If WS connection fails, seeded demo data + REST polling
remain available.
2026-05-20 11:33:17 +00:00
|
|
|
ID: "hex",
|
|
|
|
|
DisplayName: "Hex",
|
|
|
|
|
Role: "Database Specialist",
|
|
|
|
|
Status: models.AgentStatusActive,
|
2026-05-08 19:58:06 -04:00
|
|
|
CurrentTask: strPtr("Reviewing schema migrations"),
|
|
|
|
|
SessionKey: "hex-session",
|
CUB-200: implement WebSocket gateway client with v3 protocol
Replace REST poller with WebSocket client as primary gateway connection:
- wsclient.go: WebSocket client with v3 handshake (connect.challenge →
connect → hello-ok), frame routing (req/res/event), JSON-RPC Send(),
auto-reconnect with exponential backoff (1s → 30s max)
- sync.go: Initial sync via agents.list + sessions.list RPCs, merge
session runtime state into AgentCardData, broadcast fleet.update
- events.go: Real-time event handlers for sessions.changed, presence,
and agent.config — DB update first, then SSE broadcast
- client.go: REST poller retained as fallback (WS is primary)
- config.go: Add GATEWAY_WS_URL and OPENCLAW_GATEWAY_TOKEN env vars
- main.go: Wire WS client as primary, REST as fallback
- .env.example: Document new WS config vars
Fallback: If WS connection fails, seeded demo data + REST polling
remain available.
2026-05-20 11:33:17 +00:00
|
|
|
Channel: "discord",
|
2026-05-08 19:58:06 -04:00
|
|
|
LastActivity: time.Now().UTC().Format(time.RFC3339),
|
|
|
|
|
},
|
|
|
|
|
{
|
CUB-200: implement WebSocket gateway client with v3 protocol
Replace REST poller with WebSocket client as primary gateway connection:
- wsclient.go: WebSocket client with v3 handshake (connect.challenge →
connect → hello-ok), frame routing (req/res/event), JSON-RPC Send(),
auto-reconnect with exponential backoff (1s → 30s max)
- sync.go: Initial sync via agents.list + sessions.list RPCs, merge
session runtime state into AgentCardData, broadcast fleet.update
- events.go: Real-time event handlers for sessions.changed, presence,
and agent.config — DB update first, then SSE broadcast
- client.go: REST poller retained as fallback (WS is primary)
- config.go: Add GATEWAY_WS_URL and OPENCLAW_GATEWAY_TOKEN env vars
- main.go: Wire WS client as primary, REST as fallback
- .env.example: Document new WS config vars
Fallback: If WS connection fails, seeded demo data + REST polling
remain available.
2026-05-20 11:33:17 +00:00
|
|
|
ID: "pip",
|
|
|
|
|
DisplayName: "Pip",
|
|
|
|
|
Role: "Edge Device Dev",
|
|
|
|
|
Status: models.AgentStatusIdle,
|
2026-05-08 19:58:06 -04:00
|
|
|
SessionKey: "pip-session",
|
CUB-200: implement WebSocket gateway client with v3 protocol
Replace REST poller with WebSocket client as primary gateway connection:
- wsclient.go: WebSocket client with v3 handshake (connect.challenge →
connect → hello-ok), frame routing (req/res/event), JSON-RPC Send(),
auto-reconnect with exponential backoff (1s → 30s max)
- sync.go: Initial sync via agents.list + sessions.list RPCs, merge
session runtime state into AgentCardData, broadcast fleet.update
- events.go: Real-time event handlers for sessions.changed, presence,
and agent.config — DB update first, then SSE broadcast
- client.go: REST poller retained as fallback (WS is primary)
- config.go: Add GATEWAY_WS_URL and OPENCLAW_GATEWAY_TOKEN env vars
- main.go: Wire WS client as primary, REST as fallback
- .env.example: Document new WS config vars
Fallback: If WS connection fails, seeded demo data + REST polling
remain available.
2026-05-20 11:33:17 +00:00
|
|
|
Channel: "discord",
|
2026-05-08 19:58:06 -04:00
|
|
|
LastActivity: time.Now().UTC().Add(-1 * time.Hour).Format(time.RFC3339),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, a := range demoAgents {
|
|
|
|
|
if err := agents.Create(ctx, a); err != nil {
|
|
|
|
|
return fmt.Errorf("seed agent %s: %w", a.ID, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
slog.Info("demo agents seeded", "count", len(demoAgents))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
CUB-200: implement WebSocket gateway client with v3 protocol
Replace REST poller with WebSocket client as primary gateway connection:
- wsclient.go: WebSocket client with v3 handshake (connect.challenge →
connect → hello-ok), frame routing (req/res/event), JSON-RPC Send(),
auto-reconnect with exponential backoff (1s → 30s max)
- sync.go: Initial sync via agents.list + sessions.list RPCs, merge
session runtime state into AgentCardData, broadcast fleet.update
- events.go: Real-time event handlers for sessions.changed, presence,
and agent.config — DB update first, then SSE broadcast
- client.go: REST poller retained as fallback (WS is primary)
- config.go: Add GATEWAY_WS_URL and OPENCLAW_GATEWAY_TOKEN env vars
- main.go: Wire WS client as primary, REST as fallback
- .env.example: Document new WS config vars
Fallback: If WS connection fails, seeded demo data + REST polling
remain available.
2026-05-20 11:33:17 +00:00
|
|
|
func strPtr(s string) *string { return &s }
|