Elyra
Elyra The coding agent eTerm The terminal that knows where each command ends Huskarl AI colleagues on your own Mac Starf An activity monitor for Apple silicon that never invents a number etrans An SSH and SFTP client for macOS Elyra VM Virtual machines for macOS, Linux and Windows on your Mac Litr A small, native web browser for macOS Notr A notebook for macOS e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Refr Local-first PDF workspace for macOS Elyra Workspace A desktop workspace for coding agents Elyra SQL Server MySQL-compatible SQL server in Rust Elyra Félagi Agents as teammates on one board Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Development
Development
Release notes
Changelog
Elyra
Elyra Desktop changelog

Elyra Desktop changelog

All notable changes to Elyra Desktop. Format follows the reference project: sections under ## [Unreleased] for Breaking Changes, Added, Changed, Fixed, Removed.

[Unreleased]

[0.5.0] - 2026-10-01

MCP servers, built in: the servers you already set up for Claude Code, Cursor, VS Code or the terminal version are available to the agent through two stable tools, with OAuth logins shared with the terminal version and directTools for servers you want by name. No Node is needed. Project trust now works the terminal version's way, with four answers and the same shell commands; the system prompt names the project's own checks; and /bug writes a redacted report.

Breaking Changes

  • elyra trust with no arguments now trusts the current folder, as the terminal version's does (0.9.45). It used to show the status; that is elyra trust --status now. The earlier allow, status, list and revoke words still work.

