@contextableai/openclaw-memory-rebac 0.3.1 → 0.3.3
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 +6 -2
- package/dist/cli.js +47 -18
- package/dist/index.js +12 -3
- package/package.json +1 -1
- package/schema.zed +7 -3
package/README.md
CHANGED
|
@@ -168,7 +168,10 @@ When enabled (default: `true`), the plugin captures the last N messages from eac
|
|
|
168
168
|
The SpiceDB schema defines four object types:
|
|
169
169
|
|
|
170
170
|
```
|
|
171
|
-
definition person {
|
|
171
|
+
definition person {
|
|
172
|
+
relation agent: agent
|
|
173
|
+
permission represents = agent
|
|
174
|
+
}
|
|
172
175
|
|
|
173
176
|
definition agent {
|
|
174
177
|
relation owner: person
|
|
@@ -186,7 +189,8 @@ definition memory_fragment {
|
|
|
186
189
|
relation involves: person | agent
|
|
187
190
|
relation shared_by: person | agent
|
|
188
191
|
|
|
189
|
-
|
|
192
|
+
// involves->represents: if a person is involved, their agent can also view
|
|
193
|
+
permission view = involves + shared_by + source_group->access + involves->represents
|
|
190
194
|
permission delete = shared_by
|
|
191
195
|
}
|
|
192
196
|
```
|
package/dist/cli.js
CHANGED
|
@@ -70,31 +70,42 @@ export function registerCommands(cmd, ctx) {
|
|
|
70
70
|
limit: parseInt(opts.limit),
|
|
71
71
|
})
|
|
72
72
|
: [];
|
|
73
|
-
//
|
|
74
|
-
let
|
|
75
|
-
if (
|
|
73
|
+
// Fragment-based recall via involves/view permission
|
|
74
|
+
let fragmentResults = [];
|
|
75
|
+
if (backend.getFragmentsByIds) {
|
|
76
76
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
// Determine the person to search as
|
|
78
|
+
let personSubject;
|
|
79
|
+
if (subject.type === "person") {
|
|
80
|
+
// Direct person search — look up fragments they can view
|
|
81
|
+
personSubject = subject;
|
|
82
|
+
}
|
|
83
|
+
else if (subject.type === "agent") {
|
|
84
|
+
// Agent search — resolve owner identity first
|
|
85
|
+
const ownerId = await lookupAgentOwner(spicedb, subject.id, token);
|
|
86
|
+
if (ownerId) {
|
|
87
|
+
personSubject = { type: "person", id: ownerId };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (personSubject) {
|
|
91
|
+
const viewableIds = await lookupViewableFragments(spicedb, personSubject, token);
|
|
81
92
|
if (viewableIds.length > 0) {
|
|
82
93
|
const groupResultIds = new Set(groupResults.map((r) => r.uuid));
|
|
83
94
|
const newIds = viewableIds.filter((id) => !groupResultIds.has(id));
|
|
84
95
|
if (newIds.length > 0) {
|
|
85
|
-
|
|
96
|
+
fragmentResults = await backend.getFragmentsByIds(newIds);
|
|
86
97
|
}
|
|
87
98
|
}
|
|
88
|
-
if (
|
|
89
|
-
console.log(` + ${
|
|
99
|
+
if (fragmentResults.length > 0) {
|
|
100
|
+
console.log(` + ${fragmentResults.length} result(s) via involves (${personSubject.type}:${personSubject.id})`);
|
|
90
101
|
}
|
|
91
102
|
}
|
|
92
103
|
}
|
|
93
104
|
catch {
|
|
94
|
-
//
|
|
105
|
+
// Fragment lookup failed — proceed with group results only
|
|
95
106
|
}
|
|
96
107
|
}
|
|
97
|
-
const allResults = [...groupResults, ...
|
|
108
|
+
const allResults = [...groupResults, ...fragmentResults];
|
|
98
109
|
if (allResults.length === 0) {
|
|
99
110
|
console.log("No results found.");
|
|
100
111
|
return;
|
|
@@ -552,14 +563,23 @@ export function registerCommands(cmd, ctx) {
|
|
|
552
563
|
.argument("<person-id>", "Owner person ID")
|
|
553
564
|
.action(async (agentId, personId) => {
|
|
554
565
|
try {
|
|
555
|
-
await spicedb.writeRelationships([
|
|
566
|
+
await spicedb.writeRelationships([
|
|
567
|
+
{
|
|
556
568
|
resourceType: "agent",
|
|
557
569
|
resourceId: agentId,
|
|
558
570
|
relation: "owner",
|
|
559
571
|
subjectType: "person",
|
|
560
572
|
subjectId: personId,
|
|
561
|
-
}
|
|
562
|
-
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
resourceType: "person",
|
|
576
|
+
resourceId: personId,
|
|
577
|
+
relation: "agent",
|
|
578
|
+
subjectType: "agent",
|
|
579
|
+
subjectId: agentId,
|
|
580
|
+
},
|
|
581
|
+
]);
|
|
582
|
+
console.log(`Linked agent:${agentId} ↔ person:${personId}`);
|
|
563
583
|
}
|
|
564
584
|
catch (err) {
|
|
565
585
|
console.error(`Failed to write identity link: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -579,14 +599,23 @@ export function registerCommands(cmd, ctx) {
|
|
|
579
599
|
console.log(`No owner link found for agent:${agentId}`);
|
|
580
600
|
return;
|
|
581
601
|
}
|
|
582
|
-
await spicedb.deleteRelationships([
|
|
602
|
+
await spicedb.deleteRelationships([
|
|
603
|
+
{
|
|
583
604
|
resourceType: "agent",
|
|
584
605
|
resourceId: agentId,
|
|
585
606
|
relation: "owner",
|
|
586
607
|
subjectType: "person",
|
|
587
608
|
subjectId: ownerId,
|
|
588
|
-
}
|
|
589
|
-
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
resourceType: "person",
|
|
612
|
+
resourceId: ownerId,
|
|
613
|
+
relation: "agent",
|
|
614
|
+
subjectType: "agent",
|
|
615
|
+
subjectId: agentId,
|
|
616
|
+
},
|
|
617
|
+
]);
|
|
618
|
+
console.log(`Unlinked agent:${agentId} (was ↔ person:${ownerId})`);
|
|
590
619
|
}
|
|
591
620
|
catch (err) {
|
|
592
621
|
console.error(`Failed to remove identity link: ${err instanceof Error ? err.message : String(err)}`);
|
package/dist/index.js
CHANGED
|
@@ -686,16 +686,25 @@ const rebacMemoryPlugin = {
|
|
|
686
686
|
// Write agent → owner relationships from identities config
|
|
687
687
|
for (const [agentId, personId] of Object.entries(cfg.identities)) {
|
|
688
688
|
try {
|
|
689
|
-
const token = await spicedb.writeRelationships([
|
|
689
|
+
const token = await spicedb.writeRelationships([
|
|
690
|
+
{
|
|
690
691
|
resourceType: "agent",
|
|
691
692
|
resourceId: agentId,
|
|
692
693
|
relation: "owner",
|
|
693
694
|
subjectType: "person",
|
|
694
695
|
subjectId: personId,
|
|
695
|
-
}
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
resourceType: "person",
|
|
699
|
+
resourceId: personId,
|
|
700
|
+
relation: "agent",
|
|
701
|
+
subjectType: "agent",
|
|
702
|
+
subjectId: agentId,
|
|
703
|
+
},
|
|
704
|
+
]);
|
|
696
705
|
if (token)
|
|
697
706
|
defaultState.lastWriteToken = token;
|
|
698
|
-
api.logger.info(`openclaw-memory-rebac: linked agent:${agentId}
|
|
707
|
+
api.logger.info(`openclaw-memory-rebac: linked agent:${agentId} ↔ person:${personId}`);
|
|
699
708
|
}
|
|
700
709
|
catch (err) {
|
|
701
710
|
api.logger.warn(`openclaw-memory-rebac: failed to write owner for agent:${agentId}: ${err}`);
|
package/package.json
CHANGED
package/schema.zed
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
definition person {
|
|
1
|
+
definition person {
|
|
2
|
+
relation agent: agent
|
|
3
|
+
permission represents = agent
|
|
4
|
+
}
|
|
2
5
|
|
|
3
6
|
definition agent {
|
|
4
7
|
relation owner: person
|
|
@@ -16,8 +19,9 @@ definition memory_fragment {
|
|
|
16
19
|
relation involves: person | agent
|
|
17
20
|
relation shared_by: person | agent
|
|
18
21
|
|
|
19
|
-
// Can view if: directly involved, shared it,
|
|
20
|
-
|
|
22
|
+
// Can view if: directly involved, shared it, have access to the source group,
|
|
23
|
+
// or are an agent whose owner is involved (involves->agent traversal)
|
|
24
|
+
permission view = involves + shared_by + source_group->access + involves->represents
|
|
21
25
|
// Can delete if: you shared it (owner-level control)
|
|
22
26
|
permission delete = shared_by
|
|
23
27
|
}
|