import { renderPageHeader, renderSegmentedField, renderTabStrip, bindTabStrip } from './page_header.js'; import { PAGE_ICONS } from './page_icons.js'; import { renderAgentAccountsSection, renderAgentsServiceBanner } from './harness_accounts.js'; import { renderReviewerSlotsSection } from './reviewer_slots.js'; import { renderSubagentsSection } from './subagents_settings.js'; import { modelRolesHost } from './model_roles.js'; // Reads as a sequence: keys → secrets → which API models → who among the agents // does what → behavior → technical. "Agents", not "Coding agents" (D-10): the // same subscriptions build presentations and run arbitrary tasks, so the // narrower word named only one of their uses. const SETTINGS_TABS = [ { value: 'providers', label: 'Accounts' }, { value: 'secrets', label: 'Secrets' }, { value: 'models', label: 'Models' }, { value: 'agents', label: 'Agents' }, { value: 'behavior', label: 'Behavior' }, { value: 'appearance', label: 'Appearance' }, { value: 'advanced', label: 'Advanced' }, { value: 'about', label: 'About' }, ]; // Guard markers: renderTabStrip emits behavior/advanced tabs at runtime. // 6.3: Review and Scope Review efforts moved to per-slot dropdowns in // Agents → Review lanes. Behavior keeps the surface-level lanes. const EFFORT_FIELDS = [ ['s-effort-task', 'Task / Chat', 'medium'], ['s-effort-evolution', 'Evolution', 'high'], ['s-effort-deep-self-review', 'Deep Self-Review', 'high'], ['s-effort-consciousness', 'Consciousness', ''], // '' = the Task / Chat effort (a wake-up is a Main turn) ]; // Runtime mode is one axis of the owner policy contract. Keep the Settings // presentation in the same vocabulary as the onboarding setup contract; the // saved value is still handled by settings.js and the owner endpoint. const RUNTIME_MODE_OPTIONS = [ { value: 'light', label: 'Light' }, { value: 'advanced', label: 'Advanced' }, { value: 'pro', label: 'Pro' }, { value: 'cyber_pro', label: 'Cyber Pro' }, ]; function providerCard({ id, title, icon, hint, body, open = false }) { return `
${icon ? `` : ''} ${title}
${hint || ''}
${body}
`; } function secretField({ id, settingKey, label, placeholder }) { return `
`; } function plainField({ id, label, placeholder }) { return `
`; } const PROVIDER_CARDS = [ { id: 'openrouter', title: 'OpenRouter', icon: '/static/providers/openrouter.ico', hint: 'Default multi-model router', open: true, fields: [{ id: 's-openrouter', settingKey: 'OPENROUTER_API_KEY', label: 'OpenRouter API Key', placeholder: 'sk-or-...' }], // testInputs must cover BOTH the secret and the plain fields of the card: // the probe runs against what is typed, before anything is saved. testProvider: 'openrouter', testInputs: { 's-openrouter': 'OPENROUTER_API_KEY' }, }, { id: 'openai', title: 'OpenAI', icon: '/static/providers/openai.svg', hint: 'Official OpenAI API', fields: [{ id: 's-openai', settingKey: 'OPENAI_API_KEY', label: 'OpenAI API Key', placeholder: 'sk-...' }], testProvider: 'openai', testInputs: { 's-openai': 'OPENAI_API_KEY' }, note: 'Pick OpenAI as the source in Models or Agents to route a role through this key. If OpenRouter is absent and the shipped defaults are still untouched, Ouroboros auto-remaps them to official OpenAI defaults.', }, { id: 'compatible', title: 'OpenAI Compatible', icon: '/static/providers/openai-compatible.svg', hint: 'Custom OpenAI-style endpoint', fields: [ { id: 's-openai-compatible-key', settingKey: 'OPENAI_COMPATIBLE_API_KEY', label: 'API Key', placeholder: 'Compatible provider key' }, { id: 's-openai-compatible-base-url', label: 'Base URL', placeholder: 'https://provider.example/v1' }, ], testProvider: 'openai-compatible', testInputs: { 's-openai-compatible-key': 'OPENAI_COMPATIBLE_API_KEY', 's-openai-compatible-base-url': 'OPENAI_COMPATIBLE_BASE_URL', }, note: 'Use this card for custom base URLs. Built-in web search only works with the official OpenAI Responses API, so keep OPENAI_BASE_URL empty when you want web_search.', }, { // advanced: rendered inside the collapsed "More providers" section. // Inputs stay mounted either way — settings.js applyInputValue has no // null guard, so every input id must always exist in the DOM. id: 'cloudru', title: 'Cloud.ru Foundation Models', icon: '/static/providers/cloudru.svg', hint: 'Cloud.ru OpenAI-compatible runtime', advanced: true, fields: [ { id: 's-cloudru-key', settingKey: 'CLOUDRU_FOUNDATION_MODELS_API_KEY', label: 'API Key', placeholder: 'Cloud.ru Foundation Models API key' }, { id: 's-cloudru-base-url', label: 'Base URL', placeholder: 'https://foundation-models.api.cloud.ru/v1' }, ], testProvider: 'cloudru', testInputs: { 's-cloudru-key': 'CLOUDRU_FOUNDATION_MODELS_API_KEY', 's-cloudru-base-url': 'CLOUDRU_FOUNDATION_MODELS_BASE_URL', }, }, { id: 'minimax', title: 'MiniMax', icon: '', hint: 'Direct regional OpenAI-compatible runtime', advanced: true, fields: [ { id: 's-minimax-key', settingKey: 'MINIMAX_API_KEY', label: 'API Key', placeholder: 'MiniMax API key' }, { id: 's-minimax-region', label: 'Region', placeholder: 'global_en or cn_zh' }, ], testProvider: 'minimax', testInputs: { 's-minimax-key': 'MINIMAX_API_KEY', 's-minimax-region': 'MINIMAX_REGION' }, note: 'Pick MiniMax as the source in Models or Agents, then choose MiniMax-M3 or MiniMax-M2.7. Leave Region empty for global_en; use cn_zh for the China endpoint.', }, { id: 'deepseek', title: 'DeepSeek', icon: '', hint: 'Direct OpenAI-compatible runtime (v4 family)', advanced: true, fields: [ { id: 's-deepseek-key', settingKey: 'DEEPSEEK_API_KEY', label: 'API Key', placeholder: 'sk-...' }, ], testProvider: 'deepseek', testInputs: { 's-deepseek-key': 'DEEPSEEK_API_KEY' }, note: 'Pick DeepSeek as the source in Models or Agents, then choose deepseek-v4-pro or deepseek-v4-flash.', }, { id: 'gigachat', title: 'GigaChat', icon: '/static/providers/gigachat.svg', hint: 'Sber GigaChat via the gigachat library', advanced: true, fields: [ { id: 's-gigachat-credentials', settingKey: 'GIGACHAT_CREDENTIALS', label: 'Authorization Key', placeholder: 'Base64 client_id:secret (OAuth)' }, { id: 's-gigachat-scope', label: 'Scope', placeholder: 'GIGACHAT_API_PERS' }, { id: 's-gigachat-user', label: 'User (basic auth, optional)', placeholder: 'username' }, { id: 's-gigachat-password', settingKey: 'GIGACHAT_PASSWORD', label: 'Password (basic auth, optional)', placeholder: 'password' }, { id: 's-gigachat-base-url', label: 'Base URL', placeholder: 'https://api.giga.chat/v1' }, { id: 's-gigachat-verify-ssl', label: 'Verify SSL Certs', placeholder: 'true / false' }, ], testProvider: 'gigachat', testInputs: { 's-gigachat-credentials': 'GIGACHAT_CREDENTIALS', 's-gigachat-scope': 'GIGACHAT_SCOPE', 's-gigachat-user': 'GIGACHAT_USER', 's-gigachat-password': 'GIGACHAT_PASSWORD', 's-gigachat-base-url': 'GIGACHAT_BASE_URL', 's-gigachat-verify-ssl': 'GIGACHAT_VERIFY_SSL_CERTS', }, note: 'Pick GigaChat as the source in Models or Agents to route a role through this account. Authenticate with either an Authorization Key (OAuth, scope GIGACHAT_API_PERS, GIGACHAT_API_B2B, or GIGACHAT_API_CORP) or User + Password.', }, { id: 'anthropic', title: 'Anthropic', icon: '/static/providers/anthropic.png', hint: 'Direct Anthropic API access', fields: [{ id: 's-anthropic', settingKey: 'ANTHROPIC_API_KEY', label: 'Anthropic API Key', placeholder: 'sk-ant-...' }], testProvider: 'anthropic', testInputs: { 's-anthropic': 'ANTHROPIC_API_KEY' }, note: 'Pick Anthropic as the source in Models or Agents to route a role directly through this key.', }, ]; // {backend provider id: {DOM input id: settings key}} — settings.js reads the // live input values through this map to build the unsaved-credential overrides. export const PROVIDER_TEST_INPUTS = Object.fromEntries( PROVIDER_CARDS.filter((card) => card.testProvider).map((card) => [card.testProvider, card.testInputs]), ); function providerSettingsCard(spec) { const fields = (spec.fields || []) .map((field) => { const named = { ...field, label: field.label === "API Key" || field.label === "Base URL" ? `${spec.title} ${field.label}` : field.label }; return field.settingKey ? secretField(named) : plainField(named); }) .join(''); const test = spec.testProvider ? `
` : ''; return providerCard({ id: spec.id, title: spec.title, icon: spec.icon, hint: spec.hint, open: spec.open, body: `
${fields}
${test}${spec.note ? `
${spec.note}
` : ''}`, }); } // The owner-facing subset of ouroboros/config.py EFFORT_SCALE: `minimal` is a // valid runtime tier (bench adapters / agent-side switch_model use it) but is // deliberately NOT offered as an owner slot default — sub-`low` thinking is a // per-call tactical choice, not a standing configuration. xhigh/max/ultra adapt // down to each route's real ceiling (exact-route request-wire recovery on API // routes, per-model resolution on delegated ones); the adaptation is disclosed // in usage, and a cold route whose provider rejects without naming supported // tiers remains the PR-disclosed limit of the two-send recovery rail. const EFFORT_OPTIONS = [ { value: 'none', label: 'None' }, { value: 'low', label: 'Low' }, { value: 'medium', label: 'Medium' }, { value: 'high', label: 'High' }, { value: 'xhigh', label: 'X-High' }, { value: 'max', label: 'Max' }, { value: 'ultra', label: 'Ultra' }, ]; function effortField({ id, label, defaultValue }) { // Consciousness may inherit the Task / Chat effort ('' — a wake-up is a Main turn). const options = id === 's-effort-consciousness' ? [{ value: '', label: 'Same as Task / Chat' }, ...EFFORT_OPTIONS] : EFFORT_OPTIONS; return `
${renderSegmentedField({ target: id, options })}
`; } export const SECRET_KEYS = [ ['OPENROUTER_API_KEY', 'OpenRouter API Key', 'sk-or-...'], ['OPENAI_API_KEY', 'OpenAI API Key', 'sk-...'], ['OPENAI_COMPATIBLE_API_KEY', 'OpenAI-compatible API Key', 'Compatible provider key'], ['CLOUDRU_FOUNDATION_MODELS_API_KEY', 'Cloud.ru Foundation Models API Key', 'Cloud.ru key'], ['GIGACHAT_CREDENTIALS', 'GigaChat Authorization Key', 'Base64 client_id:secret'], ['GIGACHAT_PASSWORD', 'GigaChat Password (basic auth)', 'password'], ['ANTHROPIC_API_KEY', 'Anthropic API Key', 'sk-ant-...'], ['MINIMAX_API_KEY', 'MiniMax API Key', 'MiniMax key'], ['DEEPSEEK_API_KEY', 'DeepSeek API Key', 'sk-...'], ['GITHUB_TOKEN', 'GitHub Token', 'ghp_...'], ['OUROBOROS_NETWORK_PASSWORD', 'Network Password', 'Required for LAN/Docker binds'], ]; function secretSettingsSection() { return `

