using System.Text.Json; using MarketAlly.AIPlugin; namespace MarketAlly.AIPlugin.Context { /// /// 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. /// [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 SupportedParameters => new Dictionary { ["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 ExecuteAsync(IReadOnlyDictionary 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 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 { ["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 { ["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 { ["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 StoreDecisionAsync(IReadOnlyDictionary 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 { ["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 FindRelevantContextAsync(IReadOnlyDictionary 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 { ["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 { ["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 SummarizeSessionAsync(IReadOnlyDictionary 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 { ["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 GetProjectContextAsync(string? projectPath) { var contextRetrieval = new ContextRetrievalPlugin(); var result = await contextRetrieval.ExecuteAsync(new Dictionary { ["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 { ["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; } = ""; } }