Navigácia
ELOWEN · Dokumentácia

Posledná aktualizácia: 20. októbra 2026

Prechádzať dokumentáciu · Problem and update settings

Tento článok zatiaľ nie je preložený. Je dostupný v angličtine.

Developer reference

Problem and update settings

Settings mutation ownership

web/lib/mutations/index.ts is the deliberate public facade for domain mutation modules. Existing hook imports remain stable. Personal-settings conflicts adopt the server current snapshot through adoptCurrentSettings, while each caller retains its non-conflict policy, expected revision and schema checks. Alert mutations share only query cancellation, exact snapshot rollback and invalidation; their transforms retain their distinct unread rules.

The runtime setting codeModeDefaultYieldTimeMs is labelled "Default exec observation time" (web/lib/i18n/dictionaries/en.ts:1978), and its bounds are in web/lib/runtimeLimits.ts:24.

Settings sections

SETTINGS_SECTIONS in web/modules/settings/categories.ts is the one list of Settings sections, in draw order: system, brain, models, plugins, plugins-available, access, dashboard, voice and data. plugins-available is the Available plugins registry section, and the assistant's own section takes its label from the configured assistant name. The deck's navigation and the mounted panels both read it, and settingsSectionHref builds the ?cat= address. Plugin-owned settings are contributed at runtime rather than listed there. A retired id resolves through SECTION_ALIASES (memory opens models), and any other unknown ?cat= value falls back to system.

Access section

Settings → Access (?cat=access, admin-only like the whole deck) is registered in SETTINGS_SECTIONS (web/modules/settings/categories.ts) and rendered by AccessSection (web/modules/settings/AccessSection.tsx). It reads only the plugin listing (usePlugins) and keeps every enabled plugin whose entry carries credentials (the daemon's credentialFieldKeys over the manifest configSchema, plus the vault's corrupt keys; seam catalog row 73). It never names a plugin.

Each plugin is one collapsed SettingsGroup with the plugin icon, display name and a PluginAccessBadge. The row fetches that plugin's detail, mounts the existing usePluginConfigDraft and renders PluginConfigEditor with mode="credentials", so secrets keep the write-only blur/Enter commit and companions autosave through PATCH /plugins/:name/config. pluginAccessStatus (web/modules/settings/PluginAccess.tsx) is the one status rule: reconnect when the vault reports a corrupt key, then missing when a required credential is empty, then connected when any credential holds a value, otherwise not set. A detail that fails to load shows ErrorState inside the row while the listing still reports reconnect. The listing has its own loading, error and empty states.

The fold is owned above the detail fetch, so a deep link survives the detail arriving. The link is accessHref(plugin), /settings?cat=access&row=access.<plugin>: the row's body carries data-row-id, and the existing useRowAnchor opens the fold around it, scrolls to it and consumes the parameter. Row anchors here are dynamic, so SETTINGS_ROW_SPECS.access is empty and the palette indexes only the section.

PluginConfigEditor drops the credential fields, and any section they leave empty, from every other mode. In setup and all it renders PluginAccessCard (status plus a ShellLink to accessHref). The plugin detail's setup checklist lists only non-credential required fields; its first tab is still Setup while credentials are missing or need a reconnect. Personal plugin settings carry no credentials and are unchanged. Because an Access row and a plugin's own form can hold drafts of the same plugin at once, usePluginConfigDraft adopts a newer cached revision while its draft is clean; a draft with an unsaved edit keeps its revision and gets the existing conflict choice.

Problem reporting Settings row

Settings → System renders the admin-only ProblemReportingRows in SystemSettingsSection. The shared SettingsRow holds the switch, a short delivery status and the full notice behind its help affordance. Enabling opens ConfirmDialog with the formal cs/sk/en explanation of reported fields, redaction, identifiable licence, recipient, 14-day remote retention and disable semantics. Saving still sends problemReportingConfirmation: true and the current configuration revision. Loading and error states use SettingsState beside the row. Settings has no sent-batch inspection action or drawer; diagnostic records remain owned by the existing reporting/logging subsystem.

