Repository navigation
Fix desktop settings, local diagnostics, and built app validation - #396
Merged
Merged
Conversation
…n, not Electron's, in a dev launch A launch that hands Electron .vite/build/main.js directly loads no package.json, so app.getVersion() answered Electron's own version (39.8.10): the Version row showed it, and the GitHub fallback updater compared release tags against it, so a dev launch was never offered an update. utils/biorouterVersion.ts replaces that one answer with the package.json version Vite inlines at build time; main.ts adopts it into Electron at startup (so electron-updater's currentVersion agrees) and answers get-app-version, the About panel, the Help menu label, the daemon version match, the dependency report and both updater paths through it. Packaged builds keep what app.getVersion() reports.
…by their rows and each row is still announced once The per-chat switches in the Extensions, Skills and Knowledge lists had no accessible name, unlike every switch on the Extensions page. Each switch is now named by the row's visible name through aria-labelledby (the base's name, not the knowledge list's state-saying label). The wrapper keeps it out of the accessibility tree; the switch no longer carries its own aria-hidden, which would leave it with no name at all. Naming it exposed a Chromium quirk: a name built from an option's content read the hidden switch's name in again (a focusable button under aria-hidden, and the switch's ::after), so every row said its name twice. jsdom and Playwright's aria snapshot did not show it; Chromium's own accessibility tree did. The option is now named by reference to its title and note (or by its label), so nothing inside it reaches its name. The row click still goes through the switch. Tests query each switch by role and name in all three lists, and a Chromium test reads the real accessibility tree for one node per row.
…chine's own settings, not as a built-in server (#386) An app's agent.extensions names an extension and nothing else. Every name that was not a platform extension was armed as one of Biorouter's own servers, so an installed stdio extension the drafter's catalog offered (ucsfomopagent) failed on every connect with "Unknown builtin extension". A name that is not built in now resolves against the extensions configured on the machine running the app, with that machine's command and credentials, so the manifest stays portable. An entry the person turned off, a platform or frontend entry, and a name nothing is installed under are not armed, and the daemon log says which. The SDK reference says how to declare one.
The literal { omop_rules: OMOP_RULES, required_outputs: Object.keys(patient) }
keeps its values and builds, and the same literal across lines and the
local modules ./prevent and ./preventData are refused by line with nothing
written. The behaviour landed in a423670; this holds it to the reported
input rather than to a look-alike.
…he Extensions page's load-failure actions At 1440 the sticky '1 of 2 extensions loaded' report (1144 to 1424) covered the right 32px of the load-failure notice's Copy error and all of its dismiss mark. The stack has nowhere to go there that covers nothing (under the notice is the list's first switch, beside the actions is the error), so the page's actions step aside instead: a group marked data-toast-aside moves left while a toast stands beside it, out from under it and 16px clear, never closer than 8px to the title before it, as a translate (main.css 'toast clears the corner'), so nothing else moves or rewraps. toastClearance measures it against the stack's target box, gives it back when the toast goes or the watcher stops, follows scrolls, and never moves it from under a resting pointer. The notice lays its title, actions and error out in its own grid (the Note's slot cannot see the title), keeping the error wrapped before the actions and the note 60px tall. Tests: a Chromium case with the real notice in the real reading column, the real report and container (fails before: Copy error and the X under the toast), hover hold, 1048; unit cases for the step, its cap and the watcher; source pins.
…of the failure toast The live check of a671173 found the rest of the same defect: once the load-failure notice was dismissed, the sticky '1 of 2 extensions loaded' stood over the list's first switch, at 1440 and at 1048 wide. The page already says what the toast says, with the error and its actions, so while the notice is mounted it is the announcement: a failure recorded then is shown there and counted announced, no toast is raised for it, and a failure toast already standing (the grouped report or the single one) is taken down when the notice shows a failure. Progress and clean-run reports are left alone. Leaving the page does not re-announce a failure it showed. Tests: the reporting rules with a notice on screen and after it closes, the withdrawal of each failure toast (and only those), and the notice's presence and withdrawal on mount and on a failure recorded while it is open.
… an extension Crew ships with Biorouter (PLATFORM_EXTENSIONS["crew"]) and the daemon already classified it as a capability, but the desktop's CAPABILITIES list left it out, so it still showed on the Extensions page, in its count, in the Tools popover and in the load toast's denominator. It is now a capability row in Settings > Chat > Capabilities, labelled Crew, with the same switch: the row writes the same crew entry in config.yaml the Extensions page did, so a saved on or off carries over with no migration. A chat connected with /crew and a Crew task still get the tools from their grant, and only Revoke ends access. - capabilities.ts: add the crew entry (default on) and its InfoTip. - Tools popover: drop the Crew row lock (crewLocked, lockedByCrew, accessCopy.extensionLocked, isCrewExtensionName), dead once the row is gone. - /ext: palette: Crew is listed as a capability; its row reads "Crew tools (advanced)". - Rust: display name "Crew" for /ext: messages, crew in the capability name table and defaults test, and a crew_is_a_capability test. - CLI status line: count extensions with ExtensionConfig::is_capability instead of a stale six-key list that counted Crew and seven other capabilities. - Docs: Crew listed with the capabilities; landing/docs.html regenerated.
…uter shipped (diagnostics 20261006_2) The stock-copy list held two hashes that match no Meditation text ever committed, so startup never upgraded an install. A v1.85.2 copy kept requiring soul-writer, the skill v1.85.3 renamed to update-soul, and since v1.90.0 made workflow skills required the job failed every night. The list now holds the hash of every text that shipped, and a test reads each text back from tests/fixtures/meditation/ and hashes it. A pin on the current text's hash fails when the text changes without its outgoing hash being recorded. The reporter's exact bytes upgrade in both the library and the schedule's copy, with the job's bookkeeping kept.
…issing skill says what to do (diagnostics 20261006_2) A Meditation copy the person edited is not upgraded at startup, and named soul-writer, a skill startup deletes and nothing can install. The old name now resolves to the skill it became, unless a skill or bundle by that name is installed. A workflow skill that really is missing is refused with the next step: add it under Skills, or remove it from the workflow's skills.
…mpty chat (diagnostics 20261006_2) The required-skill check runs after a scheduled run makes its chat, so each night's Daily Meditation failure left one empty 'Scheduled job' row in the store, hidden from History. Setup after the chat is made (the bind, the workflow's skills and its own setup) now deletes that chat when it fails, and the run's error still stands.
…Meditation's cursor stays put (diagnostics 20261006_2) The agent ends a turn that did not do its work (the model call failed, the loop guard stopped it) with a 'Ran into this error' message and a TurnAborted event, and the stream then ends normally. The scheduler ignored the event and recorded a success: last_error cleared and last_run, Daily Meditation's Chat Recall cursor, moved past a day nobody digested. Found re-running the reporter's Daily Meditation once its skill check passed, against a local model that was not running. TurnAborted now fails the run with its code and message.
…or says nothing ran (diagnostics 20261006_2) The reporter's llama3.1:8b, in a chat with Code Execution on, answered with calls written as JSON, and the person read the JSON as the answer with no word that nothing had happened. - Two call objects in one reply (reply 11: workspace_read_conversation then workspace_read_panel) were neither a call nor an attempt, so the turn ended on the JSON. attempted_calls now reads the shapes recovery reads, a run or an array of objects, and Ollama's one corrective retry names every tool. - A reply that is nothing but calls nothing can run (reply 1 text_editor, reply 7 shell, reply 15 agent_drafter: tools that chat did not offer) gets one line after it, for the person only, saying nothing ran and what may help. The agent adds it, so it covers every provider; the model never sees it, and the call is never run from text. The reporter's exact replies are fixtures, and a unit test classifies each: nothing runs from text, 9 and 11 are asked about, 1, 7 and 15 are noticed, and the answers (3, 5, 13, 17, 19) are left alone.
…is sent once (diagnostics 20261006_2) Ollama sends nothing while it reads a prompt. On the reporter's Intel Mac (about 25 prompt tokens a second) the whole first prompt of about 37,000 tokens outlasts the 600-second bound, and the person read 'Request failed: error sending request for url (.../api/chat) (timeout)'. The provider already refuses to re-send a timeout, since the server is still reading the first copy, but the agent classed it as a network blip and sent the whole prompt again: twenty minutes for a sentence that names neither the bound nor the prompt. A timeout before the reply begins is now ProviderError::IdleTimeout, a sentence naming the seconds waited, the prompt's rough size in tokens and the setting to raise (OLLAMA_TIMEOUT, or a custom provider's timeout_seconds). It is classed as network for exit codes and wire frames, and the agent never retries it.
…the agent The reporter's exact replies, served from a mock Ollama with the usage the bundle measured, through the real provider: the cut first turn (164,005 bytes answered from 2,046 + 4 prompt tokens) is refused and the request asks for the window it needs; qwen2.5-coder at its 32,768 trained window refuses the reporter's prompt size; replies 9 and 11 are asked about once, naming the real tools; replies 1, 7 and 15 in the Code Execution chat are shown and never run; reply 7 with Developer offered is asked about; the answers are left alone; a first prompt slower than the idle bound names the setting and is sent once. Through the agent, reply 7 gets the line saying nothing ran, stored for the person only and kept out of the model's next request.
…ed about, a first-byte timeout names its setting (diagnostics 20261006_2)
…nder the band, with 24px of headroom [R-17 R-34]
Opening Settings at a section (the update notice's "update", "usage",
"project", even "privacy") left the first lines under the 44px band after
the wheel reached the top, on every tab, until Settings was reopened.
The ScrollArea viewport was not positioned, so absolutely positioned
content without a positioned box of its own (every InfoTip's sr-only
description) was placed against the overflow:hidden ROOT, past the
viewport's clip, and gave the root up to ~1,600px of overflow. A wheel
cannot scroll a hidden box; scrollIntoView can, so the deep link scrolled
the root as well, and nothing scrolled it back.
- main.css: `.br-scroll-viewport { position: relative }`, so that content
belongs to the viewport and the root has nothing to scroll.
- ui/scroll-area.tsx: the root is put back at 0 whenever anything scrolls
it; `data-scrolled` (the top fade's hook) counts a whole pixel, so a
sub-pixel rest at the top keeps no fade.
- `--page-headroom: 24px` and `.br-page-headroom`: one clear space under the
band for Settings (was py-5, 20px), the first-run setup page and the
provider page (were py-6), taller than the 10px fade.
Tests: settings/pageHeadroom.browser.test.tsx mounts the real SettingsView
and ProviderGuard first-run page in Chromium under main.css compiled by
Tailwind, wheels/flings back to the top on every tab and after deep links
at 1048x600 and 1440x900, and asserts scrollTop 0 (viewport and root),
data-scrolled false, no mask, and the first row >= 24px under the band.
Its instrument removes both halves of the fix and sees the defect return
(each half alone holds). Run against HEAD's code it fails 9/9 (20px
headroom; root scrolled 174/411px on landing). Unit tests for the root
guard and the threshold; settingsScrollFade.test.ts pins the token.
…nly notice streams as user-only (diagnostics 20261006_2) categorize_tool_requests rebuilt the message the client is shown live with default metadata. The notice that nothing ran was stored for the person only but streamed as visible to the model, so a client holding the live copy held the wrong one. The rebuilt message now carries the reply's own metadata; the agent replay of diagnostics 20261006_2 asserts the live notice is user-only.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Settings deep links could leave the first rows hidden beneath the top bar, extension failure toasts could cover the page's error actions, and the diagnostics report showed broken meditation runs and local models printing tool calls without running them. Exported apps also started a second service and could be reported successful despite a broken page. This PR fixes those paths, moves Crew into Settings > Chat > Capabilities, reuses the existing service where supported, and checks app artifacts by default.
Desktop behavior
Diagnostics 20261006_2
soul-writerrequirement resolves toupdate-soul, and missing-skill errors explain the remedy.OLLAMA_TIMEOUT(or a custom provider's timeout setting) and is sent once, including when response headers arrive before the body stalls. Provider and agent fixtures replay the reporter's replies; a raw TCP regression covers the body stall.Built apps and #386
agent.extensionsentry resolves an enabled installed extension using the running machine's configuration. Disabled, platform and frontend entries are not loaded. The manifest reference is documented.Installed extension behavior
This expands what a built app may arm to include extensions the person installed and enabled on the running machine. It uses that machine's command and credentials; the app does not supply them or override a disabled switch. Tool dispatch remains subject to the existing permission and privacy gates.
Validation
Native Electron validation passed 60 Settings deep-link/headroom cases, 54 real-window geometry cases, 8 first-run/provider setup cases and 4 Crew placement/persistence cases, across light/dark themes and actual OS window sizes. Native fullscreen and preview-driven window growth also passed. No viewport or device emulation was used. Brief 51–82ms sidebar commit costs remain measured on the development artifact with 10,854 seeded chats; the audit found no resize-triggered full-history sort/load.
On a fresh daemon, an HTTP response whose body stalled after headers produced actionable
OLLAMA_TIMEOUTguidance with exactly one request. The captured malformed Qwen call produced exactly one user-only notice in both streaming and persisted history, with no tool execution or corrective retry.Reporter-model replays used an isolated Ollama port and synthetic data. Qwen's bounded narrow replay recovered and executed a valid text call, but did not complete the clinical extraction task. Llama used native
/api/chat,num_ctx=131072,truncate:false, and the full prompt; it returned prose and call examples without executing tools. Its scheduled Meditation run migrated the legacy workflow, created a nonempty chat, cleared the previous error, and advanced the scheduler cursor, but did not ingest the soul. These model completion limits remain; ordinary prose is deliberately excluded from tool-call recovery.The focused app-authoring suite passed 386 tests (0 failed, 2 ignored), including per-operation service identity, exactly-once payload imports, page rejection and preservation of the previous bundle. The complete
just check-everythinggate passed again on clean headcbb6600f8574106a246836d89fa0209350163e4f, including Clippy, UI lint, OpenAPI regeneration and every consistency/registry guard. A reproduced UI fixture race was corrected by waiting for the visible dialog overlay before clicking outside; all six focused cases and three additional runs pass with the original assertions and timeouts. Independent source and live-evidence review is clear. The complete UI suite passed with exit 0: 1,069 files, 14,735 tests passed and 13 skipped; formatting also passed. All four local library suites passed: core 5,059 (3 ignored), MCP 1,849 (6 ignored), server 998 (1 ignored), and CLI 789. Diagnostics integration (11), privacy wiring (3), and self-test isolation (5) also passed. Every suite exited zero. All normal CI workflows are green on the exact head: Rust on Ubuntu/macOS/Windows, Frontend, Apps smoke, and commit-message check.Fresh final-head daemon checks passed 57/57 assertions with 46 saved HTTP/MCP responses, including hard-defect refusal despite a lint override, staged-output preservation and repair. Actual macOS exported launchers passed 30/30 service reuse and scoped-page assertions; full payload imports are covered by both generated launcher lifecycle fixtures. A bounded run of the tracked apps workflow exercised seven actual MCP subcalls through a deterministic provider fixture. Its separate browser-smoke phase was unavailable and is not claimed passed.
The previously deferred CLI error-turn exit status and the fixed prompt size on 32k-window models remain outside this change. No release is published.