Fixed AppStream metainfo release for Linux Flatpak (#918)

This commit is contained in:
Thorsten Sommer 2026-08-13 10:43:13 +02:00 committed by GitHub
parent 6001cb5f42
commit abe450c320
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 249 additions and 32 deletions

View File

@ -91,6 +91,40 @@ public sealed partial class UpdateMetadataCommands
await this.Build(offline);
}
[Command("update-metainfo", Description = "Update the AppStream metainfo entry of one release from its changelog")]
public async Task UpdateMetainfo(
[Option("version", ['v'], Description = "The release version, e.g., 26.1.2. Defaults to the version from the metadata")] string? version = null,
[Option("date", ['d'], Description = "The release date as yyyy-MM-dd. Defaults to the build time from the metadata")] string? date = null)
{
const int APP_VERSION_INDEX = 0;
const int BUILD_TIME_INDEX = 1;
if(!Environment.IsWorkingDirectoryValid())
return;
Console.WriteLine("==============================");
try
{
var metadataLines = SplitLines(await File.ReadAllTextAsync(Environment.GetMetadataPath(), Encoding.UTF8));
var appVersion = string.IsNullOrWhiteSpace(version) ? metadataLines[APP_VERSION_INDEX].Trim() : version.Trim();
if (!ExactAppVersionRegex().IsMatch(appVersion))
throw new InvalidOperationException($"The version '{appVersion}' is not a valid app version.");
DateTime releaseTime;
if (string.IsNullOrWhiteSpace(date))
releaseTime = ParseMetadataBuildTime(metadataLines[BUILD_TIME_INDEX]);
else if (!DateTime.TryParseExact(date.Trim(), "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out releaseTime))
throw new InvalidOperationException($"The release date '{date}' is not a valid date in the yyyy-MM-dd format.");
await WriteMetainfoRelease(appVersion, releaseTime);
}
catch (InvalidOperationException exception)
{
Console.WriteLine($"- Error: {exception.Message}");
}
}
[Command("update-versions", Description = "The command will update the package versions in the metadata file")]
public async Task UpdateVersions()
{
@ -154,10 +188,20 @@ public sealed partial class UpdateMetadataCommands
var appVersion = await this.UpdateAppVersion(action, version);
if (!string.IsNullOrWhiteSpace(appVersion.VersionText))
{
// The changelog is the source for the AppStream description. Check it before we write
// any further metadata, so that a missing changelog cannot leave a half-prepared release:
var changelogPath = GetChangelogPath(appVersion.VersionText);
if (!File.Exists(changelogPath))
{
Console.WriteLine($"- Error: The changelog file '{Path.GetFileName(changelogPath)}' does not exist.");
return;
}
var buildNumber = await this.IncreaseBuildNumber();
var buildTime = await this.UpdateBuildTime();
await this.UpdateChangelog(buildNumber, appVersion.VersionText, buildTime);
await this.CreateNextChangelog(buildNumber, appVersion);
await WriteMetainfoRelease(appVersion.VersionText, ParseMetadataBuildTime(buildTime));
await this.UpdateProjectCommitHash();
await this.UpdateReleaseDependenciesAndLicence();
Console.WriteLine();
@ -413,9 +457,7 @@ public sealed partial class UpdateMetadataCommands
if (!ExactAppVersionRegex().IsMatch(appVersion))
throw new InvalidOperationException($"The metadata version '{appVersion}' is not a valid app version.");
if (!DateTime.TryParseExact(metadataLines[BUILD_TIME_INDEX].Trim(), "yyyy-MM-dd HH:mm:ss 'UTC'", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var buildTime))
throw new InvalidOperationException($"The metadata build time '{metadataLines[BUILD_TIME_INDEX]}' is not a valid UTC build time.");
var buildTime = ParseMetadataBuildTime(metadataLines[BUILD_TIME_INDEX]);
if (!int.TryParse(metadataLines[BUILD_NUMBER_INDEX].Trim(), out var buildNumber))
throw new InvalidOperationException($"The metadata build number '{metadataLines[BUILD_NUMBER_INDEX]}' is not a number.");
@ -455,19 +497,15 @@ public sealed partial class UpdateMetadataCommands
throw new InvalidOperationException($"Expected exactly one future changelog reserving build {nextChangelogBuildNumber}, but found {nextChangelogCandidates.Count}.");
var nextChangelog = nextChangelogCandidates[0];
var metainfoPath = Path.Combine(Environment.GetRustRuntimeDirectory(), "packaging", "linux", "org.mindworkai.AIStudio.metainfo.xml");
// The release entry itself is written by ApplyRebuildReleaseState, which adds it when it is
// missing and moves it to the top otherwise. Here, we only ensure that there is a file to write to:
var metainfoPath = GetMetainfoPath();
if (!File.Exists(metainfoPath))
throw new InvalidOperationException("The AppStream metainfo file does not exist.");
var metainfoContent = await File.ReadAllTextAsync(metainfoPath, Encoding.UTF8);
var releaseTags = ReleaseTagRegex().Matches(metainfoContent).Cast<Match>().ToList();
var matchingReleaseTags = releaseTags.Where(match => ReleaseTagHasVersion(match.Value, appVersion)).ToList();
if (matchingReleaseTags.Count != 1 || releaseTags.Count == 0 || matchingReleaseTags[0].Index != releaseTags[0].Index)
throw new InvalidOperationException($"The AppStream metainfo must contain v{appVersion} exactly once as its first release.");
var metainfoReleaseTag = matchingReleaseTags[0].Value;
if (!StableReleaseTypeRegex().IsMatch(metainfoReleaseTag) || !ReleaseDateRegex().IsMatch(metainfoReleaseTag))
throw new InvalidOperationException($"The AppStream entry for v{appVersion} must be stable and contain a release date.");
if (!ReleasesStartRegex().IsMatch(await File.ReadAllTextAsync(metainfoPath, Encoding.UTF8)))
throw new InvalidOperationException("The AppStream metainfo does not contain a <releases> element.");
var headCommitHash = (await this.ReadCommandOutput(Environment.GetAIStudioDirectory(), "git", "rev-parse HEAD")).Trim();
if (!GitCommitHashRegex().IsMatch(headCommitHash))
@ -489,9 +527,6 @@ public sealed partial class UpdateMetadataCommands
nextChangelog.Content,
nextChangelog.Header,
nextChangelog.Version,
metainfoPath,
metainfoContent,
metainfoReleaseTag,
headCommitHash[..11]);
}
@ -530,11 +565,119 @@ public sealed partial class UpdateMetadataCommands
await File.WriteAllTextAsync(releaseState.NextChangelogPath, updatedNextChangelog, Environment.UTF8_NO_BOM);
Console.WriteLine($"- Reserved build {buildNumber + 1} for '{Path.GetFileName(releaseState.NextChangelogPath)}'.");
var releaseDate = buildTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
var updatedMetainfoReleaseTag = ReleaseDateRegex().Replace(releaseState.MetainfoReleaseTag, $"date=\"{releaseDate}\"", 1);
var updatedMetainfo = ReplaceExactlyOnce(releaseState.MetainfoContent, releaseState.MetainfoReleaseTag, updatedMetainfoReleaseTag);
await File.WriteAllTextAsync(releaseState.MetainfoPath, updatedMetainfo, Environment.UTF8_NO_BOM);
Console.WriteLine($"- Updated the AppStream release date to '{releaseDate}'.");
await WriteMetainfoRelease(releaseState.AppVersion, buildTime);
}
private static string GetMetainfoPath() => Path.Combine(Environment.GetRustRuntimeDirectory(), "packaging", "linux", "org.mindworkai.AIStudio.metainfo.xml");
private static string GetChangelogPath(string appVersion) => Path.Combine(Environment.GetAIStudioDirectory(), "wwwroot", "changelog", $"v{appVersion}.md");
/// <summary>
/// Writes the AppStream release entry for the given version, using the changelog of that version as its description.
/// </summary>
/// <remarks>
/// The entry always becomes the first release, and any earlier entry of the same version is replaced. This is what
/// the Flatpak pipeline validates through 'update-metainfo.py --check' before it syncs a release. The release date
/// is derived from the build time, because the pipeline reads it from the second line of the metadata file.
/// </remarks>
private static async Task WriteMetainfoRelease(string appVersion, DateTime releaseTime)
{
const string RELEASE_INDENT = " ";
var metainfoPath = GetMetainfoPath();
if (!File.Exists(metainfoPath))
throw new InvalidOperationException("The AppStream metainfo file does not exist.");
var metainfo = await File.ReadAllTextAsync(metainfoPath, Encoding.UTF8);
if (!ReleasesStartRegex().IsMatch(metainfo))
throw new InvalidOperationException("The AppStream metainfo does not contain a <releases> element.");
var changelogEntries = await ReadChangelogEntries(appVersion);
// Drop any earlier entry of this version, so that the version stays unique and moves to the top.
// We remove from the back, so that the index of the remaining matches stays valid:
foreach (var previousRelease in ReleaseBlockRegex().Matches(metainfo).Cast<Match>().Where(match => ReleaseTagHasVersion(match.Value, appVersion)).Reverse())
metainfo = metainfo.Remove(previousRelease.Index, previousRelease.Length);
var lineEnding = metainfo.Contains("\r\n", StringComparison.Ordinal) ? "\r\n" : "\n";
var releaseDate = releaseTime.ToUniversalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
var releaseBlock = new StringBuilder();
releaseBlock.Append($"{RELEASE_INDENT}<release type=\"stable\" version=\"{appVersion}\" date=\"{releaseDate}\">{lineEnding}");
releaseBlock.Append($"{RELEASE_INDENT} <description>{lineEnding}");
releaseBlock.Append($"{RELEASE_INDENT} <ul>{lineEnding}");
foreach (var changelogEntry in changelogEntries)
releaseBlock.Append($"{RELEASE_INDENT} <li>{changelogEntry}</li>{lineEnding}");
releaseBlock.Append($"{RELEASE_INDENT} </ul>{lineEnding}");
releaseBlock.Append($"{RELEASE_INDENT} </description>{lineEnding}");
releaseBlock.Append($"{RELEASE_INDENT}</release>{lineEnding}");
var releasesStart = ReleasesStartRegex().Match(metainfo);
var insertionPoint = releasesStart.Index + releasesStart.Length;
if (metainfo.AsSpan(insertionPoint).StartsWith(lineEnding))
insertionPoint += lineEnding.Length;
else
releaseBlock.Insert(0, lineEnding);
metainfo = metainfo.Insert(insertionPoint, releaseBlock.ToString());
await File.WriteAllTextAsync(metainfoPath, metainfo, Environment.UTF8_NO_BOM);
Console.WriteLine($"- Updated the AppStream metainfo for v{appVersion}, released on {releaseDate}, with {changelogEntries.Count} changelog entries.");
}
private static async Task<IReadOnlyList<string>> ReadChangelogEntries(string appVersion)
{
var changelogPath = GetChangelogPath(appVersion);
if (!File.Exists(changelogPath))
throw new InvalidOperationException($"The changelog file '{Path.GetFileName(changelogPath)}' does not exist.");
// The first line is the changelog header, every other non-empty line must be a changelog entry:
var changelogLines = SplitLines(await File.ReadAllTextAsync(changelogPath, Encoding.UTF8));
var changelogEntries = new List<string>();
foreach (var changelogLine in changelogLines.Skip(1))
{
var changelogEntry = changelogLine.Trim();
if (changelogEntry.Length is 0)
continue;
if (!changelogEntry.StartsWith("- ", StringComparison.Ordinal))
throw new InvalidOperationException($"The changelog '{Path.GetFileName(changelogPath)}' contains a line which is no changelog entry: '{changelogEntry}'.");
changelogEntries.Add(ConvertChangelogEntryToAppStream(changelogEntry[2..].Trim()));
}
if (changelogEntries.Count is 0)
throw new InvalidOperationException($"The changelog '{Path.GetFileName(changelogPath)}' does not contain any entry.");
return changelogEntries;
}
private static string ConvertChangelogEntryToAppStream(string changelogEntry)
{
var escapedEntry = changelogEntry
.Replace("&", "&amp;", StringComparison.Ordinal)
.Replace("<", "&lt;", StringComparison.Ordinal)
.Replace(">", "&gt;", StringComparison.Ordinal);
// Markdown code spans become AppStream code elements. Every second segment is inside a code span,
// which requires an even number of markers and therefore an odd number of segments:
var codeSpans = escapedEntry.Split('`');
if (codeSpans.Length % 2 is 0)
throw new InvalidOperationException($"The changelog entry contains an unbalanced code marker: '{changelogEntry}'.");
var convertedEntry = new StringBuilder();
for (var index = 0; index < codeSpans.Length; index++)
convertedEntry.Append(index % 2 is 0 ? codeSpans[index] : $"<code>{codeSpans[index]}</code>");
return convertedEntry.ToString();
}
private static DateTime ParseMetadataBuildTime(string buildTime)
{
if (!DateTime.TryParseExact(buildTime.Trim(), "yyyy-MM-dd HH:mm:ss 'UTC'", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var parsedBuildTime))
throw new InvalidOperationException($"The metadata build time '{buildTime}' is not a valid UTC build time.");
return parsedBuildTime;
}
private static string FormatChangelogHeader(string appVersion, int buildNumber, DateTime buildTime)
@ -983,9 +1126,6 @@ public sealed partial class UpdateMetadataCommands
string NextChangelogContent,
string NextChangelogHeader,
string NextChangelogVersion,
string MetainfoPath,
string MetainfoContent,
string MetainfoReleaseTag,
string HeadCommitHash);
[GeneratedRegex("""(?ms).?(NET\s+SDK|SDK\s+\.NET)\s*:\s+Version:\s+(?<sdkVersion>[0-9.]+).+Commit:\s+(?<sdkCommit>[a-zA-Z0-9]+).+Host:\s+Version:\s+(?<hostVersion>[0-9.]+).+Commit:\s+(?<hostCommit>[a-zA-Z0-9]+)""")]
@ -1015,14 +1155,13 @@ public sealed partial class UpdateMetadataCommands
[GeneratedRegex("""^[0-9]+\.[0-9]+\.[0-9]+$""")]
private static partial Regex ExactAppVersionRegex();
[GeneratedRegex("""<release\b[^>]*>""")]
private static partial Regex ReleaseTagRegex();
[GeneratedRegex("""<releases\b[^>]*>""")]
private static partial Regex ReleasesStartRegex();
[GeneratedRegex("\\btype=\"stable\"")]
private static partial Regex StableReleaseTypeRegex();
[GeneratedRegex("\\bdate=\"[^\"]*\"")]
private static partial Regex ReleaseDateRegex();
// Matches one entire release element, including its indentation and its trailing line break. The
// self-closing form comes first, so that it is never mistaken for the start of a longer element:
[GeneratedRegex("""(?ms)^[ \t]*<release\b[^>]*/>[ \t]*\r?\n?|^[ \t]*<release\b[^>]*>.*?</release>[ \t]*\r?\n?""")]
private static partial Regex ReleaseBlockRegex();
[GeneratedRegex("^[0-9a-fA-F]{40,64}$")]
private static partial Regex GitCommitHashRegex();

