First commit!
This commit is contained in:
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+398
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 308 KiB |
Reference in New Issue
Block a user