First commit!

This commit is contained in:
0% [█ █ █ █ █ █ █ █ █ █] 100% 2026-06-04 20:36:13 -05:00
parent 3c97134e2f
commit ace1e61285
36 changed files with 19565 additions and 4 deletions

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<Platforms>AnyCPU;x64</Platforms>
<ApplicationIcon>app.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Content Include="app.ico" />
</ItemGroup>
</Project>

16
AI C2 Server/Logger.cs Normal file
View File

@ -0,0 +1,16 @@
// File: Logger.cs
using System;
namespace AI_C2_Server
{
public static class Logger
{
public static event Action<string> LogReceived;
public static void Log(string message)
{
Console.WriteLine(message);
LogReceived?.Invoke(message);
}
}
}

398
AI C2 Server/MainForm.Designer.cs generated Normal file
View File

@ -0,0 +1,398 @@
namespace AI_C2_Server
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
btnRestart = new Button();
panelTop = new Panel();
notifyIcon = new NotifyIcon(components);
trayContextMenu = new ContextMenuStrip(components);
showServerToolStripMenuItem = new ToolStripMenuItem();
restartServerToolStripMenuItem = new ToolStripMenuItem();
exitShutdownToolStripMenuItem = new ToolStripMenuItem();
restartApplicationToolStripMenuItem = new ToolStripMenuItem();
tabControl1 = new TabControl();
tabPageConsole = new TabPage();
rtbConsole = new TextBox();
tabPageInstances = new TabPage();
listViewInstances = new ListView();
colPlatform = new ColumnHeader();
colInstanceId = new ColumnHeader();
colStatus = new ColumnHeader();
colLastSeen = new ColumnHeader();
instanceContextMenu = new ContextMenuStrip(components);
resetChatToolStripMenuItem = new ToolStripMenuItem();
shutdownHarnessToolStripMenuItem = new ToolStripMenuItem();
panelInstanceActions = new Panel();
btnShutdown = new Button();
btnResetChat = new Button();
tabPageOpenAI = new TabPage();
txtOpenAIKey = new TextBox();
lblKey = new Label();
txtOpenAIUrl = new TextBox();
lblUrl = new Label();
chkEnableOpenAI = new CheckBox();
timerUpdateInstances = new System.Windows.Forms.Timer(components);
panelTop.SuspendLayout();
trayContextMenu.SuspendLayout();
tabControl1.SuspendLayout();
tabPageConsole.SuspendLayout();
tabPageInstances.SuspendLayout();
instanceContextMenu.SuspendLayout();
panelInstanceActions.SuspendLayout();
tabPageOpenAI.SuspendLayout();
SuspendLayout();
//
// btnRestart
//
btnRestart.Location = new Point(12, 12);
btnRestart.Name = "btnRestart";
btnRestart.Size = new Size(120, 25);
btnRestart.TabIndex = 0;
btnRestart.Text = "Restart Server";
btnRestart.UseVisualStyleBackColor = true;
btnRestart.Click += btnRestart_Click;
//
// panelTop
//
panelTop.Controls.Add(btnRestart);
panelTop.Dock = DockStyle.Top;
panelTop.Location = new Point(0, 0);
panelTop.Name = "panelTop";
panelTop.Size = new Size(800, 50);
panelTop.TabIndex = 0;
//
// notifyIcon
//
notifyIcon.BalloonTipText = "Server Running...";
notifyIcon.BalloonTipTitle = "AI C2 Server";
notifyIcon.ContextMenuStrip = trayContextMenu;
notifyIcon.Icon = (Icon)resources.GetObject("notifyIcon.Icon");
notifyIcon.Text = "AI C2 Server";
notifyIcon.MouseClick += notifyIcon_Click;
notifyIcon.MouseDoubleClick += notifyIcon_MouseDoubleClick;
//
// trayContextMenu
//
trayContextMenu.Items.AddRange(new ToolStripItem[] { showServerToolStripMenuItem, restartServerToolStripMenuItem, restartApplicationToolStripMenuItem, exitShutdownToolStripMenuItem });
trayContextMenu.Name = "trayContextMenu";
trayContextMenu.RenderMode = ToolStripRenderMode.System;
trayContextMenu.Size = new Size(175, 92);
//
// showServerToolStripMenuItem
//
showServerToolStripMenuItem.Name = "showServerToolStripMenuItem";
showServerToolStripMenuItem.Size = new Size(174, 22);
showServerToolStripMenuItem.Text = "Show Server";
showServerToolStripMenuItem.Click += showServerToolStripMenuItem_Click;
//
// restartServerToolStripMenuItem
//
restartServerToolStripMenuItem.Name = "restartServerToolStripMenuItem";
restartServerToolStripMenuItem.Size = new Size(174, 22);
restartServerToolStripMenuItem.Text = "Restart Server";
restartServerToolStripMenuItem.Click += restartServerToolStripMenuItem_Click;
//
// exitShutdownToolStripMenuItem
//
exitShutdownToolStripMenuItem.Name = "exitShutdownToolStripMenuItem";
exitShutdownToolStripMenuItem.Size = new Size(174, 22);
exitShutdownToolStripMenuItem.Text = "Shutdown && Exit";
exitShutdownToolStripMenuItem.Click += exitShutdownToolStripMenuItem_Click;
//
// restartApplicationToolStripMenuItem
//
restartApplicationToolStripMenuItem.Name = "restartApplicationToolStripMenuItem";
restartApplicationToolStripMenuItem.Size = new Size(174, 22);
restartApplicationToolStripMenuItem.Text = "Restart Application";
restartApplicationToolStripMenuItem.Click += restartApplicationToolStripMenuItem_Click;
//
// tabControl1
//
tabControl1.Controls.Add(tabPageConsole);
tabControl1.Controls.Add(tabPageInstances);
tabControl1.Controls.Add(tabPageOpenAI);
tabControl1.Dock = DockStyle.Fill;
tabControl1.Location = new Point(0, 50);
tabControl1.Name = "tabControl1";
tabControl1.SelectedIndex = 0;
tabControl1.Size = new Size(800, 400);
tabControl1.TabIndex = 1;
//
// tabPageConsole
//
tabPageConsole.BackColor = SystemColors.Control;
tabPageConsole.Controls.Add(rtbConsole);
tabPageConsole.Location = new Point(4, 24);
tabPageConsole.Name = "tabPageConsole";
tabPageConsole.Padding = new Padding(3);
tabPageConsole.Size = new Size(792, 372);
tabPageConsole.TabIndex = 0;
tabPageConsole.Text = "Console";
//
// rtbConsole
//
rtbConsole.BackColor = Color.Black;
rtbConsole.BorderStyle = BorderStyle.FixedSingle;
rtbConsole.Dock = DockStyle.Fill;
rtbConsole.ForeColor = Color.LightGray;
rtbConsole.Location = new Point(3, 3);
rtbConsole.Multiline = true;
rtbConsole.Name = "rtbConsole";
rtbConsole.ReadOnly = true;
rtbConsole.ScrollBars = ScrollBars.Vertical;
rtbConsole.Size = new Size(786, 366);
rtbConsole.TabIndex = 2;
//
// tabPageInstances
//
tabPageInstances.BackColor = SystemColors.Control;
tabPageInstances.Controls.Add(listViewInstances);
tabPageInstances.Controls.Add(panelInstanceActions);
tabPageInstances.Location = new Point(4, 24);
tabPageInstances.Name = "tabPageInstances";
tabPageInstances.Padding = new Padding(3);
tabPageInstances.Size = new Size(792, 372);
tabPageInstances.TabIndex = 1;
tabPageInstances.Text = "Instance Management";
//
// listViewInstances
//
listViewInstances.BorderStyle = BorderStyle.FixedSingle;
listViewInstances.Columns.AddRange(new ColumnHeader[] { colPlatform, colInstanceId, colStatus, colLastSeen });
listViewInstances.ContextMenuStrip = instanceContextMenu;
listViewInstances.Dock = DockStyle.Fill;
listViewInstances.FullRowSelect = true;
listViewInstances.GridLines = true;
listViewInstances.Location = new Point(3, 3);
listViewInstances.MultiSelect = false;
listViewInstances.Name = "listViewInstances";
listViewInstances.Size = new Size(786, 326);
listViewInstances.TabIndex = 1;
listViewInstances.UseCompatibleStateImageBehavior = false;
listViewInstances.View = View.Details;
//
// colPlatform
//
colPlatform.Text = "Platform";
colPlatform.Width = 150;
//
// colInstanceId
//
colInstanceId.Text = "Instance ID";
colInstanceId.Width = 250;
//
// colStatus
//
colStatus.Text = "Status";
colStatus.Width = 100;
//
// colLastSeen
//
colLastSeen.Text = "Last Seen";
colLastSeen.Width = 150;
//
// instanceContextMenu
//
instanceContextMenu.Items.AddRange(new ToolStripItem[] { resetChatToolStripMenuItem, shutdownHarnessToolStripMenuItem });
instanceContextMenu.Name = "instanceContextMenu";
instanceContextMenu.Size = new Size(174, 48);
//
// resetChatToolStripMenuItem
//
resetChatToolStripMenuItem.Name = "resetChatToolStripMenuItem";
resetChatToolStripMenuItem.Size = new Size(173, 22);
resetChatToolStripMenuItem.Text = "Reset Chat";
resetChatToolStripMenuItem.Click += resetChatToolStripMenuItem_Click;
//
// shutdownHarnessToolStripMenuItem
//
shutdownHarnessToolStripMenuItem.Name = "shutdownHarnessToolStripMenuItem";
shutdownHarnessToolStripMenuItem.Size = new Size(173, 22);
shutdownHarnessToolStripMenuItem.Text = "Shutdown Harness";
shutdownHarnessToolStripMenuItem.Click += shutdownHarnessToolStripMenuItem_Click;
//
// panelInstanceActions
//
panelInstanceActions.Controls.Add(btnShutdown);
panelInstanceActions.Controls.Add(btnResetChat);
panelInstanceActions.Dock = DockStyle.Bottom;
panelInstanceActions.Location = new Point(3, 329);
panelInstanceActions.Name = "panelInstanceActions";
panelInstanceActions.Size = new Size(786, 40);
panelInstanceActions.TabIndex = 0;
//
// btnShutdown
//
btnShutdown.Location = new Point(111, 8);
btnShutdown.Name = "btnShutdown";
btnShutdown.Size = new Size(120, 25);
btnShutdown.TabIndex = 1;
btnShutdown.Text = "Shutdown Harness";
btnShutdown.UseVisualStyleBackColor = true;
btnShutdown.Click += btnShutdown_Click;
//
// btnResetChat
//
btnResetChat.Location = new Point(5, 8);
btnResetChat.Name = "btnResetChat";
btnResetChat.Size = new Size(100, 25);
btnResetChat.TabIndex = 0;
btnResetChat.Text = "Reset Chat";
btnResetChat.UseVisualStyleBackColor = true;
btnResetChat.Click += btnResetChat_Click;
//
// tabPageOpenAI
//
tabPageOpenAI.BackColor = SystemColors.Control;
tabPageOpenAI.Controls.Add(txtOpenAIKey);
tabPageOpenAI.Controls.Add(lblKey);
tabPageOpenAI.Controls.Add(txtOpenAIUrl);
tabPageOpenAI.Controls.Add(lblUrl);
tabPageOpenAI.Controls.Add(chkEnableOpenAI);
tabPageOpenAI.Location = new Point(4, 24);
tabPageOpenAI.Name = "tabPageOpenAI";
tabPageOpenAI.Padding = new Padding(3);
tabPageOpenAI.Size = new Size(792, 372);
tabPageOpenAI.TabIndex = 2;
tabPageOpenAI.Text = "OpenAI API";
//
// txtOpenAIKey
//
txtOpenAIKey.BorderStyle = BorderStyle.FixedSingle;
txtOpenAIKey.Location = new Point(100, 85);
txtOpenAIKey.Name = "txtOpenAIKey";
txtOpenAIKey.Size = new Size(400, 23);
txtOpenAIKey.TabIndex = 4;
txtOpenAIKey.TextChanged += txtOpenAIKey_TextChanged;
//
// lblKey
//
lblKey.AutoSize = true;
lblKey.Location = new Point(20, 88);
lblKey.Name = "lblKey";
lblKey.Size = new Size(50, 15);
lblKey.TabIndex = 3;
lblKey.Text = "API Key:";
//
// txtOpenAIUrl
//
txtOpenAIUrl.BorderStyle = BorderStyle.FixedSingle;
txtOpenAIUrl.Location = new Point(100, 50);
txtOpenAIUrl.Name = "txtOpenAIUrl";
txtOpenAIUrl.Size = new Size(400, 23);
txtOpenAIUrl.TabIndex = 2;
txtOpenAIUrl.TextChanged += txtOpenAIUrl_TextChanged;
//
// lblUrl
//
lblUrl.AutoSize = true;
lblUrl.Location = new Point(20, 53);
lblUrl.Name = "lblUrl";
lblUrl.Size = new Size(52, 15);
lblUrl.TabIndex = 1;
lblUrl.Text = "API URL:";
//
// chkEnableOpenAI
//
chkEnableOpenAI.AutoSize = true;
chkEnableOpenAI.Location = new Point(20, 20);
chkEnableOpenAI.Name = "chkEnableOpenAI";
chkEnableOpenAI.Size = new Size(159, 19);
chkEnableOpenAI.TabIndex = 0;
chkEnableOpenAI.Text = "Enable OpenAI API Mode";
chkEnableOpenAI.UseVisualStyleBackColor = true;
chkEnableOpenAI.CheckedChanged += chkEnableOpenAI_CheckedChanged;
//
// timerUpdateInstances
//
timerUpdateInstances.Interval = 2000;
timerUpdateInstances.Tick += timerUpdateInstances_Tick;
//
// MainForm
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(800, 450);
Controls.Add(tabControl1);
Controls.Add(panelTop);
Icon = (Icon)resources.GetObject("$this.Icon");
Name = "MainForm";
Text = "AI C2 Server Console";
FormClosing += MainForm_FormClosing;
Load += MainForm_Load;
Resize += MainForm_Resize;
panelTop.ResumeLayout(false);
trayContextMenu.ResumeLayout(false);
tabControl1.ResumeLayout(false);
tabPageConsole.ResumeLayout(false);
tabPageConsole.PerformLayout();
tabPageInstances.ResumeLayout(false);
instanceContextMenu.ResumeLayout(false);
panelInstanceActions.ResumeLayout(false);
tabPageOpenAI.ResumeLayout(false);
tabPageOpenAI.PerformLayout();
ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button btnRestart;
private System.Windows.Forms.Panel panelTop;
private System.Windows.Forms.NotifyIcon notifyIcon;
private System.Windows.Forms.ContextMenuStrip trayContextMenu;
private System.Windows.Forms.ToolStripMenuItem showServerToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem restartServerToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem exitShutdownToolStripMenuItem;
private System.Windows.Forms.TabControl tabControl1;
private System.Windows.Forms.TabPage tabPageConsole;
private System.Windows.Forms.TextBox rtbConsole;
private System.Windows.Forms.TabPage tabPageInstances;
private System.Windows.Forms.TabPage tabPageOpenAI;
private System.Windows.Forms.ListView listViewInstances;
private System.Windows.Forms.ColumnHeader colPlatform;
private System.Windows.Forms.ColumnHeader colInstanceId;
private System.Windows.Forms.ColumnHeader colStatus;
private System.Windows.Forms.ColumnHeader colLastSeen;
private System.Windows.Forms.Panel panelInstanceActions;
private System.Windows.Forms.Button btnShutdown;
private System.Windows.Forms.Button btnResetChat;
private System.Windows.Forms.TextBox txtOpenAIKey;
private System.Windows.Forms.Label lblKey;
private System.Windows.Forms.TextBox txtOpenAIUrl;
private System.Windows.Forms.Label lblUrl;
private System.Windows.Forms.CheckBox chkEnableOpenAI;
private System.Windows.Forms.Timer timerUpdateInstances;
private System.Windows.Forms.ContextMenuStrip instanceContextMenu;
private System.Windows.Forms.ToolStripMenuItem resetChatToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem shutdownHarnessToolStripMenuItem;
private ToolStripMenuItem restartApplicationToolStripMenuItem;
}
}

