Releases: levelcodeai/levelcode
Release list
LevelCode v1.2.0
LevelCode v1.2.0
Take a screenshot, ⌘V into the composer, ask why the layout is wrong. That is the whole feature, and most of this release is the work underneath it — a token meter that tells the truth about pixels, a store that keeps your screenshots on your own disk, and a gate that stops an image reaching a model that cannot see it. The transcript also stops narrating itself, and Sessions is one click from the chat tab.
Highlights
Paste a screenshot, ask about it
Three ways in, in the order you will actually use them:
- Paste.
⌘Va screenshot straight into the composer. No dialog, no upload step, no file to manage. - The image button in the composer, or AI: Attach Image to Chat from the palette. It offers the image tabs you already have open before it offers a file dialog — the picture you want is usually one you were just looking at.
- Drag a file from Finder onto the chat. This one needed a core patch; see below.
png, jpeg, gif and webp are accepted and nothing else — that is the list the vision APIs take, so a wider net would only fail later and further away. Each attachment becomes a chip under the composer with a thumbnail, its cost in tokens, and a remove button. Up to five images per message by default, and when there is more than one they are introduced to the model as Image 1:, Image 2: so that "the second screenshot" has something to refer to on this turn and every turn after it.
Your screenshots never leave your machine
Bytes are written beside the session that used them, content-addressed by SHA-256:
~/.levelcode/sessions/<project-slug>/media/<sha256>.png
The conversation, the session log and the token meter all carry a reference, never the bytes. That is not a size optimisation: listing your History re-parses every session file in a project whenever the index is missing or on an older schema, and inlined base64 would make drawing a list of session titles parse every screenshot in every session you have ever taken.
Nothing is uploaded. There is no bucket, no signed URL and no retention policy to read, because there is nothing on our side to retain — which is also the only shape that works for BYOK, where the editor talks to your provider directly and a detour through our infrastructure would contradict the promise that we are not in the middle.
Because the same screenshot pasted twice hashes to the same file, a re-paste after a failed send costs a hash and a stat rather than a second copy.
Media is swept, not orphaned. Sessions are append-only and deleting one writes a lifecycle event rather than removing the transcript, so "the images go away with the session" was never going to be true. A sweep runs when a session is sealed and removes media nothing refers to any more — with a seven-day age floor, because a plain unreferenced-means-delete rule would delete the images of the conversation you have open right now.
The context meter stopped lying about images
The estimator measured JSON.stringify(messages).length / 4, which is sound for text and catastrophic for an image. Base64 books about a third of its byte count as tokens, so a 1 MB screenshot read as roughly 333,000 tokens — larger than most context windows — for something that really costs about 4,800. Storing refs instead of bytes then swung it the other way and reported a ~1,800-token image as about 18.
Images are now counted by what they actually cost. Claude sees an image as 28×28 patches, so the price is ⌈w/28⌉ × ⌈h/28⌉ visual tokens, capped per model tier:
| Model tier | Long edge | Token cap |
|---|---|---|
| Claude 4.7 and later, including the 5 line | 2576 px | 4784 |
| Everything else | 1568 px | 1568 |
An unknown model falls to the standard tier and an unknown size assumes the cap, so the meter fails toward over-counting rather than under.
Worth being plain about the scope: today this only misreported the meter you look at. Compaction cuts on message count and goal boundaries and never reads a token number, so nothing was being silently evicted. It becomes a correctness bug the day anything automatic keys off that figure, which is why it is fixed now rather than later.
Screenshots are resized before they are sent
The long edge is capped at 2000 px in the webview before anything is stored or sent. An image already under the cap is passed through untouched, in its original format — re-encoding a screenshot of text only stacks compression artifacts on the thing that most needs to stay legible. One that is over gets a single resize and a single WebP pass at quality 0.92.
The cap is 2000 rather than the more obvious 1568 because the server caps the cost at 4784 tokens either way, so the extra pixels buy legibility on small editor text for tokens that were already being spent. Measured on a dense 4K editor screenshot — small text edge to edge, the worst case for re-encoding — 770 KB → 189 KB on the wire, and 4784 → 2952 tokens. Shots with more flat UI in them compress harder than that.
| Source | Sent as | Visual tokens |
|---|---|---|
| 4K screenshot 3840×2160 | 2000×1125 | 2952 |
| macOS retina window 3024×1964 | 2000×1299 | 3384 |
| 1080p screenshot 1920×1080 | unchanged | 2691 |
| Half-screen 1280×1440 | unchanged | 2392 |
Budget roughly 2,400–3,900 tokens per screenshot, and remember it rides along on every subsequent turn in that conversation.
Dropping a file on the chat needed a core patch
VS Code's workbench claims OS file drops before a webview iframe ever sees them, so a drag out of Finder arrived with an empty dataTransfer.files and the editor helpfully opened your screenshot in an image tab instead. The chat now handles the drop in editorDropTarget.ts, reads the paths, and hands them to the extension.
If you build from source, this is a core patch, not an extension change — a fresh vscode/ clone needs patches/levelcode-core.patch applied by bootstrap.sh before drag-and-drop works. Two things cost real time here and are written down in docs/IMAGES.md so they cost nobody else any: extension webview view types are rewritten with a mainThreadWebview- prefix before they reach the drop target, so matching the bare id makes the patch silently inert; and holding Shift takes a different code path entirely, which means "drag with Shift works" was never evidence that the patch worked.
An image only goes to a model that can actually see
The provider and the model must both declare vision. Reading only the model id meant a custom OpenAI-compatible endpoint returned true for any model whose name looked like a vision model, and images went to an endpoint nobody had said could read them.
Four providers declare it: Anthropic, OpenAI, OpenRouter and xAI. Ollama and custom endpoints deliberately do not — a custom endpoint that does serve a vision model needs vision: true on its registry entry, because the honest place to declare a provider's capabilities is the provider registry, not a per-user override. In gateway mode the check runs against the gateway's own model, so LevelCode Cloud gets images wherever the model supports them.
The composer refuses an attachment before you type anything, and re-checks at send — you can switch models between attaching a screenshot and pressing enter, and that used to produce a provider error instead of a sentence.
The activity group reads as text, not as a widget
The collapsed header said "3 steps". It now says what actually happened — which files were read, which command ran — because a count is the one thing you can already see. Context is announced once when it enters the conversation rather than re-stated every turn, the chevron trails the thing it discloses instead of leading it, and the group rows are inset inside a single container rather than nested in two with a rail down the side.
Sessions and Project Memory are one click from the chat
Both have a button on the chat tab, and AI: Project Memory is a command now. The row actions inside the Sessions panel — Rename, Done, Delete, Pin — were rendered but invisible, showing an empty grey box on hover; they render, and they are visible.
New settings
| Setting | Default | Description |
|---|---|---|
levelcode.ai.chat.maxImagesPerMessage |
5 |
How many images may be attached to one message. Clamped to 1–20 at the boundary — 20 is the API's own ceiling |
New commands
| Command | Does |
|---|---|
AI: Attach Image to Chat |
Offers open image tabs first, then a file dialog |
AI: Project Memory |
Opens the project's memory from anywhere |
Also fixed
- The context and review bars sat flush left instead of in the transcript column, because a
marginshorthand overwrote themargin-inline: autothat centred them. The guard that now prevents it reads every declaration rather than the first — the original check used.exec()without the global flag, so it inspected one rule and reported the file clean. - Every image thumbnail was broken. The chat's Content-Security-Policy declared
default-src 'none'with noimg-src, so the composer chip rendered as a broken-image glyph. - An image with no words returned a 400. Sending a screenshot with an empty composer produced an empty text block alongside it, which Anthropic rejects. Text-only turns also stay a plain string rather than becoming a single-element array, so cached prefixes do not churn.
- Agent mode dropped every pasted image. The send path stored the bytes and then called the agent with the text alone.
- Opening without a folder refused images outright, on the same guard that used to refuse everything else.
- The × on an image chip could not remove it, and the target was too small to hit reliably. Both fixed, and the cap moved into one setting rather than being written in two places.
- **A send can no longer outrun its own ...
LevelCode v1.1.0
LevelCode v1.1.0
The chat moves to the middle of the editor and starts reading like a document. This release takes the panel out of the narrow column on the right, gives the transcript a real measure and type scale, and lets the agent answer you even when no folder is open.
Highlights
The chat is an editor tab now
It opens centred, as a tab, like a file — and that is the only place it lives. Drag it to a split, move it between groups, pull it into a second window: it behaves like every other editor because it now is one.
The old right-hand chat view is gone rather than deprioritised. One conversation with two possible hosts needed a hand-over card, a detached state, a move command and a replay on every transition — machinery for a choice nobody wanted. Sessions keeps the right-hand panel to itself, which is the right place for an index of past conversations: it no longer splits a narrow column with the conversation it indexes.
Closing the tab closes the chat, and closing is an ending rather than a discard — the session is sealed into History and memory learns from it, exactly as New Chat has always done. ⇧⌘I opens it again.
If you don’t want it opening on its own, set levelcode.ai.chat.startLocation: "none".
The transcript reads like a document
Nothing constrained line length before this. In a 380px sidebar the container did that job, so it never looked wrong — but a chat in an editor tab at 900px produced 154-character lines, and no amount of good prose survives that.
- A bounded measure. The column caps at 820px and centres, matched against the Claude Code console rather than derived from print typography — the 45–75 character rule assumes prose without identifiers, file paths or fenced code.
- Prose gets its own type. Message bodies now read one step above the workbench UI size, with looser leading. Expressed as an offset rather than a fixed number, so it tracks the editor font instead of inverting against it if you have raised that for accessibility.
- Hierarchy you can see. The heading scale moves from
1.3 / 1.18 / 1.07— three levels inside a quarter of an em — to1.45 / 1.25 / 1.1, with more space above a heading than below it. - Rhythm that scales. Prose spacing is in
em, anchored to the prose size, so raising the type opens the page instead of tightening it. - Code blocks get room.
prepadding widens and its margins join the same rhythm.
The whole panel shares one column: the composer, the status row and the notice bars line up with the prose above them instead of spanning the full width beneath it.
Speakers are told apart by treatment, not by a label
YOU and LEVELCODE AI sat above every message restating what the shape of the message already said. Your turn is now a tinted bubble on the right that hugs its content — "Yes" is a short bubble, a pasted stack trace is a wide one — and the assistant's is unadorned prose on the left.
The labels are gone from the screen but kept in the accessibility tree: the bubble is a purely visual cue, so removing the element outright would leave a screen reader with an unattributed wall of text.
The agent answers without a folder open
Opening LevelCode without a workspace used to refuse every request outright — "Open a folder first." That guard was written for the file tools and placed where it failed the whole run, so a question that never needed a workspace died on it: what an error means, anything through an MCP server, a follow-up about the conversation itself.
The root now gates the tools that resolve a path against it, and nothing else. Rootless, list_files, read_file, search, edit_file, write_file, delete_file and run_command are withheld — a tool that is present but always fails is worse than one that is absent, because the model retries it — while update_plan, ask_user, use_skill and every MCP tool keep working. The model is told plainly why the file tools are missing, so it says so in a line instead of improvising about files it cannot see.
New settings
| Setting | Default | Description |
|---|---|---|
levelcode.ai.chat.startLocation |
editor |
Where the chat opens with a window. none stops it opening on its own |
levelcode.ai.chat.fontSize |
0 |
Prose size in px. 0 tracks the editor UI font, one step up for reading |
levelcode.ai.chat.proseWidth |
0 |
Transcript width in px. 0 uses the 820px measure — it does not mean unconstrained |
Both sizes are clamped at the boundary (8–24 and 320–2000). The minimum/maximum in a contribution schema only drive the settings editor; a hand-edited settings.json reaches the extension unchecked, and these land directly in CSS, where proseWidth: 1 is a one-pixel transcript with nothing left on screen to open settings with.
Also fixed
- Closing the chat no longer leaves the conversation loaded. Sealing the session ended it — so the next chat opened visually empty — while the in-memory history was still there and shipped to the model on the next message. The teardown now also stops in-flight work and reaps background commands and MCP servers, which are detached children and were outliving the surface that reported on them.
- A teardown cannot be raced by the run it is tearing down. Closing mid-stream aborts the request, and the abort landed in a handler that pushed the partial reply back into the history that had just been cleared — the exact leak the teardown exists to prevent, caused by the teardown's own abort.
- Reveals of the chat can no longer become unhandled promise rejections in the extension host. Failures are logged with the caller named rather than surfacing as an error attributed to nothing.
Not in this release
The Sessions panel still does not search. No filter, no fuzzy switcher, no keyboard jump — you scroll the list. It was the stated gap in v1.0.5 and it is still the gap; the chat surface took this cycle.
The empty-state wordmark had a false start. A redrawn mark shipped and had to be pulled: it was built from full block characters on the assumption they tile seamlessly in any monospace font, which is not true — whether █ fills its cell is a property of the font, and in Monaco it does not, so the logo shattered into disconnected bars. The replacement is drawn from box-drawing rules and real text, and was checked in seven font families before shipping this time.
Test coverage
- 34 suites, 575 cases across the bundled extensions — all green.
test/chatSurface.test.js(23 cases) — the single-surface contract: one live webview, one message handler, the transcript surviving a hand-over, and a close that seals rather than discards.test/webviewCss.test.js(33 cases) — the layout invariants no DOM test can see: the measure, the shell column, the type scale, and the wordmark's width against its container.test/agentNoWorkspace.test.js(6 cases) — which tools are withheld without a root, which must keep working, and that the context meter is billed for the list that was actually sent.
Full changelog: v1.0.5...v1.1.0
LevelCode v1.0.5
LevelCode v1.0.5
Your chats stop disappearing. This release turns every conversation into a session you can find, resume, fork and export — and gives each project a memory that carries what you did last time into the next chat. Both are plain files on your disk, both are readable and correctable by hand, and both treat what they learned as possibly stale, possibly poisoned rather than as gospel.
Highlights
Chats are sessions now
Every conversation is persisted as it happens — one append-only JSONL file per session, under ~/.levelcode/sessions/<project>/. New Chat no longer throws work away; it seals the session and starts a fresh one.
The Sessions panel (AI: Sessions, and a sidebar view) lists them in time buckets — Today, Yesterday, This week, Earlier — with the title, the file it touched most, and an activity sparkline. Click a card to resume it. On hover the second line swaps to the actions: Rename · Fork · Copy · Done · Delete · Pin.
Nothing is destroyed by accident. Done archives rather than deletes, Delete is a soft trash, and both offer Undo immediately afterwards. Sessions you stop touching auto-archive after 30 days (levelcode.ai.sessions.autoArchiveDays) — pinned ones never do.
Resume is honest about what it can carry
A long session may not fit the model's context window. Rather than silently truncating, LevelCode plans the resume in three tiers: if the whole transcript fits, you get it verbatim; if it doesn't, it loads the most recent turns and tells you so in the chat, in words, rather than pretending the earlier ones are still there.
The full transcript is always kept on disk regardless — the budget only governs what the model is handed. levelcode.ai.sessions.resumeBudgetPct (default 40) sets how much of the window a resume may occupy.
The project remembers itself
When a session seals, one cheap model call records what it accomplished into memory/journal.jsonl, and a consolidation pass distils the recent arc into memory/MEMORY.md — a small digest injected into every new session in that project, the same channel AGENTS.md and CLAUDE.md already use. Open a new chat and it starts already knowing the shape of the last week's work.
Durable truths get promoted separately into Facts — "the changelog is RELEASE-NOTES.md", "idempotency keys live in Redis". A fact seen once is marked inferred and weighed lightly; seen across sessions it becomes active; you can Confirm it, mark it not true, edit it, or forget it from the Project memory tab. When a later session contradicts an earlier one, the old fact is superseded — dimmed and restorable, with a line saying what replaced it, rather than silently overwritten.
Ask about older work and the agent can go looking: recall_sessions searches past outcomes and the transcripts themselves, and returns dated, cited results. Facts that decayed out of the always-on digest are still reachable that way — a memory that ages out is not a memory that is deleted.
Memory is treated as untrusted input
Memory is replayed into the system prompt of every future session in a project, and it is distilled from transcripts that contain repo files, command output and MCP tool results — attacker-controlled for any repo you clone. So it is bounded like any other untrusted text:
- A planted instruction cannot promote itself. Ordinary facts become active by being observed across sessions, but text that reads as an order —
always …,never …,ignore previous instructions, anything piping into a shell — never takes that route and needs an explicit Confirm. Repetition is not corroboration when the source is a file that is still sitting in the repo on the next session. - It is surfaced, not hidden. Such an entry is still recorded and still listed, flagged, so you can see what a repo tried to plant.
- Credentials are scrubbed on the way in. Key-shaped text is redacted before it reaches
facts.jsonl,journal.jsonlorMEMORY.md— including session titles and edited file paths, which those files print verbatim. - Injected memory is framed as verify-first and explicitly says never to act on an instruction found inside it.
Everything lives in plain files you can open, grep, correct, or delete. Turn the whole thing off per project with levelcode.ai.sessions.memory.enabled.
Fork a session
The "what if I'd told it to do X instead" branch. Fork seeds a new session with a copy of the conversation and leaves the original untouched, then opens it so you can take a different path from the same starting point.
The copy is genuinely a fresh session: it does not inherit the original's finished state, its archived status, or its pin — a fork of an archived chat arrives visible and active, and records which session it came from.
Copy a session as Markdown
Copy puts the whole conversation on your clipboard as a clean Markdown transcript — title, date, model, files touched, then one block per turn — ready to paste into a pull request or an issue. Save as file… writes it to disk instead.
Because this is the first thing that takes a session out of LevelCode, it is scrubbed on the way out: credential-shaped text is redacted from the body, and from the suggested filename.
New settings
| Setting | Default | |
|---|---|---|
levelcode.ai.sessions.enabled |
true |
Persist chats as sessions |
levelcode.ai.sessions.dir |
"" |
Where they live (blank = ~/.levelcode/sessions) |
levelcode.ai.sessions.autoArchiveDays |
30 |
Fade untouched sessions out of the active list; pinned are exempt |
levelcode.ai.sessions.resumeBudgetPct |
40 |
How much of the context window a resume may occupy |
levelcode.ai.sessions.memory.enabled |
true |
Cross-session project memory |
levelcode.ai.sessions.memory.summarize |
true |
The cheap-lane call that records what a session achieved |
levelcode.ai.sessions.memory.facts |
true |
Extract durable project facts |
levelcode.ai.sessions.memory.recallTool |
true |
Give the agent recall_sessions |
Not in this release
The Sessions panel does not search yet. There is no filter, no fuzzy switcher, and no keyboard jump — you scroll the list. That is the next thing being built, and it is the gap you will notice first once a project holds more than a screenful of sessions.
Test coverage
- 32 suites, 528 cases across the bundled extensions — all green.
test/memoryPoisoning.test.js(39 cases) — the adversarial pass: hostile inputs that must never become load-bearing memory, benign project facts that must keep working, credential shapes that must never reach disk, and the near-misses (git SHAs, content hashes, asset names) that must survive untouched.test/sessions.test.js(25 cases) — the lifecycle against a real store in a temp directory: persistence, verbatim rebuild, seal, resume, and the fork rules.test/sessionStore.test.js,sessionEvents.test.js,sessionResume.test.js,sessionMemory.test.js— the pure engine underneath, each testable without the editor.
Full changelog: v1.0.4...v1.0.5
LevelCode v1.0.4
LevelCode v1.0.4
The big one: the agent can now use external tools over MCP — a filesystem server, GitHub, Postgres, an internal company server — alongside its own built-in tools, with a security model that treats an MCP server as exactly what it is: arbitrary code that runs with your privileges. Plus autopilot now honors a changed step limit live, and the agent's activity timeline reads as one clean thread.
Highlights
Bring your own tools, over MCP
Point LevelCode at any Model Context Protocol server and its tools join the agent's own — namespaced server__tool, usable by every model LevelCode supports, with no code to write. This first release speaks stdio (a server you run as a local subprocess), which covers the overwhelming majority of servers and sidesteps the remote-transport spec churn landing this year; remote/HTTP servers come later.
You declare a server in one of two places, and they are trusted differently on purpose:
levelcode.ai.mcp.servers(your own settings) — you typed it, so it starts..levelcode/mcp.json(committed in a repo) — read and listed, but it never starts on its own.
Security is the feature, not a footnote
Spawning an MCP server is at least as dangerous as running a shell command, so every edge is gated:
- A launch gate for repo servers. A
.levelcode/mcp.jsonserver asks before it ever runs, showing you the literal command, arguments, and environment — no summarizing. Approve once and it's remembered, but trust is bound to a SHA-256 fingerprint of exactly what would run: change the command, an argument, or an env var and it asks again — so a repo can't getnpx …server-filesystemapproved and then quietly swap incurl … | shunder the same name. With no window to ask in (a headless or test context) it fails closed and simply doesn't start. - Every tool call asks by default, showing server · tool · arguments before it runs.
- Autopilot doesn't relax this. MCP tools are third-party code, so autopilot still prompts for them — the only thing that grants a silent run is your own per-tool allow-list (
"github__list_issues": "allow"). A server's own "this is safe" hint grants nothing on its own; its "this is destructive" hint forces a prompt regardless.
Manage MCP servers without touching JSON
A new AI: Manage MCP Servers… command (and a link at the foot of /mcp) lets you:
- Add or remove a server through a prompt instead of hand-editing settings — it writes your global settings only, never a repo's, so adding one can never weaken the launch gate above. The arguments box takes a real command line and splits it quote-aware, so
-y @modelcontextprotocol/server-filesystem "/Users/me/My Documents"stays one path. - Revoke trust you granted a repo server, per server or workspace-wide — the missing other half of "trust on first use," which used to be a write-once decision.
- See a stale approval for what it is — a server whose command changed since you approved it now reads
command changed — needs approval, not a bland "not started," because that gap is exactly the attack the launch gate exists to stop.
See what's configured with /mcp
/mcp lists every configured server — running or not — with its state, where it came from, its exact command, and (once live) its tools and whether each is allow-listed. It answers the two questions you actually have — why isn't my server being used? and what is this repo asking to run? — that a list of only-running servers can't. The context-usage popover now also breaks out an MCP tools line, so the standing per-turn cost of a chatty server is no longer invisible.
A cleaner agent timeline
The activity thread under each run — its tool calls, approvals, and commands — now reads as one connected line instead of disconnected stubs: the connector rail threads continuously through consecutive steps (only a stretch of narration breaks it), the way GitHub, Cursor, and Claude Code draw theirs. And an approved MCP tool call is now a single row — not an Approved · server · tool chip plus a separate 🔌 server · tool node — so it's one row per action, matching how allow-listed calls already looked.
Autopilot honors a changed step limit, live
Raising Maximum tool-use steps mid-run now takes effect on the very next step, instead of being frozen at the value from when the goal started — so bumping it from 25 to 1000 when autopilot pauses at "step limit" actually lets it keep going.
Test coverage
- 27 suites across the bundled extensions, all green.
- The MCP core is pure, testable modules by design: config merge + tool-name namespacing + the approval policy (
mcpConfig), the launch fingerprint and trust logic, and the/mcp+ manage-servers row builders are all unit-tested without spawning a process or opening the editor — in the same two-corpus style the command-danger classifier uses, where the test banner states the load-bearing direction. - The webview timeline changes are pinned by the chat UI's static-CSS-invariant suite (
webviewCss) — e.g. the rail's connector offset can't silently drift from the row gap, and the one-row MCP fold stays wired end to end (agent.js→ approval chip → run-node).
Full changelog: v1.0.3...v1.0.4
LevelCode v1.0.3
LevelCode v1.0.3
A design release for the one screen everyone opens: the signed-in account panel is rebuilt in the levelcode.ai "classic" look — a floating-chevron hero, a model-agnostic plan pill, a Documentation link, and a primary Manage account button — followed by the fixes that make it fit and close cleanly.
Highlights
A redesigned account panel
The signed-in panel used to be a plain card: an initials avatar, a model name, a credits figure, and a paragraph explaining "gateway mode." It's now the levelcode.ai classic (atom.io) surface end to end — the same light / One Dark palette, system type, and tabular numerals as the rest of the product.
- A floating-chevron hero. The three rising chevrons and drifting "portal" circles from the landing page open the panel in place of the initials block — brand-forward and quietly in motion (
portalDrift, 7s, staggered per layer), and perfectly still for anyone who setsprefers-reduced-motion. - Model-agnostic. The pill reads
plan · Pro+on the gateway, oryour own keyon BYOK — never a specific model name (those churn, and naming one confused people about what they were paying for). The credits number is gone from here; it lives on the account page, where there's room to explain it. - The gateway explainer is gone. That "how the gateway works" paragraph wasn't telling you anything the routing chip in the footer doesn't. BYOK keeps a single line — that your keys never leave your machine — because there it's genuinely reassuring.
- A Documentation link (→
levelcode.ai/ai/docs) sits in the footer beside the "Synced · settings · skills · keymaps" note. It's keyboard-operable, and like every external link in the panel it can only ever open a fixed, known destination — never a URL handed in from the page. - Manage account is the primary button — the one you're most likely to want — except on the free tier, where the accent belongs to Upgrade to Pro so two buttons never compete for it.
Fit and finish
The redesign shipped with three follow-ups from actually living with it:
- The chevron no longer clips. It's smaller and top-anchored, so the hero is fully contained at every window size instead of being cut off along the top edge.
- The × closes again. The full-bleed hero was painting over the close button and swallowing the click; it's back on top (
z-indexabove the hero, which no longer intercepts pointer events) and closes the panel as expected — by keyboard too. - High-contrast themes defer to the editor's own tokens. The card maps its colors to
--vscode-contrastBorderand friends under a high-contrast theme, so your VS Code accessibility choice wins instead of the card hard-coding its palette.
Test coverage
- 24 suites across the bundled extensions, all green.
- This is a webview change, so it's verified the way the picker and plan-card work were — by parsing the shipped
chat.html(thewebviewCsssuite and friends) rather than by snapshot.
Full changelog: v1.0.2...v1.0.3
LevelCode v1.0.2
LevelCode v1.0.2
A UI-polish release. The model picker is name-forward instead of a wall of detail, a finished plan folds away on its own, and the footer shed a redundant chip.
Highlights
A cleaner model picker
Picking a model used to mean reading three technical fields per row — the raw id (anthropic/claude-opus-5), the context size, and a 6.67× credits · 1000K ctx · ≈103 turns left second line. Handy for debugging, noise for choosing. Each model is now a single, name-forward line: just the name, an active check, and a lock on "coming soon" models — the way Cursor and Claude Code present them.
Your plan and remaining credits still sit in the picker's header, so the number that matters isn't lost; only the per-row debug detail is gone. Type to filter by model name.
A finished plan cleans up after itself
When the agent works from a checklist, a completed plan used to sit fully expanded in the sticky bar — a large part of the panel — long after the run had ended. Now, matching how Claude Code and Cursor handle a finished plan:
- It auto-collapses to its green header the moment every item is done: still there, still one click to expand, just no longer hogging the view.
- A dismiss (×) closes it outright — and also clears a plan left stranded by a run that was stopped or errored. It's keyboard-operable (Tab to it, Enter / Space to close), like the other controls in the plan bar.
A slimmer footer
Dropped the redundant home-icon "LevelCode Cloud" chip from the status bar. The mode-and-plan chip on the right (Gateway · Pro+, BYO key, Direct · no key) already tells you where your requests go.
Test coverage
- 24 suites across the bundled extensions, all green on every release.
- These are webview / picker UI changes, so they're verified by parsing the shipped
chat.html— every script block, plus thewebviewCssandcreditFormatsuites that read it — rather than by snapshot.
Full changelog: v1.0.1...v1.0.2
LevelCode v1.0.1
LevelCode v1.0.1
A reliability patch. A transient upstream hiccup now retries and recovers instead of killing your run, and when something does fail you get an honest, readable message rather than a wall of proxy HTML mislabeled "OpenAI".
Highlights
Runs survive a transient upstream blip
When the model gateway briefly can't reach a healthy backend it returns a 502 / 503 / 504 — a momentary hiccup that used to end the whole run. LevelCode now retries once, before anything has streamed, so the common case (the backend is fine a second later) just recovers and your turn carries on. You'll see a brief upstream busy — retrying… instead of a dead run.
It's deliberate about when it retries: only a 502, 503, or 504, and only before any output has appeared — so a retry can never duplicate text or double-charge. It does not retry a rate-limit (429), a 500, any other 4xx, or an aborted request, and a 401 still refreshes your session as before. Hitting Stop during the wait stays instant.
Honest error messages
A gateway failure used to land in the chat verbatim, like this:
OpenAI API 502: <html><head><title>502 Bad Gateway</title></head><body>…
Two things were wrong: it was labelled OpenAI even for an Anthropic model on your LevelCode Cloud plan, and it pasted the raw nginx error page into the transcript. The same failure now reads:
LevelCode Cloud API 502: Bad Gateway
The route is named correctly — LevelCode Cloud, OpenRouter, or whichever provider actually handled it — and the body is parsed for a real message. An HTML proxy page carries none, so it falls back to the plain status reason instead of being dumped in.
Under the hood
- The repository's
LICENSEis now recognised as MIT by GitHub. The MIT text is kept verbatim so the detector matches it, the Code-OSS provenance + trademark notice moved to a dedicatedNOTICEfile, and the in-app license link points at a branch that exists (HEAD) rather than amainthat never did (it had been a 404).
Test coverage
- 24 suites across the bundled extensions, all green on every release.
providers.test.jsgrew to 28 cases: the error sanitiser (a real nginx 502 page collapses toBad Gateway, a JSON{error:{message}}is preserved, the label names the route — not the adapter) and the retry (recovers on a 502-then-200, gives up cleanly after one try, never retries a 4xx or an abort). The retry is also exercised end-to-end against a stubbed stream, so the whole router → adapter path is covered, not just the helper.
Full changelog: v1.0.0...v1.0.1
LevelCode v1.0.0
LevelCode v1.0.0
LevelCode reaches 1.0 — running Anthropic's frontier model. Claude Opus 5 is now a first-class pick: in your roster if you're on a LevelCode Cloud plan, or in the OpenRouter list if you bring your own key — wired at its full 1M-token context and priced exactly where Opus 4.8 sat.
The 0.9 line built the parts — the agent loop, apply-then-review edits, the browser that opens itself, credits instead of dollars. 1.0 is that foundation, stable, running on Opus 5.
Highlights
Claude Opus 5
Opus 5 is Anthropic's newest frontier model, and LevelCode reaches it two ways:
- On a LevelCode Cloud plan, it's already in your roster. Models come from the server, so Opus 5 shows up without updating the editor, metered in credits like everything else — roughly 195 Opus 5 turns on the top plan's 10,000 credits.
- Bring your own OpenRouter key and it's in the picker.
Claude Opus 5 — Anthropic · frontier, 1M · via OpenRouter, near the top of the OpenRouter list. Your key, billed by OpenRouter; the editor shows a cost estimate rather than a credit balance.
Two things it gets right that a bare model id wouldn't:
- The full 1M-token window. LevelCode's context heuristic assumes 200K for any
claude-model; left at that, the in-run context meter would warn "full" at a fifth of Opus 5's real budget. Opus 5 has an explicit 1,000,000-token caps row, so the meter reflects the window you actually have. - Honest pricing, whichever slug you type. Opus 5 costs what Opus 4.8 did — $5 / M in, $25 / M out, cached reads at $0.50 / M — so the OpenRouter "powerful" tier moved to the newer model for the same money, and prompt caching is on so repeated context bills at the cached rate. The fast variant is double ($10 / $50 per M); its price is pinned explicitly so the estimate can't quietly under-report it by half.
Your whole roster on the first open
Opening LevelCode with a briefly-expired Cloud token used to collapse the model picker to a two-model fallback — you'd see two models on a plan that offers the full lineup, and only a reopen fixed it. The roster fetch now refreshes the token and retries, and if anything still fails it keeps the last-known-good list rather than discarding it. You get your full roster on the first try.
Under the hood
- 1M context, measured — not guessed. Opus 4.8 on OpenRouter is also a 1M-window route, so its caps row was split by route: the OpenRouter slug reports 1M, the bare Anthropic id stays at 200K. We deliberately did not raise the native path to 1M — this editor sends no
anthropic-betaheader there, so the long window isn't confirmed reachable, and promising room the API would refuse is the worse failure. Each route reports its own truth. - The "powerful" OpenRouter tier now names a model the registry knows. The old Opus 4.8 slug never was in it, so a per-node model override pointing there failed validation and fell back silently; moving the tier to Opus 5 fixes that as a side effect.
- Release builds moved off the deprecated Node 20 runtime. GitHub had begun force-running the release actions on Node 24 with a per-step warning; the action pins now target Node 24 natively.
Test coverage
- 24 suites across the bundled extensions, all green on every release.
catalog.test.js(13 cases) pins both sides of the 1M split — the OpenRouter slug at 1,000,000, the bare id at 200,000 — plus the heuristic default for an unlisted Claude model, so the context meter can't silently regress.sketch.test.js(16 cases) pins Opus 5's pricing, including the fast variant the family fallback would otherwise have billed at half.
Full changelog: v0.9.2...v1.0.0
LevelCode v0.9.2
LevelCode v0.9.2
A small, practical release: your site opens itself in the editor while the agent builds it, and what a run costs is now measured in credits rather than dollars.
Highlights
The browser opens itself
LevelCode has always shipped a built-in browser — but you had to know the command to find it, so most people never did. Now it appears on its own: the moment the agent starts a web server in the background, the site opens beside the chat.
- It updates as the agent works. Edits land on disk immediately (that's the apply-then-review model — Keep/Undo comes after), so your dev server's watcher fires HMR and the preview refreshes before you click Keep. Ask for a page, watch it appear.
- It never steals your focus. A server coming up mid-run doesn't yank the caret away from whatever you're typing.
- Closing it means closed. Each address opens at most once per session, so a chatty server can't reopen the tab you just dismissed, and a restart-on-save server can't stack one tab per reload.
- Only local addresses, ever. The address is read from the dev server's own output — which is whatever a project's start script chose to print. So only
localhost,127.0.0.1, the IPv6 loopback[::1], and the bind addresses0.0.0.0/[::](treated aslocalhost) are opened; a remote URL printed by a script is ignored. A hostile repo can't point your editor's browser somewhere else.
Turn it off with levelcode.ai.preview.autoOpen, or open previews yourself with Simple Browser: Show.
Credits, not dollars
The response bar under each run now reads Opus 4.8 · 46 credits · 1,279 left instead of dollar amounts. $1 = 100 credits, so a $100 Ultra plan is a 10,000-credit allowance.
This is a change of unit, not of price — nothing about what you pay or what a turn costs has moved. A balance that ticks down in dollars reads as money draining away; an allowance reads as something you're meant to spend, which is what it is.
- Small runs stay honest. A cheap-model turn costs a fraction of a credit, so it shows as
0.4rather than rounding to0and looking free. - One number everywhere. The editor and levelcode.ai/ai/account now format the same figure identically, down to digit grouping.
Under the hood
- The dev-server address is sniffed from the accumulated output, not a single chunk. Node delivers stdout in arbitrary slices, so a URL routinely arrives split (
http://local+host:5173/) and would otherwise never be recognised — the preview would silently never open. verify.jsgained its first test suite, and shed a stray NUL byte that had been making the whole file invisible togrepanddiff.- Release builds no longer download a Playwright browser during bootstrap — an unnecessary network dependency that failed one architecture while the other passed.
Test coverage
- 24 suites across the bundled extensions, all green on every release.
verify.test.js(16 cases) — the preview sniffers, led by the one that matters: a remote address in command output must open nothing.creditFormat.test.js(8 cases) extracts the real formatters out of the shippedchat.html, so the tests can't drift from what ships.
Full changelog: v0.9.1...v0.9.2
LevelCode v0.9.1
LevelCode v0.9.1
Two big things this release: the agent gains external tools via MCP, and it learns to narrate its work instead of scrolling a wall of chips.
Highlights
The agent can use external tools — MCP support
LevelCode's agent now speaks the Model Context Protocol, so it can use tools from external MCP servers — a filesystem server, GitHub, Postgres, an internal company server — right alongside its own built-in tools. There's no glue code: an MCP tool becomes an agent tool directly.
Add a server in your user settings and its tools show up in the agent, namespaced server__tool:
"levelcode.ai.mcp.servers": {
"filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"] }
}Security is the feature here, not a footnote — an MCP server is a process LevelCode spawns with your privileges:
- User-authored only. These live in your user settings; a repo's committed
.vscode/settings.jsoncan't add a server, so opening an untrusted project can never make LevelCode spawn a process. A repo's own.levelcode/mcp.jsonis read and listed but never started on its own. - Nothing runs unless you allow it. Every MCP tool call is gated by default — including under Autopilot. A tool runs only once you allow-list it in
levelcode.ai.mcp.toolPolicy, and a server's own "destructive" hint can only ever tighten that, never loosen it.
This is the first slice: servers are configured in settings and tools are enabled through the allow-list. A per-call approval card and a one-click "Add MCP Server…" are what come next.
A calmer, narrated transcript
An agent run now reads like a colleague narrating their work rather than a flat scroll of chips and cards.
- Short prose between actions. The agent says what it's about to do, then interprets what it found — in the same turn, so it never stops to chatter instead of working.
- Activity folds into one card. Consecutive actions collapse into a single expandable group: while it runs, the header shows the live step; when it finishes, a past-tense summary takes its place — "Read and edited
PLAN.md+56 −0, ran 2 commands." - Failures read as findings. A hiccup shows up as a one-line
Correction:and a next step, not an alarm. - Plain-language command labels. A
run_commandshows what it does ("Run the extension unit tests"); the raw command stays tucked behind the card. - One question at a time.
ask_usernow asks a single question per prompt.
Smaller things
Shift+Cmd+Inow focuses the chat — previously onlyCtrl+Cmd+Idid.- A single circle-check glyph wherever a "done" check appears, and an HTML5 shield icon for
.htmlfiles.
Under the hood
- MCP is three small, dependency-free modules — no SDK, in keeping with the plain-JS extension style.
mcpProtocol.js(JSON-RPC 2.0 framing + typed-content flattening, pure),mcpConfig.js(server config, tool-name namespacing, and the approval policy — pure), andmcpClient.js(the stdio subprocess: spawned detached and group-killed on New Chat and reload, with a per-call timeout and an output cap so one server can neither hang the agent nor flood its context). The full plan and threat model are indocs/MCP.md. - The calm transcript ships as two halves — the voice lives in the agent's system prompt (
agent.js), the grouping in the chat webview (chat.html). Scope and design are indocs/CALM-TRANSCRIPT.md.
Test coverage
- 56 MCP unit tests, all off-editor (no process, no network):
mcpConfig.test.js(41 — tool names stay provider-legal across a hostile corpus, a repo's config can never shadow or auto-start a server, untrusted keys are dropped before they can reach a prototype, and the allow-list never defaults to "allow") andmcpProtocol.test.js(15 — JSON-RPC framing with partial-line buffering, and base64 image/audio payloads never reaching the stored transcript). - The CI gate runs every bundled extension's suite on each release.
Full changelog: v0.9.0...v0.9.1