@akashabot/openclaw-memory-offline-core 0.3.0 → 0.4.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/dist/index.d.ts CHANGED
@@ -166,3 +166,72 @@ export declare function extractFactsSimple(text: string, entityId?: string): Arr
166
166
  object: string;
167
167
  confidence: number;
168
168
  }>;
169
+ export type GraphEdge = {
170
+ subject: string;
171
+ predicate: string;
172
+ object: string;
173
+ confidence: number;
174
+ };
175
+ export type GraphPath = {
176
+ from: string;
177
+ to: string;
178
+ path: Array<{
179
+ entity: string;
180
+ edge?: GraphEdge;
181
+ }>;
182
+ length: number;
183
+ };
184
+ export type GraphStats = {
185
+ totalFacts: number;
186
+ totalEntities: number;
187
+ totalPredicates: number;
188
+ avgConnectionsPerEntity: number;
189
+ mostConnectedEntities: Array<{
190
+ entity: string;
191
+ connections: number;
192
+ }>;
193
+ mostUsedPredicates: Array<{
194
+ predicate: string;
195
+ count: number;
196
+ }>;
197
+ };
198
+ /**
199
+ * Get all facts where the entity is either subject or object.
200
+ */
201
+ export declare function getEntityGraph(db: Database.Database, entity: string): GraphEdge[];
202
+ /**
203
+ * Get all entities directly connected to a given entity.
204
+ */
205
+ export declare function getRelatedEntities(db: Database.Database, entity: string): string[];
206
+ /**
207
+ * Find paths between two entities using BFS (breadth-first search).
208
+ * Returns up to `maxPaths` paths with a maximum depth of `maxDepth`.
209
+ */
210
+ export declare function findPaths(db: Database.Database, fromEntity: string, toEntity: string, maxDepth?: number, maxPaths?: number): GraphPath[];
211
+ /**
212
+ * Get statistics about the knowledge graph.
213
+ */
214
+ export declare function getGraphStats(db: Database.Database): GraphStats;
215
+ /**
216
+ * Export the graph as JSON (for visualization tools).
217
+ */
218
+ export declare function exportGraphJson(db: Database.Database, options?: {
219
+ limit?: number;
220
+ minConfidence?: number;
221
+ entity?: string;
222
+ }): {
223
+ nodes: Array<{
224
+ id: string;
225
+ label: string;
226
+ }>;
227
+ edges: Array<{
228
+ from: string;
229
+ to: string;
230
+ label: string;
231
+ confidence: number;
232
+ }>;
233
+ };
234
+ /**
235
+ * Find all entities matching a pattern (LIKE search).
236
+ */
237
+ export declare function searchEntities(db: Database.Database, pattern: string, limit?: number): string[];
package/dist/index.js CHANGED
@@ -714,3 +714,195 @@ export function extractFactsSimple(text, entityId) {
714
714
  return true;
715
715
  });
716
716
  }