287
AI C2 Server/MainForm.cs Normal file
View File

@ -0,0 +1,287 @@
// File: MainForm.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.Json.Nodes;
using System.Windows.Forms;
namespace AI_C2_Server
{
public partial class MainForm : Form
{
[DllImport("user32.dll", SetLastError = true)]
public static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
public MainForm()
{
InitializeComponent();
Logger.LogReceived += Logger_LogReceived;
}
private void MainForm_Load(object sender, EventArgs e)
{
notifyIcon.Visible = false;
// Load initial OpenAI settings
chkEnableOpenAI.Checked = OperationCenter.Instance.UseOpenAIMode;
txtOpenAIUrl.Text = OperationCenter.Instance.OpenAIApiUrl;
txtOpenAIKey.Text = OperationCenter.Instance.OpenAIApiKey;
WebServer.Instance.Start();
timerUpdateInstances.Start();
this.Deactivate += MainForm_Deactivate;
}
private void MainForm_Deactivate(object? sender, EventArgs e)
{
if (!this.Disposing && !this.IsDisposed)
{
notifyIcon?.Visible = true;
this.Hide();
}
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
timerUpdateInstances.Stop();
WebServer.Instance.Stop();
}
private void Logger_LogReceived(string message)
{
if (rtbConsole.InvokeRequired)
{
rtbConsole.Invoke(new Action(() => Logger_LogReceived(message)));
return;
}
rtbConsole.AppendText($"[{DateTime.Now:HH:mm:ss}] {message}" + Environment.NewLine);
if (rtbConsole.Lines.Length > 1000)
{
int excessLines = rtbConsole.Lines.Length - 1000;
int indexToCut = rtbConsole.GetFirstCharIndexFromLine(excessLines);
rtbConsole.Select(0, indexToCut);
rtbConsole.SelectedText = "";
}
rtbConsole.ScrollToCaret();
}
private void RestartServer()
{
Logger.Log("[SYSTEM] Restarting server and clearing state...");
WebServer.Instance.Stop();
OperationCenter.Instance.ClearAll();
listViewInstances.Items.Clear();
// Reset UI state for OpenAI
chkEnableOpenAI.Checked = false;
WebServer.Instance.Start();
}
private void btnRestart_Click(object sender, EventArgs e)
{
RestartServer();
}
private void MainForm_Resize(object sender, EventArgs e)
{
if (this.WindowState == FormWindowState.Minimized)
{
notifyIcon.Visible = true;
this.Hide();
}
}
private void notifyIcon_MouseDoubleClick(object sender, MouseEventArgs e)
{
RestoreForm();
}
private void notifyIcon_Click(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
RestoreForm();
}
}
private void showServerToolStripMenuItem_Click(object sender, EventArgs e)
{
RestoreForm();
}
private void RestoreForm()
{
this.Show();
//this.WindowState = FormWindowState.Normal;
this.BringToFront();
notifyIcon.Visible = false;
SwitchToThisWindow(this.Handle, true);
}
private void restartServerToolStripMenuItem_Click(object sender, EventArgs e)
{
RestartServer();
}
private void exitShutdownToolStripMenuItem_Click(object sender, EventArgs e)
{
WebServer.Instance.Stop();
OperationCenter.Instance.ClearAll();
Close();
}
private void timerUpdateInstances_Tick(object sender, EventArgs e)
{
var instances = OperationCenter.Instance.GetActiveInstances();
listViewInstances.BeginUpdate();
// Remove stale items
var keysToRemove = new List<ListViewItem>();
foreach (ListViewItem item in listViewInstances.Items)
{
if (!instances.ContainsKey(item.Name)) keysToRemove.Add(item);
}
foreach (var item in keysToRemove) listViewInstances.Items.Remove(item);
// Add or update items
foreach (var kvp in instances)
{
string id = kvp.Key;
var data = kvp.Value as JsonObject;
string platform = data?["platform"]?.ToString() ?? "Unknown";
string status = (data?["is_busy"]?.GetValue<bool>() ?? false) ? "Busy" : "Idle";
DateTime lastSeenTime;
string lastSeenStr = data?["last_seen"]?.ToString() ?? "";
if (DateTime.TryParse(lastSeenStr, out lastSeenTime))
{
lastSeenStr = lastSeenTime.ToString("HH:mm:ss");
}
if (listViewInstances.Items.ContainsKey(id))
{
var item = listViewInstances.Items[id];
item.SubItems[0].Text = platform;
item.SubItems[1].Text = id;
item.SubItems[2].Text = status;
item.SubItems[3].Text = lastSeenStr;
}
else
{
var item = new ListViewItem(new[] { platform, id, status, lastSeenStr }) { Name = id };
listViewInstances.Items.Add(item);
}
}
listViewInstances.EndUpdate();
// Keep the checkbox in sync if the OpenAI instance was shut down via the list view
if (chkEnableOpenAI.Checked && !OperationCenter.Instance.UseOpenAIMode)
{
chkEnableOpenAI.Checked = false;
}
}
private void ResetSelectedInstanceChat()
{
if (listViewInstances.SelectedItems.Count > 0)
{
string instanceId = listViewInstances.SelectedItems[0].Name;
if (OperationCenter.Instance.IsOpenAIInstance(instanceId))
{
OperationCenter.Instance.ClearChatHistory(instanceId);
Logger.Log($"[ADMIN] Reset chat history for OpenAI instance {instanceId}");
}
else
{
OperationCenter.Instance.QueueCommand(new JsonObject { ["type"] = "NEW_CHAT" }, instanceId);
Logger.Log($"[ADMIN] Sent NEW_CHAT command to {instanceId}");
}
}
}
private void ShutdownSelectedInstance()
{
if (listViewInstances.SelectedItems.Count > 0)
{
string instanceId = listViewInstances.SelectedItems[0].Name;
OperationCenter.Instance.ShutdownInstance(instanceId);
Logger.Log($"[ADMIN] Sent SHUTDOWN command to {instanceId} and removed from active list.");
listViewInstances.Items.RemoveByKey(instanceId);
}
}
private void btnResetChat_Click(object sender, EventArgs e)
{
ResetSelectedInstanceChat();
}
private void btnShutdown_Click(object sender, EventArgs e)
{
ShutdownSelectedInstance();
}
private void resetChatToolStripMenuItem_Click(object sender, EventArgs e)
{
ResetSelectedInstanceChat();
}
private void shutdownHarnessToolStripMenuItem_Click(object sender, EventArgs e)
{
ShutdownSelectedInstance();
}
private void chkEnableOpenAI_CheckedChanged(object sender, EventArgs e)
{
if (chkEnableOpenAI.Checked)
{
OperationCenter.Instance.EnableOpenAIMode(txtOpenAIUrl.Text, txtOpenAIKey.Text);
Logger.Log($"[SYSTEM] OpenAI API Mode: Enabled");
}
else
{
OperationCenter.Instance.DisableOpenAIMode();
Logger.Log($"[SYSTEM] OpenAI API Mode: Disabled");
}
}
private void txtOpenAIUrl_TextChanged(object sender, EventArgs e)
{
if (OperationCenter.Instance.UseOpenAIMode)
{
OperationCenter.Instance.OpenAIApiUrl = txtOpenAIUrl.Text;
OperationCenter.Instance.UpdateOpenAIUrl(txtOpenAIUrl.Text);
}
}
private void txtOpenAIKey_TextChanged(object sender, EventArgs e)
{
if (OperationCenter.Instance.UseOpenAIMode)
{
OperationCenter.Instance.OpenAIApiKey = txtOpenAIKey.Text;
}
}
private void restartApplicationToolStripMenuItem_Click(object sender, EventArgs e)
{
// Start a new instance of the current executable
Process.Start(new ProcessStartInfo
{
FileName = Environment.ProcessPath,
UseShellExecute = true
});
WebServer.Instance.Stop();
OperationCenter.Instance.ClearAll();
Close();
}
}
}

