Repository navigation
Releases: km0e/flux
Release list
0.2.2 - 2026-09-17
Release Notes
Added
- Single-file multi-op file tools (
read_file/edit_file), one file per call — the path is named once for any number of operations. The E5 convergence replaces the four-tool read/edit surface (read_file,read_files,edit_file,edit_files— fs tools 7 → 5) with two tools that take one file and an operation list:read_filegains optionalranges: [{offset?, limit?}](omitted/empty = whole file, unchanged bare output; a single window keeps the bare output too; 2+ windows render one=== path ===section per window, each withread_file's continuation footer, ≤ 16 windows), andedit_filetakesedits: [{old_string, new_string, replace_all?}](1..=16), applied in listed order with each edit matching the content the previous edit left — the same-file multi-edit workflow that previously required repeatingfile_pathper item inedit_files. Atomicity strengthens: the edits all apply to an in-memory string and the file is written once at the end, so one failed edit rolls the whole call back (edit N: <reason>. File untouched., with the nearest-line hint and the edit's 1-based index in the call's own list); upfront validation rejects empty/identical/exactly-duplicated edits and the cap before any I/O. The oldedit_filesHashMap file-grouping layer is deleted outright;read_file_segment/apply_edit_ops/ the CRLF gate / the nearest-line hint carry over unchanged,EditOpis now the#[derive(ToolItem)]wire item, and the pinned schema contract test tracks the new shapes. - Batched search tools on ripgrep's library split (
grep/glob). grep moves from the hand-rolled single-threaded line scanner togrep-regex+grep-searcher+ theignoreparallel walker: multi-threaded walking, .gitignore/.ignore semantics (target/, node_modules/ no longer scanned — rg rules: an explicitly given FILE is still searched, a DIRECTORY arg respects the ignore rules, ignored trees go through bash), binary NUL-quit detection, and dotfiles stay searchable (hidden(false)). Both tools accept 1..=8 patterns per call: grep's single-pattern output stays the flatpath:line: text(legacy format, 500-row cap); multi-pattern output groups per pattern with a 200-row soft cap per pattern (a hot pattern cannot starve its siblings) under the 500-row global cap, an optionalincludeglob filter, and optionalcontextlines (0-5, rendered rg-stylepath-line- text, counted toward the caps); an invalid pattern renders as its group's error section while the rest still search (all-invalid fails the call). Results sort by (path, line, document order) so the parallel walk's nondeterministic order never leaks. glob's batched patterns compile up front — any invalid one fails the call (a flat list has no per-pattern error slot: deliberate asymmetry) — and first-match-claims dedup across overlapping patterns. All existing guards survive the rewrite: the 16MB file skip, the 500-char match-centered line window (truncate_around_matchunchanged, now called inside the Sink), the boundaryctx.resolve, and the pinned schema contract test updated to the newpatterns/include/contextshape.
Fixed
- "Load earlier messages" no longer splits tool calls from their results. The earlier-page path builds the whole page into an unmounted
DocumentFragmentbefore a single insert into the pane, but the result-merge step queried the PANE for the call card ([data-tool-call-id]) — at that moment the card built earlier in the SAME page was still inside the fragment, the query always missed, and every same-page pair degraded into a call card plus an anonymous orphan "result" card (the initial tail appends directly into the pane, so only the click-loaded path broke; page-boundary alignment to user messages only protects ACROSS pages).appendHistoryMessagenow carries a per-pagetool_call_id → cardregistry that the earlier-page loop maintains and the tool branch consults before the pane fallback. Pinned by a pagination test that click-loads a page containing a tool round. - Streaming follow no longer dies when an expanded reasoning block shrinks mid-stream. The stick machine's only detach signal was "the scroll event reads as an up-scroll" — but while pinned at the bottom, an incremental streaming render whose boundary frame nets NEGATIVE height (paragraph-commit reflow, code-fence fold-back removing committed nodes) makes the browser clamp
scrollTopdown to the new bottom and dispatch a scroll event that reads exactly like the user leaving. One such clamp detached the stick state and silently no-op'd every later delta'sscheduleFollowuntil the user scrolled back into the 96px re-attach window — visible when streaming an expanded reasoning block (collapsed blocks never occupied scroll height, hence "probabilistic"). Detach now requires scrolling up AND off the bottom (scrollTop + clientHeight < scrollHeight - 2px): a clamp lands exactly on the bottom and is no longer mistaken for intent. Accepted cost: the first ≤2px of a real scroll-up from the bottom may detach one event late — the next tick always leaves the tolerance. Pinned by a new stick-machine test suite driving mocked scroll metrics.
Install flux-server 0.2.2
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/km0e/flux/releases/download/v0.2.2/flux-server-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/km0e/flux/releases/download/v0.2.2/flux-server-installer.ps1 | iex"Download flux-server 0.2.2
| File | Platform | Checksum |
|---|---|---|
| flux-server-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| flux-server-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| flux-server-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| flux-server-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| flux-server-x86_64-unknown-linux-gnu.tar.xz | x64 Linux | checksum |
0.2.1 - 2026-09-17
Release Notes
Added
-
Multi-item fs tools (
read_files/edit_files) — several operations in ONE call (cap 16 items), cutting tool-flight round trips and giving multi-edit workflows real atomicity.read_filestakesfiles: [{file_path, offset?, limit?}]— one segment per item withread_file's exact semantics behind a=== path ===header; a failed item reports its error inline and the rest still read (all items failing → one consolidated error).edit_filestakesedits: [{file_path, old_string, new_string, replace_all?}]— same-file edits apply in listed order on the content the previous edit left (later edits may target text earlier edits introduced), and each FILE is atomic: one failed edit leaves that file untouched while other files still apply, so the report shows exactly which files were written and which edit (by its 1-based index in the model's own list) failed, with the nearest-line hint. Upfront validation rejects empty/identical/exactly-duplicated items and the cap before any I/O; CRLF handling generalizesedit_file's gate (any\r-carrying old_string keeps that file in raw space). Under the hood: a new#[derive(ToolItem)]proc-macro generates object schemas for nested array-of-object parameters (theToolderive delegatesVec<Item>items to<Item>::item_schema()for non-primitive item types — a missing derive fails compilation), andread_file/edit_fileremain as the single-item path over the same extracted cores (read_file_segment,apply_edit_ops), their pinned schemas unchanged. The web tool-card summary renders multi-item args as "N items: ". -
The e2e harness runs axe-core accessibility scans — a devDependency whose SOURCE the harness injects into the page (the runtime harness stays zero-dep): three surfaces per run (the fresh chat's empty state, the rendered conversation with prose + both verdict cards, the 390×844 mobile chat) must carry no critical/serious violations. The color-contrast rule is off by documented design (the faint token's tiered hierarchy — tokens.css) and
regionoff (the shell is not landmark-partitioned). The first run caught three real defects, all fixed: the notification bell's unread badge was INSIDE the DropdownMenuTrigger'sasChildspan, so the trigger's button semantics landed on a span (aria-allowed-attr, critical) and AT lost the expanded state; the sidebar chat row wasrole=buttonwith the ⋯ menu INSIDE it (nested interactive, serious) — the row and menu are now siblings under a positioning wrapper, same geometry, same keyboard path; prose links had no underline (Tailwind preflight strips it) and color alone is under the distinguishability threshold in the dark theme (link-in-text-block, serious) —.prose aunderlines explicitly. -
Coverage floors + a CI bundle treemap — vitest coverage thresholds act as regression floors set just under the measured baseline (global 80/75/65 lines/functions/branches;
src/lib/**92/85/80;src/core/**72/72/52 — the wire translation is exercised mostly through the e2e harness, not jsdom), so a silent coverage drop fails CI instead of passing quietly; verified the mechanism fails an impossible threshold.FLUX_BUNDLE_ANALYZE=1(the CI web job sets it) emits a rollup-plugin-visualizer treemap todist/stats.html, uploaded as a CI artifact — build-time only, never shipped. -
A skeleton primitive joins the loading voices —
components/ui/skeleton.tsx: static, token-derived shape placeholders (a fg wash reads on every surface) with the codebase's existing gentle opacity pulse — no shimmer sweep, the placeholder promises shape, not progress. Applied where a known shape is seconds away: the terminal host's first frame (undefined/connecting states paint three quiet bars instead of a blank inset void) and the Providers panel's first uncached catalog probe (the preview pane holds the "Upstream catalog" block's shape while the upstream round-trip runs — the badge alone carried it before). Deliberately NOT applied where loading is either instant or invisible: the "load earlier" pagination is a local slice over already-fetched messages, and the @-mention popup appears only once its listing resolves (a skeleton flash would add motion to a completion gesture). -
File references in the composer (
@-mention + drop-to-reference) — typing@opens a path completion popup over the composer (services/mention.ts): workdir-RELATIVE completions with/-drill-down (choosing a directory keeps the@and continues into the next segment; choosing a file consumes it and trails a space), dirs first, capped, matched case-insensitively on the segment after the last/. Directory listings cache per session with a 15s TTL — the Explorer's refresh rhythm — so a file the agent JUST created is completable; the cache is independent of the Explorer's tree state (completion works with the Files tab never opened). The popup is composer-local: ArrowUp/Down rove, Enter/Tab insert, Escape closes locally (stopPropagation shields the app-level chain — closing a popup must not cancel a round), mousedown-before-blur keeps focus in the textarea. Dropping an Explorer row onto the composer inserts its path at the caret — rows are drag SOURCES (text/plain, the absolute path relativized against the chat's workdir; outside paths stay absolute and let the tool boundary reject them honestly); the tree still accepts no drops. IME-safe: an Enter during composition confirms the candidate, it does not insert. -
Error rounds gain retry affordances; keyboard shortcuts get a sheet — pane-context errors (
services/stream.ts) render as a small danger card with two quiet token controls under the message: Retry walks back to the nearest user bubble and puts that turn's exact text back into the composer via the sharedflux:composebridge — it refills, NEVER auto-sends (the failed round stays in the transcript; stacking a second round on its context is the user's call, per the accepted T-08 split-manually tradeoff), and Copy carries the raw error text. The bare no-chat error (services/dispatch.ts) stays action-free — there is no turn to retry. The shortcuts sheet (?on the bare app surface, or the palette's "Keyboard shortcuts" action) reads its Application rows from the registry's ownshortcutfields vialistShortcutRows()— chords can never drift from the actions — plus the composer rows (Enter / Shift+Enter / Escape) that belong to no action; typing targets keep their literal?. -
Command palette (Ctrl/Cmd+K) — one input over a single action registry (
services/commands.ts): every open conversation (switch = the sharedselectChatpath the sidebar rows use) plus the application verbs (new chat, transcript search, new terminal, toggle sidebar/dock, cycle theme, open settings — plain or directly at the Providers / MCP / Skills section, a one-shot intent the store carries), and reconnect (gated to an actual disconnection). Actions delegate to the owning services — the registry reimplements nothing; availability gates are evaluated per open. The palette is a Radix dialog with real listbox semantics (combobox input +aria-activedescendant, ↑/↓ wrap-around, Enter runs-and-closes, click runs, Escape dismisses through Radix before the app-level chain); its query is one-shot — reopening starts empty, unlike the find bar's remembered search. Two pieces of global state the palette exposed got promoted out of component-local state: the theme CHOICE now lives in the store (services/theme.tsowns what the choice does — the data-theme flip and the live terminals' palette re-read — so a store action never touches the DOM), and the settings dialog's open flag + section intent likewise; the TopBar consumes both from the store and behaves identically. -
In-transcript search (Ctrl/Cmd+F) — a scoped find over the ACTIVE chat's rendered pane (
services/transcript-search.ts+TranscriptSearchBar): cross-node, case-insensitive, non-overlapping matches painted by the CSS Custom Highlight API (::highlight(flux-search[-current])in stream.css) — the append-only streaming DOM is never touched; engines without the API keep native find (the intercept is gated on the API + an active chat). Streaming appends rescan through a 150ms-debounced MutationObserver that re-counts but never re-scrolls (the view is not yanked by the round's output); Enter/Shift+Enter wrap around; Escape closes the bar BEFORE the round-cancel layer (one surface per Escape, same rule as the mobile drawer); the query survives close/open (find-bar memory); a chat switch closes the bar (search is per-conversation). Scope is the rendered transcript — older tail-paginated history joins the index when loaded. A batched switch+open commit cannot close what it opened: the bar's switch-close effect compares against the service's opened-for chat, not a mount-time ref. -
The screenshot tour doubles as a visual regression net —
npm run shots:updatesnapshots the tour into a LOCAL, gitignorede2e/.baseline/(the session's accepted appearance — deliberately not a committed design contract: rendering is machine-coupled and the design stays free to move);npm run shots:checkre-tours and pixel-diffs every shot against it, exiting 1 over a 0.2% budget with<name>.diff.pngheatmaps in.shots/. Zero extra dependencies: the PNG decode and per-pixel diff run inside the already-launched Chrome (createImageBitmap + OffscreenCanvas,e2e/visual-diff.mjs); captures awaitdocument.fonts.readyand phase-lock infinite animations, so same-machine re-runs land 0 px on static shots. -
The sidebar marks chats with a running round — a global
runningflag joins the wire:ChatInfo.running(truth = the live task's round state; a task that died mid-round is done-filtered, so no ghost marker). The chats broadcast now also re-fires on ROUND boundaries, not just lease transitions: lifecycle's watcher subscribes the engine's round-st...
0.2.0 - 2026-09-16
Release Notes
Migration (script-installer users): the browser UI now ships INSIDE the binary —
after upgrading, delete a previously unpacked~/.flux/web-ui/once (a stale dir
becomes a per-path OVERRIDE that shadows the embedded UI; the startup log warns when
it carries an oldweb-ui-version.txt). Source builds viacargonow need
node/pnpm by default (a toolchain-less build embeds a placeholder page; headless-only:
--no-default-features).
Changed
- The shipped binary drops from 32MB to 20MB —
[profile.dist]now builds withlto = "fat"+codegen-units = 1(cross-crate inlining/DCE, −4.5MB of.textat a release-only build-time cost) andstrip = "symbols"(the ELF.symtab/.strtabwere 8.2MB of dead weight withdebug = 0). Accepted tradeoff:RUST_BACKTRACE=1in a shipped binary shows addresses without names — tool panics are caught and reported by payload (flux-chattool_exec) and the shutdown contract is crash-only (T-11); dev[profile.release]stays unstripped for debugging. Attribution via cargo-bloat:.text16.7MB led by rmcp 2.3MB, ~2.5MB of C/asm natives (sqlite3/aws-lc/zstd), std 1.6MB, the rustls/reqwest/h2/hyper stack ~1.6MB; embedded UI 2.5MB sits in.rodata.
Added
- The browser UI is EMBEDDED in the server binary (rust-embed, feature
web-ui-embed, default-on; decision T-14) — release builds serveclients/web/diststraight from the binary (+~2.5MB, the woff2 fonts dominate), debug builds keep reading the repo dist from disk at runtime (pinned to the compile-time path: the dev fallback — a UI rebuild needs nocargo build). Serving gains compile-time sha256 strong ETags (conditional requests 304) on top of the existing immutable/no-store split; MIME, HEAD/405 and the traversal guard (..%2Funit test) moved fromServeDirinto the custom handler. crates/flux-server/build.rsdrives the embed (Meilisearch-style):rerun-if-changedover web sources/dist/lockfile/proto (a vite rebuild's NEW hashed names are invisible to cargo'sinclude_bytes!tracking — the build script rerun forces the macro to re-walk); invokesscripts/package-web.{sh,ps1}when the dist is missing/stale (node 24 + pnpm required); writes a placeholder index.html on toolchain-less checkouts so the binary still compiles (rust-embed validates the folder in ALL modes). Escapes:FLUX_WEB_UI_NO_BUILD=1skips the UI build;--no-default-featuresbuilds headless-only.- Per-path disk override with Gitea
custom/semantics —--web-assets-diror~/.flux/web-ui(only when it exists) shadows the embedded bundle file-by-file; anything absent falls through to the embedded base. The four-level asset-resolution chain collapses to override-dir-or-embedded. A stale override dir still carriesweb-ui-version.txtand warns at startup (the stamp advisory now guards the only surface that CAN go stale — the embedded side cannot mismatch by construction).
Removed
- The standalone
flux-web-ui.tar.gzrelease artifact (and theweb-ui/payload in platform archives) — the binary is self-contained; the script-installer flow is ONE command now, and the "② unpack the UI tarball into~/.flux/web-ui" README step is gone.web-ui/next to the binary is no longer a resolution level (re-populate~/.flux/web-ui/if you deliberately want a disk override). dist config:include = ["web-ui/"]and the[[dist.extra-artifacts]]block deleted;.github/build-setup.ymlnow supplies protoc + node/pnpm and lets build.rs build the UI inside the cargo build. flux-server ispublish = false+[package.metadata.dist] dist = true(crates.io was never a distribution channel, and the embedded folder lives outside the crate dir).
Fixed
- The workdir VALUE now actually reaches the model —
DEFAULT_PREAMBLE's comment promised per-chat workdir interpolation at thebegincall site, butcompose_system_promptused the workdir solely to look up the skill catalog and never injected the path, so the model's only programmatic source for the path wasstate_get("workdir")— a round-trip the state schema's own enum advertised. Nowcompose_system_promptstates the boundary line (path + "sandbox boundary, fixed for this conversation") before the catalog at every begin (stable-before-volatile: the boundary never moves, the catalog refreshes at rebuild gates), and the state enum drops theworkdirkey (INITIAL_STATEkeeps onlycurrent_dir) — the write refusal and the hidden read (get("workdir")serves the fixed field) stay.
Install flux-server 0.2.0
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/km0e/flux/releases/download/v0.2.0/flux-server-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/km0e/flux/releases/download/v0.2.0/flux-server-installer.ps1 | iex"Download flux-server 0.2.0
| File | Platform | Checksum |
|---|---|---|
| flux-server-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| flux-server-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| flux-server-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| flux-server-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| flux-server-x86_64-unknown-linux-gnu.tar.xz | x64 Linux | checksum |
0.1.5 - 2026-09-16
Release Notes
Added
- The Tier-1 skill catalog rides the system prompt — every connection begin (the initial spawn AND an in-place engine rebuild) composes a begin-time catalog snapshot into the system prompt (
flux-chat/src/skills.rs, new; the skill-format authority stays in flux-tools): the model sees what skills exist without spending askill_listround-trip.skill_listremains the live rescan,skill_readthe progressive-disclosure loader — the catalog is the passive overview, never a gate. skill_readactivation dedup is transcript-derived — aSkillActivationIndexre-derives each chat's already-activated skill keys from the history at the two assembly points, hashed on the skill-format authority's normalization (frontmatter-stripped manifest body; raw bytes otherwise): re-reading an activated skill collapses to a dedup note instead of a second full injection, and fork / engine rebuild / restart inherit the dedup for free — no dedicated persistence, no directory watch (an accepted tradeoff,docs/decisionsT-13).- The flux-server CLI is complete —
--version/-Vfollows the workspace release; every flag gains anFLUX_*environment fallback (--host→FLUX_HOST,--port→FLUX_PORT,--db-path→FLUX_DB_PATH,--preamble→FLUX_PREAMBLE,--no-web→FLUX_NO_WEB,--web-assets-dir→FLUX_WEB_ASSETS_DIR; precedence: CLI flag > env > default);--generate-completions <bash|zsh|fish|powershell|elvish>prints the completion script to stdout and exits before ANY logging or server setup (the stream stays clean for>> ~/.bash_completion);--helpwraps to terminal width (wrap_help) and ends in copy-pasteable examples. - The saved-model list is session-level — preloaded at session attach (
handlers.session_resumed, re-pulled on every reconnect) so the ContextMeter and the cost estimate read it without any dialog ever being opened; the pickers and the Providers panel only READ the store (mutation broadcasts keep it fresh) — the panel is an editor, not the loader.
Changed
- The web UI component library is unified —
components/uiis ONE library: control primitives split intobutton/fields/badge/spinnerbehind an index barrel with the Radix wrappers as siblings (the monolithicui.tsxdeleted; imports unchanged); the Settings family extracted fromdialogs/tocomponents/settings/(SettingsDialog + the Providers/Mcp/Skills panels + ModelSection + the shared building blocks, exintegration-ui.tsx), anddialogs/keeps the promise-shaped family (registry.tsx, eximpl.tsx, + ConfirmDialog/NewChatDialog/QuestionCard); tests re-pointed. --fx-*text tokens are WCAG-AA calibrated — faint lifts from ~3:1 to 4.5+ on panel/elev (light#6b7772, dark#7d8b87) without merging into muted; light semantics + code voice clear 4.5 on inset; the audit is recorded intokens.csscomments. Alongside: empty-state rhythm (a larger brand wave, tuned spacing, the prompt rows borrowing the sidebar's 2px selection-rail vocabulary — inset shadow, no layout shift) and type/radius discipline (the topbar unread badge on the token scale, FileIcon radius on xs, the provider chip back to the control radius).- The models.dev catalog is served through an
Arc— cache hits clone theArc, never the multi-MB catalog map (thousands of models) per hit. - Tests are hermetic by construction — the proxy/
FLUX_*-stripping ctor guard (a hosthttp_proxyhijacked in-process reqwest clients into 502s — 17 phantom grpc failures — andFLUX_*vars leaked through the env into e2e server children) and the proxy-proof loopback test client moved into a dedicated DEV-DEPENDENCY-ONLY crate,flux-test-support(test_env_guard!; the var list lives once, and the crate docs forbid regular dependencies — the guard mutates process env). Wired into flux-server (main.rs + the separateconnect_e2e.rsbinary, replacing two copy-pasted ctor modules), flux-provider (lib tests + the separatetests/models.rsbinary) and flux-mcp's Streamable HTTP test;scripts/test.shstill scopesno_proxyto loopback for the workspace run.
Fixed
- Switching between already-open conversations no longer flashes — two compounding causes, both removed. (1)
chat_closeis a full exit (unsubscribe + release), so every switch back re-claimed, the server re-delivered the FULL history snapshot, and the client tore the pane down and rebuilt it unconditionally — 60× markdown+highlight on the main thread plus a whole-pagemsgInreplay, landing right after the pane had already faded in.renderHistoryMessagesnow keeps a per-pane fingerprint of the rendered snapshot (message count + last row id/content-length — the transcript is append-only) and skips the entire rebuild when the incoming snapshot is identical: DOM, scroll position, stick state and any "load earlier" pages survive untouched (a rebuild used to drop the loaded earlier pages on every switch). The DOM-presence gate keeps a wiped stale pane (whose cursor survives in the WeakMap) on the full-render path, and snapshots changed elsewhere while away (another window streamed) miss the fingerprint and re-render exactly as before. (2)switchToChatfaded the target pane in from opacity 0 WHILE the outgoing pane faded out, dipping both to ~50% opacity at the transition midpoint — the background bleeding through read as the flash. A populated target now reveals instantly (the outgoing pane dissolves on top of it — a true crossfade); only a blank pane (first open / post-evict, still awaiting its snapshot) keeps the fade-in.
Install flux-server 0.1.5
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/km0e/flux/releases/download/v0.1.5/flux-server-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/km0e/flux/releases/download/v0.1.5/flux-server-installer.ps1 | iex"Download flux-server 0.1.5
| File | Platform | Checksum |
|---|---|---|
| flux-server-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| flux-server-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| flux-server-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| flux-server-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| flux-server-x86_64-unknown-linux-gnu.tar.xz | x64 Linux | checksum |
0.1.4 - 2026-09-16
Release Notes
Added
-
The document title is a state surface — the tab / PWA switcher / window list now reads
(2) Support — working… — Fluxinstead of a staticFlux: the active conversation's name, aworking…mark while its round streams, and a(n)counter of background attention (rounds finishing or questions arriving in a NON-active chat while the tab is HIDDEN — a visible window already shows panes and toasts). Written on state transitions only (never per delta); the counter clears when the page becomes visible again (the browser-tab convention). Composition lives inservices/title.ts, wired by one transition-gated subscription in mount. -
Escape closes the mobile sidebar drawer — the drawer is a fixed overlay without a Radix layer, so until now nothing dismissed it from the keyboard (the backdrop is mouse-only). It is the topmost surface while open: Escape closes it first and does not also cancel a streaming round underneath.
-
MCP over Streamable HTTP — the MCP launch list gains a second transport: rows are now
stdio(local child processes, unchanged) orhttp(a remote Streamable HTTP endpoint, the spec's other standard transport and the registry's dominant remote form). One row shape (mcp_servers.kind), one connect config (McpServerConfig::Stdio | Http), and the ENTIRE downstream machinery untouched — handshake, tool wrapping,tools/list_changeddual-path notices, the notice gate, the self-healing supervisor (a dropped HTTP session is the sameQuitReason::Closedthe backoff loop already respawns; the transport's own SSE-retry + session-recovery + re-init-on-404 ring absorbs transient blips inside), tool registration and engine rebuilds. HTTP rows carryurl+headers— auth rides as an ordinary header (e.g.Authorization); header VALUES live in the database and never leave the server (secrets parity with env values / api_key), wire summaries carryheader_keysonly. Stateless servers accepted (allow_stateless); proxies come from the server process environment (http_proxy/https_proxy/all_proxy) with zero config — loopback targets are exempt (a127.0.0.1/localhostendpoint is a LOCAL server, the stdio tier's sibling; an ambient proxy that cannot reach the user's loopback would answer 502). -
MCP panel transport picker — the add form opens with a two-segment transport control (Command / URL in the app's segmented-tab language): stdio keeps the command/args/env fields, http shows url + headers (
KEY=VALUEper line, the env parser's grammar). Rows and previews render the connect line per kind (launch line vs endpoint URL) with header-key badges. -
Per-chat composer drafts — switching conversations no longer loses the half-typed message. Each chat's pending composer text is saved when the composer unmounts (a switch away) and restored on return, keyed by chat id (
services/drafts.ts; LRU-capped at 64, write-through to sessionStorage so a page reload restores too; writes only on save/clear, never per keystroke). Cleared on send; a fork's redo-turn prefill wins over the saved draft. Deletion paths (the authoritative chats broadcast, the sidebar confirm flow) prune drafts by id — deliberately NOT viaclearChatPane, since pane LRU eviction ≠ chat deletion and the draft must survive an eviction.
Changed
- The streaming caret is gone — the blinking
▍at the live text tail is removed (CSS only). Streaming state stays visible through the typing indicator, the Stop button, and the composer's streaming border.
Fixed
- Composer sends during the disconnect window actually queue now — the connection manager gated queueing on "an abort controller exists", but that object stays alive across the entire disconnect window (stream end → next backoff attempt, indefinitely on
failed), so a message typed while the banner promised "queued messages will be sent on reconnect" instead fired a doomed RPC straight into the outage and surfaced as a spurious internal-error bubble. The gate now rides an explicit stream-attached flag (set by thereadyhandshake, cleared by connect/dispose/disconnect), an in-flight send that loses the stream mid-flight re-queues on a bare transport failure (definitive lease-gate statuses still map to their error frames), and the reconnect flush stops the moment the stream detaches (each failed send re-queues itself, so the drain loop cannot spin). - The assistant's question card is answerable again in multi-chat sessions — the inline
questiontool card mounted into.chat-pane:not([hidden]), but panes hide with inlinedisplay:none(never thehiddenattribute), so the query always hit the FIRST-created pane: with more than one visited conversation, the question (and its option buttons) materialized inside a background pane, invisible. Mounting is now by chat id (question-card-<chatId>), a question for a background chat pushes a discoverability toast, a pane wipe / chat deletion resolves the pending answer promise as dismissed through a new pending-question registry (services/dialogs) instead of hanging forever, and a newer question supersedes an older pending one. - Escape no longer cancels the streaming round while a Radix surface is open — the window-level Escape handler fired alongside Radix's DismissableLayer (which does not stop propagation), so closing the Settings dialog or a row menu also killed the live round. The handler now ignores consumed events (
defaultPrevented) and bails when an openrole="dialog"/role="menu"/role="listbox"is mounted. - The mobile drawer no longer hides its covered content from keyboard and AT users — the drawer is a fixed overlay on narrow viewports, but Tab focus walked straight through it into the main content it visually covers (and the dock's full-screen sheet behind it). While the drawer is open, the covered surfaces (
#main,#right-dock) goinertvia auseCoveredByDrawerhook (mobile regime only — the desktop sidebar is a side-by-side sibling, never an overlay); the TopBar stays outside so its toggle remains the way to close. - The sidebar resizer is keyboard-operable — it carried
role="separator"but no tabindex or arrow handling, unlike the dock's resizer (WCAG 2.1.1). Both separators now share one keyboard contract (edgeResizeKeysin the resize hook: ±24px per press, committed and persisted at once, direction named by the handle's side) and expose splitter ARIA (tabindex,aria-valuenow/min/max). - A cancelled resize gesture commits and detaches —
pointercancel(touch interrupted by a call, the browser claiming the pointer) left the drag listeners and the body drag-class attached, so the next pointermove dragged a dead gesture; the cancel path now commits the last moved width (the cancel event itself carries no meaningful coordinates) and fully detaches. - The Explorer's auto-refresh tick survives directory loads — the interval effect keyed on the
refreshcallback (which changes with everyloaded/workdir mutation), tearing down and re-arming on each load; a directory expanded within 15s of every previous load would never reach a tick. The tick is mount-once and reads the latest refresh through a ref. - Dock tab strip is a real ARIA tablist — tabs carried
role="tab"with no tablist, no panel association and a permanently-zero tabindex. The strip now ownsrole="tablist"with roving tabindex and Left/Right/Home/End activation; each tab names its panel (aria-controls/aria-labelledbyon the single swapping tabpanel). - A
chatCreatedelement without a chat payload is dropped and logged — the translation used a non-null assertion (a malformed element would throw into the stream loop's catch as an opaque failure); it now throws a named error the loop reports precisely. - bash tool output is ANSI-stripped — programs whose logging emits color unconditionally (e.g.
tracingsubscribers with the ansi feature; the runner's capture pipes already silence isatty-aware programs) no longer leak escape sequences like[2m…[0minto the tool result, the model context, and the UI card.strip_ansilives in flux-tools and is applied ONLY in the bash tool by design — file tools (read_file/grep) stay byte-faithful so the model's edits round-trip against the real file content (T-12).
Install flux-server 0.1.4
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/km0e/flux/releases/download/v0.1.4/flux-server-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/km0e/flux/releases/download/v0.1.4/flux-server-installer.ps1 | iex"Download flux-server 0.1.4
| File | Platform | Checksum |
|---|---|---|
| flux-server-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| flux-server-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| flux-server-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| flux-server-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| [flux-server-x86_64-unknown-linux-gnu.tar.xz]... |
0.1.3 - 2026-09-15
Release Notes
Added
- MCP
tools/list_changed→ live re-sync — a server's tool list change now lands without a restart. Two spec-dependent receive paths, verified against the official specs: spec ≤ 2025-06-18 pushes unsolicited (handler hook); spec 2026-07-28 notifies only subscriptions/listen streams — flux opens one fortoolsListChangedand pumps it into the same sink (rmcp routes subscription notifications exclusively to the channel, so the paths never double-fire; a legacy server's rejection degrades to the hook). Re-list + re-register over the old set, then the established rebuild + broadcast. - MCP server log notices forwarded to the UI — each server's log messages ride a per-server token bucket (burst 5, sustained 10/min, 500-char message cap, drops folded into a synthetic
suppressednotice) onto a session-levelmcp_noticestream element (fire-and-forget, outside R2 reconciliation): warning+ surface as toasts, and the TopBar gains a bell (newest first, cap 100, unread badge). Thenotifications/messagedeprecation (SEP-2577, spec 2026-07-28) is handled with a scoped allow — it remains what deployed servers emit; progress notifications are deliberately NOT forwarded (request-scoped, token mapping would be guesswork). - Round-artifacts dock tab — the current round's touched files and tool invocations, folded declaratively from the stream's
tool_startevents (write/edit/replace_lines → path; bash → command label; MCP tools by name; read-class/plumbing ignored). A user message resets it, a history snapshot rebuilds it from the last user message, a reconnect clears it. File rows open preview tabs (the fs-layer mock keeps intercepting); invocation rows pulse their tool card in the stream. - MCP panel UX — per-tool registration results ride the AddServer ack (partial success stays success; a skipped tool carries its reason — collision/reserved — visible in the UI, never only in the log);
McpServerSummarygainstool_names(the live session's registered set, state-distinguished empty); the add form's env field explains the no-inheritance / never-echoed contract in place.
Changed
- CI actions moved to node24 targets — the node20 deprecation warnings cleared across the workflows and the release build-setup fragment.
Fixed
- rustls 0.23.43 → 0.23.45 — RUSTSEC-2026-0285.
Install flux-server 0.1.3
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/km0e/flux/releases/download/v0.1.3/flux-server-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/km0e/flux/releases/download/v0.1.3/flux-server-installer.ps1 | iex"Download flux-server 0.1.3
| File | Platform | Checksum |
|---|---|---|
| flux-server-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| flux-server-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| flux-server-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| flux-server-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| flux-server-x86_64-unknown-linux-gnu.tar.xz | x64 Linux | checksum |
0.1.2 - 2026-09-14
Release Notes
Added
- models.dev catalog status in the log — the lazy catalog was completely silent: a fresh download logs at
infowith provider/model entry counts and elapsed time, every failure path (request, body read, HTTP status, parse) logs awarn, local cache hits log atdebugwith the cache age, and save-time enrichment logs its outcome. The default log filter is nowINFOwhenRUST_LOGis unset — an emptyEnvFilterpasses errors only, which silenced startup and runtime logs alike. - Providers panel: filterable model catalog — the probed upstream catalog (per provider) gains a live id-substring filter with a shown/total count, so import discovery works on catalogs that run to hundreds of ids.
- models.dev matching ladder — a catalog miss no longer silently drops enrichment. Matching is a three-stage best-effort ladder: literal forms (exact,
vendor/-stripped,:tag-stripped), canonical equality (case +./-spelling folded — vendors swap the two freely), and a canonical dash-bounded prefix fallback (dated snapshots like-260828or-2024-08-06extend a catalog base id; the longest catalog id wins). The zhipu/z.ai host family (api.z.ai,open.bigmodel.cn,api.zhipu.ai) anchors its GLM sections. An aliased match always reports the catalog's own id, and the saved-model row surfaces it inline (matched: provider/model+ badge tooltip) — a wrong match is visible, never silent.
Changed
- Frontend package manager: npm → pnpm — strict, symlinked resolution makes phantom-dependency imports structurally impossible; the pnpm version is pinned via
packageManagerand the scripts self-provision it when missing; the buf CLI ridesclients/webdevDependencies (no global installs); dependency build scripts are deny-by-default with an allowlist for the first-party toolchain (esbuild, @bufbuild/buf). Fixes the fresh-machine failurebuf: command not found(the old skip-if-exists install could keep a stale install whose.binlacked buf). - Node 24 LTS is the ONE supported frontend line —
enginesdeclares>=24 <25(pnpm warns on anything else without breaking); CI and the release pipeline pin Node 24 explicitly; the scripts echo the resolved toolchain versions (node,pnpm) into the build log so environment drift is visible without digging. - The chat pickers' model datalist carries ONLY saved models — the probed upstream catalog never bloats it; discovery/import lives in the Providers dialog, and free text still pins (the registry is a convenience, never a gate).
Fixed
- Model removal broadcasts the fresh list — removing a saved model acked cleanly but never broadcast, so the deleted row stayed visible until the dialog was reopened; the removal now broadcasts the fresh list and rebuilds the matching chats (pinned params fall back to unset), symmetric with save.
- pnpm 11's build-allowlist key —
onlyBuiltDependencieswas renamed toallowBuildsin pnpm 11; a fresh install failed withERR_PNPM_IGNORED_BUILDSbefore buf could resolve. The workspace yaml now uses the v11 key. - Script noise on newer Node — pnpm 11's vendored
debugprobes storage backends and prints a localStorageExperimentalWarningon every invocation on affected machines; the scripts suppress exactly that warning for their own node processes (guarded by a probe, so old Node that rejects the flag is unaffected).
Install flux-server 0.1.2
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/km0e/flux/releases/download/v0.1.2/flux-server-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/km0e/flux/releases/download/v0.1.2/flux-server-installer.ps1 | iex"Download flux-server 0.1.2
| File | Platform | Checksum |
|---|---|---|
| flux-server-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| flux-server-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| flux-server-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| flux-server-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| flux-server-x86_64-unknown-linux-gnu.tar.xz | x64 Linux | checksum |
v0.1.1
Install flux-server 0.1.1
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/km0e/flux/releases/download/v0.1.1/flux-server-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/km0e/flux/releases/download/v0.1.1/flux-server-installer.ps1 | iex"Download flux-server 0.1.1
| File | Platform | Checksum |
|---|---|---|
| flux-server-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| flux-server-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| flux-server-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| flux-server-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| flux-server-x86_64-unknown-linux-gnu.tar.xz | x64 Linux | checksum |
v0.1.0
Install flux-server 0.1.0
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/km0e/flux/releases/download/v0.1.0/flux-server-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/km0e/flux/releases/download/v0.1.0/flux-server-installer.ps1 | iex"Download flux-server 0.1.0
| File | Platform | Checksum |
|---|---|---|
| flux-server-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| flux-server-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| flux-server-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| flux-server-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| flux-server-x86_64-unknown-linux-gnu.tar.xz | x64 Linux | checksum |