{
  "preferences": {
    "language": {
      "title": "Language",
      "help": "Choose the display language for the interface. Your choice is saved in this browser."
    },
    "visibility": {
      "title": "Default visibility for new tasks",
      "private": "Private",
      "org": "Organization",
      "public": "Public",
      "help": "🔒 Private: only you can view ／ 🏢 Organization: members of the same Gitea org can view ／ 🌐 Public: any logged-in user can view"
    },
    "orgs": {
      "title": "Gitea organizations you belong to",
      "none": "(none — sign in with Gitea to populate this)",
      "refreshHint": "Log out and back in to refresh this list."
    },
    "save": "Save settings",
    "saving": "Saving…"
  },
  "tools": {
    "categoriesAria": "Tool categories",
    "web": {
      "webfetchTimeout": "WebFetch Timeout (s)",
      "websearchTimeout": "WebSearch Timeout (s)",
      "searxngHelp": "SearXNG endpoint used as a WebSearch fallback.",
      "ssrfHelp": "Hostnames / IP addresses exempt from SSRF protection. Applies to all of WebFetch and BrowseWeb.",
      "blockedLabel": "Blocked Patterns",
      "blockedHelp": "Patterns (regular expressions) filtered out of WebSearch queries.",
      "autoBlockLabel": "Auto Block",
      "autoBlockHelp": "Automatically block sensitive information contained in search queries.",
      "autoBlock": {
        "privateIp": "Private IP",
        "internalDomain": "Internal domain",
        "email": "Email address",
        "phone": "Phone number"
      }
    },
    "vision": {
      "timeout": "Vision Timeout (s)",
      "modelHelp": "Model name used for image analysis (e.g. qwen2-vl:8b-instruct)",
      "baseUrlPlaceholder": "Leave blank if the same as the provider Base URL",
      "baseUrlHelp": "API endpoint for the Vision model.",
      "ocrModelHelp": "Model name used by GLM-OCR."
    },
    "speech": {
      "timeout": "Speech Timeout (s)",
      "serverHelp": "API endpoint of the speech-recognition server (used by TranscribeAudio).",
      "timeoutHelp": "Timeout for handling long audio files.",
      "languageHelp": "Default language code for transcription."
    },
    "office": {
      "sectionHelp": "Office file size limits (MB)",
      "excelLabel": "ReadExcel max size",
      "excelHelp": "Maximum .xlsx / .xls file size accepted by ReadExcel (default: 10 MB)",
      "docxLabel": "ReadDocx max size",
      "docxHelp": "Maximum .docx file size accepted by ReadDocx (default: 10 MB)",
      "pdfLabel": "ReadPdf max size",
      "pdfHelp": "Maximum .pdf file size accepted by ReadPdf (default: 10 MB)",
      "pptxLabel": "ReadPPTX max size",
      "pptxHelp": "Maximum .pptx file size accepted by ReadPPTX (default: 50 MB)",
      "pptxUncompressedLabel": "ReadPPTX uncompressed size limit",
      "pptxUncompressedHelp": "Total size limit after unzipping a PPTX (for ZIP-bomb detection, default: 200 MB)"
    },
    "uploads": {
      "sectionHelp": "Request body limit for the upload API from the UI (MB)",
      "maxLabel": "Max upload size for task creation / comments",
      "maxHelp": "Request body limit for POST /api/local/tasks and POST /api/local/tasks/:id/comments (the whole JSON with attachments base64-encoded). Raw attachment size is roughly value × 0.75 (e.g. 50 MB body ≒ 37 MB raw). Clamped to 1–1000 MB. Default 50 MB. Applied without a server restart.",
      "maxHelpStorage": "Request body limit for POST /api/local/tasks and POST /api/local/tasks/:id/comments (the whole JSON with attachments base64-encoded). Raw attachment size is roughly value × 0.75 (e.g. 50 MB body ≒ 37 MB raw). Clamped to 1–1000 MB. Default 50 MB. Applied without a server restart. This setting can also be edited under Paths & Storage (same storage.task_upload_max_size_mb key)."
    },
    "x": {
      "timeout": "X Timeout (s)",
      "mediaFetchTimeout": "X Media Fetch Timeout (s)",
      "authTokenHelp": "X / Twitter auth_token cookie",
      "ct0Help": "X / Twitter ct0 cookie",
      "cliHelp": "Command that runs twitter-cli.",
      "chromeProfileHelp": "Chrome profile directory used for cookie extraction.",
      "mediaHelp": "Auto-download of images and other media in X posts. Default: auto",
      "mediaAuto": "auto (fetch images/media automatically · default)",
      "mediaNever": "never (don't fetch)",
      "videoHelp": "Download mode for videos in X posts. Default: thumbnail (saves bandwidth)",
      "videoThumbnail": "thumbnail (thumbnail only · default)",
      "videoFull": "full (fetch the video itself)",
      "videoNever": "never (don't fetch)",
      "mediaMaxHelp": "Maximum download size per media item (MB)",
      "mediaFetchTimeoutHelp": "Timeout for fetching media (seconds)"
    },
    "maps": {
      "timeout": "Maps Timeout (s)",
      "keyHelp": "Google Maps Places / Directions API key. When unset, Nominatim / OSRM (free) is used.",
      "timeoutHelp": "Timeout for Maps / Nominatim / OSRM calls (seconds). Default: 30"
    },
    "amazon": {
      "affiliateHelp": "Associate tag used by SearchAmazon.",
      "keepaHelp": "Keepa API key (for fetching price-history data). Graph image links are provided even when unset."
    },
    "userScripts": {
      "enableLabel": "Enable RunUserScript",
      "enabledToggle": "Enabled (browser-macros: the LLM’s RunUserScript + scheduled script tasks run)",
      "enableHelp": "The plain runtime is sandboxed with Node --permission, denying child_process / worker / FS access outside tmpdir. browser-macros can't be sandboxed (Playwright needs child_process / native bindings / network) and gets full Node.js capability. Enable only for trusted users.",
      "allowlistLabel": "Allowlist of permitted users (blank = everyone)",
      "allowlistPlaceholder": "user id (e.g. 12345)",
      "allowlistHelp": "When unset, controlled only by user_scripts_enabled. When set, only the listed IDs are allowed to run browser-macros (RunUserScript / scheduled script tasks).",
      "trashRetentionLabel": "Trash Retention (days)",
      "trashRetentionHelp": "Days before files under data/users/{userId}/trash/ are auto-deleted. Sweeps at startup and every 24h. Set 0 to delete immediately on each sweep. Default 30 days."
    },
    "tagInput": {
      "placeholder": "Type a tool name...",
      "help": "Only the tools listed here are offered to the LLM. Offline MCP tools and unknown tools are not auto-removed; they stay until you delete them explicitly.",
      "unknownTip": "This tool is not in the current catalog. It is kept until you delete it explicitly."
    }
  },
  "ssh": {
    "header": {
      "title": "SSH management",
      "desc": "Global SSH settings / connection registry / access grants / master-key rotation / audit log. Per-user SSH connections are managed from User Folder → ssh-connections/."
    },
    "common": {
      "reasonLabel": "Reason: {{reason}}",
      "reasonModalPlaceholder": "Describe the reason to record in the audit log",
      "cancel": "Cancel",
      "run": "Run",
      "submitting": "Submitting…"
    },
    "config": {
      "enableLabel": "Enable SSH",
      "enableHelp": "While OFF, all SshExec / SshUpload / SshDownload / SshConsole* tools are unavailable and the related APIs are not registered on the router. The MCP_ENCRYPTION_KEY environment variable is also required (without the key, the subsystem starts disabled even when ON).",
      "allowPrivateLabel": "Allow connections to private / loopback addresses",
      "allowPrivateHelp": "Needed when connecting to self-hosted / LAN servers. While OFF, connections to private addresses such as 10.x, 192.168.x, 127.0.0.1 are rejected.",
      "adminBypassLabel": "Admins can use connections without a grant",
      "adminBypassHelp": "ON: admin-role users can access all connections without a per-connection grant (still recorded in the audit log). OFF: admins also need an explicit grant like everyone else.",
      "callTimeout": "Call timeout (s)",
      "callTimeoutHelp": "Wall-clock limit for SshExec / SshUpload / SshDownload (including TCP connect + auth + execution). Default 30. Not applied to SshConsole* (those are governed by the idle/duration caps).",
      "maxOutputHelp": "Combined byte limit for SshExec stdout/stderr. Excess is truncated and returned with truncated_stdout: true. Default 32768 (32 KiB).",
      "transferSizeHelp": "File-size limit for SshUpload / SshDownload. Excess is rejected before transfer.",
      "auditRetention": "Audit retention (days)",
      "auditRetentionHelp": "Retention days for the ssh_audit_log table. The \"Prune\" button in the Audit tab deletes rows older than this value.",
      "abuseHelp": "Temporarily locks a connection when host-key mismatches / auth failures / command failures cluster in a short window. While locked, the same connection is rejected with an abuse_locked error.",
      "abuseWindow": "Window (min)",
      "abuseLock": "Lock duration (min)",
      "consoleSectionTitle": "Interactive Console (SSH tab / SshConsole* tools)",
      "consoleEnableLabel": "Enable Console",
      "consoleEnableHelp": "While OFF, SshConsole* tools and the SSH tab are disabled. \"Enable SSH\" above and MCP_ENCRYPTION_KEY are also required.",
      "idleTimeout": "Idle timeout (s)",
      "idleTimeoutHelp": "Threshold for auto-closing a session with no I/O for a set number of seconds. Both human input and AI input count as activity. Default 1800 (30 min).",
      "maxSessionDuration": "Max session duration (s)",
      "maxDurationHelp": "Absolute limit for a single session. It is force-closed past this time even if not idle. Default 14400 (4 hours).",
      "scrollbackHelp": "Ring-buffer capacity for the PTY output history kept server-side; the amount replayed when the browser reconnects. Default 524288 (512 KiB).",
      "maxSessionsHelp": "Limit on parallel sessions using the same connection. When exceeded, the oldest session is closed with the session_cap_evict reason.",
      "maxInputHelp": "Maximum bytes sendable per SshConsoleSend. Default 16384 (16 KiB).",
      "autoInjectHelp": "Number of trailing screen lines inserted at the end of each LLM iteration's system prompt. More gives the AI better situational awareness but consumes context. Default 24 lines.",
      "ptySizeHelp": "Initial PTY size. Overridden if the client sends a resize event."
    },
    "connections": {
      "listTitle": "Global connections ({{count}})",
      "addButton": "+ Add global connection",
      "newConnTitle": "New global connection",
      "empty": "No global connections yet.",
      "disabledSubsystem": "The SSH subsystem is disabled. Set ssh.enabled: true and MCP_ENCRYPTION_KEY in config.yaml, then restart the server.",
      "testing": "Testing…",
      "test": "Test",
      "pubKeyTitle": "Show the public key to paste into authorized_keys",
      "fetching": "Fetching…",
      "pubKey": "Public key",
      "edit": "Edit",
      "enable": "Enable",
      "disable": "Disable",
      "delete": "Delete",
      "copyTooltip": "Click to copy the UUID: {{value}}",
      "copied": "✓ Copied",
      "opTitle": {
        "delete": "Delete: {{label}}",
        "disable": "Disable: {{label}}",
        "enable": "Enable: {{label}}",
        "forceUnlock": "force-unlock: {{label}}"
      },
      "toast": {
        "created": "Created the global connection",
        "updated": "Updated the global connection",
        "disabled": "Disabled the connection",
        "enabled": "Enabled the connection",
        "deleted": "Deleted the connection",
        "unlocked": "Released the abuse lock",
        "unlockFailed": "unlock failed",
        "pubKeyFailed": "Failed to fetch the public key",
        "pubKeyFailedShort": "public-key fetch failed",
        "hostKeyMatch": "Host key matches ({{fp}}…)",
        "algNotAllowed": "The host key algorithm is not in the allowlist",
        "testFailed": "test failed",
        "hostKeyVerified": "Verified the host key"
      }
    },
    "grants": {
      "title": "Access grants",
      "disabledTitle": "The SSH subsystem is disabled",
      "disabledBody": "Set ssh.enabled: true in config.yaml, export the MCP_ENCRYPTION_KEY (64 hex chars) environment variable, then restart the server. See docs/ssh.md for details.",
      "issueButton": "+ Issue grant",
      "needConnFirst": "Register a global connection first, then you can issue grants.",
      "noGrants": "No grants — users can't use it.",
      "grantsCount": "{{count}} grants",
      "revoke": "Revoke",
      "revokeTitle": "Revoke grant",
      "createTitle": "Issue grant",
      "globalConnection": "Global connection",
      "subject": "Subject",
      "subjectUserPlaceholder": "gitea user ID",
      "subjectOrgPlaceholder": "org ID",
      "appliesAll": "Available to all pieces (applies_to_all_pieces)",
      "appliesAllWarn": "⚠️ This grant lets any piece use this connection. Only when truly necessary.",
      "pieceName": "Piece name",
      "pieceNamePlaceholder": "piece name (e.g. db-maintenance)",
      "expiresLabel": "Expires at (optional, ISO8601)",
      "reasonOpPlaceholder": "Operational reason",
      "expiresInline": "Expires: {{at}}",
      "issuing": "Issuing…",
      "issue": "Issue",
      "toast": {
        "created": "Created the grant",
        "deleted": "Deleted the grant"
      }
    },
    "rotation": {
      "title": "Master Key Rotation",
      "desc": "Rotates MCP_ENCRYPTION_KEY. While running, SSH write APIs return 503 and saves/updates from the UI are paused (reads still work). In v1 this only sets the maintenance flag; actual DEK re-wrapping is not implemented.",
      "currentState": "Current state",
      "idle": "idle (rotation not run)",
      "checking": "Checking…",
      "jobDoneOrCleared": "job {{jobId}} is complete or already cleared",
      "startedAt": "Started: {{ts}}",
      "startButton": "Start rotation",
      "confirmTitle": "⚠️ Start Master Key Rotation",
      "confirmBody1": "This operation enables maintenance mode. Creating / updating / deleting / testing SSH connections will all temporarily return 503.",
      "confirmBody2": "⚠️ In v1, DEK re-wrapping is not implemented. To clear maintenance you must clear the flag manually.",
      "reasonPlaceholder": "To replace MCP_ENCRYPTION_KEY with a new value",
      "typeToConfirm": "Type ROTATE to confirm",
      "starting": "Starting…",
      "toast": {
        "started": "Rotation job started: {{jobId}}",
        "startFailed": "Failed to start rotation"
      }
    },
    "audit": {
      "title": "Audit log",
      "refreshing": "Refreshing…",
      "reload": "Reload",
      "countDisplay": "{{count}} shown (limit {{limit}})",
      "empty": "No matching audit log entries"
    }
  },
  "gateway": {
    "server": {
      "intro": "Run AAO itself as an LLM Gateway. When enabled, endpoints like /v1/chat/completions listen on the same port as the worker UI (no separate process needed). Point another AAO's provider.workers[].endpoint at this URL to share the GPU pool.",
      "enable": "Enable Gateway",
      "listenPortHelp": "Not used in same-process mode: it shares the same port as the worker UI ({{port}}). Only effective when started as a separate process with AAO_MODE=gateway.",
      "separateDeploy": "Separate-process deploy:",
      "backendsHelp1": "Routing targets such as llama-server / Ollama / vLLM. The Gateway routes to the least-busy backend among those serving the role the worker sends (a backend with no roles set serves all roles). It falls back to an exact request.model = id/model match only when no backend serves the role.",
      "backendsHelp2": "api_key storage format: the value entered in the form is saved to config.yaml in plain text. A ${VAR}-style env-var reference is saved as a literal string on form save, so edit config.yaml directly if you want to pass it via env.",
      "backendsEmpty": "No backends registered. Add at least one.",
      "removeBackendTitle": "Remove this backend",
      "rolesLabel": "roles (optional)",
      "rolesPlaceholder": "quality, auto (blank = all roles)",
      "rolesHelp": "Comma-separated performance tiers (auto / fast / quality / reflection) this backend serves. Workers send the job's role as a routing key, and the Gateway routes to the least-busy backend serving that role. Blank = serves all roles (as before). GPUs with different model names can be grouped under the same role.",
      "apiKeyLabel": "api_key (optional)",
      "apiKeyEnvWarn": "env var reference detected: on save, {{key}} is written verbatim into config.yaml and the startup env substitution stops working. To pass it via env, edit config.yaml directly.",
      "virtualKeysHelp1": "Issue / rotate / revoke the sk-aao-* bearer keys used to access through this Gateway.",
      "virtualKeysHelp2": "Note: actions here apply immediately via an admin API independent of the Gateway Server Save & Apply (no need to press Save).",
      "advRequestHelp": "Budget for the whole chat (including streaming)",
      "advUpstreamHelp": "Idle limit per chunk",
      "advShutdownHelp": "Drain limit after SIGTERM",
      "hotReload": "Hot reload: changes here apply to the same-process gateway right after Save (backend / virtual_key changes trigger a bounce, and in-flight streams are gracefully drained)."
    },
    "keys": {
      "newIssue": "+ Issue new",
      "fetchError": "Fetch error: {{msg}}",
      "empty": "No keys registered. Create one from \"+ Issue new\".",
      "detail": "Detail",
      "footer": "Tokens budget resets monthly in UTC. Rate limit (rpm) is a 60-second sliding window. config-import keys (imported from config.yaml) cannot have their values edited.",
      "titleConfigManaged": "config-import keys are managed in config.yaml",
      "toast": {
        "created": "Issued the gateway key",
        "rotated": "Rotated",
        "revoked": "Revoked",
        "updated": "Updated"
      },
      "confirmRotate": "Rotate the key for team={{team}}?\nThe old key will be invalidated.",
      "confirmRevoke": "Revoke the key ({{prefix}}…) for team={{team}}?\nThis cannot be undone."
    },
    "createDialog": {
      "title": "Issue a new Gateway Key",
      "allowedModelsLabel": "Allowed models (one per line / comma-separated, blank = no limit)",
      "unlimited": "Unlimited",
      "issuing": "Issuing...",
      "issue": "Issue"
    },
    "rawKeyDialog": {
      "titleCreated": "Issued a new Gateway Key",
      "titleRotated": "Rotated the Gateway Key",
      "warnTitle": "⚠️ This key will never be shown again",
      "warnBody": "Be sure to copy and save it to a password manager or LLM-client config before closing. If lost, you must re-issue it with Rotate.",
      "ackLabel": "I have saved the key securely. I understand this key can no longer be displayed.",
      "alertNotSaved": "API key has not been saved. Copy it and tick \"Saved\" before navigating away."
    },
    "usagePanel": {
      "title": "Key usage",
      "thisMonth": "This month ({{period}})",
      "past12": "Past 12 months",
      "noHistory": "No history"
    }
  },
  "memoryLearning": {
    "title": "Memory & Learning",
    "navLabel": "🧠 Reflection history",
    "rootTitle": "Reflection history",
    "rootIntro": "Review the run history, diffs, and revert controls for automatic learning (reflection). Viewing and editing memory entries has moved to User Folder → memory/.",
    "intro": "Manage the persistent memory entries the agent references every session, and review the history of automatic learning (reflection).",
    "loading": "Loading…",
    "cancel": "Cancel",
    "close": "Close",
    "before": "Before",
    "after": "After",
    "empty": "(empty)",
    "err": {
      "loadEntries": "Failed to load memory entries ({{status}})",
      "loadHistory": "Failed to load history ({{status}})",
      "loadSnapshot": "Failed to load snapshot ({{status}})",
      "loadMetrics": "Failed to load metrics ({{status}})"
    },
    "outcome": {
      "applied": "Applied",
      "partial": "Partially applied",
      "abstained": "No learning",
      "rejected": "Rejected",
      "failed": "Failed"
    },
    "rejection": {
      "rejected_bad_name": "Invalid name (alphanumeric, hyphen, underscore; 1–64 chars)",
      "rejected_bad_description": "Description is required and must fit on one line",
      "rejected_unknown_type": "Type must be one of user / feedback / project / reference",
      "rejected_bad_body": "Body must be a string",
      "rejected_body_too_large": "Body exceeds the allowed size",
      "rejected_bad_request": "The request format is invalid",
      "rejected_missing_target": "The update / merge / remove change is missing its target entry",
      "rejected_stale_target": "The target entry was modified by another change (revision mismatch)",
      "rejected_name_collision": "A memory entry with the same name already exists",
      "rejected_target_piece_mismatch": "The target piece does not match the piece used by the task",
      "rejected_invalid_yaml": "The piece YAML could not be parsed",
      "rejected_invalid_piece": "The piece failed validation (lint)",
      "rejected_dangerous_piece": "Rejected because the piece transition rules contain COMPLETE / ABORT / ASK",
      "rejected_injected_directive": "The body looks like an injected agent-steering or data-exfiltration directive"
    },
    "modal": {
      "titleNew": "New memory entry",
      "titleEdit": "Edit — {{name}}",
      "nameLabel": "Name",
      "nameHint": "(alphanumeric, hyphen, underscore)",
      "descLabel": "Description",
      "descPlaceholder": "A one-line description shown in the memory list",
      "typeLabel": "Type",
      "typeHelp": "user: your own preferences / role ／ feedback: past feedback / lessons ／ project: per-project context ／ reference: reference material / external info",
      "bodyLabel": "Body",
      "bodyPlaceholder": "Markdown or plain text…",
      "saving": "Saving…",
      "save": "Save"
    },
    "entries": {
      "title": "Memory entries",
      "subtitle": "Persistent information injected into every agent session.",
      "newEntry": "+ New entry",
      "loadError": "Failed to load memory entries: {{err}}",
      "deleteConfirm": "Delete the memory entry \"{{name}}\"?",
      "deleteError": "Failed to delete \"{{name}}\": {{err}}",
      "emptyTitle": "No memory entries yet.",
      "emptyHint": "The reflection engine adds them automatically after a task completes. You can also add them manually.",
      "edit": "Edit",
      "delete": "Delete"
    },
    "snapshot": {
      "reverted": "reverted",
      "loadingDetail": "Loading detail…",
      "detailLoadError": "Failed to load: {{err}}",
      "reasoning": "Reasoning",
      "rejectionsTitle": "Rejection reasons",
      "changes": "Changes",
      "pieceDiff": "Piece diff",
      "revertedOk": "Reverted successfully.",
      "alreadyReverted": "Already reverted.",
      "revertBtn": "Revert this snapshot…",
      "revertConfirmQ": "Restore the state before this snapshot's changes?",
      "reverting": "Reverting…",
      "revertConfirm": "Confirm revert"
    },
    "diff": {
      "title": "Memory file diff",
      "added": "Added",
      "removed": "Removed"
    },
    "metrics": {
      "loading": "Loading metrics…",
      "loadError": "Failed to load metrics: {{err}}",
      "summary30": "30-day summary",
      "totalRuns": "Total runs",
      "appliedRate": "Applied rate",
      "abstainRate": "No-learning rate",
      "tokens": "Tokens",
      "pieceEdits": "Piece edits",
      "noRuns": "No reflection runs yet. Metrics appear once the first reflection completes."
    },
    "timeline": {
      "title": "Reflection timeline",
      "subtitle": "History of reflection runs. Expand a row to see the reasoning, before/after diff, and revert controls.",
      "showReverted": "Show reverted",
      "resultLabel": "Result:",
      "reset": "Reset",
      "loadError": "Failed to load: {{err}}",
      "emptyTitle": "No reflection runs yet.",
      "emptyHint": "Reflection runs automatically after a task completes.",
      "loadMore": "Show more"
    }
  },
  "delegations": {
    "navLabel": "🔑 A2A Delegations"
  },
  "reflection": {
    "intro": "Every time a normal job completes, the LLM extracts the lessons learned from that job and automatically updates the user's memory (data/users/{userId}/memory/) and, when needed, a custom piece. All changes are saved as snapshots and can be reverted from the Memory & Learning tab.",
    "enableLabel": "Enable Reflection (auto-apply)",
    "enableHelp": "When ON, a reflection job runs in the background after every agent job and rewrites memory automatically. Default: disabled.",
    "workerWarn": {
      "title": "⚠ No reflection worker configured",
      "body": "Even with Reflection enabled, jobs are not enqueued unless a worker with reflection in its roles exists. Add a worker like the following on the LLM → Workers tab:",
      "snippet": "id: reflection-1\nconnection_type: direct\nendpoint: http://localhost:11434/v1\nmodel: qwen2.5:3b      # cheap model recommended\nroles: [reflection]\nmax_concurrency: 1"
    },
    "workerRequiredLabel": "Require a dedicated reflection worker",
    "workerRequiredHelp": "ON: when no worker has roles: [reflection], reflection jobs are skipped instead of enqueued (default). OFF: with only enabled set, they may be picked up by other workers.",
    "maxMemHelp": "Maximum memory entries one job's reflection can write. Default: 3",
    "maxBodyHelp": "Maximum bytes for a memory entry body. The semantic validator rejects anything larger. Default: 8192",
    "cooldownHelp": "Cooldown that suppresses consecutive edits to the same piece. Default: 24 (after 2 edits within 24h, the 3rd and later are skipped)",
    "activityLogHelp": "Compression limit for the activity log passed to the reflection LLM. Default: 4096",
    "dailyBudgetHelp": "Total tokens one user can spend per day on reflection. Reflections beyond this are not enqueued. Default: 200000",
    "snapRetentionHelp": "Retention days for reflection-history snapshots. Default: 90",
    "snapMaxUserHelp": "Total size limit (bytes) of the snapshot directory per user. Oldest are deleted when exceeded. Default: 100 MiB",
    "snapMaxEntryHelp": "Maximum size (bytes) of a single snapshot entry. Default: 1 MiB",
    "storeLlmRawLabel": "Store the raw LLM response in the snapshot",
    "storeLlmRawHelp": "When ON, llm-raw.json is included in the snapshot. For debugging; default OFF (to save disk).",
    "abstainFloorHelp": "When the abstain (nothing-to-learn) rate falls below this, it warns as an over-learning sign (operational signal). Default: 0.3. A warn is logged when it drops below this value. If the apply rate is too high, consider lowering max_memory_changes_per_job."
  },
  "notifications": {
    "v1Title": "Browser notifications (V1: foreground)",
    "v1UnsupportedTitle": "Browser notifications",
    "v1Unsupported": "Your browser does not support the Notification API.",
    "statusLabel": "Status: {{status}}",
    "status": {
      "enabled": "✅ Enabled",
      "paused": "⏸ Paused",
      "denied": "🚫 Blocked by browser",
      "notAllowed": "❌ Not allowed"
    },
    "enableButton": "Enable browser notifications",
    "deniedHelp": "Change \"Notifications\" to Allow from the settings icon at the left of the browser address bar.",
    "masterToggle": "Receive notifications (master ON/OFF)",
    "testV1": "Test notification (in-page)",
    "testTitle": "Test notification",
    "testBody": "Browser notifications are working",
    "v2Title": "📱 Mobile / background notifications (V2)",
    "pushUnsupported": "Your browser does not support the Web Push API",
    "iosPwaHelp": "On iOS Safari, install it as an app via \"Share → Add to Home Screen\", then open it from the home-screen icon to enable notifications.",
    "deviceSubscribed": "✅ Subscribed on this device",
    "deviceNotSubscribed": "❌ This device is not subscribed",
    "deviceCount": " ({{count}} devices total)",
    "subscribe": "Subscribe on this device",
    "unsubscribe": "Unsubscribe",
    "testV2": "Test notification (via server)",
    "deviceListTitle": "Subscribed devices",
    "unknownDevice": "(unknown)",
    "failures": "⚠ {{count}} failures",
    "removeDevice": "Remove",
    "includeDetails": "Include task details (title, piece name) in notifications",
    "includeDetailsHint": "(OFF: only \"Task #N done\")",
    "error": "Error: {{msg}}",
    "eventsTitle": "Events to notify",
    "events": {
      "running": "Task started (running)",
      "succeeded": "Task completed (succeeded)",
      "failed": "Task failed (failed / aborted)",
      "waiting_human": "Waiting for your input (waiting_human)"
    },
    "footer1": "ⓘ V1 (foreground) works only while the tab is open and focused",
    "footer2": "ⓘ V2 (mobile / background) works reliably only over HTTPS + PWA install",
    "footer3": "ⓘ Only tasks you own trigger notifications"
  },
  "pushNotifications": {
    "title": "Web Push (Server)",
    "intro": "Server settings for browser notifications V2 (Web Push). HTTPS hosting required (plus PWA install on iOS). Each user manages their own subscription on the 🔔 Notifications tab.",
    "enableLabel": "Enable Web Push",
    "enableHelp": "Master switch. Default: disabled (the operator must explicitly opt in).",
    "subjectLabel": "VAPID Subject",
    "subjectHelp": "VAPID subject per RFC 8292. An operational URL is preferred over a generic mailto:.",
    "currentPathLabel": "VAPID Current Key Path",
    "currentPathHelp": "Path to the current VAPID key-pair file (auto-generated with mode 0600 if not present).",
    "historyDirLabel": "VAPID History Dir",
    "historyDirHelp": "Directory where revoked VAPID keys are archived.",
    "payloadMaxLabel": "Payload Max Bytes",
    "payloadMaxHelp": "Maximum push-payload bytes before encryption (cap 4096). Default: 3072",
    "queueConcurrencyLabel": "Queue Concurrency",
    "queueConcurrencyHelp": "Number of concurrent sends from the queue. Default: 8",
    "perSendTimeoutLabel": "Per-Send Timeout (ms)",
    "perSendTimeoutHelp": "Timeout per send in milliseconds. Default: 10000"
  },
  "llmWorkers": {
    "title": "LLM Workers",
    "intro": "This section defines the LLM connections (workers) that AAO calls to run jobs. The setting that exposes AAO itself as a gateway is under LLM → Gateway Server.",
    "rolesHelp": "Roles: auto (candidate for every job) / fast · quality (performance profiles) / reflection (reflection only) / title (title generation only). Multiple values allowed.",
    "empty": "No workers registered. Add at least one.",
    "moveUp": "Move up",
    "moveDown": "Move down",
    "removeWorker": "Delete this worker",
    "connectionType": "Connection type",
    "endpointOverride": "Overridden by the OLLAMA_BASE_URL environment variable",
    "selfLoopWarn": "The endpoint looks like it points at this instance itself (self-loop). You can ignore this warning when it goes through a reverse proxy.",
    "apiKeyRequired": "API key (required)",
    "apiKeyOptional": "API key (optional)",
    "apiKeyOptionalPlaceholder": "sk-... (optional)",
    "apiKeyGatewayHelp": "Paste an sk-aao-* issued under another AAO's LLM → Gateway Server.",
    "apiKeyDirectHelp": "Set only when Bearer auth is required. Leave empty for standalone Ollama.",
    "modelHelp": "If the endpoint serves /models, candidates appear in the dropdown. If not (auth required, behind a proxy, etc.), type it in directly.",
    "maxConcurrency": "Max concurrency",
    "enabled": "Enabled",
    "vlmTitle": "When the model supports VLM, ReadImage uses the worker's own model",
    "addWorker": "+ Add worker",
    "globalTitle": "Global LLM Settings",
    "timeoutHelp": "Timeout for an LLM request (minutes). Default: 10",
    "retryTitle": "Retry (per-call HTTP)",
    "maxAttemptsHelp": "Maximum attempts for a single LLM API call",
    "backoffHelp": "Wait time between retries (ms). Consumed in array order.",
    "retryableStatusHelp": "HTTP status codes to retry on."
  },
  "branding": {
    "notSet": "Not set",
    "replace": "Replace",
    "upload": "Upload",
    "delete": "Delete",
    "accept": "{{accept}} / max {{kb}}KB",
    "sizeExceeded": "File size exceeds the {{kb}}KB limit",
    "uploadFailed": "Upload failed ({{status}})",
    "deleteFailed": "Delete failed ({{status}})",
    "title": "Branding",
    "intro": "Customize the UI title, colors, logo, and footer. Settings live in the branding section of config.yaml and images are stored under data/branding/. Both are gitignored and unaffected by git pull.",
    "appName": "App name",
    "appNameHelp": "Shown at the top-left of the TopBar and in the browser title.",
    "primaryColor": "Primary color",
    "primaryColorHelp": "Reflected in the brand color, such as the app name in the header (hex / rgb).",
    "loginTitle": "Login page heading",
    "loginTitleHelp": "Falls back to the app name when not set.",
    "logo": "Logo",
    "logoHelp": "Shown at the top-left of the TopBar. Uses the default icon when not set.",
    "favicon": "Favicon",
    "faviconHelp": "Shown on the browser tab.",
    "footer": "Footer text",
    "footerHelp": "Shown small at the bottom of the screen. Hidden when not set."
  },
  "safety": {
    "title": "Safety",
    "maxIterHelp": "Max iterations per movement. Default: 200",
    "maxRevisitsHelp": "Re-visit limit for the same movement (loop detection). Default: 3",
    "maxToolLoopHelp": "When the exact same tool call (tool name + args) repeats consecutively this many times within one movement, it is treated as a loop and force-aborted (2 or more, default: 5). A warning is injected to the agent one step before.",
    "promptGuardHelp": "What fraction of the context limit the prompt may occupy before auto-compaction kicks in prior to sending (0.5–0.95, default: 0.8)",
    "deadlineTitle": "Execution deadline",
    "maxJobMinutesHelp": "Hard wall-clock ceiling (minutes) for one job's active execution. Past this the job is auto-terminated (marked cancelled, reason \"timed out\") so its worker slot is released — the last-resort backstop for runaways or hangs in non-abortable tools. Default: 180, 0 disables.",
    "deadlineGraceHelp": "Grace period (seconds) after the deadline fires before the worker force-releases the slot and marks the job cancelled (reason \"timed out\"), in case the cooperative abort is ignored. Insurance against jobs stuck in tools that don't honor the abort signal. Default: 15, 0 disables this fallback (not recommended).",
    "bashSandboxTitle": "Bash sandbox",
    "bashSandboxAuto": "auto (sandboxed if bwrap is present, otherwise hardened-whitelist)",
    "bashSandboxAlways": "always (force sandboxed; fail at startup if bwrap is missing)",
    "bashSandboxOff": "off (raw exec; backward-compatible / not recommended)",
    "bashSandboxHelp": "Sandbox mechanism for the Bash tool. Default: auto",
    "bashUnrestricted": "Lift the command whitelist (unrestricted Bash)",
    "bashUnrestrictedHelp": "Allow arbitrary commands under the bwrap sandbox. Only the workspace (rw) and system directories (ro) are bind-mounted. Requires bwrap user-namespace support. Default: disabled (change carefully for security reasons).",
    "bashAllowNetwork": "Allow network in the sandbox (bash / python / npm can reach the network)",
    "bashAllowNetworkWarnLabel": "⚠ Security warning:",
    "bashAllowNetworkHelp": " Normally bash, python, and npm inside the sandbox have the network cut off (--unshare-net). Enabling this lets pip / npm install / curl etc. work, but the sandbox isolation weakens and arbitrary data exfiltration and reaching internal-network or metadata endpoints (SSRF) become possible. Enable only in a trusted environment. Default: disabled. (When Bash Sandbox Mode is off or bwrap is absent there is no network cutoff to begin with, so this setting only takes effect when sandboxed.)",
    "historyTitle": "History Summarization",
    "historyEnable": "Enable automatic history summarization",
    "historyEnableHelp": "Automatically summarize old conversation history to save context. Default: enabled",
    "tailTurnsHelp": "Number of recent assistant+tool turns to always keep. Default: 2",
    "preserveRecentHelp": "Token budget of recent messages preserved without summarization. Default: 8000"
  },
  "mcp": {
    "intro": "Settings for connecting to and running external MCP (Model Context Protocol) servers. To add a target server, specify its MCP server URL from a task or from settings.",
    "securityTitle": "Security",
    "allowPrivate": "Allow connections to private IPs (for self-hosted / localhost MCP servers)",
    "allowPrivateHelp": "When enabled, allows MCP connections to localhost and LAN addresses (192.168.x.x, 10.x.x.x, etc.). There is an SSRF risk, so use only in a trusted network environment. Default: disabled",
    "timeoutTitle": "Timeout / cache",
    "callTimeout": "Tool call timeout (seconds)",
    "callTimeoutHelp": "Maximum time allowed for a single MCP tool call (seconds). Default: 60",
    "cacheTtl": "Tool list cache TTL (seconds)",
    "cacheTtlHelp": "How long to cache the tool list fetched from an MCP server (seconds). Default: 600",
    "oauthTtl": "OAuth pending state TTL (minutes)",
    "oauthTtlHelp": "How long to keep the pending state of the MCP OAuth authorization flow (minutes). Default: 10",
    "capacityTitle": "Capacity limits",
    "maxBinary": "Max size per tool-output binary (MB)",
    "maxBinaryHelp": "Maximum size of a single binary output file returned by an MCP tool (MB). Default: 20",
    "maxFiles": "Max binary files per job",
    "maxFilesHelp": "Maximum number of binary files an MCP tool can save in one job. Default: 10",
    "maxTotal": "Max total binary size per job (MB)",
    "maxTotalHelp": "Maximum total size of binary output an MCP tool can save in one job (MB). Default: 200"
  },
  "auth": {
    "title": "Authentication",
    "intro": "Login authentication. Leaving providers unset runs in no-auth mode (everyone is a local admin). A server restart is required after changes.",
    "primaryProvider": "Primary Provider",
    "primaryNone": "(none — all enabled)",
    "primaryGoogle": "google only",
    "primaryGitea": "gitea only",
    "primaryLocal": "local only",
    "primaryHelp": "Specify to restrict to a single provider. If unset, login is allowed with every configured provider (OAuth + local).",
    "adminEmails": "Admin Emails",
    "adminEmailsHelp": "Email addresses to grant the admin role.",
    "secureCookie": "Secure Cookie (send cookie over HTTPS only)",
    "secureCookieHelp": "Recommended on in production (HTTPS). Off for local HTTP development.",
    "sessionMaxAgeHelp": "Session lifetime (milliseconds).",
    "sessionSecretHelp": "Session signing key. Set a sufficiently long random string (masked after saving).",
    "localTitle": "Local accounts (email + password)",
    "localEnable": "Enable local login",
    "localEnableHelp": "Allow email/password login without standing up an external IdP. Can coexist with OAuth (restrict to local only with primary_provider: local).",
    "allowSignup": "Allow self-signup",
    "allowSignupHelp": "Show a sign-up form on the login screen. Sign-ups are created pending approval and cannot log in until an admin approves them in user management.",
    "bootstrapHelp": "Because entering the UI already requires an admin login, the first admin (bootstrap_admin) is configured via auth.local.bootstrap_admin in config.yaml (seeded with id='local', inheriting data from the no-auth era). Subsequent users can be created and have their passwords reset from the user management screen.",
    "googleTitle": "Google OAuth",
    "giteaTitle": "Gitea OAuth"
  },
  "browser": {
    "title": "Browser",
    "pageTimeoutHelp": "Page-load timeout (milliseconds). Default: 60000",
    "actionTimeoutHelp": "Browser-action timeout (milliseconds). Default: 30000",
    "channelHelp": "If Google login etc. rejects you as an \"insecure browser\", switch to chrome. The host must have google-chrome installed.",
    "execPathHelp": "Specify only when using a browser at a non-standard path. Follows the channel when unset.",
    "sessionsTitle": "Sessions (CDP)",
    "sessionMode": "Browser Session Mode",
    "headless": "Headless (no live view, default)",
    "novnc": "noVNC (live view of browser / CAPTCHA)",
    "sessionModeHelp": "Master switch for the live-view feature. Setting novnc enables live view on the Browser tab, InteractiveBrowse, and CAPTCHA solving. The host needs Xvfb / x11vnc / websockify (falls back to headless automatically if missing). In headless (default) those live-view features are unavailable.",
    "maxCaptchaHelp": "Upper limit on the number of pages the CAPTCHA pool can open at once (effective in novnc mode only). Default: 5",
    "vncPortHelp": "Base port for the VNC server. Default: 5900",
    "sessionDirHelp": "Directory where cookies are persisted.",
    "maxSessionsHelp": "Maximum number of sessions that can run at once. Default: 3",
    "idleTtl": "Task Session Idle TTL (seconds)",
    "idleTtlHelp": "Seconds before a task's CDP session is discarded after going idle (GC). Default: 300"
  },
  "orgs": {
    "intro": "Organizations for local accounts. Set a task/schedule visibility to org to share with members of the same organization (separate from Gitea organizations).",
    "newNamePlaceholder": "New organization name",
    "create": "+ Create organization",
    "loading": "Loading...",
    "empty": "No organizations. Create one above.",
    "confirmDelete": "Delete the organization \"{{name}}\"?\nTasks shared with this organization revert to private.",
    "memberHint": "Adding/removing a member invalidates that user's session; the change applies on their next access.",
    "delete": "Delete",
    "membersCount": "Members ({{count}})",
    "noMembers": "No members",
    "removeMemberTitle": "Remove",
    "addMemberPlaceholder": "Add a member...",
    "add": "Add"
  },
  "pathsStorage": {
    "intro": "Filesystem storage locations and limits. In config v2 these are consolidated under storage.*.",
    "worktreeOverride": "Overridden by the WORKTREE_DIR environment variable",
    "worktreeHelp": "Base path for the working directory during job execution",
    "customPiecesHelp": "A directory for additional Pieces, separate from the repository's pieces/. When omitted, only pieces/ is used.",
    "userFolderHelp": "Root directory storing per-user settings, scripts, memory, etc.",
    "taskUploadLabel": "Task Upload max size (MB)",
    "taskUploadHelp": "Request-body limit for POST /api/local/tasks and POST /api/local/tasks/:id/comments. Range 1–1000 MB, default 50.",
    "trashLabel": "Trash Retention (days)",
    "trashHelp": "Days before files under data/users/{userId}/trash/ are auto-deleted. 0 deletes immediately. Default 30 days."
  },
  "execution": {
    "intro": "Concurrency, the movement cap per job, and job-failure retry settings.",
    "concurrencyOverride": "Overridden by the CONCURRENCY environment variable",
    "concurrencyHelp": "Number of jobs that can run concurrently",
    "maxMovementsHelp": "Maximum number of movements per job",
    "maxAttemptsHelp": "Maximum retry attempts on job failure. Default: 3",
    "backoffHelp": "Retry interval (seconds). Comma-separated. Default: 60, 300, 900"
  },
  "context": {
    "limitPlaceholder": "auto (fetched from the Ollama API)",
    "limitHelp": "Manually set the token limit. Leave blank to auto-detect.",
    "thresholdsLabel": "Thresholds",
    "thresholdsHelp": "Actions based on context-usage ratio. ratio is 0–1. warn: only logs a warning / prompt: injects a message urging the LLM to transition / force_transition: forces a transition to default_next"
  },
  "metrics": {
    "intro": "Prometheus-compatible metrics for the LLM Worker and AAO Gateway Server. In config v2 these are split into llm.metrics and gateway.metrics.",
    "enable": "Enable",
    "prefixHelp": "Prefix for Prometheus metric names (e.g. {{prefix}})",
    "bearerHelp": "Bearer token required when accessing the /metrics endpoint. Use env:NAME to reference an environment variable.",
    "allowedHostsHelp": "Allowed client hosts (IP / hostname). When empty, only the token is used for auth."
  },
  "configForm": {
    "adminOnly": "Only administrators can view this setting.",
    "conflict": "The configuration was changed elsewhere. Reload?",
    "saved": "Saved",
    "error": "Error: {{msg}}",
    "saving": "Saving...",
    "loadError": "Failed to load configuration",
    "unsaved": "Unsaved: {{count}} items — changes don't apply until you press \"Save & Apply\"",
    "unsavedShort": "Unsaved {{count}}"
  },
  "searchFilter": {
    "blockedLabel": "Blocked Patterns",
    "blockedHelp": "Patterns to filter from WebSearch queries (regular expressions).",
    "autoBlockLabel": "Auto Block",
    "autoBlockPrivateIp": "Private IP",
    "autoBlockInternalDomain": "Internal domain",
    "autoBlockEmail": "Email address",
    "autoBlockPhone": "Phone number",
    "autoBlockHelp": "Automatically blocks sensitive information contained in search queries."
  },
  "pieceEditor": {
    "toastSaveYamlParse": "Error: failed to parse YAML",
    "toastSaveYamlError": "Error: YAML parse error — {{msg}}",
    "toastSaved": "Saved",
    "toastError": "Error: {{msg}}",
    "confirmDelete": "Delete the piece \"{{name}}\"? This cannot be undone.",
    "loadError": "Failed to load the piece",
    "readonly": "Read-only",
    "yamlHelp": "You can edit the YAML directly. Switching to Visual mode parses it automatically.",
    "yamlParseFailed": "Failed to parse YAML"
  },
  "pieceMeta": {
    "nameHelp": "Only lowercase letters, digits, and hyphens are allowed",
    "maxMovementsHelp": "Maximum number of movements one job can run. Prevents loops.",
    "initialMovementHelp": "The movement run first when a job starts",
    "keywordsHelp": "When a task body contains these keywords, this piece is auto-selected"
  },
  "movement": {
    "instructionHelp": "The instruction passed to the LLM. Markdown is supported"
  },
  "rules": {
    "conditionPlaceholder": "condition...",
    "help": "The condition the LLM uses when picking a transition target via the transition tool"
  },
  "askSubtasks": {
    "askMaxHelp": "Limit of ASKs (questions to the user) per job",
    "maxDepthHelp": "Maximum nesting depth for subtasks",
    "maxPerParentHelp": "Maximum number of subtasks a single parent job can spawn. Default: 10"
  },
  "namespaceEditor": {
    "addAriaDisabled": "Adding new entries is disabled",
    "addAria": "Add new",
    "add": "+ Add",
    "mcpGuide": "MCP guide"
  },
  "stringArray": {
    "add": "Add"
  },
  "movementAccordion": {
    "confirmDelete": "Delete the movement \"{{name}}\"?"
  },
  "formUtils": {
    "envOverride": "Overridden by an environment variable (saving will not apply)"
  },
  "settingsPage": {
    "sectionList": "Section list"
  },
  "serverTls": {
    "title": "HTTPS / TLS",
    "port": "HTTP(S) port",
    "portHelp": "Port the server listens on (default 9876). The PORT environment variable (incl. .env) takes precedence over this value. Requires a restart to apply.",
    "enabled": "Serve over HTTPS",
    "enabledHelp": "Terminate TLS in the app. Self-signed by default — browsers (and the noVNC / SSH console over wss) will warn until you install a real certificate. Requires a restart to apply.",
    "certFile": "Certificate file (PEM)",
    "keyFile": "Private key file (PEM)",
    "certHelp": "Leave both empty to use an auto-generated self-signed certificate.",
    "httpRedirect": "Redirect HTTP to HTTPS",
    "httpRedirectHelp": "Sent as a temporary redirect (302), so turning this off stops the redirect — browsers don't cache it permanently. Requires a restart to apply.",
    "httpRedirectPort": "HTTP redirect port(s)",
    "httpRedirectPortHelp": "Port(s) that redirect plain HTTP to HTTPS. One port, or a comma-separated list (e.g. 80, 9876). Each must differ from the HTTPS port; ports below 1024 (like 80) require privileges.",
    "redirectHost": "Redirect host (optional)",
    "hsts": "Send HSTS (advanced — off by default)",
    "hstsHelp": "Turn on only when you serve a real certificate. HSTS pins browsers to HTTPS and cannot be undone once sent (with a self-signed cert it is pure footgun). While off, the server sends a clearing signal (max-age=0) so a browser pinned by an earlier build recovers. If you were stuck being redirected to HTTPS, leave this off and load the app once over HTTPS to clear the pin. Requires a restart to apply.",
    "selfSignedHosts": "Additional certificate hostnames",
    "restartBanner": "Changes to HTTPS settings require a server restart to take effect."
  }
}
