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.
1 line
238 KiB
JSON
1 line
238 KiB
JSON
{"repo": "971023als/pisa", "files": {"pentagi-main/pentagi-main/docker-compose.yml": {"148f693e1ee122f37e9f842d23481503fe8c48f5": {"t": 1785745265.8271344, "n": 10633, "h": "768dd5d5e6487d6a", "c": "volumes:\n pentagi-data:\n driver: local\n pentagi-ssl:\n driver: local\n pentagi-ollama:\n driver: local\n scraper-ssl:\n driver: local\n pentagi-postgres-data:\n driver: local\n\nnetworks:\n pentagi-network:\n driver: bridge\n name: pentagi-network\n observability-network:\n driver: bridge\n name: observability-network\n langfuse-network:\n driver: bridge\n name: langfuse-network\n\nservices:\n pentagi:\n image: ${PENTAGI_IMAGE:-vxcontrol/pentagi:latest}\n restart: unless-stopped\n container_name: pentagi\n hostname: pentagi\n expose:\n - 8443/tcp\n ports:\n - ${PENTAGI_LISTEN_IP:-127.0.0.1}:${PENTAGI_LISTEN_PORT:-8443}:8443\n depends_on:\n pgvector:\n condition: service_healthy\n environment:\n - DEBUG=${DEBUG:-false}\n - DOCKER_GID=998\n - CORS_ORIGINS=${CORS_ORIGINS:-}\n - COOKIE_SIGNING_SALT=${COOKIE_SIGNING_SALT:-}\n - INSTALLATION_ID=${INSTALLATION_ID:-}\n - LICENSE_KEY=${LICENSE_KEY:-}\n - ASK_USER=${ASK_USER:-false}\n - OPEN_AI_KEY=${OPEN_AI_KEY:-}\n - OPEN_AI_SERVER_URL=${OPEN_AI_SERVER_URL:-}\n - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}\n - ANTHROPIC_SERVER_URL=${ANTHROPIC_SERVER_URL:-}\n - GEMINI_API_KEY=${GEMINI_API_KEY:-}\n - GEMINI_SERVER_URL=${GEMINI_SERVER_URL:-}\n - BEDROCK_REGION=${BEDROCK_REGION:-}\n - BEDROCK_DEFAULT_AUTH=${BEDROCK_DEFAULT_AUTH:-}\n - BEDROCK_BEARER_TOKEN=${BEDROCK_BEARER_TOKEN:-}\n - BEDROCK_ACCESS_KEY_ID=${BEDROCK_ACCESS_KEY_ID:-}\n - BEDROCK_SECRET_ACCESS_KEY=${BEDROCK_SECRET_ACCESS_KEY:-}\n - BEDROCK_SESSION_TOKEN=${BEDROCK_SESSION_TOKEN:-}\n - BEDROCK_SERVER_URL=${BEDROCK_SERVER_URL:-}\n - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY:-}\n - DEEPSEEK_SERVER_URL=${DEEPSEEK_SERVER_URL:-}\n - DEEPSEEK_PROVIDER=${DEEPSEEK_PROVIDER:-}\n - GLM_API_KEY=${GLM_API_KEY:-}\n - GLM_SERVER_URL=${GLM_SERVER_URL:-}\n - GLM_PROVIDER=${GLM_PROVIDER:-}\n - KIMI_API_KEY=${KIMI_API_KEY:-}\n - KIMI_SERVER_URL=${KIMI_SERVER_URL:-}\n - KIMI_PROVIDER=${KIMI_PROVIDER:-}\n - QWEN_API_KEY=${QWEN_API_KEY:-}\n - QWEN_SERVER_URL=${QWEN_SERVER_URL:-}\n - QWEN_PROVIDER=${QWEN_PROVIDER:-}\n - LLM_SERVER_URL=${LLM_SERVER_URL:-}\n - LLM_SERVER_KEY=${LLM_SERVER_KEY:-}\n - LLM_SERVER_MODEL=${LLM_SERVER_MODEL:-}\n - LLM_SERVER_PROVIDER=${LLM_SERVER_PROVIDER:-}\n - LLM_SERVER_CONFIG_PATH=${LLM_SERVER_CONFIG_PATH:-}\n - LLM_SERVER_LEGACY_REASONING=${LLM_SERVER_LEGACY_REASONING:-}\n - LLM_SERVER_PRESERVE_REASONING=${LLM_SERVER_PRESERVE_REASONING:-}\n - OLLAMA_SERVER_URL=${OLLAMA_SERVER_URL:-}\n - OLLAMA_SERVER_API_KEY=${OLLAMA_SERVER_API_KEY:-}\n - OLLAMA_SERVER_MODEL=${OLLAMA_SERVER_MODEL:-}\n - OLLAMA_SERVER_CONFIG_PATH=${OLLAMA_SERVER_CONFIG_PATH:-}\n - OLLAMA_SERVER_PULL_MODELS_TIMEOUT=${OLLAMA_SERVER_PULL_MODELS_TIMEOUT:-}\n - OLLAMA_SERVER_PULL_MODELS_ENABLED=${OLLAMA_SERVER_PULL_MODELS_ENABLED:-}\n - OLLAMA_SERVER_LOAD_MODELS_ENABLED=${OLLAMA_SERVER_LOAD_MODELS_ENABLED:-}\n - EMBEDDING_URL=${EMBEDDING_URL:-}\n - EMBEDDING_KEY=${EMBEDDING_KEY:-}\n - EMBEDDING_MODEL=${EMBEDDING_MODEL:-}\n - EMBEDDING_PROVIDER=${EMBEDDING_PROVIDER:-}\n - EMBEDDING_BATCH_SIZE=${EMBEDDING_BATCH_SIZE:-}\n - EMBEDDING_STRIP_NEW_LINES=${EMBEDDING_STRIP_NEW_LINES:-}\n - SUMMARIZER_PRESERVE_LAST=${SUMMARIZER_PRESERVE_LAST:-}\n - SUMMARIZER_USE_QA=${SUMMARIZER_USE_QA:-}\n - SUMMARIZER_SUM_MSG_HUMAN_IN_QA=${SUMMARIZER_SUM_MSG_HUMAN_IN_QA:-}\n - SUMMARIZER_LAST_SEC_BYTES=${SUMMARIZER_LAST_SEC_BYTES:-}\n - SUMMARIZER_MAX_BP_BYTES=${SUMMARIZER_MAX_BP_BYTES:-}\n - SUMMARIZER_MAX_QA_SECTIONS=${SUMMARIZER_MAX_QA_SECTIONS:-}\n - SUMMARIZER_MAX_QA_BYTES=${SUMMARIZER_MAX_QA_BYTES:-}\n - SUMMARIZER_KEEP_QA_SECTIONS=${SUMMARIZER_KEEP_QA_SECTIONS:-}\n - ASSISTANT_USE_AGENTS=${ASSISTANT_USE_AGENTS:-}\n - ASSISTANT_SUMMARIZER_PRESERVE_LAST=${ASSISTANT_SUMMARIZER_PRESERVE_LAST:-}\n - ASSISTANT_SUMMARIZER_LAST_SEC_BYTES=${ASSISTANT_SUMMARIZER_LAST_SEC_BYTES:-}\n - ASSISTANT_SUMMARIZER_MAX_BP_BYTES=${ASSISTANT_SUMMARIZER_MAX_BP_BYTES:-}\n - ASSISTANT_SUMMARIZER_MAX_QA_SECTIONS=${ASSISTANT_SUMMARIZER_MAX_QA_SECTIONS:-}\n - ASSISTANT_SUMMARIZER_MAX_QA_BYTES=${ASSISTANT_SUMMARIZER_MAX_QA_BYTES:-}\n - ASSISTANT_SUMMARIZER_KEEP_QA_SECTIONS=${ASSISTANT_SUMMARIZER_KEEP_QA_SECTIONS:-}\n - EXECUTION_MONITOR_ENABLED=${EXECUTION_MONITOR_ENABLED:-}\n - EXECUTION_MONITOR_SAME_TOOL_LIMIT=${EXECUTION_MONITOR_SAME_TOOL_LIMIT:-}\n - EXECUTION_MONITOR_TOTAL_TOOL_LIMIT=${EXECUTION_MONITOR_TOTAL_TOOL_LIMIT:-}\n - MAX_GENERAL_AGENT_TOOL_CALLS=${MAX_GENERAL_AGENT_TOOL_CALLS:-}\n - MAX_LIMITED_AGENT_TOOL_CALLS=${MAX_LIMITED_AGENT_TOOL_CALLS:-}\n - AGENT_PLANNING_STEP_ENABLED=${AGENT_PLANNING_STEP_ENABLED:-}\n - PROXY_URL=${PROXY_URL:-}\n - EXTERNAL_SSL_CA_PATH=${EXTERNAL_SSL_CA_PATH:-}\n - EXTERNAL_SSL_INSECURE=${EXTERNAL_SSL_INSECURE:-}\n - HTTP_CLIENT_TIMEOUT=${HTTP_CLIENT_TIMEOUT:-}\n - SCRAPER_PUBLIC_URL=${SCRAPER_PUBLIC_URL:-}\n - SCRAPER_PRIVATE_URL=${SCRAPER_PRIVATE_URL:-}\n - GRAPHITI_ENABLED=${GRAPHITI_ENABLED:-}\n - GRAPHITI_TIMEOUT=${GRAPHITI_TIMEOUT:-}\n - GRAPHITI_URL=${GRAPHITI_URL:-}\n - PUBLIC_URL=${PUBLIC_URL:-}\n - STATIC_DIR=${STATIC_DIR:-}\n - STATIC_URL=${STATIC_URL:-}\n - SERVER_PORT=${SERVER_PORT:-8443}\n - SERVER_HOST=${SERVER_HOST:-0.0.0.0}\n - SERVER_SSL_CRT=${SERVER_SSL_CRT:-}\n - SERVER_SSL_KEY=${SERVER_SSL_KEY:-}\n - SERVER_USE_SSL=${SERVER_USE_SSL:-true}\n - OAUTH_GOOGLE_CLIENT_ID=${OAUTH_GOOGLE_CLIENT_ID:-}\n - OAUTH_GOOGLE_CLIENT_SECRET=${OAUTH_GOOGLE_CLIENT_SECRET:-}\n - OAUTH_GITHUB_CLIENT_ID=${OAUTH_GITHUB_CLIENT_ID:-}\n - OAUTH_GITHUB_CLIENT_SECRET=${OAUTH_GITHUB_CLIENT_SECRET:-}\n - DATABASE_URL=postgres://${PENTAGI_POSTGRES_USER:-postgres}:${PENTAGI_POSTGRES_PASSWORD:-postgres}@pgvector:5432/${PENTAGI_POSTGRES_DB:-pentagidb}?sslmode=disable\n - DUCKDUCKGO_ENABLED=${DUCKDUCKGO_ENABLED:-}\n - DUCKDUCKGO_REGION=${DUCKDUCKGO_REGION:-}\n - DUCKDUCKGO_SAFESEARCH=${DUCKDUCKGO_SAFESEARCH:-}\n - DUCKDUCKGO_TIME_RANGE=${DUCKDUCKGO_TIME_RANGE:-}\n - SPLOITUS_ENABLED=${SPLOITUS_ENABLED:-}\n - SEARXNG_URL=${SEARXNG_URL:-}\n - SEARXNG_CATEGORIES=${SEARXNG_CATEGORIES:-}\n - SEARXNG_LANGUAGE=${SEARXNG_LANGUAGE:-}\n - SEARXNG_SAFESEARCH=${SEARXNG_SAFESEARCH:-}\n - SEARXNG_TIME_RANGE=${SEARXNG_TIME_RANGE:-}\n - SEARXNG_TIMEOUT=${SEARXNG_TIMEOUT:-}\n - GOOGLE_API_KEY=${GOOGLE_API_KEY:-}\n - GOOGLE_CX_KEY=${GOOGLE_CX_KEY:-}\n - GOOGLE_LR_KEY=${GOOGLE_LR_KEY:-}\n - TRAVERSAAL_API_KEY=${TRAVERSAAL_API_KEY:-}\n - TAVILY_API_KEY=${TAVILY_API_KEY:-}\n - PERPLEXITY_API_KEY=${PERPLEXITY_API_KEY:-}\n - PERPLEXITY_MODEL=${PERPLEXITY_MODEL:-sonar}\n - PERPLEXITY_CONTEXT_SIZE=${PERPLEXITY_CONTEXT_SIZE:-low}\n - LANGFUSE_BASE_URL=${LANGFUSE_BASE_URL:-}\n - LANGFUSE_PROJECT_ID=${LANGFUSE_PROJECT_ID:-}\n - LANGFUSE_PUBLIC_KEY=${LANGFUSE_PUBLIC_KEY:-}\n - LANGFUSE_SECRET_KEY=${LANGFUSE_SECRET_KEY:-}\n - OTEL_HOST=${OTEL_HOST:-}\n - DOCKER_HOST=${DOCKER_HOST:-unix:///var/run/docker.sock}\n - DOCKER_TLS_VERIFY=${DOCKER_TLS_VERIFY:-}\n - DOCKER_CERT_PATH=${DOCKER_CERT_PATH:-}\n - DOCKER_INSIDE=${DOCKER_INSIDE:-false}\n - DOCKER_NET_ADMIN=${DOCKER_NET_ADMIN:-false}\n - DOCKER_SOCKET=${DOCKER_SOCKET:-}\n - DOCKER_NETWORK=${DOCKER_NETWORK:-}\n - DOCKER_PUBLIC_IP=${DOCKER_PUBLIC_IP:-}\n - DOCKER_WORK_DIR=${DOCKER_WORK_DIR:-}\n - DOCKER_DEFAULT_IMAGE=${DOCKER_DEFAULT_IMAGE:-}\n - DOCKER_DEFAULT_IMAGE_FOR_PENTEST=${DOCKER_DEFAULT_IMAGE_FOR_PENTEST:-}\n logging:\n options:\n max-size: 50m\n max-file: \"7\"\n volumes:\n - ${PENTAGI_DATA_DIR:-pentagi-data}:/opt/pentagi/data\n - ${PENTAGI_SSL_DIR:-pentagi-ssl}:/opt/pentagi/ssl\n - ${PENTAGI_OLLAMA_DIR:-pentagi-ollama}:/root/.ollama\n - ${PENTAGI_DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock\n - ${PENTAGI_LLM_SERVER_CONFIG_PATH:-./example.custom.provider.yml}:/opt/pentagi/conf/custom.provider.yml\n - ${PENTAGI_OLLAMA_SERVER_CONFIG_PATH:-./example.ollama.provider.yml}:/opt/pentagi/conf/ollama.provider.yml\n - ${PENTAGI_DOCKER_CERT_PATH:-./docker-ssl}:/opt/pentagi/docker/ssl\n user: root:root # while using docker.sock\n networks:\n - pentagi-network\n - observability-network\n - langfuse-network\n\n pgvector:\n image: vxcontrol/pgvector:latest\n restart: unless-stopped\n container_name: pgvector\n hostname: pgvector\n expose:\n - 5432/tcp\n ports:\n - ${PGVECTOR_LISTEN_IP:-127.0.0.1}:${PGVECTOR_LISTEN_PORT:-5432}:5432\n environment:\n POSTGRES_USER: ${PENTAGI_POSTGRES_USER:-postgres}\n POSTGRES_PASSWORD: ${PENTAGI_POSTGRES_PASSWORD:-postgres}\n POSTGRES_DB: ${PENTAGI_POSTGRES_DB:-pentagidb}\n logging:\n options:\n max-size: 50m\n max-file: \"7\"\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U $${PENTAGI_POSTGRES_USER:-postgres}\"]\n interval: 3s\n timeout: 3s\n retries: 10\n start_period: 5s\n volumes:\n - pentagi-postgres-data:/var/lib/postgresql/data\n networks:\n - pentagi-network\n\n pgexporter:\n image: quay.io/prometheuscommunity/postgres-exporter:v0.16.0\n restart: unless-stopped\n depends_on:\n pgvector:\n condition: service_healthy\n container_name: pgexporter\n hostname: pgexporter\n expose:\n - 9187/tcp\n ports:\n - ${POSTGRES_EXPORTER_LISTEN_IP:-127.0.0.1}:${POSTGRES_EXPORTER_LISTEN_PORT:-9187}:9187\n environment:\n - DATA_SOURCE_NAME=postgresql://${PENTAGI_POSTGRES_USER:-postgres}:${PENTAGI_POSTGRES_PASSWORD:-postgres}@pgvector:5432/${PENTAGI_POSTGRES_DB:-pentagidb}?sslmode=disable\n logging:\n options:\n max-size: 50m\n max-file: \"7\"\n networks:\n - pentagi-network\n\n scraper:\n image: vxcontrol/scraper:latest\n restart: unless-stopped\n container_name: scraper\n hostname: scraper\n expose:\n - 443/tcp\n ports:\n - ${SCRAPER_LISTEN_IP:-127.0.0.1}:${SCRAPER_LISTEN_PORT:-9443}:443\n environment:\n - MAX_CONCURRENT_SESSIONS=${LOCAL_SCRAPER_MAX_CONCURRENT_SESSIONS:-10}\n - USERNAME=${LOCAL_SCRAPER_USERNAME:-someuser}\n - PASSWORD=${LOCAL_SCRAPER_PASSWORD:-somepass}\n logging:\n options:\n max-size: 50m\n max-file: \"7\"\n volumes:\n - scraper-ssl:/usr/src/app/ssl\n networks:\n - pentagi-network\n shm_size: 2g\n"}}, "pentagi-main/pentagi-main/backend/pkg/providers/deepseek/deepseek.go": {"148f693e1ee122f37e9f842d23481503fe8c48f5": {"t": 1785745632.0291667, "n": 4772, "h": "7bb5cbb662af80eb", "c": "package deepseek\n\nimport (\n\t\"context\"\n\t\"embed\"\n\t\"fmt\"\n\n\t\"pentagi/pkg/config\"\n\t\"pentagi/pkg/providers/pconfig\"\n\t\"pentagi/pkg/providers/provider\"\n\t\"pentagi/pkg/system\"\n\t\"pentagi/pkg/templates\"\n\n\t\"github.com/vxcontrol/langchaingo/llms\"\n\t\"github.com/vxcontrol/langchaingo/llms/openai\"\n\t\"github.com/vxcontrol/langchaingo/llms/streaming\"\n)\n\n//go:embed config.yml models.yml\nvar configFS embed.FS\n\nconst DeepSeekAgentModel = \"deepseek-chat\"\n\nconst DeepSeekToolCallIDTemplate = \"call_{r:2:d}_{r:24:b}\"\n\nfunc BuildProviderConfig(configData []byte) (*pconfig.ProviderConfig, error) {\n\tdefaultOptions := []llms.CallOption{\n\t\tllms.WithModel(DeepSeekAgentModel),\n\t\tllms.WithN(1),\n\t\tllms.WithMaxTokens(4000),\n\t}\n\n\tproviderConfig, err := pconfig.LoadConfigData(configData, defaultOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn providerConfig, nil\n}\n\nfunc DefaultProviderConfig() (*pconfig.ProviderConfig, error) {\n\tconfigData, err := configFS.ReadFile(\"config.yml\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn BuildProviderConfig(configData)\n}\n\nfunc DefaultModels() (pconfig.ModelsConfig, error) {\n\tconfigData, err := configFS.ReadFile(\"models.yml\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pconfig.LoadModelsConfigData(configData)\n}\n\ntype deepseekProvider struct {\n\tllm *openai.LLM\n\tmodels pconfig.ModelsConfig\n\tproviderName provider.ProviderName\n\tproviderConfig *pconfig.ProviderConfig\n\tproviderPrefix string\n}\n\nfunc New(\n\tcfg *config.Config,\n\tproviderName provider.ProviderName,\n\tproviderConfig *pconfig.ProviderConfig,\n) (provider.Provider, error) {\n\tif cfg.DeepSeekAPIKey == \"\" {\n\t\treturn nil, fmt.Errorf(\"missing DEEPSEEK_API_KEY environment variable\")\n\t}\n\n\thttpClient, err := system.GetHTTPClient(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmodels, err := DefaultModels()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tclient, err := openai.New(\n\t\topenai.WithToken(cfg.DeepSeekAPIKey),\n\t\topenai.WithModel(DeepSeekAgentModel),\n\t\topenai.WithBaseURL(cfg.DeepSeekServerURL),\n\t\topenai.WithHTTPClient(httpClient),\n\t\topenai.WithPreserveReasoningContent(),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &deepseekProvider{\n\t\tllm: client,\n\t\tmodels: models,\n\t\tproviderName: providerName,\n\t\tproviderConfig: providerConfig,\n\t\tproviderPrefix: cfg.DeepSeekProvider,\n\t}, nil\n}\n\nfunc (p *deepseekProvider) Type() provider.ProviderType {\n\treturn provider.ProviderDeepSeek\n}\n\nfunc (p *deepseekProvider) Name() provider.ProviderName {\n\treturn p.providerName\n}\n\nfunc (p *deepseekProvider) GetRawConfig() []byte {\n\treturn p.providerConfig.GetRawConfig()\n}\n\nfunc (p *deepseekProvider) GetProviderConfig() *pconfig.ProviderConfig {\n\treturn p.providerConfig\n}\n\nfunc (p *deepseekProvider) GetPriceInfo(opt pconfig.ProviderOptionsType) *pconfig.PriceInfo {\n\treturn p.providerConfig.GetPriceInfoForType(opt)\n}\n\nfunc (p *deepseekProvider) GetModels() pconfig.ModelsConfig {\n\treturn p.models\n}\n\nfunc (p *deepseekProvider) Model(opt pconfig.ProviderOptionsType) string {\n\tmodel := DeepSeekAgentModel\n\topts := llms.CallOptions{Model: &model}\n\tfor _, option := range p.providerConfig.GetOptionsForType(opt) {\n\t\toption(&opts)\n\t}\n\n\treturn opts.GetModel()\n}\n\nfunc (p *deepseekProvider) ModelWithPrefix(opt pconfig.ProviderOptionsType) string {\n\treturn provider.ApplyModelPrefix(p.Model(opt), p.providerPrefix)\n}\n\nfunc (p *deepseekProvider) Call(\n\tctx context.Context,\n\topt pconfig.ProviderOptionsType,\n\tprompt string,\n) (string, error) {\n\treturn provider.WrapGenerateFromSinglePrompt(\n\t\tctx, p, opt, p.llm, prompt,\n\t\tp.providerConfig.GetOptionsForType(opt)...,\n\t)\n}\n\nfunc (p *deepseekProvider) CallEx(\n\tctx context.Context,\n\topt pconfig.ProviderOptionsType,\n\tchain []llms.MessageContent,\n\tstreamCb streaming.Callback,\n) (*llms.ContentResponse, error) {\n\treturn provider.WrapGenerateContent(\n\t\tctx, p, opt, p.llm.GenerateContent, chain,\n\t\tappend([]llms.CallOption{\n\t\t\tllms.WithStreamingFunc(streamCb),\n\t\t}, p.providerConfig.GetOptionsForType(opt)...)...,\n\t)\n}\n\nfunc (p *deepseekProvider) CallWithTools(\n\tctx context.Context,\n\topt pconfig.ProviderOptionsType,\n\tchain []llms.MessageContent,\n\ttools []llms.Tool,\n\tstreamCb streaming.Callback,\n) (*llms.ContentResponse, error) {\n\treturn provider.WrapGenerateContent(\n\t\tctx, p, opt, p.llm.GenerateContent, chain,\n\t\tappend([]llms.CallOption{\n\t\t\tllms.WithTools(tools),\n\t\t\tllms.WithStreamingFunc(streamCb),\n\t\t}, p.providerConfig.GetOptionsForType(opt)...)...,\n\t)\n}\n\nfunc (p *deepseekProvider) GetUsage(info map[string]any) pconfig.CallUsage {\n\treturn pconfig.NewCallUsage(info)\n}\n\nfunc (p *deepseekProvider) GetToolCallIDTemplate(ctx context.Context, prompter templates.Prompter) (string, error) {\n\treturn provider.DetermineToolCallIDTemplate(ctx, p, pconfig.OptionsTypeSimple, prompter, DeepSeekToolCallIDTemplate)\n}\n"}}, "pentagi-main/pentagi-main/backend/cmd/installer/wizard/locale/locale.go": {"148f693e1ee122f37e9f842d23481503fe8c48f5": {"t": 1785745649.3844008, "n": 117570, "h": "1b193c6e5ef54432", "c": "package locale\n\n// Common status and UI strings\nconst (\n\t// Common status and UI strings\n\tUIStatistics = \"Statistics\"\n\tUIStatus = \"Status: \"\n\tUIMode = \"Mode: \"\n\tUINoConfigSelected = \"No configuration selected\"\n\tUILoading = \"Loading...\"\n\tUINotImplemented = \"Not implemented yet\"\n\tUIUnsavedChanges = \"Unsaved changes\"\n\tUIConfigSaved = \"Configuration saved\"\n\n\t// Status labels\n\tStatusEnabled = \"Enabled\"\n\tStatusDisabled = \"Disabled\"\n\tStatusConfigured = \"Configured\"\n\tStatusNotConfigured = \"Not configured\"\n\tStatusEmbedded = \"Embedded\"\n\tStatusExternal = \"External\"\n\n\t// Success/Warning messages\n\tMessageSearchEnginesNone = \"⚠ No search engines configured\"\n\tMessageSearchEnginesConfigured = \"✓ %d search engines configured\"\n\tMessageDockerConfigured = \"✓ Docker environment configured\"\n\tMessageDockerNotConfigured = \"⚠ Docker environment not configured\"\n)\n\n// Legend constants\nconst (\n\tLegendConfigured = \"✓ Configured\"\n\tLegendNotConfigured = \"✗ Not configured\"\n)\n\n// Common Navigation Actions (always available)\nconst (\n\tNavBack = \"Esc: Back\"\n\tNavExit = \"Ctrl+Q: Exit\"\n\tNavUpDown = \"↑/↓: Scroll/Select\"\n\tNavLeftRight = \"←/→: Move\"\n\tNavPgUpPgDown = \"PgUp/PgDn: Page\"\n\tNavHomeEnd = \"Home/End: Start/End\"\n\tNavEnter = \"Enter: Continue\"\n\tNavYn = \"Y/N: Accept/Reject\"\n\tNavCtrlC = \"Ctrl+C: Cancel\"\n\tNavCtrlS = \"Ctrl+S: Save\"\n\tNavCtrlR = \"Ctrl+R: Reset\"\n\tNavCtrlH = \"Ctrl+H: Show/Hide\"\n\tNavTab = \"Tab: Complete\"\n\tNavSeparator = \" • \"\n)\n\n// Welcome Screen constants\nconst (\n\t// Form interface implementation\n\tWelcomeFormTitle = \"Welcome to PentAGI\"\n\tWelcomeFormDescription = \"PentAGI is an autonomous penetration testing platform that leverages AI technologies to perform comprehensive security assessments.\"\n\tWelcomeFormName = \"Welcome\"\n\tWelcomeFormOverview = `System checks verify:\n• Environment configuration file presence\n• Docker API accessibility and version compatibility\n• Worker environment readiness\n• System resources (CPU, memory, disk space)\n• Network connectivity for external dependencies\n\nOnce all checks pass, proceed through the configuration wizard to set up LLM providers, monitoring, and security tools.\n\nThe installer guides you through each component setup with recommendations for different deployment scenarios.`\n\n\t// Configuration status messages\n\tWelcomeConfigurationFailed = \"⚠ Failed checks: %s\"\n\tWelcomeConfigurationPassed = \"✓ All system checks passed\"\n\n\t// Workflow steps\n\tWelcomeWorkflowTitle = \"Installation Workflow:\"\n\tWelcomeWorkflowStep1 = \"1. Accept End User License Agreement\"\n\tWelcomeWorkflowStep2 = \"2. Configure LLM providers (OpenAI, Anthropic, etc.)\"\n\tWelcomeWorkflowStep3 = \"3. Set up integrations (Langfuse, Observability)\"\n\tWelcomeWorkflowStep4 = \"4. Configure security settings\"\n\tWelcomeWorkflowStep5 = \"5. Deploy and start PentAGI services\"\n\tWelcomeSystemReady = \"✓ System ready - Press Enter to continue\"\n)\n\n// Troubleshooting on welcome screen constants\nconst (\n\tTroubleshootTitle = \"System Requirements Not Met\"\n\n\t// Environment file issues\n\tTroubleshootEnvFileTitle = \"Environment Configuration Missing\"\n\tTroubleshootEnvFileDesc = \"The .env file is required for PentAGI configuration but was not found or is not readable.\"\n\tTroubleshootEnvFileFix = `To fix:\n1. Copy .env.example to .env in your installation directory\n2. Edit .env and configure at least one LLM provider API key\n3. Ensure the file has read permissions (chmod 644 .env)\n\nQuick fix:\ncp .env.example .env && chmod 644 .env`\n\n\t// Write permissions\n\tTroubleshootWritePermTitle = \"Write Permissions Required\"\n\tTroubleshootWritePermDesc = \"The installer needs write access to the configuration directory to save settings and deploy services.\"\n\tTroubleshootWritePermFix = `To fix:\n1. Check directory permissions: ls -la\n2. Grant write access: chmod 755 .\n3. Or run installer from a writable location\n4. Ensure sufficient disk space is available`\n\n\t// Docker not installed\n\tTroubleshootDockerNotInstalledTitle = \"Docker Not Installed\"\n\tTroubleshootDockerNotInstalledDesc = \"Docker is not installed on this system. PentAGI requires Docker to run containers.\"\n\tTroubleshootDockerNotInstalledFix = `To fix:\n1. Install Docker Desktop: https://docs.docker.com/get-docker/\n2. For Linux: Follow distribution-specific instructions\n3. Verify installation: docker --version\n4. Ensure docker command is in your PATH`\n\n\t// Docker not running\n\tTroubleshootDockerNotRunningTitle = \"Docker Daemon Not Running\"\n\tTroubleshootDockerNotRunningDesc = \"Docker is installed but the daemon is not running. The Docker service must be active.\"\n\tTroubleshootDockerNotRunningFix = `To fix:\n1. Start Docker Desktop (Windows/Mac)\n2. Linux: sudo systemctl start docker\n3. Check status: docker ps\n4. If using DOCKER_HOST, verify the remote daemon is accessible`\n\n\t// Docker permission issues\n\tTroubleshootDockerPermissionTitle = \"Docker Permission Denied\"\n\tTroubleshootDockerPermissionDesc = \"Your user account lacks permission to access Docker. This is common on Linux systems.\"\n\tTroubleshootDockerPermissionFix = `To fix:\n1. Add user to docker group: sudo usermod -aG docker $USER\n2. Log out and back in for changes to take effect\n3. Or run with sudo (not recommended for production)\n4. Verify: docker ps (should work without sudo)`\n\n\t// Generic Docker API issues\n\tTroubleshootDockerAPITitle = \"Docker API Connection Failed\"\n\tTroubleshootDockerAPIDesc = \"Cannot establish connection to Docker API. This may be due to configuration or network issues.\"\n\tTroubleshootDockerAPIFix = `To fix:\n1. Check DOCKER_HOST environment variable\n2. Verify Docker is running: docker version\n3. For remote Docker: ensure network connectivity\n4. Check firewall settings if using TCP connection\n5. Try: export DOCKER_HOST=unix:///var/run/docker.sock`\n\n\t// Docker version issues\n\tTroubleshootDockerVersionTitle = \"Docker Version Too Old\"\n\tTroubleshootDockerVersionDesc = \"Your Docker version is incompatible. PentAGI requires Docker 20.0.0 or newer.\"\n\tTroubleshootDockerVersionFix = `To fix:\n1. Update Docker to version 20.0.0 or newer\n2. Visit https://docs.docker.com/engine/install/\n\nCurrent version: %s\nRequired: 20.0.0+`\n\n\t// Docker Compose issues\n\tTroubleshootComposeTitle = \"Docker Compose Not Found\"\n\tTroubleshootComposeDesc = \"Docker Compose is required but not installed or not in PATH.\"\n\tTroubleshootComposeFix = `To fix:\n1. Install Docker Desktop (includes Compose) or\n2. Install standalone: https://docs.docker.com/compose/install/\n\nVerify installation: docker compose version`\n\n\t// Docker Compose version issues\n\tTroubleshootComposeVersionTitle = \"Docker Compose Version Too Old\"\n\tTroubleshootComposeVersionDesc = \"Your Docker Compose version is incompatible. PentAGI requires Docker Compose 1.25.0 or newer.\"\n\tTroubleshootComposeVersionFix = `Current version: %s\nRequired: 1.25.0+\n\nTo fix:\n1. Update Docker Desktop to latest version\n2. Or install newer Docker Compose:\n https://docs.docker.com/compose/install/`\n\n\t// Worker environment issues\n\tTroubleshootWorkerTitle = \"Worker Docker Environment Not Accessible\"\n\tTroubleshootWorkerDesc = \"Cannot connect to the Docker environment for worker containers. This may be a remote or local Docker setup issue.\"\n\tTroubleshootWorkerFix = `To fix:\n1. For remote Docker, set env vars before installer:\n export DOCKER_HOST=tcp://remote:2376\n export DOCKER_CERT_PATH=/path/to/certs\n export DOCKER_TLS_VERIFY=1\n2. Verify connection: docker -H $DOCKER_HOST ps\n3. For local Docker, leave these vars unset\n4. Check firewall allows Docker port (2375/2376)\n5. Ensure certificates are valid if using TLS`\n\n\t// CPU issues\n\tTroubleshootCPUTitle = \"Insufficient CPU Cores\"\n\tTroubleshootCPUDesc = \"PentAGI requires at least 2 CPU cores for proper operation.\"\n\tTroubleshootCPUFix = `Your system has %d CPU core(s), but 2+ are required.\n\nFor virtual machines:\n1. Increase CPU allocation in VM settings\n2. Ensure host has sufficient resources\n\nDocker Desktop users:\nSettings → Resources → CPUs: Set to 2 or more`\n\n\t// Memory issues\n\tTroubleshootMemoryTitle = \"Insufficient Memory\"\n\tTroubleshootMemoryDesc = \"Not enough free memory for selected components.\"\n\tTroubleshootMemoryFix = `Memory requirements:\n• Base system: 0.5 GB\n• PentAGI core: +0.5 GB\n• Langfuse (if enabled): +1.5 GB\n• Observability (if enabled): +1.5 GB\n\nTotal needed: %.1f GB\nAvailable: %.1f GB\n\nTo fix:\n1. Close unnecessary applications\n2. Increase Docker memory limit\n3. Disable optional components (Langfuse/Observability)`\n\n\t// Disk space issues\n\tTroubleshootDiskTitle = \"Insufficient Disk Space\"\n\tTroubleshootDiskDesc = \"Not enough free disk space for installation and operation.\"\n\tTroubleshootDiskFix = `Disk requirements:\n• Base installation: 5 GB minimum\n• With components: 10 GB + 2 GB per component\n• Worker images: 25 GB (includes 6GB+ Kali image)\n\nRequired: %.1f GB\nAvailable: %.1f GB\n\nTo fix:\n1. Free up disk space\n2. Use external storage for Docker\n3. Prune unused Docker resources:\n docker system prune -a`\n\n\t// Network issues\n\tTroubleshootNetworkTitle = \"Network Connectivity Failed\"\n\tTroubleshootNetworkDesc = \"Cannot reach required external services. This prevents downloading Docker images and updates.\"\n\tTroubleshootNetworkFix = `Failed checks:\n%s\n\nTo fix:\n1. Verify internet connection: ping docker.io\n2. Check DNS resolution: nslookup docker.io\n3. If behind proxy, set before running installer:\n export HTTP_PROXY=http://proxy:port\n export HTTPS_PROXY=http://proxy:port\n4. For persistent proxy, add to .env:\n PROXY_URL=http://proxy:port\n5. Check firewall allows outbound HTTPS (port 443)\n6. Try alternative DNS servers if DNS fails`\n\n\t// Generic hint at the bottom\n\tTroubleshootFixHint = \"\\nResolve the issues above and run the installer again.\"\n\n\t// Network failure messages (used in checker/helpers.go)\n\tNetworkFailureDNS = \"• DNS resolution failed for docker.io\"\n\tNetworkFailureHTTPS = \"• Cannot reach external services via HTTPS\"\n\tNetworkFailureDockerPull = \"• Cannot pull Docker images from registry\"\n)\n\n// System Checks constants\nconst (\n\tChecksTitle = \"System Checks\"\n\tChecksWarningFailed = \"⚠ Some checks failed\"\n\tCheckEnvironmentFile = \"Environment file\"\n\tCheckWritePermissions = \"Write permissions\"\n\tCheckDockerAPI = \"Docker API\"\n\tCheckDockerVersion = \"Docker version\"\n\tCheckDockerCompose = \"Docker Compose\"\n\tCheckDockerComposeVersion = \"Docker Compose version\"\n\tCheckWorkerEnvironment = \"Worker environment\"\n\tCheckSystemResources = \"System resources\"\n\tCheckNetworkConnectivity = \"Network connectivity\"\n)\n\n// EULA Screen constants\nconst (\n\t// Form interface implementation\n\tEULAFormDescription = \"Legal terms and conditions for PentAGI usage\"\n\tEULAFormName = \"EULA\"\n\tEULAFormOverview = `Review and accept the End User License Agreement to proceed with PentAGI installation.\n\nThe EULA contains:\n• Software license terms and usage rights\n• Limitation of liability and warranties\n• Data collection and privacy policies\n• Compliance requirements and restrictions\n• Support and maintenance terms\n\nYou must scroll through the entire document and accept the terms to continue with the installation process.\n\nUse arrow keys, page up/down, or home/end keys to navigate through the document.`\n\n\t// Error and status messages\n\tEULAErrorLoadingTitle = \"# Error Loading EULA\\n\\nFailed to load EULA: %v\"\n\tEULAContentFallback = \"# EULA Content\\n\\n%s\\n\\n---\\n\\n*Note: Markdown rendering failed: %v*\"\n\tEULAConfigurationRead = \"✓ EULA reviewed\"\n\tEULAConfigurationAccepted = \"✓ EULA accepted\"\n\tEULAConfigurationPending = \"⚠ EULA not reviewed\"\n\tEULALoading = \"Loading EULA...\"\n\tEULAProgress = \"Progress: %d%%\"\n\tEULAProgressComplete = \" • Complete\"\n)\n\n// Main Menu Screen constants\nconst (\n\tMainMenuTitle = \"PentAGI Configuration\"\n\tMainMenuDescription = \"Configure all PentAGI components and settings\"\n\tMainMenuName = \"Main Menu\"\n\tMainMenuOverview = `Welcome to PentAGI Configuration Center.\n\nConfigure essential components:\n• LLM Providers - AI language models for autonomous testing\n• Monitoring - Observability and analytics platforms\n• Tools - Additional capabilities for enhanced testing\n• System Settings - Environment and deployment options\n\nNavigate through each section to complete your PentAGI setup.`\n\n\tMenuTitle = \"Configuration Menu\"\n\tMenuSystemStatus = \"System Status\"\n)\n\n// Main Menu Status Labels (not used)\nconst (\n\tMainMenuStatusPentagiRunning = \"PentAGI is already running\"\n\tMainMenuStatusPentagiNotRunning = \"Ready to start PentAGI services\"\n\tMainMenuStatusUpToDate = \"PentAGI is up to date\"\n\tMainMenuStatusUpdatesAvailable = \"Updates are available\"\n\tMainMenuStatusReadyToStart = \"Ready to start\"\n\tMainMenuStatusAllServicesRunning = \"All services are running\"\n\tMainMenuStatusNoUpdatesAvailable = \"No updates available\"\n)\n\n// LLM Providers Screen constants\nconst (\n\tLLMProvidersTitle = \"LLM Providers Configuration\"\n\tLLMProvidersDescription = \"Configure Large Language Model providers for AI agents\"\n\tLLMProvidersName = \"LLM Providers\"\n\tLLMProvidersOverview = `PentAGI uses specialized AI agents (researcher, developer, executor, pentester) that require different LLM capabilities for optimal penetration testing results.\n\nWhy multiple providers matter:\n• Agent Specialization: Different agents benefit from models optimized for reasoning, coding, or analysis\n• Cost Efficiency: Mix expensive reasoning models (o3, grok-4, claude-sonnet-4, gemini-2.5-pro) for complex tasks with cheaper models for simple operations\n• Performance Optimization: Each provider excels in different areas - OpenAI for medium tasks, Anthropic for complex tasks, Gemini for saving costs\n\nProvider Selection Guide:\n• Cloud Production: OpenAI + Anthropic + Gemini for industry-leading performance and reliability\n• Enterprise/Compliance: AWS Bedrock for SOC2, HIPAA, and access to multiple model families\n• Privacy/On-premises: Ollama or vLLM with Llama 3.1, Qwen3, or other open models for complete data control\n\nReady-to-use configurations for OpenRouter, DeepInfra, vLLM, Ollama, and other providers are available in the /opt/pentagi/conf/ directory inside the container`\n)\n\n// LLM Provider titles and descriptions\nconst (\n\tLLMProviderOpenAI = \"OpenAI\"\n\tLLMProviderAnthropic = \"Anthropic\"\n\tLLMProviderGemini = \"Google Gemini\"\n\tLLMProviderBedrock = \"AWS Bedrock\"\n\tLLMProviderOllama = \"Ollama\"\n\tLLMProviderDeepSeek = \"DeepSeek\"\n\tLLMProviderGLM = \"GLM Zhipu AI\"\n\tLLMProviderKimi = \"Kimi Moonshot AI\"\n\tLLMProviderQwen = \"Qwen Alibaba Cloud\"\n\tLLMProviderCustom = \"Custom\"\n\tLLMProviderOpenAIDesc = \"Industry-leading GPT models with excellent general performance\"\n\tLLMProviderAnthropicDesc = \"Claude models with superior reasoning and safety features\"\n\tLLMProviderGeminiDesc = \"Google's advanced multimodal models with broad knowledge\"\n\tLLMProviderBedrockDesc = \"Enterprise AWS access to multiple foundation model providers\"\n\tLLMProviderOllamaDesc = \"Local and cloud open-source models for privacy and flexibility\"\n\tLLMProviderDeepSeekDesc = \"Advanced Chinese AI models with strong reasoning and multilingual capabilities\"\n\tLLMProviderGLMDesc = \"Zhipu AI's GLM models for Chinese and English tasks\"\n\tLLMProviderKimiDesc = \"Moonshot AI's long-context models for document analysis\"\n\tLLMProviderQwenDesc = \"Alibaba Cloud's Qwen models for multilingual tasks\"\n\tLLMProviderCustomDesc = \"Custom OpenAI-compatible endpoint for maximum flexibility\"\n)\n\n// Provider-specific help text\nconst (\n\tLLMFormOpenAIHelp = `OpenAI delivers industry-leading models with cutting-edge reasoning capabilities perfect for sophisticated penetration testing.\n\nDefault PentAGI Models:\n• o1, o4-mini: Advanced reasoning models for complex vulnerability analysis and strategic planning\n• GPT-4.1, GPT-4.1-mini: Flagship models optimized for exploit development and code generation\n• Automatic model selection based on agent type and task complexity\n\nKey Advantages:\n• Most advanced reasoning capabilities with step-by-step analysis (o-series models)\n• Excellent coding abilities for custom exploit development and payload generation\n• Reliable performance with consistent uptime and extensive API documentation\n• Proven track record in security research and penetration testing scenarios\n\nBest for: Production environments requiring cutting-edge AI capabilities, teams prioritizing performance over cost\nCost: Premium pricing, but optimized configurations balance cost with quality\n\nSetup: Get your API key from https://platform.openai.com/api-keys`\n\n\tLLMFormAnthropicHelp = `Anthropic Claude models excel in safety-conscious penetration testing with superior reasoning and analytical capabilities.\n\nDefault PentAGI Models:\n• Claude Sonnet-4: Premium reasoning model for complex security analysis and strategic vulnerability assessment\n• Claude 3.5 Haiku: High-speed model optimized for rapid information gathering and simple parsing tasks\n• Balanced cost-performance ratio across all security testing scenarios\n\nKey Advantages:\n• Exceptional safety and ethics focus - reduces harmful output while maintaining security testing effectiveness\n• Superior reasoning for methodical vulnerability analysis and systematic penetration testing approaches\n• Large context windows ideal for analyzing extensive codebases and configuration files\n• Excellent at understanding complex security contexts and regulatory compliance requirements\n\nBest for: Security teams prioritizing responsible testing practices, compliance-focused environments, detailed analysis\nCost: Mid-range pricing with excellent value for reasoning-heavy security workflows\n\nSetup: Get your API key from https://console.anthropic.com/`\n\n\tLLMFormGeminiHelp = `Google Gemini combines multimodal capabilities with advanced reasoning, perfect for comprehensive security assessments.\n\nDefault PentAGI Models:\n• Gemini 2.5 Pro: Advanced reasoning model for deep vulnerability analysis and complex exploit development\n• Gemini 2.5 Flash: High-performance model balancing speed and intelligence for most security testing tasks\n• Gemini 2.0 Flash Lite: Cost-effective model for rapid scanning and information gathering operations\n• Reasoning capabilities with step-by-step analysis for thorough penetration testing\n\nKey Advantages:\n• Multimodal support enables analysis of screenshots, network diagrams, and security documentation\n• Competitive pricing with generous rate limits for development and testing environments\n• Large context windows (up to 2M tokens) for analyzing massive codebases and system configurations\n• Strong performance in code analysis and vulnerability identification across multiple programming languages\n\nBest for: Budget-conscious teams, development environments, scenarios requiring image/document analysis\nCost: Most cost-effective option among major cloud providers with excellent performance/price ratio\n\nSetup: Get your API key from https://aistudio.google.com/app/apikey`\n\n\tLLMFormBedrockHelp = `AWS Bedrock provides enterprise-grade access to 20+ foundation models with multiple authentication methods and enhanced security.\n\nDefault PentAGI Models:\n• Claude Sonnet-4.5 (via Bedrock): Premium reasoning model with AWS enterprise security and extended thinking capabilities\n• OpenAI GPT OSS 120B: Strong reasoning model for scientific analysis and complex security tasks\n• Claude Haiku-4.5, DeepSeek V3.2, Qwen3-32B: Efficient models for specific agent roles and cost optimization\n• Access to Amazon Nova (multimodal), Mistral, Moonshot, and more through single unified interface\n\nAuthentication Methods (priority order):\n1. Default AWS Auth (BEDROCK_DEFAULT_AUTH=true): Use AWS SDK credential chain - recommended for EC2/ECS/Lambda\n2. Bearer Token (BEDROCK_BEARER_TOKEN): Token-based authentication for custom auth scenarios\n3. Static Credentials (ACCESS_KEY + SECRET_KEY): Traditional IAM credentials for development and testing\n\nKey Advantages:\n• Enterprise compliance: SOC2, HIPAA, FedRAMP certifications with data residency and governance controls\n• Multi-provider access: 20+ models from Anthropic, Amazon, OpenAI, Qwen, DeepSeek, Cohere, Mistral, Moonshot\n• Flexible authentication: Three methods to suit different deployment scenarios and security requirements\n• Enhanced security: VPC integration, CloudTrail logging, IAM controls, private endpoints for complete isolation\n• Regional deployment: Deploy in preferred AWS regions for latency optimization and data sovereignty\n\nBest for: Enterprise environments, regulated industries, teams requiring compliance controls and flexible authentication\nCost: Competitive pricing with provisioned throughput options, but new accounts have restrictive rate limits (2-20 req/min)\nImportant: Request quota increases through AWS Service Quotas console for production penetration testing workflows\n\nSetup: Choose authentication method and configure credentials. Verify rate limits at https://docs.aws.amazon.com/bedrock/`\n\n\tLLMFormOllamaHelp = `Ollama supports two deployment scenarios for complete flexibility.\n\nScenario 1: Local Ollama Server (Self-Hosted)\n• Run Ollama on your own hardware (8GB+ RAM recommended, GPU optional but beneficial)\n• Complete data privacy - all processing happens locally\n• Zero ongoing costs - only infrastructure\n• No API key needed - authentication handled by network access\n• Setup: Install from https://ollama.ai/ and configure OLLAMA_SERVER_URL=http://ollama-server:11434\n\nScenario 2: Ollama Cloud (Managed Service)\n• Cloud-hosted models without local infrastructure requirements\n• No hardware needed - models run on Ollama's infrastructure\n• Pay-per-use pricing with free tier available\n• API key required - generate at https://ollama.com/settings/keys\n• Setup: Register at https://ollama.com, configure OLLAMA_SERVER_URL=https://ollama.com + OLLAMA_SERVER_API_KEY=your_key\n\nDefault PentAGI Models:\n• Llama 3.1:8b, Qwen3:32b, and other open models\n• Customizable - switch between 100+ available models\n• Model auto-download and loading options for convenience\n\nKey Advantages:\n• Dual deployment options: Choose between privacy (local) and convenience (cloud)\n• Cost flexibility: Zero ongoing costs for local, pay-per-use for cloud\n• Extensive model library: Access to latest open-source models (Llama, Qwen, Mistral, Gemma, and more)\n• Air-gapped support: Local deployment works in isolated networks\n\nBest for: Privacy-focused teams (local), budget-conscious deployments (cloud), organizations with data sovereignty requirements\nSetup options: Local installation from https://10.10.10.10:11434 or cloud registration at https://ollama.com`\n\n\tLLMFormDeepSeekHelp = `DeepSeek provides advanced AI models with strong reasoning capabilities and multilingual support.\n\nDefault PentAGI Models:\n• DeepSeek-Chat: Flagship model for general-purpose tasks with strong coding and reasoning capabilities\n• DeepSeek-Reasoner: Advanced reasoning model for complex security analysis\n• Cost-effective pricing with competitive performance compared to leading models\n\nKey Advantages:\n• Strong coding and reasoning capabilities for security analysis and exploit development\n• Multilingual support (Chinese and English) for international penetration testing scenarios\n• Competitive pricing with excellent performance-to-cost ratio\n• OpenAI-compatible API for seamless integration\n\nLiteLLM Integration:\n• Set Provider Name to 'deepseek' when using LiteLLM proxy\n• Enables model prefix (e.g., deepseek/deepseek-chat) without modifying config.yml\n• Optional for direct DeepSeek API usage\n\nBest for: Teams requiring multilingual support, cost-conscious deployments, Chinese language security testing\nCost: Highly competitive pricing with strong performance characteristics\n\nSetup: Get your API key from https://platform.deepseek.com/`\n\n\tLLMFormGLMHelp = `GLM from Zhipu AI provides advanced language models with strong NLP and reasoning capabilities developed by Tsinghua University.\n\nDefault PentAGI Models:\n• GLM-4-Air: High performance general dialogue model optimized for regular tasks and tool calling\n• GLM-4-Plus: Flagship model with strong reasoning and code generation capabilities\n• GLM-Z1-Plus: Advanced reasoning model with deep analysis capabilities for security research\n\nKey Advantages:\n• Exceptional Chinese and English NLP capabilities\n• Strong performance in multilingual security testing and analysis scenarios\n• GLM-4 and GLM-Z1 model families with enhanced reasoning and coding\n• OpenAI-compatible API for easy integration\n\nAlternative API Endpoints:\n• International: https://api.z.ai/api/paas/v4 (default)\n• China: https://open.bigmodel.cn/api/paas/v4\n• Coding-specific: https://api.z.ai/api/coding/paas/v4\n\nLiteLLM Integration:\n• Set Provider Name to 'zai' when using LiteLLM proxy\n• Enables model prefix (e.g., zai/glm-4) without modifying config.yml\n• Optional for direct GLM API usage\n\nBest for: Chinese and English multilingual penetration testing, teams operating in Asian markets\nCost: Competitive pricing with good performance for multilingual tasks\n\nSetup: Get your API key from https://open.bigmodel.cn/`\n\n\tLLMFormKimiHelp = `Kimi from Moonshot AI provides ultra-long context models perfect for analyzing extensive codebases and documentation.\n\nDefault PentAGI Models:\n• Moonshot-v1-8k: Long-context model supporting up to 8K tokens for general dialogue\n• Kimi-k2.5: Advanced model with strong reasoning and document understanding\n• Optimized for processing large volumes of text and code\n\nKey Advantages:\n• Ultra-long context windows (up to 1M tokens) for comprehensive codebase analysis\n• Strong Chinese and English language support for multilingual penetration testing\n• Cost-effective for document-heavy security assessments and threat intelligence analysis\n• Excellent at understanding complex system architectures and long-form technical documentation\n\nAlternative API Endpoints:\n• International: https://api.moonshot.ai/v1 (default)\n• China: https://api.moonshot.cn/v1\n\nLiteLLM Integration:\n• Set Provider Name to 'moonshot' when using LiteLLM proxy\n• Enables model prefix (e.g., moonshot/kimi-k2.5) without modifying config.yml\n• Optional for direct Kimi API usage\n\nBest for: Large codebase analysis, document-heavy assessments, teams needing extended context for security research\nCost: Competitive pricing with excellent value for long-context use cases\n\nSetup: Get your API key from https://platform.moonshot.ai/`\n\n\tLLMFormQwenHelp = `Qwen from Alibaba Cloud Model Studio (DashScope) provides powerful multilingual models with multimodal capabilities.\n\nDefault PentAGI Models:\n• Qwen-Turbo: Fastest lightweight model for high-frequency tasks and real-time response scenarios\n• Qwen-Plus: Balanced performance model for general dialogue, code generation, and tool calling\n• Qwen-Max: Flagship reasoning model with strong instruction following and complex task handling\n• QwQ-Plus: Deep reasoning model with extended chain-of-thought for complex logic analysis\n\nKey Advantages:\n• Strong multilingual support (Chinese, English, and multiple other languages)\n• Multimodal capabilities with Qwen-VL for visual security analysis\n• Alibaba Cloud integration for enterprise deployments\n• DashScope ecosystem with additional AI services and tools\n• Qwen2.5, Qwen3, and QwQ model families with various sizes and specializations\n\nAlternative API Endpoints:\n• US: https://dashscope-us.aliyuncs.com/compatible-mode/v1 (default)\n• Singapore: https://dashscope-intl.aliyuncs.com/compatible-mode/v1\n• China: https://dashscope.aliyuncs.com/compatible-mode/v1\n\nLiteLLM Integration:\n• Set Provider Name to 'dashscope' when using LiteLLM proxy\n• Enables model prefix (e.g., dashscope/qwen-plus) without modifying config.yml\n• Optional for direct Qwen API usage\n\nBest for: Teams operating in Asian markets, multilingual security testing, visual analysis with Qwen-VL, Alibaba Cloud ecosystem integration\nCost: Competitive pricing with flexible tiers for different use cases\n\nSetup: Get your API key from https://dashscope.console.aliyun.com/`\n\n\tLLMFormCustomHelp = `Configure any OpenAI-compatible API endpoint for maximum flexibility and integration with existing infrastructure.\n\nReady-to-use Configurations:\n• vLLM deployments: High-throughput on-premises inference with optimal GPU utilization\n• OpenRouter: Access 200+ models from multiple providers through single API with competitive pricing\n• DeepInfra: Serverless inference for popular open models with pay-per-use pricing\n• Together AI, Groq, Fireworks: Alternative cloud providers with specialized performance optimizations\n• LiteLLM Proxy: Universal gateway to 100+ providers with load balancing and unified interface (use LLM_SERVER_PROVIDER for model prefixing)\n• Some reasoning models and LLM providers may require preserving reasoning content while using tool calls (LLM_SERVER_PRESERVE_REASONING=true)\n\nPopular On-Premises Options:\n• vLLM: Production-grade serving for Qwen, Llama, Mistral models with batching and GPU optimization\n• LocalAI: OpenAI-compatible API wrapper for various local models and embedding services\n• Text Generation WebUI: Community-favorite interface with extensive model support and fine-tuning capabilities\n• Hugging Face TGI: Enterprise text generation inference with auto-scaling and monitoring\n\nKey Advantages:\n• Unlimited flexibility: Use any OpenAI-compatible endpoint or service\n• Cost optimization: Choose providers with competitive pricing or deploy models on your own infrastructure\n• Vendor independence: Avoid lock-in with ability to switch between providers and models seamlessly\n• Custom fine-tuning: Deploy specialized models trained on your security testing scenarios\n\nBest for: Teams with specific model requirements, cost optimization needs, or existing LLM infrastructure\nLiteLLM Integration: Set LLM_SERVER_PROVIDER to match your provider name (e.g., \"openrouter\", \"moonshot\") to use the same config files with both direct API access and LiteLLM proxy\nExamples available: Pre-configured setups for major providers in /opt/pentagi/conf/ directory inside the container`\n)\n\n// LLM Provider Form field labels and descriptions\nconst (\n\tLLMFormFieldBaseURL = \"Base URL\"\n\tLLMFormFieldAPIKey = \"API Key\"\n\tLLMFormFieldDefaultAuth = \"Use Default AWS Auth\"\n\tLLMFormFieldBearerToken = \"Bearer Token\"\n\tLLMFormFieldAccessKey = \"Access Key ID\"\n\tLLMFormFieldSecretKey = \"Secret Access Key\"\n\tLLMFormFieldSessionToken = \"Session Token\"\n\tLLMFormFieldRegion = \"Region\"\n\tLLMFormFieldModel = \"Model\"\n\tLLMFormFieldConfigPath = \"Config Path\"\n\tLLMFormFieldLegacyReasoning = \"Legacy Reasoning\"\n\tLLMFormFieldPreserveReasoning = \"Preserve Reasoning\"\n\tLLMFormFieldProviderName = \"Provider Name\"\n\tLLMFormFieldPullTimeout = \"Model Pull Timeout\"\n\tLLMFormFieldPullEnabled = \"Auto-pull Models\"\n\tLLMFormFieldLoadModelsEnabled = \"Load Models from Server\"\n\tLLMFormBaseURLDesc = \"API endpoint URL for the provider\"\n\tLLMFormAPIKeyDesc = \"Your API key for authentication\"\n\tLLMFormDefaultAuthDesc = \"Use AWS SDK default credential chain (environment, EC2 role, ~/.aws/credentials) - highest priority\"\n\tLLMFormBearerTokenDesc = \"Bearer token for authentication - takes priority over static credentials\"\n\tLLMFormAccessKeyDesc = \"AWS Access Key ID for static credentials authentication\"\n\tLLMFormSecretKeyDesc = \"AWS Secret Access Key for static credentials authentication\"\n\tLLMFormSessionTokenDesc = \"AWS Session Token for temporary credentials (optional, used with static credentials)\"\n\tLLMFormRegionDesc = \"AWS region for Bedrock service\"\n\tLLMFormModelDesc = \"Default model to use for this provider\"\n\tLLMFormConfigPathDesc = \"Path to configuration file (optional)\"\n\tLLMFormLegacyReasoningDesc = \"Enable legacy reasoning mode (true/false)\"\n\tLLMFormPreserveReasoningDesc = \"Preserve reasoning content in multi-turn conversations (required by some providers)\"\n\tLLMFormProviderNameDesc = \"Provider name prefix for model names (useful for LiteLLM proxy)\"\n\tLLMFormPullTimeoutDesc = \"Timeout in seconds for downloading models (default: 600)\"\n\tLLMFormPullEnabledDesc = \"Automatically download required models on startup\"\n\tLLMFormLoadModelsEnabledDesc = \"Load available models list from Ollama server\"\n\tLLMFormOllamaAPIKeyDesc = \"Ollama Cloud API key (optional, leave empty for local Ollama server)\"\n)\n\n// LLM Provider Form status messages\nconst (\n\tLLMProviderFormTitle = \"LLM Provider %s Configuration\"\n\tLLMProviderFormDescription = \"Configure your Large Language Model provider settings\"\n\tLLMProviderFormName = \"LLM Provider %s\"\n\tLLMProviderFormOverview = `Agent Role Assignment:\n• Primary Agent & Pentester: Use reasoning models (o3, grok-4, claude-sonnet-4, gemini-2.5-pro) for complex vulnerability analysis\n• Assistant & Adviser: Advanced models (o4-mini, claude-sonnet-4) for strategic planning and recommendations\n• Coder & Installer: Precision models (gpt-4.1, claude-sonnet-4) for exploit development and system configuration\n• Searcher & Enricher: Fast models (gpt-4.1-mini, claude-3.5-haiku, gemini-2.0-flash-lite) for information gathering\n• Simple tasks: Lightweight models for JSON parsing and basic operations\n\nPerformance Considerations:\n• Reasoning models provide step-by-step analysis but are slower and more expensive\n• Standard models offer faster responses suitable for high-frequency agent interactions\n• Each agent type uses provider-specific model configurations optimized for security testing workflows\n\nYour configuration will determine which models each agent uses for different penetration testing scenarios.`\n)\n\n// Monitoring Screen\nconst (\n\tMonitoringTitle = \"Monitoring Configuration\"\n\tMonitoringDescription = \"Configure monitoring and observability platforms for comprehensive system insights\"\n\tMonitoringName = \"Monitoring\"\n\tMonitoringOverview = `Comprehensive monitoring and observability for production-ready deployments.\n\nWhy monitoring matters:\n• Track performance bottlenecks: Identify slow LLM calls, database queries, and system resources\n• Debug issues faster: Detailed traces help diagnose problems across distributed components\n• Optimize costs: Monitor token usage patterns and optimize expensive LLM interactions\n• Production readiness: Essential for reliable operation in critical environments\n\nPlatform Options:\nLangfuse: Specialized LLM observability with conversation tracking, prompt engineering insights, and cost analytics\nObservability: Full-stack monitoring with metrics, traces, logs, and alerting for infrastructure and application health\n\nQuick Setup:\n• Development: Enable Langfuse for LLM insights only\n• Production: Enable both platforms for comprehensive monitoring\n• Cost-conscious: Use embedded modes to avoid external service fees`\n)\n\n// Langfuse Integration constants\nconst (\n\tMonitoringLangfuseFormTitle = \"Langfuse Configuration\"\n\tMonitoringLangfuseFormDescription = \"Configuration of Langfuse integration for LLM monitoring\"\n\tMonitoringLangfuseFormName = \"Langfuse\"\n\tMonitoringLangfuseFormOverview = `Langfuse provides:\n• Complete conversation tracking\n• Model performance metrics\n• Cost monitoring and optimization\n• User behavior analytics\n• Debug traces for AI interactions\n\nChoose between embedded instance or external connection.`\n\n\t// Deployment types\n\tMonitoringLangfuseEmbedded = \"Embedded Server\"\n\tMonitoringLangfuseExternal = \"External Server\"\n\tMonitoringLangfuseDisabled = \"Disabled\"\n\n\t// Form fields\n\tMonitoringLangfuseDeploymentType = \"Deployment Type\"\n\tMonitoringLangfuseDeploymentTypeDesc = \"Select the deployment type for Langfuse\"\n\tMonitoringLangfuseBaseURL = \"Server URL\"\n\tMonitoringLangfuseBaseURLDesc = \"Address of the Langfuse server (e.g., https://cloud.langfuse.com)\"\n\tMonitoringLangfuseProjectID = \"Project ID\"\n\tMonitoringLangfuseProjectIDDesc = \"Project identifier in Langfuse\"\n\tMonitoringLangfusePublicKey = \"Public Key\"\n\tMonitoringLangfusePublicKeyDesc = \"Public API key for project access\"\n\tMonitoringLangfuseSecretKey = \"Secret Key\"\n\tMonitoringLangfuseSecretKeyDesc = \"Secret API key for project access\"\n\tMonitoringLangfuseListenIP = \"Listen IP\"\n\tMonitoringLangfuseListenIPDesc = \"Bind address used by Docker port mapping (e.g., 0.0.0.0 to expose on all interfaces)\"\n\tMonitoringLangfuseListenPort = \"Listen Port\"\n\tMonitoringLangfuseListenPortDesc = \"External TCP port exposed by Docker for Langfuse web UI\"\n\n\t// Admin settings for embedded\n\tMonitoringLangfuseAdminEmail = \"Admin Email\"\n\tMonitoringLangfuseAdminEmailDesc = \"Email for accessing the Langfuse admin panel\"\n\tMonitoringLangfuseAdminPassword = \"Admin Password\"\n\tMonitoringLangfuseAdminPasswordDesc = \"Password for accessing the Langfuse admin panel\"\n\tMonitoringLangfuseAdminName = \"Admin Username\"\n\tMonitoringLangfuseAdminNameDesc = \"Administrator username in Langfuse\"\n\tMonitoringLangfuseLicenseKey = \"Enterprise License Key\"\n\tMonitoringLangfuseLicenseKeyDesc = \"Langfuse Enterprise license key (optional)\"\n\n\t// Help text\n\tMonitoringLangfuseModeGuide = \"Choose deployment: Embedded (local control), External (cloud/existing), Disabled (no analytics)\"\n\tMonitoringLangfuseEmbeddedHelp = `Embedded deploys complete Langfuse stack:\n• PostgreSQL + ClickHouse databases\n• MinIO S3 storage + Redis cache\n• Full LLM conversation tracking\n• Cost analysis and performance metrics\n• Private data stays on your server\n\nResource requirements:\n• ~2GB RAM, 5GB disk space minimum\n• Additional storage for conversation logs\n• Automatic setup and maintenance\n\nBest for: Teams wanting data privacy, custom configurations, or no external dependencies. All analytics data stored locally with full administrative control.\n\nDefault admin access:\n• Web UI: http://localhost:4000\n• Login: admin@pentagi.com\n• Password: password (change required)`\n\tMonitoringLangfuseExternalHelp = `External connects to cloud.langfuse.com or your existing Langfuse server:\n\n• No local infrastructure needed\n• Managed updates and maintenance\n• Shared analytics across teams\n• Enterprise features available\n• Data stored on external provider\n\nSetup requirements:\n• Langfuse account and API keys\n• Internet connectivity required\n• Project ID and authentication keys\n\nBest for: Teams using cloud services, wanting managed infrastructure, or integrating with existing Langfuse deployments across organizations.`\n\tMonitoringLangfuseDisabledHelp = `Langfuse is disabled. Without LLM observability you will not have:\n\n• Conversation history tracking\n• Token usage and cost analysis\n• Model performance metrics\n• Debug traces for AI interactions\n• User behavior analytics\n• Prompt engineering insights\n\nConsider enabling for production use\nto monitor AI agent performance and\noptimize costs effectively.`\n)\n\n// Graphiti Integration constants\nconst (\n\tMonitoringGraphitiFormTitle = \"Graphiti Configuration (beta)\"\n\tMonitoringGraphitiFormDescription = \"Configuration of Graphiti knowledge graph integration\"\n\tMonitoringGraphitiFormName = \"Graphiti (beta)\"\n\tMonitoringGraphitiFormOverview = `⚠️ BETA FEATURE: This functionality is currently under active development. Please monitor updates for improvements and stability fixes.\n\nGraphiti provides temporal knowledge graph capabilities:\n• Entity and relationship extraction\n• Semantic memory for AI agents\n• Temporal context tracking\n• Knowledge reuse across flows\n\n⚠️ REQUIREMENT: Graphiti requires configured OpenAI provider (LLM Providers → OpenAI) for entity extraction.\n\nChoose between embedded instance or external connection.`\n\n\t// Deployment types\n\tMonitoringGraphitiEmbedded = \"Embedded Stack\"\n\tMonitoringGraphitiExternal = \"External Service\"\n\tMonitoringGraphitiDisabled = \"Disabled\"\n\n\t// Form fields\n\tMonitoringGraphitiDeploymentType = \"Deployment Type\"\n\tMonitoringGraphitiDeploymentTypeDesc = \"Select the deployment type for Graphiti\"\n\tMonitoringGraphitiURL = \"Graphiti Server URL\"\n\tMonitoringGraphitiURLDesc = \"Address of the Graphiti API server\"\n\tMonitoringGraphitiTimeout = \"Request Timeout\"\n\tMonitoringGraphitiTimeoutDesc = \"Timeout in seconds for Graphiti operations\"\n\tMonitoringGraphitiModelName = \"Extraction Model\"\n\tMonitoringGraphitiModelNameDesc = \"LLM model for entity extraction (uses OpenAI provider from LLM Providers configuration)\"\n\tMonitoringGraphitiNeo4jUser = \"Neo4j Username\"\n\tMonitoringGraphitiNeo4jUserDesc = \"Username for Neo4j database access\"\n\tMonitoringGraphitiNeo4jPassword = \"Neo4j Password\"\n\tMonitoringGraphitiNeo4jPasswordDesc = \"Password for Neo4j database access\"\n\tMonitoringGraphitiNeo4jDatabase = \"Neo4j Database\"\n\tMonitoringGraphitiNeo4jDatabaseDesc = \"Neo4j database name\"\n\n\t// Help text\n\tMonitoringGraphitiModeGuide = \"Choose deployment: Embedded (local Neo4j), External (existing Graphiti), Disabled (no knowledge graph)\"\n\tMonitoringGraphitiEmbeddedHelp = `⚠️ BETA: This feature is under active development. Monitor updates for improvements.\n\nEmbedded deploys complete Graphiti stack:\n• Neo4j graph database\n• Graphiti API service\n• Automatic entity extraction from agent interactions\n• Temporal relationship tracking\n• Private knowledge graph on your server\n\nPrerequisites:\n• OpenAI provider must be configured (LLM Providers → OpenAI)\n• OpenAI API key is used for entity extraction\n• Configured model will be used for knowledge graph operations\n\nResource requirements:\n• ~1.5GB RAM, 3GB disk space minimum\n• Neo4j UI: http://localhost:7474\n• Graphiti API: http://localhost:8000\n• Automatic setup and maintenance\n\nBest for: Teams wanting knowledge graph capabilities with full data control and privacy.`\n\tMonitoringGraphitiExternalHelp = `⚠️ BETA: This feature is under active development. Monitor updates for improvements.\n\nExternal connects to your existing Graphiti server:\n\n• No local infrastructure needed\n• Managed updates and maintenance\n• Shared knowledge graph across teams\n• Data stored on external provider\n\nSetup requirements:\n• Graphiti server URL and access\n• Network connectivity required\n• External server must be configured with OpenAI API key\n• Model and extraction settings configured on external server\n\nBest for: Teams using existing Graphiti deployments or cloud services.`\n\tMonitoringGraphitiDisabledHelp = `Graphiti is disabled. You will not have:\n\n• Temporal knowledge graph\n• Entity and relationship extraction\n• Semantic memory for AI agents\n• Knowledge reuse across flows\n• Advanced contextual search\n\nNote: Graphiti is currently in beta.\nConsider enabling for production use\nto build a knowledge base from\npenetration testing results.`\n)\n\n// Observability Integration constants\nconst (\n\tMonitoringObservabilityFormTitle = \"Observability Configuration\"\n\tMonitoringObservabilityFormDescription = \"Configuration of monitoring and observability stack\"\n\tMonitoringObservabilityFormName = \"Observability\"\n\tMonitoringObservabilityFormOverview = `Observability stack includes:\n• Grafana dashboards for visualization\n• VictoriaMetrics for time-series data\n• Jaeger for distributed tracing\n• Loki for log aggregation\n• OpenTelemetry for data collection\n\nMonitor PentAGI performance and system health.`\n\n\t// Deployment types\n\tMonitoringObservabilityEmbedded = \"Embedded Stack\"\n\tMonitoringObservabilityExternal = \"External Collector\"\n\tMonitoringObservabilityDisabled = \"Disabled\"\n\n\t// Form fields\n\tMonitoringObservabilityDeploymentType = \"Deployment Type\"\n\tMonitoringObservabilityDeploymentTypeDesc = \"Select the deployment type for monitoring\"\n\tMonitoringObservabilityOTelHost = \"OpenTelemetry Host\"\n\tMonitoringObservabilityOTelHostDesc = \"Address of the external OpenTelemetry collector\"\n\n\t// embedded listen fields\n\tMonitoringObservabilityGrafanaListenIP = \"Grafana Listen IP\"\n\tMonitoringObservabilityGrafanaListenIPDesc = \"Bind address used by Docker port mapping (e.g., 0.0.0.0 to expose on all interfaces)\"\n\tMonitoringObservabilityGrafanaListenPort = \"Grafana Listen Port\"\n\tMonitoringObservabilityGrafanaListenPortDesc = \"External TCP port exposed by Docker for Grafana web UI\"\n\tMonitoringObservabilityOTelGrpcListenIP = \"OTel gRPC Listen IP\"\n\tMonitoringObservabilityOTelGrpcListenIPDesc = \"Bind address used by Docker port mapping (e.g., 0.0.0.0 to expose on all interfaces)\"\n\tMonitoringObservabilityOTelGrpcListenPort = \"OTel gRPC Listen Port\"\n\tMonitoringObservabilityOTelGrpcListenPortDesc = \"External TCP port exposed by Docker for OTel gRPC receiver\"\n\tMonitoringObservabilityOTelHttpListenIP = \"OTel HTTP Listen IP\"\n\tMonitoringObservabilityOTelHttpListenIPDesc = \"Bind address used by Docker port mapping (e.g., 0.0.0.0 to expose on all interfaces)\"\n\tMonitoringObservabilityOTelHttpListenPort = \"OTel HTTP Listen Port\"\n\tMonitoringObservabilityOTelHttpListenPortDesc = \"External TCP port exposed by Docker for OTel HTTP receiver\"\n\n\t// Help text\n\tMonitoringObservabilityModeGuide = \"Choose monitoring: Embedded (full stack), External (existing infra), Disabled (no monitoring)\"\n\tMonitoringObservabilityEmbeddedHelp = `Embedded deploys complete monitoring:\n• Grafana dashboards and alerting\n• VictoriaMetrics time-series database\n• Jaeger distributed tracing UI\n• Loki log aggregation system\n• ClickHouse analytical database\n• Node Exporter + cAdvisor metrics\n• OpenTelemetry data collection\n\nAuto-instrumented components with\npre-built dashboards for system health,\nperformance analysis, and debugging.\n\nResource requirements:\n• ~1.5GB RAM, 3GB disk space minimum\n• Grafana UI: http://localhost:3000\n• Profiling: http://localhost:7777\n\nBest for: Complete system visibility,\ntroubleshooting, and performance tuning.`\n\tMonitoringObservabilityExternalHelp = `External sends telemetry to your existing monitoring infrastructure:\n\n• OTLP protocol over HTTP/2 (no TLS)\n• Your collector must support:\n - OTLP HTTP receiver (port 4318)\n - OTLP gRPC receiver (port 8148)\n - tls: insecure: true setting\n• Sends metrics, traces, and logs\n• Compatible with enterprise platforms:\n Datadog, New Relic, Splunk, etc.\n\nOTEL_HOST example:\nyour-collector:4318\n\nCollector config requirement:\ntls: insecure: true\n\nBest for: Organizations with existing\nmonitoring infrastructure or centralized\nobservability platforms.`\n\tMonitoringObservabilityDisabledHelp = `Observability is disabled. You will not have:\n\n• System performance monitoring\n• Distributed request tracing\n• Structured log aggregation\n• Resource usage analytics\n• Error tracking and alerting\n• Performance bottleneck analysis\n\nConsider enabling for production use\nto monitor system health, debug issues,\nand optimize performance effectively.`\n)\n\n// Summarizer Screen\nconst (\n\tSummarizerTitle = \"Summarizer Configuration\"\n\tSummarizerDescription = \"Enable conversation summarization to reduce LLM costs and improve context management\"\n\tSummarizerName = \"Summarizer\"\n\tSummarizerOverview = `Optimize context usage, reduce LLM costs, and match your model capabilities.\n\nWhen to adjust summarization:\n• High token costs: Reduce context size (4K-12K vs 22K+ tokens)\n• \"Context too long\" errors: Configure for your model's limits\n• Poor conversation flow: Increase context retention for quality\n• Different model types: Short-context vs long-context model tuning\n\nGeneral Summarization: Maximum cost control and precision tuning for research/analysis tasks\nAssistant Summarization: Optimal conversation quality with intelligent context management for interactive sessions\n\nQuick wins:\n• Cost reduction: Use General, reduce Recent Sections to 1-2\n• Context errors: Match limits to your model (8K/32K/128K)\n• Quality priority: Use Assistant with increased limits`\n\n\tSummarizerTypeGeneralName = \"General Summarization\"\n\tSummarizerTypeGeneralDesc = \"Global summarization settings for conversation context management\"\n\n\tSummarizerTypeGeneralInfo = `Choose this for maximum cost control and short-context model compatibility.\n\nPerfect when you need:\n• Aggressive cost reduction: Fine-tune every parameter for minimal token usage\n• Short-context models (8K-32K): Precise limits to avoid overflow errors\n• Research/analysis tasks: Controlled compression without losing key data\n• Custom QA handling: Full control over question-answer pair processing\n\nTypical results:\n• 40-70% cost reduction vs default settings\n• 4K-12K token contexts (vs 22K+ in Assistant mode)\n• Better performance on GPT-3.5, Claude Instant, smaller models\n• Precise control over conversation memory vs fresh context balance\n\nBest practices:\n• Start with 1-2 Recent Sections for maximum savings\n• Enable Size Management for automatic overflow protection\n• Disable QA compression only for critical reasoning tasks`\n\n\tSummarizerTypeAssistantName = \"Assistant Summarization\"\n\tSummarizerTypeAssistantDesc = \"Specialized summarization settings for AI assistant contexts\"\n\n\tSummarizerTypeAssistantInfo = `Choose this for optimal conversation quality and dialogue continuity.\n\nPerfect when you need:\n• Extended reasoning chains: Maintain context for complex multi-step thinking\n• High-quality conversations: Preserve dialogue flow and assistant personality\n• Long-context models (64K+): Leverage full model capabilities efficiently\n• Interactive sessions: Better memory of user preferences and conversation history\n\nTypical results:\n• 8K-40K token contexts with intelligent compression\n• Superior conversation continuity vs manual settings\n• Automatic context optimization for reasoning tasks\n• Balanced cost vs quality (3x more context than General mode)\n\nBest practices:\n• Use default settings for most scenarios - they're pre-optimized\n• Increase Recent Sections only for very complex tasks\n• Monitor context usage - costs scale with token count\n• Perfect for GPT-4, Claude, and other large context models`\n)\n\n// Summarizer Form Screen\nconst (\n\tSummarizerFormGeneralTitle = \"General Summarizer Configuration\"\n\tSummarizerFormAssistantTitle = \"Assistant Summarizer Configuration\"\n\tSummarizerFormDescription = \"Configure %s Settings\"\n\n\t// Field Labels and Descriptions\n\tSummarizerFormPreserveLast = \"Size Management\"\n\tSummarizerFormPreserveLastDesc = \"Controls last section compression. Enabled: sections fit LastSecBytes (smaller context). Disabled: sections grow freely (larger context)\"\n\n\tSummarizerFormUseQA = \"QA Summarization\"\n\tSummarizerFormUseQADesc = \"Enables question-answer pair compression when total QA content exceeds MaxQABytes or MaxQASections limits\"\n\n\tSummarizerFormSumHumanInQA = \"Compress User Messages\"\n\tSummarizerFormSumHumanInQADesc = \"Include user messages in QA compression. Disabled: preserves original user text (recommended for most cases)\"\n\n\tSummarizerFormLastSecBytes = \"Section Size Limit\"\n\tSummarizerFormLastSecBytesDesc = \"Maximum bytes per recent section when Size Management enabled. Larger: more detail per section, higher token usage\"\n\n\tSummarizerFormMaxBPBytes = \"Response Size Limit\"\n\tSummarizerFormMaxBPBytesDesc = \"Maximum bytes for individual AI responses before compression. Prevents single large responses from dominating context\"\n\n\tSummarizerFormMaxQASections = \"QA Section Limit\"\n\tSummarizerFormMaxQASectionsDesc = \"Maximum question-answer sections before QA compression triggers. Works with MaxQABytes to control total QA memory\"\n\n\tSummarizerFormMaxQABytes = \"Total QA Memory\"\n\tSummarizerFormMaxQABytesDesc = \"Maximum bytes for all QA sections combined. When exceeded (with MaxQASections), triggers QA compression to fit limit\"\n\n\tSummarizerFormKeepQASections = \"Recent Sections\"\n\tSummarizerFormKeepQASectionsDesc = \"Number of most recent conversation sections preserved without compression. PRIMARY parameter affecting context size\"\n\n\t// Enhanced Help Text - General (common principles)\n\tSummarizerFormGeneralHelp = `Context estimation: 4K-22K tokens (typical), up to 94K (maximum settings).\n\nKey relationships:\n• Recent Sections: Most critical - each +1 adds ~1.5-9K tokens\n• Size Management OFF: 2-3x larger context (less compression)\n• Section/Response Limits: Control individual component sizes\n• QA Memory: Manages total conversation history when limits exceeded\n\nParameter interactions:\n• QA compression activates when BOTH MaxQABytes AND MaxQASections exceeded\n• Size Management disabled → sections can grow 2x larger than limits\n• Response Limit prevents single large outputs from dominating context\n• User message compression (SummHumanInQA) saves 5% but loses original phrasing\n\nReduce for smaller models:\n• Recent Sections: 1-2 (vs 3+ default)\n• Section Limit: 25-35KB (vs 50KB+)\n• Disable Size Management for simple conversations\n\nCommon mistakes:\n• Setting Recent Sections too high (main cause of context overflow)\n• Enabling Size Management with very low Section Limits (over-compression)\n• Mismatched QA limits (high bytes + low sections = ineffective)\n\nCurrent algorithm compresses older content while preserving recent context quality.`\n\n\t// Enhanced Help Text - Assistant specific (interactive conversations)\n\tSummarizerFormAssistantHelp = `Optimized for interactive conversations requiring context continuity.\n\nDefault tuning (3 Recent Sections, 75KB limits):\n• Typical range: 8K-40K tokens\n• Good for: Extended dialogues, reasoning chains, context-dependent tasks\n• Models: Works well with 32K+ context models\n\nAdjustments by model type:\n• Short context (≤16K): Recent Sections=1-2, Section Limit=45KB\n• Long context (128K+): Can increase Recent Sections=5-7\n• High-frequency chat: Reduce Recent Sections=2 for faster responses\n\nAdvanced tuning:\n• QA Memory 200KB+ for document analysis conversations\n• Response Limit 24-32KB for detailed technical responses\n• Keep User Messages uncompressed (SummHumanInQA=false) for better context\n\nPerformance optimization:\n• Each Recent Section ≈ 9-18KB in assistant mode\n• Size Management reduces growth by ~20% but may lose detail\n• QA compression triggers less often due to larger default limits\n\nSize Management enabled by default - maintains conversation flow while preventing context overflow.\nMonitor actual token usage and adjust Recent Sections first, then limits.`\n\n\t// Context size estimation\n\tSummarizerContextEstimatedSize = \"Estimated context size: %s\\n%s\"\n\tSummarizerContextTokenRange = \"~%s tokens\"\n\tSummarizerContextTokenRangeMinMax = \"~%s-%s tokens\"\n\tSummarizerContextRequires256K = \"Requires 256K+ context model\"\n\tSummarizerContextRequires128K = \"Requires 128K+ context model\"\n\tSummarizerContextRequires64K = \"Requires 64K+ context model\"\n\tSummarizerContextRequires32K = \"Requires 32K+ context model\"\n\tSummarizerContextRequires16K = \"Requires 16K+ context model\"\n\tSummarizerContextFitsIn8K = \"Fits in 8K+ context model\"\n)\n\n// Tools screen strings\nconst (\n\tToolsTitle = \"Tools Configuration\"\n\tToolsDescription = \"Enhance agent capabilities with additional tools and options\"\n\tToolsName = \"Tools\"\n\tToolsOverview = `Configure additional tools and capabilities for AI agents.\nEach tool can be enabled and configured according to your requirements.\n\nAvailable settings:\n• Human-in-the-loop - Enable user interaction during testing\n• AI Agents Settings - Configure global behavior for AI agents\n• Search Engines - Configure external search providers\n• Scraper - Web content extraction and analysis\n• Graphiti (beta) - Temporal knowledge graph for semantic memory\n• Docker - Container environment configuration`\n)\n\n// Server Settings screen strings\nconst (\n\tServerSettingsFormTitle = \"Server Settings\"\n\tServerSettingsFormDescription = \"Configure PentAGI server network access and public routing\"\n\tServerSettingsFormName = \"Server Settings\"\n\tServerSettingsFormOverview = `• Network binding - control which interface and port PentAGI listens on\n• Public URL - external address and optional base path used in redirects\n• CORS - allowed origins for browser access\n• Proxy - HTTP/HTTPS proxy for outbound traffic to LLM/search providers\n• SSL directory - custom certificates directory containing server.crt and server.key (PEM)\n• Data directory - persistent storage for agent artifacts and flow workspaces`\n\n\t// Field labels and descriptions\n\tServerSettingsLicenseKey = \"License Key\"\n\tServerSettingsLicenseKeyDesc = \"PentAGI License Key in format of XXXX-XXXX-XXXX-XXXX\"\n\n\tServerSettingsHost = \"Server Host (Listen IP)\"\n\tServerSettingsHostDesc = \"Bind address used by Docker port mapping (e.g., 0.0.0.0 to expose on all interfaces)\"\n\n\tServerSettingsPort = \"Server Port (Listen Port)\"\n\tServerSettingsPortDesc = \"External TCP port exposed by Docker for PentAGI web UI\"\n\n\tServerSettingsPublicURL = \"Public URL\"\n\tServerSettingsPublicURLDesc = \"Base public URL for redirects and links (supports base path, e.g., https://example.com/pentagi/)\"\n\n\tServerSettingsCORSOrigins = \"CORS Origins\"\n\tServerSettingsCORSOriginsDesc = \"Comma-separated list of allowed origins (e.g., https://localhost:8443,https://localhost)\"\n\n\tServerSettingsProxyURL = \"HTTP/HTTPS Proxy\"\n\tServerSettingsProxyURLDesc = \"Proxy for outbound requests to LLMs and external tools (not used for Docker API access)\"\n\n\tServerSettingsProxyUsername = \"Proxy Username\"\n\tServerSettingsProxyUsernameDesc = \"Username for proxy authentication (optional)\"\n\tServerSettingsProxyPassword = \"Proxy Password\"\n\tServerSettingsProxyPasswordDesc = \"Password for proxy authentication (optional)\"\n\n\tServerSettingsHTTPClientTimeout = \"HTTP Client Timeout\"\n\tServerSettingsHTTPClientTimeoutDesc = \"Timeout in seconds for external API calls (LLM providers, search engines, etc.)\"\n\n\tServerSettingsExternalSSLCAPath = \"Custom CA Certificate Path\"\n\tServerSettingsExternalSSLCAPathDesc = \"Path inside container to custom root CA cert (e.g., /opt/pentagi/ssl/ca-bundle.pem)\"\n\n\tServerSettingsExternalSSLInsecure = \"Skip SSL Verification\"\n\tServerSettingsExternalSSLInsecureDesc = \"Disable SSL/TLS certificate validation (use only for testing with self-signed certs)\"\n\n\tServerSettingsSSLDir = \"SSL Directory\"\n\tServerSettingsSSLDirDesc = \"Directory containing server.crt and server.key in PEM format (server.crt may include fullchain)\"\n\n\tServerSettingsDataDir = \"Data Directory\"\n\tServerSettingsDataDirDesc = \"Directory for all agent-generated files; contains flow-N subdirectories used as /work in worker containers\"\n\n\tServerSettingsCookieSigningSalt = \"Cookie Signing Salt\"\n\tServerSettingsCookieSigningSaltDesc = \"Secret used to sign cookies (keep private)\"\n\n\t// Hints for fields overview\n\tServerSettingsLicenseKeyHint = \"License Key\"\n\tServerSettingsHostHint = \"Listen IP\"\n\tServerSettingsPortHint = \"Listen Port\"\n\tServerSettingsPublicURLHint = \"Public URL\"\n\tServerSettingsCORSOriginsHint = \"CORS Origins\"\n\tServerSettingsProxyURLHint = \"Proxy URL\"\n\tServerSettingsProxyUsernameHint = \"Proxy Username\"\n\tServerSettingsProxyPasswordHint = \"Proxy Password\"\n\tServerSettingsHTTPClientTimeoutHint = \"HTTP Timeout\"\n\tServerSettingsExternalSSLCAPathHint = \"Custom CA Path\"\n\tServerSettingsExternalSSLInsecureHint = \"Skip SSL Verification\"\n\tServerSettingsSSLDirHint = \"SSL Directory\"\n\tServerSettingsDataDirHint = \"Data Directory\"\n\n\t// Help texts per-field\n\tServerSettingsGeneralHelp = `PentAGI exposes its web UI via Docker with configurable host and port.\n\nPublic URL must reflect how users reach the server. If using a subpath (e.g., /pentagi/), include it here. CORS controls browser access from specified origins. Proxy affects outbound traffic to LLM/search providers and other external services used by Tools.\n\nSSL directory allows providing custom certificates. When set, server will use server.crt and server.key from that directory. Data directory stores artifacts and working files for flows.`\n\n\tServerSettingsLicenseKeyHelp = `PentAGI License Key in format of XXXX-XXXX-XXXX-XXXX. It's used to communicate with PentAGI Cloud API.`\n\n\tServerSettingsHostHelp = `Bind address for published port in docker-compose mapping.\n\nExamples:\n• 127.0.0.1 — local-only access\n• 0.0.0.0 — expose on all interfaces`\n\n\tServerSettingsPortHelp = `External port for PentAGI UI. Must be available on the host. Example: 8443.`\n\n\tServerSettingsPublicURLHelp = `Set the public base URL used in redirects and links.\n\nExamples:\n• http://localhost:8443\n• https://example.com/\n• https://example.com/pentagi/ (with base path)`\n\n\tServerSettingsCORSOriginsHelp = `Comma-separated allowed origins for browser access.`\n\n\tServerSettingsProxyURLHelp = `HTTP or HTTPS proxy for outbound requests to LLM providers and external tools. Not used for Docker API communication.`\n\n\tServerSettingsHTTPClientTimeoutHelp = `Timeout in seconds for all external HTTP/HTTPS API calls including:\n• LLM provider requests (OpenAI, Anthropic, Bedrock, etc.)\n• Search engine queries (Google, Tavily, Perplexity, etc.)\n• External tool integrations\n• Embedding generation requests\n\nDefault: 600 seconds (10 minutes)\nSetting to 0 disables timeout (not recommended in production)\nToo low values may cause legitimate long-running requests to fail.`\n\n\tServerSettingsExternalSSLCAPathHelp = `Path to custom CA certificate file (PEM format) inside the container.\n\nMust point to /opt/pentagi/ssl/ directory, which is mounted from pentagi-ssl volume on the host.\n\nExamples:\n• /opt/pentagi/ssl/ca-bundle.pem\n• /opt/pentagi/ssl/corporate-ca.pem\n\nFile can contain multiple root and intermediate certificates.`\n\n\tServerSettingsExternalSSLInsecureHelp = `Disable SSL/TLS certificate validation for connections to LLM providers and external services.\n\n⚠ WARNING: Use only for testing with self-signed certificates. Never enable in production.\n\nWhen enabled, all certificate validation is bypassed, making connections vulnerable to man-in-the-middle attacks.`\n\n\tServerSettingsSSLDirHelp = `Path to directory with server.crt and server.key in PEM format. server.crt may include fullchain. Overrides default generated certificate behavior.`\n\n\tServerSettingsDataDirHelp = `Host directory for persistent data. PentAGI stores agent artifacts under flow-N subdirectories, which map to /work inside worker containers.`\n\n\tServerSettingsCookieSigningSaltHelp = `Secret salt used to sign cookies. Keep it private.`\n)\n\n// Human-in-the-loop screen strings\nconst (\n\t// AI Agents Settings screen strings\n\tToolsAIAgentsSettingsFormTitle = \"AI Agents Settings\"\n\tToolsAIAgentsSettingsFormDescription = \"Configure global behavior for AI agents\"\n\tToolsAIAgentsSettingsFormName = \"AI Agents Settings\"\n\tToolsAIAgentsSettingsFormOverview = `This section configures global behavior of AI agents across PentAGI.\n\nBasic Settings:\n• Enable User Interaction: allow agents to request user input when needed\n• Use Multi-Agent Mode: enable assistant to orchestrate multiple specialized agents\n\nExecution Monitoring (⚠️ BETA):\n• Enable Execution Monitoring: automatic mentor supervision for pattern analysis\n• Same Tool Call Threshold: consecutive identical tool calls before mentor review\n• Total Tool Call Threshold: total tool calls before mentor review\n\nTool Call Limits:\n• Max Tool Calls (General Agents): prevent runaway executions for Assistant, Primary Agent, Pentester, Coder, Installer\n• Max Tool Calls (Limited Agents): prevent runaway executions for Searcher, Enricher, Memorist, etc.\n\nTask Planning (⚠️ BETA):\n• Enable Task Planning: generate structured execution plans for specialist agents\n\n⚠️ BETA features are under active development. Enable for testing only.`\n\n\t// field labels and descriptions\n\tToolsAIAgentsSettingHumanInTheLoop = \"Enable User Interaction\"\n\tToolsAIAgentsSettingHumanInTheLoopDesc = \"Allow agents to ask for user input when needed\"\n\tToolsAIAgentsSettingUseAgents = \"Use Multi-Agent Mode\"\n\tToolsAIAgentsSettingUseAgentsDesc = \"Enable assistant to orchestrate multiple specialized agents\"\n\tToolsAIAgentsSettingExecutionMonitor = \"Enable Execution Monitoring (beta)\"\n\tToolsAIAgentsSettingExecutionMonitorDesc = \"Automatically invoke mentor for execution pattern analysis\"\n\tToolsAIAgentsSettingSameToolLimit = \"Same Tool Call Threshold\"\n\tToolsAIAgentsSettingSameToolLimitDesc = \"Consecutive identical tool calls before mentor review\"\n\tToolsAIAgentsSettingTotalToolLimit = \"Total Tool Call Threshold\"\n\tToolsAIAgentsSettingTotalToolLimitDesc = \"Total tool calls before mentor review\"\n\tToolsAIAgentsSettingMaxGeneralToolCalls = \"Max Tool Calls (General Agents)\"\n\tToolsAIAgentsSettingMaxGeneralToolCallsDesc = \"Maximum tool calls for Assistant, Primary Agent, Pentester, Coder, Installer\"\n\tToolsAIAgentsSettingMaxLimitedToolCalls = \"Max Tool Calls (Limited Agents)\"\n\tToolsAIAgentsSettingMaxLimitedToolCallsDesc = \"Maximum tool calls for Searcher, Enricher, Memorist, etc.\"\n\tToolsAIAgentsSettingTaskPlanning = \"Enable Task Planning (beta)\"\n\tToolsAIAgentsSettingTaskPlanningDesc = \"Generate structured execution plans for specialist agents\"\n\n\t// help content\n\tToolsAIAgentsSettingsHelp = `AI Agents Settings define how agents collaborate, interact with users, and handle execution control.\n\nBasic Settings:\n• Enable User Interaction: allow agents to ask for user input when needed\n• Use Multi-Agent Mode: enable assistant to orchestrate specialized agents for complex tasks\n\nExecution Monitoring (⚠️ BETA):\nAutomatically invokes adviser (mentor) to analyze execution patterns, detect loops, suggest alternative strategies, and prevent agents from fixating on single approach. Thresholds: consecutive identical calls (default: 5) and total calls (default: 10).\n\nTask Planning (⚠️ BETA):\nGenerates 3-7 step execution plans before specialist agents begin work. Prevents scope creep and improves success rates. Works best when adviser uses enhanced configuration (stronger model or maximum reasoning mode).\n\nTool Call Limits (always active):\nHard limits prevent infinite loops: General agents default 100, Limited agents default 20. Works independently from beta features.\n\nOPEN SOURCE MODELS < 32B (Qwen3.5-27B, DeepSeek-V3, Llama-3.1-70B):\n✓ ENABLE both beta features - ESSENTIAL for quality results\n✓ Testing shows 2x improvement in result quality vs. baseline\n✓ Configure adviser with enhanced settings for best performance\n✓ Ideal for air-gapped deployments with local LLM inference\n\nPerformance: 2-3x increase in tokens/time, 2x improvement in quality for models < 32B.\n\n⚠️ BETA WARNING: Features under active development. Recommended for open source models < 32B despite beta status. For cloud APIs with larger models, keep disabled.\n\nNote: Changes require service restart.`\n)\n\n// Search Engines screen strings\nconst (\n\tToolsSearchEnginesFormTitle = \"Search Engines Configuration\"\n\tToolsSearchEnginesFormDescription = \"Configure search engines for AI agents to gather intelligence during testing\"\n\tToolsSearchEnginesFormName = \"Search Engines\"\n\tToolsSearchEnginesFormOverview = `Available search engines:\n• DuckDuckGo - Free search engine (no API key required)\n• Sploitus - Security exploits and vulnerabilities database (no API key required)\n• Perplexity - AI-powered search with reasoning\n• Tavily - Search API for AI applications\n• Traversaal - Web scraping and search\n• Google Search - Requires API key and Custom Search Engine ID\n• Searxng - Internet metasearch engine\n\nGet API keys from:\n• Perplexity: https://www.perplexity.ai/\n• Tavily: https://tavily.com/\n• Traversaal: https://traversaal.ai/\n• Google: https://developers.google.com/custom-search/v1/introduction`\n\n\tToolsSearchEnginesDuckDuckGo = \"DuckDuckGo Search\"\n\tToolsSearchEnginesDuckDuckGoDesc = \"Enable DuckDuckGo search (no API key required)\"\n\tToolsSearchEnginesDuckDuckGoRegion = \"DuckDuckGo Region\"\n\tToolsSearchEnginesDuckDuckGoRegionDesc = \"DuckDuckGo region code (e.g., us-en, uk-en, cn-zh)\"\n\tToolsSearchEnginesDuckDuckGoSafeSearch = \"DuckDuckGo Safe Search\"\n\tToolsSearchEnginesDuckDuckGoSafeSearchDesc = \"DuckDuckGo safe search (strict, moderate, off)\"\n\tToolsSearchEnginesDuckDuckGoTimeRange = \"DuckDuckGo Time Range\"\n\tToolsSearchEnginesDuckDuckGoTimeRangeDesc = \"DuckDuckGo time range (d: day, w: week, m: month, y: year)\"\n\tToolsSearchEnginesSploitus = \"Sploitus Search\"\n\tToolsSearchEnginesSploitusDesc = \"Enable Sploitus search for exploits and vulnerabilities (no API key required)\"\n\tToolsSearchEnginesPerplexityKey = \"Perplexity API Key\"\n\tToolsSearchEnginesPerplexityKeyDesc = \"API key for Perplexity AI search\"\n\tToolsSearchEnginesTavilyKey = \"Tavily API Key\"\n\tToolsSearchEnginesTavilyKeyDesc = \"API key for Tavily search service\"\n\tToolsSearchEnginesTraversaalKey = \"Traversaal API Key\"\n\tToolsSearchEnginesTraversaalKeyDesc = \"API key for Traversaal web scraping\"\n\tToolsSearchEnginesGoogleKey = \"Google Search API Key\"\n\tToolsSearchEnginesGoogleKeyDesc = \"Google Custom Search API key\"\n\tToolsSearchEnginesGoogleCX = \"Google Search Engine ID\"\n\tToolsSearchEnginesGoogleCXDesc = \"Google Custom Search Engine ID\"\n\tToolsSearchEnginesGoogleLR = \"Google Language Restriction\"\n\tToolsSearchEnginesGoogleLRDesc = \"Google Search Engine language restriction (e.g., lang_en, lang_cn, etc.)\"\n\tToolsSearchEnginesSearxngURL = \"Searxng Search URL\"\n\tToolsSearchEnginesSearxngURLDesc = \"Searxng search engine URL\"\n\tToolsSearchEnginesSearxngCategories = \"Searxng Search Categories\"\n\tToolsSearchEnginesSearxngCategoriesDesc = \"Searxng search engine categories (e.g., general, it, web, news, technology, science, health, other)\"\n\tToolsSearchEnginesSearxngLanguage = \"Searxng Search Language\"\n\tToolsSearchEnginesSearxngLanguageDesc = \"Searxng search engine language (en, ch, fr, de, it, es, pt, ru, zh, empty for all languages)\"\n\tToolsSearchEnginesSearxngSafeSearch = \"Searxng Safe Search\"\n\tToolsSearchEnginesSearxngSafeSearchDesc = \"Searxng search engine safe search (0: off, 1: moderate, 2: strict)\"\n\tToolsSearchEnginesSearxngTimeRange = \"Searxng Time Range\"\n\tToolsSearchEnginesSearxngTimeRangeDesc = \"Searxng search engine time range (day, month, year)\"\n\tToolsSearchEnginesSearxngTimeout = \"Searxng Timeout\"\n\tToolsSearchEnginesSearxngTimeoutDesc = \"Searxng request timeout in seconds\"\n)\n\n// Scraper screen strings\nconst (\n\tToolsScraperFormTitle = \"Scraper Configuration\"\n\tToolsScraperFormDescription = \"Configure web scraping service\"\n\tToolsScraperFormName = \"Scraper\"\n\tToolsScraperFormOverview = `Web scraper service for content extraction and analysis using vxcontrol/scraper Docker image.\n\nModes:\n• Embedded - Run local scraper container (recommended)\n• External - Use external scraper services\n• Disabled - No web scraping capabilities\n\nDocker image: https://hub.docker.com/r/vxcontrol/scraper\n\nThe scraper supports:\n• Public URL access for external links\n• Private URL access for internal/local links\n• Content extraction and analysis\n• Multiple output formats`\n\n\tToolsScraperModeTitle = \"Scraper Mode\"\n\tToolsScraperModeDesc = \"Select how the scraper service should operate\"\n\tToolsScraperEmbedded = \"Embedded Container\"\n\tToolsScraperExternal = \"External Service\"\n\tToolsScraperDisabled = \"Disabled\"\n\tToolsScraperPublicURL = \"Public Scraper URL\"\n\tToolsScraperPublicURLDesc = \"URL for scraping public/external websites. If empty, the same value as private URL will be used.\"\n\tToolsScraperPublicURLEmbeddedDesc = \"URL for embedded scraper (optional override). If empty, the same value as private URL will be used.\"\n\tToolsScraperPrivateURL = \"Private Scraper URL\"\n\tToolsScraperPrivateURLDesc = \"URL for scraping private/internal websites\"\n\tToolsScraperPublicUsername = \"Public URL Username\"\n\tToolsScraperPublicUsernameDesc = \"Username for public scraper access\"\n\tToolsScraperPublicPassword = \"Public URL Password\"\n\tToolsScraperPublicPasswordDesc = \"Password for public scraper access\"\n\tToolsScraperPrivateUsername = \"Private URL Username\"\n\tToolsScraperPrivateUsernameDesc = \"Username for private scraper access\"\n\tToolsScraperPrivatePassword = \"Private URL Password\"\n\tToolsScraperPrivatePasswordDesc = \"Password for private scraper access\"\n\tToolsScraperLocalUsername = \"Local URL Username\"\n\tToolsScraperLocalUsernameDesc = \"Username for embedded scraper service\"\n\tToolsScraperLocalPassword = \"Local URL Password\"\n\tToolsScraperLocalPasswordDesc = \"Password for embedded scraper service\"\n\tToolsScraperMaxConcurrentSessions = \"Max Concurrent Sessions\"\n\tToolsScraperMaxConcurrentSessionsDesc = \"Maximum number of concurrent scraping sessions\"\n\tToolsScraperEmbeddedHelp = \"Embedded mode runs a local scraper container that can access both public and private resources. The default configuration uses https://someuser:somepass@scraper/.\"\n\tToolsScraperExternalHelp = \"External mode uses separate scraper services. Configure different URLs for public and private access as needed.\"\n\tToolsScraperDisabledHelp = \"Scraper is disabled. Web content extraction and analysis capabilities will not be available.\"\n)\n\n// Docker Environment screen strings\nconst (\n\tToolsDockerFormTitle = \"Docker Environment Configuration\"\n\tToolsDockerFormDescription = \"Configure Docker environment for worker containers\"\n\tToolsDockerFormName = \"Docker Environment\"\n\tToolsDockerFormOverview = `• Worker Isolation - Containers provide security boundaries for tasks\n• Network Capabilities - Enable privileged network operations for pentesting\n• Container Management - Control how workers access Docker daemon\n• Storage Configuration - Define workspace and artifact storage\n• Image Selection - Set default images for different task types\n\nCritical for penetration testing workflows requiring network scanning, custom tools, and secure task isolation.`\n\n\t// General help text\n\tToolsDockerGeneralHelp = `Each AI agent task runs in an isolated Docker container with two ports (28000-32000 range) automatically allocated per flow. Worker containers are created on-demand from default images or agent-selected ones.\n\nBasic setup requires enabling capabilities: Docker Access allows spawning additional containers for specialized tools, while Network Admin grants low-level network permissions essential for scanning tools like nmap.\n\nStorage operates via Docker volumes by default, or host directories when Work Directory is specified. Connection settings control the Docker daemon location - local socket for standard setups, or remote TCP with TLS for distributed environments.\n\nDefault images serve as fallbacks: general tasks use standard images, while security testing defaults to pentesting-focused containers. Public IP enables reverse shell attacks by providing workers with a reachable address for target callbacks. Usually it's a local interface address of the host machine with Docker daemon running for the workers containers.\n\nConfiguration combines based on scenario: enable both capabilities for full pentesting, use Work Directory for persistent artifacts, or configure remote connection for isolated Docker environments.`\n\n\t// Container capabilities\n\tToolsDockerInside = \"Docker Access\"\n\tToolsDockerInsideDesc = \"Allow workers to manage Docker containers\"\n\tToolsDockerNetAdmin = \"Network Admin\"\n\tToolsDockerNetAdminDesc = \"Grant NET_ADMIN capability for network scanning tools like nmap\"\n\n\t// Connection settings\n\tToolsDockerSocket = \"Docker Socket\"\n\tToolsDockerSocketDesc = \"Path to Docker socket on host filesystem\"\n\tToolsDockerNetwork = \"Docker Network\"\n\tToolsDockerNetworkDesc = \"Custom network name for worker containers, or 'host' for direct host network access\"\n\tToolsDockerPublicIP = \"Public IP Address\"\n\tToolsDockerPublicIPDesc = \"Public IP for reverse connections in OOB attacks\"\n\n\t// Storage configuration\n\tToolsDockerWorkDir = \"Work Directory\"\n\tToolsDockerWorkDirDesc = \"Host directory for worker filesystems (default: Docker volumes)\"\n\n\t// Default images\n\tToolsDockerDefaultImage = \"Default Image\"\n\tToolsDockerDefaultImageDesc = \"Default Docker image for general tasks\"\n\tToolsDockerDefaultImageForPentest = \"Pentesting Image\"\n\tToolsDockerDefaultImageForPentestDesc = \"Default Docker image for security testing tasks\"\n\n\t// TLS connection settings (optional)\n\tToolsDockerHost = \"Docker Host\"\n\tToolsDockerHostDesc = \"Docker daemon connection (unix:// or tcp://)\"\n\tToolsDockerTLSVerify = \"TLS Verification\"\n\tToolsDockerTLSVerifyDesc = \"Enable TLS verification for Docker connection\"\n\tToolsDockerCertPath = \"TLS Certificates\"\n\tToolsDockerCertPathDesc = \"Directory containing ca.pem, cert.pem, key.pem files\"\n\n\t// Help content for specific configurations\n\tToolsDockerInsideHelp = `Docker Access enables workers to spawn additional containers for specialized tools and environments. Required when tasks need custom software not available in default images.\n\nWhen enabled, workers can pull and run any Docker image, providing maximum flexibility for complex testing scenarios.`\n\n\tToolsDockerNetAdminHelp = `Network Admin capability allows workers to perform low-level network operations essential for penetration testing.\n\nRequired for:\n• Network scanning with nmap, masscan\n• Custom packet crafting\n• Network interface manipulation\n• Raw socket operations\n\nCritical for comprehensive security assessments.`\n\n\tToolsDockerSocketHelp = `Docker Socket path defines how workers access the Docker daemon. Use only file path to the socket file. Used with Docker Access to enable container management.\n\nFor enhanced security, consider using docker-in-docker (DinD) instead of exposing the main Docker daemon directly to workers.\nWhen using DinD, use the path to the Docker socket file of the DinD container which binded to the host filesystem.\n\nExample: /var/run/docker.sock`\n\n\tToolsDockerNetworkHelp = `Docker Network controls network isolation mode for worker containers:\n\nBridge Mode (custom network name):\n• Isolated communication between containers\n• Port forwarding from container to host\n• Enhanced security boundaries\n• Network-based monitoring and filtering\n• Recommended for most use cases\n\nHost Mode (value: 'host'):\n• Direct access to host network interfaces\n• No port forwarding - ports bind directly to host\n• Required for raw packet manipulation\n• Advanced network testing capabilities\n• Lower isolation - use with caution\n\nExamples:\n• 'pentagi-network' - creates isolated bridge network\n• 'host' - enables direct host network access\n\nSecurity Note: Host network mode reduces container isolation. Only use when necessary for advanced penetration testing tasks requiring direct network stack access.`\n\n\tToolsDockerPublicIPHelp = `Public IP Address enables out-of-band (OOB) attack techniques by providing workers with a reachable address for reverse connections.\n\nWorkers automatically receive two random ports (28000-32000 range) mapped to this IP for receiving callbacks from exploited targets.\n\nBy default agents will try to get public address from the services api.ipify.org, ipinfo.io/ip or ifconfig.me.`\n\n\tToolsDockerWorkDirHelp = `Work Directory specifies host filesystem location for worker storage. When set, replaces default Docker volumes with host directory mounts.\n\nBenefits:\n• Persistent storage across restarts\n• Direct file system access\n• Easier artifact management\n• Custom backup strategies\n\nBy default uses Docker dedicated volume per worker container.\n\nExample: /path/to/workdir/`\n\n\tToolsDockerDefaultImageHelp = `Default Image provides fallback for workers when task requirements don't specify a particular container image.\n\nShould contain basic utilities and tools for general-purpose tasks. Default: debian:latest`\n\n\tToolsDockerDefaultImageForPentestHelp = `Pentesting Image serves as default for security testing tasks. Should include comprehensive security tools and utilities.\n\nRecommended images include Kali Linux, Parrot Security, or custom security-focused containers. Default: vxcontrol/kali-linux`\n\n\tToolsDockerHostHelp = `Docker Host uses for start primary worker containers and overrides default Docker daemon connection. Supports Unix sockets and TCP connections.\n\nExamples:\n• unix:///var/run/docker.sock (local)\n• tcp://docker-host:2376 (remote)\n\nEnable TLS for remote connections.`\n\n\tToolsDockerTLSVerifyHelp = `TLS Verification secures Docker daemon connections over TCP. Strongly recommended for remote Docker hosts.\n\nRequires valid certificates in the specified certificate directory.`\n\n\tToolsDockerCertPathHelp = `TLS Certificates directory must contain:\n• ca.pem - Certificate Authority\n• cert.pem - Client certificate\n• key.pem - Private key\n\nRequired for secure remote Docker connections when using TLS to manage worker containers.\n\nExample: /path/to/certs`\n)\n\n// Embedder form strings\nconst (\n\tEmbedderFormTitle = \"Embedder Configuration\"\n\tEmbedderFormDescription = \"Configure text vectorization for semantic search and knowledge storage\"\n\tEmbedderFormName = \"Embedder\"\n\tEmbedderFormOverview = `Text embeddings convert documents into vectors for semantic search and knowledge storage.\nDifferent providers offer various models with different capabilities and pricing.\n\nChoose carefully as changing providers requires reindexing all stored data.`\n\n\tEmbedderFormProvider = \"Embedding Provider\"\n\tEmbedderFormProviderDesc = \"Select the provider for text vectorization. Embeddings are used for semantic search and knowledge storage.\"\n\n\tEmbedderFormURL = \"API Endpoint URL\"\n\tEmbedderFormURLDesc = \"Custom API endpoint (leave empty to use default)\"\n\n\tEmbedderFormAPIKey = \"API Key\"\n\tEmbedderFormAPIKeyDesc = \"Authentication key for the provider (not required for Ollama)\"\n\n\tEmbedderFormModel = \"Model Name\"\n\tEmbedderFormModelDesc = \"Specific embedding model to use (leave empty for provider default)\"\n\n\tEmbedderFormBatchSize = \"Batch Size\"\n\tEmbedderFormBatchSizeDesc = \"Number of documents to process in a single batch (1-1000)\"\n\n\tEmbedderFormStripNewLines = \"Strip New Lines\"\n\tEmbedderFormStripNewLinesDesc = \"Remove line breaks from text before embedding (true/false)\"\n\n\tEmbedderFormHelpTitle = \"Embedding Configuration\"\n\tEmbedderFormHelpContent = `Configure text vectorization for semantic search and knowledge storage.\n\nIf no specific embedding settings are configured, the system will use OpenAI embeddings with the API key from LLM Providers.\n\nChange providers carefully - different embedders produce incompatible vectors requiring database reindexing.`\n\n\tEmbedderFormHelpOpenAI = \"OpenAI: Most reliable option with excellent quality. Requires API key from LLM Providers if not set here.\"\n\tEmbedderFormHelpOllama = \"Ollama: Local embeddings, no API key needed. Requires Ollama server running.\"\n\tEmbedderFormHelpHuggingFace = \"HuggingFace: Open source models with API key required.\"\n\tEmbedderFormHelpGoogleAI = \"Google AI: Quality embeddings, requires API key.\"\n\n\t// Provider names and descriptions\n\tEmbedderProviderDefault = \"Default (OpenAI)\"\n\tEmbedderProviderDefaultDesc = \"Use OpenAI embeddings with API key from LLM Providers configuration\"\n\tEmbedderProviderOpenAI = \"OpenAI\"\n\tEmbedderProviderOpenAIDesc = \"OpenAI text embeddings API (text-embedding-3-small, ada-002)\"\n\tEmbedderProviderOllama = \"Ollama\"\n\tEmbedderProviderOllamaDesc = \"Local Ollama server for open-source embedding models\"\n\tEmbedderProviderMistral = \"Mistral\"\n\tEmbedderProviderMistralDesc = \"Mistral AI embedding models\"\n\tEmbedderProviderJina = \"Jina\"\n\tEmbedderProviderJinaDesc = \"Jina AI embedding API\"\n\tEmbedderProviderHuggingFace = \"HuggingFace\"\n\tEmbedderProviderHuggingFaceDesc = \"HuggingFace inference API for embedding models\"\n\tEmbedderProviderGoogleAI = \"Google AI\"\n\tEmbedderProviderGoogleAIDesc = \"Google AI embedding models (embedding-001)\"\n\tEmbedderProviderVoyageAI = \"VoyageAI\"\n\tEmbedderProviderVoyageAIDesc = \"VoyageAI embedding API\"\n\tEmbedderProviderDisabled = \"Disabled\"\n\tEmbedderProviderDisabledDesc = \"Disable embeddings functionality completely\"\n\n\t// Provider-specific placeholders and help\n\tEmbedderURLPlaceholderOpenAI = \"https://api.openai.com/v1\"\n\tEmbedderURLPlaceholderOllama = \"http://localhost:11434\"\n\tEmbedderURLPlaceholderMistral = \"https://api.mistral.ai/v1\"\n\tEmbedderURLPlaceholderJina = \"https://api.jina.ai/v1\"\n\tEmbedderURLPlaceholderHuggingFace = \"https://api-inference.huggingface.co\"\n\tEmbedderURLPlaceholderGoogleAI = \"Not supported - uses default endpoint\"\n\tEmbedderURLPlaceholderVoyageAI = \"Not supported - uses default endpoint\"\n\n\tEmbedderAPIKeyPlaceholderOllama = \"Not required for local models\"\n\tEmbedderAPIKeyPlaceholderMistral = \"Mistral API key\"\n\tEmbedderAPIKeyPlaceholderJina = \"Jina API key\"\n\tEmbedderAPIKeyPlaceholderHuggingFace = \"HuggingFace API key\"\n\tEmbedderAPIKeyPlaceholderGoogleAI = \"Google AI API key\"\n\tEmbedderAPIKeyPlaceholderVoyageAI = \"VoyageAI API key\"\n\tEmbedderAPIKeyPlaceholderDefault = \"API key for the provider\"\n\n\tEmbedderModelPlaceholderOpenAI = \"text-embedding-3-small\"\n\tEmbedderModelPlaceholderOllama = \"nomic-embed-text\"\n\tEmbedderModelPlaceholderMistral = \"mistral-embed\"\n\tEmbedderModelPlaceholderJina = \"jina-embeddings-v2-base-en\"\n\tEmbedderModelPlaceholderHuggingFace = \"sentence-transformers/all-MiniLM-L6-v2\"\n\tEmbedderModelPlaceholderGoogleAI = \"gemini-embedding-001\"\n\tEmbedderModelPlaceholderVoyageAI = \"voyage-2\"\n\tEmbedderModelPlaceholderDefault = \"Model name\"\n\n\t// Provider IDs for internal use\n\tEmbedderProviderIDDefault = \"default\"\n\tEmbedderProviderIDOpenAI = \"openai\"\n\tEmbedderProviderIDOllama = \"ollama\"\n\tEmbedderProviderIDMistral = \"mistral\"\n\tEmbedderProviderIDJina = \"jina\"\n\tEmbedderProviderIDHuggingFace = \"huggingface\"\n\tEmbedderProviderIDGoogleAI = \"googleai\"\n\tEmbedderProviderIDVoyageAI = \"voyageai\"\n\tEmbedderProviderIDDisabled = \"none\"\n\n\tEmbedderHelpGeneral = `Embeddings convert text into vectors for semantic search and knowledge storage. This enables PentAGI to understand meaning rather than just keywords, making search results more relevant and intelligent.\n\nKey benefits:\n• Find documents by meaning, not exact words\n• Build a smart knowledge base from pentesting results\n• Enable AI agents to locate relevant information quickly\n• Support advanced reasoning with contextual data\n\nChoose Ollama for completely local processing - your data never leaves your infrastructure. Other providers offer cloud-based processing with different model capabilities and pricing.\n\nConfigure carefully as changing providers requires rebuilding the entire knowledge base.`\n\n\tEmbedderHelpAttentionPrefix = \"Important:\"\n\tEmbedderHelpAttention = `Different embedding providers create incompatible vectors. Changing providers or models will break existing semantic search.\n\nYou must flush or reindex your entire knowledge base using the etester utility:\n• Run 'etester flush' to clear old embeddings\n• Run 'etester reindex' to rebuild with new provider\n• This process can take significant time for large datasets`\n\n\tEmbedderHelpAttentionSuffix = `Only change providers if absolutely necessary.`\n\n\t// Provider help texts\n\tEmbedderHelpDefault = `Default mode uses OpenAI embeddings with the API key configured in LLM Providers.\n\nThis is the recommended option for most users as it requires no additional configuration if you already have OpenAI set up.`\n\n\tEmbedderHelpOpenAI = `Direct OpenAI API access for embedding generation.\n\nGet your API key from:\nhttps://platform.openai.com/api-keys\n\nRecommended models:\n• text-embedding-3-small (cost-effective, 1536 dimensions)\n• text-embedding-3-large (highest quality, 3072 dimensions)\n• text-embedding-ada-002 (legacy, still supported)`\n\n\tEmbedderHelpOllama = `Local Ollama server for open-source embedding models.\n\nPopular embedding models:\n• nomic-embed-text (recommended, 768 dimensions)\n• mxbai-embed-large (large model, 1024 dimensions)\n• snowflake-arctic-embed (multilingual support)\n\nInstall Ollama from:\nhttps://ollama.com/\n\nStart with: ollama pull nomic-embed-text`\n\n\tEmbedderHelpMistral = `Mistral AI embedding models via API.\n\nGet your API key from:\nhttps://console.mistral.ai/\n\nUses Mistral's embedding model with fixed configuration.\nNo model selection required - uses the default embedding model.`\n\n\tEmbedderHelpJina = `Jina AI embedding API with specialized models.\n\nGet your API key from:\nhttps://jina.ai/\n\nRecommended models:\n• jina-embeddings-v2-base-en (general purpose, 768 dimensions)\n• jina-embeddings-v2-small-en (lightweight, 512 dimensions)\n• jina-embeddings-v2-base-code (code-specific embeddings)`\n\n\tEmbedderHelpHuggingFace = `HuggingFace Inference API for open-source models.\n\nGet your API key from:\nhttps://huggingface.co/settings/tokens\n\nPopular models:\n• sentence-transformers/all-MiniLM-L6-v2 (384 dimensions)\n• sentence-transformers/all-mpnet-base-v2 (768 dimensions)\n• intfloat/e5-large-v2 (1024 dimensions)`\n\n\tEmbedderHelpGoogleAI = `Google AI embedding models (Gemini).\n\nGet your API key from:\nhttps://aistudio.google.com/app/apikey\n\nAvailable models:\n• gemini-embedding-001 (latest model, 768 dimensions)\n• text-embedding-004 (legacy Vertex AI model)\n\nUses Google's fixed endpoint - URL configuration not supported.`\n\n\tEmbedderHelpVoyageAI = `VoyageAI embedding API optimized for retrieval.\n\nGet your API key from:\nhttps://www.voyageai.com/\n\nRecommended models:\n• voyage-2 (general purpose, 1024 dimensions)\n• voyage-large-2 (highest quality, 1536 dimensions)\n• voyage-code-2 (code embeddings, 1536 dimensions)`\n\n\tEmbedderHelpDisabled = `Disables all embedding functionality.\n\nThis will:\n• Disable semantic search capabilities\n• Turn off knowledge storage vectorization\n• Reduce memory and computational requirements\n\nOnly recommended if embeddings are not needed for your use case.`\n)\n\n// Development and Mock Screen constants\nconst (\n\tMockScreenTitle = \"Development Screen\"\n\tMockScreenDescription = \"This screen is under development\"\n)\n\n// Apply Changes screen constants\nconst (\n\tApplyChangesFormTitle = \"Apply Configuration Changes\"\n\tApplyChangesFormName = \"Apply Changes\"\n\tApplyChangesFormDescription = \"Review and apply your configuration changes\"\n\n\t// Apply Changes overview and help\n\tApplyChangesFormOverview = `This screen allows you to review all pending configuration changes and apply them to your PentAGI installation.\n\nWhen you apply changes, the system will:\n• Save all modified environment variables to the .env file\n• Restart affected services with the new configuration\n• Install additional components if needed`\n\n\t// Apply Changes status messages\n\tApplyChangesNotStarted = \"Configuration changes are ready to be applied\"\n\tApplyChangesInProgress = \"Applying configuration changes...\\n\"\n\tApplyChangesCompleted = \"Configuration changes have been successfully applied\\n\"\n\tApplyChangesFailed = \"Failed to perform configuration changes\"\n\tApplyChangesResetCompleted = \"Configuration changes have been successfully reset\\n\"\n\n\tApplyChangesTerminalIsNotInitialized = \"Terminal is not initialized\"\n\n\t// Apply Changes instructions\n\tApplyChangesInstructions = `Press Enter to begin applying the configuration changes.`\n\n\tApplyChangesNoChanges = \"No configuration changes are pending\"\n\n\t// Apply Changes installation status\n\tApplyChangesInstallNotFound = `PentAGI is not currently installed on this system.\n\nThe following actions will be performed:\n• Docker environment setup and validation\n• Creation of docker-compose.yml file\n• Installation and startup of PentAGI core services`\n\n\tApplyChangesInstallFoundLangfuse = `• Installation of Langfuse observability stack (docker-compose-langfuse.yml)`\n\tApplyChangesInstallFoundObservability = `• Installation of comprehensive observability stack with Grafana, VictoriaMetrics, and Jaeger (docker-compose-observability.yml)`\n\n\tApplyChangesUpdateFound = `PentAGI is currently installed on this system.\n\nThe following actions will be performed:\n• Update environment variables in .env file\n• Recreate and restart affected Docker containers\n• Apply new configuration to running services`\n\n\t// Apply Changes warnings and notes\n\tApplyChangesWarningCritical = \"⚠️ Critical changes detected - services will be restarted\"\n\tApplyChangesWarningSecrets = \"🔒 Secret values detected - they will be securely stored\"\n\tApplyChangesNoteBackup = \"💾 Current configuration will be backed up before changes\"\n\tApplyChangesNoteTime = \"⏱️ This process may take less than a minute depending on selected components\"\n\n\t// Apply Changes progress messages\n\tApplyChangesStageValidation = \"Validating environment and dependencies...\"\n\tApplyChangesStageBackup = \"Creating configuration backup...\"\n\tApplyChangesStageEnvFile = \"Updating environment file...\"\n\tApplyChangesStageCompose = \"Generating Docker Compose files...\"\n\tApplyChangesStageDocker = \"Managing Docker containers...\"\n\tApplyChangesStageServices = \"Starting services...\"\n\tApplyChangesStageComplete = \"Configuration changes applied successfully\"\n\n\t// Apply Changes change list headers\n\tApplyChangesChangesTitle = \"Pending Configuration Changes\"\n\tApplyChangesChangesCount = \"Total changes: %d\"\n\tApplyChangesChangesMasked = \"(hidden for security)\"\n\tApplyChangesChangesEmpty = \"No changes to apply\"\n\n\t// Apply Changes help content\n\tApplyChangesHelpTitle = \"Applying Configuration Changes\"\n\tApplyChangesHelpContent = `Be sure to check the current configuration before applying changes.`\n)\n\n// apply changes integrity prompt\nconst (\n\tApplyChangesIntegrityPromptTitle = \"File integrity check\"\n\tApplyChangesIntegrityPromptMessage = \"Out-of-date files were detected.\\nDo you want to update them to the latest version?\"\n\tApplyChangesIntegrityOutdatedList = \"Out-of-date files:\\n%s\\nConfirm update? (y/n)\"\n\tApplyChangesIntegrityChecking = \"Collecting file integrity information...\"\n\tApplyChangesIntegrityNoOutdated = \"No out-of-date files found. Proceeding with apply.\"\n)\n\n// Maintenance Screen constants\nconst (\n\tMaintenanceTitle = \"System Maintenance\"\n\tMaintenanceDescription = \"Manage PentAGI services and perform maintenance operations\"\n\tMaintenanceName = \"Maintenance\"\n\tMaintenanceOverview = `Perform system maintenance operations for PentAGI.\n\nAvailable operations depend on the current system state and will only be shown when applicable.\n\nOperations include:\n• Service lifecycle management (Start/Stop/Restart)\n• Component updates and downloads\n• System reset and cleanup\n• Container and image management\n\nEach operation will provide real-time status updates and confirmation when required.`\n\n\t// Maintenance menu items\n\tMaintenanceStartPentagi = \"Start PentAGI\"\n\tMaintenanceStartPentagiDesc = \"Start all configured PentAGI services\"\n\tMaintenanceStopPentagi = \"Stop PentAGI\"\n\tMaintenanceStopPentagiDesc = \"Stop all running PentAGI services\"\n\tMaintenanceRestartPentagi = \"Restart PentAGI\"\n\tMaintenanceRestartPentagiDesc = \"Restart all PentAGI services\"\n\tMaintenanceDownloadWorkerImage = \"Download Worker Image\"\n\tMaintenanceDownloadWorkerImageDesc = \"Download pentesting container image for worker tasks\"\n\tMaintenanceUpdateWorkerImage = \"Update Worker Image\"\n\tMaintenanceUpdateWorkerImageDesc = \"Update pentesting container image to latest version\"\n\tMaintenanceUpdatePentagi = \"Update PentAGI\"\n\tMaintenanceUpdatePentagiDesc = \"Update PentAGI to the latest version\"\n\tMaintenanceUpdateInstaller = \"Update Installer\"\n\tMaintenanceUpdateInstallerDesc = \"Update this installer to the latest version\"\n\tMaintenanceFactoryReset = \"Factory Reset\"\n\tMaintenanceFactoryResetDesc = \"Reset PentAGI to factory defaults\"\n\tMaintenanceRemovePentagi = \"Remove PentAGI\"\n\tMaintenanceRemovePentagiDesc = \"Remove PentAGI containers but keep data\"\n\tMaintenancePurgePentagi = \"Purge PentAGI\"\n\tMaintenancePurgePentagiDesc = \"Completely remove PentAGI including all data\"\n\tMaintenanceResetPassword = \"Reset Admin Password\"\n\tMaintenanceResetPasswordDesc = \"Reset the administrator password for PentAGI\"\n)\n\n// Reset Password Screen constants\nconst (\n\tResetPasswordFormTitle = \"Reset Admin Password\"\n\tResetPasswordFormDescription = \"Reset the administrator password for PentAGI\"\n\tResetPasswordFormName = \"Reset Password\"\n\tResetPasswordFormOverview = `Reset the password for the default administrator account (admin@pentagi.com).\n\nThis operation requires PentAGI to be running and will update the password in the PostgreSQL database.\n\nEnter your new password twice to confirm and press Enter to apply the change.\n\nPassword requirements:\n• Minimum 5 characters\n• Both password fields must match`\n\n\t// Form fields\n\tResetPasswordNewPassword = \"New Password\"\n\tResetPasswordNewPasswordDesc = \"Enter the new administrator password\"\n\tResetPasswordConfirmPassword = \"Confirm Password\"\n\tResetPasswordConfirmPasswordDesc = \"Re-enter the new password to confirm\"\n\n\t// Status messages\n\tResetPasswordNotAvailable = \"PentAGI must be running to reset password\"\n\tResetPasswordAvailable = \"Password reset is available\"\n\tResetPasswordInProgress = \"Resetting password...\"\n\tResetPasswordSuccess = \"Password has been successfully reset\"\n\tResetPasswordErrorPrefix = \"Error: \"\n\n\t// Validation errors\n\tResetPasswordErrorEmptyPassword = \"Password cannot be empty\"\n\tResetPasswordErrorShortPassword = \"Password must be at least 5 characters long\"\n\tResetPasswordErrorMismatch = \"Passwords do not match\"\n\n\t// Help content\n\tResetPasswordHelpContent = `Reset the administrator password for accessing PentAGI.\n\nThis operation:\n• Updates the password for admin@pentagi.com account\n• Sets the user status to 'active'\n• Requires PentAGI database to be accessible\n• Does not affect other user accounts\n\nThe password change takes effect immediately after successful completion.\n\nEnter the same password in both fields and press Enter to confirm the change.`\n)\n\n// Processor Operation Form constants\nconst (\n\t// Dynamic title templates\n\tProcessorOperationFormTitle = \"%s\"\n\tProcessorOperationFormDescription = \"Execute %s operation\"\n\tProcessorOperationFormName = \"%s\"\n\n\t// Common status messages\n\tProcessorOperationNotStarted = \"Ready to execute %s operation\"\n\tProcessorOperationInProgress = \"Executing %s operation...\\n\"\n\tProcessorOperationCompleted = \"%s operation completed successfully\\n\"\n\tProcessorOperationFailed = \"Failed to execute %s operation\"\n\n\t// Confirmation messages\n\tProcessorOperationConfirmation = \"Are you sure you want to %s?\"\n\tProcessorOperationPressEnter = \"Press Enter to %s\"\n\tProcessorOperationPressYN = \"Press Y to confirm, N to cancel\"\n\t// Short notice without hotkeys (for static help panel)\n\tProcessorOperationRequiresConfirmationShort = \"This operation requires confirmation\"\n\t// Additional terminal messages\n\tProcessorOperationCancelled = \"Operation cancelled\"\n\tProcessorOperationUnknown = \"Unknown operation: %s\"\n\n\t// Operation specific messages\n\tProcessorOperationStarting = \"Starting services...\"\n\tProcessorOperationStopping = \"Stopping services...\"\n\tProcessorOperationRestarting = \"Restarting services...\"\n\tProcessorOperationDownloading = \"Downloading images...\"\n\tProcessorOperationUpdating = \"Updating components...\"\n\tProcessorOperationResetting = \"Resetting to factory defaults...\"\n\tProcessorOperationRemoving = \"Removing containers...\"\n\tProcessorOperationPurging = \"Purging all data...\"\n\tProcessorOperationInstalling = \"Installing PentAGI services...\"\n\n\t// Help text templates\n\tProcessorOperationHelpTitle = \"%s Operation\"\n\tProcessorOperationHelpContent = \"This operation will %s.\"\n\tProcessorOperationHelpContentDownload = \"This operation will download %s components.\"\n\tProcessorOperationHelpContentUpdate = \"This operation will update %s components.\"\n\t// Generic title/description/builders for dynamic operations\n\tOperationTitleInstallPentagi = \"Install PentAGI\"\n\tOperationDescInstallPentagi = \"Install and configure PentAGI services\"\n\tOperationTitleDownload = \"Download %s\"\n\tOperationDescDownloadComponents = \"Download %s components\"\n\tOperationTitleUpdate = \"Update %s\"\n\tOperationDescUpdateToLatest = \"Update %s to latest version\"\n\tOperationTitleExecute = \"Execute %s\"\n\tOperationDescExecuteOn = \"Execute %s on %s\"\n\tOperationProgressExecuting = \"Executing %s...\"\n\n\t// Terminal not initialized\n\tProcessorOperationTerminalNotInitialized = \"Terminal is not initialized\"\n)\n\n// Operation-specific help texts\nconst (\n\tProcessorHelpInstallPentagi = `This will:\n• Deploy Docker containers for selected services\n• Configure networking and volumes\n• Start all enabled services\n• Set up monitoring if configured\n\nInstallation will use your current configuration settings.`\n\n\tProcessorHelpStartPentagi = `This will:\n• Core PentAGI API and web interface\n• Configured Langfuse analytics (if enabled)\n• Observability stack (if enabled)\n\nServices will be started in the correct dependency order.`\n\n\tProcessorHelpStopPentagi = `This will:\n• Gracefully shutdown containers\n• Preserve all data and configurations\n• Network connections will be closed\n\nYou can restart services later without losing any data.`\n\n\tProcessorHelpRestartPentagi = `This will:\n• Stop running containers\n• Apply any configuration changes\n• Start services with fresh state\n\nUseful after configuration updates or to resolve issues.`\n\n\tProcessorHelpDownloadWorkerImage = `This large image (6GB+) contains:\n• Kali Linux tools and utilities\n• Security testing frameworks\n• Network analysis software\n\nRequired for pentesting operations.`\n\n\tProcessorHelpUpdateWorkerImage = `This will:\n• Pull the latest pentesting image\n• Update security tools and frameworks\n• Preserve existing worker containers\n\nNote: This is a large download (6GB+).`\n\n\tProcessorHelpUpdatePentagi = `This will:\n• Download latest container images\n• Perform rolling update of services\n• Preserve all data and configurations\n\nServices will be briefly unavailable during update.`\n\n\tProcessorHelpUpdateInstaller = `This will:\n• Download the latest installer binary\n• Replace the current installer\n• Exit for manual restart\n\nYou'll need to restart the installer after update.`\n\n\tProcessorHelpFactoryReset = `⚠️ WARNING: This operation will:\n• Remove all containers and networks\n• Delete all configuration files\n• Clear stored data and volumes\n• Restore default settings\n\nThis action cannot be undone!`\n\n\tProcessorHelpRemovePentagi = `This will:\n• Stop and remove all containers\n• Remove Docker networks\n• Preserve volumes and data\n• Keep configuration files\n\nYou can reinstall later without losing data.`\n\n\tProcessorHelpPurgePentagi = `⚠️ WARNING: This will permanently delete:\n• All containers and images\n• All data volumes\n• All configuration files\n• All stored results\n\nThis action cannot be undone!`\n)\n\n// environment variable descriptions (centralized)\nconst (\n\tEnvDesc_OPEN_AI_KEY = \"OpenAI API Key\"\n\tEnvDesc_OPEN_AI_SERVER_URL = \"OpenAI Server URL\"\n\tEnvDesc_ANTHROPIC_API_KEY = \"Anthropic API Key\"\n\tEnvDesc_ANTHROPIC_SERVER_URL = \"Anthropic Server URL\"\n\tEnvDesc_GEMINI_API_KEY = \"Google Gemini API Key\"\n\tEnvDesc_GEMINI_SERVER_URL = \"Gemini Server URL\"\n\tEnvDesc_BEDROCK_DEFAULT_AUTH = \"AWS Bedrock Use Default Credential Chain\"\n\tEnvDesc_BEDROCK_BEARER_TOKEN = \"AWS Bedrock Bearer Token\"\n\tEnvDesc_BEDROCK_ACCESS_KEY_ID = \"AWS Bedrock Access Key ID\"\n\tEnvDesc_BEDROCK_SECRET_ACCESS_KEY = \"AWS Bedrock Secret Access Key\"\n\tEnvDesc_BEDROCK_SESSION_TOKEN = \"AWS Bedrock Session Token\"\n\tEnvDesc_BEDROCK_REGION = \"AWS Bedrock Region\"\n\tEnvDesc_BEDROCK_SERVER_URL = \"AWS Bedrock Custom Endpoint URL\"\n\tEnvDesc_OLLAMA_SERVER_URL = \"Ollama Server URL\"\n\tEnvDesc_OLLAMA_SERVER_API_KEY = \"Ollama Server API Key (Cloud)\"\n\tEnvDesc_OLLAMA_SERVER_MODEL = \"Ollama Default Model\"\n\tEnvDesc_OLLAMA_SERVER_CONFIG_PATH = \"Ollama Container Config Path\"\n\tEnvDesc_OLLAMA_SERVER_PULL_MODELS_TIMEOUT = \"Ollama Model Pull Timeout\"\n\tEnvDesc_OLLAMA_SERVER_PULL_MODELS_ENABLED = \"Ollama Auto-pull Models\"\n\tEnvDesc_OLLAMA_SERVER_LOAD_MODELS_ENABLED = \"Ollama Load Models List\"\n\tEnvDesc_DEEPSEEK_API_KEY = \"DeepSeek API Key\"\n\tEnvDesc_DEEPSEEK_SERVER_URL = \"DeepSeek Server URL\"\n\tEnvDesc_DEEPSEEK_PROVIDER = \"DeepSeek Provider Name Prefix (for LiteLLM, e.g., 'deepseek')\"\n\tEnvDesc_GLM_API_KEY = \"GLM API Key\"\n\tEnvDesc_GLM_SERVER_URL = \"GLM Server URL\"\n\tEnvDesc_GLM_PROVIDER = \"GLM Provider Name Prefix (for LiteLLM, e.g., 'zai')\"\n\tEnvDesc_KIMI_API_KEY = \"Kimi API Key\"\n\tEnvDesc_KIMI_SERVER_URL = \"Kimi Server URL\"\n\tEnvDesc_KIMI_PROVIDER = \"Kimi Provider Name Prefix (for LiteLLM, e.g., 'moonshot')\"\n\tEnvDesc_QWEN_API_KEY = \"Qwen API Key\"\n\tEnvDesc_QWEN_SERVER_URL = \"Qwen Server URL\"\n\tEnvDesc_QWEN_PROVIDER = \"Qwen Provider Name Prefix (for LiteLLM, e.g., 'dashscope')\"\n\tEnvDesc_LLM_SERVER_URL = \"Custom LLM Server URL\"\n\tEnvDesc_LLM_SERVER_KEY = \"Custom LLM API Key\"\n\tEnvDesc_LLM_SERVER_MODEL = \"Custom LLM Model\"\n\tEnvDesc_LLM_SERVER_CONFIG_PATH = \"Custom LLM Container Config Path\"\n\tEnvDesc_LLM_SERVER_LEGACY_REASONING = \"Custom LLM Legacy Reasoning\"\n\tEnvDesc_LLM_SERVER_PRESERVE_REASONING = \"Custom LLM Preserve Reasoning Content\"\n\tEnvDesc_LLM_SERVER_PROVIDER = \"Custom LLM Provider Name\"\n\n\tEnvDesc_LANGFUSE_LISTEN_IP = \"Langfuse Listen IP\"\n\tEnvDesc_LANGFUSE_LISTEN_PORT = \"Langfuse Listen Port\"\n\tEnvDesc_LANGFUSE_BASE_URL = \"Langfuse Base URL\"\n\tEnvDesc_LANGFUSE_PROJECT_ID = \"Langfuse Project ID\"\n\tEnvDesc_LANGFUSE_PUBLIC_KEY = \"Langfuse Public Key\"\n\tEnvDesc_LANGFUSE_SECRET_KEY = \"Langfuse Secret Key\"\n\n\t// langfuse init variables\n\tEnvDesc_LANGFUSE_INIT_PROJECT_ID = \"Langfuse Init Project ID\"\n\tEnvDesc_LANGFUSE_INIT_PROJECT_PUBLIC_KEY = \"Langfuse Init Project Public Key\"\n\tEnvDesc_LANGFUSE_INIT_PROJECT_SECRET_KEY = \"Langfuse Init Project Secret Key\"\n\tEnvDesc_LANGFUSE_INIT_USER_EMAIL = \"Langfuse Init User Email\"\n\tEnvDesc_LANGFUSE_INIT_USER_NAME = \"Langfuse Init User Name\"\n\tEnvDesc_LANGFUSE_INIT_USER_PASSWORD = \"Langfuse Init User Password\"\n\n\tEnvDesc_LANGFUSE_OTEL_EXPORTER_OTLP_ENDPOINT = \"Langfuse OTLP endpoint for OpenTelemetry exporter\"\n\n\tEnvDesc_GRAFANA_LISTEN_IP = \"Grafana Listen IP\"\n\tEnvDesc_GRAFANA_LISTEN_PORT = \"Grafana Listen Port\"\n\tEnvDesc_OTEL_GRPC_LISTEN_IP = \"OTel gRPC Listen IP\"\n\tEnvDesc_OTEL_GRPC_LISTEN_PORT = \"OTel gRPC Listen Port\"\n\tEnvDesc_OTEL_HTTP_LISTEN_IP = \"OTel HTTP Listen IP\"\n\tEnvDesc_OTEL_HTTP_LISTEN_PORT = \"OTel HTTP Listen Port\"\n\tEnvDesc_OTEL_HOST = \"OpenTelemetry Host\"\n\n\tEnvDesc_SUMMARIZER_PRESERVE_LAST = \"Summarizer Preserve Last\"\n\tEnvDesc_SUMMARIZER_USE_QA = \"Summarizer Use QA\"\n\tEnvDesc_SUMMARIZER_SUM_MSG_HUMAN_IN_QA = \"Summarizer Human in QA\"\n\tEnvDesc_SUMMARIZER_LAST_SEC_BYTES = \"Summarizer Last Section Bytes\"\n\tEnvDesc_SUMMARIZER_MAX_BP_BYTES = \"Summarizer Max BP Bytes\"\n\tEnvDesc_SUMMARIZER_MAX_QA_BYTES = \"Summarizer Max QA Bytes\"\n\tEnvDesc_SUMMARIZER_MAX_QA_SECTIONS = \"Summarizer Max QA Sections\"\n\tEnvDesc_SUMMARIZER_KEEP_QA_SECTIONS = \"Summarizer Keep QA Sections\"\n\n\tEnvDesc_ASSISTANT_SUMMARIZER_PRESERVE_LAST = \"Assistant Summarizer Preserve Last\"\n\tEnvDesc_ASSISTANT_SUMMARIZER_LAST_SEC_BYTES = \"Assistant Summarizer Last Section Bytes\"\n\tEnvDesc_ASSISTANT_SUMMARIZER_MAX_BP_BYTES = \"Assistant Summarizer Max BP Bytes\"\n\tEnvDesc_ASSISTANT_SUMMARIZER_MAX_QA_BYTES = \"Assistant Summarizer Max QA Bytes\"\n\tEnvDesc_ASSISTANT_SUMMARIZER_MAX_QA_SECTIONS = \"Assistant Summarizer Max QA Sections\"\n\tEnvDesc_ASSISTANT_SUMMARIZER_KEEP_QA_SECTIONS = \"Assistant Summarizer Keep QA Sections\"\n\n\tEnvDesc_EMBEDDING_PROVIDER = \"Embedding Provider\"\n\tEnvDesc_EMBEDDING_URL = \"Embedding URL\"\n\tEnvDesc_EMBEDDING_KEY = \"Embedding API Key\"\n\tEnvDesc_EMBEDDING_MODEL = \"Embedding Model\"\n\tEnvDesc_EMBEDDING_BATCH_SIZE = \"Embedding Batch Size\"\n\tEnvDesc_EMBEDDING_STRIP_NEW_LINES = \"Embedding Strip New Lines\"\n\n\tEnvDesc_ASK_USER = \"Human-in-the-loop\"\n\n\tEnvDesc_ASSISTANT_USE_AGENTS = \"Enable multi-agent mode for assistant\"\n\n\tEnvDesc_EXECUTION_MONITOR_ENABLED = \"Enable Execution Monitoring (beta)\"\n\tEnvDesc_EXECUTION_MONITOR_SAME_TOOL_LIMIT = \"Same Tool Call Threshold\"\n\tEnvDesc_EXECUTION_MONITOR_TOTAL_TOOL_LIMIT = \"Total Tool Call Threshold\"\n\tEnvDesc_MAX_GENERAL_AGENT_TOOL_CALLS = \"Max Tool Calls for General Agents\"\n\tEnvDesc_MAX_LIMITED_AGENT_TOOL_CALLS = \"Max Tool Calls for Limited Agents\"\n\tEnvDesc_AGENT_PLANNING_STEP_ENABLED = \"Enable Task Planning (beta)\"\n\n\tEnvDesc_SCRAPER_PUBLIC_URL = \"Scraper Public URL\"\n\tEnvDesc_SCRAPER_PRIVATE_URL = \"Scraper Private URL\"\n\tEnvDesc_LOCAL_SCRAPER_USERNAME = \"Local Scraper Username\"\n\tEnvDesc_LOCAL_SCRAPER_PASSWORD = \"Local Scraper Password\"\n\tEnvDesc_LOCAL_SCRAPER_MAX_CONCURRENT_SESSIONS = \"Scraper Max Concurrent Sessions\"\n\n\tEnvDesc_DUCKDUCKGO_ENABLED = \"DuckDuckGo Search\"\n\tEnvDesc_DUCKDUCKGO_REGION = \"DuckDuckGo Region\"\n\tEnvDesc_DUCKDUCKGO_SAFESEARCH = \"DuckDuckGo Safe Search\"\n\tEnvDesc_DUCKDUCKGO_TIME_RANGE = \"DuckDuckGo Time Range\"\n\tEnvDesc_SPLOITUS_ENABLED = \"Sploitus Search\"\n\tEnvDesc_PERPLEXITY_API_KEY = \"Perplexity API Key\"\n\tEnvDesc_TAVILY_API_KEY = \"Tavily API Key\"\n\tEnvDesc_TRAVERSAAL_API_KEY = \"Traversaal API Key\"\n\tEnvDesc_GOOGLE_API_KEY = \"Google Search API Key\"\n\tEnvDesc_GOOGLE_CX_KEY = \"Google Search CX Key\"\n\tEnvDesc_GOOGLE_LR_KEY = \"Google Search LR Key\"\n\n\tEnvDesc_DOCKER_INSIDE = \"Docker Inside Container\"\n\tEnvDesc_DOCKER_NET_ADMIN = \"Docker Network Admin\"\n\tEnvDesc_DOCKER_SOCKET = \"Docker Socket Path\"\n\tEnvDesc_DOCKER_NETWORK = \"Docker Network\"\n\tEnvDesc_DOCKER_PUBLIC_IP = \"Docker Public IP\"\n\tEnvDesc_DOCKER_WORK_DIR = \"Docker Work Directory\"\n\tEnvDesc_DOCKER_DEFAULT_IMAGE = \"Docker Default Image\"\n\tEnvDesc_DOCKER_DEFAULT_IMAGE_FOR_PENTEST = \"Docker Pentest Image\"\n\tEnvDesc_DOCKER_HOST = \"Docker Host\"\n\tEnvDesc_DOCKER_TLS_VERIFY = \"Docker TLS Verify\"\n\tEnvDesc_DOCKER_CERT_PATH = \"Docker Certificate Path\"\n\n\tEnvDesc_LICENSE_KEY = \"PentAGI License Key\"\n\tEnvDesc_PENTAGI_LISTEN_IP = \"PentAGI Server Host\"\n\tEnvDesc_PENTAGI_LISTEN_PORT = \"PentAGI Server Port\"\n\tEnvDesc_PUBLIC_URL = \"PentAGI Public URL\"\n\tEnvDesc_CORS_ORIGINS = \"PentAGI CORS Origins\"\n\tEnvDesc_COOKIE_SIGNING_SALT = \"PentAGI Cookie Signing Salt\"\n\tEnvDesc_PROXY_URL = \"HTTP/HTTPS Proxy URL\"\n\tEnvDesc_HTTP_CLIENT_TIMEOUT = \"HTTP Client Timeout (seconds)\"\n\tEnvDesc_EXTERNAL_SSL_CA_PATH = \"Custom CA Certificate Path\"\n\tEnvDesc_EXTERNAL_SSL_INSECURE = \"Skip SSL Verification\"\n\tEnvDesc_PENTAGI_SSL_DIR = \"PentAGI SSL Directory\"\n\tEnvDesc_PENTAGI_DATA_DIR = \"PentAGI Data Directory\"\n\tEnvDesc_PENTAGI_DOCKER_SOCKET = \"Mount Docker Socket Path\"\n\tEnvDesc_PENTAGI_DOCKER_CERT_PATH = \"Mount Docker Certificate Path\"\n\tEnvDesc_PENTAGI_LLM_SERVER_CONFIG_PATH = \"Custom LLM Host Config Path\"\n\tEnvDesc_PENTAGI_OLLAMA_SERVER_CONFIG_PATH = \"Ollama Host Config Path\"\n\n\tEnvDesc_STATIC_DIR = \"Frontend Static Directory\"\n\tEnvDesc_STATIC_URL = \"Frontend Static URL\"\n\tEnvDesc_SERVER_PORT = \"Backend Server Port\"\n\tEnvDesc_SERVER_HOST = \"Backend Server Host\"\n\tEnvDesc_SERVER_SSL_CRT = \"Backend Server SSL Certificate Path\"\n\tEnvDesc_SERVER_SSL_KEY = \"Backend Server SSL Key Path\"\n\tEnvDesc_SERVER_USE_SSL = \"Backend Server Use SSL\"\n\n\tEnvDesc_PERPLEXITY_MODEL = \"Perplexity Model\"\n\tEnvDesc_PERPLEXITY_CONTEXT_SIZE = \"Perplexity Context Size\"\n\n\tEnvDesc_SEARXNG_URL = \"Searxng Search URL\"\n\tEnvDesc_SEARXNG_CATEGORIES = \"Searxng Search Categories\"\n\tEnvDesc_SEARXNG_LANGUAGE = \"Searxng Search Language\"\n\tEnvDesc_SEARXNG_SAFESEARCH = \"Searxng Safe Search\"\n\tEnvDesc_SEARXNG_TIME_RANGE = \"Searxng Time Range\"\n\tEnvDesc_SEARXNG_TIMEOUT = \"Searxng Timeout\"\n\n\tEnvDesc_OAUTH_GOOGLE_CLIENT_ID = \"OAuth Google Client ID\"\n\tEnvDesc_OAUTH_GOOGLE_CLIENT_SECRET = \"OAuth Google Client Secret\"\n\tEnvDesc_OAUTH_GITHUB_CLIENT_ID = \"OAuth GitHub Client ID\"\n\tEnvDesc_OAUTH_GITHUB_CLIENT_SECRET = \"OAuth GitHub Client Secret\"\n\n\tEnvDesc_LANGFUSE_EE_LICENSE_KEY = \"Langfuse Enterprise License Key\"\n\tEnvDesc_PENTAGI_POSTGRES_PASSWORD = \"PentAGI PostgreSQL Password\"\n\n\tEnvDesc_GRAPHITI_URL = \"Graphiti Server URL\"\n\tEnvDesc_GRAPHITI_TIMEOUT = \"Graphiti Request Timeout\"\n\tEnvDesc_GRAPHITI_MODEL_NAME = \"Graphiti Extraction Model\"\n\tEnvDesc_NEO4J_USER = \"Neo4j Username\"\n\tEnvDesc_NEO4J_DATABASE = \"Neo4j Database Name\"\n\tEnvDesc_NEO4J_PASSWORD = \"Neo4j Database Password\"\n)\n\n// dynamic, contextual sections used in processor operation forms\nconst (\n\t// section headers\n\tProcessorSectionCurrentState = \"Current state\"\n\tProcessorSectionPlanned = \"Planned actions\"\n\tProcessorSectionEffects = \"Effects\"\n\n\t// component labels\n\tProcessorComponentPentagi = \"PentAGI\"\n\tProcessorComponentLangfuse = \"Langfuse\"\n\tProcessorComponentObservability = \"Observability\"\n\n\tProcessorComponentWorkerImage = \"worker image\"\n\tProcessorComponentComposeStacks = \"compose stacks\"\n\tProcessorComponentDefaultFiles = \"default files\"\n\tProcessorItemComposeFiles = \"compose files\"\n\tProcessorItemComposeStacksImagesVolumes = \"compose stacks, images, volumes\"\n\n\t// common states\n\tProcessorStateInstalled = \"installed\"\n\tProcessorStateMissing = \"not installed\"\n\tProcessorStateRunning = \"running\"\n\tProcessorStateStopped = \"stopped\"\n\tProcessorStateEmbedded = \"embedded\"\n\tProcessorStateExternal = \"external\"\n\tProcessorStateConnected = \"connected\"\n\tProcessorStateDisabled = \"disabled\"\n\tProcessorStateUnknown = \"unknown\"\n\n\t// planned action bullet prefixes\n\tPlannedWillStart = \"will start:\"\n\tPlannedWillStop = \"will stop:\"\n\tPlannedWillRestart = \"will restart:\"\n\tPlannedWillUpdate = \"will update:\"\n\tPlannedWillSkip = \"will skip:\"\n\tPlannedWillRemove = \"will remove:\"\n\tPlannedWillPurge = \"will purge:\"\n\tPlannedWillDownload = \"will download:\"\n\tPlannedWillRestore = \"will restore:\"\n\n\t// effect notes per operation (concise and practical)\n\tEffectsStart = \"PentAGI web UI becomes available. Background services are brought online in the required order.\"\n\tEffectsStop = \"Web UI becomes unavailable. In-progress flows pause safely. When you start PentAGI again, flows resume automatically. A small portion of the current agent step may be lost.\"\n\tEffectsRestart = \"Services stop and start again with a clean state. Brief downtime is expected. Flows resume automatically afterwards.\"\n\tEffectsUpdateAll = \"Images are pulled and services are recreated where needed. External or disabled components are skipped. Temporary downtime is expected.\"\n\tEffectsDownloadWorker = \"Running worker containers are not touched. New flows will use the downloaded image. To switch an existing flow to the new image, finish the flow and start a new task or create a new assistant.\"\n\tEffectsUpdateWorker = \"Pulls latest worker image. Running worker containers keep using the old image; new containers will use the updated one.\"\n\tEffectsUpdateInstaller = \"The installer binary will be updated and the app will exit. Start the installer again to continue.\"\n\tEffectsFactoryReset = \"Removes containers, volumes and networks, restores default .env and embedded files. Produces a clean baseline. This action cannot be undone.\"\n\tEffectsRemove = \"Stops and removes containers but keeps volumes and images. Data is preserved. Web UI becomes unavailable until you start again.\"\n\tEffectsPurge = \"Complete cleanup: containers, images, volumes and configuration files are deleted. Irreversible.\"\n\tEffectsInstall = \"Required files are created and services are started. External components are detected and skipped.\"\n)\n"}}, "pentagi-main/pentagi-main/backend/cmd/installer/wizard/controller/controller.go": {"148f693e1ee122f37e9f842d23481503fe8c48f5": {"t": 1786068292.3018568, "n": 94987, "h": "b7da0e5a02e4dea9", "c": "package controller\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"slices\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"pentagi/cmd/installer/checker\"\n\t\"pentagi/cmd/installer/files\"\n\t\"pentagi/cmd/installer/loader\"\n\t\"pentagi/cmd/installer/state\"\n\t\"pentagi/cmd/installer/wizard/locale\"\n)\n\nconst (\n\tEmbeddedLLMConfigsPath = \"providers-configs\"\n\tDefaultDockerCertPath = \"/opt/pentagi/docker/ssl\"\n\tDefaultCustomConfigsPath = \"/opt/pentagi/conf/custom.provider.yml\"\n\tDefaultOllamaConfigsPath = \"/opt/pentagi/conf/ollama.provider.yml\"\n\tDefaultLLMConfigsPath = \"/opt/pentagi/conf/\"\n\tDefaultScraperBaseURL = \"https://scraper/\"\n\tDefaultScraperDomain = \"scraper\"\n\tDefaultScraperSchema = \"https\"\n)\n\ntype Controller interface {\n\tGetState() state.State\n\tGetChecker() *checker.CheckResult\n\n\tstate.State\n\n\tLLMProviderConfigController\n\tLangfuseConfigController\n\tGraphitiConfigController\n\tObservabilityConfigController\n\tSummarizerConfigController\n\tEmbedderConfigController\n\tAIAgentsConfigController\n\tScraperConfigController\n\tSearchEnginesConfigController\n\tDockerConfigController\n\tChangesConfigController\n\tServerSettingsConfigController\n}\n\ntype LLMProviderConfigController interface {\n\tGetLLMProviders() map[string]*LLMProviderConfig\n\tGetLLMProviderConfig(providerID string) *LLMProviderConfig\n\tUpdateLLMProviderConfig(providerID string, config *LLMProviderConfig) error\n\tResetLLMProviderConfig(providerID string) map[string]*LLMProviderConfig\n}\n\ntype LangfuseConfigController interface {\n\tGetLangfuseConfig() *LangfuseConfig\n\tUpdateLangfuseConfig(config *LangfuseConfig) error\n\tResetLangfuseConfig() *LangfuseConfig\n}\n\ntype GraphitiConfigController interface {\n\tGetGraphitiConfig() *GraphitiConfig\n\tUpdateGraphitiConfig(config *GraphitiConfig) error\n\tResetGraphitiConfig() *GraphitiConfig\n}\n\ntype ObservabilityConfigController interface {\n\tGetObservabilityConfig() *ObservabilityConfig\n\tUpdateObservabilityConfig(config *ObservabilityConfig) error\n\tResetObservabilityConfig() *ObservabilityConfig\n}\n\ntype SummarizerConfigController interface {\n\tGetSummarizerConfig(summarizerType SummarizerType) *SummarizerConfig\n\tUpdateSummarizerConfig(config *SummarizerConfig) error\n\tResetSummarizerConfig(summarizerType SummarizerType) *SummarizerConfig\n}\n\ntype EmbedderConfigController interface {\n\tGetEmbedderConfig() *EmbedderConfig\n\tUpdateEmbedderConfig(config *EmbedderConfig) error\n\tResetEmbedderConfig() *EmbedderConfig\n}\n\ntype AIAgentsConfigController interface {\n\tGetAIAgentsConfig() *AIAgentsConfig\n\tUpdateAIAgentsConfig(config *AIAgentsConfig) error\n\tResetAIAgentsConfig() *AIAgentsConfig\n}\n\ntype ScraperConfigController interface {\n\tGetScraperConfig() *ScraperConfig\n\tUpdateScraperConfig(config *ScraperConfig) error\n\tResetScraperConfig() *ScraperConfig\n}\n\ntype SearchEnginesConfigController interface {\n\tGetSearchEnginesConfig() *SearchEnginesConfig\n\tUpdateSearchEnginesConfig(config *SearchEnginesConfig) error\n\tResetSearchEnginesConfig() *SearchEnginesConfig\n}\n\ntype DockerConfigController interface {\n\tGetDockerConfig() *DockerConfig\n\tUpdateDockerConfig(config *DockerConfig) error\n\tResetDockerConfig() *DockerConfig\n}\n\ntype ChangesConfigController interface {\n\tGetApplyChangesConfig() *ApplyChangesConfig\n}\n\ntype ServerSettingsConfigController interface {\n\tGetServerSettingsConfig() *ServerSettingsConfig\n\tUpdateServerSettingsConfig(config *ServerSettingsConfig) error\n\tResetServerSettingsConfig() *ServerSettingsConfig\n}\n\n// controller bridges TUI models with the state package\ntype controller struct {\n\tfiles files.Files\n\tchecker checker.CheckResult\n\tstate.State\n}\n\nfunc NewController(state state.State, files files.Files, checker checker.CheckResult) Controller {\n\treturn &controller{\n\t\tfiles: files,\n\t\tchecker: checker,\n\t\tState: state,\n\t}\n}\n\n// GetState returns the underlying state interface for processor integration\nfunc (c *controller) GetState() state.State {\n\treturn c.State\n}\n\n// GetChecker returns the checker result for processor integration\nfunc (c *controller) GetChecker() *checker.CheckResult {\n\treturn &c.checker\n}\n\n// LLMProviderConfig represents LLM provider configuration\ntype LLMProviderConfig struct {\n\t// dependent on the provider type\n\tName string\n\n\t// direct form field mappings using loader.EnvVar\n\t// these fields directly correspond to environment variables and form inputs (not computed)\n\tBaseURL loader.EnvVar // OPEN_AI_SERVER_URL | ANTHROPIC_SERVER_URL | GEMINI_SERVER_URL | BEDROCK_SERVER_URL | OLLAMA_SERVER_URL | DEEPSEEK_SERVER_URL | GLM_SERVER_URL | KIMI_SERVER_URL | QWEN_SERVER_URL | LLM_SERVER_URL\n\tAPIKey loader.EnvVar // OPEN_AI_KEY | ANTHROPIC_API_KEY | GEMINI_API_KEY | LLM_SERVER_KEY | DEEPSEEK_API_KEY | GLM_API_KEY | KIMI_API_KEY | QWEN_API_KEY | OLLAMA_SERVER_API_KEY\n\tModel loader.EnvVar // LLM_SERVER_MODEL\n\t// AWS Bedrock specific fields\n\tDefaultAuth loader.EnvVar // BEDROCK_DEFAULT_AUTH\n\tBearerToken loader.EnvVar // BEDROCK_BEARER_TOKEN\n\tAccessKey loader.EnvVar // BEDROCK_ACCESS_KEY_ID\n\tSecretKey loader.EnvVar // BEDROCK_SECRET_ACCESS_KEY\n\tSessionToken loader.EnvVar // BEDROCK_SESSION_TOKEN\n\tRegion loader.EnvVar // BEDROCK_REGION\n\t// Ollama and Custom specific fields\n\tConfigPath loader.EnvVar // OLLAMA_SERVER_CONFIG_PATH | LLM_SERVER_CONFIG_PATH\n\tHostConfigPath loader.EnvVar // PENTAGI_OLLAMA_SERVER_CONFIG_PATH | PENTAGI_LLM_SERVER_CONFIG_PATH\n\tLegacyReasoning loader.EnvVar // LLM_SERVER_LEGACY_REASONING\n\tPreserveReasoning loader.EnvVar // LLM_SERVER_PRESERVE_REASONING\n\t// Custom specific fields\n\tProviderName loader.EnvVar // LLM_SERVER_PROVIDER | DEEPSEEK_PROVIDER | GLM_PROVIDER | KIMI_PROVIDER | QWEN_PROVIDER\n\t// Ollama specific fields\n\tPullTimeout loader.EnvVar // OLLAMA_SERVER_PULL_MODELS_TIMEOUT\n\tPullEnabled loader.EnvVar // OLLAMA_SERVER_PULL_MODELS_ENABLED\n\tLoadModelsEnabled loader.EnvVar // OLLAMA_SERVER_LOAD_MODELS_ENABLED\n\n\t// computed fields (not directly mapped to env vars)\n\tConfigured bool\n\n\t// local path to the embedded LLM config files inside the container\n\tEmbeddedLLMConfigsPath []string\n}\n\nfunc GetEmbeddedLLMConfigsPath(files files.Files) []string {\n\tprovidersConfigsPath := make([]string, 0)\n\tif confFiles, err := files.List(EmbeddedLLMConfigsPath); err == nil {\n\t\tfor _, confFile := range confFiles {\n\t\t\tconfPath := DefaultLLMConfigsPath + strings.TrimPrefix(confFile, EmbeddedLLMConfigsPath+\"/\")\n\t\t\tprovidersConfigsPath = append(providersConfigsPath, confPath)\n\t\t}\n\t\tsort.Strings(providersConfigsPath)\n\t}\n\n\treturn providersConfigsPath\n}\n\n// GetLLMProviders returns configured LLM providers\nfunc (c *controller) GetLLMProviders() map[string]*LLMProviderConfig {\n\treturn map[string]*LLMProviderConfig{\n\t\t\"openai\": c.GetLLMProviderConfig(\"openai\"),\n\t\t\"anthropic\": c.GetLLMProviderConfig(\"anthropic\"),\n\t\t\"gemini\": c.GetLLMProviderConfig(\"gemini\"),\n\t\t\"bedrock\": c.GetLLMProviderConfig(\"bedrock\"),\n\t\t\"ollama\": c.GetLLMProviderConfig(\"ollama\"),\n\t\t\"deepseek\": c.GetLLMProviderConfig(\"deepseek\"),\n\t\t\"glm\": c.GetLLMProviderConfig(\"glm\"),\n\t\t\"kimi\": c.GetLLMProviderConfig(\"kimi\"),\n\t\t\"qwen\": c.GetLLMProviderConfig(\"qwen\"),\n\t\t\"custom\": c.GetLLMProviderConfig(\"custom\"),\n\t}\n}\n\n// GetLLMProviderConfig returns the current LLM provider configuration\nfunc (c *controller) GetLLMProviderConfig(providerID string) *LLMProviderConfig {\n\tprovidersConfigsPath := GetEmbeddedLLMConfigsPath(c.files)\n\tproviderConfig := &LLMProviderConfig{\n\t\tName: \"Unknown\",\n\t\tEmbeddedLLMConfigsPath: providersConfigsPath,\n\t}\n\n\tswitch providerID {\n\tcase \"openai\":\n\t\tproviderConfig.Name = \"OpenAI\"\n\t\tproviderConfig.APIKey, _ = c.GetVar(\"OPEN_AI_KEY\")\n\t\tproviderConfig.BaseURL, _ = c.GetVar(\"OPEN_AI_SERVER_URL\")\n\t\tproviderConfig.Configured = providerConfig.APIKey.Value != \"\"\n\n\tcase \"anthropic\":\n\t\tproviderConfig.Name = \"Anthropic\"\n\t\tproviderConfig.APIKey, _ = c.GetVar(\"ANTHROPIC_API_KEY\")\n\t\tproviderConfig.BaseURL, _ = c.GetVar(\"ANTHROPIC_SERVER_URL\")\n\t\tproviderConfig.Configured = providerConfig.APIKey.Value != \"\"\n\n\tcase \"gemini\":\n\t\tproviderConfig.Name = \"Google Gemini\"\n\t\tproviderConfig.APIKey, _ = c.GetVar(\"GEMINI_API_KEY\")\n\t\tproviderConfig.BaseURL, _ = c.GetVar(\"GEMINI_SERVER_URL\")\n\t\tproviderConfig.Configured = providerConfig.APIKey.Value != \"\"\n\n\tcase \"bedrock\":\n\t\tproviderConfig.Name = \"AWS Bedrock\"\n\t\tproviderConfig.Region, _ = c.GetVar(\"BEDROCK_REGION\")\n\t\tproviderConfig.DefaultAuth, _ = c.GetVar(\"BEDROCK_DEFAULT_AUTH\")\n\t\tproviderConfig.BearerToken, _ = c.GetVar(\"BEDROCK_BEARER_TOKEN\")\n\t\tproviderConfig.AccessKey, _ = c.GetVar(\"BEDROCK_ACCESS_KEY_ID\")\n\t\tproviderConfig.SecretKey, _ = c.GetVar(\"BEDROCK_SECRET_ACCESS_KEY\")\n\t\tproviderConfig.SessionToken, _ = c.GetVar(\"BEDROCK_SESSION_TOKEN\")\n\t\tproviderConfig.BaseURL, _ = c.GetVar(\"BEDROCK_SERVER_URL\")\n\t\t// Configured if any of three auth methods is set: DefaultAuth, BearerToken, or AccessKey+SecretKey\n\t\tproviderConfig.Configured = providerConfig.DefaultAuth.Value == \"true\" ||\n\t\t\tproviderConfig.BearerToken.Value != \"\" ||\n\t\t\t(providerConfig.AccessKey.Value != \"\" && providerConfig.SecretKey.Value != \"\")\n\n\tcase \"ollama\":\n\t\tproviderConfig.Name = \"Ollama\"\n\t\tproviderConfig.BaseURL, _ = c.GetVar(\"OLLAMA_SERVER_URL\")\n\t\tproviderConfig.APIKey, _ = c.GetVar(\"OLLAMA_SERVER_API_KEY\")\n\t\tproviderConfig.ConfigPath, _ = c.GetVar(\"OLLAMA_SERVER_CONFIG_PATH\")\n\t\tproviderConfig.HostConfigPath, _ = c.GetVar(\"PENTAGI_OLLAMA_SERVER_CONFIG_PATH\")\n\t\tif slices.Contains(providersConfigsPath, providerConfig.ConfigPath.Value) {\n\t\t\tproviderConfig.HostConfigPath.Value = providerConfig.ConfigPath.Value\n\t\t}\n\t\tproviderConfig.Model, _ = c.GetVar(\"OLLAMA_SERVER_MODEL\")\n\t\tproviderConfig.PullTimeout, _ = c.GetVar(\"OLLAMA_SERVER_PULL_MODELS_TIMEOUT\")\n\t\tproviderConfig.PullEnabled, _ = c.GetVar(\"OLLAMA_SERVER_PULL_MODELS_ENABLED\")\n\t\tproviderConfig.LoadModelsEnabled, _ = c.GetVar(\"OLLAMA_SERVER_LOAD_MODELS_ENABLED\")\n\t\tproviderConfig.Configured = providerConfig.BaseURL.Value != \"\"\n\n\tcase \"deepseek\":\n\t\tproviderConfig.Name = \"DeepSeek\"\n\t\tproviderConfig.APIKey, _ = c.GetVar(\"DEEPSEEK_API_KEY\")\n\t\tproviderConfig.BaseURL, _ = c.GetVar(\"DEEPSEEK_SERVER_URL\")\n\t\tproviderConfig.ProviderName, _ = c.GetVar(\"DEEPSEEK_PROVIDER\")\n\t\tproviderConfig.Configured = providerConfig.APIKey.Value != \"\"\n\n\tcase \"glm\":\n\t\tproviderConfig.Name = \"GLM\"\n\t\tproviderConfig.APIKey, _ = c.GetVar(\"GLM_API_KEY\")\n\t\tproviderConfig.BaseURL, _ = c.GetVar(\"GLM_SERVER_URL\")\n\t\tproviderConfig.ProviderName, _ = c.GetVar(\"GLM_PROVIDER\")\n\t\tproviderConfig.Configured = providerConfig.APIKey.Value != \"\"\n\n\tcase \"kimi\":\n\t\tproviderConfig.Name = \"Kimi\"\n\t\tproviderConfig.APIKey, _ = c.GetVar(\"KIMI_API_KEY\")\n\t\tproviderConfig.BaseURL, _ = c.GetVar(\"KIMI_SERVER_URL\")\n\t\tproviderConfig.ProviderName, _ = c.GetVar(\"KIMI_PROVIDER\")\n\t\tproviderConfig.Configured = providerConfig.APIKey.Value != \"\"\n\n\tcase \"qwen\":\n\t\tproviderConfig.Name = \"Qwen\"\n\t\tproviderConfig.APIKey, _ = c.GetVar(\"QWEN_API_KEY\")\n\t\tproviderConfig.BaseURL, _ = c.GetVar(\"QWEN_SERVER_URL\")\n\t\tproviderConfig.ProviderName, _ = c.GetVar(\"QWEN_PROVIDER\")\n\t\tproviderConfig.Configured = providerConfig.APIKey.Value != \"\"\n\n\tcase \"custom\":\n\t\tproviderConfig.Name = \"Custom\"\n\t\tproviderConfig.BaseURL, _ = c.GetVar(\"LLM_SERVER_URL\")\n\t\tproviderConfig.APIKey, _ = c.GetVar(\"LLM_SERVER_KEY\")\n\t\tproviderConfig.Model, _ = c.GetVar(\"LLM_SERVER_MODEL\")\n\t\tproviderConfig.ConfigPath, _ = c.GetVar(\"LLM_SERVER_CONFIG_PATH\")\n\t\tproviderConfig.HostConfigPath, _ = c.GetVar(\"PENTAGI_LLM_SERVER_CONFIG_PATH\")\n\t\tif slices.Contains(providersConfigsPath, providerConfig.ConfigPath.Value) {\n\t\t\tproviderConfig.HostConfigPath.Value = providerConfig.ConfigPath.Value\n\t\t}\n\t\tproviderConfig.LegacyReasoning, _ = c.GetVar(\"LLM_SERVER_LEGACY_REASONING\")\n\t\tproviderConfig.PreserveReasoning, _ = c.GetVar(\"LLM_SERVER_PRESERVE_REASONING\")\n\t\tproviderConfig.ProviderName, _ = c.GetVar(\"LLM_SERVER_PROVIDER\")\n\t\tproviderConfig.Configured = providerConfig.BaseURL.Value != \"\" && providerConfig.APIKey.Value != \"\" &&\n\t\t\t(providerConfig.Model.Value != \"\" || providerConfig.ConfigPath.Value != \"\")\n\t}\n\n\treturn providerConfig\n}\n\n// UpdateLLMProviderConfig updates a specific LLM provider configuration\nfunc (c *controller) UpdateLLMProviderConfig(providerID string, config *LLMProviderConfig) error {\n\tswitch providerID {\n\tcase \"openai\":\n\t\tif err := c.SetVar(config.APIKey.Name, config.APIKey.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.APIKey.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.BaseURL.Name, config.BaseURL.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.BaseURL.Name, err)\n\t\t}\n\n\tcase \"anthropic\":\n\t\tif err := c.SetVar(config.APIKey.Name, config.APIKey.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.APIKey.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.BaseURL.Name, config.BaseURL.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.BaseURL.Name, err)\n\t\t}\n\n\tcase \"gemini\":\n\t\tif err := c.SetVar(config.APIKey.Name, config.APIKey.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.APIKey.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.BaseURL.Name, config.BaseURL.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.BaseURL.Name, err)\n\t\t}\n\n\tcase \"bedrock\":\n\t\tif err := c.SetVar(config.Region.Name, config.Region.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.Region.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.DefaultAuth.Name, config.DefaultAuth.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.DefaultAuth.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.BearerToken.Name, config.BearerToken.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.BearerToken.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.AccessKey.Name, config.AccessKey.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.AccessKey.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.SecretKey.Name, config.SecretKey.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.SecretKey.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.SessionToken.Name, config.SessionToken.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.SessionToken.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.BaseURL.Name, config.BaseURL.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.BaseURL.Name, err)\n\t\t}\n\n\tcase \"ollama\":\n\t\tif err := c.SetVar(config.BaseURL.Name, config.BaseURL.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.BaseURL.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.APIKey.Name, config.APIKey.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.APIKey.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.Model.Name, config.Model.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.Model.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.PullTimeout.Name, config.PullTimeout.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.PullTimeout.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.PullEnabled.Name, config.PullEnabled.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.PullEnabled.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.LoadModelsEnabled.Name, config.LoadModelsEnabled.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.LoadModelsEnabled.Name, err)\n\t\t}\n\n\t\tvar containerPath, hostPath string\n\t\tif config.HostConfigPath.Value != \"\" {\n\t\t\tif slices.Contains(config.EmbeddedLLMConfigsPath, config.HostConfigPath.Value) {\n\t\t\t\tcontainerPath = config.HostConfigPath.Value\n\t\t\t\thostPath = \"\"\n\t\t\t} else {\n\t\t\t\tcontainerPath = DefaultOllamaConfigsPath\n\t\t\t\thostPath = config.HostConfigPath.Value\n\t\t\t}\n\t\t}\n\n\t\tif err := c.SetVar(config.ConfigPath.Name, containerPath); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.ConfigPath.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.HostConfigPath.Name, hostPath); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.HostConfigPath.Name, err)\n\t\t}\n\n\tcase \"deepseek\":\n\t\tif err := c.SetVar(config.APIKey.Name, config.APIKey.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.APIKey.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.BaseURL.Name, config.BaseURL.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.BaseURL.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.ProviderName.Name, config.ProviderName.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.ProviderName.Name, err)\n\t\t}\n\n\tcase \"glm\":\n\t\tif err := c.SetVar(config.APIKey.Name, config.APIKey.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.APIKey.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.BaseURL.Name, config.BaseURL.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.BaseURL.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.ProviderName.Name, config.ProviderName.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.ProviderName.Name, err)\n\t\t}\n\n\tcase \"kimi\":\n\t\tif err := c.SetVar(config.APIKey.Name, config.APIKey.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.APIKey.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.BaseURL.Name, config.BaseURL.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.BaseURL.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.ProviderName.Name, config.ProviderName.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.ProviderName.Name, err)\n\t\t}\n\n\tcase \"qwen\":\n\t\tif err := c.SetVar(config.APIKey.Name, config.APIKey.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.APIKey.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.BaseURL.Name, config.BaseURL.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.BaseURL.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.ProviderName.Name, config.ProviderName.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.ProviderName.Name, err)\n\t\t}\n\n\tcase \"custom\":\n\t\tif err := c.SetVar(config.BaseURL.Name, config.BaseURL.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.BaseURL.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.APIKey.Name, config.APIKey.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.APIKey.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.Model.Name, config.Model.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.Model.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.LegacyReasoning.Name, config.LegacyReasoning.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.LegacyReasoning.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.PreserveReasoning.Name, config.PreserveReasoning.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.PreserveReasoning.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.ProviderName.Name, config.ProviderName.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.ProviderName.Name, err)\n\t\t}\n\n\t\tvar containerPath, hostPath string\n\t\tif config.HostConfigPath.Value != \"\" {\n\t\t\tif slices.Contains(config.EmbeddedLLMConfigsPath, config.HostConfigPath.Value) {\n\t\t\t\tcontainerPath = config.HostConfigPath.Value\n\t\t\t\thostPath = \"\"\n\t\t\t} else {\n\t\t\t\tcontainerPath = DefaultCustomConfigsPath\n\t\t\t\thostPath = config.HostConfigPath.Value\n\t\t\t}\n\t\t}\n\n\t\tif err := c.SetVar(config.ConfigPath.Name, containerPath); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.ConfigPath.Name, err)\n\t\t}\n\t\tif err := c.SetVar(config.HostConfigPath.Name, hostPath); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", config.HostConfigPath.Name, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ResetLLMProviderConfig resets a specific LLM provider configuration\nfunc (c *controller) ResetLLMProviderConfig(providerID string) map[string]*LLMProviderConfig {\n\tvar vars []string\n\tswitch providerID {\n\tcase \"openai\":\n\t\tvars = []string{\"OPEN_AI_KEY\", \"OPEN_AI_SERVER_URL\"}\n\tcase \"anthropic\":\n\t\tvars = []string{\"ANTHROPIC_API_KEY\", \"ANTHROPIC_SERVER_URL\"}\n\tcase \"gemini\":\n\t\tvars = []string{\"GEMINI_API_KEY\", \"GEMINI_SERVER_URL\"}\n\tcase \"bedrock\":\n\t\tvars = []string{\n\t\t\t\"BEDROCK_DEFAULT_AUTH\", \"BEDROCK_BEARER_TOKEN\",\n\t\t\t\"BEDROCK_ACCESS_KEY_ID\", \"BEDROCK_SECRET_ACCESS_KEY\", \"BEDROCK_SESSION_TOKEN\",\n\t\t\t\"BEDROCK_REGION\", \"BEDROCK_SERVER_URL\",\n\t\t}\n\tcase \"ollama\":\n\t\tvars = []string{\n\t\t\t\"OLLAMA_SERVER_URL\",\n\t\t\t\"OLLAMA_SERVER_API_KEY\",\n\t\t\t\"OLLAMA_SERVER_MODEL\",\n\t\t\t\"OLLAMA_SERVER_CONFIG_PATH\",\n\t\t\t\"OLLAMA_SERVER_PULL_MODELS_TIMEOUT\",\n\t\t\t\"OLLAMA_SERVER_PULL_MODELS_ENABLED\",\n\t\t\t\"OLLAMA_SERVER_LOAD_MODELS_ENABLED\",\n\t\t\t\"PENTAGI_OLLAMA_SERVER_CONFIG_PATH\",\n\t\t}\n\tcase \"deepseek\":\n\t\tvars = []string{\"DEEPSEEK_API_KEY\", \"DEEPSEEK_SERVER_URL\", \"DEEPSEEK_PROVIDER\"}\n\tcase \"glm\":\n\t\tvars = []string{\"GLM_API_KEY\", \"GLM_SERVER_URL\", \"GLM_PROVIDER\"}\n\tcase \"kimi\":\n\t\tvars = []string{\"KIMI_API_KEY\", \"KIMI_SERVER_URL\", \"KIMI_PROVIDER\"}\n\tcase \"qwen\":\n\t\tvars = []string{\"QWEN_API_KEY\", \"QWEN_SERVER_URL\", \"QWEN_PROVIDER\"}\n\tcase \"custom\":\n\t\tvars = []string{\n\t\t\t\"LLM_SERVER_URL\", \"LLM_SERVER_KEY\", \"LLM_SERVER_MODEL\",\n\t\t\t\"LLM_SERVER_CONFIG_PATH\", \"LLM_SERVER_LEGACY_REASONING\",\n\t\t\t\"LLM_SERVER_PRESERVE_REASONING\", \"LLM_SERVER_PROVIDER\",\n\t\t\t\"PENTAGI_LLM_SERVER_CONFIG_PATH\", // local path to the LLM config file\n\t\t}\n\t}\n\n\tif len(vars) != 0 {\n\t\tif err := c.ResetVars(vars); err != nil {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn c.GetLLMProviders()\n}\n\n// LangfuseConfig represents Langfuse configuration\ntype LangfuseConfig struct {\n\t// deployment configuration\n\tDeploymentType string // \"embedded\" or \"external\" or \"disabled\"\n\n\t// embedded listen settings\n\tListenIP loader.EnvVar // LANGFUSE_LISTEN_IP\n\tListenPort loader.EnvVar // LANGFUSE_LISTEN_PORT\n\n\t// integration settings (always required)\n\tBaseURL loader.EnvVar // LANGFUSE_BASE_URL\n\tProjectID loader.EnvVar // LANGFUSE_PROJECT_ID | LANGFUSE_INIT_PROJECT_ID\n\tPublicKey loader.EnvVar // LANGFUSE_PUBLIC_KEY | LANGFUSE_INIT_PROJECT_PUBLIC_KEY\n\tSecretKey loader.EnvVar // LANGFUSE_SECRET_KEY | LANGFUSE_INIT_PROJECT_SECRET_KEY\n\n\t// embedded instance settings (only for embedded mode)\n\tAdminEmail loader.EnvVar // LANGFUSE_INIT_USER_EMAIL\n\tAdminPassword loader.EnvVar // LANGFUSE_INIT_USER_PASSWORD\n\tAdminName loader.EnvVar // LANGFUSE_INIT_USER_NAME\n\n\t// enterprise license (optional for embedded mode)\n\tLicenseKey loader.EnvVar // LANGFUSE_EE_LICENSE_KEY\n\n\t// computed fields (not directly mapped to env vars)\n\tInstalled bool\n}\n\n// GetLangfuseConfig returns the current Langfuse configuration\nfunc (c *controller) GetLangfuseConfig() *LangfuseConfig {\n\tvars, _ := c.GetVars([]string{\n\t\t\"LANGFUSE_LISTEN_IP\",\n\t\t\"LANGFUSE_LISTEN_PORT\",\n\t\t\"LANGFUSE_BASE_URL\",\n\t\t\"LANGFUSE_PROJECT_ID\",\n\t\t\"LANGFUSE_PUBLIC_KEY\",\n\t\t\"LANGFUSE_SECRET_KEY\",\n\t\t\"LANGFUSE_INIT_USER_EMAIL\",\n\t\t\"LANGFUSE_INIT_USER_PASSWORD\",\n\t\t\"LANGFUSE_INIT_USER_NAME\",\n\t\t\"LANGFUSE_EE_LICENSE_KEY\",\n\t})\n\t// defaults\n\tif v := vars[\"LANGFUSE_LISTEN_IP\"]; v.Default == \"\" {\n\t\tv.Default = \"127.0.0.1\"\n\t\tvars[\"LANGFUSE_LISTEN_IP\"] = v\n\t}\n\tif v := vars[\"LANGFUSE_LISTEN_PORT\"]; v.Default == \"\" {\n\t\tv.Default = \"4000\"\n\t\tvars[\"LANGFUSE_LISTEN_PORT\"] = v\n\t}\n\n\t// Determine deployment type based on endpoint value\n\tvar deploymentType string\n\tbaseURL := vars[\"LANGFUSE_BASE_URL\"]\n\tprojectID := vars[\"LANGFUSE_PROJECT_ID\"]\n\tpublicKey := vars[\"LANGFUSE_PUBLIC_KEY\"]\n\tsecretKey := vars[\"LANGFUSE_SECRET_KEY\"]\n\tadminEmail := vars[\"LANGFUSE_INIT_USER_EMAIL\"]\n\tadminPassword := vars[\"LANGFUSE_INIT_USER_PASSWORD\"]\n\tadminName := vars[\"LANGFUSE_INIT_USER_NAME\"]\n\tlicenseKey := vars[\"LANGFUSE_EE_LICENSE_KEY\"]\n\n\tswitch baseURL.Value {\n\tcase \"\":\n\t\tdeploymentType = \"disabled\"\n\tcase checker.DefaultLangfuseEndpoint:\n\t\tdeploymentType = \"embedded\"\n\t\tif projectID.Value == \"\" && !projectID.IsChanged {\n\t\t\tif initProjectID, ok := c.GetVar(\"LANGFUSE_INIT_PROJECT_ID\"); ok {\n\t\t\t\tprojectID.Value = initProjectID.Value\n\t\t\t\tprojectID.IsChanged = true\n\t\t\t}\n\t\t}\n\t\tif publicKey.Value == \"\" && !publicKey.IsChanged {\n\t\t\tif initPublicKey, ok := c.GetVar(\"LANGFUSE_INIT_PROJECT_PUBLIC_KEY\"); ok {\n\t\t\t\tpublicKey.Value = initPublicKey.Value\n\t\t\t\tpublicKey.IsChanged = true\n\t\t\t}\n\t\t}\n\t\tif secretKey.Value == \"\" && !secretKey.IsChanged {\n\t\t\tif initSecretKey, ok := c.GetVar(\"LANGFUSE_INIT_PROJECT_SECRET_KEY\"); ok {\n\t\t\t\tsecretKey.Value = initSecretKey.Value\n\t\t\t\tsecretKey.IsChanged = true\n\t\t\t}\n\t\t}\n\tdefault:\n\t\tdeploymentType = \"external\"\n\t}\n\n\treturn &LangfuseConfig{\n\t\tDeploymentType: deploymentType,\n\t\tListenIP: vars[\"LANGFUSE_LISTEN_IP\"],\n\t\tListenPort: vars[\"LANGFUSE_LISTEN_PORT\"],\n\t\tBaseURL: baseURL,\n\t\tProjectID: projectID,\n\t\tPublicKey: publicKey,\n\t\tSecretKey: secretKey,\n\t\tAdminEmail: adminEmail,\n\t\tAdminPassword: adminPassword,\n\t\tAdminName: adminName,\n\t\tInstalled: c.checker.LangfuseInstalled,\n\t\tLicenseKey: licenseKey,\n\t}\n}\n\n// UpdateLangfuseConfig updates Langfuse configuration with proper endpoint handling\nfunc (c *controller) UpdateLangfuseConfig(config *LangfuseConfig) error {\n\tif config == nil {\n\t\treturn fmt.Errorf(\"config cannot be nil\")\n\t}\n\n\t// Set deployment type based configuration\n\tswitch config.DeploymentType {\n\tcase \"embedded\":\n\t\t// for embedded mode, use default endpoint and sync with docker-compose settings\n\t\tconfig.BaseURL.Value = checker.DefaultLangfuseEndpoint\n\n\t\tif err := c.SetVar(\"LANGFUSE_LISTEN_IP\", config.ListenIP.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set LANGFUSE_LISTEN_IP: %w\", err)\n\t\t}\n\t\tif err := c.SetVar(\"LANGFUSE_LISTEN_PORT\", config.ListenPort.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set LANGFUSE_LISTEN_PORT: %w\", err)\n\t\t}\n\n\t\t// update enterprise license key if provided\n\t\tif err := c.SetVar(\"LANGFUSE_EE_LICENSE_KEY\", config.LicenseKey.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set LANGFUSE_EE_LICENSE_KEY: %w\", err)\n\t\t}\n\n\t\t// Sync with docker-compose environment variables\n\t\tif !config.Installed {\n\t\t\tif err := c.SetVar(\"LANGFUSE_INIT_PROJECT_ID\", config.ProjectID.Value); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to set LANGFUSE_INIT_PROJECT_ID: %w\", err)\n\t\t\t}\n\t\t\tif err := c.SetVar(\"LANGFUSE_INIT_PROJECT_PUBLIC_KEY\", config.PublicKey.Value); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to set LANGFUSE_INIT_PROJECT_PUBLIC_KEY: %w\", err)\n\t\t\t}\n\t\t\tif err := c.SetVar(\"LANGFUSE_INIT_PROJECT_SECRET_KEY\", config.SecretKey.Value); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to set LANGFUSE_INIT_PROJECT_SECRET_KEY: %w\", err)\n\t\t\t}\n\t\t\tif err := c.SetVar(\"LANGFUSE_INIT_USER_EMAIL\", config.AdminEmail.Value); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to set LANGFUSE_INIT_USER_EMAIL: %w\", err)\n\t\t\t}\n\t\t\tif err := c.SetVar(\"LANGFUSE_INIT_USER_NAME\", config.AdminName.Value); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to set LANGFUSE_INIT_USER_NAME: %w\", err)\n\t\t\t}\n\t\t\tif err := c.SetVar(\"LANGFUSE_INIT_USER_PASSWORD\", config.AdminPassword.Value); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to set LANGFUSE_INIT_USER_PASSWORD: %w\", err)\n\t\t\t}\n\t\t}\n\n\tcase \"external\":\n\t\t// for external mode, use provided endpoint\n\n\tcase \"disabled\":\n\t\t// for disabled mode, clear endpoint and disable\n\t\tconfig.BaseURL.Value = \"\"\n\t}\n\n\t// update integration environment variables\n\tif err := c.SetVar(\"LANGFUSE_BASE_URL\", config.BaseURL.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set LANGFUSE_BASE_URL: %w\", err)\n\t}\n\tif err := c.SetVar(\"LANGFUSE_PROJECT_ID\", config.ProjectID.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set LANGFUSE_PROJECT_ID: %w\", err)\n\t}\n\tif err := c.SetVar(\"LANGFUSE_PUBLIC_KEY\", config.PublicKey.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set LANGFUSE_PUBLIC_KEY: %w\", err)\n\t}\n\tif err := c.SetVar(\"LANGFUSE_SECRET_KEY\", config.SecretKey.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set LANGFUSE_SECRET_KEY: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (c *controller) ResetLangfuseConfig() *LangfuseConfig {\n\tvars := []string{\n\t\t\"LANGFUSE_BASE_URL\",\n\t\t\"LANGFUSE_PROJECT_ID\",\n\t\t\"LANGFUSE_PUBLIC_KEY\",\n\t\t\"LANGFUSE_SECRET_KEY\",\n\t\t\"LANGFUSE_LISTEN_IP\",\n\t\t\"LANGFUSE_LISTEN_PORT\",\n\t\t\"LANGFUSE_EE_LICENSE_KEY\",\n\t}\n\n\tif !c.checker.LangfuseInstalled {\n\t\tvars = append(vars,\n\t\t\t\"LANGFUSE_INIT_USER_EMAIL\",\n\t\t\t\"LANGFUSE_INIT_USER_NAME\",\n\t\t\t\"LANGFUSE_INIT_USER_PASSWORD\",\n\t\t\t\"LANGFUSE_INIT_PROJECT_ID\",\n\t\t\t\"LANGFUSE_INIT_PROJECT_PUBLIC_KEY\",\n\t\t\t\"LANGFUSE_INIT_PROJECT_SECRET_KEY\",\n\t\t)\n\t}\n\n\tif err := c.ResetVars(vars); err != nil {\n\t\treturn nil\n\t}\n\n\treturn c.GetLangfuseConfig()\n}\n\n// GraphitiConfig represents Graphiti knowledge graph configuration\ntype GraphitiConfig struct {\n\t// deployment configuration\n\tDeploymentType string // \"embedded\" or \"external\" or \"disabled\"\n\n\t// integration settings (always)\n\tGraphitiURL loader.EnvVar // GRAPHITI_URL\n\tTimeout loader.EnvVar // GRAPHITI_TIMEOUT\n\tModelName loader.EnvVar // GRAPHITI_MODEL_NAME\n\n\t// neo4j settings (embedded only)\n\tNeo4jUser loader.EnvVar // NEO4J_USER\n\tNeo4jPassword loader.EnvVar // NEO4J_PASSWORD\n\tNeo4jDatabase loader.EnvVar // NEO4J_DATABASE\n\tNeo4jURI loader.EnvVar // NEO4J_URI\n\n\t// computed fields (not directly mapped to env vars)\n\tInstalled bool\n}\n\n// GetGraphitiConfig returns the current Graphiti configuration\nfunc (c *controller) GetGraphitiConfig() *GraphitiConfig {\n\tvars, _ := c.GetVars([]string{\n\t\t\"GRAPHITI_URL\",\n\t\t\"GRAPHITI_TIMEOUT\",\n\t\t\"GRAPHITI_MODEL_NAME\",\n\t\t\"NEO4J_USER\",\n\t\t\"NEO4J_PASSWORD\",\n\t\t\"NEO4J_DATABASE\",\n\t\t\"NEO4J_URI\",\n\t})\n\n\t// set defaults if missing\n\tif v := vars[\"GRAPHITI_TIMEOUT\"]; v.Default == \"\" {\n\t\tv.Default = \"30\"\n\t\tvars[\"GRAPHITI_TIMEOUT\"] = v\n\t}\n\tif v := vars[\"GRAPHITI_MODEL_NAME\"]; v.Default == \"\" {\n\t\tv.Default = \"gpt-5-mini\"\n\t\tvars[\"GRAPHITI_MODEL_NAME\"] = v\n\t}\n\tif v := vars[\"NEO4J_USER\"]; v.Default == \"\" {\n\t\tv.Default = \"neo4j\"\n\t\tvars[\"NEO4J_USER\"] = v\n\t}\n\tif v := vars[\"NEO4J_PASSWORD\"]; v.Default == \"\" {\n\t\tv.Default = \"devpassword\"\n\t\tvars[\"NEO4J_PASSWORD\"] = v\n\t}\n\tif v := vars[\"NEO4J_DATABASE\"]; v.Default == \"\" {\n\t\tv.Default = \"neo4j\"\n\t\tvars[\"NEO4J_DATABASE\"] = v\n\t}\n\tif v := vars[\"NEO4J_URI\"]; v.Default == \"\" {\n\t\tv.Default = \"bolt://neo4j:7687\"\n\t\tvars[\"NEO4J_URI\"] = v\n\t}\n\n\tgraphitiURL := vars[\"GRAPHITI_URL\"]\n\n\t// determine deployment type based on GRAPHITI_ENABLED and GRAPHITI_URL\n\tgraphitiEnabled, _ := c.GetVar(\"GRAPHITI_ENABLED\")\n\n\tvar deploymentType string\n\tif graphitiEnabled.Value != \"true\" || graphitiURL.Value == \"\" {\n\t\tdeploymentType = \"disabled\"\n\t} else if graphitiURL.Value == checker.DefaultGraphitiEndpoint {\n\t\tdeploymentType = \"embedded\"\n\t} else {\n\t\tdeploymentType = \"external\"\n\t}\n\n\treturn &GraphitiConfig{\n\t\tDeploymentType: deploymentType,\n\t\tGraphitiURL: graphitiURL,\n\t\tTimeout: vars[\"GRAPHITI_TIMEOUT\"],\n\t\tModelName: vars[\"GRAPHITI_MODEL_NAME\"],\n\t\tNeo4jUser: vars[\"NEO4J_USER\"],\n\t\tNeo4jPassword: vars[\"NEO4J_PASSWORD\"],\n\t\tNeo4jDatabase: vars[\"NEO4J_DATABASE\"],\n\t\tNeo4jURI: vars[\"NEO4J_URI\"],\n\t\tInstalled: c.checker.GraphitiInstalled,\n\t}\n}\n\n// UpdateGraphitiConfig updates Graphiti configuration\nfunc (c *controller) UpdateGraphitiConfig(config *GraphitiConfig) error {\n\tif config == nil {\n\t\treturn fmt.Errorf(\"config cannot be nil\")\n\t}\n\n\t// set deployment type based configuration\n\tswitch config.DeploymentType {\n\tcase \"embedded\":\n\t\t// for embedded mode, use default endpoint\n\t\tconfig.GraphitiURL.Value = checker.DefaultGraphitiEndpoint\n\n\t\t// enable Graphiti\n\t\tif err := c.SetVar(\"GRAPHITI_ENABLED\", \"true\"); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set GRAPHITI_ENABLED: %w\", err)\n\t\t}\n\n\t\t// update timeout, model, and neo4j settings\n\t\tif err := c.SetVar(\"GRAPHITI_TIMEOUT\", config.Timeout.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set GRAPHITI_TIMEOUT: %w\", err)\n\t\t}\n\t\tif err := c.SetVar(\"GRAPHITI_MODEL_NAME\", config.ModelName.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set GRAPHITI_MODEL_NAME: %w\", err)\n\t\t}\n\t\tif err := c.SetVar(\"NEO4J_USER\", config.Neo4jUser.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set NEO4J_USER: %w\", err)\n\t\t}\n\t\tif err := c.SetVar(\"NEO4J_PASSWORD\", config.Neo4jPassword.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set NEO4J_PASSWORD: %w\", err)\n\t\t}\n\t\tif err := c.SetVar(\"NEO4J_DATABASE\", config.Neo4jDatabase.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set NEO4J_DATABASE: %w\", err)\n\t\t}\n\n\tcase \"external\":\n\t\t// for external mode, use provided endpoint\n\t\t// enable Graphiti\n\t\tif err := c.SetVar(\"GRAPHITI_ENABLED\", \"true\"); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set GRAPHITI_ENABLED: %w\", err)\n\t\t}\n\n\t\t// update timeout only (model is configured on external server)\n\t\tif err := c.SetVar(\"GRAPHITI_TIMEOUT\", config.Timeout.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set GRAPHITI_TIMEOUT: %w\", err)\n\t\t}\n\n\tcase \"disabled\":\n\t\t// for disabled mode, disable Graphiti\n\t\tif err := c.SetVar(\"GRAPHITI_ENABLED\", \"false\"); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set GRAPHITI_ENABLED: %w\", err)\n\t\t}\n\t\tconfig.GraphitiURL.Value = \"\"\n\t}\n\n\t// update integration environment variables\n\tif err := c.SetVar(\"GRAPHITI_URL\", config.GraphitiURL.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set GRAPHITI_URL: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (c *controller) ResetGraphitiConfig() *GraphitiConfig {\n\tvars := []string{\n\t\t\"GRAPHITI_ENABLED\",\n\t\t\"GRAPHITI_URL\",\n\t\t\"GRAPHITI_TIMEOUT\",\n\t\t\"GRAPHITI_MODEL_NAME\",\n\t\t\"NEO4J_USER\",\n\t\t\"NEO4J_PASSWORD\",\n\t\t\"NEO4J_DATABASE\",\n\t\t\"NEO4J_URI\",\n\t}\n\n\tif err := c.ResetVars(vars); err != nil {\n\t\treturn nil\n\t}\n\n\treturn c.GetGraphitiConfig()\n}\n\n// ObservabilityConfig represents observability configuration\ntype ObservabilityConfig struct {\n\t// deployment configuration\n\tDeploymentType string // \"embedded\" or \"external\" or \"disabled\"\n\n\t// embedded listen settings\n\tGrafanaListenIP loader.EnvVar // GRAFANA_LISTEN_IP\n\tGrafanaListenPort loader.EnvVar // GRAFANA_LISTEN_PORT\n\tOTelGrpcListenIP loader.EnvVar // OTEL_GRPC_LISTEN_IP\n\tOTelGrpcListenPort loader.EnvVar // OTEL_GRPC_LISTEN_PORT\n\tOTelHttpListenIP loader.EnvVar // OTEL_HTTP_LISTEN_IP\n\tOTelHttpListenPort loader.EnvVar // OTEL_HTTP_LISTEN_PORT\n\n\t// integration settings\n\tOTelHost loader.EnvVar // OTEL_HOST\n}\n\n// GetObservabilityConfig returns the current observability configuration\nfunc (c *controller) GetObservabilityConfig() *ObservabilityConfig {\n\tvars, _ := c.GetVars([]string{\n\t\t\"OTEL_HOST\",\n\t\t\"GRAFANA_LISTEN_IP\",\n\t\t\"GRAFANA_LISTEN_PORT\",\n\t\t\"OTEL_GRPC_LISTEN_IP\",\n\t\t\"OTEL_GRPC_LISTEN_PORT\",\n\t\t\"OTEL_HTTP_LISTEN_IP\",\n\t\t\"OTEL_HTTP_LISTEN_PORT\",\n\t})\n\n\t// set defaults if missing\n\tdefaults := map[string]string{\n\t\t\"GRAFANA_LISTEN_IP\": \"127.0.0.1\",\n\t\t\"GRAFANA_LISTEN_PORT\": \"3000\",\n\t\t\"OTEL_GRPC_LISTEN_IP\": \"127.0.0.1\",\n\t\t\"OTEL_GRPC_LISTEN_PORT\": \"8148\",\n\t\t\"OTEL_HTTP_LISTEN_IP\": \"127.0.0.1\",\n\t\t\"OTEL_HTTP_LISTEN_PORT\": \"4318\",\n\t}\n\tfor k, def := range defaults {\n\t\tif v := vars[k]; v.Default == \"\" {\n\t\t\tv.Default = def\n\t\t\tvars[k] = v\n\t\t}\n\t}\n\n\totelHost := vars[\"OTEL_HOST\"]\n\n\t// determine deployment type based on endpoint value\n\tvar deploymentType string\n\tswitch otelHost.Value {\n\tcase \"\":\n\t\tdeploymentType = \"disabled\"\n\tcase checker.DefaultObservabilityEndpoint:\n\t\tdeploymentType = \"embedded\"\n\tdefault:\n\t\tdeploymentType = \"external\"\n\t}\n\n\treturn &ObservabilityConfig{\n\t\tDeploymentType: deploymentType,\n\t\tOTelHost: otelHost,\n\t\tGrafanaListenIP: vars[\"GRAFANA_LISTEN_IP\"],\n\t\tGrafanaListenPort: vars[\"GRAFANA_LISTEN_PORT\"],\n\t\tOTelGrpcListenIP: vars[\"OTEL_GRPC_LISTEN_IP\"],\n\t\tOTelGrpcListenPort: vars[\"OTEL_GRPC_LISTEN_PORT\"],\n\t\tOTelHttpListenIP: vars[\"OTEL_HTTP_LISTEN_IP\"],\n\t\tOTelHttpListenPort: vars[\"OTEL_HTTP_LISTEN_PORT\"],\n\t}\n}\n\n// UpdateObservabilityConfig updates the observability configuration\nfunc (c *controller) UpdateObservabilityConfig(config *ObservabilityConfig) error {\n\tif config == nil {\n\t\treturn fmt.Errorf(\"config cannot be nil\")\n\t}\n\n\tlangfuseOtelEnvVar, _ := c.GetVar(\"LANGFUSE_OTEL_EXPORTER_OTLP_ENDPOINT\")\n\n\t// set deployment type based configuration\n\tswitch config.DeploymentType {\n\tcase \"embedded\":\n\t\t// for embedded mode, use default endpoints\n\t\tconfig.OTelHost.Value = checker.DefaultObservabilityEndpoint\n\t\tlangfuseOtelEnvVar.Value = checker.DefaultLangfuseOtelEndpoint\n\n\t\t// update listen settings for embedded mode\n\t\tupdates := map[string]string{\n\t\t\t\"GRAFANA_LISTEN_IP\": config.GrafanaListenIP.Value,\n\t\t\t\"GRAFANA_LISTEN_PORT\": config.GrafanaListenPort.Value,\n\t\t\t\"OTEL_GRPC_LISTEN_IP\": config.OTelGrpcListenIP.Value,\n\t\t\t\"OTEL_GRPC_LISTEN_PORT\": config.OTelGrpcListenPort.Value,\n\t\t\t\"OTEL_HTTP_LISTEN_IP\": config.OTelHttpListenIP.Value,\n\t\t\t\"OTEL_HTTP_LISTEN_PORT\": config.OTelHttpListenPort.Value,\n\t\t}\n\t\tif err := c.SetVars(updates); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set embedded listen vars: %w\", err)\n\t\t}\n\n\t\t// note: langfuse listen vars are set in UpdateLangfuseConfig\n\tcase \"external\":\n\t\t// for external mode, use provided endpoint\n\t\tlangfuseOtelEnvVar.Value = \"\"\n\n\tcase \"disabled\":\n\t\t// for disabled mode, clear endpoint and disable\n\t\tconfig.OTelHost.Value = \"\"\n\t\tlangfuseOtelEnvVar.Value = \"\"\n\t}\n\n\t// update Langfuse and Observability integration if it's enabled\n\tif err := c.SetVar(langfuseOtelEnvVar.Name, langfuseOtelEnvVar.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set LANGFUSE_OTEL_EXPORTER_OTLP_ENDPOINT: %w\", err)\n\t}\n\n\t// update integration environment variables\n\tif err := c.SetVar(config.OTelHost.Name, config.OTelHost.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set OTEL_HOST: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (c *controller) ResetObservabilityConfig() *ObservabilityConfig {\n\tvars := []string{\n\t\t\"OTEL_HOST\",\n\t\t\"GRAFANA_LISTEN_IP\",\n\t\t\"GRAFANA_LISTEN_PORT\",\n\t\t\"OTEL_GRPC_LISTEN_IP\",\n\t\t\"OTEL_GRPC_LISTEN_PORT\",\n\t\t\"OTEL_HTTP_LISTEN_IP\",\n\t\t\"OTEL_HTTP_LISTEN_PORT\",\n\t\t// langfuse integration with observability\n\t\t\"LANGFUSE_OTEL_EXPORTER_OTLP_ENDPOINT\",\n\t}\n\n\tif err := c.ResetVars(vars); err != nil {\n\t\treturn nil\n\t}\n\n\treturn c.GetObservabilityConfig()\n}\n\ntype SummarizerType string\n\nconst (\n\tSummarizerTypeGeneral SummarizerType = \"general\"\n\tSummarizerTypeAssistant SummarizerType = \"assistant\"\n)\n\n// SummarizerConfig represents summarizer configuration settings\ntype SummarizerConfig struct {\n\t// type identifier (\"general\" or \"assistant\")\n\tType SummarizerType\n\n\t// common boolean settings\n\tPreserveLast loader.EnvVar // PREFIX + PRESERVE_LAST\n\tUseQA loader.EnvVar // PREFIX + USE_QA\n\tSumHumanInQA loader.EnvVar // PREFIX + SUM_MSG_HUMAN_IN_QA\n\n\t// size settings (in bytes)\n\tLastSecBytes loader.EnvVar // PREFIX + LAST_SEC_BYTES\n\tMaxBPBytes loader.EnvVar // PREFIX + MAX_BP_BYTES\n\tMaxQABytes loader.EnvVar // PREFIX + MAX_QA_BYTES\n\n\t// count settings\n\tMaxQASections loader.EnvVar // PREFIX + MAX_QA_SECTIONS\n\tKeepQASections loader.EnvVar // PREFIX + KEEP_QA_SECTIONS\n}\n\n// GetSummarizerConfig returns summarizer configuration for specified type\nfunc (c *controller) GetSummarizerConfig(summarizerType SummarizerType) *SummarizerConfig {\n\tvar prefix string\n\tif summarizerType == SummarizerTypeAssistant {\n\t\tprefix = \"ASSISTANT_SUMMARIZER_\"\n\t} else {\n\t\tprefix = \"SUMMARIZER_\"\n\t}\n\n\tconfig := &SummarizerConfig{\n\t\tType: summarizerType,\n\t}\n\n\t// Read variables directly from state (defaults handled by loader)\n\tconfig.PreserveLast, _ = c.GetVar(prefix + \"PRESERVE_LAST\")\n\n\tif summarizerType == SummarizerTypeGeneral {\n\t\tconfig.UseQA, _ = c.GetVar(prefix + \"USE_QA\")\n\t\tconfig.SumHumanInQA, _ = c.GetVar(prefix + \"SUM_MSG_HUMAN_IN_QA\")\n\t}\n\n\t// Size settings\n\tconfig.LastSecBytes, _ = c.GetVar(prefix + \"LAST_SEC_BYTES\")\n\tconfig.MaxBPBytes, _ = c.GetVar(prefix + \"MAX_BP_BYTES\")\n\tconfig.MaxQABytes, _ = c.GetVar(prefix + \"MAX_QA_BYTES\")\n\n\t// Count settings\n\tconfig.MaxQASections, _ = c.GetVar(prefix + \"MAX_QA_SECTIONS\")\n\tconfig.KeepQASections, _ = c.GetVar(prefix + \"KEEP_QA_SECTIONS\")\n\n\treturn config\n}\n\n// UpdateSummarizerConfig updates summarizer configuration\nfunc (c *controller) UpdateSummarizerConfig(config *SummarizerConfig) error {\n\tvar prefix string\n\tif config.Type == SummarizerTypeAssistant {\n\t\tprefix = \"ASSISTANT_SUMMARIZER_\"\n\t} else {\n\t\tprefix = \"SUMMARIZER_\"\n\t}\n\n\t// Update boolean settings\n\tif err := c.SetVar(prefix+\"PRESERVE_LAST\", config.PreserveLast.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set %s: %w\", prefix+\"PRESERVE_LAST\", err)\n\t}\n\n\t// General-specific boolean settings\n\tif config.Type == SummarizerTypeGeneral {\n\t\tif err := c.SetVar(prefix+\"USE_QA\", config.UseQA.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", prefix+\"USE_QA\", err)\n\t\t}\n\t\tif err := c.SetVar(prefix+\"SUM_MSG_HUMAN_IN_QA\", config.SumHumanInQA.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set %s: %w\", prefix+\"SUM_MSG_HUMAN_IN_QA\", err)\n\t\t}\n\t}\n\n\t// Update size settings\n\tif err := c.SetVar(prefix+\"LAST_SEC_BYTES\", config.LastSecBytes.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set %s: %w\", prefix+\"LAST_SEC_BYTES\", err)\n\t}\n\tif err := c.SetVar(prefix+\"MAX_BP_BYTES\", config.MaxBPBytes.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set %s: %w\", prefix+\"MAX_BP_BYTES\", err)\n\t}\n\tif err := c.SetVar(prefix+\"MAX_QA_BYTES\", config.MaxQABytes.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set %s: %w\", prefix+\"MAX_QA_BYTES\", err)\n\t}\n\n\t// Update count settings\n\tif err := c.SetVar(prefix+\"MAX_QA_SECTIONS\", config.MaxQASections.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set %s: %w\", prefix+\"MAX_QA_SECTIONS\", err)\n\t}\n\tif err := c.SetVar(prefix+\"KEEP_QA_SECTIONS\", config.KeepQASections.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set %s: %w\", prefix+\"KEEP_QA_SECTIONS\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (c *controller) ResetSummarizerConfig(summarizerType SummarizerType) *SummarizerConfig {\n\tvar prefix string\n\tif summarizerType == SummarizerTypeAssistant {\n\t\tprefix = \"ASSISTANT_SUMMARIZER_\"\n\t} else {\n\t\tprefix = \"SUMMARIZER_\"\n\t}\n\n\tvars := []string{\n\t\tprefix + \"PRESERVE_LAST\",\n\t\tprefix + \"LAST_SEC_BYTES\",\n\t\tprefix + \"MAX_BP_BYTES\",\n\t\tprefix + \"MAX_QA_BYTES\",\n\t\tprefix + \"MAX_QA_SECTIONS\",\n\t\tprefix + \"KEEP_QA_SECTIONS\",\n\t}\n\n\tif summarizerType == SummarizerTypeGeneral {\n\t\tvars = append(vars,\n\t\t\tprefix+\"USE_QA\",\n\t\t\tprefix+\"SUM_MSG_HUMAN_IN_QA\",\n\t\t)\n\t}\n\n\tif err := c.ResetVars(vars); err != nil {\n\t\treturn nil\n\t}\n\n\treturn c.GetSummarizerConfig(summarizerType)\n}\n\n// EmbedderConfig represents embedder configuration settings\ntype EmbedderConfig struct {\n\t// direct form field mappings using loader.EnvVar\n\t// these fields directly correspond to environment variables and form inputs (not computed)\n\tProvider loader.EnvVar // EMBEDDING_PROVIDER\n\tURL loader.EnvVar // EMBEDDING_URL\n\tAPIKey loader.EnvVar // EMBEDDING_KEY\n\tModel loader.EnvVar // EMBEDDING_MODEL\n\tBatchSize loader.EnvVar // EMBEDDING_BATCH_SIZE\n\tStripNewLines loader.EnvVar // EMBEDDING_STRIP_NEW_LINES\n\n\t// computed fields (not directly mapped to env vars)\n\tConfigured bool\n\tInstalled bool\n}\n\n// GetEmbedderConfig returns current embedder configuration\nfunc (c *controller) GetEmbedderConfig() *EmbedderConfig {\n\tconfig := &EmbedderConfig{}\n\tconfig.Provider, _ = c.GetVar(\"EMBEDDING_PROVIDER\")\n\tconfig.URL, _ = c.GetVar(\"EMBEDDING_URL\")\n\tconfig.APIKey, _ = c.GetVar(\"EMBEDDING_KEY\")\n\tconfig.Model, _ = c.GetVar(\"EMBEDDING_MODEL\")\n\tconfig.BatchSize, _ = c.GetVar(\"EMBEDDING_BATCH_SIZE\")\n\tconfig.StripNewLines, _ = c.GetVar(\"EMBEDDING_STRIP_NEW_LINES\")\n\tconfig.Installed = c.checker.PentagiInstalled\n\n\t// Determine if configured based on provider requirements\n\tswitch config.Provider.Value {\n\tcase \"openai\", \"\":\n\t\t// For OpenAI, check if we have API key either in EMBEDDING_KEY or OPEN_AI_KEY\n\t\topenaiKey, _ := c.GetVar(\"OPEN_AI_KEY\")\n\t\tconfig.Configured = config.APIKey.Value != \"\" || openaiKey.Value != \"\"\n\tcase \"ollama\":\n\t\t// for Ollama, no API key required, but URL must be provided\n\t\tconfig.Configured = config.URL.Value != \"\"\n\tcase \"huggingface\", \"googleai\":\n\t\t// These require API key\n\t\tconfig.Configured = config.APIKey.Value != \"\"\n\tdefault:\n\t\t// Others are configured if API key is present\n\t\tconfig.Configured = config.APIKey.Value != \"\"\n\t}\n\n\treturn config\n}\n\n// UpdateEmbedderConfig updates embedder configuration\nfunc (c *controller) UpdateEmbedderConfig(config *EmbedderConfig) error {\n\tif config == nil {\n\t\treturn fmt.Errorf(\"config cannot be nil\")\n\t}\n\n\t// Update environment variables\n\tif err := c.SetVar(\"EMBEDDING_PROVIDER\", config.Provider.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set EMBEDDING_PROVIDER: %w\", err)\n\t}\n\tif err := c.SetVar(\"EMBEDDING_URL\", config.URL.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set EMBEDDING_URL: %w\", err)\n\t}\n\tif err := c.SetVar(\"EMBEDDING_KEY\", config.APIKey.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set EMBEDDING_KEY: %w\", err)\n\t}\n\tif err := c.SetVar(\"EMBEDDING_MODEL\", config.Model.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set EMBEDDING_MODEL: %w\", err)\n\t}\n\tif err := c.SetVar(\"EMBEDDING_BATCH_SIZE\", config.BatchSize.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set EMBEDDING_BATCH_SIZE: %w\", err)\n\t}\n\tif err := c.SetVar(\"EMBEDDING_STRIP_NEW_LINES\", config.StripNewLines.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set EMBEDDING_STRIP_NEW_LINES: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (c *controller) ResetEmbedderConfig() *EmbedderConfig {\n\tvars := []string{\n\t\t\"EMBEDDING_PROVIDER\",\n\t\t\"EMBEDDING_URL\",\n\t\t\"EMBEDDING_KEY\",\n\t\t\"EMBEDDING_MODEL\",\n\t\t\"EMBEDDING_BATCH_SIZE\",\n\t\t\"EMBEDDING_STRIP_NEW_LINES\",\n\t}\n\n\tif err := c.ResetVars(vars); err != nil {\n\t\treturn nil\n\t}\n\n\treturn c.GetEmbedderConfig()\n}\n\n// AIAgentsConfig represents extra AI agents configuration\ntype AIAgentsConfig struct {\n\t// direct form field mappings using loader.EnvVar\n\t// these fields directly correspond to environment variables and form inputs (not computed)\n\tHumanInTheLoop loader.EnvVar // ASK_USER\n\tAssistantUseAgents loader.EnvVar // ASSISTANT_USE_AGENTS\n\tExecutionMonitorEnabled loader.EnvVar // EXECUTION_MONITOR_ENABLED\n\tExecutionMonitorSameToolLimit loader.EnvVar // EXECUTION_MONITOR_SAME_TOOL_LIMIT\n\tExecutionMonitorTotalToolLimit loader.EnvVar // EXECUTION_MONITOR_TOTAL_TOOL_LIMIT\n\tMaxGeneralAgentToolCalls loader.EnvVar // MAX_GENERAL_AGENT_TOOL_CALLS\n\tMaxLimitedAgentToolCalls loader.EnvVar // MAX_LIMITED_AGENT_TOOL_CALLS\n\tAgentPlanningStepEnabled loader.EnvVar // AGENT_PLANNING_STEP_ENABLED\n}\n\nfunc (c *controller) GetAIAgentsConfig() *AIAgentsConfig {\n\tconfig := &AIAgentsConfig{}\n\n\tconfig.HumanInTheLoop, _ = c.GetVar(\"ASK_USER\")\n\tconfig.AssistantUseAgents, _ = c.GetVar(\"ASSISTANT_USE_AGENTS\")\n\tconfig.ExecutionMonitorEnabled, _ = c.GetVar(\"EXECUTION_MONITOR_ENABLED\")\n\tconfig.ExecutionMonitorSameToolLimit, _ = c.GetVar(\"EXECUTION_MONITOR_SAME_TOOL_LIMIT\")\n\tconfig.ExecutionMonitorTotalToolLimit, _ = c.GetVar(\"EXECUTION_MONITOR_TOTAL_TOOL_LIMIT\")\n\tconfig.MaxGeneralAgentToolCalls, _ = c.GetVar(\"MAX_GENERAL_AGENT_TOOL_CALLS\")\n\tconfig.MaxLimitedAgentToolCalls, _ = c.GetVar(\"MAX_LIMITED_AGENT_TOOL_CALLS\")\n\tconfig.AgentPlanningStepEnabled, _ = c.GetVar(\"AGENT_PLANNING_STEP_ENABLED\")\n\n\treturn config\n}\n\nfunc (c *controller) UpdateAIAgentsConfig(config *AIAgentsConfig) error {\n\tif config == nil {\n\t\treturn fmt.Errorf(\"config cannot be nil\")\n\t}\n\n\tif err := c.SetVar(\"ASK_USER\", config.HumanInTheLoop.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set ASK_USER: %w\", err)\n\t}\n\tif err := c.SetVar(\"ASSISTANT_USE_AGENTS\", config.AssistantUseAgents.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set ASSISTANT_USE_AGENTS: %w\", err)\n\t}\n\tif err := c.SetVar(\"EXECUTION_MONITOR_ENABLED\", config.ExecutionMonitorEnabled.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set EXECUTION_MONITOR_ENABLED: %w\", err)\n\t}\n\tif err := c.SetVar(\"EXECUTION_MONITOR_SAME_TOOL_LIMIT\", config.ExecutionMonitorSameToolLimit.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set EXECUTION_MONITOR_SAME_TOOL_LIMIT: %w\", err)\n\t}\n\tif err := c.SetVar(\"EXECUTION_MONITOR_TOTAL_TOOL_LIMIT\", config.ExecutionMonitorTotalToolLimit.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set EXECUTION_MONITOR_TOTAL_TOOL_LIMIT: %w\", err)\n\t}\n\tif err := c.SetVar(\"MAX_GENERAL_AGENT_TOOL_CALLS\", config.MaxGeneralAgentToolCalls.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set MAX_GENERAL_AGENT_TOOL_CALLS: %w\", err)\n\t}\n\tif err := c.SetVar(\"MAX_LIMITED_AGENT_TOOL_CALLS\", config.MaxLimitedAgentToolCalls.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set MAX_LIMITED_AGENT_TOOL_CALLS: %w\", err)\n\t}\n\tif err := c.SetVar(\"AGENT_PLANNING_STEP_ENABLED\", config.AgentPlanningStepEnabled.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set AGENT_PLANNING_STEP_ENABLED: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (c *controller) ResetAIAgentsConfig() *AIAgentsConfig {\n\tvars := []string{\n\t\t\"ASK_USER\",\n\t\t\"ASSISTANT_USE_AGENTS\",\n\t}\n\n\tif err := c.ResetVars(vars); err != nil {\n\t\treturn nil\n\t}\n\n\treturn c.GetAIAgentsConfig()\n}\n\n// ScraperConfig represents scraper configuration settings\ntype ScraperConfig struct {\n\t// direct form field mappings using loader.EnvVar\n\t// these fields directly correspond to environment variables and form inputs (not computed)\n\tPublicURL loader.EnvVar // SCRAPER_PUBLIC_URL\n\tPrivateURL loader.EnvVar // SCRAPER_PRIVATE_URL\n\tLocalUsername loader.EnvVar // LOCAL_SCRAPER_USERNAME\n\tLocalPassword loader.EnvVar // LOCAL_SCRAPER_PASSWORD\n\tMaxConcurrentSessions loader.EnvVar // LOCAL_SCRAPER_MAX_CONCURRENT_SESSIONS\n\n\t// computed fields (not directly mapped to env vars)\n\t// these are derived from the above EnvVar fields\n\tMode string // \"embedded\", \"external\", \"disabled\" - computed from PrivateURL\n\n\t// parsed credentials for external mode (extracted from URLs)\n\tPublicUsername string\n\tPublicPassword string\n\tPrivateUsername string\n\tPrivatePassword string\n}\n\n// GetScraperConfig returns current scraper configuration\nfunc (c *controller) GetScraperConfig() *ScraperConfig {\n\t// get all environment variables using the state controller\n\tpublicURL, _ := c.GetVar(\"SCRAPER_PUBLIC_URL\")\n\tprivateURL, _ := c.GetVar(\"SCRAPER_PRIVATE_URL\")\n\tlocalUsername, _ := c.GetVar(\"LOCAL_SCRAPER_USERNAME\")\n\tlocalPassword, _ := c.GetVar(\"LOCAL_SCRAPER_PASSWORD\")\n\tmaxSessions, _ := c.GetVar(\"LOCAL_SCRAPER_MAX_CONCURRENT_SESSIONS\")\n\n\tconfig := &ScraperConfig{\n\t\tPublicURL: publicURL,\n\t\tPrivateURL: privateURL,\n\t\tLocalUsername: localUsername,\n\t\tLocalPassword: localPassword,\n\t\tMaxConcurrentSessions: maxSessions,\n\t}\n\n\tconfig.Mode = c.determineScraperMode(privateURL.Value, publicURL.Value)\n\n\tif config.Mode == \"external\" || config.Mode == \"embedded\" {\n\t\tconfig.PublicUsername, config.PublicPassword = c.extractCredentialsFromURL(publicURL.Value)\n\t\tconfig.PrivateUsername, config.PrivatePassword = c.extractCredentialsFromURL(privateURL.Value)\n\t\tconfig.PublicURL.Value = RemoveCredentialsFromURL(publicURL.Value)\n\t\tconfig.PrivateURL.Value = RemoveCredentialsFromURL(privateURL.Value)\n\t}\n\n\treturn config\n}\n\n// determineScraperMode determines scraper mode based on private URL\nfunc (c *controller) determineScraperMode(privateURL, publicURL string) string {\n\tif privateURL == \"\" && publicURL == \"\" {\n\t\treturn \"disabled\"\n\t}\n\n\t// parse URL to check if this is embedded mode (domain \"scraper\" and schema \"https\")\n\tparsedURL, err := url.Parse(privateURL)\n\tif err != nil {\n\t\t// if URL is malformed, treat as external\n\t\treturn \"external\"\n\t}\n\n\tif parsedURL.Scheme == DefaultScraperSchema && parsedURL.Hostname() == DefaultScraperDomain {\n\t\treturn \"embedded\"\n\t}\n\n\treturn \"external\"\n}\n\n// extractCredentialsFromURL extracts username and password from URL\nfunc (c *controller) extractCredentialsFromURL(urlStr string) (username, password string) {\n\tif urlStr == \"\" {\n\t\treturn \"\", \"\"\n\t}\n\n\tparsedURL, err := url.Parse(urlStr)\n\tif err != nil {\n\t\treturn \"\", \"\"\n\t}\n\n\tif parsedURL.User == nil {\n\t\treturn \"\", \"\"\n\t}\n\n\tusername = parsedURL.User.Username()\n\tpassword, _ = parsedURL.User.Password()\n\n\treturn username, password\n}\n\n// UpdateScraperConfig updates scraper configuration\nfunc (c *controller) UpdateScraperConfig(config *ScraperConfig) error {\n\tif config == nil {\n\t\treturn fmt.Errorf(\"config cannot be nil\")\n\t}\n\n\tswitch config.Mode {\n\tcase \"disabled\":\n\t\t// clear scraper URLs, preserve local settings\n\t\tif err := c.SetVar(\"SCRAPER_PUBLIC_URL\", \"\"); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to clear SCRAPER_PUBLIC_URL: %w\", err)\n\t\t}\n\t\tif err := c.SetVar(\"SCRAPER_PRIVATE_URL\", \"\"); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to clear SCRAPER_PRIVATE_URL: %w\", err)\n\t\t}\n\t\t// local settings remain unchanged\n\n\tcase \"external\":\n\t\t// construct URLs with credentials if provided\n\t\tprivateURL := config.PrivateURL.Value\n\t\tif config.PrivateUsername != \"\" && config.PrivatePassword != \"\" {\n\t\t\tprivateURL = c.addCredentialsToURL(config.PrivateURL.Value, config.PrivateUsername, config.PrivatePassword)\n\t\t}\n\n\t\tpublicURL := config.PublicURL.Value\n\t\tif config.PublicUsername != \"\" && config.PublicPassword != \"\" {\n\t\t\tpublicURL = c.addCredentialsToURL(config.PublicURL.Value, config.PublicUsername, config.PublicPassword)\n\t\t}\n\n\t\tif err := c.SetVar(\"SCRAPER_PUBLIC_URL\", publicURL); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set SCRAPER_PUBLIC_URL: %w\", err)\n\t\t}\n\t\tif err := c.SetVar(\"SCRAPER_PRIVATE_URL\", privateURL); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set SCRAPER_PRIVATE_URL: %w\", err)\n\t\t}\n\t\t// local settings remain unchanged\n\n\tcase \"embedded\":\n\t\t// handle embedded mode\n\t\tprivateURL := DefaultScraperBaseURL\n\t\tif config.PrivateUsername != \"\" && config.PrivatePassword != \"\" {\n\t\t\tprivateURL = c.addCredentialsToURL(privateURL, config.PrivateUsername, config.PrivatePassword)\n\t\t}\n\n\t\tpublicURL := config.PublicURL.Value\n\t\tif config.PublicUsername != \"\" && config.PublicPassword != \"\" {\n\t\t\t// fallback to private URL if public URL is not set\n\t\t\tif publicURL == \"\" {\n\t\t\t\tpublicURL = privateURL\n\t\t\t}\n\t\t\tpublicURL = c.addCredentialsToURL(publicURL, config.PublicUsername, config.PublicPassword)\n\t\t}\n\n\t\t// update all relevant variables\n\t\tif err := c.SetVar(\"SCRAPER_PUBLIC_URL\", publicURL); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set SCRAPER_PUBLIC_URL: %w\", err)\n\t\t}\n\t\tif err := c.SetVar(\"SCRAPER_PRIVATE_URL\", privateURL); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set SCRAPER_PRIVATE_URL: %w\", err)\n\t\t}\n\t\tif err := c.SetVar(\"LOCAL_SCRAPER_USERNAME\", config.PrivateUsername); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set LOCAL_SCRAPER_USERNAME: %w\", err)\n\t\t}\n\t\tif err := c.SetVar(\"LOCAL_SCRAPER_PASSWORD\", config.PrivatePassword); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set LOCAL_SCRAPER_PASSWORD: %w\", err)\n\t\t}\n\t\tif err := c.SetVar(\"LOCAL_SCRAPER_MAX_CONCURRENT_SESSIONS\", config.MaxConcurrentSessions.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set LOCAL_SCRAPER_MAX_CONCURRENT_SESSIONS: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// addCredentialsToURL adds username and password to URL\nfunc (c *controller) addCredentialsToURL(urlStr, username, password string) string {\n\tif username == \"\" || password == \"\" {\n\t\treturn urlStr\n\t}\n\n\tif urlStr == \"\" {\n\t\t// do not implicitly set a default base url for non-scraper contexts\n\t\t// caller must provide a valid base url; return empty to avoid crafting invalid urls\n\t\treturn \"\"\n\t}\n\n\tparsedURL, err := url.Parse(urlStr)\n\tif err != nil {\n\t\treturn urlStr\n\t}\n\n\t// set user info\n\tparsedURL.User = url.UserPassword(username, password)\n\n\treturn parsedURL.String()\n}\n\n// ResetScraperConfig resets scraper configuration to defaults\nfunc (c *controller) ResetScraperConfig() *ScraperConfig {\n\t// reset all scraper-related environment variables to their defaults\n\tvars := []string{\n\t\t\"SCRAPER_PUBLIC_URL\",\n\t\t\"SCRAPER_PRIVATE_URL\",\n\t\t\"LOCAL_SCRAPER_USERNAME\",\n\t\t\"LOCAL_SCRAPER_PASSWORD\",\n\t\t\"LOCAL_SCRAPER_MAX_CONCURRENT_SESSIONS\",\n\t}\n\n\tif err := c.ResetVars(vars); err != nil {\n\t\treturn nil\n\t}\n\n\treturn c.GetScraperConfig()\n}\n\n// SearchEnginesConfig represents search engines configuration settings\ntype SearchEnginesConfig struct {\n\t// direct form field mappings using loader.EnvVar\n\t// these fields directly correspond to environment variables and form inputs (not computed)\n\tDuckDuckGoEnabled loader.EnvVar // DUCKDUCKGO_ENABLED\n\tSploitusEnabled loader.EnvVar // SPLOITUS_ENABLED\n\tPerplexityAPIKey loader.EnvVar // PERPLEXITY_API_KEY\n\tTavilyAPIKey loader.EnvVar // TAVILY_API_KEY\n\tTraversaalAPIKey loader.EnvVar // TRAVERSAAL_API_KEY\n\tGoogleAPIKey loader.EnvVar // GOOGLE_API_KEY\n\tGoogleCXKey loader.EnvVar // GOOGLE_CX_KEY\n\tGoogleLRKey loader.EnvVar // GOOGLE_LR_KEY\n\n\t// duckduckgo extra settings\n\tDuckDuckGoRegion loader.EnvVar // DUCKDUCKGO_REGION\n\tDuckDuckGoSafeSearch loader.EnvVar // DUCKDUCKGO_SAFESEARCH\n\tDuckDuckGoTimeRange loader.EnvVar // DUCKDUCKGO_TIME_RANGE\n\n\t// perplexity extra settings\n\tPerplexityModel loader.EnvVar // PERPLEXITY_MODEL\n\tPerplexityContextSize loader.EnvVar // PERPLEXITY_CONTEXT_SIZE\n\n\t// searxng extra settings\n\tSearxngURL loader.EnvVar // SEARXNG_URL\n\tSearxngCategories loader.EnvVar // SEARXNG_CATEGORIES\n\tSearxngLanguage loader.EnvVar // SEARXNG_LANGUAGE\n\tSearxngSafeSearch loader.EnvVar // SEARXNG_SAFESEARCH\n\tSearxngTimeRange loader.EnvVar // SEARXNG_TIME_RANGE\n\tSearxngTimeout loader.EnvVar // SEARXNG_TIMEOUT\n\n\t// computed fields (not directly mapped to env vars)\n\tConfiguredCount int // number of configured engines\n}\n\n// GetSearchEnginesConfig returns current search engines configuration\nfunc (c *controller) GetSearchEnginesConfig() *SearchEnginesConfig {\n\t// get all environment variables using the state controller\n\tduckduckgoEnabled, _ := c.GetVar(\"DUCKDUCKGO_ENABLED\")\n\tduckduckgoRegion, _ := c.GetVar(\"DUCKDUCKGO_REGION\")\n\tduckduckgoSafeSearch, _ := c.GetVar(\"DUCKDUCKGO_SAFESEARCH\")\n\tduckduckgoTimeRange, _ := c.GetVar(\"DUCKDUCKGO_TIME_RANGE\")\n\tsploitusEnabled, _ := c.GetVar(\"SPLOITUS_ENABLED\")\n\tperplexityAPIKey, _ := c.GetVar(\"PERPLEXITY_API_KEY\")\n\ttavilyAPIKey, _ := c.GetVar(\"TAVILY_API_KEY\")\n\ttraversaalAPIKey, _ := c.GetVar(\"TRAVERSAAL_API_KEY\")\n\tgoogleAPIKey, _ := c.GetVar(\"GOOGLE_API_KEY\")\n\tgoogleCXKey, _ := c.GetVar(\"GOOGLE_CX_KEY\")\n\tgoogleLRKey, _ := c.GetVar(\"GOOGLE_LR_KEY\")\n\tperplexityModel, _ := c.GetVar(\"PERPLEXITY_MODEL\")\n\tperplexityContextSize, _ := c.GetVar(\"PERPLEXITY_CONTEXT_SIZE\")\n\tsearxngURL, _ := c.GetVar(\"SEARXNG_URL\")\n\tsearxngCategories, _ := c.GetVar(\"SEARXNG_CATEGORIES\")\n\tsearxngLanguage, _ := c.GetVar(\"SEARXNG_LANGUAGE\")\n\tsearxngSafeSearch, _ := c.GetVar(\"SEARXNG_SAFESEARCH\")\n\tsearxngTimeRange, _ := c.GetVar(\"SEARXNG_TIME_RANGE\")\n\tsearxngTimeout, _ := c.GetVar(\"SEARXNG_TIMEOUT\")\n\n\tconfig := &SearchEnginesConfig{\n\t\tDuckDuckGoEnabled: duckduckgoEnabled,\n\t\tDuckDuckGoRegion: duckduckgoRegion,\n\t\tDuckDuckGoSafeSearch: duckduckgoSafeSearch,\n\t\tDuckDuckGoTimeRange: duckduckgoTimeRange,\n\t\tSploitusEnabled: sploitusEnabled,\n\t\tPerplexityAPIKey: perplexityAPIKey,\n\t\tPerplexityModel: perplexityModel,\n\t\tPerplexityContextSize: perplexityContextSize,\n\t\tTavilyAPIKey: tavilyAPIKey,\n\t\tTraversaalAPIKey: traversaalAPIKey,\n\t\tGoogleAPIKey: googleAPIKey,\n\t\tGoogleCXKey: googleCXKey,\n\t\tGoogleLRKey: googleLRKey,\n\t\tSearxngURL: searxngURL,\n\t\tSearxngCategories: searxngCategories,\n\t\tSearxngLanguage: searxngLanguage,\n\t\tSearxngSafeSearch: searxngSafeSearch,\n\t\tSearxngTimeRange: searxngTimeRange,\n\t\tSearxngTimeout: searxngTimeout,\n\t}\n\n\t// compute configured count\n\tconfiguredCount := 0\n\tif duckduckgoEnabled.Value == \"true\" {\n\t\tconfiguredCount++\n\t} else if duckduckgoEnabled.Value == \"\" && duckduckgoEnabled.Default == \"true\" {\n\t\tconfiguredCount++\n\t}\n\tif sploitusEnabled.Value == \"true\" {\n\t\tconfiguredCount++\n\t} else if sploitusEnabled.Value == \"\" && sploitusEnabled.Default == \"true\" {\n\t\tconfiguredCount++\n\t}\n\tif perplexityAPIKey.Value != \"\" {\n\t\tconfiguredCount++\n\t}\n\tif tavilyAPIKey.Value != \"\" {\n\t\tconfiguredCount++\n\t}\n\tif traversaalAPIKey.Value != \"\" {\n\t\tconfiguredCount++\n\t}\n\tif googleAPIKey.Value != \"\" && googleCXKey.Value != \"\" {\n\t\tconfiguredCount++\n\t}\n\tif searxngURL.Value != \"\" {\n\t\tconfiguredCount++\n\t}\n\tconfig.ConfiguredCount = configuredCount\n\n\treturn config\n}\n\n// UpdateSearchEnginesConfig updates search engines configuration\nfunc (c *controller) UpdateSearchEnginesConfig(config *SearchEnginesConfig) error {\n\tif config == nil {\n\t\treturn fmt.Errorf(\"config cannot be nil\")\n\t}\n\n\t// update environment variables\n\tif err := c.SetVar(\"DUCKDUCKGO_ENABLED\", config.DuckDuckGoEnabled.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set DUCKDUCKGO_ENABLED: %w\", err)\n\t}\n\tif err := c.SetVar(\"DUCKDUCKGO_REGION\", config.DuckDuckGoRegion.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set DUCKDUCKGO_REGION: %w\", err)\n\t}\n\tif err := c.SetVar(\"DUCKDUCKGO_SAFESEARCH\", config.DuckDuckGoSafeSearch.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set DUCKDUCKGO_SAFESEARCH: %w\", err)\n\t}\n\tif err := c.SetVar(\"DUCKDUCKGO_TIME_RANGE\", config.DuckDuckGoTimeRange.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set DUCKDUCKGO_TIME_RANGE: %w\", err)\n\t}\n\tif err := c.SetVar(\"SPLOITUS_ENABLED\", config.SploitusEnabled.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set SPLOITUS_ENABLED: %w\", err)\n\t}\n\tif err := c.SetVar(\"PERPLEXITY_API_KEY\", config.PerplexityAPIKey.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set PERPLEXITY_API_KEY: %w\", err)\n\t}\n\tif err := c.SetVar(\"PERPLEXITY_MODEL\", config.PerplexityModel.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set PERPLEXITY_MODEL: %w\", err)\n\t}\n\tif err := c.SetVar(\"PERPLEXITY_CONTEXT_SIZE\", config.PerplexityContextSize.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set PERPLEXITY_CONTEXT_SIZE: %w\", err)\n\t}\n\tif err := c.SetVar(\"TAVILY_API_KEY\", config.TavilyAPIKey.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set TAVILY_API_KEY: %w\", err)\n\t}\n\tif err := c.SetVar(\"TRAVERSAAL_API_KEY\", config.TraversaalAPIKey.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set TRAVERSAAL_API_KEY: %w\", err)\n\t}\n\tif err := c.SetVar(\"GOOGLE_API_KEY\", config.GoogleAPIKey.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set GOOGLE_API_KEY: %w\", err)\n\t}\n\tif err := c.SetVar(\"GOOGLE_CX_KEY\", config.GoogleCXKey.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set GOOGLE_CX_KEY: %w\", err)\n\t}\n\tif err := c.SetVar(\"GOOGLE_LR_KEY\", config.GoogleLRKey.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set GOOGLE_LR_KEY: %w\", err)\n\t}\n\tif err := c.SetVar(\"SEARXNG_URL\", config.SearxngURL.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set SEARXNG_URL: %w\", err)\n\t}\n\tif err := c.SetVar(\"SEARXNG_CATEGORIES\", config.SearxngCategories.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set SEARXNG_CATEGORIES: %w\", err)\n\t}\n\tif err := c.SetVar(\"SEARXNG_LANGUAGE\", config.SearxngLanguage.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set SEARXNG_LANGUAGE: %w\", err)\n\t}\n\tif err := c.SetVar(\"SEARXNG_SAFESEARCH\", config.SearxngSafeSearch.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set SEARXNG_SAFESEARCH: %w\", err)\n\t}\n\tif err := c.SetVar(\"SEARXNG_TIME_RANGE\", config.SearxngTimeRange.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set SEARXNG_TIME_RANGE: %w\", err)\n\t}\n\tif err := c.SetVar(\"SEARXNG_TIMEOUT\", config.SearxngTimeout.Value); err != nil {\n\t\treturn fmt.Errorf(\"failed to set SEARXNG_TIMEOUT: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// ResetSearchEnginesConfig resets search engines configuration to defaults\nfunc (c *controller) ResetSearchEnginesConfig() *SearchEnginesConfig {\n\t// reset all search engines-related environment variables to their defaults\n\tvars := []string{\n\t\t\"DUCKDUCKGO_ENABLED\",\n\t\t\"DUCKDUCKGO_REGION\",\n\t\t\"DUCKDUCKGO_SAFESEARCH\",\n\t\t\"DUCKDUCKGO_TIME_RANGE\",\n\t\t\"SPLOITUS_ENABLED\",\n\t\t\"PERPLEXITY_API_KEY\",\n\t\t\"PERPLEXITY_MODEL\",\n\t\t\"PERPLEXITY_CONTEXT_SIZE\",\n\t\t\"TAVILY_API_KEY\",\n\t\t\"TRAVERSAAL_API_KEY\",\n\t\t\"GOOGLE_API_KEY\",\n\t\t\"GOOGLE_CX_KEY\",\n\t\t\"GOOGLE_LR_KEY\",\n\t\t\"SEARXNG_URL\",\n\t\t\"SEARXNG_CATEGORIES\",\n\t\t\"SEARXNG_LANGUAGE\",\n\t\t\"SEARXNG_SAFESEARCH\",\n\t\t\"SEARXNG_TIME_RANGE\",\n\t\t\"SEARXNG_TIMEOUT\",\n\t}\n\n\tif err := c.ResetVars(vars); err != nil {\n\t\treturn nil\n\t}\n\n\treturn c.GetSearchEnginesConfig()\n}\n\n// DockerConfig represents Docker environment configuration\ntype DockerConfig struct {\n\t// direct form field mappings using loader.EnvVar\n\t// these fields directly correspond to environment variables and form inputs (not computed)\n\tDockerInside loader.EnvVar // DOCKER_INSIDE\n\tDockerNetAdmin loader.EnvVar // DOCKER_NET_ADMIN\n\tDockerSocket loader.EnvVar // DOCKER_SOCKET\n\tDockerNetwork loader.EnvVar // DOCKER_NETWORK\n\tDockerPublicIP loader.EnvVar // DOCKER_PUBLIC_IP\n\tDockerWorkDir loader.EnvVar // DOCKER_WORK_DIR\n\tDockerDefaultImage loader.EnvVar // DOCKER_DEFAULT_IMAGE\n\tDockerDefaultImageForPentest loader.EnvVar // DOCKER_DEFAULT_IMAGE_FOR_PENTEST\n\n\t// TLS connection settings (optional)\n\tDockerHost loader.EnvVar // DOCKER_HOST\n\tDockerTLSVerify loader.EnvVar // DOCKER_TLS_VERIFY\n\tHostDockerCertPath loader.EnvVar // PENTAGI_DOCKER_CERT_PATH\n\n\t// computed fields (not directly mapped to env vars)\n\tConfigured bool\n}\n\n// GetDockerConfig returns the current Docker configuration\nfunc (c *controller) GetDockerConfig() *DockerConfig {\n\tvars, _ := c.GetVars([]string{\n\t\t\"DOCKER_INSIDE\",\n\t\t\"DOCKER_NET_ADMIN\",\n\t\t\"DOCKER_SOCKET\",\n\t\t\"DOCKER_NETWORK\",\n\t\t\"DOCKER_PUBLIC_IP\",\n\t\t\"DOCKER_WORK_DIR\",\n\t\t\"DOCKER_DEFAULT_IMAGE\",\n\t\t\"DOCKER_DEFAULT_IMAGE_FOR_PENTEST\",\n\t\t\"DOCKER_HOST\",\n\t\t\"DOCKER_TLS_VERIFY\",\n\t\t\"PENTAGI_DOCKER_CERT_PATH\",\n\t})\n\n\tconfig := &DockerConfig{\n\t\tDockerInside: vars[\"DOCKER_INSIDE\"],\n\t\tDockerNetAdmin: vars[\"DOCKER_NET_ADMIN\"],\n\t\tDockerSocket: vars[\"DOCKER_SOCKET\"],\n\t\tDockerNetwork: vars[\"DOCKER_NETWORK\"],\n\t\tDockerPublicIP: vars[\"DOCKER_PUBLIC_IP\"],\n\t\tDockerWorkDir: vars[\"DOCKER_WORK_DIR\"],\n\t\tDockerDefaultImage: vars[\"DOCKER_DEFAULT_IMAGE\"],\n\t\tDockerDefaultImageForPentest: vars[\"DOCKER_DEFAULT_IMAGE_FOR_PENTEST\"],\n\t\tDockerHost: vars[\"DOCKER_HOST\"],\n\t\tDockerTLSVerify: vars[\"DOCKER_TLS_VERIFY\"],\n\t\tHostDockerCertPath: vars[\"PENTAGI_DOCKER_CERT_PATH\"],\n\t}\n\n\t// patch docker host default value\n\tif config.DockerHost.Default == \"\" {\n\t\tconfig.DockerHost.Default = \"unix:///var/run/docker.sock\"\n\t}\n\n\t// determine if Docker is configured\n\t// basic configuration is considered complete if DOCKER_INSIDE is set or default images are specified\n\tconfig.Configured = config.DockerInside.Value != \"\" ||\n\t\tconfig.DockerDefaultImage.Value != \"\" ||\n\t\tconfig.DockerDefaultImageForPentest.Value != \"\"\n\n\treturn config\n}\n\n// UpdateDockerConfig updates the Docker configuration\nfunc (c *controller) UpdateDockerConfig(config *DockerConfig) error {\n\tupdates := map[string]string{\n\t\t\"DOCKER_INSIDE\": config.DockerInside.Value,\n\t\t\"DOCKER_NET_ADMIN\": config.DockerNetAdmin.Value,\n\t\t\"DOCKER_SOCKET\": config.DockerSocket.Value,\n\t\t\"DOCKER_NETWORK\": config.DockerNetwork.Value,\n\t\t\"DOCKER_PUBLIC_IP\": config.DockerPublicIP.Value,\n\t\t\"DOCKER_WORK_DIR\": config.DockerWorkDir.Value,\n\t\t\"DOCKER_DEFAULT_IMAGE\": config.DockerDefaultImage.Value,\n\t\t\"DOCKER_DEFAULT_IMAGE_FOR_PENTEST\": config.DockerDefaultImageForPentest.Value,\n\t\t\"DOCKER_HOST\": config.DockerHost.Value,\n\t\t\"DOCKER_TLS_VERIFY\": config.DockerTLSVerify.Value,\n\t\t\"PENTAGI_DOCKER_CERT_PATH\": config.HostDockerCertPath.Value,\n\t}\n\n\tdockerHost := config.DockerHost.Value\n\tif strings.HasPrefix(dockerHost, \"unix://\") && !config.DockerHost.IsDefault() {\n\t\t// mount custom docker socket to the pentagi container\n\t\tupdates[\"PENTAGI_DOCKER_SOCKET\"] = strings.TrimPrefix(dockerHost, \"unix://\")\n\t} else {\n\t\t// ensure previous custom socket mapping is cleared when not using unix socket\n\t\tupdates[\"PENTAGI_DOCKER_SOCKET\"] = \"\"\n\t}\n\n\tif config.HostDockerCertPath.Value != \"\" {\n\t\tupdates[\"DOCKER_CERT_PATH\"] = DefaultDockerCertPath\n\t} else {\n\t\tupdates[\"DOCKER_CERT_PATH\"] = \"\"\n\t}\n\n\tif err := c.SetVars(updates); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// ResetDockerConfig resets the Docker configuration to defaults\nfunc (c *controller) ResetDockerConfig() *DockerConfig {\n\tvars := []string{\n\t\t\"DOCKER_INSIDE\",\n\t\t\"DOCKER_NET_ADMIN\",\n\t\t\"DOCKER_SOCKET\",\n\t\t\"DOCKER_NETWORK\",\n\t\t\"DOCKER_PUBLIC_IP\",\n\t\t\"DOCKER_WORK_DIR\",\n\t\t\"DOCKER_DEFAULT_IMAGE\",\n\t\t\"DOCKER_DEFAULT_IMAGE_FOR_PENTEST\",\n\t\t\"DOCKER_HOST\",\n\t\t\"DOCKER_TLS_VERIFY\",\n\t\t\"DOCKER_CERT_PATH\",\n\t\t// Volume mapping for docker socket\n\t\t\"PENTAGI_DOCKER_SOCKET\",\n\t\t\"PENTAGI_DOCKER_CERT_PATH\",\n\t}\n\n\tif err := c.ResetVars(vars); err != nil {\n\t\treturn nil\n\t}\n\n\treturn c.GetDockerConfig()\n}\n\n// ServerSettingsConfig represents PentAGI server settings configuration\ntype ServerSettingsConfig struct {\n\t// direct form field mappings using loader.EnvVar\n\tLicenseKey loader.EnvVar // LICENSE_KEY\n\tListenIP loader.EnvVar // PENTAGI_LISTEN_IP\n\tListenPort loader.EnvVar // PENTAGI_LISTEN_PORT\n\tPublicURL loader.EnvVar // PUBLIC_URL\n\tCorsOrigins loader.EnvVar // CORS_ORIGINS\n\tCookieSigningSalt loader.EnvVar // COOKIE_SIGNING_SALT\n\tProxyURL loader.EnvVar // PROXY_URL\n\tHTTPClientTimeout loader.EnvVar // HTTP_CLIENT_TIMEOUT\n\tExternalSSLCAPath loader.EnvVar // EXTERNAL_SSL_CA_PATH\n\tExternalSSLInsecure loader.EnvVar // EXTERNAL_SSL_INSECURE\n\tSSLDir loader.EnvVar // PENTAGI_SSL_DIR\n\tDataDir loader.EnvVar // PENTAGI_DATA_DIR\n\n\t// parsed credentials for proxy server (extracted from URLs)\n\tProxyUsername string\n\tProxyPassword string\n}\n\n// GetServerSettingsConfig returns current server settings\nfunc (c *controller) GetServerSettingsConfig() *ServerSettingsConfig {\n\tvars, _ := c.GetVars([]string{\n\t\t\"LICENSE_KEY\",\n\t\t\"PENTAGI_LISTEN_IP\",\n\t\t\"PENTAGI_LISTEN_PORT\",\n\t\t\"PUBLIC_URL\",\n\t\t\"CORS_ORIGINS\",\n\t\t\"COOKIE_SIGNING_SALT\",\n\t\t\"PROXY_URL\",\n\t\t\"HTTP_CLIENT_TIMEOUT\",\n\t\t\"EXTERNAL_SSL_CA_PATH\",\n\t\t\"EXTERNAL_SSL_INSECURE\",\n\t\t\"PENTAGI_SSL_DIR\",\n\t\t\"PENTAGI_DATA_DIR\",\n\t})\n\n\tdefaults := map[string]string{\n\t\t\"LICENSE_KEY\": \"\",\n\t\t\"PENTAGI_LISTEN_IP\": \"127.0.0.1\",\n\t\t\"PENTAGI_LISTEN_PORT\": \"8443\",\n\t\t\"PUBLIC_URL\": \"https://localhost:8443\",\n\t\t\"CORS_ORIGINS\": \"https://localhost:8443\",\n\t\t\"PENTAGI_DATA_DIR\": \"pentagi-data\",\n\t\t\"PENTAGI_SSL_DIR\": \"pentagi-ssl\",\n\t\t\"HTTP_CLIENT_TIMEOUT\": \"600\",\n\t\t\"EXTERNAL_SSL_INSECURE\": \"false\",\n\t}\n\n\tfor varName, defaultValue := range defaults {\n\t\tif v := vars[varName]; v.Default == \"\" {\n\t\t\tv.Default = defaultValue\n\t\t\tvars[varName] = v\n\t\t}\n\t}\n\n\tcfg := &ServerSettingsConfig{\n\t\tLicenseKey: vars[\"LICENSE_KEY\"],\n\t\tListenIP: vars[\"PENTAGI_LISTEN_IP\"],\n\t\tListenPort: vars[\"PENTAGI_LISTEN_PORT\"],\n\t\tPublicURL: vars[\"PUBLIC_URL\"],\n\t\tCorsOrigins: vars[\"CORS_ORIGINS\"],\n\t\tCookieSigningSalt: vars[\"COOKIE_SIGNING_SALT\"],\n\t\tProxyURL: vars[\"PROXY_URL\"],\n\t\tHTTPClientTimeout: vars[\"HTTP_CLIENT_TIMEOUT\"],\n\t\tExternalSSLCAPath: vars[\"EXTERNAL_SSL_CA_PATH\"],\n\t\tExternalSSLInsecure: vars[\"EXTERNAL_SSL_INSECURE\"],\n\t\tSSLDir: vars[\"PENTAGI_SSL_DIR\"],\n\t\tDataDir: vars[\"PENTAGI_DATA_DIR\"],\n\t}\n\n\t// split proxy URL into credentials + naked URL for UI\n\tif cfg.ProxyURL.Value != \"\" {\n\t\tuser, pass := c.extractCredentialsFromURL(cfg.ProxyURL.Value)\n\t\tcfg.ProxyUsername = user\n\t\tcfg.ProxyPassword = pass\n\t\tcfg.ProxyURL.Value = RemoveCredentialsFromURL(cfg.ProxyURL.Value)\n\t}\n\n\treturn cfg\n}\n\n// UpdateServerSettingsConfig updates server settings\nfunc (c *controller) UpdateServerSettingsConfig(config *ServerSettingsConfig) error {\n\tif config == nil {\n\t\treturn fmt.Errorf(\"config cannot be nil\")\n\t}\n\n\t// build proxy URL with credentials if provided\n\tproxyURL := config.ProxyURL.Value\n\tif proxyURL != \"\" && config.ProxyUsername != \"\" && config.ProxyPassword != \"\" {\n\t\t// add credentials to proxy URL only if proxy URL is provided\n\t\tproxyURL = c.addCredentialsToURL(proxyURL, config.ProxyUsername, config.ProxyPassword)\n\t}\n\n\tupdates := map[string]string{\n\t\t\"LICENSE_KEY\": config.LicenseKey.Value,\n\t\t\"PENTAGI_LISTEN_IP\": config.ListenIP.Value,\n\t\t\"PENTAGI_LISTEN_PORT\": config.ListenPort.Value,\n\t\t\"PUBLIC_URL\": config.PublicURL.Value,\n\t\t\"CORS_ORIGINS\": config.CorsOrigins.Value,\n\t\t\"COOKIE_SIGNING_SALT\": config.CookieSigningSalt.Value,\n\t\t\"PROXY_URL\": proxyURL,\n\t\t\"HTTP_CLIENT_TIMEOUT\": config.HTTPClientTimeout.Value,\n\t\t\"EXTERNAL_SSL_CA_PATH\": config.ExternalSSLCAPath.Value,\n\t\t\"EXTERNAL_SSL_INSECURE\": config.ExternalSSLInsecure.Value,\n\t\t\"PENTAGI_SSL_DIR\": config.SSLDir.Value,\n\t\t\"PENTAGI_DATA_DIR\": config.DataDir.Value,\n\t}\n\n\tif err := c.SetVars(updates); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// ResetServerSettingsConfig resets server settings to defaults\nfunc (c *controller) ResetServerSettingsConfig() *ServerSettingsConfig {\n\tvars := []string{\n\t\t\"LICENSE_KEY\",\n\t\t\"PENTAGI_LISTEN_IP\",\n\t\t\"PENTAGI_LISTEN_PORT\",\n\t\t\"PUBLIC_URL\",\n\t\t\"CORS_ORIGINS\",\n\t\t\"COOKIE_SIGNING_SALT\",\n\t\t\"PROXY_URL\",\n\t\t\"HTTP_CLIENT_TIMEOUT\",\n\t\t\"EXTERNAL_SSL_CA_PATH\",\n\t\t\"EXTERNAL_SSL_INSECURE\",\n\t\t\"PENTAGI_SSL_DIR\",\n\t\t\"PENTAGI_DATA_DIR\",\n\t}\n\n\tif err := c.ResetVars(vars); err != nil {\n\t\treturn nil\n\t}\n\n\treturn c.GetServerSettingsConfig()\n}\n\n// ChangeInfo represents information about a single environment variable change\ntype ChangeInfo struct {\n\tVariable string // environment variable name\n\tDescription string // localized description\n\tNewValue string // new value being set\n\tMasked bool // whether the value should be masked in display\n}\n\n// ApplyChangesConfig contains information about pending changes and installation status\ntype ApplyChangesConfig struct {\n\t// installation state\n\tIsInstalled bool // whether PentAGI is currently installed\n\n\t// deployment selections\n\tLangfuseEnabled bool // whether Langfuse embedded deployment is selected\n\tObservabilityEnabled bool // whether Observability embedded deployment is selected\n\n\t// changes information\n\tChanges []ChangeInfo // list of pending environment variable changes\n\tChangesCount int // total number of changes\n\tHasCritical bool // whether there are critical changes requiring restart\n\tHasSecrets bool // whether there are secret/sensitive changes\n}\n\n// GetApplyChangesConfig returns the current apply changes configuration\nfunc (c *controller) GetApplyChangesConfig() *ApplyChangesConfig {\n\tconfig := &ApplyChangesConfig{\n\t\tIsInstalled: c.checker.PentagiInstalled,\n\t\tChanges: []ChangeInfo{},\n\t}\n\n\t// check deployment selections\n\tlangfuseConfig := c.GetLangfuseConfig()\n\tconfig.LangfuseEnabled = langfuseConfig.DeploymentType == \"embedded\"\n\n\tobservabilityConfig := c.GetObservabilityConfig()\n\tconfig.ObservabilityEnabled = observabilityConfig.DeploymentType == \"embedded\"\n\n\t// collect all changed variables\n\tallVars := c.GetAllVars()\n\tfor varName, envVar := range allVars {\n\t\tif envVar.IsChanged {\n\t\t\tdescription := c.getVariableDescription(varName)\n\t\t\tmasked := c.isVariableMasked(varName)\n\t\t\tvalue := envVar.Value\n\t\t\tif value == \"\" {\n\t\t\t\tvalue = \"{EMPTY}\"\n\t\t\t}\n\n\t\t\tconfig.Changes = append(config.Changes, ChangeInfo{\n\t\t\t\tVariable: varName,\n\t\t\t\tDescription: description,\n\t\t\t\tNewValue: value,\n\t\t\t\tMasked: masked,\n\t\t\t})\n\n\t\t\t// mark critical and secret changes\n\t\t\tif c.isCriticalVariable(varName) {\n\t\t\t\tconfig.HasCritical = true\n\t\t\t}\n\t\t\tif masked {\n\t\t\t\tconfig.HasSecrets = true\n\t\t\t}\n\t\t}\n\t}\n\n\tslices.SortFunc(config.Changes, func(a, b ChangeInfo) int {\n\t\treturn strings.Compare(a.Description, b.Description)\n\t})\n\n\tconfig.ChangesCount = len(config.Changes)\n\treturn config\n}\n\n// getVariableDescription returns a user-friendly description for an environment variable\nfunc (c *controller) getVariableDescription(varName string) string {\n\t// map of environment variable name -> description\n\tenvVarDescriptions := map[string]string{\n\t\t\"OPEN_AI_KEY\": locale.EnvDesc_OPEN_AI_KEY,\n\t\t\"OPEN_AI_SERVER_URL\": locale.EnvDesc_OPEN_AI_SERVER_URL,\n\t\t\"ANTHROPIC_API_KEY\": locale.EnvDesc_ANTHROPIC_API_KEY,\n\t\t\"ANTHROPIC_SERVER_URL\": locale.EnvDesc_ANTHROPIC_SERVER_URL,\n\t\t\"GEMINI_API_KEY\": locale.EnvDesc_GEMINI_API_KEY,\n\t\t\"GEMINI_SERVER_URL\": locale.EnvDesc_GEMINI_SERVER_URL,\n\t\t\"BEDROCK_DEFAULT_AUTH\": locale.EnvDesc_BEDROCK_DEFAULT_AUTH,\n\t\t\"BEDROCK_BEARER_TOKEN\": locale.EnvDesc_BEDROCK_BEARER_TOKEN,\n\t\t\"BEDROCK_ACCESS_KEY_ID\": locale.EnvDesc_BEDROCK_ACCESS_KEY_ID,\n\t\t\"BEDROCK_SECRET_ACCESS_KEY\": locale.EnvDesc_BEDROCK_SECRET_ACCESS_KEY,\n\t\t\"BEDROCK_SESSION_TOKEN\": locale.EnvDesc_BEDROCK_SESSION_TOKEN,\n\t\t\"BEDROCK_REGION\": locale.EnvDesc_BEDROCK_REGION,\n\t\t\"BEDROCK_SERVER_URL\": locale.EnvDesc_BEDROCK_SERVER_URL,\n\t\t\"OLLAMA_SERVER_URL\": locale.EnvDesc_OLLAMA_SERVER_URL,\n\t\t\"OLLAMA_SERVER_API_KEY\": locale.EnvDesc_OLLAMA_SERVER_API_KEY,\n\t\t\"OLLAMA_SERVER_MODEL\": locale.EnvDesc_OLLAMA_SERVER_MODEL,\n\t\t\"OLLAMA_SERVER_CONFIG_PATH\": locale.EnvDesc_OLLAMA_SERVER_CONFIG_PATH,\n\t\t\"OLLAMA_SERVER_PULL_MODELS_TIMEOUT\": locale.EnvDesc_OLLAMA_SERVER_PULL_MODELS_TIMEOUT,\n\t\t\"OLLAMA_SERVER_PULL_MODELS_ENABLED\": locale.EnvDesc_OLLAMA_SERVER_PULL_MODELS_ENABLED,\n\t\t\"OLLAMA_SERVER_LOAD_MODELS_ENABLED\": locale.EnvDesc_OLLAMA_SERVER_LOAD_MODELS_ENABLED,\n\t\t\"DEEPSEEK_API_KEY\": locale.EnvDesc_DEEPSEEK_API_KEY,\n\t\t\"DEEPSEEK_SERVER_URL\": locale.EnvDesc_DEEPSEEK_SERVER_URL,\n\t\t\"DEEPSEEK_PROVIDER\": locale.EnvDesc_DEEPSEEK_PROVIDER,\n\t\t\"GLM_API_KEY\": locale.EnvDesc_GLM_API_KEY,\n\t\t\"GLM_SERVER_URL\": locale.EnvDesc_GLM_SERVER_URL,\n\t\t\"GLM_PROVIDER\": locale.EnvDesc_GLM_PROVIDER,\n\t\t\"KIMI_API_KEY\": locale.EnvDesc_KIMI_API_KEY,\n\t\t\"KIMI_SERVER_URL\": locale.EnvDesc_KIMI_SERVER_URL,\n\t\t\"KIMI_PROVIDER\": locale.EnvDesc_KIMI_PROVIDER,\n\t\t\"QWEN_API_KEY\": locale.EnvDesc_QWEN_API_KEY,\n\t\t\"QWEN_SERVER_URL\": locale.EnvDesc_QWEN_SERVER_URL,\n\t\t\"QWEN_PROVIDER\": locale.EnvDesc_QWEN_PROVIDER,\n\t\t\"LLM_SERVER_URL\": locale.EnvDesc_LLM_SERVER_URL,\n\t\t\"LLM_SERVER_KEY\": locale.EnvDesc_LLM_SERVER_KEY,\n\t\t\"LLM_SERVER_MODEL\": locale.EnvDesc_LLM_SERVER_MODEL,\n\t\t\"LLM_SERVER_CONFIG_PATH\": locale.EnvDesc_LLM_SERVER_CONFIG_PATH,\n\t\t\"LLM_SERVER_LEGACY_REASONING\": locale.EnvDesc_LLM_SERVER_LEGACY_REASONING,\n\t\t\"LLM_SERVER_PRESERVE_REASONING\": locale.EnvDesc_LLM_SERVER_PRESERVE_REASONING,\n\t\t\"LLM_SERVER_PROVIDER\": locale.EnvDesc_LLM_SERVER_PROVIDER,\n\n\t\t\"LANGFUSE_LISTEN_IP\": locale.EnvDesc_LANGFUSE_LISTEN_IP,\n\t\t\"LANGFUSE_LISTEN_PORT\": locale.EnvDesc_LANGFUSE_LISTEN_PORT,\n\t\t\"LANGFUSE_BASE_URL\": locale.EnvDesc_LANGFUSE_BASE_URL,\n\t\t\"LANGFUSE_PROJECT_ID\": locale.EnvDesc_LANGFUSE_PROJECT_ID,\n\t\t\"LANGFUSE_PUBLIC_KEY\": locale.EnvDesc_LANGFUSE_PUBLIC_KEY,\n\t\t\"LANGFUSE_SECRET_KEY\": locale.EnvDesc_LANGFUSE_SECRET_KEY,\n\n\t\t// langfuse init variables\n\t\t\"LANGFUSE_INIT_PROJECT_ID\": locale.EnvDesc_LANGFUSE_INIT_PROJECT_ID,\n\t\t\"LANGFUSE_INIT_PROJECT_PUBLIC_KEY\": locale.EnvDesc_LANGFUSE_INIT_PROJECT_PUBLIC_KEY,\n\t\t\"LANGFUSE_INIT_PROJECT_SECRET_KEY\": locale.EnvDesc_LANGFUSE_INIT_PROJECT_SECRET_KEY,\n\t\t\"LANGFUSE_INIT_USER_EMAIL\": locale.EnvDesc_LANGFUSE_INIT_USER_EMAIL,\n\t\t\"LANGFUSE_INIT_USER_NAME\": locale.EnvDesc_LANGFUSE_INIT_USER_NAME,\n\t\t\"LANGFUSE_INIT_USER_PASSWORD\": locale.EnvDesc_LANGFUSE_INIT_USER_PASSWORD,\n\n\t\t\"LANGFUSE_OTEL_EXPORTER_OTLP_ENDPOINT\": locale.EnvDesc_LANGFUSE_OTEL_EXPORTER_OTLP_ENDPOINT,\n\n\t\t\"GRAFANA_LISTEN_IP\": locale.EnvDesc_GRAFANA_LISTEN_IP,\n\t\t\"GRAFANA_LISTEN_PORT\": locale.EnvDesc_GRAFANA_LISTEN_PORT,\n\t\t\"OTEL_GRPC_LISTEN_IP\": locale.EnvDesc_OTEL_GRPC_LISTEN_IP,\n\t\t\"OTEL_GRPC_LISTEN_PORT\": locale.EnvDesc_OTEL_GRPC_LISTEN_PORT,\n\t\t\"OTEL_HTTP_LISTEN_IP\": locale.EnvDesc_OTEL_HTTP_LISTEN_IP,\n\t\t\"OTEL_HTTP_LISTEN_PORT\": locale.EnvDesc_OTEL_HTTP_LISTEN_PORT,\n\t\t\"OTEL_HOST\": locale.EnvDesc_OTEL_HOST,\n\n\t\t\"SUMMARIZER_PRESERVE_LAST\": locale.EnvDesc_SUMMARIZER_PRESERVE_LAST,\n\t\t\"SUMMARIZER_USE_QA\": locale.EnvDesc_SUMMARIZER_USE_QA,\n\t\t\"SUMMARIZER_SUM_MSG_HUMAN_IN_QA\": locale.EnvDesc_SUMMARIZER_SUM_MSG_HUMAN_IN_QA,\n\t\t\"SUMMARIZER_LAST_SEC_BYTES\": locale.EnvDesc_SUMMARIZER_LAST_SEC_BYTES,\n\t\t\"SUMMARIZER_MAX_BP_BYTES\": locale.EnvDesc_SUMMARIZER_MAX_BP_BYTES,\n\t\t\"SUMMARIZER_MAX_QA_BYTES\": locale.EnvDesc_SUMMARIZER_MAX_QA_BYTES,\n\t\t\"SUMMARIZER_MAX_QA_SECTIONS\": locale.EnvDesc_SUMMARIZER_MAX_QA_SECTIONS,\n\t\t\"SUMMARIZER_KEEP_QA_SECTIONS\": locale.EnvDesc_SUMMARIZER_KEEP_QA_SECTIONS,\n\n\t\t\"ASSISTANT_SUMMARIZER_PRESERVE_LAST\": locale.EnvDesc_ASSISTANT_SUMMARIZER_PRESERVE_LAST,\n\t\t\"ASSISTANT_SUMMARIZER_LAST_SEC_BYTES\": locale.EnvDesc_ASSISTANT_SUMMARIZER_LAST_SEC_BYTES,\n\t\t\"ASSISTANT_SUMMARIZER_MAX_BP_BYTES\": locale.EnvDesc_ASSISTANT_SUMMARIZER_MAX_BP_BYTES,\n\t\t\"ASSISTANT_SUMMARIZER_MAX_QA_BYTES\": locale.EnvDesc_ASSISTANT_SUMMARIZER_MAX_QA_BYTES,\n\t\t\"ASSISTANT_SUMMARIZER_MAX_QA_SECTIONS\": locale.EnvDesc_ASSISTANT_SUMMARIZER_MAX_QA_SECTIONS,\n\t\t\"ASSISTANT_SUMMARIZER_KEEP_QA_SECTIONS\": locale.EnvDesc_ASSISTANT_SUMMARIZER_KEEP_QA_SECTIONS,\n\n\t\t\"EMBEDDING_PROVIDER\": locale.EnvDesc_EMBEDDING_PROVIDER,\n\t\t\"EMBEDDING_URL\": locale.EnvDesc_EMBEDDING_URL,\n\t\t\"EMBEDDING_KEY\": locale.EnvDesc_EMBEDDING_KEY,\n\t\t\"EMBEDDING_MODEL\": locale.EnvDesc_EMBEDDING_MODEL,\n\t\t\"EMBEDDING_BATCH_SIZE\": locale.EnvDesc_EMBEDDING_BATCH_SIZE,\n\t\t\"EMBEDDING_STRIP_NEW_LINES\": locale.EnvDesc_EMBEDDING_STRIP_NEW_LINES,\n\n\t\t\"ASK_USER\": locale.EnvDesc_ASK_USER,\n\n\t\t\"ASSISTANT_USE_AGENTS\": locale.EnvDesc_ASSISTANT_USE_AGENTS,\n\n\t\t\"EXECUTION_MONITOR_ENABLED\": locale.EnvDesc_EXECUTION_MONITOR_ENABLED,\n\t\t\"EXECUTION_MONITOR_SAME_TOOL_LIMIT\": locale.EnvDesc_EXECUTION_MONITOR_SAME_TOOL_LIMIT,\n\t\t\"EXECUTION_MONITOR_TOTAL_TOOL_LIMIT\": locale.EnvDesc_EXECUTION_MONITOR_TOTAL_TOOL_LIMIT,\n\t\t\"MAX_GENERAL_AGENT_TOOL_CALLS\": locale.EnvDesc_MAX_GENERAL_AGENT_TOOL_CALLS,\n\t\t\"MAX_LIMITED_AGENT_TOOL_CALLS\": locale.EnvDesc_MAX_LIMITED_AGENT_TOOL_CALLS,\n\t\t\"AGENT_PLANNING_STEP_ENABLED\": locale.EnvDesc_AGENT_PLANNING_STEP_ENABLED,\n\n\t\t\"SCRAPER_PUBLIC_URL\": locale.EnvDesc_SCRAPER_PUBLIC_URL,\n\t\t\"SCRAPER_PRIVATE_URL\": locale.EnvDesc_SCRAPER_PRIVATE_URL,\n\t\t\"LOCAL_SCRAPER_USERNAME\": locale.EnvDesc_LOCAL_SCRAPER_USERNAME,\n\t\t\"LOCAL_SCRAPER_PASSWORD\": locale.EnvDesc_LOCAL_SCRAPER_PASSWORD,\n\t\t\"LOCAL_SCRAPER_MAX_CONCURRENT_SESSIONS\": locale.EnvDesc_LOCAL_SCRAPER_MAX_CONCURRENT_SESSIONS,\n\n\t\t\"DUCKDUCKGO_ENABLED\": locale.EnvDesc_DUCKDUCKGO_ENABLED,\n\t\t\"DUCKDUCKGO_REGION\": locale.EnvDesc_DUCKDUCKGO_REGION,\n\t\t\"DUCKDUCKGO_SAFESEARCH\": locale.EnvDesc_DUCKDUCKGO_SAFESEARCH,\n\t\t\"DUCKDUCKGO_TIME_RANGE\": locale.EnvDesc_DUCKDUCKGO_TIME_RANGE,\n\t\t\"SPLOITUS_ENABLED\": locale.EnvDesc_SPLOITUS_ENABLED,\n\t\t\"PERPLEXITY_API_KEY\": locale.EnvDesc_PERPLEXITY_API_KEY,\n\t\t\"TAVILY_API_KEY\": locale.EnvDesc_TAVILY_API_KEY,\n\t\t\"TRAVERSAAL_API_KEY\": locale.EnvDesc_TRAVERSAAL_API_KEY,\n\t\t\"GOOGLE_API_KEY\": locale.EnvDesc_GOOGLE_API_KEY,\n\t\t\"GOOGLE_CX_KEY\": locale.EnvDesc_GOOGLE_CX_KEY,\n\t\t\"GOOGLE_LR_KEY\": locale.EnvDesc_GOOGLE_LR_KEY,\n\n\t\t\"PERPLEXITY_MODEL\": locale.EnvDesc_PERPLEXITY_MODEL,\n\t\t\"PERPLEXITY_CONTEXT_SIZE\": locale.EnvDesc_PERPLEXITY_CONTEXT_SIZE,\n\n\t\t\"SEARXNG_URL\": locale.EnvDesc_SEARXNG_URL,\n\t\t\"SEARXNG_CATEGORIES\": locale.EnvDesc_SEARXNG_CATEGORIES,\n\t\t\"SEARXNG_LANGUAGE\": locale.EnvDesc_SEARXNG_LANGUAGE,\n\t\t\"SEARXNG_SAFESEARCH\": locale.EnvDesc_SEARXNG_SAFESEARCH,\n\t\t\"SEARXNG_TIME_RANGE\": locale.EnvDesc_SEARXNG_TIME_RANGE,\n\t\t\"SEARXNG_TIMEOUT\": locale.EnvDesc_SEARXNG_TIMEOUT,\n\n\t\t\"DOCKER_INSIDE\": locale.EnvDesc_DOCKER_INSIDE,\n\t\t\"DOCKER_NET_ADMIN\": locale.EnvDesc_DOCKER_NET_ADMIN,\n\t\t\"DOCKER_SOCKET\": locale.EnvDesc_DOCKER_SOCKET,\n\t\t\"DOCKER_NETWORK\": locale.EnvDesc_DOCKER_NETWORK,\n\t\t\"DOCKER_PUBLIC_IP\": locale.EnvDesc_DOCKER_PUBLIC_IP,\n\t\t\"DOCKER_WORK_DIR\": locale.EnvDesc_DOCKER_WORK_DIR,\n\t\t\"DOCKER_DEFAULT_IMAGE\": locale.EnvDesc_DOCKER_DEFAULT_IMAGE,\n\t\t\"DOCKER_DEFAULT_IMAGE_FOR_PENTEST\": locale.EnvDesc_DOCKER_DEFAULT_IMAGE_FOR_PENTEST,\n\t\t\"DOCKER_HOST\": locale.EnvDesc_DOCKER_HOST,\n\t\t\"DOCKER_TLS_VERIFY\": locale.EnvDesc_DOCKER_TLS_VERIFY,\n\t\t\"DOCKER_CERT_PATH\": locale.EnvDesc_DOCKER_CERT_PATH,\n\n\t\t\"LICENSE_KEY\": locale.EnvDesc_LICENSE_KEY,\n\t\t\"PENTAGI_LISTEN_IP\": locale.EnvDesc_PENTAGI_LISTEN_IP,\n\t\t\"PENTAGI_LISTEN_PORT\": locale.EnvDesc_PENTAGI_LISTEN_PORT,\n\t\t\"PUBLIC_URL\": locale.EnvDesc_PUBLIC_URL,\n\t\t\"CORS_ORIGINS\": locale.EnvDesc_CORS_ORIGINS,\n\t\t\"COOKIE_SIGNING_SALT\": locale.EnvDesc_COOKIE_SIGNING_SALT,\n\t\t\"PROXY_URL\": locale.EnvDesc_PROXY_URL,\n\t\t\"EXTERNAL_SSL_CA_PATH\": locale.EnvDesc_EXTERNAL_SSL_CA_PATH,\n\t\t\"EXTERNAL_SSL_INSECURE\": locale.EnvDesc_EXTERNAL_SSL_INSECURE,\n\t\t\"PENTAGI_SSL_DIR\": locale.EnvDesc_PENTAGI_SSL_DIR,\n\t\t\"PENTAGI_DATA_DIR\": locale.EnvDesc_PENTAGI_DATA_DIR,\n\t\t\"PENTAGI_DOCKER_SOCKET\": locale.EnvDesc_PENTAGI_DOCKER_SOCKET,\n\t\t\"PENTAGI_DOCKER_CERT_PATH\": locale.EnvDesc_PENTAGI_DOCKER_CERT_PATH,\n\t\t\"PENTAGI_LLM_SERVER_CONFIG_PATH\": locale.EnvDesc_PENTAGI_LLM_SERVER_CONFIG_PATH,\n\t\t\"PENTAGI_OLLAMA_SERVER_CONFIG_PATH\": locale.EnvDesc_PENTAGI_OLLAMA_SERVER_CONFIG_PATH,\n\n\t\t\"STATIC_DIR\": locale.EnvDesc_STATIC_DIR,\n\t\t\"STATIC_URL\": locale.EnvDesc_STATIC_URL,\n\t\t\"SERVER_PORT\": locale.EnvDesc_SERVER_PORT,\n\t\t\"SERVER_HOST\": locale.EnvDesc_SERVER_HOST,\n\t\t\"SERVER_SSL_CRT\": locale.EnvDesc_SERVER_SSL_CRT,\n\t\t\"SERVER_SSL_KEY\": locale.EnvDesc_SERVER_SSL_KEY,\n\t\t\"SERVER_USE_SSL\": locale.EnvDesc_SERVER_USE_SSL,\n\n\t\t\"OAUTH_GOOGLE_CLIENT_ID\": locale.EnvDesc_OAUTH_GOOGLE_CLIENT_ID,\n\t\t\"OAUTH_GOOGLE_CLIENT_SECRET\": locale.EnvDesc_OAUTH_GOOGLE_CLIENT_SECRET,\n\t\t\"OAUTH_GITHUB_CLIENT_ID\": locale.EnvDesc_OAUTH_GITHUB_CLIENT_ID,\n\t\t\"OAUTH_GITHUB_CLIENT_SECRET\": locale.EnvDesc_OAUTH_GITHUB_CLIENT_SECRET,\n\n\t\t\"LANGFUSE_EE_LICENSE_KEY\": locale.EnvDesc_LANGFUSE_EE_LICENSE_KEY,\n\n\t\t\"GRAPHITI_URL\": locale.EnvDesc_GRAPHITI_URL,\n\t\t\"GRAPHITI_TIMEOUT\": locale.EnvDesc_GRAPHITI_TIMEOUT,\n\t\t\"GRAPHITI_MODEL_NAME\": locale.EnvDesc_GRAPHITI_MODEL_NAME,\n\t\t\"NEO4J_USER\": locale.EnvDesc_NEO4J_USER,\n\t\t\"NEO4J_DATABASE\": locale.EnvDesc_NEO4J_DATABASE,\n\n\t\t\"PENTAGI_POSTGRES_PASSWORD\": locale.EnvDesc_PENTAGI_POSTGRES_PASSWORD,\n\t\t\"NEO4J_PASSWORD\": locale.EnvDesc_NEO4J_PASSWORD,\n\t}\n\tif desc, ok := envVarDescriptions[varName]; ok {\n\t\treturn desc\n\t}\n\treturn varName\n}\n\n// maskedVariables contains environment variable names that should be masked in display\nvar maskedVariables = map[string]bool{\n\t// API keys and Secrets\n\t\"OPEN_AI_KEY\": true,\n\t\"ANTHROPIC_API_KEY\": true,\n\t\"GEMINI_API_KEY\": true,\n\t\"BEDROCK_BEARER_TOKEN\": true,\n\t\"BEDROCK_ACCESS_KEY_ID\": true,\n\t\"BEDROCK_SECRET_ACCESS_KEY\": true,\n\t\"BEDROCK_SESSION_TOKEN\": true,\n\t\"OLLAMA_SERVER_API_KEY\": true,\n\t\"DEEPSEEK_API_KEY\": true,\n\t\"GLM_API_KEY\": true,\n\t\"KIMI_API_KEY\": true,\n\t\"QWEN_API_KEY\": true,\n\t\"LLM_SERVER_KEY\": true,\n\t\"LANGFUSE_PUBLIC_KEY\": true,\n\t\"LANGFUSE_SECRET_KEY\": true,\n\t\"EMBEDDING_KEY\": true,\n\t\"LOCAL_SCRAPER_PASSWORD\": true,\n\t\"PERPLEXITY_API_KEY\": true,\n\t\"TAVILY_API_KEY\": true,\n\t\"TRAVERSAAL_API_KEY\": true,\n\t\"GOOGLE_API_KEY\": true,\n\t\"GOOGLE_CX_KEY\": true,\n\n\t// oauth client secrets\n\t\"OAUTH_GOOGLE_CLIENT_SECRET\": true,\n\t\"OAUTH_GITHUB_CLIENT_SECRET\": true,\n\n\t// urls can embed credentials; mask to avoid leaking secrets\n\t\"PROXY_URL\": true,\n\t\"SCRAPER_PUBLIC_URL\": true,\n\t\"SCRAPER_PRIVATE_URL\": true,\n\n\t// langfuse init secrets\n\t\"LANGFUSE_INIT_PROJECT_PUBLIC_KEY\": true,\n\t\"LANGFUSE_INIT_PROJECT_SECRET_KEY\": true,\n\t\"LANGFUSE_INIT_USER_PASSWORD\": true,\n\n\t// langfuse license key\n\t\"LANGFUSE_EE_LICENSE_KEY\": true,\n\n\t// postgres password for pentagi service (pgvector binds on localhost)\n\t\"PENTAGI_POSTGRES_PASSWORD\": true,\n\n\t// neo4j password for graphiti service (neo4j binds on localhost)\n\t\"NEO4J_PASSWORD\": true,\n\n\t// langfuse stack secrets (compose-managed)\n\t\"LANGFUSE_SALT\": true,\n\t\"LANGFUSE_ENCRYPTION_KEY\": true,\n\t\"LANGFUSE_NEXTAUTH_SECRET\": true,\n\t\"LANGFUSE_CLICKHOUSE_PASSWORD\": true,\n\t\"LANGFUSE_S3_ACCESS_KEY_ID\": true,\n\t\"LANGFUSE_S3_SECRET_ACCESS_KEY\": true,\n\t\"LANGFUSE_REDIS_AUTH\": true,\n\t\"LANGFUSE_AUTH_CUSTOM_CLIENT_SECRET\": true,\n\n\t// server settings\n\t\"COOKIE_SIGNING_SALT\": true,\n}\n\n// isVariableMasked returns true if the variable should be masked in display\nfunc (c *controller) isVariableMasked(varName string) bool {\n\treturn maskedVariables[varName]\n}\n\n// criticalVariables contains environment variable names that require service restart\nvar criticalVariables = map[string]bool{\n\t// LLM Provider changes\n\t\"OPEN_AI_KEY\": true,\n\t\"OPEN_AI_SERVER_URL\": true,\n\t\"ANTHROPIC_API_KEY\": true,\n\t\"ANTHROPIC_SERVER_URL\": true,\n\t\"GEMINI_API_KEY\": true,\n\t\"GEMINI_SERVER_URL\": true,\n\t\"BEDROCK_DEFAULT_AUTH\": true,\n\t\"BEDROCK_BEARER_TOKEN\": true,\n\t\"BEDROCK_ACCESS_KEY_ID\": true,\n\t\"BEDROCK_SECRET_ACCESS_KEY\": true,\n\t\"BEDROCK_SESSION_TOKEN\": true,\n\t\"BEDROCK_REGION\": true,\n\t\"OLLAMA_SERVER_URL\": true,\n\t\"OLLAMA_SERVER_API_KEY\": true,\n\t\"OLLAMA_SERVER_MODEL\": true,\n\t\"OLLAMA_SERVER_CONFIG_PATH\": true,\n\t\"OLLAMA_SERVER_PULL_MODELS_TIMEOUT\": true,\n\t\"OLLAMA_SERVER_PULL_MODELS_ENABLED\": true,\n\t\"OLLAMA_SERVER_LOAD_MODELS_ENABLED\": true,\n\t\"DEEPSEEK_API_KEY\": true,\n\t\"DEEPSEEK_SERVER_URL\": true,\n\t\"DEEPSEEK_PROVIDER\": true,\n\t\"GLM_API_KEY\": true,\n\t\"GLM_SERVER_URL\": true,\n\t\"GLM_PROVIDER\": true,\n\t\"KIMI_API_KEY\": true,\n\t\"KIMI_SERVER_URL\": true,\n\t\"KIMI_PROVIDER\": true,\n\t\"QWEN_API_KEY\": true,\n\t\"QWEN_SERVER_URL\": true,\n\t\"QWEN_PROVIDER\": true,\n\t\"LLM_SERVER_URL\": true,\n\t\"LLM_SERVER_KEY\": true,\n\t\"LLM_SERVER_MODEL\": true,\n\t\"LLM_SERVER_CONFIG_PATH\": true,\n\t\"LLM_SERVER_LEGACY_REASONING\": true,\n\t\"LLM_SERVER_PRESERVE_REASONING\": true,\n\t\"LLM_SERVER_PROVIDER\": true,\n\n\t// tools changes\n\t\"DUCKDUCKGO_ENABLED\": true,\n\t\"DUCKDUCKGO_REGION\": true,\n\t\"DUCKDUCKGO_SAFESEARCH\": true,\n\t\"DUCKDUCKGO_TIME_RANGE\": true,\n\t\"SPLOITUS_ENABLED\": true,\n\t\"PERPLEXITY_API_KEY\": true,\n\t\"PERPLEXITY_MODEL\": true,\n\t\"PERPLEXITY_CONTEXT_SIZE\": true,\n\t\"TAVILY_API_KEY\": true,\n\t\"TRAVERSAAL_API_KEY\": true,\n\t\"GOOGLE_API_KEY\": true,\n\t\"GOOGLE_CX_KEY\": true,\n\t\"GOOGLE_LR_KEY\": true,\n\t\"SEARXNG_URL\": true,\n\t\"SEARXNG_CATEGORIES\": true,\n\t\"SEARXNG_LANGUAGE\": true,\n\t\"SEARXNG_SAFESEARCH\": true,\n\t\"SEARXNG_TIME_RANGE\": true,\n\t\"SEARXNG_TIMEOUT\": true,\n\n\t// mounting custom LLM server config into pentagi container changes volume mapping\n\t\"PENTAGI_LLM_SERVER_CONFIG_PATH\": true,\n\t\"PENTAGI_OLLAMA_SERVER_CONFIG_PATH\": true,\n\n\t// Embedding provider changes\n\t\"EMBEDDING_PROVIDER\": true,\n\t\"EMBEDDING_URL\": true,\n\t\"EMBEDDING_KEY\": true,\n\t\"EMBEDDING_MODEL\": true,\n\t\"EMBEDDING_BATCH_SIZE\": true,\n\t\"EMBEDDING_STRIP_NEW_LINES\": true,\n\n\t// Docker configuration changes\n\t\"DOCKER_INSIDE\": true,\n\t\"DOCKER_NET_ADMIN\": true,\n\t\"DOCKER_SOCKET\": true,\n\t\"DOCKER_NETWORK\": true,\n\t\"DOCKER_PUBLIC_IP\": true,\n\t\"DOCKER_DEFAULT_IMAGE\": true,\n\t\"DOCKER_DEFAULT_IMAGE_FOR_PENTEST\": true,\n\t\"DOCKER_HOST\": true,\n\t\"DOCKER_TLS_VERIFY\": true,\n\t\"DOCKER_CERT_PATH\": true,\n\t\"PENTAGI_DOCKER_SOCKET\": true,\n\n\t// observability changes\n\t\"OTEL_HOST\": true,\n\n\t// graphiti changes\n\t\"GRAPHITI_URL\": true,\n\t\"GRAPHITI_TIMEOUT\": true,\n\t\"GRAPHITI_MODEL_NAME\": true,\n\n\t// server settings changes\n\t\"ASK_USER\": true,\n\t\"EXECUTION_MONITOR_ENABLED\": true,\n\t\"EXECUTION_MONITOR_SAME_TOOL_LIMIT\": true,\n\t\"EXECUTION_MONITOR_TOTAL_TOOL_LIMIT\": true,\n\t\"MAX_GENERAL_AGENT_TOOL_CALLS\": true,\n\t\"MAX_LIMITED_AGENT_TOOL_CALLS\": true,\n\t\"AGENT_PLANNING_STEP_ENABLED\": true,\n\n\t\"LICENSE_KEY\": true,\n\t\"PENTAGI_LISTEN_IP\": true,\n\t\"PENTAGI_LISTEN_PORT\": true,\n\t\"PUBLIC_URL\": true,\n\t\"CORS_ORIGINS\": true,\n\t\"COOKIE_SIGNING_SALT\": true,\n\t\"PROXY_URL\": true,\n\t\"EXTERNAL_SSL_CA_PATH\": true,\n\t\"EXTERNAL_SSL_INSECURE\": true,\n\t\"STATIC_DIR\": true,\n\t\"STATIC_URL\": true,\n\t\"SERVER_PORT\": true,\n\t\"SERVER_HOST\": true,\n\t\"SERVER_SSL_CRT\": true,\n\t\"SERVER_SSL_KEY\": true,\n\t\"SERVER_USE_SSL\": true,\n\t\"PENTAGI_SSL_DIR\": true,\n\t\"PENTAGI_DATA_DIR\": true,\n\n\t// scraper settings\n\t\"SCRAPER_PUBLIC_URL\": true,\n\t\"SCRAPER_PRIVATE_URL\": true,\n\n\t// oauth settings\n\t\"OAUTH_GOOGLE_CLIENT_ID\": true,\n\t\"OAUTH_GOOGLE_CLIENT_SECRET\": true,\n\t\"OAUTH_GITHUB_CLIENT_ID\": true,\n\t\"OAUTH_GITHUB_CLIENT_SECRET\": true,\n\n\t// langfuse integration settings passed to pentagi\n\t\"LANGFUSE_BASE_URL\": true,\n\t\"LANGFUSE_PROJECT_ID\": true,\n\t\"LANGFUSE_PUBLIC_KEY\": true,\n\t\"LANGFUSE_SECRET_KEY\": true,\n\n\t// summarizer settings (general)\n\t\"SUMMARIZER_PRESERVE_LAST\": true,\n\t\"SUMMARIZER_USE_QA\": true,\n\t\"SUMMARIZER_SUM_MSG_HUMAN_IN_QA\": true,\n\t\"SUMMARIZER_LAST_SEC_BYTES\": true,\n\t\"SUMMARIZER_MAX_BP_BYTES\": true,\n\t\"SUMMARIZER_MAX_QA_SECTIONS\": true,\n\t\"SUMMARIZER_MAX_QA_BYTES\": true,\n\t\"SUMMARIZER_KEEP_QA_SECTIONS\": true,\n\n\t// assistant-level settings\n\t\"ASSISTANT_USE_AGENTS\": true,\n\t\"ASSISTANT_SUMMARIZER_PRESERVE_LAST\": true,\n\t\"ASSISTANT_SUMMARIZER_LAST_SEC_BYTES\": true,\n\t\"ASSISTANT_SUMMARIZER_MAX_BP_BYTES\": true,\n\t\"ASSISTANT_SUMMARIZER_MAX_QA_SECTIONS\": true,\n\t\"ASSISTANT_SUMMARIZER_MAX_QA_BYTES\": true,\n\t\"ASSISTANT_SUMMARIZER_KEEP_QA_SECTIONS\": true,\n}\n\n// isCriticalVariable returns true if changing this variable requires service restart\nfunc (c *controller) isCriticalVariable(varName string) bool {\n\treturn criticalVariables[varName]\n}\n\n// RemoveCredentialsFromURL removes credentials from URL - public method for form display\nfunc RemoveCredentialsFromURL(urlStr string) string {\n\tif urlStr == \"\" {\n\t\treturn urlStr\n\t}\n\n\tparsedURL, err := url.Parse(urlStr)\n\tif err != nil {\n\t\treturn urlStr\n\t}\n\n\tparsedURL.User = nil\n\n\treturn parsedURL.String()\n}\n"}}}} |