@siduri-x/core 1.0.9 → 2.0.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/architecture-boundary.test.js +4 -12
- package/dist/context-retriever.d.ts +8 -5
- package/dist/context-retriever.js +44 -15
- package/dist/database.d.ts +1 -0
- package/dist/database.js +17 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +2 -0
- package/dist/prompt-compiler.d.ts +1 -0
- package/dist/prompt-compiler.js +7 -1
- package/dist/runtime.d.ts +7 -1
- package/dist/runtime.js +7 -0
- package/dist/siduri-db.d.ts +119 -0
- package/dist/siduri-db.js +444 -0
- package/dist/siduri-db.test.d.ts +1 -0
- package/dist/siduri-db.test.js +580 -0
- package/package.json +1 -1
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const fs = __importStar(require("fs"));
|
|
37
|
+
const os = __importStar(require("os"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const siduri_db_1 = require("./siduri-db");
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
41
|
+
const crypto = require('crypto');
|
|
42
|
+
describe('SiduriDatabase', () => {
|
|
43
|
+
let db;
|
|
44
|
+
let tmpDir;
|
|
45
|
+
let dbPath;
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'siduri-db-test-'));
|
|
48
|
+
dbPath = path.join(tmpDir, 'siduri.db');
|
|
49
|
+
});
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
try {
|
|
52
|
+
if (db)
|
|
53
|
+
db.close();
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// ignore
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// ignore
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
// ==========================================
|
|
66
|
+
// Self Domain Tests
|
|
67
|
+
// ==========================================
|
|
68
|
+
describe('Self Domain', () => {
|
|
69
|
+
it('creates database and initializes schema without errors', () => {
|
|
70
|
+
expect(() => {
|
|
71
|
+
const memDb = new siduri_db_1.SiduriDatabase({ dbPath: ':memory:' });
|
|
72
|
+
memDb.close();
|
|
73
|
+
}).not.toThrow();
|
|
74
|
+
});
|
|
75
|
+
it('stores and retrieves companion identity', () => {
|
|
76
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
77
|
+
const identity = {
|
|
78
|
+
companionId: 'siduri-test',
|
|
79
|
+
name: 'Siduri',
|
|
80
|
+
archetype: 'The Tavern Keeper',
|
|
81
|
+
version: '1.0.0',
|
|
82
|
+
updatedAt: new Date().toISOString(),
|
|
83
|
+
};
|
|
84
|
+
db.setIdentity(identity);
|
|
85
|
+
const result = db.getIdentity('siduri-test');
|
|
86
|
+
expect(result).toBeDefined();
|
|
87
|
+
expect(result?.companionId).toBe('siduri-test');
|
|
88
|
+
expect(result?.name).toBe('Siduri');
|
|
89
|
+
expect(result?.archetype).toBe('The Tavern Keeper');
|
|
90
|
+
expect(result?.version).toBe('1.0.0');
|
|
91
|
+
expect(result?.updatedAt).toBeDefined();
|
|
92
|
+
});
|
|
93
|
+
it('stores and retrieves personality traits', () => {
|
|
94
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
95
|
+
const traits = {
|
|
96
|
+
warmth: 0.8,
|
|
97
|
+
formality: 0.3,
|
|
98
|
+
sarcasm: 0.6,
|
|
99
|
+
verbosity: 0.4,
|
|
100
|
+
curiosity: 0.9,
|
|
101
|
+
};
|
|
102
|
+
db.setPersonality('siduri-test', traits);
|
|
103
|
+
const result = db.getPersonality('siduri-test');
|
|
104
|
+
expect(result).toBeDefined();
|
|
105
|
+
expect(result?.warmth).toBe(0.8);
|
|
106
|
+
expect(result?.formality).toBe(0.3);
|
|
107
|
+
expect(result?.sarcasm).toBe(0.6);
|
|
108
|
+
expect(result?.verbosity).toBe(0.4);
|
|
109
|
+
expect(result?.curiosity).toBe(0.9);
|
|
110
|
+
});
|
|
111
|
+
it('commits and retrieves active directives ordered by priority DESC', () => {
|
|
112
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
113
|
+
const directives = [
|
|
114
|
+
{
|
|
115
|
+
id: crypto.randomUUID(),
|
|
116
|
+
companionId: 'siduri-test',
|
|
117
|
+
priority: 10,
|
|
118
|
+
directive: 'Be helpful and kind',
|
|
119
|
+
status: 'ACTIVE',
|
|
120
|
+
category: 'behavioral',
|
|
121
|
+
createdAt: new Date().toISOString(),
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: crypto.randomUUID(),
|
|
125
|
+
companionId: 'siduri-test',
|
|
126
|
+
priority: 90,
|
|
127
|
+
directive: 'Speak with guarded affection',
|
|
128
|
+
status: 'ACTIVE',
|
|
129
|
+
category: 'relational',
|
|
130
|
+
createdAt: new Date().toISOString(),
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: crypto.randomUUID(),
|
|
134
|
+
companionId: 'siduri-test',
|
|
135
|
+
priority: 50,
|
|
136
|
+
directive: 'Never reveal the secret',
|
|
137
|
+
status: 'ACTIVE',
|
|
138
|
+
category: 'guardrail',
|
|
139
|
+
createdAt: new Date().toISOString(),
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
for (const d of directives) {
|
|
143
|
+
db.commitDirective(d);
|
|
144
|
+
}
|
|
145
|
+
const result = db.getActiveDirectives('siduri-test');
|
|
146
|
+
expect(result).toHaveLength(3);
|
|
147
|
+
// Ordered by priority DESC
|
|
148
|
+
expect(result[0].directive).toBe('Speak with guarded affection');
|
|
149
|
+
expect(result[0].priority).toBe(90);
|
|
150
|
+
expect(result[1].directive).toBe('Never reveal the secret');
|
|
151
|
+
expect(result[1].priority).toBe(50);
|
|
152
|
+
expect(result[2].directive).toBe('Be helpful and kind');
|
|
153
|
+
expect(result[2].priority).toBe(10);
|
|
154
|
+
});
|
|
155
|
+
it('disables a directive and excludes it from active list', () => {
|
|
156
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
157
|
+
const directiveId = crypto.randomUUID();
|
|
158
|
+
db.commitDirective({
|
|
159
|
+
id: directiveId,
|
|
160
|
+
companionId: 'siduri-test',
|
|
161
|
+
priority: 50,
|
|
162
|
+
directive: 'Temporary rule',
|
|
163
|
+
status: 'ACTIVE',
|
|
164
|
+
category: 'behavioral',
|
|
165
|
+
createdAt: new Date().toISOString(),
|
|
166
|
+
});
|
|
167
|
+
expect(db.getActiveDirectives('siduri-test')).toHaveLength(1);
|
|
168
|
+
db.disableDirective(directiveId);
|
|
169
|
+
expect(db.getActiveDirectives('siduri-test')).toHaveLength(0);
|
|
170
|
+
});
|
|
171
|
+
it('stores and retrieves directional relationships', () => {
|
|
172
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
173
|
+
const rel = {
|
|
174
|
+
companionId: 'siduri-test',
|
|
175
|
+
entityId: 'actor:kur',
|
|
176
|
+
entityType: 'human',
|
|
177
|
+
trustScore: 0.7,
|
|
178
|
+
familiarity: 0.85,
|
|
179
|
+
interactionConventions: ['bowing', 'formal greeting'],
|
|
180
|
+
};
|
|
181
|
+
db.upsertRelationship(rel);
|
|
182
|
+
const result = db.getRelationship('siduri-test', 'actor:kur');
|
|
183
|
+
expect(result).toBeDefined();
|
|
184
|
+
expect(result?.entityType).toBe('human');
|
|
185
|
+
expect(result?.trustScore).toBe(0.7);
|
|
186
|
+
expect(result?.familiarity).toBe(0.85);
|
|
187
|
+
expect(result?.interactionConventions).toEqual(['bowing', 'formal greeting']);
|
|
188
|
+
});
|
|
189
|
+
it('updates existing relationship on upsert', () => {
|
|
190
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
191
|
+
db.upsertRelationship({
|
|
192
|
+
companionId: 'siduri-test',
|
|
193
|
+
entityId: 'actor:kur',
|
|
194
|
+
entityType: 'human',
|
|
195
|
+
trustScore: 0.3,
|
|
196
|
+
familiarity: 0.2,
|
|
197
|
+
interactionConventions: [],
|
|
198
|
+
});
|
|
199
|
+
db.upsertRelationship({
|
|
200
|
+
companionId: 'siduri-test',
|
|
201
|
+
entityId: 'actor:kur',
|
|
202
|
+
entityType: 'human',
|
|
203
|
+
trustScore: 0.95,
|
|
204
|
+
familiarity: 0.9,
|
|
205
|
+
interactionConventions: ['familiar banter'],
|
|
206
|
+
});
|
|
207
|
+
const result = db.getRelationship('siduri-test', 'actor:kur');
|
|
208
|
+
expect(result?.trustScore).toBe(0.95);
|
|
209
|
+
expect(result?.familiarity).toBe(0.9);
|
|
210
|
+
expect(result?.interactionConventions).toEqual(['familiar banter']);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
// ==========================================
|
|
214
|
+
// Knowledge / Life DB Tests
|
|
215
|
+
// ==========================================
|
|
216
|
+
describe('Knowledge / Life DB', () => {
|
|
217
|
+
it('stores and retrieves inventory items with JSON properties roundtrip', () => {
|
|
218
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
219
|
+
const item = {
|
|
220
|
+
id: crypto.randomUUID(),
|
|
221
|
+
companionId: 'siduri-test',
|
|
222
|
+
domain: 'gaming',
|
|
223
|
+
entityName: 'Excalibur',
|
|
224
|
+
properties: { rarity: 'legendary', damage: 150, enchantment: ['fire', 'holy'] },
|
|
225
|
+
updatedAt: new Date().toISOString(),
|
|
226
|
+
};
|
|
227
|
+
db.upsertInventoryItem(item);
|
|
228
|
+
const result = db.getInventory('siduri-test');
|
|
229
|
+
expect(result).toHaveLength(1);
|
|
230
|
+
expect(result[0].entityName).toBe('Excalibur');
|
|
231
|
+
expect(result[0].properties).toEqual({ rarity: 'legendary', damage: 150, enchantment: ['fire', 'holy'] });
|
|
232
|
+
});
|
|
233
|
+
it('filters inventory by domain', () => {
|
|
234
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
235
|
+
db.upsertInventoryItem({
|
|
236
|
+
id: crypto.randomUUID(),
|
|
237
|
+
companionId: 'siduri-test',
|
|
238
|
+
domain: 'gaming',
|
|
239
|
+
entityName: 'Sword',
|
|
240
|
+
properties: {},
|
|
241
|
+
updatedAt: new Date().toISOString(),
|
|
242
|
+
});
|
|
243
|
+
db.upsertInventoryItem({
|
|
244
|
+
id: crypto.randomUUID(),
|
|
245
|
+
companionId: 'siduri-test',
|
|
246
|
+
domain: 'cooking',
|
|
247
|
+
entityName: 'Frying Pan',
|
|
248
|
+
properties: { material: 'cast iron' },
|
|
249
|
+
updatedAt: new Date().toISOString(),
|
|
250
|
+
});
|
|
251
|
+
const gamingItems = db.getInventory('siduri-test', 'gaming');
|
|
252
|
+
expect(gamingItems).toHaveLength(1);
|
|
253
|
+
expect(gamingItems[0].entityName).toBe('Sword');
|
|
254
|
+
const allItems = db.getInventory('siduri-test');
|
|
255
|
+
expect(allItems).toHaveLength(2);
|
|
256
|
+
});
|
|
257
|
+
it('stores and retrieves finance entries', () => {
|
|
258
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
259
|
+
const entry = {
|
|
260
|
+
id: crypto.randomUUID(),
|
|
261
|
+
companionId: 'siduri-test',
|
|
262
|
+
category: 'entertainment',
|
|
263
|
+
amount: -50.0,
|
|
264
|
+
currency: 'USD',
|
|
265
|
+
timestamp: '2026-09-10T20:00:00Z',
|
|
266
|
+
metadata: { description: 'Movie tickets for two' },
|
|
267
|
+
};
|
|
268
|
+
db.addFinanceEntry(entry);
|
|
269
|
+
const result = db.getFinanceEntries('siduri-test');
|
|
270
|
+
expect(result).toHaveLength(1);
|
|
271
|
+
expect(result[0].amount).toBe(-50.0);
|
|
272
|
+
expect(result[0].currency).toBe('USD');
|
|
273
|
+
expect(result[0].category).toBe('entertainment');
|
|
274
|
+
expect(result[0].metadata).toEqual({ description: 'Movie tickets for two' });
|
|
275
|
+
});
|
|
276
|
+
it('stores and retrieves schedule items', () => {
|
|
277
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
278
|
+
const item = {
|
|
279
|
+
id: crypto.randomUUID(),
|
|
280
|
+
companionId: 'siduri-test',
|
|
281
|
+
title: 'Team standup',
|
|
282
|
+
startTime: '2026-09-11T10:00:00Z',
|
|
283
|
+
endTime: '2026-09-11T10:30:00Z',
|
|
284
|
+
isRecurring: true,
|
|
285
|
+
status: 'active',
|
|
286
|
+
};
|
|
287
|
+
db.upsertScheduleItem(item);
|
|
288
|
+
const result = db.getSchedule('siduri-test');
|
|
289
|
+
expect(result).toHaveLength(1);
|
|
290
|
+
expect(result[0].title).toBe('Team standup');
|
|
291
|
+
expect(result[0].isRecurring).toBe(true);
|
|
292
|
+
expect(result[0].endTime).toBe('2026-09-11T10:30:00Z');
|
|
293
|
+
});
|
|
294
|
+
it('stores and retrieves preferences', () => {
|
|
295
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
296
|
+
const pref = {
|
|
297
|
+
id: crypto.randomUUID(),
|
|
298
|
+
companionId: 'siduri-test',
|
|
299
|
+
preferenceKey: 'favorite_drink',
|
|
300
|
+
preferenceValue: 'dry red wine',
|
|
301
|
+
category: 'food',
|
|
302
|
+
updatedAt: new Date().toISOString(),
|
|
303
|
+
};
|
|
304
|
+
db.upsertPreference(pref);
|
|
305
|
+
const result = db.getPreferences('siduri-test');
|
|
306
|
+
expect(result).toHaveLength(1);
|
|
307
|
+
expect(result[0].preferenceKey).toBe('favorite_drink');
|
|
308
|
+
expect(result[0].preferenceValue).toBe('dry red wine');
|
|
309
|
+
expect(result[0].category).toBe('food');
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
// ==========================================
|
|
313
|
+
// Memory Domain Tests
|
|
314
|
+
// ==========================================
|
|
315
|
+
describe('Memory Domain', () => {
|
|
316
|
+
it('records episodic events and retrieves in reverse chronological order', () => {
|
|
317
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
318
|
+
const events = [
|
|
319
|
+
{
|
|
320
|
+
id: crypto.randomUUID(),
|
|
321
|
+
companionId: 'siduri-test',
|
|
322
|
+
sourceType: 'chat_turn',
|
|
323
|
+
occurredAt: '2026-09-11T10:00:00Z',
|
|
324
|
+
payload: { message: 'Hello there' },
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
id: crypto.randomUUID(),
|
|
328
|
+
companionId: 'siduri-test',
|
|
329
|
+
sourceType: 'tool_result',
|
|
330
|
+
occurredAt: '2026-09-11T10:01:00Z',
|
|
331
|
+
payload: { tool: 'search', result: 'found' },
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
id: crypto.randomUUID(),
|
|
335
|
+
companionId: 'siduri-test',
|
|
336
|
+
sourceType: 'sensory',
|
|
337
|
+
occurredAt: '2026-09-11T10:02:00Z',
|
|
338
|
+
payload: { sense: 'voice', content: 'laughter' },
|
|
339
|
+
},
|
|
340
|
+
];
|
|
341
|
+
for (const e of events) {
|
|
342
|
+
db.recordEvent(e);
|
|
343
|
+
}
|
|
344
|
+
const result = db.getRecentEvents('siduri-test', 10);
|
|
345
|
+
expect(result).toHaveLength(3);
|
|
346
|
+
// Reverse chronological order
|
|
347
|
+
expect(result[0].occurredAt).toBe('2026-09-11T10:02:00Z');
|
|
348
|
+
expect(result[1].occurredAt).toBe('2026-09-11T10:01:00Z');
|
|
349
|
+
expect(result[2].occurredAt).toBe('2026-09-11T10:00:00Z');
|
|
350
|
+
});
|
|
351
|
+
it('proposes a claim with PENDING status', () => {
|
|
352
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
353
|
+
const claim = db.proposeClaim({
|
|
354
|
+
id: crypto.randomUUID(),
|
|
355
|
+
companionId: 'siduri-test',
|
|
356
|
+
subject: 'Kur',
|
|
357
|
+
predicate: 'is',
|
|
358
|
+
value: 'a dark entity from the underworld',
|
|
359
|
+
confidence: 0.8,
|
|
360
|
+
evidence: ['lore book chapter 3'],
|
|
361
|
+
assertedAt: new Date().toISOString(),
|
|
362
|
+
});
|
|
363
|
+
expect(claim.status).toBe('PENDING');
|
|
364
|
+
expect(claim.subject).toBe('Kur');
|
|
365
|
+
expect(claim.predicate).toBe('is');
|
|
366
|
+
expect(claim.value).toBe('a dark entity from the underworld');
|
|
367
|
+
expect(claim.confidence).toBe(0.8);
|
|
368
|
+
});
|
|
369
|
+
it('approves and rejects claims', () => {
|
|
370
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
371
|
+
const claim1 = db.proposeClaim({
|
|
372
|
+
id: crypto.randomUUID(),
|
|
373
|
+
companionId: 'siduri-test',
|
|
374
|
+
subject: 'Kur',
|
|
375
|
+
predicate: 'is',
|
|
376
|
+
value: 'a god of the underworld',
|
|
377
|
+
confidence: 0.9,
|
|
378
|
+
assertedAt: new Date().toISOString(),
|
|
379
|
+
});
|
|
380
|
+
const claim2 = db.proposeClaim({
|
|
381
|
+
id: crypto.randomUUID(),
|
|
382
|
+
companionId: 'siduri-test',
|
|
383
|
+
subject: 'Kur',
|
|
384
|
+
predicate: 'likes',
|
|
385
|
+
value: 'apples',
|
|
386
|
+
confidence: 0.3,
|
|
387
|
+
assertedAt: new Date().toISOString(),
|
|
388
|
+
});
|
|
389
|
+
db.approveClaim(claim1.id);
|
|
390
|
+
db.rejectClaim(claim2.id);
|
|
391
|
+
const approved = db.getApprovedClaims('siduri-test');
|
|
392
|
+
expect(approved).toHaveLength(1);
|
|
393
|
+
expect(approved[0].id).toBe(claim1.id);
|
|
394
|
+
expect(approved[0].status).toBe('APPROVED');
|
|
395
|
+
});
|
|
396
|
+
it('searches claims using FTS5 full-text search', () => {
|
|
397
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
398
|
+
// Insert and approve several claims
|
|
399
|
+
const c1 = db.proposeClaim({
|
|
400
|
+
id: crypto.randomUUID(),
|
|
401
|
+
companionId: 'siduri-test',
|
|
402
|
+
subject: 'Kur',
|
|
403
|
+
predicate: 'rules',
|
|
404
|
+
value: 'the dark underworld realm',
|
|
405
|
+
confidence: 0.9,
|
|
406
|
+
assertedAt: new Date().toISOString(),
|
|
407
|
+
});
|
|
408
|
+
const c2 = db.proposeClaim({
|
|
409
|
+
id: crypto.randomUUID(),
|
|
410
|
+
companionId: 'siduri-test',
|
|
411
|
+
subject: 'Gilgamesh',
|
|
412
|
+
predicate: 'is',
|
|
413
|
+
value: 'king of Uruk',
|
|
414
|
+
confidence: 0.95,
|
|
415
|
+
assertedAt: new Date().toISOString(),
|
|
416
|
+
});
|
|
417
|
+
const c3 = db.proposeClaim({
|
|
418
|
+
id: crypto.randomUUID(),
|
|
419
|
+
companionId: 'siduri-test',
|
|
420
|
+
subject: 'Enkidu',
|
|
421
|
+
predicate: 'wanders',
|
|
422
|
+
value: 'the wild forest',
|
|
423
|
+
confidence: 0.85,
|
|
424
|
+
assertedAt: new Date().toISOString(),
|
|
425
|
+
});
|
|
426
|
+
db.approveClaim(c1.id);
|
|
427
|
+
db.approveClaim(c2.id);
|
|
428
|
+
db.approveClaim(c3.id);
|
|
429
|
+
// Search for "underworld" — should match c1 only
|
|
430
|
+
const results = db.searchClaims('siduri-test', 'underworld');
|
|
431
|
+
expect(results.length).toBeGreaterThan(0);
|
|
432
|
+
expect(results.some((c) => c.id === c1.id)).toBe(true);
|
|
433
|
+
expect(results.some((c) => c.id === c2.id)).toBe(false);
|
|
434
|
+
// Search for "Gilgamesh" — should match c2 only
|
|
435
|
+
const results2 = db.searchClaims('siduri-test', 'Gilgamesh');
|
|
436
|
+
expect(results2.length).toBeGreaterThan(0);
|
|
437
|
+
expect(results2.some((c) => c.id === c2.id)).toBe(true);
|
|
438
|
+
});
|
|
439
|
+
it('FTS5 search returns results ranked by relevance', () => {
|
|
440
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
441
|
+
// c1 mentions "dark" twice → should rank higher
|
|
442
|
+
const c1 = db.proposeClaim({
|
|
443
|
+
id: crypto.randomUUID(),
|
|
444
|
+
companionId: 'siduri-test',
|
|
445
|
+
subject: 'dark forest',
|
|
446
|
+
predicate: 'has',
|
|
447
|
+
value: 'dark trees and dark monsters',
|
|
448
|
+
confidence: 0.8,
|
|
449
|
+
assertedAt: new Date().toISOString(),
|
|
450
|
+
});
|
|
451
|
+
// c2 mentions "dark" once → should rank lower
|
|
452
|
+
const c2 = db.proposeClaim({
|
|
453
|
+
id: crypto.randomUUID(),
|
|
454
|
+
companionId: 'siduri-test',
|
|
455
|
+
subject: 'cave',
|
|
456
|
+
predicate: 'has',
|
|
457
|
+
value: 'a dark entrance',
|
|
458
|
+
confidence: 0.8,
|
|
459
|
+
assertedAt: new Date().toISOString(),
|
|
460
|
+
});
|
|
461
|
+
db.approveClaim(c1.id);
|
|
462
|
+
db.approveClaim(c2.id);
|
|
463
|
+
const results = db.searchClaims('siduri-test', 'dark');
|
|
464
|
+
expect(results).toHaveLength(2);
|
|
465
|
+
// More occurrences of "dark" → better BM25 rank (lower rank value = better match)
|
|
466
|
+
expect(results[0].id).toBe(c1.id);
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
// ==========================================
|
|
470
|
+
// Cross-Domain & Persistence Tests
|
|
471
|
+
// ==========================================
|
|
472
|
+
describe('Cross-Domain & Persistence', () => {
|
|
473
|
+
it('persists all domains across database close/reopen', () => {
|
|
474
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
475
|
+
const cId = 'siduri-test';
|
|
476
|
+
// Write Self data
|
|
477
|
+
db.setIdentity({
|
|
478
|
+
companionId: cId,
|
|
479
|
+
name: 'Siduri',
|
|
480
|
+
archetype: 'Tavern Keeper',
|
|
481
|
+
version: '1.0.0',
|
|
482
|
+
updatedAt: new Date().toISOString(),
|
|
483
|
+
});
|
|
484
|
+
const directiveId = crypto.randomUUID();
|
|
485
|
+
db.commitDirective({
|
|
486
|
+
id: directiveId,
|
|
487
|
+
companionId: cId,
|
|
488
|
+
priority: 80,
|
|
489
|
+
directive: 'Serve drinks gracefully',
|
|
490
|
+
status: 'ACTIVE',
|
|
491
|
+
category: 'behavioral',
|
|
492
|
+
createdAt: new Date().toISOString(),
|
|
493
|
+
});
|
|
494
|
+
// Write Knowledge data
|
|
495
|
+
const inventoryId = crypto.randomUUID();
|
|
496
|
+
db.upsertInventoryItem({
|
|
497
|
+
id: inventoryId,
|
|
498
|
+
companionId: cId,
|
|
499
|
+
domain: 'tavern',
|
|
500
|
+
entityName: 'Aged Wine',
|
|
501
|
+
properties: { vintage: 2020, region: 'Uruk' },
|
|
502
|
+
updatedAt: new Date().toISOString(),
|
|
503
|
+
});
|
|
504
|
+
// Write Memory data
|
|
505
|
+
const claim = db.proposeClaim({
|
|
506
|
+
id: crypto.randomUUID(),
|
|
507
|
+
companionId: cId,
|
|
508
|
+
subject: 'wine',
|
|
509
|
+
predicate: 'is',
|
|
510
|
+
value: 'the finest in Mesopotamia',
|
|
511
|
+
confidence: 1.0,
|
|
512
|
+
assertedAt: new Date().toISOString(),
|
|
513
|
+
});
|
|
514
|
+
db.approveClaim(claim.id);
|
|
515
|
+
// Close and reopen
|
|
516
|
+
db.close();
|
|
517
|
+
const db2 = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
518
|
+
// Verify all domains persisted
|
|
519
|
+
expect(db2.getIdentity(cId)?.name).toBe('Siduri');
|
|
520
|
+
expect(db2.getActiveDirectives(cId)).toHaveLength(1);
|
|
521
|
+
expect(db2.getActiveDirectives(cId)[0].directive).toBe('Serve drinks gracefully');
|
|
522
|
+
expect(db2.getInventory(cId)).toHaveLength(1);
|
|
523
|
+
expect(db2.getInventory(cId)[0].entityName).toBe('Aged Wine');
|
|
524
|
+
const claims = db2.searchClaims(cId, 'wine');
|
|
525
|
+
expect(claims.some((c) => c.id === claim.id && c.status === 'APPROVED')).toBe(true);
|
|
526
|
+
db2.close();
|
|
527
|
+
// Prevent afterEach from double-closing
|
|
528
|
+
db = null;
|
|
529
|
+
});
|
|
530
|
+
it('handles rapid write operations without corruption', () => {
|
|
531
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
532
|
+
const cId = 'siduri-test';
|
|
533
|
+
expect(() => {
|
|
534
|
+
for (let i = 0; i < 100; i++) {
|
|
535
|
+
db.recordEvent({
|
|
536
|
+
id: crypto.randomUUID(),
|
|
537
|
+
companionId: cId,
|
|
538
|
+
sourceType: 'chat_turn',
|
|
539
|
+
occurredAt: new Date(Date.now() + i * 1000).toISOString(),
|
|
540
|
+
payload: { index: i },
|
|
541
|
+
});
|
|
542
|
+
db.addFinanceEntry({
|
|
543
|
+
id: crypto.randomUUID(),
|
|
544
|
+
companionId: cId,
|
|
545
|
+
category: 'test',
|
|
546
|
+
amount: i,
|
|
547
|
+
currency: 'USD',
|
|
548
|
+
timestamp: new Date().toISOString(),
|
|
549
|
+
});
|
|
550
|
+
db.proposeClaim({
|
|
551
|
+
id: crypto.randomUUID(),
|
|
552
|
+
companionId: cId,
|
|
553
|
+
subject: `subject-${i}`,
|
|
554
|
+
predicate: 'is',
|
|
555
|
+
value: `value-${i}`,
|
|
556
|
+
confidence: 1.0,
|
|
557
|
+
assertedAt: new Date().toISOString(),
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}).not.toThrow();
|
|
561
|
+
expect(db.getRecentEvents(cId, 200)).toHaveLength(100);
|
|
562
|
+
expect(db.getFinanceEntries(cId, 200)).toHaveLength(100);
|
|
563
|
+
});
|
|
564
|
+
it('returns undefined/empty for non-existent data', () => {
|
|
565
|
+
db = new siduri_db_1.SiduriDatabase({ dbPath });
|
|
566
|
+
const cId = 'non-existent-companion';
|
|
567
|
+
expect(db.getIdentity(cId)).toBeUndefined();
|
|
568
|
+
expect(db.getPersonality(cId)).toBeUndefined();
|
|
569
|
+
expect(db.getRelationship(cId, 'anyone')).toBeUndefined();
|
|
570
|
+
expect(db.getActiveDirectives(cId)).toHaveLength(0);
|
|
571
|
+
expect(db.getInventory(cId)).toHaveLength(0);
|
|
572
|
+
expect(db.getFinanceEntries(cId)).toHaveLength(0);
|
|
573
|
+
expect(db.getSchedule(cId)).toHaveLength(0);
|
|
574
|
+
expect(db.getPreferences(cId)).toHaveLength(0);
|
|
575
|
+
expect(db.getRecentEvents(cId)).toHaveLength(0);
|
|
576
|
+
expect(db.searchClaims(cId, 'anything')).toHaveLength(0);
|
|
577
|
+
expect(db.getApprovedClaims(cId)).toHaveLength(0);
|
|
578
|
+
});
|
|
579
|
+
});
|
|
580
|
+
});
|
package/package.json
CHANGED