mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-07-07 08:06:27 +00:00
Added support for organization-approved assistant plugins (#821)
Some checks are pending
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Some checks are pending
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
This commit is contained in:
parent
972ed73fe8
commit
13cc2f837a
47
app/Build/Commands/AssistantPluginHashCommand.cs
Normal file
47
app/Build/Commands/AssistantPluginHashCommand.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using SharedTools;
|
||||
|
||||
namespace Build.Commands;
|
||||
|
||||
public sealed class AssistantPluginHashCommand
|
||||
{
|
||||
[Command("assistant-plugin-hash", Description = "Compute the canonical assistant-plugin hash for a plugin directory")]
|
||||
public void ComputeAssistantPluginHash(
|
||||
[Argument(Description = "Path to the assistant plugin directory")] string pluginDir,
|
||||
[Option("lua-snippet", Description = "Also print a Lua snippet for CONFIG[\"SETTINGS\"]")] bool luaSnippet = false)
|
||||
{
|
||||
if (!Environment.IsWorkingDirectoryValid())
|
||||
return;
|
||||
|
||||
var resolvedPath = Path.GetFullPath(pluginDir, Directory.GetCurrentDirectory());
|
||||
if (!Directory.Exists(resolvedPath))
|
||||
{
|
||||
Console.WriteLine($"- Error: The plugin directory '{resolvedPath}' does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
var pluginHash = AssistantPluginHash.Compute(resolvedPath);
|
||||
if (string.IsNullOrWhiteSpace(pluginHash))
|
||||
{
|
||||
Console.WriteLine($"- Error: No Lua files were found in '{resolvedPath}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine(pluginHash);
|
||||
|
||||
if (!luaSnippet)
|
||||
return;
|
||||
|
||||
var displayName = Path.GetFileName(resolvedPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
|
||||
var approvedAtUtc = DateTimeOffset.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("""CONFIG["SETTINGS"]["DataAssistantPluginAudit.EnterpriseApprovedPlugins"] = {""");
|
||||
Console.WriteLine(" {");
|
||||
Console.WriteLine($""" ["PluginHash"] = "{pluginHash}",""");
|
||||
Console.WriteLine($""" ["DisplayName"] = "{displayName}",""");
|
||||
Console.WriteLine(""" ["Comment"] = "<optional comment>",""");
|
||||
Console.WriteLine(""" ["ApprovedBy"] = "<optional approver>",""");
|
||||
Console.WriteLine($""" ["ApprovedAtUtc"] = "{approvedAtUtc}",""");
|
||||
Console.WriteLine(" }");
|
||||
Console.WriteLine("}");
|
||||
}
|
||||
}
|
||||
@ -6,4 +6,5 @@ app.AddCommands<CheckRidsCommand>();
|
||||
app.AddCommands<UpdateMetadataCommands>();
|
||||
app.AddCommands<UpdateWebAssetsCommand>();
|
||||
app.AddCommands<CollectI18NKeysCommand>();
|
||||
app.Run();
|
||||
app.AddCommands<AssistantPluginHashCommand>();
|
||||
app.Run();
|
||||
|
||||
@ -2005,6 +2005,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T661906146"] = "The resul
|
||||
-- Show or hide the detailed security information.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1045105126"] = "Show or hide the detailed security information."
|
||||
|
||||
-- This plugin is approved by your organization. A manual security audit is not required.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1213338416"] = "This plugin is approved by your organization. A manual security audit is not required."
|
||||
|
||||
-- Assistant Audit
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1506922856"] = "Assistant Audit"
|
||||
|
||||
@ -2020,6 +2023,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1805629238"
|
||||
-- Assistant Security
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1841954939"] = "Assistant Security"
|
||||
|
||||
-- Company approved
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2036497459"] = "Company approved"
|
||||
|
||||
-- Approved name
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2282386733"] = "Approved name"
|
||||
|
||||
-- Required minimum
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2354026284"] = "Required minimum"
|
||||
|
||||
@ -2029,6 +2038,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2757790517"
|
||||
-- Technical Details
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2769062110"] = "Technical Details"
|
||||
|
||||
-- Approval comment
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2906887599"] = "Approval comment"
|
||||
|
||||
-- No audit yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3138877447"] = "No audit yet"
|
||||
|
||||
@ -2044,21 +2056,39 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3448155331"
|
||||
-- No stored audit details are available yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3647137899"] = "No stored audit details are available yet."
|
||||
|
||||
-- Enterprise approval is active
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3816183955"] = "Enterprise approval is active"
|
||||
|
||||
-- Current hash
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3896860082"] = "Current hash"
|
||||
|
||||
-- No user audit required
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3916957031"] = "No user audit required"
|
||||
|
||||
-- Audited at
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T4103354206"] = "Audited at"
|
||||
|
||||
-- Approved hash
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T4170340306"] = "Approved hash"
|
||||
|
||||
-- No security findings were stored for this assistant plugin.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T4256679240"] = "No security findings were stored for this assistant plugin."
|
||||
|
||||
-- Status source
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T4289123040"] = "Status source"
|
||||
|
||||
-- Audit hash
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T53507304"] = "Audit hash"
|
||||
|
||||
-- {0} Finding(s)
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T631393016"] = "{0} Finding(s)"
|
||||
|
||||
-- Approved by
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T894543751"] = "Approved by"
|
||||
|
||||
-- Approved at
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T978873131"] = "Approved at"
|
||||
|
||||
-- Click the paperclip to attach files, or click the number to see your attached files.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T1358313858"] = "Click the paperclip to attach files, or click the number to see your attached files."
|
||||
|
||||
@ -3457,6 +3487,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3652671056"] =
|
||||
-- Unavailable
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3662391977"] = "Unavailable"
|
||||
|
||||
-- This assistant plugin is approved by your organization. A manual security audit is not required.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3680374624"] = "This assistant plugin is approved by your organization. A manual security audit is not required."
|
||||
|
||||
-- Plugin Structure
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T371537943"] = "Plugin Structure"
|
||||
|
||||
@ -7627,6 +7660,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANT
|
||||
-- Button
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPEEXTENSIONS::T864557713"] = "Button"
|
||||
|
||||
-- The ASSISTANT table contains an invalid LaunchBehavior value.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T109828905"] = "The ASSISTANT table contains an invalid LaunchBehavior value."
|
||||
|
||||
-- The ASSISTANT table contains an unsupported LaunchBehavior value.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T1194373781"] = "The ASSISTANT table contains an unsupported LaunchBehavior value."
|
||||
|
||||
-- Failed to parse the UI render tree from the ASSISTANT lua table.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T1318499252"] = "Failed to parse the UI render tree from the ASSISTANT lua table."
|
||||
|
||||
@ -7642,12 +7681,18 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T2
|
||||
-- The ASSISTANT lua table does not exist or is not a valid table.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3017816936"] = "The ASSISTANT lua table does not exist or is not a valid table."
|
||||
|
||||
-- The ASSISTANT table contains an empty WorkspaceName for LaunchBehavior 'OPEN_WORKSPACE_CHAT_BY_NAME'.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3233001282"] = "The ASSISTANT table contains an empty WorkspaceName for LaunchBehavior 'OPEN_WORKSPACE_CHAT_BY_NAME'."
|
||||
|
||||
-- The provided ASSISTANT lua table does not contain a valid system prompt.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3402798667"] = "The provided ASSISTANT lua table does not contain a valid system prompt."
|
||||
|
||||
-- The ASSISTANT table does not contain a valid system prompt.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3723171842"] = "The ASSISTANT table does not contain a valid system prompt."
|
||||
|
||||
-- The ASSISTANT table contains the LaunchBehavior 'OPEN_WORKSPACE_CHAT_BY_NAME' but no valid WorkspaceName.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T4215554842"] = "The ASSISTANT table contains the LaunchBehavior 'OPEN_WORKSPACE_CHAT_BY_NAME' but no valid WorkspaceName."
|
||||
|
||||
-- ASSISTANT.BuildPrompt exists but is not a Lua function or has invalid syntax.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T683382975"] = "ASSISTANT.BuildPrompt exists but is not a Lua function or has invalid syntax."
|
||||
|
||||
@ -7690,6 +7735,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECUR
|
||||
-- The current audit result is '{0}', which is below your required minimum level '{1}'. Audit enforcement is currently disabled, so this assistant plugin can still be enabled or used.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T2774333862"] = "The current audit result is '{0}', which is below your required minimum level '{1}'. Audit enforcement is currently disabled, so this assistant plugin can still be enabled or used."
|
||||
|
||||
-- The current plugin hash matches an enterprise-managed approval. No manual security audit is required for activation or usage.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T2824524534"] = "The current plugin hash matches an enterprise-managed approval. No manual security audit is required for activation or usage."
|
||||
|
||||
-- Not Audited
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T2828154864"] = "Not Audited"
|
||||
|
||||
@ -7699,12 +7747,18 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECUR
|
||||
-- Open Security Check
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T290241209"] = "Open Security Check"
|
||||
|
||||
-- User Audit
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3293963409"] = "User Audit"
|
||||
|
||||
-- Restricted
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3325062668"] = "Restricted"
|
||||
|
||||
-- Unknown
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3424652889"] = "Unknown"
|
||||
|
||||
-- Approved by your organization
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3508214481"] = "Approved by your organization"
|
||||
|
||||
-- Unlocked
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3606159420"] = "Unlocked"
|
||||
|
||||
@ -7720,9 +7774,24 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECUR
|
||||
-- No security audit exists yet. Your current security settings do not require an audit before this assistant plugin may be used.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3899951594"] = "No security audit exists yet. Your current security settings do not require an audit before this assistant plugin may be used."
|
||||
|
||||
-- No Approval
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T515592229"] = "No Approval"
|
||||
|
||||
-- This assistant was approved by your organization.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T538196816"] = "This assistant was approved by your organization."
|
||||
|
||||
-- Safe
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T760494712"] = "Safe"
|
||||
|
||||
-- Open Security Details
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T803119455"] = "Open Security Details"
|
||||
|
||||
-- Start Security Check
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T811648299"] = "Start Security Check"
|
||||
|
||||
-- This assistant was approved by your organization as '{0}'.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T834246718"] = "This assistant was approved by your organization as '{0}'."
|
||||
|
||||
-- This assistant currently has no stored audit.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T921972844"] = "This assistant currently has no stored audit."
|
||||
|
||||
|
||||
@ -34,9 +34,18 @@
|
||||
<MudCardActions>
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Style="width: 100%;">
|
||||
<MudButtonGroup Variant="Variant.Outlined">
|
||||
<MudButton Size="Size.Large" Variant="Variant.Filled" StartIcon="@this.Icon" Color="Color.Default" Href="@this.Link" Disabled="@this.Disabled">
|
||||
@this.ButtonText
|
||||
</MudButton>
|
||||
@if (this.HasStartAction)
|
||||
{
|
||||
<MudButton Size="Size.Large" Variant="Variant.Filled" StartIcon="@this.Icon" Color="Color.Default" OnClick="@this.OnClick" Disabled="@this.Disabled">
|
||||
@this.ButtonText
|
||||
</MudButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudButton Size="Size.Large" Variant="Variant.Filled" StartIcon="@this.Icon" Color="Color.Default" Href="@this.Link" Disabled="@this.Disabled">
|
||||
@this.ButtonText
|
||||
</MudButton>
|
||||
}
|
||||
@if (this.HasSettingsPanel)
|
||||
{
|
||||
<MudIconButton Variant="Variant.Text" Icon="@Icons.Material.Filled.Settings" Color="Color.Default" OnClick="@this.OpenSettingsDialog"/>
|
||||
|
||||
@ -31,6 +31,9 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
|
||||
[Parameter]
|
||||
public string Link { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public EventCallback OnClick { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
@ -80,6 +83,8 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
|
||||
|
||||
private bool HasSettingsPanel => typeof(TSettings) != typeof(NoSettingsPanel);
|
||||
|
||||
private bool HasStartAction => this.OnClick.HasDelegate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the newest assistant session snapshot represented by this block.
|
||||
/// </summary>
|
||||
|
||||
@ -33,6 +33,12 @@
|
||||
<MudChip T="string" Size="Size.Small" Variant="Variant.Filled" Color="@state.AuditColor">
|
||||
@state.AuditLabel
|
||||
</MudChip>
|
||||
@if (!string.IsNullOrWhiteSpace(state.SourceLabel))
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Variant="Variant.Filled" Color="@state.SourceColor" Icon="@state.SourceIcon">
|
||||
@state.SourceLabel
|
||||
</MudChip>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(state.AvailabilityLabel))
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined" Color="@state.AvailabilityColor" Icon="@state.AvailabilityIcon">
|
||||
@ -53,18 +59,28 @@
|
||||
|
||||
<MudCardContent Class="pt-0 pb-2">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="4" Class="flex-wrap">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Speed" Size="Size.Small" />
|
||||
<MudText Typo="Typo.body2">@T("Confidence"):</MudText>
|
||||
<MudProgressLinear Color="@state.AuditColor"
|
||||
Value="@this.GetConfidencePercentage()"
|
||||
Rounded="@true"
|
||||
Size="Size.Medium"
|
||||
Style="width: 80px; min-width: 80px;" />
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
@this.GetConfidenceLabel()
|
||||
</MudText>
|
||||
</MudStack>
|
||||
@if (state.IsEnterpriseApproved)
|
||||
{
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Business" Size="Size.Small" Color="@state.SourceColor" />
|
||||
<MudText Typo="Typo.body2">@T("Enterprise approval is active")</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Speed" Size="Size.Small" />
|
||||
<MudText Typo="Typo.body2">@T("Confidence"):</MudText>
|
||||
<MudProgressLinear Color="@state.AuditColor"
|
||||
Value="@this.GetConfidencePercentage()"
|
||||
Rounded="@true"
|
||||
Size="Size.Medium"
|
||||
Style="width: 80px; min-width: 80px;" />
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
@this.GetConfidenceLabel()
|
||||
</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
<MudDivider Vertical="@true" FlexItem="@true" />
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.BugReport" Size="Size.Small" Color="@state.AuditColor" />
|
||||
@ -104,12 +120,63 @@
|
||||
</td>
|
||||
<td><code style="font-size: 0.8rem;">@this.Plugin.Id</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<MudText Typo="Typo.body2"><b>@T("Status source")</b></MudText>
|
||||
</td>
|
||||
<td><MudText Typo="Typo.body2">@state.SourceLabel</MudText></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<MudText Typo="Typo.body2"><b>@T("Current hash")</b></MudText>
|
||||
</td>
|
||||
<td><code style="font-size: 0.8rem;">@GetShortHash(state.CurrentHash)</code></td>
|
||||
</tr>
|
||||
@if (state.EnterpriseApproval is not null)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<MudText Typo="Typo.body2"><b>@T("Approved hash")</b></MudText>
|
||||
</td>
|
||||
<td><code style="font-size: 0.8rem;">@GetShortHash(state.EnterpriseApproval.PluginHash)</code></td>
|
||||
</tr>
|
||||
@if (!string.IsNullOrWhiteSpace(state.EnterpriseApproval.DisplayName))
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<MudText Typo="Typo.body2"><b>@T("Approved name")</b></MudText>
|
||||
</td>
|
||||
<td><MudText Typo="Typo.body2">@state.EnterpriseApproval.DisplayName</MudText></td>
|
||||
</tr>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(state.EnterpriseApproval.ApprovedBy))
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<MudText Typo="Typo.body2"><b>@T("Approved by")</b></MudText>
|
||||
</td>
|
||||
<td><MudText Typo="Typo.body2">@state.EnterpriseApproval.ApprovedBy</MudText></td>
|
||||
</tr>
|
||||
}
|
||||
@if (state.EnterpriseApproval.ApprovedAtUtc is not null)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<MudText Typo="Typo.body2"><b>@T("Approved at")</b></MudText>
|
||||
</td>
|
||||
<td><MudText Typo="Typo.body2">@this.FormatFileTimestamp(state.EnterpriseApproval.ApprovedAtUtc.Value.ToLocalTime().DateTime)</MudText></td>
|
||||
</tr>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(state.EnterpriseApproval.Comment))
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<MudText Typo="Typo.body2"><b>@T("Approval comment")</b></MudText>
|
||||
</td>
|
||||
<td><MudText Typo="Typo.body2">@state.EnterpriseApproval.Comment</MudText></td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
@if (state.Audit is not null)
|
||||
{
|
||||
<tr>
|
||||
@ -156,9 +223,18 @@
|
||||
|
||||
@if (state.Audit is null)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="@true">
|
||||
@T("No stored audit details are available yet.")
|
||||
</MudAlert>
|
||||
@if (state.IsEnterpriseApproved)
|
||||
{
|
||||
<MudAlert Severity="Severity.Success" Variant="Variant.Text" Dense="@true">
|
||||
@T("This plugin is approved by your organization. A manual security audit is not required.")
|
||||
</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="@true">
|
||||
@T("No stored audit details are available yet.")
|
||||
</MudAlert>
|
||||
}
|
||||
}
|
||||
else if (state.Audit.Findings.Count == 0)
|
||||
{
|
||||
|
||||
@ -103,12 +103,23 @@ public partial class AssistantPluginSecurityCard : MSGComponentBase
|
||||
|
||||
private string GetFindingSummary()
|
||||
{
|
||||
if (this.SecurityState.IsEnterpriseApproved)
|
||||
return this.T("No user audit required");
|
||||
|
||||
var count = this.SecurityState.Audit?.Findings.Count ?? 0;
|
||||
return string.Format(this.T("{0} Finding(s)"), count);
|
||||
}
|
||||
|
||||
private string GetAuditTimestampLabel()
|
||||
{
|
||||
if (this.SecurityState.IsEnterpriseApproved)
|
||||
{
|
||||
var approvedAt = this.SecurityState.EnterpriseApproval?.ApprovedAtUtc;
|
||||
return approvedAt is null
|
||||
? this.T("Company approved")
|
||||
: this.FormatFileTimestamp(approvedAt.Value.ToLocalTime().DateTime);
|
||||
}
|
||||
|
||||
var auditedAt = this.SecurityState.Audit?.AuditedAtUtc;
|
||||
return auditedAt is null
|
||||
? this.T("No audit yet")
|
||||
|
||||
@ -12,9 +12,18 @@
|
||||
else
|
||||
{
|
||||
<MudStack Spacing="2">
|
||||
<MudAlert Severity="Severity.Info" Dense="true">
|
||||
@T("This security check uses a sample prompt preview. Empty or placeholder values in the preview are expected.")
|
||||
</MudAlert>
|
||||
@if (this.securityState.IsEnterpriseApproved)
|
||||
{
|
||||
<MudAlert Severity="Severity.Success" Dense="true">
|
||||
@T("This assistant plugin is approved by your organization. A manual security audit is not required.")
|
||||
</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Dense="true">
|
||||
@T("This security check uses a sample prompt preview. Empty or placeholder values in the preview are expected.")
|
||||
</MudAlert>
|
||||
}
|
||||
|
||||
<MudPaper Class="pa-3 border-dashed border rounded-lg">
|
||||
<MudText Typo="Typo.h6">@this.plugin.Name</MudText>
|
||||
@ -298,9 +307,12 @@
|
||||
<MudButton OnClick="@this.CloseWithoutActivation" Variant="Variant.Filled">
|
||||
@(this.audit is null ? T("Cancel") : T("Close"))
|
||||
</MudButton>
|
||||
<MudButton OnClick="@this.RunAudit" Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!this.CanRunAudit || this.justAudited)">
|
||||
@T("Start Security Check")
|
||||
</MudButton>
|
||||
@if (!this.securityState.IsEnterpriseApproved)
|
||||
{
|
||||
<MudButton OnClick="@this.RunAudit" Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!this.CanRunAudit || this.justAudited)">
|
||||
@T("Start Security Check")
|
||||
</MudButton>
|
||||
}
|
||||
@if (this.CanEnablePlugin)
|
||||
{
|
||||
<MudButton OnClick="@this.EnablePlugin" Variant="Variant.Filled" Color="@this.EnableButtonColor">
|
||||
|
||||
@ -37,6 +37,7 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
|
||||
private IReadOnlyCollection<TreeItemData<ITreeItem>> fileSystemTreeItems = [];
|
||||
private CultureInfo currentCultureInfo = CultureInfo.InvariantCulture;
|
||||
private bool isAuditing;
|
||||
private PluginAssistantSecurityState securityState = new();
|
||||
|
||||
private AIStudio.Settings.Provider CurrentProvider => this.SettingsManager.GetPreselectedProvider(Tools.Components.AGENT_ASSISTANT_PLUGIN_AUDIT, null, true);
|
||||
|
||||
@ -50,7 +51,7 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
|
||||
|
||||
private string MinimumLevelLabel => this.MinimumLevel.GetName();
|
||||
|
||||
private bool CanRunAudit => this.plugin is not null && this.CurrentProvider != AIStudio.Settings.Provider.NONE && !this.isAuditing;
|
||||
private bool CanRunAudit => this.plugin is not null && this.CurrentProvider != AIStudio.Settings.Provider.NONE && !this.isAuditing && !this.securityState.IsEnterpriseApproved;
|
||||
|
||||
private bool IsAuditBelowMinimum => this.audit is not null && this.audit.Level < this.MinimumLevel;
|
||||
|
||||
@ -74,6 +75,7 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
|
||||
.FirstOrDefault(x => x.Id == this.PluginId);
|
||||
if (this.plugin is not null)
|
||||
{
|
||||
this.securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, this.plugin);
|
||||
this.promptPreview = await this.plugin.BuildAuditPromptPreviewAsync();
|
||||
this.promptFallbackPreview = this.plugin.BuildAuditPromptFallbackPreview();
|
||||
this.plugin.CreateAuditComponentSummary();
|
||||
@ -96,6 +98,7 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
|
||||
try
|
||||
{
|
||||
this.audit = await this.AssistantPluginAuditService.RunAuditAsync(this.plugin);
|
||||
this.securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, this.plugin);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@ -42,13 +42,15 @@
|
||||
@foreach (var assistantPlugin in this.AssistantPlugins)
|
||||
{
|
||||
var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, assistantPlugin);
|
||||
var launchLink = assistantPlugin.StartsChatDirectly ? string.Empty : $"{Routes.ASSISTANT_DYNAMIC}?assistantId={assistantPlugin.Id}";
|
||||
<AssistantBlock TSettings="NoSettingsPanel"
|
||||
Name="@T(assistantPlugin.AssistantTitle)"
|
||||
Description="@T(assistantPlugin.Description)"
|
||||
Icon="@Icons.Material.Filled.FindInPage"
|
||||
Disabled="@(!securityState.CanStartAssistant)"
|
||||
AssistantSessionInstanceId="@assistantPlugin.Id.ToString()"
|
||||
Link="@($"{Routes.ASSISTANT_DYNAMIC}?assistantId={assistantPlugin.Id}")">
|
||||
Link="@launchLink"
|
||||
OnClick="@(() => this.StartAssistantPluginAsync(assistantPlugin))">
|
||||
<SecurityBadge>
|
||||
<AssistantPluginSecurityCard Plugin="@assistantPlugin" Compact="@true" />
|
||||
</SecurityBadge>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Agents.AssistantAudit;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
@ -12,6 +13,12 @@ public partial class Assistants : MSGComponentBase
|
||||
|
||||
[Inject]
|
||||
private AssistantPluginAuditService AssistantPluginAuditService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private NavigationManager NavigationManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<Assistants> Logger { get; init; } = null!;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@ -81,6 +88,50 @@ public partial class Assistants : MSGComponentBase
|
||||
audits.Add(audit);
|
||||
}
|
||||
|
||||
private async Task StartAssistantPluginAsync(PluginAssistants assistantPlugin)
|
||||
{
|
||||
var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, assistantPlugin);
|
||||
if (!securityState.CanStartAssistant)
|
||||
return;
|
||||
|
||||
if (!assistantPlugin.StartsChatDirectly)
|
||||
{
|
||||
this.NavigationManager.NavigateTo($"{Routes.ASSISTANT_DYNAMIC}?assistantId={assistantPlugin.Id}");
|
||||
return;
|
||||
}
|
||||
|
||||
var chatThread = await this.TryCreateDirectChatThreadAsync(assistantPlugin);
|
||||
if (chatThread is null)
|
||||
return;
|
||||
|
||||
MessageBus.INSTANCE.DeferMessage(this, Event.SEND_TO_CHAT, chatThread);
|
||||
this.NavigationManager.NavigateTo(Routes.CHAT);
|
||||
}
|
||||
|
||||
private async Task<ChatThread?> TryCreateDirectChatThreadAsync(PluginAssistants assistantPlugin)
|
||||
{
|
||||
var workspaceId = await WorkspaceBehaviour.ResolveOrCreateWorkspaceIdByNameAsync(assistantPlugin.LaunchWorkspaceName);
|
||||
if (workspaceId == Guid.Empty)
|
||||
{
|
||||
this.Logger.LogWarning("Assistant plugin '{PluginName}' could not resolve or create workspace '{WorkspaceName}'.", assistantPlugin.Name, assistantPlugin.LaunchWorkspaceName);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ChatThread
|
||||
{
|
||||
IncludeDateTime = true,
|
||||
SelectedProvider = string.Empty,
|
||||
SelectedProfile = string.Empty,
|
||||
SelectedChatTemplate = string.Empty,
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
WorkspaceId = workspaceId,
|
||||
ChatId = Guid.NewGuid(),
|
||||
Name = assistantPlugin.AssistantTitle,
|
||||
DataSourceOptions = this.SettingsManager.ConfigurationData.Chat.PreselectedDataSourceOptions.CreateCopy(),
|
||||
Blocks = [],
|
||||
};
|
||||
}
|
||||
|
||||
protected override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
|
||||
{
|
||||
if (triggeredEvent is Event.PLUGINS_RELOADED)
|
||||
|
||||
@ -81,6 +81,9 @@ Each assistant plugin lives in its own directory under the assistants plugin roo
|
||||
|
||||
## Structure
|
||||
- `ASSISTANT` is the root table. It must contain `Title`, `Description`, `SystemPrompt`, `SubmitText`, `AllowProfiles`, and the nested `UI` definition.
|
||||
- `ASSISTANT` may optionally define direct-launch metadata for assistant tiles:
|
||||
- `LaunchBehavior = "OPEN_WORKSPACE_CHAT_BY_NAME"`
|
||||
- `WorkspaceName = "<target workspace name>"`
|
||||
- `UI.Type` is always `"FORM"` and `UI.Children` is a list of component tables.
|
||||
- Each component table declares `Type`, an optional `Children` array, and a `Props` table that feeds the component’s parameters.
|
||||
|
||||
@ -92,6 +95,8 @@ ASSISTANT = {
|
||||
["SystemPrompt"] = "",
|
||||
["SubmitText"] = "",
|
||||
["AllowProfiles"] = true,
|
||||
["LaunchBehavior"] = "OPEN_WORKSPACE_CHAT_BY_NAME",
|
||||
["WorkspaceName"] = "",
|
||||
["UI"] = {
|
||||
["Type"] = "FORM",
|
||||
["Children"] = {
|
||||
@ -101,6 +106,29 @@ ASSISTANT = {
|
||||
}
|
||||
```
|
||||
|
||||
## Direct Launch to Workspace Chat
|
||||
Assistant plugins can optionally skip the normal assistant page and open a chat directly from the tile.
|
||||
|
||||
```lua
|
||||
ASSISTANT = {
|
||||
["Title"] = "Open Chat",
|
||||
["Description"] = "Open a new chat in the XXX workspace.",
|
||||
["SystemPrompt"] = "",
|
||||
["SubmitText"] = "Start",
|
||||
["AllowProfiles"] = true,
|
||||
["LaunchBehavior"] = "OPEN_WORKSPACE_CHAT_BY_NAME",
|
||||
["WorkspaceName"] = "XXX",
|
||||
["UI"] = {
|
||||
["Type"] = "FORM",
|
||||
["Children"] = {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `WorkspaceName` is resolved case-insensitively after trimming.
|
||||
- If the workspace does not exist yet, AI Studio creates it automatically.
|
||||
- The opened chat uses the normal default chat settings of AI Studio.
|
||||
|
||||
|
||||
#### Supported types (matching the Blazor UI components):
|
||||
|
||||
|
||||
@ -62,6 +62,8 @@ ASSISTANT = {
|
||||
["SystemPrompt"] = "<prompt that fundamentally changes behaviour, personality and task focus of your assistant. Invisible to the user>", -- required
|
||||
["SubmitText"] = "<label for submit button>", -- required
|
||||
["AllowProfiles"] = true, -- if true, allows AiStudios profiles; required
|
||||
["LaunchBehavior"] = "<NONE|OPEN_WORKSPACE_CHAT_BY_NAME>", -- optional; when set to OPEN_WORKSPACE_CHAT_BY_NAME the tile opens a chat directly
|
||||
["WorkspaceName"] = "<name of the workspace to open or create>", -- optional; required for OPEN_WORKSPACE_CHAT_BY_NAME
|
||||
["UI"] = {
|
||||
["Type"] = "FORM",
|
||||
["Children"] = {
|
||||
|
||||
@ -319,6 +319,23 @@ CONFIG["SETTINGS"] = {}
|
||||
-- DOCUMENT_ANALYSIS_ASSISTANT, SLIDE_BUILDER_ASSISTANT, I18N_ASSISTANT
|
||||
-- CONFIG["SETTINGS"]["DataApp.HiddenAssistants"] = { "ERI_ASSISTANT", "I18N_ASSISTANT" }
|
||||
|
||||
-- Configure enterprise approvals for assistant plugins.
|
||||
-- Each approval is matched only by the current SHA-256 hash over all Lua files
|
||||
-- in the assistant plugin folder, in canonical sorted order.
|
||||
-- When the hash matches, the assistant plugin is treated as SAFE immediately and
|
||||
-- no user-run security audit is required.
|
||||
-- You can generate the exact hash with the build-script command:
|
||||
-- dotnet run --project app/Build -- assistant-plugin-hash "<plugin-dir>" --lua-snippet
|
||||
-- CONFIG["SETTINGS"]["DataAssistantPluginAudit.EnterpriseApprovedPlugins"] = {
|
||||
-- {
|
||||
-- ["PluginHash"] = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF",
|
||||
-- ["DisplayName"] = "Name of Plugin",
|
||||
-- ["Comment"] = "Optional comment",
|
||||
-- ["ApprovedBy"] = "Optional Approver",
|
||||
-- ["ApprovedAtUtc"] = "2026-07-02T09:30:00Z",
|
||||
-- }
|
||||
-- }
|
||||
|
||||
-- Configure a global shortcut for starting and stopping dictation.
|
||||
--
|
||||
-- The format follows the Rust and Tauri conventions. Especially,
|
||||
|
||||
@ -2007,6 +2007,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T661906146"] = "Das Ergeb
|
||||
-- Show or hide the detailed security information.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1045105126"] = "Detaillierte Sicherheitsinformationen anzeigen oder ausblenden."
|
||||
|
||||
-- This plugin is approved by your organization. A manual security audit is not required.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1213338416"] = "Dieses Plugin ist von Ihrer Organisation freigegeben. Eine manuelle Sicherheitsprüfung ist nicht erforderlich."
|
||||
|
||||
-- Assistant Audit
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1506922856"] = "Assistentenprüfung"
|
||||
|
||||
@ -2022,6 +2025,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1805629238"
|
||||
-- Assistant Security
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1841954939"] = "Sicherheit des Assistenten"
|
||||
|
||||
-- Company approved
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2036497459"] = "Organisationsfreigabe"
|
||||
|
||||
-- Approved name
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2282386733"] = "Genehmigter Name"
|
||||
|
||||
-- Required minimum
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2354026284"] = "Erforderliches Minimum"
|
||||
|
||||
@ -2031,6 +2040,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2757790517"
|
||||
-- Technical Details
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2769062110"] = "Technische Details"
|
||||
|
||||
-- Approval comment
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2906887599"] = "Genehmigungskommentar"
|
||||
|
||||
-- No audit yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3138877447"] = "Noch keine Prüfung vorhanden"
|
||||
|
||||
@ -2046,21 +2058,39 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3448155331"
|
||||
-- No stored audit details are available yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3647137899"] = "Es sind noch keine gespeicherten Audit-Details verfügbar."
|
||||
|
||||
-- Enterprise approval is active
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3816183955"] = "Organisationsfreigabe ist aktiv"
|
||||
|
||||
-- Current hash
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3896860082"] = "Aktueller Hash"
|
||||
|
||||
-- No user audit required
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3916957031"] = "Keine Benutzerprüfung erforderlich"
|
||||
|
||||
-- Audited at
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T4103354206"] = "Geprüft am"
|
||||
|
||||
-- Approved hash
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T4170340306"] = "Genehmigter Hash"
|
||||
|
||||
-- No security findings were stored for this assistant plugin.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T4256679240"] = "Für dieses Assistenten-Plugin wurden keine Sicherheitsbefunde gespeichert."
|
||||
|
||||
-- Status source
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T4289123040"] = "Quellstatus"
|
||||
|
||||
-- Audit hash
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T53507304"] = "Prüf-Hash"
|
||||
|
||||
-- {0} Finding(s)
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T631393016"] = "{0} Fund(e)"
|
||||
|
||||
-- Approved by
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T894543751"] = "Genehmigt von"
|
||||
|
||||
-- Approved at
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T978873131"] = "Genehmigt am"
|
||||
|
||||
-- Click the paperclip to attach files, or click the number to see your attached files.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T1358313858"] = "Klicken Sie auf die Büroklammer, um Dateien anzuhängen, oder klicken Sie auf die Zahl, um Ihre angehängten Dateien anzuzeigen."
|
||||
|
||||
@ -3459,6 +3489,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3652671056"] =
|
||||
-- Unavailable
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3662391977"] = "Nicht verfügbar"
|
||||
|
||||
-- This assistant plugin is approved by your organization. A manual security audit is not required.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3680374624"] = "Dieses Assistenten-Plugin ist von Ihrer Organisation freigegeben. Eine manuelle Sicherheitsprüfung ist nicht erforderlich."
|
||||
|
||||
-- Plugin Structure
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T371537943"] = "Plugin-Struktur"
|
||||
|
||||
@ -7629,6 +7662,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANT
|
||||
-- Button
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPEEXTENSIONS::T864557713"] = "Schaltfläche"
|
||||
|
||||
-- The ASSISTANT table contains an invalid LaunchBehavior value.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T109828905"] = "Die Tabelle ASSISTANT enthält einen ungültigen Wert für LaunchBehavior."
|
||||
|
||||
-- The ASSISTANT table contains an unsupported LaunchBehavior value.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T1194373781"] = "Die ASSISTANT-Tabelle enthält einen nicht unterstützten LaunchBehavior-Wert."
|
||||
|
||||
-- Failed to parse the UI render tree from the ASSISTANT lua table.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T1318499252"] = "Der UI-Render-Baum konnte nicht aus der ASSISTANT-Lua-Tabelle geparst werden."
|
||||
|
||||
@ -7644,12 +7683,18 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T2
|
||||
-- The ASSISTANT lua table does not exist or is not a valid table.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3017816936"] = "Die Lua-Tabelle **ASSISTANT** existiert nicht oder ist keine gültige Tabelle."
|
||||
|
||||
-- The ASSISTANT table contains an empty WorkspaceName for LaunchBehavior 'OPEN_WORKSPACE_CHAT_BY_NAME'.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3233001282"] = "Die ASSISTANT-Tabelle enthält einen leeren Arbeitsbereichsnamen für das LaunchBehavior 'OPEN_WORKSPACE_CHAT_BY_NAME'."
|
||||
|
||||
-- The provided ASSISTANT lua table does not contain a valid system prompt.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3402798667"] = "Die bereitgestellte ASSISTANT-Lua-Tabelle enthält keine gültige Systemaufforderung."
|
||||
|
||||
-- The ASSISTANT table does not contain a valid system prompt.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3723171842"] = "Die Tabelle **ASSISTANT** enthält keine gültige Systemanweisung."
|
||||
|
||||
-- The ASSISTANT table contains the LaunchBehavior 'OPEN_WORKSPACE_CHAT_BY_NAME' but no valid WorkspaceName.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T4215554842"] = "Die ASSISTANT-Tabelle enthält das LaunchBehavior 'OPEN_WORKSPACE_CHAT_BY_NAME', aber keinen gültigen WorkspaceNamen."
|
||||
|
||||
-- ASSISTANT.BuildPrompt exists but is not a Lua function or has invalid syntax.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T683382975"] = "`ASSISTANT.BuildPrompt` ist vorhanden, aber keine Lua-Funktion oder hat eine ungültige Syntax."
|
||||
|
||||
@ -7692,6 +7737,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECUR
|
||||
-- The current audit result is '{0}', which is below your required minimum level '{1}'. Audit enforcement is currently disabled, so this assistant plugin can still be enabled or used.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T2774333862"] = "Das aktuelle Prüfergebnis ist „{0}“, was unter Ihrem erforderlichen Mindestniveau „{1}“ liegt. Die Prüfungsdurchsetzung ist derzeit deaktiviert, daher kann dieses Assistenten-Plugin trotzdem aktiviert oder verwendet werden."
|
||||
|
||||
-- The current plugin hash matches an enterprise-managed approval. No manual security audit is required for activation or usage.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T2824524534"] = "Der aktuelle Plugin-Hash entspricht einer organisationsverwalteten Freigabe. Für die Aktivierung oder Nutzung ist keine manuelle Sicherheitsprüfung erforderlich."
|
||||
|
||||
-- Not Audited
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T2828154864"] = "Nicht geprüft"
|
||||
|
||||
@ -7701,12 +7749,18 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECUR
|
||||
-- Open Security Check
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T290241209"] = "Sicherheitsprüfung öffnen"
|
||||
|
||||
-- User Audit
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3293963409"] = "Benutzerprüfung"
|
||||
|
||||
-- Restricted
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3325062668"] = "Eingeschränkt"
|
||||
|
||||
-- Unknown
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3424652889"] = "Unbekannt"
|
||||
|
||||
-- Approved by your organization
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3508214481"] = "Von Ihrer Organisation genehmigt"
|
||||
|
||||
-- Unlocked
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3606159420"] = "Entsperrt"
|
||||
|
||||
@ -7722,9 +7776,24 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECUR
|
||||
-- No security audit exists yet. Your current security settings do not require an audit before this assistant plugin may be used.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3899951594"] = "Es gibt noch kein Sicherheitsaudit. Ihre aktuellen Sicherheitseinstellungen verlangen kein Audit, bevor dieses Assistenten-Plugin verwendet werden darf."
|
||||
|
||||
-- No Approval
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T515592229"] = "Keine Freigabe"
|
||||
|
||||
-- This assistant was approved by your organization.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T538196816"] = "Dieser Assistent wurde von Ihrer Organisation freigegeben."
|
||||
|
||||
-- Safe
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T760494712"] = "Sicher"
|
||||
|
||||
-- Open Security Details
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T803119455"] = "Sicherheitsdetails öffnen"
|
||||
|
||||
-- Start Security Check
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T811648299"] = "Sicherheitsprüfung starten"
|
||||
|
||||
-- This assistant was approved by your organization as '{0}'.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T834246718"] = "Dieser Assistent wurde von Ihrer Organisation als '{0}' freigegeben."
|
||||
|
||||
-- This assistant currently has no stored audit.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T921972844"] = "Für diesen Assistenten ist derzeit kein gespeichertes Audit vorhanden."
|
||||
|
||||
|
||||
@ -2007,6 +2007,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T661906146"] = "The resul
|
||||
-- Show or hide the detailed security information.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1045105126"] = "Show or hide the detailed security information."
|
||||
|
||||
-- This plugin is approved by your organization. A manual security audit is not required.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1213338416"] = "This plugin is approved by your organization. A manual security audit is not required."
|
||||
|
||||
-- Assistant Audit
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1506922856"] = "Assistant Audit"
|
||||
|
||||
@ -2022,6 +2025,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1805629238"
|
||||
-- Assistant Security
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1841954939"] = "Assistant Security"
|
||||
|
||||
-- Company approved
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2036497459"] = "Company approved"
|
||||
|
||||
-- Approved name
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2282386733"] = "Approved name"
|
||||
|
||||
-- Required minimum
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2354026284"] = "Required minimum"
|
||||
|
||||
@ -2031,6 +2040,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2757790517"
|
||||
-- Technical Details
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2769062110"] = "Technical Details"
|
||||
|
||||
-- Approval comment
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2906887599"] = "Approval comment"
|
||||
|
||||
-- No audit yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3138877447"] = "No audit yet"
|
||||
|
||||
@ -2046,21 +2058,39 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3448155331"
|
||||
-- No stored audit details are available yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3647137899"] = "No stored audit details are available yet."
|
||||
|
||||
-- Enterprise approval is active
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3816183955"] = "Enterprise approval is active"
|
||||
|
||||
-- Current hash
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3896860082"] = "Current hash"
|
||||
|
||||
-- No user audit required
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3916957031"] = "No user audit required"
|
||||
|
||||
-- Audited at
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T4103354206"] = "Audited at"
|
||||
|
||||
-- Approved hash
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T4170340306"] = "Approved hash"
|
||||
|
||||
-- No security findings were stored for this assistant plugin.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T4256679240"] = "No security findings were stored for this assistant plugin."
|
||||
|
||||
-- Status source
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T4289123040"] = "Status source"
|
||||
|
||||
-- Audit hash
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T53507304"] = "Audit hash"
|
||||
|
||||
-- {0} Finding(s)
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T631393016"] = "{0} Finding(s)"
|
||||
|
||||
-- Approved by
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T894543751"] = "Approved by"
|
||||
|
||||
-- Approved at
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T978873131"] = "Approved at"
|
||||
|
||||
-- Click the paperclip to attach files, or click the number to see your attached files.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T1358313858"] = "Click the paperclip to attach files, or click the number to see your attached files."
|
||||
|
||||
@ -3459,6 +3489,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3652671056"] =
|
||||
-- Unavailable
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3662391977"] = "Unavailable"
|
||||
|
||||
-- This assistant plugin is approved by your organization. A manual security audit is not required.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3680374624"] = "This assistant plugin is approved by your organization. A manual security audit is not required."
|
||||
|
||||
-- Plugin Structure
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T371537943"] = "Plugin Structure"
|
||||
|
||||
@ -7629,6 +7662,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANT
|
||||
-- Button
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPEEXTENSIONS::T864557713"] = "Button"
|
||||
|
||||
-- The ASSISTANT table contains an invalid LaunchBehavior value.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T109828905"] = "The ASSISTANT table contains an invalid LaunchBehavior value."
|
||||
|
||||
-- The ASSISTANT table contains an unsupported LaunchBehavior value.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T1194373781"] = "The ASSISTANT table contains an unsupported LaunchBehavior value."
|
||||
|
||||
-- Failed to parse the UI render tree from the ASSISTANT lua table.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T1318499252"] = "Failed to parse the UI render tree from the ASSISTANT lua table."
|
||||
|
||||
@ -7644,12 +7683,18 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T2
|
||||
-- The ASSISTANT lua table does not exist or is not a valid table.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3017816936"] = "The ASSISTANT lua table does not exist or is not a valid table."
|
||||
|
||||
-- The ASSISTANT table contains an empty WorkspaceName for LaunchBehavior 'OPEN_WORKSPACE_CHAT_BY_NAME'.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3233001282"] = "The ASSISTANT table contains an empty WorkspaceName for LaunchBehavior 'OPEN_WORKSPACE_CHAT_BY_NAME'."
|
||||
|
||||
-- The provided ASSISTANT lua table does not contain a valid system prompt.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3402798667"] = "The provided ASSISTANT lua table does not contain a valid system prompt."
|
||||
|
||||
-- The ASSISTANT table does not contain a valid system prompt.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3723171842"] = "The ASSISTANT table does not contain a valid system prompt."
|
||||
|
||||
-- The ASSISTANT table contains the LaunchBehavior 'OPEN_WORKSPACE_CHAT_BY_NAME' but no valid WorkspaceName.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T4215554842"] = "The ASSISTANT table contains the LaunchBehavior 'OPEN_WORKSPACE_CHAT_BY_NAME' but no valid WorkspaceName."
|
||||
|
||||
-- ASSISTANT.BuildPrompt exists but is not a Lua function or has invalid syntax.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T683382975"] = "ASSISTANT.BuildPrompt exists but is not a Lua function or has invalid syntax."
|
||||
|
||||
@ -7692,6 +7737,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECUR
|
||||
-- The current audit result is '{0}', which is below your required minimum level '{1}'. Audit enforcement is currently disabled, so this assistant plugin can still be enabled or used.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T2774333862"] = "The current audit result is '{0}', which is below your required minimum level '{1}'. Audit enforcement is currently disabled, so this assistant plugin can still be enabled or used."
|
||||
|
||||
-- The current plugin hash matches an enterprise-managed approval. No manual security audit is required for activation or usage.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T2824524534"] = "The current plugin hash matches an enterprise-managed approval. No manual security audit is required for activation or usage."
|
||||
|
||||
-- Not Audited
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T2828154864"] = "Not Audited"
|
||||
|
||||
@ -7701,12 +7749,18 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECUR
|
||||
-- Open Security Check
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T290241209"] = "Open Security Check"
|
||||
|
||||
-- User Audit
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3293963409"] = "User Audit"
|
||||
|
||||
-- Restricted
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3325062668"] = "Restricted"
|
||||
|
||||
-- Unknown
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3424652889"] = "Unknown"
|
||||
|
||||
-- Approved by your organization
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3508214481"] = "Approved by your organization"
|
||||
|
||||
-- Unlocked
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3606159420"] = "Unlocked"
|
||||
|
||||
@ -7722,9 +7776,24 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECUR
|
||||
-- No security audit exists yet. Your current security settings do not require an audit before this assistant plugin may be used.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T3899951594"] = "No security audit exists yet. Your current security settings do not require an audit before this assistant plugin may be used."
|
||||
|
||||
-- No Approval
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T515592229"] = "No Approval"
|
||||
|
||||
-- This assistant was approved by your organization.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T538196816"] = "This assistant was approved by your organization."
|
||||
|
||||
-- Safe
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T760494712"] = "Safe"
|
||||
|
||||
-- Open Security Details
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T803119455"] = "Open Security Details"
|
||||
|
||||
-- Start Security Check
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T811648299"] = "Start Security Check"
|
||||
|
||||
-- This assistant was approved by your organization as '{0}'.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T834246718"] = "This assistant was approved by your organization as '{0}'."
|
||||
|
||||
-- This assistant currently has no stored audit.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T921972844"] = "This assistant currently has no stored audit."
|
||||
|
||||
|
||||
@ -40,4 +40,9 @@ public sealed class DataAssistantPluginAudit(Expression<Func<Data, DataAssistant
|
||||
/// If true, the security audit will be hidden from the user and done in the background
|
||||
/// </summary>
|
||||
public bool AutomaticallyAuditAssistants { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AutomaticallyAuditAssistants, false);
|
||||
|
||||
/// <summary>
|
||||
/// Enterprise-managed assistant plugin hashes that are approved without requiring a user audit.
|
||||
/// </summary>
|
||||
public IList<DataAssistantPluginEnterpriseApproval> EnterpriseApprovedPlugins { get; set; } = ManagedConfiguration.Register(configSelection, n => n.EnterpriseApprovedPlugins, []);
|
||||
}
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
/// <summary>
|
||||
/// Enterprise-managed approval entry for an assistant plugin hash.
|
||||
/// </summary>
|
||||
public sealed class DataAssistantPluginEnterpriseApproval
|
||||
{
|
||||
public string PluginHash { get; init; } = string.Empty;
|
||||
public string DisplayName { get; init; } = string.Empty;
|
||||
public string Comment { get; init; } = string.Empty;
|
||||
public string ApprovedBy { get; init; } = string.Empty;
|
||||
public DateTimeOffset? ApprovedAtUtc { get; init; }
|
||||
}
|
||||
@ -373,4 +373,4 @@ public static partial class ProviderExtensions
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -362,4 +362,4 @@ public sealed class AssistantSessionService(MessageBus messageBus)
|
||||
State = new Dictionary<string, IAssistantSessionSnapshotField>(session.State, StringComparer.Ordinal),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -31,12 +31,27 @@ public static class CommonTools
|
||||
if (string.IsNullOrWhiteSpace(ietfTag))
|
||||
return CultureInfo.InvariantCulture;
|
||||
|
||||
var normalizedTag = ietfTag.Trim().Replace('_', '-');
|
||||
|
||||
try
|
||||
{
|
||||
return CultureInfo.GetCultureInfo(ietfTag);
|
||||
return CultureInfo.GetCultureInfo(normalizedTag);
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
var separatorIndex = normalizedTag.IndexOf('-');
|
||||
if (separatorIndex > 0)
|
||||
{
|
||||
var neutralLanguageTag = normalizedTag[..separatorIndex];
|
||||
try
|
||||
{
|
||||
return CultureInfo.GetCultureInfo(neutralLanguageTag);
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return CultureInfo.InvariantCulture;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
namespace AIStudio.Tools.PluginSystem.Assistants;
|
||||
|
||||
public enum AssistantPluginLaunchBehavior
|
||||
{
|
||||
NONE,
|
||||
OPEN_WORKSPACE_CHAT_BY_NAME,
|
||||
}
|
||||
@ -8,6 +8,8 @@ public static class PluginAssistantSecurityResolver
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginAssistantSecurityResolver).Namespace, nameof(PluginAssistantSecurityResolver));
|
||||
|
||||
private static string NormalizeHash(string hash) => string.IsNullOrWhiteSpace(hash) ? string.Empty : hash.Trim().ToUpperInvariant();
|
||||
|
||||
private static string GetAvailabilityLabel(bool requiresAudit, bool hasAudit, bool hasHashMismatch, bool isBlocked, bool canOverride)
|
||||
{
|
||||
if (hasHashMismatch)
|
||||
@ -75,10 +77,55 @@ public static class PluginAssistantSecurityResolver
|
||||
var auditSettings = settingsManager.ConfigurationData.AssistantPluginAudit;
|
||||
var enforceAuditBeforeActivation = auditSettings.RequireAuditBeforeActivation;
|
||||
var isEnforcementDisabled = !enforceAuditBeforeActivation;
|
||||
var currentHash = plugin.ComputeAuditHash();
|
||||
var currentHash = NormalizeHash(plugin.ComputeAuditHash());
|
||||
var enterpriseApproval = auditSettings.EnterpriseApprovedPlugins
|
||||
.FirstOrDefault(x => string.Equals(NormalizeHash(x.PluginHash), currentHash, StringComparison.Ordinal));
|
||||
|
||||
if (enterpriseApproval is not null)
|
||||
{
|
||||
var enterpriseHeadline = string.IsNullOrWhiteSpace(enterpriseApproval.DisplayName)
|
||||
? TB("This assistant was approved by your organization.")
|
||||
: string.Format(TB("This assistant was approved by your organization as '{0}'."), enterpriseApproval.DisplayName);
|
||||
|
||||
return new PluginAssistantSecurityState
|
||||
{
|
||||
Plugin = plugin,
|
||||
Audit = null,
|
||||
EnterpriseApproval = enterpriseApproval,
|
||||
Settings = auditSettings,
|
||||
Source = PluginAssistantSecurityStatusSource.ENTERPRISE_APPROVAL,
|
||||
CurrentHash = currentHash,
|
||||
HashMatches = true,
|
||||
HasHashMismatch = false,
|
||||
IsBelowMinimum = false,
|
||||
MeetsMinimumLevel = true,
|
||||
RequiresAudit = false,
|
||||
IsBlocked = false,
|
||||
CanOverride = false,
|
||||
CanActivatePlugin = true,
|
||||
CanStartAssistant = true,
|
||||
AuditLabel = TB("Safe"),
|
||||
AuditColor = AssistantAuditLevel.SAFE.GetColor(),
|
||||
AuditIcon = AssistantAuditLevel.SAFE.GetIcon(),
|
||||
AvailabilityLabel = GetAvailabilityLabel(requiresAudit: false, hasAudit: true, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
AvailabilityColor = GetAvailabilityColor(requiresAudit: false, hasAudit: true, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
AvailabilityIcon = GetAvailabilityIcon(requiresAudit: false, hasAudit: true, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
StatusLabel = TB("Unlocked"),
|
||||
SourceLabel = TB("Approved by your organization"),
|
||||
SourceColor = Color.Success,
|
||||
SourceIcon = MudBlazor.Icons.Material.Filled.Business,
|
||||
BadgeIcon = MudBlazor.Icons.Material.Filled.Business,
|
||||
Headline = enterpriseHeadline,
|
||||
Description = TB("The current plugin hash matches an enterprise-managed approval. No manual security audit is required for activation or usage."),
|
||||
StatusColor = Color.Success,
|
||||
StatusIcon = MudBlazor.Icons.Material.Filled.VerifiedUser,
|
||||
ActionLabel = TB("Open Security Details"),
|
||||
};
|
||||
}
|
||||
|
||||
var audit = settingsManager.ConfigurationData.AssistantPluginAudits.FirstOrDefault(x => x.PluginId == plugin.Id);
|
||||
var hasAudit = audit is not null && audit.Level is not AssistantAuditLevel.UNKNOWN;
|
||||
var hashMatches = hasAudit && string.Equals(audit!.PluginHash, currentHash, StringComparison.Ordinal);
|
||||
var hashMatches = hasAudit && string.Equals(NormalizeHash(audit!.PluginHash), currentHash, StringComparison.Ordinal);
|
||||
var hasHashMismatch = hasAudit && !hashMatches;
|
||||
var isBelowMinimum = hashMatches && audit is not null && audit.Level < auditSettings.MinimumLevel;
|
||||
var meetsMinimum = hashMatches && audit is not null && audit.Level >= auditSettings.MinimumLevel;
|
||||
@ -94,6 +141,7 @@ public static class PluginAssistantSecurityResolver
|
||||
Plugin = plugin,
|
||||
Audit = null,
|
||||
Settings = auditSettings,
|
||||
Source = PluginAssistantSecurityStatusSource.NONE,
|
||||
CurrentHash = currentHash,
|
||||
HashMatches = false,
|
||||
HasHashMismatch = false,
|
||||
@ -111,6 +159,9 @@ public static class PluginAssistantSecurityResolver
|
||||
AvailabilityColor = GetAvailabilityColor(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
AvailabilityIcon = GetAvailabilityIcon(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
StatusLabel = GetAvailabilityLabel(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
SourceLabel = TB("No Approval"),
|
||||
SourceColor = Color.Default,
|
||||
SourceIcon = MudBlazor.Icons.Material.Filled.HelpOutline,
|
||||
BadgeIcon = GetSecurityBadgeIcon(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
Headline = requiresAudit ? TB("This assistant is currently locked.") : TB("This assistant currently has no stored audit."),
|
||||
Description = requiresAudit
|
||||
@ -129,6 +180,7 @@ public static class PluginAssistantSecurityResolver
|
||||
Plugin = plugin,
|
||||
Audit = audit,
|
||||
Settings = auditSettings,
|
||||
Source = PluginAssistantSecurityStatusSource.NONE,
|
||||
CurrentHash = currentHash,
|
||||
HashMatches = false,
|
||||
HasHashMismatch = true,
|
||||
@ -146,6 +198,9 @@ public static class PluginAssistantSecurityResolver
|
||||
AvailabilityColor = GetAvailabilityColor(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
AvailabilityIcon = GetAvailabilityIcon(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
StatusLabel = GetAvailabilityLabel(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
SourceLabel = TB("No Approval"),
|
||||
SourceColor = Color.Default,
|
||||
SourceIcon = MudBlazor.Icons.Material.Filled.Warning,
|
||||
BadgeIcon = GetSecurityBadgeIcon(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
Headline = requiresAudit ? TB("This assistant is locked until it is audited again.") : TB("This assistant changed after its last audit."),
|
||||
Description = requiresAudit
|
||||
@ -167,6 +222,7 @@ public static class PluginAssistantSecurityResolver
|
||||
Plugin = plugin,
|
||||
Audit = audit,
|
||||
Settings = auditSettings,
|
||||
Source = PluginAssistantSecurityStatusSource.USER_AUDIT,
|
||||
CurrentHash = currentHash,
|
||||
HashMatches = true,
|
||||
HasHashMismatch = false,
|
||||
@ -184,6 +240,9 @@ public static class PluginAssistantSecurityResolver
|
||||
AvailabilityColor = GetAvailabilityColor(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride),
|
||||
AvailabilityIcon = GetAvailabilityIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride),
|
||||
StatusLabel = GetAvailabilityLabel(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride),
|
||||
SourceLabel = TB("User Audit"),
|
||||
SourceColor = auditLevel.GetColor(),
|
||||
SourceIcon = MudBlazor.Icons.Material.Filled.Verified,
|
||||
BadgeIcon = GetSecurityBadgeIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride),
|
||||
Headline = isBlockedByMinimum
|
||||
? TB("This assistant is currently locked.")
|
||||
@ -208,6 +267,7 @@ public static class PluginAssistantSecurityResolver
|
||||
Plugin = plugin,
|
||||
Audit = audit,
|
||||
Settings = auditSettings,
|
||||
Source = PluginAssistantSecurityStatusSource.USER_AUDIT,
|
||||
CurrentHash = currentHash,
|
||||
HashMatches = true,
|
||||
HasHashMismatch = false,
|
||||
@ -225,6 +285,9 @@ public static class PluginAssistantSecurityResolver
|
||||
AvailabilityColor = GetAvailabilityColor(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
AvailabilityIcon = GetAvailabilityIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
StatusLabel = GetAvailabilityLabel(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
SourceLabel = TB("User Audit"),
|
||||
SourceColor = auditLevelDefault.GetColor(),
|
||||
SourceIcon = MudBlazor.Icons.Material.Filled.Verified,
|
||||
BadgeIcon = GetSecurityBadgeIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
Headline = TB("This assistant is currently unlocked."),
|
||||
Description = string.Format(TB("The stored audit matches the current plugin code and meets your required minimum level '{0}'."), auditSettings.MinimumLevel.GetName()),
|
||||
|
||||
@ -13,9 +13,12 @@ public sealed class PluginAssistantSecurityState
|
||||
{
|
||||
public PluginAssistants Plugin { get; init; } = null!;
|
||||
public PluginAssistantAudit? Audit { get; init; }
|
||||
public DataAssistantPluginEnterpriseApproval? EnterpriseApproval { get; init; }
|
||||
public DataAssistantPluginAudit Settings { get; init; } = new();
|
||||
public PluginAssistantSecurityStatusSource Source { get; init; } = PluginAssistantSecurityStatusSource.NONE;
|
||||
public string CurrentHash { get; init; } = string.Empty;
|
||||
public bool HasAudit => this.Audit is not null;
|
||||
public bool IsEnterpriseApproved => this.Source is PluginAssistantSecurityStatusSource.ENTERPRISE_APPROVAL;
|
||||
public bool HashMatches { get; init; }
|
||||
public bool HasHashMismatch { get; init; }
|
||||
public bool IsBelowMinimum { get; init; }
|
||||
@ -32,6 +35,9 @@ public sealed class PluginAssistantSecurityState
|
||||
public Color AvailabilityColor { get; init; } = Color.Info;
|
||||
public string AvailabilityIcon { get; init; } = MudBlazor.Icons.Material.Filled.Lock;
|
||||
public string StatusLabel { get; init; } = string.Empty;
|
||||
public string SourceLabel { get; init; } = string.Empty;
|
||||
public Color SourceColor { get; init; } = Color.Info;
|
||||
public string SourceIcon { get; init; } = MudBlazor.Icons.Material.Filled.Info;
|
||||
public string Headline { get; init; } = string.Empty;
|
||||
public string Description { get; init; } = string.Empty;
|
||||
public Color StatusColor { get; init; } = Color.Info;
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
namespace AIStudio.Tools.PluginSystem.Assistants;
|
||||
|
||||
public enum PluginAssistantSecurityStatusSource
|
||||
{
|
||||
NONE,
|
||||
USER_AUDIT,
|
||||
ENTERPRISE_APPROVAL,
|
||||
}
|
||||
@ -2,8 +2,8 @@ using System.Collections.Immutable;
|
||||
using AIStudio.Tools.PluginSystem.Assistants.DataModel;
|
||||
using AIStudio.Tools.PluginSystem.Assistants.DataModel.Layout;
|
||||
using Lua;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using AssistantPluginHash = SharedTools.AssistantPluginHash;
|
||||
|
||||
namespace AIStudio.Tools.PluginSystem.Assistants;
|
||||
|
||||
@ -36,6 +36,9 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
|
||||
public bool AllowProfiles { get; private set; } = true;
|
||||
public bool HasEmbeddedProfileSelection { get; private set; }
|
||||
public bool HasCustomPromptBuilder => this.buildPromptFunction is not null;
|
||||
public AssistantPluginLaunchBehavior LaunchBehavior { get; private set; }
|
||||
public string LaunchWorkspaceName { get; private set; } = string.Empty;
|
||||
public bool StartsChatDirectly => this.LaunchBehavior is AssistantPluginLaunchBehavior.OPEN_WORKSPACE_CHAT_BY_NAME;
|
||||
public const int TEXT_AREA_MAX_VALUE = 524288;
|
||||
|
||||
private LuaFunction? buildPromptFunction;
|
||||
@ -61,6 +64,8 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
|
||||
message = string.Empty;
|
||||
this.HasEmbeddedProfileSelection = false;
|
||||
this.buildPromptFunction = null;
|
||||
this.LaunchBehavior = AssistantPluginLaunchBehavior.NONE;
|
||||
this.LaunchWorkspaceName = string.Empty;
|
||||
|
||||
this.RegisterLuaHelpers();
|
||||
|
||||
@ -123,6 +128,12 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
|
||||
this.SubmitText = assistantSubmitText;
|
||||
this.AllowProfiles = assistantAllowProfiles;
|
||||
|
||||
if (!this.TryReadLaunchConfiguration(assistantTable, out var launchConfigIssue))
|
||||
{
|
||||
message = launchConfigIssue;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure that the UI table exists nested in the ASSISTANT table and is a valid Lua table:
|
||||
if (!assistantTable.TryGetValue("UI", out var uiVal) || !uiVal.TryRead<LuaTable>(out var uiTable))
|
||||
{
|
||||
@ -140,6 +151,51 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryReadLaunchConfiguration(LuaTable assistantTable, out string message)
|
||||
{
|
||||
message = string.Empty;
|
||||
|
||||
if (!assistantTable.TryGetValue("LaunchBehavior", out var launchBehaviorValue))
|
||||
return true;
|
||||
|
||||
if (!launchBehaviorValue.TryRead<string>(out var launchBehaviorText) ||
|
||||
!Enum.TryParse<AssistantPluginLaunchBehavior>(launchBehaviorText, true, out var launchBehavior))
|
||||
{
|
||||
message = TB("The ASSISTANT table contains an invalid LaunchBehavior value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.LaunchBehavior = launchBehavior;
|
||||
if (launchBehavior is AssistantPluginLaunchBehavior.NONE)
|
||||
return true;
|
||||
|
||||
switch (launchBehavior)
|
||||
{
|
||||
case AssistantPluginLaunchBehavior.OPEN_WORKSPACE_CHAT_BY_NAME:
|
||||
if (!assistantTable.TryGetValue("WorkspaceName", out var workspaceNameValue) ||
|
||||
!workspaceNameValue.TryRead<string>(out var workspaceName))
|
||||
{
|
||||
message = TB("The ASSISTANT table contains the LaunchBehavior 'OPEN_WORKSPACE_CHAT_BY_NAME' but no valid WorkspaceName.");
|
||||
return false;
|
||||
}
|
||||
|
||||
workspaceName = workspaceName.Trim();
|
||||
if (string.IsNullOrWhiteSpace(workspaceName))
|
||||
{
|
||||
message = TB("The ASSISTANT table contains an empty WorkspaceName for LaunchBehavior 'OPEN_WORKSPACE_CHAT_BY_NAME'.");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.LaunchWorkspaceName = workspaceName;
|
||||
|
||||
return true;
|
||||
|
||||
default:
|
||||
message = TB("The ASSISTANT table contains an unsupported LaunchBehavior value.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string?> TryBuildPromptAsync(LuaTable input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this.buildPromptFunction is null)
|
||||
@ -224,33 +280,7 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
|
||||
/// sequence of relative path length, relative path, content length, and content
|
||||
/// for each file in ordinal path order.
|
||||
/// </summary>
|
||||
public string ComputeAuditHash()
|
||||
{
|
||||
var luaFiles = this.ReadAllLuaFiles();
|
||||
|
||||
if (luaFiles.Count == 0)
|
||||
return string.Empty;
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
|
||||
|
||||
foreach (var (relativePath, content) in luaFiles.OrderBy(pair => pair.Key, StringComparer.Ordinal))
|
||||
{
|
||||
var normalizedPath = relativePath.Replace('\\', '/');
|
||||
var pathBytes = Encoding.UTF8.GetBytes(normalizedPath);
|
||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||
|
||||
writer.Write(pathBytes.Length);
|
||||
writer.Write(pathBytes);
|
||||
writer.Write(contentBytes.Length);
|
||||
writer.Write(contentBytes);
|
||||
}
|
||||
|
||||
writer.Flush();
|
||||
|
||||
var bytes = SHA256.HashData(stream.ToArray());
|
||||
return Convert.ToHexString(bytes);
|
||||
}
|
||||
public string ComputeAuditHash() => AssistantPluginHash.Compute(this.PluginPath);
|
||||
|
||||
private static string BuildSecureSystemPrompt(string pluginSystemPrompt)
|
||||
{
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.Services;
|
||||
@ -221,6 +222,9 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.AssistantPluginAudit, x => x.MinimumLevel, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.AssistantPluginAudit, x => x.BlockActivationBelowMinimum, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.AssistantPluginAudit, x => x.AutomaticallyAuditAssistants, this.Id, settingsTable, dryRun);
|
||||
|
||||
// Config: enterprise-managed approvals for assistant plugins
|
||||
this.TryProcessEnterpriseApprovedAssistantPlugins(settingsTable, dryRun);
|
||||
|
||||
// Handle configured LLM providers:
|
||||
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, x => x.NextProviderNum, mainTable, this.Id, ref this.configObjects, dryRun);
|
||||
@ -273,6 +277,120 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TryProcessEnterpriseApprovedAssistantPlugins(LuaTable settingsTable, bool dryRun)
|
||||
{
|
||||
if (!ManagedConfiguration.TryGet(x => x.AssistantPluginAudit, x => x.EnterpriseApprovedPlugins, out ConfigMeta<DataAssistantPluginAudit, IList<DataAssistantPluginEnterpriseApproval>> configMeta))
|
||||
return;
|
||||
|
||||
var settingName = SettingsManager.ToSettingName<DataAssistantPluginAudit, IList<DataAssistantPluginEnterpriseApproval>>(x => x.EnterpriseApprovedPlugins);
|
||||
var successful = false;
|
||||
IList<DataAssistantPluginEnterpriseApproval> configuredApprovals = [];
|
||||
|
||||
if (settingsTable.TryGetValue(settingName, out var configuredLuaValue)
|
||||
&& configuredLuaValue.Type is LuaValueType.Table
|
||||
&& configuredLuaValue.TryRead<LuaTable>(out var approvalsTable))
|
||||
{
|
||||
var approvals = new List<DataAssistantPluginEnterpriseApproval>(approvalsTable.ArrayLength);
|
||||
for (var index = 1; index <= approvalsTable.ArrayLength; index++)
|
||||
{
|
||||
var entryValue = approvalsTable[index];
|
||||
if (entryValue.TryRead<string>(out var hashText))
|
||||
{
|
||||
var normalizedHash = NormalizeApprovalHash(hashText);
|
||||
if (!string.IsNullOrWhiteSpace(normalizedHash))
|
||||
approvals.Add(new() { PluginHash = normalizedHash });
|
||||
else
|
||||
LOG.LogWarning("The enterprise assistant approval entry at index {Index} contains an empty hash (config plugin id: {ConfigPluginId}).", index, this.Id);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entryValue.TryRead<LuaTable>(out var entryTable))
|
||||
{
|
||||
LOG.LogWarning("The enterprise assistant approval entry at index {Index} is neither a string nor a table (config plugin id: {ConfigPluginId}).", index, this.Id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryParseEnterpriseApprovedAssistantPlugin(index, entryTable, this.Id, out var approval))
|
||||
continue;
|
||||
|
||||
approvals.Add(approval);
|
||||
}
|
||||
|
||||
configuredApprovals = approvals;
|
||||
successful = true;
|
||||
}
|
||||
|
||||
if (dryRun)
|
||||
return;
|
||||
|
||||
switch (successful)
|
||||
{
|
||||
case true:
|
||||
configMeta.SetValue(configuredApprovals);
|
||||
configMeta.LockConfiguration(this.Id);
|
||||
break;
|
||||
|
||||
case false when configMeta.IsLocked && configMeta.LockedByConfigPluginId == this.Id:
|
||||
configMeta.ResetLockedConfiguration();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryParseEnterpriseApprovedAssistantPlugin(int index, LuaTable table, Guid configPluginId, out DataAssistantPluginEnterpriseApproval approval)
|
||||
{
|
||||
approval = new();
|
||||
|
||||
if (!table.TryGetValue("PluginHash", out var pluginHashValue) || !pluginHashValue.TryRead<string>(out var pluginHash))
|
||||
{
|
||||
LOG.LogWarning("The enterprise assistant approval entry at index {Index} is missing a valid PluginHash (config plugin id: {ConfigPluginId}).", index, configPluginId);
|
||||
return false;
|
||||
}
|
||||
|
||||
var normalizedHash = NormalizeApprovalHash(pluginHash);
|
||||
if (string.IsNullOrWhiteSpace(normalizedHash))
|
||||
{
|
||||
LOG.LogWarning("The enterprise assistant approval entry at index {Index} contains an empty PluginHash (config plugin id: {ConfigPluginId}).", index, configPluginId);
|
||||
return false;
|
||||
}
|
||||
|
||||
var displayName = TryReadOptionalString(table, "DisplayName");
|
||||
var comment = TryReadOptionalString(table, "Comment");
|
||||
var approvedBy = TryReadOptionalString(table, "ApprovedBy");
|
||||
var approvedAtUtc = TryReadOptionalDateTimeOffset(table, "ApprovedAtUtc", index, configPluginId);
|
||||
|
||||
approval = new()
|
||||
{
|
||||
PluginHash = normalizedHash,
|
||||
DisplayName = displayName,
|
||||
Comment = comment,
|
||||
ApprovedBy = approvedBy,
|
||||
ApprovedAtUtc = approvedAtUtc,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string TryReadOptionalString(LuaTable table, string key)
|
||||
{
|
||||
return table.TryGetValue(key, out var value) && value.TryRead<string>(out var text)
|
||||
? text
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
private static DateTimeOffset? TryReadOptionalDateTimeOffset(LuaTable table, string key, int index, Guid configPluginId)
|
||||
{
|
||||
if (!table.TryGetValue(key, out var value))
|
||||
return null;
|
||||
|
||||
if (value.TryRead<string>(out var text) && DateTimeOffset.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsed))
|
||||
return parsed.ToUniversalTime();
|
||||
|
||||
LOG.LogWarning("The enterprise assistant approval entry at index {Index} contains an invalid {Key} value (config plugin id: {ConfigPluginId}).", index, key, configPluginId);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string NormalizeApprovalHash(string hash) => string.IsNullOrWhiteSpace(hash) ? string.Empty : hash.Trim().ToUpperInvariant();
|
||||
|
||||
private void TryReadMandatoryInfos(LuaTable mainTable)
|
||||
{
|
||||
if (!mainTable.TryGetValue("MANDATORY_INFOS", out var mandatoryInfosValue) || !mandatoryInfosValue.TryRead<LuaTable>(out var mandatoryInfosTable))
|
||||
|
||||
@ -354,6 +354,10 @@ public static partial class PluginFactory
|
||||
// Check if security audits are invoked automatically and transparent for the user
|
||||
if(ManagedConfiguration.IsConfigurationLeftOver(x => x.AssistantPluginAudit, x => x.AutomaticallyAuditAssistants, AVAILABLE_PLUGINS))
|
||||
wasConfigurationChanged = true;
|
||||
|
||||
// Check enterprise-managed assistant plugin approvals
|
||||
if(ManagedConfiguration.IsConfigurationLeftOver(x => x.AssistantPluginAudit, x => x.EnterpriseApprovedPlugins, AVAILABLE_PLUGINS))
|
||||
wasConfigurationChanged = true;
|
||||
|
||||
if (wasConfigurationChanged)
|
||||
{
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
using System.Text;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.PluginSystem.Assistants;
|
||||
|
||||
namespace AIStudio.Tools.PluginSystem;
|
||||
|
||||
@ -78,11 +81,36 @@ public static partial class PluginFactory
|
||||
LOG.LogError(e, $"An error occurred while starting the plugin: Id='{availablePlugin.Id}', Type='{availablePlugin.Type}', Name='{availablePlugin.Name}', Version='{availablePlugin.Version}'.");
|
||||
}
|
||||
}
|
||||
|
||||
LogAssistantPluginStartupState();
|
||||
|
||||
// Inform all components that the plugins have been reloaded or started:
|
||||
await MessageBus.INSTANCE.SendMessage<bool>(null, Event.PLUGINS_RELOADED);
|
||||
return configObjects;
|
||||
}
|
||||
|
||||
private static void LogAssistantPluginStartupState()
|
||||
{
|
||||
ManagedConfiguration.TryGet(x => x.AssistantPluginAudit, x => x.EnterpriseApprovedPlugins, out ConfigMeta<DataAssistantPluginAudit, IList<DataAssistantPluginEnterpriseApproval>> configMeta);
|
||||
var approvedByConfigPluginId = configMeta is { IsLocked: true } ? configMeta.LockedByConfigPluginId : Guid.Empty;
|
||||
var approvedByConfigPluginName = approvedByConfigPluginId == Guid.Empty
|
||||
? string.Empty
|
||||
: AVAILABLE_PLUGINS.FirstOrDefault(x => x.Id == approvedByConfigPluginId)?.Name ?? string.Empty;
|
||||
|
||||
foreach (var assistantPlugin in RUNNING_PLUGINS.OfType<PluginAssistants>())
|
||||
{
|
||||
var securityState = PluginAssistantSecurityResolver.Resolve(SettingsManagerAccess, assistantPlugin);
|
||||
if (securityState.IsEnterpriseApproved)
|
||||
{
|
||||
LOG.LogInformation(
|
||||
$"Successfully started assistant plugin: Id='{assistantPlugin.Id}', Type='{assistantPlugin.Type}', Name='{assistantPlugin.Name}', Version='{assistantPlugin.Version}', SecuritySource='EnterpriseApproval', ApprovedByConfigPluginId='{approvedByConfigPluginId}', ApprovedByConfigPluginName='{approvedByConfigPluginName}'");
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG.LogInformation(
|
||||
$"Successfully started assistant plugin: Id='{assistantPlugin.Id}', Type='{assistantPlugin.Type}', Name='{assistantPlugin.Name}', Version='{assistantPlugin.Version}'");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<PluginBase> Start(IAvailablePlugin meta, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
@ -206,4 +206,4 @@ public sealed class DataSourceService
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -617,6 +617,46 @@ public static class WorkspaceBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Guid> ResolveOrCreateWorkspaceIdByNameAsync(string workspaceName)
|
||||
{
|
||||
var normalizedWorkspaceName = NormalizeWorkspaceName(workspaceName);
|
||||
if (string.IsNullOrWhiteSpace(normalizedWorkspaceName))
|
||||
return Guid.Empty;
|
||||
|
||||
await WORKSPACE_TREE_CACHE_SEMAPHORE.WaitAsync();
|
||||
try
|
||||
{
|
||||
await EnsureTreeShellLoadedCoreAsync();
|
||||
|
||||
var existingWorkspace = WORKSPACE_TREE_CACHE.Workspaces.Values.FirstOrDefault(workspace =>
|
||||
string.Equals(workspace.WorkspaceName.Trim(), normalizedWorkspaceName, StringComparison.OrdinalIgnoreCase));
|
||||
if (existingWorkspace is not null)
|
||||
return existingWorkspace.WorkspaceId;
|
||||
}
|
||||
finally
|
||||
{
|
||||
WORKSPACE_TREE_CACHE_SEMAPHORE.Release();
|
||||
}
|
||||
|
||||
var result = await TryCreateWorkspaceAsync(normalizedWorkspaceName);
|
||||
if (result.Success)
|
||||
return result.Workspace.WorkspaceId;
|
||||
|
||||
await WORKSPACE_TREE_CACHE_SEMAPHORE.WaitAsync();
|
||||
try
|
||||
{
|
||||
await EnsureTreeShellLoadedCoreAsync();
|
||||
|
||||
var existingWorkspace = WORKSPACE_TREE_CACHE.Workspaces.Values.FirstOrDefault(workspace =>
|
||||
string.Equals(workspace.WorkspaceName.Trim(), normalizedWorkspaceName, StringComparison.OrdinalIgnoreCase));
|
||||
return existingWorkspace?.WorkspaceId ?? Guid.Empty;
|
||||
}
|
||||
finally
|
||||
{
|
||||
WORKSPACE_TREE_CACHE_SEMAPHORE.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<TryCreateWorkspaceResult> TryCreateWorkspaceAsync(string workspaceName)
|
||||
{
|
||||
var normalizedWorkspaceName = NormalizeWorkspaceName(workspaceName);
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
# v26.6.3, build 243 (2026-06-xx xx:xx UTC)
|
||||
- Added expert capability overrides for providers, so advanced users and configuration plugins can manually adjust selected model capabilities, including reasoning (thinking) behavior, when automatic detection needs adjustment.
|
||||
- Added configuration plugin options for default chat data source behavior and the related data source selection and validation agents.
|
||||
- Added support for organization-approved assistant plugins, so trusted assistant plugins can be enabled without requiring each user to run a separate security audit.
|
||||
- Added support for assistant plugin tiles that can open a chat directly in a chosen workspace.
|
||||
- Improved the provider selection by showing small capability icons for supported audio, image, speech, and reasoning features of the selected model.
|
||||
- Improved source links in chat answers when file or document names contained spaces, umlauts, or other special characters. Source entries now open much more reliably for documents with names such as PDFs from shared portals or internal knowledge bases.
|
||||
- Improved all assistants, so running tasks can continue when you leave the assistant and return later.
|
||||
|
||||
40
app/SharedTools/AssistantPluginHash.cs
Normal file
40
app/SharedTools/AssistantPluginHash.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace SharedTools;
|
||||
|
||||
/// <summary>
|
||||
/// Computes the canonical assistant-plugin hash across all Lua files in a plugin directory.
|
||||
/// </summary>
|
||||
public static class AssistantPluginHash
|
||||
{
|
||||
public static string Compute(string pluginDirectory)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pluginDirectory) || !Directory.Exists(pluginDirectory))
|
||||
return string.Empty;
|
||||
|
||||
var luaFiles = Directory.EnumerateFiles(pluginDirectory, "*.lua", SearchOption.AllDirectories)
|
||||
.OrderBy(path => path, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
if (luaFiles.Count == 0)
|
||||
return string.Empty;
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
|
||||
|
||||
foreach (var filePath in luaFiles)
|
||||
{
|
||||
var relativePath = Path.GetRelativePath(pluginDirectory, filePath).Replace('\\', '/');
|
||||
var pathBytes = Encoding.UTF8.GetBytes(relativePath);
|
||||
var contentBytes = File.ReadAllBytes(filePath);
|
||||
|
||||
writer.Write(pathBytes.Length);
|
||||
writer.Write(pathBytes);
|
||||
writer.Write(contentBytes.Length);
|
||||
writer.Write(contentBytes);
|
||||
}
|
||||
|
||||
writer.Flush();
|
||||
return Convert.ToHexString(SHA256.HashData(stream.ToArray()));
|
||||
}
|
||||
}
|
||||
@ -287,11 +287,53 @@ Currently, you can configure the following things:
|
||||
- Any number of LLM providers (self-hosted or cloud providers with encrypted API keys)
|
||||
- Any number of transcription providers for voice-to-text functionality
|
||||
- Any number of embedding providers for RAG
|
||||
- Enterprise hash approvals for assistant plugins
|
||||
- The update behavior of AI Studio
|
||||
- Various UI and feature settings (see the example configuration for details)
|
||||
|
||||
All other settings can be made by the user themselves. If you need additional settings, feel free to create an issue in our planning repository: https://github.com/MindWorkAI/Planning/issues
|
||||
|
||||
## Enterprise approval for assistant plugins
|
||||
|
||||
Enterprise configurations can approve assistant plugins by hash so that users do not need to run a local assistant audit before activation. The approval is based only on the current plugin content, not on the plugin GUID.
|
||||
|
||||
AI Studio computes the approval hash as a SHA-256 digest over all `.lua` files in the assistant plugin directory:
|
||||
|
||||
- recursively
|
||||
- sorted by relative path in ordinal order
|
||||
- using canonical `/` path separators
|
||||
- hashing relative-path length, relative path, content length, and file content for each Lua file
|
||||
|
||||
If any Lua file changes, the hash changes automatically and the enterprise approval no longer applies.
|
||||
|
||||
### Configuration example
|
||||
|
||||
Add the approval list to `CONFIG["SETTINGS"]` in your configuration plugin:
|
||||
|
||||
```lua
|
||||
CONFIG["SETTINGS"]["DataAssistantPluginAudit.EnterpriseApprovedPlugins"] = {
|
||||
{
|
||||
["PluginHash"] = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF",
|
||||
["DisplayName"] = "Corporate Translation Assistant",
|
||||
["Comment"] = "Approved for internal rollout",
|
||||
["ApprovedBy"] = "AI Governance Board",
|
||||
["ApprovedAtUtc"] = "2026-07-02T09:30:00Z",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`PluginHash` is required. All other fields are optional and are shown in the UI as approval metadata.
|
||||
|
||||
### Generating the hash
|
||||
|
||||
Use the build-script command from the repository root:
|
||||
|
||||
```bash
|
||||
dotnet run --project app/Build -- assistant-plugin-hash "<plugin-dir>" --lua-snippet
|
||||
```
|
||||
|
||||
This prints the canonical hash and, with `--lua-snippet`, also prints a ready-to-paste Lua snippet for `CONFIG["SETTINGS"]`.
|
||||
|
||||
## Encrypted API Keys
|
||||
|
||||
You can include encrypted API keys in your configuration plugins for cloud providers (like OpenAI, Anthropic) or secured on-premise models. This feature provides obfuscation to prevent casual exposure of API keys in configuration files.
|
||||
|
||||
@ -23,4 +23,4 @@ This lets older settings files load without a settings version migration and kee
|
||||
- Confirm supported settings files are expected to contain `PreselectedDataSourcesDisabled`, `PreselectedDataSourcesAutomaticSelection`, `PreselectedDataSourcesAutomaticValidation`, and `PreselectedDataSourceIds`.
|
||||
- Remove `DataChat.PreselectedDataSourceOptions`.
|
||||
- Update any remaining callers to use the individual fields or a dedicated helper.
|
||||
- Update this document's status to `Removed`.
|
||||
- Update this document's status to `Removed`.
|
||||
Loading…
Reference in New Issue
Block a user