From eb1ef1ad37d2e4441a4326f457843aa0084a4910 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 14 Sep 2026 09:15:44 +0200 Subject: [PATCH] Let assistant plugins launch a chat without a workspace --- .../AssistantChatLaunchConfiguration.cs | 10 ++++- .../AssistantPluginLaunchBehavior.cs | 1 + .../Assistants/DirectChatLauncherLuaWriter.cs | 14 +++++- .../Assistants/PluginAssistants.cs | 43 ++++++++++++++----- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/AssistantChatLaunchConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/AssistantChatLaunchConfiguration.cs index fb77341f..02a3e19b 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/AssistantChatLaunchConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/AssistantChatLaunchConfiguration.cs @@ -1,4 +1,12 @@ namespace AIStudio.Tools.PluginSystem.Assistants; +/// The workspace the chat is created in. An empty name means the launcher opens a chat without a workspace, which the app shows as a disappearing chat. /// The tools preselected for the chat, or null when the launcher names none. -public sealed record AssistantChatLaunchConfiguration(string WorkspaceName, Guid? ProviderId, Guid? ProfileId, Guid? ChatTemplateId, IReadOnlyList? DataSourceIds, IReadOnlyList? ToolIds); +public sealed record AssistantChatLaunchConfiguration(string WorkspaceName, Guid? ProviderId, Guid? ProfileId, Guid? ChatTemplateId, IReadOnlyList? DataSourceIds, IReadOnlyList? ToolIds) +{ + /// + /// Whether the launcher opens a chat that belongs to no workspace. The missing workspace name is + /// the whole condition, so the launch behavior and the written plugin follow from it. + /// + public bool OpensTemporaryChat => string.IsNullOrWhiteSpace(this.WorkspaceName); +} diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/AssistantPluginLaunchBehavior.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/AssistantPluginLaunchBehavior.cs index 2d96f224..30b36142 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/AssistantPluginLaunchBehavior.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/AssistantPluginLaunchBehavior.cs @@ -4,4 +4,5 @@ public enum AssistantPluginLaunchBehavior { NONE, OPEN_WORKSPACE_CHAT_BY_NAME, + OPEN_TEMPORARY_CHAT, } diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DirectChatLauncherLuaWriter.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DirectChatLauncherLuaWriter.cs index b41d7c66..976318dc 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DirectChatLauncherLuaWriter.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DirectChatLauncherLuaWriter.cs @@ -165,8 +165,18 @@ public static class DirectChatLauncherLuaWriter builder.AppendLine("ASSISTANT = {"); builder.AppendLine($" [\"Title\"] = \"{Escape(definition.Title)}\","); builder.AppendLine($" [\"Description\"] = \"{Escape(definition.Description)}\","); - builder.AppendLine($" [\"LaunchBehavior\"] = \"{nameof(AssistantPluginLaunchBehavior.OPEN_WORKSPACE_CHAT_BY_NAME)}\","); - builder.AppendLine($" [\"WorkspaceName\"] = \"{Escape(definition.Launch.WorkspaceName.Trim())}\","); + // + // The behavior follows from the workspace rather than being tracked next to it. A launcher + // without one has no name to write, and the plugin loader rejects a WorkspaceName there, so + // the field is left out the same way the optional IDs below are: + // + if (definition.Launch.OpensTemporaryChat) + builder.AppendLine($" [\"LaunchBehavior\"] = \"{nameof(AssistantPluginLaunchBehavior.OPEN_TEMPORARY_CHAT)}\","); + else + { + builder.AppendLine($" [\"LaunchBehavior\"] = \"{nameof(AssistantPluginLaunchBehavior.OPEN_WORKSPACE_CHAT_BY_NAME)}\","); + builder.AppendLine($" [\"WorkspaceName\"] = \"{Escape(definition.Launch.WorkspaceName.Trim())}\","); + } // // Omitted IDs mean "use the chat defaults", while an empty GUID explicitly selects no diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistants.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistants.cs index acbdbba0..1256e655 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistants.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistants.cs @@ -223,38 +223,61 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType if (launchBehavior is AssistantPluginLaunchBehavior.NONE) return true; + // + // Both launch behaviors describe the same chat and differ only in where it is kept, so only + // the workspace is read per behavior. Everything else follows below, for both of them: + // + var workspaceName = string.Empty; switch (launchBehavior) { case AssistantPluginLaunchBehavior.OPEN_WORKSPACE_CHAT_BY_NAME: if (!assistantTable.TryGetValue("WorkspaceName", out var workspaceNameValue) || - !workspaceNameValue.TryRead(out var workspaceName)) + !workspaceNameValue.TryRead(out var configuredWorkspaceName)) { message = TB("The ASSISTANT table contains the LaunchBehavior 'OPEN_WORKSPACE_CHAT_BY_NAME' but no valid WorkspaceName."); return false; } - workspaceName = workspaceName.Trim(); + workspaceName = configuredWorkspaceName.Trim(); if (string.IsNullOrWhiteSpace(workspaceName)) { message = TB("The ASSISTANT table contains an empty WorkspaceName for LaunchBehavior 'OPEN_WORKSPACE_CHAT_BY_NAME'."); return false; } - if (!TryReadOptionalGuid(assistantTable, "ProviderId", false, out var providerId, out message) || - !TryReadOptionalGuid(assistantTable, "ProfileId", true, out var profileId, out message) || - !TryReadOptionalGuid(assistantTable, "ChatTemplateId", true, out var chatTemplateId, out message) || - !TryReadOptionalDataSourceIds(assistantTable, out var dataSourceIds, out message) || - !TryReadOptionalToolIds(assistantTable, out var toolIds, out message)) + break; + + // + // A chat without a workspace has no name to carry, so one written here can only be a + // mistake. We reject it rather than dropping it silently: a misspelled LaunchBehavior + // would otherwise turn a workspace launcher into a disappearing one, and the author + // would only notice it by the chats going missing. + // + case AssistantPluginLaunchBehavior.OPEN_TEMPORARY_CHAT: + if (assistantTable.TryGetValue("WorkspaceName", out var unexpectedWorkspaceNameValue) && + unexpectedWorkspaceNameValue.TryRead(out var unexpectedWorkspaceName) && + !string.IsNullOrWhiteSpace(unexpectedWorkspaceName)) + { + message = TB("The ASSISTANT table contains a WorkspaceName for LaunchBehavior 'OPEN_TEMPORARY_CHAT'. A chat without a workspace cannot have one."); return false; + } - this.ChatLaunchConfiguration = new(workspaceName, providerId, profileId, chatTemplateId, dataSourceIds, toolIds); - - return true; + break; default: message = TB("The ASSISTANT table contains an unsupported LaunchBehavior value."); return false; } + + if (!TryReadOptionalGuid(assistantTable, "ProviderId", false, out var providerId, out message) || + !TryReadOptionalGuid(assistantTable, "ProfileId", true, out var profileId, out message) || + !TryReadOptionalGuid(assistantTable, "ChatTemplateId", true, out var chatTemplateId, out message) || + !TryReadOptionalDataSourceIds(assistantTable, out var dataSourceIds, out message) || + !TryReadOptionalToolIds(assistantTable, out var toolIds, out message)) + return false; + + this.ChatLaunchConfiguration = new(workspaceName, providerId, profileId, chatTemplateId, dataSourceIds, toolIds); + return true; } private static bool TryReadOptionalGuid(LuaTable assistantTable, string fieldName, bool allowEmpty, out Guid? id, out string message)