@agentmemory/agentmemory 0.8.7 → 0.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/cli.mjs +4 -4
- package/dist/index.mjs +281 -157
- package/dist/index.mjs.map +1 -1
- package/dist/{src-DUTxjrOM.mjs → src-DJpwR1mt.mjs} +279 -158
- package/dist/src-DJpwR1mt.mjs.map +1 -0
- package/dist/{standalone-DF1_-GHr.mjs → standalone-DpxhaZLn.mjs} +59 -13
- package/dist/standalone-DpxhaZLn.mjs.map +1 -0
- package/dist/standalone.d.mts.map +1 -1
- package/dist/standalone.mjs +58 -12
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-CqxObfG_.mjs → tools-registry-CfbSegvn.mjs} +6 -3
- package/dist/tools-registry-CfbSegvn.mjs.map +1 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +8 -0
- package/plugin/skills/forget/SKILL.md +11 -17
- package/plugin/skills/recall/SKILL.md +9 -6
- package/plugin/skills/remember/SKILL.md +14 -14
- package/plugin/skills/session-history/SKILL.md +10 -9
- package/dist/src-DUTxjrOM.mjs.map +0 -1
- package/dist/standalone-DF1_-GHr.mjs.map +0 -1
- package/dist/tools-registry-CqxObfG_.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as generateId } from "./cli.mjs";
|
|
2
|
-
import { n as VERSION, o as getStandalonePersistPath, t as getVisibleTools } from "./tools-registry-
|
|
2
|
+
import { n as VERSION, o as getStandalonePersistPath, t as getVisibleTools } from "./tools-registry-CfbSegvn.mjs";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { dirname } from "node:path";
|
|
5
5
|
import { createInterface } from "node:readline";
|
|
@@ -143,9 +143,11 @@ function createStdioTransport(handler) {
|
|
|
143
143
|
const IMPLEMENTED_TOOLS = new Set([
|
|
144
144
|
"memory_save",
|
|
145
145
|
"memory_recall",
|
|
146
|
+
"memory_smart_search",
|
|
146
147
|
"memory_sessions",
|
|
147
148
|
"memory_export",
|
|
148
|
-
"memory_audit"
|
|
149
|
+
"memory_audit",
|
|
150
|
+
"memory_governance_delete"
|
|
149
151
|
]);
|
|
150
152
|
const SERVER_INFO = {
|
|
151
153
|
name: "agentmemory",
|
|
@@ -153,11 +155,26 @@ const SERVER_INFO = {
|
|
|
153
155
|
protocolVersion: "2024-11-05"
|
|
154
156
|
};
|
|
155
157
|
const kv = new InMemoryKV(getStandalonePersistPath());
|
|
158
|
+
function normalizeList(value) {
|
|
159
|
+
if (!value) return [];
|
|
160
|
+
if (Array.isArray(value)) return value.map((v) => typeof v === "string" ? v.trim() : "").filter((v) => v.length > 0);
|
|
161
|
+
if (typeof value === "string") return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
const DEFAULT_LIMIT = 10;
|
|
165
|
+
const MAX_LIMIT = 100;
|
|
166
|
+
function parseLimit(raw, fallback = DEFAULT_LIMIT) {
|
|
167
|
+
if (typeof raw !== "number" && typeof raw !== "string") return fallback;
|
|
168
|
+
const n = Number(raw);
|
|
169
|
+
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
170
|
+
return Math.min(Math.floor(n), MAX_LIMIT);
|
|
171
|
+
}
|
|
156
172
|
async function handleToolCall(toolName, args, kvInstance = kv) {
|
|
157
173
|
switch (toolName) {
|
|
158
174
|
case "memory_save": {
|
|
159
|
-
const
|
|
160
|
-
if (!
|
|
175
|
+
const rawContent = args.content;
|
|
176
|
+
if (typeof rawContent !== "string" || !rawContent.trim()) throw new Error("content is required");
|
|
177
|
+
const content = rawContent;
|
|
161
178
|
const id = generateId("mem");
|
|
162
179
|
const isoNow = (/* @__PURE__ */ new Date()).toISOString();
|
|
163
180
|
await kvInstance.set("mem:memories", id, {
|
|
@@ -165,8 +182,8 @@ async function handleToolCall(toolName, args, kvInstance = kv) {
|
|
|
165
182
|
type: args.type || "fact",
|
|
166
183
|
title: content.slice(0, 80),
|
|
167
184
|
content,
|
|
168
|
-
concepts: args.concepts
|
|
169
|
-
files: args.files
|
|
185
|
+
concepts: normalizeList(args.concepts),
|
|
186
|
+
files: normalizeList(args.files),
|
|
170
187
|
createdAt: isoNow,
|
|
171
188
|
updatedAt: isoNow,
|
|
172
189
|
strength: 7,
|
|
@@ -180,11 +197,21 @@ async function handleToolCall(toolName, args, kvInstance = kv) {
|
|
|
180
197
|
text: JSON.stringify({ saved: id })
|
|
181
198
|
}] };
|
|
182
199
|
}
|
|
183
|
-
case "memory_recall":
|
|
184
|
-
|
|
185
|
-
const
|
|
200
|
+
case "memory_recall":
|
|
201
|
+
case "memory_smart_search": {
|
|
202
|
+
const rawQuery = args.query;
|
|
203
|
+
if (typeof rawQuery !== "string" || !rawQuery.trim()) throw new Error("query is required");
|
|
204
|
+
const query = rawQuery.trim().toLowerCase();
|
|
205
|
+
const limit = parseLimit(args.limit);
|
|
186
206
|
const results = (await kvInstance.list("mem:memories")).filter((m) => {
|
|
187
|
-
return
|
|
207
|
+
return [
|
|
208
|
+
typeof m["title"] === "string" ? m["title"] : "",
|
|
209
|
+
typeof m["content"] === "string" ? m["content"] : "",
|
|
210
|
+
Array.isArray(m["files"]) ? m["files"].join(" ") : "",
|
|
211
|
+
Array.isArray(m["concepts"]) ? m["concepts"].join(" ") : "",
|
|
212
|
+
Array.isArray(m["sessionIds"]) ? m["sessionIds"].join(" ") : "",
|
|
213
|
+
typeof m["id"] === "string" ? m["id"] : ""
|
|
214
|
+
].join(" ").toLowerCase().includes(query);
|
|
188
215
|
}).slice(0, limit);
|
|
189
216
|
return { content: [{
|
|
190
217
|
type: "text",
|
|
@@ -193,9 +220,28 @@ async function handleToolCall(toolName, args, kvInstance = kv) {
|
|
|
193
220
|
}
|
|
194
221
|
case "memory_sessions": {
|
|
195
222
|
const sessions = await kvInstance.list("mem:sessions");
|
|
223
|
+
const limit = parseLimit(args.limit, 20);
|
|
196
224
|
return { content: [{
|
|
197
225
|
type: "text",
|
|
198
|
-
text: JSON.stringify({ sessions }, null, 2)
|
|
226
|
+
text: JSON.stringify({ sessions: sessions.slice(0, limit) }, null, 2)
|
|
227
|
+
}] };
|
|
228
|
+
}
|
|
229
|
+
case "memory_governance_delete": {
|
|
230
|
+
const ids = normalizeList(args.memoryIds);
|
|
231
|
+
if (ids.length === 0) throw new Error("memoryIds is required");
|
|
232
|
+
let deleted = 0;
|
|
233
|
+
for (const id of ids) if (await kvInstance.get("mem:memories", id)) {
|
|
234
|
+
await kvInstance.delete("mem:memories", id);
|
|
235
|
+
deleted++;
|
|
236
|
+
}
|
|
237
|
+
kvInstance.persist();
|
|
238
|
+
return { content: [{
|
|
239
|
+
type: "text",
|
|
240
|
+
text: JSON.stringify({
|
|
241
|
+
deleted,
|
|
242
|
+
requested: ids.length,
|
|
243
|
+
reason: args.reason || "plugin skill request"
|
|
244
|
+
})
|
|
199
245
|
}] };
|
|
200
246
|
}
|
|
201
247
|
case "memory_export": {
|
|
@@ -212,7 +258,7 @@ async function handleToolCall(toolName, args, kvInstance = kv) {
|
|
|
212
258
|
}
|
|
213
259
|
case "memory_audit": {
|
|
214
260
|
const entries = await kvInstance.list("mem:audit");
|
|
215
|
-
const limit = args.limit
|
|
261
|
+
const limit = parseLimit(args.limit, 50);
|
|
216
262
|
return { content: [{
|
|
217
263
|
type: "text",
|
|
218
264
|
text: JSON.stringify(entries.slice(0, limit), null, 2)
|
|
@@ -264,4 +310,4 @@ process.on("SIGTERM", () => {
|
|
|
264
310
|
|
|
265
311
|
//#endregion
|
|
266
312
|
export { };
|
|
267
|
-
//# sourceMappingURL=standalone-
|
|
313
|
+
//# sourceMappingURL=standalone-DpxhaZLn.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standalone-DpxhaZLn.mjs","names":[],"sources":["../src/mcp/in-memory-kv.ts","../src/mcp/transport.ts","../src/mcp/standalone.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nexport class InMemoryKV {\n private store = new Map<string, Map<string, unknown>>();\n\n constructor(private persistPath?: string) {\n if (persistPath && existsSync(persistPath)) {\n try {\n const data = JSON.parse(readFileSync(persistPath, \"utf-8\"));\n for (const [scope, entries] of Object.entries(data)) {\n const map = new Map<string, unknown>();\n for (const [key, value] of Object.entries(\n entries as Record<string, unknown>,\n )) {\n map.set(key, value);\n }\n this.store.set(scope, map);\n }\n } catch {\n // start fresh\n }\n }\n }\n\n async get<T = unknown>(scope: string, key: string): Promise<T | null> {\n return (this.store.get(scope)?.get(key) as T) ?? null;\n }\n\n async set<T = unknown>(scope: string, key: string, data: T): Promise<T> {\n if (!this.store.has(scope)) this.store.set(scope, new Map());\n this.store.get(scope)!.set(key, data);\n return data;\n }\n\n async delete(scope: string, key: string): Promise<void> {\n this.store.get(scope)?.delete(key);\n }\n\n async list<T = unknown>(scope: string): Promise<T[]> {\n const entries = this.store.get(scope);\n return entries ? (Array.from(entries.values()) as T[]) : [];\n }\n\n persist(): void {\n if (!this.persistPath) return;\n try {\n const dir = dirname(this.persistPath);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const data: Record<string, Record<string, unknown>> = {};\n for (const [scope, entries] of this.store) {\n data[scope] = Object.fromEntries(entries);\n }\n writeFileSync(this.persistPath, JSON.stringify(data), \"utf-8\");\n } catch (err) {\n process.stderr.write(\n `[@agentmemory/mcp] Persist failed: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n }\n }\n}\n","import { createInterface } from \"node:readline\";\n\nexport interface JsonRpcRequest {\n jsonrpc: \"2.0\";\n id?: string | number;\n method: string;\n params?: Record<string, unknown>;\n}\n\nexport interface JsonRpcResponse {\n jsonrpc: \"2.0\";\n id: string | number | null;\n result?: unknown;\n error?: { code: number; message: string; data?: unknown };\n}\n\nexport type RequestHandler = (\n method: string,\n params: Record<string, unknown>,\n) => Promise<unknown>;\n\n// JSON-RPC 2.0 notifications are messages without an `id` field. The spec\n// (and the MCP transport contract) requires the server to NOT send a\n// response for notifications. Some clients tolerate spurious responses;\n// stricter clients (e.g. Codex CLI) treat them as protocol violations and\n// close the transport. See agentmemory#129.\nfunction isNotification(req: JsonRpcRequest): boolean {\n return req.id === undefined || req.id === null;\n}\n\n// Per JSON-RPC 2.0 §4, a valid request id must be a String, Number, or Null\n// (Null is technically only allowed in responses; in requests, omitting id\n// is the convention for notifications, which we treat the same as null).\n// Any other runtime type (object, array, boolean) is an Invalid Request.\nfunction isValidId(id: unknown): id is string | number | null | undefined {\n return (\n id === undefined ||\n id === null ||\n typeof id === \"string\" ||\n typeof id === \"number\"\n );\n}\n\n// Exported for unit tests so the line-handling logic is exercised\n// independently of process.stdin / process.stdout.\nexport async function processLine(\n line: string,\n handler: RequestHandler,\n writeOut: (response: JsonRpcResponse) => void,\n writeErr: (msg: string) => void = (msg) => process.stderr.write(msg),\n): Promise<void> {\n const trimmed = line.trim();\n if (!trimmed) return;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n writeOut({\n jsonrpc: \"2.0\",\n id: null,\n error: { code: -32700, message: \"Parse error\" },\n });\n return;\n }\n\n const request = parsed as JsonRpcRequest;\n const rawId = (request as { id?: unknown } | null)?.id;\n\n // Invalid request shape (missing/wrong jsonrpc, non-string method).\n if (\n !request ||\n typeof request !== \"object\" ||\n request.jsonrpc !== \"2.0\" ||\n typeof request.method !== \"string\"\n ) {\n // Echo the id back only if it's a valid string/number. Notifications\n // (missing/null id) and malformed ids both drop silently — we don't\n // want to respond to something that could be a notification, and we\n // can't invent an id for a malformed one.\n if (typeof rawId === \"string\" || typeof rawId === \"number\") {\n writeOut({\n jsonrpc: \"2.0\",\n id: rawId,\n error: { code: -32600, message: \"Invalid Request\" },\n });\n }\n return;\n }\n\n // Request shape is valid but id may still be of the wrong type\n // (object, array, boolean). Per the spec, that's an Invalid Request.\n // Respond with id: null because we can't safely echo a non-JSON-RPC id.\n if (!isValidId(rawId)) {\n writeOut({\n jsonrpc: \"2.0\",\n id: null,\n error: { code: -32600, message: \"Invalid Request: id must be string, number, or null\" },\n });\n return;\n }\n\n const notification = isNotification(request);\n\n try {\n const result = await handler(request.method, request.params || {});\n if (notification) return;\n writeOut({\n jsonrpc: \"2.0\",\n id: request.id as string | number,\n result,\n });\n } catch (err) {\n if (notification) {\n writeErr(\n `[mcp-transport] notification handler error for ${request.method}: ${\n err instanceof Error ? err.message : String(err)\n }\\n`,\n );\n return;\n }\n writeOut({\n jsonrpc: \"2.0\",\n id: request.id as string | number,\n error: {\n code: -32603,\n message: err instanceof Error ? err.message : String(err),\n },\n });\n }\n}\n\nexport function createStdioTransport(handler: RequestHandler): {\n start: () => void;\n stop: () => void;\n} {\n let rl: ReturnType<typeof createInterface> | null = null;\n\n const writeResponse = (response: JsonRpcResponse) => {\n process.stdout.write(JSON.stringify(response) + \"\\n\");\n };\n\n const onLine = (line: string) => processLine(line, handler, writeResponse);\n\n return {\n start() {\n rl = createInterface({ input: process.stdin });\n rl.on(\"line\", onLine);\n },\n stop() {\n rl?.close();\n rl = null;\n },\n };\n}\n","#!/usr/bin/env node\n\nimport { InMemoryKV } from \"./in-memory-kv.js\";\nimport { createStdioTransport } from \"./transport.js\";\nimport { getVisibleTools } from \"./tools-registry.js\";\nimport { getStandalonePersistPath } from \"../config.js\";\nimport { VERSION } from \"../version.js\";\nimport { generateId } from \"../state/schema.js\";\n\nconst IMPLEMENTED_TOOLS = new Set([\n \"memory_save\",\n \"memory_recall\",\n \"memory_smart_search\",\n \"memory_sessions\",\n \"memory_export\",\n \"memory_audit\",\n \"memory_governance_delete\",\n]);\n\nconst SERVER_INFO = {\n name: \"agentmemory\",\n version: VERSION,\n protocolVersion: \"2024-11-05\",\n};\n\nconst kv = new InMemoryKV(getStandalonePersistPath());\n\n// Accept arrays, comma-separated strings, or a single string and always\n// return a trimmed, non-empty string array. Plugin skills (#139) tell\n// Claude to pass arrays; older clients may still pass CSV strings.\nfunction normalizeList(value: unknown): string[] {\n if (!value) return [];\n if (Array.isArray(value)) {\n return value\n .map((v) => (typeof v === \"string\" ? v.trim() : \"\"))\n .filter((v) => v.length > 0);\n }\n if (typeof value === \"string\") {\n return value\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n }\n return [];\n}\n\n// Parse a user-supplied limit argument, clamping to a sane range so an\n// adversarial or buggy caller can't request a million memories or pass\n// a negative / NaN / Infinity value that breaks .slice().\nconst DEFAULT_LIMIT = 10;\nconst MAX_LIMIT = 100;\nfunction parseLimit(raw: unknown, fallback = DEFAULT_LIMIT): number {\n // Only accept explicit numbers or numeric strings. Reject booleans,\n // objects, arrays, null, undefined — they shouldn't silently coerce\n // (e.g. `Number(true) === 1` would sneak a limit of 1 through).\n if (typeof raw !== \"number\" && typeof raw !== \"string\") return fallback;\n const n = Number(raw);\n if (!Number.isFinite(n) || n <= 0) return fallback;\n return Math.min(Math.floor(n), MAX_LIMIT);\n}\n\nexport async function handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n kvInstance: InMemoryKV = kv,\n): Promise<{ content: Array<{ type: string; text: string }> }> {\n switch (toolName) {\n case \"memory_save\": {\n const rawContent = args.content;\n if (typeof rawContent !== \"string\" || !rawContent.trim()) {\n throw new Error(\"content is required\");\n }\n const content = rawContent;\n const id = generateId(\"mem\");\n const isoNow = new Date().toISOString();\n await kvInstance.set(\"mem:memories\", id, {\n id,\n type: (args.type as string) || \"fact\",\n title: content.slice(0, 80),\n content,\n concepts: normalizeList(args.concepts),\n files: normalizeList(args.files),\n createdAt: isoNow,\n updatedAt: isoNow,\n strength: 7,\n version: 1,\n isLatest: true,\n sessionIds: [],\n });\n kvInstance.persist();\n return {\n content: [{ type: \"text\", text: JSON.stringify({ saved: id }) }],\n };\n }\n\n case \"memory_recall\":\n case \"memory_smart_search\": {\n // memory_smart_search would normally run hybrid BM25+vector+graph\n // in the engine-backed path, but the standalone shim (#139) only\n // has the in-memory KV store, so we fall back to a substring\n // filter. The tool name is kept so plugin skills that say \"use\n // memory_smart_search\" still work.\n //\n // Empty/missing query is rejected — the forget skill uses this\n // as its confirmation step before a destructive delete, and an\n // empty query would match every memory in the store, surfacing\n // unrelated IDs for deletion.\n const rawQuery = args.query;\n if (typeof rawQuery !== \"string\" || !rawQuery.trim()) {\n throw new Error(\"query is required\");\n }\n const query = rawQuery.trim().toLowerCase();\n const limit = parseLimit(args.limit);\n const all =\n await kvInstance.list<Record<string, unknown>>(\"mem:memories\");\n const results = all\n .filter((m) => {\n // Search title/content AND files/concepts/sessionIds so the\n // forget skill can find memories by file path or session\n // ID, not just by narrative text.\n const text = [\n typeof m[\"title\"] === \"string\" ? m[\"title\"] : \"\",\n typeof m[\"content\"] === \"string\" ? m[\"content\"] : \"\",\n Array.isArray(m[\"files\"]) ? m[\"files\"].join(\" \") : \"\",\n Array.isArray(m[\"concepts\"]) ? m[\"concepts\"].join(\" \") : \"\",\n Array.isArray(m[\"sessionIds\"]) ? m[\"sessionIds\"].join(\" \") : \"\",\n typeof m[\"id\"] === \"string\" ? m[\"id\"] : \"\",\n ]\n .join(\" \")\n .toLowerCase();\n return text.includes(query);\n })\n .slice(0, limit);\n return {\n content: [{ type: \"text\", text: JSON.stringify(results, null, 2) }],\n };\n }\n\n case \"memory_sessions\": {\n const sessions =\n await kvInstance.list<Record<string, unknown>>(\"mem:sessions\");\n const limit = parseLimit(args.limit, 20);\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(\n { sessions: sessions.slice(0, limit) },\n null,\n 2,\n ),\n },\n ],\n };\n }\n\n case \"memory_governance_delete\": {\n // Deletes memories by id. Accepts either a memoryIds array or a\n // comma-separated memoryIds string (same normalization pattern as\n // concepts/files) so plugin skills and legacy clients both work.\n // Unknown ids are silently skipped — the response reports how many\n // were actually removed so the caller can tell the user.\n const ids = normalizeList(args.memoryIds);\n if (ids.length === 0) {\n throw new Error(\"memoryIds is required\");\n }\n let deleted = 0;\n for (const id of ids) {\n const existing = await kvInstance.get(\"mem:memories\", id);\n if (existing) {\n await kvInstance.delete(\"mem:memories\", id);\n deleted++;\n }\n }\n kvInstance.persist();\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify({\n deleted,\n requested: ids.length,\n reason: (args.reason as string) || \"plugin skill request\",\n }),\n },\n ],\n };\n }\n\n case \"memory_export\": {\n const memories = await kvInstance.list(\"mem:memories\");\n const sessions = await kvInstance.list(\"mem:sessions\");\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(\n { version: VERSION, memories, sessions },\n null,\n 2,\n ),\n },\n ],\n };\n }\n\n case \"memory_audit\": {\n const entries = await kvInstance.list(\"mem:audit\");\n const limit = parseLimit(args.limit, 50);\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(\n (entries as Array<Record<string, unknown>>).slice(0, limit),\n null,\n 2,\n ),\n },\n ],\n };\n }\n\n default:\n throw new Error(`Unknown tool: ${toolName}`);\n }\n}\n\nconst transport = createStdioTransport(async (method, params) => {\n switch (method) {\n case \"initialize\":\n return {\n protocolVersion: SERVER_INFO.protocolVersion,\n capabilities: {\n tools: { listChanged: false },\n },\n serverInfo: {\n name: SERVER_INFO.name,\n version: SERVER_INFO.version,\n },\n };\n\n case \"notifications/initialized\":\n return {};\n\n case \"tools/list\":\n return {\n tools: getVisibleTools().filter((t) => IMPLEMENTED_TOOLS.has(t.name)),\n };\n\n case \"tools/call\": {\n const toolName = params.name as string;\n const toolArgs = (params.arguments as Record<string, unknown>) || {};\n try {\n return await handleToolCall(toolName, toolArgs);\n } catch (err) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: ${err instanceof Error ? err.message : String(err)}`,\n },\n ],\n isError: true,\n };\n }\n }\n\n default:\n throw new Error(`Unknown method: ${method}`);\n }\n});\n\nprocess.stderr.write(\n `[@agentmemory/mcp] Standalone MCP server v${SERVER_INFO.version} starting...\\n`,\n);\ntransport.start();\n\nprocess.on(\"SIGINT\", () => {\n kv.persist();\n process.exit(0);\n});\nprocess.on(\"SIGTERM\", () => {\n kv.persist();\n process.exit(0);\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standalone.d.mts","names":[],"sources":["../src/mcp/in-memory-kv.ts","../src/mcp/standalone.ts"],"mappings":";cAGa,UAAA;EAAA,QAGS,WAAA;EAAA,QAFZ,KAAA;cAEY,WAAA;EAmBd,GAAA,aAAA,CAAiB,KAAA,UAAe,GAAA,WAAc,OAAA,CAAQ,CAAA;EAItD,GAAA,aAAA,CAAiB,KAAA,UAAe,GAAA,UAAa,IAAA,EAAM,CAAA,GAAI,OAAA,CAAQ,CAAA;EAM/D,MAAA,CAAO,KAAA,UAAe,GAAA,WAAc,OAAA;EAIpC,IAAA,aAAA,CAAkB,KAAA,WAAgB,OAAA,CAAQ,CAAA;EAKhD,OAAA,CAAA;AAAA;;;
|
|
1
|
+
{"version":3,"file":"standalone.d.mts","names":[],"sources":["../src/mcp/in-memory-kv.ts","../src/mcp/standalone.ts"],"mappings":";cAGa,UAAA;EAAA,QAGS,WAAA;EAAA,QAFZ,KAAA;cAEY,WAAA;EAmBd,GAAA,aAAA,CAAiB,KAAA,UAAe,GAAA,WAAc,OAAA,CAAQ,CAAA;EAItD,GAAA,aAAA,CAAiB,KAAA,UAAe,GAAA,UAAa,IAAA,EAAM,CAAA,GAAI,OAAA,CAAQ,CAAA;EAM/D,MAAA,CAAO,KAAA,UAAe,GAAA,WAAc,OAAA;EAIpC,IAAA,aAAA,CAAkB,KAAA,WAAgB,OAAA,CAAQ,CAAA;EAKhD,OAAA,CAAA;AAAA;;;iBCiBoB,cAAA,CACpB,QAAA,UACA,IAAA,EAAM,MAAA,mBACN,UAAA,GAAY,UAAA,GACX,OAAA;EAAU,OAAA,EAAS,KAAA;IAAQ,IAAA;IAAc,IAAA;EAAA;AAAA"}
|
package/dist/standalone.mjs
CHANGED
|
@@ -1090,7 +1090,7 @@ function getStandalonePersistPath() {
|
|
|
1090
1090
|
|
|
1091
1091
|
//#endregion
|
|
1092
1092
|
//#region src/version.ts
|
|
1093
|
-
const VERSION = "0.8.
|
|
1093
|
+
const VERSION = "0.8.9";
|
|
1094
1094
|
|
|
1095
1095
|
//#endregion
|
|
1096
1096
|
//#region src/state/schema.ts
|
|
@@ -1103,9 +1103,11 @@ function generateId(prefix) {
|
|
|
1103
1103
|
const IMPLEMENTED_TOOLS = new Set([
|
|
1104
1104
|
"memory_save",
|
|
1105
1105
|
"memory_recall",
|
|
1106
|
+
"memory_smart_search",
|
|
1106
1107
|
"memory_sessions",
|
|
1107
1108
|
"memory_export",
|
|
1108
|
-
"memory_audit"
|
|
1109
|
+
"memory_audit",
|
|
1110
|
+
"memory_governance_delete"
|
|
1109
1111
|
]);
|
|
1110
1112
|
const SERVER_INFO = {
|
|
1111
1113
|
name: "agentmemory",
|
|
@@ -1113,11 +1115,26 @@ const SERVER_INFO = {
|
|
|
1113
1115
|
protocolVersion: "2024-11-05"
|
|
1114
1116
|
};
|
|
1115
1117
|
const kv = new InMemoryKV(getStandalonePersistPath());
|
|
1118
|
+
function normalizeList(value) {
|
|
1119
|
+
if (!value) return [];
|
|
1120
|
+
if (Array.isArray(value)) return value.map((v) => typeof v === "string" ? v.trim() : "").filter((v) => v.length > 0);
|
|
1121
|
+
if (typeof value === "string") return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1122
|
+
return [];
|
|
1123
|
+
}
|
|
1124
|
+
const DEFAULT_LIMIT = 10;
|
|
1125
|
+
const MAX_LIMIT = 100;
|
|
1126
|
+
function parseLimit(raw, fallback = DEFAULT_LIMIT) {
|
|
1127
|
+
if (typeof raw !== "number" && typeof raw !== "string") return fallback;
|
|
1128
|
+
const n = Number(raw);
|
|
1129
|
+
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
1130
|
+
return Math.min(Math.floor(n), MAX_LIMIT);
|
|
1131
|
+
}
|
|
1116
1132
|
async function handleToolCall(toolName, args, kvInstance = kv) {
|
|
1117
1133
|
switch (toolName) {
|
|
1118
1134
|
case "memory_save": {
|
|
1119
|
-
const
|
|
1120
|
-
if (!
|
|
1135
|
+
const rawContent = args.content;
|
|
1136
|
+
if (typeof rawContent !== "string" || !rawContent.trim()) throw new Error("content is required");
|
|
1137
|
+
const content = rawContent;
|
|
1121
1138
|
const id = generateId("mem");
|
|
1122
1139
|
const isoNow = (/* @__PURE__ */ new Date()).toISOString();
|
|
1123
1140
|
await kvInstance.set("mem:memories", id, {
|
|
@@ -1125,8 +1142,8 @@ async function handleToolCall(toolName, args, kvInstance = kv) {
|
|
|
1125
1142
|
type: args.type || "fact",
|
|
1126
1143
|
title: content.slice(0, 80),
|
|
1127
1144
|
content,
|
|
1128
|
-
concepts: args.concepts
|
|
1129
|
-
files: args.files
|
|
1145
|
+
concepts: normalizeList(args.concepts),
|
|
1146
|
+
files: normalizeList(args.files),
|
|
1130
1147
|
createdAt: isoNow,
|
|
1131
1148
|
updatedAt: isoNow,
|
|
1132
1149
|
strength: 7,
|
|
@@ -1140,11 +1157,21 @@ async function handleToolCall(toolName, args, kvInstance = kv) {
|
|
|
1140
1157
|
text: JSON.stringify({ saved: id })
|
|
1141
1158
|
}] };
|
|
1142
1159
|
}
|
|
1143
|
-
case "memory_recall":
|
|
1144
|
-
|
|
1145
|
-
const
|
|
1160
|
+
case "memory_recall":
|
|
1161
|
+
case "memory_smart_search": {
|
|
1162
|
+
const rawQuery = args.query;
|
|
1163
|
+
if (typeof rawQuery !== "string" || !rawQuery.trim()) throw new Error("query is required");
|
|
1164
|
+
const query = rawQuery.trim().toLowerCase();
|
|
1165
|
+
const limit = parseLimit(args.limit);
|
|
1146
1166
|
const results = (await kvInstance.list("mem:memories")).filter((m) => {
|
|
1147
|
-
return
|
|
1167
|
+
return [
|
|
1168
|
+
typeof m["title"] === "string" ? m["title"] : "",
|
|
1169
|
+
typeof m["content"] === "string" ? m["content"] : "",
|
|
1170
|
+
Array.isArray(m["files"]) ? m["files"].join(" ") : "",
|
|
1171
|
+
Array.isArray(m["concepts"]) ? m["concepts"].join(" ") : "",
|
|
1172
|
+
Array.isArray(m["sessionIds"]) ? m["sessionIds"].join(" ") : "",
|
|
1173
|
+
typeof m["id"] === "string" ? m["id"] : ""
|
|
1174
|
+
].join(" ").toLowerCase().includes(query);
|
|
1148
1175
|
}).slice(0, limit);
|
|
1149
1176
|
return { content: [{
|
|
1150
1177
|
type: "text",
|
|
@@ -1153,9 +1180,28 @@ async function handleToolCall(toolName, args, kvInstance = kv) {
|
|
|
1153
1180
|
}
|
|
1154
1181
|
case "memory_sessions": {
|
|
1155
1182
|
const sessions = await kvInstance.list("mem:sessions");
|
|
1183
|
+
const limit = parseLimit(args.limit, 20);
|
|
1184
|
+
return { content: [{
|
|
1185
|
+
type: "text",
|
|
1186
|
+
text: JSON.stringify({ sessions: sessions.slice(0, limit) }, null, 2)
|
|
1187
|
+
}] };
|
|
1188
|
+
}
|
|
1189
|
+
case "memory_governance_delete": {
|
|
1190
|
+
const ids = normalizeList(args.memoryIds);
|
|
1191
|
+
if (ids.length === 0) throw new Error("memoryIds is required");
|
|
1192
|
+
let deleted = 0;
|
|
1193
|
+
for (const id of ids) if (await kvInstance.get("mem:memories", id)) {
|
|
1194
|
+
await kvInstance.delete("mem:memories", id);
|
|
1195
|
+
deleted++;
|
|
1196
|
+
}
|
|
1197
|
+
kvInstance.persist();
|
|
1156
1198
|
return { content: [{
|
|
1157
1199
|
type: "text",
|
|
1158
|
-
text: JSON.stringify({
|
|
1200
|
+
text: JSON.stringify({
|
|
1201
|
+
deleted,
|
|
1202
|
+
requested: ids.length,
|
|
1203
|
+
reason: args.reason || "plugin skill request"
|
|
1204
|
+
})
|
|
1159
1205
|
}] };
|
|
1160
1206
|
}
|
|
1161
1207
|
case "memory_export": {
|
|
@@ -1172,7 +1218,7 @@ async function handleToolCall(toolName, args, kvInstance = kv) {
|
|
|
1172
1218
|
}
|
|
1173
1219
|
case "memory_audit": {
|
|
1174
1220
|
const entries = await kvInstance.list("mem:audit");
|
|
1175
|
-
const limit = args.limit
|
|
1221
|
+
const limit = parseLimit(args.limit, 50);
|
|
1176
1222
|
return { content: [{
|
|
1177
1223
|
type: "text",
|
|
1178
1224
|
text: JSON.stringify(entries.slice(0, limit), null, 2)
|