The status and report schemas use the browser-mirror boundary in web/lib/problemReporting.ts. tests/contract/webMirrorContracts.test.ts pins the marked problem-report and problem-status schemas to their core sources, ignoring only export keywords. No daemon runtime import reaches Next.

SystemSettingsSection gives the instance group a Server icon and its application-name heading, with the existing check-updates badge and last-successful-update description. The service readings and update action share one wrapping strip inside that group. Each service name, port and state stays together; a narrow screen wraps whole entries and the button without a separate empty action row. Update mutations, pending-state disabling and the hero's restart confirmations retain their existing owners.

The web application is a Next.js App Router application in web/. It is rendered per request because authentication, branding, locale, skins, plugin availability, and navigation depend on current state. The root layout provides the shell, localization, branding, React Query, event handling, overlays, toasts, and advisor surfaces.

Marketplace update status

elowenClient.installPlugin, updatePlugin, repairPlugin and systemUpdate receive a queued acknowledgement { requestId, queued: true } (web/lib/elowenClient.ts:546, :688-699). This acknowledges a durable request, not installed files, enablement or successful startup. Install/update still answer consent 409 with the daemon's grant list; usePluginConsent replays the same operation with exactly those acknowledgeGrants (web/modules/settings/usePluginConsent.tsx:147-167). Candidate control dependency errors retain their existing human explanation (usePluginConsent.tsx:40-60). The same pluginChangeRefusalError mapper in usePluginConsent handles HTTP 409 update in progress with a request id for toggle and removal toasts, using cs/sk/en copy rather than raw updater text (usePluginConsent.tsx:42-44). No second error dialog or client-side mutation guard is introduced.

useSystemUpdateStatus in web/lib/queries.ts owns the single shared system-update query for admin GET /system/update (queries.ts:384-404). Its wire DTOs are re-exported by web/lib/types.ts from the core contract, covering the normalized queue, journal summary including coreRequest, and UpdateReport with run request ids and per-plugin outcomes. Accepted mutations seed a queued hint in that cache and invalidate only status, never optimistically install or enable a plugin (web/lib/mutations/system.ts:57-65). All cards and the System panel share the read; there is no request or poll per plugin. It polls once per second while the queue or journal exists, stops when settled or needing operator attention, and refreshes on focus (queries.ts:401-402). A new terminal report invalidates installed/detail/marketplace, configuration, command, plugin UI, conversation-link and system-version views (queries.ts:392-395). The shell's existing useElowenEvents stream cancels the outstanding system-update or memory-maintenance query before invalidating it on its core event and stream open. Plain invalidation reuses a pending first read with no cached data, so a pre-event idle answer could otherwise hide newly started work and leave active-only polling stopped. Cancellation retires that answer through React Query; the shared enabled observers immediately read fresh status, while disabled or absent observers do not fetch. No second event stream, idle polling, retry, or feature-local cache is introduced. Memory events still refresh the memory list and vitality through their existing keys.

