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; } = "";
}
}