chore: Format files

This commit is contained in:
2026-07-15 19:20:13 +05:30
parent 9838d4ce59
commit 9d18444220
78 changed files with 9416 additions and 5242 deletions

View File

@@ -26,7 +26,11 @@ export function serializeSubjectiveBufferEntry(
const isSelf = viewer.id === entry.intent.actorId;
if (isSelf) {
let details = (entry.intent.selfDescription || entry.intent.description || entry.intent.originalText).trim();
let details = (
entry.intent.selfDescription ||
entry.intent.description ||
entry.intent.originalText
).trim();
if (details.length > 0) {
details = details.charAt(0).toUpperCase() + details.slice(1);
}
@@ -69,7 +73,9 @@ export class BufferRepository {
`);
try {
this.db.exec(`ALTER TABLE buffer_entries ADD COLUMN pinned INTEGER DEFAULT 0;`);
this.db.exec(
`ALTER TABLE buffer_entries ADD COLUMN pinned INTEGER DEFAULT 0;`,
);
} catch {
// ignore
}

View File

@@ -1,6 +1,10 @@
import { z } from "zod";
import { Entity, naturalizeTime } from "@omnia/core";
import { BufferEntry, serializeSubjectiveBufferEntry, BufferRepository } from "./buffer.js";
import {
BufferEntry,
serializeSubjectiveBufferEntry,
BufferRepository,
} from "./buffer.js";
import { LedgerEntry, LedgerRepository } from "./ledger.js";
import { ILLMProvider, IEmbeddingProvider } from "@omnia/llm";
@@ -56,12 +60,18 @@ function checkSceneExit(entity: Entity, bufferEntries: BufferEntry[]): boolean {
// Find the location of the most recent buffer entries
const lastEntry = bufferEntries[bufferEntries.length - 1];
if (lastEntry.locationId && entity.locationId && lastEntry.locationId !== entity.locationId) {
if (
lastEntry.locationId &&
entity.locationId &&
lastEntry.locationId !== entity.locationId
) {
return true;
}
// Also check if there are entries from different locations in the buffer
const locations = new Set(bufferEntries.map(e => e.locationId).filter(loc => loc !== null));
const locations = new Set(
bufferEntries.map((e) => e.locationId).filter((loc) => loc !== null),
);
if (locations.size > 1) {
return true;
}
@@ -75,17 +85,25 @@ function checkIdleDecay(bufferEntries: BufferEntry[]): boolean {
// Check the last N entries
const lastN = bufferEntries.slice(-N);
return lastN.every(e => e.intent.type === "monologue");
return lastN.every((e) => e.intent.type === "monologue");
}
function checkAttributeTrigger(entity: Entity): boolean {
const consciousness = entity.attributes.get("consciousness");
if (consciousness && consciousness.getValue().toLowerCase() === "unconscious") {
if (
consciousness &&
consciousness.getValue().toLowerCase() === "unconscious"
) {
return true;
}
const status = entity.attributes.get("status");
if (status && ["unconscious", "asleep", "dead", "inactive"].includes(status.getValue().toLowerCase())) {
if (
status &&
["unconscious", "asleep", "dead", "inactive"].includes(
status.getValue().toLowerCase(),
)
) {
return true;
}
@@ -108,7 +126,7 @@ export function checkHandoffTrigger(
// Involuntary triggers first (hard)
if (maxContext > 0) {
const memoryLength = getMemorySectionLength(entity, bufferEntries, now);
const charCeiling = maxContext * 4 * 0.60;
const charCeiling = maxContext * 4 * 0.6;
if (memoryLength > charCeiling) {
return "involuntary";
}
@@ -201,10 +219,12 @@ export class HandoffEngine {
return false;
}
const candidatesList = candidates.map((entry) => {
const serialized = serializeSubjectiveBufferEntry(entry, entity);
return `ID: ${entry.id} | Timestamp: ${entry.timestamp} | Location: ${entry.locationId || "None"}\nContent: ${serialized}`;
}).join("\n---\n");
const candidatesList = candidates
.map((entry) => {
const serialized = serializeSubjectiveBufferEntry(entry, entity);
return `ID: ${entry.id} | Timestamp: ${entry.timestamp} | Location: ${entry.locationId || "None"}\nContent: ${serialized}`;
})
.join("\n---\n");
const systemPrompt = `
You are the memory Handoff Engine. Your task is to process a list of recent working memory buffer entries for an entity and select which memories to promote to the long-term Ledger, and which to forget or summarize.
@@ -239,7 +259,11 @@ ${candidatesList}
}
const result = response.data;
const db = (this.bufferRepo as unknown as { db: { transaction: (fn: () => void) => void } }).db;
const db = (
this.bufferRepo as unknown as {
db: { transaction: (fn: () => void) => void };
}
).db;
const ledgerEntries: LedgerEntry[] = [];
for (const chunk of result.chunks) {
@@ -252,7 +276,11 @@ ${candidatesList}
}
ledgerEntries.push({
id: "ledger-" + Math.random().toString(36).substr(2, 9) + "-" + Date.now(),
id:
"ledger-" +
Math.random().toString(36).substr(2, 9) +
"-" +
Date.now(),
ownerId: entity.id,
timestamp: now.toISOString(),
locationId: entity.locationId,

View File

@@ -82,7 +82,7 @@ export class LedgerRepository {
entry.importance,
entry.embedding.length > 0
? Buffer.from(new Float32Array(entry.embedding).buffer)
: null
: null,
);
deleteEntities.run(entry.id);
@@ -92,14 +92,18 @@ export class LedgerRepository {
})();
}
private mapRowToEntry(row: Record<string, unknown>, involvedEntityIds: string[]): LedgerEntry {
private mapRowToEntry(
row: Record<string, unknown>,
involvedEntityIds: string[],
): LedgerEntry {
const embedding: number[] = row.embedding
? Array.from(
new Float32Array(
(row.embedding as Buffer).buffer,
(row.embedding as Buffer).byteOffset,
(row.embedding as Buffer).byteLength / Float32Array.BYTES_PER_ELEMENT
)
(row.embedding as Buffer).byteLength /
Float32Array.BYTES_PER_ELEMENT,
),
)
: [];
@@ -123,7 +127,7 @@ export class LedgerRepository {
SELECT id, owner_id, timestamp, location_id, content, quotes_json, importance, embedding
FROM ledger_entries
WHERE id = ?
`
`,
)
.get(id) as Record<string, unknown> | undefined;
@@ -133,11 +137,14 @@ export class LedgerRepository {
.prepare(
`
SELECT entity_id FROM ledger_involved_entities WHERE entry_id = ?
`
`,
)
.all(id) as { entity_id: string }[];
return this.mapRowToEntry(row, entitiesRows.map((er) => er.entity_id));
return this.mapRowToEntry(
row,
entitiesRows.map((er) => er.entity_id),
);
}
/**
@@ -151,7 +158,7 @@ export class LedgerRepository {
ownerId: string,
currentLocationId: string | null,
currentInvolvedEntityIds: string[],
limit: number = 20
limit: number = 20,
): LedgerEntry[] {
let query = `
SELECT DISTINCT le.id, le.owner_id, le.timestamp, le.location_id, le.content, le.quotes_json, le.importance, le.embedding
@@ -182,7 +189,10 @@ export class LedgerRepository {
`;
params.push(limit);
const rows = this.db.prepare(query).all(...params) as Record<string, unknown>[];
const rows = this.db.prepare(query).all(...params) as Record<
string,
unknown
>[];
if (rows.length === 0) return [];
@@ -193,7 +203,7 @@ export class LedgerRepository {
`
SELECT entry_id, entity_id FROM ledger_involved_entities
WHERE entry_id IN (${placeholders})
`
`,
)
.all(...entryIds) as { entry_id: string; entity_id: string }[];
@@ -205,7 +215,9 @@ export class LedgerRepository {
entitiesMap.get(er.entry_id)!.push(er.entity_id);
}
return rows.map((row) => this.mapRowToEntry(row, entitiesMap.get(row.id as string) || []));
return rows.map((row) =>
this.mapRowToEntry(row, entitiesMap.get(row.id as string) || []),
);
}
private fetchRawNeighbors(ownerId: string, timestamp: string): LedgerEntry[] {
@@ -220,7 +232,7 @@ export class LedgerRepository {
WHERE owner_id = ? AND timestamp < ?
ORDER BY timestamp DESC
LIMIT 1
`
`,
)
.get(ownerId, timestamp) as Record<string, unknown> | undefined;
@@ -237,7 +249,7 @@ export class LedgerRepository {
WHERE owner_id = ? AND timestamp > ?
ORDER BY timestamp ASC
LIMIT 1
`
`,
)
.get(ownerId, timestamp) as Record<string, unknown> | undefined;
@@ -269,16 +281,22 @@ export class LedgerRepository {
importanceWeight?: number;
relevanceWeight?: number;
decayRate?: number;
}
},
): LedgerEntry[] {
const includeAssociativeNeighbors = options?.includeAssociativeNeighbors ?? false;
const includeAssociativeNeighbors =
options?.includeAssociativeNeighbors ?? false;
const recencyWeight = options?.recencyWeight ?? 1.0;
const importanceWeight = options?.importanceWeight ?? 1.0;
const relevanceWeight = options?.relevanceWeight ?? 1.0;
const decayRate = options?.decayRate ?? 0.99;
// Fetch candidate pool (limit 100 to provide enough options for Phase 2 ranking)
const candidates = this.getRelevant(ownerId, currentLocationId, currentInvolvedEntityIds, 100);
const candidates = this.getRelevant(
ownerId,
currentLocationId,
currentInvolvedEntityIds,
100,
);
if (candidates.length === 0) return [];
// Score candidates
@@ -318,7 +336,10 @@ export class LedgerRepository {
for (const entry of selected) {
const rawNeighbors = this.fetchRawNeighbors(ownerId, entry.timestamp);
for (const rn of rawNeighbors) {
if (!finalEntries.some((fe) => fe.id === rn.id) && !neighborMap.has(rn.id)) {
if (
!finalEntries.some((fe) => fe.id === rn.id) &&
!neighborMap.has(rn.id)
) {
neighborMap.set(rn.id, rn);
}
}
@@ -333,7 +354,7 @@ export class LedgerRepository {
`
SELECT entry_id, entity_id FROM ledger_involved_entities
WHERE entry_id IN (${placeholders})
`
`,
)
.all(...neighborIds) as { entry_id: string; entity_id: string }[];
@@ -353,7 +374,10 @@ export class LedgerRepository {
}
// Sort chronologically ASC for the final prompt output
finalEntries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
finalEntries.sort(
(a, b) =>
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(),
);
return finalEntries;
}

View File

@@ -20,7 +20,9 @@ describe("Memory Handoff Tests (Tier 1)", () => {
// Add 12 older entries (older than 30 minutes)
for (let i = 0; i < 12; i++) {
const minutesAgo = 60 - i;
const timestamp = new Date(now.getTime() - minutesAgo * 60 * 1000).toISOString();
const timestamp = new Date(
now.getTime() - minutesAgo * 60 * 1000,
).toISOString();
entries.push({
id: `entry-old-${i}`,
ownerId: "alice",
@@ -80,7 +82,13 @@ describe("Memory Handoff Tests (Tier 1)", () => {
ownerId: "alice",
timestamp: now.toISOString(),
locationId: "room-1",
intent: { type: "dialogue", originalText: "hello", description: "says hello", actorId: "alice", targetIds: [] },
intent: {
type: "dialogue",
originalText: "hello",
description: "says hello",
actorId: "alice",
targetIds: [],
},
};
expect(checkHandoffTrigger(entity, [entryAtRoom1], now)).toBe("voluntary");
@@ -90,7 +98,13 @@ describe("Memory Handoff Tests (Tier 1)", () => {
ownerId: "alice",
timestamp: now.toISOString(),
locationId: "room-2",
intent: { type: "monologue", originalText: "think", description: "thinks", actorId: "alice", targetIds: [] },
intent: {
type: "monologue",
originalText: "think",
description: "thinks",
actorId: "alice",
targetIds: [],
},
}));
expect(checkHandoffTrigger(entity, monologues, now)).toBe("voluntary");
});
@@ -114,7 +128,9 @@ describe("Memory Handoff Tests (Tier 1)", () => {
const entries: BufferEntry[] = [];
for (let i = 0; i < 10; i++) {
const timestamp = new Date(now.getTime() - (50 - i) * 60 * 1000).toISOString();
const timestamp = new Date(
now.getTime() - (50 - i) * 60 * 1000,
).toISOString();
const entry: BufferEntry = {
id: `entry-${i}`,
ownerId: "alice",
@@ -147,14 +163,23 @@ describe("Memory Handoff Tests (Tier 1)", () => {
const llmProvider = new MockLLMProvider([mockHandoffResult]);
const embedProvider = new MockEmbeddingProvider();
const engine = new HandoffEngine(llmProvider, embedProvider, bufferRepo, ledgerRepo);
const engine = new HandoffEngine(
llmProvider,
embedProvider,
bufferRepo,
ledgerRepo,
);
const success = await engine.runHandoff(entity, entries, now);
expect(success).toBe(true);
const ledgerRows = db.prepare("SELECT * FROM ledger_entries WHERE owner_id = ?").all("alice") as Record<string, unknown>[];
const ledgerRows = db
.prepare("SELECT * FROM ledger_entries WHERE owner_id = ?")
.all("alice") as Record<string, unknown>[];
expect(ledgerRows.length).toBe(1);
expect(ledgerRows[0].content).toBe("Alice initiated dialogue and performed various tasks.");
expect(ledgerRows[0].content).toBe(
"Alice initiated dialogue and performed various tasks.",
);
expect(JSON.parse(ledgerRows[0].quotes_json)).toEqual(["Event 0"]);
expect(ledgerRows[0].importance).toBe(5);

View File

@@ -8,7 +8,7 @@ describe("LedgerRepository", () => {
beforeEach(() => {
db = new Database(":memory:");
// We need to create a dummy objects table to satisfy foreign keys
db.exec(`
CREATE TABLE objects (
@@ -51,7 +51,7 @@ describe("LedgerRepository", () => {
expect(loaded?.content).toBe(entry.content);
expect(loaded?.quotes).toEqual(entry.quotes);
expect(loaded?.importance).toBe(5);
// Check float precision
expect(loaded?.embedding[0]).toBeCloseTo(0.1);
expect(loaded?.embedding[1]).toBeCloseTo(0.2);
@@ -113,7 +113,7 @@ describe("LedgerRepository", () => {
});
const relevant = repo.getRelevant("alice", "loc1", ["bob"]);
expect(relevant).toHaveLength(3);
const ids = relevant.map((r) => r.id);
expect(ids).toContain("mem_high_salience"); // due to importance >= 8
@@ -210,16 +210,32 @@ describe("LedgerRepository", () => {
});
// Without neighbors: only returns mem_target
const withoutNeighbors = repo.retrieve("alice", "loc1", [], undefined, new Date("2024-01-10T14:00:00.000Z"), 1, {
includeAssociativeNeighbors: false,
});
const withoutNeighbors = repo.retrieve(
"alice",
"loc1",
[],
undefined,
new Date("2024-01-10T14:00:00.000Z"),
1,
{
includeAssociativeNeighbors: false,
},
);
expect(withoutNeighbors).toHaveLength(1);
expect(withoutNeighbors[0].id).toBe("mem_target");
// With neighbors: returns preceding, target, and succeeding sorted chronologically
const withNeighbors = repo.retrieve("alice", "loc1", [], undefined, new Date("2024-01-10T14:00:00.000Z"), 1, {
includeAssociativeNeighbors: true,
});
const withNeighbors = repo.retrieve(
"alice",
"loc1",
[],
undefined,
new Date("2024-01-10T14:00:00.000Z"),
1,
{
includeAssociativeNeighbors: true,
},
);
expect(withNeighbors).toHaveLength(3);
expect(withNeighbors[0].id).toBe("mem_preceding");
expect(withNeighbors[1].id).toBe("mem_target");

View File

@@ -41,7 +41,9 @@ describe("Subjective Buffer Entry Serializer Tests (Tier 1)", () => {
};
const result = serializeSubjectiveBufferEntry(entry, viewer);
expect(result).toBe("The hooded figure says, 'Hello there' to the bartender");
expect(result).toBe(
"The hooded figure says, 'Hello there' to the bartender",
);
});
test("serializes action intent with outcome details", () => {
@@ -69,7 +71,9 @@ describe("Subjective Buffer Entry Serializer Tests (Tier 1)", () => {
};
const result = serializeSubjectiveBufferEntry(entry, viewer);
expect(result).toBe('The hooded figure attempts to break the lock latch (Outcome: Failed - The lock is made of reinforced steel.)');
expect(result).toBe(
"The hooded figure attempts to break the lock latch (Outcome: Failed - The lock is made of reinforced steel.)",
);
});
test("serializes self-reference and unfamiliar actors", () => {
@@ -110,7 +114,10 @@ describe("Subjective Buffer Entry Serializer Tests (Tier 1)", () => {
},
};
const resultUnfamiliar = serializeSubjectiveBufferEntry(entryUnfamiliar, viewer);
const resultUnfamiliar = serializeSubjectiveBufferEntry(
entryUnfamiliar,
viewer,
);
expect(resultUnfamiliar).toBe("An unfamiliar figure knocks on the door");
});
});
@@ -118,7 +125,7 @@ describe("Subjective Buffer Entry Serializer Tests (Tier 1)", () => {
describe("BufferRepository Persistence Tests (Tier 1)", () => {
test("saves, loads, lists, and deletes buffer entries in SQLite database", () => {
const db = new Database(":memory:");
// We need SQLiteRepository to initialize the objects table because buffer_entries depends on objects(id) via FK
const coreRepo = new SQLiteRepository(db);
const repo = new BufferRepository(db);