aissia/src/modules/StorageModule.cpp
StillHammer 059709cd0d feat: Implement MCP client and internal tools for agentic LLM
Add complete tool calling infrastructure for Claude Code-like functionality:

Internal Tools (via GroveEngine IIO):
- Scheduler: get_current_task, list_tasks, start_task, complete_task, start_break
- Monitoring: get_focus_stats, get_current_app
- Storage: save_note, query_notes, get_session_history
- Voice: speak

MCP Client (for external servers):
- StdioTransport for fork/exec JSON-RPC communication
- MCPClient for multi-server orchestration
- Support for filesystem, brave-search, fetch servers

Architecture:
- IOBridge for sync request/response over async IIO pub/sub
- Tool handlers added to all modules (SchedulerModule, MonitoringModule, StorageModule, VoiceModule)
- LLMService unifies internal tools + MCP tools in ToolRegistry

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 16:50:30 +08:00

252 lines
8.7 KiB
C++

#include "StorageModule.h"
#include <grove/IIO.h>
#include <grove/JsonDataNode.h>
namespace aissia {
StorageModule::StorageModule() {
m_logger = spdlog::get("StorageModule");
if (!m_logger) {
m_logger = spdlog::stdout_color_mt("StorageModule");
}
m_config = std::make_unique<grove::JsonDataNode>("config");
}
void StorageModule::setConfiguration(const grove::IDataNode& configNode,
grove::IIO* io,
grove::ITaskScheduler* scheduler) {
m_io = io;
m_config = std::make_unique<grove::JsonDataNode>("config");
// Subscribe to topics
if (m_io) {
grove::SubscriptionConfig subConfig;
m_io->subscribe("scheduler:task_completed", subConfig);
m_io->subscribe("monitoring:app_changed", subConfig);
m_io->subscribe("storage:session_saved", subConfig);
m_io->subscribe("storage:error", subConfig);
// Tool request handlers
m_io->subscribe("storage:query", subConfig);
m_io->subscribe("storage:command", subConfig);
}
m_logger->info("StorageModule configure (v2 - sans infrastructure)");
}
const grove::IDataNode& StorageModule::getConfiguration() {
return *m_config;
}
void StorageModule::process(const grove::IDataNode& input) {
processMessages();
}
void StorageModule::processMessages() {
if (!m_io) return;
while (m_io->hasMessages() > 0) {
auto msg = m_io->pullMessage();
if (msg.topic == "scheduler:task_completed" && msg.data) {
handleTaskCompleted(*msg.data);
}
else if (msg.topic == "monitoring:app_changed" && msg.data) {
handleAppChanged(*msg.data);
}
else if (msg.topic == "storage:session_saved" && msg.data) {
handleSessionSaved(*msg.data);
}
else if (msg.topic == "storage:error" && msg.data) {
handleStorageError(*msg.data);
}
// Tool handlers
else if (msg.topic == "storage:query" && msg.data) {
handleToolQuery(*msg.data);
}
else if (msg.topic == "storage:command" && msg.data) {
handleToolCommand(*msg.data);
}
}
}
void StorageModule::handleToolQuery(const grove::IDataNode& request) {
std::string correlationId = request.getString("correlation_id", "");
std::string action = request.getString("action", "");
auto response = std::make_unique<grove::JsonDataNode>("response");
response->setString("correlation_id", correlationId);
if (action == "query_notes") {
std::string query = request.getString("query", "");
std::string tag = request.getString("tag", "");
int limit = request.getInt("limit", 10);
// Search in-memory notes
int count = 0;
for (const auto& note : m_notes) {
if (count >= limit) break;
bool matches = query.empty() ||
note.content.find(query) != std::string::npos;
if (!tag.empty()) {
bool hasTag = false;
for (const auto& t : note.tags) {
if (t == tag) { hasTag = true; break; }
}
matches = matches && hasTag;
}
if (matches) {
auto noteNode = std::make_unique<grove::JsonDataNode>("note_" + std::to_string(count));
noteNode->setString("id", note.id);
noteNode->setString("content", note.content);
noteNode->setString("timestamp", note.timestamp);
response->setChild("note_" + std::to_string(count), std::move(noteNode));
count++;
}
}
response->setInt("count", count);
}
else if (action == "get_session_history") {
int days = request.getInt("days", 7);
response->setInt("last_session_id", m_lastSessionId);
response->setInt("total_saved", m_totalSaved);
response->setString("note", "Full history requires StorageService query");
}
else {
response->setString("error", "unknown_action");
}
m_io->publish("storage:response", std::move(response));
}
void StorageModule::handleToolCommand(const grove::IDataNode& request) {
std::string correlationId = request.getString("correlation_id", "");
std::string action = request.getString("action", "");
auto response = std::make_unique<grove::JsonDataNode>("response");
response->setString("correlation_id", correlationId);
if (action == "save_note") {
std::string content = request.getString("content", "");
if (content.empty()) {
response->setString("error", "missing_content");
} else {
// Create note in memory
Note note;
note.id = "note_" + std::to_string(m_notes.size());
note.content = content;
note.timestamp = "now"; // TODO: proper timestamp
m_notes.push_back(note);
m_logger->info("Note saved: {}", note.id);
// Also publish to StorageService for persistence
if (m_io) {
auto saveReq = std::make_unique<grove::JsonDataNode>("save_note");
saveReq->setString("content", content);
m_io->publish("storage:save_note", std::move(saveReq));
}
response->setBool("success", true);
response->setString("note_id", note.id);
}
}
else {
response->setString("error", "unknown_action");
}
m_io->publish("storage:response", std::move(response));
}
void StorageModule::handleTaskCompleted(const grove::IDataNode& data) {
std::string taskName = data.getString("taskName", "unknown");
int duration = data.getInt("duration", 0);
bool hyperfocus = data.getBool("hyperfocus", false);
m_logger->debug("Task completed: {} ({}min), publishing save request", taskName, duration);
if (m_io) {
auto request = std::make_unique<grove::JsonDataNode>("save");
request->setString("taskName", taskName);
request->setInt("durationMinutes", duration);
request->setBool("hyperfocus", hyperfocus);
m_io->publish("storage:save_session", std::move(request));
m_pendingSaves++;
}
}
void StorageModule::handleAppChanged(const grove::IDataNode& data) {
std::string appName = data.getString("oldApp", "");
int duration = data.getInt("duration", 0);
bool productive = data.getBool("wasProductive", false);
if (appName.empty() || duration <= 0) return;
m_logger->debug("App usage: {} ({}s), publishing save request", appName, duration);
if (m_io) {
auto request = std::make_unique<grove::JsonDataNode>("save");
request->setInt("sessionId", m_lastSessionId);
request->setString("appName", appName);
request->setInt("durationSeconds", duration);
request->setBool("productive", productive);
m_io->publish("storage:save_app_usage", std::move(request));
m_pendingSaves++;
}
}
void StorageModule::handleSessionSaved(const grove::IDataNode& data) {
m_lastSessionId = data.getInt("sessionId", 0);
m_pendingSaves--;
m_totalSaved++;
m_logger->debug("Session saved: id={}", m_lastSessionId);
}
void StorageModule::handleStorageError(const grove::IDataNode& data) {
std::string message = data.getString("message", "Unknown error");
m_pendingSaves--;
m_logger->error("Storage error: {}", message);
}
std::unique_ptr<grove::IDataNode> StorageModule::getHealthStatus() {
auto status = std::make_unique<grove::JsonDataNode>("status");
status->setString("status", "running");
status->setInt("lastSessionId", m_lastSessionId);
status->setInt("pendingSaves", m_pendingSaves);
status->setInt("totalSaved", m_totalSaved);
return status;
}
void StorageModule::shutdown() {
m_logger->info("StorageModule arrete. Total saved: {}", m_totalSaved);
}
std::unique_ptr<grove::IDataNode> StorageModule::getState() {
auto state = std::make_unique<grove::JsonDataNode>("state");
state->setInt("lastSessionId", m_lastSessionId);
state->setInt("totalSaved", m_totalSaved);
return state;
}
void StorageModule::setState(const grove::IDataNode& state) {
m_lastSessionId = state.getInt("lastSessionId", 0);
m_totalSaved = state.getInt("totalSaved", 0);
m_logger->info("Etat restore: lastSession={}, saved={}", m_lastSessionId, m_totalSaved);
}
} // namespace aissia
extern "C" {
grove::IModule* createModule() {
return new aissia::StorageModule();
}
void destroyModule(grove::IModule* module) {
delete module;
}
}