@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/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(`${this.endpoint}/mcp`, {
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(`${this.endpoint}/mcp`, {
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(`${this.endpoint}/mcp`, {
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(`${this.endpoint}/mcp`, {
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
- return { episode_uuid: trackingUuid };
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
  // ============================================================================