shabti 2.10.1 → 2.12.0
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 +14 -0
- package/package.json +2 -1
- package/src/a2a/agentCard.js +48 -0
- package/src/a2a/server.js +130 -0
- package/src/commands/get.js +42 -0
- package/src/index.js +2 -0
- package/src/mcp/server.js +92 -0
package/README.md
CHANGED
|
@@ -60,6 +60,7 @@ shabti status
|
|
|
60
60
|
| ----------------- | ----------------------------------- |
|
|
61
61
|
| `store <content>` | Store a memory entry |
|
|
62
62
|
| `search <query>` | Search memory entries |
|
|
63
|
+
| `get <id>` | Retrieve a memory entry by ID |
|
|
63
64
|
| `delete <id>` | Delete a memory entry by ID |
|
|
64
65
|
| `status` | Show engine status |
|
|
65
66
|
| `export` | Export memories as JSONL |
|
|
@@ -92,6 +93,13 @@ shabti search "recent events" --min-score 0.5
|
|
|
92
93
|
shabti search "query" --json # JSON output
|
|
93
94
|
```
|
|
94
95
|
|
|
96
|
+
### get
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
shabti get <uuid>
|
|
100
|
+
shabti get <uuid> --json
|
|
101
|
+
```
|
|
102
|
+
|
|
95
103
|
### delete
|
|
96
104
|
|
|
97
105
|
```bash
|
|
@@ -202,11 +210,13 @@ Or manually add to your MCP settings:
|
|
|
202
210
|
| --------------- | ----------------------------------------------------------- |
|
|
203
211
|
| `memory_store` | Store a memory entry with optional namespace, tags, and TTL |
|
|
204
212
|
| `memory_search` | Search memories by semantic similarity |
|
|
213
|
+
| `memory_get` | Retrieve a specific memory entry by UUID |
|
|
205
214
|
| `memory_delete` | Delete a memory entry by ID |
|
|
206
215
|
| `memory_list` | List recent memory entries |
|
|
207
216
|
| `memory_export` | Export entries as JSONL |
|
|
208
217
|
| `memory_gc` | Garbage collect expired entries |
|
|
209
218
|
| `memory_status` | Get engine status |
|
|
219
|
+
| `memory_health` | Run health checks on engine components |
|
|
210
220
|
|
|
211
221
|
### MCP Resources
|
|
212
222
|
|
|
@@ -247,6 +257,10 @@ Discoverable at `GET /.well-known/agent-card.json`.
|
|
|
247
257
|
| `tasks/get` | Retrieve task status and results |
|
|
248
258
|
| `tasks/cancel` | Cancel a running task |
|
|
249
259
|
|
|
260
|
+
### CORS
|
|
261
|
+
|
|
262
|
+
The A2A server sets `Access-Control-Allow-Origin: *` to allow requests from any origin. This is intentional for local development and inter-agent communication where the caller's origin is not known in advance. For production deployments behind a reverse proxy, restrict the origin at the proxy level.
|
|
263
|
+
|
|
250
264
|
## Node.js API
|
|
251
265
|
|
|
252
266
|
```javascript
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shabti",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
4
4
|
"description": "Agent Memory OS — semantic memory for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "native.cjs",
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
"@commitlint/config-conventional": "^20.4.1",
|
|
78
78
|
"@eslint/js": "^10.0.1",
|
|
79
79
|
"@napi-rs/cli": "^3.5.1",
|
|
80
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
80
81
|
"@vitest/coverage-v8": "^4.0.18",
|
|
81
82
|
"eslint": "^10.0.0",
|
|
82
83
|
"eslint-config-prettier": "^10.1.8",
|
package/src/a2a/agentCard.js
CHANGED
|
@@ -43,6 +43,54 @@ export function buildAgentCard(baseUrl) {
|
|
|
43
43
|
inputModes: ["text"],
|
|
44
44
|
outputModes: ["application/json"],
|
|
45
45
|
},
|
|
46
|
+
{
|
|
47
|
+
id: "memory_get",
|
|
48
|
+
name: "Get Memory",
|
|
49
|
+
description: "Retrieve a specific memory entry by its ID",
|
|
50
|
+
tags: ["memory", "get", "retrieve", "fetch"],
|
|
51
|
+
inputModes: ["application/json"],
|
|
52
|
+
outputModes: ["application/json"],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "memory_delete",
|
|
56
|
+
name: "Delete Memory",
|
|
57
|
+
description: "Delete a specific memory entry by its ID",
|
|
58
|
+
tags: ["memory", "delete", "remove"],
|
|
59
|
+
inputModes: ["application/json"],
|
|
60
|
+
outputModes: ["application/json"],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "memory_list",
|
|
64
|
+
name: "List Memories",
|
|
65
|
+
description: "List stored memory entries with optional filters",
|
|
66
|
+
tags: ["memory", "list", "entries"],
|
|
67
|
+
inputModes: ["text", "application/json"],
|
|
68
|
+
outputModes: ["application/json"],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "memory_export",
|
|
72
|
+
name: "Export Memories",
|
|
73
|
+
description: "Export all memory entries as JSONL data",
|
|
74
|
+
tags: ["memory", "export", "dump", "backup"],
|
|
75
|
+
inputModes: ["text", "application/json"],
|
|
76
|
+
outputModes: ["application/json"],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "memory_gc",
|
|
80
|
+
name: "Garbage Collect",
|
|
81
|
+
description: "Remove expired memory entries and reclaim storage",
|
|
82
|
+
tags: ["memory", "gc", "garbage", "cleanup"],
|
|
83
|
+
inputModes: ["text"],
|
|
84
|
+
outputModes: ["application/json"],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "memory_health",
|
|
88
|
+
name: "Health Check",
|
|
89
|
+
description: "Check the health of the memory engine and its dependencies",
|
|
90
|
+
tags: ["memory", "health", "diagnostics"],
|
|
91
|
+
inputModes: ["text"],
|
|
92
|
+
outputModes: ["application/json"],
|
|
93
|
+
},
|
|
46
94
|
],
|
|
47
95
|
};
|
|
48
96
|
}
|
package/src/a2a/server.js
CHANGED
|
@@ -122,6 +122,112 @@ function handleMemoryStatus(engine) {
|
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
async function handleMemoryDelete(engine, parts) {
|
|
126
|
+
let id = null;
|
|
127
|
+
|
|
128
|
+
for (const part of parts) {
|
|
129
|
+
if (part.kind === "data" && part.data?.id) {
|
|
130
|
+
id = part.data.id;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!id) {
|
|
135
|
+
throw Object.assign(new Error("Missing id in message parts"), { code: -32602 });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await engine.delete(id);
|
|
139
|
+
return {
|
|
140
|
+
artifactId: randomUUID(),
|
|
141
|
+
name: "delete_result",
|
|
142
|
+
parts: [{ kind: "data", data: { deleted: true, id } }],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function handleMemoryGet(engine, parts) {
|
|
147
|
+
let id = null;
|
|
148
|
+
|
|
149
|
+
for (const part of parts) {
|
|
150
|
+
if (part.kind === "data" && part.data?.id) {
|
|
151
|
+
id = part.data.id;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!id) {
|
|
156
|
+
throw Object.assign(new Error("Missing id in message parts"), { code: -32602 });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const entry = await engine.get(id);
|
|
160
|
+
return {
|
|
161
|
+
artifactId: randomUUID(),
|
|
162
|
+
name: "get_result",
|
|
163
|
+
parts: [{ kind: "data", data: entry }],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function handleMemoryList(engine, parts) {
|
|
168
|
+
let opts = {};
|
|
169
|
+
|
|
170
|
+
for (const part of parts) {
|
|
171
|
+
if (part.kind === "data" && part.data) {
|
|
172
|
+
if (part.data.limit) opts.limit = part.data.limit;
|
|
173
|
+
if (part.data.namespace) opts.namespace = part.data.namespace;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const entries = engine.listEntries(opts);
|
|
178
|
+
return {
|
|
179
|
+
artifactId: randomUUID(),
|
|
180
|
+
name: "list_result",
|
|
181
|
+
parts: [{ kind: "data", data: { entries, count: entries.length } }],
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function handleMemoryExport(engine) {
|
|
186
|
+
const entries = engine.listEntries();
|
|
187
|
+
const lines = entries.map((e) => JSON.stringify(e));
|
|
188
|
+
return {
|
|
189
|
+
artifactId: randomUUID(),
|
|
190
|
+
name: "export_result",
|
|
191
|
+
parts: [{ kind: "data", data: { entries: entries.length, data: lines.join("\n") } }],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function handleMemoryGc(engine) {
|
|
196
|
+
const removed = await engine.gc();
|
|
197
|
+
return {
|
|
198
|
+
artifactId: randomUUID(),
|
|
199
|
+
name: "gc_result",
|
|
200
|
+
parts: [{ kind: "data", data: { removed } }],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function handleMemoryHealth(engine) {
|
|
205
|
+
const checks = [];
|
|
206
|
+
const status = engine.status();
|
|
207
|
+
const qdrantUrl = status.qdrantUrl.replace(/:\d+$/, ":6333");
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const res = await fetch(`${qdrantUrl}/healthz`, { signal: AbortSignal.timeout(3000) });
|
|
211
|
+
checks.push({
|
|
212
|
+
name: "qdrant",
|
|
213
|
+
status: res.ok ? "ok" : "degraded",
|
|
214
|
+
message: res.ok ? "Reachable" : `HTTP ${res.status}`,
|
|
215
|
+
});
|
|
216
|
+
} catch (err) {
|
|
217
|
+
checks.push({ name: "qdrant", status: "error", message: err.message });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
checks.push({ name: "engine", status: "ok", message: `${status.entryCount} entries` });
|
|
221
|
+
checks.push({ name: "embedding", status: "ok", message: `Model: ${engine.modelId()}` });
|
|
222
|
+
|
|
223
|
+
const healthy = checks.every((c) => c.status === "ok");
|
|
224
|
+
return {
|
|
225
|
+
artifactId: randomUUID(),
|
|
226
|
+
name: "health_result",
|
|
227
|
+
parts: [{ kind: "data", data: { healthy, checks } }],
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
125
231
|
// ============================================================
|
|
126
232
|
// Skill Router
|
|
127
233
|
// ============================================================
|
|
@@ -140,6 +246,11 @@ export function resolveSkill(parts) {
|
|
|
140
246
|
const lower = part.text.toLowerCase();
|
|
141
247
|
if (/\b(store|save|remember)\b/.test(lower)) return "memory_store";
|
|
142
248
|
if (/\b(search|find|recall|query)\b/.test(lower)) return "memory_search";
|
|
249
|
+
if (/\b(delete|remove)\b/.test(lower)) return "memory_delete";
|
|
250
|
+
if (/\b(get|retrieve)\b/.test(lower)) return "memory_get";
|
|
251
|
+
if (/\b(list)\b/.test(lower)) return "memory_list";
|
|
252
|
+
if (/\b(export|dump)\b/.test(lower)) return "memory_export";
|
|
253
|
+
if (/\b(garbage|cleanup)\b/.test(lower)) return "memory_gc";
|
|
143
254
|
if (/\b(status|health|stats|info)\b/.test(lower)) return "memory_status";
|
|
144
255
|
}
|
|
145
256
|
}
|
|
@@ -149,6 +260,7 @@ export function resolveSkill(parts) {
|
|
|
149
260
|
if (part.kind === "data" && part.data) {
|
|
150
261
|
if (part.data.content) return "memory_store";
|
|
151
262
|
if (part.data.query) return "memory_search";
|
|
263
|
+
if (part.data.id) return "memory_get";
|
|
152
264
|
}
|
|
153
265
|
}
|
|
154
266
|
|
|
@@ -204,6 +316,24 @@ async function handleMessageSend(engine, taskStore, params) {
|
|
|
204
316
|
case "memory_status":
|
|
205
317
|
artifact = handleMemoryStatus(engine);
|
|
206
318
|
break;
|
|
319
|
+
case "memory_delete":
|
|
320
|
+
artifact = await handleMemoryDelete(engine, message.parts);
|
|
321
|
+
break;
|
|
322
|
+
case "memory_get":
|
|
323
|
+
artifact = await handleMemoryGet(engine, message.parts);
|
|
324
|
+
break;
|
|
325
|
+
case "memory_list":
|
|
326
|
+
artifact = handleMemoryList(engine, message.parts);
|
|
327
|
+
break;
|
|
328
|
+
case "memory_export":
|
|
329
|
+
artifact = handleMemoryExport(engine);
|
|
330
|
+
break;
|
|
331
|
+
case "memory_gc":
|
|
332
|
+
artifact = await handleMemoryGc(engine);
|
|
333
|
+
break;
|
|
334
|
+
case "memory_health":
|
|
335
|
+
artifact = await handleMemoryHealth(engine);
|
|
336
|
+
break;
|
|
207
337
|
default:
|
|
208
338
|
task.status = { state: "failed", timestamp: new Date().toISOString() };
|
|
209
339
|
taskStore.set(task.id, task);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { createEngine } from "../core/engine.js";
|
|
3
|
+
import { error, heading } from "../utils/style.js";
|
|
4
|
+
|
|
5
|
+
export function registerGet(program) {
|
|
6
|
+
program
|
|
7
|
+
.command("get")
|
|
8
|
+
.description("Retrieve a memory entry by ID")
|
|
9
|
+
.argument("<id>", "UUID of the memory entry to retrieve")
|
|
10
|
+
.option("-j, --json", "Output as JSON")
|
|
11
|
+
.action(async (id, opts) => {
|
|
12
|
+
try {
|
|
13
|
+
const engine = createEngine();
|
|
14
|
+
const entry = await engine.get(id);
|
|
15
|
+
|
|
16
|
+
if (opts.json) {
|
|
17
|
+
console.log(JSON.stringify(entry, null, 2));
|
|
18
|
+
} else {
|
|
19
|
+
heading(`Memory Entry`);
|
|
20
|
+
console.log();
|
|
21
|
+
console.log(` ${chalk.cyan("ID:")} ${entry.id}`);
|
|
22
|
+
console.log(` ${chalk.cyan("Content:")} ${entry.content}`);
|
|
23
|
+
console.log(` ${chalk.cyan("Namespace:")} ${entry.namespace}`);
|
|
24
|
+
console.log(` ${chalk.cyan("Score:")} ${entry.score}`);
|
|
25
|
+
console.log(
|
|
26
|
+
` ${chalk.cyan("Created:")} ${new Date(entry.createdAt * 1000).toISOString()}`,
|
|
27
|
+
);
|
|
28
|
+
if (entry.expiresAt) {
|
|
29
|
+
console.log(
|
|
30
|
+
` ${chalk.cyan("Expires:")} ${new Date(entry.expiresAt * 1000).toISOString()}`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
console.log();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await engine.shutdown();
|
|
37
|
+
} catch (err) {
|
|
38
|
+
error(err.message);
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
package/src/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { registerExport } from "./commands/export.js";
|
|
|
16
16
|
import { registerImport } from "./commands/import.js";
|
|
17
17
|
import { registerStore } from "./commands/store.js";
|
|
18
18
|
import { registerDelete } from "./commands/delete.js";
|
|
19
|
+
import { registerGet } from "./commands/get.js";
|
|
19
20
|
import { registerHealth } from "./commands/health.js";
|
|
20
21
|
|
|
21
22
|
const { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
@@ -87,6 +88,7 @@ function buildProgram() {
|
|
|
87
88
|
registerImport(program);
|
|
88
89
|
registerStore(program);
|
|
89
90
|
registerDelete(program);
|
|
91
|
+
registerGet(program);
|
|
90
92
|
registerHealth(program);
|
|
91
93
|
|
|
92
94
|
program
|
package/src/mcp/server.js
CHANGED
|
@@ -103,6 +103,26 @@ const TOOLS = [
|
|
|
103
103
|
properties: {},
|
|
104
104
|
},
|
|
105
105
|
},
|
|
106
|
+
{
|
|
107
|
+
name: "memory_health",
|
|
108
|
+
description:
|
|
109
|
+
"Run health checks on the memory engine (Qdrant connectivity, engine status, embedding model)",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
type: "object",
|
|
112
|
+
properties: {},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "memory_get",
|
|
117
|
+
description: "Retrieve a specific memory entry by its UUID",
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
id: { type: "string", description: "UUID of the memory entry to retrieve" },
|
|
122
|
+
},
|
|
123
|
+
required: ["id"],
|
|
124
|
+
},
|
|
125
|
+
},
|
|
106
126
|
];
|
|
107
127
|
|
|
108
128
|
const RESOURCES = [
|
|
@@ -373,6 +393,78 @@ async function handleToolsCall(id, params) {
|
|
|
373
393
|
}
|
|
374
394
|
}
|
|
375
395
|
|
|
396
|
+
if (name === "memory_health") {
|
|
397
|
+
const checks = [];
|
|
398
|
+
const config = loadConfig();
|
|
399
|
+
const qdrantUrl = config.qdrant_url.replace(/:\d+$/, ":6333");
|
|
400
|
+
try {
|
|
401
|
+
const res = await fetch(`${qdrantUrl}/healthz`, {
|
|
402
|
+
signal: AbortSignal.timeout(3000),
|
|
403
|
+
});
|
|
404
|
+
checks.push({
|
|
405
|
+
name: "qdrant",
|
|
406
|
+
status: res.ok ? "ok" : "degraded",
|
|
407
|
+
message: res.ok ? "Reachable" : `HTTP ${res.status}`,
|
|
408
|
+
});
|
|
409
|
+
} catch (err) {
|
|
410
|
+
checks.push({ name: "qdrant", status: "error", message: err.message });
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (eng) {
|
|
414
|
+
try {
|
|
415
|
+
const status = eng.status();
|
|
416
|
+
checks.push({
|
|
417
|
+
name: "engine",
|
|
418
|
+
status: "ok",
|
|
419
|
+
message: `${status.entryCount} entries, tier: ${status.tier}`,
|
|
420
|
+
});
|
|
421
|
+
} catch (err) {
|
|
422
|
+
checks.push({ name: "engine", status: "error", message: err.message });
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
const modelId = eng.modelId();
|
|
426
|
+
checks.push({ name: "embedding", status: "ok", message: modelId });
|
|
427
|
+
} catch (err) {
|
|
428
|
+
checks.push({ name: "embedding", status: "error", message: err.message });
|
|
429
|
+
}
|
|
430
|
+
} else {
|
|
431
|
+
checks.push({ name: "engine", status: "error", message: "Engine not available" });
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const healthy = checks.every((c) => c.status === "ok");
|
|
435
|
+
return respond(id, {
|
|
436
|
+
content: [
|
|
437
|
+
{
|
|
438
|
+
type: "text",
|
|
439
|
+
text: JSON.stringify({ healthy, checks }, null, 2),
|
|
440
|
+
},
|
|
441
|
+
],
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (name === "memory_get") {
|
|
446
|
+
if (!eng) {
|
|
447
|
+
return respondError(id, -32603, "Engine not available");
|
|
448
|
+
}
|
|
449
|
+
const entryId = args?.id;
|
|
450
|
+
if (!entryId) {
|
|
451
|
+
return respondError(id, -32602, "Missing required parameter: id");
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
const entry = await eng.get(entryId);
|
|
455
|
+
return respond(id, {
|
|
456
|
+
content: [
|
|
457
|
+
{
|
|
458
|
+
type: "text",
|
|
459
|
+
text: JSON.stringify(entry, null, 2),
|
|
460
|
+
},
|
|
461
|
+
],
|
|
462
|
+
});
|
|
463
|
+
} catch (err) {
|
|
464
|
+
return respondError(id, -32603, err.message);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
376
468
|
respondError(id, -32601, `Unknown tool: ${name}`);
|
|
377
469
|
}
|
|
378
470
|
|