cozo-memory 1.0.3 → 1.0.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/README.md +246 -37
- package/dist/export-import-service.js +472 -0
- package/dist/index.js +242 -7
- package/dist/inference-engine.js +9 -2
- package/dist/test-bugfixes.js +374 -0
- package/dist/test-delete-comprehensive.js +174 -0
- package/dist/test-export-import.js +152 -0
- package/dist/test-fixes-simple.js +50 -0
- package/package.json +3 -1
- package/dist/verify_transaction_tool.js +0 -46
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.MemoryServer = exports.USER_ENTITY_TYPE = exports.USER_ENTITY_NAME = exports.USER_ENTITY_ID = exports.DB_PATH = void 0;
|
|
8
8
|
const embedding_service_1 = require("./embedding-service");
|
|
9
|
+
const export_import_service_1 = require("./export-import-service");
|
|
9
10
|
const fastmcp_1 = require("fastmcp");
|
|
10
11
|
const cozo_node_1 = require("cozo-node");
|
|
11
12
|
const zod_1 = require("zod");
|
|
@@ -27,6 +28,47 @@ class MemoryServer {
|
|
|
27
28
|
hybridSearch;
|
|
28
29
|
inferenceEngine;
|
|
29
30
|
initPromise;
|
|
31
|
+
// Metrics tracking
|
|
32
|
+
metrics = {
|
|
33
|
+
operations: {
|
|
34
|
+
create_entity: 0,
|
|
35
|
+
update_entity: 0,
|
|
36
|
+
delete_entity: 0,
|
|
37
|
+
add_observation: 0,
|
|
38
|
+
create_relation: 0,
|
|
39
|
+
search: 0,
|
|
40
|
+
graph_operations: 0
|
|
41
|
+
},
|
|
42
|
+
errors: {
|
|
43
|
+
total: 0,
|
|
44
|
+
by_operation: {}
|
|
45
|
+
},
|
|
46
|
+
performance: {
|
|
47
|
+
last_operation_ms: 0,
|
|
48
|
+
avg_operation_ms: 0,
|
|
49
|
+
total_operations: 0
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
trackOperation(operation, startTime) {
|
|
53
|
+
const duration = Date.now() - startTime;
|
|
54
|
+
this.metrics.performance.last_operation_ms = duration;
|
|
55
|
+
this.metrics.performance.total_operations++;
|
|
56
|
+
// Calculate running average
|
|
57
|
+
const prevAvg = this.metrics.performance.avg_operation_ms;
|
|
58
|
+
const n = this.metrics.performance.total_operations;
|
|
59
|
+
this.metrics.performance.avg_operation_ms = (prevAvg * (n - 1) + duration) / n;
|
|
60
|
+
// Track operation count
|
|
61
|
+
if (operation in this.metrics.operations) {
|
|
62
|
+
this.metrics.operations[operation]++;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
trackError(operation) {
|
|
66
|
+
this.metrics.errors.total++;
|
|
67
|
+
if (!this.metrics.errors.by_operation[operation]) {
|
|
68
|
+
this.metrics.errors.by_operation[operation] = 0;
|
|
69
|
+
}
|
|
70
|
+
this.metrics.errors.by_operation[operation]++;
|
|
71
|
+
}
|
|
30
72
|
constructor(dbPath = exports.DB_PATH) {
|
|
31
73
|
const fullDbPath = DB_ENGINE === "sqlite" ? dbPath + ".db" : dbPath;
|
|
32
74
|
this.db = new cozo_node_1.CozoDb(DB_ENGINE, fullDbPath);
|
|
@@ -934,6 +976,7 @@ class MemoryServer {
|
|
|
934
976
|
});
|
|
935
977
|
}
|
|
936
978
|
async createEntity(args) {
|
|
979
|
+
const startTime = Date.now();
|
|
937
980
|
try {
|
|
938
981
|
if (!args.name || args.name.trim() === "") {
|
|
939
982
|
return { error: "Entity name must not be empty" };
|
|
@@ -957,9 +1000,12 @@ class MemoryServer {
|
|
|
957
1000
|
}
|
|
958
1001
|
}
|
|
959
1002
|
const id = (0, uuid_1.v4)();
|
|
960
|
-
|
|
1003
|
+
const result = await this.createEntityWithId(id, args.name, args.type, args.metadata);
|
|
1004
|
+
this.trackOperation('create_entity', startTime);
|
|
1005
|
+
return result;
|
|
961
1006
|
}
|
|
962
1007
|
catch (error) {
|
|
1008
|
+
this.trackError('create_entity');
|
|
963
1009
|
console.error("Error in create_entity:", error);
|
|
964
1010
|
if (error.display) {
|
|
965
1011
|
console.error("CozoDB Error Details:", error.display);
|
|
@@ -1168,6 +1214,28 @@ class MemoryServer {
|
|
|
1168
1214
|
if (toEntity.rows.length === 0) {
|
|
1169
1215
|
return { error: `Target entity with ID '${args.to_id}' not found` };
|
|
1170
1216
|
}
|
|
1217
|
+
// FIX: Check for duplicate relations
|
|
1218
|
+
try {
|
|
1219
|
+
const existingRel = await this.db.run('?[from_id, to_id, relation_type, strength, metadata] := *relationship{from_id, to_id, relation_type, strength, metadata, @ "NOW"}, from_id = $from, to_id = $to, relation_type = $type', { from: args.from_id, to: args.to_id, type: args.relation_type });
|
|
1220
|
+
if (existingRel.rows.length > 0) {
|
|
1221
|
+
// Update existing relation instead of creating duplicate
|
|
1222
|
+
const now = Date.now() * 1000;
|
|
1223
|
+
await this.db.run(`
|
|
1224
|
+
?[from_id, to_id, relation_type, created_at, strength, metadata] <- [[$from_id, $to_id, $relation_type, [${now}, true], $strength, $metadata]]
|
|
1225
|
+
:put relationship {from_id, to_id, relation_type, created_at => strength, metadata}
|
|
1226
|
+
`, {
|
|
1227
|
+
from_id: args.from_id,
|
|
1228
|
+
to_id: args.to_id,
|
|
1229
|
+
relation_type: args.relation_type,
|
|
1230
|
+
strength: args.strength ?? 1.0,
|
|
1231
|
+
metadata: args.metadata || {}
|
|
1232
|
+
});
|
|
1233
|
+
return { status: "Relationship updated (was duplicate)" };
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
catch (e) {
|
|
1237
|
+
console.error('[Relation] Duplicate check failed, proceeding with insert:', e);
|
|
1238
|
+
}
|
|
1171
1239
|
const now = Date.now() * 1000;
|
|
1172
1240
|
await this.db.run(`?[from_id, to_id, relation_type, created_at, strength, metadata] <- [[$from_id, $to_id, $relation_type, [${now}, true], $strength, $metadata]] :insert relationship {from_id, to_id, relation_type, created_at => strength, metadata}`, {
|
|
1173
1241
|
from_id: args.from_id,
|
|
@@ -1707,23 +1775,54 @@ ids[id] <- $ids
|
|
|
1707
1775
|
}
|
|
1708
1776
|
}
|
|
1709
1777
|
async deleteEntity(args) {
|
|
1778
|
+
const startTime = Date.now();
|
|
1710
1779
|
try {
|
|
1780
|
+
console.error(`[Delete] Starting deletion for entity: ${args.entity_id}`);
|
|
1711
1781
|
// 1. Check if entity exists
|
|
1712
1782
|
const entityRes = await this.db.run('?[name] := *entity{id: $id, name, @ "NOW"}', { id: args.entity_id });
|
|
1713
1783
|
if (entityRes.rows.length === 0) {
|
|
1784
|
+
console.error(`[Delete] Entity not found: ${args.entity_id}`);
|
|
1714
1785
|
return { error: `Entity with ID '${args.entity_id}' not found` };
|
|
1715
1786
|
}
|
|
1716
|
-
|
|
1787
|
+
console.error(`[Delete] Entity found: ${entityRes.rows[0][0]}`);
|
|
1788
|
+
// 2. Count related data before deletion
|
|
1789
|
+
const obsCount = await this.db.run('?[count(id)] := *observation{id, entity_id, @ "NOW"}, entity_id = $id', { id: args.entity_id });
|
|
1790
|
+
const relOutCount = await this.db.run('?[count(from_id)] := *relationship{from_id, to_id, @ "NOW"}, from_id = $id', { id: args.entity_id });
|
|
1791
|
+
const relInCount = await this.db.run('?[count(to_id)] := *relationship{from_id, to_id, @ "NOW"}, to_id = $id', { id: args.entity_id });
|
|
1792
|
+
console.error(`[Delete] Related data: ${obsCount.rows[0][0]} observations, ${relOutCount.rows[0][0]} outgoing relations, ${relInCount.rows[0][0]} incoming relations`);
|
|
1793
|
+
// 3. Delete all related data in a transaction (block)
|
|
1794
|
+
console.error(`[Delete] Executing deletion transaction...`);
|
|
1717
1795
|
await this.db.run(`
|
|
1718
1796
|
{ ?[id, created_at] := *observation{id, entity_id, created_at}, entity_id = $target_id :rm observation {id, created_at} }
|
|
1719
1797
|
{ ?[from_id, to_id, relation_type, created_at] := *relationship{from_id, to_id, relation_type, created_at}, from_id = $target_id :rm relationship {from_id, to_id, relation_type, created_at} }
|
|
1720
1798
|
{ ?[from_id, to_id, relation_type, created_at] := *relationship{from_id, to_id, relation_type, created_at}, to_id = $target_id :rm relationship {from_id, to_id, relation_type, created_at} }
|
|
1721
1799
|
{ ?[id, created_at] := *entity{id, created_at}, id = $target_id :rm entity {id, created_at} }
|
|
1722
1800
|
`, { target_id: args.entity_id });
|
|
1723
|
-
|
|
1801
|
+
// 4. Verify deletion
|
|
1802
|
+
const verifyEntity = await this.db.run('?[id] := *entity{id, @ "NOW"}, id = $id', { id: args.entity_id });
|
|
1803
|
+
const verifyObs = await this.db.run('?[count(id)] := *observation{id, entity_id, @ "NOW"}, entity_id = $id', { id: args.entity_id });
|
|
1804
|
+
const verifyRelOut = await this.db.run('?[count(from_id)] := *relationship{from_id, @ "NOW"}, from_id = $id', { id: args.entity_id });
|
|
1805
|
+
const verifyRelIn = await this.db.run('?[count(to_id)] := *relationship{to_id, @ "NOW"}, to_id = $id', { id: args.entity_id });
|
|
1806
|
+
console.error(`[Delete] Verification: entity exists=${verifyEntity.rows.length > 0}, observations=${verifyObs.rows[0][0]}, outgoing_relations=${verifyRelOut.rows[0][0]}, incoming_relations=${verifyRelIn.rows[0][0]}`);
|
|
1807
|
+
if (verifyEntity.rows.length > 0) {
|
|
1808
|
+
console.error(`[Delete] WARNING: Entity still visible after deletion!`);
|
|
1809
|
+
}
|
|
1810
|
+
else {
|
|
1811
|
+
console.error(`[Delete] ✓ Entity successfully deleted`);
|
|
1812
|
+
}
|
|
1813
|
+
this.trackOperation('delete_entity', startTime);
|
|
1814
|
+
return {
|
|
1815
|
+
status: "Entity and all related data deleted",
|
|
1816
|
+
deleted: {
|
|
1817
|
+
observations: obsCount.rows[0][0],
|
|
1818
|
+
outgoing_relations: relOutCount.rows[0][0],
|
|
1819
|
+
incoming_relations: relInCount.rows[0][0]
|
|
1820
|
+
}
|
|
1821
|
+
};
|
|
1724
1822
|
}
|
|
1725
1823
|
catch (error) {
|
|
1726
|
-
console.error("Error during deletion:", error);
|
|
1824
|
+
console.error("[Delete] Error during deletion:", error);
|
|
1825
|
+
this.trackError('delete_entity');
|
|
1727
1826
|
return { error: "Deletion failed", message: error.message };
|
|
1728
1827
|
}
|
|
1729
1828
|
}
|
|
@@ -1865,6 +1964,26 @@ ids[id] <- $ids
|
|
|
1865
1964
|
to_id = existingId;
|
|
1866
1965
|
}
|
|
1867
1966
|
}
|
|
1967
|
+
// FIX: Validate that both entities exist before creating relation
|
|
1968
|
+
try {
|
|
1969
|
+
const [fromExists, toExists] = await Promise.all([
|
|
1970
|
+
this.db.run('?[id, name] := *entity{id, name, @ "NOW"}, id = $id', { id: from_id }),
|
|
1971
|
+
this.db.run('?[id, name] := *entity{id, name, @ "NOW"}, id = $id', { id: to_id })
|
|
1972
|
+
]);
|
|
1973
|
+
if (fromExists.rows.length === 0) {
|
|
1974
|
+
return { error: `Transaction failed: Source entity '${from_id}' not found in operation ${i}` };
|
|
1975
|
+
}
|
|
1976
|
+
if (toExists.rows.length === 0) {
|
|
1977
|
+
return { error: `Transaction failed: Target entity '${to_id}' not found in operation ${i}` };
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
catch (e) {
|
|
1981
|
+
return { error: `Transaction failed: Entity validation error in operation ${i}: ${e.message}` };
|
|
1982
|
+
}
|
|
1983
|
+
// FIX: Check for self-reference
|
|
1984
|
+
if (from_id === to_id) {
|
|
1985
|
+
return { error: `Transaction failed: Self-references not allowed in operation ${i}` };
|
|
1986
|
+
}
|
|
1868
1987
|
const now = Date.now() * 1000;
|
|
1869
1988
|
allParams[`rel_from${suffix}`] = from_id;
|
|
1870
1989
|
allParams[`rel_to${suffix}`] = to_id;
|
|
@@ -1991,6 +2110,22 @@ ids[id] <- $ids
|
|
|
1991
2110
|
await this.initPromise;
|
|
1992
2111
|
return this.hybridSearch.graphRag(args);
|
|
1993
2112
|
}
|
|
2113
|
+
async exportMemory(args) {
|
|
2114
|
+
await this.initPromise;
|
|
2115
|
+
const dbService = { run: (query, params) => this.db.run(query, params) };
|
|
2116
|
+
const exportService = new export_import_service_1.ExportImportService(dbService);
|
|
2117
|
+
return exportService.exportMemory(args);
|
|
2118
|
+
}
|
|
2119
|
+
async importMemory(args) {
|
|
2120
|
+
await this.initPromise;
|
|
2121
|
+
const dbService = { run: (query, params) => this.db.run(query, params) };
|
|
2122
|
+
const exportService = new export_import_service_1.ExportImportService(dbService);
|
|
2123
|
+
return exportService.importMemory(args.data, {
|
|
2124
|
+
sourceFormat: args.sourceFormat,
|
|
2125
|
+
mergeStrategy: args.mergeStrategy,
|
|
2126
|
+
defaultEntityType: args.defaultEntityType
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
1994
2129
|
registerTools() {
|
|
1995
2130
|
const MetadataSchema = zod_1.z.record(zod_1.z.string(), zod_1.z.any());
|
|
1996
2131
|
const MutateMemorySchema = zod_1.z.discriminatedUnion("action", [
|
|
@@ -2881,6 +3016,23 @@ Supported actions:
|
|
|
2881
3016
|
});
|
|
2882
3017
|
const ManageSystemSchema = zod_1.z.discriminatedUnion("action", [
|
|
2883
3018
|
zod_1.z.object({ action: zod_1.z.literal("health") }),
|
|
3019
|
+
zod_1.z.object({ action: zod_1.z.literal("metrics") }),
|
|
3020
|
+
zod_1.z.object({
|
|
3021
|
+
action: zod_1.z.literal("export_memory"),
|
|
3022
|
+
format: zod_1.z.enum(["json", "markdown", "obsidian"]).describe("Export format"),
|
|
3023
|
+
includeMetadata: zod_1.z.boolean().optional().describe("Include metadata in export"),
|
|
3024
|
+
includeRelationships: zod_1.z.boolean().optional().describe("Include relationships in export"),
|
|
3025
|
+
includeObservations: zod_1.z.boolean().optional().describe("Include observations in export"),
|
|
3026
|
+
entityTypes: zod_1.z.array(zod_1.z.string()).optional().describe("Filter by entity types"),
|
|
3027
|
+
since: zod_1.z.number().optional().describe("Unix timestamp (ms) - only export data created after this time"),
|
|
3028
|
+
}),
|
|
3029
|
+
zod_1.z.object({
|
|
3030
|
+
action: zod_1.z.literal("import_memory"),
|
|
3031
|
+
data: zod_1.z.union([zod_1.z.string(), zod_1.z.any()]).describe("Import data (JSON string or object)"),
|
|
3032
|
+
sourceFormat: zod_1.z.enum(["mem0", "memgpt", "markdown", "cozo"]).describe("Source format"),
|
|
3033
|
+
mergeStrategy: zod_1.z.enum(["skip", "overwrite", "merge"]).optional().default("skip").describe("How to handle existing entities"),
|
|
3034
|
+
defaultEntityType: zod_1.z.string().optional().default("Note").describe("Default type for imported entities"),
|
|
3035
|
+
}),
|
|
2884
3036
|
zod_1.z.object({
|
|
2885
3037
|
action: zod_1.z.literal("snapshot_create"),
|
|
2886
3038
|
metadata: MetadataSchema.optional().describe("Additional metadata for the snapshot"),
|
|
@@ -2911,8 +3063,18 @@ Supported actions:
|
|
|
2911
3063
|
]);
|
|
2912
3064
|
const ManageSystemParameters = zod_1.z.object({
|
|
2913
3065
|
action: zod_1.z
|
|
2914
|
-
.enum(["health", "snapshot_create", "snapshot_list", "snapshot_diff", "cleanup", "reflect", "clear_memory"])
|
|
3066
|
+
.enum(["health", "metrics", "export_memory", "import_memory", "snapshot_create", "snapshot_list", "snapshot_diff", "cleanup", "reflect", "clear_memory"])
|
|
2915
3067
|
.describe("Action (determines which fields are required)"),
|
|
3068
|
+
format: zod_1.z.enum(["json", "markdown", "obsidian"]).optional().describe("Export format (for export_memory)"),
|
|
3069
|
+
includeMetadata: zod_1.z.boolean().optional().describe("Include metadata (for export_memory)"),
|
|
3070
|
+
includeRelationships: zod_1.z.boolean().optional().describe("Include relationships (for export_memory)"),
|
|
3071
|
+
includeObservations: zod_1.z.boolean().optional().describe("Include observations (for export_memory)"),
|
|
3072
|
+
entityTypes: zod_1.z.array(zod_1.z.string()).optional().describe("Filter by entity types (for export_memory)"),
|
|
3073
|
+
since: zod_1.z.number().optional().describe("Unix timestamp in ms (for export_memory)"),
|
|
3074
|
+
data: zod_1.z.union([zod_1.z.string(), zod_1.z.any()]).optional().describe("Import data (for import_memory)"),
|
|
3075
|
+
sourceFormat: zod_1.z.enum(["mem0", "memgpt", "markdown", "cozo"]).optional().describe("Source format (for import_memory)"),
|
|
3076
|
+
mergeStrategy: zod_1.z.enum(["skip", "overwrite", "merge"]).optional().describe("Merge strategy (for import_memory)"),
|
|
3077
|
+
defaultEntityType: zod_1.z.string().optional().describe("Default entity type (for import_memory)"),
|
|
2916
3078
|
snapshot_id_a: zod_1.z.string().optional().describe("Required for snapshot_diff"),
|
|
2917
3079
|
snapshot_id_b: zod_1.z.string().optional().describe("Required for snapshot_diff"),
|
|
2918
3080
|
metadata: MetadataSchema.optional().describe("Optional for snapshot_create"),
|
|
@@ -2927,7 +3089,17 @@ Supported actions:
|
|
|
2927
3089
|
name: "manage_system",
|
|
2928
3090
|
description: `System maintenance and memory management. Select operation via 'action'.
|
|
2929
3091
|
Supported actions:
|
|
2930
|
-
- 'health': Status check. Returns DB counts
|
|
3092
|
+
- 'health': Status check. Returns DB counts, embedding cache statistics, and performance metrics.
|
|
3093
|
+
- 'metrics': Detailed metrics. Returns operation counts, error statistics, and performance data.
|
|
3094
|
+
- 'export_memory': Export memory to various formats. Params: { format: 'json'|'markdown'|'obsidian', includeMetadata?: boolean, includeRelationships?: boolean, includeObservations?: boolean, entityTypes?: string[], since?: number }.
|
|
3095
|
+
* format='json': Native Cozo format (re-importable)
|
|
3096
|
+
* format='markdown': Human-readable markdown document
|
|
3097
|
+
* format='obsidian': ZIP archive with wiki-links, ready for Obsidian vault
|
|
3098
|
+
- 'import_memory': Import memory from external sources. Params: { data: string|object, sourceFormat: 'mem0'|'memgpt'|'markdown'|'cozo', mergeStrategy?: 'skip'|'overwrite'|'merge', defaultEntityType?: string }.
|
|
3099
|
+
* sourceFormat='mem0': Import from Mem0 format (user_id becomes entity)
|
|
3100
|
+
* sourceFormat='memgpt': Import from MemGPT archival/recall memory
|
|
3101
|
+
* sourceFormat='markdown': Parse markdown sections as entities
|
|
3102
|
+
* sourceFormat='cozo': Import from native Cozo JSON export
|
|
2931
3103
|
- 'snapshot_create': Creates a backup point. Params: { metadata?: object }.
|
|
2932
3104
|
- 'snapshot_list': Lists all available snapshots.
|
|
2933
3105
|
- 'snapshot_diff': Compares two snapshots. Params: { snapshot_id_a: string, snapshot_id_b: string }.
|
|
@@ -2957,7 +3129,16 @@ Supported actions:
|
|
|
2957
3129
|
observations: obsResult.rows.length,
|
|
2958
3130
|
relations: relResult.rows.length,
|
|
2959
3131
|
},
|
|
2960
|
-
performance: {
|
|
3132
|
+
performance: {
|
|
3133
|
+
embedding_cache: this.embeddingService.getCacheStats(),
|
|
3134
|
+
last_operation_ms: this.metrics.performance.last_operation_ms,
|
|
3135
|
+
avg_operation_ms: this.metrics.performance.avg_operation_ms,
|
|
3136
|
+
total_operations: this.metrics.performance.total_operations
|
|
3137
|
+
},
|
|
3138
|
+
metrics: {
|
|
3139
|
+
operations: this.metrics.operations,
|
|
3140
|
+
errors: this.metrics.errors
|
|
3141
|
+
},
|
|
2961
3142
|
timestamp: new Date().toISOString(),
|
|
2962
3143
|
});
|
|
2963
3144
|
}
|
|
@@ -2965,6 +3146,60 @@ Supported actions:
|
|
|
2965
3146
|
return JSON.stringify({ status: "error", error: error.message, timestamp: new Date().toISOString() });
|
|
2966
3147
|
}
|
|
2967
3148
|
}
|
|
3149
|
+
if (input.action === "metrics") {
|
|
3150
|
+
try {
|
|
3151
|
+
return JSON.stringify({
|
|
3152
|
+
operations: this.metrics.operations,
|
|
3153
|
+
errors: this.metrics.errors,
|
|
3154
|
+
performance: this.metrics.performance,
|
|
3155
|
+
embedding_cache: this.embeddingService.getCacheStats(),
|
|
3156
|
+
timestamp: new Date().toISOString()
|
|
3157
|
+
});
|
|
3158
|
+
}
|
|
3159
|
+
catch (error) {
|
|
3160
|
+
return JSON.stringify({ error: "Failed to retrieve metrics", message: error.message });
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
if (input.action === "export_memory") {
|
|
3164
|
+
try {
|
|
3165
|
+
const result = await this.exportMemory({
|
|
3166
|
+
format: input.format,
|
|
3167
|
+
includeMetadata: input.includeMetadata,
|
|
3168
|
+
includeRelationships: input.includeRelationships,
|
|
3169
|
+
includeObservations: input.includeObservations,
|
|
3170
|
+
entityTypes: input.entityTypes,
|
|
3171
|
+
since: input.since
|
|
3172
|
+
});
|
|
3173
|
+
if (result.format === 'obsidian' && result.zipBuffer) {
|
|
3174
|
+
// For Obsidian ZIP, return base64 encoded buffer
|
|
3175
|
+
return JSON.stringify({
|
|
3176
|
+
format: 'obsidian',
|
|
3177
|
+
data: result.zipBuffer.toString('base64'),
|
|
3178
|
+
stats: result.stats,
|
|
3179
|
+
encoding: 'base64',
|
|
3180
|
+
filename: `memory_export_${Date.now()}.zip`
|
|
3181
|
+
});
|
|
3182
|
+
}
|
|
3183
|
+
return JSON.stringify(result);
|
|
3184
|
+
}
|
|
3185
|
+
catch (error) {
|
|
3186
|
+
return JSON.stringify({ error: "Export failed", message: error.message });
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
if (input.action === "import_memory") {
|
|
3190
|
+
try {
|
|
3191
|
+
const result = await this.importMemory({
|
|
3192
|
+
data: input.data,
|
|
3193
|
+
sourceFormat: input.sourceFormat,
|
|
3194
|
+
mergeStrategy: input.mergeStrategy,
|
|
3195
|
+
defaultEntityType: input.defaultEntityType
|
|
3196
|
+
});
|
|
3197
|
+
return JSON.stringify(result);
|
|
3198
|
+
}
|
|
3199
|
+
catch (error) {
|
|
3200
|
+
return JSON.stringify({ error: "Import failed", message: error.message });
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
2968
3203
|
if (input.action === "snapshot_create") {
|
|
2969
3204
|
try {
|
|
2970
3205
|
// Optimization: Sequential execution and count aggregation instead of full fetch
|
package/dist/inference-engine.js
CHANGED
|
@@ -324,9 +324,16 @@ class InferenceEngine {
|
|
|
324
324
|
for (const r of res.rows) {
|
|
325
325
|
if (!r || r.length < 5)
|
|
326
326
|
continue;
|
|
327
|
+
const fromId = String(r[0]);
|
|
328
|
+
const toId = String(r[1]);
|
|
329
|
+
// FIX: Filter out self-references
|
|
330
|
+
if (fromId === toId) {
|
|
331
|
+
console.error(`[Inference] Skipping self-reference in custom rule ${ruleName}: ${fromId} -> ${toId}`);
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
327
334
|
results.push({
|
|
328
|
-
from_id:
|
|
329
|
-
to_id:
|
|
335
|
+
from_id: fromId,
|
|
336
|
+
to_id: toId,
|
|
330
337
|
relation_type: String(r[2]),
|
|
331
338
|
confidence: Number(r[3]),
|
|
332
339
|
reason: String(r[4]),
|