717
+ /**
718
+ * Get all facts where the entity is either subject or object.
719
+ */
720
+ export function getEntityGraph(db, entity) {
721
+ const rows = db
722
+ .prepare(`SELECT subject, predicate, object, confidence
723
+ FROM facts
724
+ WHERE subject = ? OR object = ?
725
+ ORDER BY confidence DESC`)
726
+ .all(entity, entity);
727
+ return rows.map(r => ({
728
+ subject: r.subject,
729
+ predicate: r.predicate,
730
+ object: r.object,
731
+ confidence: r.confidence,
732
+ }));
733
+ }
734
+ /**
735
+ * Get all entities directly connected to a given entity.
736
+ */
737
+ export function getRelatedEntities(db, entity) {
738
+ const rows = db
739
+ .prepare(`SELECT DISTINCT CASE
740
+ WHEN subject = ? THEN object
741
+ ELSE subject
742
+ END AS related
743
+ FROM facts
744
+ WHERE subject = ? OR object = ?`)
745
+ .all(entity, entity, entity);
746
+ return rows.map(r => r.related).filter(e => e !== entity);
747
+ }
748
+ /**
749
+ * Find paths between two entities using BFS (breadth-first search).
750
+ * Returns up to `maxPaths` paths with a maximum depth of `maxDepth`.
751
+ */
752
+ export function findPaths(db, fromEntity, toEntity, maxDepth = 4, maxPaths = 5) {
753
+ // Get all edges for efficient graph traversal
754
+ const allEdges = db
755
+ .prepare(`SELECT subject, predicate, object, confidence FROM facts`)
756
+ .all();
757
+ // Build adjacency list (undirected - we can traverse both ways)
758
+ const adjacency = new Map();
759
+ for (const e of allEdges) {
760
+ const edge = {
761
+ subject: e.subject,
762
+ predicate: e.predicate,
763
+ object: e.object,
764
+ confidence: e.confidence,
765
+ };
766
+ if (!adjacency.has(e.subject))
767
+ adjacency.set(e.subject, []);
768
+ if (!adjacency.has(e.object))
769
+ adjacency.set(e.object, []);
770
+ adjacency.get(e.subject).push({ entity: e.object, edge });
771
+ adjacency.get(e.object).push({ entity: e.subject, edge });
772
+ }
773
+ // BFS to find paths
774
+ const paths = [];
775
+ const queue = [
776
+ { entity: fromEntity, path: [{ entity: fromEntity }] }
777
+ ];
778
+ const visited = new Set();
779
+ while (queue.length > 0 && paths.length < maxPaths) {
780
+ const current = queue.shift();
781
+ if (current.entity === toEntity && current.path.length > 1) {
782
+ paths.push({
783
+ from: fromEntity,
784
+ to: toEntity,
785
+ path: current.path,
786
+ length: current.path.length - 1,
787
+ });
788
+ continue;
789
+ }
790
+ if (current.path.length > maxDepth)
791
+ continue;
792
+ const neighbors = adjacency.get(current.entity) || [];
793
+ for (const neighbor of neighbors) {
794
+ const pathKey = `${current.entity}|${neighbor.entity}`;
795
+ if (visited.has(pathKey))
796
+ continue;
797
+ visited.add(pathKey);
798
+ queue.push({
799
+ entity: neighbor.entity,
800
+ path: [...current.path, { entity: neighbor.entity, edge: neighbor.edge }],
801
+ });
802
+ }
803
+ }
804
+ return paths.sort((a, b) => a.length - b.length);
805
+ }
806
+ /**
807
+ * Get statistics about the knowledge graph.
808
+ */
809
+ export function getGraphStats(db) {
810
+ // Total facts
811
+ const totalFactsRow = db.prepare(`SELECT COUNT(*) as count FROM facts`).get();
812
+ const totalFacts = totalFactsRow?.count ?? 0;
813
+ // Total unique entities (subjects + objects)
814
+ const entitiesRow = db
815
+ .prepare(`SELECT COUNT(DISTINCT entity) as count FROM (
816
+ SELECT subject as entity FROM facts
817
+ UNION
818
+ SELECT object as entity FROM facts
819
+ )`)
820
+ .get();
821
+ const totalEntities = entitiesRow?.count ?? 0;
822
+ // Total predicates
823
+ const predicatesRow = db
824
+ .prepare(`SELECT COUNT(DISTINCT predicate) as count FROM facts`)
825
+ .get();
826
+ const totalPredicates = predicatesRow?.count ?? 0;
827
+ // Most connected entities
828
+ const mostConnected = db
829
+ .prepare(`SELECT entity, COUNT(*) as connections FROM (
830
+ SELECT subject as entity FROM facts
831
+ UNION ALL
832
+ SELECT object as entity FROM facts
833
+ ) GROUP BY entity ORDER BY connections DESC LIMIT 10`)
834
+ .all();
835
+ // Most used predicates
836
+ const mostUsedPredicates = db
837
+ .prepare(`SELECT predicate, COUNT(*) as count FROM facts GROUP BY predicate ORDER BY count DESC LIMIT 10`)
838
+ .all();
839
+ // Average connections per entity
840
+ const avgConnections = totalEntities > 0
841
+ ? Math.round((totalFacts * 2 / totalEntities) * 10) / 10
842
+ : 0;
843
+ return {
844
+ totalFacts,
845
+ totalEntities,
846
+ totalPredicates,
847
+ avgConnectionsPerEntity: avgConnections,
848
+ mostConnectedEntities: mostConnected,
849
+ mostUsedPredicates: mostUsedPredicates,
850
+ };
851
+ }
852
+ /**
853
+ * Export the graph as JSON (for visualization tools).
854
+ */
855
+ export function exportGraphJson(db, options) {
856
+ let facts;
857
+ if (options?.entity) {
858
+ facts = db
859
+ .prepare(`SELECT subject, predicate, object, confidence FROM facts
860
+ WHERE subject = ? OR object = ?
861
+ ORDER BY confidence DESC
862
+ LIMIT ?`)
863
+ .all(options.entity, options.entity, options?.limit ?? 1000);
864
+ }
865
+ else if (options?.minConfidence) {
866
+ facts = db
867
+ .prepare(`SELECT subject, predicate, object, confidence FROM facts
868
+ WHERE confidence >= ?
869
+ ORDER BY confidence DESC
870
+ LIMIT ?`)
871
+ .all(options.minConfidence, options?.limit ?? 1000);
872
+ }
873
+ else {
874
+ facts = db
875
+ .prepare(`SELECT subject, predicate, object, confidence FROM facts
876
+ ORDER BY confidence DESC
877
+ LIMIT ?`)
878
+ .all(options?.limit ?? 1000);
879
+ }
880
+ // Build unique nodes
881
+ const nodeSet = new Set();
882
+ for (const f of facts) {
883
+ nodeSet.add(f.subject);
884
+ nodeSet.add(f.object);
885
+ }
886
+ const nodes = Array.from(nodeSet).map(id => ({ id, label: id }));
887
+ const edges = facts.map(f => ({
888
+ from: f.subject,
889
+ to: f.object,
890
+ label: f.predicate,
891
+ confidence: f.confidence,
892
+ }));
893
+ return { nodes, edges };
894
+ }
895
+ /**
896
+ * Find all entities matching a pattern (LIKE search).
897
+ */
898
+ export function searchEntities(db, pattern, limit = 50) {
899
+ const likePattern = `%${pattern}%`;
900
+ const rows = db
901
+ .prepare(`SELECT DISTINCT entity FROM (
902
+ SELECT subject as entity FROM facts WHERE subject LIKE ?
903
+ UNION
904
+ SELECT object as entity FROM facts WHERE object LIKE ?
905
+ ) LIMIT ?`)
906
+ .all(likePattern, likePattern, limit);
907
+ return rows.map(r => r.entity);
908
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akashabot/openclaw-memory-offline-core",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "Core library for OpenClaw Offline Memory (SQLite FTS + optional embeddings)",
6
6
  "main": "./dist/index.js",