You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A quality release covering the 58 commits since 0.8.0 — no new subsystems; the tree gets hardened end to end. The full UI/UX audit lands across desktop and mobile (dialogs, confirms, focus, contrast, touch targets), the chat run loop becomes cancellable and bounded (stale-turn teardown, a 138K output limit, honest loop guards), strict reasoning-echo gateways stop bricking sessions, automations gain a real tool loop and stand down while the GUI is open, boot gets faster and lands on a chat, and the Sprite Studio pixel pets join the bots as companions. Alongside: a desktop approval surface for phone-added project roots, the tool registry consolidated 84 → 74 tools with a hard 120-tool wire cap, real per-kind previews in the Artifacts library, and the website rebuilt as Six Panes with a complete feature tour.
Version note: 0.8.1 was never published — it is the name of the 0.8.0-cycle build attempt that died at the old 60-minute CI ceiling (see the 0.8.0 packaging note), so this release takes 0.8.2.
Release packaging: cut locally on Windows without CI (GitHub Actions unavailable), following the manual path in docs/ai-context/RELEASE.md — bundled resources staged, both sidecars built and staged, NSIS bundle built, installer signed with the updater key and latest.json generated, connector client credentials baked from the local environment as the CI build did. The phone build is an EAS cloud build (production-apk) attached to the same release.
Added
Pixel pets — the Sprite Studio species as bot companions — the "Relay Pets: Sprite Studio" renderer is ported 1:1 into src/lib/pets/relayPets.ts (verified pixel-for-pixel against the studio page: 1,120 frames, 0 mismatches) so a bot can wear one of five pixel species — Cat, Pup, Bunny, Fox and Botlet — beside the six 3D pets, with the studio's full customisation: per-task dock animations (work at a desk with a flickering screen, sleep curled with zzz, the "!" alert ping while an approval waits, dance for celebrations, walk/run strolls with occasional zoomies, a click-to-pet happy burst, idle), and a "Pixel pet studio" section in the bot editor — body / belly+accent swatches with custom color pickers, Round/Big/Narrow eyes, seven accessories, and a live preview that can play all nine animations. Styles persist per bot (state/botPetStyles, localStorage, sanitized on load so stale rows fall back to species defaults) and every animation renders with the chosen look; RelayPetSprite draws cached frames on its own canvas (jsdom falls back to the species emoji chip) while PetAvatar/PetPreviewGrid stay 3D-only via a BOT_PETS_3D split. The review pass closed the gaps that would otherwise have shipped broken: the backend rejected the new ids (validate_bot_pet only knew the six 3D species — saving a bot with a relay-* pet failed "Unknown pet"; BOT_PETS and the agent-facing create_bot/update_bot schemas now list the five), Settings → Bot pets gains a "pixel pals" row rendering them without WebGL, and the phone's Bots list maps the five ids instead of falling back to the bot's emoji. The roster rail shrinks (strip 74 → 62px, tiles 64 → 54px) and rail pixel pets box at 42×40 so they sit dead-centre with a 3D animal's mass instead of towering over the tile. Tests: 12 new relayPets cases plus the pet/bots suites (141 tests).
Desktop approval surface for phone-added projects (the M17 follow-up) — a project added from the phone (AddProject) is stored unapproved and excluded from the model's fs_roots, and until now nothing on the desktop could lift that exclusion, so phone chats in the project silently couldn't touch its files. The Projects panel now badges those rows ("phone · needs approval") with an Approve action behind a danger-styled confirm that spells out the grant — approving lets the AI agent read and edit files under the folder. New phone_unapproved_projects / approve_phone_project commands wrap the existing M17 helpers (approve_phone_project_root was written for this and never called); the relay's ProjectInfo grows an approved flag (serde-defaulting true, so older payloads still parse) carried by ProjectList/ProjectUpserted, and the phone's Projects sheet shows "Waiting for desktop approval — the AI can't access this folder's files yet." on unapproved rows instead of pretending everything works. Approving broadcasts a refreshed ProjectUpserted to every connected phone so its badge clears without a reconnect; ListProjects/AddProject/RenameProject now serialize through the same approval-aware mapper (a phone-supplied name also lands in the upsert instead of the derived default), and remove_project drops the approval entry so a re-added folder never inherits a stale exclusion. The panel re-syncs the list whenever it opens (a phone tap gives the desktop no event), and at boot.
The tool registry merges 11 tools into 7 op-fronts, and the wire is capped at 120 — the 2026-10 registry-audit follow-up shrinks the tool surface and closes the one path that could still overflow a provider's 128-item tools cap (the live 400 that killed the scheduled automations on Oct 24). copy_file+move_file → file_transfer(op), get_task_status+cancel_task → background_task(op), get_source_ledger+reset_source_ledger → source_ledger(op), list_automations+get_automation → automations(id?), search_wiki+read_wiki_page → wiki(op), memory_save/recall/forget → memory(op), and the five app_* self-control tools → computer(action) — old names stay dispatchable so history replays, and the relay bridge accepts the four legacy names it used to serve. Registry: 84 → 74 full / 41 → 36 disclosed unlocked; the fresh-chat wire drops 20 → 16 tools (−12% chars) and the browser-open ceiling 97 → 87; the long descriptions (write_file, run_shell, web_search, Task, open_url ~40%, visualize 2626 → ~1600 chars) are trimmed with contracts preserved, and read_agents_md plus the bot observation trio moved behind tool_search. MAX_WIRE_TOOLS = 120 spends the remaining budget on attached connector/MCP schemas and truncates with a [tool-cap] log instead of exceeding the provider cap (pinned by a test). The plan gate and bot write-classification became args-aware so computer(action) counts as mutating except the snapshot read — closing the bypass the merge would otherwise have opened — and file_transfer joins the progress detector, the mobile always-allow path and the deterministic approval summary.
Real per-kind previews in the Artifacts library gallery — every artifact kind now gets an actual thumbnail instead of the generic outline icon: raster images stay cover-cropped; SVG diagrams and mermaid sources render contained; PDFs show their first page through pdf.js (lazy-imported, rendered to an offscreen canvas over a white paper fill, the loading task destroyed on unmount, icon fallback on any failure); html/widget/diagram/office documents show a static scripts-blocked page frame — a 4×-oversized sandboxed iframe scaled to ¼, a still "browser shot", never a live embed per card; JSX artifacts preview as their source snippet. The mermaid pipeline (theme init, parse-first, error-bomb checks, sanitize, cache) is extracted into a shared renderMermaidSvg so the chat fence and the card thumbnail use one pipeline and one theme:source cache; card frames sanitize as whole documents (generated pages and diagrams carry their formatting in <head><style>, which DOMPurify's body-only default strips — the "unstyled preview" bug; sanitizeOfficeDocumentHtml generalizes to sanitizeDocumentHtml with a back-compat alias, and the preview pane's static DiagramFrame had the same latent bug and now uses it too). Tests: per-kind thumb branches (frames + sandbox attrs, contained svg/mermaid images, jsx snippet, pdf fallback, script stripping, head-style survival).
A web-search-keys usage card, board/skills/logs polish, and phone visualization parity — the Usage dashboard grows a compact search-keys card under Cost quality: live Tavily /usage, Brave X-RateLimit header capture, and a Serper local monthly counter, with reset lines in the usage-limits vocabulary. The shared board renders markdown note cards (GFM tables, authored bylines, tag chips, clamped bodies with Show more) on a wider 288–340px rail; the Skills library gains a SKILL.md Preview/Edit toggle and tokenized search with highlights/counts/clear driven by the SKILL_HARNESSES registry; the Logs view gets a no-selection side bar and chips that truncate in their grid tracks; session rows align the kebab/spinner to the harness icon column; and expanding a transcript block mid-stream no longer pin-locks scroll. Text-only reasoning endpoints no longer 400 on image turns — image parts are stripped into a text note and the send retries once (tool loop and plain path). The phone builds the desktop-parity system prompt (memory-tool guidance), renders visualize payloads natively, and shows render_widget/generate_diagram HTML inline as themed WebView cards that hoist out of the "Worked for" fold.
Stashed projects settle into a pinned Settled section, and the Browser tab keeps a desktop-width layout — a stashed project now parks in a pinned "Settled" section of the sidebar's projects panel instead of falling out of view, and the tool panel's Browser tab holds its own desktop-width layout instead of reusing the phone-narrow one.
Changed
Tool-call rendering: per-kind icon tints, honest glyphs, and tall-output caps — every tool-call surface the chat renders was audited in one self-contained page (docs/audits/TOOLCALL_AUDIT_2026-10-25.html, built by scripts/inline-toolcall-audit.mjs: all 12 marker kinds in done and live states, aggregate surfaces, and a full turn through the real parse → group → render pipeline from raw <tool> markers). Driven by the audit: per-kind i...
A feature release covering 56 commits since 0.7.0 — Relay grows persistent identities: Bots (named agents with persona, memory, a pet, standing jobs and a real thread), /sidechat (a private sub-thread seeded with the current chat), and idle compaction (long-stale threads fold themselves in the background). The model can now put real UI on screen through visualize cards and render_widget mini-apps, the composer predicts your next message and recalls earlier prompts, and the phone reaches desktop parity with queued sends, undo checkpoints, diff sheets and read-aloud. Alongside: per-model context windows, two explicit OpenAI API kinds, usage/limit metering across every provider and harness, a rebuilt Pulls panel, a redesigned browser URL bar, a shared shell-glass recipe, and the full remediation of the 2026-10-20 desktop/Rust/mobile audit.
Release packaging: the production frontend build runs with a 4 GB Node heap (.npmrc), and the Windows build job gets a 150-minute ceiling. The initial tag attempt hit the runner's 2 GB heap default in Vite; the next completed the frontend and both sidecars but reached the old 60-minute job limit during the final relay compile. No app behavior changed in this packaging fix.
Added
Sidechat — /sidechat opens a private sub-thread of the chat you're in, seeded with its context — a manual second thread pinned right of the focused pane through the same fork-to-panes split machinery (6-pane cap), so the main session is never touched: its pane keeps streaming, its transcript is never written to, and the sidechat runs its own agent turn on the source's provider/model/agent (config is carried: provider, model, agent, project, cwd override, permission/sandbox/approval policies, effort, watch mode). Its context is a snapshot taken at open and kept hidden — db::open_sidechat persists the source's live (non-superseded) history as ONE context-only seed row (kind = 'sidechat_seed', role user, assembled by the new agent_sessions::sidechat_seed_from_records under the primer's character budget — newest turns verbatim, omitted older ones marked), so the sidechat's pane starts visually empty and only ever shows the sidechat's own turns; the model still reads the seed on every turn (list_active_chat_messages), and a CLI/ACP handoff embeds it VERBATIM and pinned outside the primer's truncation budget (context_primer_from_records — the per-message cap would otherwise clip a long handoff to its first 6k chars; the head summary is skipped for a seed-led session, which is already a budgeted handoff). Every display reader (list_display_chat_messages behind get_chat_messages, export, title generation, mobile paging/regenerate), full-text search, the first-send auto-title counter, and the session-fabric user-turn count / transcript excerpt (count_user_messages / transcript_excerpt) excludes it, and the memory cursor is parked past the seed so the source's turns are never re-extracted as the sidechat's memories. The session row itself rides the new insert_session_copy helper that copy_chat_session_with_history (the fork path) now shares — with origin side_of:<id>, title '<original> · sidechat' (a NULL title stays NULL and auto-titles off the sidechat's first send), and worktree_path deliberately NOT copied, since a worktree belongs to exactly one session and the source's is in use (the sidechat provisions its own, fire-and-forget, like a fork). At most ONE sidechat per source chat: a second open returns the existing row as-is (re-focusing its pane, or re-pinning it when that pane was closed) — the snapshot is frozen at creation, so re-opening is a re-focus, not a re-sync, and deleting the row makes the next open build a fresh one. Entry points: the /sidechat slash command (client-intercepted for every engine; trailing text after the command is asked IN the sidechat, not the source chat), a Sidechat action in the right tool panel's picker and "+" menu, and the command palette (hint /sidechat). In the sidebar the sidechat nests under its parent chat through the same grouping spawned sessions use — chatSessionGroups' spawnParentId generalizes to nestedParentId (spawned_by: and side_of: nest, fork_of: stays top-level). New: db::open_sidechat (+ Rust tests: one hidden seed row, empty display reads, parked memory cursor, reuse and per-source isolation, and the worktree-not-copied invariant; agent_sessions tests pin the seed's budget, its omission marker, and that a CLI primer embeds it verbatim without the [User]: double label or the 6k per-message truncation), the open_sidechat Tauri command, openSidechatSession IPC, the openSidechat chat-store action (reuse/re-pin, row registered before the pane insert, worktree provisioning), the composer/palette/tool-panel entries, and the pane's in-place explanation of the handoff — a "Sidechat of " banner atop the transcript, or the welcome hint while the sidechat is still empty (chatSessionGroups.sidechatSourceId); vitest covers pane pin/reuse/re-pin, the empty-chat refusal, the slash interception, and the source-id parsing.
Bots — named persistent agents, with their own persona, memory, pet, standing jobs and conversation (docs/architecture/BOTS.md) — a bot is a persisted identity (name, description, emoji, dot color, pet, persona prompt, pinned or inherited engine, and four toggles) that owns one lazily-created thread (chat_sessions.bot_id + bot_role = 'thread', ensure_bot_session) and runs everything else in run sessions (bot_role = 'run', hidden from Chat History, opened from the bot's activity strip). The Bots view (new ActiveView, sidebar pill) is a rail of bots — state dot, pet, name, one-line description, gold pending-approval count, pulse while a run is in flight, inline double-click rename, Approve on a disabled row — beside the real ChatView on the selected bot's thread, with an activity area holding the approval inbox, the standing-jobs list and the recent-runs strip; bot threads are ordinary sessions and open from Chat History too. The persona layers on the user's global assistant prompt (compose_prompt, inherit_global_prompt opts out; empty persona = a shell over the global prompt), and memory is per-bot: extraction/tool writes land in profile bot:<id> (memory_forget refuses a row that is not the session's own — read-through recall included), recall reads its own profile plus default when memory_read_default is on, and the editor's Memory section lists/deletes what the bot knows (guarded to bot:<id> rows). Unattended safety: a background run (bot_policy::gate, keyed on bot_role = 'run' and hooked at the top of dispatch::run_tool) never executes a write-class tool — it queues a bot_approvals row with a deterministic summary built from the tool name + args (never model-written text), tells the model the action did not run and must not be retried, and notifies desktop + phone; pending rows expire after 72h; resolve_bot_approval replays the stored call verbatim through the family executors on approve (with the dispatcher deliberately not re-entered — its cards need a paused live turn), writes the row off on deny, and appends a truthful ✓/✕ note to the run session. Bot-bound automations (automations.bot_id, no FK) run in fresh run sessions with the persona + tool loop and the same gate; a disabled bot's jobs are skipped and run_bot/ask_bot refuse while its thread stays usable (so agent-created bots, which land disabled, can be reviewed). tools_enabled = false means the turn is assembled with an empty tool set (the toggle used to be decorative). Main-chat tools list_bots / create_bot / update_bot / delete_bot / run_bot / ask_bot mirror the automation family — reads and the read-class ask_bot always-on, the CRUD/run half mutating-gated and family-locked (attach_connector("bots") or the "bot/persona/standing job" keyword fast-path), agent-origin updates dropping enabled, the agent-facing delete keeping memories, and the whole family excluded from the harness relay bridge (no live turn ⇒ no approval card). Mobile gets the approvals console: ListBots → BotsList, ListBotApprovals → BotApprovalsList, ResolveBotApproval → BotApprovalResolved, and BotsScreen (pending rows with summary/tool/arguments + Approve/Deny through the same command as the desktop, plus the roster with dot state, job count and pet). bots:changed {action, botId} drives every surface. Deleting a bot is one transaction: jobs removed, run sessions deleted, the thread unbound explicitly (no reliance on PRAGMA foreign_keys) with its memory cursor frozen, bot:<id> memories purged only when asked, approvals gone. New: db/bots.rs, db/bot_approvals.rs, bot_runs.rs (run executor + run_bot_now/ask_bot), chat/bot_policy.rs, chat/tools/bots.rs, commands/bot_cmds.rs, src/lib/ipc/bots.ts, src/hooks/useBots.ts, src/components/bots/{BotsView,BotEditorModal,ApprovalInbox,RunsStrip}.tsx, src/styles/bots.css, mobile/src/screens/BotsScreen.tsx, and the tables bots / bot_approvals / bot_runs. Documented deviation from the plan's v1: the companion is the six 3D pets, not an emoji dot (see the pets entry below).
Pets — six species, rendered in 3D, docked on the composer and the search bar — the companion feature now ships the six animals from docs/research/ade-pets (cat, fox, panda, dragon, axolotl, robot) drawn by a ported three.js engine (src/lib/pets/adePets.ts, new dependency three, dynamically imported so it stays out of the entry chunk) through ONE shared WebGL stage (src/lib/pets/stage.ts) that renders each pet into its host element's rectangle and draws nothing while the window is hidden or the pet is scrolled out of view. PetAvatar hosts a pet anywhere (rail row, editor picker, dock slot) and falls b...
A feature release covering 96 commits since 0.6.0 — the agent stops living inside the transcript. It can now drive the browser pane and Relay's own interface (computer use, phases 0–2), the roles it kept re-describing in prose become a registry it can read and write (Subagents), every project grows a generated wiki whose freshness is computed from git rather than guessed, answers speak as they stream and the vault reads itself aloud, local models get a request log and a loopback gateway, and the phone reaches desktop parity — steering queued sends, peeking at diffs, mirroring a terminal — on an overhauled relay. Alongside: fork-to-panes, browser full screen, a skills gallery, a live MCP registry, Qwen-Image Turbo, an embedding-model picker, the 2026-10-03 audit remediation (9 critical / 43 high / ~110 medium findings), and two black-box E2E suites.
Added
Computer use, phases 0–2 — the agent drives the browser pane, and Relay itself — six ops that existed as relay-browser MCP tools but were unreachable from a chat turn (browser_find, browser_zoom, browser_press_key, browser_fill_form, browser_select_option, browser_batch), plus dual-mode targeting: click/type accept x/y viewport pixels for the canvas, chart and map content the element tree cannot name — the pixel path hit-tests via elementFromPoint, climbs to the nearest interactive ancestor and reports what it actually hit, so a misjudged pixel is debuggable instead of silent, and typing by coordinate focuses rather than clicks so it cannot submit the form it is filling. browser_batch validates every step before running any (a typo in step 9 must not leave step 0 applied) and reports every step, including after a failure. Phase 2 is Relay self-control: the app's own React DOM is addressed with the same ref contract as the browser pane (bridge_refs.js is now its single definition, replacing three hand-synced copies, so a ref cannot mean different things on the two surfaces), refs survive layout shifts and are readable back, elements under data-relay-agent-exclude are filtered before numbering so a ref can never silently resolve into excluded chrome (the composer's Stop button and the approval dialog are marked), typing uses the native value setter so React registers the change and never submits, and an orphaned ref returns an explicit stale_ref telling the model to re-read. Phase 0 is the safety floor this rides on: the tool loop is capped (500 iterations, 1000 in research — MAX_TOOL_ITERS was usize::MAX, which made the existing "stopped after reaching the tool-call limit" exit unreachable), and run_code now answers permission exactly like run_shell with an approval card that shows the snippet and says there is no sandbox — it had been falling straight through to execute_tool with no card in any posture while codeexec::sandbox_available is hardcoded false, i.e. an ungated path to the same host control.
Declarative subagents — named agents in a registry, spawnable from the Task tool, a Run button, Session Mesh and automations — user-defined agents (prompt body, tool allowlist, permission scope, engine/model, worktree policy, spawn budget) stored in a subagents table with the 7 builtin roles seeded as data carrying their exact prompts, so nothing changes for users who never open the panel; managed in Settings → Subagents. Spawn surfaces: the Task tool (dynamic role enum + explicit agent arg — enforced tier: schema filter, execution check, project-root write scope, policy filtering), a manual Run button (run_subagent — the run is a normal keep-chatting session tagged with the agent), Session Mesh spawn_session with agent:<id> (model fan-out, mesh caps still govern), and automations via agent:<id> (engine-resolved, works in the run-while-closed sidecar). Per-agent hook scoping (origins on hooks; subagent runs dispatch agent:<id>) is the guardrail for harness/ACP engines, whose native tools Relay cannot restrict — the UI badges that tier honestly ("advisory") instead of pretending. Worktree auto-provisioning (worktree_policy: always) via the extracted session worktree seam; origin-scoped hook denies verified end-to-end; .md export/import (Claude-Code-compatible frontmatter, strict import); subagent_runs history; send-time credential pre-flight and per-agent max_concurrent budgets. Live-tested against real models: builtin→OpenRouter and harness→commandcode/deepseek tiers.
The registry is a link, not a copy — and harnesses can find it — the Subagents panel lists each harness CLI's own native agents discovered on disk and imports them into the registry in one click, harness sessions get a read-only view of the registry (so they can run a named agent without being able to invent one), and rows the model authored are badged "made by agent" via the subagents.origin column. One settings surface: the retired sidebar view is gone and the native stores ride the Settings panel, so there is exactly one place an agent is defined.
Agents can author the registry — list_subagents rides every main-loop turn, and create_subagent / update_subagent / delete_subagent let the model define, refine, or remove agents on request — approval-carded in every posture (an agent can never exist or vanish without an explicit yes), name/policy validated by the same code the panel uses, builtins and in-flight agents deletion-refused, and the CRUD trio stripped from the relay bridge and subagent registries so a harness is never advertised a tool it cannot dispatch (harnesses run registry agents via spawn_session agent:<id> instead).
Project wiki — a generated per-project knowledge base whose freshness is COMPUTED, not guessed — a repo analysis, an LLM outline, and one call per page, where every page ends in a Grounded-Claims ledger (claim → repo-relative path + line range + blob SHA). The update pass diffs HEADs, joins changed paths against stored evidence and re-derives only the affected pages — a clean repo costs zero model calls. Engine (src-tauri/src/wiki/) carries a single-call fast path, a commit-triggered freshness watcher and a slow sweep; persistence (wiki_projects / wiki_pages / wiki_claims) adds an external-content FTS5 index over title/summary/body (keyword-only by design — pages are short and keyword-dense, raw file content stays in the RAG corpora). The build is dependable in practice: a malformed response is handed back to the same model with the parser's error for one repair round before the two-phase fallback, retries cover the one-pass and outline calls, × kills the in-flight CLI call (with a terminal cancelled event from every path), the generation bound raised 180s → 600s for CLI context, and permanent failures (credits/quota/auth, observed as exit-0 CLI replies) surface actionable errors instead of "no JSON in response". Delete is one glass confirm popover; the feed is live during a build.
Streaming voice — the answer speaks as it generates, hands-free — read-aloud now rides the token stream instead of waiting for it: a streaming speech feeder voices complete sentences only (markers like code fences and links are never voiced), the opening words are fed mid-sentence with the engine warmed before the answer so the first sound is immediate, and the first spoken chunk is capped at a clause boundary on CPU reads where the engine is slow to start. The hands-free loop is a root controller with a composer toggle and a status bar: it listens, yields the turn on extended silence, speaks the reply, and returns to listening — the toggle button itself is the loop status (the wave replaces the bar). Barge-in lets a normal voice interrupt a read: the gate calibrates on sounding audio only (it used to confirm on its own speaker echo and kill its own read), pauses to check, ducks the read's volume on a real spike, and covers the whole answer lifecycle — speech interrupts instead of queueing behind the rest of the reply. Live dictation transcript in the composer, level-fed wave, and [voice] timeline logging in devtools for debugging the loop.
A request log for local models, plus a loopback gateway — every call to a local model (the llama.cpp sidecars Relay spawns, and traffic other apps route through the new gateway) is recorded with its request and response bodies verbatim, then summarised into token counts, TTFT and throughput — surfaced in a full-page Logs view, with retention and body-cap settings under Local Models. The gateway forwards bytes and never re-frames: Ollama streams bare newline-delimited JSON, llama.cpp plain SSE, LM Studio named-event SSE, and a proxy that understood the stream would need three framing paths and corrupt responses the moment one drifted — so it forwards unchanged, keeps a copy, and hands the copy to a normalizer, meaning a bug there can cost a field but never a response, and raw bodies let telemetry be re-derived later. Relay's own turns are captured in-process at the provider wire bodies and at each tool-loop round (one row per model round). Local models also gained per-model tool-calling badges (GGUF template scan + tri-state override) and a raw tool-call pane on the Logs page, with the tool loop gated off when the active model explicitly cannot do tools.
Fork a chat into side-by-side panes, and Mod+/ for every shortcut — fork copies a chat's config and live (non-superseded) history into new sessions pinned beside the focused pane (equalized split ratios, 6-pane cap), with entry points in the ⋮ menu, the command palette, and a fork-here glyph on a bubble that truncates the copy at that message inclusive (fork_chat_session upto_message_id); forks record a fork_of:<id> origin, title '<original> · fork N', and isolate their own worktree like new chats. The HotkeyOverlay opens with Mod+/ (Alt stays exclusive to STT push-to-talk) and lists every binding grouped by surf...
Local model context compaction — automatically summarizes older conversation history when a local GGUF model's context window approaches capacity. The most recent exchanges stay verbatim; aged-out turns are condensed via a non-streaming summarization call. Configurable threshold and pin count in Settings → Local Models → Compaction.
Context window meter — a small circular SVG ring below the send button shows how much context was used by the last turn (green < 70%, amber 70–90%, red > 90%).
Skills system overhaul — skills moved from the old JSON table to on-disk files in harness skill directories (~/.claude/skills/, ~/.agents/skills/) plus four built-in skills (docx, pptx, pdf, diagram). A new / slash-menu in the composer lists all available skills, and a new get_skill(slug) tool lets the model load skill instructions on demand.
UI refresh — Codex-style aesthetic — thinking blocks collapsed by default with muted monospace styling, tool-call activity in compact monospace log lines, inline unified diffs, blinking cursor typing indicator, compaction markers in the timeline, and session context menus that flip upward near the viewport bottom.
Notification sound — new setting (on by default) plays a short chime alongside PTY notifications. Per-pane 30s cooldown prevents spam.
Model selector improvements — local model names shortened (quantization tags and .gguf stripped), duplicate entries resolved, context-size number input alongside the slider, and n_ctx propagated end-to-end for accurate context metering.
Mobile companion enhancements — new StartLocalModel/LocalModelReady/LocalModelError protocol messages let the phone trigger a sidecar spawn on the desktop.
Windows console window suppression — llama-server, Python, git, and code-execution subprocesses use CREATE_NO_WINDOW to prevent console window flashes.
Delete session also closes pane — removing a session from the Dev tab sidebar now kills the underlying PTY process and closes the terminal pane.
Browser pane polish — address bar selects all text on focus/click; full pane close on React unmount prevents floating frozen native webviews.
Fix auto-updater signing key — corrected the updater public key baked into the app so it matches the signing keypair. v0.3.0 installs could not verify update signatures because of a one-character mismatch in the embedded public key; this release restores the auto-update chain. (Existing v0.2.0 and v0.3.0 installs should manually install 0.3.1 once; updates work automatically after that.)