src/shared/updateStatus.ts::pluginUpdateRecords(status, name, requestId?) selects the matching queue, journal and report records for both web and setup (src/shared/updateStatus.ts:5). It scans each retained list by plugin name and optional request id, returns undefined for absent records, and makes no completion decision. The web cannot import daemon runtime, so web/lib/updateRecords.ts carries the same body below its own type import, pinned byte-identical by tests/contract/webMirrorContracts.test.ts. web/lib/updateStatus.ts projects those records as queue, active journal and terminal outcomes in that order (pluginUpdateProgress), retaining request ids so an older result cannot stand in for a newer request. Applied, rolled back with detail, needs consent, not licensed, superseded, failed, skipped and operator attention are distinct. The same file owns which of those states a person must act on and the badge tone each reads in (updateStateTone: failed and operator attention in the danger tone, a run blocked before activation, consent and licence in the warning tone, everything else muted, with no success green). Only System renders UpdateStatusPanel (web/modules/settings/UpdateStatus.tsx), and only while updateStatusItems lists something: queued or running core and plugin work, or a failed, rolled-back, operator, consent or licence outcome. Its records are direct children of the card's field group; the aria-live region is a display: contents wrapper around the card. The card draws nothing while the first read is pending, so an idle instance never flashes a skeleton; a failed read shows the shared error state with retry. When the last run committed and nothing is listed, lastSuccessfulUpdateAt dates it and the System update record says so in its description (settings.lastUpdateSucceeded); with no report it says nothing. The Installed plugin list has no separate update-status card, even when idle. Each plugin row keeps its UpdateProgressBadge for queued, active and terminal states (UpdateStatus.tsx). An explicit update already satisfied by the unchanged installed receipt and approved pin reports applied, so the existing badge and setup waiter both accept it; downgrades remain failed. Update availability remains the marketplace catalog's comparison of the offered version with the installed manifest; badges remain the coordinator's recorded verdicts. Both refresh after a new terminal report. An existing incorrect report is replaced by the next real transaction, never rewritten or overridden by presentation code. Settings Plugins names its shared filter/action panel Actions and keeps the category picker and active chips (en.ts:446; web/modules/settings/PluginsSection.tsx:457). Update all plugins opens the shared restart confirmation, then sends one POST /plugins/marketplace/update (elowenClient.ts:695-696). The shared consent hook collects any required grants by plugin and retries the whole batch; a refusal queues nothing. useUpdateAllPlugins seeds all accepted operations in the same status cache, without a request or status query per plugin (web/lib/mutations/plugins.ts:74-80). The action is disabled while nothing is offered or a request/run is pending. Disabled plugins stay disabled. Rolled back, failed, needs-consent, not-licensed and superseded reasons appear below the plugin description through the same span and classes as the degraded reason; distinct update and load reasons are both retained, identical reasons appear once. Every progress badge carries its detail in its native title. No detail means no reason line or title. Run-level report badges never borrow an individual request id; plugin badges carry their own report outcome and request id. The committed run label is distinct from the applied plugin label in cs/sk/en. React Activity retains inactive settings panels, so browser assertions scope to the visible status group. A failed status read offers Retry, never a guessed success. Only the server's last report is retained; this surface is not an update-history store. An accepted request id absent from queue, active journal and report is terminal superseded, including ids removed by normalization or disable/uninstall. A different request's outcome is never success for it (web/lib/updateStatus.ts:38).

A receipted unloadable plugin remains Installed with degraded.installedVersion and degraded.reason, a visible load-failure explanation and Repair. Repair queues the same coordinator transaction. Degraded state blocks enabling, not disabling: an enabled degraded plugin remains switchable off when no active request makes it busy. Active requests disable duplicate install, update and repair controls. System Update now uses the same query rather than declaring completion from its POST acknowledgement. Every state has Czech, Slovak and English copy. web/tests/e2e/specs/settings.updates.e2e.ts drives queued install, applied proof, consent replay, rollback reason, degraded repair and System status at 320 and 1440 px in all three locales, including empty, delayed-loading and failed-status retry, with behavioural and containment assertions instead of pixel baselines. Its Czech capture matrix also checks the correlated HTTP 409 toggle refusal and retained enablement, then records the plugin list at both widths through the existing full-height design harness. Repair controls belong to the plugin list, not the System run-status panel.

Account notification bounds

AccountView consumes the same bounds as AlertStore and the self-settings schema through web/lib/alertPushPrefs.ts, the byte-identical mirror of elowen-plugin-shared/alertPushPrefs. Before autosave, its muted-source editor trims and clips entries, removes duplicates and empty entries, and caps the list. Both limits remain 100. Missing preferences retain warning/empty-list defaults; persistence remains revision checked and stored oversized lists still reset as a whole. No backend source runtime import or metadata request is required. See the neutral authority.

Shared limit arithmetic

web/lib/limitFormat.ts shares only pure limit arithmetic for BrainLimitsModal and RuntimeLimitsModal. clampLimitValue(displayValue, divisor, min, max) multiplies the editor's explicit numeric divisor, rounds to a canonical integer and clamps to the supplied bounds. limitHintNumbers(locale, divisor, decimals, min, max, defaultValue) formats min/max/default through Intl.NumberFormat in the explicit display unit and precision. The editors retain their own fields, order, icons, unit labels, bounds and default owners; no field registry or backend import is introduced. These helpers do no I/O and perform no save. Without an open editor they do nothing; auto-save flush, retry and close policy remain in the existing consumers.

