@contextableai/openclaw-memory-rebac 0.1.2 → 0.1.4
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/dist/backend.d.ts
CHANGED
|
@@ -117,7 +117,7 @@ export interface MemoryBackend {
|
|
|
117
117
|
* Optional: not all backends support sub-dataset deletion.
|
|
118
118
|
* Returns true if deleted, false if the backend doesn't support it.
|
|
119
119
|
*/
|
|
120
|
-
deleteFragment?(uuid: string): Promise<boolean>;
|
|
120
|
+
deleteFragment?(uuid: string, type?: string): Promise<boolean>;
|
|
121
121
|
/**
|
|
122
122
|
* Discover fragment (fact/edge) UUIDs that were extracted from a stored episode.
|
|
123
123
|
* Called after store() resolves the episode ID to write per-fragment SpiceDB
|
|
@@ -61,7 +61,7 @@ export declare class GraphitiBackend implements MemoryBackend {
|
|
|
61
61
|
getStatus(): Promise<Record<string, unknown>>;
|
|
62
62
|
deleteGroup(groupId: string): Promise<void>;
|
|
63
63
|
listGroups(): Promise<BackendDataset[]>;
|
|
64
|
-
deleteFragment(uuid: string): Promise<boolean>;
|
|
64
|
+
deleteFragment(uuid: string, type?: string): Promise<boolean>;
|
|
65
65
|
getEpisodes(groupId: string, lastN: number): Promise<GraphitiEpisode[]>;
|
|
66
66
|
discoverFragmentIds(episodeId: string): Promise<string[]>;
|
|
67
67
|
getEntityEdge(uuid: string): Promise<FactResult>;
|
|
@@ -146,8 +146,11 @@ export class GraphitiBackend {
|
|
|
146
146
|
// Graphiti has no list-groups API; the CLI can query SpiceDB for this
|
|
147
147
|
return [];
|
|
148
148
|
}
|
|
149
|
-
async deleteFragment(uuid) {
|
|
150
|
-
|
|
149
|
+
async deleteFragment(uuid, type) {
|
|
150
|
+
const path = type === "fact"
|
|
151
|
+
? `/entity-edge/${encodeURIComponent(uuid)}`
|
|
152
|
+
: `/episode/${encodeURIComponent(uuid)}`;
|
|
153
|
+
await this.restCall("DELETE", path);
|
|
151
154
|
return true;
|
|
152
155
|
}
|
|
153
156
|
// --------------------------------------------------------------------------
|
package/dist/cli.js
CHANGED
|
@@ -421,17 +421,26 @@ export function registerCommands(cmd, ctx) {
|
|
|
421
421
|
console.log(); // newline after dots
|
|
422
422
|
}
|
|
423
423
|
console.log(`\nDiscovered ${totalFacts} fact(s) across ${totalEpisodes} episode(s).`);
|
|
424
|
+
// Deduplicate: the same edge can appear in multiple episodes
|
|
425
|
+
const seen = new Set();
|
|
426
|
+
const unique = tuples.filter((t) => {
|
|
427
|
+
const key = `${t.resourceType}:${t.resourceId}#${t.relation}@${t.subjectType}:${t.subjectId}`;
|
|
428
|
+
if (seen.has(key))
|
|
429
|
+
return false;
|
|
430
|
+
seen.add(key);
|
|
431
|
+
return true;
|
|
432
|
+
});
|
|
424
433
|
if (opts.dryRun) {
|
|
425
|
-
console.log(`[dry-run] Would write ${
|
|
434
|
+
console.log(`[dry-run] Would write ${unique.length} SpiceDB relationships (${tuples.length - unique.length} duplicates removed).`);
|
|
426
435
|
return;
|
|
427
436
|
}
|
|
428
|
-
if (
|
|
437
|
+
if (unique.length === 0) {
|
|
429
438
|
console.log("No relationships to write.");
|
|
430
439
|
return;
|
|
431
440
|
}
|
|
432
|
-
console.log(`Writing ${
|
|
441
|
+
console.log(`Writing ${unique.length} SpiceDB relationships (${tuples.length - unique.length} duplicates removed)...`);
|
|
433
442
|
try {
|
|
434
|
-
const count = await spicedb.bulkImportRelationships(
|
|
443
|
+
const count = await spicedb.bulkImportRelationships(unique);
|
|
435
444
|
console.log(`Done: ${count} relationships written.`);
|
|
436
445
|
}
|
|
437
446
|
catch (err) {
|
package/dist/index.js
CHANGED
|
@@ -283,6 +283,7 @@ const rebacMemoryPlugin = {
|
|
|
283
283
|
// Parse optional type prefix — strip it to get the bare UUID
|
|
284
284
|
const colonIdx = id.indexOf(":");
|
|
285
285
|
let uuid;
|
|
286
|
+
let fragmentType;
|
|
286
287
|
if (colonIdx > 0 && colonIdx < 10) {
|
|
287
288
|
const prefix = id.slice(0, colonIdx);
|
|
288
289
|
// "entity" type cannot be deleted this way (graph-backend specific)
|
|
@@ -292,6 +293,7 @@ const rebacMemoryPlugin = {
|
|
|
292
293
|
details: { action: "error", id },
|
|
293
294
|
};
|
|
294
295
|
}
|
|
296
|
+
fragmentType = prefix;
|
|
295
297
|
uuid = id.slice(colonIdx + 1);
|
|
296
298
|
}
|
|
297
299
|
else {
|
|
@@ -323,7 +325,7 @@ const rebacMemoryPlugin = {
|
|
|
323
325
|
// Attempt backend deletion (optional — not all backends support it)
|
|
324
326
|
if (backend.deleteFragment) {
|
|
325
327
|
try {
|
|
326
|
-
await backend.deleteFragment(uuid);
|
|
328
|
+
await backend.deleteFragment(uuid, fragmentType);
|
|
327
329
|
}
|
|
328
330
|
catch (err) {
|
|
329
331
|
api.logger.warn(`openclaw-memory-rebac: backend deletion failed for ${uuid}: ${err}`);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# ============================================================================
|
|
2
2
|
# Graphiti FastAPI REST server configuration
|
|
3
3
|
# ============================================================================
|
|
4
|
+
# Copy this file to .env and fill in your values:
|
|
5
|
+
# cp .env.example .env
|
|
6
|
+
#
|
|
4
7
|
# Each AI component (LLM, embedder, reranker) can independently point to a
|
|
5
8
|
# different service. If EMBEDDING_BASE_URL is not set, it defaults to
|
|
6
9
|
# LLM_BASE_URL (or the OpenAI API if LLM_BASE_URL is also unset).
|
|
@@ -12,19 +15,19 @@
|
|
|
12
15
|
# Ollama: http://host.docker.internal:11434/v1
|
|
13
16
|
# vLLM: http://your-vllm-server:8000/v1
|
|
14
17
|
# OpenAI: (leave LLM_BASE_URL empty to use default)
|
|
15
|
-
LLM_BASE_URL=http://
|
|
16
|
-
LLM_MODEL=
|
|
17
|
-
LLM_API_KEY=
|
|
18
|
+
# LLM_BASE_URL=http://host.docker.internal:11434/v1
|
|
19
|
+
LLM_MODEL=gpt-4o-mini
|
|
20
|
+
LLM_API_KEY=your-api-key-here
|
|
18
21
|
|
|
19
22
|
# ============================================================================
|
|
20
23
|
# Embedder
|
|
21
24
|
# ============================================================================
|
|
22
25
|
# Default: OpenAI text-embedding-3-small.
|
|
23
26
|
# If EMBEDDING_BASE_URL is empty, uses LLM_BASE_URL (or OpenAI default).
|
|
24
|
-
EMBEDDING_MODEL=
|
|
27
|
+
# EMBEDDING_MODEL=text-embedding-3-small
|
|
25
28
|
# EMBEDDING_BASE_URL=
|
|
26
29
|
# EMBEDDING_API_KEY=
|
|
27
|
-
EMBEDDING_DIM=
|
|
30
|
+
# EMBEDDING_DIM=
|
|
28
31
|
|
|
29
32
|
# ============================================================================
|
|
30
33
|
# Reranker / cross-encoder
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# SpiceDB configuration
|
|
2
|
+
# Copy this file to .env and fill in your values:
|
|
3
|
+
# cp .env.example .env
|
|
2
4
|
|
|
3
|
-
# Preshared key for gRPC authentication
|
|
4
|
-
SPICEDB_PRESHARED_KEY=
|
|
5
|
+
# Preshared key for gRPC authentication (CHANGE THIS for production!)
|
|
6
|
+
SPICEDB_PRESHARED_KEY=your-preshared-key-here
|
|
5
7
|
|
|
6
8
|
# PostgreSQL password for SpiceDB backing store
|
|
7
9
|
# SPICEDB_POSTGRES_PASSWORD=spicedb
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contextableai/openclaw-memory-rebac",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "OpenClaw two-layer memory plugin: SpiceDB ReBAC authorization + Graphiti knowledge graph",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
"openclaw.plugin.json",
|
|
24
24
|
"plugin.defaults.json",
|
|
25
25
|
"schema.zed",
|
|
26
|
-
"docker
|
|
26
|
+
"docker/**/*.yml",
|
|
27
|
+
"docker/**/*.py",
|
|
28
|
+
"docker/**/*.example",
|
|
29
|
+
"docker/**/Dockerfile",
|
|
27
30
|
"scripts/",
|
|
28
31
|
"bin/"
|
|
29
32
|
],
|