Improved Secret Service diagnostics on Linux (#868)

This commit is contained in:
Thorsten Sommer 2026-07-19 11:33:56 +02:00 committed by GitHub
parent 4cc9d1f9a8
commit 3bcd45d676
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 363 additions and 23 deletions

View File

@ -7069,6 +7069,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2868174483"] = "The .NET backend
-- AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are not yet available.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2924964415"] = "AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are not yet available."
-- On Linux, this library communicates with the FreeDesktop Secret Service. AI Studio uses its structured errors to provide helpful guidance when secure credential storage is unavailable or not configured correctly.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2928990457"] = "On Linux, this library communicates with the FreeDesktop Secret Service. AI Studio uses its structured errors to provide helpful guidance when secure credential storage is unavailable or not configured correctly."
-- Copies the configuration source to the clipboard
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2929232062"] = "Copies the configuration source to the clipboard"
@ -8923,6 +8926,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T8163
-- The generated assistant plugin is invalid. Issue: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T939708112"] = "The generated assistant plugin is invalid. Issue: {0}"
-- The global shortcut could not be registered. The previous shortcut remains active.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T2266307101"] = "The global shortcut could not be registered. The previous shortcut remains active."
-- The global shortcut change was cancelled. The previous shortcut remains active.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T3299913860"] = "The global shortcut change was cancelled. The previous shortcut remains active."
-- The configured transcription provider could not be created.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1235984176"] = "The configured transcription provider could not be created."
@ -8968,9 +8977,15 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T25964655
-- Failed to store the secret data due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1110203516"] = "Failed to store the secret data due to an API issue."
-- No compatible secure-storage service is available. Configure a password manager that provides the FreeDesktop Secret Service.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1238078807"] = "No compatible secure-storage service is available. Configure a password manager that provides the FreeDesktop Secret Service."
-- Failed to store the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1704298921"] = "Failed to store the API key due to an API issue."
-- The global shortcut could not be registered because of a desktop integration error.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2032590244"] = "The global shortcut could not be registered because of a desktop integration error."
-- The runtime file manager endpoint returned '{0}'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2158262203"] = "The runtime file manager endpoint returned '{0}'."
@ -8980,12 +8995,24 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2303057928"] = "Failed
-- The runtime file manager endpoint is not available.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2486847754"] = "The runtime file manager endpoint is not available."
-- The global shortcut could not be registered because the desktop service is unavailable.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2944914452"] = "The global shortcut could not be registered because the desktop service is unavailable."
-- AI Studio could not access secure storage because the default collection is locked. Open your password manager and unlock the default collection.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3005355097"] = "AI Studio could not access secure storage because the default collection is locked. Open your password manager and unlock the default collection."
-- The runtime file manager endpoint failed without details.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3082220817"] = "The runtime file manager endpoint failed without details."
-- Successfully copied the text to your clipboard
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3351807428"] = "Successfully copied the text to your clipboard"
-- The desktop service returned an invalid response while registering the global shortcut.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3369097283"] = "The desktop service returned an invalid response while registering the global shortcut."
-- AI Studio could not access secure storage because no default collection is configured. Open a compatible password manager, create or select a collection, unlock it, and set it as the default.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3611400673"] = "AI Studio could not access secure storage because no default collection is configured. Open a compatible password manager, create or select a collection, unlock it, and set it as the default."
-- Failed to delete the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3658273365"] = "Failed to delete the API key due to an API issue."
@ -8995,9 +9022,18 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3724548108"] = "Failed
-- Failed to get the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3875720022"] = "Failed to get the API key due to an API issue."
-- No saved secret was found.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3929880252"] = "No saved secret was found."
-- Failed to get the secret data due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T4007657575"] = "Failed to get the secret data due to an API issue."
-- AI Studio could not access secure storage. See the log for technical details.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T624023541"] = "AI Studio could not access secure storage. See the log for technical details."
-- The secure-storage confirmation was canceled. Repeat the operation and confirm the password manager prompt.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T824858123"] = "The secure-storage confirmation was canceled. Repeat the operation and confirm the password manager prompt."
-- No update found.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T1015418291"] = "No update found."

View File

@ -298,6 +298,7 @@
<ThirdPartyComponent Name="serde" Developer="Erick Tryzelaar, David Tolnay & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/serde-rs/serde/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/serde-rs/serde" UseCase="@T("Now we have multiple systems, some developed in .NET and others in Rust. The data format JSON is responsible for translating data between both worlds (called data serialization and deserialization). Serde takes on this task in the Rust world. The counterpart in the .NET world is an integral part of .NET and is located in System.Text.Json.")"/>
<ThirdPartyComponent Name="strum_macros" Developer="Peter Glotfelty & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/Peternator7/strum/blob/master/LICENSE" RepositoryUrl="https://github.com/Peternator7/strum" UseCase="@T("This crate provides derive macros for Rust enums, which we use to reduce boilerplate when implementing string conversions and metadata for runtime types. This is helpful for the communication between our Rust and .NET systems.")"/>
<ThirdPartyComponent Name="keyring-core" Developer="Daniel Brotsky & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/open-source-cooperative/keyring-core/blob/main/LICENSE-MIT" RepositoryUrl="https://github.com/open-source-cooperative/keyring-core" UseCase="@T("AI Studio stores secrets like API keys in your operating systems secure credential store. The keyring-core library handles this by connecting to macOS Keychain, Windows Credential Manager, and Linux Secret Service.")"/>
<ThirdPartyComponent Name="dbus-secret-service" Developer="Daniel Brotsky, Walther Chen, ComplexSpaces, Rasmus Thomsen & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/open-source-cooperative/dbus-secret-service/blob/main/LICENSE-MIT" RepositoryUrl="https://github.com/open-source-cooperative/dbus-secret-service" UseCase="@T("On Linux, this library communicates with the FreeDesktop Secret Service. AI Studio uses its structured errors to provide helpful guidance when secure credential storage is unavailable or not configured correctly.")"/>
<ThirdPartyComponent Name="arboard" Developer="Artur Kovacs, Avi Weinstock, 1Password & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/1Password/arboard/blob/master/LICENSE-MIT.txt" RepositoryUrl="https://github.com/1Password/arboard" UseCase="@T("To be able to use the responses of the LLM in other apps, we often use the clipboard of the respective operating system. Unfortunately, in .NET there is no solution that works with all operating systems. Therefore, I have opted for this library in Rust. This way, data transfer to other apps works on every system.")"/>
<ThirdPartyComponent Name="tokio" Developer="Alex Crichton, Carl Lerche, Alice Ryhl, Taiki Endo, Ivan Petkov, Eliza Weisman, Lucio Franco & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/tokio-rs/tokio/blob/master/LICENSE" RepositoryUrl="https://github.com/tokio-rs/tokio" UseCase="@T("Code in the Rust language can be specified as synchronous or asynchronous. Unlike .NET and the C# language, Rust cannot execute asynchronous code by itself. Rust requires support in the form of an executor for this. Tokio is one such executor.")"/>
<ThirdPartyComponent Name="futures" Developer="Alex Crichton, Taiki Endo, Taylor Cramer, Nemo157, Josef Brandl, Aaron Turon & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rust-lang/futures-rs/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/rust-lang/futures-rs" UseCase="@T("This is a library providing the foundations for asynchronous programming in Rust. It includes key trait definitions like Stream, as well as utilities like join!, select!, and various futures combinator methods which enable expressive asynchronous control flow.")"/>

View File

@ -7071,6 +7071,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2868174483"] = "Das .NET-Backend
-- AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are not yet available.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2924964415"] = "AI Studio wird mit Unternehmenskonfigurationen und Konfigurationsservern betrieben. Die Konfigurations-Plugins sind noch nicht verfügbar."
-- On Linux, this library communicates with the FreeDesktop Secret Service. AI Studio uses its structured errors to provide helpful guidance when secure credential storage is unavailable or not configured correctly.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2928990457"] = "Unter Linux kommuniziert diese Bibliothek mit dem FreeDesktop Secret Service. AI Studio nutzt dessen strukturierte Fehlermeldungen, um hilfreiche Hinweise zu geben, wenn die sichere Speicherung von Zugangsdaten nicht verfügbar oder nicht korrekt konfiguriert ist."
-- Copies the configuration source to the clipboard
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2929232062"] = "Kopiert die Quelle der Konfiguration in die Zwischenablage"
@ -8925,6 +8928,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T8163
-- The generated assistant plugin is invalid. Issue: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T939708112"] = "Das generierte Assistenten-Plugin ist ungültig. Problem: {0}"
-- The global shortcut could not be registered. The previous shortcut remains active.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T2266307101"] = "Die globale Tastenkombination konnte nicht registriert werden. Die vorherige Tastenkombination bleibt aktiv."
-- The global shortcut change was cancelled. The previous shortcut remains active.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T3299913860"] = "Die Änderung der globalen Tastenkombination wurde abgebrochen. Die vorherige Tastenkombination bleibt aktiv."
-- The configured transcription provider could not be created.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1235984176"] = "Der konfigurierte Transkriptionsanbieter konnte nicht erstellt werden."
@ -8970,9 +8979,15 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T25964655
-- Failed to store the secret data due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1110203516"] = "Fehler beim Speichern der geheimen Daten aufgrund eines API-Problems."
-- No compatible secure-storage service is available. Configure a password manager that provides the FreeDesktop Secret Service.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1238078807"] = "Es ist kein kompatibler Dienst zur sicheren Speicherung verfügbar. Richten Sie einen Passwortmanager ein, der den FreeDesktop Secret Service bereitstellt."
-- Failed to store the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1704298921"] = "Fehler beim Speichern des API-Schlüssels aufgrund eines API-Problems."
-- The global shortcut could not be registered because of a desktop integration error.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2032590244"] = "Die globale Tastenkombination konnte aufgrund eines Fehlers bei der Desktop-Integration nicht registriert werden."
-- The runtime file manager endpoint returned '{0}'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2158262203"] = "Der Laufzeit-Dateimanager-Endpunkt hat '{0}' zurückgegeben."
@ -8982,12 +8997,24 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2303057928"] = "Das L
-- The runtime file manager endpoint is not available.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2486847754"] = "Der Laufzeit-Dateimanager-Endpunkt ist nicht verfügbar."
-- The global shortcut could not be registered because the desktop service is unavailable.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2944914452"] = "Die globale Tastenkombination konnte nicht registriert werden, da der Desktopdienst nicht verfügbar ist."
-- AI Studio could not access secure storage because the default collection is locked. Open your password manager and unlock the default collection.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3005355097"] = "AI Studio konnte nicht auf den sicheren Speicher zugreifen, da die Standardsammlung gesperrt ist. Öffnen Sie Ihren Passwortmanager und entsperren Sie die Standardsammlung."
-- The runtime file manager endpoint failed without details.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3082220817"] = "Der Laufzeit-Dateimanager-Endpunkt ist ohne Details fehlgeschlagen."
-- Successfully copied the text to your clipboard
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3351807428"] = "Der Text wurde erfolgreich in die Zwischenablage kopiert."
-- The desktop service returned an invalid response while registering the global shortcut.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3369097283"] = "Der Desktop-Dienst hat beim Registrieren des globalen Tastaturkürzels eine ungültige Antwort zurückgegeben."
-- AI Studio could not access secure storage because no default collection is configured. Open a compatible password manager, create or select a collection, unlock it, and set it as the default.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3611400673"] = "AI Studio konnte nicht auf den sicheren Speicher zugreifen, da keine Standardsammlung konfiguriert ist. Öffnen Sie einen kompatiblen Passwortmanager, erstellen Sie eine Sammlung oder wählen Sie eine aus, entsperren sie und legen Sie diese als Standard fest."
-- Failed to delete the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3658273365"] = "Das API-Schlüssel konnte aufgrund eines API-Problems nicht gelöscht werden."
@ -8997,9 +9024,18 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3724548108"] = "Der Te
-- Failed to get the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3875720022"] = "Der API-Schlüssel konnte aufgrund eines API-Problems nicht abgerufen werden."
-- No saved secret was found.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3929880252"] = "Es wurde kein gespeichertes Geheimnis gefunden."
-- Failed to get the secret data due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T4007657575"] = "Abrufen der geheimen Daten aufgrund eines API-Problems fehlgeschlagen."
-- AI Studio could not access secure storage. See the log for technical details.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T624023541"] = "AI Studio konnte nicht auf den sicheren Speicher zugreifen. Technische Details finden Sie im Protokoll."
-- The secure-storage confirmation was canceled. Repeat the operation and confirm the password manager prompt.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T824858123"] = "Die Bestätigung für den sicheren Speicher wurde abgebrochen. Wiederholen Sie den Vorgang und bestätigen Sie die Aufforderung des Passwort-Managers."
-- No update found.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T1015418291"] = "Kein Update gefunden."

View File

@ -7071,6 +7071,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2868174483"] = "The .NET backend
-- AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are not yet available.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2924964415"] = "AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are not yet available."
-- On Linux, this library communicates with the FreeDesktop Secret Service. AI Studio uses its structured errors to provide helpful guidance when secure credential storage is unavailable or not configured correctly.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2928990457"] = "On Linux, this library communicates with the FreeDesktop Secret Service. AI Studio uses its structured errors to provide helpful guidance when secure credential storage is unavailable or not configured correctly."
-- Copies the configuration source to the clipboard
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2929232062"] = "Copies the configuration source to the clipboard"
@ -8925,6 +8928,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T8163
-- The generated assistant plugin is invalid. Issue: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T939708112"] = "The generated assistant plugin is invalid. Issue: {0}"
-- The global shortcut could not be registered. The previous shortcut remains active.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T2266307101"] = "The global shortcut could not be registered. The previous shortcut remains active."
-- The global shortcut change was cancelled. The previous shortcut remains active.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T3299913860"] = "The global shortcut change was cancelled. The previous shortcut remains active."
-- The configured transcription provider could not be created.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1235984176"] = "The configured transcription provider could not be created."
@ -8970,9 +8979,15 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T25964655
-- Failed to store the secret data due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1110203516"] = "Failed to store the secret data due to an API issue."
-- No compatible secure-storage service is available. Configure a password manager that provides the FreeDesktop Secret Service.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1238078807"] = "No compatible secure-storage service is available. Configure a password manager that provides the FreeDesktop Secret Service."
-- Failed to store the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1704298921"] = "Failed to store the API key due to an API issue."
-- The global shortcut could not be registered because of a desktop integration error.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2032590244"] = "The global shortcut could not be registered because of a desktop integration error."
-- The runtime file manager endpoint returned '{0}'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2158262203"] = "The runtime file manager endpoint returned '{0}'."
@ -8982,12 +8997,24 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2303057928"] = "Failed
-- The runtime file manager endpoint is not available.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2486847754"] = "The runtime file manager endpoint is not available."
-- The global shortcut could not be registered because the desktop service is unavailable.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2944914452"] = "The global shortcut could not be registered because the desktop service is unavailable."
-- AI Studio could not access secure storage because the default collection is locked. Open your password manager and unlock the default collection.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3005355097"] = "AI Studio could not access secure storage because the default collection is locked. Open your password manager and unlock the default collection."
-- The runtime file manager endpoint failed without details.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3082220817"] = "The runtime file manager endpoint failed without details."
-- Successfully copied the text to your clipboard
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3351807428"] = "Successfully copied the text to your clipboard"
-- The desktop service returned an invalid response while registering the global shortcut.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3369097283"] = "The desktop service returned an invalid response while registering the global shortcut."
-- AI Studio could not access secure storage because no default collection is configured. Open a compatible password manager, create or select a collection, unlock it, and set it as the default.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3611400673"] = "AI Studio could not access secure storage because no default collection is configured. Open a compatible password manager, create or select a collection, unlock it, and set it as the default."
-- Failed to delete the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3658273365"] = "Failed to delete the API key due to an API issue."
@ -8997,9 +9024,18 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3724548108"] = "Failed
-- Failed to get the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3875720022"] = "Failed to get the API key due to an API issue."
-- No saved secret was found.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3929880252"] = "No saved secret was found."
-- Failed to get the secret data due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T4007657575"] = "Failed to get the secret data due to an API issue."
-- AI Studio could not access secure storage. See the log for technical details.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T624023541"] = "AI Studio could not access secure storage. See the log for technical details."
-- The secure-storage confirmation was canceled. Repeat the operation and confirm the password manager prompt.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T824858123"] = "The secure-storage confirmation was canceled. Repeat the operation and confirm the password manager prompt."
-- No update found.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T1015418291"] = "No update found."

View File

@ -6,4 +6,5 @@ namespace AIStudio.Tools.Rust;
/// <param name="Success">True, when the secret was successfully deleted or not found.</param>
/// <param name="Issue">The issue, when the secret could not be deleted.</param>
/// <param name="WasEntryFound">True, when the entry was found and deleted.</param>
public readonly record struct DeleteSecretResponse(bool Success, string Issue, bool WasEntryFound);
/// <param name="IssueCode">The structured issue reported by the native credential store.</param>
public readonly record struct DeleteSecretResponse(bool Success, string Issue, bool WasEntryFound, SecretStoreIssueCode IssueCode = SecretStoreIssueCode.NONE);

View File

@ -6,4 +6,5 @@ namespace AIStudio.Tools.Rust;
/// <param name="Success">True, when the secret was successfully retrieved.</param>
/// <param name="Secret">The secret, e.g., API key.</param>
/// <param name="Issue">The issue, when the secret could not be retrieved.</param>
public readonly record struct RequestedSecret(bool Success, EncryptedText Secret, string Issue);
/// <param name="IssueCode">The structured issue reported by the native credential store.</param>
public readonly record struct RequestedSecret(bool Success, EncryptedText Secret, string Issue, SecretStoreIssueCode IssueCode = SecretStoreIssueCode.NONE);

View File

@ -0,0 +1,15 @@
namespace AIStudio.Tools.Rust;
/// <summary>
/// A structured issue reported by the native credential store.
/// </summary>
public enum SecretStoreIssueCode
{
NONE,
SECRET_NOT_FOUND,
NO_DEFAULT_COLLECTION,
COLLECTION_LOCKED,
PROMPT_DISMISSED,
SERVICE_UNAVAILABLE,
UNKNOWN,
}

View File

@ -5,4 +5,5 @@ namespace AIStudio.Tools.Rust;
/// </summary>
/// <param name="Success">True, when the secret was successfully stored.</param>
/// <param name="Issue">The issue, when the secret could not be stored.</param>
public readonly record struct StoreSecretResponse(bool Success, string Issue);
/// <param name="IssueCode">The structured issue reported by the native credential store.</param>
public readonly record struct StoreSecretResponse(bool Success, string Issue, SecretStoreIssueCode IssueCode = SecretStoreIssueCode.NONE);

View File

@ -57,9 +57,6 @@ public sealed partial class RustService
return legacySecret;
}
if (!isTrying)
this.logger!.LogError($"Failed to get the API key for '{secretKey}': '{secret.Issue}'");
return secret;
}
@ -79,8 +76,10 @@ public sealed partial class RustService
this.logger!.LogDebug($"Successfully retrieved the API key for '{secretKey}'.");
else if (isTrying)
this.logger!.LogDebug($"No API key configured for '{secretKey}' (try mode): '{secret.Issue}'");
else
this.logger!.LogError($"Failed to get the API key for '{secretKey}': '{secret.Issue}'");
return secret;
return TranslateSecretStoreIssue(secret);
}
/// <summary>
@ -101,7 +100,7 @@ public sealed partial class RustService
await this.DeleteAPIKeyByKey(legacySecretKey, isTrying: true);
}
return state;
return TranslateSecretStoreIssue(state);
}
private async Task<StoreSecretResponse> StoreEncryptedAPIKeyByKey(string secretKey, EncryptedText encryptedKey)
@ -161,6 +160,6 @@ public sealed partial class RustService
if (!state.Success && !isTrying)
this.logger!.LogError($"Failed to delete the API key for '{secretKey}': '{state.Issue}'");
return state;
return TranslateSecretStoreIssue(state);
}
}