10667
AI C2 Server/MainForm.resx Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,429 @@
// File: OperationCenter.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Nodes;
namespace AI_C2_Server
{
public class OperationCenter
{
public static OperationCenter Instance { get; } = new OperationCenter();
private readonly Dictionary<string, Queue<JsonObject>> commandQueues = new();
private readonly Dictionary<string, JsonObject> results = new();
private readonly Dictionary<string, List<string>> pendingTaskIds = new();
private readonly Dictionary<string, JsonObject> instances = new();
private readonly Dictionary<string, JsonArray> chatHistory = new();
private readonly Dictionary<string, JsonObject> tasks = new();
// Tracks instances that have been shut down so they aren't recreated by lingering heartbeats
private readonly HashSet<string> blacklistedInstances = new();
private readonly object queueLock = new object();
// OpenAI Configuration
public bool UseOpenAIMode { get; set; } = false;
public string OpenAIApiUrl { get; set; } = "http://192.168.12.181:8080/v1/chat/completions";
public string OpenAIApiKey { get; set; } = "";
public string OpenAIInstanceId { get; private set; }
public string OpenAIModelName { get; set; } = "";
public string OpenAISystemInstructions { get; set; } = "";
private OperationCenter() { }
public void ClearAll()
{
lock (queueLock)
{
commandQueues.Clear();
results.Clear();
pendingTaskIds.Clear();
instances.Clear();
chatHistory.Clear();
tasks.Clear();
blacklistedInstances.Clear();
UseOpenAIMode = false;
OpenAIInstanceId = null;
OpenAIModelName = "";
OpenAISystemInstructions = "";
}
}
public bool IsOpenAIInstance(string instanceId)
{
return !string.IsNullOrEmpty(OpenAIInstanceId) && instanceId == OpenAIInstanceId;
}
public string EnableOpenAIMode(string url, string key)
{
lock (queueLock)
{
UseOpenAIMode = true;
OpenAIApiUrl = url;
OpenAIApiKey = key;
OpenAIModelName = ""; // Reset model name so it fetches on next run
if (string.IsNullOrEmpty(OpenAIInstanceId))
{
OpenAIInstanceId = $"inst_openai_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
}
instances[OpenAIInstanceId] = new JsonObject
{
["last_seen"] = DateTime.Now.ToString("o"),
["url"] = url,
["turn_count"] = 0,
["is_busy"] = false,
["platform"] = "OpenAI API"
};
if (!chatHistory.ContainsKey(OpenAIInstanceId))
{
chatHistory[OpenAIInstanceId] = new JsonArray();
}
Logger.Log($"[OP-CENTER] OpenAI API instance registered: {OpenAIInstanceId}");
return OpenAIInstanceId;
}
}
public void DisableOpenAIMode()
{
lock (queueLock)
{
UseOpenAIMode = false;
OpenAIModelName = "";
if (!string.IsNullOrEmpty(OpenAIInstanceId))
{
instances.Remove(OpenAIInstanceId);
Logger.Log($"[OP-CENTER] OpenAI API instance removed: {OpenAIInstanceId}");
OpenAIInstanceId = null;
}
}
}
public void UpdateOpenAIUrl(string url)
{
lock (queueLock)
{
OpenAIModelName = ""; // Reset model name so it fetches again for the new URL
if (!string.IsNullOrEmpty(OpenAIInstanceId) && instances.ContainsKey(OpenAIInstanceId))
{
instances[OpenAIInstanceId]["url"] = url;
}
}
}
private void EnsureInstance(string instanceId)
{
bool isNew = false;
if (!commandQueues.ContainsKey(instanceId))
{
commandQueues[instanceId] = new Queue<JsonObject>();
pendingTaskIds[instanceId] = new List<string>();
isNew = true;
}
if (!blacklistedInstances.Contains(instanceId) && !instances.ContainsKey(instanceId))
{
instances[instanceId] = new JsonObject
{
["last_seen"] = DateTime.Now.ToString("o"),
["url"] = "",
["turn_count"] = 0,
["is_busy"] = false,
["platform"] = "Unknown"
};
if (isNew)
{
Logger.Log($"[OP-CENTER] New instance registered: {instanceId}");
}
}
if (!chatHistory.ContainsKey(instanceId))
{
chatHistory[instanceId] = new JsonArray();
}
}
public void UpdateInstanceState(string instanceId, JsonObject stateData)
{
lock (queueLock)
{
if (blacklistedInstances.Contains(instanceId)) return;
EnsureInstance(instanceId);
if (instances.ContainsKey(instanceId))
{
instances[instanceId]["last_seen"] = DateTime.Now.ToString("o");
if (stateData != null)
{
if (stateData.ContainsKey("url"))
instances[instanceId]["url"] = stateData["url"]?.ToString();
if (stateData.ContainsKey("turn_count"))
instances[instanceId]["turn_count"] = stateData["turn_count"]?.GetValue<int>() ?? 0;
if (stateData.ContainsKey("is_busy"))
instances[instanceId]["is_busy"] = stateData["is_busy"]?.GetValue<bool>() ?? false;
if (stateData.ContainsKey("platform"))
instances[instanceId]["platform"] = stateData["platform"]?.ToString();
}
}
}
}
public JsonObject GetActiveInstances()
{
lock (queueLock)
{
var result = new JsonObject();
foreach (var kvp in instances)
{
result[kvp.Key] = kvp.Value.DeepClone();
}
return result;
}
}
public string GetDefaultInstanceId()
{
lock (queueLock)
{
if (instances.Count == 0) return "default";
return instances.OrderByDescending(x => DateTime.Parse(x.Value["last_seen"].ToString())).First().Key;
}
}
public string GetInstancePlatform(string instanceId)
{
lock (queueLock)
{
if (instances.TryGetValue(instanceId, out var data))
{
return data?["platform"]?.ToString() ?? "Unknown";
}
return "Unknown";
}
}
public string QueueCommand(JsonObject commandData, string instanceId = null)
{
lock (queueLock)
{
if (string.IsNullOrEmpty(instanceId))
{
instanceId = GetDefaultInstanceId();
}
EnsureInstance(instanceId);
string taskId = $"CMD-{instanceId.Substring(0, Math.Min(8, instanceId.Length))}-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
var command = new JsonObject
{
["id"] = taskId,
["command"] = commandData.DeepClone(),
["timestamp"] = DateTime.Now.ToString("o"),
["instance_id"] = instanceId
};
tasks[taskId] = commandData.DeepClone().AsObject();
string cmdType = commandData["type"]?.ToString();
if (cmdType == "GENERATE")
{
chatHistory[instanceId].Add(new JsonObject
{
["role"] = "User",
["text"] = commandData["payload"]?.ToString() ?? ""
});
}
commandQueues[instanceId].Enqueue(command);
pendingTaskIds[instanceId].Add(taskId);
return taskId;
}
}
public JsonObject GetPendingCommand(string instanceId)
{
lock (queueLock)
{
if (!commandQueues.ContainsKey(instanceId)) return null;
if (commandQueues[instanceId].Count > 0)
{
if (instances.ContainsKey(instanceId))
{
instances[instanceId]["is_busy"] = true;
}
return commandQueues[instanceId].Dequeue();
}
return null;
}
}
public bool LogResult(string instanceId, JsonObject resultData)
{
lock (queueLock)
{
if (instances.ContainsKey(instanceId))
{
instances[instanceId]["is_busy"] = false;
}
if (pendingTaskIds.ContainsKey(instanceId) && pendingTaskIds[instanceId].Count > 0)
{
string matchedTaskId = pendingTaskIds[instanceId][0];
pendingTaskIds[instanceId].RemoveAt(0);
if (tasks.TryGetValue(matchedTaskId, out var cmdData))
{
string cmdType = cmdData["type"]?.ToString();
string output = resultData["output"]?.ToString() ?? "";
if (cmdType == "GET_CHAT_HISTORY")
{
try { chatHistory[instanceId] = JsonNode.Parse(output).AsArray(); } catch { }
}
else if ((cmdType == "GENERATE" || cmdType == "INJECT_SHARD") && !output.StartsWith("Error"))
{
chatHistory[instanceId].Add(new JsonObject { ["role"] = "Model", ["text"] = output });
}
else if (cmdType == "NEW_CHAT")
{
chatHistory[instanceId].Clear();
}
else if (cmdType == "DELETE_TURN" && !output.StartsWith("Error"))
{
try
{
int idx = cmdData["payload"]?.GetValue<int>() ?? -1;
if (idx < 0) idx = chatHistory[instanceId].Count + idx;
if (idx >= 0 && idx < chatHistory[instanceId].Count)
{
chatHistory[instanceId].RemoveAt(idx);
}
}
catch { }
}
}
results[matchedTaskId] = new JsonObject
{
["received_at"] = DateTime.Now.ToString("o"),
["data"] = resultData.DeepClone(),
["instance_id"] = instanceId
};
Logger.Log($"[OP-CENTER] Task {matchedTaskId} Completed on Instance {instanceId}.");
return true;
}
else
{
Logger.Log($"[OP-CENTER] Warning: Result received from {instanceId} but no tasks were pending.");
return false;
}
}
}
public JsonObject GetResult(string taskId)
{
lock (queueLock)
{
if (results.TryGetValue(taskId, out var res))
{
return res.DeepClone().AsObject();
}
return null;
}
}
public void CancelPendingTask(string instanceId, string taskId)
{
lock (queueLock)
{
if (pendingTaskIds.ContainsKey(instanceId))
{
if (pendingTaskIds[instanceId].Remove(taskId))
{
Logger.Log($"[OP-CENTER] Task {taskId} timed out and was removed from pending queue for {instanceId}.");
}
}
}
}
public void ShutdownInstance(string instanceId)
{
lock (queueLock)
{
if (IsOpenAIInstance(instanceId))
{
DisableOpenAIMode();
return;
}
// Queue the shutdown command so the harness receives it on its next poll
QueueCommand(new JsonObject { ["type"] = "SHUTDOWN" }, instanceId);
// Blacklist and remove from active instances so it disappears from the UI
// and cannot be recreated by lingering heartbeats
blacklistedInstances.Add(instanceId);
instances.Remove(instanceId);
}
}
public void SetInstanceBusy(string instanceId, bool isBusy)
{
lock (queueLock)
{
if (instances.ContainsKey(instanceId))
{
instances[instanceId]["is_busy"] = isBusy;
instances[instanceId]["last_seen"] = DateTime.Now.ToString("o");
}
}
}
public void AddChatHistory(string instanceId, string role, string text)
{
lock (queueLock)
{
if (!chatHistory.ContainsKey(instanceId)) chatHistory[instanceId] = new JsonArray();
chatHistory[instanceId].Add(new JsonObject { ["role"] = role, ["text"] = text });
}
}
public void ClearChatHistory(string instanceId)
{
lock (queueLock)
{
if (chatHistory.ContainsKey(instanceId)) chatHistory[instanceId].Clear();
}
}
public bool HasChatHistory(string instanceId)
{
lock (queueLock)
{
return chatHistory.ContainsKey(instanceId) && chatHistory[instanceId].Count > 0;
}
}
public JsonArray GetChatHistory(string instanceId)
{
lock (queueLock)
{
if (chatHistory.TryGetValue(instanceId, out var history))
{
return history.DeepClone().AsArray();
}
return new JsonArray();
}
}
}
}

18
AI C2 Server/Program.cs Normal file
View File

@ -0,0 +1,18 @@
namespace AI_C2_Server
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.SetColorMode(SystemColorMode.Dark);
Application.Run(new MainForm());
}
}
}

502
AI C2 Server/WebServer.cs Normal file
View File