Added

  • Extensions can register tools with { defer: true } and remove them with unregisterTool, as in the terminal version (0.9.45): a tool that appears or disappears mid-conversation waits for the next prompt-cache boundary (the cache expiring, a model switch, or applyPendingToolChanges()), so the cached prompt survives. Changing MCP tool lists use the same mechanism.
  • MCP servers, built in, as in the terminal version: the servers you already configured for Claude Code, Cursor, VS Code or Elyra (~/.claude.json, ~/.cursor/mcp.json, ~/.elyra/agent/mcp.json, and in trusted projects .mcp.json, .elyra/mcp.json, .cursor/mcp.json, .vscode/mcp.json) are available to the agent through two tools, mcp_search and mcp_call, so the tools block stays the same however many servers you have. Servers start only when a tool needs them, and tool lists are cached in the same file the terminal version uses. Local servers get a minimal environment, not your shell's API keys. Remote servers work over streamable HTTP and the older SSE transport. /mcp shows every server, skipped project files and config problems, and does refresh, enable, disable and reload; "mcp": false in settings turns it off. Servers that require OAuth log in with /mcp login <server>, and the agent offers the login itself when a call needs it; the login is shared with the terminal version, and /mcp logout forgets it. "directTools": true on a server also gives the agent each of its tools by name (mcp__<server>__<tool>). No Node is needed.
  • Four answers to "do you trust this project?", as in the terminal version (0.9.45): trust this folder, trust its parent folder (which covers every project in it), trust it for this session only, or continue without its code. The prompt lists exactly what would run. /trust offers the same choices when a project is restricted, /trust revoke takes trust away (and says so if a parent folder still covers the project), and /trust list shows every trusted folder.
  • Project trust from the shell, the terminal version's way: elyra trust [path], elyra trust --status, --list and --revoke [path], and --trust-project for a single run (for scripts and CI, where no prompt can be shown; unlike ELYRA_TRUST_PROJECT, commands the agent runs do not inherit it). elyra install -l now trusts the project, since adding a project package yourself is that decision, and elyra update, elyra list and elyra config say when they skip an untrusted project's packages instead of leaving them silently out.
  • Extensions can ask ctx.isProjectTrusted(), so an extension that runs project-supplied configuration can refuse while the folder is untrusted. When the agent writes a project extension in an untrusted folder, it is told the extension will not load until you run /trust.
  • /bug <description> writes a bug report, as in the terminal version (0.9.41): version, OS, model and thinking level, session counts, the last tool calls, loaded extensions and their load errors, and the last error if a turn failed. Keys, tokens and your home directory are taken out. The report is shown with a copy button and saved to a file; unlike the terminal version it does not open a GitHub issue, because the desktop has no public issue tracker.
  • The system prompt names the project's own checks, as the terminal version's does (0.9.38): the typecheck, lint, test and build commands found in package.json (with the lockfile's package manager), composer.json, Cargo.toml, pyproject.toml and go.mod are listed under the technology stack, so "verify your work" points at the exact commands. The Elyra prompt promised this and now keeps the promise.

Changed

  • Old tool output that ended in an error is no longer pruned from the context, as in the terminal version (0.9.38). It records what did not work; recovering it would mean repeating the failure.
  • systemPromptStyle: "reference" follows the terminal version as of 0.9.45: its "How to work" rules and its stack-relevant skill ranking (a lead-in and <relevant-to> for skills that name a detected framework). The prompt versions stamped in the routing ledger are now elyra-3 and reference-0.9.45, so turns before and after the change are compared separately.

[0.4.5] - 2026-09-30

A fix that should have been there from the first build: compacted sessions now keep their memory, because the model finally receives the summary, and !command output and messages from extensions reach it too. Opening an untrusted folder no longer installs its packages, and the desktop and the terminal version now share one list of trusted folders instead of erasing each other's. Plus /system, a tools block that survives switching tools on and off, and working defaults for Fireworks, Together and the Cloudflare gateway.

Added

  • /system <text> adds an instruction from this point on, as in the terminal version (0.9.41): "answer in Norwegian from now on", "tests must pass before committing". It is kept in the session, replayed when you resume, carried through compaction, and reaches the model wrapped in <system-reminder> tags that the system prompt explains as instructions rather than something you said. Because it is appended to the end of the conversation, it leaves the prompt cache intact. The transcript shows it as a system instruction. Extensions get the same through elyra.sendSystemMessage(text, { display, triggerTurn, deliverAs }). Sessions from the terminal version with such instructions now behave the same here. The Elyra prompt is version elyra-2 from here on, so the routing ledger does not average it with the previous text.

Changed

  • Switching tools on and off no longer throws away the prompt cache, as in the terminal version (0.9.41, 0.9.45). The tools block is the root of Anthropic's cache and part of OpenAI's hashed prefix, so removing a tool made the next request pay for everything again. A switched-off tool now stays in the request and the system prompt, and the model gets a clear refusal if it calls it anyway; a tool switched on is added at the end, so nothing already cached moves. stableToolSet: false sends only the active tools, as before. The context panel counts what is actually sent.
  • The model registry is synced again: 1379 models across 32 providers, including Claude Sonnet 5.5; 35 new, 25 retired, 65 with new prices or limits.

Fixed

  • Compacted sessions kept their memory, !command output reached the model, and so did messages from extensions — which, until now, none of them did. The session converted its history for the provider with the bare agent's default, which keeps only your messages, the model's replies and tool results and silently drops everything else. After a compaction the model therefore got neither the summary nor anything before it; the output of a !command you ran never reached it; branch summaries and every message an extension sent were lost. This had been so since the first build. The session now uses the coding agent's conversion, as the terminal version does, and there is a test for each of those cases that fails on the old one.
  • An untrusted folder no longer installs its packages. A project's .elyra/settings.json can list packages, and opening the folder resolved and installed them — a git clone of the repository's choosing, or an npm install whose lifecycle scripts run whenever npmIgnoreScripts is off. The trust gate covered extensions and shell settings but not this. Project packages, extensions, autoExtensions and autoSkills are now gated with the rest, the same set the terminal version gates since 0.9.45, and nothing is installed, updated or loaded for them until the folder is trusted.
  • The desktop and the terminal version agree on which folders are trusted. Both keep the list in ~/.elyra/agent/trusted-projects.json, and since the terminal version's own project trust (0.9.45) they wrote it in two different formats, so each quietly erased the other's decisions the next time it saved. The desktop now reads and writes the terminal version's format, still reads its own old one and converts it on the next change, and — like the terminal version — treats a trusted folder as covering everything below it. The file is written readable only by you. Revoking a project that a trusted parent still covers now says so, in /trust off and in elyra trust revoke, instead of claiming it worked. ELYRA_TRUST_PROJECT=1 trusts the project for one run, as in the terminal version.
  • Fireworks and Together have a default model again. Both retired the Kimi K2.6 models Elyra used as their defaults; they move to Kimi K3, as in the terminal version (0.9.44, 0.9.45).
  • The Cloudflare AI Gateway default is a model the gateway has. 0.4.4 copied the terminal version's claude-opus-5-5, but the gateway spells it claude-opus-5.5, so new sessions there had no valid default. A test now checks every default against the catalog.

[0.4.4] - 2026-09-27

The models moved on and the app now follows them: the registry is current again, with Claude Opus 5.5 and GPT-6 Sol as the new defaults, and it is regenerated from the terminal version rather than copied by hand.

Changed

  • Claude Opus 5.5 and GPT-6 Sol are the new defaults, as in the terminal version (0.9.42, 0.9.43). claude-opus-5-5 is now the default for anthropic, amazon-bedrock and cloudflare-ai-gateway, replacing Claude Sonnet 5, and the smart-routing powerful tier is pinned to it. gpt-6-sol is the default for openai, azure-openai-responses and github-copilot, replacing GPT-5.6 (and GPT-5.5 on Copilot); openai-codex stays on gpt-5.5 because Codex does not expose GPT-6 yet. Opus 5.5 costs $4/$20 per million tokens against Sonnet 5's $2/$10 (cache reads $0.20 on both, and cheaper than Opus 5), so expect up to double the cost per turn on the default and the powerful tier; GPT-6 Sol is half the price of GPT-5.6 at the same 1M context. Set defaultModel or pin smartRoutingModels.powerful to keep the old choice. Existing sessions and explicit settings are unaffected.
  • The model registry is current again. 1369 models across 32 providers, synced from the terminal version's generated registry: 129 new (Claude Opus 5.5 on Anthropic, Bedrock, Copilot, OpenRouter, Vercel and Cloudflare; GPT-6 Sol, Luna and their Pro and fast variants), 31 retired, and 160 with new prices or limits. Three new image models. scripts/sync-models.sh now does this from the reference checkout, so the embedded catalog is regenerated rather than copied by hand.

[0.4.3] - 2026-09-19

A decision model beside the coding model. TypeSafe Jev answers a typed question in about a second for about $0.00002, which is cheap enough to ask before spending a turn: is this command destructive, how much model does this instruction need, and — afterwards — did the run actually do what was asked. The last two turn guesses into measurements.

Added

  • A second, much cheaper mind for yes/no questions (decide, /jev). TypeSafe Jev is a decision model, not a chat model: give it a piece of content and typed questions and it answers with calibrated probabilities in about a second for about $0.00002 — it never writes prose. The decide tool asks it noul (yes/no with a probability), choice (one option plus the whole distribution) or score (an ordinal scale) questions, several at once for the price of one, so classifying, triaging or gating no longer costs a main-model turn. It works through TypeSafe directly (TYPESAFE_API_KEY) or through the OpenRouter key you already have, preferring the first when both are there; jev.backend, jev.model and jev.baseUrl override the choice, and the JEV_* environment variables from the terminal version still work. The tool only appears when a key is configured. /jev shows the active backend, both key states and the gate.
  • An optional second opinion before a destructive command (/jev gate on). With the gate on, every shell command is first sent to Jev with one question — would running this destroy data or make a change that is hard to reverse — and anything at or above jev.bashGateThreshold (0.7) goes through the same confirmation dialog the rule-based guard uses; with nobody to ask (print and RPC modes) it is not run. It is off by default because it adds about a second per command, and it fails open: no key or a failed call is reported and the command runs, because this is a second opinion and not a security boundary. Commands below the threshold pass in silence.
  • Smart routing stops guessing from keywords. The tier for a turn was decided by matching words like "refactor" and "architect", so "refactor the spelling of this one variable" bought the most expensive model and "the checkout flow drops every third order" bought the cheapest. With a Jev key and smart routing on, the instruction is classified by Jev instead — one call, about a second, about $0.00002, cached per instruction so it is not paid per turn — and the reason line says what it decided and how sure it was. The signals that read the turn rather than the message (a tool error, many edits, a huge result) are untouched and can still escalate: those are evidence, not opinion. If Jev cannot be reached the keyword rules take over for that run. jev.routing: false keeps the old behaviour.
  • The ledger can record whether the work got done, not just whether the call errored. Everything Elyra measures about a turn has been mechanical — stop reason, failed edits, retries, cost — so a model that cheerfully answers the wrong question scores perfectly, and comparing two system prompts compares their error rates. With jev.turnLabels on, each finished run is judged by Jev afterwards: completed, partial, blocked or failed, plus whether the final message claimed something works without the run containing anything that would show it. The verdicts land in the routing ledger and /routing-stats reports them per model and per system prompt, including what a completed run actually costs on each. It runs after the turn, so it costs no waiting, and it is off by default: everything else in the ledger stays on your machine, while a label sends the instruction, the final message and the list of tool names with their targets. File contents are never sent.

[0.4.2] - 2026-09-15

The machinery around the model was the strong part; what the model was shown and asked to do was still the reference's. This release is that other half: a prompt that says what the harness already knows, a turn that is checked before it counts as done, a context window that stops paying for answers from thirty turns ago, thinking spent where it changes the outcome, and a router that reads the measurements it has been writing all along.

Added

  • The model is told what the harness knows. The system prompt was the reference's: a role sentence, the tool list, and bullets ending in "be concise". Everything else the model had to infer — that reads are cheap in parallel, that a failed edit usually means the file changed after the read rather than that the text was wrong, that the project has its own type checker and test command, that why and history can be asked instead of guessing why a line looks odd, that a failing test is reported as failing. Elyra's own prompt says all of it in four short sections, plus tool notes that only appear when the tool is active, and it is now the default. systemPromptStyle: "reference" builds the old text byte for byte, and each prompt carries a version (elyra-1) that is stamped on every routing-ledger turn, so /routing-stats compares them on success rate, edit failures, retries per ten turns and cost per turn once both have been used. A prompt change is a measurement here, not a matter of taste.
  • The turn is checked before it is called done. A turn ended when the model decided it was finished, which is not the same as the code compiling; the only verification loop was /goal, and only for a command you typed. Now, after a turn that actually wrote files, Elyra runs the check the project already has — cargo check for a Cargo workspace, your own check or typecheck script with the right package manager, phpstan or psalm when the binary is there, go vet, mypy or ruff when pyproject.toml configures them — and hands a failure back to the model as work to finish, with the tail of the output. It runs once per instruction (verify.maxAttempts), never for a turn that only read code or wrote Markdown, never while a /goal is running, and never a formatter or a test suite unless you name one in verify.command. The transcript shows a loader while it runs and one line for the result. verify.enabled: false turns it off.
  • The same failure twice now says so. A failed tool result went back to the model exactly as the tool wrote it, so "the text to replace was not found" arrived for the third time with nothing to say it was the third time — and the usual outcome was the same edit retried with the same text until the turn ended. Failures are now counted per call (which tool, what it was about, and a fingerprint of the error that ignores line numbers and byte counts, so a repeat is recognised but a genuinely different error is not lumped in). The second identical failure carries one line naming the call and what to change — read the file again, check the command exists, check the path — and the third says to stop retrying and either solve it another way or say what is blocking it. A call that starts working forgets its history. Nothing is blocked and no arguments are rewritten: it is information the model was missing, not a new rule.
  • Old tool output stops being paid for every turn. A 50 KB file read on turn 3 used to be sent again, in full, on turn 40, and nothing dropped it until compaction rewrote the whole conversation. Tool results older than ten turns are now sent as one line that names the tool, what it was about and roughly how much output was dropped, so the model knows the answer existed and can run it again. Nothing is lost: the session file, the transcript and the archive keep the whole output, and the context panel marks those blocks pruned and shows what the saving is. The boundary moves in steps of ten turns rather than sliding every turn, because rewriting a message invalidates the prompt cache from that point on and a cache read costs a tenth of fresh input — one cache miss buys ten cheap turns. Short results and results carrying an image are left alone. contextPruning.enabled: false restores the old behaviour, and keepTurns, batchTurns and minBytes adjust the rule.
  • Skills say whether they are about this project. Twenty skills arrived as twenty names and descriptions in load order, and the model had to guess which of them had anything to do with the repository in front of it. Elyra now names the project — the frameworks it detects, the stack profile, and the languages its manifests are unambiguous about — and marks each skill that names one of them, listing those first. Matching is on whole words against the skill's name, description, compatibility and metadata, so go inside google is not Go. Nothing is hidden: a skill the detector cannot explain may still be the right one, and the prompt says so.
  • Thinking is spent where it matters, and the router finally reads its own measurements. Thinking was one switch for the whole session, so "what does this flag do" paid for the same extended reasoning as "refactor the payment module". It is now scaled per turn inside the level you set: a powerful turn gets all of it, an ordinary one a step less, a short question answered with read-only tools none. Your level is a ceiling that is never exceeded, off stays off, nothing is written to the session file, and /route shows what the next turn would use (thinkingAuto: false brings back the single switch). And the routing ledger Elyra has been writing after every turn — stop reasons, failed edits, retries, failovers — now decides something: inside a tier, models are ranked by what those turns actually measured before price is considered, so a cheap model that fails a third of its edits stops being the default pick. Five measured turns minimum, your pins still win, and when the measurements change the choice the reason says which model was passed over and why.

[0.4.1] - 2026-09-15

The provenance chain turned into tools you and the model can use, the doors that opening a folder used to open closed, and an app that writes down what happened when it goes wrong.

Added

  • Every push is checked now. The only workflow was the tag-triggered release, and it went straight to a build without running a test, so a tag could ship a tree that did not compile. CI runs the gates the contributing guide asks for on every push and pull request (format, clippy with warnings denied, the Rust tests, the frontend check, the frontend tests, the build, the extension-host tests and the version check), and the release waits for it.

  • A licence file, and credit where it is due. The project declared MIT in three manifests without shipping the licence text; it is there now, with both copyright holders, since this is a port of a jointly licensed project. THIRD-PARTY.md is generated from the lockfiles and lists 730 crates and the production npm packages with their licences, and the four palettes adapted from published themes (Catppuccin, One Dark, Palenight, Tokyo Night) are credited both there and in their own theme files.

  • Tests where a mistake would be expensive. The agent loop's turn decisions (a tool call answered before the next request, a terminating result ending the turn), the command-boundary error contract and the project-list header reader now have tests. The two largest files in the project had none at all.

  • One version, four files, one command. The version had to be edited by hand in four places and the extension host had quietly sat at 0.1.0 for four releases. scripts/check-versions.sh verifies they agree and --set writes them all; CI checks it and both release scripts refuse to build without it. The bundle configuration now names what is actually built instead of claiming "all".

  • What the context costs, and what to do about it. Every block in /context now carries its price at the model's input rate and at its cache-read rate, with the provider's own split for the last call beside it (new tokens, read from cache, written to cache). Below the bar: concrete suggestions with the money attached, such as a pinned file nothing has read in ten turns, tool results holding more than half the conversation, or compaction past 55 percent of the window. Cache hits are not attributed to single blocks, because prompt caching is prefix-based with at most four breakpoints; the totals are shown as the provider reports them.

  • Find, copy, and a cost per reply. cmd+f filters the session by words, matching what was said and what was run rather than tool output. Every message has a copy button, every code block has one in its corner, and a finished reply shows what it cost. A half-written prompt now survives a restart, kept per project, and the sidebar remembers whether it was open.

  • Errors say what to do about them. A missing API key, no model selected, a provider that is rate limiting, an extension host that will not start and a restricted project each render with an explanation and the one or two buttons that fix them: sign in, choose a model, reload extensions, trust the folder, try again, show the log. Anything unrecognised keeps the plain line it always had. And a crashed extension host that recovers now says so instead of blinking the tools out and back in silence.

  • A notification when a turn finishes and you are elsewhere. The plugin was a dependency that had never been called. Nothing is sent while the window has focus, permission is asked for once, and desktop.notifyOnFinish turns it off.

  • Long answers and long sessions stay light. While a reply streams, only the paragraph being written is re-rendered: everything above it is parsed once, when its paragraph closed, and an unfinished code block is plain text until its fence closes, so highlighting no longer runs on every token. Transcript items are skipped by the browser until they scroll into view, which is most of what virtualising the list would buy, without breaking find-in-page, select-all or copy.

  • Screen readers get the answer, not every keystroke. The transcript was one big polite live region around token-level updates, so a screen reader read each delta of each reply. It is now marked busy while a turn runs, and one short announcement per finished reply goes to a hidden status region.

  • cmd+c copies again. It was bound to "clear the editor" when nothing was selected, which threw away a draft, its attachments and its pasted content with no way back, and took over the system Copy shortcut while doing it. Clearing stays on ctrl+c, as in the terminal, and undo now puts the cleared draft back when the editor is empty.

  • The frontend has tests. pnpm test runs vitest over the transcript reducer and the keybinding grammar, the two places where a quiet mistake changes what every key does. The in-memory mock that already backs browser development is the fixture, so tests and manual runs exercise the same double.

  • There is a log now. The app wrote its diagnostics to stderr, which is nowhere when it is started from Finder, and the last two hotfixes were spent on failures a log file would have named in a minute. Events go to one file per day under ~/.elyra/agent/logs (five kept, the folder readable only by you), and to the terminal as well when there is one. Panics are logged with their location and a backtrace before the process dies. Errors inside the interface land in the same file and now say so with a toast instead of leaving a silently frozen panel, which is exactly how the 0.3.0 freeze presented itself. elyra doctor logs prints the path and the last lines, and /debug reports it too. The log holds Elyra's own events only, never prompts or tool output.

  • The stores survive a bad day. Rewriting a session (migration, fork, move) now goes through a temporary file and an atomic rename, so a crash mid-write can no longer truncate the session that everything else is derived from. A session index that cannot be opened is moved aside and rebuilt rather than failing every archive query from then on; the unreadable file is kept and named in the log. The background index refresh no longer holds the shared handle, so /archive stays responsive while a large archive is being indexed. And a failed prune of the intent board is logged instead of ignored.

  • Three smaller holes closed. Package installs now run with --ignore-scripts, because an npm postinstall is code from the package author that used to run the moment you installed an extension (npmIgnoreScripts turns it back on for packages that need to build a native binding). The webview's asset protocol was enabled with access to everything under your home directory and nothing used it, so it is off and out of the content security policy. And the credential store is created with owner-only permissions instead of being written first and locked down a moment later.

  • /share asks before uploading, and takes the secrets out. The command used to export the session and create a gist immediately, with whatever the tools had printed in it: .env contents, env output, bearer tokens, connection strings. Now the export is scanned first and anything unmistakably a credential is replaced with a label, and you approve the upload with the size, the entry count and a list of what was removed in front of you. The dialog is explicit that the gist is private but readable by anyone with the link, and that the same content stays on this machine in the session file, the archive index and any HTML export. The redaction is a net with holes, not a guarantee, and says so.

  • One question before the commands that do damage. A short list gets confirmed rather than just run: a recursive force-delete that leaves the project, sudo, a download piped into a shell, git push --force, git reset --hard, git clean -f, history rewrites, writing over a disk, chmod 777, publishing a release, changing what the machine runs later, appends into your home configuration. The dialog says which command and why, and a "no" goes back to the model as a reason so it can propose something narrower. Ordinary work is never interrupted, bashGuard: "off" turns it off, and headless hosts run everything as before. A command that arrives without a timeout is now killed after ten minutes (bashDefaultTimeout, 0 for none), so a hung process cannot hold a turn open forever.

  • Commands the model runs no longer see your keys. The whole process environment used to be handed to every command, so a single env put provider keys, GITHUB_TOKEN, AWS_SECRET_ACCESS_KEY and database URLs into the transcript, the session file and the archive index. Variables whose names read as secrets are now withheld from tool commands and from the extension host, while everything a build needs passes through, SSH_AUTH_SOCK included so git push keeps working. The bash tool tells the model that it cannot read them. Your own !command is untouched, shellEnvAllow and shellEnvDeny adjust the rule, and shellEnvPolicy: "inherit" restores the old behaviour. The read, write and edit tools also refuse the canonical credential files (Elyra's auth.json, SSH keys, AWS and gh credentials, .netrc, .npmrc, .git-credentials, Docker and Kubernetes configs); bash can still reach them, so this removes the accident rather than the ability.

  • Opening a folder no longer runs its code (trusted projects). A repository can carry .elyra/extensions (TypeScript with full Node access) and a .elyra/settings.json that sets how commands run: the shell binary, a prefix prepended to every bash command, the npm argv, extra extension paths. Cloning a repository and opening it was enough to run all of it. Now those four keys are ignored and no extension inside the project is loaded until you trust the folder, whichever way it was discovered, including through the package manager's resolution, which is the path the app itself uses. The app asks once when a folder actually carries something, /trust and /trust off change the decision later, elyra trust [allow|revoke|list] does the same in the terminal, and elyra doctor preflight says what is being withheld. Reading files, AGENTS.md, skills, prompts and themes are unaffected. Switching project also rebuilds the settings manager, so the new project's settings actually apply, which they did not before.

  • A file viewer, not an editor (/view <path>, a click in the explorer). The file as it is on disk, with layers you switch on: which turn wrote each line and what it was asked to do, which lines are uncommitted, and identifiers that jump to their definition. Selecting lines is the point: enter mentions @path:12-40 in the editor, "Ask why" sends the question, and "Change…" prefills the editor so the agent makes the change and the provenance chain stays whole. Writing from the window is deliberately absent, because a human write would bypass the mutation queue and leave lines behind with nothing to explain them; "Open in the system editor" is there for when you do want to type. Unlike the old blame-only panel it also works for files git does not track and for projects that are not repositories, and the explorer's @ button still mentions a file without opening it.

  • Parallel agents can see each other (intent board). Every agent appends what it just wrote to one board per repository, <agentDir>/worktrees/<repo>/intents.jsonl: the file, the symbols its changed lines sit in and their signatures. Each model call then carries a short list of what the other agents have touched in the last hour, and an edit into a symbol another agent is holding comes back with a note naming the agent, its branch and the symbol. A shared symbol whose signature the two changed differently is called out as the conflict a merge cannot reconcile. Worktrees of a repository share the board, agents in separate processes see each other without a server, and the worktrees panel shows the same rows as "Editing right now". It is advisory and never blocks an edit; intentBoard turns it off.

  • Who read the code, not just who wrote it (Elyra-Reviewed). Provenance says which prompt and model produced a line; a review record now says whether a person has signed off on it. Marking a commit reviewed writes an empty commit carrying one Elyra-Reviewed trailer per commit, with the repository's git identity as the reviewer, so the record lives in the repository and travels with a clone. The index and the working tree are left untouched, staged changes included. In the provenance panel every agent-written line whose commit nobody has reviewed carries a dot in the gutter, and the footer records a review of that commit; in the intent panel each instruction is marked reviewed, partly reviewed or not reviewed, with a button that covers all its commits. The header counts the share of agent-written lines with a recorded review. An LLM /review does not count: only an explicit human action does.

  • Changes by intent (/intent, /pr). A branch reviewed by what was asked for instead of by file: each group is one instruction, with the commits that answered it, the files and hunks they touched, the model, what the turns cost, the decisions recorded in that session and the test commands that ran green in those turns. Commits without an Elyra-Turns trailer are kept in their own group and named as written outside Elyra. /pr turns the same data into a pull-request description, generated from the commit trailers and the session archive with no model call, and the panel copies it.

  • edit says when the file changed after you read it. The read tool stamps every line with a content hash (#xxxx | ), and edit already stripped those prefixes from oldText. It now checks them first: a hash that is no longer anywhere in the file means the file changed after the read, a hash that does not match the text sent with it means the line was rewritten after copying, and hashes that are all still present mean an insertion or a move broke the block apart. The edit still fails and writes nothing, but the message says which of the three it is instead of repeating that the text must match exactly.

  • Two tools that read the provenance: why and history. why(path, line) blames the line and answers with the instruction behind the commit, the model that wrote it, what the turn cost and the decisions recorded for the file; where the session archive is missing the commit's own Elyra-Prompt digest still answers, and a commit made outside Elyra is reported as such. history(path | symbol) lists the earlier turns that read or wrote the file, newest first, with the instruction behind each. Both are in the default active tool set, so the model can check whether odd-looking code was deliberate before rewriting it.

  • Elyra-Prompt commit trailer. Up to 120 characters of the instruction behind the commit, so git blame reads back to a prompt on a machine that does not have the session archive: the turn ids alone resolve to nothing there. Prompt text therefore lands in the git history; gitPromptTrailer turns it off.

Fixed

  • A timed-out or cancelled command killed Elyra itself on Ubuntu 22.04. Ending a command's process group ran kill -9 -<pgid>, and procps-ng 3.3.17 -- the kill Jammy ships -- reads that form as a signal to the caller's own group: the app died, the command it meant to stop lived on. It is a killpg syscall now. Found by the new CI gate, whose runner is Ubuntu 22.04: the test binary, cargo and the runner's shell vanished together, four times, without a log.
  • The documentation said the wrong default theme. Two places claimed Tokyo Night; the default is elyra, with dark as the fallback.
  • Closing the window shuts the sessions down. The extension hosts were left to the process teardown and the archive never saw the session that had just ended, because the command that does this had no caller.
  • No turn id is dropped from a commit any more. Elyra-Turns listed at most 32 ids and abbreviated the rest as +N more, losing the provenance of every later turn for good. Long windows now write one trailer line per 16 ids, and the reader concatenates them (older abbreviated commits still parse).

Removed

  • Two commands nothing called. models_all and theme_current were registered and reachable without a single consumer; a way in that nobody maintains is worth less than the line it takes to declare it.

[0.4.0] - 2026-09-14

Seeing what the model actually gets, and deciding what it does not.

Added

  • Context inspector (/context, cmd+i). The context window as a stacked bar and a block list: system prompt by section (base instructions, AGENTS.md files, memory, decisions, skills), tool definitions, pinned files, and every conversation message with a token estimate next to the provider's last reported usage. Any conversation block can be excluded from the next model calls without deleting it: the exclusion is recorded in the session as a custom entry, tool results are replaced by a short stub so the call they answer stays valid, and the transcript marks excluded entries.

[0.3.2] - 2026-09-13

Fixed

  • Extensions never loaded from the installed app: "extension host exited before replying (restart)", five times. The host decides whether it was started as a script by comparing process.argv[1] with its own module URL, and URL.pathname keeps %20 for the space in Elyra Desktop.app while the argument has a real space; the check failed, main() never ran and the process exited silently with code 0. Real paths are compared now (also through the /tmp → /private/tmp symlink), with a regression test that launches the host from a directory with a space.
  • The error now says why. When the host exits before replying, the message carries the exit code and the last lines it printed, so a missing module or an old node is visible in the toast instead of a bare "(restart)".

[0.3.1] - 2026-09-13

A fix for the 0.3.0 freeze and for tools not being found when the app is started from Finder.

Fixed

  • The app froze after start in 0.3.0: "Loading sessions…" and "Reading project…" never finished, cmd+p did nothing and picking another folder had no effect. The file explorer's reload effect tracked the state it wrote (children, loading) and re-ran on its own writes until Svelte raised effect_update_depth_exceeded; that unhandled error stopped every later render in the app. The effect now depends on the project directory only.
  • Packaged app could not find npm or node. Launched from Finder or the Dock the process inherits the bare system PATH, so packages failed with "Failed to run npm root -g: No such file or directory" and the extension host could not start. At startup the app now asks the login shell for its PATH once, merges it in, and adds the usual tool directories (Homebrew, ~/.npm-global/bin, nvm, volta, bun, cargo) when they exist.

[0.3.0] - 2026-09-13

Knowing where code came from, and getting around a project faster. Every line the agent wrote can now be traced back to the prompt behind it; projects and files are reachable without the file picker.

Added

  • About panel. /about, or click the version in the title bar: the app mark, the version, the website (elyracode.com/elyra) and who made it (Wirelabs AS / Knut W. Horne). No repository link -- the repository is private.
  • File explorer on the right (cmd+1). The project tree with .gitignore honoured, listed one directory level at a time as folders are expanded so a large repository costs nothing until it is opened. Clicking a file appends @path to the editor, which is how the agent is pointed at it; each file also has the provenance button. Off by default, toggled with cmd+1 or the desktop.fileExplorer setting, and it remembers its state.
  • desktop.transcriptWidth. The transcript was capped at 880 px and centred, which left a wide gap on a large window. The cap is now a setting; 0 lets the transcript fill whatever the sidebar and explorer leave. The default is unchanged, because a fixed measure keeps prose and code readable.
  • Line provenance: "why is this line here?" The Files tab now has a history button on every pinned and changed file; it opens the file with a gutter attributing each line to the commit that last touched it and, for commits Elyra made, to the turn behind it -- the prompt, the model and what it cost. git blame --porcelain -C does the line tracking on demand (so a later edit above a line cannot invalidate the answer, and lines moved between files keep their history), the Elyra-Turns trailer maps the commit to a session and its turns, and the archive index maps those to the prompt. Commits written outside Elyra are shown as such rather than left blank.
  • Elyra-Turns: commit trailer, the first half of line-level provenance. Agent commits now also carry Elyra-Turns: <sessionId> <entryId>,<entryId>, so a line can be traced back to the turn that wrote it: git blame resolves the line to a commit, the trailer resolves the commit to a session and its turns, and the session archive resolves those to the prompt, model and cost. Errored turns are left out -- they changed nothing a line can descend from. Off with gitTurnsTrailer. Deliberately no stored line ranges: they are wrong as soon as anything edits the file above them, whereas git recomputes line ancestry on demand and follows moves and merges. Trailers rather than git notes because a trailer is part of the commit message and therefore survives rebase and push without per-clone notes.rewriteRef configuration.
  • Project switcher (cmd+p). Every folder with sessions on disk, most recently used first, with fuzzy search; Enter continues that folder's most recent session. ctrl+p pins a project to the top, stored in the desktop-only setting desktop.pinnedProjects. The list is derived from <agentDir>/sessions/ rather than kept as state, so folders only ever opened in the terminal version are there too, and folders that have been moved, deleted or live on an unmounted disk are shown as missing instead of failing to open. The exact path comes from each session's cwd header field, because encode_cwd maps both separators and literal dashes to - and cannot be decoded unambiguously.

Fixed

  • The transcript stopped following streaming output. onScroll turned auto-scroll off whenever the distance to the bottom exceeded 50 px, including for the scroll events scrollToBottom itself produced: those are delivered a frame later, by which time new tokens had grown scrollHeight, so the handler measured a large distance and disarmed the follow. Only wheel, touch and scrollbar interaction from the user disarms it now. The transcript also follows when the viewport shrinks -- a growing editor or an opening panel leaves scrollHeight untouched, so observing the content alone let the bottom slide out of view.

Changed

  • The mock backend is no longer shipped. It was dynamically imported and so stayed out of the main chunk, but it was still built into a 131 kB file the packaged app can never load. It is now reached through import.meta.env.DEV, which makes the branch statically dead when building. pnpm preview has no backend as a result.

[0.2.0] - 2026-09-13

Three things a terminal cannot do well: search every session you ever had, run several agents on one repository at once, and look symbols up instead of reading whole files.

Added

  • Session archive index (/archive, elyra archive). A derived SQLite/FTS5 cache at <agentDir>/index/sessions.sqlite over the JSONL sessions, refreshed incrementally at startup, on session close and when the panel opens. Full-text search across every session (project or all, per role), the history of one file ("every time we touched settings.rs"), and cost reports by day, model, session or project. The JSONL files stay the source of truth; delete the index and it is rebuilt.
  • Parallel agents in git worktrees (/worktrees). Start a second agent on its own branch (elyra/<name>) in a worktree under <agentDir>/worktrees/, switch the transcript between the live sessions, and merge the result back file by file: the changes are collected with a temporary index (the agent's staging area is left alone) and applied to the project tree with git apply --3way, uncommitted, for review. Files that also changed in the project are skipped unless you force a three-way merge.
  • Tree-sitter symbol index and the symbols tool. Rust, TypeScript, TSX, JavaScript, Python, Go, PHP and C files are parsed with tree-sitter and their definitions and references stored in <agentDir>/index/symbols/. The new symbols tool answers "where is X defined, who calls X" with file:line and signatures so the model reads only the relevant range; /symbols <name> and elyra symbols <name> expose the same index. read now outlines large Rust, Go, PHP and C files with it.

Changed

  • symbols is part of the default active tool set (read, bash, edit, write, symbols); the reference's four tools are unchanged otherwise.
  • elyra config and elyra doctor no longer defer to the desktop app.

Fixed

  • /update from 0.1.0 failed with [object Object]. Two things. The updater tarball carried a ._Elyra Desktop.app AppleDouble entry first (BSD tar writing the top-level directory's xattrs), and the updater strips the first path component of every entry -- so that one was unpacked over the extraction directory itself. The 0.1.1 tarball was rebuilt without Mac metadata and re-signed; release-macos.sh does that from now on. And the frontend printed the error object rather than its message, so nobody could see why.
  • Nothing asks GitHub any more. The update fallback and the session-side /update read https://elyracode.com/elyra/desktop/latest.json and send people to elyracode.com/elyra; the repository is private, and the old links 404ed for everyone but the author.

[0.1.1] - 2026-09-12

Added

  • Elyra theme, the new default. Deep navy surfaces (#0b0f1a, cards #111827), crisp 1px navy lines and an indigo accent (#7b6cff) with teal, green, amber and magenta beside it — built-in as elyra, alongside Tokyo Night and the six reference themes. Existing installs keep whatever theme they saved.

[0.1.0] - 2026-09-12

The first release. Free to use; downloads and updates come from https://elyracode.com/elyra, not GitHub.

Added

  • Desktop application built with Rust (Tauri 2) and Svelte 5, sharing ~/.elyra/agent/ with the terminal version.
  • Providers: Anthropic, OpenAI completions and responses, Google, with the full model catalog and cost tracking.
  • Tools: read, write, edit, bash, grep, find, ls, plus skill_write, extension_write and decision_record.
  • Sessions: JSONL v3 tree, fork, clone, tree navigation with branch summaries, rewind with git checkpoints.
  • Compaction (manual, threshold, overflow recovery, mid-turn), auto-retry, cross-provider failover, smart routing.
  • Themes: the six reference themes plus Tokyo Night (default), custom themes with hot reload, a legibility floor for text tokens.
  • Slash commands, keybindings with user overrides, prompt templates, skills, pinned files, cost and session statistics.
  • Providers (phase 2): Mistral, Azure OpenAI responses, OpenAI Codex responses (incl. WebSocket), Google Vertex (ADC), Amazon Bedrock (SigV4, event stream); OAuth logins for Anthropic, OpenAI Codex, GitHub Copilot, Google Antigravity, Gemini CLI and Cloud Code with a token refresher.
  • Goal loop with budget, /review (incl. cross-vendor), /bisect, /replay, /learn, HTML export with the reference templates, /share (secret gist), /doctor (preflight, model probes, routing), decisions, codebase memory, blueprints, snippets, routing preview and statistics, update check, native project picker.
  • Package manager (npm, git, local) with a package browser (catalog, install/remove, update check).
  • Extension host: Node sidecar loading TypeScript extensions over a JSONL protocol (docs/EXTENSION-HOST.md), the Rust ExtensionHostClient, extension tools, commands, UI dialogs, status lines and widgets in the desktop app, /reload re-discovering extension paths, extensions_status/extensions_reload commands and extension:log toasts.
  • Headless elyra CLI (crates/elyra-cli): print and RPC modes, install/remove/update/list, extension loading, used by extensions that shell out to elyra -p.
  • Image generation (openrouter-images) in elyra-ai, exposed to extensions through @elyracode/ai (generateImages, getImageModel, …) via the extension host.
  • elyra doctor resources and the resources check in /doctor: running Elyra instances with memory, age and child processes (extension hosts, language servers, browsers), flagging forgotten ones.
  • elyra config (list, enable, disable) and the /config panel for turning individual extensions, skills, prompts and themes on or off.
  • Self-update through the Tauri updater: /update downloads and installs a signed release and restarts; the startup banner offers Install when a release is installable.
  • "What's New" block on the first start after an upgrade (desktop.lastChangelogVersion, honours collapseChangelog).