Skip to content

VAF v0.1.0a19

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 27 Jul 05:42
· 200 commits to main since this release

Added

  • Workflows can now run as the person who started them. Until now a saved workflow always
    acted as the machine owner, no matter who ran it - so anything it did with memory, mail,
    messages, the calendar or contacts was filed under the owner's account. The new setting
    workflow_identity_injection switches this over: leave it at legacy for the previous
    behaviour, set it to declared to have workflows carry the identity of whoever started
    them. Off by default, because it changes where a running workflow's data goes.
  • You can now choose who a skill is for. The skill editor has a visibility setting -
    everyone, only you, or named people - the same one custom tools already had. It decides who
    sees the skill and who can use it, and now also whose agent may open the files bundled with
    it. Existing skills keep the setting they have; a new one starts as visible to everyone.
  • Tools you write yourself can now be told who is calling them. VAF only handed the
    current user's identity to its own built-in tools, which meant a tool added through the
    framework could not tell one person from another - and the documentation could only warn
    about it. A tool now states what it needs and receives exactly that, whether it ships with
    VAF or you wrote it. Nothing changes for existing tools.
  • A completely new built-in mail client, and it is now the only one
    (design doc docs/integrations/EMAIL_CLIENT.md). VAF has a real mail engine:
    a per-user local mail store (SQLite with full-text search over
    subject/sender/recipients/BODY, threaded conversations, encrypted-at-rest
    cached bodies, configurable retention with headers kept forever) and an
    RFC 4549 incremental IMAP sync engine (UID-based, UIDVALIDITY-safe, batched
    fetches, IMAP IDLE push on the inbox plus periodic sweeps, native Gmail
    thread/label handling). The mail window is a three-pane client: folder sidebar
    with unread counts and collapsible labels, conversation view, HTML mail
    rendered sanitized in a sandboxed frame with remote images blocked by default
    for tracking protection, attachment download, and search over message bodies.
    Mail opens offline from the local store. New permissive-licensed dependencies
    (in the mail extra): IMAPClient (BSD-3-Clause), nh3 (MIT),
    zstandard (BSD-3-Clause), listed in THIRD_PARTY.md and the About tab's
    third-party license list.
  • Mail can be acted on, not just read: read/unread, star, archive and trash in
    the mail window (trash-only delete semantics, nothing is ever expunged),
    reply/reply-all/forward with proper quoting and threading, compose with a
    15-second undo window (the mail is held locally and can be withdrawn before it
    leaves the machine), and automatic filing of sent mail into the Sent folder.
    Changes apply locally first and replay to the mail server through a durable
    operation queue once mail_engine_write_enabled is on. The agent gains
    reply_mail, forward_mail, archive_mail and delete_mail tools (all
    excluded from the front-office contact lane by design).
  • Outgoing mail is sent natively over SMTP (password or OAuth XOAUTH2), so it no
    longer depends on the provider REST APIs; an ambiguous failure after the message
    is handed to the server is parked, never re-sent, so a mail is never delivered
    twice. IMAP/SMTP server presets added for GMX, web.de, T-Online and outlook.de
    addresses.
  • Blocked remote images in mail can now be loaded on explicit opt-in through
    a privacy proxy: the sender's server never sees the reader's address, SVG
    and non-image responses are refused, and refused hosts are logged to the
    security event log.
  • The mail client warns about suspicious (possible phishing) messages: the
    conversation list shows a warning badge and the reader a warning banner on mails
    the agent's phishing filter would hide, so nothing dangerous is silently
    surfaced only to the human.
  • The mail client shows which mail has already been answered: a reply marker with
    the date in the reader and a marker on answered conversations in the list, so a
    reply is not accidentally sent twice.
  • Gmail-style categories: a category chip on Promotions/Social conversations and a
    relabel picker in the reader. Relabeling also teaches the category - VAF
    remembers a rule for that sender and labels every other mail from them, past and
    future. All of it is local; nothing is changed on the mail server.
  • Mail Composer: the compose window can now write the reply for you, or rewrite
    what you typed. The window is wider now, with the message on the left and the
    Composer beside it on the right: a chat where you say what the reply should say,
    ask for changes ("shorter", "now more formal") and it refines what it just wrote,
    with Stop while it writes and an Undo that restores your text exactly. Draft and
    Rewrite sit in the footer next to Send. It puts a suggestion in the text field and stops there, so nothing is
    ever sent without you reading it and pressing Send, and it tells you how much of
    the conversation it actually read. It refuses to draft from a message flagged as
    possible phishing, and it can use what VAF remembers about you when you say what
    the reply should be about. New: a light-mode button in compose, so you can read
    the draft the way the recipient will. Admins can turn all of it off or change how
    much of a thread it reads (mail_composer_* settings). If the local model is not
    running yet, it is started for you instead of asking you to do it, with a note
    while it loads. Admins can additionally allow the Composer to quote older mail
    from other conversations (mail_composer_mailbox_search_enabled, off by default).
    The Composer knows what VAF remembers about you the same way the chat does, every
    time rather than only when you phrase a request a certain way, and it now writes a
    complete message (greeting, the point, a closing) in the language of the mail it is
    answering instead of a single bare sentence. It also tells apart which messages in
    a conversation you wrote and which the other person wrote, and writes in YOUR tone
    by following how you replied earlier in the same thread; if there is nothing of
    yours to go by it says so and stays neutral instead of inventing a style.
  • The mail window's gear opens a built-in account panel: see your mail accounts,
    connect a new Gmail/Microsoft or IMAP account (with a Test button), reconnect,
    verify a connection, rename an account, toggle auto-sync, and remove an account.
    Removing an account that also powers your Calendar keeps it connected for
    Calendar.
  • Library embedders can now set the agent's persona directly:
    Agent(system_prompt="...") replaces the on-disk "Soul" in the system prompt
    for that instance only, while the engine's technical instructions are kept.
    Previously the persona was a global on-disk file with no public API.
    Documented in EMBEDDING.md with a runnable example (examples/06_custom_persona.py).
  • EMBEDDING.md now has a "Sub-agents as a library" section explaining that
    sub-agents run inline in a bare library process, while their windowed/async
    modes and the coder's sandbox need the full product's services.
  • The skill scanner module gained a dependency-free content-hashing facility
    (SHA-2 and SHA-3): hash_bytes / hash_text and a deterministic,
    tamper-evident hash_skill_folder fingerprint, on a strong-only algorithm
    allow-list. Available for later integrity checks; not yet wired into the
    scan result.