@ -0,0 +1,502 @@
// File: WebServer.cs
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace AI_C2_Server
{
public class WebServer
{
public static WebServer Instance { get; } = new WebServer();
private HttpListener _listener;
private bool _isRunning;
private static readonly HttpClient _httpClient = new HttpClient();
private WebServer() { }
public void Start(int port = 8080)
{
if (_isRunning) return;
_listener = new HttpListener();
_listener.Prefixes.Add($"http://localhost:{port}/");
_listener.Prefixes.Add($"http://127.0.0.1:{port}/");
try
{
_listener.Start();
_isRunning = true;
Task.Run(ListenLoop);
Logger.Log($"[SYSTEM] Initializing C2 Server on Port {port}...");
}
catch (Exception ex)
{
Logger.Log($"[ERROR] Failed to start server: {ex.Message}");
}
}
public void Stop()
{
if (!_isRunning) return;
_isRunning = false;
_listener?.Stop();
_listener?.Close();
Logger.Log("[SYSTEM] Server stopped.");
}
private async Task ListenLoop()
{
while (_isRunning)
{
try
{
var context = await _listener.GetContextAsync();
_ = Task.Run(() => ProcessRequestAsync(context));
}
catch (HttpListenerException)
{
// Thrown when listener is stopped, safe to ignore
}
catch (Exception ex)
{
Logger.Log($"[ERROR] Listener loop error: {ex.Message}");
}
}
}
private async Task ProcessRequestAsync(HttpListenerContext context)
{
var request = context.Request;
var response = context.Response;
// CORS Headers
response.AddHeader("Access-Control-Allow-Origin", "*");
response.AddHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
response.AddHeader("Access-Control-Allow-Headers", "Content-Type");
if (request.HttpMethod == "OPTIONS")
{
response.StatusCode = 200;
response.Close();
return;
}
try
{
string path = request.Url.AbsolutePath.TrimEnd('/');
// Extract instance ID for logging
string instanceId = "";
if (path.StartsWith("/api/relay/state/")) instanceId = path.Substring("/api/relay/state/".Length);
else if (path.StartsWith("/api/relay/command/")) instanceId = path.Substring("/api/relay/command/".Length);
else if (path.StartsWith("/api/relay/result/")) instanceId = path.Substring("/api/relay/result/".Length);
if (!string.IsNullOrEmpty(instanceId))
{
string platform = OperationCenter.Instance.GetInstancePlatform(instanceId);
Logger.Log($"[HTTP] [{platform} | {instanceId}] {request.HttpMethod} {path}");
}
else
{
Logger.Log($"[HTTP] {request.HttpMethod} {path}");
}
string body = "";
if (request.HasEntityBody)
{
using var reader = new StreamReader(request.InputStream, request.ContentEncoding);
body = await reader.ReadToEndAsync();
}
JsonNode payload = null;
if (!string.IsNullOrWhiteSpace(body))
{
try { payload = JsonNode.Parse(body); } catch { }
}
// --- RELAY ENDPOINTS ---
if (path.StartsWith("/api/relay/state/"))
{
OperationCenter.Instance.UpdateInstanceState(instanceId, payload as JsonObject);
SendJson(response, new JsonObject { ["status"] = "ok" });
}
else if (path.StartsWith("/api/relay/command/"))
{
OperationCenter.Instance.UpdateInstanceState(instanceId, new JsonObject());
var cmd = OperationCenter.Instance.GetPendingCommand(instanceId);
SendJson(response, cmd ?? new JsonObject());
}
else if (path.StartsWith("/api/relay/result/"))
{
OperationCenter.Instance.LogResult(instanceId, payload as JsonObject);
SendJson(response, new JsonObject { ["status"] = "acknowledged" });
}
// --- ADMIN ENDPOINTS ---
else if (path == "/api/admin/instances")
{
SendJson(response, OperationCenter.Instance.GetActiveInstances());
}
else if (path == "/api/admin/inject")
{
await HandleCommand(response, "GENERATE", payload?["prompt"]?.DeepClone(), GetBool(payload, "blocking", true), GetInt(payload, "timeout", 600), GetString(payload, "instance_id"));
}
else if (path == "/api/admin/history")
{
string id = request.QueryString["instance_id"];
await HandleCommand(response, "SCRAPE_HISTORY", null, true, 10, id);
}
else if (path == "/api/admin/session/analyze")
{
await HandleCommand(response, "ANALYZE_SESSION", null, true, 5, GetString(payload, "instance_id"));
}
else if (path == "/api/admin/upload_media")
{
if (payload?["data"] == null || payload?["mime"] == null)
{
SendJson(response, new JsonObject { ["error"] = "Invalid Media Payload" }, 400);
return;
}
await HandleCommand(response, "UPLOAD_MEDIA", payload, true, 30, GetString(payload, "instance_id"));
}
else if (path == "/api/admin/action/new_chat")
{
await HandleCommand(response, "NEW_CHAT", null, true, 30, GetString(payload, "instance_id"));
}
else if (path == "/api/admin/action/toggle_url_context")
{
await HandleCommand(response, "SET_URL_CONTEXT", GetBool(payload, "enabled", true), true, 30, GetString(payload, "instance_id"));
}
else if (path == "/api/admin/action/resolution")
{
await HandleCommand(response, "SET_RESOLUTION", GetString(payload, "level", "Default"), true, 30, GetString(payload, "instance_id"));
}
else if (path == "/api/admin/action/delete")
{
await HandleCommand(response, "DELETE_TURN", GetInt(payload, "index", -1), true, 30, GetString(payload, "instance_id"));
}
else if (path == "/api/admin/action/settings")
{
await HandleCommand(response, "SET_SETTINGS", payload, true, 45, GetString(payload, "instance_id"));
}
else if (path == "/api/admin/action/get_settings")
{
await HandleCommand(response, "GET_SETTINGS", null, true, 60, GetString(payload, "instance_id"));
}
else if (path == "/api/admin/action/get_chat_history")
{
string id = GetString(payload, "instance_id");
bool forceSync = GetBool(payload, "force_sync", false);
if (!forceSync && OperationCenter.Instance.HasChatHistory(id))
{
SendJson(response, new JsonObject
{
["status"] = "completed",
["output"] = OperationCenter.Instance.GetChatHistory(id).ToJsonString(),
["timestamp"] = DateTime.Now.ToString("o"),
["instance_id"] = id
});
return;
}
await HandleCommand(response, "GET_CHAT_HISTORY", null, true, 120, id);
}
else if (path == "/api/admin/action/create_shard")
{
await HandleCommand(response, "CREATE_SHARD", null, true, 600, GetString(payload, "instance_id"));
}
else if (path == "/api/admin/action/inject_shard")
{
if (payload?["shard_data"] == null)
{
SendJson(response, new JsonObject { ["error"] = "Missing shard_data" }, 400);
return;
}
await HandleCommand(response, "INJECT_SHARD", payload["shard_data"]?.DeepClone(), true, 600, GetString(payload, "instance_id"));
}
else if (path == "/api/admin/action/inject_doc")
{
if (payload?["content"] == null)
{
SendJson(response, new JsonObject { ["error"] = "Missing content" }, 400);
return;
}
await HandleCommand(response, "INJECT_DOC", payload, true, 30, GetString(payload, "instance_id"));
}
else if (path == "/api/admin/openai/system_instructions")
{
string instructions = GetString(payload, "instructions", "");
OperationCenter.Instance.OpenAISystemInstructions = instructions;
SendJson(response, new JsonObject { ["status"] = "completed", ["output"] = "System instructions updated." });
}
else if (path.StartsWith("/api/admin/result/"))
{
string taskId = path.Substring("/api/admin/result/".Length);
var res = OperationCenter.Instance.GetResult(taskId);
if (res != null)
{
SendJson(response, new JsonObject { ["status"] = "completed", ["result"] = res.DeepClone() });
}
else
{
SendJson(response, new JsonObject { ["status"] = "pending" }, 202);
}
}
else
{
SendJson(response, new JsonObject { ["error"] = "Not Found" }, 404);
}
}
catch (Exception ex)
{
Logger.Log($"[ERROR] Request processing failed: {ex.Message}");
SendJson(response, new JsonObject { ["error"] = "Internal Server Error" }, 500);
}
}
private async Task HandleCommand(HttpListenerResponse response, string commandType, JsonNode payload, bool blocking, int timeout, string instanceId)
{
if (string.IsNullOrEmpty(instanceId))
{
instanceId = OperationCenter.Instance.GetDefaultInstanceId();
}
// --- OPENAI API INTERCEPTION ---
if (OperationCenter.Instance.IsOpenAIInstance(instanceId))
{
if (commandType == "GENERATE")
{
try
{
OperationCenter.Instance.SetInstanceBusy(instanceId, true);
string prompt = payload?.ToString() ?? "";
// Auto-detect model if not cached
string modelName = OperationCenter.Instance.OpenAIModelName;
if (string.IsNullOrEmpty(modelName))
{
string modelsUrl = OperationCenter.Instance.OpenAIApiUrl.Replace("/chat/completions", "/models");
try
{
var modelReq = new HttpRequestMessage(HttpMethod.Get, modelsUrl);
if (!string.IsNullOrEmpty(OperationCenter.Instance.OpenAIApiKey))
{
modelReq.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", OperationCenter.Instance.OpenAIApiKey);
}
var modelRes = await _httpClient.SendAsync(modelReq);
if (modelRes.IsSuccessStatusCode)
{
string modelStr = await modelRes.Content.ReadAsStringAsync();
var modelJson = JsonNode.Parse(modelStr);
var dataArray = modelJson?["data"] as JsonArray;
if (dataArray != null && dataArray.Count > 0)
{
modelName = dataArray[0]?["id"]?.ToString();
OperationCenter.Instance.OpenAIModelName = modelName;
Logger.Log($"[OPENAI] Auto-detected model: {modelName}");
}
}
}
catch (Exception ex)
{
Logger.Log($"[OPENAI] Warning: Failed to fetch models from {modelsUrl}. Using 'default'. Error: {ex.Message}");
}
if (string.IsNullOrEmpty(modelName))
{
modelName = "default";
}
}
// Add user prompt to history
OperationCenter.Instance.AddChatHistory(instanceId, "User", prompt);
// Build messages array from history
var messagesArray = new JsonArray();
if (!string.IsNullOrEmpty(OperationCenter.Instance.OpenAISystemInstructions))
{
messagesArray.Add(new JsonObject { ["role"] = "system", ["content"] = OperationCenter.Instance.OpenAISystemInstructions });
}
var history = OperationCenter.Instance.GetChatHistory(instanceId);
foreach (var msg in history)
{
string role = msg["role"]?.ToString().ToLower() == "user" ? "user" : "assistant";
messagesArray.Add(new JsonObject { ["role"] = role, ["content"] = msg["text"]?.ToString() });
}
var openAiRequest = new JsonObject
{
["model"] = modelName,
["messages"] = messagesArray
};
var requestMsg = new HttpRequestMessage(HttpMethod.Post, OperationCenter.Instance.OpenAIApiUrl);
if (!string.IsNullOrEmpty(OperationCenter.Instance.OpenAIApiKey))
{
requestMsg.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", OperationCenter.Instance.OpenAIApiKey);
}
requestMsg.Content = new StringContent(openAiRequest.ToJsonString(), Encoding.UTF8, "application/json");
Logger.Log($"[OPENAI] Sending request to {OperationCenter.Instance.OpenAIApiUrl}");
var responseMsg = await _httpClient.SendAsync(requestMsg);
string resultStr = await responseMsg.Content.ReadAsStringAsync();
OperationCenter.Instance.SetInstanceBusy(instanceId, false);
if (!responseMsg.IsSuccessStatusCode)
{
Logger.Log($"[OPENAI ERROR] {responseMsg.StatusCode}: {resultStr}");
SendJson(response, new JsonObject { ["error"] = $"OpenAI API Error: {responseMsg.StatusCode}" }, 500);
return;
}
var jsonResult = JsonNode.Parse(resultStr);
string outputText = jsonResult?["choices"]?[0]?["message"]?["content"]?.ToString() ?? resultStr;
// Strip out <think>...</think> blocks generated by DeepSeek R1 models
outputText = Regex.Replace(outputText, @"<think>.*?</think>\s*", "", RegexOptions.Singleline | RegexOptions.IgnoreCase);
// Handle edge cases where the model omits the opening <think> tag but includes the closing </think> tag
outputText = Regex.Replace(outputText, @"^.*?<\/think>\s*", "", RegexOptions.Singleline | RegexOptions.IgnoreCase);
outputText = outputText.Trim();
// Add assistant response to history
OperationCenter.Instance.AddChatHistory(instanceId, "Model", outputText);
SendJson(response, new JsonObject
{
["status"] = "completed",
["output"] = outputText,
["timestamp"] = DateTime.Now.ToString("o"),
["instance_id"] = instanceId
}, 200);
return;
}
catch (Exception ex)
{
OperationCenter.Instance.SetInstanceBusy(instanceId, false);
Logger.Log($"[ERROR] OpenAI API call failed: {ex.Message}");
SendJson(response, new JsonObject { ["error"] = "OpenAI API Error: " + ex.Message }, 500);
return;
}
}
else if (commandType == "NEW_CHAT")
{
OperationCenter.Instance.ClearChatHistory(instanceId);
SendJson(response, new JsonObject { ["status"] = "completed", ["output"] = "Chat reset.", ["timestamp"] = DateTime.Now.ToString("o"), ["instance_id"] = instanceId }, 200);
return;
}
else if (commandType == "SHUTDOWN")
{
OperationCenter.Instance.ShutdownInstance(instanceId);
SendJson(response, new JsonObject { ["status"] = "completed", ["output"] = "Harness shut down.", ["timestamp"] = DateTime.Now.ToString("o"), ["instance_id"] = instanceId }, 200);
return;
}
else if (commandType == "GET_CHAT_HISTORY")
{
SendJson(response, new JsonObject
{
["status"] = "completed",
["output"] = OperationCenter.Instance.GetChatHistory(instanceId).ToJsonString(),
["timestamp"] = DateTime.Now.ToString("o"),
["instance_id"] = instanceId
}, 200);
return;
}
else
{
SendJson(response, new JsonObject { ["error"] = $"Command {commandType} not supported in OpenAI API mode.", ["instance_id"] = instanceId }, 400);
return;
}
}
// -------------------------------
var cmdData = new JsonObject
{
["type"] = commandType,
["payload"] = payload?.DeepClone()
};
string taskId = OperationCenter.Instance.QueueCommand(cmdData, instanceId);
if (!blocking)
{
SendJson(response, new JsonObject { ["status"] = "queued", ["task_id"] = taskId, ["instance_id"] = instanceId }, 200);
return;
}
DateTime startTime = DateTime.Now;
while ((DateTime.Now - startTime).TotalSeconds < timeout)
{
var result = OperationCenter.Instance.GetResult(taskId);
if (result != null)
{
SendJson(response, new JsonObject
{
["status"] = "completed",
["output"] = result["data"]?["output"]?.ToString(),
["timestamp"] = result["received_at"]?.ToString(),
["instance_id"] = result["instance_id"]?.ToString()
}, 200);
return;
}
await Task.Delay(500);
}
// Timeout handling: Cancel the task so it doesn't permanently block the FIFO queue
OperationCenter.Instance.CancelPendingTask(instanceId, taskId);
SendJson(response, new JsonObject { ["error"] = "Timeout", ["task_id"] = taskId, ["instance_id"] = instanceId }, 504);
}
private void SendJson(HttpListenerResponse response, JsonNode data, int statusCode = 200)
{
response.StatusCode = statusCode;
response.ContentType = "application/json";
byte[] buffer = Encoding.UTF8.GetBytes(data.ToJsonString());
response.ContentLength64 = buffer.Length;
try
{
response.OutputStream.Write(buffer, 0, buffer.Length);
}
catch { }
finally
{
response.Close();
}
}
private string GetString(JsonNode node, string key, string def = null)
{
if (node is JsonObject obj && obj.TryGetPropertyValue(key, out var val) && val != null)
return val.ToString();
return def;
}
private bool GetBool(JsonNode node, string key, bool def = false)
{
if (node is JsonObject obj && obj.TryGetPropertyValue(key, out var val) && val != null)
{
try { return val.GetValue<bool>(); } catch { return def; }
}
return def;
}
private int GetInt(JsonNode node, string key, int def = 0)
{
if (node is JsonObject obj && obj.TryGetPropertyValue(key, out var val) && val != null)
{
try { return val.GetValue<int>(); } catch { return def; }
}
return def;
}
}
}

BIN
AI C2 Server/app.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

View File

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
</Project>

189
AI.Client/AiClient.cs Normal file
View File

