cli: add --warm-cache (policy/ad-hoc) and --rehydrate; mcp: warm_cache accepts since/tags/key; add delete_context (stub) and upsert_and_embed wiring; resources: basic NL regex mappings
This commit is contained in:
parent
6fd938d4f3
commit
720d445f1f
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{ "regex": "^open (.+) in editor$", "tool": "file.open", "keys": ["path"] },
|
||||
{ "regex": "^list containers$", "tool": "docker.list", "keys": [] },
|
||||
{ "regex": "^compose up (.+)$", "tool": "docker.compose.up", "keys": ["service"] }
|
||||
,{ "regex": "^save snapshot (.+)$", "tool": "kom.memory.v1.save_context", "keys": ["key"] }
|
||||
,{ "regex": "^load snapshot (.+)$", "tool": "kom.memory.v1.recall_context", "keys": ["key"] }
|
||||
,{ "regex": "^warm cache (.+)$", "tool": "kom.memory.v1.warm_cache", "keys": ["namespace"] }
|
||||
]
|
||||
|
|
@ -879,6 +879,30 @@ int main(int argc, char** argv) {
|
|||
"key", "session:last");
|
||||
parser.addOption(keyOption);
|
||||
|
||||
// Warm cache + rehydrate helpers
|
||||
QCommandLineOption warmCacheOption(QStringList() << "--warm-cache",
|
||||
"Warm precomputed embeddings (policy or ad-hoc). Use with --policy or --id.");
|
||||
parser.addOption(warmCacheOption);
|
||||
QCommandLineOption policyOption(QStringList() << "--policy",
|
||||
"Policy file (YAML/JSON) describing namespaces, model, limit, window_days.",
|
||||
"path");
|
||||
parser.addOption(policyOption);
|
||||
QCommandLineOption idOption(QStringList() << "--id",
|
||||
"Explicit item id for ad-hoc warm cache (use with --stdin or -r).",
|
||||
"id");
|
||||
parser.addOption(idOption);
|
||||
QCommandLineOption rehydrateOption(QStringList() << "--rehydrate",
|
||||
"Compose a rehydration frame: snapshot + top-K search for --text.");
|
||||
parser.addOption(rehydrateOption);
|
||||
QCommandLineOption textQOption(QStringList() << "--text",
|
||||
"Text query for rehydrate/search.",
|
||||
"q");
|
||||
parser.addOption(textQOption);
|
||||
QCommandLineOption kOption(QStringList() << "-k",
|
||||
"Top-K for rehydrate/search.",
|
||||
"k", "8");
|
||||
parser.addOption(kOption);
|
||||
|
||||
parser.addPositionalArgument("tool", "Tool name to invoke.");
|
||||
parser.addPositionalArgument("payload", "Optional JSON payload or file path (use '-' for stdin).", "[payload]");
|
||||
|
||||
|
|
@ -894,6 +918,8 @@ int main(int argc, char** argv) {
|
|||
const bool runMcp = parser.isSet(mcpServeOption);
|
||||
const bool snapSave = parser.isSet(snapshotSaveOption);
|
||||
const bool snapLoad = parser.isSet(snapshotLoadOption);
|
||||
const bool warmCache = parser.isSet(warmCacheOption);
|
||||
const bool rehydrate = parser.isSet(rehydrateOption);
|
||||
|
||||
std::optional<std::string> configDsn = readDsnFromConfig();
|
||||
const char* envDsn = std::getenv("PG_DSN");
|
||||
|
|
@ -974,6 +1000,89 @@ int main(int argc, char** argv) {
|
|||
}
|
||||
}
|
||||
|
||||
// Warm cache
|
||||
if (warmCache) {
|
||||
const QString ns = parser.value(nsNameOption);
|
||||
const QString id = parser.value(idOption);
|
||||
const QString policyPath = parser.value(policyOption);
|
||||
const QString model = parser.value(QStringLiteral("--model")); // optional generic pass-through
|
||||
const int limit = parser.value(limitOption).toInt();
|
||||
|
||||
if (!id.isEmpty()) {
|
||||
// Ad-hoc enqueue: upsert_and_embed with single item from stdin/-r/payload
|
||||
std::string raw; QString err;
|
||||
if (!resolveRequestPayload(parser, parser.positionalArguments(), requestOption, stdinOption, raw, &err)) {
|
||||
qerr << (err.isEmpty() ? QStringLiteral("Failed to read content for --id") : err) << "\n"; return 1;
|
||||
}
|
||||
std::ostringstream req;
|
||||
req << "{\"namespace\":\"" << ns.toStdString() << "\",\"model\":\"" << model.toStdString() << "\",\"items\":[{\"id\":\"" << id.toStdString() << "\",\"text\":" << raw << "}]}";
|
||||
std::cout << server.dispatch("kom.memory.v1.upsert_and_embed", req.str()) << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!policyPath.isEmpty()) {
|
||||
// Minimal policy parser (YAML/JSON): namespaces, model, limit, window_days
|
||||
QFile f(policyPath); if (!f.open(QIODevice::ReadOnly|QIODevice::Text)) { qerr << "Cannot open policy" << "\n"; return 1; }
|
||||
const QString pol = QString::fromUtf8(f.readAll());
|
||||
// Extract namespaces as lines starting with '-'
|
||||
QStringList nss;
|
||||
QRegularExpression rxNs("^\\s*-\\s*([A-Za-z0-9_:\\-]+)\\s*$");
|
||||
for (const QString &line : pol.split('\n')) {
|
||||
auto m = rxNs.match(line); if (m.hasMatch()) nss << m.captured(1);
|
||||
}
|
||||
if (nss.isEmpty() && !ns.isEmpty()) nss << ns;
|
||||
// Extract window_days
|
||||
int windowDays = 0; {
|
||||
QRegularExpression rx("window_days\\s*:\\s*([0-9]+)"); auto m = rx.match(pol); if (m.hasMatch()) windowDays = m.captured(1).toInt();
|
||||
}
|
||||
// Extract model
|
||||
QString pModel = model; if (pModel.isEmpty()) { QRegularExpression rx("model\\s*:\\s*([A-Za-z0-9_:\\-]+)"); auto m = rx.match(pol); if (m.hasMatch()) pModel = m.captured(1); }
|
||||
// Extract limit
|
||||
int pLimit = limit>0?limit:10; { QRegularExpression rx("limit\\s*:\\s*([0-9]+)"); auto m = rx.match(pol); if (m.hasMatch()) pLimit = m.captured(1).toInt(); }
|
||||
|
||||
// Compute since timestamp if windowDays>0
|
||||
QString since;
|
||||
if (windowDays > 0) {
|
||||
const auto now = QDateTime::currentDateTimeUtc();
|
||||
since = now.addDays(-windowDays).toString(Qt::ISODate);
|
||||
}
|
||||
for (const QString &nsv : nss) {
|
||||
std::ostringstream req;
|
||||
req << "{\"namespace\":\"" << nsv.toStdString() << "\"";
|
||||
if (!pModel.isEmpty()) req << ",\"model\":\"" << pModel.toStdString() << "\"";
|
||||
if (!since.isEmpty()) req << ",\"since\":\"" << since.toStdString() << "\"";
|
||||
req << ",\"limit\":" << pLimit << "}";
|
||||
std::cout << server.dispatch("kom.memory.v1.warm_cache", req.str()) << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Simple case: call warm_cache once for ns
|
||||
std::ostringstream req;
|
||||
req << "{\"namespace\":\"" << ns.toStdString() << "\"";
|
||||
if (!model.isEmpty()) req << ",\"model\":\"" << model.toStdString() << "\"";
|
||||
req << ",\"limit\":" << (limit>0?limit:10) << "}";
|
||||
std::cout << server.dispatch("kom.memory.v1.warm_cache", req.str()) << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Rehydrate composition
|
||||
if (rehydrate) {
|
||||
const QString ns = parser.value(nsNameOption);
|
||||
const QString key = parser.value(keyOption);
|
||||
const QString text = parser.value(textQOption);
|
||||
bool ok=false; int k = parser.value(kOption).toInt(&ok); if (!ok || k<=0) k=8;
|
||||
// Recall snapshot
|
||||
std::ostringstream r1; r1 << "{\"namespace\":\"" << ns.toStdString() << "\",\"key\":\"" << key.toStdString() << "\"}";
|
||||
const std::string snapshot = server.dispatch("kom.memory.v1.recall_context", r1.str());
|
||||
// Search
|
||||
std::ostringstream r2; r2 << "{\"namespace\":\"" << ns.toStdString() << "\",\"query\":{\"text\":\"" << detail::json_escape(text.toStdString()) << "\",\"k\":" << k << "}}";
|
||||
const std::string matches = server.dispatch("kom.memory.v1.search_memory", r2.str());
|
||||
// Compose
|
||||
std::cout << "{\"snapshot\":" << snapshot << ",\"search\":" << matches << "}" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// DB inspection helpers (exclusive)
|
||||
if (parser.isSet(dbNsOption)) {
|
||||
return dbListNamespaces(qout) ? 0 : 1;
|
||||
|
|
|
|||
|
|
@ -733,15 +733,19 @@ inline std::string warm_cache(const std::string& reqJson) {
|
|||
std::string model = detail::extract_string_field(reqJson, "model");
|
||||
int limit = 10;
|
||||
if (auto lim = detail::extract_int_field(reqJson, "limit")) { if (*lim > 0) limit = *lim; }
|
||||
std::string key = detail::extract_string_field(reqJson, "key");
|
||||
auto tags = detail::parse_string_array(reqJson, "tags");
|
||||
std::string since = detail::extract_string_field(reqJson, "since");
|
||||
|
||||
auto nsRow = detail::database().findNamespace(nsName);
|
||||
if (!nsRow) {
|
||||
return std::string("{\"queued\":0}");
|
||||
return std::string("{\\\"queued\\\":0}");
|
||||
}
|
||||
|
||||
// Fetch recent items
|
||||
std::vector<std::string> tags; // empty
|
||||
auto rows = detail::database().fetchContext(nsRow->id, std::nullopt, tags, std::nullopt, limit);
|
||||
// Fetch recent items with optional filters
|
||||
std::optional<std::string> keyOpt; if (!key.empty()) keyOpt = key;
|
||||
std::optional<std::string> sinceOpt; if (!since.empty()) sinceOpt = since;
|
||||
auto rows = detail::database().fetchContext(nsRow->id, keyOpt, tags, sinceOpt, limit);
|
||||
if (rows.empty()) {
|
||||
return std::string("{\"queued\":0}");
|
||||
}
|
||||
|
|
@ -782,6 +786,17 @@ inline std::string warm_cache(const std::string& reqJson) {
|
|||
std::ostringstream os; os << "{\"queued\":" << persisted << "}"; return os.str();
|
||||
}
|
||||
|
||||
/** delete_context (stub MVP)
|
||||
* Request: { namespace, key?, tags? }
|
||||
* Response: { deleted: 0 }
|
||||
* Note: Full deletion (soft-delete) can be added in DAL later.
|
||||
*/
|
||||
inline std::string delete_context(const std::string& reqJson) {
|
||||
const std::string nsName = detail::extract_string_field(reqJson, "namespace");
|
||||
if (nsName.empty()) return detail::error_response("bad_request","namespace is required");
|
||||
return std::string("{\\\"deleted\\\":0}");
|
||||
}
|
||||
|
||||
} // namespace Handlers
|
||||
/**
|
||||
* upsert_and_embed
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ inline void register_default_tools(KomMcpServer& server) {
|
|||
server.registerTool("echo", Handlers::echo);
|
||||
server.registerTool("kom.memory.v1.save_context", Handlers::save_context);
|
||||
server.registerTool("kom.memory.v1.recall_context", Handlers::recall_context);
|
||||
server.registerTool("kom.memory.v1.delete_context", Handlers::delete_context);
|
||||
server.registerTool("kom.memory.v1.embed_text", Handlers::embed_text);
|
||||
server.registerTool("kom.memory.v1.upsert_memory", Handlers::upsert_memory);
|
||||
server.registerTool("kom.memory.v1.search_memory", Handlers::search_memory);
|
||||
|
|
|
|||
|
|
@ -162,6 +162,25 @@
|
|||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"delete_context": {
|
||||
"description": "Delete stored context entries filtered by key and/or tags.",
|
||||
"input": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"namespace": { "type": "string" },
|
||||
"key": { "type": "string" },
|
||||
"tags": { "$ref": "#/$defs/stringList" }
|
||||
},
|
||||
"required": ["namespace"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output": {
|
||||
"type": "object",
|
||||
"properties": { "deleted": { "type": "integer" } },
|
||||
"required": ["deleted"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"embed_text": {
|
||||
"description": "Return embedding vectors for provided text inputs.",
|
||||
"input": {
|
||||
|
|
@ -400,6 +419,41 @@
|
|||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"upsert_and_embed": {
|
||||
"description": "Upsert items and compute embeddings for each item (ord=0 chunk).",
|
||||
"input": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"namespace": { "type": "string" },
|
||||
"model": { "type": "string" },
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"text": { "type": "string" },
|
||||
"tags": { "$ref": "#/$defs/stringList" },
|
||||
"metadata": { "type": "object" }
|
||||
},
|
||||
"required": ["text"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["namespace","items"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"upserted": { "type": "integer" },
|
||||
"embedded": { "type": "integer" }
|
||||
},
|
||||
"required": ["upserted","embedded"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"kom.local.v1.backup.export_encrypted": {
|
||||
"description": "Queue an encrypted backup export for the requested namespaces.",
|
||||
"input": {
|
||||
|
|
|
|||
Loading…
Reference in New Issue