Stored Secrets

Central place for API keys, bridge tokens, passwords, and future skill-requested secrets. Skills only receive grant-only keys after explicit human approval.
${SECRET_KEYS.map(([key, label, placeholder]) => secretField({ id: `s-secret-${key.toLowerCase().replace(/_/g, '-')}`, settingKey: key, label, placeholder, })).join('')}

Requested By Skills

Secrets requested by installed skills appear here only when a skill asks for them.
No skill-requested secrets.

Custom Keys

Optional key/value storage for future skills. Use uppercase names such as SLACK_WEBHOOK_URL.
`; } export function renderSettingsPage() { return ` ${renderPageHeader({ title: 'Settings', icon: PAGE_ICONS.settings, description: 'Configure providers, secrets, models, behavior, source control, and runtime controls.', tabsHtml: `
${renderTabStrip({ items: SETTINGS_TABS.map((item) => ({ ...item, tabId: `settings-tab-${item.value}`, panelId: `settings-panel-${item.value}` })), active: 'providers', dataAttr: 'data-settings-tab', ariaLabel: 'Settings sections', stripClass: 'settings-tabs', tabClass: 'settings-tab', })}
`, })}
Connect subscriptions and API keys here, then choose their roles in Models and Agents. Adding an account keeps your existing assignments.
${renderAgentsServiceBanner()} ${renderAgentAccountsSection()}