View File

@ -102,9 +102,87 @@
</screenshots>
<releases>
<release type="stable" version="26.8.1" date="2026-08-13">
<description>
<ul>
<li>Added Hetzner's experimental inference API as an LLM provider. It runs open-source models in the EU and supports text and image chats through its OpenAI-compatible API.</li>
<li>Added support for the new open source models DeepSeek V4 Flash and Pro, GLM 5.2, Kimi K2.7 Code and K3, as well as Qwen 3.6 and 3.8.</li>
<li>Added a prototype Visual Briefing Assistant that turns documents, data, images, audio, and video into self-contained interactive HTML briefings. When you want to test it, you have to enable this preview feature in your app settings.</li>
<li>Added organization-configurable defaults and visibility controls for the Visual Briefing Assistant.</li>
<li>Added a share button for assistants, configurations, and language plugins. It uses the native share dialog on Windows and macOS. For Linux, we added an export option, which stores the plugin archive at a location of your choice. When you work on a translation for a new language, you can now hand your current state to testers or to us with one click.</li>
<li>Added the option to install plugin archives from your files: use the import button on the plugin page or simply drop an archive onto that page. Assistants, configurations, and language plugins are supported, and plugin archives now have their own file extension <code>.mwplugin</code>. Before installing a configuration, AI Studio shows what it sets up: which LLM providers and data sources it adds and where each of them sends your data, plus how many settings it takes control of. A configuration takes effect right away and has no on/off switch, so please install one only when you trust its source. You can remove it again at any time.</li>
<li>Added a delete button for assistants, configurations, and language plugins you installed or placed yourself. Until now, such a plugin could only be removed from the data directory by hand, which was especially painful for configurations because they have no on/off switch. Before deleting a configuration, AI Studio lists what disappears with it, such as providers, data sources, and settings that return to their default. When you delete the language plugin you had chosen, AI Studio returns to choosing your language automatically. Plugins shipped with AI Studio and plugins deployed by your IT department cannot be deleted.</li>
<li>Added options for organizations to disable importing, sharing, and exporting plugins, with a separate option for configuration plugins. Organizations can now let people import assistants while keeping configurations to their IT department.</li>
<li>Added a priority for configuration plugins. Organizations that deploy several configurations can now decide which one wins: a configuration with a higher priority overrides the settings and providers of a lower one. This allows a company-wide base configuration that each department refines for itself.</li>
<li>Added the Batch Processing Assistant: process all documents of a folder in one run. Each document is sent to the AI along with your instructions either a free prompt, one of your document analysis policies, or instructions you import from a file. You choose whether each answer is stored as its own Markdown file or whether all answers are collected in one CSV results table, which you can name yourself. Every run writes a log that lists each document with its processing time, the model, the status, and the reason for any error. When you start another run on the same output folder, we ask whether you want to continue it: documents that failed or are missing from the log are processed again, which is helpful after a crash or when documents exceeded the context window of your model. A single failing document never stops the run, and you can cancel at any time. The assistant was contributed by Jens Erler (<code>j-erler</code>) and marks his first contribution to AI Studio. Thank you, Jens, for this wonderful and useful contribution.</li>
<li>Added a way for IT departments to try out a configuration before rolling it out. A configuration placed in the new <code>.config-tests</code> directory below the plugins directory acts like one your organization deployed, including the approval of assistant plugins, so a test shows exactly what colleagues will see later. No configuration server is needed for this. AI Studio empties that directory every time it starts, so a test configuration is valid for one session, and the information page reports it while it is active. The Enterprise IT documentation describes the whole procedure.</li>
<li>Added CSV and TSV files to the file types you can attach. AI Studio was already able to read them, but they could not be selected.</li>
<li>Improved how your organization's configuration behaves when a configuration plugin is present but cannot be loaded, e.g. because of an error in the plugin. Such a plugin still manages your app, so its settings, providers, data sources, profiles, and chat templates now stay in place instead of being removed.</li>
<li>Improved reading large files from slow locations such as network drives. AI Studio now waits considerably longer before it gives up, and it tells you when it does.</li>
<li>Improved how Word documents (<code>.docx</code>) and OpenDocument files (<code>.odt</code>) are read. AI Studio now reads them itself instead of handing them to Pandoc, so these documents no longer need a Pandoc installation. It reads them section by section, which keeps even large documents responsive, and it now picks up more of the document: the title, the author, headers and footers, footnotes, endnotes, and comments. This was contributed by Nils Kruthoff (<code>nilskruthoff</code>), who also wrote the library behind it. Thank you, Nils, for this great contribution.</li>
<li>Improved the order of your models. Every list of models is now sorted by provider and name, so all models of one provider stay together. Until now, models appeared in the order they were created, which meant that a model added later always showed up at the end of the list. This was especially confusing when your organization rolled out new models. In the settings, the tables for LLM providers, embeddings, and transcription are now grouped by provider and start collapsed, so even a long list stays easy to survey.</li>
<li>Changed how approvals for assistant plugins combine when your organization deploys several configurations. They now add up, so a department can approve additional assistant plugins without repeating the approvals of the company-wide configuration. Previously, the last configuration replaced all earlier approvals, which silently required a new security check for those assistants.</li>
<li>Fixed attached files reaching the AI as empty documents when AI Studio could not read them. The AI then answered as if your file had no content, and nothing pointed to a problem. AI Studio now names the cause instead, for example, an unavailable network drive, a file another program is blocking, a protected PDF, or a scanned PDF without a text layer, and it no longer attaches such a file.</li>
<li>Fixed files that are open in another program being reported as an unrecognized file type. AI Studio now tells you that the file is currently open elsewhere and asks you to close it. This also works for files on shared network drives, where a colleague might have the file open.</li>
<li>Fixed files with a wrong file extension being reported as empty. AI Studio now recognizes what a file really is by looking at its content, for example, a PowerPoint presentation that was renamed to <code>.txt</code>, and reads it accordingly. It also points out the wrong extension, so you can correct it.</li>
<li>Fixed files whose content is not text being sent as an empty document. AI Studio now tells you that the file is not readable as text, which usually means it carries a wrong file extension.</li>
<li>Fixed executable programs with a harmless file extension being read as text. They are now recognized by their content and refused.</li>
<li>Fixed a single unreadable page of a PDF silently cutting off the rest of the document. The remaining pages are now used, and AI Studio tells you which pages are missing.</li>
<li>Fixed a single unreadable sheet of a spreadsheet silently dropping all remaining sheets.</li>
<li>Fixed PDFs, text files, spreadsheets, and presentations requiring Pandoc. Only HTML files need Pandoc now, so every other file can be attached and read without it.</li>
<li>Fixed attached files that are temporarily unavailable, disappearing from your message without a word. This could happen when a file was stored on a network drive.</li>
<li>Fixed the file preview showing an empty document when reading the file failed. It now shows what went wrong, so the preview again answers what AI Studio will hand to the AI.</li>
<li>Fixed the file preview looking like an empty file while AI Studio was still reading it. Larger documents and PDFs need a moment to be read, and until now that moment looked like a file without any content. The preview now says that it is still loading and shows the content as soon as it is ready.</li>
<li>Fixed problems while reading files being missing from the log file after the first one. This made exactly those issues hard to track down that only appeared later on.</li>
<li>Fixed reset buttons in assistants. As you may have noticed in the Document Analysis Assistant, resetting it could leave content from the previous analysis visible. Reset buttons now clear previous results completely.</li>
<li>Fixed dropping files after you closed a dialog that accepts files itself. Such a dialog takes over dropped files while it is open, but never handed that role back when you closed it. Afterward, the chat and the assistants silently ignored dropped files until you switched to another page. Each time you opened such a dialog again, the problem got worse.</li>
<li>Fixed configuration-managed settings, remaining active after their configuration plugin was removed.</li>
<li>Fixed settings not returning to your own value after a configuration was removed. When a configuration takes control of a setting, AI Studio now remembers the value you had chosen before and hands it back once no configuration manages that setting anymore. This covers an IT department withdrawing a configuration, deleting one yourself, and an administrator ending a test configuration. When a configuration only suggested a value, and you changed it afterward, your choice stays as it is.</li>
<li>Fixed the integrated code editor to keep errors and other issues in plugin code visible in the footer while scrolling.</li>
<li>Fixed the trusted badge so you can now see at a glance which models are trusted. It is shown consistently for self-hosted models and models from trusted providers.</li>
<li>Fixed approvals for assistant plugins being accepted from any configuration plugin. An approval marks an assistant as safe without a security check, and the app states that your organization approved it. Only configurations your IT department deploys, or that an administrator stages for a test, can do that now; approvals from any other locally placed configuration plugin are ignored and reported in the log.</li>
<li>Fixed withdrawing a configuration your organization deployed. A configuration that declared itself as locally managed stayed on the device even after the IT department stopped deploying it, and it kept every right of an organization configuration, such as approving assistant plugins. Where a configuration is stored now decides this instead of what the configuration says about itself, so withdrawing one always takes effect. This also applies to a device that was offline while the organization changed its policy: the withdrawal is applied when AI Studio starts again.</li>
<li>Fixed preview features contributed by several configuration plugins at once. Only the most recent contribution was recognized as coming from your organization, so features enabled by another configuration looked as if you had switched them on yourself. Each configuration is now tracked separately, which lets your organization enable one preview feature company-wide and another one for a single department.</li>
<li>Fixed which configuration wins when two configuration plugins collide, e.g. by claiming the same plugin ID, by managing the same setting, or by defining the same provider. Previously, this was down to chance, so a local configuration plugin could take over parts of the configuration your IT department deployed. Configurations from your organization now always win, and every ignored attempt is reported in the log.</li>
<li>Fixed the assistant categories when your organization hides individual assistants. A category heading could stay visible above an empty area, and the Log Viewer could disappear together with the Localization assistant. Each heading now follows the assistants actually shown below it.</li>
<li>Removed the legacy PowerPoint format (<code>.ppt</code>) from the selectable file types. AI Studio has no reader for it, so such a file could be attached but never read. The modern <code>.pptx</code> format is not affected.</li>
<li>Upgraded dependencies to their latest versions to improve security and stability.</li>
</ul>
</description>
</release>
<release type="stable" version="26.7.3" date="2026-07-21">
<description>
<p>Update</p>
<ul>
<li>Added support for OpenAI GPT-5.6 Sol, Terra, and Luna; Anthropic Claude Fable 5 and Mythos 5; and Google Gemini 3 Flash, Gemini 3.1 Flash-Lite, Gemini 3.1 Pro, and Gemini 3.5 Flash.</li>
<li>Added support for OpenDocument presentations (<code>.odp</code>) when attaching and reading presentation files.</li>
<li>Added a log viewer assistant that shows AI Studio log files in a read-only view with search, log filters, highlighting, and auto-refresh.</li>
<li>Added audio and video transcription for chats and assistants. AI Studio now prepares supported media locally, sends only normalized audio to the configured transcription provider, and attaches the resulting transcript instead of the original media.</li>
<li>Added AI-assisted editing and revision for assistants created with the Assistant Builder. Thanks, Nils Kruthoff (<code>nilskruthoff</code>), for this contribution.</li>
<li>Added options to view and edit the code of AI-generated assistants and to delete your own generated assistants. Thanks, Nils Kruthoff (<code>nilskruthoff</code>), for this contribution.</li>
<li>Added enterprise configuration options to hide the last changelog and vision panels on the welcome page. Thanks, Dominic Neuburg (<code>donework</code>), for the contribution.</li>
<li>Improved the "My Tasks Assistant": you can now provide one or more documents in addition to text or use documents alone when asking to identify tasks.</li>
<li>Improved presentation imports so AI Studio can include speaker notes, slide comments, and presentation metadata in the extracted content.</li>
<li>Improved update guidance for Flatpak installations and added an enterprise option that lets organizations manage updates entirely through their IT department.</li>
<li>Improved secure API-key storage diagnostics on Linux. AI Studio now provides specific guidance when the default password collection is missing or locked, a password-manager prompt is dismissed, or no compatible Secret Service is available.</li>
<li>Improved the file dialogs to prevent opening multiple times when you click "Open" or "Save" multiple times in a row.</li>
<li>Improved assistant plugins so they clearly indicate when content from a document has been loaded and clear the indicator when the assistant is reset.</li>
<li>Improved the Assistant Builder security check so it can use the selected provider when no dedicated security audit agent provider is configured.</li>
<li>Fixed an issue that could leave AI Studio unresponsive after waking the computer from sleep. Yes, we know this was an annoying bug, and we apologize for the inconvenience.</li>
<li>Fixed connections to internal HTTPS services and enterprise configuration servers that use organization-provided root certificates on Linux.</li>
<li>Fixed enterprise configuration plugins from Windows-created ZIP files may not load correctly on Linux when the ZIP contained plugin files inside a folder.</li>
<li>Fixed the voice recording shortcut on Linux so it works globally on supported desktops and while AI Studio is focused on other Linux desktops.</li>
<li>Fixed voice recording and transcription on Linux.</li>
<li>Fixed copied content from AI Studio not remaining available on the clipboard on Linux.</li>
<li>Fixed dragging and dropping files from the home folder into the Linux Flatpak version.</li>
<li>Fixed being able to switch document analysis policies while an analysis or media transcription was still in progress.</li>
<li>Fixed file extension handling so files are recognized correctly regardless of uppercase or lowercase letters in their extensions. Thanks, Paul Schweiß, for reporting this issue.</li>
<li>Fixed AI Studio failing to start on Linux systems &amp; showing an outdated version on the Flatpak page.</li>
<li>Upgraded Rust to v1.97.1.</li>
<li>Upgraded .NET to v9.0.18.</li>
<li>Upgraded Tauri to v2.11.5.</li>
<li>Upgraded common dependencies.</li>
<li>Upgraded runtime dependencies.</li>
</ul>
</description>
</release>
</releases>