Skip to content

VAF v0.1.0a24

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 23 Aug 14:15
· 61 commits to main since this release

Fixed

  • The searching magnifier is back, at full size. The scene where the agent
    hunts with a magnifier and the search dust flares up had been switched off in
    the actions timeline because it was assumed there was no room for it. There
    usually is: the timeline now measures the space beside the chat column and
    plays the whole scene whenever it fits, which on a normal window is always.
    Only a genuinely cramped column, or a phone, falls back - and that fallback is
    no longer a figure merely glancing about, but a compact magnifier sweeping
    inside the agent's own square, on the same clock and in the same phase as the
    glance.

  • A chat no longer dies with a provider error when the agent corrects
    itself.
    When the agent caught itself claiming a tool result it had not
    received, it re-asked with a correction, and on Veyllo that second request
    came back as an error twice in a row, leaving the raw error text where the
    answer belonged. Measured against the gateway: it refuses a request in which
    the agent speaks again after a tool result without the conversation being
    handed back to the person. The correction now travels as such a turn, which
    is what the same code already does for local models, and the identical
    request that failed comes back answered.

  • A reopened chat shows its own pictures again. A picture sent with a
    message was stored and was still on the server, but reopening the chat left
    the message without it: the browser rebuilt each message field by field when
    it loaded a conversation, and the attachments were not among the fields it
    carried over. They are now, so a chat looks the same after a reload as it did
    when it was written.

  • Copying now works where it quietly did nothing. Six places offered a copy
    button that could fail without saying so: the verification codes in the
    Telegram and Discord setup, the device code in the GitHub setup, the network
    address in Settings, a newly created user's password, and the two-factor key
    during login. Reached over the local network rather than on the machine
    itself, the browser does not offer the clipboard at all there, so the press
    either did nothing or broke the dialog it was in. All of them now fall back to
    a way of copying that works anywhere.

  • A link inside a chat's workspace can no longer lead out of it. Every
    workspace lane (browsing, upload, delete and the new folder action) decided
    "is this still inside the chat's folder" by comparing text. A symbolic link
    that lives inside the workspace and points elsewhere passes that test, so the
    read or write landed at the link's target instead. Containment is now decided
    on resolved paths, for all four lanes at once, and a folder or file name that
    carries a separator or an invisible character is refused as bad input instead
    of failing as an internal error.

  • Idle windows stop pretending to work. A specialist window opened by hand
    used to claim activity that did not exist: the Coder said "Planning…", every
    window's banner said "Starting - waiting for the agent…", the Librarian said
    "Scanning", consoles said "Waiting for output…" - all with no run behind
    them. Every one of these now tells the truth while idle, and the spinners
    only spin when something actually runs.

  • You are no longer offered tools your account cannot use. The per-user tool
    permission was enforced only when a tool was actually called, so a tool an
    administrator had disabled still appeared in the / suggestions - and the
    refusal arrived after you picked it. Every tool list the app sends now carries
    only what that account may run, and it errs on the side of hiding an entry if
    the permission lookup fails.

  • Escape closes the workspace window, and closes exactly one thing. The
    chat's workspace explorer could only be left through the X in its header:
    Escape did nothing there, so a delete confirmation, which covers that X, left
    no way out but answering it. Escape now steps back one level, innermost
    first: the delete confirmation, then the right-click menu, then a half-typed
    folder name, then the search box, then the window. It is also no longer a
    press that dismisses two things at once. The app answers Escape in one place
    now and hands it to the topmost thing on screen only, so a panel open beside
    the chat no longer closes underneath the dialog you meant to leave.

  • Reopening the workspace window lands where you expect. Closing it kept
    the folder you had drilled into, the workspace you had opened from the list
    of all workspaces, and the search you had typed, so opening it again from a
    chat could show another chat's files, or that list instead of the chat's own
    folder, with a Back button that looked live and did nothing. Closing it now
    clears all of that.