Changed

  • The wording around mail image loading was corrected in the documentation. It
    protects the reader's browser identity (no cookies, referrer or browser
    fingerprint reach the sender) but it does NOT hide the reader's IP address, and
    it does not stop open-tracking: a tracking pixel's address is unique per
    recipient, so loading it still tells the sender the message was opened and when.
    Blocking images by default is the protection. docs/integrations/EMAIL_CLIENT.md
    now states both halves for privacy reviews.
  • Server-side mailbox changes (read/unread, archive and delete replayed to the
    mail server) stay behind mail_engine_write_enabled, still off by default. Note
    that SENDING is deliberately NOT gated by it: a queued mail must be able to
    leave, so the agent's reply_mail and forward_mail verbs are live regardless
    (each still passes the high-risk send gate).
  • Removed the dead Apple OAuth lane (provider entry, config keys
    email_oauth_apple_client_id/_secret, admin settings inputs and their
    locale strings, a dead sign-in URL branch in the setup wizard): Apple
    offers no OAuth mail API. iCloud Mail continues to connect via IMAP with
    an app-specific password, unchanged.

Removed

  • The batch tool is gone. It was listed as a Coder tool but could never be called: it was
    not registered for the main agent and not part of the Coder's tool set either. What it
    offered - running several tools at once - is what the agent already does in a single turn.
  • The old mail dashboard and the separate email setup wizard are gone. Everything
    they did is in the mail window: reading mail, and the account panel behind its
    gear for connecting, reconnecting, testing, renaming, auto-sync and removing
    accounts (including the IMAP and SMTP server overrides the wizard offered, and
    the same hiding of sign-in buttons for providers an admin has not configured).
    The Overview security page reads its mail data from the new engine now.
  • The old mail REST endpoints under /api/email are gone (message list, search,
    body, categories, category change, sender-rule backfill and the per-account
    sync). Mail is served from /api/mail. Sign-in and account management under
    /api/email are unchanged, because Calendar and the Connections page use them.
  • The 30-minute background mail sync was removed. The mail engine's own sync
    (continuous, with push updates) is now the only one, so every mailbox was being
    fetched twice.
  • The old mail transport (its own IMAP, Gmail API and Microsoft Graph paths for
    fetching, reading and sending) is gone; the mail engine handles all of it. An
    account that has not been connected for the engine can no longer send through the
    old path - it is refused with a message telling you to reconnect it, rather than
    failing quietly.
  • The mail_engine_v2_enabled setting is gone. The mail engine is simply how mail
    works now, so there is nothing left to switch. mail_engine_write_enabled is
    unchanged and still off by default: it remains the separate switch for changes
    written back to the mail server (read/unread, archive, delete).
  • Mail: with several accounts, only the first one kept its labels and "answered"
    markers when moving to the new mail engine. Every account now carries its own
    over, including labels belonging to mail that only arrives on a later sync, and
    it also happens when you sync manually rather than waiting for the background
    sync.
  • Mail: adding an account with a password for an address that is already connected
    through sign-in is now refused with an explanation, instead of silently replacing
    the connection and disconnecting that account's calendar.
  • The Logs page no longer dead-ends when debug logging is off. Three fixes
    from a live incident on a macOS install where a legacy config had
    debug_logs_enabled: false: the chain badge no longer claims "Chain
    intact" for an empty or missing timeline (an empty chain is vacuously
    intact; it now shows a neutral "No data yet" state), the timeline empty
    state is localized instead of hardcoded English, and the Debug Logs
    switch is back in Settings → Advanced - the empty states tell the user to
    enable it, so the switch has to exist in the UI.
  • Corrected stale embedder docs: the FAQ said VAF was "not yet on PyPI" (it is,
    as a prerelease) and three docs claimed "no async API" despite the shipped
    run_async.

