@agentmemory/agentmemory 0.8.8 → 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.
@@ -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-BuDo4gKj.mjs";
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 content = args.content;
160
- if (!content?.trim()) throw new Error("content is required");
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 ? args.concepts.split(",").map((c) => c.trim()) : [],
169
- files: args.files ? args.files.split(",").map((f) => f.trim()) : [],
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
- const query = args.query?.toLowerCase() || "";
185
- const limit = args.limit || 10;
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 `${m.title} ${m.content}`.toLowerCase().includes(query);
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 || 50;
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-Qmvspmgi.mjs.map
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;;;iBCnBoB,cAAA,CACpB,QAAA,UACA,IAAA,EAAM,MAAA,mBACN,UAAA,GAAY,UAAA,GACX,OAAA;EAAU,OAAA,EAAS,KAAA;IAAQ,IAAA;IAAc,IAAA;EAAA;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"}
@@ -1090,7 +1090,7 @@ function getStandalonePersistPath() {
1090
1090
 
1091
1091
  //#endregion
1092
1092
  //#region src/version.ts
1093
- const VERSION = "0.8.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 content = args.content;
1120
- if (!content?.trim()) throw new Error("content is required");
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 ? args.concepts.split(",").map((c) => c.trim()) : [],
1129
- files: args.files ? args.files.split(",").map((f) => f.trim()) : [],
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
- const query = args.query?.toLowerCase() || "";
1145
- const limit = args.limit || 10;
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 `${m.title} ${m.content}`.toLowerCase().includes(query);
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({ sessions }, null, 2)
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 || 50;
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)