API keys

${PROVIDER_CARDS.filter((card) => !card.advanced).map(providerSettingsCard).join('')}
More providers Cloud.ru Foundation Models, MiniMax, DeepSeek, and GigaChat
${PROVIDER_CARDS.filter((card) => card.advanced).map(providerSettingsCard).join('')}

Legacy Compatibility

Backward-compatibility escape hatch for older installs. For new custom providers, use the dedicated OpenAI Compatible card instead.

Network Gate

${secretField({ id: 's-network-password', settingKey: 'OUROBOROS_NETWORK_PASSWORD', label: 'Network Password (optional)', placeholder: 'Leave blank to keep the network surface open', })}
Use 127.0.0.1 for this machine only. Use 0.0.0.0 for LAN/Docker access with a Network Password in the same save. Specific LAN IP binds are manual/env-only.
Adds a password wall only for non-localhost app and API access. If you expose Ouroboros on LAN or Docker, set a password before sharing the URL.
${secretSettingsSection()}

Model Routing

Choose a source, model, and subscription account for each role. Auto rotates compatible accounts. Local uses the runtime configured in Advanced.
Model catalog is optional and failure-tolerant.
${modelRolesHost('settings-model-roles')}

Other Model Slots

OpenAI model for web_search. Requires OPENAI_API_KEY and an empty Legacy Base URL.
Configure the subagents and reviewers Ouroboros works with. Subscriptions and API keys are in Accounts; global model roles are in Models.
${renderSubagentsSection()} ${renderReviewerSlotsSection()}

