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>
252 lines
8.7 KiB
C++
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;
|
|
}
|
|
|
|
}
|