Files
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
244 KiB
JSON

{"repo": "chaoyihuang/aiagent", "files": {"aiagent/pkg/handler/openclaw/converter.go": {"696913067c9d070f9c9a40d5922aa18e8ec609f7": {"t": 1785745630.8686233, "n": 38133, "h": "8caec13f0fe14292", "c": "// Package openclaw provides configuration conversion for OpenClaw framework.\n// This converter transforms AIAgentSpec + HarnessConfig into OpenClaw JSON config.\n//\n// Configuration Sources:\n// - agentConfig (from AIAgentSpec): Gateway port, internal agents, overrides\n// - HarnessConfig (from AgentRuntime): Models, Skills, Memory (shared)\npackage openclaw\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"aiagent/api/v1\"\n\t\"aiagent/pkg/handler\"\n)\n\n// ConfigConverter converts AIAgentSpec and HarnessConfig to OpenClaw config.\ntype ConfigConverter struct{}\n\n// NewConfigConverter creates a new config converter.\nfunc NewConfigConverter() *ConfigConverter {\n\treturn &ConfigConverter{}\n}\n\n// ============================================================\n// Core Conversion Methods (called by Handler)\n// ============================================================\n\n// ConvertToOpenClawConfig generates full openclaw.json from AIAgentSpec and HarnessConfig.\n// This is the primary method called by GenerateFrameworkConfig.\nfunc (c *ConfigConverter) ConvertToOpenClawConfig(spec *v1.AIAgentSpec, harnessCfg *handler.HarnessConfig) ([]byte, error) {\n\tconfig := &OpenClawConfig{\n\t\tAgents: &AgentsConfig{},\n\t\tGateway: &GatewayConfig{\n\t\t\tMode: \"local\",\n\t\t},\n\t}\n\n\t// 1. Parse agentConfig JSON (agent-specific configuration)\n\tvar agentConfig *AgentConfigJSON\n\tif spec.AgentConfig != nil && spec.AgentConfig.Raw != nil {\n\t\tparsedConfig, err := c.parseAgentConfigJSON(spec.AgentConfig.Raw)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse agentConfig: %w\", err)\n\t\t}\n\t\tagentConfig = parsedConfig\n\n\t\t// Apply agentConfig to OpenClaw config\n\t\tif agentConfig.Gateway != nil {\n\t\t\tconfig.Gateway = c.convertGatewayInstanceConfig(agentConfig.Gateway)\n\t\t}\n\t\tif agentConfig.InternalAgents != nil {\n\t\t\tconfig.Agents = c.convertInternalAgentsConfig(agentConfig.InternalAgents)\n\t\t}\n\t\t// Apply overrides after agents are set\n\t\tif agentConfig.Overrides != nil {\n\t\t\tc.applyAgentOverrides(config, agentConfig.Overrides)\n\t\t}\n\t\t// Apply channels configuration (Discord, Telegram, Slack)\n\t\tif agentConfig.Channels != nil {\n\t\t\tconfig.Channels = c.convertChannelsConfig(agentConfig.Channels)\n\t\t\t// Set plugins.entries for enabled channels so Gateway loads channel plugins\n\t\t\tpluginEntries := make(map[string]PluginEntry)\n\t\t\tif agentConfig.Channels.Discord != nil && agentConfig.Channels.Discord.Enabled {\n\t\t\t\tpluginEntries[\"discord\"] = PluginEntry{Enabled: true}\n\t\t\t}\n\t\t\tif agentConfig.Channels.Telegram != nil && agentConfig.Channels.Telegram.Enabled {\n\t\t\t\tpluginEntries[\"telegram\"] = PluginEntry{Enabled: true}\n\t\t\t}\n\t\t\tif agentConfig.Channels.Slack != nil && agentConfig.Channels.Slack.Enabled {\n\t\t\t\tpluginEntries[\"slack\"] = PluginEntry{Enabled: true}\n\t\t\t}\n\t\t\tif len(pluginEntries) > 0 {\n\t\t\t\tif config.Plugins == nil {\n\t\t\t\t\tconfig.Plugins = &PluginsConfig{}\n\t\t\t\t}\n\t\t\t\tconfig.Plugins.Entries = pluginEntries\n\t\t\t}\n\t\t}\n\t}\n\n\t// 2. Apply Harness configuration (shared platform capabilities)\n\tif harnessCfg != nil {\n\t\tconfig.Agents.Defaults = c.convertAgentDefaultsFromHarness(harnessCfg)\n\t\t// Pass agentConfig for merge mode configuration\n\t\tconfig.Models = c.convertModelsConfig(harnessCfg, agentConfig)\n\t\tconfig.Skills = c.convertSkillsConfig(harnessCfg)\n\t\t// Memory config not supported in OpenClaw v2026.5.7 config schema\n\t\t// OpenClaw uses internal session management\n\t\t// config.Memory = c.convertMemoryConfig(harnessCfg)\n\t\tconfig.Sandbox = c.convertSandboxConfig(harnessCfg)\n\t\t// If external sandbox, set plugins path\n\t\tif config.Sandbox != nil && config.Sandbox.Mode == \"external\" && config.Sandbox.Plugins != nil {\n\t\t\tconfig.Plugins = config.Sandbox.Plugins\n\t\t}\n\t}\n\n\t// 3. Create main agent entry from AIAgentSpec metadata\n\tmainAgent := &AgentConfig{\n\t\tID: spec.Description,\n\t\tName: spec.Description,\n\t\tDescription: spec.Description,\n\t}\n\n\t// Apply model from harness (use provider/model format)\n\tif harnessCfg != nil && harnessCfg.Model != nil {\n\t\tmainAgent.Model = &AgentModelConfig{\n\t\t\tPrimary: harnessCfg.Model.Provider + \"/\" + harnessCfg.Model.DefaultModel,\n\t\t}\n\t}\n\n\t// Apply skills from harness\n\tif harnessCfg != nil && harnessCfg.Skills != nil {\n\t\tfor _, skill := range harnessCfg.Skills.Skills {\n\t\t\tif skill.Allowed {\n\t\t\t\tmainAgent.Skills = append(mainAgent.Skills, skill.Name)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add main agent to list if no internal agents defined\n\tif len(config.Agents.List) == 0 {\n\t\tconfig.Agents.List = []*AgentConfig{mainAgent}\n\t}\n\n\treturn json.MarshalIndent(config, \"\", \" \")\n}\n\n// ConvertAgentSpec generates AgentConfig section for a single agent.\nfunc (c *ConfigConverter) ConvertAgentSpec(spec *v1.AIAgentSpec, harnessCfg *handler.HarnessConfig) (*AgentConfig, error) {\n\tagentCfg := &AgentConfig{\n\t\tID: spec.Description,\n\t\tName: spec.Description,\n\t\tDescription: spec.Description,\n\t}\n\n\t// Apply model from harness (use provider/model format)\n\tif harnessCfg != nil && harnessCfg.Model != nil {\n\t\tagentCfg.Model = &AgentModelConfig{\n\t\t\tPrimary: harnessCfg.Model.Provider + \"/\" + harnessCfg.Model.DefaultModel,\n\t\t}\n\t}\n\n\t// Apply skills from harness\n\tif harnessCfg != nil && harnessCfg.Skills != nil {\n\t\tfor _, skill := range harnessCfg.Skills.Skills {\n\t\t\tif skill.Allowed {\n\t\t\t\tagentCfg.Skills = append(agentCfg.Skills, skill.Name)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Apply overrides from spec\n\tc.applySpecOverrides(agentCfg, spec.HarnessOverride)\n\n\treturn agentCfg, nil\n}\n\n// ConvertHarnessConfig generates harness-specific config sections.\n// Note: This method doesn't have agentConfig context, so models.mode won't be set.\n// Use ConvertToOpenClawConfig for full config generation with agentConfig support.\nfunc (c *ConfigConverter) ConvertHarnessConfig(harnessCfg *handler.HarnessConfig) ([]byte, error) {\n\tharnessSection := &HarnessSection{\n\t\tModel: c.convertModelsConfig(harnessCfg, nil), // nil agentConfig means no merge mode\n\t\tSkills: c.convertSkillsConfig(harnessCfg),\n\t\tMemory: c.convertMemoryConfig(harnessCfg),\n\t}\n\n\treturn json.MarshalIndent(harnessSection, \"\", \" \")\n}\n\n// ============================================================\n// agentConfig Parsing (AIAgentSpec.agentConfig.Raw)\n// ============================================================\n\n// AgentConfigJSON represents the agentConfig JSON structure from AIAgentSpec.\n// ONLY contains agent-specific configuration (not shared Harness capabilities).\n// Fields use JSON names matching the schema defined earlier.\ntype AgentConfigJSON struct {\n\t// Gateway process configuration for this agent instance\n\tGateway *GatewayInstanceConfigJSON `json:\"gateway\"`\n\n\t// Internal sub-agents managed by this Gateway (keyed as \"agents\" in JSON)\n\tInternalAgents *InternalAgentsConfigJSON `json:\"agents,omitempty\"`\n\n\t// Per-agent overrides of inherited Harness defaults\n\tOverrides *AgentOverridesConfigJSON `json:\"overrides,omitempty\"`\n\n\t// Channel integrations (Discord, Telegram, Slack)\n\tChannels *ChannelsInstanceConfigJSON `json:\"channels,omitempty\"`\n\n\t// Models merge mode configuration (OpenClaw-specific)\n\t// When mergeModels is true, OpenClaw will merge harness providers with built-in providers\n\tModels *ModelsMergeModeConfig `json:\"models,omitempty\"`\n}\n\n// ChannelsInstanceConfigJSON - Channel integrations for this Gateway instance\ntype ChannelsInstanceConfigJSON struct {\n\t// Discord channel configuration\n\tDiscord *DiscordChannelConfigJSON `json:\"discord,omitempty\"`\n\n\t// Telegram channel configuration\n\tTelegram *TelegramChannelConfigJSON `json:\"telegram,omitempty\"`\n\n\t// Slack channel configuration\n\tSlack *SlackChannelConfigJSON `json:\"slack,omitempty\"`\n}\n\n// DiscordChannelConfigJSON - Discord channel configuration from agentConfig\ntype DiscordChannelConfigJSON struct {\n\t// Enable Discord integration\n\tEnabled bool `json:\"enabled,omitempty\"`\n\n\t// Discord bot token (can reference secret or use direct value)\n\tToken string `json:\"token,omitempty\"`\n\n\t// Secret reference for bot token (recommended for production)\n\tTokenSecretRef string `json:\"tokenSecretRef,omitempty\"`\n\n\t// Allowed Discord user IDs (whitelist)\n\t// Accept both \"allowedUsers\" (legacy) and \"allowFrom\" (v2026.5.7)\n\tAllowedUsers []string `json:\"allowedUsers,omitempty\"`\n\tAllowFrom []string `json:\"allowFrom,omitempty\"`\n\n\t// DM policy for direct messages\n\tDmPolicy string `json:\"dmPolicy,omitempty\"`\n\n\t// Allowed Discord guild/server IDs (whitelist)\n\tAllowedGuilds []string `json:\"allowedGuilds,omitempty\"`\n\n\t// Allowed Discord channel IDs (whitelist)\n\tAllowedChannels []string `json:\"allowedChannels,omitempty\"`\n\n\t// Bot command prefix\n\tCommandPrefix string `json:\"commandPrefix,omitempty\"`\n\n\t// Require @mention to trigger bot response\n\tMentionRequired bool `json:\"mentionRequired,omitempty\"`\n}\n\n// TelegramChannelConfigJSON - Telegram channel configuration from agentConfig\ntype TelegramChannelConfigJSON struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n\tTokenSecretRef string `json:\"tokenSecretRef,omitempty\"`\n\tAllowedUsers []string `json:\"allowedUsers,omitempty\"`\n}\n\n// SlackChannelConfigJSON - Slack channel configuration from agentConfig\ntype SlackChannelConfigJSON struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n\tTokenSecretRef string `json:\"tokenSecretRef,omitempty\"`\n\tAllowedChannels []string `json:\"allowedChannels,omitempty\"`\n}\n\n// GatewayInstanceConfigJSON - Gateway process specific config\ntype GatewayInstanceConfigJSON struct {\n\t// Port for this Gateway instance\n\tPort int `json:\"port,omitempty\"`\n\n\t// Bind address: loopback, lan, auto\n\tBind string `json:\"bind,omitempty\"`\n\n\t// Authentication for this Gateway instance\n\tAuth *GatewayAuthConfigJSON `json:\"auth,omitempty\"`\n\n\t// Control UI settings\n\tControlUI *GatewayControlUIConfigJSON `json:\"controlUi,omitempty\"`\n\n\t// Trusted proxy IPs for X-Forwarded-For\n\tTrustedProxies []string `json:\"trustedProxies,omitempty\"`\n}\n\n// GatewayAuthConfigJSON - Gateway authentication\ntype GatewayAuthConfigJSON struct {\n\t// Auth mode: none, token, password\n\tMode string `json:\"mode,omitempty\"`\n\n\t// Token for token mode\n\tToken string `json:\"token,omitempty\"`\n}\n\n// GatewayControlUIConfigJSON - Control UI for this instance\ntype GatewayControlUIConfigJSON struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tBasePath string `json:\"basePath,omitempty\"`\n\tAllowedOrigins []string `json:\"allowedOrigins,omitempty\"`\n}\n\n// InternalAgentsConfigJSON - Sub-agents inside Gateway\ntype InternalAgentsConfigJSON struct {\n\t// Default settings for internal agents\n\tDefaults *InternalAgentDefaultsJSON `json:\"defaults,omitempty\"`\n\n\t// List of internal sub-agents\n\tList []*InternalAgentDefinitionJSON `json:\"list,omitempty\"`\n}\n\n// InternalAgentDefaultsJSON - Default settings for sub-agents\ntype InternalAgentDefaultsJSON struct {\n\t// Override inherited model\n\tModel string `json:\"model,omitempty\"`\n\n\t// Thinking level: off, minimal, low, medium, high, xhigh, adaptive\n\tThinkingDefault string `json:\"thinkingDefault,omitempty\"`\n\n\t// Reasoning visibility: on, off, stream\n\tReasoningDefault string `json:\"reasoningDefault,omitempty\"`\n\n\t// Fast mode default\n\tFastModeDefault bool `json:\"fastModeDefault,omitempty\"`\n\n\t// Identity for all sub-agents\n\tIdentity *AgentIdentityJSON `json:\"identity,omitempty\"`\n}\n\n// InternalAgentDefinitionJSON - Single internal sub-agent\ntype InternalAgentDefinitionJSON struct {\n\t// Agent identifier\n\tID string `json:\"id\"`\n\n\t// Display name\n\tName string `json:\"name,omitempty\"`\n\n\t// Override model for this sub-agent\n\tModel string `json:\"model,omitempty\"`\n\n\t// Allowed skills (subset of Harness.Skills)\n\tSkills []string `json:\"skills,omitempty\"`\n\n\t// Identity for this sub-agent\n\tIdentity *AgentIdentityJSON `json:\"identity,omitempty\"`\n\n\t// Tool restrictions (subset)\n\tTools *ToolsOverrideJSON `json:\"tools,omitempty\"`\n\n\t// Subagent spawning config\n\tSubagents *SubagentsConfigJSON `json:\"subagents,omitempty\"`\n}\n\n// AgentIdentityJSON - Agent name and avatar\ntype AgentIdentityJSON struct {\n\tName string `json:\"name,omitempty\"`\n\tAvatar string `json:\"avatar,omitempty\"`\n}\n\n// ToolsOverrideJSON - Per-agent tool restrictions\ntype ToolsOverrideJSON struct {\n\tAllow []string `json:\"allow,omitempty\"`\n\tDeny []string `json:\"deny,omitempty\"`\n}\n\n// SubagentsConfigJSON - Sub-agent spawning rules\ntype SubagentsConfigJSON struct {\n\t// Allowed sub-agent IDs (\"*\" = any)\n\tAllowAgents []string `json:\"allowAgents,omitempty\"`\n\n\t// Default model for spawned sub-agents\n\tModel string `json:\"model,omitempty\"`\n\n\t// Require explicit agentId in spawn calls\n\tRequireAgentID bool `json:\"requireAgentId,omitempty\"`\n}\n\n// AgentOverridesConfigJSON - Override inherited Harness defaults\ntype AgentOverridesConfigJSON struct {\n\t// Override model selection\n\tModel string `json:\"model,omitempty\"`\n\n\t// Skills subset (cannot add new, only restrict)\n\tAllowedSkills []string `json:\"allowedSkills,omitempty\"`\n\tDeniedSkills []string `json:\"deniedSkills,omitempty\"`\n\n\t// Model fallback chain override\n\tModelFallbacks []string `json:\"modelFallbacks,omitempty\"`\n}\n\n// parseAgentConfigJSON parses raw agentConfig JSON\nfunc (c *ConfigConverter) parseAgentConfigJSON(raw []byte) (*AgentConfigJSON, error) {\n\tif len(raw) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tagentConfig := &AgentConfigJSON{}\n\tif err := json.Unmarshal(raw, agentConfig); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid agentConfig JSON: %w\", err)\n\t}\n\n\treturn agentConfig, nil\n}\n\n// ParseAgentConfig is the public method for Handler to call\nfunc (c *ConfigConverter) ParseAgentConfig(raw []byte) (*AgentConfigJSON, error) {\n\treturn c.parseAgentConfigJSON(raw)\n}\n\n// convertGatewayInstanceConfig converts agentConfig.Gateway to OpenClaw GatewayConfig\n// For v2026.5.7: use bind (not host), auth object (not authMode)\nfunc (c *ConfigConverter) convertGatewayInstanceConfig(gateway *GatewayInstanceConfigJSON) *GatewayConfig {\n\tcfg := &GatewayConfig{\n\t\tMode: \"local\",\n\t}\n\n\tif gateway.Port > 0 {\n\t\tcfg.Port = gateway.Port\n\t}\n\tif gateway.Bind != \"\" {\n\t\tcfg.Bind = gateway.Bind\n\t}\n\tif gateway.Auth != nil {\n\t\tcfg.Auth = &GatewayAuth{\n\t\t\tMode: gateway.Auth.Mode,\n\t\t\tToken: gateway.Auth.Token,\n\t\t}\n\t}\n\tif gateway.ControlUI != nil {\n\t\tcfg.ControlUI = &ControlUIConfig{\n\t\t\tEnabled: gateway.ControlUI.Enabled,\n\t\t\tBasePath: gateway.ControlUI.BasePath,\n\t\t\tAllowedOrigins: gateway.ControlUI.AllowedOrigins,\n\t\t}\n\t}\n\tcfg.TrustedProxies = gateway.TrustedProxies\n\n\treturn cfg\n}\n\n// convertInternalAgentsConfig converts agentConfig.InternalAgents to OpenClaw AgentsConfig\n// For OpenClaw v2026.5.7: Only supported fields are model, skills, workspace at defaults level\nfunc (c *ConfigConverter) convertInternalAgentsConfig(internal *InternalAgentsConfigJSON) *AgentsConfig {\n\tagents := &AgentsConfig{}\n\n\t// Convert defaults - only use model, skills, workspace (v2026.5.7 schema)\n\tif internal.Defaults != nil {\n\t\tagents.Defaults = &AgentDefaultsConfig{}\n\t\tif internal.Defaults.Model != \"\" {\n\t\t\tagents.Defaults.Model = &AgentModelConfig{\n\t\t\t\tPrimary: internal.Defaults.Model,\n\t\t\t}\n\t\t}\n\t\t// Note: identity and thinkingDefault are NOT supported at defaults level in v2026.5.7\n\t}\n\n\t// Convert internal agent list - use v2026.5.7 schema fields\n\tfor _, def := range internal.List {\n\t\tagent := &AgentConfig{\n\t\t\tID: def.ID,\n\t\t\tName: def.Name,\n\t\t}\n\t\tif def.Model != \"\" {\n\t\t\tagent.Model = &AgentModelConfig{\n\t\t\t\tPrimary: def.Model,\n\t\t\t}\n\t\t}\n\t\tagent.Skills = def.Skills\n\t\t// Note: identity is NOT a top-level field in agents.list[] for v2026.5.7\n\t\tagents.List = append(agents.List, agent)\n\t}\n\n\treturn agents\n}\n\n// applyAgentOverrides applies agentConfig.Overrides to OpenClaw config\nfunc (c *ConfigConverter) applyAgentOverrides(config *OpenClawConfig, overrides *AgentOverridesConfigJSON) {\n\t// Override default model\n\tif overrides.Model != \"\" && config.Agents.Defaults != nil {\n\t\tconfig.Agents.Defaults.Model = &AgentModelConfig{\n\t\t\tPrimary: overrides.Model,\n\t\t}\n\t}\n\n\t// Filter skills for all agents\n\tif len(overrides.AllowedSkills) > 0 || len(overrides.DeniedSkills) > 0 {\n\t\tfor _, agent := range config.Agents.List {\n\t\t\tagent.Skills = c.filterSkills(agent.Skills, overrides.AllowedSkills, overrides.DeniedSkills)\n\t\t}\n\t}\n\n\t// Set model fallbacks\n\tif len(overrides.ModelFallbacks) > 0 && config.Agents.Defaults != nil && config.Agents.Defaults.Model != nil {\n\t\tconfig.Agents.Defaults.Model.Fallbacks = overrides.ModelFallbacks\n\t}\n}\n\n// convertChannelsConfig converts agentConfig.Channels to OpenClaw ChannelsConfig\nfunc (c *ConfigConverter) convertChannelsConfig(channels *ChannelsInstanceConfigJSON) *ChannelsConfig {\n\tif channels == nil {\n\t\treturn nil\n\t}\n\n\tcfg := &ChannelsConfig{}\n\n\t// Convert Discord configuration (v2026.5.12 schema)\n\t// Token uses SecretInput pointing to env var: DISCORD_BOT_TOKEN\n\t// ConfigDaemon resolves tokenSecretRef → writes token to hostPath\n\t// Handler extracts token from hostPath → injects as subprocess env var\n\t//\n\t// dmPolicy options (from OpenClaw zod-schema.providers-core.ts):\n\t// - \"pairing\" - requires pairing before DM (default)\n\t// - \"allowlist\" - requires specific user IDs in allowFrom\n\t// - \"open\" - allows all users (requires \"*\" in allowFrom)\n\t// - \"disabled\" - disables DM\n\tif channels.Discord != nil {\n\t\t// Use AllowFrom if provided directly, otherwise use AllowedUsers\n\t\tallowFrom := channels.Discord.AllowFrom\n\t\tif len(allowFrom) == 0 && len(channels.Discord.AllowedUsers) > 0 {\n\t\t\tallowFrom = channels.Discord.AllowedUsers\n\t\t}\n\n\t\tdmPolicy := channels.Discord.DmPolicy\n\t\t// Normalize dmPolicy: \"all\" (legacy) → \"open\", empty → default based on allowFrom\n\t\tif dmPolicy == \"all\" {\n\t\t\tdmPolicy = \"open\"\n\t\t\t// For \"open\" policy, OpenClaw requires \"*\" in allowFrom\n\t\t\tallowFrom = []string{\"*\"}\n\t\t} else if dmPolicy == \"\" {\n\t\t\t// Default based on allowFrom content:\n\t\t\t// - [\"*\"] → \"open\" (allow all users)\n\t\t\t// - specific IDs → \"allowlist\" (allow listed users)\n\t\t\t// - empty → \"pairing\" (require pairing)\n\t\t\tif containsString(allowFrom, \"*\") {\n\t\t\t\tdmPolicy = \"open\"\n\t\t\t} else if len(allowFrom) > 0 {\n\t\t\t\tdmPolicy = \"allowlist\"\n\t\t\t} else {\n\t\t\t\tdmPolicy = \"pairing\"\n\t\t\t}\n\t\t}\n\t\t// For \"open\" policy without \"*\", add \"*\" to satisfy OpenClaw validation\n\t\tif dmPolicy == \"open\" && !containsString(allowFrom, \"*\") {\n\t\t\tallowFrom = []string{\"*\"}\n\t\t}\n\n\t\tcfg.Discord = &DiscordConfig{\n\t\t\tEnabled: channels.Discord.Enabled,\n\t\t\tToken: &SecretInput{Source: \"env\", Provider: \"default\", ID: \"DISCORD_BOT_TOKEN\"},\n\t\t\tAllowFrom: allowFrom,\n\t\t\tDmPolicy: dmPolicy,\n\t\t\tMentionRequired: channels.Discord.MentionRequired,\n\t\t}\n\t}\n\n\t// Convert Telegram configuration\n\tif channels.Telegram != nil {\n\t\tcfg.Telegram = &TelegramConfig{\n\t\t\tEnabled: channels.Telegram.Enabled,\n\t\t\tToken: channels.Telegram.Token,\n\t\t\tAllowedUsers: channels.Telegram.AllowedUsers,\n\t\t}\n\t}\n\n\t// Convert Slack configuration\n\tif channels.Slack != nil {\n\t\tcfg.Slack = &SlackConfig{\n\t\t\tEnabled: channels.Slack.Enabled,\n\t\t\tToken: channels.Slack.Token,\n\t\t}\n\t}\n\n\treturn cfg\n}\n\n// filterSkills filters skills based on allow/deny lists\nfunc (c *ConfigConverter) filterSkills(skills []string, allowed []string, denied []string) []string {\n\tif len(skills) == 0 {\n\t\treturn skills\n\t}\n\n\t// Build allowed set\n\tallowedSet := make(map[string]bool)\n\tfor _, s := range allowed {\n\t\tallowedSet[s] = true\n\t}\n\n\t// Build denied set\n\tdeniedSet := make(map[string]bool)\n\tfor _, s := range denied {\n\t\tdeniedSet[s] = true\n\t}\n\n\t// Filter skills\n\tresult := []string{}\n\tfor _, s := range skills {\n\t\t// Skip if denied\n\t\tif deniedSet[s] {\n\t\t\tcontinue\n\t\t}\n\t\t// If allowed list exists, only keep allowed skills\n\t\tif len(allowedSet) > 0 && !allowedSet[s] {\n\t\t\tcontinue\n\t\t}\n\t\tresult = append(result, s)\n\t}\n\n\treturn result\n}\n\n// ============================================================\n// Harness Interface Conversion Methods\n// ============================================================\n\n// ConvertModelHarness converts ModelHarnessInterface to OpenClaw models config.\nfunc (c *ConfigConverter) ConvertModelHarness(harness handler.ModelHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tmodelConfig := &ModelsConfig{\n\t\tProviders: map[string]*ModelProviderConfig{},\n\t}\n\n\tprovider := harness.GetProvider()\n\tmodelConfig.Providers[provider] = &ModelProviderConfig{\n\t\tBaseURL: harness.GetEndpoint(),\n\t\tAPIKey: harness.GetAPIKeyRef(),\n\t\tModels: c.convertModelList(harness.GetAllowedModels()),\n\t}\n\n\treturn json.MarshalIndent(modelConfig, \"\", \" \")\n}\n\n// ConvertSkillsHarness converts SkillsHarnessInterface to OpenClaw skills config.\n// For v2026.5.7: Skills are per-agent, this returns endpoint info only.\nfunc (c *ConfigConverter) ConvertSkillsHarness(harness handler.SkillsHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\t// For v2026.5.7: Only endpoint and localPath are valid\n\tskillsConfig := &SkillsConfig{\n\t\tEndpoint: harness.GetEndpoint(),\n\t\tLocalPath: \"\", // LocalPath not available from interface\n\t}\n\n\treturn json.MarshalIndent(skillsConfig, \"\", \" \")\n}\n\n// ConvertMemoryHarness converts MemoryHarnessInterface to OpenClaw memory config.\nfunc (c *ConfigConverter) ConvertMemoryHarness(harness handler.MemoryHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tmemoryConfig := &MemoryConfig{\n\t\tType: harness.GetType(),\n\t\tEndpoint: harness.GetEndpoint(),\n\t\tTTL: harness.GetTTL(),\n\t\tPersistence: harness.IsPersistenceEnabled(),\n\t}\n\n\treturn json.MarshalIndent(memoryConfig, \"\", \" \")\n}\n\n// ConvertSandboxHarness converts SandboxHarnessInterface to OpenClaw sandbox config.\n// For External Sandbox, generates plugins configuration for harness-bridge plugin.\n// For Embedded Sandbox, generates local sandbox configuration.\nfunc (c *ConfigConverter) ConvertSandboxHarness(harness handler.SandboxHarnessInterface, pluginDir string) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tsandboxConfig := &SandboxConfig{\n\t\tMode: string(harness.GetMode()),\n\t}\n\n\tif harness.IsExternal() {\n\t\t// External Sandbox: Configure harness-bridge plugin path\n\t\tsandboxConfig.Plugins = &PluginsConfig{\n\t\t\tLoad: &PluginsLoadConfig{\n\t\t\t\tPaths: []string{pluginDir},\n\t\t\t},\n\t\t}\n\t\tsandboxConfig.Endpoint = harness.GetEndpoint()\n\t\tsandboxConfig.Timeout = harness.GetTimeout()\n\t} else {\n\t\t// Embedded Sandbox: Local execution (no plugin needed)\n\t\tsandboxConfig.Mode = \"embedded\"\n\t}\n\n\treturn json.MarshalIndent(sandboxConfig, \"\", \" \")\n}\n\n// ============================================================\n// Helper Conversion Methods\n// ============================================================\n\n// convertAgentDefaultsFromHarness converts harness to agent defaults configuration.\nfunc (c *ConfigConverter) convertAgentDefaultsFromHarness(harnessCfg *handler.HarnessConfig) *AgentDefaultsConfig {\n\tdefaults := &AgentDefaultsConfig{}\n\n\tif harnessCfg.Model != nil {\n\t\tdefaults.Model = &AgentModelConfig{\n\t\t\tPrimary: harnessCfg.Model.Provider + \"/\" + harnessCfg.Model.DefaultModel,\n\t\t}\n\t}\n\n\treturn defaults\n}\n\n// convertModelsConfig converts harness model config to OpenClaw models section.\nfunc (c *ConfigConverter) convertModelsConfig(harnessCfg *handler.HarnessConfig, agentConfig *AgentConfigJSON) *ModelsConfig {\n\tif harnessCfg == nil || harnessCfg.Model == nil {\n\t\treturn nil\n\t}\n\n\tprovider := harnessCfg.Model.Provider\n\tmodelsConfig := &ModelsConfig{\n\t\tProviders: map[string]*ModelProviderConfig{},\n\t}\n\n\t// Determine merge mode from agentConfig\n\t// If agentConfig.Models.MergeModels is true, use \"merge\" mode\n\t// Otherwise, use \"\" (default) which means providers are the only source\n\tif agentConfig != nil && agentConfig.Models != nil && agentConfig.Models.MergeModels {\n\t\tmodelsConfig.Mode = \"merge\"\n\t}\n\n\t// Determine API type based on provider\n\tapiType := \"openai-completions\"\n\tif provider == \"anthropic\" {\n\t\tapiType = \"anthropic-messages\"\n\t}\n\n\t// Get API key from environment variable (uppercase provider name)\n\t// Controller injects PROVIDER_API_KEY from Secret as Pod env var\n\t// e.g., DEEPSEEK_API_KEY, ANTHROPIC_API_KEY\n\tapiKeyEnvVar := fmt.Sprintf(\"%s_API_KEY\", strings.ToUpper(provider))\n\n\tmodelsConfig.Providers[provider] = &ModelProviderConfig{\n\t\tBaseURL: harnessCfg.Model.Endpoint,\n\t\tAPIKey: apiKeyEnvVar,\n\t\tAPI: apiType,\n\t\tModels: c.convertModelListFromSpec(harnessCfg.Model.Models),\n\t}\n\n\treturn modelsConfig\n}\n\n// convertSkillsConfig converts harness skills config to OpenClaw skills section.\n// For v2026.5.7: Skills are per-agent, not global. This config is for endpoint discovery only.\nfunc (c *ConfigConverter) convertSkillsConfig(harnessCfg *handler.HarnessConfig) *SkillsConfig {\n\tif harnessCfg == nil || harnessCfg.Skills == nil {\n\t\treturn nil\n\t}\n\n\t// For v2026.5.7: Only endpoint and localPath are valid\n\t// Skills are defined at agent level (agents.defaults.skills, agents.list[].skills)\n\treturn &SkillsConfig{\n\t\tEndpoint: harnessCfg.Skills.Endpoint,\n\t\tLocalPath: harnessCfg.Skills.LocalPath,\n\t}\n}\n\n// convertMemoryConfig converts harness memory config to OpenClaw memory section.\nfunc (c *ConfigConverter) convertMemoryConfig(harnessCfg *handler.HarnessConfig) *MemoryConfig {\n\tif harnessCfg == nil || harnessCfg.Memory == nil {\n\t\treturn nil\n\t}\n\n\treturn &MemoryConfig{\n\t\tType: harnessCfg.Memory.Type,\n\t\tEndpoint: harnessCfg.Memory.Endpoint,\n\t\tTTL: int64(harnessCfg.Memory.TTL),\n\t\tPersistence: harnessCfg.Memory.PersistenceEnabled,\n\t}\n}\n\n// convertSandboxConfig converts harness sandbox config to OpenClaw sandbox section.\n// For External Sandbox, configures plugins path for harness-bridge plugin.\nfunc (c *ConfigConverter) convertSandboxConfig(harnessCfg *handler.HarnessConfig) *SandboxConfig {\n\tif harnessCfg == nil || harnessCfg.Sandbox == nil {\n\t\treturn nil\n\t}\n\n\tsandboxConfig := &SandboxConfig{\n\t\tMode: string(harnessCfg.Sandbox.Mode),\n\t}\n\n\tif harnessCfg.Sandbox.Mode == v1.SandboxModeExternal {\n\t\t// External Sandbox: Configure plugins path for harness-bridge plugin\n\t\tpluginDir := \"/etc/aiagent/plugins/harness-bridge\"\n\t\tsandboxConfig.Endpoint = harnessCfg.Sandbox.Endpoint\n\t\tsandboxConfig.Timeout = int64(harnessCfg.Sandbox.Timeout)\n\t\tsandboxConfig.Plugins = &PluginsConfig{\n\t\t\tLoad: &PluginsLoadConfig{\n\t\t\t\tPaths: []string{pluginDir},\n\t\t\t},\n\t\t}\n\t}\n\n\treturn sandboxConfig\n}\n\n// convertModelList converts string array to model definitions.\nfunc (c *ConfigConverter) convertModelList(models []string) []*ModelDefinitionConfig {\n\tif len(models) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*ModelDefinitionConfig, len(models))\n\tfor i, name := range models {\n\t\tresult[i] = &ModelDefinitionConfig{\n\t\t\tID: name,\n\t\t\tName: name,\n\t\t}\n\t}\n\treturn result\n}\n\n// convertModelListFromSpec converts spec model items to model definitions.\nfunc (c *ConfigConverter) convertModelListFromSpec(models []v1.ModelConfig) []*ModelDefinitionConfig {\n\tif len(models) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*ModelDefinitionConfig, len(models))\n\tfor i, m := range models {\n\t\tresult[i] = &ModelDefinitionConfig{\n\t\t\tID: m.Name,\n\t\t\tName: m.Name,\n\t\t\tContextWindow: int(m.ContextWindow),\n\t\t}\n\t}\n\treturn result\n}\n\n// convertSkillInfoList converts SkillInfo to OpenClaw skill items.\nfunc (c *ConfigConverter) convertSkillInfoList(skills []handler.SkillInfo) []*SkillItemConfig {\n\tif len(skills) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*SkillItemConfig, len(skills))\n\tfor i, s := range skills {\n\t\tresult[i] = &SkillItemConfig{\n\t\t\tName: s.Name,\n\t\t\tVersion: s.Version,\n\t\t\tPath: s.Path,\n\t\t\tAllowed: s.Allowed,\n\t\t}\n\t}\n\treturn result\n}\n\n// convertSkillItemsFromSpec converts spec skill items to OpenClaw skill items.\nfunc (c *ConfigConverter) convertSkillItemsFromSpec(skills []v1.SkillConfig) []*SkillItemConfig {\n\tif len(skills) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*SkillItemConfig, len(skills))\n\tfor i, s := range skills {\n\t\tresult[i] = &SkillItemConfig{\n\t\t\tName: s.Name,\n\t\t\tVersion: s.Version,\n\t\t\tAllowed: s.Allowed,\n\t\t}\n\t}\n\treturn result\n}\n\n// applySpecOverrides applies HarnessOverrideSpec from AIAgentSpec.\nfunc (c *ConfigConverter) applySpecOverrides(cfg *AgentConfig, override v1.HarnessOverrideSpec) {\n\t// Apply skills overrides\n\tfor _, skillsOverride := range override.Skills {\n\t\tcfg.Skills = append(cfg.Skills, skillsOverride.AllowedSkills...)\n\t}\n\n\t// Apply model overrides\n\tfor _, modelOverride := range override.Model {\n\t\tif len(modelOverride.AllowedModels) > 0 {\n\t\t\tif cfg.Model == nil {\n\t\t\t\tcfg.Model = &AgentModelConfig{}\n\t\t\t}\n\t\t\tif cfg.Model.Primary == \"\" && len(modelOverride.AllowedModels) > 0 {\n\t\t\t\tcfg.Model.Primary = modelOverride.AllowedModels[0]\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ============================================================\n// OpenClaw Config Types (matching TypeScript types)\n// ============================================================\n\n// OpenClawConfig represents the full OpenClaw config file structure.\ntype OpenClawConfig struct {\n\tAgents *AgentsConfig `json:\"agents,omitempty\"`\n\tModels *ModelsConfig `json:\"models,omitempty\"`\n\tSkills *SkillsConfig `json:\"skills,omitempty\"`\n\tTools *ToolsConfig `json:\"tools,omitempty\"`\n\tMemory *MemoryConfig `json:\"memory,omitempty\"`\n\tGateway *GatewayConfig `json:\"gateway,omitempty\"`\n\tChannels *ChannelsConfig `json:\"channels,omitempty\"`\n\tSandbox *SandboxConfig `json:\"sandbox,omitempty\"`\n\tPlugins *PluginsConfig `json:\"plugins,omitempty\"`\n}\n\n// AgentsConfig represents agents configuration.\ntype AgentsConfig struct {\n\tDefaults *AgentDefaultsConfig `json:\"defaults,omitempty\"`\n\tList []*AgentConfig `json:\"list,omitempty\"`\n}\n\n// AgentDefaultsConfig represents default agent configuration for OpenClaw v2026.5.7.\n// Schema: model, skills, workspace (identity and thinking are NOT supported at defaults level)\ntype AgentDefaultsConfig struct {\n\tModel *AgentModelConfig `json:\"model,omitempty\"`\n\tSkills []string `json:\"skills,omitempty\"`\n\tWorkspace string `json:\"workspace,omitempty\"`\n}\n\n// AgentConfig represents a single agent configuration for OpenClaw v2026.5.7.\n// Schema: id (required), name, description, workspace, model, skills, thinking, agents, etc.\n// Note: identity is NOT a top-level field in agents.list[]\ntype AgentConfig struct {\n\tID string `json:\"id\"`\n\tName string `json:\"name,omitempty\"`\n\tDescription string `json:\"description,omitempty\"`\n\tWorkspace string `json:\"workspace,omitempty\"`\n\tModel *AgentModelConfig `json:\"model,omitempty\"`\n\tSkills []string `json:\"skills,omitempty\"`\n\tThinking string `json:\"thinking,omitempty\"`\n\tAgents []string `json:\"agents,omitempty\"`\n}\n\n// AgentModelConfig represents agent model configuration.\ntype AgentModelConfig struct {\n\tPrimary string `json:\"primary,omitempty\"`\n\tFallbacks []string `json:\"fallbacks,omitempty\"`\n\tTemperature float64 `json:\"temperature,omitempty\"`\n\tMaxTokens int `json:\"maxTokens,omitempty\"`\n}\n\n// AgentToolsConfig represents agent tools configuration.\ntype AgentToolsConfig struct {\n\tAllow []string `json:\"allow,omitempty\"`\n\tDeny []string `json:\"deny,omitempty\"`\n}\n\n// IdentityConfig represents agent identity configuration.\ntype IdentityConfig struct {\n\tName string `json:\"name,omitempty\"`\n\tAvatar string `json:\"avatar,omitempty\"`\n}\n\n// SubagentsConfigOpenClaw represents sub-agent configuration.\ntype SubagentsConfigOpenClaw struct {\n\tAllowAgents []string `json:\"allowAgents,omitempty\"`\n\tModel *AgentModelConfig `json:\"model,omitempty\"`\n\tRequireAgentID bool `json:\"requireAgentId,omitempty\"`\n}\n\n// ModelsConfig represents models configuration for OpenClaw v2026.5.7.\n// Schema: mode, providers\ntype ModelsConfig struct {\n\tMode string `json:\"mode,omitempty\"`\n\tProviders map[string]*ModelProviderConfig `json:\"providers,omitempty\"`\n}\n\n// ModelsMergeModeConfig represents models merge mode configuration in agentConfig.\n// When mergeModels is true, OpenClaw will merge these providers with built-in providers.\ntype ModelsMergeModeConfig struct {\n\t// MergeModels indicates whether to merge with built-in model providers\n\t// If true, OpenClaw config will have models.mode = \"merge\"\n\t// If false or unset, providers will be the only source (mode = \"\" or \"override\")\n\tMergeModels bool `json:\"mergeModels,omitempty\"`\n}\n\n// ModelProviderConfig represents a model provider.\ntype ModelProviderConfig struct {\n\tBaseURL string `json:\"baseUrl,omitempty\"`\n\tAPIKey string `json:\"apiKey,omitempty\"`\n\tAPI string `json:\"api,omitempty\"` // \"openai-completions\" or \"anthropic-messages\"\n\tModels []*ModelDefinitionConfig `json:\"models,omitempty\"`\n}\n\n// ModelDefinitionConfig represents a model definition.\ntype ModelDefinitionConfig struct {\n\tID string `json:\"id\"`\n\tName string `json:\"name,omitempty\"`\n\tContextWindow int `json:\"contextWindow,omitempty\"`\n}\n\n// SkillsConfig represents skills configuration for OpenClaw v2026.5.7.\n// Schema: endpoint, localPath (no \"hubType\" or \"skills\" array - skills are per-agent)\n// Note: Skills are defined at agent level (agents.defaults.skills, agents.list[].skills)\n// The skills config here is only for plugin/endpoint discovery if using external skills hub.\ntype SkillsConfig struct {\n\tEndpoint string `json:\"endpoint,omitempty\"`\n\tLocalPath string `json:\"localPath,omitempty\"`\n}\n\n// SkillItemConfig represents a skill item.\ntype SkillItemConfig struct {\n\tName string `json:\"name\"`\n\tVersion string `json:\"version,omitempty\"`\n\tPath string `json:\"path,omitempty\"`\n\tAllowed bool `json:\"allowed,omitempty\"`\n}\n\n// MemoryConfig represents memory configuration.\ntype MemoryConfig struct {\n\tType string `json:\"type,omitempty\"`\n\tEndpoint string `json:\"endpoint,omitempty\"`\n\tTTL int64 `json:\"ttl,omitempty\"`\n\tPersistence bool `json:\"persistence,omitempty\"`\n}\n\n// GatewayConfig represents gateway configuration for OpenClaw v2026.5.7.\n// Schema: mode, bind, port, auth (object with mode/token), controlUi, trustedProxies\ntype GatewayConfig struct {\n\tMode string `json:\"mode,omitempty\"`\n\tBind string `json:\"bind,omitempty\"`\n\tPort int `json:\"port,omitempty\"`\n\tAuth *GatewayAuth `json:\"auth,omitempty\"`\n\tControlUI *ControlUIConfig `json:\"controlUi,omitempty\"`\n\tTrustedProxies []string `json:\"trustedProxies,omitempty\"`\n}\n\n// GatewayAuth represents gateway authentication for v2026.5.7.\ntype GatewayAuth struct {\n\tMode string `json:\"mode,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n}\n\n// ControlUIConfig represents Control UI configuration.\ntype ControlUIConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tBasePath string `json:\"basePath,omitempty\"`\n\tAllowedOrigins []string `json:\"allowedOrigins,omitempty\"`\n}\n\n// ToolsConfig represents tools configuration.\ntype ToolsConfig struct {\n\tAllow []string `json:\"allow,omitempty\"`\n\tDeny []string `json:\"deny,omitempty\"`\n}\n\n// ChannelsConfig represents channels configuration.\ntype ChannelsConfig struct {\n\tTelegram *TelegramConfig `json:\"telegram,omitempty\"`\n\tDiscord *DiscordConfig `json:\"discord,omitempty\"`\n\tSlack *SlackConfig `json:\"slack,omitempty\"`\n}\n\n// TelegramConfig represents Telegram channel config.\ntype TelegramConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n\tAllowedUsers []string `json:\"allowedUsers,omitempty\"` // Telegram user IDs whitelist\n}\n\n// SecretInput represents a secret reference for OpenClaw v2026.5.7.\n// Schema: source (env|file|value), provider (default), id (env var name or file path)\ntype SecretInput struct {\n\tSource string `json:\"source\"` // \"env\" | \"file\" | \"value\"\n\tProvider string `json:\"provider,omitempty\"` // \"default\"\n\tID string `json:\"id\"` // Environment variable name or file path\n}\n\n// DiscordConfig represents Discord channel config for OpenClaw v2026.5.7.\n// Schema: enabled, token (env var name), dmPolicy, allowFrom, mentionRequired\ntype DiscordConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken *SecretInput `json:\"token,omitempty\"` // Secret ref for DISCORD_BOT_TOKEN\n\tAllowFrom []string `json:\"allowFrom,omitempty\"`\n\tDmPolicy string `json:\"dmPolicy,omitempty\"` // \"all\" | \"pairing\" | \"allowlist\" | \"disabled\"\n\tMentionRequired bool `json:\"mentionRequired,omitempty\"`\n\tGuilds map[string]interface{} `json:\"guilds,omitempty\"`\n}\n\n// SlackConfig represents Slack channel config.\ntype SlackConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n}\n\n// HarnessSection represents harness-only config section.\ntype HarnessSection struct {\n\tModel *ModelsConfig `json:\"model,omitempty\"`\n\tSkills *SkillsConfig `json:\"skills,omitempty\"`\n\tMemory *MemoryConfig `json:\"memory,omitempty\"`\n}\n\n// SandboxConfig represents sandbox configuration.\n// For External Sandbox, includes plugins configuration for harness-bridge.\ntype SandboxConfig struct {\n\t// Mode: \"external\" or \"embedded\"\n\tMode string `json:\"mode\"`\n\n\t// Endpoint for External Sandbox (HTTP API URL)\n\tEndpoint string `json:\"endpoint,omitempty\"`\n\n\t// Timeout for sandbox operations (seconds)\n\tTimeout int64 `json:\"timeout,omitempty\"`\n\n\t// Plugins configuration for External Sandbox\n\tPlugins *PluginsConfig `json:\"plugins,omitempty\"`\n}\n\n// PluginEntry represents a single plugin entry in plugins.entries.\ntype PluginEntry struct {\n\tEnabled bool `json:\"enabled\"`\n}\n\n// PluginsConfig represents OpenClaw plugins configuration.\ntype PluginsConfig struct {\n\t// Load configuration for plugin discovery\n\tLoad *PluginsLoadConfig `json:\"load,omitempty\"`\n\t// Entries lists enabled plugins (e.g., {\"discord\": {\"enabled\": true}})\n\tEntries map[string]PluginEntry `json:\"entries,omitempty\"`\n}\n\n// PluginsLoadConfig specifies plugin discovery paths.\ntype PluginsLoadConfig struct {\n\t// Paths to search for plugins\n\tPaths []string `json:\"paths,omitempty\"`\n}\n\n// containsString checks if a string is in a slice.\nfunc containsString(slice []string, s string) bool {\n\tfor _, item := range slice {\n\t\tif item == s {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}"}, "f70d3362ae9d09e5e5a1c6760b674475bb4da045": {"t": 1785755068.8446863, "n": 38133, "h": "8caec13f0fe14292", "c": "// Package openclaw provides configuration conversion for OpenClaw framework.\n// This converter transforms AIAgentSpec + HarnessConfig into OpenClaw JSON config.\n//\n// Configuration Sources:\n// - agentConfig (from AIAgentSpec): Gateway port, internal agents, overrides\n// - HarnessConfig (from AgentRuntime): Models, Skills, Memory (shared)\npackage openclaw\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"aiagent/api/v1\"\n\t\"aiagent/pkg/handler\"\n)\n\n// ConfigConverter converts AIAgentSpec and HarnessConfig to OpenClaw config.\ntype ConfigConverter struct{}\n\n// NewConfigConverter creates a new config converter.\nfunc NewConfigConverter() *ConfigConverter {\n\treturn &ConfigConverter{}\n}\n\n// ============================================================\n// Core Conversion Methods (called by Handler)\n// ============================================================\n\n// ConvertToOpenClawConfig generates full openclaw.json from AIAgentSpec and HarnessConfig.\n// This is the primary method called by GenerateFrameworkConfig.\nfunc (c *ConfigConverter) ConvertToOpenClawConfig(spec *v1.AIAgentSpec, harnessCfg *handler.HarnessConfig) ([]byte, error) {\n\tconfig := &OpenClawConfig{\n\t\tAgents: &AgentsConfig{},\n\t\tGateway: &GatewayConfig{\n\t\t\tMode: \"local\",\n\t\t},\n\t}\n\n\t// 1. Parse agentConfig JSON (agent-specific configuration)\n\tvar agentConfig *AgentConfigJSON\n\tif spec.AgentConfig != nil && spec.AgentConfig.Raw != nil {\n\t\tparsedConfig, err := c.parseAgentConfigJSON(spec.AgentConfig.Raw)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse agentConfig: %w\", err)\n\t\t}\n\t\tagentConfig = parsedConfig\n\n\t\t// Apply agentConfig to OpenClaw config\n\t\tif agentConfig.Gateway != nil {\n\t\t\tconfig.Gateway = c.convertGatewayInstanceConfig(agentConfig.Gateway)\n\t\t}\n\t\tif agentConfig.InternalAgents != nil {\n\t\t\tconfig.Agents = c.convertInternalAgentsConfig(agentConfig.InternalAgents)\n\t\t}\n\t\t// Apply overrides after agents are set\n\t\tif agentConfig.Overrides != nil {\n\t\t\tc.applyAgentOverrides(config, agentConfig.Overrides)\n\t\t}\n\t\t// Apply channels configuration (Discord, Telegram, Slack)\n\t\tif agentConfig.Channels != nil {\n\t\t\tconfig.Channels = c.convertChannelsConfig(agentConfig.Channels)\n\t\t\t// Set plugins.entries for enabled channels so Gateway loads channel plugins\n\t\t\tpluginEntries := make(map[string]PluginEntry)\n\t\t\tif agentConfig.Channels.Discord != nil && agentConfig.Channels.Discord.Enabled {\n\t\t\t\tpluginEntries[\"discord\"] = PluginEntry{Enabled: true}\n\t\t\t}\n\t\t\tif agentConfig.Channels.Telegram != nil && agentConfig.Channels.Telegram.Enabled {\n\t\t\t\tpluginEntries[\"telegram\"] = PluginEntry{Enabled: true}\n\t\t\t}\n\t\t\tif agentConfig.Channels.Slack != nil && agentConfig.Channels.Slack.Enabled {\n\t\t\t\tpluginEntries[\"slack\"] = PluginEntry{Enabled: true}\n\t\t\t}\n\t\t\tif len(pluginEntries) > 0 {\n\t\t\t\tif config.Plugins == nil {\n\t\t\t\t\tconfig.Plugins = &PluginsConfig{}\n\t\t\t\t}\n\t\t\t\tconfig.Plugins.Entries = pluginEntries\n\t\t\t}\n\t\t}\n\t}\n\n\t// 2. Apply Harness configuration (shared platform capabilities)\n\tif harnessCfg != nil {\n\t\tconfig.Agents.Defaults = c.convertAgentDefaultsFromHarness(harnessCfg)\n\t\t// Pass agentConfig for merge mode configuration\n\t\tconfig.Models = c.convertModelsConfig(harnessCfg, agentConfig)\n\t\tconfig.Skills = c.convertSkillsConfig(harnessCfg)\n\t\t// Memory config not supported in OpenClaw v2026.5.7 config schema\n\t\t// OpenClaw uses internal session management\n\t\t// config.Memory = c.convertMemoryConfig(harnessCfg)\n\t\tconfig.Sandbox = c.convertSandboxConfig(harnessCfg)\n\t\t// If external sandbox, set plugins path\n\t\tif config.Sandbox != nil && config.Sandbox.Mode == \"external\" && config.Sandbox.Plugins != nil {\n\t\t\tconfig.Plugins = config.Sandbox.Plugins\n\t\t}\n\t}\n\n\t// 3. Create main agent entry from AIAgentSpec metadata\n\tmainAgent := &AgentConfig{\n\t\tID: spec.Description,\n\t\tName: spec.Description,\n\t\tDescription: spec.Description,\n\t}\n\n\t// Apply model from harness (use provider/model format)\n\tif harnessCfg != nil && harnessCfg.Model != nil {\n\t\tmainAgent.Model = &AgentModelConfig{\n\t\t\tPrimary: harnessCfg.Model.Provider + \"/\" + harnessCfg.Model.DefaultModel,\n\t\t}\n\t}\n\n\t// Apply skills from harness\n\tif harnessCfg != nil && harnessCfg.Skills != nil {\n\t\tfor _, skill := range harnessCfg.Skills.Skills {\n\t\t\tif skill.Allowed {\n\t\t\t\tmainAgent.Skills = append(mainAgent.Skills, skill.Name)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add main agent to list if no internal agents defined\n\tif len(config.Agents.List) == 0 {\n\t\tconfig.Agents.List = []*AgentConfig{mainAgent}\n\t}\n\n\treturn json.MarshalIndent(config, \"\", \" \")\n}\n\n// ConvertAgentSpec generates AgentConfig section for a single agent.\nfunc (c *ConfigConverter) ConvertAgentSpec(spec *v1.AIAgentSpec, harnessCfg *handler.HarnessConfig) (*AgentConfig, error) {\n\tagentCfg := &AgentConfig{\n\t\tID: spec.Description,\n\t\tName: spec.Description,\n\t\tDescription: spec.Description,\n\t}\n\n\t// Apply model from harness (use provider/model format)\n\tif harnessCfg != nil && harnessCfg.Model != nil {\n\t\tagentCfg.Model = &AgentModelConfig{\n\t\t\tPrimary: harnessCfg.Model.Provider + \"/\" + harnessCfg.Model.DefaultModel,\n\t\t}\n\t}\n\n\t// Apply skills from harness\n\tif harnessCfg != nil && harnessCfg.Skills != nil {\n\t\tfor _, skill := range harnessCfg.Skills.Skills {\n\t\t\tif skill.Allowed {\n\t\t\t\tagentCfg.Skills = append(agentCfg.Skills, skill.Name)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Apply overrides from spec\n\tc.applySpecOverrides(agentCfg, spec.HarnessOverride)\n\n\treturn agentCfg, nil\n}\n\n// ConvertHarnessConfig generates harness-specific config sections.\n// Note: This method doesn't have agentConfig context, so models.mode won't be set.\n// Use ConvertToOpenClawConfig for full config generation with agentConfig support.\nfunc (c *ConfigConverter) ConvertHarnessConfig(harnessCfg *handler.HarnessConfig) ([]byte, error) {\n\tharnessSection := &HarnessSection{\n\t\tModel: c.convertModelsConfig(harnessCfg, nil), // nil agentConfig means no merge mode\n\t\tSkills: c.convertSkillsConfig(harnessCfg),\n\t\tMemory: c.convertMemoryConfig(harnessCfg),\n\t}\n\n\treturn json.MarshalIndent(harnessSection, \"\", \" \")\n}\n\n// ============================================================\n// agentConfig Parsing (AIAgentSpec.agentConfig.Raw)\n// ============================================================\n\n// AgentConfigJSON represents the agentConfig JSON structure from AIAgentSpec.\n// ONLY contains agent-specific configuration (not shared Harness capabilities).\n// Fields use JSON names matching the schema defined earlier.\ntype AgentConfigJSON struct {\n\t// Gateway process configuration for this agent instance\n\tGateway *GatewayInstanceConfigJSON `json:\"gateway\"`\n\n\t// Internal sub-agents managed by this Gateway (keyed as \"agents\" in JSON)\n\tInternalAgents *InternalAgentsConfigJSON `json:\"agents,omitempty\"`\n\n\t// Per-agent overrides of inherited Harness defaults\n\tOverrides *AgentOverridesConfigJSON `json:\"overrides,omitempty\"`\n\n\t// Channel integrations (Discord, Telegram, Slack)\n\tChannels *ChannelsInstanceConfigJSON `json:\"channels,omitempty\"`\n\n\t// Models merge mode configuration (OpenClaw-specific)\n\t// When mergeModels is true, OpenClaw will merge harness providers with built-in providers\n\tModels *ModelsMergeModeConfig `json:\"models,omitempty\"`\n}\n\n// ChannelsInstanceConfigJSON - Channel integrations for this Gateway instance\ntype ChannelsInstanceConfigJSON struct {\n\t// Discord channel configuration\n\tDiscord *DiscordChannelConfigJSON `json:\"discord,omitempty\"`\n\n\t// Telegram channel configuration\n\tTelegram *TelegramChannelConfigJSON `json:\"telegram,omitempty\"`\n\n\t// Slack channel configuration\n\tSlack *SlackChannelConfigJSON `json:\"slack,omitempty\"`\n}\n\n// DiscordChannelConfigJSON - Discord channel configuration from agentConfig\ntype DiscordChannelConfigJSON struct {\n\t// Enable Discord integration\n\tEnabled bool `json:\"enabled,omitempty\"`\n\n\t// Discord bot token (can reference secret or use direct value)\n\tToken string `json:\"token,omitempty\"`\n\n\t// Secret reference for bot token (recommended for production)\n\tTokenSecretRef string `json:\"tokenSecretRef,omitempty\"`\n\n\t// Allowed Discord user IDs (whitelist)\n\t// Accept both \"allowedUsers\" (legacy) and \"allowFrom\" (v2026.5.7)\n\tAllowedUsers []string `json:\"allowedUsers,omitempty\"`\n\tAllowFrom []string `json:\"allowFrom,omitempty\"`\n\n\t// DM policy for direct messages\n\tDmPolicy string `json:\"dmPolicy,omitempty\"`\n\n\t// Allowed Discord guild/server IDs (whitelist)\n\tAllowedGuilds []string `json:\"allowedGuilds,omitempty\"`\n\n\t// Allowed Discord channel IDs (whitelist)\n\tAllowedChannels []string `json:\"allowedChannels,omitempty\"`\n\n\t// Bot command prefix\n\tCommandPrefix string `json:\"commandPrefix,omitempty\"`\n\n\t// Require @mention to trigger bot response\n\tMentionRequired bool `json:\"mentionRequired,omitempty\"`\n}\n\n// TelegramChannelConfigJSON - Telegram channel configuration from agentConfig\ntype TelegramChannelConfigJSON struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n\tTokenSecretRef string `json:\"tokenSecretRef,omitempty\"`\n\tAllowedUsers []string `json:\"allowedUsers,omitempty\"`\n}\n\n// SlackChannelConfigJSON - Slack channel configuration from agentConfig\ntype SlackChannelConfigJSON struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n\tTokenSecretRef string `json:\"tokenSecretRef,omitempty\"`\n\tAllowedChannels []string `json:\"allowedChannels,omitempty\"`\n}\n\n// GatewayInstanceConfigJSON - Gateway process specific config\ntype GatewayInstanceConfigJSON struct {\n\t// Port for this Gateway instance\n\tPort int `json:\"port,omitempty\"`\n\n\t// Bind address: loopback, lan, auto\n\tBind string `json:\"bind,omitempty\"`\n\n\t// Authentication for this Gateway instance\n\tAuth *GatewayAuthConfigJSON `json:\"auth,omitempty\"`\n\n\t// Control UI settings\n\tControlUI *GatewayControlUIConfigJSON `json:\"controlUi,omitempty\"`\n\n\t// Trusted proxy IPs for X-Forwarded-For\n\tTrustedProxies []string `json:\"trustedProxies,omitempty\"`\n}\n\n// GatewayAuthConfigJSON - Gateway authentication\ntype GatewayAuthConfigJSON struct {\n\t// Auth mode: none, token, password\n\tMode string `json:\"mode,omitempty\"`\n\n\t// Token for token mode\n\tToken string `json:\"token,omitempty\"`\n}\n\n// GatewayControlUIConfigJSON - Control UI for this instance\ntype GatewayControlUIConfigJSON struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tBasePath string `json:\"basePath,omitempty\"`\n\tAllowedOrigins []string `json:\"allowedOrigins,omitempty\"`\n}\n\n// InternalAgentsConfigJSON - Sub-agents inside Gateway\ntype InternalAgentsConfigJSON struct {\n\t// Default settings for internal agents\n\tDefaults *InternalAgentDefaultsJSON `json:\"defaults,omitempty\"`\n\n\t// List of internal sub-agents\n\tList []*InternalAgentDefinitionJSON `json:\"list,omitempty\"`\n}\n\n// InternalAgentDefaultsJSON - Default settings for sub-agents\ntype InternalAgentDefaultsJSON struct {\n\t// Override inherited model\n\tModel string `json:\"model,omitempty\"`\n\n\t// Thinking level: off, minimal, low, medium, high, xhigh, adaptive\n\tThinkingDefault string `json:\"thinkingDefault,omitempty\"`\n\n\t// Reasoning visibility: on, off, stream\n\tReasoningDefault string `json:\"reasoningDefault,omitempty\"`\n\n\t// Fast mode default\n\tFastModeDefault bool `json:\"fastModeDefault,omitempty\"`\n\n\t// Identity for all sub-agents\n\tIdentity *AgentIdentityJSON `json:\"identity,omitempty\"`\n}\n\n// InternalAgentDefinitionJSON - Single internal sub-agent\ntype InternalAgentDefinitionJSON struct {\n\t// Agent identifier\n\tID string `json:\"id\"`\n\n\t// Display name\n\tName string `json:\"name,omitempty\"`\n\n\t// Override model for this sub-agent\n\tModel string `json:\"model,omitempty\"`\n\n\t// Allowed skills (subset of Harness.Skills)\n\tSkills []string `json:\"skills,omitempty\"`\n\n\t// Identity for this sub-agent\n\tIdentity *AgentIdentityJSON `json:\"identity,omitempty\"`\n\n\t// Tool restrictions (subset)\n\tTools *ToolsOverrideJSON `json:\"tools,omitempty\"`\n\n\t// Subagent spawning config\n\tSubagents *SubagentsConfigJSON `json:\"subagents,omitempty\"`\n}\n\n// AgentIdentityJSON - Agent name and avatar\ntype AgentIdentityJSON struct {\n\tName string `json:\"name,omitempty\"`\n\tAvatar string `json:\"avatar,omitempty\"`\n}\n\n// ToolsOverrideJSON - Per-agent tool restrictions\ntype ToolsOverrideJSON struct {\n\tAllow []string `json:\"allow,omitempty\"`\n\tDeny []string `json:\"deny,omitempty\"`\n}\n\n// SubagentsConfigJSON - Sub-agent spawning rules\ntype SubagentsConfigJSON struct {\n\t// Allowed sub-agent IDs (\"*\" = any)\n\tAllowAgents []string `json:\"allowAgents,omitempty\"`\n\n\t// Default model for spawned sub-agents\n\tModel string `json:\"model,omitempty\"`\n\n\t// Require explicit agentId in spawn calls\n\tRequireAgentID bool `json:\"requireAgentId,omitempty\"`\n}\n\n// AgentOverridesConfigJSON - Override inherited Harness defaults\ntype AgentOverridesConfigJSON struct {\n\t// Override model selection\n\tModel string `json:\"model,omitempty\"`\n\n\t// Skills subset (cannot add new, only restrict)\n\tAllowedSkills []string `json:\"allowedSkills,omitempty\"`\n\tDeniedSkills []string `json:\"deniedSkills,omitempty\"`\n\n\t// Model fallback chain override\n\tModelFallbacks []string `json:\"modelFallbacks,omitempty\"`\n}\n\n// parseAgentConfigJSON parses raw agentConfig JSON\nfunc (c *ConfigConverter) parseAgentConfigJSON(raw []byte) (*AgentConfigJSON, error) {\n\tif len(raw) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tagentConfig := &AgentConfigJSON{}\n\tif err := json.Unmarshal(raw, agentConfig); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid agentConfig JSON: %w\", err)\n\t}\n\n\treturn agentConfig, nil\n}\n\n// ParseAgentConfig is the public method for Handler to call\nfunc (c *ConfigConverter) ParseAgentConfig(raw []byte) (*AgentConfigJSON, error) {\n\treturn c.parseAgentConfigJSON(raw)\n}\n\n// convertGatewayInstanceConfig converts agentConfig.Gateway to OpenClaw GatewayConfig\n// For v2026.5.7: use bind (not host), auth object (not authMode)\nfunc (c *ConfigConverter) convertGatewayInstanceConfig(gateway *GatewayInstanceConfigJSON) *GatewayConfig {\n\tcfg := &GatewayConfig{\n\t\tMode: \"local\",\n\t}\n\n\tif gateway.Port > 0 {\n\t\tcfg.Port = gateway.Port\n\t}\n\tif gateway.Bind != \"\" {\n\t\tcfg.Bind = gateway.Bind\n\t}\n\tif gateway.Auth != nil {\n\t\tcfg.Auth = &GatewayAuth{\n\t\t\tMode: gateway.Auth.Mode,\n\t\t\tToken: gateway.Auth.Token,\n\t\t}\n\t}\n\tif gateway.ControlUI != nil {\n\t\tcfg.ControlUI = &ControlUIConfig{\n\t\t\tEnabled: gateway.ControlUI.Enabled,\n\t\t\tBasePath: gateway.ControlUI.BasePath,\n\t\t\tAllowedOrigins: gateway.ControlUI.AllowedOrigins,\n\t\t}\n\t}\n\tcfg.TrustedProxies = gateway.TrustedProxies\n\n\treturn cfg\n}\n\n// convertInternalAgentsConfig converts agentConfig.InternalAgents to OpenClaw AgentsConfig\n// For OpenClaw v2026.5.7: Only supported fields are model, skills, workspace at defaults level\nfunc (c *ConfigConverter) convertInternalAgentsConfig(internal *InternalAgentsConfigJSON) *AgentsConfig {\n\tagents := &AgentsConfig{}\n\n\t// Convert defaults - only use model, skills, workspace (v2026.5.7 schema)\n\tif internal.Defaults != nil {\n\t\tagents.Defaults = &AgentDefaultsConfig{}\n\t\tif internal.Defaults.Model != \"\" {\n\t\t\tagents.Defaults.Model = &AgentModelConfig{\n\t\t\t\tPrimary: internal.Defaults.Model,\n\t\t\t}\n\t\t}\n\t\t// Note: identity and thinkingDefault are NOT supported at defaults level in v2026.5.7\n\t}\n\n\t// Convert internal agent list - use v2026.5.7 schema fields\n\tfor _, def := range internal.List {\n\t\tagent := &AgentConfig{\n\t\t\tID: def.ID,\n\t\t\tName: def.Name,\n\t\t}\n\t\tif def.Model != \"\" {\n\t\t\tagent.Model = &AgentModelConfig{\n\t\t\t\tPrimary: def.Model,\n\t\t\t}\n\t\t}\n\t\tagent.Skills = def.Skills\n\t\t// Note: identity is NOT a top-level field in agents.list[] for v2026.5.7\n\t\tagents.List = append(agents.List, agent)\n\t}\n\n\treturn agents\n}\n\n// applyAgentOverrides applies agentConfig.Overrides to OpenClaw config\nfunc (c *ConfigConverter) applyAgentOverrides(config *OpenClawConfig, overrides *AgentOverridesConfigJSON) {\n\t// Override default model\n\tif overrides.Model != \"\" && config.Agents.Defaults != nil {\n\t\tconfig.Agents.Defaults.Model = &AgentModelConfig{\n\t\t\tPrimary: overrides.Model,\n\t\t}\n\t}\n\n\t// Filter skills for all agents\n\tif len(overrides.AllowedSkills) > 0 || len(overrides.DeniedSkills) > 0 {\n\t\tfor _, agent := range config.Agents.List {\n\t\t\tagent.Skills = c.filterSkills(agent.Skills, overrides.AllowedSkills, overrides.DeniedSkills)\n\t\t}\n\t}\n\n\t// Set model fallbacks\n\tif len(overrides.ModelFallbacks) > 0 && config.Agents.Defaults != nil && config.Agents.Defaults.Model != nil {\n\t\tconfig.Agents.Defaults.Model.Fallbacks = overrides.ModelFallbacks\n\t}\n}\n\n// convertChannelsConfig converts agentConfig.Channels to OpenClaw ChannelsConfig\nfunc (c *ConfigConverter) convertChannelsConfig(channels *ChannelsInstanceConfigJSON) *ChannelsConfig {\n\tif channels == nil {\n\t\treturn nil\n\t}\n\n\tcfg := &ChannelsConfig{}\n\n\t// Convert Discord configuration (v2026.5.12 schema)\n\t// Token uses SecretInput pointing to env var: DISCORD_BOT_TOKEN\n\t// ConfigDaemon resolves tokenSecretRef → writes token to hostPath\n\t// Handler extracts token from hostPath → injects as subprocess env var\n\t//\n\t// dmPolicy options (from OpenClaw zod-schema.providers-core.ts):\n\t// - \"pairing\" - requires pairing before DM (default)\n\t// - \"allowlist\" - requires specific user IDs in allowFrom\n\t// - \"open\" - allows all users (requires \"*\" in allowFrom)\n\t// - \"disabled\" - disables DM\n\tif channels.Discord != nil {\n\t\t// Use AllowFrom if provided directly, otherwise use AllowedUsers\n\t\tallowFrom := channels.Discord.AllowFrom\n\t\tif len(allowFrom) == 0 && len(channels.Discord.AllowedUsers) > 0 {\n\t\t\tallowFrom = channels.Discord.AllowedUsers\n\t\t}\n\n\t\tdmPolicy := channels.Discord.DmPolicy\n\t\t// Normalize dmPolicy: \"all\" (legacy) → \"open\", empty → default based on allowFrom\n\t\tif dmPolicy == \"all\" {\n\t\t\tdmPolicy = \"open\"\n\t\t\t// For \"open\" policy, OpenClaw requires \"*\" in allowFrom\n\t\t\tallowFrom = []string{\"*\"}\n\t\t} else if dmPolicy == \"\" {\n\t\t\t// Default based on allowFrom content:\n\t\t\t// - [\"*\"] → \"open\" (allow all users)\n\t\t\t// - specific IDs → \"allowlist\" (allow listed users)\n\t\t\t// - empty → \"pairing\" (require pairing)\n\t\t\tif containsString(allowFrom, \"*\") {\n\t\t\t\tdmPolicy = \"open\"\n\t\t\t} else if len(allowFrom) > 0 {\n\t\t\t\tdmPolicy = \"allowlist\"\n\t\t\t} else {\n\t\t\t\tdmPolicy = \"pairing\"\n\t\t\t}\n\t\t}\n\t\t// For \"open\" policy without \"*\", add \"*\" to satisfy OpenClaw validation\n\t\tif dmPolicy == \"open\" && !containsString(allowFrom, \"*\") {\n\t\t\tallowFrom = []string{\"*\"}\n\t\t}\n\n\t\tcfg.Discord = &DiscordConfig{\n\t\t\tEnabled: channels.Discord.Enabled,\n\t\t\tToken: &SecretInput{Source: \"env\", Provider: \"default\", ID: \"DISCORD_BOT_TOKEN\"},\n\t\t\tAllowFrom: allowFrom,\n\t\t\tDmPolicy: dmPolicy,\n\t\t\tMentionRequired: channels.Discord.MentionRequired,\n\t\t}\n\t}\n\n\t// Convert Telegram configuration\n\tif channels.Telegram != nil {\n\t\tcfg.Telegram = &TelegramConfig{\n\t\t\tEnabled: channels.Telegram.Enabled,\n\t\t\tToken: channels.Telegram.Token,\n\t\t\tAllowedUsers: channels.Telegram.AllowedUsers,\n\t\t}\n\t}\n\n\t// Convert Slack configuration\n\tif channels.Slack != nil {\n\t\tcfg.Slack = &SlackConfig{\n\t\t\tEnabled: channels.Slack.Enabled,\n\t\t\tToken: channels.Slack.Token,\n\t\t}\n\t}\n\n\treturn cfg\n}\n\n// filterSkills filters skills based on allow/deny lists\nfunc (c *ConfigConverter) filterSkills(skills []string, allowed []string, denied []string) []string {\n\tif len(skills) == 0 {\n\t\treturn skills\n\t}\n\n\t// Build allowed set\n\tallowedSet := make(map[string]bool)\n\tfor _, s := range allowed {\n\t\tallowedSet[s] = true\n\t}\n\n\t// Build denied set\n\tdeniedSet := make(map[string]bool)\n\tfor _, s := range denied {\n\t\tdeniedSet[s] = true\n\t}\n\n\t// Filter skills\n\tresult := []string{}\n\tfor _, s := range skills {\n\t\t// Skip if denied\n\t\tif deniedSet[s] {\n\t\t\tcontinue\n\t\t}\n\t\t// If allowed list exists, only keep allowed skills\n\t\tif len(allowedSet) > 0 && !allowedSet[s] {\n\t\t\tcontinue\n\t\t}\n\t\tresult = append(result, s)\n\t}\n\n\treturn result\n}\n\n// ============================================================\n// Harness Interface Conversion Methods\n// ============================================================\n\n// ConvertModelHarness converts ModelHarnessInterface to OpenClaw models config.\nfunc (c *ConfigConverter) ConvertModelHarness(harness handler.ModelHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tmodelConfig := &ModelsConfig{\n\t\tProviders: map[string]*ModelProviderConfig{},\n\t}\n\n\tprovider := harness.GetProvider()\n\tmodelConfig.Providers[provider] = &ModelProviderConfig{\n\t\tBaseURL: harness.GetEndpoint(),\n\t\tAPIKey: harness.GetAPIKeyRef(),\n\t\tModels: c.convertModelList(harness.GetAllowedModels()),\n\t}\n\n\treturn json.MarshalIndent(modelConfig, \"\", \" \")\n}\n\n// ConvertSkillsHarness converts SkillsHarnessInterface to OpenClaw skills config.\n// For v2026.5.7: Skills are per-agent, this returns endpoint info only.\nfunc (c *ConfigConverter) ConvertSkillsHarness(harness handler.SkillsHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\t// For v2026.5.7: Only endpoint and localPath are valid\n\tskillsConfig := &SkillsConfig{\n\t\tEndpoint: harness.GetEndpoint(),\n\t\tLocalPath: \"\", // LocalPath not available from interface\n\t}\n\n\treturn json.MarshalIndent(skillsConfig, \"\", \" \")\n}\n\n// ConvertMemoryHarness converts MemoryHarnessInterface to OpenClaw memory config.\nfunc (c *ConfigConverter) ConvertMemoryHarness(harness handler.MemoryHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tmemoryConfig := &MemoryConfig{\n\t\tType: harness.GetType(),\n\t\tEndpoint: harness.GetEndpoint(),\n\t\tTTL: harness.GetTTL(),\n\t\tPersistence: harness.IsPersistenceEnabled(),\n\t}\n\n\treturn json.MarshalIndent(memoryConfig, \"\", \" \")\n}\n\n// ConvertSandboxHarness converts SandboxHarnessInterface to OpenClaw sandbox config.\n// For External Sandbox, generates plugins configuration for harness-bridge plugin.\n// For Embedded Sandbox, generates local sandbox configuration.\nfunc (c *ConfigConverter) ConvertSandboxHarness(harness handler.SandboxHarnessInterface, pluginDir string) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tsandboxConfig := &SandboxConfig{\n\t\tMode: string(harness.GetMode()),\n\t}\n\n\tif harness.IsExternal() {\n\t\t// External Sandbox: Configure harness-bridge plugin path\n\t\tsandboxConfig.Plugins = &PluginsConfig{\n\t\t\tLoad: &PluginsLoadConfig{\n\t\t\t\tPaths: []string{pluginDir},\n\t\t\t},\n\t\t}\n\t\tsandboxConfig.Endpoint = harness.GetEndpoint()\n\t\tsandboxConfig.Timeout = harness.GetTimeout()\n\t} else {\n\t\t// Embedded Sandbox: Local execution (no plugin needed)\n\t\tsandboxConfig.Mode = \"embedded\"\n\t}\n\n\treturn json.MarshalIndent(sandboxConfig, \"\", \" \")\n}\n\n// ============================================================\n// Helper Conversion Methods\n// ============================================================\n\n// convertAgentDefaultsFromHarness converts harness to agent defaults configuration.\nfunc (c *ConfigConverter) convertAgentDefaultsFromHarness(harnessCfg *handler.HarnessConfig) *AgentDefaultsConfig {\n\tdefaults := &AgentDefaultsConfig{}\n\n\tif harnessCfg.Model != nil {\n\t\tdefaults.Model = &AgentModelConfig{\n\t\t\tPrimary: harnessCfg.Model.Provider + \"/\" + harnessCfg.Model.DefaultModel,\n\t\t}\n\t}\n\n\treturn defaults\n}\n\n// convertModelsConfig converts harness model config to OpenClaw models section.\nfunc (c *ConfigConverter) convertModelsConfig(harnessCfg *handler.HarnessConfig, agentConfig *AgentConfigJSON) *ModelsConfig {\n\tif harnessCfg == nil || harnessCfg.Model == nil {\n\t\treturn nil\n\t}\n\n\tprovider := harnessCfg.Model.Provider\n\tmodelsConfig := &ModelsConfig{\n\t\tProviders: map[string]*ModelProviderConfig{},\n\t}\n\n\t// Determine merge mode from agentConfig\n\t// If agentConfig.Models.MergeModels is true, use \"merge\" mode\n\t// Otherwise, use \"\" (default) which means providers are the only source\n\tif agentConfig != nil && agentConfig.Models != nil && agentConfig.Models.MergeModels {\n\t\tmodelsConfig.Mode = \"merge\"\n\t}\n\n\t// Determine API type based on provider\n\tapiType := \"openai-completions\"\n\tif provider == \"anthropic\" {\n\t\tapiType = \"anthropic-messages\"\n\t}\n\n\t// Get API key from environment variable (uppercase provider name)\n\t// Controller injects PROVIDER_API_KEY from Secret as Pod env var\n\t// e.g., DEEPSEEK_API_KEY, ANTHROPIC_API_KEY\n\tapiKeyEnvVar := fmt.Sprintf(\"%s_API_KEY\", strings.ToUpper(provider))\n\n\tmodelsConfig.Providers[provider] = &ModelProviderConfig{\n\t\tBaseURL: harnessCfg.Model.Endpoint,\n\t\tAPIKey: apiKeyEnvVar,\n\t\tAPI: apiType,\n\t\tModels: c.convertModelListFromSpec(harnessCfg.Model.Models),\n\t}\n\n\treturn modelsConfig\n}\n\n// convertSkillsConfig converts harness skills config to OpenClaw skills section.\n// For v2026.5.7: Skills are per-agent, not global. This config is for endpoint discovery only.\nfunc (c *ConfigConverter) convertSkillsConfig(harnessCfg *handler.HarnessConfig) *SkillsConfig {\n\tif harnessCfg == nil || harnessCfg.Skills == nil {\n\t\treturn nil\n\t}\n\n\t// For v2026.5.7: Only endpoint and localPath are valid\n\t// Skills are defined at agent level (agents.defaults.skills, agents.list[].skills)\n\treturn &SkillsConfig{\n\t\tEndpoint: harnessCfg.Skills.Endpoint,\n\t\tLocalPath: harnessCfg.Skills.LocalPath,\n\t}\n}\n\n// convertMemoryConfig converts harness memory config to OpenClaw memory section.\nfunc (c *ConfigConverter) convertMemoryConfig(harnessCfg *handler.HarnessConfig) *MemoryConfig {\n\tif harnessCfg == nil || harnessCfg.Memory == nil {\n\t\treturn nil\n\t}\n\n\treturn &MemoryConfig{\n\t\tType: harnessCfg.Memory.Type,\n\t\tEndpoint: harnessCfg.Memory.Endpoint,\n\t\tTTL: int64(harnessCfg.Memory.TTL),\n\t\tPersistence: harnessCfg.Memory.PersistenceEnabled,\n\t}\n}\n\n// convertSandboxConfig converts harness sandbox config to OpenClaw sandbox section.\n// For External Sandbox, configures plugins path for harness-bridge plugin.\nfunc (c *ConfigConverter) convertSandboxConfig(harnessCfg *handler.HarnessConfig) *SandboxConfig {\n\tif harnessCfg == nil || harnessCfg.Sandbox == nil {\n\t\treturn nil\n\t}\n\n\tsandboxConfig := &SandboxConfig{\n\t\tMode: string(harnessCfg.Sandbox.Mode),\n\t}\n\n\tif harnessCfg.Sandbox.Mode == v1.SandboxModeExternal {\n\t\t// External Sandbox: Configure plugins path for harness-bridge plugin\n\t\tpluginDir := \"/etc/aiagent/plugins/harness-bridge\"\n\t\tsandboxConfig.Endpoint = harnessCfg.Sandbox.Endpoint\n\t\tsandboxConfig.Timeout = int64(harnessCfg.Sandbox.Timeout)\n\t\tsandboxConfig.Plugins = &PluginsConfig{\n\t\t\tLoad: &PluginsLoadConfig{\n\t\t\t\tPaths: []string{pluginDir},\n\t\t\t},\n\t\t}\n\t}\n\n\treturn sandboxConfig\n}\n\n// convertModelList converts string array to model definitions.\nfunc (c *ConfigConverter) convertModelList(models []string) []*ModelDefinitionConfig {\n\tif len(models) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*ModelDefinitionConfig, len(models))\n\tfor i, name := range models {\n\t\tresult[i] = &ModelDefinitionConfig{\n\t\t\tID: name,\n\t\t\tName: name,\n\t\t}\n\t}\n\treturn result\n}\n\n// convertModelListFromSpec converts spec model items to model definitions.\nfunc (c *ConfigConverter) convertModelListFromSpec(models []v1.ModelConfig) []*ModelDefinitionConfig {\n\tif len(models) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*ModelDefinitionConfig, len(models))\n\tfor i, m := range models {\n\t\tresult[i] = &ModelDefinitionConfig{\n\t\t\tID: m.Name,\n\t\t\tName: m.Name,\n\t\t\tContextWindow: int(m.ContextWindow),\n\t\t}\n\t}\n\treturn result\n}\n\n// convertSkillInfoList converts SkillInfo to OpenClaw skill items.\nfunc (c *ConfigConverter) convertSkillInfoList(skills []handler.SkillInfo) []*SkillItemConfig {\n\tif len(skills) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*SkillItemConfig, len(skills))\n\tfor i, s := range skills {\n\t\tresult[i] = &SkillItemConfig{\n\t\t\tName: s.Name,\n\t\t\tVersion: s.Version,\n\t\t\tPath: s.Path,\n\t\t\tAllowed: s.Allowed,\n\t\t}\n\t}\n\treturn result\n}\n\n// convertSkillItemsFromSpec converts spec skill items to OpenClaw skill items.\nfunc (c *ConfigConverter) convertSkillItemsFromSpec(skills []v1.SkillConfig) []*SkillItemConfig {\n\tif len(skills) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*SkillItemConfig, len(skills))\n\tfor i, s := range skills {\n\t\tresult[i] = &SkillItemConfig{\n\t\t\tName: s.Name,\n\t\t\tVersion: s.Version,\n\t\t\tAllowed: s.Allowed,\n\t\t}\n\t}\n\treturn result\n}\n\n// applySpecOverrides applies HarnessOverrideSpec from AIAgentSpec.\nfunc (c *ConfigConverter) applySpecOverrides(cfg *AgentConfig, override v1.HarnessOverrideSpec) {\n\t// Apply skills overrides\n\tfor _, skillsOverride := range override.Skills {\n\t\tcfg.Skills = append(cfg.Skills, skillsOverride.AllowedSkills...)\n\t}\n\n\t// Apply model overrides\n\tfor _, modelOverride := range override.Model {\n\t\tif len(modelOverride.AllowedModels) > 0 {\n\t\t\tif cfg.Model == nil {\n\t\t\t\tcfg.Model = &AgentModelConfig{}\n\t\t\t}\n\t\t\tif cfg.Model.Primary == \"\" && len(modelOverride.AllowedModels) > 0 {\n\t\t\t\tcfg.Model.Primary = modelOverride.AllowedModels[0]\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ============================================================\n// OpenClaw Config Types (matching TypeScript types)\n// ============================================================\n\n// OpenClawConfig represents the full OpenClaw config file structure.\ntype OpenClawConfig struct {\n\tAgents *AgentsConfig `json:\"agents,omitempty\"`\n\tModels *ModelsConfig `json:\"models,omitempty\"`\n\tSkills *SkillsConfig `json:\"skills,omitempty\"`\n\tTools *ToolsConfig `json:\"tools,omitempty\"`\n\tMemory *MemoryConfig `json:\"memory,omitempty\"`\n\tGateway *GatewayConfig `json:\"gateway,omitempty\"`\n\tChannels *ChannelsConfig `json:\"channels,omitempty\"`\n\tSandbox *SandboxConfig `json:\"sandbox,omitempty\"`\n\tPlugins *PluginsConfig `json:\"plugins,omitempty\"`\n}\n\n// AgentsConfig represents agents configuration.\ntype AgentsConfig struct {\n\tDefaults *AgentDefaultsConfig `json:\"defaults,omitempty\"`\n\tList []*AgentConfig `json:\"list,omitempty\"`\n}\n\n// AgentDefaultsConfig represents default agent configuration for OpenClaw v2026.5.7.\n// Schema: model, skills, workspace (identity and thinking are NOT supported at defaults level)\ntype AgentDefaultsConfig struct {\n\tModel *AgentModelConfig `json:\"model,omitempty\"`\n\tSkills []string `json:\"skills,omitempty\"`\n\tWorkspace string `json:\"workspace,omitempty\"`\n}\n\n// AgentConfig represents a single agent configuration for OpenClaw v2026.5.7.\n// Schema: id (required), name, description, workspace, model, skills, thinking, agents, etc.\n// Note: identity is NOT a top-level field in agents.list[]\ntype AgentConfig struct {\n\tID string `json:\"id\"`\n\tName string `json:\"name,omitempty\"`\n\tDescription string `json:\"description,omitempty\"`\n\tWorkspace string `json:\"workspace,omitempty\"`\n\tModel *AgentModelConfig `json:\"model,omitempty\"`\n\tSkills []string `json:\"skills,omitempty\"`\n\tThinking string `json:\"thinking,omitempty\"`\n\tAgents []string `json:\"agents,omitempty\"`\n}\n\n// AgentModelConfig represents agent model configuration.\ntype AgentModelConfig struct {\n\tPrimary string `json:\"primary,omitempty\"`\n\tFallbacks []string `json:\"fallbacks,omitempty\"`\n\tTemperature float64 `json:\"temperature,omitempty\"`\n\tMaxTokens int `json:\"maxTokens,omitempty\"`\n}\n\n// AgentToolsConfig represents agent tools configuration.\ntype AgentToolsConfig struct {\n\tAllow []string `json:\"allow,omitempty\"`\n\tDeny []string `json:\"deny,omitempty\"`\n}\n\n// IdentityConfig represents agent identity configuration.\ntype IdentityConfig struct {\n\tName string `json:\"name,omitempty\"`\n\tAvatar string `json:\"avatar,omitempty\"`\n}\n\n// SubagentsConfigOpenClaw represents sub-agent configuration.\ntype SubagentsConfigOpenClaw struct {\n\tAllowAgents []string `json:\"allowAgents,omitempty\"`\n\tModel *AgentModelConfig `json:\"model,omitempty\"`\n\tRequireAgentID bool `json:\"requireAgentId,omitempty\"`\n}\n\n// ModelsConfig represents models configuration for OpenClaw v2026.5.7.\n// Schema: mode, providers\ntype ModelsConfig struct {\n\tMode string `json:\"mode,omitempty\"`\n\tProviders map[string]*ModelProviderConfig `json:\"providers,omitempty\"`\n}\n\n// ModelsMergeModeConfig represents models merge mode configuration in agentConfig.\n// When mergeModels is true, OpenClaw will merge these providers with built-in providers.\ntype ModelsMergeModeConfig struct {\n\t// MergeModels indicates whether to merge with built-in model providers\n\t// If true, OpenClaw config will have models.mode = \"merge\"\n\t// If false or unset, providers will be the only source (mode = \"\" or \"override\")\n\tMergeModels bool `json:\"mergeModels,omitempty\"`\n}\n\n// ModelProviderConfig represents a model provider.\ntype ModelProviderConfig struct {\n\tBaseURL string `json:\"baseUrl,omitempty\"`\n\tAPIKey string `json:\"apiKey,omitempty\"`\n\tAPI string `json:\"api,omitempty\"` // \"openai-completions\" or \"anthropic-messages\"\n\tModels []*ModelDefinitionConfig `json:\"models,omitempty\"`\n}\n\n// ModelDefinitionConfig represents a model definition.\ntype ModelDefinitionConfig struct {\n\tID string `json:\"id\"`\n\tName string `json:\"name,omitempty\"`\n\tContextWindow int `json:\"contextWindow,omitempty\"`\n}\n\n// SkillsConfig represents skills configuration for OpenClaw v2026.5.7.\n// Schema: endpoint, localPath (no \"hubType\" or \"skills\" array - skills are per-agent)\n// Note: Skills are defined at agent level (agents.defaults.skills, agents.list[].skills)\n// The skills config here is only for plugin/endpoint discovery if using external skills hub.\ntype SkillsConfig struct {\n\tEndpoint string `json:\"endpoint,omitempty\"`\n\tLocalPath string `json:\"localPath,omitempty\"`\n}\n\n// SkillItemConfig represents a skill item.\ntype SkillItemConfig struct {\n\tName string `json:\"name\"`\n\tVersion string `json:\"version,omitempty\"`\n\tPath string `json:\"path,omitempty\"`\n\tAllowed bool `json:\"allowed,omitempty\"`\n}\n\n// MemoryConfig represents memory configuration.\ntype MemoryConfig struct {\n\tType string `json:\"type,omitempty\"`\n\tEndpoint string `json:\"endpoint,omitempty\"`\n\tTTL int64 `json:\"ttl,omitempty\"`\n\tPersistence bool `json:\"persistence,omitempty\"`\n}\n\n// GatewayConfig represents gateway configuration for OpenClaw v2026.5.7.\n// Schema: mode, bind, port, auth (object with mode/token), controlUi, trustedProxies\ntype GatewayConfig struct {\n\tMode string `json:\"mode,omitempty\"`\n\tBind string `json:\"bind,omitempty\"`\n\tPort int `json:\"port,omitempty\"`\n\tAuth *GatewayAuth `json:\"auth,omitempty\"`\n\tControlUI *ControlUIConfig `json:\"controlUi,omitempty\"`\n\tTrustedProxies []string `json:\"trustedProxies,omitempty\"`\n}\n\n// GatewayAuth represents gateway authentication for v2026.5.7.\ntype GatewayAuth struct {\n\tMode string `json:\"mode,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n}\n\n// ControlUIConfig represents Control UI configuration.\ntype ControlUIConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tBasePath string `json:\"basePath,omitempty\"`\n\tAllowedOrigins []string `json:\"allowedOrigins,omitempty\"`\n}\n\n// ToolsConfig represents tools configuration.\ntype ToolsConfig struct {\n\tAllow []string `json:\"allow,omitempty\"`\n\tDeny []string `json:\"deny,omitempty\"`\n}\n\n// ChannelsConfig represents channels configuration.\ntype ChannelsConfig struct {\n\tTelegram *TelegramConfig `json:\"telegram,omitempty\"`\n\tDiscord *DiscordConfig `json:\"discord,omitempty\"`\n\tSlack *SlackConfig `json:\"slack,omitempty\"`\n}\n\n// TelegramConfig represents Telegram channel config.\ntype TelegramConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n\tAllowedUsers []string `json:\"allowedUsers,omitempty\"` // Telegram user IDs whitelist\n}\n\n// SecretInput represents a secret reference for OpenClaw v2026.5.7.\n// Schema: source (env|file|value), provider (default), id (env var name or file path)\ntype SecretInput struct {\n\tSource string `json:\"source\"` // \"env\" | \"file\" | \"value\"\n\tProvider string `json:\"provider,omitempty\"` // \"default\"\n\tID string `json:\"id\"` // Environment variable name or file path\n}\n\n// DiscordConfig represents Discord channel config for OpenClaw v2026.5.7.\n// Schema: enabled, token (env var name), dmPolicy, allowFrom, mentionRequired\ntype DiscordConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken *SecretInput `json:\"token,omitempty\"` // Secret ref for DISCORD_BOT_TOKEN\n\tAllowFrom []string `json:\"allowFrom,omitempty\"`\n\tDmPolicy string `json:\"dmPolicy,omitempty\"` // \"all\" | \"pairing\" | \"allowlist\" | \"disabled\"\n\tMentionRequired bool `json:\"mentionRequired,omitempty\"`\n\tGuilds map[string]interface{} `json:\"guilds,omitempty\"`\n}\n\n// SlackConfig represents Slack channel config.\ntype SlackConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n}\n\n// HarnessSection represents harness-only config section.\ntype HarnessSection struct {\n\tModel *ModelsConfig `json:\"model,omitempty\"`\n\tSkills *SkillsConfig `json:\"skills,omitempty\"`\n\tMemory *MemoryConfig `json:\"memory,omitempty\"`\n}\n\n// SandboxConfig represents sandbox configuration.\n// For External Sandbox, includes plugins configuration for harness-bridge.\ntype SandboxConfig struct {\n\t// Mode: \"external\" or \"embedded\"\n\tMode string `json:\"mode\"`\n\n\t// Endpoint for External Sandbox (HTTP API URL)\n\tEndpoint string `json:\"endpoint,omitempty\"`\n\n\t// Timeout for sandbox operations (seconds)\n\tTimeout int64 `json:\"timeout,omitempty\"`\n\n\t// Plugins configuration for External Sandbox\n\tPlugins *PluginsConfig `json:\"plugins,omitempty\"`\n}\n\n// PluginEntry represents a single plugin entry in plugins.entries.\ntype PluginEntry struct {\n\tEnabled bool `json:\"enabled\"`\n}\n\n// PluginsConfig represents OpenClaw plugins configuration.\ntype PluginsConfig struct {\n\t// Load configuration for plugin discovery\n\tLoad *PluginsLoadConfig `json:\"load,omitempty\"`\n\t// Entries lists enabled plugins (e.g., {\"discord\": {\"enabled\": true}})\n\tEntries map[string]PluginEntry `json:\"entries,omitempty\"`\n}\n\n// PluginsLoadConfig specifies plugin discovery paths.\ntype PluginsLoadConfig struct {\n\t// Paths to search for plugins\n\tPaths []string `json:\"paths,omitempty\"`\n}\n\n// containsString checks if a string is in a slice.\nfunc containsString(slice []string, s string) bool {\n\tfor _, item := range slice {\n\t\tif item == s {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}"}, "f5e44f9a08a2d0d4f017b7f6092f620edbaa3884": {"t": 1785755071.4938617, "n": 37136, "h": "3e566984edd58e1a", "c": "// Package openclaw provides configuration conversion for OpenClaw framework.\n// This converter transforms AIAgentSpec + HarnessConfig into OpenClaw JSON config.\n//\n// Configuration Sources:\n// - agentConfig (from AIAgentSpec): Gateway port, internal agents, overrides\n// - HarnessConfig (from AgentRuntime): Models, Skills, Memory (shared)\npackage openclaw\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"aiagent/api/v1\"\n\t\"aiagent/pkg/handler\"\n)\n\n// ConfigConverter converts AIAgentSpec and HarnessConfig to OpenClaw config.\ntype ConfigConverter struct{}\n\n// NewConfigConverter creates a new config converter.\nfunc NewConfigConverter() *ConfigConverter {\n\treturn &ConfigConverter{}\n}\n\n// ============================================================\n// Core Conversion Methods (called by Handler)\n// ============================================================\n\n// ConvertToOpenClawConfig generates full openclaw.json from AIAgentSpec and HarnessConfig.\n// This is the primary method called by GenerateFrameworkConfig.\nfunc (c *ConfigConverter) ConvertToOpenClawConfig(spec *v1.AIAgentSpec, harnessCfg *handler.HarnessConfig) ([]byte, error) {\n\tconfig := &OpenClawConfig{\n\t\tAgents: &AgentsConfig{},\n\t\tGateway: &GatewayConfig{\n\t\t\tMode: \"local\",\n\t\t},\n\t}\n\n\t// 1. Parse agentConfig JSON (agent-specific configuration)\n\tvar agentConfig *AgentConfigJSON\n\tif spec.AgentConfig != nil && spec.AgentConfig.Raw != nil {\n\t\tparsedConfig, err := c.parseAgentConfigJSON(spec.AgentConfig.Raw)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse agentConfig: %w\", err)\n\t\t}\n\t\tagentConfig = parsedConfig\n\n\t\t// Apply agentConfig to OpenClaw config\n\t\tif agentConfig.Gateway != nil {\n\t\t\tconfig.Gateway = c.convertGatewayInstanceConfig(agentConfig.Gateway)\n\t\t}\n\t\tif agentConfig.InternalAgents != nil {\n\t\t\tconfig.Agents = c.convertInternalAgentsConfig(agentConfig.InternalAgents)\n\t\t}\n\t\t// Apply overrides after agents are set\n\t\tif agentConfig.Overrides != nil {\n\t\t\tc.applyAgentOverrides(config, agentConfig.Overrides)\n\t\t}\n\t\t// Apply channels configuration (Discord, Telegram, Slack)\n\t\tif agentConfig.Channels != nil {\n\t\t\tconfig.Channels = c.convertChannelsConfig(agentConfig.Channels)\n\t\t\t// Set plugins.entries for enabled channels so Gateway loads channel plugins\n\t\t\tpluginEntries := make(map[string]PluginEntry)\n\t\t\tif agentConfig.Channels.Discord != nil && agentConfig.Channels.Discord.Enabled {\n\t\t\t\tpluginEntries[\"discord\"] = PluginEntry{Enabled: true}\n\t\t\t}\n\t\t\tif agentConfig.Channels.Telegram != nil && agentConfig.Channels.Telegram.Enabled {\n\t\t\t\tpluginEntries[\"telegram\"] = PluginEntry{Enabled: true}\n\t\t\t}\n\t\t\tif agentConfig.Channels.Slack != nil && agentConfig.Channels.Slack.Enabled {\n\t\t\t\tpluginEntries[\"slack\"] = PluginEntry{Enabled: true}\n\t\t\t}\n\t\t\tif len(pluginEntries) > 0 {\n\t\t\t\tif config.Plugins == nil {\n\t\t\t\t\tconfig.Plugins = &PluginsConfig{}\n\t\t\t\t}\n\t\t\t\tconfig.Plugins.Entries = pluginEntries\n\t\t\t}\n\t\t}\n\t}\n\n\t// 2. Apply Harness configuration (shared platform capabilities)\n\tif harnessCfg != nil {\n\t\tconfig.Agents.Defaults = c.convertAgentDefaultsFromHarness(harnessCfg)\n\t\t// Pass agentConfig for merge mode configuration\n\t\tconfig.Models = c.convertModelsConfig(harnessCfg, agentConfig)\n\t\tconfig.Skills = c.convertSkillsConfig(harnessCfg)\n\t\t// Memory config not supported in OpenClaw v2026.5.7 config schema\n\t\t// OpenClaw uses internal session management\n\t\t// config.Memory = c.convertMemoryConfig(harnessCfg)\n\t\tconfig.Sandbox = c.convertSandboxConfig(harnessCfg)\n\t\t// If external sandbox, set plugins path\n\t\tif config.Sandbox != nil && config.Sandbox.Mode == \"external\" && config.Sandbox.Plugins != nil {\n\t\t\tconfig.Plugins = config.Sandbox.Plugins\n\t\t}\n\t}\n\n\t// 3. Create main agent entry from AIAgentSpec metadata\n\tmainAgent := &AgentConfig{\n\t\tID: spec.Description,\n\t\tName: spec.Description,\n\t\tDescription: spec.Description,\n\t}\n\n\t// Apply model from harness (use provider/model format)\n\tif harnessCfg != nil && harnessCfg.Model != nil {\n\t\tmainAgent.Model = &AgentModelConfig{\n\t\t\tPrimary: harnessCfg.Model.Provider + \"/\" + harnessCfg.Model.DefaultModel,\n\t\t}\n\t}\n\n\t// Apply skills from harness\n\tif harnessCfg != nil && harnessCfg.Skills != nil {\n\t\tfor _, skill := range harnessCfg.Skills.Skills {\n\t\t\tif skill.Allowed {\n\t\t\t\tmainAgent.Skills = append(mainAgent.Skills, skill.Name)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add main agent to list if no internal agents defined\n\tif len(config.Agents.List) == 0 {\n\t\tconfig.Agents.List = []*AgentConfig{mainAgent}\n\t}\n\n\treturn json.MarshalIndent(config, \"\", \" \")\n}\n\n// ConvertAgentSpec generates AgentConfig section for a single agent.\nfunc (c *ConfigConverter) ConvertAgentSpec(spec *v1.AIAgentSpec, harnessCfg *handler.HarnessConfig) (*AgentConfig, error) {\n\tagentCfg := &AgentConfig{\n\t\tID: spec.Description,\n\t\tName: spec.Description,\n\t\tDescription: spec.Description,\n\t}\n\n\t// Apply model from harness (use provider/model format)\n\tif harnessCfg != nil && harnessCfg.Model != nil {\n\t\tagentCfg.Model = &AgentModelConfig{\n\t\t\tPrimary: harnessCfg.Model.Provider + \"/\" + harnessCfg.Model.DefaultModel,\n\t\t}\n\t}\n\n\t// Apply skills from harness\n\tif harnessCfg != nil && harnessCfg.Skills != nil {\n\t\tfor _, skill := range harnessCfg.Skills.Skills {\n\t\t\tif skill.Allowed {\n\t\t\t\tagentCfg.Skills = append(agentCfg.Skills, skill.Name)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Apply overrides from spec\n\tc.applySpecOverrides(agentCfg, spec.HarnessOverride)\n\n\treturn agentCfg, nil\n}\n\n// ConvertHarnessConfig generates harness-specific config sections.\n// Note: This method doesn't have agentConfig context, so models.mode won't be set.\n// Use ConvertToOpenClawConfig for full config generation with agentConfig support.\nfunc (c *ConfigConverter) ConvertHarnessConfig(harnessCfg *handler.HarnessConfig) ([]byte, error) {\n\tharnessSection := &HarnessSection{\n\t\tModel: c.convertModelsConfig(harnessCfg, nil), // nil agentConfig means no merge mode\n\t\tSkills: c.convertSkillsConfig(harnessCfg),\n\t\tMemory: c.convertMemoryConfig(harnessCfg),\n\t}\n\n\treturn json.MarshalIndent(harnessSection, \"\", \" \")\n}\n\n// ============================================================\n// agentConfig Parsing (AIAgentSpec.agentConfig.Raw)\n// ============================================================\n\n// AgentConfigJSON represents the agentConfig JSON structure from AIAgentSpec.\n// ONLY contains agent-specific configuration (not shared Harness capabilities).\n// Fields use JSON names matching the schema defined earlier.\ntype AgentConfigJSON struct {\n\t// Gateway process configuration for this agent instance\n\tGateway *GatewayInstanceConfigJSON `json:\"gateway\"`\n\n\t// Internal sub-agents managed by this Gateway (keyed as \"agents\" in JSON)\n\tInternalAgents *InternalAgentsConfigJSON `json:\"agents,omitempty\"`\n\n\t// Per-agent overrides of inherited Harness defaults\n\tOverrides *AgentOverridesConfigJSON `json:\"overrides,omitempty\"`\n\n\t// Channel integrations (Discord, Telegram, Slack)\n\tChannels *ChannelsInstanceConfigJSON `json:\"channels,omitempty\"`\n\n\t// Models merge mode configuration (OpenClaw-specific)\n\t// When mergeModels is true, OpenClaw will merge harness providers with built-in providers\n\tModels *ModelsMergeModeConfig `json:\"models,omitempty\"`\n}\n\n// ChannelsInstanceConfigJSON - Channel integrations for this Gateway instance\ntype ChannelsInstanceConfigJSON struct {\n\t// Discord channel configuration\n\tDiscord *DiscordChannelConfigJSON `json:\"discord,omitempty\"`\n\n\t// Telegram channel configuration\n\tTelegram *TelegramChannelConfigJSON `json:\"telegram,omitempty\"`\n\n\t// Slack channel configuration\n\tSlack *SlackChannelConfigJSON `json:\"slack,omitempty\"`\n}\n\n// DiscordChannelConfigJSON - Discord channel configuration from agentConfig\ntype DiscordChannelConfigJSON struct {\n\t// Enable Discord integration\n\tEnabled bool `json:\"enabled,omitempty\"`\n\n\t// Discord bot token (can reference secret or use direct value)\n\tToken string `json:\"token,omitempty\"`\n\n\t// Secret reference for bot token (recommended for production)\n\tTokenSecretRef string `json:\"tokenSecretRef,omitempty\"`\n\n\t// Allowed Discord user IDs (whitelist)\n\t// Accept both \"allowedUsers\" (legacy) and \"allowFrom\" (v2026.5.7)\n\tAllowedUsers []string `json:\"allowedUsers,omitempty\"`\n\tAllowFrom []string `json:\"allowFrom,omitempty\"`\n\n\t// DM policy for direct messages\n\tDmPolicy string `json:\"dmPolicy,omitempty\"`\n\n\t// Allowed Discord guild/server IDs (whitelist)\n\tAllowedGuilds []string `json:\"allowedGuilds,omitempty\"`\n\n\t// Allowed Discord channel IDs (whitelist)\n\tAllowedChannels []string `json:\"allowedChannels,omitempty\"`\n\n\t// Bot command prefix\n\tCommandPrefix string `json:\"commandPrefix,omitempty\"`\n\n\t// Require @mention to trigger bot response\n\tMentionRequired bool `json:\"mentionRequired,omitempty\"`\n}\n\n// TelegramChannelConfigJSON - Telegram channel configuration from agentConfig\ntype TelegramChannelConfigJSON struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n\tTokenSecretRef string `json:\"tokenSecretRef,omitempty\"`\n\tAllowedUsers []string `json:\"allowedUsers,omitempty\"`\n}\n\n// SlackChannelConfigJSON - Slack channel configuration from agentConfig\ntype SlackChannelConfigJSON struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n\tTokenSecretRef string `json:\"tokenSecretRef,omitempty\"`\n\tAllowedChannels []string `json:\"allowedChannels,omitempty\"`\n}\n\n// GatewayInstanceConfigJSON - Gateway process specific config\ntype GatewayInstanceConfigJSON struct {\n\t// Port for this Gateway instance\n\tPort int `json:\"port,omitempty\"`\n\n\t// Bind address: loopback, lan, auto\n\tBind string `json:\"bind,omitempty\"`\n\n\t// Authentication for this Gateway instance\n\tAuth *GatewayAuthConfigJSON `json:\"auth,omitempty\"`\n\n\t// Control UI settings\n\tControlUI *GatewayControlUIConfigJSON `json:\"controlUi,omitempty\"`\n\n\t// Trusted proxy IPs for X-Forwarded-For\n\tTrustedProxies []string `json:\"trustedProxies,omitempty\"`\n}\n\n// GatewayAuthConfigJSON - Gateway authentication\ntype GatewayAuthConfigJSON struct {\n\t// Auth mode: none, token, password\n\tMode string `json:\"mode,omitempty\"`\n\n\t// Token for token mode\n\tToken string `json:\"token,omitempty\"`\n}\n\n// GatewayControlUIConfigJSON - Control UI for this instance\ntype GatewayControlUIConfigJSON struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tBasePath string `json:\"basePath,omitempty\"`\n\tAllowedOrigins []string `json:\"allowedOrigins,omitempty\"`\n}\n\n// InternalAgentsConfigJSON - Sub-agents inside Gateway\ntype InternalAgentsConfigJSON struct {\n\t// Default settings for internal agents\n\tDefaults *InternalAgentDefaultsJSON `json:\"defaults,omitempty\"`\n\n\t// List of internal sub-agents\n\tList []*InternalAgentDefinitionJSON `json:\"list,omitempty\"`\n}\n\n// InternalAgentDefaultsJSON - Default settings for sub-agents\ntype InternalAgentDefaultsJSON struct {\n\t// Override inherited model\n\tModel string `json:\"model,omitempty\"`\n\n\t// Thinking level: off, minimal, low, medium, high, xhigh, adaptive\n\tThinkingDefault string `json:\"thinkingDefault,omitempty\"`\n\n\t// Reasoning visibility: on, off, stream\n\tReasoningDefault string `json:\"reasoningDefault,omitempty\"`\n\n\t// Fast mode default\n\tFastModeDefault bool `json:\"fastModeDefault,omitempty\"`\n\n\t// Identity for all sub-agents\n\tIdentity *AgentIdentityJSON `json:\"identity,omitempty\"`\n}\n\n// InternalAgentDefinitionJSON - Single internal sub-agent\ntype InternalAgentDefinitionJSON struct {\n\t// Agent identifier\n\tID string `json:\"id\"`\n\n\t// Display name\n\tName string `json:\"name,omitempty\"`\n\n\t// Override model for this sub-agent\n\tModel string `json:\"model,omitempty\"`\n\n\t// Allowed skills (subset of Harness.Skills)\n\tSkills []string `json:\"skills,omitempty\"`\n\n\t// Identity for this sub-agent\n\tIdentity *AgentIdentityJSON `json:\"identity,omitempty\"`\n\n\t// Tool restrictions (subset)\n\tTools *ToolsOverrideJSON `json:\"tools,omitempty\"`\n\n\t// Subagent spawning config\n\tSubagents *SubagentsConfigJSON `json:\"subagents,omitempty\"`\n}\n\n// AgentIdentityJSON - Agent name and avatar\ntype AgentIdentityJSON struct {\n\tName string `json:\"name,omitempty\"`\n\tAvatar string `json:\"avatar,omitempty\"`\n}\n\n// ToolsOverrideJSON - Per-agent tool restrictions\ntype ToolsOverrideJSON struct {\n\tAllow []string `json:\"allow,omitempty\"`\n\tDeny []string `json:\"deny,omitempty\"`\n}\n\n// SubagentsConfigJSON - Sub-agent spawning rules\ntype SubagentsConfigJSON struct {\n\t// Allowed sub-agent IDs (\"*\" = any)\n\tAllowAgents []string `json:\"allowAgents,omitempty\"`\n\n\t// Default model for spawned sub-agents\n\tModel string `json:\"model,omitempty\"`\n\n\t// Require explicit agentId in spawn calls\n\tRequireAgentID bool `json:\"requireAgentId,omitempty\"`\n}\n\n// AgentOverridesConfigJSON - Override inherited Harness defaults\ntype AgentOverridesConfigJSON struct {\n\t// Override model selection\n\tModel string `json:\"model,omitempty\"`\n\n\t// Skills subset (cannot add new, only restrict)\n\tAllowedSkills []string `json:\"allowedSkills,omitempty\"`\n\tDeniedSkills []string `json:\"deniedSkills,omitempty\"`\n\n\t// Model fallback chain override\n\tModelFallbacks []string `json:\"modelFallbacks,omitempty\"`\n}\n\n// parseAgentConfigJSON parses raw agentConfig JSON\nfunc (c *ConfigConverter) parseAgentConfigJSON(raw []byte) (*AgentConfigJSON, error) {\n\tif len(raw) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tagentConfig := &AgentConfigJSON{}\n\tif err := json.Unmarshal(raw, agentConfig); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid agentConfig JSON: %w\", err)\n\t}\n\n\treturn agentConfig, nil\n}\n\n// ParseAgentConfig is the public method for Handler to call\nfunc (c *ConfigConverter) ParseAgentConfig(raw []byte) (*AgentConfigJSON, error) {\n\treturn c.parseAgentConfigJSON(raw)\n}\n\n// convertGatewayInstanceConfig converts agentConfig.Gateway to OpenClaw GatewayConfig\n// For v2026.5.7: use bind (not host), auth object (not authMode)\nfunc (c *ConfigConverter) convertGatewayInstanceConfig(gateway *GatewayInstanceConfigJSON) *GatewayConfig {\n\tcfg := &GatewayConfig{\n\t\tMode: \"local\",\n\t}\n\n\tif gateway.Port > 0 {\n\t\tcfg.Port = gateway.Port\n\t}\n\tif gateway.Bind != \"\" {\n\t\tcfg.Bind = gateway.Bind\n\t}\n\tif gateway.Auth != nil {\n\t\tcfg.Auth = &GatewayAuth{\n\t\t\tMode: gateway.Auth.Mode,\n\t\t\tToken: gateway.Auth.Token,\n\t\t}\n\t}\n\tif gateway.ControlUI != nil {\n\t\tcfg.ControlUI = &ControlUIConfig{\n\t\t\tEnabled: gateway.ControlUI.Enabled,\n\t\t\tBasePath: gateway.ControlUI.BasePath,\n\t\t\tAllowedOrigins: gateway.ControlUI.AllowedOrigins,\n\t\t}\n\t}\n\tcfg.TrustedProxies = gateway.TrustedProxies\n\n\treturn cfg\n}\n\n// convertInternalAgentsConfig converts agentConfig.InternalAgents to OpenClaw AgentsConfig\n// For OpenClaw v2026.5.7: Only supported fields are model, skills, workspace at defaults level\nfunc (c *ConfigConverter) convertInternalAgentsConfig(internal *InternalAgentsConfigJSON) *AgentsConfig {\n\tagents := &AgentsConfig{}\n\n\t// Convert defaults - only use model, skills, workspace (v2026.5.7 schema)\n\tif internal.Defaults != nil {\n\t\tagents.Defaults = &AgentDefaultsConfig{}\n\t\tif internal.Defaults.Model != \"\" {\n\t\t\tagents.Defaults.Model = &AgentModelConfig{\n\t\t\t\tPrimary: internal.Defaults.Model,\n\t\t\t}\n\t\t}\n\t\t// Note: identity and thinkingDefault are NOT supported at defaults level in v2026.5.7\n\t}\n\n\t// Convert internal agent list - use v2026.5.7 schema fields\n\tfor _, def := range internal.List {\n\t\tagent := &AgentConfig{\n\t\t\tID: def.ID,\n\t\t\tName: def.Name,\n\t\t}\n\t\tif def.Model != \"\" {\n\t\t\tagent.Model = &AgentModelConfig{\n\t\t\t\tPrimary: def.Model,\n\t\t\t}\n\t\t}\n\t\tagent.Skills = def.Skills\n\t\t// Note: identity is NOT a top-level field in agents.list[] for v2026.5.7\n\t\tagents.List = append(agents.List, agent)\n\t}\n\n\treturn agents\n}\n\n// applyAgentOverrides applies agentConfig.Overrides to OpenClaw config\nfunc (c *ConfigConverter) applyAgentOverrides(config *OpenClawConfig, overrides *AgentOverridesConfigJSON) {\n\t// Override default model\n\tif overrides.Model != \"\" && config.Agents.Defaults != nil {\n\t\tconfig.Agents.Defaults.Model = &AgentModelConfig{\n\t\t\tPrimary: overrides.Model,\n\t\t}\n\t}\n\n\t// Filter skills for all agents\n\tif len(overrides.AllowedSkills) > 0 || len(overrides.DeniedSkills) > 0 {\n\t\tfor _, agent := range config.Agents.List {\n\t\t\tagent.Skills = c.filterSkills(agent.Skills, overrides.AllowedSkills, overrides.DeniedSkills)\n\t\t}\n\t}\n\n\t// Set model fallbacks\n\tif len(overrides.ModelFallbacks) > 0 && config.Agents.Defaults != nil && config.Agents.Defaults.Model != nil {\n\t\tconfig.Agents.Defaults.Model.Fallbacks = overrides.ModelFallbacks\n\t}\n}\n\n// convertChannelsConfig converts agentConfig.Channels to OpenClaw ChannelsConfig\nfunc (c *ConfigConverter) convertChannelsConfig(channels *ChannelsInstanceConfigJSON) *ChannelsConfig {\n\tif channels == nil {\n\t\treturn nil\n\t}\n\n\tcfg := &ChannelsConfig{}\n\n\t// Convert Discord configuration (v2026.5.12 schema)\n\t// Token uses SecretInput pointing to env var: DISCORD_BOT_TOKEN\n\t// ConfigDaemon resolves tokenSecretRef → writes token to hostPath\n\t// Handler extracts token from hostPath → injects as subprocess env var\n\tif channels.Discord != nil {\n\t\t// Use AllowFrom if provided directly, otherwise use AllowedUsers\n\t\tallowFrom := channels.Discord.AllowFrom\n\t\tif len(allowFrom) == 0 && len(channels.Discord.AllowedUsers) > 0 {\n\t\t\tallowFrom = channels.Discord.AllowedUsers\n\t\t}\n\n\t\tdmPolicy := channels.Discord.DmPolicy\n\t\tif dmPolicy == \"\" {\n\t\t\tdmPolicy = \"open\"\n\t\t} else if dmPolicy == \"all\" {\n\t\t\tdmPolicy = \"open\"\n\t\t}\n\t\tcfg.Discord = &DiscordConfig{\n\t\t\tEnabled: channels.Discord.Enabled,\n\t\t\tToken: &SecretInput{Source: \"env\", Provider: \"default\", ID: \"DISCORD_BOT_TOKEN\"},\n\t\t\tAllowFrom: allowFrom,\n\t\t\tDmPolicy: dmPolicy,\n\t\t\tMentionRequired: channels.Discord.MentionRequired,\n\t\t}\n\t\t// When dmPolicy is \"all\", set allowFrom to [\"*\"] to allow all users\n\t\tif dmPolicy == \"all\" {\n\t\t\tcfg.Discord.AllowFrom = []string{\"*\"}\n\t\t}\n\t}\n\n\t// Convert Telegram configuration\n\tif channels.Telegram != nil {\n\t\tcfg.Telegram = &TelegramConfig{\n\t\t\tEnabled: channels.Telegram.Enabled,\n\t\t\tToken: channels.Telegram.Token,\n\t\t\tAllowedUsers: channels.Telegram.AllowedUsers,\n\t\t}\n\t}\n\n\t// Convert Slack configuration\n\tif channels.Slack != nil {\n\t\tcfg.Slack = &SlackConfig{\n\t\t\tEnabled: channels.Slack.Enabled,\n\t\t\tToken: channels.Slack.Token,\n\t\t}\n\t}\n\n\treturn cfg\n}\n\n// filterSkills filters skills based on allow/deny lists\nfunc (c *ConfigConverter) filterSkills(skills []string, allowed []string, denied []string) []string {\n\tif len(skills) == 0 {\n\t\treturn skills\n\t}\n\n\t// Build allowed set\n\tallowedSet := make(map[string]bool)\n\tfor _, s := range allowed {\n\t\tallowedSet[s] = true\n\t}\n\n\t// Build denied set\n\tdeniedSet := make(map[string]bool)\n\tfor _, s := range denied {\n\t\tdeniedSet[s] = true\n\t}\n\n\t// Filter skills\n\tresult := []string{}\n\tfor _, s := range skills {\n\t\t// Skip if denied\n\t\tif deniedSet[s] {\n\t\t\tcontinue\n\t\t}\n\t\t// If allowed list exists, only keep allowed skills\n\t\tif len(allowedSet) > 0 && !allowedSet[s] {\n\t\t\tcontinue\n\t\t}\n\t\tresult = append(result, s)\n\t}\n\n\treturn result\n}\n\n// ============================================================\n// Harness Interface Conversion Methods\n// ============================================================\n\n// ConvertModelHarness converts ModelHarnessInterface to OpenClaw models config.\nfunc (c *ConfigConverter) ConvertModelHarness(harness handler.ModelHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tmodelConfig := &ModelsConfig{\n\t\tProviders: map[string]*ModelProviderConfig{},\n\t}\n\n\tprovider := harness.GetProvider()\n\tmodelConfig.Providers[provider] = &ModelProviderConfig{\n\t\tBaseURL: harness.GetEndpoint(),\n\t\tAPIKey: harness.GetAPIKeyRef(),\n\t\tModels: c.convertModelList(harness.GetAllowedModels()),\n\t}\n\n\treturn json.MarshalIndent(modelConfig, \"\", \" \")\n}\n\n// ConvertSkillsHarness converts SkillsHarnessInterface to OpenClaw skills config.\n// For v2026.5.7: Skills are per-agent, this returns endpoint info only.\nfunc (c *ConfigConverter) ConvertSkillsHarness(harness handler.SkillsHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\t// For v2026.5.7: Only endpoint and localPath are valid\n\tskillsConfig := &SkillsConfig{\n\t\tEndpoint: harness.GetEndpoint(),\n\t\tLocalPath: \"\", // LocalPath not available from interface\n\t}\n\n\treturn json.MarshalIndent(skillsConfig, \"\", \" \")\n}\n\n// ConvertMemoryHarness converts MemoryHarnessInterface to OpenClaw memory config.\nfunc (c *ConfigConverter) ConvertMemoryHarness(harness handler.MemoryHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tmemoryConfig := &MemoryConfig{\n\t\tType: harness.GetType(),\n\t\tEndpoint: harness.GetEndpoint(),\n\t\tTTL: harness.GetTTL(),\n\t\tPersistence: harness.IsPersistenceEnabled(),\n\t}\n\n\treturn json.MarshalIndent(memoryConfig, \"\", \" \")\n}\n\n// ConvertSandboxHarness converts SandboxHarnessInterface to OpenClaw sandbox config.\n// For External Sandbox, generates plugins configuration for harness-bridge plugin.\n// For Embedded Sandbox, generates local sandbox configuration.\nfunc (c *ConfigConverter) ConvertSandboxHarness(harness handler.SandboxHarnessInterface, pluginDir string) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tsandboxConfig := &SandboxConfig{\n\t\tMode: string(harness.GetMode()),\n\t}\n\n\tif harness.IsExternal() {\n\t\t// External Sandbox: Configure harness-bridge plugin path\n\t\tsandboxConfig.Plugins = &PluginsConfig{\n\t\t\tLoad: &PluginsLoadConfig{\n\t\t\t\tPaths: []string{pluginDir},\n\t\t\t},\n\t\t}\n\t\tsandboxConfig.Endpoint = harness.GetEndpoint()\n\t\tsandboxConfig.Timeout = harness.GetTimeout()\n\t} else {\n\t\t// Embedded Sandbox: Local execution (no plugin needed)\n\t\tsandboxConfig.Mode = \"embedded\"\n\t}\n\n\treturn json.MarshalIndent(sandboxConfig, \"\", \" \")\n}\n\n// ============================================================\n// Helper Conversion Methods\n// ============================================================\n\n// convertAgentDefaultsFromHarness converts harness to agent defaults configuration.\nfunc (c *ConfigConverter) convertAgentDefaultsFromHarness(harnessCfg *handler.HarnessConfig) *AgentDefaultsConfig {\n\tdefaults := &AgentDefaultsConfig{}\n\n\tif harnessCfg.Model != nil {\n\t\tdefaults.Model = &AgentModelConfig{\n\t\t\tPrimary: harnessCfg.Model.Provider + \"/\" + harnessCfg.Model.DefaultModel,\n\t\t}\n\t}\n\n\treturn defaults\n}\n\n// convertModelsConfig converts harness model config to OpenClaw models section.\nfunc (c *ConfigConverter) convertModelsConfig(harnessCfg *handler.HarnessConfig, agentConfig *AgentConfigJSON) *ModelsConfig {\n\tif harnessCfg == nil || harnessCfg.Model == nil {\n\t\treturn nil\n\t}\n\n\tprovider := harnessCfg.Model.Provider\n\tmodelsConfig := &ModelsConfig{\n\t\tProviders: map[string]*ModelProviderConfig{},\n\t}\n\n\t// Determine merge mode from agentConfig\n\t// If agentConfig.Models.MergeModels is true, use \"merge\" mode\n\t// Otherwise, use \"\" (default) which means providers are the only source\n\tif agentConfig != nil && agentConfig.Models != nil && agentConfig.Models.MergeModels {\n\t\tmodelsConfig.Mode = \"merge\"\n\t}\n\n\t// Determine API type based on provider\n\tapiType := \"openai-completions\"\n\tif provider == \"anthropic\" {\n\t\tapiType = \"anthropic-messages\"\n\t}\n\n\t// Get API key from environment variable (uppercase provider name)\n\t// Controller injects PROVIDER_API_KEY from Secret as Pod env var\n\t// e.g., DEEPSEEK_API_KEY, ANTHROPIC_API_KEY\n\tapiKeyEnvVar := fmt.Sprintf(\"%s_API_KEY\", strings.ToUpper(provider))\n\n\tmodelsConfig.Providers[provider] = &ModelProviderConfig{\n\t\tBaseURL: harnessCfg.Model.Endpoint,\n\t\tAPIKey: apiKeyEnvVar,\n\t\tAPI: apiType,\n\t\tModels: c.convertModelListFromSpec(harnessCfg.Model.Models),\n\t}\n\n\treturn modelsConfig\n}\n\n// convertSkillsConfig converts harness skills config to OpenClaw skills section.\n// For v2026.5.7: Skills are per-agent, not global. This config is for endpoint discovery only.\nfunc (c *ConfigConverter) convertSkillsConfig(harnessCfg *handler.HarnessConfig) *SkillsConfig {\n\tif harnessCfg == nil || harnessCfg.Skills == nil {\n\t\treturn nil\n\t}\n\n\t// For v2026.5.7: Only endpoint and localPath are valid\n\t// Skills are defined at agent level (agents.defaults.skills, agents.list[].skills)\n\treturn &SkillsConfig{\n\t\tEndpoint: harnessCfg.Skills.Endpoint,\n\t\tLocalPath: harnessCfg.Skills.LocalPath,\n\t}\n}\n\n// convertMemoryConfig converts harness memory config to OpenClaw memory section.\nfunc (c *ConfigConverter) convertMemoryConfig(harnessCfg *handler.HarnessConfig) *MemoryConfig {\n\tif harnessCfg == nil || harnessCfg.Memory == nil {\n\t\treturn nil\n\t}\n\n\treturn &MemoryConfig{\n\t\tType: harnessCfg.Memory.Type,\n\t\tEndpoint: harnessCfg.Memory.Endpoint,\n\t\tTTL: int64(harnessCfg.Memory.TTL),\n\t\tPersistence: harnessCfg.Memory.PersistenceEnabled,\n\t}\n}\n\n// convertSandboxConfig converts harness sandbox config to OpenClaw sandbox section.\n// For External Sandbox, configures plugins path for harness-bridge plugin.\nfunc (c *ConfigConverter) convertSandboxConfig(harnessCfg *handler.HarnessConfig) *SandboxConfig {\n\tif harnessCfg == nil || harnessCfg.Sandbox == nil {\n\t\treturn nil\n\t}\n\n\tsandboxConfig := &SandboxConfig{\n\t\tMode: string(harnessCfg.Sandbox.Mode),\n\t}\n\n\tif harnessCfg.Sandbox.Mode == v1.SandboxModeExternal {\n\t\t// External Sandbox: Configure plugins path for harness-bridge plugin\n\t\tpluginDir := \"/etc/aiagent/plugins/harness-bridge\"\n\t\tsandboxConfig.Endpoint = harnessCfg.Sandbox.Endpoint\n\t\tsandboxConfig.Timeout = int64(harnessCfg.Sandbox.Timeout)\n\t\tsandboxConfig.Plugins = &PluginsConfig{\n\t\t\tLoad: &PluginsLoadConfig{\n\t\t\t\tPaths: []string{pluginDir},\n\t\t\t},\n\t\t}\n\t}\n\n\treturn sandboxConfig\n}\n\n// convertModelList converts string array to model definitions.\nfunc (c *ConfigConverter) convertModelList(models []string) []*ModelDefinitionConfig {\n\tif len(models) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*ModelDefinitionConfig, len(models))\n\tfor i, name := range models {\n\t\tresult[i] = &ModelDefinitionConfig{\n\t\t\tID: name,\n\t\t\tName: name,\n\t\t}\n\t}\n\treturn result\n}\n\n// convertModelListFromSpec converts spec model items to model definitions.\nfunc (c *ConfigConverter) convertModelListFromSpec(models []v1.ModelConfig) []*ModelDefinitionConfig {\n\tif len(models) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*ModelDefinitionConfig, len(models))\n\tfor i, m := range models {\n\t\tresult[i] = &ModelDefinitionConfig{\n\t\t\tID: m.Name,\n\t\t\tName: m.Name,\n\t\t\tContextWindow: int(m.ContextWindow),\n\t\t}\n\t}\n\treturn result\n}\n\n// convertSkillInfoList converts SkillInfo to OpenClaw skill items.\nfunc (c *ConfigConverter) convertSkillInfoList(skills []handler.SkillInfo) []*SkillItemConfig {\n\tif len(skills) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*SkillItemConfig, len(skills))\n\tfor i, s := range skills {\n\t\tresult[i] = &SkillItemConfig{\n\t\t\tName: s.Name,\n\t\t\tVersion: s.Version,\n\t\t\tPath: s.Path,\n\t\t\tAllowed: s.Allowed,\n\t\t}\n\t}\n\treturn result\n}\n\n// convertSkillItemsFromSpec converts spec skill items to OpenClaw skill items.\nfunc (c *ConfigConverter) convertSkillItemsFromSpec(skills []v1.SkillConfig) []*SkillItemConfig {\n\tif len(skills) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*SkillItemConfig, len(skills))\n\tfor i, s := range skills {\n\t\tresult[i] = &SkillItemConfig{\n\t\t\tName: s.Name,\n\t\t\tVersion: s.Version,\n\t\t\tAllowed: s.Allowed,\n\t\t}\n\t}\n\treturn result\n}\n\n// applySpecOverrides applies HarnessOverrideSpec from AIAgentSpec.\nfunc (c *ConfigConverter) applySpecOverrides(cfg *AgentConfig, override v1.HarnessOverrideSpec) {\n\t// Apply skills overrides\n\tfor _, skillsOverride := range override.Skills {\n\t\tcfg.Skills = append(cfg.Skills, skillsOverride.AllowedSkills...)\n\t}\n\n\t// Apply model overrides\n\tfor _, modelOverride := range override.Model {\n\t\tif len(modelOverride.AllowedModels) > 0 {\n\t\t\tif cfg.Model == nil {\n\t\t\t\tcfg.Model = &AgentModelConfig{}\n\t\t\t}\n\t\t\tif cfg.Model.Primary == \"\" && len(modelOverride.AllowedModels) > 0 {\n\t\t\t\tcfg.Model.Primary = modelOverride.AllowedModels[0]\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ============================================================\n// OpenClaw Config Types (matching TypeScript types)\n// ============================================================\n\n// OpenClawConfig represents the full OpenClaw config file structure.\ntype OpenClawConfig struct {\n\tAgents *AgentsConfig `json:\"agents,omitempty\"`\n\tModels *ModelsConfig `json:\"models,omitempty\"`\n\tSkills *SkillsConfig `json:\"skills,omitempty\"`\n\tTools *ToolsConfig `json:\"tools,omitempty\"`\n\tMemory *MemoryConfig `json:\"memory,omitempty\"`\n\tGateway *GatewayConfig `json:\"gateway,omitempty\"`\n\tChannels *ChannelsConfig `json:\"channels,omitempty\"`\n\tSandbox *SandboxConfig `json:\"sandbox,omitempty\"`\n\tPlugins *PluginsConfig `json:\"plugins,omitempty\"`\n}\n\n// AgentsConfig represents agents configuration.\ntype AgentsConfig struct {\n\tDefaults *AgentDefaultsConfig `json:\"defaults,omitempty\"`\n\tList []*AgentConfig `json:\"list,omitempty\"`\n}\n\n// AgentDefaultsConfig represents default agent configuration for OpenClaw v2026.5.7.\n// Schema: model, skills, workspace (identity and thinking are NOT supported at defaults level)\ntype AgentDefaultsConfig struct {\n\tModel *AgentModelConfig `json:\"model,omitempty\"`\n\tSkills []string `json:\"skills,omitempty\"`\n\tWorkspace string `json:\"workspace,omitempty\"`\n}\n\n// AgentConfig represents a single agent configuration for OpenClaw v2026.5.7.\n// Schema: id (required), name, description, workspace, model, skills, thinking, agents, etc.\n// Note: identity is NOT a top-level field in agents.list[]\ntype AgentConfig struct {\n\tID string `json:\"id\"`\n\tName string `json:\"name,omitempty\"`\n\tDescription string `json:\"description,omitempty\"`\n\tWorkspace string `json:\"workspace,omitempty\"`\n\tModel *AgentModelConfig `json:\"model,omitempty\"`\n\tSkills []string `json:\"skills,omitempty\"`\n\tThinking string `json:\"thinking,omitempty\"`\n\tAgents []string `json:\"agents,omitempty\"`\n}\n\n// AgentModelConfig represents agent model configuration.\ntype AgentModelConfig struct {\n\tPrimary string `json:\"primary,omitempty\"`\n\tFallbacks []string `json:\"fallbacks,omitempty\"`\n\tTemperature float64 `json:\"temperature,omitempty\"`\n\tMaxTokens int `json:\"maxTokens,omitempty\"`\n}\n\n// AgentToolsConfig represents agent tools configuration.\ntype AgentToolsConfig struct {\n\tAllow []string `json:\"allow,omitempty\"`\n\tDeny []string `json:\"deny,omitempty\"`\n}\n\n// IdentityConfig represents agent identity configuration.\ntype IdentityConfig struct {\n\tName string `json:\"name,omitempty\"`\n\tAvatar string `json:\"avatar,omitempty\"`\n}\n\n// SubagentsConfigOpenClaw represents sub-agent configuration.\ntype SubagentsConfigOpenClaw struct {\n\tAllowAgents []string `json:\"allowAgents,omitempty\"`\n\tModel *AgentModelConfig `json:\"model,omitempty\"`\n\tRequireAgentID bool `json:\"requireAgentId,omitempty\"`\n}\n\n// ModelsConfig represents models configuration for OpenClaw v2026.5.7.\n// Schema: mode, providers\ntype ModelsConfig struct {\n\tMode string `json:\"mode,omitempty\"`\n\tProviders map[string]*ModelProviderConfig `json:\"providers,omitempty\"`\n}\n\n// ModelsMergeModeConfig represents models merge mode configuration in agentConfig.\n// When mergeModels is true, OpenClaw will merge these providers with built-in providers.\ntype ModelsMergeModeConfig struct {\n\t// MergeModels indicates whether to merge with built-in model providers\n\t// If true, OpenClaw config will have models.mode = \"merge\"\n\t// If false or unset, providers will be the only source (mode = \"\" or \"override\")\n\tMergeModels bool `json:\"mergeModels,omitempty\"`\n}\n\n// ModelProviderConfig represents a model provider.\ntype ModelProviderConfig struct {\n\tBaseURL string `json:\"baseUrl,omitempty\"`\n\tAPIKey string `json:\"apiKey,omitempty\"`\n\tAPI string `json:\"api,omitempty\"` // \"openai-completions\" or \"anthropic-messages\"\n\tModels []*ModelDefinitionConfig `json:\"models,omitempty\"`\n}\n\n// ModelDefinitionConfig represents a model definition.\ntype ModelDefinitionConfig struct {\n\tID string `json:\"id\"`\n\tName string `json:\"name,omitempty\"`\n\tContextWindow int `json:\"contextWindow,omitempty\"`\n}\n\n// SkillsConfig represents skills configuration for OpenClaw v2026.5.7.\n// Schema: endpoint, localPath (no \"hubType\" or \"skills\" array - skills are per-agent)\n// Note: Skills are defined at agent level (agents.defaults.skills, agents.list[].skills)\n// The skills config here is only for plugin/endpoint discovery if using external skills hub.\ntype SkillsConfig struct {\n\tEndpoint string `json:\"endpoint,omitempty\"`\n\tLocalPath string `json:\"localPath,omitempty\"`\n}\n\n// SkillItemConfig represents a skill item.\ntype SkillItemConfig struct {\n\tName string `json:\"name\"`\n\tVersion string `json:\"version,omitempty\"`\n\tPath string `json:\"path,omitempty\"`\n\tAllowed bool `json:\"allowed,omitempty\"`\n}\n\n// MemoryConfig represents memory configuration.\ntype MemoryConfig struct {\n\tType string `json:\"type,omitempty\"`\n\tEndpoint string `json:\"endpoint,omitempty\"`\n\tTTL int64 `json:\"ttl,omitempty\"`\n\tPersistence bool `json:\"persistence,omitempty\"`\n}\n\n// GatewayConfig represents gateway configuration for OpenClaw v2026.5.7.\n// Schema: mode, bind, port, auth (object with mode/token), controlUi, trustedProxies\ntype GatewayConfig struct {\n\tMode string `json:\"mode,omitempty\"`\n\tBind string `json:\"bind,omitempty\"`\n\tPort int `json:\"port,omitempty\"`\n\tAuth *GatewayAuth `json:\"auth,omitempty\"`\n\tControlUI *ControlUIConfig `json:\"controlUi,omitempty\"`\n\tTrustedProxies []string `json:\"trustedProxies,omitempty\"`\n}\n\n// GatewayAuth represents gateway authentication for v2026.5.7.\ntype GatewayAuth struct {\n\tMode string `json:\"mode,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n}\n\n// ControlUIConfig represents Control UI configuration.\ntype ControlUIConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tBasePath string `json:\"basePath,omitempty\"`\n\tAllowedOrigins []string `json:\"allowedOrigins,omitempty\"`\n}\n\n// ToolsConfig represents tools configuration.\ntype ToolsConfig struct {\n\tAllow []string `json:\"allow,omitempty\"`\n\tDeny []string `json:\"deny,omitempty\"`\n}\n\n// ChannelsConfig represents channels configuration.\ntype ChannelsConfig struct {\n\tTelegram *TelegramConfig `json:\"telegram,omitempty\"`\n\tDiscord *DiscordConfig `json:\"discord,omitempty\"`\n\tSlack *SlackConfig `json:\"slack,omitempty\"`\n}\n\n// TelegramConfig represents Telegram channel config.\ntype TelegramConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n\tAllowedUsers []string `json:\"allowedUsers,omitempty\"` // Telegram user IDs whitelist\n}\n\n// SecretInput represents a secret reference for OpenClaw v2026.5.7.\n// Schema: source (env|file|value), provider (default), id (env var name or file path)\ntype SecretInput struct {\n\tSource string `json:\"source\"` // \"env\" | \"file\" | \"value\"\n\tProvider string `json:\"provider,omitempty\"` // \"default\"\n\tID string `json:\"id\"` // Environment variable name or file path\n}\n\n// DiscordConfig represents Discord channel config for OpenClaw v2026.5.7.\n// Schema: enabled, token (env var name), dmPolicy, allowFrom, mentionRequired\ntype DiscordConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken *SecretInput `json:\"token,omitempty\"` // Secret ref for DISCORD_BOT_TOKEN\n\tAllowFrom []string `json:\"allowFrom,omitempty\"`\n\tDmPolicy string `json:\"dmPolicy,omitempty\"` // \"all\" | \"pairing\" | \"allowlist\" | \"disabled\"\n\tMentionRequired bool `json:\"mentionRequired,omitempty\"`\n\tGuilds map[string]interface{} `json:\"guilds,omitempty\"`\n}\n\n// SlackConfig represents Slack channel config.\ntype SlackConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n}\n\n// HarnessSection represents harness-only config section.\ntype HarnessSection struct {\n\tModel *ModelsConfig `json:\"model,omitempty\"`\n\tSkills *SkillsConfig `json:\"skills,omitempty\"`\n\tMemory *MemoryConfig `json:\"memory,omitempty\"`\n}\n\n// SandboxConfig represents sandbox configuration.\n// For External Sandbox, includes plugins configuration for harness-bridge.\ntype SandboxConfig struct {\n\t// Mode: \"external\" or \"embedded\"\n\tMode string `json:\"mode\"`\n\n\t// Endpoint for External Sandbox (HTTP API URL)\n\tEndpoint string `json:\"endpoint,omitempty\"`\n\n\t// Timeout for sandbox operations (seconds)\n\tTimeout int64 `json:\"timeout,omitempty\"`\n\n\t// Plugins configuration for External Sandbox\n\tPlugins *PluginsConfig `json:\"plugins,omitempty\"`\n}\n\n// PluginEntry represents a single plugin entry in plugins.entries.\ntype PluginEntry struct {\n\tEnabled bool `json:\"enabled\"`\n}\n\n// PluginsConfig represents OpenClaw plugins configuration.\ntype PluginsConfig struct {\n\t// Load configuration for plugin discovery\n\tLoad *PluginsLoadConfig `json:\"load,omitempty\"`\n\t// Entries lists enabled plugins (e.g., {\"discord\": {\"enabled\": true}})\n\tEntries map[string]PluginEntry `json:\"entries,omitempty\"`\n}\n\n// PluginsLoadConfig specifies plugin discovery paths.\ntype PluginsLoadConfig struct {\n\t// Paths to search for plugins\n\tPaths []string `json:\"paths,omitempty\"`\n}"}, "f1130b5a109b38f56ba4b82ca460818c6a004ec4": {"t": 1785755072.8143811, "n": 28341, "h": "6fef1327fd6dd371", "c": "// Package openclaw provides configuration conversion for OpenClaw framework.\n// This converter transforms AIAgentSpec + HarnessConfig into OpenClaw JSON config.\n//\n// Configuration Sources:\n// - agentConfig (from AIAgentSpec): Gateway port, internal agents, overrides\n// - HarnessConfig (from AgentRuntime): Models, Skills, Memory (shared)\npackage openclaw\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"aiagent/api/v1\"\n\t\"aiagent/pkg/handler\"\n)\n\n// ConfigConverter converts AIAgentSpec and HarnessConfig to OpenClaw config.\ntype ConfigConverter struct{}\n\n// NewConfigConverter creates a new config converter.\nfunc NewConfigConverter() *ConfigConverter {\n\treturn &ConfigConverter{}\n}\n\n// ============================================================\n// Core Conversion Methods (called by Handler)\n// ============================================================\n\n// ConvertToOpenClawConfig generates full openclaw.json from AIAgentSpec and HarnessConfig.\n// This is the primary method called by GenerateFrameworkConfig.\nfunc (c *ConfigConverter) ConvertToOpenClawConfig(spec *v1.AIAgentSpec, harnessCfg *handler.HarnessConfig) ([]byte, error) {\n\tconfig := &OpenClawConfig{\n\t\tAgents: &AgentsConfig{},\n\t\tGateway: &GatewayConfig{\n\t\t\tMode: \"local\",\n\t\t},\n\t}\n\n\t// 1. Parse agentConfig JSON (agent-specific configuration)\n\tif spec.AgentConfig != nil && spec.AgentConfig.Raw != nil {\n\t\tagentConfig, err := c.parseAgentConfigJSON(spec.AgentConfig.Raw)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse agentConfig: %w\", err)\n\t\t}\n\n\t\t// Apply agentConfig to OpenClaw config\n\t\tif agentConfig.Gateway != nil {\n\t\t\tconfig.Gateway = c.convertGatewayInstanceConfig(agentConfig.Gateway)\n\t\t}\n\t\tif agentConfig.InternalAgents != nil {\n\t\t\tconfig.Agents = c.convertInternalAgentsConfig(agentConfig.InternalAgents)\n\t\t}\n\t\t// Apply overrides after agents are set\n\t\tif agentConfig.Overrides != nil {\n\t\t\tc.applyAgentOverrides(config, agentConfig.Overrides)\n\t\t}\n\t}\n\n\t// 2. Apply Harness configuration (shared platform capabilities)\n\tif harnessCfg != nil {\n\t\tconfig.Agents.Defaults = c.convertAgentDefaultsFromHarness(harnessCfg)\n\t\tconfig.Models = c.convertModelsConfig(harnessCfg)\n\t\tconfig.Skills = c.convertSkillsConfig(harnessCfg)\n\t\tconfig.Memory = c.convertMemoryConfig(harnessCfg)\n\t\tconfig.Sandbox = c.convertSandboxConfig(harnessCfg)\n\t\t// If external sandbox, set plugins path\n\t\tif config.Sandbox != nil && config.Sandbox.Mode == \"external\" && config.Sandbox.Plugins != nil {\n\t\t\tconfig.Plugins = config.Sandbox.Plugins\n\t\t}\n\t}\n\n\t// 3. Create main agent entry from AIAgentSpec metadata\n\tmainAgent := &AgentConfig{\n\t\tID: spec.Description,\n\t\tName: spec.Description,\n\t\tDescription: spec.Description,\n\t}\n\n\t// Apply model from harness\n\tif harnessCfg != nil && harnessCfg.Model != nil {\n\t\tmainAgent.Model = &AgentModelConfig{\n\t\t\tPrimary: harnessCfg.Model.DefaultModel,\n\t\t}\n\t}\n\n\t// Apply skills from harness\n\tif harnessCfg != nil && harnessCfg.Skills != nil {\n\t\tfor _, skill := range harnessCfg.Skills.Skills {\n\t\t\tif skill.Allowed {\n\t\t\t\tmainAgent.Skills = append(mainAgent.Skills, skill.Name)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add main agent to list if no internal agents defined\n\tif len(config.Agents.List) == 0 {\n\t\tconfig.Agents.List = []*AgentConfig{mainAgent}\n\t}\n\n\treturn json.MarshalIndent(config, \"\", \" \")\n}\n\n// ConvertAgentSpec generates AgentConfig section for a single agent.\nfunc (c *ConfigConverter) ConvertAgentSpec(spec *v1.AIAgentSpec, harnessCfg *handler.HarnessConfig) (*AgentConfig, error) {\n\tagentCfg := &AgentConfig{\n\t\tID: spec.Description,\n\t\tName: spec.Description,\n\t\tDescription: spec.Description,\n\t}\n\n\t// Apply model from harness\n\tif harnessCfg != nil && harnessCfg.Model != nil {\n\t\tagentCfg.Model = &AgentModelConfig{\n\t\t\tPrimary: harnessCfg.Model.DefaultModel,\n\t\t}\n\t}\n\n\t// Apply skills from harness\n\tif harnessCfg != nil && harnessCfg.Skills != nil {\n\t\tfor _, skill := range harnessCfg.Skills.Skills {\n\t\t\tif skill.Allowed {\n\t\t\t\tagentCfg.Skills = append(agentCfg.Skills, skill.Name)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Apply overrides from spec\n\tc.applySpecOverrides(agentCfg, spec.HarnessOverride)\n\n\treturn agentCfg, nil\n}\n\n// ConvertHarnessConfig generates harness-specific config sections.\nfunc (c *ConfigConverter) ConvertHarnessConfig(harnessCfg *handler.HarnessConfig) ([]byte, error) {\n\tharnessSection := &HarnessSection{\n\t\tModel: c.convertModelsConfig(harnessCfg),\n\t\tSkills: c.convertSkillsConfig(harnessCfg),\n\t\tMemory: c.convertMemoryConfig(harnessCfg),\n\t}\n\n\treturn json.MarshalIndent(harnessSection, \"\", \" \")\n}\n\n// ============================================================\n// agentConfig Parsing (AIAgentSpec.agentConfig.Raw)\n// ============================================================\n\n// AgentConfigJSON represents the agentConfig JSON structure from AIAgentSpec.\n// ONLY contains agent-specific configuration (not shared Harness capabilities).\n// Fields use JSON names matching the schema defined earlier.\ntype AgentConfigJSON struct {\n\t// Gateway process configuration for this agent instance\n\tGateway *GatewayInstanceConfigJSON `json:\"gateway\"`\n\n\t// Internal sub-agents managed by this Gateway (keyed as \"agents\" in JSON)\n\tInternalAgents *InternalAgentsConfigJSON `json:\"agents,omitempty\"`\n\n\t// Per-agent overrides of inherited Harness defaults\n\tOverrides *AgentOverridesConfigJSON `json:\"overrides,omitempty\"`\n}\n\n// GatewayInstanceConfigJSON - Gateway process specific config\ntype GatewayInstanceConfigJSON struct {\n\t// Port for this Gateway instance\n\tPort int `json:\"port,omitempty\"`\n\n\t// Bind address: loopback, lan, auto\n\tBind string `json:\"bind,omitempty\"`\n\n\t// Authentication for this Gateway instance\n\tAuth *GatewayAuthConfigJSON `json:\"auth,omitempty\"`\n\n\t// Control UI settings\n\tControlUI *GatewayControlUIConfigJSON `json:\"controlUi,omitempty\"`\n\n\t// Trusted proxy IPs for X-Forwarded-For\n\tTrustedProxies []string `json:\"trustedProxies,omitempty\"`\n}\n\n// GatewayAuthConfigJSON - Gateway authentication\ntype GatewayAuthConfigJSON struct {\n\t// Auth mode: none, token, password\n\tMode string `json:\"mode,omitempty\"`\n\n\t// Token for token mode\n\tToken string `json:\"token,omitempty\"`\n}\n\n// GatewayControlUIConfigJSON - Control UI for this instance\ntype GatewayControlUIConfigJSON struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tBasePath string `json:\"basePath,omitempty\"`\n\tAllowedOrigins []string `json:\"allowedOrigins,omitempty\"`\n}\n\n// InternalAgentsConfigJSON - Sub-agents inside Gateway\ntype InternalAgentsConfigJSON struct {\n\t// Default settings for internal agents\n\tDefaults *InternalAgentDefaultsJSON `json:\"defaults,omitempty\"`\n\n\t// List of internal sub-agents\n\tList []*InternalAgentDefinitionJSON `json:\"list,omitempty\"`\n}\n\n// InternalAgentDefaultsJSON - Default settings for sub-agents\ntype InternalAgentDefaultsJSON struct {\n\t// Override inherited model\n\tModel string `json:\"model,omitempty\"`\n\n\t// Thinking level: off, minimal, low, medium, high, xhigh, adaptive\n\tThinkingDefault string `json:\"thinkingDefault,omitempty\"`\n\n\t// Reasoning visibility: on, off, stream\n\tReasoningDefault string `json:\"reasoningDefault,omitempty\"`\n\n\t// Fast mode default\n\tFastModeDefault bool `json:\"fastModeDefault,omitempty\"`\n\n\t// Identity for all sub-agents\n\tIdentity *AgentIdentityJSON `json:\"identity,omitempty\"`\n}\n\n// InternalAgentDefinitionJSON - Single internal sub-agent\ntype InternalAgentDefinitionJSON struct {\n\t// Agent identifier\n\tID string `json:\"id\"`\n\n\t// Display name\n\tName string `json:\"name,omitempty\"`\n\n\t// Override model for this sub-agent\n\tModel string `json:\"model,omitempty\"`\n\n\t// Allowed skills (subset of Harness.Skills)\n\tSkills []string `json:\"skills,omitempty\"`\n\n\t// Identity for this sub-agent\n\tIdentity *AgentIdentityJSON `json:\"identity,omitempty\"`\n\n\t// Tool restrictions (subset)\n\tTools *ToolsOverrideJSON `json:\"tools,omitempty\"`\n\n\t// Subagent spawning config\n\tSubagents *SubagentsConfigJSON `json:\"subagents,omitempty\"`\n}\n\n// AgentIdentityJSON - Agent name and avatar\ntype AgentIdentityJSON struct {\n\tName string `json:\"name,omitempty\"`\n\tAvatar string `json:\"avatar,omitempty\"`\n}\n\n// ToolsOverrideJSON - Per-agent tool restrictions\ntype ToolsOverrideJSON struct {\n\tAllow []string `json:\"allow,omitempty\"`\n\tDeny []string `json:\"deny,omitempty\"`\n}\n\n// SubagentsConfigJSON - Sub-agent spawning rules\ntype SubagentsConfigJSON struct {\n\t// Allowed sub-agent IDs (\"*\" = any)\n\tAllowAgents []string `json:\"allowAgents,omitempty\"`\n\n\t// Default model for spawned sub-agents\n\tModel string `json:\"model,omitempty\"`\n\n\t// Require explicit agentId in spawn calls\n\tRequireAgentID bool `json:\"requireAgentId,omitempty\"`\n}\n\n// AgentOverridesConfigJSON - Override inherited Harness defaults\ntype AgentOverridesConfigJSON struct {\n\t// Override model selection\n\tModel string `json:\"model,omitempty\"`\n\n\t// Skills subset (cannot add new, only restrict)\n\tAllowedSkills []string `json:\"allowedSkills,omitempty\"`\n\tDeniedSkills []string `json:\"deniedSkills,omitempty\"`\n\n\t// Model fallback chain override\n\tModelFallbacks []string `json:\"modelFallbacks,omitempty\"`\n}\n\n// parseAgentConfigJSON parses raw agentConfig JSON\nfunc (c *ConfigConverter) parseAgentConfigJSON(raw []byte) (*AgentConfigJSON, error) {\n\tif len(raw) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tagentConfig := &AgentConfigJSON{}\n\tif err := json.Unmarshal(raw, agentConfig); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid agentConfig JSON: %w\", err)\n\t}\n\n\treturn agentConfig, nil\n}\n\n// ParseAgentConfig is the public method for Handler to call\nfunc (c *ConfigConverter) ParseAgentConfig(raw []byte) (*AgentConfigJSON, error) {\n\treturn c.parseAgentConfigJSON(raw)\n}\n\n// convertGatewayInstanceConfig converts agentConfig.Gateway to OpenClaw GatewayConfig\nfunc (c *ConfigConverter) convertGatewayInstanceConfig(gateway *GatewayInstanceConfigJSON) *GatewayConfig {\n\tcfg := &GatewayConfig{\n\t\tMode: \"local\",\n\t}\n\n\tif gateway.Port > 0 {\n\t\tcfg.Port = gateway.Port\n\t}\n\tif gateway.Bind != \"\" {\n\t\tcfg.Host = gateway.Bind\n\t}\n\tif gateway.Auth != nil {\n\t\tcfg.AuthMode = gateway.Auth.Mode\n\t}\n\tif gateway.ControlUI != nil {\n\t\tcfg.ControlUI = &ControlUIConfig{\n\t\t\tEnabled: gateway.ControlUI.Enabled,\n\t\t\tBasePath: gateway.ControlUI.BasePath,\n\t\t\tAllowedOrigins: gateway.ControlUI.AllowedOrigins,\n\t\t}\n\t}\n\tcfg.TrustedProxies = gateway.TrustedProxies\n\n\treturn cfg\n}\n\n// convertInternalAgentsConfig converts agentConfig.InternalAgents to OpenClaw AgentsConfig\nfunc (c *ConfigConverter) convertInternalAgentsConfig(internal *InternalAgentsConfigJSON) *AgentsConfig {\n\tagents := &AgentsConfig{}\n\n\t// Convert defaults\n\tif internal.Defaults != nil {\n\t\tagents.Defaults = &AgentDefaultsConfig{\n\t\t\tThinkingDefault: internal.Defaults.ThinkingDefault,\n\t\t\tReasoningDefault: internal.Defaults.ReasoningDefault,\n\t\t\tFastModeDefault: internal.Defaults.FastModeDefault,\n\t\t}\n\t\tif internal.Defaults.Model != \"\" {\n\t\t\tagents.Defaults.Model = &AgentModelConfig{\n\t\t\t\tPrimary: internal.Defaults.Model,\n\t\t\t}\n\t\t}\n\t\tif internal.Defaults.Identity != nil {\n\t\t\tagents.Defaults.Identity = &IdentityConfig{\n\t\t\t\tName: internal.Defaults.Identity.Name,\n\t\t\t\tAvatar: internal.Defaults.Identity.Avatar,\n\t\t\t}\n\t\t}\n\t}\n\n\t// Convert internal agent list\n\tfor _, def := range internal.List {\n\t\tagent := &AgentConfig{\n\t\t\tID: def.ID,\n\t\t\tName: def.Name,\n\t\t}\n\t\tif def.Model != \"\" {\n\t\t\tagent.Model = &AgentModelConfig{\n\t\t\t\tPrimary: def.Model,\n\t\t\t}\n\t\t}\n\t\tagent.Skills = def.Skills\n\t\tif def.Identity != nil {\n\t\t\tagent.Identity = &IdentityConfig{\n\t\t\t\tName: def.Identity.Name,\n\t\t\t\tAvatar: def.Identity.Avatar,\n\t\t\t}\n\t\t}\n\t\tif def.Tools != nil {\n\t\t\tagent.Tools = &AgentToolsConfig{\n\t\t\t\tAllow: def.Tools.Allow,\n\t\t\t\tDeny: def.Tools.Deny,\n\t\t\t}\n\t\t}\n\t\tif def.Subagents != nil {\n\t\t\tagent.Subagents = &SubagentsConfigOpenClaw{\n\t\t\t\tAllowAgents: def.Subagents.AllowAgents,\n\t\t\t\tRequireAgentID: def.Subagents.RequireAgentID,\n\t\t\t}\n\t\t\tif def.Subagents.Model != \"\" {\n\t\t\t\tagent.Subagents.Model = &AgentModelConfig{\n\t\t\t\t\tPrimary: def.Subagents.Model,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tagents.List = append(agents.List, agent)\n\t}\n\n\treturn agents\n}\n\n// applyAgentOverrides applies agentConfig.Overrides to OpenClaw config\nfunc (c *ConfigConverter) applyAgentOverrides(config *OpenClawConfig, overrides *AgentOverridesConfigJSON) {\n\t// Override default model\n\tif overrides.Model != \"\" && config.Agents.Defaults != nil {\n\t\tconfig.Agents.Defaults.Model = &AgentModelConfig{\n\t\t\tPrimary: overrides.Model,\n\t\t}\n\t}\n\n\t// Filter skills for all agents\n\tif len(overrides.AllowedSkills) > 0 || len(overrides.DeniedSkills) > 0 {\n\t\tfor _, agent := range config.Agents.List {\n\t\t\tagent.Skills = c.filterSkills(agent.Skills, overrides.AllowedSkills, overrides.DeniedSkills)\n\t\t}\n\t}\n\n\t// Set model fallbacks\n\tif len(overrides.ModelFallbacks) > 0 && config.Agents.Defaults != nil && config.Agents.Defaults.Model != nil {\n\t\tconfig.Agents.Defaults.Model.Fallbacks = overrides.ModelFallbacks\n\t}\n}\n\n// filterSkills filters skills based on allow/deny lists\nfunc (c *ConfigConverter) filterSkills(skills []string, allowed []string, denied []string) []string {\n\tif len(skills) == 0 {\n\t\treturn skills\n\t}\n\n\t// Build allowed set\n\tallowedSet := make(map[string]bool)\n\tfor _, s := range allowed {\n\t\tallowedSet[s] = true\n\t}\n\n\t// Build denied set\n\tdeniedSet := make(map[string]bool)\n\tfor _, s := range denied {\n\t\tdeniedSet[s] = true\n\t}\n\n\t// Filter skills\n\tresult := []string{}\n\tfor _, s := range skills {\n\t\t// Skip if denied\n\t\tif deniedSet[s] {\n\t\t\tcontinue\n\t\t}\n\t\t// If allowed list exists, only keep allowed skills\n\t\tif len(allowedSet) > 0 && !allowedSet[s] {\n\t\t\tcontinue\n\t\t}\n\t\tresult = append(result, s)\n\t}\n\n\treturn result\n}\n\n// ============================================================\n// Harness Interface Conversion Methods\n// ============================================================\n\n// ConvertModelHarness converts ModelHarnessInterface to OpenClaw models config.\nfunc (c *ConfigConverter) ConvertModelHarness(harness handler.ModelHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tmodelConfig := &ModelsConfig{\n\t\tProviders: map[string]*ModelProviderConfig{},\n\t}\n\n\tprovider := harness.GetProvider()\n\tmodelConfig.Providers[provider] = &ModelProviderConfig{\n\t\tBaseURL: harness.GetEndpoint(),\n\t\tAPIKey: harness.GetAPIKeyRef(),\n\t\tModels: c.convertModelList(harness.GetAllowedModels()),\n\t}\n\n\treturn json.MarshalIndent(modelConfig, \"\", \" \")\n}\n\n// ConvertSkillsHarness converts SkillsHarnessInterface to OpenClaw skills config.\nfunc (c *ConfigConverter) ConvertSkillsHarness(harness handler.SkillsHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tskillsConfig := &SkillsConfig{\n\t\tHubType: harness.GetHubType(),\n\t\tEndpoint: harness.GetEndpoint(),\n\t\tSkills: c.convertSkillInfoList(harness.GetSkills()),\n\t}\n\n\treturn json.MarshalIndent(skillsConfig, \"\", \" \")\n}\n\n// ConvertMemoryHarness converts MemoryHarnessInterface to OpenClaw memory config.\nfunc (c *ConfigConverter) ConvertMemoryHarness(harness handler.MemoryHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tmemoryConfig := &MemoryConfig{\n\t\tType: harness.GetType(),\n\t\tEndpoint: harness.GetEndpoint(),\n\t\tTTL: harness.GetTTL(),\n\t\tPersistence: harness.IsPersistenceEnabled(),\n\t}\n\n\treturn json.MarshalIndent(memoryConfig, \"\", \" \")\n}\n\n// ConvertSandboxHarness converts SandboxHarnessInterface to OpenClaw sandbox config.\n// For External Sandbox, generates plugins configuration for harness-bridge plugin.\n// For Embedded Sandbox, generates local sandbox configuration.\nfunc (c *ConfigConverter) ConvertSandboxHarness(harness handler.SandboxHarnessInterface, pluginDir string) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tsandboxConfig := &SandboxConfig{\n\t\tMode: string(harness.GetMode()),\n\t}\n\n\tif harness.IsExternal() {\n\t\t// External Sandbox: Configure harness-bridge plugin path\n\t\tsandboxConfig.Plugins = &PluginsConfig{\n\t\t\tLoad: &PluginsLoadConfig{\n\t\t\t\tPaths: []string{pluginDir},\n\t\t\t},\n\t\t}\n\t\tsandboxConfig.Endpoint = harness.GetEndpoint()\n\t\tsandboxConfig.Timeout = harness.GetTimeout()\n\t} else {\n\t\t// Embedded Sandbox: Local execution (no plugin needed)\n\t\tsandboxConfig.Mode = \"embedded\"\n\t}\n\n\treturn json.MarshalIndent(sandboxConfig, \"\", \" \")\n}\n\n// ============================================================\n// Helper Conversion Methods\n// ============================================================\n\n// convertAgentDefaultsFromHarness converts harness to agent defaults configuration.\nfunc (c *ConfigConverter) convertAgentDefaultsFromHarness(harnessCfg *handler.HarnessConfig) *AgentDefaultsConfig {\n\tdefaults := &AgentDefaultsConfig{}\n\n\tif harnessCfg.Model != nil {\n\t\tdefaults.Model = &AgentModelConfig{\n\t\t\tPrimary: harnessCfg.Model.DefaultModel,\n\t\t}\n\t}\n\n\treturn defaults\n}\n\n// convertModelsConfig converts harness model config to OpenClaw models section.\nfunc (c *ConfigConverter) convertModelsConfig(harnessCfg *handler.HarnessConfig) *ModelsConfig {\n\tif harnessCfg == nil || harnessCfg.Model == nil {\n\t\treturn nil\n\t}\n\n\tmodelsConfig := &ModelsConfig{\n\t\tProviders: map[string]*ModelProviderConfig{},\n\t}\n\n\tprovider := harnessCfg.Model.Provider\n\tmodelsConfig.Providers[provider] = &ModelProviderConfig{\n\t\tBaseURL: harnessCfg.Model.Endpoint,\n\t\tAPIKey: harnessCfg.Model.AuthSecretRef,\n\t\tModels: c.convertModelListFromSpec(harnessCfg.Model.Models),\n\t}\n\n\treturn modelsConfig\n}\n\n// convertSkillsConfig converts harness skills config to OpenClaw skills section.\nfunc (c *ConfigConverter) convertSkillsConfig(harnessCfg *handler.HarnessConfig) *SkillsConfig {\n\tif harnessCfg == nil || harnessCfg.Skills == nil {\n\t\treturn nil\n\t}\n\n\treturn &SkillsConfig{\n\t\tHubType: harnessCfg.Skills.HubType,\n\t\tEndpoint: harnessCfg.Skills.Endpoint,\n\t\tLocalPath: harnessCfg.Skills.LocalPath,\n\t\tSkills: c.convertSkillItemsFromSpec(harnessCfg.Skills.Skills),\n\t}\n}\n\n// convertMemoryConfig converts harness memory config to OpenClaw memory section.\nfunc (c *ConfigConverter) convertMemoryConfig(harnessCfg *handler.HarnessConfig) *MemoryConfig {\n\tif harnessCfg == nil || harnessCfg.Memory == nil {\n\t\treturn nil\n\t}\n\n\treturn &MemoryConfig{\n\t\tType: harnessCfg.Memory.Type,\n\t\tEndpoint: harnessCfg.Memory.Endpoint,\n\t\tTTL: int64(harnessCfg.Memory.TTL),\n\t\tPersistence: harnessCfg.Memory.PersistenceEnabled,\n\t}\n}\n\n// convertSandboxConfig converts harness sandbox config to OpenClaw sandbox section.\n// For External Sandbox, configures plugins path for harness-bridge plugin.\nfunc (c *ConfigConverter) convertSandboxConfig(harnessCfg *handler.HarnessConfig) *SandboxConfig {\n\tif harnessCfg == nil || harnessCfg.Sandbox == nil {\n\t\treturn nil\n\t}\n\n\tsandboxConfig := &SandboxConfig{\n\t\tMode: string(harnessCfg.Sandbox.Mode),\n\t}\n\n\tif harnessCfg.Sandbox.Mode == v1.SandboxModeExternal {\n\t\t// External Sandbox: Configure plugins path for harness-bridge plugin\n\t\tpluginDir := \"/etc/aiagent/plugins/harness-bridge\"\n\t\tsandboxConfig.Endpoint = harnessCfg.Sandbox.Endpoint\n\t\tsandboxConfig.Timeout = int64(harnessCfg.Sandbox.Timeout)\n\t\tsandboxConfig.Plugins = &PluginsConfig{\n\t\t\tLoad: &PluginsLoadConfig{\n\t\t\t\tPaths: []string{pluginDir},\n\t\t\t},\n\t\t}\n\t}\n\n\treturn sandboxConfig\n}\n\n// convertModelList converts string array to model definitions.\nfunc (c *ConfigConverter) convertModelList(models []string) []*ModelDefinitionConfig {\n\tif len(models) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*ModelDefinitionConfig, len(models))\n\tfor i, name := range models {\n\t\tresult[i] = &ModelDefinitionConfig{\n\t\t\tID: name,\n\t\t\tName: name,\n\t\t}\n\t}\n\treturn result\n}\n\n// convertModelListFromSpec converts spec model items to model definitions.\nfunc (c *ConfigConverter) convertModelListFromSpec(models []v1.ModelConfig) []*ModelDefinitionConfig {\n\tif len(models) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*ModelDefinitionConfig, len(models))\n\tfor i, m := range models {\n\t\tresult[i] = &ModelDefinitionConfig{\n\t\t\tID: m.Name,\n\t\t\tName: m.Name,\n\t\t}\n\t}\n\treturn result\n}\n\n// convertSkillInfoList converts SkillInfo to OpenClaw skill items.\nfunc (c *ConfigConverter) convertSkillInfoList(skills []handler.SkillInfo) []*SkillItemConfig {\n\tif len(skills) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*SkillItemConfig, len(skills))\n\tfor i, s := range skills {\n\t\tresult[i] = &SkillItemConfig{\n\t\t\tName: s.Name,\n\t\t\tVersion: s.Version,\n\t\t\tPath: s.Path,\n\t\t\tAllowed: s.Allowed,\n\t\t}\n\t}\n\treturn result\n}\n\n// convertSkillItemsFromSpec converts spec skill items to OpenClaw skill items.\nfunc (c *ConfigConverter) convertSkillItemsFromSpec(skills []v1.SkillConfig) []*SkillItemConfig {\n\tif len(skills) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*SkillItemConfig, len(skills))\n\tfor i, s := range skills {\n\t\tresult[i] = &SkillItemConfig{\n\t\t\tName: s.Name,\n\t\t\tVersion: s.Version,\n\t\t\tAllowed: s.Allowed,\n\t\t}\n\t}\n\treturn result\n}\n\n// applySpecOverrides applies HarnessOverrideSpec from AIAgentSpec.\nfunc (c *ConfigConverter) applySpecOverrides(cfg *AgentConfig, override v1.HarnessOverrideSpec) {\n\t// Apply skills overrides\n\tfor _, skillsOverride := range override.Skills {\n\t\tcfg.Skills = append(cfg.Skills, skillsOverride.AllowedSkills...)\n\t}\n\n\t// Apply model overrides\n\tfor _, modelOverride := range override.Model {\n\t\tif len(modelOverride.AllowedModels) > 0 {\n\t\t\tif cfg.Model == nil {\n\t\t\t\tcfg.Model = &AgentModelConfig{}\n\t\t\t}\n\t\t\tif cfg.Model.Primary == \"\" && len(modelOverride.AllowedModels) > 0 {\n\t\t\t\tcfg.Model.Primary = modelOverride.AllowedModels[0]\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ============================================================\n// OpenClaw Config Types (matching TypeScript types)\n// ============================================================\n\n// OpenClawConfig represents the full OpenClaw config file structure.\ntype OpenClawConfig struct {\n\tAgents *AgentsConfig `json:\"agents,omitempty\"`\n\tModels *ModelsConfig `json:\"models,omitempty\"`\n\tSkills *SkillsConfig `json:\"skills,omitempty\"`\n\tTools *ToolsConfig `json:\"tools,omitempty\"`\n\tMemory *MemoryConfig `json:\"memory,omitempty\"`\n\tGateway *GatewayConfig `json:\"gateway,omitempty\"`\n\tChannels *ChannelsConfig `json:\"channels,omitempty\"`\n\tSandbox *SandboxConfig `json:\"sandbox,omitempty\"`\n\tPlugins *PluginsConfig `json:\"plugins,omitempty\"`\n}\n\n// AgentsConfig represents agents configuration.\ntype AgentsConfig struct {\n\tDefaults *AgentDefaultsConfig `json:\"defaults,omitempty\"`\n\tList []*AgentConfig `json:\"list,omitempty\"`\n}\n\n// AgentDefaultsConfig represents default agent configuration.\ntype AgentDefaultsConfig struct {\n\tModel *AgentModelConfig `json:\"model,omitempty\"`\n\tIdentity *IdentityConfig `json:\"identity,omitempty\"`\n\tThinkingDefault string `json:\"thinkingDefault,omitempty\"`\n\tReasoningDefault string `json:\"reasoningDefault,omitempty\"`\n\tFastModeDefault bool `json:\"fastModeDefault,omitempty\"`\n}\n\n// AgentConfig represents a single agent configuration.\ntype AgentConfig struct {\n\tID string `json:\"id\"`\n\tName string `json:\"name,omitempty\"`\n\tDescription string `json:\"description,omitempty\"`\n\tWorkspace string `json:\"workspace,omitempty\"`\n\tModel *AgentModelConfig `json:\"model,omitempty\"`\n\tSkills []string `json:\"skills,omitempty\"`\n\tTools *AgentToolsConfig `json:\"tools,omitempty\"`\n\tIdentity *IdentityConfig `json:\"identity,omitempty\"`\n\tSubagents *SubagentsConfigOpenClaw `json:\"subagents,omitempty\"`\n}\n\n// AgentModelConfig represents agent model configuration.\ntype AgentModelConfig struct {\n\tPrimary string `json:\"primary,omitempty\"`\n\tFallbacks []string `json:\"fallbacks,omitempty\"`\n\tTemperature float64 `json:\"temperature,omitempty\"`\n\tMaxTokens int `json:\"maxTokens,omitempty\"`\n}\n\n// AgentToolsConfig represents agent tools configuration.\ntype AgentToolsConfig struct {\n\tAllow []string `json:\"allow,omitempty\"`\n\tDeny []string `json:\"deny,omitempty\"`\n}\n\n// IdentityConfig represents agent identity configuration.\ntype IdentityConfig struct {\n\tName string `json:\"name,omitempty\"`\n\tAvatar string `json:\"avatar,omitempty\"`\n}\n\n// SubagentsConfigOpenClaw represents sub-agent configuration.\ntype SubagentsConfigOpenClaw struct {\n\tAllowAgents []string `json:\"allowAgents,omitempty\"`\n\tModel *AgentModelConfig `json:\"model,omitempty\"`\n\tRequireAgentID bool `json:\"requireAgentId,omitempty\"`\n}\n\n// ModelsConfig represents models configuration.\ntype ModelsConfig struct {\n\tMode string `json:\"mode,omitempty\"`\n\tProviders map[string]*ModelProviderConfig `json:\"providers,omitempty\"`\n}\n\n// ModelProviderConfig represents a model provider.\ntype ModelProviderConfig struct {\n\tBaseURL string `json:\"baseUrl,omitempty\"`\n\tAPIKey string `json:\"apiKey,omitempty\"`\n\tModels []*ModelDefinitionConfig `json:\"models,omitempty\"`\n}\n\n// ModelDefinitionConfig represents a model definition.\ntype ModelDefinitionConfig struct {\n\tID string `json:\"id\"`\n\tName string `json:\"name,omitempty\"`\n}\n\n// SkillsConfig represents skills configuration.\ntype SkillsConfig struct {\n\tHubType string `json:\"hubType,omitempty\"`\n\tEndpoint string `json:\"endpoint,omitempty\"`\n\tLocalPath string `json:\"localPath,omitempty\"`\n\tSkills []*SkillItemConfig `json:\"skills,omitempty\"`\n}\n\n// SkillItemConfig represents a skill item.\ntype SkillItemConfig struct {\n\tName string `json:\"name\"`\n\tVersion string `json:\"version,omitempty\"`\n\tPath string `json:\"path,omitempty\"`\n\tAllowed bool `json:\"allowed,omitempty\"`\n}\n\n// MemoryConfig represents memory configuration.\ntype MemoryConfig struct {\n\tType string `json:\"type,omitempty\"`\n\tEndpoint string `json:\"endpoint,omitempty\"`\n\tTTL int64 `json:\"ttl,omitempty\"`\n\tPersistence bool `json:\"persistence,omitempty\"`\n}\n\n// GatewayConfig represents gateway configuration.\ntype GatewayConfig struct {\n\tMode string `json:\"mode,omitempty\"`\n\tHost string `json:\"host,omitempty\"`\n\tPort int `json:\"port,omitempty\"`\n\tAuthMode string `json:\"authMode,omitempty\"`\n\tControlUI *ControlUIConfig `json:\"controlUi,omitempty\"`\n\tTrustedProxies []string `json:\"trustedProxies,omitempty\"`\n}\n\n// ControlUIConfig represents Control UI configuration.\ntype ControlUIConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tBasePath string `json:\"basePath,omitempty\"`\n\tAllowedOrigins []string `json:\"allowedOrigins,omitempty\"`\n}\n\n// ToolsConfig represents tools configuration.\ntype ToolsConfig struct {\n\tAllow []string `json:\"allow,omitempty\"`\n\tDeny []string `json:\"deny,omitempty\"`\n}\n\n// ChannelsConfig represents channels configuration.\ntype ChannelsConfig struct {\n\tTelegram *TelegramConfig `json:\"telegram,omitempty\"`\n\tDiscord *DiscordConfig `json:\"discord,omitempty\"`\n\tSlack *SlackConfig `json:\"slack,omitempty\"`\n}\n\n// TelegramConfig represents Telegram channel config.\ntype TelegramConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n}\n\n// DiscordConfig represents Discord channel config.\ntype DiscordConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n}\n\n// SlackConfig represents Slack channel config.\ntype SlackConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n}\n\n// HarnessSection represents harness-only config section.\ntype HarnessSection struct {\n\tModel *ModelsConfig `json:\"model,omitempty\"`\n\tSkills *SkillsConfig `json:\"skills,omitempty\"`\n\tMemory *MemoryConfig `json:\"memory,omitempty\"`\n}\n\n// SandboxConfig represents sandbox configuration.\n// For External Sandbox, includes plugins configuration for harness-bridge.\ntype SandboxConfig struct {\n\t// Mode: \"external\" or \"embedded\"\n\tMode string `json:\"mode\"`\n\n\t// Endpoint for External Sandbox (HTTP API URL)\n\tEndpoint string `json:\"endpoint,omitempty\"`\n\n\t// Timeout for sandbox operations (seconds)\n\tTimeout int64 `json:\"timeout,omitempty\"`\n\n\t// Plugins configuration for External Sandbox\n\tPlugins *PluginsConfig `json:\"plugins,omitempty\"`\n}\n\n// PluginsConfig represents OpenClaw plugins configuration.\ntype PluginsConfig struct {\n\t// Load configuration for plugin discovery\n\tLoad *PluginsLoadConfig `json:\"load,omitempty\"`\n}\n\n// PluginsLoadConfig specifies plugin discovery paths.\ntype PluginsLoadConfig struct {\n\t// Paths to search for plugins\n\tPaths []string `json:\"paths,omitempty\"`\n}"}, "24237c39c208883cd3d33da149520f002aabf6a2": {"t": 1785755075.1564605, "n": 19988, "h": "7ddde9c3998e965c", "c": "// Package openclaw provides configuration conversion for OpenClaw framework.\n// This converter transforms AIAgentSpec + HarnessConfig into OpenClaw JSON config.\n//\n// OpenClaw Config Structure (from src/config/types.openclaw.ts):\n// ┌─────────────────────────────────────────────────────────────────┐\n// │ openclaw.json │\n// ├─────────────────────────────────────────────────────────────────┤\n// │ { │\n// │ \"agents\": { │\n// │ \"defaults\": { ... }, // Default agent configuration │\n// │ \"list\": [ ... ] // Agent instances │\n// │ }, │\n// │ \"models\": { // Model providers │\n// │ \"providers\": { │\n// │ \"deepseek\": { \"baseUrl\": \"...\", \"apiKey\": \"...\" } │\n// │ } │\n// │ }, │\n// │ \"skills\": { ... }, // Skills configuration │\n// │ \"tools\": { ... }, // Tools configuration │\n// │ \"memory\": { ... }, // Memory configuration │\n// │ \"gateway\": { ... }, // Gateway configuration │\n// │ \"channels\": { ... } // Channel configuration │\n// │ } │\n// └─────────────────────────────────────────────────────────────────┘\npackage openclaw\n\nimport (\n\t\"encoding/json\"\n\n\t\"aiagent/api/v1\"\n\t\"aiagent/pkg/handler\"\n)\n\n// ConfigConverter converts AIAgentSpec and HarnessConfig to OpenClaw config.\ntype ConfigConverter struct{}\n\n// NewConfigConverter creates a new config converter.\nfunc NewConfigConverter() *ConfigConverter {\n\treturn &ConfigConverter{}\n}\n\n// ============================================================\n// Core Conversion Methods (called by Handler)\n// ============================================================\n\n// ConvertToOpenClawConfig generates full openclaw.json from AIAgentSpec and HarnessConfig.\n// This is the primary method called by GenerateFrameworkConfig.\nfunc (c *ConfigConverter) ConvertToOpenClawConfig(spec *v1.AIAgentSpec, harnessCfg *handler.HarnessConfig) ([]byte, error) {\n\tconfig := &OpenClawConfig{\n\t\tAgents: &AgentsConfig{},\n\t\tGateway: &GatewayConfig{\n\t\t\tMode: \"local\",\n\t\t},\n\t}\n\n\t// Convert agent defaults from harness\n\tif harnessCfg != nil {\n\t\tconfig.Agents.Defaults = c.convertAgentDefaults(harnessCfg)\n\t\tconfig.Models = c.convertModelsConfig(harnessCfg)\n\t\tconfig.Skills = c.convertSkillsConfig(harnessCfg)\n\t\tconfig.Memory = c.convertMemoryConfig(harnessCfg)\n\t}\n\n\t// Convert agent from spec\n\tagentCfg, err := c.ConvertAgentSpec(spec, harnessCfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconfig.Agents.List = []*AgentConfig{agentCfg}\n\n\treturn json.MarshalIndent(config, \"\", \" \")\n}\n\n// ConvertAgentSpec generates AgentConfig section for a single agent.\n// This is called by GenerateAgentConfig.\nfunc (c *ConfigConverter) ConvertAgentSpec(spec *v1.AIAgentSpec, harnessCfg *handler.HarnessConfig) (*AgentConfig, error) {\n\tagentCfg := &AgentConfig{\n\t\tID: spec.Description,\n\t\tName: spec.Description,\n\t\tDescription: spec.Description,\n\t}\n\n\t// Apply model from harness\n\tif harnessCfg != nil && harnessCfg.Model != nil {\n\t\tagentCfg.Model = &AgentModelConfig{\n\t\t\tPrimary: harnessCfg.Model.DefaultModel,\n\t\t}\n\t}\n\n\t// Apply skills from harness\n\tif harnessCfg != nil && harnessCfg.Skills != nil {\n\t\tfor _, skill := range harnessCfg.Skills.Skills {\n\t\t\tif skill.Allowed {\n\t\t\t\tagentCfg.Skills = append(agentCfg.Skills, skill.Name)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Apply overrides from spec\n\tc.applySpecOverrides(agentCfg, spec.HarnessOverride)\n\n\treturn agentCfg, nil\n}\n\n// ConvertHarnessConfig generates harness-specific config sections.\n// This is called by GenerateHarnessConfig.\nfunc (c *ConfigConverter) ConvertHarnessConfig(harnessCfg *handler.HarnessConfig) ([]byte, error) {\n\tharnessSection := &HarnessSection{\n\t\tModel: c.convertModelsConfig(harnessCfg),\n\t\tSkills: c.convertSkillsConfig(harnessCfg),\n\t\tMemory: c.convertMemoryConfig(harnessCfg),\n\t\tMCP: c.convertMCPConfig(harnessCfg),\n\t\tSandbox: c.convertSandboxConfig(harnessCfg),\n\t}\n\n\treturn json.MarshalIndent(harnessSection, \"\", \" \")\n}\n\n// ============================================================\n// Harness Interface Conversion Methods\n// ============================================================\n\n// ConvertModelHarness converts ModelHarnessInterface to OpenClaw models config.\nfunc (c *ConfigConverter) ConvertModelHarness(harness handler.ModelHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tmodelConfig := &ModelsConfig{\n\t\tProviders: map[string]*ModelProviderConfig{},\n\t}\n\n\tprovider := harness.GetProvider()\n\tmodelConfig.Providers[provider] = &ModelProviderConfig{\n\t\tBaseURL: harness.GetEndpoint(),\n\t\tAPIKey: harness.GetAPIKeyRef(),\n\t\tModels: c.convertModelList(harness.GetAllowedModels()),\n\t}\n\n\treturn json.MarshalIndent(modelConfig, \"\", \" \")\n}\n\n// ConvertMCPHarness converts MCPHarnessInterface to OpenClaw MCP config.\nfunc (c *ConfigConverter) ConvertMCPHarness(harness handler.MCPHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tmcpConfig := &MCPConfig{\n\t\tRegistryType: harness.GetRegistryType(),\n\t\tEndpoint: harness.GetEndpoint(),\n\t\tServers: c.convertMCPServers(harness.GetServers()),\n\t}\n\n\treturn json.MarshalIndent(mcpConfig, \"\", \" \")\n}\n\n// ConvertMemoryHarness converts MemoryHarnessInterface to OpenClaw memory config.\nfunc (c *ConfigConverter) ConvertMemoryHarness(harness handler.MemoryHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tmemoryConfig := &MemoryConfig{\n\t\tType: harness.GetType(),\n\t\tEndpoint: harness.GetEndpoint(),\n\t\tTTL: harness.GetTTL(),\n\t\tPersistence: harness.IsPersistenceEnabled(),\n\t}\n\n\treturn json.MarshalIndent(memoryConfig, \"\", \" \")\n}\n\n// ConvertSandboxHarness converts SandboxHarnessInterface to OpenClaw sandbox config.\nfunc (c *ConfigConverter) ConvertSandboxHarness(harness handler.SandboxHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tsandboxConfig := &SandboxConfig{\n\t\tMode: string(harness.GetMode()),\n\t\tEndpoint: harness.GetEndpoint(),\n\t\tTimeout: harness.GetTimeout(),\n\t}\n\n\tif limits := harness.GetResourceLimits(); limits != nil {\n\t\tsandboxConfig.Resources = &ResourceConfig{\n\t\t\tCPU: limits.CPU,\n\t\t\tMemory: limits.Memory,\n\t\t}\n\t}\n\n\treturn json.MarshalIndent(sandboxConfig, \"\", \" \")\n}\n\n// ConvertSkillsHarness converts SkillsHarnessInterface to OpenClaw skills config.\nfunc (c *ConfigConverter) ConvertSkillsHarness(harness handler.SkillsHarnessInterface) ([]byte, error) {\n\tif harness == nil {\n\t\treturn nil, nil\n\t}\n\n\tskillsConfig := &SkillsConfig{\n\t\tHubType: harness.GetHubType(),\n\t\tEndpoint: harness.GetEndpoint(),\n\t\tSkills: c.convertSkillInfoList(harness.GetSkills()),\n\t}\n\n\treturn json.MarshalIndent(skillsConfig, \"\", \" \")\n}\n\n// ============================================================\n// Helper Conversion Methods\n// ============================================================\n\n// convertAgentDefaults converts harness to agent defaults configuration.\nfunc (c *ConfigConverter) convertAgentDefaults(harnessCfg *handler.HarnessConfig) *AgentDefaultsConfig {\n\tdefaults := &AgentDefaultsConfig{}\n\n\tif harnessCfg.Model != nil {\n\t\tdefaults.Model = &AgentModelConfig{\n\t\t\tPrimary: harnessCfg.Model.DefaultModel,\n\t\t}\n\t}\n\n\tif harnessCfg.Sandbox != nil {\n\t\tdefaults.Sandbox = &AgentSandboxConfig{\n\t\t\tEnabled: harnessCfg.Sandbox.Mode != \"\",\n\t\t\tMode: string(harnessCfg.Sandbox.Mode),\n\t\t}\n\t}\n\n\treturn defaults\n}\n\n// convertModelsConfig converts harness model config to OpenClaw models section.\nfunc (c *ConfigConverter) convertModelsConfig(harnessCfg *handler.HarnessConfig) *ModelsConfig {\n\tif harnessCfg == nil || harnessCfg.Model == nil {\n\t\treturn nil\n\t}\n\n\tmodelsConfig := &ModelsConfig{\n\t\tProviders: map[string]*ModelProviderConfig{},\n\t}\n\n\tprovider := harnessCfg.Model.Provider\n\tmodelsConfig.Providers[provider] = &ModelProviderConfig{\n\t\tBaseURL: harnessCfg.Model.Endpoint,\n\t\tAPIKey: harnessCfg.Model.AuthSecretRef,\n\t\tModels: c.convertModelListFromSpec(harnessCfg.Model.Models),\n\t}\n\n\treturn modelsConfig\n}\n\n// convertSkillsConfig converts harness skills config to OpenClaw skills section.\nfunc (c *ConfigConverter) convertSkillsConfig(harnessCfg *handler.HarnessConfig) *SkillsConfig {\n\tif harnessCfg == nil || harnessCfg.Skills == nil {\n\t\treturn nil\n\t}\n\n\treturn &SkillsConfig{\n\t\tHubType: harnessCfg.Skills.HubType,\n\t\tEndpoint: harnessCfg.Skills.Endpoint,\n\t\tLocalPath: harnessCfg.Skills.LocalPath,\n\t\tSkills: c.convertSkillItemsFromSpec(harnessCfg.Skills.Skills),\n\t}\n}\n\n// convertMemoryConfig converts harness memory config to OpenClaw memory section.\nfunc (c *ConfigConverter) convertMemoryConfig(harnessCfg *handler.HarnessConfig) *MemoryConfig {\n\tif harnessCfg == nil || harnessCfg.Memory == nil {\n\t\treturn nil\n\t}\n\n\treturn &MemoryConfig{\n\t\tType: harnessCfg.Memory.Type,\n\t\tEndpoint: harnessCfg.Memory.Endpoint,\n\t\tTTL: int64(harnessCfg.Memory.TTL),\n\t\tPersistence: harnessCfg.Memory.PersistenceEnabled,\n\t}\n}\n\n// convertMCPConfig converts harness MCP config to OpenClaw MCP section.\nfunc (c *ConfigConverter) convertMCPConfig(harnessCfg *handler.HarnessConfig) *MCPConfig {\n\tif harnessCfg == nil || harnessCfg.MCP == nil {\n\t\treturn nil\n\t}\n\n\treturn &MCPConfig{\n\t\tRegistryType: harnessCfg.MCP.RegistryType,\n\t\tEndpoint: harnessCfg.MCP.Endpoint,\n\t\tServers: c.convertMCPServersFromSpec(harnessCfg.MCP.Servers),\n\t}\n}\n\n// convertSandboxConfig converts harness sandbox config to OpenClaw sandbox section.\nfunc (c *ConfigConverter) convertSandboxConfig(harnessCfg *handler.HarnessConfig) *SandboxConfig {\n\tif harnessCfg == nil || harnessCfg.Sandbox == nil {\n\t\treturn nil\n\t}\n\n\tsandboxConfig := &SandboxConfig{\n\t\tMode: string(harnessCfg.Sandbox.Mode),\n\t\tEndpoint: harnessCfg.Sandbox.Endpoint,\n\t\tTimeout: int64(harnessCfg.Sandbox.Timeout),\n\t}\n\n\tif harnessCfg.Sandbox.ResourceLimits != nil {\n\t\tsandboxConfig.Resources = &ResourceConfig{\n\t\t\tCPU: harnessCfg.Sandbox.ResourceLimits.CPU,\n\t\t\tMemory: harnessCfg.Sandbox.ResourceLimits.Memory,\n\t\t}\n\t}\n\n\treturn sandboxConfig\n}\n\n// convertModelList converts string array to model definitions.\nfunc (c *ConfigConverter) convertModelList(models []string) []*ModelDefinitionConfig {\n\tif len(models) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*ModelDefinitionConfig, len(models))\n\tfor i, name := range models {\n\t\tresult[i] = &ModelDefinitionConfig{\n\t\t\tID: name,\n\t\t\tName: name,\n\t\t}\n\t}\n\treturn result\n}\n\n// convertModelListFromSpec converts spec model items to model definitions.\nfunc (c *ConfigConverter) convertModelListFromSpec(models []v1.ModelConfig) []*ModelDefinitionConfig {\n\tif len(models) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*ModelDefinitionConfig, len(models))\n\tfor i, m := range models {\n\t\tresult[i] = &ModelDefinitionConfig{\n\t\t\tID: m.Name,\n\t\t\tName: m.Name,\n\t\t}\n\t}\n\treturn result\n}\n\n// convertMCPServers converts MCPServerInfo to OpenClaw MCP servers.\nfunc (c *ConfigConverter) convertMCPServers(servers []handler.MCPServerInfo) []*MCPServerConfig {\n\tif len(servers) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*MCPServerConfig, len(servers))\n\tfor i, s := range servers {\n\t\tresult[i] = &MCPServerConfig{\n\t\t\tName: s.Name,\n\t\t\tType: s.Type,\n\t\t\tCommand: s.Command,\n\t\t\tArgs: s.Args,\n\t\t\tURL: s.Endpoint,\n\t\t\tAllowed: s.Allowed,\n\t\t}\n\t}\n\treturn result\n}\n\n// convertMCPServersFromSpec converts spec MCP servers to OpenClaw MCP servers.\nfunc (c *ConfigConverter) convertMCPServersFromSpec(servers []v1.MCPServerConfig) []*MCPServerConfig {\n\tif len(servers) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*MCPServerConfig, len(servers))\n\tfor i, s := range servers {\n\t\tresult[i] = &MCPServerConfig{\n\t\t\tName: s.Name,\n\t\t\tType: s.Type,\n\t\t\tCommand: s.Command,\n\t\t\tArgs: s.Args,\n\t\t\tURL: s.URL,\n\t\t\tAllowed: s.Allowed,\n\t\t}\n\t}\n\treturn result\n}\n\n// convertSkillInfoList converts SkillInfo to OpenClaw skill items.\nfunc (c *ConfigConverter) convertSkillInfoList(skills []handler.SkillInfo) []*SkillItemConfig {\n\tif len(skills) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*SkillItemConfig, len(skills))\n\tfor i, s := range skills {\n\t\tresult[i] = &SkillItemConfig{\n\t\t\tName: s.Name,\n\t\t\tVersion: s.Version,\n\t\t\tPath: s.Path,\n\t\t\tAllowed: s.Allowed,\n\t\t}\n\t}\n\treturn result\n}\n\n// convertSkillItemsFromSpec converts spec skill items to OpenClaw skill items.\nfunc (c *ConfigConverter) convertSkillItemsFromSpec(skills []v1.SkillConfig) []*SkillItemConfig {\n\tif len(skills) == 0 {\n\t\treturn nil\n\t}\n\n\tresult := make([]*SkillItemConfig, len(skills))\n\tfor i, s := range skills {\n\t\tresult[i] = &SkillItemConfig{\n\t\t\tName: s.Name,\n\t\t\tVersion: s.Version,\n\t\t\tAllowed: s.Allowed,\n\t\t}\n\t}\n\treturn result\n}\n\n// applySpecOverrides applies spec overrides to agent config.\nfunc (c *ConfigConverter) applySpecOverrides(cfg *AgentConfig, override v1.HarnessOverrideSpec) {\n\t// Apply skills overrides\n\tfor _, skillsOverride := range override.Skills {\n\t\tcfg.Skills = append(cfg.Skills, skillsOverride.AllowedSkills...)\n\t}\n\n\t// Apply model overrides\n\tfor _, modelOverride := range override.Model {\n\t\tif len(modelOverride.AllowedModels) > 0 {\n\t\t\tif cfg.Model == nil {\n\t\t\t\tcfg.Model = &AgentModelConfig{}\n\t\t\t}\n\t\t\tif cfg.Model.Primary == \"\" && len(modelOverride.AllowedModels) > 0 {\n\t\t\t\tcfg.Model.Primary = modelOverride.AllowedModels[0]\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ============================================================\n// OpenClaw Config Types (matching TypeScript types)\n// ============================================================\n\n// OpenClawConfig represents the full OpenClaw config file structure.\ntype OpenClawConfig struct {\n\tAgents *AgentsConfig `json:\"agents,omitempty\"`\n\tModels *ModelsConfig `json:\"models,omitempty\"`\n\tSkills *SkillsConfig `json:\"skills,omitempty\"`\n\tTools *ToolsConfig `json:\"tools,omitempty\"`\n\tMemory *MemoryConfig `json:\"memory,omitempty\"`\n\tGateway *GatewayConfig `json:\"gateway,omitempty\"`\n\tChannels *ChannelsConfig `json:\"channels,omitempty\"`\n\tMCP *MCPConfig `json:\"mcp,omitempty\"`\n}\n\n// AgentsConfig represents agents configuration.\ntype AgentsConfig struct {\n\tDefaults *AgentDefaultsConfig `json:\"defaults,omitempty\"`\n\tList []*AgentConfig `json:\"list,omitempty\"`\n}\n\n// AgentDefaultsConfig represents default agent configuration.\ntype AgentDefaultsConfig struct {\n\tModel *AgentModelConfig `json:\"model,omitempty\"`\n\tSandbox *AgentSandboxConfig `json:\"sandbox,omitempty\"`\n\tIdentity *IdentityConfig `json:\"identity,omitempty\"`\n}\n\n// AgentConfig represents a single agent configuration.\ntype AgentConfig struct {\n\tID string `json:\"id\"`\n\tName string `json:\"name,omitempty\"`\n\tDescription string `json:\"description,omitempty\"`\n\tWorkspace string `json:\"workspace,omitempty\"`\n\tModel *AgentModelConfig `json:\"model,omitempty\"`\n\tSkills []string `json:\"skills,omitempty\"`\n\tTools []string `json:\"tools,omitempty\"`\n\tRuntime *RuntimeConfig `json:\"runtime,omitempty\"`\n}\n\n// AgentModelConfig represents agent model configuration.\ntype AgentModelConfig struct {\n\tPrimary string `json:\"primary,omitempty\"`\n\tFallbacks []string `json:\"fallbacks,omitempty\"`\n\tTemperature float64 `json:\"temperature,omitempty\"`\n\tMaxTokens int `json:\"maxTokens,omitempty\"`\n}\n\n// AgentSandboxConfig represents agent sandbox configuration.\ntype AgentSandboxConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tMode string `json:\"mode,omitempty\"`\n}\n\n// RuntimeConfig represents agent runtime configuration.\ntype RuntimeConfig struct {\n\tType string `json:\"type,omitempty\"`\n}\n\n// ModelsConfig represents models configuration.\ntype ModelsConfig struct {\n\tMode string `json:\"mode,omitempty\"`\n\tProviders map[string]*ModelProviderConfig `json:\"providers,omitempty\"`\n}\n\n// ModelProviderConfig represents a model provider.\ntype ModelProviderConfig struct {\n\tBaseURL string `json:\"baseUrl,omitempty\"`\n\tAPIKey string `json:\"apiKey,omitempty\"`\n\tModels []*ModelDefinitionConfig `json:\"models,omitempty\"`\n}\n\n// ModelDefinitionConfig represents a model definition.\ntype ModelDefinitionConfig struct {\n\tID string `json:\"id\"`\n\tName string `json:\"name,omitempty\"`\n}\n\n// SkillsConfig represents skills configuration.\ntype SkillsConfig struct {\n\tHubType string `json:\"hubType,omitempty\"`\n\tEndpoint string `json:\"endpoint,omitempty\"`\n\tLocalPath string `json:\"localPath,omitempty\"`\n\tSkills []*SkillItemConfig `json:\"skills,omitempty\"`\n}\n\n// SkillItemConfig represents a skill item.\ntype SkillItemConfig struct {\n\tName string `json:\"name\"`\n\tVersion string `json:\"version,omitempty\"`\n\tPath string `json:\"path,omitempty\"`\n\tAllowed bool `json:\"allowed,omitempty\"`\n}\n\n// MemoryConfig represents memory configuration.\ntype MemoryConfig struct {\n\tType string `json:\"type,omitempty\"`\n\tEndpoint string `json:\"endpoint,omitempty\"`\n\tTTL int64 `json:\"ttl,omitempty\"`\n\tPersistence bool `json:\"persistence,omitempty\"`\n}\n\n// MCPConfig represents MCP configuration.\ntype MCPConfig struct {\n\tRegistryType string `json:\"registryType,omitempty\"`\n\tEndpoint string `json:\"endpoint,omitempty\"`\n\tServers []*MCPServerConfig `json:\"servers,omitempty\"`\n}\n\n// MCPServerConfig represents an MCP server.\ntype MCPServerConfig struct {\n\tName string `json:\"name\"`\n\tType string `json:\"type,omitempty\"`\n\tCommand string `json:\"command,omitempty\"`\n\tArgs []string `json:\"args,omitempty\"`\n\tURL string `json:\"url,omitempty\"`\n\tAllowed bool `json:\"allowed,omitempty\"`\n}\n\n// SandboxConfig represents sandbox configuration.\ntype SandboxConfig struct {\n\tMode string `json:\"mode,omitempty\"`\n\tEndpoint string `json:\"endpoint,omitempty\"`\n\tTimeout int64 `json:\"timeout,omitempty\"`\n\tResources *ResourceConfig `json:\"resources,omitempty\"`\n}\n\n// ResourceConfig represents resource limits.\ntype ResourceConfig struct {\n\tCPU string `json:\"cpu,omitempty\"`\n\tMemory string `json:\"memory,omitempty\"`\n}\n\n// GatewayConfig represents gateway configuration.\ntype GatewayConfig struct {\n\tMode string `json:\"mode,omitempty\"`\n\tHost string `json:\"host,omitempty\"`\n\tPort int `json:\"port,omitempty\"`\n}\n\n// ToolsConfig represents tools configuration.\ntype ToolsConfig struct {\n\tAllow []string `json:\"allow,omitempty\"`\n}\n\n// ChannelsConfig represents channels configuration.\ntype ChannelsConfig struct {\n\tTelegram *TelegramConfig `json:\"telegram,omitempty\"`\n\tDiscord *DiscordConfig `json:\"discord,omitempty\"`\n\tSlack *SlackConfig `json:\"slack,omitempty\"`\n}\n\n// TelegramConfig represents Telegram channel config.\ntype TelegramConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n}\n\n// DiscordConfig represents Discord channel config.\ntype DiscordConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n}\n\n// SlackConfig represents Slack channel config.\ntype SlackConfig struct {\n\tEnabled bool `json:\"enabled,omitempty\"`\n\tToken string `json:\"token,omitempty\"`\n}\n\n// IdentityConfig represents agent identity configuration.\ntype IdentityConfig struct {\n\tName string `json:\"name,omitempty\"`\n\tAvatar string `json:\"avatar,omitempty\"`\n}\n\n// HarnessSection represents harness-only config section.\ntype HarnessSection struct {\n\tModel *ModelsConfig `json:\"model,omitempty\"`\n\tSkills *SkillsConfig `json:\"skills,omitempty\"`\n\tMemory *MemoryConfig `json:\"memory,omitempty\"`\n\tMCP *MCPConfig `json:\"mcp,omitempty\"`\n\tSandbox *SandboxConfig `json:\"sandbox,omitempty\"`\n}"}}, "aiagent/cmd/adk-framework/main.go": {"696913067c9d070f9c9a40d5922aa18e8ec609f7": {"t": 1785745631.1272118, "n": 14515, "h": "934bf8802131ee9e", "c": "// ADK Framework launcher.\n// This program reads JSON-RPC requests from stdin and writes responses to stdout.\n// No HTTP/gRPC server - pure stdin/stdout communication.\n// Integrates with adk-go library for real agent execution.\npackage main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"iter\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"google.golang.org/genai\"\n\n\tadkagent \"google.golang.org/adk/agent\"\n\t\"google.golang.org/adk/agent/llmagent\"\n\tadkmodel \"google.golang.org/adk/model\"\n\t\"google.golang.org/adk/runner\"\n\t\"google.golang.org/adk/session\"\n)\n\nvar (\n\tagentID = flag.String(\"agent-id\", \"\", \"Agent ID\")\n\tconfigPath = flag.String(\"config\", \"\", \"Config file path\")\n\tworkDir = flag.String(\"workdir\", \"\", \"Working directory\")\n\tdebug = flag.Bool(\"debug\", false, \"Enable debug logging\")\n\tprocessMode = flag.String(\"process-mode\", \"isolated\", \"Process mode: shared or isolated\")\n)\n\n// Framework holds all agents and the runner\ntype Framework struct {\n\tloader adkagent.Loader\n\trunner *runner.Runner\n\tagents map[string]adkagent.Agent\n\tmodel adkmodel.LLM\n\tappName string\n\tsessionSvc session.Service\n\n\t// For tracking running invocations\n\tmu sync.RWMutex\n\tinvocations map[string]*InvocationState\n}\n\ntype InvocationState struct {\n\tID string\n\tAgentName string\n\tSessionID string\n\tStartTime time.Time\n\tCancel context.CancelFunc\n}\n\nfunc main() {\n\tflag.Parse()\n\n\tlog.Printf(\"ADK Framework starting with adk-go integration...\")\n\tlog.Printf(\"Agent ID: %s\", *agentID)\n\tlog.Printf(\"Config: %s\", *configPath)\n\tlog.Printf(\"Work Dir: %s\", *workDir)\n\tlog.Printf(\"Process Mode: %s\", *processMode)\n\n\tctx := context.Background()\n\n\t// Load configuration\n\tconfig, err := loadConfig(*configPath)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to load config: %v\", err)\n\t}\n\n\t// Create framework with real adk-go agents\n\tframework, err := createFramework(ctx, config)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create framework: %v\", err)\n\t}\n\n\tlog.Printf(\"Framework created with %d agents\", len(framework.agents))\n\tfor name := range framework.agents {\n\t\tlog.Printf(\" - Agent: %s\", name)\n\t}\n\n\t// Start JSON-RPC server (stdin/stdout)\n\tserver := NewStdioJSONRPCServer(os.Stdin, os.Stdout)\n\n\t// Register handlers\n\tserver.RegisterMethod(\"agent.run\", framework.handleAgentRun)\n\tserver.RegisterMethod(\"agent.status\", framework.handleAgentStatus)\n\tserver.RegisterMethod(\"agent.stop\", framework.handleAgentStop)\n\tserver.RegisterMethod(\"agent.list\", framework.handleAgentList)\n\tserver.RegisterMethod(\"framework.status\", framework.handleFrameworkStatus)\n\n\tlog.Printf(\"ADK Framework ready, listening on stdin\")\n\n\t// Run until stdin closes\n\tserver.Run()\n}\n\n// StdioJSONRPCServer handles JSON-RPC over stdin/stdout.\ntype StdioJSONRPCServer struct {\n\tstdin io.Reader\n\tstdout io.Writer\n\n\tmethods map[string]MethodHandler\n\tmu sync.RWMutex\n}\n\ntype MethodHandler func(ctx context.Context, params json.RawMessage) (json.RawMessage, error)\n\ntype jsonRPCRequest struct {\n\tJsonrpc string `json:\"jsonrpc\"`\n\tMethod string `json:\"method\"`\n\tParams json.RawMessage `json:\"params,omitempty\"`\n\tID int `json:\"id,omitempty\"`\n}\n\ntype jsonRPCResponse struct {\n\tJsonrpc string `json:\"jsonrpc\"`\n\tResult json.RawMessage `json:\"result,omitempty\"`\n\tError *jsonRPCError `json:\"error,omitempty\"`\n\tID int `json:\"id\"`\n}\n\ntype jsonRPCError struct {\n\tCode int `json:\"code\"`\n\tMessage string `json:\"message\"`\n}\n\nfunc NewStdioJSONRPCServer(stdin io.Reader, stdout io.Writer) *StdioJSONRPCServer {\n\treturn &StdioJSONRPCServer{\n\t\tstdin: stdin,\n\t\tstdout: stdout,\n\t\tmethods: make(map[string]MethodHandler),\n\t}\n}\n\nfunc (s *StdioJSONRPCServer) RegisterMethod(method string, handler MethodHandler) {\n\ts.mu.Lock()\n\ts.methods[method] = handler\n\ts.mu.Unlock()\n}\n\nfunc (s *StdioJSONRPCServer) Run() {\n\tscanner := bufio.NewScanner(s.stdin)\n\tencoder := json.NewEncoder(s.stdout)\n\n\tfor scanner.Scan() {\n\t\tline := scanner.Bytes()\n\n\t\tvar req jsonRPCRequest\n\t\tif err := json.Unmarshal(line, &req); err != nil {\n\t\t\t// Invalid request, skip\n\t\t\tcontinue\n\t\t}\n\n\t\t// Handle request\n\t\tctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)\n\t\tresult, err := s.handleRequest(ctx, req)\n\t\tcancel()\n\n\t\t// Build response\n\t\tresp := jsonRPCResponse{\n\t\t\tJsonrpc: \"2.0\",\n\t\t\tID: req.ID,\n\t\t}\n\n\t\tif err != nil {\n\t\t\tresp.Error = &jsonRPCError{\n\t\t\t\tCode: -1,\n\t\t\t\tMessage: err.Error(),\n\t\t\t}\n\t\t} else {\n\t\t\tresp.Result = result\n\t\t}\n\n\t\t// Write response\n\t\tencoder.Encode(resp)\n\t}\n\n\tlog.Printf(\"stdin closed, shutting down\")\n}\n\nfunc (s *StdioJSONRPCServer) handleRequest(ctx context.Context, req jsonRPCRequest) (json.RawMessage, error) {\n\ts.mu.RLock()\n\thandler, exists := s.methods[req.Method]\n\ts.mu.RUnlock()\n\n\tif !exists {\n\t\treturn nil, fmt.Errorf(\"method not found: %s\", req.Method)\n\t}\n\n\treturn handler(ctx, req.Params)\n}\n\n// Handler implementations using real adk-go\n\nfunc (f *Framework) handleAgentRun(ctx context.Context, params json.RawMessage) (json.RawMessage, error) {\n\tvar req struct {\n\t\tInvocationID string `json:\"invocation_id\"`\n\t\tSessionID string `json:\"session_id\"`\n\t\tUserID string `json:\"user_id\"`\n\t\tAgentName string `json:\"agent_name\"`\n\t\tMessage string `json:\"message\"`\n\t}\n\n\tif err := json.Unmarshal(params, &req); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid params: %w\", err)\n\t}\n\n\t// Determine which agent to run\n\tagentName := req.AgentName\n\tif agentName == \"\" {\n\t\tagentName = *agentID\n\t}\n\n\t// Get the agent (for verification only)\n\t_, err := f.loader.LoadAgent(agentName)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"agent not found: %s\", err)\n\t}\n\n\t// Create user message content\n\tmsg := genai.NewContentFromText(req.Message, genai.RoleUser)\n\n\t// Run the agent using runner\n\trunConfig := adkagent.RunConfig{\n\t\tStreamingMode: adkagent.StreamingModeNone,\n\t}\n\n\tevents := make([]map[string]any, 0)\n\n\t// Use runner to execute\n\tfor event, runErr := range f.runner.Run(ctx, req.UserID, req.SessionID, msg, runConfig) {\n\t\tif runErr != nil {\n\t\t\tevents = append(events, map[string]any{\n\t\t\t\t\"type\": \"error\",\n\t\t\t\t\"message\": runErr.Error(),\n\t\t\t})\n\t\t\tbreak\n\t\t}\n\n\t\t// Convert event to response format\n\t\teventData := convertEventToMap(event)\n\t\tevents = append(events, eventData)\n\t}\n\n\t// Add completion event\n\tevents = append(events, map[string]any{\"type\": \"complete\"})\n\n\treturn json.Marshal(map[string]any{\"events\": events})\n}\n\nfunc (f *Framework) handleAgentStatus(ctx context.Context, params json.RawMessage) (json.RawMessage, error) {\n\tvar req struct {\n\t\tAgentName string `json:\"agent_name\"`\n\t}\n\n\tif err := json.Unmarshal(params, &req); err != nil {\n\t\treq.AgentName = *agentID\n\t}\n\n\tagentName := req.AgentName\n\tif agentName == \"\" {\n\t\tagentName = *agentID\n\t}\n\n\t// Get agent info from loader\n\tagent, err := f.loader.LoadAgent(agentName)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"agent not found: %s\", err)\n\t}\n\n\tstatus := map[string]any{\n\t\t\"agent_id\": agentName,\n\t\t\"name\": agent.Name(),\n\t\t\"description\": agent.Description(),\n\t\t\"type\": \"llm\",\n\t\t\"running\": true,\n\t\t\"timestamp\": time.Now().Unix(),\n\t\t\"sub_agents\": getSubAgentNames(agent),\n\t}\n\n\treturn json.Marshal(status)\n}\n\nfunc (f *Framework) handleAgentStop(ctx context.Context, params json.RawMessage) (json.RawMessage, error) {\n\tvar req struct {\n\t\tAgentName string `json:\"agent_name\"`\n\t}\n\n\tif err := json.Unmarshal(params, &req); err != nil {\n\t\treq.AgentName = *agentID\n\t}\n\n\tagentName := req.AgentName\n\tif agentName == \"\" {\n\t\tagentName = *agentID\n\t}\n\n\tresult := map[string]any{\n\t\t\"agent_id\": agentName,\n\t\t\"stopped\": true,\n\t\t\"timestamp\": time.Now().Unix(),\n\t}\n\n\treturn json.Marshal(result)\n}\n\nfunc (f *Framework) handleAgentList(ctx context.Context, params json.RawMessage) (json.RawMessage, error) {\n\tagentNames := f.loader.ListAgents()\n\n\tagentsInfo := make([]map[string]any, 0)\n\tfor _, name := range agentNames {\n\t\tagent, err := f.loader.LoadAgent(name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tagentsInfo = append(agentsInfo, map[string]any{\n\t\t\t\"name\": agent.Name(),\n\t\t\t\"description\": agent.Description(),\n\t\t\t\"sub_agents\": getSubAgentNames(agent),\n\t\t})\n\t}\n\n\treturn json.Marshal(map[string]any{\n\t\t\"agents\": agentsInfo,\n\t\t\"count\": len(agentsInfo),\n\t})\n}\n\nfunc (f *Framework) handleFrameworkStatus(ctx context.Context, params json.RawMessage) (json.RawMessage, error) {\n\tf.mu.RLock()\n\tinvCount := len(f.invocations)\n\tf.mu.RUnlock()\n\n\tstatus := map[string]any{\n\t\t\"framework_id\": *agentID,\n\t\t\"process_mode\": *processMode,\n\t\t\"agent_count\": len(f.agents),\n\t\t\"running_invocations\": invCount,\n\t\t\"model_name\": f.model.Name(),\n\t\t\"timestamp\": time.Now().Unix(),\n\t}\n\n\treturn json.Marshal(status)\n}\n\nfunc convertEventToMap(event *session.Event) map[string]any {\n\tif event == nil {\n\t\treturn nil\n\t}\n\n\tdata := map[string]any{\n\t\t\"author\": event.Author,\n\t\t\"timestamp\": event.Timestamp.Unix(),\n\t\t\"invocation_id\": event.InvocationID,\n\t}\n\n\t// Extract content from LLMResponse\n\tif event.Content != nil && len(event.Content.Parts) > 0 {\n\t\ttextParts := make([]string, 0)\n\t\tfor _, part := range event.Content.Parts {\n\t\t\tif part.Text != \"\" {\n\t\t\t\ttextParts = append(textParts, part.Text)\n\t\t\t}\n\t\t}\n\t\tif len(textParts) > 0 {\n\t\t\tdata[\"content\"] = strings.Join(textParts, \"\\n\")\n\t\t}\n\t}\n\n\t// Add partial flag\n\tdata[\"partial\"] = event.LLMResponse.Partial\n\tdata[\"is_final\"] = event.IsFinalResponse()\n\n\t// Add state delta if present\n\tif event.Actions.StateDelta != nil && len(event.Actions.StateDelta) > 0 {\n\t\tdata[\"state_delta\"] = event.Actions.StateDelta\n\t}\n\n\t// Add transfer info\n\tif event.Actions.TransferToAgent != \"\" {\n\t\tdata[\"transfer_to\"] = event.Actions.TransferToAgent\n\t}\n\n\treturn data\n}\n\nfunc getSubAgentNames(agent adkagent.Agent) []string {\n\tnames := make([]string, 0)\n\tfor _, sub := range agent.SubAgents() {\n\t\tnames = append(names, sub.Name())\n\t}\n\treturn names\n}\n\n// Config and Framework creation\n\ntype AgentConfig struct {\n\tName string `yaml:\"name\"`\n\tDescription string `yaml:\"description\"`\n\tModel string `yaml:\"model\"`\n\tInstruction string `yaml:\"instruction\"`\n\tTools []string `yaml:\"tools\"`\n\tAPIKey string `yaml:\"api_key\"`\n\tEndpoint string `yaml:\"endpoint\"`\n\tSubAgents []AgentConfig `yaml:\"sub_agents\"`\n}\n\ntype FrameworkConfig struct {\n\tAppName string `yaml:\"app_name\"`\n\tProcessMode string `yaml:\"process_mode\"`\n\tAgents []AgentConfig `yaml:\"agents\"`\n}\n\nfunc loadConfig(path string) (*FrameworkConfig, error) {\n\tif path == \"\" {\n\t\t// Default config for single agent\n\t\treturn &FrameworkConfig{\n\t\t\tAppName: *agentID,\n\t\t\tProcessMode: *processMode,\n\t\t\tAgents: []AgentConfig{\n\t\t\t\t{\n\t\t\t\t\tName: *agentID,\n\t\t\t\t\tDescription: \"AI Agent\",\n\t\t\t\t\tModel: \"deepseek-chat\",\n\t\t\t\t\tInstruction: \"You are a helpful AI assistant.\",\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\n\t// TODO: Parse YAML config file\n\t// For now, return default config\n\treturn &FrameworkConfig{\n\t\tAppName: *agentID,\n\t\tProcessMode: *processMode,\n\t\tAgents: []AgentConfig{\n\t\t\t{\n\t\t\t\tName: *agentID,\n\t\t\t\tDescription: \"AI Agent\",\n\t\t\t\tModel: \"deepseek-chat\",\n\t\t\t\tInstruction: \"You are a helpful AI assistant.\",\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\n// Custom model that implements adkmodel.LLM interface\n// This model can use OpenAI-compatible APIs (like DeepSeek)\ntype customModel struct {\n\tname string\n\tapiKey string\n\tendpoint string\n}\n\nfunc newCustomModel(name, apiKey, endpoint string) *customModel {\n\tif endpoint == \"\" {\n\t\tendpoint = \"https://api.deepseek.com\"\n\t}\n\treturn &customModel{\n\t\tname: name,\n\t\tapiKey: apiKey,\n\t\tendpoint: endpoint,\n\t}\n}\n\nfunc (m *customModel) Name() string {\n\treturn m.name\n}\n\nfunc (m *customModel) GenerateContent(ctx context.Context, req *adkmodel.LLMRequest, stream bool) iter.Seq2[*adkmodel.LLMResponse, error] {\n\treturn func(yield func(*adkmodel.LLMResponse, error) bool) {\n\t\t// For now, return a mock response\n\t\t// In production, this would call the actual API\n\t\tresp := &adkmodel.LLMResponse{\n\t\t\tContent: genai.NewContentFromText(\n\t\t\t\tfmt.Sprintf(\"Response from %s model. Model is ready but API integration pending.\", m.name),\n\t\t\t\tgenai.RoleModel,\n\t\t\t),\n\t\t\tPartial: false,\n\t\t}\n\t\tyield(resp, nil)\n\t}\n}\n\nfunc createFramework(ctx context.Context, config *FrameworkConfig) (*Framework, error) {\n\t// Get API key from environment\n\tapiKey := os.Getenv(\"DEEPSEEK_API_KEY\")\n\tif apiKey == \"\" {\n\t\tapiKey = os.Getenv(\"OPENAI_API_KEY\")\n\t}\n\n\t// Create the model\n\tmodel := newCustomModel(\n\t\tconfig.Agents[0].Model,\n\t\tapiKey,\n\t\tconfig.Agents[0].Endpoint,\n\t)\n\n\t// Create agents based on config\n\tagents := make(map[string]adkagent.Agent)\n\tvar rootAgent adkagent.Agent\n\n\tfor i, agentCfg := range config.Agents {\n\t\tagent, err := createRealAgent(ctx, model, agentCfg)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to create agent %s: %w\", agentCfg.Name, err)\n\t\t}\n\t\tagents[agentCfg.Name] = agent\n\n\t\t// First agent is root\n\t\tif i == 0 {\n\t\t\trootAgent = agent\n\t\t}\n\t}\n\n\t// Create loader based on process mode\n\tvar loader adkagent.Loader\n\tvar err error\n\tif len(agents) == 1 {\n\t\tloader = adkagent.NewSingleLoader(rootAgent)\n\t} else {\n\t\tagentList := make([]adkagent.Agent, 0)\n\t\tfor _, a := range agents {\n\t\t\tif a != rootAgent {\n\t\t\t\tagentList = append(agentList, a)\n\t\t\t}\n\t\t}\n\t\tloader, err = adkagent.NewMultiLoader(rootAgent, agentList...)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to create multi loader: %w\", err)\n\t\t}\n\t}\n\n\t// Create session service (in-memory for now)\n\tsessionSvc := session.InMemoryService()\n\n\t// Create runner\n\tr, err := runner.New(runner.Config{\n\t\tAppName: config.AppName,\n\t\tAgent: rootAgent,\n\t\tSessionService: sessionSvc,\n\t\tAutoCreateSession: true,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create runner: %w\", err)\n\t}\n\n\treturn &Framework{\n\t\tloader: loader,\n\t\trunner: r,\n\t\tagents: agents,\n\t\tmodel: model,\n\t\tappName: config.AppName,\n\t\tsessionSvc: sessionSvc,\n\t\tinvocations: make(map[string]*InvocationState),\n\t}, nil\n}\n\nfunc createRealAgent(ctx context.Context, model adkmodel.LLM, config AgentConfig) (adkagent.Agent, error) {\n\tinstruction := config.Instruction\n\tif instruction == \"\" {\n\t\tinstruction = \"You are a helpful AI assistant. Respond to user queries appropriately.\"\n\t}\n\n\t// Create LLM agent using adk-go\n\tagent, err := llmagent.New(llmagent.Config{\n\t\tName: config.Name,\n\t\tModel: model,\n\t\tDescription: config.Description,\n\t\tInstruction: instruction,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create llmagent: %w\", err)\n\t}\n\n\treturn agent, nil\n}"}, "a90849dd9ce9af578045fd2b22d54d270f943e33": {"t": 1785755067.7673366, "n": 14515, "h": "934bf8802131ee9e", "c": "// ADK Framework launcher.\n// This program reads JSON-RPC requests from stdin and writes responses to stdout.\n// No HTTP/gRPC server - pure stdin/stdout communication.\n// Integrates with adk-go library for real agent execution.\npackage main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"iter\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"google.golang.org/genai\"\n\n\tadkagent \"google.golang.org/adk/agent\"\n\t\"google.golang.org/adk/agent/llmagent\"\n\tadkmodel \"google.golang.org/adk/model\"\n\t\"google.golang.org/adk/runner\"\n\t\"google.golang.org/adk/session\"\n)\n\nvar (\n\tagentID = flag.String(\"agent-id\", \"\", \"Agent ID\")\n\tconfigPath = flag.String(\"config\", \"\", \"Config file path\")\n\tworkDir = flag.String(\"workdir\", \"\", \"Working directory\")\n\tdebug = flag.Bool(\"debug\", false, \"Enable debug logging\")\n\tprocessMode = flag.String(\"process-mode\", \"isolated\", \"Process mode: shared or isolated\")\n)\n\n// Framework holds all agents and the runner\ntype Framework struct {\n\tloader adkagent.Loader\n\trunner *runner.Runner\n\tagents map[string]adkagent.Agent\n\tmodel adkmodel.LLM\n\tappName string\n\tsessionSvc session.Service\n\n\t// For tracking running invocations\n\tmu sync.RWMutex\n\tinvocations map[string]*InvocationState\n}\n\ntype InvocationState struct {\n\tID string\n\tAgentName string\n\tSessionID string\n\tStartTime time.Time\n\tCancel context.CancelFunc\n}\n\nfunc main() {\n\tflag.Parse()\n\n\tlog.Printf(\"ADK Framework starting with adk-go integration...\")\n\tlog.Printf(\"Agent ID: %s\", *agentID)\n\tlog.Printf(\"Config: %s\", *configPath)\n\tlog.Printf(\"Work Dir: %s\", *workDir)\n\tlog.Printf(\"Process Mode: %s\", *processMode)\n\n\tctx := context.Background()\n\n\t// Load configuration\n\tconfig, err := loadConfig(*configPath)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to load config: %v\", err)\n\t}\n\n\t// Create framework with real adk-go agents\n\tframework, err := createFramework(ctx, config)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create framework: %v\", err)\n\t}\n\n\tlog.Printf(\"Framework created with %d agents\", len(framework.agents))\n\tfor name := range framework.agents {\n\t\tlog.Printf(\" - Agent: %s\", name)\n\t}\n\n\t// Start JSON-RPC server (stdin/stdout)\n\tserver := NewStdioJSONRPCServer(os.Stdin, os.Stdout)\n\n\t// Register handlers\n\tserver.RegisterMethod(\"agent.run\", framework.handleAgentRun)\n\tserver.RegisterMethod(\"agent.status\", framework.handleAgentStatus)\n\tserver.RegisterMethod(\"agent.stop\", framework.handleAgentStop)\n\tserver.RegisterMethod(\"agent.list\", framework.handleAgentList)\n\tserver.RegisterMethod(\"framework.status\", framework.handleFrameworkStatus)\n\n\tlog.Printf(\"ADK Framework ready, listening on stdin\")\n\n\t// Run until stdin closes\n\tserver.Run()\n}\n\n// StdioJSONRPCServer handles JSON-RPC over stdin/stdout.\ntype StdioJSONRPCServer struct {\n\tstdin io.Reader\n\tstdout io.Writer\n\n\tmethods map[string]MethodHandler\n\tmu sync.RWMutex\n}\n\ntype MethodHandler func(ctx context.Context, params json.RawMessage) (json.RawMessage, error)\n\ntype jsonRPCRequest struct {\n\tJsonrpc string `json:\"jsonrpc\"`\n\tMethod string `json:\"method\"`\n\tParams json.RawMessage `json:\"params,omitempty\"`\n\tID int `json:\"id,omitempty\"`\n}\n\ntype jsonRPCResponse struct {\n\tJsonrpc string `json:\"jsonrpc\"`\n\tResult json.RawMessage `json:\"result,omitempty\"`\n\tError *jsonRPCError `json:\"error,omitempty\"`\n\tID int `json:\"id\"`\n}\n\ntype jsonRPCError struct {\n\tCode int `json:\"code\"`\n\tMessage string `json:\"message\"`\n}\n\nfunc NewStdioJSONRPCServer(stdin io.Reader, stdout io.Writer) *StdioJSONRPCServer {\n\treturn &StdioJSONRPCServer{\n\t\tstdin: stdin,\n\t\tstdout: stdout,\n\t\tmethods: make(map[string]MethodHandler),\n\t}\n}\n\nfunc (s *StdioJSONRPCServer) RegisterMethod(method string, handler MethodHandler) {\n\ts.mu.Lock()\n\ts.methods[method] = handler\n\ts.mu.Unlock()\n}\n\nfunc (s *StdioJSONRPCServer) Run() {\n\tscanner := bufio.NewScanner(s.stdin)\n\tencoder := json.NewEncoder(s.stdout)\n\n\tfor scanner.Scan() {\n\t\tline := scanner.Bytes()\n\n\t\tvar req jsonRPCRequest\n\t\tif err := json.Unmarshal(line, &req); err != nil {\n\t\t\t// Invalid request, skip\n\t\t\tcontinue\n\t\t}\n\n\t\t// Handle request\n\t\tctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)\n\t\tresult, err := s.handleRequest(ctx, req)\n\t\tcancel()\n\n\t\t// Build response\n\t\tresp := jsonRPCResponse{\n\t\t\tJsonrpc: \"2.0\",\n\t\t\tID: req.ID,\n\t\t}\n\n\t\tif err != nil {\n\t\t\tresp.Error = &jsonRPCError{\n\t\t\t\tCode: -1,\n\t\t\t\tMessage: err.Error(),\n\t\t\t}\n\t\t} else {\n\t\t\tresp.Result = result\n\t\t}\n\n\t\t// Write response\n\t\tencoder.Encode(resp)\n\t}\n\n\tlog.Printf(\"stdin closed, shutting down\")\n}\n\nfunc (s *StdioJSONRPCServer) handleRequest(ctx context.Context, req jsonRPCRequest) (json.RawMessage, error) {\n\ts.mu.RLock()\n\thandler, exists := s.methods[req.Method]\n\ts.mu.RUnlock()\n\n\tif !exists {\n\t\treturn nil, fmt.Errorf(\"method not found: %s\", req.Method)\n\t}\n\n\treturn handler(ctx, req.Params)\n}\n\n// Handler implementations using real adk-go\n\nfunc (f *Framework) handleAgentRun(ctx context.Context, params json.RawMessage) (json.RawMessage, error) {\n\tvar req struct {\n\t\tInvocationID string `json:\"invocation_id\"`\n\t\tSessionID string `json:\"session_id\"`\n\t\tUserID string `json:\"user_id\"`\n\t\tAgentName string `json:\"agent_name\"`\n\t\tMessage string `json:\"message\"`\n\t}\n\n\tif err := json.Unmarshal(params, &req); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid params: %w\", err)\n\t}\n\n\t// Determine which agent to run\n\tagentName := req.AgentName\n\tif agentName == \"\" {\n\t\tagentName = *agentID\n\t}\n\n\t// Get the agent (for verification only)\n\t_, err := f.loader.LoadAgent(agentName)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"agent not found: %s\", err)\n\t}\n\n\t// Create user message content\n\tmsg := genai.NewContentFromText(req.Message, genai.RoleUser)\n\n\t// Run the agent using runner\n\trunConfig := adkagent.RunConfig{\n\t\tStreamingMode: adkagent.StreamingModeNone,\n\t}\n\n\tevents := make([]map[string]any, 0)\n\n\t// Use runner to execute\n\tfor event, runErr := range f.runner.Run(ctx, req.UserID, req.SessionID, msg, runConfig) {\n\t\tif runErr != nil {\n\t\t\tevents = append(events, map[string]any{\n\t\t\t\t\"type\": \"error\",\n\t\t\t\t\"message\": runErr.Error(),\n\t\t\t})\n\t\t\tbreak\n\t\t}\n\n\t\t// Convert event to response format\n\t\teventData := convertEventToMap(event)\n\t\tevents = append(events, eventData)\n\t}\n\n\t// Add completion event\n\tevents = append(events, map[string]any{\"type\": \"complete\"})\n\n\treturn json.Marshal(map[string]any{\"events\": events})\n}\n\nfunc (f *Framework) handleAgentStatus(ctx context.Context, params json.RawMessage) (json.RawMessage, error) {\n\tvar req struct {\n\t\tAgentName string `json:\"agent_name\"`\n\t}\n\n\tif err := json.Unmarshal(params, &req); err != nil {\n\t\treq.AgentName = *agentID\n\t}\n\n\tagentName := req.AgentName\n\tif agentName == \"\" {\n\t\tagentName = *agentID\n\t}\n\n\t// Get agent info from loader\n\tagent, err := f.loader.LoadAgent(agentName)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"agent not found: %s\", err)\n\t}\n\n\tstatus := map[string]any{\n\t\t\"agent_id\": agentName,\n\t\t\"name\": agent.Name(),\n\t\t\"description\": agent.Description(),\n\t\t\"type\": \"llm\",\n\t\t\"running\": true,\n\t\t\"timestamp\": time.Now().Unix(),\n\t\t\"sub_agents\": getSubAgentNames(agent),\n\t}\n\n\treturn json.Marshal(status)\n}\n\nfunc (f *Framework) handleAgentStop(ctx context.Context, params json.RawMessage) (json.RawMessage, error) {\n\tvar req struct {\n\t\tAgentName string `json:\"agent_name\"`\n\t}\n\n\tif err := json.Unmarshal(params, &req); err != nil {\n\t\treq.AgentName = *agentID\n\t}\n\n\tagentName := req.AgentName\n\tif agentName == \"\" {\n\t\tagentName = *agentID\n\t}\n\n\tresult := map[string]any{\n\t\t\"agent_id\": agentName,\n\t\t\"stopped\": true,\n\t\t\"timestamp\": time.Now().Unix(),\n\t}\n\n\treturn json.Marshal(result)\n}\n\nfunc (f *Framework) handleAgentList(ctx context.Context, params json.RawMessage) (json.RawMessage, error) {\n\tagentNames := f.loader.ListAgents()\n\n\tagentsInfo := make([]map[string]any, 0)\n\tfor _, name := range agentNames {\n\t\tagent, err := f.loader.LoadAgent(name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tagentsInfo = append(agentsInfo, map[string]any{\n\t\t\t\"name\": agent.Name(),\n\t\t\t\"description\": agent.Description(),\n\t\t\t\"sub_agents\": getSubAgentNames(agent),\n\t\t})\n\t}\n\n\treturn json.Marshal(map[string]any{\n\t\t\"agents\": agentsInfo,\n\t\t\"count\": len(agentsInfo),\n\t})\n}\n\nfunc (f *Framework) handleFrameworkStatus(ctx context.Context, params json.RawMessage) (json.RawMessage, error) {\n\tf.mu.RLock()\n\tinvCount := len(f.invocations)\n\tf.mu.RUnlock()\n\n\tstatus := map[string]any{\n\t\t\"framework_id\": *agentID,\n\t\t\"process_mode\": *processMode,\n\t\t\"agent_count\": len(f.agents),\n\t\t\"running_invocations\": invCount,\n\t\t\"model_name\": f.model.Name(),\n\t\t\"timestamp\": time.Now().Unix(),\n\t}\n\n\treturn json.Marshal(status)\n}\n\nfunc convertEventToMap(event *session.Event) map[string]any {\n\tif event == nil {\n\t\treturn nil\n\t}\n\n\tdata := map[string]any{\n\t\t\"author\": event.Author,\n\t\t\"timestamp\": event.Timestamp.Unix(),\n\t\t\"invocation_id\": event.InvocationID,\n\t}\n\n\t// Extract content from LLMResponse\n\tif event.Content != nil && len(event.Content.Parts) > 0 {\n\t\ttextParts := make([]string, 0)\n\t\tfor _, part := range event.Content.Parts {\n\t\t\tif part.Text != \"\" {\n\t\t\t\ttextParts = append(textParts, part.Text)\n\t\t\t}\n\t\t}\n\t\tif len(textParts) > 0 {\n\t\t\tdata[\"content\"] = strings.Join(textParts, \"\\n\")\n\t\t}\n\t}\n\n\t// Add partial flag\n\tdata[\"partial\"] = event.LLMResponse.Partial\n\tdata[\"is_final\"] = event.IsFinalResponse()\n\n\t// Add state delta if present\n\tif event.Actions.StateDelta != nil && len(event.Actions.StateDelta) > 0 {\n\t\tdata[\"state_delta\"] = event.Actions.StateDelta\n\t}\n\n\t// Add transfer info\n\tif event.Actions.TransferToAgent != \"\" {\n\t\tdata[\"transfer_to\"] = event.Actions.TransferToAgent\n\t}\n\n\treturn data\n}\n\nfunc getSubAgentNames(agent adkagent.Agent) []string {\n\tnames := make([]string, 0)\n\tfor _, sub := range agent.SubAgents() {\n\t\tnames = append(names, sub.Name())\n\t}\n\treturn names\n}\n\n// Config and Framework creation\n\ntype AgentConfig struct {\n\tName string `yaml:\"name\"`\n\tDescription string `yaml:\"description\"`\n\tModel string `yaml:\"model\"`\n\tInstruction string `yaml:\"instruction\"`\n\tTools []string `yaml:\"tools\"`\n\tAPIKey string `yaml:\"api_key\"`\n\tEndpoint string `yaml:\"endpoint\"`\n\tSubAgents []AgentConfig `yaml:\"sub_agents\"`\n}\n\ntype FrameworkConfig struct {\n\tAppName string `yaml:\"app_name\"`\n\tProcessMode string `yaml:\"process_mode\"`\n\tAgents []AgentConfig `yaml:\"agents\"`\n}\n\nfunc loadConfig(path string) (*FrameworkConfig, error) {\n\tif path == \"\" {\n\t\t// Default config for single agent\n\t\treturn &FrameworkConfig{\n\t\t\tAppName: *agentID,\n\t\t\tProcessMode: *processMode,\n\t\t\tAgents: []AgentConfig{\n\t\t\t\t{\n\t\t\t\t\tName: *agentID,\n\t\t\t\t\tDescription: \"AI Agent\",\n\t\t\t\t\tModel: \"deepseek-chat\",\n\t\t\t\t\tInstruction: \"You are a helpful AI assistant.\",\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\n\t// TODO: Parse YAML config file\n\t// For now, return default config\n\treturn &FrameworkConfig{\n\t\tAppName: *agentID,\n\t\tProcessMode: *processMode,\n\t\tAgents: []AgentConfig{\n\t\t\t{\n\t\t\t\tName: *agentID,\n\t\t\t\tDescription: \"AI Agent\",\n\t\t\t\tModel: \"deepseek-chat\",\n\t\t\t\tInstruction: \"You are a helpful AI assistant.\",\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\n// Custom model that implements adkmodel.LLM interface\n// This model can use OpenAI-compatible APIs (like DeepSeek)\ntype customModel struct {\n\tname string\n\tapiKey string\n\tendpoint string\n}\n\nfunc newCustomModel(name, apiKey, endpoint string) *customModel {\n\tif endpoint == \"\" {\n\t\tendpoint = \"https://api.deepseek.com\"\n\t}\n\treturn &customModel{\n\t\tname: name,\n\t\tapiKey: apiKey,\n\t\tendpoint: endpoint,\n\t}\n}\n\nfunc (m *customModel) Name() string {\n\treturn m.name\n}\n\nfunc (m *customModel) GenerateContent(ctx context.Context, req *adkmodel.LLMRequest, stream bool) iter.Seq2[*adkmodel.LLMResponse, error] {\n\treturn func(yield func(*adkmodel.LLMResponse, error) bool) {\n\t\t// For now, return a mock response\n\t\t// In production, this would call the actual API\n\t\tresp := &adkmodel.LLMResponse{\n\t\t\tContent: genai.NewContentFromText(\n\t\t\t\tfmt.Sprintf(\"Response from %s model. Model is ready but API integration pending.\", m.name),\n\t\t\t\tgenai.RoleModel,\n\t\t\t),\n\t\t\tPartial: false,\n\t\t}\n\t\tyield(resp, nil)\n\t}\n}\n\nfunc createFramework(ctx context.Context, config *FrameworkConfig) (*Framework, error) {\n\t// Get API key from environment\n\tapiKey := os.Getenv(\"DEEPSEEK_API_KEY\")\n\tif apiKey == \"\" {\n\t\tapiKey = os.Getenv(\"OPENAI_API_KEY\")\n\t}\n\n\t// Create the model\n\tmodel := newCustomModel(\n\t\tconfig.Agents[0].Model,\n\t\tapiKey,\n\t\tconfig.Agents[0].Endpoint,\n\t)\n\n\t// Create agents based on config\n\tagents := make(map[string]adkagent.Agent)\n\tvar rootAgent adkagent.Agent\n\n\tfor i, agentCfg := range config.Agents {\n\t\tagent, err := createRealAgent(ctx, model, agentCfg)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to create agent %s: %w\", agentCfg.Name, err)\n\t\t}\n\t\tagents[agentCfg.Name] = agent\n\n\t\t// First agent is root\n\t\tif i == 0 {\n\t\t\trootAgent = agent\n\t\t}\n\t}\n\n\t// Create loader based on process mode\n\tvar loader adkagent.Loader\n\tvar err error\n\tif len(agents) == 1 {\n\t\tloader = adkagent.NewSingleLoader(rootAgent)\n\t} else {\n\t\tagentList := make([]adkagent.Agent, 0)\n\t\tfor _, a := range agents {\n\t\t\tif a != rootAgent {\n\t\t\t\tagentList = append(agentList, a)\n\t\t\t}\n\t\t}\n\t\tloader, err = adkagent.NewMultiLoader(rootAgent, agentList...)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to create multi loader: %w\", err)\n\t\t}\n\t}\n\n\t// Create session service (in-memory for now)\n\tsessionSvc := session.InMemoryService()\n\n\t// Create runner\n\tr, err := runner.New(runner.Config{\n\t\tAppName: config.AppName,\n\t\tAgent: rootAgent,\n\t\tSessionService: sessionSvc,\n\t\tAutoCreateSession: true,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create runner: %w\", err)\n\t}\n\n\treturn &Framework{\n\t\tloader: loader,\n\t\trunner: r,\n\t\tagents: agents,\n\t\tmodel: model,\n\t\tappName: config.AppName,\n\t\tsessionSvc: sessionSvc,\n\t\tinvocations: make(map[string]*InvocationState),\n\t}, nil\n}\n\nfunc createRealAgent(ctx context.Context, model adkmodel.LLM, config AgentConfig) (adkagent.Agent, error) {\n\tinstruction := config.Instruction\n\tif instruction == \"\" {\n\t\tinstruction = \"You are a helpful AI assistant. Respond to user queries appropriately.\"\n\t}\n\n\t// Create LLM agent using adk-go\n\tagent, err := llmagent.New(llmagent.Config{\n\t\tName: config.Name,\n\t\tModel: model,\n\t\tDescription: config.Description,\n\t\tInstruction: instruction,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create llmagent: %w\", err)\n\t}\n\n\treturn agent, nil\n}"}, "24237c39c208883cd3d33da149520f002aabf6a2": {"t": 1785755069.117297, "n": 6872, "h": "93db3c6866003310", "c": "// ADK Framework launcher.\n// This program reads JSON-RPC requests from stdin and writes responses to stdout.\n// No HTTP/gRPC server - pure stdin/stdout communication.\npackage main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n\n\t\"aiagent/pkg/agent\"\n)\n\nvar (\n\tagentID = flag.String(\"agent-id\", \"\", \"Agent ID\")\n\tconfigPath = flag.String(\"config\", \"\", \"Config file path\")\n\tworkDir = flag.String(\"workdir\", \"\", \"Working directory\")\n\tdebug = flag.Bool(\"debug\", false, \"Enable debug logging\")\n)\n\nfunc main() {\n\tflag.Parse()\n\n\tlog.Printf(\"ADK Framework starting...\")\n\tlog.Printf(\"Agent ID: %s\", *agentID)\n\tlog.Printf(\"Config: %s\", *configPath)\n\tlog.Printf(\"Work Dir: %s\", *workDir)\n\n\t// Load configuration\n\tconfig, err := loadConfig(*configPath)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to load config: %v\", err)\n\t}\n\n\t// Create agent\n\tag, err := createAgent(config)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create agent: %v\", err)\n\t}\n\n\t// Start JSON-RPC server (stdin/stdout)\n\tserver := NewStdioJSONRPCServer(os.Stdin, os.Stdout)\n\n\t// Register handlers\n\tserver.RegisterMethod(\"agent.run\", handleAgentRun(ag))\n\tserver.RegisterMethod(\"agent.status\", handleAgentStatus(ag))\n\tserver.RegisterMethod(\"agent.stop\", handleAgentStop(ag))\n\n\tlog.Printf(\"ADK Framework ready, listening on stdin\")\n\n\t// Run until stdin closes\n\tserver.Run()\n}\n\n// StdioJSONRPCServer handles JSON-RPC over stdin/stdout.\ntype StdioJSONRPCServer struct {\n\tstdin io.Reader\n\tstdout io.Writer\n\n\tmethods map[string]MethodHandler\n\tmu sync.RWMutex\n}\n\ntype MethodHandler func(ctx context.Context, params json.RawMessage) (json.RawMessage, error)\n\ntype jsonRPCRequest struct {\n\tJsonrpc string `json:\"jsonrpc\"`\n\tMethod string `json:\"method\"`\n\tParams json.RawMessage `json:\"params,omitempty\"`\n\tID int `json:\"id,omitempty\"`\n}\n\ntype jsonRPCResponse struct {\n\tJsonrpc string `json:\"jsonrpc\"`\n\tResult json.RawMessage `json:\"result,omitempty\"`\n\tError *jsonRPCError `json:\"error,omitempty\"`\n\tID int `json:\"id\"`\n}\n\ntype jsonRPCError struct {\n\tCode int `json:\"code\"`\n\tMessage string `json:\"message\"`\n}\n\nfunc NewStdioJSONRPCServer(stdin io.Reader, stdout io.Writer) *StdioJSONRPCServer {\n\treturn &StdioJSONRPCServer{\n\t\tstdin: stdin,\n\t\tstdout: stdout,\n\t\tmethods: make(map[string]MethodHandler),\n\t}\n}\n\nfunc (s *StdioJSONRPCServer) RegisterMethod(method string, handler MethodHandler) {\n\ts.mu.Lock()\n\ts.methods[method] = handler\n\ts.mu.Unlock()\n}\n\nfunc (s *StdioJSONRPCServer) Run() {\n\tscanner := bufio.NewScanner(s.stdin)\n\tencoder := json.NewEncoder(s.stdout)\n\n\tfor scanner.Scan() {\n\t\tline := scanner.Bytes()\n\n\t\tvar req jsonRPCRequest\n\t\tif err := json.Unmarshal(line, &req); err != nil {\n\t\t\t// Invalid request, skip\n\t\t\tcontinue\n\t\t}\n\n\t\t// Handle request\n\t\tctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)\n\t\tresult, err := s.handleRequest(ctx, req)\n\t\tcancel()\n\n\t\t// Build response\n\t\tresp := jsonRPCResponse{\n\t\t\tJsonrpc: \"2.0\",\n\t\t\tID: req.ID,\n\t\t}\n\n\t\tif err != nil {\n\t\t\tresp.Error = &jsonRPCError{\n\t\t\t\tCode: -1,\n\t\t\t\tMessage: err.Error(),\n\t\t\t}\n\t\t} else {\n\t\t\tresp.Result = result\n\t\t}\n\n\t\t// Write response\n\t\tencoder.Encode(resp)\n\t}\n\n\tlog.Printf(\"stdin closed, shutting down\")\n}\n\nfunc (s *StdioJSONRPCServer) handleRequest(ctx context.Context, req jsonRPCRequest) (json.RawMessage, error) {\n\ts.mu.RLock()\n\thandler, exists := s.methods[req.Method]\n\ts.mu.RUnlock()\n\n\tif !exists {\n\t\treturn nil, fmt.Errorf(\"method not found: %s\", req.Method)\n\t}\n\n\treturn handler(ctx, req.Params)\n}\n\n// Handler implementations\n\nfunc handleAgentRun(ag agent.Agent) MethodHandler {\n\treturn func(ctx context.Context, params json.RawMessage) (json.RawMessage, error) {\n\t\tvar req struct {\n\t\t\tInvocationID string `json:\"invocation_id\"`\n\t\t\tSessionID string `json:\"session_id\"`\n\t\t\tContent *agent.Content `json:\"content\"`\n\t\t}\n\n\t\tif err := json.Unmarshal(params, &req); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid params: %w\", err)\n\t\t}\n\n\t\t// Create invocation context\n\t\tsession := agent.NewSession(req.SessionID, *agentID, \"user\", agent.NewMapState())\n\t\tinvCtx := agent.NewInvocationContext(\n\t\t\tctx,\n\t\t\tag,\n\t\t\tnil, nil, session,\n\t\t\treq.InvocationID, \"\", req.Content,\n\t\t\t&agent.RunConfig{},\n\t\t)\n\n\t\t// Run agent and collect events\n\t\tevents := make([]map[string]any, 0)\n\t\tfor event, err := range ag.Run(invCtx) {\n\t\t\tif err != nil {\n\t\t\t\tevents = append(events, map[string]any{\n\t\t\t\t\t\"type\": \"error\",\n\t\t\t\t\t\"message\": err.Error(),\n\t\t\t\t})\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// Convert event\n\t\t\teventData := convertEventToMap(event)\n\t\t\tevents = append(events, eventData)\n\t\t}\n\n\t\t// Add completion event\n\t\tevents = append(events, map[string]any{\"type\": \"complete\"})\n\n\t\treturn json.Marshal(map[string]any{\"events\": events})\n\t}\n}\n\nfunc handleAgentStatus(ag agent.Agent) MethodHandler {\n\treturn func(ctx context.Context, params json.RawMessage) (json.RawMessage, error) {\n\t\tstatus := map[string]any{\n\t\t\t\"agent_id\": *agentID,\n\t\t\t\"name\": ag.Name(),\n\t\t\t\"description\": ag.Description(),\n\t\t\t\"type\": string(ag.Type()),\n\t\t\t\"running\": true,\n\t\t\t\"timestamp\": time.Now().Unix(),\n\t\t}\n\n\t\treturn json.Marshal(status)\n\t}\n}\n\nfunc handleAgentStop(ag agent.Agent) MethodHandler {\n\treturn func(ctx context.Context, params json.RawMessage) (json.RawMessage, error) {\n\t\t// Cleanup agent resources\n\t\tresult := map[string]any{\n\t\t\t\"agent_id\": *agentID,\n\t\t\t\"stopped\": true,\n\t\t\t\"timestamp\": time.Now().Unix(),\n\t\t}\n\n\t\treturn json.Marshal(result)\n\t}\n}\n\nfunc convertEventToMap(event *agent.Event) map[string]any {\n\tif event == nil {\n\t\treturn nil\n\t}\n\n\tdata := map[string]any{\n\t\t\"author\": event.Author,\n\t\t\"timestamp\": event.Timestamp.Unix(),\n\t}\n\n\t// Extract content\n\tif event.LLMResponse.Content != nil && len(event.LLMResponse.Content.Parts) > 0 {\n\t\tdata[\"content\"] = event.LLMResponse.Content.Parts[0].Text\n\t}\n\n\treturn data\n}\n\n// Config and Agent creation\n\ntype AgentConfig struct {\n\tName string `yaml:\"name\"`\n\tDescription string `yaml:\"description\"`\n\tModel string `yaml:\"model\"`\n\tInstruction string `yaml:\"instruction\"`\n\tTools []string `yaml:\"tools\"`\n\tSkills []string `yaml:\"skills\"`\n\tCustom map[string]any `yaml:\"custom\"`\n}\n\nfunc loadConfig(path string) (*AgentConfig, error) {\n\tif path == \"\" {\n\t\treturn &AgentConfig{\n\t\t\tName: *agentID,\n\t\t\tDescription: *agentID,\n\t\t\tModel: \"deepseek-chat\",\n\t\t}, nil\n\t}\n\n\t// For now, return default config\n\t// In production, would parse YAML file\n\treturn &AgentConfig{\n\t\tName: *agentID,\n\t\tDescription: *agentID,\n\t\tModel: \"deepseek-chat\",\n\t}, nil\n}\n\nfunc createAgent(config *AgentConfig) (agent.Agent, error) {\n\treturn agent.NewBaseAgent(agent.Config{\n\t\tName: config.Name,\n\t\tDescription: config.Description,\n\t}, agent.AgentTypeLLM), nil\n}"}}, "README.md": {"696913067c9d070f9c9a40d5922aa18e8ec609f7": {"t": 1785747290.0423543, "n": 579, "h": "abd77f2e3c677232", "c": "# AI Agent\nuniformly abstract any Agent framework in Kubernetes\n\ndesign doc in folder docs/design\n\n# PoC code\n\n```\ngit clone https://github.com/ChaoyiHuang/aiagent.git\ncd aiagent/aiagent/test/e2e/kind/\n./run-e2e-test.sh all\n```\n\n```\ngit clone https://github.com/ChaoyiHuang/aiagent.git\nexport DISCORD_BOT_TOKEN=\"token\" DISCORD_USER_IDS=\"ids\" DEEPSEEK_API_KEY=\"key\"\n./aiagent/aiagent/test/e2e/kind/run-e2e-test-discord.sh all\n```\n\n# Design Mode\nCo-crafted the doc with coding agent, and finally the doc was revised by human.\n\nconversation history doc in folder docs/conversation\n\n"}, "e620ea79bb82941551a7b1f440c3da17b31146e1": {"t": 1785755066.2350154, "n": 579, "h": "abd77f2e3c677232", "c": "# AI Agent\nuniformly abstract any Agent framework in Kubernetes\n\ndesign doc in folder docs/design\n\n# PoC code\n\n```\ngit clone https://github.com/ChaoyiHuang/aiagent.git\ncd aiagent/aiagent/test/e2e/kind/\n./run-e2e-test.sh all\n```\n\n```\ngit clone https://github.com/ChaoyiHuang/aiagent.git\nexport DISCORD_BOT_TOKEN=\"token\" DISCORD_USER_IDS=\"ids\" DEEPSEEK_API_KEY=\"key\"\n./aiagent/aiagent/test/e2e/kind/run-e2e-test-discord.sh all\n```\n\n# Design Mode\nCo-crafted the doc with coding agent, and finally the doc was revised by human.\n\nconversation history doc in folder docs/conversation\n\n"}, "563f4de6890a4ee6d10c691bd3ca07716f562671": {"t": 1785755067.6606998, "n": 248, "h": "d0a68ed1e71b3f97", "c": "# AI Agent\nuniformly abstract any Agent framework in Kubernetes\n\ndesign doc in folder docs/design\n\n# Design Mode\nCo-crafted the doc with coding agent, and finally the doc was revised by human.\n\nconversation history doc in folder docs/conversation\n\n"}, "e351cdff71879109f492b2f9d0b7b0c901aa4e44": {"t": 1785755068.914541, "n": 197, "h": "2e8a031a19176e0a", "c": "# AI Agent\nuniformly abstract any Agent framework in Kubernetes\n\ndesign doc in folder docs/design\n\n# Design Mode\nco-created with coding agent\n\nconversation history doc in folder docs/conversation\n\n"}, "e26351ba8afb04d907fd7f9809357c5fa461fc54": {"t": 1785755070.1173594, "n": 195, "h": "a1b5b5da8a081151", "c": "# AI Agent\nuniformly abstract any Agent framework in Kubernetes\ndesign doc in folder docs/design\n\n# Design Mode\nco-created with coding agent\nconversation history doc in folder docs/conversation\n\n"}, "73ffd2838893703db12764359d889b1a065972cf": {"t": 1785755071.6305673, "n": 197, "h": "dfab86e02a267254", "c": "# AI Agent\nuniformly abstract any Agent framework in Kubernetes\ndesign doc in folder docs/design\n\n# Devekope Mode\nco-created with coding agent\nconversation history doc in folder docs/conversation\n\n"}, "7ff1f624ffe55162db627cf97f66a65429efbd6c": {"t": 1785755072.9290926, "n": 63, "h": "ba6f80a2216131ce", "c": "# aiagent\nuniformly abstract any Agent framework in Kubernetes\n"}}, "aiagent/cmd/openclaw-handler/main.go": {"696913067c9d070f9c9a40d5922aa18e8ec609f7": {"t": 1786068295.3212762, "n": 29102, "h": "45692e3fa527b2b8", "c": "// OpenClaw Handler entry point.\n// Process Manager for OpenClaw Framework.\n// Uses ImageVolume to access Framework's filesystem at /framework-rootfs.\n//\n// Architecture (Solution M - hostPath + Config Daemon):\n// ┌─────────────────────────────────────────────────────────────────┐\n// │ Config Daemon (DaemonSet on same node) │\n// │ - Watches AIAgent CRDs via Informer │\n// │ - Writes AgentConfig to hostPath │\n// │ - Path: /var/lib/aiagent/configs/<namespace>/<agent-name>/ │\n// │ - Files: agent-config.json, agent-meta.yaml │\n// │ - Creates agent-index.yaml in namespace directory │\n// │ │\n// │ Pod (AgentRuntime) │\n// │ - Mounts hostPath as /etc/agent-config │\n// │ │\n// │ Handler Container (Gateway Manager) │\n// │ - Reads agent-index.yaml to discover agents │\n// │ - Reads agent-config.json for each agent │\n// │ - Starts Gateway process for each AIAgent │\n// │ - Each Gateway manages internal sub-agents │\n// │ - No K8s API calls needed │\n// │ │\n// │ Gateway Process #1 (AIAgent: openclaw-1) │\n// │ Port: 18789 │\n// │ Internal agents: weather, calculator, assistant (invisible) │\n// │ │\n// │ Gateway Process #2 (AIAgent: openclaw-2) │\n// │ Port: 18790 │\n// │ Internal agents: weather, calculator, assistant (invisible) │\n// │ │\n// │ Framework Container (DUMMY) │\n// │ - ENTRYPOINT: pause (just sleeps) │\n// │ - Provides image content for ImageVolume │\n// └─────────────────────────────────────────────────────────────────┘\npackage main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"syscall\"\n\t\"time\"\n\n\tapiextensionsv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\t\"gopkg.in/yaml.v3\"\n\n\t\"aiagent/api/v1\"\n\t\"aiagent/pkg/handler\"\n\t\"aiagent/pkg/handler/openclaw\"\n\t\"aiagent/pkg/harness\"\n)\n\nvar (\n\tgatewayURL = flag.String(\"gateway\", \"\", \"OpenClaw Gateway URL (e.g., http://localhost:18789)\")\n\tworkDir = flag.String(\"workdir\", \"\", \"Shared work directory (e.g., /shared/workdir)\")\n\tconfigDir = flag.String(\"configdir\", \"\", \"Shared config directory (e.g., /shared/config)\")\n\tframeworkBin = flag.String(\"framework\", \"\", \"Framework binary path (ImageVolume: /framework-rootfs/usr/local/bin/openclaw)\")\n\tharnessDir = flag.String(\"harness\", \"\", \"Harness config directory (e.g., /etc/harness)\")\n\tagentConfigDir = flag.String(\"agentconfig\", \"\", \"Agent config directory (e.g., /etc/agent-config)\")\n\tnamespace = flag.String(\"namespace\", \"\", \"Namespace for agent configs\")\n\tbasePort = flag.Int(\"baseport\", 18789, \"Base Gateway port (each instance gets port + offset)\")\n\tdebug = flag.Bool(\"debug\", false, \"Enable debug logging\")\n)\n\n// AgentMeta contains metadata about an agent.\ntype AgentMeta struct {\n\tName string `yaml:\"name\"`\n\tNamespace string `yaml:\"namespace\"`\n\tPhase string `yaml:\"phase\"`\n\tRuntime string `yaml:\"runtime,omitempty\"`\n\tUID string `yaml:\"uid,omitempty\"`\n}\n\n// AgentIndexEntry represents an entry in the agent index.\ntype AgentIndexEntry struct {\n\tName string `yaml:\"name\"`\n\tNamespace string `yaml:\"namespace\"`\n\tPhase string `yaml:\"phase\"`\n\tRuntime string `yaml:\"runtime,omitempty\"`\n\tUID string `yaml:\"uid,omitempty\"`\n}\n\n// AgentIndex represents the agent index structure.\ntype AgentIndex struct {\n\tAgents []AgentIndexEntry `yaml:\"agents\"`\n}\n\nfunc main() {\n\tflag.Parse()\n\n\t// Get Gateway URL from environment or flag\n\tgwURL := *gatewayURL\n\tif gwURL == \"\" {\n\t\tgwURL = os.Getenv(\"OPENCLAW_GATEWAY_URL\")\n\t\tif gwURL == \"\" {\n\t\t\tgwURL = \"http://localhost:18789\"\n\t\t}\n\t}\n\n\t// Get work directory from environment or flag\n\twd := *workDir\n\tif wd == \"\" {\n\t\twd = os.Getenv(\"WORK_DIR\")\n\t\tif wd == \"\" {\n\t\t\twd = \"/shared/workdir\"\n\t\t}\n\t}\n\n\t// Get config directory from environment or flag\n\tcfgDir := *configDir\n\tif cfgDir == \"\" {\n\t\tcfgDir = os.Getenv(\"CONFIG_DIR\")\n\t\tif cfgDir == \"\" {\n\t\t\tcfgDir = \"/shared/config\"\n\t\t}\n\t}\n\n\t// Get framework binary path from environment or flag\n\tfwBin := *frameworkBin\n\tif fwBin == \"\" {\n\t\tfwBin = os.Getenv(\"FRAMEWORK_BIN\")\n\t\tif fwBin == \"\" {\n\t\t\tfwBin = \"/framework-rootfs/usr/local/bin/openclaw\"\n\t\t}\n\t}\n\n\t// Get harness directory from environment or flag\n\thDir := *harnessDir\n\tif hDir == \"\" {\n\t\thDir = os.Getenv(\"HARNESS_DIR\")\n\t\tif hDir == \"\" {\n\t\t\thDir = \"/etc/harness\"\n\t\t}\n\t}\n\n\t// Get agent config directory from environment or flag\n\tagCfgDir := *agentConfigDir\n\tif agCfgDir == \"\" {\n\t\tagCfgDir = os.Getenv(\"AGENT_CONFIG_DIR\")\n\t\tif agCfgDir == \"\" {\n\t\t\tagCfgDir = \"/etc/agent-config\"\n\t\t}\n\t}\n\n\t// Get namespace from environment or flag\n\tns := *namespace\n\tif ns == \"\" {\n\t\tns = os.Getenv(\"NAMESPACE\")\n\t}\n\tif ns == \"\" {\n\t\tns = \"aiagent-system\" // Default namespace\n\t}\n\n\t// Get base port from environment or flag\n\tbaseP := *basePort\n\tif bp := os.Getenv(\"BASE_GATEWAY_PORT\"); bp != \"\" {\n\t\tif parsed, err := parsePort(bp); err == nil {\n\t\t\tbaseP = parsed\n\t\t}\n\t}\n\n\tlog.Printf(\"OpenClaw Handler starting...\")\n\tlog.Printf(\"Gateway URL: %s\", gwURL)\n\tlog.Printf(\"Work Directory: %s\", wd)\n\tlog.Printf(\"Config Directory: %s\", cfgDir)\n\tlog.Printf(\"Framework Binary: %s\", fwBin)\n\tlog.Printf(\"Harness Directory: %s\", hDir)\n\tlog.Printf(\"Agent Config Directory: %s\", agCfgDir)\n\tlog.Printf(\"Namespace: %s\", ns)\n\tlog.Printf(\"Base Gateway Port: %d\", baseP)\n\n\t// Initialize OpenClaw state directory with plugins from ImageVolume\n\t// OpenClaw 2026 Architecture:\n\t// - Build time: OPENCLAW_STATE_DIR=/openclaw-state (plugins installed here)\n\t// - Runtime: Copy /framework-rootfs/openclaw-state to /shared/workdir\n\t// - Set OPENCLAW_STATE_DIR=/shared/workdir (registry paths will match)\n\t// ImageVolume is read-only, so we copy plugin files to a writable location\n\tif err := initializeOpenClawState(wd, fwBin); err != nil {\n\t\tlog.Printf(\"Warning: failed to initialize OpenClaw state: %v\", err)\n\t}\n\n\t// Create Handler configuration\n\thandlerCfg := &handler.HandlerConfig{\n\t\tType: handler.HandlerTypeOpenClaw,\n\t\tFrameworkBin: fwBin,\n\t\tWorkDir: wd,\n\t\tConfigDir: cfgDir,\n\t\tDebugMode: *debug,\n\t}\n\n\t// Create OpenClaw Handler\n\th := openclaw.NewOpenClawHandler(handlerCfg)\n\th.SetBasePort(baseP)\n\n\t// Setup signal handling\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tsigChan := make(chan os.Signal, 1)\n\tsignal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\tsig := <-sigChan\n\t\tlog.Printf(\"Received signal: %v\", sig)\n\t\tcancel()\n\t}()\n\n\t// Run handler service\n\tif err := runHandler(ctx, h, hDir, agCfgDir, ns, wd, cfgDir, baseP); err != nil {\n\t\tlog.Fatalf(\"Handler error: %v\", err)\n\t}\n\n\tlog.Printf(\"OpenClaw Handler shutdown complete\")\n}\n\nfunc parsePort(s string) (int, error) {\n\tvar port int\n\t_, err := fmt.Sscanf(s, \"%d\", &port)\n\treturn port, err\n}\n\n// runHandler runs the handler service loop with Gateway management from hostPath.\nfunc runHandler(ctx context.Context, h *openclaw.OpenClawHandler, harnessDir string, agentConfigDir string, namespace string, workDir string, configDir string, basePort int) error {\n\tlog.Printf(\"Initializing OpenClaw Handler service...\")\n\n\t// 1. Initialize Harness Manager\n\tharnessMgr := harness.NewHarnessManager()\n\tharnessSpecs, err := loadHarnessConfigs(harnessDir)\n\tif err != nil {\n\t\tlog.Printf(\"Warning: failed to load harness configs: %v\", err)\n\t} else {\n\t\tlog.Printf(\"Loaded %d harness configs\", len(harnessSpecs))\n\t\tif err := harnessMgr.Initialize(ctx, harnessSpecs); err != nil {\n\t\t\tlog.Printf(\"Error initializing harness manager: %v\", err)\n\t\t}\n\t}\n\n\t// 2. Prepare work directory\n\tif err := h.PrepareWorkDirectory(ctx, workDir); err != nil {\n\t\tlog.Printf(\"Warning: failed to prepare work directory: %v\", err)\n\t}\n\n\t// 3. Build HarnessConfig from HarnessManager\n\tharnessCfg := buildHarnessConfig(harnessMgr)\n\tif *debug {\n\t\tif harnessCfg.Model != nil {\n\t\t\tlog.Printf(\"DEBUG: Model harness loaded: Provider=%s, Endpoint=%s, DefaultModel=%s\",\n\t\t\t\tharnessCfg.Model.Provider, harnessCfg.Model.Endpoint, harnessCfg.Model.DefaultModel)\n\t\t} else {\n\t\t\tlog.Printf(\"DEBUG: Model harness is nil\")\n\t\t}\n\t}\n\n\t// 4. Prepare config directory\n\tconfigDir = filepath.Join(workDir, \"openclaw-config\")\n\tos.MkdirAll(configDir, 0755)\n\n\t// 5. Watch AgentIndex for changes (agent-index.yaml written by Config Daemon)\n\t// Path structure: mount is /var/lib/aiagent/configs/<namespace> -> /etc/agent-config\n\t// So agent-index.yaml is directly at /etc/agent-config/agent-index.yaml\n\tagentIndexPath := filepath.Join(agentConfigDir, \"agent-index.yaml\")\n\tlog.Printf(\"Watching AgentIndex at: %s\", agentIndexPath)\n\n\t// Track loaded gateways\n\tloadedGateways := make(map[string]bool)\n\tportAssignments := make(map[string]int)\n\tnextPort := basePort\n\n\t// Track last state for change detection\n\tlastAgentCount := 0\n\tlastGatewayRunning := false\n\tlastGatewayInstances := 0\n\n\t// Poll interval (reduced to 30s to minimize log spam)\n\tpollInterval := 30 * time.Second\n\tticker := time.NewTicker(pollInterval)\n\tdefer ticker.Stop()\n\n\t// Initial load\n\tloadGatewaysFromIndex(ctx, h, agentIndexPath, agentConfigDir, workDir, configDir, harnessCfg, loadedGateways, portAssignments, &nextPort, true)\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tlog.Printf(\"Shutting down handler...\")\n\t\t\treturn cleanup(ctx, h, loadedGateways)\n\n\t\tcase <-ticker.C:\n\t\t\t// Poll for changes (silent mode - only log on changes)\n\t\t\tloadGatewaysFromIndex(ctx, h, agentIndexPath, agentConfigDir, workDir, configDir, harnessCfg, loadedGateways, portAssignments, &nextPort, false)\n\n\t\t\t// Check Gateway health (only log on changes)\n\t\t\tlastAgentCount, lastGatewayRunning, lastGatewayInstances = checkGatewayHealthWithChangeDetection(ctx, h, lastAgentCount, lastGatewayRunning, lastGatewayInstances)\n\t\t}\n\t}\n}\n\n// loadHarnessConfigs loads Harness configurations from mounted directory.\nfunc loadHarnessConfigs(harnessDir string) ([]*v1.HarnessSpec, error) {\n\tspecs := []*v1.HarnessSpec{}\n\n\tentries, err := os.ReadDir(harnessDir)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn specs, nil\n\t\t}\n\t\treturn nil, fmt.Errorf(\"failed to read harness directory: %w\", err)\n\t}\n\n\tfor _, entry := range entries {\n\t\tif !entry.IsDir() {\n\t\t\tcontinue\n\t\t}\n\n\t\tharnessPath := filepath.Join(harnessDir, entry.Name())\n\t\tspec, err := loadSingleHarnessConfig(harnessPath, entry.Name())\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Warning: failed to load harness %s: %v\", entry.Name(), err)\n\t\t\tcontinue\n\t\t}\n\t\tspecs = append(specs, spec)\n\t}\n\n\treturn specs, nil\n}\n\n// loadSingleHarnessConfig loads a single Harness configuration.\n// Priority: 1) harness.json (new format from Controller)\n// 2) Individual YAML files (legacy format)\nfunc loadSingleHarnessConfig(harnessPath string, harnessName string) (*v1.HarnessSpec, error) {\n\t// First, try to load harness.json (new format from Controller)\n\tjsonPath := filepath.Join(harnessPath, \"harness.json\")\n\tjsonData, err := os.ReadFile(jsonPath)\n\tif err == nil {\n\t\t// Parse JSON format directly\n\t\tvar spec v1.HarnessSpec\n\t\tif err := json.Unmarshal(jsonData, &spec); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse harness.json: %w\", err)\n\t\t}\n\t\treturn &spec, nil\n\t}\n\n\t// Fall back to legacy format: read harness-type and load type-specific YAML\n\ttypePath := filepath.Join(harnessPath, \"harness-type\")\n\ttypeData, err := os.ReadFile(typePath)\n\tif err != nil {\n\t\treturn inferHarnessType(harnessPath, harnessName)\n\t}\n\tharnessType := string(typeData)\n\n\tspec := &v1.HarnessSpec{\n\t\tType: v1.HarnessType(harnessType),\n\t}\n\n\tswitch v1.HarnessType(harnessType) {\n\tcase v1.HarnessTypeModel:\n\t\tmodelSpec, err := loadModelHarnessConfig(harnessPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tspec.Model = modelSpec\n\n\tcase v1.HarnessTypeMCP:\n\t\tmcpSpec, err := loadMCPHarnessConfig(harnessPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tspec.MCP = mcpSpec\n\n\tcase v1.HarnessTypeMemory:\n\t\tmemorySpec, err := loadMemoryHarnessConfig(harnessPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tspec.Memory = memorySpec\n\n\tcase v1.HarnessTypeSandbox:\n\t\tsandboxSpec, err := loadSandboxHarnessConfig(harnessPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tspec.Sandbox = sandboxSpec\n\n\tcase v1.HarnessTypeSkills:\n\t\tskillsSpec, err := loadSkillsHarnessConfig(harnessPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tspec.Skills = skillsSpec\n\t}\n\n\treturn spec, nil\n}\n\n// inferHarnessType infers harness type from directory content.\nfunc inferHarnessType(harnessPath string, harnessName string) (*v1.HarnessSpec, error) {\n\tif _, err := os.Stat(filepath.Join(harnessPath, \"model.yaml\")); err == nil {\n\t\tmodelSpec, err := loadModelHarnessConfig(harnessPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &v1.HarnessSpec{Type: v1.HarnessTypeModel, Model: modelSpec}, nil\n\t}\n\n\tif _, err := os.Stat(filepath.Join(harnessPath, \"mcp.yaml\")); err == nil {\n\t\tmcpSpec, err := loadMCPHarnessConfig(harnessPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &v1.HarnessSpec{Type: v1.HarnessTypeMCP, MCP: mcpSpec}, nil\n\t}\n\n\tif _, err := os.Stat(filepath.Join(harnessPath, \"memory.yaml\")); err == nil {\n\t\tmemorySpec, err := loadMemoryHarnessConfig(harnessPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &v1.HarnessSpec{Type: v1.HarnessTypeMemory, Memory: memorySpec}, nil\n\t}\n\n\tif _, err := os.Stat(filepath.Join(harnessPath, \"sandbox.yaml\")); err == nil {\n\t\tsandboxSpec, err := loadSandboxHarnessConfig(harnessPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &v1.HarnessSpec{Type: v1.HarnessTypeSandbox, Sandbox: sandboxSpec}, nil\n\t}\n\n\tif _, err := os.Stat(filepath.Join(harnessPath, \"skills.yaml\")); err == nil {\n\t\tskillsSpec, err := loadSkillsHarnessConfig(harnessPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &v1.HarnessSpec{Type: v1.HarnessTypeSkills, Skills: skillsSpec}, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"cannot infer harness type from %s\", harnessPath)\n}\n\nfunc loadModelHarnessConfig(harnessPath string) (*v1.ModelHarnessSpec, error) {\n\tconfigPath := filepath.Join(harnessPath, \"model.yaml\")\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read model.yaml: %w\", err)\n\t}\n\n\t// Convert YAML to JSON first to use JSON tags correctly\n\t// YAML unmarshaling uses struct field names, not JSON tags\n\t// So we need to convert YAML -> map -> JSON -> struct\n\tvar yamlMap map[string]interface{}\n\tif err := yaml.Unmarshal(data, &yamlMap); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse model.yaml: %w\", err)\n\t}\n\n\tjsonData, err := json.Marshal(yamlMap)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to convert YAML to JSON: %w\", err)\n\t}\n\n\tvar spec v1.ModelHarnessSpec\n\tif err := json.Unmarshal(jsonData, &spec); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse model config: %w\", err)\n\t}\n\n\treturn &spec, nil\n}\n\n// yamlToJSON converts YAML data to JSON for proper struct unmarshaling using JSON tags.\nfunc yamlToJSON(data []byte, target interface{}) error {\n\tvar yamlMap map[string]interface{}\n\tif err := yaml.Unmarshal(data, &yamlMap); err != nil {\n\t\treturn err\n\t}\n\tjsonData, err := json.Marshal(yamlMap)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn json.Unmarshal(jsonData, target)\n}\n\nfunc loadMCPHarnessConfig(harnessPath string) (*v1.MCPHarnessSpec, error) {\n\tconfigPath := filepath.Join(harnessPath, \"mcp.yaml\")\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read mcp.yaml: %w\", err)\n\t}\n\n\tvar spec v1.MCPHarnessSpec\n\tif err := yamlToJSON(data, &spec); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse mcp.yaml: %w\", err)\n\t}\n\n\treturn &spec, nil\n}\n\nfunc loadMemoryHarnessConfig(harnessPath string) (*v1.MemoryHarnessSpec, error) {\n\tconfigPath := filepath.Join(harnessPath, \"memory.yaml\")\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read memory.yaml: %w\", err)\n\t}\n\n\tvar spec v1.MemoryHarnessSpec\n\tif err := yamlToJSON(data, &spec); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse memory.yaml: %w\", err)\n\t}\n\n\treturn &spec, nil\n}\n\nfunc loadSandboxHarnessConfig(harnessPath string) (*v1.SandboxHarnessSpec, error) {\n\tconfigPath := filepath.Join(harnessPath, \"sandbox.yaml\")\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read sandbox.yaml: %w\", err)\n\t}\n\n\tvar spec v1.SandboxHarnessSpec\n\tif err := yamlToJSON(data, &spec); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse sandbox.yaml: %w\", err)\n\t}\n\n\treturn &spec, nil\n}\n\nfunc loadSkillsHarnessConfig(harnessPath string) (*v1.SkillsHarnessSpec, error) {\n\tconfigPath := filepath.Join(harnessPath, \"skills.yaml\")\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read skills.yaml: %w\", err)\n\t}\n\n\tvar spec v1.SkillsHarnessSpec\n\tif err := yamlToJSON(data, &spec); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse skills.yaml: %w\", err)\n\t}\n\n\treturn &spec, nil\n}\n\n// buildHarnessConfig builds Handler's HarnessConfig from HarnessManager.\nfunc buildHarnessConfig(mgr *harness.HarnessManager) *handler.HarnessConfig {\n\tcfg := &handler.HarnessConfig{}\n\n\tif modelHarness := mgr.GetModelHarness(); modelHarness != nil {\n\t\tcfg.Model = modelHarness.GetSpec()\n\t}\n\n\tif mcpHarness := mgr.GetMCPHarness(); mcpHarness != nil {\n\t\tcfg.MCP = mcpHarness.GetSpec()\n\t}\n\n\tif memoryHarness := mgr.GetMemoryHarness(); memoryHarness != nil {\n\t\tcfg.Memory = memoryHarness.GetSpec()\n\t}\n\n\tif sandboxHarness := mgr.GetSandboxHarness(); sandboxHarness != nil {\n\t\tcfg.Sandbox = sandboxHarness.GetSpec()\n\t}\n\n\tif skillsHarness := mgr.GetSkillsHarness(); skillsHarness != nil {\n\t\tcfg.Skills = skillsHarness.GetSpec()\n\t}\n\n\treturn cfg\n}\n\n// loadGatewaysFromIndex loads gateways from agent-index.yaml (written by Config Daemon).\n// verbose: if true, always log status; if false, only log on changes (new agents)\nfunc loadGatewaysFromIndex(ctx context.Context, h *openclaw.OpenClawHandler, indexPath string, agentConfigDir string, workDir string, configDir string, harnessCfg *handler.HarnessConfig, loadedGateways map[string]bool, portAssignments map[string]int, nextPort *int, verbose bool) {\n\t// Read agent index\n\tdata, err := os.ReadFile(indexPath)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\tif verbose {\n\t\t\t\tlog.Printf(\"Agent index not found at %s, waiting for Config Daemon\", indexPath)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tlog.Printf(\"Error reading agent index: %v\", err)\n\t\treturn\n\t}\n\n\tvar index AgentIndex\n\tif err := yaml.Unmarshal(data, &index); err != nil {\n\t\tlog.Printf(\"Error parsing agent index: %v\", err)\n\t\treturn\n\t}\n\n\t// Count new agents to load\n\tnewAgents := 0\n\tfor _, entry := range index.Agents {\n\t\tif loadedGateways[entry.Name] {\n\t\t\tcontinue // Already loaded\n\t\t}\n\t\tif entry.Phase != \"Running\" && entry.Phase != \"Pending\" && entry.Phase != \"Migrating\" && entry.Phase != \"Scheduling\" {\n\t\t\tcontinue\n\t\t}\n\t\tnewAgents++\n\t}\n\n\t// Only log if verbose or there are new agents (silent otherwise)\n\tif verbose || newAgents > 0 {\n\t\tlog.Printf(\"AgentIndex: %d agents, %d loaded, %d new\", len(index.Agents), len(loadedGateways), newAgents)\n\t}\n\n\t// Process each agent (each becomes a Gateway instance)\n\tfor _, entry := range index.Agents {\n\t\tif loadedGateways[entry.Name] {\n\t\t\tcontinue // Already loaded\n\t\t}\n\n\t\tif entry.Phase != \"Running\" && entry.Phase != \"Pending\" && entry.Phase != \"Migrating\" && entry.Phase != \"Scheduling\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tlog.Printf(\"Loading Gateway for agent: %s (phase: %s)\", entry.Name, entry.Phase)\n\n\t\t// Load agent config from hostPath (Config Daemon writes to /etc/agent-config/<agent-name>/)\n\t\tagentSpec, agentConfig, err := loadAgentFromHostPath(entry.Name, agentConfigDir)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error loading agent %s from hostPath: %v\", entry.Name, err)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Generate OpenClaw JSON config using agentConfig if available\n\t\tconfigData, err := generateGatewayConfig(h, agentSpec, agentConfig, harnessCfg)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error generating config for %s: %v\", entry.Name, err)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Write config file\n\t\tconfigPath := filepath.Join(configDir, fmt.Sprintf(\"%s.json\", entry.Name))\n\t\tif err := os.WriteFile(configPath, configData, 0644); err != nil {\n\t\t\tlog.Printf(\"Error writing config for %s: %v\", entry.Name, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tlog.Printf(\"Generated config for %s at %s\", entry.Name, configPath)\n\n\t\t// Assign port\n\t\tport := *nextPort\n\t\t*nextPort = *nextPort + 1\n\t\tportAssignments[entry.Name] = port\n\n\t\t// Extract channel token env vars from ConfigDaemon-resolved agentConfig\n\t\tchannelEnv := extractOpenClawChannelEnvVars(agentConfig)\n\t\tif len(channelEnv) > 0 {\n\t\t\tlog.Printf(\"Injecting %d channel env vars for %s\", len(channelEnv), entry.Name)\n\t\t}\n\n\t\t// Start Gateway instance with channel token env vars\n\t\tif err := h.StartFrameworkInstance(ctx, entry.Name, configPath, channelEnv...); err != nil {\n\t\t\tlog.Printf(\"Error starting Gateway for %s: %v\", entry.Name, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tlog.Printf(\"Started Gateway for %s on port %d\", entry.Name, port)\n\t\tloadedGateways[entry.Name] = true\n\t}\n}\n\n// extractOpenClawChannelEnvVars extracts channel token env vars from\n// ConfigDaemon-resolved agentConfig. ConfigDaemon has already recursively\n// replaced all tokenSecretRef fields with actual token values, so this\n// function simply reads the resolved tokens from known channel paths\n// and produces subprocess env vars (e.g., DISCORD_BOT_TOKEN=<token>).\n// This is OpenClaw-specific logic — it knows the channel config schema.\nfunc extractOpenClawChannelEnvVars(agentConfig map[string]interface{}) []string {\n\tif agentConfig == nil {\n\t\treturn nil\n\t}\n\n\tchannels, ok := agentConfig[\"channels\"].(map[string]interface{})\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tvar envVars []string\n\n\t// Discord: channels.discord.token -> DISCORD_BOT_TOKEN env var\n\tif discord, ok := channels[\"discord\"].(map[string]interface{}); ok {\n\t\tif token, ok := discord[\"token\"].(string); ok && token != \"\" {\n\t\t\tenvVars = append(envVars, \"DISCORD_BOT_TOKEN=\"+token)\n\t\t}\n\t}\n\n\t// Telegram: channels.telegram.token -> TELEGRAM_BOT_TOKEN env var\n\tif telegram, ok := channels[\"telegram\"].(map[string]interface{}); ok {\n\t\tif token, ok := telegram[\"token\"].(string); ok && token != \"\" {\n\t\t\tenvVars = append(envVars, \"TELEGRAM_BOT_TOKEN=\"+token)\n\t\t}\n\t}\n\n\t// Slack: channels.slack.token -> SLACK_BOT_TOKEN env var\n\tif slack, ok := channels[\"slack\"].(map[string]interface{}); ok {\n\t\tif token, ok := slack[\"token\"].(string); ok && token != \"\" {\n\t\t\tenvVars = append(envVars, \"SLACK_BOT_TOKEN=\"+token)\n\t\t}\n\t}\n\n\treturn envVars\n}\n\n\n// loadAgentFromHostPath loads agent config from hostPath directory.\n// Config Daemon writes: /var/lib/aiagent/configs/<namespace>/<agent-name>/agent-config.json and agent-meta.yaml\n// But the mount is /var/lib/aiagent/configs/<namespace> -> /etc/agent-config\n// So inside Pod: /etc/agent-config/<agent-name>/agent-config.json\nfunc loadAgentFromHostPath(agentName string, agentConfigDir string) (*v1.AIAgentSpec, map[string]interface{}, error) {\n\t// Read agent-meta.yaml for basic metadata\n\t// Path structure inside Pod: /etc/agent-config/<agent-name>/agent-meta.yaml\n\tmetaPath := filepath.Join(agentConfigDir, agentName, \"agent-meta.yaml\")\n\tmetaData, err := os.ReadFile(metaPath)\n\tif err != nil {\n\t\tif *debug {\n\t\t\tlog.Printf(\"Agent meta not found for %s: %v\", agentName, err)\n\t\t}\n\t\tagentSpec := &v1.AIAgentSpec{\n\t\t\tDescription: agentName,\n\t\t\tRuntimeRef: v1.RuntimeReference{Type: \"openclaw\"},\n\t\t}\n\t\treturn agentSpec, nil, nil\n\t}\n\n\tvar meta AgentMeta\n\tif err := yaml.Unmarshal(metaData, &meta); err != nil {\n\t\tlog.Printf(\"Error parsing agent meta for %s: %v\", agentName, err)\n\t\tagentSpec := &v1.AIAgentSpec{\n\t\t\tDescription: agentName,\n\t\t\tRuntimeRef: v1.RuntimeReference{Type: \"openclaw\"},\n\t\t}\n\t\treturn agentSpec, nil, nil\n\t}\n\n\t// Create AIAgentSpec from meta\n\tagentSpec := &v1.AIAgentSpec{\n\t\tDescription: meta.Name,\n\t\tRuntimeRef: v1.RuntimeReference{\n\t\t\tType: \"openclaw\",\n\t\t\tName: meta.Runtime,\n\t\t},\n\t}\n\n\t// Read agent-config.json\n\t// Path structure inside Pod: /etc/agent-config/<agent-name>/agent-config.json\n\tagentConfigPath := filepath.Join(agentConfigDir, agentName, \"agent-config.json\")\n\tagentConfigData, err := os.ReadFile(agentConfigPath)\n\tif err != nil {\n\t\tif *debug {\n\t\t\tlog.Printf(\"Agent config not found for %s, using defaults\", agentName)\n\t\t}\n\t\treturn agentSpec, nil, nil\n\t}\n\n\t// Parse agent config JSON\n\tvar agentConfig map[string]interface{}\n\tif err := json.Unmarshal(agentConfigData, &agentConfig); err != nil {\n\t\tlog.Printf(\"Error parsing agent config for %s: %v\", agentName, err)\n\t\treturn agentSpec, nil, nil\n\t}\n\n\tif *debug {\n\t\tlog.Printf(\"Loaded agent config for %s: %v\", agentName, agentConfig)\n\t}\n\n\treturn agentSpec, agentConfig, nil\n}\n\n// generateGatewayConfig generates Gateway config by merging agentConfig with harnessCfg.\n// agentConfig contains agent-specific settings (channels, gateway, internal agents)\n// harnessCfg contains shared platform capabilities (models, skills, memory)\n// Config uses env var names (e.g., \"DEEPSEEK_API_KEY\") - OpenClaw resolves from env at runtime.\nfunc generateGatewayConfig(h *openclaw.OpenClawHandler, agentSpec *v1.AIAgentSpec, agentConfig map[string]interface{}, harnessCfg *handler.HarnessConfig) ([]byte, error) {\n\t// Build AIAgentSpec with agentConfig.Raw from the loaded config\n\tspec := agentSpec\n\tif agentConfig != nil {\n\t\t// Marshal agentConfig to Raw JSON so converter can process it\n\t\trawConfig, err := json.Marshal(agentConfig)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error marshaling agentConfig: %v\", err)\n\t\t} else {\n\t\t\tspec = &v1.AIAgentSpec{\n\t\t\t\tDescription: agentSpec.Description,\n\t\t\t\tRuntimeRef: agentSpec.RuntimeRef,\n\t\t\t\tAgentConfig: &apiextensionsv1.JSON{Raw: rawConfig},\n\t\t\t\tHarnessOverride: agentSpec.HarnessOverride,\n\t\t\t}\n\t\t}\n\t}\n\n\t// Generate config by merging agentSpec with harnessCfg\n\t// Converter outputs env var names (e.g., \"DEEPSEEK_API_KEY\") in config\n\t// OpenClaw resolves these from environment at runtime\n\treturn h.GenerateFrameworkConfig(spec, harnessCfg)\n}\n\n// checkGatewayHealthWithChangeDetection checks health and only logs on state changes.\n// Returns the current state for comparison in next iteration.\nfunc checkGatewayHealthWithChangeDetection(ctx context.Context, h *openclaw.OpenClawHandler, lastAgentCount int, lastRunning bool, lastInstances int) (int, bool, int) {\n\tstatus, err := h.GetFrameworkStatus(ctx)\n\tif err != nil {\n\t\t// Only log error if it's new (previous state was healthy)\n\t\tif lastRunning {\n\t\t\tlog.Printf(\"Error getting Gateway status: %v\", err)\n\t\t}\n\t\treturn lastAgentCount, false, 0\n\t}\n\n\t// Only log if state changed (running status or instance count)\n\tif status.Running != lastRunning || status.InstanceCount != lastInstances {\n\t\tlog.Printf(\"Gateway status: running=%v, instances=%d, health=%s\", status.Running, status.InstanceCount, status.Health)\n\t}\n\n\treturn lastAgentCount, status.Running, status.InstanceCount\n}\n\n// cleanup stops all Gateway processes.\nfunc cleanup(ctx context.Context, h *openclaw.OpenClawHandler, loadedGateways map[string]bool) error {\n\tlog.Printf(\"Stopping all Gateways...\")\n\n\tfor gatewayName := range loadedGateways {\n\t\tif err := h.StopAgent(ctx, gatewayName); err != nil {\n\t\t\tlog.Printf(\"Error stopping Gateway %s: %v\", gatewayName, err)\n\t\t}\n\t}\n\n\tlog.Printf(\"Stopping Framework...\")\n\tif err := h.StopFramework(ctx); err != nil {\n\t\tlog.Printf(\"Error stopping Framework: %v\", err)\n\t}\n\n\treturn nil\n}\n\n// initializeOpenClawState caches OpenClaw state from the ImageVolume to a base directory.\n// The cached state is then copied per-instance in the handler's StartFrameworkInstance.\n// This avoids copying from the read-only ImageVolume for every Gateway instance.\nfunc initializeOpenClawState(workDir string, frameworkBin string) error {\n\tsourceDir := \"/framework-rootfs/openclaw-state\"\n\tif _, err := os.Stat(sourceDir); err != nil {\n\t\treturn fmt.Errorf(\"OpenClaw state source not found in ImageVolume: %w\", err)\n\t}\n\n\t// Cache to .openclaw-base/ subdirectory (not directly in workDir)\n\tbaseDir := filepath.Join(workDir, \".openclaw-base\")\n\n\tif _, err := os.Stat(baseDir); err == nil {\n\t\tlog.Printf(\"OpenClaw state cache already exists at %s\", baseDir)\n\t\treturn nil\n\t}\n\n\tlog.Printf(\"Caching OpenClaw state at %s (copying from ImageVolume %s)\", baseDir, sourceDir)\n\n\tif err := os.MkdirAll(baseDir, 0755); err != nil {\n\t\treturn fmt.Errorf(\"failed to create cache directory: %w\", err)\n\t}\n\n\tif err := openclaw.CopyDir(sourceDir, baseDir); err != nil {\n\t\treturn fmt.Errorf(\"failed to copy OpenClaw state: %w\", err)\n\t}\n\n\tlog.Printf(\"OpenClaw state cached successfully at %s\", baseDir)\n\treturn nil\n}"}}}}