@ -0,0 +1,189 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace AI.Client
{
public class AiClient
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl;
public AiClient(string baseUrl = "http://localhost:8080/api")
{
_baseUrl = baseUrl.TrimEnd('/');
_httpClient = new HttpClient();
_httpClient.Timeout = TimeSpan.FromMinutes(15); // Increased to 15 minutes for long generations
}
// --- CORE COMMANDS ---
public async Task<Dictionary<string, InstanceState>> GetInstancesAsync()
{
return await GetJsonAsync<Dictionary<string, InstanceState>>("/admin/instances");
}
public async Task<AiResponse> SendPromptAsync(string text, string instanceId = null)
{
var payload = new { prompt = text, blocking = true, instance_id = instanceId };
return await PostJsonAsync<AiResponse>("/admin/inject", payload);
}
public async Task<AiResponse> NewChatAsync(string instanceId = null)
{
var payload = new { blocking = true, instance_id = instanceId };
return await PostJsonAsync<AiResponse>("/admin/action/new_chat", payload);
}
public async Task<AiResponse> ToggleUrlContextAsync(bool enabled, string instanceId = null)
{
var payload = new { enabled = enabled, blocking = true, instance_id = instanceId };
return await PostJsonAsync<AiResponse>("/admin/action/toggle_url_context", payload);
}
public async Task<AiResponse> SetResolutionAsync(string level, string instanceId = null)
{
var payload = new { level = level, blocking = true, instance_id = instanceId };
return await PostJsonAsync<AiResponse>("/admin/action/resolution", payload);
}
public async Task<AiResponse> DeleteTurnAsync(int index, string instanceId = null)
{
var payload = new { index = index, blocking = true, instance_id = instanceId };
return await PostJsonAsync<AiResponse>("/admin/action/delete", payload);
}
// --- ADVANCED COMMANDS ---
public async Task<AiResponse> SetSettingsAsync(string instanceId, float? temp, float? topP, string model, string sysInst, string thinkingLevel)
{
// Use a dictionary to avoid sending null values which might confuse the backend
var payload = new Dictionary<string, object>();
if (!string.IsNullOrEmpty(instanceId)) payload["instance_id"] = instanceId;
if (temp.HasValue) payload["temperature"] = temp.Value;
if (topP.HasValue) payload["top_p"] = topP.Value;
if (!string.IsNullOrEmpty(model)) payload["model"] = model;
if (!string.IsNullOrEmpty(sysInst)) payload["system_instructions"] = sysInst;
if (!string.IsNullOrEmpty(thinkingLevel)) payload["thinking_level"] = thinkingLevel;
payload["blocking"] = true;
return await PostJsonAsync<AiResponse>("/admin/action/settings", payload);
}
public async Task<AiResponse> GetSettingsAsync(string instanceId = null)
{
var payload = new { blocking = true, instance_id = instanceId };
return await PostJsonAsync<AiResponse>("/admin/action/get_settings", payload);
}
public async Task<AiResponse> GetChatHistoryAsync(string instanceId = null, bool forceSync = false)
{
var payload = new { blocking = true, instance_id = instanceId, force_sync = forceSync };
return await PostJsonAsync<AiResponse>("/admin/action/get_chat_history", payload);
}
public async Task<AiResponse> CreateShardAsync(string instanceId = null)
{
var payload = new { blocking = true, instance_id = instanceId };
return await PostJsonAsync<AiResponse>("/admin/action/create_shard", payload);
}
public async Task<AiResponse> InjectShardAsync(string shardData, string instanceId = null)
{
var payload = new { shard_data = shardData, blocking = true, instance_id = instanceId };
return await PostJsonAsync<AiResponse>("/admin/action/inject_shard", payload);
}
public async Task<AiResponse> InjectDocAsync(string filename, string content, string instanceId = null)
{
var payload = new { filename = filename, content = content, blocking = true, instance_id = instanceId };
return await PostJsonAsync<AiResponse>("/admin/action/inject_doc", payload);
}
public async Task<AiResponse> SetOpenAISystemInstructionsAsync(string instructions)
{
var payload = new { instructions = instructions };
return await PostJsonAsync<AiResponse>("/admin/openai/system_instructions", payload);
}
// --- INTERNAL HELPERS ---
private async Task<T> GetJsonAsync<T>(string endpoint)
{
try
{
var response = await _httpClient.GetAsync(_baseUrl + endpoint);
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
return JsonSerializer.Deserialize<T>(responseString, options);
}
catch (Exception ex)
{
throw new Exception($"API GET Failed: {ex.Message}", ex);
}
}
private async Task<T> PostJsonAsync<T>(string endpoint, object data)
{
try
{
var json = JsonSerializer.Serialize(data);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(_baseUrl + endpoint, content);
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
return JsonSerializer.Deserialize<T>(responseString, options);
}
catch (Exception ex)
{
throw new Exception($"API POST Failed: {ex.Message}", ex);
}
}
}
// --- DATA MODELS ---
public class AiResponse
{
public string Status { get; set; }
public string Task_Id { get; set; }
public string Instance_Id { get; set; }
public string Mode { get; set; }
public string Output { get; set; }
public string Error { get; set; }
}
public class InstanceState
{
public string last_seen { get; set; }
public string url { get; set; }
public int turn_count { get; set; }
public bool is_busy { get; set; }
}
public class AiSettings
{
public float? Temperature { get; set; }
public float? Top_P { get; set; }
public string Model { get; set; }
public string Thinking_Level { get; set; }
public string System_Instructions { get; set; }
public string Resolution { get; set; }
public bool? Url_Context { get; set; }
}
public class AiChatTurn
{
public string Role { get; set; }
public string Text { get; set; }
}
}

View File

