VAF v0.1.0a19
Pre-release
Pre-release
·
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_injectionswitches this over: leave it atlegacyfor the previous
behaviour, set it todeclaredto 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 docdocs/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 themailextra): IMAPClient (BSD-3-Clause), nh3 (MIT),
zstandard (BSD-3-Clause), listed inTHIRD_PARTY.mdand 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 oncemail_engine_write_enabledis on. The agent gains
reply_mail,forward_mail,archive_mailanddelete_mailtools (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_textand a deterministic,
tamper-evidenthash_skill_folderfingerprint, 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 behindmail_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'sreply_mailandforward_mailverbs 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
batchtool 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/emailare 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/emailare 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_enabledsetting is gone. The mail engine is simply how mail
works now, so there is nothing left to switch.mail_engine_write_enabledis
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-escapedurl(,image-set(...)andsrc(...)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, honouringno_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_HANDLINGbypass) and protobufjs 7.6.4 to 7.6.5 in
the WhatsApp bridge (infinite loop in.protooption parsing). - postcss is now forced to at least 8.5.12 via an override (arbitrary file
read through attacker-controlledsourceMappingURL): 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 totoken=***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 coversaccess_token/api_key/passwordif they ever
appear in a logged URL.