@contextableai/openclaw-memory-graphiti 0.1.2 → 0.2.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 +54 -0
- package/authorization.ts +27 -38
- package/bin/graphiti-mem.ts +140 -0
- package/cli.ts +522 -0
- package/docker/docker-compose.yml +2 -2
- package/graphiti.ts +75 -6
- package/index.ts +154 -337
- package/package.json +11 -6
- package/search.ts +17 -5
- package/spicedb.ts +190 -9
package/graphiti.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* session ID tracking, and SSE response parsing.
|
|
7
7
|
*
|
|
8
8
|
* Wraps core tools: add_memory, search_nodes, search_memory_facts,
|
|
9
|
-
* get_episodes, delete_episode, get_status
|
|
9
|
+
* get_episodes, delete_episode, get_status, get_entity_edge,
|
|
10
|
+
* delete_entity_edge, clear_graph.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import { randomUUID } from "node:crypto";
|
|
@@ -49,6 +50,8 @@ export type GraphitiFact = {
|
|
|
49
50
|
|
|
50
51
|
export type AddEpisodeResult = {
|
|
51
52
|
episode_uuid: string;
|
|
53
|
+
/** Resolves to the real server-side UUID once Graphiti finishes processing. */
|
|
54
|
+
resolvedUuid: Promise<string>;
|
|
52
55
|
};
|
|
53
56
|
|
|
54
57
|
type JsonRpcRequest = {
|
|
@@ -74,8 +77,17 @@ export class GraphitiClient {
|
|
|
74
77
|
private sessionId: string | null = null;
|
|
75
78
|
private initPromise: Promise<void> | null = null;
|
|
76
79
|
|
|
80
|
+
/** Polling interval (ms) for UUID resolution after addEpisode. */
|
|
81
|
+
uuidPollIntervalMs = 2000;
|
|
82
|
+
/** Max polling attempts for UUID resolution (total wait = interval * attempts). */
|
|
83
|
+
uuidPollMaxAttempts = 15;
|
|
84
|
+
|
|
77
85
|
constructor(private readonly endpoint: string) {}
|
|
78
86
|
|
|
87
|
+
private get mcpUrl(): string {
|
|
88
|
+
return `${this.endpoint}/mcp`;
|
|
89
|
+
}
|
|
90
|
+
|
|
79
91
|
// --------------------------------------------------------------------------
|
|
80
92
|
// MCP Session Lifecycle
|
|
81
93
|
// --------------------------------------------------------------------------
|
|
@@ -100,7 +112,7 @@ export class GraphitiClient {
|
|
|
100
112
|
},
|
|
101
113
|
};
|
|
102
114
|
|
|
103
|
-
const response = await fetch(
|
|
115
|
+
const response = await fetch(this.mcpUrl, {
|
|
104
116
|
method: "POST",
|
|
105
117
|
headers: {
|
|
106
118
|
"Content-Type": "application/json",
|
|
@@ -128,7 +140,7 @@ export class GraphitiClient {
|
|
|
128
140
|
if (this.sessionId) {
|
|
129
141
|
headers["mcp-session-id"] = this.sessionId;
|
|
130
142
|
}
|
|
131
|
-
await fetch(
|
|
143
|
+
await fetch(this.mcpUrl, {
|
|
132
144
|
method: "POST",
|
|
133
145
|
headers,
|
|
134
146
|
body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }),
|
|
@@ -138,7 +150,7 @@ export class GraphitiClient {
|
|
|
138
150
|
async close(): Promise<void> {
|
|
139
151
|
if (this.sessionId) {
|
|
140
152
|
try {
|
|
141
|
-
await fetch(
|
|
153
|
+
await fetch(this.mcpUrl, {
|
|
142
154
|
method: "DELETE",
|
|
143
155
|
headers: { "mcp-session-id": this.sessionId },
|
|
144
156
|
});
|
|
@@ -172,7 +184,7 @@ export class GraphitiClient {
|
|
|
172
184
|
headers["mcp-session-id"] = this.sessionId;
|
|
173
185
|
}
|
|
174
186
|
|
|
175
|
-
const response = await fetch(
|
|
187
|
+
const response = await fetch(this.mcpUrl, {
|
|
176
188
|
method: "POST",
|
|
177
189
|
headers,
|
|
178
190
|
body: JSON.stringify(request),
|
|
@@ -281,7 +293,35 @@ export class GraphitiClient {
|
|
|
281
293
|
}
|
|
282
294
|
|
|
283
295
|
await this.callTool("add_memory", args);
|
|
284
|
-
|
|
296
|
+
|
|
297
|
+
// Graphiti's add_memory queues the episode for async LLM processing and
|
|
298
|
+
// returns only a "queued" message — no UUID. We poll getEpisodes in the
|
|
299
|
+
// background to discover the real server-side UUID by name match.
|
|
300
|
+
let resolvedUuid: Promise<string>;
|
|
301
|
+
if (params.group_id) {
|
|
302
|
+
resolvedUuid = this.resolveEpisodeUuid(params.name, params.group_id);
|
|
303
|
+
resolvedUuid.catch(() => {}); // Prevent unhandled rejection if caller ignores
|
|
304
|
+
} else {
|
|
305
|
+
resolvedUuid = Promise.resolve(trackingUuid);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return { episode_uuid: trackingUuid, resolvedUuid };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private async resolveEpisodeUuid(name: string, groupId: string): Promise<string> {
|
|
312
|
+
for (let i = 0; i < this.uuidPollMaxAttempts; i++) {
|
|
313
|
+
await new Promise((r) => setTimeout(r, this.uuidPollIntervalMs));
|
|
314
|
+
try {
|
|
315
|
+
const episodes = await this.getEpisodes(groupId, 50);
|
|
316
|
+
const match = episodes.find((ep) => ep.name === name);
|
|
317
|
+
if (match) return match.uuid;
|
|
318
|
+
} catch {
|
|
319
|
+
// Retry on transient errors
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
throw new Error(
|
|
323
|
+
`Failed to resolve episode UUID for "${name}" in group "${groupId}" after ${(this.uuidPollMaxAttempts * this.uuidPollIntervalMs) / 1000}s`,
|
|
324
|
+
);
|
|
285
325
|
}
|
|
286
326
|
|
|
287
327
|
async getEpisodes(groupId: string, lastN: number): Promise<GraphitiEpisode[]> {
|
|
@@ -305,6 +345,7 @@ export class GraphitiClient {
|
|
|
305
345
|
group_id?: string;
|
|
306
346
|
group_ids?: string[];
|
|
307
347
|
limit?: number;
|
|
348
|
+
entity_types?: string[];
|
|
308
349
|
}): Promise<GraphitiNode[]> {
|
|
309
350
|
const args: Record<string, unknown> = {
|
|
310
351
|
query: params.query,
|
|
@@ -316,6 +357,9 @@ export class GraphitiClient {
|
|
|
316
357
|
if (params.limit !== undefined) {
|
|
317
358
|
args.max_nodes = params.limit;
|
|
318
359
|
}
|
|
360
|
+
if (params.entity_types && params.entity_types.length > 0) {
|
|
361
|
+
args.entity_types = params.entity_types;
|
|
362
|
+
}
|
|
319
363
|
|
|
320
364
|
const result = await this.callTool("search_nodes", args);
|
|
321
365
|
return parseToolResult<GraphitiNode[]>(result, "nodes");
|
|
@@ -326,6 +370,7 @@ export class GraphitiClient {
|
|
|
326
370
|
group_id?: string;
|
|
327
371
|
group_ids?: string[];
|
|
328
372
|
limit?: number;
|
|
373
|
+
center_node_uuid?: string;
|
|
329
374
|
/** @deprecated No longer supported by the Graphiti MCP server */
|
|
330
375
|
created_after?: string;
|
|
331
376
|
}): Promise<GraphitiFact[]> {
|
|
@@ -339,10 +384,34 @@ export class GraphitiClient {
|
|
|
339
384
|
if (params.limit !== undefined) {
|
|
340
385
|
args.max_facts = params.limit;
|
|
341
386
|
}
|
|
387
|
+
if (params.center_node_uuid) {
|
|
388
|
+
args.center_node_uuid = params.center_node_uuid;
|
|
389
|
+
}
|
|
342
390
|
|
|
343
391
|
const result = await this.callTool("search_memory_facts", args);
|
|
344
392
|
return parseToolResult<GraphitiFact[]>(result, "facts");
|
|
345
393
|
}
|
|
394
|
+
|
|
395
|
+
// --------------------------------------------------------------------------
|
|
396
|
+
// Entity Edge Operations
|
|
397
|
+
// --------------------------------------------------------------------------
|
|
398
|
+
|
|
399
|
+
async getEntityEdge(uuid: string): Promise<GraphitiFact> {
|
|
400
|
+
const result = await this.callTool("get_entity_edge", { uuid });
|
|
401
|
+
return parseJsonResult<GraphitiFact>(result);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async deleteEntityEdge(uuid: string): Promise<void> {
|
|
405
|
+
await this.callTool("delete_entity_edge", { uuid });
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async clearGraph(groupIds?: string[]): Promise<void> {
|
|
409
|
+
const args: Record<string, unknown> = {};
|
|
410
|
+
if (groupIds && groupIds.length > 0) {
|
|
411
|
+
args.group_ids = groupIds;
|
|
412
|
+
}
|
|
413
|
+
await this.callTool("clear_graph", args);
|
|
414
|
+
}
|
|
346
415
|
}
|
|
347
416
|
|
|
348
417
|
// ============================================================================
|