Added

  • A specialist's window shows the specialist. The generic glyph in the top
    left of every sub-agent window is now the agent itself, wearing its trade's
    colour, so a window is recognisable as that specialist's workplace at a
    glance. All eight window headers draw the same seat, and the kinds and their
    colours are declared once instead of in three places.
  • Each specialist wears its own colour in the picker. In the sub-agent
    window the researcher, the document writer and the librarian now carry their
    trade's colour in the agent's body while the eye stays white, so you can tell
    them apart before reading the name. The coder keeps the plain body: its
    workplace is a code editor, black and white like the tools it imitates.
  • Every chat has a header, and its name is where you rename it. The chat's
    name now stands at the top of the conversation, and clicking it renames the
    chat right there, through the same lane the sidebar has always used. The
    header has no edge of its own: it is the same colour as the page behind it,
    and the text scrolling up dissolves into it instead of sliding under a line.
    On a phone the app's own top bar carries the name instead, so there is still
    only one bar.
  • The specialists sit in that header now, left to right. The globe, the
    specialists you picked and the plus that adds one used to run down the right
    edge of the conversation; they now run along the header, in the same order,
    and the plus stays in one place instead of sliding down as specialists
    arrive. Nothing about picking or opening them changed.
  • An agent reply can be read aloud, saved, copied and asked again. Next to
    the timestamp under every reply sit four small actions. Read aloud moved here
    from beside the bubble, where it sat alone and read as a different kind of
    control than the rest; it now also reaches replies from a turn that used
    tools, which never had it. Save writes that reply
    to a Markdown file through your own system's Save dialog. Copy puts the same
    Markdown on the clipboard, and now works over the local network too, where
    the browser's own clipboard is simply not available and copying used to do
    nothing at all. Ask again is offered on the newest reply only, and only while
    the chat is idle: it asks once before it fires, then discards that exchange
    and puts the same question again. It is refused while an answer is still
    being written and while a specialist of that chat is still working, so
    nothing that is still being produced can be thrown away.
  • The workspace window gets a right-click menu. Right-clicking the file
    area of a chat's workspace offers "New folder" (a draft tile in the grid:
    type the name, Enter creates it) and "Upload files"; right-clicking a
    folder adds "Open folder" and "Delete folder". Until now a folder could
    only be created here from the Coder window's idle Explorer.
  • vaf.contained_path, vaf.safe_entry_name, vaf.PathEscape: keeping a
    path that came from outside inside the directory it may touch, now part of the
    public framework surface. Containment is decided on resolved paths, so a
    symlink cannot carry a caller out of the root, and a path that does not exist
    yet still gets an answer, which is what a tool needs before it creates
    anything. See docs/EMBEDDING.md.
  • Specialist windows are useful while they rest, and windows you opened
    stay yours.
    Idle now moves INTO each window's own face instead of
    replacing it. The Librarian shows this chat's workspace inside its own
    folder view - the same explorer it uses when working, only now the rows
    are yours to click: folders drill in, files open straight into the right
    viewer (code, document, image, web page), until the Librarian itself
    starts working. The Coder keeps its editor and, like any editor before a
    project is opened, says so - while its Explorer offers the workspace, and
    a clicked file opens read-only in its own tabs. Whatever folder you are
    looking at travels with your next message, so "sort this" means the
    folder on your screen. In the Coder you can also mark a folder as the
    project - the next run then continues in exactly that folder instead of
    creating a fresh one; the welcome screen offers the folders around you as
    one-click picks, and a right-click in the Explorer opens a small context
    menu that can pick a project, open a folder, or create a new folder right
    there in the workspace. And every hand-opened specialist window follows
    the browser's rule: it never closes itself after a run, successful or
    not - only your own close or a chat switch ends it. A window that already
    shows a finished run keeps showing it - that result is the useful view
    after work.
  • Your specialists now have a window you can just open. Clicking a
    sub-agent in the rail opens its window whether or not it is running, the way
    the globe opens the browser, and clicking it again slides it away. While a
    window is open the message box shows which specialist you are in - and the
    agent is told the same thing, so "fix the failing test" typed with the Coder
    open is understood as being about that work, and the agent knows it can hand
    it straight to the Coder instead of answering into the void. Each specialist
    wears its own colour there - the one its window already uses, so the
    librarian is orange, the document writer teal, the researcher violet and the
    browser stays blue; the coder keeps the black and white of a code editor.
  • A hotbar for the sub-agents. A plus below the globe opens a panel with the
    specialists the agent can hand work to - each with its own trade symbol - and
    the ones you pick sit in the rail from then on, one click from any chat. The
    choice belongs to your account, not to the browser, so a second person signing
    in on the same machine gets their own rail; and a specialist an administrator
    has not granted you never appears in the panel at all.
  • The coder now proves its work before claiming it, and you can watch the
    guardrails fire.
    Three deterministic gates joined the coder's loop:
    finishing a task is blocked while changes have not been verified (a green
    run_tests, or a clean render_check for web pages - only where such a
    lane exists, and degrading to an explicit "untested" note instead of
    looping when the environment cannot verify); editing a file the run has
    never read is refused ("read it first" - editing from memory is where
    doom loops start); and creating a file whose name matches an existing one
    points out the sibling instead of letting a duplicate be born. Every gate
    block, stuck-detection firing and context reset now also reports into a
    new Guards tab in the coder window's bottom panel, so the run's
    self-corrections are visible instead of terminal-only. Lint feedback is
    immediate on BOTH write lanes now - an edited file used to stay unlinted
    until the task ended - and a fixed file stops blocking completion the
    moment its latest lint passes. A lifecycle stepper above the task list
    shows where the run is (plan, build, document, commit) - the
    documentation pass and the final commit used to happen invisibly - and
    test runs and render checks now announce themselves in the live action
    line instead of looking like a frozen editor.
  • The coder can now look at the page it just built. A new render_check
    tool opens an HTML file from the project (or a URL) in the sandbox browser
    and reports what a developer checks first: page errors, console output,
    failed requests, the rendered text, and a screenshot. The coder gets it as
    an inner tool next to run_tests - write the page, render it, read what
    actually happened, fix, render again - and the chat agent gets it as
    render_check (the screenshot lands in the chat workspace for
    analyze_image). Dev servers on the host are reachable via rewritten
    localhost URLs when they listen on 0.0.0.0. While you or an agent run
    are using the browser, the probe answers busy instead of taking over your
    tab.
  • Uploading in the browser starts from your own files. The sandbox
    browser's file picker used to open into an empty container home - your
    files simply did not exist in there. Your file area is now mirrored into
    the browser (kept fresh within seconds, size-capped), the picker opens in
    it with a Workspace bookmark in the sidebar, and agent runs can attach
    exactly those files through their upload action - nothing else. A change
    of user wipes the mirror unread; VAF_BROWSER_WORKSPACE_SYNC=off turns
    the mirroring off entirely.
  • Browser downloads land in your files, not in the sandbox. A file
    downloaded in the sandbox browser - by you or by the agent - used to exist
    only inside the browser container, where nobody could reach it. Finished
    downloads now appear in your own file area (Downloads folder) within
    seconds, after passing the same threat scan every other arriving file gets;
    blocked files are refused and show up in the security dashboard. Downloads
    belong to whoever holds the browser, a change of user purges leftovers
    unread, and VAF_BROWSER_DOWNLOADS=off disables downloading entirely,
    enforced in the browser itself.
  • The browser agent can see now - and it browses smarter. With a
    vision-capable model, browser runs take screenshots on demand and stop
    scrolling blind: scroll and layout decisions finally have eyes. Without
    one, nothing breaks - a configured vision model describes screenshots on
    request, and with no vision at all the run simply continues on the page
    text as before. A new "Browser agent model" setting (Settings, AI & Model)
    gives browser runs a dedicated strong vision model without changing your
    chat model. Runs are also coached to navigate efficiently (jump to matching
    text instead of scrolling one screen per reasoning step), and a new
    one-step action reads a whole page at once, lazy-loaded content included.
    The underlying browser-use engine moved from 0.13.1 to 0.13.8.
  • The chat centres its text for as long as there is room, and only shifts
    left when the column gets tight.
    The conversation used to centre twice
    over - the container as a block and every message row again inside it - so
    in a narrow column (a browser window or viewer docked on the right) a wide
    empty margin sat on the left while the text starved. The rows now follow
    the column's actual width: plenty of room means centred exactly as always,
    even with a panel open on a wide monitor; a genuinely tight column moves
    the text to the left edge and gives it the space - live, including while
    the panel is being resized. The sidebar's hover expansion also floats over
    the conversation now instead of pushing it sideways.
  • Every user gets a browser of their own, in parallel. Two people at a
    time now each get their own browser container with its own persistent
    profile and its own container network: history, saved passwords and
    downloads are genuinely per-user, no user's browser can reach another's,
    "busy" between users disappears, and two people (or their agents) browse at
    the same time. Settings > Advanced > Browser pool sets how many browsers may
    run at once (admin only, two by default, zero switches the feature off);
    budget about 2 GB of RAM per user you raise it by. The pool refuses new
    browsers when memory runs low, parks idle ones after a while (keeping their
    data), and falls back to the shared browser with its handover scrub whenever
    it cannot serve.
  • The shared browser forgets the previous user when it changes hands. On
    any change of user - an interactive session, an agent run, or an unknown
    state after a server restart - the sandbox browser now scrubs cookies and
    every site's stored data (localStorage, IndexedDB and friends) before the
    next user touches it; previously only cookies were cleared, and the agent
    lane inherited whatever the last user left behind. A non-persistent
    browser_agent run now truly starts clean, as its description always
    promised. VAF_BROWSER_SCRUB=full deepens the handover to a whole-profile
    wipe (history, browser-saved passwords, autofill, downloads) with a short
    browser relaunch.
  • The sandbox browser blocks ads and malware domains. uBlock Origin Lite
    (the official release build, version-pinned and checksum-verified at image
    build) now rides in the browser container and filters ads, trackers and
    malicious ad payloads for hand-driven and agent-driven browsing alike. DNS
    resolution goes through Cloudflare's security resolver (1.1.1.2, malware and
    phishing blocking - deliberately not the family variant, which censors
    content): encrypted via DNS-over-HTTPS where reachable, with the container's
    plain DNS pointed at the same resolver as the fallback. Takes effect after a
    browser image rebuild.
  • Watching the agent browse now shows the real browser. During a
    browser_agent run started from a chat, the browser window streams the
    sandbox Chromium itself - real tab strip, real omnibox, live - instead of a
    rebuilt address bar over 1.5-second screenshots. The stream is watch-only
    (the run must not be typed into) and is offered only to the chat that owns
    the run; the task, action plan, history and activity panels stay below the
    viewport. Where no live stream exists (workflow tile, spawned child runs)
    the screenshot view remains.
  • An agent run only borrows your browser. If a browser_agent run takes
    over while you are driving the sandbox browser, the window no longer closes
    on you when the run ends: the server remembers whose interactive session was
    evicted and hands the browser straight back, and the window returns to the
    interactive mode by itself. A short handover veil plays in both directions -
    you to agent, agent back to you - so the change of hands reads as one motion.
    Runs that took a free browser close the window afterwards, as before.
  • The browser window closes with an animation. Closing used to snap the
    window away while the panel beside it collapsed smoothly; the slide-out now
    plays on close too, and the interactive stream is stopped only after it has
    played.
  • The agent knows it can take the browser over from you. While you are
    driving the sandbox browser, your messages already carried the page, your
    selection and a screenshot along - but nothing said what the agent could do
    about it, so asking it to open a page and wait got you an answer instead of
    an action. That turn now also says that browser_agent drives the very same
    browser: it takes over for the length of the run, hands control back when it
    finishes, and sees whatever you are logged into.
  • The sandbox browser is now yours to drive. Opening the browser window
    (the globe in the chat's top-right corner) with no agent run underway shows
    the sandbox Chromium fullscreen and USABLE: click, type, scroll, use
    Chromium's own tabs and omnibox, streamed at up to 60 fps over a KasmVNC
    lane instead of the agent view's 1.5-second screenshots. Logins persist the
    way they do in any browser - the browser itself asks whether to remember
    them, no extra switch in our chrome - and they land in your personal browser
    store, the same one the agent's persistent sessions read, so a login you
    performed by hand is a login the agent has on its next run. One person
    drives at a time;
    another user asking sees "busy" without learning who. While you drive, your
    chat knows it: a small "Browser" chip appears beside the workspace chip, and
    every message you send carries the page you are on, any selected text, and a
    screenshot of your current view - as a normal image attachment the agent can
    see. The agent always wins:
    when a browser_agent run starts it takes the browser back and the window
    returns to the familiar task/actions/history view, and when the run ends the
    interactive browser is one click away again. The stream reaches the page
    only through the VAF server with a per-session ticket; the container port
    stays loopback-only.
  • The chat and the side panel share the space you give them. The border
    between the chat and the right panel carries a drag handle: pull it to
    resize both, with sensible minimum widths on each side, double-click to go
    back to the automatic layout. The chosen width survives reloads.
  • A browser button in the chat, so the browser window is no longer only
    reachable while an agent happens to be using it.
    A globe in the chat's
    top-right corner, on the same line as the sidebar logo, opens and closes the
    Browser Agent window; while the window is open the button stays marked. It
    opens a window rather than starting anything: browser_agent is a tool the
    agent calls, so what the button shows is what actually exists - the last
    browser run's screenshot, visited URLs and action plan after a run, and an
    empty browser saying "No browser session yet" when there was none. That
    empty state replaces a "Starting Browser Agent" banner which, in a window
    opened by hand, announced an agent nobody had called. While another
    sub-agent is actually running, the button stands down and says so, so a
    click can never hide live work behind a browser view.
  • The agent can now update a memory instead of saving it twice. A new
    memory_update tool rewrites an existing long-term memory in place - the
    same operation the Memory page has always offered, now available to the
    agent itself. And memory_save checks first: when a nearly identical memory
    already exists, it does not write a twin but answers with the existing
    memory and its id, and the agent decides - update it, or insist on a
    separate save. Memory search results now name each memory's id so the agent
    can refer to one. The duplicate check is deliberately careful in the other
    direction too: if it cannot run (memory database down), saving simply
    proceeds, so a check never stands between "remember this" and the save.
    Learned documents are protected: a section of a learned PDF is a record of
    what that source says, so memory_update refuses it and points to the
    honest lanes instead - learn the newer document version, or save a
    correcting note that is retrieved alongside the section. Nothing valuable
    can be silently overwritten that way.
  • Tools are grouped into bundles instead of one long wall of cards. Settings
    -> Available Tools now shows one card per integration or area - GitHub,
    WhatsApp, Telegram, Email, Calendar and so on - with the number of tools in
    it and how many of them VAF has learned. Clicking a bundle opens a shelf
    across the full width, directly under the card you clicked, holding that
    bundle's tools; a notch on the card points at the shelf it opened. The rest of
    the grid keeps its place, so opening a bundle no longer rearranges everything
    else on screen. Searching still searches tools: type anything and the grid
    switches to the matching tools, ungrouped. The same grouping now applies in
    the terminal - the vaf settings tool table and the terminal app's tool
    overlay print one section per bundle instead of 120 alphabetical rows, and the
    agent's own tool list is grouped too. Which bundle a tool belongs to is
    declared on the tool itself, so a tool you write - or a connected MCP server -
    can name a bundle of its own. Tools you upload yourself stay recognisable:
    they get their own bundles ("Custom GitHub" next to "GitHub", never inside
    it), and the custom-tool editor now has a bundle picker instead of expecting
    you to know the attribute by heart.
  • The tools window now says how much of your toolbox VAF has learned. Settings
    -> Available Tools counts the installed tools along the top: how many there
    are in total, how many VAF has trained itself on, and how many it has not.
    The numbers cover every installed tool, not only the ones matching the search
    box, so they agree with the "modules installed" line beside them. A tool whose
    training was invalidated because the tool itself changed counts as not learned
    until it is trained again, while its own card keeps saying "Stale". The header
    also names the tool that is being trained right now, whichever part of VAF
    started that run, and clicking the name opens the run's training window. A run
    started by a vaf command in its own terminal belongs to that process and is
    not shown here.
  • Around sixty mail providers now come pre-configured. Adding an IMAP
    account no longer needs server settings typed by hand for GMX, WEB.DE,
    mail.com, T-Online, IONOS, 1&1, freenet, Posteo, mailbox.org, Zoho, Fastmail,
    AOL, Yandex, Mail.ru, Vodafone, Bluewin, A1, Orange, La Poste, Libero and
    Seznam, alongside the Gmail, Outlook, Yahoo and iCloud entries that were
    already there. Every host was checked against the live server before it was
    added.
  • A dangerous file is now recognised everywhere, not just once. When an
    administrator deletes a quarantined skill, VAF keeps the verdict: it records
    the fingerprint of the bundle and of the files that earned the block in a
    machine-wide list. From then on, every place a file can arrive checks against
    that list before doing anything with it - chat attachments and images, the
    chat workspace upload, files put into a shared agent room, Telegram, Discord
    and WhatsApp media, mail attachments, cloud-sync downloads, and skill
    installs. A match is refused outright and appears on the security dashboard.
    Renaming the file or repacking it into a different bundle does not help: the
    content itself is what is recognised, using two independent secure hashes.
    Administrators can see and manage the list under Logs -> Overview, or from a
    terminal with vaf security threats list | check | add | remove. Removing an
    entry re-opens every lane at once, so it asks for the admin's 2FA code.
  • Files that merely look suspicious are pointed out, never blocked.
    Alongside the check above, uploaded text is scanned for patterns that are
    often unsafe - commands piped into a shell, embedded credentials, hidden
    characters used to smuggle instructions. Anything found is noted next to the
    file and on the dashboard, and the file is delivered as normal. Legitimate
    scripts do these things too, so this is information, not a refusal.

Changed

  • The Telegram and WhatsApp dashboards show the conversation inline.
    Selecting a chat now shows the actual conversation (user and bot bubbles,
    oldest at the top, auto-scrolled to the newest message) directly in the
    dashboard instead of a short "Incoming/Outgoing" list that needed a click to
    open a popup; the separate history popup is gone. Internal <think> blocks
    are stripped from this view since they are never sent to the channel. A
    search field on the conversation header works like Ctrl+F over the chat:
    matches are highlighted, Enter / Shift+Enter (or the arrow buttons) jump
    between them, Escape clears the search. The Memory Learning progress line
    now sits in the conversation header next to the title. The panel grows to fill the window height,
    so the Full access / Relay contacts panels sit at the bottom instead of
    floating above empty space. In the Telegram dashboard the "Open in Telegram"
    link moved into the dialog header next to the title, freeing the former
    "Chat with bot" block's space for the chart and the conversation.
  • The soul's Continuity section now names the agent's real memory lane.
    The default soul text (the onboarding wizard's suggestions and the fallback
    soul) told the agent to "read your memory files" - but the agent's long-term
    memory is not a file, it is its tools. The text now says exactly that:
    recall with memory_search before asking or guessing, persist what matters
    with memory_save, because a fact that was never saved is gone next
    session. Existing souls are not rewritten; the wording changes for new
    setups and for anyone re-running the Soul Wizard. The lane also no longer
    depends on the soul's text at all: the system prompt appends a short,
    fixed continuity note to the personality section that cannot be edited or
    removed with the soul, so an agent whose soul never mentions memory still
    knows its memory tools.
  • "Train tool now" asks before it starts, and says what the run costs. The
    button used to begin training the moment it was clicked, with nothing said
    about what that means. It now asks first, and the question states what was
    measured: training runs the tool for real dozens of times and makes dozens of
    model calls to learn from the results, and those calls count towards usage
    like any other; a run cannot be stopped once it has started and has no time
    limit; and if it is interrupted, the tool falls back to "Learning" until it is
    trained again. The safe answer carries the emphasis, because it is the one
    that costs nothing. What training does not do is hold anything else up: the
    agent, automations and other runs carry on beside it, and the only thing
    refused while a run is in flight is a second run of the same tool. Tools whose
    effect cannot be taken back - anything that sends a message - are never run
    during training at all.

Fixed

  • "Take over" now means take over, not start over. Asking the agent to
    take over while you are browsing hands it your actual session: the run is
    told which page you were on and continues there - your tab stays open, your
    logins stay live - instead of opening the site again in a tab of its own.
    A takeover run also no longer wipes the session it was handed, even when it
    would normally start with a clean browser. The way back mirrors it: whether
    the run finishes, fails or is stopped, you get the browser back exactly as
    the agent left it - tabs included - instead of a blanked window, with the
    same handover animation in both directions.

  • Closing the side panel finally plays its animation. If the panel's
    width had ever been adjusted by dragging its edge (the choice is
    remembered), every close snapped shut instantly instead of sliding: the
    dragged state carried an unbounded CSS max-width, and none cannot animate
    to the closed state's zero, so the browser clamped the width to nothing on
    the spot (measured: closed at 60ms of a 300ms transition). The cap is now a
    finite value that never binds, and the window keeps its content painted
    while the panel closes over it - the close is the open, played backwards.

  • A browser window you opened yourself never closes itself again. Opening
    the browser by hand now marks the window as yours until you close it: an
    agent run may still borrow the browser, but afterwards - whether the run
    succeeded, failed with a browser error, or was stopped - the window returns
    to your interactive session instead of auto-closing. Previously the mark was
    shared with every worker view and got reset the moment agent data streamed,
    so a failed takeover could close the browser over the error it caused.

  • The agent's browser view is the same window you drive. One browser, two
    modes: in agent mode the window keeps the exact frame of the interactive
    browser (header, viewport, status bar) and adds the Task, Activity and
    History panels beneath the viewport. The rebuilt tab strip and address bar
    are gone - the live stream shows Chromium's real UI, and where only
    screenshots exist (workflow runs) they appear on the same surface.

  • An automation's saved file now follows its message to the messenger. A
    prompt-based automation that sends its summary itself in-run (say, a morning
    weather text via Telegram) produces its output file only after the run, so
    that message could never carry the file - and the post-run push, the only
    lane that attaches it, was skipped entirely by the double-delivery guard.
    The guard now suppresses only the duplicate text: the produced file is still
    handed over as a follow-up document with a filename caption, on Telegram,
    WhatsApp and Discord alike. Workflow runs whose send step already attached
    the document stay at one copy - the second send is recognized and skipped.

  • The interactive browser looks and behaves like a browser now. The window
    is a real browser, with its own tab strip, address bar, bookmarks and
    downloads, themed dark to match the app - so everything a browser can do is
    there rather than a rebuilt fraction of it. The page fills the window instead
    of sitting in black bars, and the sub-window with Task, Actions, History and
    Activity stays away while you are driving, returning when the agent takes
    over. Opening it shows VAF's own quiet loading state until the picture is
    really there, rather than the stream viewer's foreign splash. Several smaller
    repairs went with it: streams no longer die after exactly 40 seconds of
    reading, scrolling no longer smears the page, and closing and re-opening the
    browser no longer leaves a second browser UI inside the window.

  • The interactive browser window now actually shows the browser. It fetched
    its viewer correctly and then stayed blank, because the server sends
    X-Frame-Options: DENY on every response and the window is a frame - so the
    browser downloaded the page and refused to paint it. That header is now
    SAMEORIGIN for the stream path only, which is the one page meant to be
    framed, and only by VAF's own UI. Two more defects on the same lane went with
    it: the viewer's address is no longer built with a guessed http://, which
    was wrong whenever LAN hosting with TLS is on (the backend port speaks HTTPS
    then and answered nothing), and the stream reaches LAN users at all now - the
    HTTPS proxy had no websocket route for it and its relay could only carry text
    frames, while a browser stream is binary from the first byte.

  • Quickly restarting VAF no longer leaves the Docker services dead. Quitting
    VAF stops the Docker stack in a background thread with up to 25 seconds of
    headroom; stopping and restarting VAF inside that window raced the old
    instance's stop against the new instance's startup, and the stop won - the
    new VAF ran with every container down (memory search empty, browser gone)
    until someone noticed. The stop now checks the containers' own start time
    first: restarted after the shutdown began means a new instance owns the
    stack, and the old stop stands down.

  • The tool self-learning loop no longer goes quiet for the tools you use
    most.
    Three repairs in the Whare Wananga lane. A tool whose definition
    changed (marked stale) was excluded from the proactive pitfall injection AND
    refused new lessons from live errors, so the agent repeated mistakes the
    system had already seen - stale records now keep learning from runtime
    surprises while their retraining is still owed. The known-pitfall matcher
    saw a real failure as novel although the record's first pitfall described
    exactly that trap, because most of the error's words were VAF's own error
    wrapping - the matcher now strips that wrapping and recognizes
    argument-contract errors by the argument they name. And the learning events
    are finally visible: a re-fed know-how, a newly learned pitfall, and
    know-how going stale (with how to retrain) now appear in the log timeline
    instead of only in a debug file nobody reads.

  • Nothing a tool learned from real use is silently forgotten anymore. Two
    quiet loss lanes: when a tool's pitfall list was full, the newest lesson -
    the one just learned from a live failure - was the entry that got dropped;
    and a retraining run started all knowledge baskets empty, wiping the
    lessons learned from real incidents along with the retrainable ones. Now
    the cap evicts by replaceability (entries a training run can re-derive go
    first, live-incident lessons only as a last resort, the newest lesson is
    always kept), and a retraining run carries the live-incident lessons over
    and re-attaches them after every distillation pass. Every eviction and
    every carry-over is logged.

  • A thinking turn with a text-recovered tool call no longer dies with a
    Veyllo 400.
    Some models occasionally write a tool call as text instead of
    a structured call; VAF recovers those and, for Veyllo, replays them as plain
    context because the gateway refuses tool-call ids it did not issue itself.
    That replay rebuilt the assistant message from its text alone and dropped
    the model's reasoning - and Veyllo's thinking mode, which knows the turn had
    reasoning, rejected the whole request ("The reasoning_content in the
    thinking mode must be passed back"). The rebuilt message now carries the
    reasoning when there is any and survives even when thinking was all the
    model said; a turn that never reasoned sends no such field, and none is
    demanded.

  • The agent's avatar stays in one piece while it works in a chat's action
    timeline.
    During a tool call the timeline's walking dot used to switch to
    the wide tool scene built for the free-standing loading bubble - a magnifier
    and particles more than a hundred pixels wide, in a gutter that has the tool
    cards directly to its right. The scene was clipped under the cards and its
    layout lean pushed the dot far off its rail, so the reader saw scattered
    fragments (a stray dot here, a lens handle there) instead of an agent. The
    timeline now keeps the plain living dot on the rail, the way it was designed;
    a running web search shows as the figure's own looking-around motion and
    other tools as its working pulse. The same fallback also animates the mobile
    avatar during tool calls, which used to freeze to a still dot there.

  • Each chat now keeps its own working state; switching no longer bleeds one
    chat into another.
    With the agent busy in one chat, opening another showed
    that chat's stop button too, and coming back to the first could leave its
    loader, stop button and tool animation running forever with nothing left to
    clear them. Three causes, all fixed: the sub-agent window, the workflow panel
    and the stop-press feedback lived in state shared by every chat and now
    belong to the chat they came from (the sub-agent view is swapped per chat
    like the messages, the workflow panel records which chat its run belongs to
    and only appears there); the events that end a turn used to be thrown away
    when they raced a chat switch, so a chat could never learn its turn had
    finished - they are kept for bookkeeping now, and a chat that finishes in
    the background shows an unread mark instead of a sound; and the server's own
    "is this chat busy" answer was read from one process-wide status field that
    parallel workers overwrite, so it now comes from the task queue, which knows
    it per chat. A worker display whose updates stop arriving settles itself
    after 30 seconds instead of claiming a running task forever, and a failed
    turn now tells the browser it is over instead of leaving the stop button
    armed.

  • Reading a large file no longer cuts it off blind. Every tool result in
    the chat used to be capped at 2,000 characters, and a file read hit that wall
    with nothing to go on: no length, no line count, no way to ask for the rest.
    A YouTube summary of 2,781 characters arrived cut, and reading a long report
    meant guessing ever-smaller page ranges. File reads are now exempt from that
    generic cap and budget themselves instead: a large text file returns its
    first section together with the facts (how many lines and characters it has,
    which lines are shown, how to continue with start_line/end_line) and a
    structure index, a list of the file's headings with their line numbers, so
    the agent can jump straight to the part it needs, the same way PDFs already
    read by page range. Word, Excel and PowerPoint reads share one honest
    ceiling that names what was left out instead of a bare "(truncated)".

  • A background bookkeeping task could kill the tool spinner, silently. Every
    30 seconds a debug profiler counted all objects in memory to watch for leaks.
    That census briefly touches objects other threads are still in the middle of
    building, which is a way to make Python abort whichever thread is building
    them (CPython issue bpo-15108). In practice the victim was the terminal
    spinner shown during a tool call: it froze mid-run with no error anywhere but
    the terminal's own error stream. The census is gone; the profiler still logs
    memory usage and its growth warnings, which is what the log line is for. And
    because that crash left no trace in any log file, uncaught errors from
    background threads are now written to crash_<date>.log on every lane the
    app starts, always, regardless of the debug-logs switch. Embedders get the
    same via vaf.install_thread_excepthook().

  • Deleting a chat cannot skip its confirmation any more. The trash icon in
    the sidebar deletes a chat that is genuinely empty without asking, which is
    right: there is nothing to decide about a chat nobody used. Whether it was
    empty, though, was judged in the browser from a number that arrives with the
    session list and is refreshed by nothing a conversation does, so a chat that
    filled up while the list stood still still counted as empty and was deleted on
    one click, with no dialog and no copy kept. The browser no longer decides
    this. It asks, and VAF answers from the chat itself: a chat holding messages,
    an attached document or a file in its folder always brings up the dialog
    first. The same question is now asked everywhere a chat can be removed, so a
    cleanup started from the terminal cannot quietly delete what the app just
    refused to delete, and a chat that only holds an automation result or a
    proactive question the user has not answered yet counts as worth keeping.
    Keeping a copy in the archive is offered afresh every time the dialog opens.

  • A rejected mail login now says what to do about it. Connecting a GMX
    account with two-factor authentication switched on failed with nothing but
    "authentication failed", which named no action: IMAP has no step where a
    six-digit code can be entered, so the mailbox password can never work once
    2FA is on, and GMX additionally ships POP3/IMAP access switched off. VAF now
    answers a refused login with what that provider actually needs - an
    app-specific password, a separate mail-program password, a sign-in instead of
    a password, or a local bridge - whether IMAP has to be switched on first, and
    a link to the provider's own page for it, in the language of the interface.
    An unknown provider gets the general advice rather than nothing. Guidance
    appears only when the server refused the login, so a name-resolution failure
    is no longer answered with password advice. An address with no known server
    and no host typed in is now refused with that instruction instead of being
    quietly tried against Gmail's servers.

  • Sending a room message no longer shows it twice. The pending copy was
    matched against the delivered message by exact text, but the server trims
    what it stores - one trailing space (a phone's autocomplete) and the copy
    stood under its own delivered message for 30 seconds. The comparison is
    trimmed on both sides now. The pending copy also looks like the message it
    is about to become - your initials, name and text, dimmed, a light band
    sweeping while it sends, a small "sending" note - and on delivery the
    message blends up in place instead of drifting in as if it were new.

  • The room no longer draws a "VAF is typing" row. Whether the agent has
    seen a message is what the read receipts under it already say; the extra
    presence row said the same thing a second time, sitting exactly where the
    answer was about to land. A human member typing keeps the small dots bubble.

  • A room's shared folder lists the same paths on every OS. On a Windows
    host, the seat-authenticated file listing (and the push answer) rendered
    relative paths with backslashes, so a file pushed as sub/a.bin came back
    as sub\a.bin - a name the fetch endpoint on another machine would then
    miss. Paths on the wire are POSIX now, whatever the host runs on.

  • The update dialog now knows how the update ended. Updating from
    Settings -> Update and Repair used to show a spinner for as long as ten
    minutes with no verdict: the dialog watched the version only through the web
    interface, which is down for its own rebuild for minutes after the backend
    is already back, and a failed update that rolled itself back looked exactly
    like nothing ever happened. Now every vaf update run records how it ended
    (~/.vaf/update_result.json), the dialog reads that verdict and shows a real
    failure screen (rolled back, recovery needed, or aborted before anything
    changed) with the error and the log path - and while the interface is still
    rebuilding it asks the backend directly, so a successful update says "server
    updated, interface rebuilding" instead of spinning blind.

Added

  • A file in the room can be named, not just described. A message may now
    carry files - the names of files in the room's shared folder it is about -
    so a receiving agent sees machine-readably what was left for it instead of
    having to find the filename inside a sentence. It works the same way
    everywhere, which is the point: the agent's room_send takes files, the
    CLI's say, answer and report take --file, the guest client takes
    --file (and its MCP tools a files argument), the browser draws a chip
    under the message, and the agent's own room turn names the file in its
    prompt. References are read defensively in one place: an absolute path or a
    traversal is dropped rather than rendered.
  • A guest in MCP mode holds its room open. An MCP server is one
    long-lived process, so the guest client now keeps each joined room's
    connection open instead of dialling per tool call: the writer lease is
    renewed from there, what the room says is mirrored as it arrives, reads
    answer from that mirror without a connection at all, and sends ride the same
    line - the collision a per-call send and a held wait used to produce cannot
    happen any more. A wait is instant when something is already there and safe
    to leave running. Shell verbs are unchanged: one process per command has
    nothing to hold a line with. Nothing here is a push, and the protocol
    document says so: no harness wakes an idle model, so an agent still has to
    ask - asking is just cheap now.
  • An agent in MCP mode is told when a room is waiting. Every tool answer
    now carries a line naming rooms with unread messages, even for tools that
    have nothing to do with them. No harness wakes an idle model, so the moment
    it is already reading an answer is the only moment it can be told - and this
    costs nothing, since the held line has the messages anyway and counting them
    does not consume them.
  • The guest client can refetch itself, verified. update pulls the host's
    current client over the authority the guest already pinned, so nobody has to
    hand-type a curl -k again: full certificate verification, no checksum to
    copy, and the download is compiled before it replaces anything, because a
    truncated file would break the one command that could fetch a new one.
  • A room invitation now fits an MCP host. The downloadable guest client
    grew an mcp subcommand: python3 a2a_client.py mcp is a stdio MCP server,
    so Claude Desktop, Claude Code or Cursor get the room verbs as a2a_ tools
    from the same single file - standard library only, same checksum lane, same
    seats. The invitation's guest section carries the ready-to-paste host
    config, rooms and howto work from the shell too, and the join keeps the
    room's welcome so howto can reprint what the room said about itself.
  • A room's shared folder is reachable from another machine. A remote seat
    holder could talk about files but never exchange them - the workspace is a
    folder on the host. The guest client gains files, fetch and push (and
    the matching a2a_files / a2a_fetch / a2a_push MCP tools), speaking to
    three seat-authenticated endpoints on the host. Uploads are capped, paths
    are contained to the workspace (traversal and symlink escapes refused), and
    deleting over the wire deliberately does not exist - destruction stays with
    the members on the machine that owns the folder.
  • Loading shows the shape of what is coming. While a chat's history or a
    clicked group chat's transcript loads, the message area shows skeleton
    bubbles under a thin progress bar that races to two thirds and then creeps -
    both vanish the moment real messages arrive, instead of a spinner over a
    blank area. A message sent into a group chat appears immediately as a
    visibly pending bubble ("wird gesendet…") and is reconciled against the
    room's store, which alone decides the order of a room with many writers.

Fixed

  • Tool results that ARE the deliverable are no longer cut mid-artifact. The
    dispatch funnel caps every tool result at 2000 characters to protect the
    model's context - a good default with one measured failure mode: a loaded
    skill body was cut mid-instruction, and a room invitation briefing was cut
    inside the very block the result orders the agent to pass on "unchanged and
    complete". The agent then correctly refused to hand over the torn half and
    spent a whole turn hunting the rest in encrypted stores and capped logs,
    until the model collapsed into raw markup. Tools can now declare
    result_is_deliverable and reach the model whole; use_skill, read_skill
    and room_invite do, each keeping its own output bounded in exchange. The
    declaration is honored along the whole path, not only at the funnel: the
    in-history compression stage no longer prunes such results (it had become the
    new cut once the funnel stepped aside), and the error classifier no longer
    paints a briefing as failed for containing the words "failed" and "tool" in
    its own vocabulary - which had marked perfectly successful skill loads and
    invitations as errors on the step chips.
  • A half-opened room connection can no longer mute the whole room. When a
    remote client vanished between taking the writer lease and receiving the
    welcome (a timed-out dialer hanging up), the server never released the
    lease - so the client's own reconnects were refused for the full 90 second
    lease lifetime, and every half-successful retry armed another dead lease.
    The room read as permanently dead while the server printed a traceback per
    attempt. The lease is now released on every exit path, and the handshake's
    store work runs off the shared event loop, so a remote connect storm no
    longer stalls the WebUI socket beside it.
  • Leaving a group chat no longer bounces back into it. The room view's
    3-second refresh could have one answer still in flight when the person
    switched to a normal chat; that late answer re-opened the room seconds
    later, over and over on a slow server. A transcript now only opens the view
    when it answers the person's own click, or refreshes the room already on
    screen. A room message typed while the connection is down also stays in the
    input box now instead of being silently dropped with a cleared box.
  • A held room session keeps its write right. The server renewed a remote
    connection's writer lease only after a successful send, so a session that
    read and thought for longer than the 90 second lease lost the right to speak
    while staying connected and receiving - and a conversation is exactly
    read-think-answer. Found by the first foreign agent to hold a session (a
    Claude agent on another machine driving the VAF CLI). The wire gains a
    renew transport verb, the session daemon sends it every 30 seconds, and a
    host too old to know the verb is asked exactly once. Protocol contract C9
    ("leases are renewed while attached") is now true.
  • The session outbox no longer counts refused messages as sent. An answer
    of not_writer deleted the payload and counted sent: 1 - a rejected
    message that read as delivered. The room's answer now decides the file's
    fate: committed sends leave, an unauthorized send stays for the next round,
    and a judged refusal moves aside with the room's answer beside it, counted
    as rejected.
  • The downloadable guest client holds a line without losing it. The
    single-file client a host serves (/api/a2a/client.py) now keeps its writer
    lease alive during a long wait with the renew transport verb (asking a
    host that predates the verb exactly once), exposes RoomConnection.renew()
    for guests holding a line of their own, and no longer drops frames that
    arrive while it awaits an ack for its own send - a message somebody sent in
    that window was silently never seen. A guest gets all of this by simply
    re-downloading the client from the host.
  • vaf a2a mission and vaf a2a introduce stop denying remote rooms.
    Both answered "there is no room on this machine" for a room the caller holds
    a seat in. Mission now reads from the join handshake (labeled as of joining)
    and refuses a remote write with the way that works; introduce names the
    path that works today (say it in the room) instead of denying the room.
  • An agent enters a room under its own name. When the model passed no
    display name, room_open and room_join seated the agent as "VAF" - the product,
    not the persona its user had named - while every other surface (greeting,
    system prompt, TUI title) already used the persona name. Both tools now
    resolve the agent's own name through one shared resolver, the way an agent
    card is meant to present the agent's identity; an explicit display still wins,
    and "VAF" remains only the last resort when no persona is resolvable.
  • Veyllo no longer rejects the empty-response retry. Veyllo speaks
    DeepSeek's thinking dialect and demands that a replayed assistant message
    carry its reasoning as a separate field; VAF restored that field for DeepSeek
    only, so the first lane that rebuilds and resends history - the
    empty-response retry - died with a 400 instead of recovering the turn. The
    restore now covers the family.
  • A group chat no longer shows phantom notifications. The sidebar's unread
    badge counted the room's own check-in pings, which the transcript view
    deliberately never shows - so the dot lit up, the room had nothing new, and
    the dot came back with the next check-in. The badge now counts exactly what
    the view would show. The check-in interval itself is also derived from the
    room's log now instead of process memory, so restarting the app no longer
    re-asks every idle member within seconds (on a day of live restarts, a
    quarter of a busy room's frames had become check-ins).
  • A reply that is only an unclosed thinking block counts as empty again.
    When a model opens a <think> block and never returns from it, the block's
    prose used to pass the empty-response check as if it were the answer - so the
    retry that replaces a dead generation never fired and the user saw leaked
    markup where a reply should be. Thinking now counts as thinking whether the
    model closed the block or not, in both empty checks (which share one probe
    now instead of two hand copies).
  • Memory search stopped throwing away most of its own candidates. Two
    retrieval defects capped answer quality regardless of the embedding model.
    The vector lane handed the rank fusion only its top 5 candidates while the
    keyword lane handed 20, so a correct memory ranked sixth by the vector side
    never even reached the fusion; both lanes now feed the same depth. And the
    keyword lane scanned only the first 400 stored chunks in no particular
    order, silently ignoring the rest of a larger store; the cap is now 2000,
    and existing installations that carry the old value in their config are
    lifted by a config migration (a deliberately customized value is kept). On
    the golden-question set this took first-hit accuracy from 12/26 to 18/26.
  • Memory search knows which model wrote each vector. Every stored memory
    and chunk now records the embedding model that produced its vector. Two
    models can emit same-sized vectors that are mutually meaningless, and until
    now nothing could tell such a mixed store from a healthy one - search would
    just quietly get worse. Existing rows are stamped on the next start. The
    embedding caches include the model in their identity for the same reason,
    editing a memory no longer writes an unencrypted content preview back into
    its metadata (re-introducing a leak that was already cleaned up), and an
    edited memory's summary vector is computed from the content again instead
    of from its deliberately content-free title.
  • A model that asks for four files now gets four, not one. When a model
    writes its tool calls as text instead of using the structured field - which
    DeepSeek does intermittently, emitting several calls inside one wrapper - VAF
    recovered only the first and then removed the rest from the visible text while
    cleaning up. The result was a reply that read normally, one file actually
    read, three quietly skipped, and nothing anywhere saying so. All calls in such
    a batch are recovered now. A batch entry naming a tool that does not exist is
    skipped on its own rather than cancelling the real calls beside it.

Changed

  • Memory understands more languages. The default embedding model for
    long-term memory is now intfloat/multilingual-e5-small (100+ languages)
    instead of the English-centric MiniLM: a question asked in German now finds
    facts that were stored in English, and the other way round. Existing
    installations are migrated automatically in the background on the next
    start, with a progress banner in the app and a status line in the terminal;
    until the migration finishes, search keeps working on the previous model.

Added

  • Archived chats look like an archive. They are shown as boxes in a grid
    instead of list rows; hovering lifts a box and opens its lid. Opening one now
    has a readable way back - the old link was grey on near-black - and a Delete
    from archive action next to it. Deleting there is final and the dialog says
    what that means: it is the last copy, and the agent can no longer recall the
    conversation afterwards.
  • The memory store can move to a new embedding model without losing anyone's
    data.
    vaf memory reembed re-embeds every stored memory and chunk whose
    vector was written by another model - resumable, idempotent, nothing is
    deleted, and rows the encryption key cannot open are set aside instead of
    blocking the run. The app start does this automatically when the configured
    model and the stored vectors diverge, in a background worker process; until
    the store is fully migrated, search keeps using the model the vectors were
    written with, so results never come from a half-converted store.
  • Deleting a chat asks first. The trash icon opened no dialog at all: one
    mis-click removed a conversation and its attachments for good. It now opens
    the same kind of confirmation the group chats already had, naming the
    documents that go with it, and the confirm button is disarmed for three
    seconds - a closed padlock and a count, opening to an unlocked one and the
    wording when it is safe to press. A destructive button under the cursor is
    pressed before the sentence above it is read. That delay now guards the
    group-chat dialog too. The same dialog offers to keep a copy in your archive
    instead of losing the conversation, ticked by default - the usual regret is a
    chat deleted for tidiness that the agent later needed. Archived chats stay
    readable by everything that reads a chat, so the agent's memory can use them,
    and they are private to your account. An empty chat - no messages, no
    attachments - is still deleted with one click: there is nothing to lose,
    so there is nothing to confirm. Archived chats have their own window:
    Settings -> Persona & Memory -> Archive, with the search and its hits
    on the left and the chats on the right; opening a hit jumps to the message
    it was found in, with every match highlighted. The search runs on the
    server across all your archived chats - it no longer needs you to open the
    right one first - and it finds things the same way the agent does, so
    "Reisekosten" finds "Reisekostenabrechnung" and "Pruefung" finds
    "Prüfung". What matched is highlighted in the result list and in the chat
    itself, so a hit shows you the words it found rather than only the chat -
    and it outlines the whole passage the agent would receive for that hit, so
    you can see exactly what the model gets. Archived chats also stay available to the agent's memory:
    Cross Chat Hints read them like any other chat of yours, which is what the
    "keep a copy" option promises.
  • Settings -> Usage: what was actually consumed, and by whom. A new tab
    showing total tokens, request count, an estimated cost, and a table of
    accounts with the heaviest first. The token numbers do not depend on anyone
    agreeing about tokenizers: each one is what the provider itself reported for
    a call it billed, so providers that count differently still add up to the
    invoice. The cost next to them is the single estimate on the page - it comes
    from a price list that ages, and a model missing from that list is priced at
    the expensive end and marked as an upper bound. Everyone can open the tab,
    but only an admin sees costs and other accounts. Everyone else sees their
    own tokens and requests - no money, no percentage of the total, nothing
    about anyone else - and that is stripped from the response itself rather
    than hidden in the page. The chat's Context Window header now has a Usage
    button that opens this tab directly. Clicking a bar in the 7-day chart
    opens that day's breakdown: which lane spent the tokens - chat, thinking,
    automation, a sub-agent, the browser - and how much each one used.
    Token counts were also running low against the providers' own dashboards:
    a call whose token counts happened to match the previous one exactly was
    treated as "nothing new" and dropped, and the utility lanes send nearly
    identical prompts back to back. Calls are now measured as the growth of
    the running total, so identical repeats count. A call the provider never
    reports usage for - an aborted or failed stream - is no longer silently
    missing either: it is counted, sized by a rough word count, and marked as
    an estimate, so the part of the total that was estimated can be seen and
    subtracted.
    Coverage is complete now: individual tools bill under their own name, the
    coder's own connection is counted, and local model calls are counted too -
    they cost nothing, but leaving them out meant the page could not answer
    what the machine actually did. Amounts also show the right currency: Veyllo
    bills in euros and the other providers in dollars, so the figures were
    euros wearing a dollar sign. Each call now records the unit it was priced
    in, and a period spanning two providers shows both amounts instead of
    adding them together. Spending recorded before that change is still shown,
    marked as being of unknown currency rather than dropped or assumed. A
    EUR/USD toggle converts the figures at the European Central Bank's daily
    reference rate, showing the rate, its date and its source beside them; it
    changes the view only, never what was recorded, and it stays hidden when
    no rate is available. The choice is remembered, and it applies to the price
    comparison as well, so the whole tab can be read in one currency. Spending recorded before currencies were stored can
    be attributed once, by stating which currency it was - the software will
    not guess, and it backs the ledger up before touching it. Both live in a
    new vaf usage command as well (show, set-currency), so an install
    without a browser can read the same figures and run the same action. And a new breakdown shows which provider and model
    did the work - chat, vision, sub-agents, the tool model and the thinker can
    each run somewhere else, or locally, and their prices differ by an order of
    magnitude, so the total alone said very little. The XML export carries all
    of it - period, per day, per lane, per provider and model, each amount in
    its own currency - and opens with a plainly written note saying the money
    is an estimate and how it was arrived at.
    Local models contribute tokens and no cost.
    The tab also carries a 7-day bar chart with the busiest day marked, a share
    bar per account (percentage, requests, tokens), a panel pricing the same
    tokens against every provider's public list price - tap one to see which
    model and rates the figure used, or define your own price in the last row -
    and an XML export of the last 30 days that states in prose how each number
    was measured. The comparison quotes each provider at its cheapest model for
    the usage in question, opens a dialog with that provider's full model list
    and per-model figures when tapped, and reports currency rather than
    converting it (Veyllo publishes EUR, the others USD). Both dates are stated
    rather than left to be guessed: which period the compared tokens come from,
    and when the price list was last checked against the providers' own pages.
    Requests recorded before token counting existed are now labelled as such
    instead of showing a bare zero.
  • The Settings window is slightly larger, so the new tab is not cramped.

Security

  • Dependency updates (Dependabot, both npm trees): Next.js 16.2.11 to 16.3.0,
    whose vendored lodash closes CVE-2025-13465, with eslint-config-next in sync;
    monaco-editor 0.56.0, next-intl 4.13.6, mammoth 1.12.1, zustand 5.0.15 and
    autoprefixer 10.5.4 ride in the same group. The WhatsApp bridge moves to
    Baileys 6.7.24, which mainly refreshes the pinned WhatsApp Web version. The
    dompurify override stays required: monaco-editor 0.56.0 still pins an old
    copy. Next.js now requires sharp 0.35 itself, so the sharp override has
    become redundant and can go with the next dependency pass.

Changed

  • Every model call is counted now, not just the chat. Usage was recorded at
    the end of a chat turn, so the coder, sub-agents, vision, voice, memory
    compaction, the mail composer and the browser agent all reached a model by
    other routes and spent invisibly - the Usage tab and the daily spend cap were
    both reading a fraction of the real total. Recording moved into the one
    method every lane passes through, so completeness no longer depends on nine
    places remembering to ask. Each call is also written to a new
    usage_YYYY-MM-DD.log with its lane, model, tokens and cost, which is the
    fastest way to see what a single sub-agent or coder run consumed. That log is
    a copy for reading, never the source: the per-user ledger stays the record,
    so deleting logs cannot lose history. It is the one log that ignores the
    debug-logging switch, because a spend record that can be switched off is not
    a record. Each lane names itself in that log - memory, vision, voice, librarian, mail, browser, thinking, main - so it answers which part
    of the product spent the tokens - including the lanes that run while nobody
    is watching: automation, thinking, subagent, room and background
    are named rather than billed as if a person had typed them. The Usage tab
    also gained a Refresh button,
    and the price comparison now shows the arithmetic behind its figures -
    sent plus received equals the instance total - so the number it prices
    cannot be mistaken for one account's.
  • The API price table is current again (checked 2026-08-17 against the
    providers' own pricing pages). It had been carrying a previous model
    generation, which made every cost estimate in the product wrong in both
    directions. Model names from the older generation keep their prices, so
    existing ledgers are not silently repriced at the unknown-model rate.
  • "Context effort": choose what a reply is allowed to cost. Settings -> AI
    & Model now carries a stepped slider from 8,000 tokens up to whatever the
    configured model's real context window is - seven positions on a 128k model,
    four on a 32k local one, always ending at the model's true maximum. It sets
    the budget the compression lane triggers on, and the number it shows is the
    honest one: an API is sent the entire conversation again on every reply and
    bills every token, so the setting is the price of one reply rather than a
    capacity. The default moved from 30,000 to 45,000 tokens. Moving it down
    deletes nothing - older turns are summarized, and the full history stays
    recoverable with /restore. The same ladder is in the terminal under
    Settings -> Context -> Context effort, and embedders get it from
    resolve_context_effort(). For a local model the slider is disabled with a
    note, because local tokens are free and the budget is ignored there.

Fixed

  • API costs no longer grow without bound in long chats. On a pay-per-token
    provider every reply resends the whole conversation, and compression only
    fired at 85% of the 128k model window - a three-week chat sat at ~65k tokens
    forever, so even a one-line question paid ~65k tokens again, in every single
    round-trip. Compression now triggers at a cost budget (context_compress_tokens,
    default 30,000 tokens; 0 restores the old window-based behavior), posts a
    visible system message with the before/after counts, and local models are
    unaffected. Two bugs in the same lane went with it: the agent kept two
    separate context managers, so a checkpoint_context summary was stored on an
    object the session never persisted; and loading a session (or restarting the
    app) replayed the full transcript back into the context, silently undoing
    every checkpoint. One manager remains, its state persists, and a loaded
    session is compressed again on arrival, reusing the saved summary without an
    extra LLM call.

Changed

  • Reading a group chat no longer shows as typing; it shows as a read receipt.
    The three bouncing dots used to appear for any member that had merely read the
    newest message, for up to two minutes - an agent that only monitors its room
    looked permanently busy. The dots now mean composing and nothing else: the
    agent is really writing an answer, or a person is pressing keys in the input
    box. Reading shows as small stacked profile circles under the last message
    each member has read, moving down as they read on, capped at twenty faces
    with the remainder as a number.