2026-05-06 17:29:44 -04:00
|
|
|
// Package config provides application configuration loaded from environment
|
|
|
|
|
// variables with sensible defaults for local development.
|
|
|
|
|
package config
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"os"
|
|
|
|
|
"strconv"
|
2026-05-08 19:58:06 -04:00
|
|
|
"time"
|
2026-05-06 17:29:44 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Config holds all application configuration.
|
|
|
|
|
type Config struct {
|
2026-05-20 21:26:17 +00:00
|
|
|
Port int
|
|
|
|
|
DatabaseURL string
|
|
|
|
|
CORSOrigin string
|
|
|
|
|
LogLevel string
|
|
|
|
|
Environment string
|
|
|
|
|
GatewayRestURL string
|
|
|
|
|
GatewayRestPollInterval time.Duration
|
|
|
|
|
WSGatewayURL string
|
|
|
|
|
WSGatewayToken string
|
2026-05-06 17:29:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load reads configuration from environment variables, applying defaults where
|
|
|
|
|
// values are not set. All secrets come from the environment — nothing is hardcoded.
|
|
|
|
|
func Load() *Config {
|
|
|
|
|
return &Config{
|
2026-05-20 21:26:17 +00:00
|
|
|
Port: getEnvInt("PORT", 8080),
|
|
|
|
|
DatabaseURL: getEnv("DATABASE_URL", "postgres://controlcenter:controlcenter@localhost:5432/controlcenter?sslmode=disable"),
|
|
|
|
|
CORSOrigin: getEnv("CORS_ORIGIN", "*"),
|
|
|
|
|
LogLevel: getEnv("LOG_LEVEL", "info"),
|
|
|
|
|
Environment: getEnv("ENVIRONMENT", "development"),
|
|
|
|
|
GatewayRestURL: getEnv("GATEWAY_URL", "http://host.docker.internal:18789/api/agents"),
|
|
|
|
|
GatewayRestPollInterval: getEnvDuration("GATEWAY_POLL_INTERVAL", 5*time.Second),
|
|
|
|
|
WSGatewayURL: getEnv("WS_GATEWAY_URL", "ws://host.docker.internal:18789/"),
|
|
|
|
|
WSGatewayToken: getEnv("OPENCLAW_GATEWAY_TOKEN", ""),
|
2026-05-06 17:29:44 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getEnv(key, fallback string) string {
|
|
|
|
|
if v := os.Getenv(key); v != "" {
|
|
|
|
|
return v
|
|
|
|
|
}
|
|
|
|
|
return fallback
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getEnvInt(key string, fallback int) int {
|
|
|
|
|
if v := os.Getenv(key); v != "" {
|
|
|
|
|
if i, err := strconv.Atoi(v); err == nil {
|
|
|
|
|
return i
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return fallback
|
|
|
|
|
}
|
2026-05-08 19:58:06 -04:00
|
|
|
|
|
|
|
|
func getEnvDuration(key string, fallback time.Duration) time.Duration {
|
|
|
|
|
if v := os.Getenv(key); v != "" {
|
|
|
|
|
if d, err := time.ParseDuration(v); err == nil {
|
|
|
|
|
return d
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return fallback
|
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
|
|
|
}
|