317 lines
12 KiB
C#
Executable File
317 lines
12 KiB
C#
Executable File
using System.Text.Json;
|
|
using MarketAlly.AIPlugin;
|
|
|
|
namespace MarketAlly.AIPlugin.Context
|
|
{
|
|
/// <summary>
|
|
/// High-level plugin that orchestrates conversation continuity by combining context retrieval,
|
|
/// storage, and search operations. Provides an easy interface for Claude to maintain context
|
|
/// across long conversations and multiple sessions.
|
|
/// </summary>
|
|
[AIPlugin("ConversationContinuity", "Manages conversation continuity by retrieving relevant context, storing important information, and maintaining discussion flow across sessions")]
|
|
public class ConversationContinuityPlugin : IAIPlugin
|
|
{
|
|
[AIParameter("Action to perform: 'initialize', 'store_decision', 'find_relevant', 'summarize_session', 'get_project_context'", required: true)]
|
|
public string Action { get; set; } = "initialize";
|
|
|
|
[AIParameter("Current conversation topic or focus area", required: false)]
|
|
public string? Topic { get; set; }
|
|
|
|
[AIParameter("Information to store (for store_decision action)", required: false)]
|
|
public string? Information { get; set; }
|
|
|
|
[AIParameter("Brief summary of the information (for store_decision action)", required: false)]
|
|
public string? Summary { get; set; }
|
|
|
|
[AIParameter("Search query to find relevant context (for find_relevant action)", required: false)]
|
|
public string? SearchQuery { get; set; }
|
|
|
|
[AIParameter("Project path to work with", required: false)]
|
|
public string? ProjectPath { get; set; }
|
|
|
|
[AIParameter("Priority level for stored information: 'low', 'medium', 'high', 'critical'", required: false)]
|
|
public string Priority { get; set; } = "medium";
|
|
|
|
[AIParameter("Tags to categorize information (comma-separated)", required: false)]
|
|
public string? Tags { get; set; }
|
|
|
|
[AIParameter("Session summary for session wrap-up", required: false)]
|
|
public string? SessionSummary { get; set; }
|
|
|
|
public IReadOnlyDictionary<string, Type> SupportedParameters => new Dictionary<string, Type>
|
|
{
|
|
["action"] = typeof(string),
|
|
["topic"] = typeof(string),
|
|
["information"] = typeof(string),
|
|
["summary"] = typeof(string),
|
|
["searchQuery"] = typeof(string),
|
|
["searchquery"] = typeof(string),
|
|
["projectPath"] = typeof(string),
|
|
["projectpath"] = typeof(string),
|
|
["priority"] = typeof(string),
|
|
["tags"] = typeof(string),
|
|
["sessionSummary"] = typeof(string),
|
|
["sessionsummary"] = typeof(string)
|
|
};
|
|
|
|
public async Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> parameters)
|
|
{
|
|
try
|
|
{
|
|
var action = parameters["action"].ToString()!.ToLower();
|
|
var topic = parameters.TryGetValue("topic", out var t) ? t?.ToString() : null;
|
|
var projectPath = parameters.TryGetValue("projectPath", out var pp) ? pp?.ToString() : null;
|
|
|
|
switch (action)
|
|
{
|
|
case "initialize":
|
|
return await InitializeSessionAsync(topic, projectPath);
|
|
|
|
case "store_decision":
|
|
return await StoreDecisionAsync(parameters, projectPath);
|
|
|
|
case "find_relevant":
|
|
return await FindRelevantContextAsync(parameters, projectPath);
|
|
|
|
case "summarize_session":
|
|
return await SummarizeSessionAsync(parameters, projectPath);
|
|
|
|
case "get_project_context":
|
|
return await GetProjectContextAsync(projectPath);
|
|
|
|
default:
|
|
return new AIPluginResult(new { Error = "Unknown action" }, $"Unknown action: {action}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new AIPluginResult(ex, "Failed to execute conversation continuity action");
|
|
}
|
|
}
|
|
|
|
private async Task<AIPluginResult> InitializeSessionAsync(string? topic, string? projectPath)
|
|
{
|
|
var initializationResult = new ConversationInitialization
|
|
{
|
|
SessionId = Guid.NewGuid().ToString(),
|
|
StartTime = DateTime.UtcNow,
|
|
Topic = topic,
|
|
ProjectPath = projectPath ?? Directory.GetCurrentDirectory()
|
|
};
|
|
|
|
// Get recent conversation context
|
|
var contextRetrieval = new ContextRetrievalPlugin();
|
|
var contextResult = await contextRetrieval.ExecuteAsync(new Dictionary<string, object>
|
|
{
|
|
["contextType"] = "all",
|
|
["projectPath"] = initializationResult.ProjectPath,
|
|
["conversationLimit"] = 5,
|
|
["maxContextSize"] = 30000
|
|
});
|
|
|
|
if (contextResult.Success && contextResult.Data != null)
|
|
{
|
|
initializationResult.RecentContext = contextResult.Data;
|
|
}
|
|
|
|
// If we have a specific topic, search for related previous discussions
|
|
if (!string.IsNullOrEmpty(topic))
|
|
{
|
|
var searchPlugin = new ContextSearchPlugin();
|
|
var searchResult = await searchPlugin.ExecuteAsync(new Dictionary<string, object>
|
|
{
|
|
["query"] = topic,
|
|
["projectPath"] = initializationResult.ProjectPath,
|
|
["maxResults"] = 5,
|
|
["includeContent"] = false // Just summaries for initialization
|
|
});
|
|
|
|
if (searchResult.Success && searchResult.Data != null)
|
|
{
|
|
initializationResult.RelatedDiscussions = searchResult.Data;
|
|
}
|
|
}
|
|
|
|
// Store the session initialization
|
|
var storagePlugin = new ContextStoragePlugin();
|
|
await storagePlugin.ExecuteAsync(new Dictionary<string, object>
|
|
{
|
|
["contextType"] = "milestone",
|
|
["content"] = JsonSerializer.Serialize(initializationResult, new JsonSerializerOptions { WriteIndented = true }),
|
|
["summary"] = $"Session initialized - Topic: {topic ?? "General"}",
|
|
["tags"] = $"session-start,{topic ?? "general"}",
|
|
["projectPath"] = initializationResult.ProjectPath,
|
|
["priority"] = "medium"
|
|
});
|
|
|
|
return new AIPluginResult(initializationResult,
|
|
$"Conversation initialized. Found {((dynamic?)initializationResult.RecentContext)?.ConversationHistory?.Entries?.Count ?? 0} recent context entries and {((dynamic?)initializationResult.RelatedDiscussions)?.Results?.Count ?? 0} related discussions.");
|
|
}
|
|
|
|
private async Task<AIPluginResult> StoreDecisionAsync(IReadOnlyDictionary<string, object> parameters, string? projectPath)
|
|
{
|
|
var information = parameters.TryGetValue("information", out var info) ? info?.ToString() : null;
|
|
var summary = parameters.TryGetValue("summary", out var sum) ? sum?.ToString() : null;
|
|
var priority = parameters.TryGetValue("priority", out var p) ? p?.ToString() : "medium";
|
|
var tags = parameters.TryGetValue("tags", out var t) ? t?.ToString() : null;
|
|
|
|
if (string.IsNullOrEmpty(information) || string.IsNullOrEmpty(summary))
|
|
{
|
|
return new AIPluginResult(new { Error = "Information and summary are required" },
|
|
"Both 'information' and 'summary' parameters are required for storing decisions");
|
|
}
|
|
|
|
var storagePlugin = new ContextStoragePlugin();
|
|
var result = await storagePlugin.ExecuteAsync(new Dictionary<string, object>
|
|
{
|
|
["contextType"] = "decision",
|
|
["content"] = information,
|
|
["summary"] = summary,
|
|
["tags"] = tags ?? "decision",
|
|
["projectPath"] = projectPath ?? Directory.GetCurrentDirectory(),
|
|
["priority"] = priority,
|
|
["metadata"] = JsonSerializer.Serialize(new { StoredBy = "ConversationContinuity", Timestamp = DateTime.UtcNow })
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
private async Task<AIPluginResult> FindRelevantContextAsync(IReadOnlyDictionary<string, object> parameters, string? projectPath)
|
|
{
|
|
var searchQuery = parameters.TryGetValue("searchQuery", out var sq) ? sq?.ToString() : null;
|
|
var topic = parameters.TryGetValue("topic", out var t) ? t?.ToString() : null;
|
|
|
|
var query = searchQuery ?? topic;
|
|
if (string.IsNullOrEmpty(query))
|
|
{
|
|
return new AIPluginResult(new { Error = "Search query or topic is required" },
|
|
"Either 'searchQuery' or 'topic' parameter is required for finding relevant context");
|
|
}
|
|
|
|
var searchPlugin = new ContextSearchPlugin();
|
|
var result = await searchPlugin.ExecuteAsync(new Dictionary<string, object>
|
|
{
|
|
["query"] = query,
|
|
["projectPath"] = projectPath ?? Directory.GetCurrentDirectory(),
|
|
["maxResults"] = 8,
|
|
["includeContent"] = true,
|
|
["contextType"] = "all",
|
|
["daysBack"] = 30 // Last 30 days for relevance
|
|
});
|
|
|
|
// Also get recent file changes that might be relevant
|
|
var contextRetrieval = new ContextRetrievalPlugin();
|
|
var changesResult = await contextRetrieval.ExecuteAsync(new Dictionary<string, object>
|
|
{
|
|
["contextType"] = "changes",
|
|
["projectPath"] = projectPath ?? Directory.GetCurrentDirectory()
|
|
});
|
|
|
|
var combinedResult = new
|
|
{
|
|
SearchResults = result.Data,
|
|
RecentChanges = changesResult.Data,
|
|
Query = query,
|
|
SearchPerformed = DateTime.UtcNow
|
|
};
|
|
|
|
return new AIPluginResult(combinedResult,
|
|
$"Found relevant context for query: {query}");
|
|
}
|
|
|
|
private async Task<AIPluginResult> SummarizeSessionAsync(IReadOnlyDictionary<string, object> parameters, string? projectPath)
|
|
{
|
|
var sessionSummary = parameters.TryGetValue("sessionSummary", out var ss) ? ss?.ToString() : null;
|
|
var topic = parameters.TryGetValue("topic", out var t) ? t?.ToString() : null;
|
|
|
|
if (string.IsNullOrEmpty(sessionSummary))
|
|
{
|
|
return new AIPluginResult(new { Error = "Session summary is required" },
|
|
"sessionSummary parameter is required for summarizing sessions");
|
|
}
|
|
|
|
var sessionWrapUp = new SessionSummary
|
|
{
|
|
SessionId = Guid.NewGuid().ToString(),
|
|
EndTime = DateTime.UtcNow,
|
|
Topic = topic,
|
|
Summary = sessionSummary,
|
|
ProjectPath = projectPath ?? Directory.GetCurrentDirectory()
|
|
};
|
|
|
|
// Store the session summary
|
|
var storagePlugin = new ContextStoragePlugin();
|
|
var result = await storagePlugin.ExecuteAsync(new Dictionary<string, object>
|
|
{
|
|
["contextType"] = "milestone",
|
|
["content"] = JsonSerializer.Serialize(sessionWrapUp, new JsonSerializerOptions { WriteIndented = true }),
|
|
["summary"] = $"Session summary - {topic ?? "General discussion"}",
|
|
["tags"] = $"session-end,summary,{topic ?? "general"}",
|
|
["projectPath"] = sessionWrapUp.ProjectPath,
|
|
["priority"] = "high", // Session summaries are high priority for future reference
|
|
["metadata"] = JsonSerializer.Serialize(new
|
|
{
|
|
SessionType = "Summary",
|
|
Duration = "Unknown", // Could be calculated if we tracked session start
|
|
TopicFocus = topic
|
|
})
|
|
});
|
|
|
|
return new AIPluginResult(sessionWrapUp, "Session summarized and stored for future reference");
|
|
}
|
|
|
|
private async Task<AIPluginResult> GetProjectContextAsync(string? projectPath)
|
|
{
|
|
var contextRetrieval = new ContextRetrievalPlugin();
|
|
var result = await contextRetrieval.ExecuteAsync(new Dictionary<string, object>
|
|
{
|
|
["contextType"] = "project",
|
|
["projectPath"] = projectPath ?? Directory.GetCurrentDirectory(),
|
|
["includeFileSummaries"] = true,
|
|
["includeGitHistory"] = true,
|
|
["maxContextSize"] = 40000
|
|
});
|
|
|
|
// Also get recent decisions and insights for this project
|
|
var searchPlugin = new ContextSearchPlugin();
|
|
var decisionsResult = await searchPlugin.ExecuteAsync(new Dictionary<string, object>
|
|
{
|
|
["query"] = "decision insight architecture",
|
|
["projectPath"] = projectPath ?? Directory.GetCurrentDirectory(),
|
|
["contextType"] = "decision",
|
|
["maxResults"] = 5,
|
|
["includeContent"] = false,
|
|
["daysBack"] = 60
|
|
});
|
|
|
|
var combinedContext = new
|
|
{
|
|
ProjectInfo = result.Data,
|
|
RecentDecisions = decisionsResult.Data,
|
|
ContextRetrievedAt = DateTime.UtcNow
|
|
};
|
|
|
|
return new AIPluginResult(combinedContext, "Retrieved comprehensive project context");
|
|
}
|
|
}
|
|
|
|
// Supporting classes for conversation continuity
|
|
public class ConversationInitialization
|
|
{
|
|
public string SessionId { get; set; } = "";
|
|
public DateTime StartTime { get; set; }
|
|
public string? Topic { get; set; }
|
|
public string ProjectPath { get; set; } = "";
|
|
public object? RecentContext { get; set; }
|
|
public object? RelatedDiscussions { get; set; }
|
|
}
|
|
|
|
public class SessionSummary
|
|
{
|
|
public string SessionId { get; set; } = "";
|
|
public DateTime EndTime { get; set; }
|
|
public string? Topic { get; set; }
|
|
public string Summary { get; set; } = "";
|
|
public string ProjectPath { get; set; } = "";
|
|
}
|
|
} |