Skip to content

v1.5.0

Choose a tag to compare

@github-actions github-actions released this 17 Oct 07:20
· 9 commits to main since this release
894b943

Every deployment should upgrade: the agent, Google Drive and image MCP servers
answered without a login, an empty AUTH_PASSWORD let anyone sign in with empty
fields, any signed-in user could fire a trigger, and a widget admin key could change
another agent's files. The release brings costs in US dollars with dollar budgets,
a Playground to compare models and instructions, Trace Replay on the Visual
Builder canvas, an image tool for any LiteLLM image model, alerts on fallback
model
use and to Slack and Discord, and a Docker image on GHCR. MySQL works
for the first time, and A2A works again on a2a-sdk 1.x.

Before upgrading:

  • MCP clients of /agents/<name>/mcp, /gdrive/mcp and /images/mcp need
    credentials, for example a personal access token.
  • POST /triggers/{id}/fire without the trigger's key needs an admin.
  • With CSP_MODE=enforce, inline scripts need the nonce; report-only, the default,
    blocks nothing.
  • SUPABASE_BUCKET defaults to artifacts everywhere; set it if you relied on
    public-bucket.

Migrations V037 to V043 run on startup.

Added

  • Trace Replay on the Visual Builder canvas - a traced chat session can be played back on the agent graph as one run, turn after turn (the time between turns cut to a short pause): agents light up in the order they ran, the edge into a sub-agent lights up while it is handed the conversation, each agent shows the model or tool it is using and how long it has run, finished agents show their time, and an agent whose model failed shows which fallback model answered. The session list only offers sessions of the project on the canvas. Open it with Replay in the Visual Builder toolbar, or Replay on canvas on a trace. It reads the trace_spans rows recorded with OTEL_TRACING_ENABLED=true (ADK runtime only), through two new admin routes, GET /dashboard/api/traces/sessions and GET /dashboard/api/traces/replay. The gen_ai.inference span now records mate.model.fallback and the fallback as gen_ai.response.model when the fallback answered. See docs/user/trace-replay.md
  • Docker image on GitHub Container Registry (#249) - every release tag now publishes ghcr.io/antiv/mate for linux/amd64 and linux/arm64, tagged 1.5.0, 1.5 and latest, so MATE runs with one docker run and no clone or local build. The workflow starts the image and checks /health before it pushes anything. See docs/dev/deployment.md
  • Agent MCP servers offer a prompt and a resource - next to its tool, each agent exposed over MCP now lists the prompt ask_<agent>, which asks the agent a question through its tool, and the resource mate://agents/<agent>/about, with the agent's name, description and how to call it (not its instruction). initialize announces both capabilities. See docs/dev/mcp-servers.md
  • GET /mcp lists the agents exposed over MCP (#231) - with a personal access token (or any other way the agent servers accept), it returns the agents in MCP_EXPOSED_AGENTS that the caller may use, each with its name, description and full MCP URL, so a client can be set up without knowing the names. See docs/dev/mcp-servers.md
  • Agent MCP servers describe themselves (#231) - the initialize response of /agents/<name>/mcp now carries the agent's description in serverInfo (title, description) and as instructions. MCP registries read it: without it the LobeHub market CLI (lhm plugin init) refused the server with "Could not infer a plugin description"
  • Playground: compare models and instructions side by side (#178) - on the Evals page, a Playground tab runs one prompt, or an agent's active eval suite, on two or three variants of the agent: its current config or a stored version, with another model and/or other instructions. It shows each variant's average score, reply time, cost in US dollars and tokens, and every reply with its own score, time and cost. Variants run in memory like eval runs: nothing is saved or deployed, and a variant that cannot run shows its error while the others still run
  • Cost in US dollars, not only tokens (#178) - tokens were tracked per agent, user and project, but no price was shown anywhere. Every model call is now priced when it is logged (token_usage_logs.cost_usd), and the Usage page shows the period's cost, the cost per agent and per project, and what agents' fallback models cost apart (calls a fallback answered are now marked, is_fallback). Prices come from a price set on the new Model prices part of the Settings page, then for openrouter/ models OpenRouter's own price list (LiteLLM has no entry for most of them, and reports 0 for those), then LiteLLM's. A model none of them prices has no cost rather than a cost of $0, and the Usage page shows the share of tokens left out. Setting a price reprices the model's past calls; Fill in missing costs prices calls logged before their model had a price, and openrouter/ calls logged before OpenRouter's list arrived are priced as soon as it does. Costs are list prices, without discounts for cached input. Rate Limits takes budgets in dollars per day and per month for a user, agent or project (usd_per_day, usd_per_month), each measured against that scope's own spend, with the same warn or block actions, and the Budget threshold alert can be in dollars, with its own limit or the Rate Limits one. Calls without a price do not count toward a dollar budget
  • The image tool takes any image model LiteLLM supports - the agent form offered three models, and the tool had two hand-written paths: OpenAI's images.generate and a separate Nano Banana path through OpenRouter chat, so Imagen, FLUX, Stability, Recraft, xAI or Bedrock models could not be used. Generation now goes through litellm.aimage_generation, except for OpenRouter models, which go through its chat completions asking for an image (modalities), since LiteLLM's image call for OpenRouter leaves that out and the model then answers in text only. Models are named as for agents (gemini/gemini-2.5-flash-image, black_forest_labs/flux-pro-1.1, ...) and each provider's usual key; Gemini also takes GOOGLE_API_KEY. Image Model in the agent form is a text field with suggestions. The default for agents that name none is set on a new Settings page in the dashboard (Control Room → System), or with the new IMAGE_MODEL, else dall-e-3; the page shows which applies and whether its provider's key is set, and every change is audited. The page stores its values in a new system_settings table (migration V040). Existing configs keep working: bare OpenAI names with the same defaults and key fallbacks, nano-banana, and the tool name generate_image_nano_banana. Images are saved with the type they came in (JPEG, WebP, ...) instead of always as PNG; only PNGs carry the Art. 50 AI-generated marker. The setup check behind /images/mcp/health now looks for the default model's key instead of listing OpenAI models. See docs/dev/image-generation.md
  • Alert when agents answer on their fallback model - the fallback model hides a provider outage from the person chatting, and the Agent errors condition could not show it either: on ADK it counts the primary's failure like any other error, on LangGraph not at all. A new Fallback model used condition counts the requests a fallback answered in a time window, per agent, project, user or globally, from the agent.model_fallback audit entries both runtimes write. Its webhook event is model_fallback_alert. See docs/user/alerts.md
  • Slack and Discord as alert destinations - an alert posted to a Slack or Discord webhook URL as an HTTP destination was refused, since those take a fixed body (text, content) rather than MATE's payload. Alert rules can now name a Slack (incoming webhook) or Discord (webhook) destination, which posts the alert message as text. Slack's < > & are escaped and Discord's mentions turned off, so a rule or agent name cannot ping the channel, and the rule list shows the destination type instead of the webhook URL. See docs/user/alerts.md
  • Ratings from standalone builds - a standalone build showed no 👍/👎, since it had nowhere to keep them, so whoever distributed one never learned which answers were bad. With MATE_FEEDBACK_URL and MATE_FEEDBACK_KEY (the standalone feedback key of a widget key for the same agent, shown under Embed Code) in the build's .env, the chat shows the buttons and the note after a thumbs-down, and the build forwards each rating to that MATE, where it lands in Evals → Rated Down with Add to evals and Suggest a fix. The central server cannot read the build's sessions, so the build sends the rated question and answer, read from its own session store, never from the browser; they are stored on the rating (migration V038) and shown as sent by a standalone build, and used only when the server has no session for it. The server takes them only with the feedback key (X-Widget-Feedback-Key, migration V039 generates one for every widget key): with the public key, which is in every page that embeds the widget, the rating is recorded and the text ignored, so a visitor cannot feed made-up exchanges to Suggest a fix. A rating can no longer be changed through another agent's widget key. The build's /feedback route takes at most 30 ratings a minute per client, and forgets idle clients. Off by default. See docs/dev/standalone-build.md (#125)

Changed

  • Patch releases for an older minor version - a release tag now marks the GitHub release as Latest and the Docker image as latest only when it is the highest final vX.Y.Z tag, so a v1.5.1 cut from a release/1.5 branch after v2.0.0 leaves both on 2.0.0. CI also runs on pushes to release/**. How releases and maintenance branches work: docs/dev/releases.md
  • Dollar budgets can see calls without a price and sub-agents (#222) - a dollar budget could quietly never trigger: calls on a model with no price (openrouter/ models while OpenRouter's price list can't be fetched, local or new models) added nothing to the spend, a database error while summing counted as $0 and let the request through, and an agent's budget left out what its sub-agents spent. Each Rate Limits config now has Treat calls without a price as over the dollar budget (block_unpriced) and, for agents, Include sub-agents (include_sub_agents, counting the agent and every agent below it in its daily token limit and dollar budgets); both are off by default (migration V043). A Block dollar budget now refuses the request when its spend cannot be read. The Usage vs Limits panel warns when the scope had calls without a price, and a dollar Budget threshold alert says how many calls it left out
  • google-adk 2.9.2 → 2.11.0 (#147) - ADK 2.10 and 2.11 list no breaking changes; among their fixes, /run_sse now cancels a run when its client disconnects, and a new index on the session events table is added on startup. Only ADK moves: the rest of the lock waits for the monthly upgrade, which takes releases at least a week old. Checked with the unit tests and a real conversation through the auth server on both runtimes, on SQLite and on PostgreSQL 16, which also ran migrations V037 to V040 there for the first time
  • Monthly dependency upgrade (#147) - a scheduled workflow recompiles requirements.lock on the 1st of each month with the newest allowed releases at least a week old (a package the lock already has at a newer release stays there rather than moving back), runs the tests on it in a job with a read-only token and no stored credentials, and opens a pull request listing what moved from a second job that installs nothing; actions are pinned by commit; Dependabot keeps the GitHub Actions current. See the README's Dependencies section
  • Migration V037 drops the CHECK constraints V026 put on alert_rules.condition_type and destination_type (SQLite rebuilds the table, keeping every rule), which refused any type added after them. The dashboard API validates both lists, and tables created by SQLAlchemy never had the constraints
  • One default per environment variable (#157) - SUPABASE_BUCKET defaults to artifacts everywhere. The Supabase storage tools used public-bucket, so with the variable unset they and the artifact service wrote to different buckets; if you relied on that, set SUPABASE_BUCKET. Image links for Supabase artifacts are now built with that default too, where they used to be left out. Because the tools and the artifacts now share a bucket by default, the Supabase storage tools refuse paths under public/, where the artifact service keeps every user's chat artifacts; an agent steered by a chat message could otherwise read, overwrite or delete them. This applied already to deployments that set one bucket for both, as the old .env.example did. DB_NAME defaults to mate_agent for the application database as it already did for sessions, so PostgreSQL and MySQL need only DB_USER and DB_PASSWORD. Defaults now live in shared/utils/settings.py
  • get_otlp_endpoint() and get_otlp_protocol() are removed from the tracing config; nothing called them, and the http://localhost:4318 default they advertised never applied: without OTEL_EXPORTER_OTLP_ENDPOINT no OTLP exporter is added

Fixed

  • Traces showed no span attributes on PostgreSQL - trace_spans.attributes is JSONB there, which the driver returns already parsed, and the Traces page only parsed text, so every span showed none. It now takes both
  • A2A was off on the pinned a2a-sdk 1.2.1 (#223) - adk_main.py was written for a2a-sdk 0.3, whose a2a.server.apps no longer exists in 1.x. Starting with --a2a, which the dashboard always does, printed "A2A dependencies not available" and mounted no /a2a/* routes, so A2A calls failed, and so did agents exposed over MCP, which go through A2A.
    • The fix: the routes are now built through ADK's google.adk.a2a._compat (shared/utils/a2a_routes.py), which works on 0.3 and 1.x.
    • Method names: /a2a/{agent} accepts the 1.x methods (SendMessage, GetTask, ...) and the 0.3 ones (message/send, tasks/get, ...).
    • The card: it is served in the 1.x form (supportedInterfaces). It now names the agent's own endpoint (/a2a/{agent}) instead of the server root. Its input and output modes are text/plain, since the agent reads and writes text. An agents/<name>/agent.json in the 0.3 form is still read and converted.
    • MCP replies: the MCP bridge returns the agent's reply text, taken from the task's artifacts, instead of the whole task printed as a dictionary.
    • A test now fails when a dependency bump breaks the routes; until now the only sign was a line at startup.
  • MATE did not start on a new MySQL database (#178) - the schema was never created on a new MySQL database, so the server ran without tables.
    • The migrations stopped at V001. MySQL takes a default on a TEXT column only as an expression (DEFAULT ('x')), and the credentials unique key was longer than MySQL's 3072 bytes. Twenty later migrations then failed on the missing tables or on ADD COLUMN IF NOT EXISTS, which MySQL does not have.
    • The fix edits the released MySQL files, which the guide otherwise rules out. Those statements never ran on any MySQL, and nothing re-runs a version that is already recorded.
    • The credentials key now takes the first 255 characters of credential_key.
    • Re-runs skip indexes that exist. On MySQL, a re-run now skips "Duplicate key name" errors, as it already skipped duplicate columns.
    • The agent server kept no sessions on MySQL. It did not recognise the mysql+pymysql:// session URL and fell back to an in-memory store, which also failed to start, so every new chat failed. Sessions are now stored in MySQL through aiomysql.
    • Checked on a fresh MySQL 8.0: all 42 migrations at startup, every dashboard page, prices, dollar budgets and alert rules, and a conversation, which was stored and priced.
  • DB_TYPE=mysql failed at startup on a default install - MATE connects to MySQL with pymysql, and ADK keeps sessions on MySQL with aiomysql, but both were commented out in requirements.txt and missing from requirements.lock, so the server stopped with No module named 'pymysql'. Both are now installed, like the PostgreSQL drivers (#178)
  • The Visual Builder's History button did nothing - the agent form on the Visual Builder has a History button, but the page loaded neither the version history panel nor its script, so clicking it raised an error. It now opens the agent's version history, as on the Agents page
  • shared/migrate.py without a .env looked for PostgreSQL (#157) - run on its own, the migration runner fell back to DB_TYPE=postgresql and, for SQLite, to mate_agent.db relative to the working directory, while the application uses SQLite and my_agent_data.db in the project root. migrate.py status and run therefore failed with "No database engine available", or migrated a different file. The runner, the database client, the session store and the dashboard now read the database settings from one place
  • MySQL sessions used port 5432 (#157) - with DB_TYPE=mysql and DB_PORT unset, the application database used 3306 but the ADK session store 5432
  • Alerts from a global rule read "global None" - every message from a rule with the global scope began "global None …", in emails, webhooks and, since the Slack and Discord destinations, in chat. It now says "all agents". The message text of existing HTTP and email alerts changes accordingly; scope and scope_id in the payload do not. The fallback alert's message now reads "The fallback model answered for … N times …"
  • The widget showed agent server errors cut off (#229) - the widget chat route copied every header of the agent server's /run_sse reply, Content-Length included, and put its session event in front of the body, so an error reply ran past its length and was cut off, with an SSE line in front of the JSON. It no longer copies Content-Length, Content-Encoding, Transfer-Encoding, Connection or Keep-Alive, and adds the session event only to a successful reply, so the widget can read an error's detail
  • The Documentation page's API tab pointed at port 8000 (#240) - Swagger and ReDoc were loaded from the dashboard's host on a hard-coded port 8000, so with the dashboard published on another port (Docker -p 9000:8000) the tab showed nothing. They now load from the auth server's own /docs and /redoc, which already forward to the agent server. Thanks to @ningyixi
  • Help panel links to https://dashboard/docs (#234) - the help agent often rewrote the /dashboard/docs?... links its tool gave it as https://dashboard/docs?..., which the panel treated as an external site and opened on a host that doesn't exist. Such links (and //dashboard/..., dashboard/...) are now turned back into /dashboard/docs before the answer is shown. Thanks to @ningyixi
  • An empty Work Room offered non-admins a link to create an agent (#239) - the link led to the Agents page, which sends non-admins back to the Work Room. It is now shown to admins only; others are told to ask an admin. Thanks to @ningyixi
  • Notifications ignored their duration (#242) - showNotification always closed after 5 seconds, so the Triggers page's test-fire result, meant to stay 8 seconds, went as fast as the rest. It now takes the duration, 5 seconds by default. Thanks to @kocaemre
  • The Visual Builder's Add MCP Server and new agent dialogs were always dark (#245) - they used fixed dark colors and now use the dashboard's theme colors, so they follow the light theme. Thanks to @CRYPTONIKAV
  • Icon-only buttons on every page had no name for screen readers (#246) - the agent server's Start, Stop and Restart, the sidebar toggle, and the close buttons of the tokens dialog and of notifications now have an aria-label. Thanks to @CRYPTONIKAV
  • The Integrations page showed the Control Room sidebar (#237) - /dashboard/integrations was missing from the Studio pages in base.html, so it opened with the Control Room menu and its own item was nowhere to be seen. Thanks to @sammist2
  • Rate Limits had an Alert webhook URL field that did nothing (#241) - budget alerts moved to the Alerts page in V026, which copied the old URLs into alert rules, but the form still offered the field. It is gone; the column stays, and saving a config through the dashboard now clears it. Thanks to @cestercian

Security

  • A widget admin key could change another agent's files - the widget admin file routes took any store_name for an upload and deleted any file search document by id, so the admin key of one widget could add files to, or delete files from, any agent's stores, in any project. POST /widget/api/files/upload now refuses a store not linked to the key's agent (404), and DELETE /widget/api/files/{id} answers "File not found" for a file in such a store
  • Any signed-in user could fire a trigger - POST /triggers/{id}/fire without the trigger's key fell back to dashboard login, which accepts any user, not only admins, and the route sits outside /dashboard/api, where admin-only access is enforced. Without the key, firing a trigger now needs an admin (403 otherwise); callers with the fire key are unchanged. See docs/dev/trigger-engine.md
  • "My Usage" showed every user's usage - the usage page is open to people without the admin role, as My Usage, but it rendered the figures of the whole installation: totals, the busiest agents, cost by agent and project, ratings and latency, and in the Raw Data view the last day's model calls of every user, with their user IDs. The page now narrows every figure to the signed-in user's own traffic unless they are an admin; ratings are those given in their own conversations. The APIs behind the page were already admin-only
  • Confirmation dialogs showed names as HTML - showConfirm put its title, message and button labels into the page as markup, and some messages carry a name someone else chose (an imported template, a rate-limit scope such as a widget visitor's user ID). They are set as text now
  • The agent and Google Drive MCP servers needed no login (#231) - every route of /agents/<name>/mcp and /gdrive/mcp but the health check answered without credentials. Anyone who reached the server could run the agents in MCP_EXPOSED_AGENTS on its provider keys, outside per-user budgets and the agent's allowed_for_roles (the call reaches the agent over A2A without the caller's identity), and could list, search and read Google Drive files, sharing permissions included, through the server's service account. Both now take the callers the OpenAI-compatible API takes: a personal access token (Authorization: Bearer mate_pat_...), Basic auth, the login token or a dashboard session, of an admin or a user with a role in ALLOWED_API_ROLES; an agent's routes also require a role the agent allows, and an agent with none is admin-only. Without credentials they answer 401, without access 403. MCP clients that connected without credentials, including other MATE instances that use these agents through mcp_servers_config, now need a token; ${VAR} in headers keeps it out of the config. See docs/dev/mcp-servers.md
  • An empty AUTH_PASSWORD let anyone log in with empty fields - .env.example lists AUTH_USERNAME and AUTH_PASSWORD empty, and os.getenv returns "" for them rather than its default. So the Quick Start's cp .env.example .env configured an empty username and password, and both the login form and Basic auth (curl -u ":") were accepted, admin APIs included. The production check looked only for the default password mate, so it let an empty one through, also with MATE_ENV=production. Empty values now count as unset: the built-in account is admin / mate, which development warns about and production refuses at startup. An empty password never matches, whatever is configured. The credentials are read in one place, shared/utils/settings.py
  • Python in the Work Room canvas runs in the sandbox - HTML and JavaScript already ran on the sandboxed canvas page, but Python ran in an unsandboxed frame on MATE's origin. An agent writes that code, so instructions planted in what it reads (a web page, a document) could produce Python whose js module acted as the person who clicked Run: an admin's session could create users or read agent configs, keys included. Python now runs on the sandboxed page too, where it can reach neither the dashboard nor MATE's storage; Open in new tab also runs Python there, instead of treating it as JavaScript.
  • CSP allows scripts from jsdelivr and cdnjs only by path - script-src allowed the whole of cdn.jsdelivr.net and cdnjs.cloudflare.com, which serve any npm package or GitHub repository, so an HTML injection could load the attacker's own script even under enforce. The policy now lists each library's path with its version (Chart.js, Monaco, React and React Flow, markdown-it, Swagger UI, Redoc, Ace), and a test fails on a template script it does not cover. 'wasm-unsafe-eval' is gone from the dashboard's policy, since Pyodide no longer runs there. A custom template that loads another script from those CDNs needs its path in CSP_EXTRA_SOURCES
  • OpenRouter prices are checked before use - a price that is not a finite number from 0 to $0.1 per token is skipped, so a NaN in the list can no longer make every sum over a model's calls NaN and with it the dollar budgets, and the list is read up to 25 MB
  • Playground runs are audited - each run writes evals.playground_run with the admin who started it, the mode, and each variant's model and version; changed instructions are recorded by length and hash, not as text. The runs themselves are logged under the eval user, so this is what ties them to a person
  • CSP no longer allows unpkg.com and d3js.org - the only page that loaded from them, templates/graph.html (a graph view no route served), is removed. A custom template that loads a script from either host now needs it in CSP_EXTRA_SOURCES
  • CSP: enforce now blocks inline scripts without the nonce, and inline handlers (#124) - every response gets a fresh nonce, and the policy allows inline scripts only with it and no inline onclick= handlers. Without 'unsafe-inline', the policy stops an injected script rather than only limiting where it loads code from. CSP_MODE=enforce enforces it; in 1.4 it enforced a policy with 'unsafe-inline'. CSP_MODE=report-only (the default) reports against it and blocks nothing; it is also the way back if a page breaks under enforce. ADK's dev UI, whose markup is not MATE's, keeps 'unsafe-inline'. Handlers become data-click actions (static/js/csp-actions.js), which call only functions the page allows. Converted: the layout every dashboard page shares, the login page, the overview, the Agents page and the Visual Builder with all their modals, the monitoring pages (alerts, audit logs, guardrail logs, sessions, traces, rate limits, integrations, triggers), evals, usage, users, migrations, docs, the template gallery and the wizard pages (leads, orders, pricing), and the Work Room, the widget chat and admin pages and the standalone chat: every page. The Work Room canvas runs agent code on a page of its own (/dashboard/workroom/canvas) whose policy sandboxes it, so that code keeps its inline scripts, onclick= and CDNs once the dashboard's policy is strict. A library from a CDN no list names (cdn.plot.ly) now loads in the canvas too. The admin Swagger UI's inline script gets the nonce, and the agent wizard (/wizard/embed) takes its frame-ancestors from the partner's allowed origins: it had the dashboard's frame-ancestors 'self', so under CSP_MODE=enforce no partner site could frame it. Doing so also fixed:
    • the token list's Revoke button, which put the token name into a JavaScript string unescaped;
    • the file search store list, which did the same with store names;
    • the wizard leads list, which did the same with the company name a visitor typed into the wizard;
    • the integrations list, which showed labels, agent names and team IDs as HTML and put each row into an inline handler unescaped;
    • the usage log table and its details, which showed user IDs, agent and model names and error text as HTML (a widget visitor partly chooses their user ID);
    • the migrations page's notices, which showed a server error as HTML;
    • the widget admin panel's memory block list, which put block IDs into an inline handler unescaped
  • "Open in new tab" ran canvas code on MATE's origin - the Work Room canvas opened an agent's HTML or JavaScript in a new tab as a blob: URL. A blob: page has the origin of the page that made it and no sandbox, so code an agent wrote, steered by a prompt injection for example, could call the dashboard API as whoever clicked. The tab now opens the sandboxed canvas page, whose code has no access to MATE
  • The image tool could be pointed anywhere through tool_config - since the image tool moved to LiteLLM (unreleased), every tool_config.image_tools key but a short denylist reached litellm.aimage_generation. An agent steered by a chat into update_agent could set mock_response to an internal URL, which the tool then downloaded and saved as an artifact (cloud metadata, the ADK server), route every prompt to an outside logging host, or point Bedrock at its own endpoint. Only image parameters pass now (size, quality, n, style, aspect_ratio, ...), and an image URL from a provider is downloaded only over http(s), without redirects, at most 25 MB and not from a private address (IMAGE_ALLOW_PRIVATE_NETWORK allows it for a local model)
  • /images/mcp needed no login - its tools spend the server's provider keys, and the Nano Banana tool took the model and request parameters from the caller. Every route but the health check now needs a signed-in caller, callers can no longer choose the model, and the public health check no longer names the default model or missing keys. The image MCP server had also been off since the image tool change, whatever keys were set: its startup check read fields the setup check no longer returns
  • Notifications rendered their message as HTML - showNotification put its text into the page as markup, and some messages echo server or user input (an error naming the model you typed). It is set as text now, for every dashboard page
  • Image generation errors given to the agent carry the first line of the provider's message, not LiteLLM's traceback and server paths, which stay in the log