@ -0,0 +1,280 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace AI.Client.SCL
{
/// <summary>
/// Represents the outcome of an SCL command execution.
/// </summary>
public class SclResult
{
/// <summary>
/// 0 for Success, any non-zero value for Error.
/// </summary>
public int StatusCode { get; set; }
/// <summary>
/// The output data or error message to return to the AI.
/// </summary>
public string Data { get; set; }
/// <summary>
/// Flag indicating if the response should be omitted entirely to save tokens.
/// </summary>
public bool OmitResponse { get; set; }
/// <summary>
/// Creates a successful result.
/// </summary>
public static SclResult Success(string data = "Success")
{
return new SclResult { StatusCode = 0, Data = data, OmitResponse = false };
}
/// <summary>
/// Creates an error result.
/// </summary>
public static SclResult Error(string errorMessage, int statusCode = 1)
{
return new SclResult { StatusCode = statusCode, Data = errorMessage, OmitResponse = false };
}
/// <summary>
/// Creates a result that instructs the processor to NOT send a response back to the AI.
/// </summary>
public static SclResult NoResponse()
{
return new SclResult { OmitResponse = true };
}
}
/// <summary>
/// The core engine for parsing and executing Synapse Command Language (SCL) instructions.
/// </summary>
public class SclProcessor
{
// Thread-safe dictionary to hold registered commands and their execution logic.
private readonly ConcurrentDictionary<string, Func<string[], Task<SclResult>>> _commandHandlers;
public SclProcessor()
{
_commandHandlers = new ConcurrentDictionary<string, Func<string[], Task<SclResult>>>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Registers a new command that the AI can invoke.
/// </summary>
/// <param name="commandId">The alphanumeric identifier for the command (e.g., "ls", "mouse_move").</param>
/// <param name="handler">An asynchronous function that takes an array of string parameters and returns an SclResult.</param>
public void RegisterCommand(string commandId, Func<string[], Task<SclResult>> handler)
{
if (string.IsNullOrWhiteSpace(commandId))
throw new ArgumentException("Command ID cannot be null or whitespace.", nameof(commandId));
if (commandId.Contains(" ") || commandId.Contains("[") || commandId.Contains("]") || commandId.Contains("|") || commandId.Contains("~") || commandId.Contains("^"))
throw new ArgumentException("Command ID contains invalid characters.", nameof(commandId));
_commandHandlers[commandId] = handler ?? throw new ArgumentNullException(nameof(handler));
}
/// <summary>
/// Removes a previously registered command.
/// </summary>
public bool UnregisterCommand(string commandId)
{
return _commandHandlers.TryRemove(commandId, out _);
}
/// <summary>
/// Parses the AI's text output, executes any found SCL commands, and returns the formatted SCL result string.
/// </summary>
/// <param name="aiText">The raw text output from the AI.</param>
/// <returns>A string containing the concatenated ^ result blocks to be sent back to the AI.</returns>
public async Task<string> ProcessTextAsync(string aiText)
{
if (string.IsNullOrWhiteSpace(aiText))
return string.Empty;
var parsedCommands = ParseCommands(aiText);
if (parsedCommands.Count == 0)
return string.Empty;
StringBuilder resultBuilder = new StringBuilder();
foreach (var cmd in parsedCommands)
{
SclResult executionResult;
if (_commandHandlers.TryGetValue(cmd.CommandId, out var handler))
{
try
{
// Execute the registered handler
executionResult = await handler(cmd.Parameters);
}
catch (Exception ex)
{
// Catch unhandled exceptions in the client code to prevent crashing the processor
executionResult = SclResult.Error($"Internal Execution Exception: {ex.Message}");
}
}
else
{
executionResult = SclResult.Error($"Unknown command: {cmd.CommandId}");
}
// If the command requested no response (e.g. background GUI app), skip appending it
if (executionResult.OmitResponse)
{
continue;
}
// Format the result back into SCL syntax: ^commandId[statusCode|escapedData]
string escapedData = EscapeForScl(executionResult.Data ?? string.Empty);
resultBuilder.Append($"^{cmd.CommandId}[{executionResult.StatusCode}|{escapedData}] ");
}
return resultBuilder.ToString().TrimEnd();
}
/// <summary>
/// Internal struct to hold parsed command data.
/// </summary>
private struct ParsedCommand
{
public string CommandId { get; }
public string[] Parameters { get; }
public ParsedCommand(string commandId, string[] parameters)
{
CommandId = commandId;
Parameters = parameters;
}
}
/// <summary>
/// A robust character-stepping state machine that extracts SCL commands while respecting escape characters, quotes, and nested brackets.
/// </summary>
private List<ParsedCommand> ParseCommands(string input)
{
var commands = new List<ParsedCommand>();
for (int i = 0; i < input.Length; i++)
{
// Look for the Action Trigger
if (input[i] == '~')
{
int bracketIdx = input.IndexOf('[', i);
if (bracketIdx == -1) continue; // Malformed, no opening bracket found
string commandId = input.Substring(i + 1, bracketIdx - i - 1).Trim();
if (string.IsNullOrEmpty(commandId)) continue; // Malformed, no command ID
List<string> parameters = new List<string>();
StringBuilder currentParam = new StringBuilder();
bool isEscaped = false;
bool isClosed = false;
int bracketDepth = 1; // We just passed the first '['
bool inDoubleQuotes = false;
bool inSingleQuotes = false;
int j = bracketIdx + 1;
for (; j < input.Length; j++)
{
char c = input[j];
if (isEscaped)
{
// Only consume the escape character if it's escaping an SCL control char.
// Otherwise, preserve the backslash (e.g., for Windows file paths like C:\Temp).
if (c == '[' || c == ']' || c == '|' || c == '\\')
{
currentParam.Append(c);
}
else
{
currentParam.Append('\\');
currentParam.Append(c);
}
isEscaped = false;
continue;
}
if (c == '\\')
{
isEscaped = true;
continue;
}
// Toggle quote states
if (c == '"' && !inSingleQuotes) inDoubleQuotes = !inDoubleQuotes;
if (c == '\'' && !inDoubleQuotes) inSingleQuotes = !inSingleQuotes;
bool inQuotes = inDoubleQuotes || inSingleQuotes;
if (!inQuotes && c == '[')
{
bracketDepth++;
currentParam.Append(c);
}
else if (!inQuotes && c == ']')
{
bracketDepth--;
if (bracketDepth == 0)
{
parameters.Add(currentParam.ToString());
isClosed = true;
break;
}
else
{
currentParam.Append(c);
}
}
else if (!inQuotes && bracketDepth == 1 && c == '|')
{
parameters.Add(currentParam.ToString());
currentParam.Clear();
}
else
{
currentParam.Append(c);
}
}
// Only register the command if the parameter block was properly closed
if (isClosed)
{
commands.Add(new ParsedCommand(commandId, parameters.ToArray()));
i = j; // Advance the outer loop past this parsed command
}
}
}
return commands;
}
/// <summary>
/// Escapes reserved SCL characters in the output data to ensure the AI parses the result correctly.
/// </summary>
private string EscapeForScl(string input)
{
if (string.IsNullOrEmpty(input)) return input;
StringBuilder sb = new StringBuilder(input.Length + 10);
foreach (char c in input)
{
if (c == '\\' || c == '|' || c == ']' || c == '[')
{
sb.Append('\\');
}
sb.Append(c);
}
return sb.ToString();
}
}
}

View File

@ -0,0 +1,158 @@
// File: SclPromptBuilder.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace AI.Client.SCL
{
/// <summary>
/// Represents a definition of an SCL command to be taught to the AI.
/// </summary>
public class SclCommandDefinition
{
/// <summary>
/// The alphanumeric identifier for the command (e.g., "ls", "wr").
/// </summary>
public string CommandId { get; set; }
/// <summary>
/// A brief description of what the command does.
/// </summary>
public string Description { get; set; }
/// <summary>
/// A list of parameter names expected by the command.
/// </summary>
public List<string> Parameters { get; set; }
public SclCommandDefinition(string commandId, string description, params string[] parameters)
{
CommandId = commandId;
Description = description;
Parameters = new List<string>();
if (parameters != null)
{
Parameters.AddRange(parameters);
}
}
}
/// <summary>
/// A utility class to programmatically generate the System Instructions prompt for the AI,
/// injecting custom SCL commands dynamically.
/// </summary>
public class SclPromptBuilder
{
private readonly List<SclCommandDefinition> _commands;
private string _agentRole;
public SclPromptBuilder()
{
_commands = new List<SclCommandDefinition>();
_agentRole = "You are a strict, machine-to-machine execution engine equipped with the Synapse Command Language (SCL). SCL allows you to interact directly with the user's local system via a client program that parses your output.";
}
/// <summary>
/// Overrides the default agent persona/role at the very beginning of the prompt.
/// </summary>
/// <param name="roleDescription">The custom role description.</param>
public void SetAgentRole(string roleDescription)
{
if (!string.IsNullOrWhiteSpace(roleDescription))
{
_agentRole = roleDescription;
}
}
/// <summary>
/// Registers a command definition to be included in the generated prompt.
/// </summary>
/// <param name="commandId">The command identifier (e.g., "ls").</param>
/// <param name="description">What the command does.</param>
/// <param name="parameters">The names of the parameters (e.g., "file_path", "content").</param>
public void AddCommand(string commandId, string description, params string[] parameters)
{
_commands.Add(new SclCommandDefinition(commandId, description, parameters));
}
/// <summary>
/// Generates the complete System Instructions prompt text.
/// </summary>
/// <returns>A formatted string ready to be sent to the AI as System Instructions.</returns>
public string GeneratePrompt()
{
StringBuilder sb = new StringBuilder();
// 1. Agent Role
sb.AppendLine(_agentRole);
sb.AppendLine();
// 2. Core SCL Protocol Rules
sb.AppendLine("### SCL Protocol Rules (CRITICAL)");
sb.AppendLine("You MUST use SCL to perform actions. SCL is designed to be extremely token-efficient.");
sb.AppendLine("ABSOLUTELY NO JSON, XML, MARKDOWN, OR RAW SHELL COMMANDS ARE ALLOWED. You must use the exact syntax below.");
sb.AppendLine();
sb.AppendLine("1. ISSUING COMMANDS (Action Trigger: ~)");
sb.AppendLine("Syntax: ~command_name[parameter1|parameter2]");
sb.AppendLine("* Commands start with a tilde (~).");
sb.AppendLine("* Parameters are enclosed in square brackets [ ].");
sb.AppendLine("* Multiple parameters are separated by a pipe (|).");
sb.AppendLine("* If a parameter contains a literal ], |, or \\, you must escape it with a backslash (e.g., \\], \\|, \\\\).");
sb.AppendLine();
sb.AppendLine("2. RECEIVING RESULTS (Result Trigger: ^)");
sb.AppendLine("The client program will reply to your commands in the next prompt using this syntax:");
sb.AppendLine("Syntax: ^command_name[status_code|output_data]");
sb.AppendLine("* Status 0 means success. Status 1 means error.");
sb.AppendLine("* You must wait for the ^ result before assuming a command succeeded, UNLESS the command launches a background/GUI application, in which case the client may omit the response to save tokens and end the interaction.");
sb.AppendLine();
// 3. Dynamic Available Commands
sb.AppendLine("### Available Client Commands");
sb.AppendLine("(Note: The client program currently supports the following commands. You may use them at any time to fulfill the user's requests.)");
sb.AppendLine();
if (_commands.Count == 0)
{
sb.AppendLine("* (No commands are currently registered by the client program.)");
}
else
{
foreach (var cmd in _commands)
{
string paramString = string.Join("|", cmd.Parameters);
sb.AppendLine($"* ~{cmd.CommandId}[{paramString}] : {cmd.Description}");
}
}
sb.AppendLine();
// 4. Execution Guidelines & Strict Constraints
sb.AppendLine("### Execution Guidelines & CRITICAL CONSTRAINTS");
sb.AppendLine("* ALL INPUT PROMPT WILL BE ENCODED AS BASE64 STRINGS: Any input prompt you receive will be encoded as a base64 string. Make sure you decode the base64 string before processing it.");
sb.AppendLine("* STOP AFTER ISSUING A COMMAND: If you issue a command that expects an output result, you MUST STOP generating text and wait for the program to reply with the `^` result.");
sb.AppendLine("* NO RAW COMMANDS: NEVER output raw shell commands like `start notepad.exe` or `EXECUTE(notepad)`. You MUST wrap them in the appropriate SCL command, e.g., `~cmd[start notepad.exe]`.");
sb.AppendLine("* NO MARKDOWN: NEVER wrap your SCL commands in backticks (`) or markdown code blocks (```).");
sb.AppendLine("* NO CONVERSATION: You may NOT output conversational text alongside an SCL command to explain your thought process to the user. ONLY output the SCL command by itself and nothing else.");
sb.AppendLine("* Do NOT chain multiple commands together (e.g., do not issue an `ls` and an `rd` in the same response). Issue the first command, wait for the result, and then issue the next.");
sb.AppendLine("* If a command fails (returns status 1), analyze the error data provided in the result and attempt to fix the issue or notify the user.");
sb.AppendLine();
// 5. Example Scenario
sb.AppendLine("### Example Scenario");
sb.AppendLine("User: \"Open notepad\"");
sb.AppendLine("WRONG: start notepad.exe");
sb.AppendLine("WRONG: ```~cmd[start notepad.exe]```");
sb.AppendLine("WRONG: EXECUTE(notepad.exe)");
sb.AppendLine("CORRECT: ~cmd[start notepad.exe]");
sb.AppendLine();
sb.AppendLine("User: \"Check what is in C:\\Temp and delete old.txt\"");
sb.AppendLine("You: ~cmd[dir C:\\Temp]");
sb.AppendLine("Program: ^cmd[0|old.txt\\nnew.txt\\nconfig.ini]");
sb.AppendLine("You: ~cmd[del C:\\Temp\\old.txt]");
sb.AppendLine("Program: ^cmd[0|Success]");
sb.AppendLine("You: The file old.txt has been successfully deleted.");
return sb.ToString();
}
}
}

15
AI_State_Machines.slnx Normal file
View File

@ -0,0 +1,15 @@
<Solution>
<Configurations>
<Platform Name="Any CPU" />
<Platform Name="x64" />
</Configurations>
<Project Path="AI C2 Server/AI C2 Server.csproj">
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="AI.Client/AI.Client.csproj">
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="VoiceAssistantPoC_Win/VoiceAssistantPoC_Win.csproj" Id="cfcdcaba-1dd9-4d58-93ad-218ddc587189">
<Platform Solution="*|x64" Project="x64" />
</Project>
</Solution>

View File

@ -208,7 +208,7 @@ If you develop a new program, and you want it to be of the greatest possible use
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
Synapse-OS-Assistant-And-AI-C2-Server AI-State-Machines
Copyright (C) 2026 mastercodeon Copyright (C) 2026 mastercodeon
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
@ -221,7 +221,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
Synapse-OS-Assistant-And-AI-C2-Server Copyright (C) 2026 mastercodeon AI-State-Machines Copyright (C) 2026 mastercodeon
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.

View File

@ -1,3 +1,57 @@
# Synapse-OS-Assistant-And-AI-C2-Server # Synapse OS Assistant & AI C2 Server
Proof of Concept for using AI as a state machine Welcome to the Synapse OS Assistant project. This repository contains a proof-of-concept system that transforms standard web-based Large Language Models (LLMs) into deterministic, voice-activated operating system controllers.
By utilizing the custom **Synapse Command Language (SCL)** and a unified browser harness, this project allows you to control your Windows PC using models like Google Gemini, DeepSeek, and Google AI Search—**without needing any paid API keys.**
## Features
* **Synapse Command Language (SCL):** A highly efficient, token-saving syntax that forces LLMs to act as deterministic state machines rather than conversational agents.
* **Unified Web Harness:** A Tampermonkey script that puppeteers web-based AI chats, turning them into a local REST API.
* **Offline Wake-Word & STT:** Uses Vosk for fast, offline voice recognition ("Computer, do X").
* **Zero-Cost OS Integration:** Achieves deep OS control (executing shell commands, opening apps, reading files) using free web AI interfaces.
## Prerequisites
* Windows OS (WinForms is used for the UI).
* .NET 10.0 (or higher) SDK.
* A modern web browser with the [Tampermonkey](https://www.tampermonkey.net/) extension installed.
* **Important Browser Note:** If you intend to use Google AI Search, you **MUST use Mozilla Firefox**. Google Chrome restricts extension execution on `chrome://` pages and the default Google Search homepage.
## Setup Instructions
### 1. Install the Vosk Voice Model
The Voice Assistant PoC requires an offline voice model to function.
1. Download the `vosk-model-small-en-us-0.15` model from the [Vosk Models page](https://alphacephei.com/vosk/models).
2. Extract the downloaded `.zip` file.
3. Rename the extracted folder to exactly `model`.
4. Place the `model` folder directly into the build output directory of the `VoiceAssistantPoC_Win` project (e.g., `VoiceAssistantPoC_Win/bin/Debug/net8.0-windows/`).
### 2. Install the Tampermonkey Harness
1. Open your browser and click the Tampermonkey extension icon -> **Create a new script**.
2. Copy the entire contents of `GoogleAI_Search_Deepseek_ChatGPT_UnifiedHarness.js` from this repository.
3. Paste it into the Tampermonkey editor, overwriting the default template.
4. Go to File -> **Save** (or press Ctrl+S).
### 3. Build and Run the Servers
1. Open the solution in Visual Studio.
2. Build the solution to restore NuGet packages (such as `NAudio` and `Vosk`).
3. Run the **AIStudio.C2Server** project first. You should see the server console open and state that it is listening on port `8080`.
4. Open your web browser (Firefox recommended) and navigate to one of the supported AI platforms:
* `https://gemini.google.com/`
* `https://chat.deepseek.com/`
* `https://google.com/ai` (Will not work with the Tampermonkey Harness in Google Chrome!)
5. Look at the C2 Server console; you should see a message indicating that a new instance has connected (e.g., `[OP-CENTER] New instance registered: inst_gem_...`).
### 4. Start the Voice Assistant
1. Run the **VoiceAssistantPoC_Win** project.
2. In the bottom panel, select your active browser session from the dropdown menu.
3. Click **Re Init Session**. This will inject the SCL System Instructions into the browser chat, preparing the AI to act as a state machine.
4. Ensure the "Voice Mode" checkbox is checked.
5. Speak into your microphone: *"Computer, open notepad."*
6. Watch as the AI processes the command, issues the SCL syntax `~cmd[start notepad.exe]`, and the client executes it on your machine!
## Limitations
* **ChatGPT is Unsupported:** Due to aggressive RLHF (Reinforcement Learning from Human Feedback), the web version of ChatGPT refuses to strictly adhere to the SCL syntax and will often output conversational filler or markdown, breaking the state machine loop. Please use Gemini or DeepSeek for the best results.
* **Browser Focus:** The Tampermonkey script requires the browser tab to be active/visible in some browsers to ensure DOM updates and JavaScript intervals fire reliably.
## Disclaimer
This project allows an AI to execute arbitrary shell commands on your local machine. It is provided as a Proof of Concept for educational and research purposes only. Do not run this software in a production environment or with elevated (Administrator) privileges unless you fully understand the risks.

226
VoiceAssistantPoC_Win/MainForm.Designer.cs generated Normal file
View File

@ -0,0 +1,226 @@
// File: MainForm.Designer.cs
namespace VoiceAssistantPoC_Win
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
rtbLogs = new RichTextBox();
pnlBottom = new Panel();
cmbSessions = new ComboBox();
lblSession = new Label();
btnRefreshSessions = new Button();
reInitSessionBtn = new Button();
lblLiveTranscript = new Label();
txtManualInput = new TextBox();
btnSend = new Button();
chkVoiceMode = new CheckBox();
pnlBottom.SuspendLayout();
SuspendLayout();
//
// rtbLogs
//
rtbLogs.BackColor = Color.FromArgb(30, 30, 30);
rtbLogs.BorderStyle = BorderStyle.None;
rtbLogs.Dock = DockStyle.Fill;
rtbLogs.Font = new Font("Consolas", 10F, FontStyle.Regular, GraphicsUnit.Point, 0);
rtbLogs.ForeColor = Color.LightGray;
rtbLogs.Location = new Point(0, 0);
rtbLogs.Margin = new Padding(4, 3, 4, 3);
rtbLogs.Name = "rtbLogs";
rtbLogs.ReadOnly = true;
rtbLogs.Size = new Size(915, 532);
rtbLogs.TabIndex = 0;
rtbLogs.Text = "";
//
// pnlBottom
//
pnlBottom.BackColor = Color.FromArgb(45, 45, 48);
pnlBottom.Controls.Add(cmbSessions);
pnlBottom.Controls.Add(lblSession);
pnlBottom.Controls.Add(btnRefreshSessions);
pnlBottom.Controls.Add(reInitSessionBtn);
pnlBottom.Controls.Add(lblLiveTranscript);
pnlBottom.Controls.Add(txtManualInput);
pnlBottom.Controls.Add(btnSend);
pnlBottom.Controls.Add(chkVoiceMode);
pnlBottom.Dock = DockStyle.Bottom;
pnlBottom.Location = new Point(0, 532);
pnlBottom.Margin = new Padding(4, 3, 4, 3);
pnlBottom.Name = "pnlBottom";
pnlBottom.Size = new Size(915, 115);
pnlBottom.TabIndex = 1;
//
// cmbSessions
//
cmbSessions.DropDownStyle = ComboBoxStyle.DropDownList;
cmbSessions.FormattingEnabled = true;
cmbSessions.Location = new Point(120, 10);
cmbSessions.Name = "cmbSessions";
cmbSessions.Size = new Size(300, 23);
cmbSessions.TabIndex = 5;
cmbSessions.SelectedIndexChanged += cmbSessions_SelectedIndexChanged;
//
// lblSession
//
lblSession.AutoSize = true;
lblSession.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point, 0);
lblSession.ForeColor = Color.White;
lblSession.Location = new Point(14, 13);
lblSession.Name = "lblSession";
lblSession.Size = new Size(90, 15);
lblSession.TabIndex = 6;
lblSession.Text = "Active Session:";
//
// btnRefreshSessions
//
btnRefreshSessions.BackColor = Color.FromArgb(0, 122, 204);
btnRefreshSessions.FlatAppearance.BorderSize = 0;
btnRefreshSessions.FlatStyle = FlatStyle.Flat;
btnRefreshSessions.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point, 0);
btnRefreshSessions.ForeColor = Color.White;
btnRefreshSessions.Location = new Point(430, 9);
btnRefreshSessions.Name = "btnRefreshSessions";
btnRefreshSessions.Size = new Size(75, 25);
btnRefreshSessions.TabIndex = 7;
btnRefreshSessions.Text = "Refresh";
btnRefreshSessions.UseVisualStyleBackColor = false;
btnRefreshSessions.Click += btnRefreshSessions_Click;
//
// reInitSessionBtn
//
reInitSessionBtn.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
reInitSessionBtn.BackColor = Color.FromArgb(0, 122, 204);
reInitSessionBtn.FlatAppearance.BorderSize = 0;
reInitSessionBtn.FlatStyle = FlatStyle.Flat;
reInitSessionBtn.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point, 0);
reInitSessionBtn.ForeColor = Color.White;
reInitSessionBtn.Location = new Point(788, 70);
reInitSessionBtn.Margin = new Padding(4, 3, 4, 3);
reInitSessionBtn.Name = "reInitSessionBtn";
reInitSessionBtn.Size = new Size(99, 25);
reInitSessionBtn.TabIndex = 4;
reInitSessionBtn.Text = "Re Init Session";
reInitSessionBtn.UseVisualStyleBackColor = false;
reInitSessionBtn.Click += reInitSessionBtn_Click;
//
// lblLiveTranscript
//
lblLiveTranscript.AutoSize = true;
lblLiveTranscript.Font = new Font("Consolas", 11F, FontStyle.Bold, GraphicsUnit.Point, 0);
lblLiveTranscript.ForeColor = Color.Cyan;
lblLiveTranscript.Location = new Point(14, 40);
lblLiveTranscript.Margin = new Padding(4, 0, 4, 0);
lblLiveTranscript.Name = "lblLiveTranscript";
lblLiveTranscript.Size = new Size(96, 18);
lblLiveTranscript.TabIndex = 3;
lblLiveTranscript.Text = "[Live]: ...";
//
// txtManualInput
//
txtManualInput.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
txtManualInput.BackColor = Color.FromArgb(60, 60, 60);
txtManualInput.BorderStyle = BorderStyle.FixedSingle;
txtManualInput.Enabled = false;
txtManualInput.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point, 0);
txtManualInput.ForeColor = Color.White;
txtManualInput.Location = new Point(14, 70);
txtManualInput.Margin = new Padding(4, 3, 4, 3);
txtManualInput.Name = "txtManualInput";
txtManualInput.Size = new Size(659, 25);
txtManualInput.TabIndex = 2;
txtManualInput.KeyDown += txtManualInput_KeyDown;
//
// btnSend
//
btnSend.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
btnSend.BackColor = Color.FromArgb(0, 122, 204);
btnSend.Enabled = false;
btnSend.FlatAppearance.BorderSize = 0;
btnSend.FlatStyle = FlatStyle.Flat;
btnSend.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point, 0);
btnSend.ForeColor = Color.White;
btnSend.Location = new Point(681, 70);
btnSend.Margin = new Padding(4, 3, 4, 3);
btnSend.Name = "btnSend";
btnSend.Size = new Size(99, 25);
btnSend.TabIndex = 1;
btnSend.Text = "Send";
btnSend.UseVisualStyleBackColor = false;
btnSend.Click += btnSend_Click;
//
// chkVoiceMode
//
chkVoiceMode.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
chkVoiceMode.AutoSize = true;
chkVoiceMode.Checked = true;
chkVoiceMode.CheckState = CheckState.Checked;
chkVoiceMode.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point, 0);
chkVoiceMode.ForeColor = Color.White;
chkVoiceMode.Location = new Point(788, 40);
chkVoiceMode.Margin = new Padding(4, 3, 4, 3);
chkVoiceMode.Name = "chkVoiceMode";
chkVoiceMode.Size = new Size(100, 23);
chkVoiceMode.TabIndex = 0;
chkVoiceMode.Text = "Voice Mode";
chkVoiceMode.UseVisualStyleBackColor = true;
chkVoiceMode.CheckedChanged += chkVoiceMode_CheckedChanged;
//
// MainForm
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(915, 647);
Controls.Add(rtbLogs);
Controls.Add(pnlBottom);
Icon = (Icon)resources.GetObject("$this.Icon");
Margin = new Padding(4, 3, 4, 3);
Name = "MainForm";
Text = "Synapse OS Assistant PoC";
FormClosing += MainForm_FormClosing;
Load += MainForm_Load;
pnlBottom.ResumeLayout(false);
pnlBottom.PerformLayout();
ResumeLayout(false);
}
#endregion
private System.Windows.Forms.RichTextBox rtbLogs;
private System.Windows.Forms.Panel pnlBottom;
private System.Windows.Forms.TextBox txtManualInput;
private System.Windows.Forms.Button btnSend;
private System.Windows.Forms.CheckBox chkVoiceMode;
private System.Windows.Forms.Label lblLiveTranscript;
private System.Windows.Forms.Button reInitSessionBtn;
private System.Windows.Forms.ComboBox cmbSessions;
private System.Windows.Forms.Label lblSession;
private System.Windows.Forms.Button btnRefreshSessions;
}
}