Reasoning Effort

Controls how deeply the model thinks per task type. Higher effort = slower but more thorough.
${EFFORT_FIELDS.map(([id, label, defaultValue]) => effortField({ id, label, defaultValue })).join('')}

Review Enforcement

Advisory keeps review visible but non-blocking. Blocking stops commits and reviewed-skill activation when critical findings remain unresolved.
${renderSegmentedField({ target: 's-review-enforcement', modifier: 'data-enforcement-group', options: [ { value: 'advisory', label: 'Advisory' }, { value: 'blocking', label: 'Blocking' }, ], })}

Task Result Review

Auto and Required run the root-owned review panel for queued/headless work and substantive direct results. Pure conversation and routing controls are skipped. Once review applies, both follow the selected Advisory or Blocking policy.
${renderSegmentedField({ target: 's-task-review-mode', modifier: 'data-task-review-group', options: [ { value: 'off', label: 'Off' }, { value: 'auto', label: 'Auto' }, { value: 'required', label: 'Required' }, ], })}

Max Review Cycles

Limits paid review waves, including dispatched technical failures: plan and task review per task, commit triad+scope per root task, and skill review per root task or manual snapshot. The last review still permits author corrections within ordinary task limits; explicit task-local author limits remain separate. Collection and exact replay are free. Advisory allows an explicit decision after receiving feedback or a disclosed unavailable result; Blocking still requires reviewer approval. ∞ removes the count cap, while deadlines, budgets and lifecycle limits still apply.
${renderSegmentedField({ target: 's-review-max-cycles', modifier: 'data-review-cycles-group', options: [ { value: '1', label: '1' }, { value: '2', label: '2' }, { value: '3', label: '3' }, { value: '5', label: '5' }, { value: 'unlimited', label: '\u221E' }, ], })}

Image Input