View File

@ -4,6 +4,26 @@ namespace AIStudio.Tools.Services;
public sealed partial class RustService
{
private static string TranslateSecretStoreIssue(SecretStoreIssueCode issueCode, string issue) => issueCode switch
{
SecretStoreIssueCode.NONE => issue,
SecretStoreIssueCode.SECRET_NOT_FOUND => TB("No saved secret was found."),
SecretStoreIssueCode.NO_DEFAULT_COLLECTION => TB("AI Studio could not access secure storage because no default collection is configured. Open a compatible password manager, create or select a collection, unlock it, and set it as the default."),
SecretStoreIssueCode.COLLECTION_LOCKED => TB("AI Studio could not access secure storage because the default collection is locked. Open your password manager and unlock the default collection."),
SecretStoreIssueCode.PROMPT_DISMISSED => TB("The secure-storage confirmation was canceled. Repeat the operation and confirm the password manager prompt."),
SecretStoreIssueCode.SERVICE_UNAVAILABLE => TB("No compatible secure-storage service is available. Configure a password manager that provides the FreeDesktop Secret Service."),
_ => TB("AI Studio could not access secure storage. See the log for technical details."),
};
private static StoreSecretResponse TranslateSecretStoreIssue(StoreSecretResponse response) =>
response.Success ? response : response with { Issue = TranslateSecretStoreIssue(response.IssueCode, response.Issue) };
private static RequestedSecret TranslateSecretStoreIssue(RequestedSecret response) =>
response.Success ? response : response with { Issue = TranslateSecretStoreIssue(response.IssueCode, response.Issue) };
private static DeleteSecretResponse TranslateSecretStoreIssue(DeleteSecretResponse response) =>
response.Success ? response : response with { Issue = TranslateSecretStoreIssue(response.IssueCode, response.Issue) };
private static string SecretKey(ISecretId secretId, SecretStoreType storeType) => $"{storeType.Prefix()}::{secretId.SecretId}::{secretId.SecretName}";
private static string LegacySecretKey(ISecretId secretId) => $"secret::{secretId.SecretId}::{secretId.SecretName}";
@ -30,9 +50,6 @@ public sealed partial class RustService
return legacySecret;
}
if (!secret.Success && !isTrying)
this.logger!.LogError($"Failed to get the secret data for '{secretKey}': '{secret.Issue}'");
return secret;
}
@ -62,7 +79,7 @@ public sealed partial class RustService
if (state.Success && storeType is SecretStoreType.DATA_SOURCE)
await this.DeleteSecretByKey(LegacySecretKey(secretId));
return state;
return TranslateSecretStoreIssue(state);
}
/// <summary>
@ -95,7 +112,16 @@ public sealed partial class RustService
return new RequestedSecret(false, new EncryptedText(string.Empty), TB("Failed to get the secret data due to an API issue."));
}
return await result.Content.ReadFromJsonAsync<RequestedSecret>(this.jsonRustSerializerOptions);
var state = await result.Content.ReadFromJsonAsync<RequestedSecret>(this.jsonRustSerializerOptions);
if (!state.Success)
{
if (isTrying)
this.logger!.LogDebug($"No secret data configured for '{secretKey}' (try mode): '{state.Issue}'");
else
this.logger!.LogError($"Failed to get the secret data for '{secretKey}': '{state.Issue}'");
}
return TranslateSecretStoreIssue(state);
}
private async Task<DeleteSecretResponse> DeleteSecretByKey(string secretKey)
@ -112,6 +138,6 @@ public sealed partial class RustService
if (!state.Success)
this.logger!.LogError($"Failed to delete the secret data for '{secretKey}': '{state.Issue}'");
return state;
return TranslateSecretStoreIssue(state);
}
}