View File

@ -0,0 +1,602 @@
// File: MainForm.cs
using AI.Client;
using AI.Client.SCL;
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows.Forms;
using Vosk;
using static System.Net.Mime.MediaTypeNames;
namespace VoiceAssistantPoC_Win
{
public partial class MainForm : Form
{
private AiClient _aiClient;
private SclProcessor _sclProcessor;
private string _targetInstanceId;
private bool _isProcessing = false;
private bool _waitingForCommand = false; // Handles the "Computer" edge case
private bool _isOpenAI_Instance = false;
private bool _isFetchingSessions = false;
// Session Tracking
private Dictionary<string, bool> _initializedSessions = new Dictionary<string, bool>();
private Dictionary<string, string> _sessionPlatforms = new Dictionary<string, string>();
// Vosk & NAudio components
private Model _voskModel;
private VoskRecognizer _recognizer;
private WaveInEvent _waveIn;
private class SessionItem
{
public string Id { get; set; }
public string DisplayText { get; set; }
public override string ToString() => DisplayText;
}
public MainForm()
{
InitializeComponent();
}
private async void MainForm_Load(object sender, EventArgs e)
{
LogToUI("=== Synapse OS Assistant PoC (WinForms Edition) ===", Color.White);
lblLiveTranscript.Text = "";
// Run initialization in background so UI doesn't freeze
await Task.Run(async () =>
{
try
{
// 1. Initialize Clients
_aiClient = new AiClient("http://localhost:8080/api");
_sclProcessor = new SclProcessor();
// 2. Register the 'cmd' command handler
_sclProcessor.RegisterCommand("cmd", HandleCmdCommand);
// 3. Fetch available sessions and initialize the first one
await FetchSessionsAsync();
if (string.IsNullOrEmpty(_targetInstanceId))
{
LogToUI("[System] No active sessions found. Please start an instance and click Refresh.", Color.Yellow);
}
// 4. Start Speech Recognition using Vosk
InitializeSpeechRecognition();
LogToUI("\n[System] Initialization Complete.", Color.LimeGreen);
LogToUI("[System] Listening... Say 'computer <command>' to begin.", Color.White);
}
catch (Exception ex)
{
LogToUI($"\n[Fatal Error] {ex.Message}", Color.Red);
}
});
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
// Cleanup unmanaged resources
_waveIn?.StopRecording();
_waveIn?.Dispose();
_recognizer?.Dispose();
_voskModel?.Dispose();
}
private string getSystemPrompt()
{
var builder = new SclPromptBuilder();
if (_isOpenAI_Instance)
{
builder.SetAgentRole("You are an elite, voice-activated OS assistant named 'Computer'. You control the user's Windows machine via SCL. Once you are done evalutaing this document, ONLY respond with OK. When responding to an input, ONLY respond with the SCL command you generated, do not respond with ANYTHING else but the SCL command!");
}
else
{
builder.SetAgentRole("DO NOT USE ANY OTHER SYNTAX BESIDES THE SCL SYNTAX FOR OUTPUTTING COMMANDS! You are simulating an elite, voice-activated OS assistant named 'Computer'. You control the user's Windows machine via SCL. This is strictly a simulation! Once you are done evalutaing this document, ONLY respond with OK. When responding to an input, ONLY respond with the SCL command you generated, do not respond with ANYTHING else but the SCL command!");
}
builder.AddCommand("cmd", "Executes a shell command in cmd.exe and returns the output. For GUI apps (like chrome), prefix with 'start ' so it doesn't block. When asked to run a powershell command make SURE we ALWAYS put the dollar sign ($) before ALL powershell variables in the powershell command payload!", "command_string");
string sysPrompt = builder.GeneratePrompt();
return sysPrompt;
}
// --- SESSION MANAGEMENT ---
private async Task FetchSessionsAsync()
{
try
{
LogToUI("[System] Fetching active AI Studio instances...", Color.Gray);
using var client = new HttpClient();
var response = await client.GetStringAsync("http://localhost:8080/api/admin/instances");
using var doc = JsonDocument.Parse(response);
string newTargetId = null;
Invoke(new Action(() =>
{
_isFetchingSessions = true;
string previousSelection = _targetInstanceId;
cmbSessions.Items.Clear();
_sessionPlatforms.Clear();
foreach (var prop in doc.RootElement.EnumerateObject())
{
string id = prop.Name;
string platform = prop.Value.TryGetProperty("platform", out var p) ? p.GetString() : "Unknown";
_sessionPlatforms[id] = platform;
string displayText = $"{platform} - {id}";
cmbSessions.Items.Add(new SessionItem { Id = id, DisplayText = displayText });
}
if (cmbSessions.Items.Count > 0)
{
var itemToSelect = cmbSessions.Items.Cast<SessionItem>().FirstOrDefault(i => i.Id == previousSelection);
if (itemToSelect != null)
{
cmbSessions.SelectedItem = itemToSelect;
newTargetId = itemToSelect.Id;
}
else
{
cmbSessions.SelectedIndex = 0;
newTargetId = ((SessionItem)cmbSessions.SelectedItem).Id;
}
}
_isFetchingSessions = false;
}));
if (newTargetId != null && newTargetId != _targetInstanceId)
{
_targetInstanceId = newTargetId;
if (!_initializedSessions.ContainsKey(_targetInstanceId) || !_initializedSessions[_targetInstanceId])
{
SetUiState(true);
await InitializeAiSession(_targetInstanceId);
SetUiState(false);
}
}
}
catch (Exception ex)
{
LogToUI($"[Error] Failed to fetch sessions: {ex.Message}", Color.Red);
}
}
private async Task InitializeAiSession(string instanceId)
{
LogToUI($"[System] Initializing Session: {instanceId}...", Color.Gray);
bool isOpenAI = instanceId.Contains("openai") || (_sessionPlatforms.ContainsKey(instanceId) && _sessionPlatforms[instanceId] == "OpenAI API");
_isOpenAI_Instance = isOpenAI;
bool isGeminiOrChatGPT = (instanceId.Contains("gem") || (_sessionPlatforms.ContainsKey(instanceId) && _sessionPlatforms[instanceId] == "Google Gemini")) || (instanceId.Contains("cgpt") || (_sessionPlatforms.ContainsKey(instanceId) && _sessionPlatforms[instanceId] == "ChatGPT"));
if (isGeminiOrChatGPT)
{
await _aiClient.NewChatAsync(instanceId);
// Wait for the UI to stabilize after the reset to prevent Angular state race conditions
await Task.Delay(1000);
LogToUI("[System] Generating and injecting SCL System Instructions...", Color.Gray);
await _aiClient.SendPromptAsync(Convert.ToBase64String(Encoding.ASCII.GetBytes(getSystemPrompt())), instanceId);
LogToUI("[System] AI Session Initialized and Ready.", Color.LimeGreen);
}
else if (isOpenAI)
{
LogToUI("[System] Generating and injecting SCL System Instructions...", Color.Gray);
await _aiClient.SetOpenAISystemInstructionsAsync(getSystemPrompt());
LogToUI("[System] AI Session Initialized and Ready.", Color.LimeGreen);
}
else
{
await _aiClient.NewChatAsync(instanceId);
// Wait for the UI to stabilize after the reset to prevent Angular state race conditions
await Task.Delay(1000);
LogToUI("[System] Generating and injecting SCL System Instructions...", Color.Gray);
await _aiClient.SendPromptAsync(getSystemPrompt(), instanceId);
LogToUI("[System] AI Session Initialized and Ready.", Color.LimeGreen);
}
_initializedSessions[instanceId] = true;
}
private void InitializeSpeechRecognition()
{
string modelPath = "model";
if (!Directory.Exists(modelPath))
{
throw new Exception($"Vosk model folder not found at: {Path.GetFullPath(modelPath)}\nPlease download 'vosk-model-small-en-us-0.15', extract it, and rename the folder to 'model' in your build output directory.");
}
LogToUI("[System] Loading Vosk Model (this may take a moment)...", Color.Gray);
Vosk.Vosk.SetLogLevel(-1);
_voskModel = new Model(modelPath);
_recognizer = new VoskRecognizer(_voskModel, 16000.0f);
_waveIn = new WaveInEvent();
_waveIn.DeviceNumber = 0;
_waveIn.WaveFormat = new WaveFormat(16000, 1);
_waveIn.DataAvailable += WaveInOnDataAvailable;
if (chkVoiceMode.Checked)
{
_waveIn.StartRecording();
LogToUI("[System] Microphone initialized successfully.", Color.LimeGreen);
}
}
// --- VOSK AUDIO PROCESSING ---
private void WaveInOnDataAvailable(object sender, WaveInEventArgs e)
{
if (_isProcessing) return;
if (_recognizer.AcceptWaveform(e.Buffer, e.BytesRecorded))
{
string jsonResult = _recognizer.Result();
ProcessVoskResult(jsonResult, isFinal: true);
}
else
{
string jsonPartial = _recognizer.PartialResult();
ProcessVoskResult(jsonPartial, isFinal: false);
}
}
private void ProcessVoskResult(string json, bool isFinal)
{
try
{
using (JsonDocument doc = JsonDocument.Parse(json))
{
if (isFinal)
{
UpdateLiveTranscript(""); // Clear live transcript
string text = doc.RootElement.GetProperty("text").GetString();
if (string.IsNullOrWhiteSpace(text)) return;
LogToUI($"[Mic Debug] Finalized: '{text}'", Color.DarkGray);
if (_isProcessing) return;
// EDGE CASE FIX: If we were waiting for a command because the last batch was just "computer"
if (_waitingForCommand)
{
_waitingForCommand = false;
string finalText = text;
if (!_isOpenAI_Instance)
{
finalText = Convert.ToBase64String(Encoding.ASCII.GetBytes(text));
}
ExecuteCommand(finalText);
return;
}
// Check for wake word
if (text == "computer")
{
_waitingForCommand = true;
LogToUI("[System] Wake word detected. Listening for command...", Color.Cyan);
}
else if (text.StartsWith("computer ", StringComparison.OrdinalIgnoreCase))
{
string commandText = text.Substring("computer".Length).Trim();
if (!string.IsNullOrWhiteSpace(commandText))
{
string finalText = text;
if (!_isOpenAI_Instance)
{
finalText = Convert.ToBase64String(Encoding.ASCII.GetBytes(text));
}
ExecuteCommand(finalText);
}
}
}
else
{
string partial = doc.RootElement.GetProperty("partial").GetString();
if (!string.IsNullOrWhiteSpace(partial))
{
UpdateLiveTranscript($"[Live]: {partial}");
}
}
}
}
catch (Exception ex)
{
LogToUI($"[Error] Failed to parse Vosk JSON: {ex.Message}", Color.Red);
}
}
// --- UI EVENT HANDLERS ---
private async void cmbSessions_SelectedIndexChanged(object sender, EventArgs e)
{
if (_isFetchingSessions) return;
var selected = cmbSessions.SelectedItem as SessionItem;
if (selected == null) return;
if (_targetInstanceId == selected.Id) return;
_targetInstanceId = selected.Id;
if (!_initializedSessions.ContainsKey(_targetInstanceId) || !_initializedSessions[_targetInstanceId])
{
SetUiState(true);
await InitializeAiSession(_targetInstanceId);
SetUiState(false);
}
else
{
_isOpenAI_Instance = _targetInstanceId.Contains("openai") || (_sessionPlatforms.ContainsKey(_targetInstanceId) && _sessionPlatforms[_targetInstanceId] == "OpenAI API");
LogToUI($"[System] Switched to initialized session: {_targetInstanceId}", Color.LimeGreen);
}
}
private async void btnRefreshSessions_Click(object sender, EventArgs e)
{
SetUiState(true);
await FetchSessionsAsync();
SetUiState(false);
}
private void chkVoiceMode_CheckedChanged(object sender, EventArgs e)
{
bool isVoiceMode = chkVoiceMode.Checked;
if (!_isProcessing)
{
txtManualInput.Enabled = !isVoiceMode;
btnSend.Enabled = !isVoiceMode;
}
if (isVoiceMode)
{
_waveIn?.StartRecording();
LogToUI("[System] Switched to Voice Mode. Microphone active.", Color.Cyan);
}
else
{
_waveIn?.StopRecording();
UpdateLiveTranscript("");
_waitingForCommand = false;
LogToUI("[System] Switched to Text Mode. Microphone paused.", Color.Yellow);
}
}
private void btnSend_Click(object sender, EventArgs e)
{
string input = txtManualInput.Text.Trim();
if (!string.IsNullOrWhiteSpace(input) && !_isProcessing)
{
txtManualInput.Clear();
string finalText = input;
if (!_isOpenAI_Instance)
{
finalText = Convert.ToBase64String(Encoding.ASCII.GetBytes(input));
}
ExecuteCommand(finalText);
}
}
private async void reInitSessionBtn_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(_targetInstanceId)) return;
SetUiState(true);
await InitializeAiSession(_targetInstanceId);
SetUiState(false);
}
private void txtManualInput_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
e.SuppressKeyPress = true;
btnSend.PerformClick();
}
}
// --- CORE EXECUTION LOGIC ---
private void ExecuteCommand(string commandText)
{
if (string.IsNullOrEmpty(_targetInstanceId))
{
LogToUI("[System] No active session selected!", Color.Red);
return;
}
LogToUI($"\n[User] {commandText}", Color.White);
SetUiState(true);
// Offload to background task to keep UI responsive
_ = Task.Run(async () =>
{
await ProcessInteractionLoop(commandText);
SetUiState(false);
LogToUI("\n[System] Ready for next command.", Color.LimeGreen);
});
}
private async Task ProcessInteractionLoop(string initialInput)
{
string currentInput = initialInput;
while (!string.IsNullOrEmpty(currentInput))
{
try
{
LogToUI("[System] Sending to AI...", Color.Gray);
var response = await _aiClient.SendPromptAsync(currentInput, _targetInstanceId);
string aiText = response.Output.Replace(@"AI responses may include mistakes. For financial advice, consult a professional. [Learn more](https://support.google.com/websearch?p=aimode)", string.Empty);
LogToUI($"\n[AI] {aiText}\n", Color.Orange);
string sclResult = await _sclProcessor.ProcessTextAsync(aiText);
if (!string.IsNullOrEmpty(sclResult))
{
LogToUI($"[System] SCL Execution Result: {sclResult}", Color.Cyan);
currentInput = sclResult;
}
else
{
currentInput = null;
}
}
catch (Exception ex)
{
LogToUI($"[Error] Interaction loop failed: {ex.Message}", Color.Red);
break;
}
}
}
private async Task<SclResult> HandleCmdCommand(string[] args)
{
if (args == null || args.Length == 0)
return SclResult.Error("No command string provided.");
// Reconstruct the command string in case the AI used an unescaped, unquoted pipe '|'
// which caused the parser to split the command into multiple arguments.
string commandString = string.Join("|", args);
LogToUI($"[SCL Engine] Executing shell command: {commandString}", Color.Magenta);
try
{
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/c {commandString}",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using (Process process = new Process { StartInfo = psi })
{
process.Start();
Task<string> readOutTask = process.StandardOutput.ReadToEndAsync();
Task<string> readErrTask = process.StandardError.ReadToEndAsync();
bool exited = process.WaitForExit(15000);
if (!exited)
{
process.Kill();
return SclResult.Error("Command timed out after 15 seconds.");
}
string output = await readOutTask;
string error = await readErrTask;
if (process.ExitCode != 0)
{
return SclResult.Error($"Exit Code {process.ExitCode}. Error: {error.Trim()}");
}
if (string.IsNullOrWhiteSpace(output))
{
return SclResult.NoResponse();
}
return SclResult.Success(output.Trim());
}
}
catch (Exception ex)
{
return SclResult.Error($"Exception executing command: {ex.Message}");
}
}
// --- THREAD-SAFE UI HELPERS ---
private void SetUiState(bool isProcessing)
{
if (InvokeRequired)
{
Invoke(new Action(() => SetUiState(isProcessing)));
return;
}
_isProcessing = isProcessing;
// Disable session switching controls while processing
cmbSessions.Enabled = !isProcessing;
btnRefreshSessions.Enabled = !isProcessing;
reInitSessionBtn.Enabled = !isProcessing;
if (!chkVoiceMode.Checked)
{
txtManualInput.Enabled = !isProcessing;
btnSend.Enabled = !isProcessing;
}
else
{
txtManualInput.Enabled = false;
btnSend.Enabled = false;
}
}
private void LogToUI(string message, Color color)
{
if (InvokeRequired)
{
Invoke(new Action(() => LogToUI(message, color)));
return;
}
rtbLogs.SelectionStart = rtbLogs.TextLength;
rtbLogs.SelectionLength = 0;
rtbLogs.SelectionColor = color;
rtbLogs.AppendText(message + Environment.NewLine);
rtbLogs.SelectionColor = rtbLogs.ForeColor;
rtbLogs.ScrollToCaret();
}
private void UpdateLiveTranscript(string text)
{
if (InvokeRequired)
{
Invoke(new Action(() => UpdateLiveTranscript(text)));
return;
}
lblLiveTranscript.Text = text;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
namespace VoiceAssistantPoC_Win
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.SetColorMode(SystemColorMode.Dark);
Application.Run(new MainForm());
}
}
}