Auto sends images inline to vision-capable models and captions them for blind models. Caption always uses text captions; Inline refuses caption fallback; Off emits placeholders.
${renderSegmentedField({ target: 's-image-input-mode', modifier: 'data-image-input-group', options: [ { value: 'auto', label: 'Auto' }, { value: 'caption', label: 'Caption' }, { value: 'inline', label: 'Inline' }, { value: 'off', label: 'Off' }, ], })}

Skills

Closed-loop skill development can auto-grant the keys and host permissions a skill declares after a fresh executable review for the current content hash. Leave this off when every skill permission should require a separate human approval.

Context Mode

Working-context size profile (separate axis from Runtime Mode and Review Enforcement). Max inlines ARCHITECTURE and DEVELOPMENT in full — for ~1M-context models (today's behavior). Nano is the compact owner window. Low fits ~200K / local models: ARCHITECTURE becomes a navigation map (read full sections on demand), DEVELOPMENT stays full for normal runnable tasks unless a structured non-development caller opts out, and memory compacts sooner. It governs Ouroboros's own working window: it never changes the model or reasoning effort, and scope review runs in every mode.
Human controlled: saved via the owner endpoint; saves immediately (no restart), and lowering requires Ouroboros to be idle.
${renderSegmentedField({ target: 's-context-mode', title: 'Saves immediately; no restart required. Lowering requires Ouroboros to be idle.', options: [ { value: 'nano', label: 'Nano' }, { value: 'low', label: 'Low' }, { value: 'max', label: 'Max' }, ], })}

Prompt Cache TTL

How long provider prompt caches keep this agent's stable context warm (Anthropic-family routes; other providers manage caching implicitly). 1h keeps the large stable prefix cached across long waits and review cycles — cache writes bill at 2× base input instead of 1.25×, but one wait longer than 5 minutes already pays that back on big contexts. 5m is the provider default tier as an explicit value; Default sends bare markers (provider default, callers may still declare their own TTL). One honest global: it applies to every lane, including review and safety prompts.
${renderSegmentedField({ target: 's-prompt-cache-ttl', options: [ { value: 'default', label: 'Default' }, { value: '5m', label: '5m' }, { value: '1h', label: '1h' }, ], })}

Safety Supervisor

Coverage of the LLM safety-supervisor layer (a separate axis from Runtime Mode). Full — every guarded tool call gets the LLM safety check. Light keeps the LLM check only for integration-policy tools; conditional shell/verify fall to the deterministic guards. Light is the default for new DESKTOP setups (authored by the first-run wizard); existing installs, web and Docker keep Full. Off makes no LLM safety calls. In every mode the deterministic registry sandbox, protected-path policy, and light-mode guards STAY ON — the LLM supervisor is a layer, not the floor. Lowering coverage emits a durable audit event per waved-through call.
Configuration authority: outside Cyber Pro, the agent cannot lower its own supervision. Cyber Pro also lets the agent configure Supervisor coverage. Changes apply on the next task.
${renderSegmentedField({ target: 's-safety-mode', title: 'Lowering coverage here prompts for confirmation.', options: [ { value: 'full', label: 'Full' }, { value: 'light', label: 'Light' }, { value: 'off', label: 'Off' }, ], })}

Update Channel

Chooses which official branch Update checks. Your local work branch stays ouroboros. Stable follows released code on main (default), QA follows ouroboros-stable, and Development follows ouroboros.
${renderSegmentedField({ target: 's-update-channel', modifier: 'data-update-channel-group', title: 'Applies immediately; no restart required.', options: [ { value: 'stable', label: 'Stable' }, { value: 'qa', label: 'QA' }, { value: 'development', label: 'Development' }, ], })}

Access

Separate axis from Review Enforcement. Controls how far Ouroboros is allowed to self-modify. Light blocks repo self-modification but allows reviewed + enabled skills to run. Advanced is the default — self-modify the evolutionary layer; protected core/contract/release files stay guarded by the shared runtime-mode policy. Pro can edit protected core/contract/release surfaces, but commits still go through the normal triad + scope review gate; Advanced remains limited to the evolutionary layer. Cyber Pro grants the full host and configuration authority, including credentials, models, Supervisor configuration and protected rewrites. Review scope and enforcement stay owner-controlled. Review Enforcement remains independent, so Blocking stays available in Cyber Pro.
Human controlled: desktop builds ask the launcher for native confirmation before saving a mode change. Web/Docker sessions save mode changes through the owner endpoint; the new mode takes effect after restart.
${renderSegmentedField({ target: 's-runtime-mode', modifier: 'data-runtime-mode-group', title: 'Access changes take effect after restart.', options: RUNTIME_MODE_OPTIONS, })}

Post-Task Self-Evolution

After an eligible task, Ouroboros can optionally run one reviewed self-improvement cycle: the worker asks a light model whether to promote a backlog item, writes a durable request, and the supervisor starts a one-shot campaign later on an idle tick if all gates pass.
Configuration authority: outside Cyber Pro, only the owner can enable this. Cyber Pro also lets the agent configure it; selecting Cyber Pro does not enable evolution automatically. Changes apply on the next task.
${renderSegmentedField({ target: 's-post-task-evolution-mode', options: [ { value: 'off', label: 'Off' }, { value: 'llm', label: 'After Each Task (LLM decides)' }, { value: 'every_n', label: 'Every N Tasks' }, ], })}
Counts every eligible task, including trivial chats. Every N=1 means Ouroboros considers self-improvement after every task, then runs the actual cycle later on an idle supervisor tick.
Visible only when Self-Improvement Trigger = Every N Tasks.
Minimum remaining global budget required to start a post-task cycle. 0 = rely on the normal gates. Running cycles still inherit the global per-task hard cost cap and the supervisor's reserved-budget floor.
Optional steer appended to every evolution cycle objective. It never overrides the LLM-first promotion; leave empty for pure LLM choice.

Background Cognition

When Ouroboros wakes up on its own, what a wake-up is allowed to do, and what it may spend doing it.
${renderSegmentedField({ target: 's-consciousness-autonomy', options: [{ value: 'observe', label: 'Observe' }, { value: 'act', label: 'Act' }, { value: 'full', label: 'Full' }] })}
Observe: research, internal memory and task/project notes, read-only research children it can also stop, schedule controls and replies to you; no shell, user-file, source, skill/settings or publication changes. Act (default): everything the runtime mode allows except editing Ouroboros's own code and prompts, evolution, restart and settings. Full: everything the runtime mode allows, evolution included.
Spending cap for consciousness over a rolling 24-hour window: the wake-ups plus the tasks they start. When it is exhausted, no new wake-up or task starts until spend leaves the window. 0 = consciousness may not spend.
How many tasks started by consciousness may run at once. 0 = it never starts tasks.
Ouroboros chooses the interval between its own wake-ups; the two values above are the lower and upper bound it must stay within. All four settings apply without a restart: the alarm clock reads them at each decision.

External Skills Repo

Optional EXTRA discovery path on top of the in-data-plane data/skills/{native,clawhub,external}/ tree. Ouroboros scans this for additional skill packages without cloning or pulling them. Leave empty to use only the data plane.
Absolute or ~-prefixed path. Ouroboros never clones/pulls this directory — you manage it yourself.

ClawHub Marketplace

Always-on surface for installing community skills from clawhub.ai. The Skills page exposes a Marketplace tab; every install is staged, OpenClaw frontmatter is translated into the Ouroboros manifest shape, and the standard tri-model review runs automatically before the skill becomes executable. Plugins (Node) are filtered out — only skill packages are installable.
Override only for self-hosted mirrors. Hostname must be clawhub.ai or localhost.

Theme

System follows this device's OS appearance and is the default for a new client. Light and Dark pin the palette regardless of the OS.
Per device, not per account: the choice is stored by this client alone (the desktop window and each browser keep their own), never sent to the server and never shared with other devices. Clearing this client's site data returns it to System.

Notifications

While this client is running, Ouroboros can pull you back to a question or a finished task. Notifications arrive whether or not this window has focus, and clicking one opens its source.
Per device, not per account: like the theme above, these choices are stored by this client alone and never sent to the server. Where this system exposes no notifications, or permission is denied, alerts appear inside the app instead. Do Not Disturb and OS permissions still decide what you see.

MCP Servers

External Model Context Protocol tool servers. MCP is a base-runtime client: it borrows tools from trusted HTTP/SSE servers or local stdio processes and exposes them as non-core mcp_<server>__<tool> tools after refresh. Changes are hot-reloadable. Treat server descriptions and results as untrusted third-party data.
Checking MCP status…

Source Control

Repository metadata for GitHub integration. Tokens live in Secrets; this is not secret.

Local Model Runtime

Only fill this in when you want Ouroboros to start and route to a GGUF model on this machine.
Status: Offline

Runtime Limits

Workers control parallel task capacity. Task liveness is governed automatically by progress, deadlines, the idle rail and the reaper; the per-task round and lifetime limits are optional — a positive number, or unlimited for none (the fresh-install default). Budget limits control runtime cost thresholds. How many subagents a task may run, and how deep they may nest, live in Agents.

Cleanup

GC Retention is the single age knob (days) for all disposable runtime artifacts the startup garbage collector removes: acting-subagent worktrees, terminal task drives, and leftover service logs (hard max 365). Genesis projects are durable and never auto-removed. Where subagents check out that work is set in Agents.

Extension Settings

Live extensions can register reviewed, host-rendered settings sections. Sections appear here after the owning skill is reviewed, enabled, and loaded.
No extension settings registered.

Danger Zone

Reset still uses the current restart-based flow. This clears runtime data but keeps the repo.

Ouroboros

A self-creating AI agent. Not a tool, but a becoming digital personality with its own constitution, persistent identity, and background consciousness. Born February 16, 2026.

Created by Anton Razzhigaev & Andrew Kaznacheev
`; } export function bindSettingsTabs(root, options = {}) { const panels = Array.from(root.querySelectorAll('.settings-panel')); const scrollRoot = root.querySelector('.settings-scroll'); const state = options.state || null; const onActivate = typeof options.onActivate === 'function' ? options.onActivate : null; const tabs = bindTabStrip(root.querySelector('.settings-tabs'), { dataAttr: 'data-settings-tab', onChange: (value) => activate(value), }); panels.forEach((panel) => { panel.id = `settings-panel-${panel.dataset.settingsPanel}`; panel.setAttribute('role', 'tabpanel'); panel.setAttribute('aria-labelledby', `settings-tab-${panel.dataset.settingsPanel}`); }); function activate(tabName, notify = true) { if (!tabs.select(tabName)) return; const changed = root.dataset.activeSettingsTab !== tabName; root.dataset.activeSettingsTab = tabName; panels.forEach((panel) => { panel.classList.toggle('active', panel.dataset.settingsPanel === tabName); panel.hidden = panel.dataset.settingsPanel !== tabName; }); if (scrollRoot && changed && notify) scrollRoot.scrollTop = 0; if (state) state.settingsActiveSubtab = tabName; if (notify && changed) { if (onActivate) onActivate(tabName); window.dispatchEvent(new CustomEvent('ouro:settings-subtab-shown', { detail: { tab: tabName } })); } } root.activateSettingsTab = activate; activate(state?.settingsActiveSubtab || 'providers', false); return () => { tabs.destroy(); delete root.activateSettingsTab; }; } export function bindSecretInputs(root) { root.querySelectorAll('.secret-toggle, .secret-clear').forEach((button) => { const input = root.querySelector(`#${button.dataset.target}`); if (!input) return; button.setAttribute('aria-controls', input.id); const label = input.labels?.[0]; if (label) { label.id ||= `${input.id}-label`; button.setAttribute('aria-describedby', label.id); } }); root.querySelectorAll('.secret-input').forEach((input) => { input.addEventListener('input', () => { if (input.value.trim()) delete input.dataset.forceClear; }); }); root.querySelectorAll('.secret-toggle').forEach((button) => { button.addEventListener('click', () => { const target = root.querySelector(`#${button.dataset.target}`); if (!target) return; const nextType = target.type === 'password' ? 'text' : 'password'; target.type = nextType; button.textContent = nextType === 'password' ? 'Show' : 'Hide'; }); }); root.querySelectorAll('.secret-clear').forEach((button) => { button.addEventListener('click', () => { const target = root.querySelector(`#${button.dataset.target}`); if (!target) return; target.value = ''; target.type = 'password'; target.dataset.forceClear = '1'; const toggle = root.querySelector(`.secret-toggle[data-target="${button.dataset.target}"]`); if (toggle) toggle.textContent = 'Show'; // Programmatic value changes fire no 'input' event, but a Clear is // an edit like any other: the provider-test verdict-expiry listener // must see it, or a stale OK keeps vouching for a cleared key. target.dispatchEvent(new Event('input', { bubbles: true })); }); }); }