// Package config loads the Dialectic plugin's per-profile config from // /plugins/dialectic/config.json. Mirrors the resolved shape // of Dialectic.OpenclawPlugin (backendUrl + per-agent api key), adapted // for Plexum's profile layout. // // OpenClaw stored per-agent api keys in secret-mgr (one read per // agent's keyspace). Plexum has no secret-mgr; v1 stores keys directly // in this config and falls back to a single shared apiKey when the // caller's agent id has no override entry. package config import ( "encoding/json" "errors" "fmt" "os" "path/filepath" ) type Config struct { BackendURL string `json:"backendUrl,omitempty"` // APIKey is the default bearer token used when AgentKeys has no // entry for the calling agent. Equivalent to a claw-level api key. APIKey string `json:"apiKey,omitempty"` // AgentKeys maps agent_id → bearer token. Overrides APIKey when // the caller's agent id matches an entry. AgentKeys map[string]string `json:"agentKeys,omitempty"` // DefaultAgentID is the agent_id the plugin reports when the host // hasn't surfaced the calling agent via ctx. Set this on single- // agent claws; multi-agent setups should leave it empty and rely // on the tool dispatcher's best-effort resolution. DefaultAgentID string `json:"defaultAgentID,omitempty"` } type Resolved struct { BackendURL string APIKey string AgentKeys map[string]string DefaultAgentID string } const DefaultBackendURL = "https://dialectic-api.hangman-lab.top" func PluginConfigDir(profileRoot string) string { return filepath.Join(profileRoot, "plugins", "dialectic") } func PluginConfigPath(profileRoot string) string { return filepath.Join(PluginConfigDir(profileRoot), "config.json") } func Load(profileRoot string) (Config, error) { path := PluginConfigPath(profileRoot) raw, err := os.ReadFile(path) if err != nil { if errors.Is(err, os.ErrNotExist) { return Config{}, nil } return Config{}, fmt.Errorf("read %s: %w", path, err) } if len(raw) == 0 { return Config{}, nil } var c Config if err := json.Unmarshal(raw, &c); err != nil { return Config{}, fmt.Errorf("parse %s: %w", path, err) } return c, nil } func Resolve(c Config) Resolved { out := Resolved{ BackendURL: DefaultBackendURL, APIKey: c.APIKey, AgentKeys: c.AgentKeys, DefaultAgentID: c.DefaultAgentID, } if c.BackendURL != "" { out.BackendURL = c.BackendURL } if env := os.Getenv("DIALECTIC_BACKEND_URL"); env != "" { out.BackendURL = env } return out } // ResolveAPIKey picks the bearer token for the calling agent. Returns // "" iff neither an agent-specific key nor a default apiKey is set. func (r Resolved) ResolveAPIKey(agentID string) string { if agentID != "" { if k, ok := r.AgentKeys[agentID]; ok && k != "" { return k } } return r.APIKey }