Retry policy preview

BrainLimitsModal uses the existing LimitSliderRow children slot to show the policy scope, model/request delay preview and explicit always-on exception beside the retry controls. It calls retryDelayMs(baseDelayMs, retryNumber) from elowen-plugin-shared/retry for the current draft, without a copied formula, fabricated revision, request or retry episode. Three repeats at the default 2000 ms base display 2/4/8 seconds; zero shows one initial request with no repeat. The count excludes the original attempt. Paired-computer and platform connections are described as the unlimited exception, including maximum zero; compaction keeps its separate count. The preview excludes attempt durations and server minimum-delay hints, which may exceed the fixed 60-second exponential cap.

The retry delay label, accessible value and preview use Intl.NumberFormat with the active cs/sk/en locale and up to three fractional digits in seconds: 1501 ms remains 1.501 s in English and 1,501 s in Czech/Slovak. Edits still convert seconds to canonical integer milliseconds through clampLimitValue. Stored brain.limits.retryMaxRetries/retryBaseDelayMs, defaults 3/2000, ranges 0..10/1000..30000 ms and the existing autosave/CAS path are unchanged. BrainRuntimeSection seeds the draft from loaded config and useUpdateConfig sends the cached expected revision; a conflict adopts the server snapshot while leaving draft/error/retry ownership with the existing editor. Without loaded settings there is no preview draft. Calculation is bounded by ten delays and adds no I/O, timer, transport ownership or policy distribution. No new settings seam is introduced.

MCP server settings and OAuth

The bundled MCP settings page uses the plugin API through ElowenUiRuntime.api. GET /plugins/mcp/api/servers supplies server descriptions and authentication states (plugins/mcp/cliPicker.mjs:13; the same route is used by the browser page). Sign-in and sign-out POST paths use URL-encoded server names and the selected personal or instance ownership scope (plugins/mcp/cliPicker.mjs:69 for the sign-in path). Sign-in reserves a tab during the user gesture, removes its opener, and navigates it only after validating the returned authorizationUrl (plugins/mcp/web-src/McpServersPage.tsx:546-552). Returning focus to Elowen refreshes server state without discarding the editor draft. Opening the authorization page never implies authentication success. Without an authorization requirement, no sign-in action is shown. Stored SSE servers show an unsupported transport state and cannot connect; the editor requires an explicit switch to Streamable HTTP (plugins/mcp/elowen-plugin.json:89; plugins/mcp/docs/mcp-plugin.md:29).

Provider accounts (Settings → Elowen AI)

Settings → Elowen AI renders ONE providers group (BrainProvidersSection, web/modules/settings/BrainProvidersSection.tsx). A row is a logical provider — the identity that provider/model, grants, role pins and workflow pins name — and the accounts it may run on are listed INSIDE that row, never as a second list beside it. Renaming a provider or an account moves no id and touches no history; the id is chosen once, when the provider is created.

The single + opens the existing ActionMenu over shadcn DropdownMenu, listing every sign-in service, pasted-key service and the two custom endpoint types. Already-connected kinds remain selectable: this action creates a distinct logical provider using the catalog label and a unique counted id. Adding an account to an existing provider uses that provider's own menu. ProviderModal then collects required key/endpoint fields; OAuth kinds proceed through OAuthConnectDialog. No creation path asks for a name; renaming is the drawer's Name field. ProviderModal only creates: every field of an existing provider is edited in its drawer (below). Cancelling a key/endpoint draft makes no write. OAuth needs a provisional stored account, so cancelling or failing its first sign-in removes the newly provisioned provider through the same save mutation. The catalog is one bounded projection of the catalogs that already own the facts — providerKindCatalog() in src/shared/providerCatalog.ts, derived from OAUTH_BUILTIN/OAUTH_LABELS and keyAccountCatalog() — served as GET /brain/provider-kinds (src/api/routes/brainProviders.ts). A kind added to either source catalog appears in the menu with no frontend change; the browser adds only presentation (an icon slug per PI provider id and a translated label), never availability. The provider section consumes both the kind list and row artwork. Until the catalog loads, the group's + is disabled; a failed read offers a retry.