Fixed

  • A tool the agent had just written for itself only worked after a restart. When the
    agent creates a new tool, it is supposed to become usable straight away in the same
    conversation. The step that makes it live was never actually reached, so the tool was
    written correctly but stayed invisible until VAF was restarted next. It now appears
    immediately, as intended.
  • Mail did not work at all on some Windows and macOS installations. The search index
    used a feature that needs a recent SQLite, and it was created together with the rest of
    the mail database - so on an older SQLite the whole mail store failed to be created, not
    just search. Affected setups running Python 3.10, which still ships an older SQLite on
    those two systems. The index now checks what the system supports and falls back to a form
    that works everywhere; searching, sorting by relevance and deleting behave the same, the
    index just takes a little more disk space there.
  • A workflow could open a skill somebody kept private. Skills have a visibility setting,
    and in chat the agent respects it: a skill shared only with certain people stays closed to
    everyone else. A workflow step, however, was never told who had started it, so it acted as
    if an administrator were asking and could read any skill on the machine. Workflow steps now
    carry the identity of the person who started them. This closes the case for workflows you
    run from chat, for automations, and for a resumed workflow; saved workflows started from the
    library are covered by a broader fix still in progress.
  • The per-user tool and workflow selection said more than it does. When creating or
    editing a user you can pick which tools and workflows they should have, and the wording read
    as though the choice were already in force. It is stored but not yet checked while the agent
    runs, so "Read-only" did not actually stop anything. The dialog now says so plainly instead
    of implying a restriction that is not there yet. The selection itself is unchanged and is
    kept for when it takes effect.
  • The "Add MCP server" dialog needed scrolling to reach its last field. Test connection
    moved down next to Cancel and Save instead of occupying a row of its own, the empty band
    it left behind is gone, and the dialog is taller.
  • The failover setting looked navy in dark mode. Its slider track and the step markers
    now use the same neutral tone as the other controls.
  • The agent could read files belonging to other people using the same VAF. Writing was
    already confined to your own project folder, but reading was not, so the agent could open -
    or simply list - files in someone else's folder or elsewhere on the machine. Reading is now
    confined the same way. It is deliberately a little wider than writing: skills shared with
    you stay readable, because a skill may point the agent at its own reference files. A skill
    somebody kept private is not, which it previously was. Your own files, your uploads, and
    everything an administrator does are unaffected.
  • The local model stayed on your graphics card after switching to a cloud provider. VAF
    already frees it in that situation, but two rules were holding it back for users who could
    never benefit from it. Background thinking asked to keep the model without checking that
    the next thinking run would run in the cloud and never touch it - and it asked that not
    only while a run was in progress but also while one was merely due, which stays true for as
    long as you are away. Separately, a setup with a local voice model reserved it whenever a
    VAF window was open, although a call loads what it needs when it starts. Roughly 3.4 GB now
    comes free on a provider switch. A call, a running task or a sub-agent still keeps the
    model; only the first call after an idle period waits briefly for it to load, which the
    call window already shows and handles.
  • Changing your API key only took effect after a restart. Switching provider or key is
    handled in one place inside VAF, but the three parts that receive a settings change had
    each rebuilt that step by hand and each left pieces out. A key change with the provider
    unchanged was ignored entirely; VAF could keep telling the model, and the logs, that it
    was still the previous one; and if the new provider could not be reached, VAF could be
    left with no working model at all until you restarted, instead of simply staying on the
    one that was working. All three now go through the same implementation.
  • One of the agent's two ways to change a file skipped the per-user boundary. When
    several people use one VAF, each is confined to their own project folder. Asking the
    agent to rewrite a file respected that; asking it to change a few lines in a file did
    not, so a file belonging to someone else could be altered, and a failed attempt even
    returned a few lines of that file. Both ways now enforce the same boundary. Nothing
    changes for a single-user VAF, for administrators, or for files of your own.
  • The agent could open VAF's own settings folder, where the keys live. Asking the agent to
    read a file gave it access to the folder VAF stores itself in - the file holding every
    connected API key and the sign-in secret, saved copies of it, stored browser logins, voice
    profiles and every conversation. A key read out of there keeps working outside VAF and
    cannot be taken back, so the folder is now closed to the file tools for everyone, the owner
    of the machine included. Skills and workflows you create still live there and stay readable.
  • A second administrator was treated as an ordinary user by the file tools. VAF lets you
    create more than one administrator, and they can do everything an administrator does -
    manage users, see every conversation, read the full settings. But when they asked the agent
    to work with a file, or opened a file from a chat, VAF checked only whether they were the
    very first administrator account and otherwise confined them to their own project folder.
    Administrators are now recognised as administrators everywhere. Nobody else gains access:
    ordinary users stay confined to their own files exactly as before.
  • A single device could lock everyone else out of signing in. VAF blocks an address after
    repeated failed logins, but behind the built-in HTTPS proxy every device on the network
    counted as the same one. Five wrong passwords from anywhere blocked everybody for the next
    quarter of an hour, and the block could not be traced to whoever caused it. Attempts are now
    counted per device.
  • Conveniences meant for the computer VAF runs on reached the whole network. Signing in at
    the machine itself gives a long-lived session, and an administrator there does not have to
    re-enter a two-factor code when the session refreshes - both because the person is physically
    at the keyboard. Behind the proxy every device on the network looked like the machine itself
    and received the same treatment. VAF now recognises which device is really the local one; the
    computer you run VAF on keeps both conveniences unchanged.
  • Your login history and the security log showed every device as the local computer. Sessions
    and failed attempts from the network were recorded as coming from VAF's own machine, so it was
    impossible to see where a login had actually come from. Both now show the real device.
  • Devices on your network could reach VAF without logging in. When network access was
    switched on, VAF's built-in HTTPS proxy passed every device to the backend as if it were
    the computer itself, so any device on the same network could open pages and APIs without
    a token, and even an expired or wrong token was let through. That included user
    management, the log viewer and the security dashboard. VAF now recognises which device a
    request really came from and asks anyone who is not on this computer to sign in.
    Signing in on another device, the desktop app, and first-time setup all keep working
    exactly as before. After updating, restart VAF so the fix takes effect.
  • A device on your network could pretend to be this computer. The proxy added its own
    sender information alongside whatever a device had claimed, and the backend read the
    claimed value first. A device could therefore present itself as local and skip the check
    that ties a connected account (for example Gmail or a cloud drive) to the person who
    started the connection. The proxy now removes any claimed sender information before
    adding its own.
  • Mail: an account the new mail engine does not sync yet (a Gmail or Microsoft
    account that has not completed the IMAP sign-in) is no longer reported as an
    empty mailbox. It stays visible in the mail client with a hint that it needs to
    be reconnected, instead of silently disappearing from the account list and
    reading as deleted.
  • Mail: turning a per-account "Auto-sync" toggle off now actually stops that
    account from being polled by the new engine. It previously kept syncing (in fact
    more often than before), so the switch did the opposite of what it said.
  • Mail: removing an account from Mail while keeping it connected for Calendar no
    longer resurrects itself - the background sync used to re-import the messages the
    removal had just deleted.
  • Mail: Gmail's category tabs (Promotions, Social, Updates, Forums) are detected
    again. They are saved searches rather than labels, so reading them from the
    message labels never worked and every mail was filed as Primary; the category is
    now resolved through Gmail's own search over IMAP.
  • Mail: a sender rule learned by relabelling a mail now also labels NEW mail from
    that sender. Previously the rule only re-labelled existing mail, so the promise
    that "future mail from this sender gets the same label" did not hold.
  • Mail: connecting a Google account now grants everything the mail client needs in
    ONE sign-in. Previously a freshly connected account could not be used by the mail
    client at all and always needed a second, separate "Upgrade to IMAP" step. That
    separate step is gone: an older account that still needs it simply offers
    "Reconnect", the same sign-in as connecting. It preselects the account in
    question (so with several accounts the right one is reconnected), works in the
    standalone mail window, and the panel refreshes by itself once access is granted.
    Microsoft still needs a second sign-in for mail, because Microsoft issues mail and
    calendar access as two separate tokens that cannot be combined.
  • Mail: accounts connected with a password or app password no longer show a
    permanent "IMAP not ready" warning - they speak IMAP by definition.
  • Mail: opening a label or other non-inbox folder now fetches it on first open.
    Those folders sync on demand by design, but nothing ever requested them, so they
    stayed permanently empty.
  • Mail: a message that could not be sent is no longer reported as sent. Such a
    message is held in the outbox and the mail client now shows a warning about it,
    with "Try again" and "Discard" so the warning can actually be cleared once the
    cause is fixed.
  • Mail: the message list no longer keeps showing another folder's mail when a
    folder fails to load, and the list now names the folder it belongs to.
  • Mail: the unread counts next to the folders update again. They were read once
    when the mail window opened and then never changed, so new mail, reading a
    message and syncing all left them stale.
  • Mail: pressing Sync now reports when an account could not be synced, instead of
    spinning briefly and saying nothing.
  • Mail: a new Gmail or Microsoft account can be connected straight from the mail
    window's account panel.
  • Mail: three kinds of hidden tracker in HTML mail are now caught and reported.
    A CSS-escaped url(, image-set(...) and src(...) inside an inline style
    slipped past the filter with the third-party address intact, and the mail was
    shown as if nothing had been blocked. The browser's own rules stopped the actual
    request, so no address was ever contacted, but the warning was missing. Blocked
    styles are now counted, so the "external content blocked" notice is honest.
  • Mail: image loading now uses the network proxy configured for the machine
    (https_proxy/http_proxy, honouring no_proxy). In managed networks that
    forbid direct internet access, loading images previously just failed, and the
    organisation could not see or filter what the mail view fetched.
  • Testing or verifying a mail account no longer freezes the whole backend. The
    IMAP credential test/verify and OAuth token-exchange endpoints ran blocking
    network IO directly on the uvicorn event loop, so every other API request hung
    until the provider round-trip finished. All provider IO in the email routes now
    runs in worker threads.
  • The Logs window no longer crashes with a blank "This page couldn't load"
    after a rebuild. A calendar-follow effect had been placed after the modal's
    early return, so the number of React hooks changed when the window opened
    (React error #310); the effect is back above the guard.
  • A timed-out or stopped sandbox execution is now actually terminated inside
    the container. Slim sandbox images ship no pkill, so the old kill path
    silently did nothing: a timed-out package install kept running blind,
    finished after the workspace cleanup and left a 229MB orphan directory
    behind. The kill is also scoped to the single run's process tree, so it can
    never take down another user's concurrent sandbox execution in the shared
    container.
  • The Logs window now follows the calendar: after midnight the audit chain,
    hero and activity panels advance to the new day as soon as it has events,
    instead of silently continuing to show yesterday (fresh tool runs appeared
    to be missing from the audit chain). Date handling also switched from UTC
    to local time, which had marked the previous day as "Today" until 02:00
    CEST - the same fix applies to the sidebar chain-alert probe. Explicitly
    picking an older day in the date selector still pins it.
  • Running the test suite on a development machine no longer writes fake
    "Message sent via Discord" entries into the live Activity feed and channel
    history. A unit test executed the real send tool with only the network call
    mocked, so its bookkeeping side effects (activity notification, channel
    store row, outbound session stub for a placeholder recipient) landed in the
    real stores on every run - hundreds of entries had accumulated and looked
    like a compromise at first glance. No message ever left the machine; the
    test now isolates all side-effect stores and the debris has been removed.

Security

  • Whether an account counts as an administrator is now decided the same way everywhere.
    The check that lets administrators use restricted tools compared the stored role
    letter for letter, while the check that decides which files may be opened accepts the
    role regardless of capitalisation. An account whose role was stored as "Admin" would
    therefore have been given administrator access to files while still being refused
    restricted tools. Accounts created or edited in VAF always store the role in lower
    case, so this could not be triggered through the app; the two checks now share one
    rule regardless.
  • Mail: a request carrying a user name but no user scope could be served from the
    local administrator's mailbox by the new mail engine (read and write). Such
    callers now stay on their own mail store, matching the isolation rule the other
    two layers already enforced.
  • Outgoing email attachments now resolve their file paths under the same
    per-user filesystem jail that already protects librarian and file-write
    operations: in network mode a non-admin user's agent can no longer attach
    files outside that user's own data (previously only the static block list
    applied). The jail computation is now a single shared helper used by all
    three tools, with a regression test.
  • The agent-side phishing-filter settings
    (email_agent_phishing_filter_enabled, email_agent_phishing_score_threshold,
    email_agent_trusted_sender_domains) and the IMAP/SMTP SSRF opt-out
    (email_allow_private_hosts) are now registered config keys and
    admin-write-only. Previously they were read with inline defaults and, in the
    SSRF case, any LAN user could have toggled the guard for the whole instance.
  • Mail transport logs now mask account ids consistently (first 3 characters)
    and truncate provider error-response bodies; several error paths used to
    write the full account address and full API response into the always-on
    domain log.
  • Dependency updates closing all 13 open Dependabot alerts: Next.js 16.2.9 to
    16.2.11 (nine advisories, incl. SSRF in Server Actions and in rewrites,
    middleware bypass with Turbopack, Server Action DoS and cache confusion),
    sharp 0.34.5 to 0.35.3 (inherited libvips CVEs, now forced via an override
    because Next.js still pins the vulnerable minor), brace-expansion 1.1.14 to
    1.1.16 (exponential-time expansion DoS, dev-only), DOMPurify 3.4.11 to
    3.4.12 (CUSTOM_ELEMENT_HANDLING bypass) and protobufjs 7.6.4 to 7.6.5 in
    the WhatsApp bridge (infinite loop in .proto option parsing).
  • postcss is now forced to at least 8.5.12 via an override (arbitrary file
    read through attacker-controlled sourceMappingURL): Next.js exact-pins a
    vulnerable 8.4.31 copy, which the override dedupes away to a single
    patched node. Build-time tooling only, no runtime exposure.
  • The session token that rides in the WebSocket handshake URL
    (/ws?token=<jwt>, unavoidable because WebSockets cannot send an
    Authorization header) is now masked to token=*** in the server access log
    instead of being printed in full. A live, short-lived admin token could
    otherwise be replayed by anyone who read the terminal or the tray log. The
    same mask also covers access_token / api_key / password if they ever
    appear in a logged URL.