View File

@ -7,6 +7,7 @@
- Added enterprise configuration options to hide the last changelog and vision panels on the welcome page. Thanks, Dominic Neuburg (`donework`), for the contribution.
- 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.
- Improved update guidance for Flatpak installations and added an enterprise option that lets organizations manage updates entirely through their IT department.
- 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.
- 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.
- Fixed connections to internal HTTPS services and enterprise configuration servers that use organization-provided root certificates on Linux.
- Fixed enterprise configuration plugins from Windows-created ZIP files may not load correctly on Linux when the ZIP contained plugin files inside a folder.

View File

@ -58,15 +58,91 @@ When you are confident in the app's safety, follow these steps:
The AI Studio app should now open without any issues. Once the app is installed, it will check for updates automatically. If a new version is available, you will be prompted to install it.
## Linux
MindWork AI Studio is available for modern 64-bit Linux systems. The app is provided as an `AppImage`. We test our app using Ubuntu 22.04 and Raspberry Pi OS 12 (64-bit), but it should work on other distributions as well.
MindWork AI Studio is available for modern 64-bit Linux systems. Starting with release v26.7.3, Flatpak is the recommended installation method. We test AI Studio on Ubuntu 24.04 and 26.04, Kubuntu 24.04, Fedora 43 or newer, and openSUSE Leap 16 or newer, but it should work on other distributions as well.
We have to figure out if you have an Intel/AMD or a modern ARM system on your Linux machine. Open a terminal and run the command `uname -m`. When the output is `x86_64`, you have an Intel/AMD system. When the output is `aarch64`, you have an ARM system.
First, determine whether your system uses the Intel/AMD or ARM architecture:
- **Intel/AMD:** [Download the Intel/AMD AppImage](https://github.com/MindWorkAI/AI-Studio/releases/latest/download/mind-work-ai-studio_amd64.AppImage) of AI Studio.
```bash
uname -m
```
- **ARM:** [Download the ARM AppImage](https://github.com/MindWorkAI/AI-Studio/releases/latest/download/mind-work-ai-studio_aarch64.AppImage) of AI Studio.
`x86_64` means Intel/AMD; `aarch64` means ARM.
### AppImage Installation
### Recommended: Flatpak Installation
On Ubuntu, install Flatpak first:
```bash
sudo apt update
sudo apt install flatpak
```
For other Linux distributions, follow the [official Flatpak setup instructions](https://flatpak.org/setup/).
Open the [latest AI Studio release](https://github.com/MindWorkAI/AI-Studio/releases/latest) and download the bundle for your architecture:
- **Intel/AMD (`x86_64`):** `MindWork.AI.Studio_x86_64.flatpak`
- **ARM (`aarch64`):** `MindWork.AI.Studio_aarch64.flatpak`
Install the downloaded bundle for your user account. For Intel/AMD, run:
```bash
cd ~/Downloads
flatpak install --user ./MindWork.AI.Studio_x86_64.flatpak
```
For ARM, run:
```bash
cd ~/Downloads
flatpak install --user ./MindWork.AI.Studio_aarch64.flatpak
```
Confirm the installation of the required GNOME runtime from Flathub when Flatpak asks for it.
#### Pandoc Extension (Strongly Recommended)
Pandoc is required for essential file features, including regular file attachments in chats, importing and converting Office documents, and other document-based functionality. We therefore strongly recommend installing the Pandoc extension. AI Studio checks whether a compatible Pandoc version is already available.
For Intel/AMD, download `MindWork.AI.Studio.Plugin.Pandoc_x86_64.flatpak` and run:
```bash
cd ~/Downloads
flatpak install --user ./MindWork.AI.Studio.Plugin.Pandoc_x86_64.flatpak
```
For ARM, download `MindWork.AI.Studio.Plugin.Pandoc_aarch64.flatpak` and run:
```bash
cd ~/Downloads
flatpak install --user ./MindWork.AI.Studio.Plugin.Pandoc_aarch64.flatpak
```
#### Starting and Updating the Flatpak
Start AI Studio from your application menu or run:
```bash
flatpak run org.MindWorkAI.AIStudio
```
If no application-menu entry appears, sign out of your desktop session completely and sign in again, or restart the system.
Until AI Studio is published on Flathub, bundles installed from GitHub do not receive automatic app updates. Download each new bundle and reinstall it. For Intel/AMD, run:
```bash
cd ~/Downloads
flatpak install --user --reinstall ./MindWork.AI.Studio_x86_64.flatpak
```
Use `MindWork.AI.Studio_aarch64.flatpak` instead on ARM.
### Alternative: AppImage Installation
If you prefer not to use Flatpak, AI Studio is also available as an AppImage:
- **Intel/AMD:** [Download the Intel/AMD AppImage](https://github.com/MindWorkAI/AI-Studio/releases/latest/download/mind-work-ai-studio_amd64.AppImage).
- **ARM:** [Download the ARM AppImage](https://github.com/MindWorkAI/AI-Studio/releases/latest/download/mind-work-ai-studio_aarch64.AppImage).
**Prepare the AppImage using the desktop environment:**
1. Download the AppImage from the link above.
@ -81,7 +157,20 @@ We have to figure out if you have an Intel/AMD or a modern ARM system on your Li
**Prepare the AppImage using the terminal:**
1. Download the AppImage from the link above.
2. Open a terminal and navigate to the Downloads folder: `cd Downloads`.
2. Open a terminal and navigate to the Downloads folder: `cd ~/Downloads`.
3. Make the AppImage executable: `chmod +x mind-work-ai-studio_amd64.AppImage`.
4. You might want to move the AppImage to a more convenient location, e.g., your home directory: `mv mind-work-ai-studio_amd64.AppImage ~/`.
5. Now you can run the AppImage from your file manager (double-click) or the terminal: `./mind-work-ai-studio_amd64.AppImage`.
5. Now you can run the AppImage from your file manager (double-click) or the terminal: `~/mind-work-ai-studio_amd64.AppImage`.
Use the `aarch64` file name instead of the `amd64` file name on ARM systems.
### Secure Storage for API Keys
On Linux, AI Studio stores API keys through the FreeDesktop Secret Service API. A compatible password manager must provide this service, and it must have an unlocked default collection. AI Studio never creates, selects, unlocks, or changes a password manager's default collection itself.
Compatible configurations include:
- GNOME Keyring, which can be managed with an application such as Seahorse. Create a password collection if necessary, unlock it, and choose **Set as default**.
- KeePassXC with Secret Service integration enabled and a database group exposed to the service. Keep the relevant database and group unlocked when AI Studio needs to access secrets.
Automatic login can prevent GNOME Keyring from being unlocked automatically. If secure storage remains locked after login, unlock the default collection in your password manager.

1
runtime/Cargo.lock generated
View File

@ -4138,6 +4138,7 @@ dependencies = [
"calamine",
"cbc 0.2.1",
"cfg-if",
"dbus-secret-service",
"dbus-secret-service-keyring-store",
"file-format",
"flexi_logger",

View File

@ -74,6 +74,7 @@ apple-native-keyring-store = { version = "1.0.0", features = ["keychain"] }
[target.'cfg(target_os = "linux")'.dependencies]
ashpd = { version = "0.13.12", default-features = false, features = ["tokio", "open_uri", "global_shortcuts"] }
dbus-secret-service-keyring-store = { version = "1.0.0", features = ["crypto-rust"] }
dbus-secret-service = "4.1.0"
webkit2gtk = { version = "2.0.2", features = ["v2_8"] }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]

View File

@ -5,6 +5,46 @@ use serde::{Deserialize, Serialize};
use crate::api_token::APIToken;
use crate::encryption::{EncryptedText, ENCRYPTION};
/// A structured issue reported by the native credential store.
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub enum SecretStoreIssueCode {
None,
SecretNotFound,
NoDefaultCollection,
CollectionLocked,
PromptDismissed,
ServiceUnavailable,
Unknown,
}
fn issue_code(error: &KeyringError) -> SecretStoreIssueCode {
if matches!(error, KeyringError::NoEntry) {
return SecretStoreIssueCode::SecretNotFound;
}
#[cfg(target_os = "linux")]
if let KeyringError::PlatformFailure(error) | KeyringError::NoStorageAccess(error) = error {
if let Some(error) = error.downcast_ref::<dbus_secret_service::Error>() {
return secret_service_issue_code(error);
}
}
SecretStoreIssueCode::Unknown
}
#[cfg(target_os = "linux")]
fn secret_service_issue_code(error: &dbus_secret_service::Error) -> SecretStoreIssueCode {
use dbus_secret_service::Error;
match error {
Error::NoResult => SecretStoreIssueCode::NoDefaultCollection,
Error::Locked => SecretStoreIssueCode::CollectionLocked,
Error::Prompt => SecretStoreIssueCode::PromptDismissed,
Error::Unavailable => SecretStoreIssueCode::ServiceUnavailable,
_ => SecretStoreIssueCode::Unknown,
}
}
/// Initializes the native credential store used by keyring-core.
pub fn init_secret_store() {
cfg_if::cfg_if! {
@ -48,6 +88,7 @@ pub async fn store_secret(_token: APIToken, request: Json<StoreSecret>) -> Json<
return Json(StoreSecretResponse {
success: false,
issue: format!("Failed to decrypt the text: {e}"),
issue_code: SecretStoreIssueCode::Unknown,
})
},
};
@ -60,6 +101,7 @@ pub async fn store_secret(_token: APIToken, request: Json<StoreSecret>) -> Json<
return Json(StoreSecretResponse {
success: false,
issue: e.to_string(),
issue_code: issue_code(&e),
});
},
};
@ -70,6 +112,7 @@ pub async fn store_secret(_token: APIToken, request: Json<StoreSecret>) -> Json<
Json(StoreSecretResponse {
success: true,
issue: String::from(""),
issue_code: SecretStoreIssueCode::None,
})
},
@ -78,6 +121,7 @@ pub async fn store_secret(_token: APIToken, request: Json<StoreSecret>) -> Json<
Json(StoreSecretResponse {
success: false,
issue: e.to_string(),
issue_code: issue_code(&e),
})
},
}
@ -96,6 +140,7 @@ pub struct StoreSecret {
pub struct StoreSecretResponse {
success: bool,
issue: String,
issue_code: SecretStoreIssueCode,
}
/// Retrieves a secret from the secret store using the operating system's keyring.
@ -113,6 +158,7 @@ pub async fn get_secret(_token: APIToken, request: Json<RequestSecret>) -> Json<
success: false,
secret: EncryptedText::new(String::from("")),
issue: format!("Failed to create secret entry for '{service}' and user '{user_name}': {e}"),
issue_code: issue_code(&e),
});
},
};
@ -130,6 +176,7 @@ pub async fn get_secret(_token: APIToken, request: Json<RequestSecret>) -> Json<
success: false,
secret: EncryptedText::new(String::from("")),
issue: format!("Failed to encrypt the secret: {e}"),
issue_code: SecretStoreIssueCode::Unknown,
});
},
};
@ -138,6 +185,7 @@ pub async fn get_secret(_token: APIToken, request: Json<RequestSecret>) -> Json<
success: true,
secret: encrypted_secret,
issue: String::from(""),
issue_code: SecretStoreIssueCode::None,
})
},
@ -150,6 +198,7 @@ pub async fn get_secret(_token: APIToken, request: Json<RequestSecret>) -> Json<
success: false,
secret: EncryptedText::new(String::from("")),
issue: format!("Failed to retrieve secret for '{service}' and user '{user_name}': {e}"),
issue_code: issue_code(&e),
})
},
}
@ -169,6 +218,7 @@ pub struct RequestedSecret {
success: bool,
secret: EncryptedText,
issue: String,
issue_code: SecretStoreIssueCode,
}
/// Deletes a secret from the secret store using the operating system's keyring.
@ -183,6 +233,7 @@ pub async fn delete_secret(_token: APIToken, request: Json<RequestSecret>) -> Js
success: false,
was_entry_found: false,
issue: e.to_string(),
issue_code: issue_code(&e),
});
},
};
@ -195,6 +246,7 @@ pub async fn delete_secret(_token: APIToken, request: Json<RequestSecret>) -> Js
success: true,
was_entry_found: true,
issue: String::from(""),
issue_code: SecretStoreIssueCode::None,
})
},
@ -204,6 +256,7 @@ pub async fn delete_secret(_token: APIToken, request: Json<RequestSecret>) -> Js
success: true,
was_entry_found: false,
issue: String::from(""),
issue_code: SecretStoreIssueCode::SecretNotFound,
})
}
@ -213,6 +266,7 @@ pub async fn delete_secret(_token: APIToken, request: Json<RequestSecret>) -> Js
success: false,
was_entry_found: false,
issue: e.to_string(),
issue_code: issue_code(&e),
})
},
}
@ -224,4 +278,46 @@ pub struct DeleteSecretResponse {
success: bool,
was_entry_found: bool,
issue: String,
issue_code: SecretStoreIssueCode,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_entry_is_reported_as_secret_not_found() {
assert_eq!(issue_code(&KeyringError::NoEntry), SecretStoreIssueCode::SecretNotFound);
}
#[test]
fn unrelated_keyring_error_uses_unknown_fallback() {
let error = KeyringError::Invalid("service".to_string(), "invalid".to_string());
assert_eq!(issue_code(&error), SecretStoreIssueCode::Unknown);
}
#[test]
fn issue_code_is_included_in_json() {
let response = StoreSecretResponse {
success: false,
issue: "technical details".to_string(),
issue_code: SecretStoreIssueCode::NoDefaultCollection,
};
let json = serde_json::to_value(response).unwrap();
assert_eq!(json["issue_code"], "NoDefaultCollection");
assert_eq!(json["issue"], "technical details");
}
#[cfg(target_os = "linux")]
#[test]
fn secret_service_errors_are_mapped_to_issue_codes() {
use dbus_secret_service::Error;
assert_eq!(secret_service_issue_code(&Error::NoResult), SecretStoreIssueCode::NoDefaultCollection);
assert_eq!(secret_service_issue_code(&Error::Locked), SecretStoreIssueCode::CollectionLocked);
assert_eq!(secret_service_issue_code(&Error::Prompt), SecretStoreIssueCode::PromptDismissed);
assert_eq!(secret_service_issue_code(&Error::Unavailable), SecretStoreIssueCode::ServiceUnavailable);
assert_eq!(secret_service_issue_code(&Error::Parse), SecretStoreIssueCode::Unknown);
}
}