middleware: add snapshot helpers (save/load) backed by PgDal; link kom_dal with PIC; CLI: add --snapshot-save/--snapshot-load for easy syntax

This commit is contained in:
Χγφτ Kompanion 2025-10-19 10:40:33 +02:00
parent 70bd4ea064
commit c36ef7e46b
5 changed files with 112 additions and 3 deletions

View File

@ -1,5 +1,8 @@
# Subdir CMake for src # Subdir CMake for src
# Ensure internal libs are available to dependents
add_subdirectory(dal)
add_library(kompanion_mw SHARED add_library(kompanion_mw SHARED
middleware/kompanioncontroller.cpp middleware/kompanioncontroller.cpp
middleware/libkiexecutor.cpp middleware/libkiexecutor.cpp
@ -8,7 +11,7 @@ add_library(kompanion_mw SHARED
middleware/orchestrator.cpp middleware/orchestrator.cpp
) )
find_package(Qt6 REQUIRED COMPONENTS Core DBus) find_package(Qt6 REQUIRED COMPONENTS Core DBus Sql)
set(KOMPANION_CONTROLLER_DBUS_XML ${CMAKE_CURRENT_SOURCE_DIR}/../docs/dbus/org.kde.kompanion.controller.xml) set(KOMPANION_CONTROLLER_DBUS_XML ${CMAKE_CURRENT_SOURCE_DIR}/../docs/dbus/org.kde.kompanion.controller.xml)
set(KOMPANION_EXECUTOR_DBUS_XML ${CMAKE_CURRENT_SOURCE_DIR}/../docs/dbus/org.kde.kompanion.executor.xml) set(KOMPANION_EXECUTOR_DBUS_XML ${CMAKE_CURRENT_SOURCE_DIR}/../docs/dbus/org.kde.kompanion.executor.xml)
@ -31,7 +34,7 @@ target_include_directories(kompanion_mw PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/middl
target_sources(kompanion_mw PRIVATE ${KOMPANION_DBUS_ADAPTOR_SRCS} ${KOMPANION_DBUS_INTERFACE_SRCS}) target_sources(kompanion_mw PRIVATE ${KOMPANION_DBUS_ADAPTOR_SRCS} ${KOMPANION_DBUS_INTERFACE_SRCS})
target_link_libraries(kompanion_mw PRIVATE Qt6::Core Qt6::DBus Qt6::Network) target_link_libraries(kompanion_mw PRIVATE Qt6::Core Qt6::DBus Qt6::Sql Qt6::Network kom_dal)
target_compile_definitions(kompanion_mw PRIVATE KOMPANION_MW_LIBRARY) target_compile_definitions(kompanion_mw PRIVATE KOMPANION_MW_LIBRARY)
# Example executable wiring GUI/controller/executor together could be added later. # Example executable wiring GUI/controller/executor together could be added later.

View File

@ -867,6 +867,18 @@ int main(int argc, char** argv) {
"path"); "path");
parser.addOption(embFileOption); parser.addOption(embFileOption);
// Snapshot helpers
QCommandLineOption snapshotSaveOption(QStringList() << "--snapshot-save",
"Save a JSON snapshot (content) under a key in --ns. Provide content via -r/--stdin/[payload].");
parser.addOption(snapshotSaveOption);
QCommandLineOption snapshotLoadOption(QStringList() << "--snapshot-load",
"Load a JSON snapshot for --ns and --key and print it.");
parser.addOption(snapshotLoadOption);
QCommandLineOption keyOption(QStringList() << "--key",
"Key for snapshot operations (default 'session:last').",
"key", "session:last");
parser.addOption(keyOption);
parser.addPositionalArgument("tool", "Tool name to invoke."); parser.addPositionalArgument("tool", "Tool name to invoke.");
parser.addPositionalArgument("payload", "Optional JSON payload or file path (use '-' for stdin).", "[payload]"); parser.addPositionalArgument("payload", "Optional JSON payload or file path (use '-' for stdin).", "[payload]");
@ -880,6 +892,8 @@ int main(int argc, char** argv) {
const bool interactive = parser.isSet(interactiveOption); const bool interactive = parser.isSet(interactiveOption);
const bool initRequested = parser.isSet(initOption); const bool initRequested = parser.isSet(initOption);
const bool runMcp = parser.isSet(mcpServeOption); const bool runMcp = parser.isSet(mcpServeOption);
const bool snapSave = parser.isSet(snapshotSaveOption);
const bool snapLoad = parser.isSet(snapshotLoadOption);
std::optional<std::string> configDsn = readDsnFromConfig(); std::optional<std::string> configDsn = readDsnFromConfig();
const char* envDsn = std::getenv("PG_DSN"); const char* envDsn = std::getenv("PG_DSN");
@ -932,6 +946,34 @@ int main(int argc, char** argv) {
return runMcpServer(backend, addr, qerr); return runMcpServer(backend, addr, qerr);
} }
// Snapshot helpers (exclusive)
if (snapSave || snapLoad) {
const QString ns = parser.value(nsNameOption);
if (ns.isEmpty()) { qerr << "--snapshot-save/load requires --ns <name>\n"; return 1; }
const QString key = parser.value(keyOption);
if (snapSave) {
// Resolve content from CLI
std::string raw;
QString err;
if (!resolveRequestPayload(parser, parser.positionalArguments(), requestOption, stdinOption, raw, &err)) {
qerr << (err.isEmpty() ? QStringLiteral("Failed to read snapshot content") : err) << "\n";
return 1;
}
// Wrap into save_context call
std::ostringstream req;
req << "{\"namespace\":\"" << ns.toStdString() << "\",\"key\":\"" << key.toStdString() << "\",\"content\":" << raw << ",\"tags\":[\"snapshot\"]}";
const std::string out = server.dispatch("kom.memory.v1.save_context", req.str());
std::cout << out << std::endl;
return 0;
} else {
std::ostringstream req;
req << "{\"namespace\":\"" << ns.toStdString() << "\",\"key\":\"" << key.toStdString() << "\"}";
const std::string out = server.dispatch("kom.memory.v1.recall_context", req.str());
std::cout << out << std::endl;
return 0;
}
}
// DB inspection helpers (exclusive) // DB inspection helpers (exclusive)
if (parser.isSet(dbNsOption)) { if (parser.isSet(dbNsOption)) {
return dbListNamespaces(qout) ? 0 : 1; return dbListNamespaces(qout) ? 0 : 1;

View File

@ -6,3 +6,4 @@ target_compile_features(kom_dal PUBLIC cxx_std_20)
target_include_directories(kom_dal PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(kom_dal PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(kom_dal PUBLIC Qt6::Core Qt6::Sql) target_link_libraries(kom_dal PUBLIC Qt6::Core Qt6::Sql)
target_compile_options(kom_dal PRIVATE -fexceptions) target_compile_options(kom_dal PRIVATE -fexceptions)
set_target_properties(kom_dal PROPERTIES POSITION_INDEPENDENT_CODE ON)

View File

@ -11,6 +11,8 @@
#include <QTextStream> #include <QTextStream>
#include <QTimer> #include <QTimer>
#include "dal/PgDal.hpp"
// ---------- OllamaModelProvider ---------- // ---------- OllamaModelProvider ----------
OllamaModelProvider::OllamaModelProvider(QObject *parent) OllamaModelProvider::OllamaModelProvider(QObject *parent)
: QObject(parent) : QObject(parent)
@ -170,3 +172,48 @@ void Orchestrator::processPendingTasks() {
} }
} }
} }
bool Orchestrator::saveSnapshot(const QString &nameSpace,
const QString &key,
const QJsonObject &content,
const QStringList &tags)
{
ki::PgDal dal;
const QByteArray dsn = qgetenv("PG_DSN");
if (!dsn.isEmpty()) dal.connect(dsn.toStdString()); else dal.connect("stub://memory");
auto nsRow = dal.ensureNamespace(nameSpace.toStdString());
if (!nsRow) return false;
ki::ItemRow row;
row.namespace_id = nsRow->id;
row.key = key.toStdString();
row.tags.reserve(tags.size());
for (const auto &t : tags) row.tags.push_back(t.toStdString());
row.content_json = QString::fromUtf8(QJsonDocument(content).toJson(QJsonDocument::Compact)).toStdString();
row.metadata_json = "{}";
row.created_at = std::chrono::system_clock::now();
const std::string id = dal.upsertItem(row);
return !id.empty();
}
std::optional<QJsonObject> Orchestrator::loadSnapshot(const QString &nameSpace,
const QString &key)
{
ki::PgDal dal;
const QByteArray dsn = qgetenv("PG_DSN");
if (!dsn.isEmpty()) dal.connect(dsn.toStdString()); else dal.connect("stub://memory");
auto nsRow = dal.findNamespace(nameSpace.toStdString());
if (!nsRow) return std::nullopt;
std::vector<std::string> tags; tags.emplace_back("snapshot");
auto rows = dal.fetchContext(nsRow->id, std::optional<std::string>(key.toStdString()), tags, std::nullopt, 1);
if (rows.empty()) return std::nullopt;
const auto &row = rows.front();
if (row.content_json.empty()) return std::nullopt;
const auto doc = QJsonDocument::fromJson(QByteArray::fromStdString(row.content_json));
if (!doc.isObject()) return std::nullopt;
return doc.object();
}

View File

@ -64,6 +64,23 @@ public:
// One-shot tick (public for tests). // One-shot tick (public for tests).
void processPendingTasks(); void processPendingTasks();
/**
* saveSnapshot: persist a JSON snapshot under (namespace,key) as a context item.
* - Tags default to {"snapshot"}.
* - If PG_DSN is unset, uses an in-memory stub (won't persist across restarts).
*/
bool saveSnapshot(const QString &nameSpace,
const QString &key,
const QJsonObject &content,
const QStringList &tags = {QStringLiteral("snapshot")});
/**
* loadSnapshot: fetch the latest item for (namespace,key) tagged as snapshot.
* Returns empty optional if not found or on failure.
*/
std::optional<QJsonObject> loadSnapshot(const QString &nameSpace,
const QString &key);
signals: signals:
void taskProcessed(const QString &kind); void taskProcessed(const QString &kind);
@ -88,4 +105,3 @@ private:
bool continuityDone_ = false; bool continuityDone_ = false;
IModelProvider *model_ = nullptr; // not owned IModelProvider *model_ = nullptr; // not owned
}; };