From beab3e0ffdefd57be0542abc7cdf4c610a3657cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 00:13:36 +0000 Subject: [PATCH 1/5] Initial plan From 9781af41ee4f7733caf803d5978919e674a9c1c7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 00:18:08 +0000 Subject: [PATCH 2/5] Add asterisk action transformation to OllamaClient Co-authored-by: ExtCan <60326708+ExtCan@users.noreply.github.com> --- src/AI/OllamaClient.cs | 73 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/src/AI/OllamaClient.cs b/src/AI/OllamaClient.cs index 787724b..0e7e241 100644 --- a/src/AI/OllamaClient.cs +++ b/src/AI/OllamaClient.cs @@ -194,6 +194,65 @@ public static (string text, List animations) ExtractAnimations(string te return (text, animations); } + /// + /// Transforms asterisk-wrapped actions in user input into instructions for the AI. + /// For example: "*you decide to tell a story*" becomes "You decide to tell a story." + /// This allows users to prompt the AI to perform specific actions. + /// + public static string TransformAsteriskActions(string message) + { + if (string.IsNullOrEmpty(message)) + return message; + + // Match text wrapped in asterisks: *action text* + // This regex looks for asterisk pairs with content between them + var matches = Regex.Matches(message, @"\*([^*]+)\*"); + + if (matches.Count == 0) + return message; + + var transformedMessage = new StringBuilder(); + int lastIndex = 0; + + foreach (Match match in matches) + { + // Add any text before this match + if (match.Index > lastIndex) + { + transformedMessage.Append(message.Substring(lastIndex, match.Index - lastIndex)); + } + + // Extract the action text (without asterisks) + string actionText = match.Groups[1].Value.Trim(); + + // Transform the action into an instruction + // Convert to sentence case if needed + if (actionText.Length > 0) + { + // Capitalize first letter + actionText = char.ToUpper(actionText[0]) + actionText.Substring(1); + + // Add period if not already ending with punctuation + if (!actionText.EndsWith(".") && !actionText.EndsWith("!") && !actionText.EndsWith("?")) + { + actionText += "."; + } + + transformedMessage.Append(actionText); + } + + lastIndex = match.Index + match.Length; + } + + // Add any remaining text after the last match + if (lastIndex < message.Length) + { + transformedMessage.Append(message.Substring(lastIndex)); + } + + return transformedMessage.ToString().Trim(); + } + /// /// Sends a chat message to Ollama and gets a response /// @@ -201,6 +260,9 @@ public async Task ChatAsync(string message, CancellationToken cancellati { try { + // Transform asterisk-wrapped actions into instructions + string transformedMessage = TransformAsteriskActions(message); + // Build the messages list with personality and history var messages = new List(); @@ -222,8 +284,8 @@ public async Task ChatAsync(string message, CancellationToken cancellati }); } - // Add the new user message - messages.Add(new { role = "user", content = message }); + // Add the new user message (transformed) + messages.Add(new { role = "user", content = transformedMessage }); var request = new { @@ -251,8 +313,8 @@ public async Task ChatAsync(string message, CancellationToken cancellati { string cleanedResponse = CleanResponse(result.Message.Content); - // Add to conversation history - _conversationHistory.Add(new ChatMessage { Role = "user", Content = message }); + // Add to conversation history (use transformed message) + _conversationHistory.Add(new ChatMessage { Role = "user", Content = transformedMessage }); _conversationHistory.Add(new ChatMessage { Role = "assistant", Content = cleanedResponse }); // Try to create a memory from this conversation @@ -286,6 +348,9 @@ public async Task ChatAsync(string message, CancellationToken cancellati public async Task GenerateRandomDialogAsync(string customPrompt = null, CancellationToken cancellationToken = default) { string prompt = customPrompt ?? "Say something short, interesting, and in-character. Use /emp/ for emphasis and optionally include an &&animation trigger."; + + // Transform asterisk-wrapped actions if present in custom prompt + prompt = TransformAsteriskActions(prompt); try { From e44135545fa7b6d8013d753061d378ff9d22821c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 00:19:01 +0000 Subject: [PATCH 3/5] Update documentation for asterisk action prompts feature Co-authored-by: ExtCan <60326708+ExtCan@users.noreply.github.com> --- PIPELINE.md | 11 +++++++++++ README.md | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/PIPELINE.md b/PIPELINE.md index f0eb467..3b220d4 100644 --- a/PIPELINE.md +++ b/PIPELINE.md @@ -97,6 +97,17 @@ Commands are sent as plain text lines. Each command receives a response. | `PING` | Check if the server is running | `PING` | | `VERSION` | Get the MSAgent-AI version | `VERSION` | +### Asterisk Action Prompts + +When using the `CHAT` command, you can wrap text in asterisks to indicate actions the character should perform. The asterisks will be removed and the text will be transformed into an instruction for the AI. + +**Examples:** +- `CHAT:*you decide to tell a story*` → AI receives: "You decide to tell a story." +- `CHAT:Hello *you wave at the user* how are you?` → AI receives: "Hello You wave at the user. how are you?" +- `CHAT:*you start dancing*` → AI receives: "You start dancing." + +This feature works in both the chat interface and the Pipeline's CHAT command. + ### Response Format - `OK:COMMAND` - Command was executed successfully - `ERROR:message` - Command failed with error message diff --git a/README.md b/README.md index 5e3b17a..8a24429 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,13 @@ dotnet build 6. Use **Manage Memories** to view and manage what the AI remembers 7. Use Speak menu to make the agent tell jokes, share thoughts, or say custom text +### Asterisk Action Prompts + +In the chat interface, you can use asterisks to instruct the AI to perform actions: +- Type `*you decide to tell a story*` to make the character decide to tell a story +- Type `Hello *you wave* how are you?` to make the character wave while greeting +- Asterisks are automatically transformed into action instructions for the AI + ## Project Structure ``` From d750e626f650d1377d870d77bf6c4ada2dee83f2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 00:22:07 +0000 Subject: [PATCH 4/5] Fix code review issues: handle edge cases and optimize performance Co-authored-by: ExtCan <60326708+ExtCan@users.noreply.github.com> --- src/AI/OllamaClient.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/AI/OllamaClient.cs b/src/AI/OllamaClient.cs index 0e7e241..1bb8114 100644 --- a/src/AI/OllamaClient.cs +++ b/src/AI/OllamaClient.cs @@ -219,18 +219,24 @@ public static string TransformAsteriskActions(string message) // Add any text before this match if (match.Index > lastIndex) { - transformedMessage.Append(message.Substring(lastIndex, match.Index - lastIndex)); + transformedMessage.Append(message, lastIndex, match.Index - lastIndex); } // Extract the action text (without asterisks) string actionText = match.Groups[1].Value.Trim(); - // Transform the action into an instruction - // Convert to sentence case if needed + // Only process non-empty action text if (actionText.Length > 0) { - // Capitalize first letter - actionText = char.ToUpper(actionText[0]) + actionText.Substring(1); + // Capitalize first letter (handle single character case safely) + if (actionText.Length == 1) + { + actionText = char.ToUpper(actionText[0]).ToString(); + } + else + { + actionText = char.ToUpper(actionText[0]) + actionText.Substring(1); + } // Add period if not already ending with punctuation if (!actionText.EndsWith(".") && !actionText.EndsWith("!") && !actionText.EndsWith("?")) @@ -247,7 +253,7 @@ public static string TransformAsteriskActions(string message) // Add any remaining text after the last match if (lastIndex < message.Length) { - transformedMessage.Append(message.Substring(lastIndex)); + transformedMessage.Append(message, lastIndex, message.Length - lastIndex); } return transformedMessage.ToString().Trim(); From 0c7e58433f9a094dda1812cbcb9632959549d4a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 00:25:03 +0000 Subject: [PATCH 5/5] Optimize asterisk action transformation with compiled regex and simplified logic Co-authored-by: ExtCan <60326708+ExtCan@users.noreply.github.com> --- src/AI/OllamaClient.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/AI/OllamaClient.cs b/src/AI/OllamaClient.cs index 1bb8114..23e243f 100644 --- a/src/AI/OllamaClient.cs +++ b/src/AI/OllamaClient.cs @@ -17,6 +17,9 @@ public class OllamaClient : IDisposable { private readonly HttpClient _httpClient; private bool _disposed; + + // Compiled regex for asterisk action detection (performance optimization) + private static readonly Regex AsteriskActionRegex = new Regex(@"\*([^*]+)\*", RegexOptions.Compiled); public string BaseUrl { get; set; } = "http://localhost:11434"; public string Model { get; set; } = "llama2"; @@ -205,8 +208,8 @@ public static string TransformAsteriskActions(string message) return message; // Match text wrapped in asterisks: *action text* - // This regex looks for asterisk pairs with content between them - var matches = Regex.Matches(message, @"\*([^*]+)\*"); + // Use compiled regex for better performance + var matches = AsteriskActionRegex.Matches(message); if (matches.Count == 0) return message; @@ -228,15 +231,8 @@ public static string TransformAsteriskActions(string message) // Only process non-empty action text if (actionText.Length > 0) { - // Capitalize first letter (handle single character case safely) - if (actionText.Length == 1) - { - actionText = char.ToUpper(actionText[0]).ToString(); - } - else - { - actionText = char.ToUpper(actionText[0]) + actionText.Substring(1); - } + // Capitalize first letter + actionText = char.ToUpper(actionText[0]) + (actionText.Length > 1 ? actionText.Substring(1) : ""); // Add period if not already ending with punctuation if (!actionText.EndsWith(".") && !actionText.EndsWith("!") && !actionText.EndsWith("?"))