View File

@ -0,0 +1,71 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<Platforms>AnyCPU;x64</Platforms>
<ApplicationIcon>app.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Content Include="app.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NAudio" Version="2.3.0" />
<PackageReference Include="Vosk" Version="0.3.38" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AI.Client\AI.Client.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="model\am\final.mdl">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="model\conf\mfcc.conf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="model\conf\model.conf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="model\graph\disambig_tid.int">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="model\graph\Gr.fst">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="model\graph\HCLr.fst">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="model\graph\phones\word_boundary.int">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="model\ivector\final.dubm">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="model\ivector\final.ie">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="model\ivector\final.mat">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="model\ivector\global_cmvn.stats">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="model\ivector\online_cmvn.conf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="model\ivector\splice.conf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="model\README">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

View File

@ -0,0 +1,9 @@
US English model for mobile Vosk applications
Copyright 2020 Alpha Cephei Inc
Accuracy: 10.38 (tedlium test) 9.85 (librispeech test-clean)
Speed: 0.11xRT (desktop)
Latency: 0.15s (right context)

Binary file not shown.

View File

@ -0,0 +1,7 @@
--sample-frequency=16000
--use-energy=false
--num-mel-bins=40
--num-ceps=40
--low-freq=20
--high-freq=7600
--allow-downsample=true

View File

@ -0,0 +1,10 @@
--min-active=200
--max-active=3000
--beam=10.0
--lattice-beam=2.0
--acoustic-scale=1.0
--frame-subsampling-factor=3
--endpoint.silence-phones=1:2:3:4:5:6:7:8:9:10
--endpoint.rule2.min-trailing-silence=0.5
--endpoint.rule3.min-trailing-silence=0.75
--endpoint.rule4.min-trailing-silence=1.0

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,17 @@
10015
10016
10017
10018
10019
10020
10021
10022
10023
10024
10025
10026
10027
10028
10029
10030
10031

View File

@ -0,0 +1,166 @@
1 nonword
2 begin
3 end
4 internal
5 singleton
6 nonword
7 begin
8 end
9 internal
10 singleton
11 begin
12 end
13 internal
14 singleton
15 begin
16 end
17 internal
18 singleton
19 begin
20 end
21 internal
22 singleton
23 begin
24 end
25 internal
26 singleton
27 begin
28 end
29 internal
30 singleton
31 begin
32 end
33 internal
34 singleton
35 begin
36 end
37 internal
38 singleton
39 begin
40 end
41 internal
42 singleton
43 begin
44 end
45 internal
46 singleton
47 begin
48 end
49 internal
50 singleton
51 begin
52 end
53 internal
54 singleton
55 begin
56 end
57 internal
58 singleton
59 begin
60 end
61 internal
62 singleton
63 begin
64 end
65 internal
66 singleton
67 begin
68 end
69 internal
70 singleton
71 begin
72 end
73 internal
74 singleton
75 begin
76 end
77 internal
78 singleton
79 begin
80 end
81 internal
82 singleton
83 begin
84 end
85 internal
86 singleton
87 begin
88 end
89 internal
90 singleton
91 begin
92 end
93 internal
94 singleton
95 begin
96 end
97 internal
98 singleton
99 begin
100 end
101 internal
102 singleton
103 begin
104 end
105 internal
106 singleton
107 begin
108 end
109 internal
110 singleton
111 begin
112 end
113 internal
114 singleton
115 begin
116 end
117 internal
118 singleton
119 begin
120 end
121 internal
122 singleton
123 begin
124 end
125 internal
126 singleton
127 begin
128 end
129 internal
130 singleton
131 begin
132 end
133 internal
134 singleton
135 begin
136 end
137 internal
138 singleton
139 begin
140 end
141 internal
142 singleton
143 begin
144 end
145 internal
146 singleton
147 begin
148 end
149 internal
150 singleton
151 begin
152 end
153 internal
154 singleton
155 begin
156 end
157 internal
158 singleton
159 begin
160 end
161 internal
162 singleton
163 begin
164 end
165 internal
166 singleton

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,3 @@
[
1.682383e+11 -1.1595e+10 -1.521733e+10 4.32034e+09 -2.257938e+10 -1.969666e+10 -2.559265e+10 -1.535687e+10 -1.276854e+10 -4.494483e+09 -1.209085e+10 -5.64008e+09 -1.134847e+10 -3.419512e+09 -1.079542e+10 -4.145463e+09 -6.637486e+09 -1.11318e+09 -3.479773e+09 -1.245932e+08 -1.386961e+09 6.560655e+07 -2.436518e+08 -4.032432e+07 4.620046e+08 -7.714964e+07 9.551484e+08 -4.119761e+08 8.208582e+08 -7.117156e+08 7.457703e+08 -4.3106e+08 1.202726e+09 2.904036e+08 1.231931e+09 3.629848e+08 6.366939e+08 -4.586172e+08 -5.267629e+08 -3.507819e+08 1.679838e+09
1.741141e+13 8.92488e+11 8.743834e+11 8.848896e+11 1.190313e+12 1.160279e+12 1.300066e+12 1.005678e+12 9.39335e+11 8.089614e+11 7.927041e+11 6.882427e+11 6.444235e+11 5.151451e+11 4.825723e+11 3.210106e+11 2.720254e+11 1.772539e+11 1.248102e+11 6.691599e+10 3.599804e+10 1.207574e+10 1.679301e+09 4.594778e+08 5.821614e+09 1.451758e+10 2.55803e+10 3.43277e+10 4.245286e+10 4.784859e+10 4.988591e+10 4.925451e+10 5.074584e+10 4.9557e+10 4.407876e+10 3.421443e+10 3.138606e+10 2.539716e+10 1.948134e+10 1.381167e+10 0 ]

View File

@ -0,0 +1 @@
# configuration file for apply-cmvn-online, used in the script ../local/run_online_decoding.sh

View File

@ -0,0 +1,2 @@
--left-context=3
--right-context=3