Every account edit is one wholesale PUT /config of brain.providers. The import-free src/shared/providerAccounts.ts and its byte-pinned web/lib/providerAccounts.ts mirror own stripProvider, takenConnectionIds, the edit DTOs and upsertProvider's typed merge/replace policies. web/lib/providerEdits.ts preserves its exports and selects replace for the browser; CLI setup selects merge so its omitted optional settings survive. Both keep account and provider order, zero values, secret absence versus null and the exact default connection id, without mutating inputs. Browser-only patchConnection, addConnection, removeConnection and patchRouting remain in providerEdits.ts. useSaveBrainProviders (web/lib/mutations/brain.ts) takes an EDIT of the list, not the list: it applies the edit to the cached config and its revision when the save runs, and its mutation scope (brain-providers) runs saves one after another, so autosaving controls that fire back to back never send a stale revision or undo each other. A 409 adopts the daemon's current snapshot through adoptConfigConflict (web/lib/mutations/system.ts, shared with useUpdateConfig), so the next save starts from it; the caller reports the failure through the existing toast. A connection's secret is never read back: the save sends an ABSENT apiKey to preserve the stored one and an explicit null to erase it, and apiKeySet (a read-only flag) never travels back. New ids use uniqueSlug(base, taken) from web/lib/nameGrammar.ts, the byte-identical browser mirror of src/shared/nameGrammar.ts pinned by webMirrorContracts.test.ts. It returns the unchanged preferred id when free, then the first free base-2, base-3, … without normalizing or reserving the result. Provider creation checks provider ids; account creation checks all connection ids across providers, not provider ids. The preferred account id remains the provider's id (the built-in provider id for a sign-in), chosen once and never derived again. ProviderModal keeps its distinct lowercase ASCII slug normalization and 32-character truncation; it does not adopt setup's basename, NFKD or fallback rules. Allocation only probes the set synchronously, with cost linear in the occupied suffix prefix.

Sign-in and key-account services are records of the Linked accounts group and API-key/endpoint providers records of the AI providers group below it; a record shows only its name, state and action icons (statistics and test), the model MarkStrip and the usage rail. A connected account states nothing; only paused and not-connected show their icon. With several accounts and routing off, each account is a record of its own, titled with the account's name. With routing on, the provider is ONE record whose rail is the daemon's combined entry routing:<provider id> of GET /brain/rate-limits/all (the browser never averages), and an icon toggle (brain.accountUsage, the shared Collapsible, closed by default) opens each account's name, state and rail, with no menu of its own (layout in docs/modules/web/models.md, "Provider statistics"). A record's kebab holds only Settings (opens the drawer), Add account, Delete account (on an account record of a provider with more than one account, so only with routing off) and Remove provider. The record itself opens the same drawer, in the Users and plugin card pattern: the surface's onClick for the pointer, and the name as the one real open control for the keyboard (a button inside the heading, named brain.settings: <record title>, with the focus ring). The icon actions, the kebab, the routed toggle, the connect buttons, the model marks and the state marks stop their click, so each keeps its own action.

The provider drawer (ProviderDrawer.tsx, tabs in ProviderDrawerTabs.tsx) is a WorkspaceDetailRail sectioned like the Users drawer: brand mark, name, a DetailMeta line with a status dot, the account count and routing on/off, a kebab with Remove provider, and Segmented variant="line" tabs. There is no edit mode and no footer: every control autosaves through one ProviderEdit ((entry) => entry) applied by useSaveBrainProviders, text fields on blur (an emptied required field returns to the stored value), switches on change and sliders on commit (Slider onCommit, each slider holding its committed position until the save settles). The section renders its account windows (KeyAccountDialog, OAuthConnectDialog, the disconnect/delete/remove confirmations) inside the drawer while it is open, so they open as centred nested windows.

  • General: Name; Models (a SelectionSummary of model marks whose Manage opens ProviderModelsModal, which unions the connected accounts' catalogs and saves one models list on the logical provider; for a custom OpenAI-compatible endpoint whose catalog fails or is empty it falls back to a one-id-per-line field); then one bordered group per account: its mark and name, an active switch writing connection.enabled (the same flag, never a second state: a paused account keeps its login, settings and history and receives no new work) and a kebab with sign in again or connect, edit connection (key accounts, KeyAccountDialog), disconnect (the OAuth route, account kept) and delete account (two or more accounts). Inside the group: the usage rail and the spend-limit slider (SpendLimitSlider, only where the daemon projects a spend window). A ghost Add account closes the tab.
  • Routing writes routing: the routing switch; the default account (only with routing off, the only case selectConnectionRoute reads it); the idle-rebalance switch; and, only with routing and rebalancing both on, the idle slider over the ladder 1, 5, 10, 15, 30, 45, 60, 90, 120, 180, 240, 360, 720, 1440, 2880, 4320 and 10080 minutes (a stored value off the ladder is inserted, so it survives opening and saving; readings in min, h or d) and the minimum gain slider (0-100 percentage points). Routing off names one account, nothing switches on its own, and no client ever picks an account by itself.
  • Connection: the provider id (read-only), the endpoint (editable for a custom endpoint, the catalog's for a pasted-key service), the key format with Auto for a key service with several formats, the wire API for a custom OpenAI-compatible endpoint, and the model catalog refresh (useModelCatalogRefreshItem).
  • Advanced: the temperature override and its slider (custom Anthropic, or OpenAI-compatible on Responses; on Chat Completions temperature stays in the compatibility editor), the compatibility editor (ProviderCompatibilityModal, opened from the row's button), and the hosted tool search, stateful responses and code mode switches, each shown only when the daemon's capability status reports it. Fast mode is an account preference, not a provider field.

The drawer replaced ProviderRoutingDialog, ProviderAccountAccessDialog, ProviderAccountSpendLimitDialog, the card's ToolExecutionControl popover and the edit mode of ProviderModal.

Sign-in consumes the daemon's account-keyed OAuth contract: GET /brain/oauth/status returns { connections: [{ providerId, connectionId, type, connected, generation }] }, an account is created in the config BEFORE POST /brain/oauth/start { providerId, connectionId, method? } runs (404 unknown account, 409 login already in progress), the model list comes from GET /brain/oauth/:providerId/:connectionId/catalog, and DELETE /brain/oauth/:providerId/:connectionId drops exactly one login. OAuthConnectDialog owns the start, poll and paste-back flows for one account; web/lib/providerAccounts.ts mirrors the wire type names of src/shared/oauthConnections.ts so the integration is an import swap.

The settings deck's own readings count ACCOUNTS, not providers (settingsMetrics, which takes the login list as an input), and the plugin provider picker (PluginConfigFieldPickers.tsx) offers a provider when at least one of its accounts can authenticate.

MCP OAuth return path

MCP OAuth returns to /p/mcp/oauth/callback on the trusted instance public origin (plugins/mcp/web-src/index.tsx:19; plugins/mcp/docs/mcp-plugin.md:35). The page posts { state, code?, error?, iss? } once to /api/plugins/mcp/api/oauth/callback through the existing authenticated browser proxy and immediately strips code and iss from its URL. The daemon validates iss as an optional string and forwards it unchanged to PI's RFC 9207 issuer check. A mismatched issuer, or a missing issuer when required, fails closed before token exchange. The daemon returns HTTP 200 { server } or a 4xx response with expired, invalid_state, denied or failed. Every known state is consumed on every outcome, including a wrong account. There is no GET callback and no daemon redirect. The only public route the plugin publishes for OAuth is the client metadata document at /hooks/mcp/oauth/client-metadata, used where the server needs a metadata-document client ID (plugins/mcp/elowen-plugin.json:37). The page shows loading and bounded failure states and returns to MCP after success; it does not automatically retry an ambiguous write. Authentication performs bounded network requests, not model inference; secret storage and cross-process refresh locking are described in External MCP bridge.