n8n
Version:
n8n Workflow Automation Tool
826 lines • 40.2 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.N8nMemoryImpl = exports.N8nMemory = void 0;
const agents_1 = require("@n8n/agents");
const di_1 = require("@n8n/di");
const typeorm_1 = require("@n8n/typeorm");
const response_helper_1 = require("../../../response-helper");
const agent_memory_entry_cursor_entity_1 = require("../entities/agent-memory-entry-cursor.entity");
const agent_memory_entry_entity_1 = require("../entities/agent-memory-entry.entity");
const agent_memory_entry_lock_entity_1 = require("../entities/agent-memory-entry-lock.entity");
const agent_memory_entry_source_entity_1 = require("../entities/agent-memory-entry-source.entity");
const agent_observation_cursor_entity_1 = require("../entities/agent-observation-cursor.entity");
const agent_observation_lock_entity_1 = require("../entities/agent-observation-lock.entity");
const agent_observation_entity_1 = require("../entities/agent-observation.entity");
const agent_thread_entity_1 = require("../entities/agent-thread.entity");
const agent_message_repository_1 = require("../repositories/agent-message.repository");
const agent_memory_entry_cursor_repository_1 = require("../repositories/agent-memory-entry-cursor.repository");
const agent_memory_entry_lock_repository_1 = require("../repositories/agent-memory-entry-lock.repository");
const agent_memory_entry_source_repository_1 = require("../repositories/agent-memory-entry-source.repository");
const agent_memory_entry_repository_1 = require("../repositories/agent-memory-entry.repository");
const agent_observation_cursor_repository_1 = require("../repositories/agent-observation-cursor.repository");
const agent_observation_lock_repository_1 = require("../repositories/agent-observation-lock.repository");
const agent_observation_repository_1 = require("../repositories/agent-observation.repository");
const agent_resource_repository_1 = require("../repositories/agent-resource.repository");
const agent_thread_repository_1 = require("../repositories/agent-thread.repository");
const estimateObservationTokens = (text) => Math.ceil(text.length / 4);
let N8nMemory = class N8nMemory {
constructor(threadRepository, messageRepository, resourceRepository, observationRepository, observationCursorRepository, observationLockRepository, memoryEntryRepository, memoryEntryLockRepository, memoryEntrySourceRepository, memoryEntryCursorRepository) {
this.threadRepository = threadRepository;
this.messageRepository = messageRepository;
this.resourceRepository = resourceRepository;
this.observationRepository = observationRepository;
this.observationCursorRepository = observationCursorRepository;
this.observationLockRepository = observationLockRepository;
this.memoryEntryRepository = memoryEntryRepository;
this.memoryEntryLockRepository = memoryEntryLockRepository;
this.memoryEntrySourceRepository = memoryEntrySourceRepository;
this.memoryEntryCursorRepository = memoryEntryCursorRepository;
}
getImplementation(agentId) {
return new N8nMemoryImpl(agentId, this.threadRepository, this.messageRepository, this.resourceRepository, this.observationRepository, this.observationCursorRepository, this.observationLockRepository, this.memoryEntryRepository, this.memoryEntryLockRepository, this.memoryEntrySourceRepository, this.memoryEntryCursorRepository);
}
};
exports.N8nMemory = N8nMemory;
exports.N8nMemory = N8nMemory = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [agent_thread_repository_1.AgentThreadRepository,
agent_message_repository_1.AgentMessageRepository,
agent_resource_repository_1.AgentResourceRepository,
agent_observation_repository_1.AgentObservationRepository,
agent_observation_cursor_repository_1.AgentObservationCursorRepository,
agent_observation_lock_repository_1.AgentObservationLockRepository,
agent_memory_entry_repository_1.AgentMemoryEntryRepository,
agent_memory_entry_lock_repository_1.AgentMemoryEntryLockRepository,
agent_memory_entry_source_repository_1.AgentMemoryEntrySourceRepository,
agent_memory_entry_cursor_repository_1.AgentMemoryEntryCursorRepository])
], N8nMemory);
class N8nMemoryImpl {
constructor(agentId, threadRepository, messageRepository, resourceRepository, observationRepository, observationCursorRepository, observationLockRepository, memoryEntryRepository, memoryEntryLockRepository, memoryEntrySourceRepository, memoryEntryCursorRepository) {
this.agentId = agentId;
this.threadRepository = threadRepository;
this.messageRepository = messageRepository;
this.resourceRepository = resourceRepository;
this.observationRepository = observationRepository;
this.observationCursorRepository = observationCursorRepository;
this.observationLockRepository = observationLockRepository;
this.memoryEntryRepository = memoryEntryRepository;
this.memoryEntryLockRepository = memoryEntryLockRepository;
this.memoryEntrySourceRepository = memoryEntrySourceRepository;
this.memoryEntryCursorRepository = memoryEntryCursorRepository;
this.episodic = {
saveEntryWithSources: async (entry, sources) => await this.saveEpisodicMemoryEntryWithSources(entry, sources),
searchEntries: async (scope, query, opts) => await this.searchEpisodicMemoryEntries(scope, query, opts),
getEntrySources: async (entryIds) => await this.getEpisodicMemoryEntrySources(entryIds),
applyReflection: async (scope, reflection) => await this.applyEpisodicMemoryReflection(scope, reflection),
getCursor: async (scope) => await this.getEpisodicMemoryCursor(scope),
setCursor: async (cursor) => await this.setEpisodicMemoryCursor(cursor),
taskLock: {
acquire: async (resourceId, opts) => await this.acquireEpisodicMemoryTaskLock(resourceId, opts),
release: async (handle) => await this.releaseEpisodicMemoryTaskLock(handle),
},
};
}
async getThread(threadId) {
const entity = await this.threadRepository.findOneBy({ id: threadId });
if (!entity)
return null;
return this.toThread(entity);
}
async saveThread(thread) {
await this.ensureResource(thread.resourceId);
const existing = await this.threadRepository.findOneBy({ id: thread.id });
if (existing) {
if (thread.title !== undefined)
existing.title = thread.title;
if (thread.metadata !== undefined) {
existing.metadata = this.mergeThreadMetadata(existing.metadata, thread.metadata);
}
const saved = await this.threadRepository.save(existing);
return this.toThread(saved);
}
const entity = this.threadRepository.create({
id: thread.id,
resourceId: thread.resourceId,
title: thread.title ?? null,
metadata: thread.metadata ? JSON.stringify(thread.metadata) : null,
});
const saved = await this.threadRepository.save(entity);
return this.toThread(saved);
}
async ensureResource(resourceId) {
const exists = await this.resourceRepository.existsBy({ id: resourceId });
if (!exists) {
await this.resourceRepository.save(this.resourceRepository.create({ id: resourceId, metadata: null }));
}
}
async deleteThread(threadId) {
await this.threadRepository.manager.transaction(async (trx) => {
await this.dropEpisodicEntriesWithoutSources(trx, threadId);
const observationScope = { agentId: this.agentId, observationScopeId: threadId };
await trx.delete(agent_observation_entity_1.AgentObservationEntity, observationScope);
await trx.delete(agent_observation_cursor_entity_1.AgentObservationCursorEntity, observationScope);
await trx.delete(agent_observation_lock_entity_1.AgentObservationLockEntity, observationScope);
await trx.delete(agent_memory_entry_cursor_entity_1.AgentMemoryEntryCursorEntity, observationScope);
await trx.delete(agent_thread_entity_1.AgentThreadEntity, { id: threadId });
});
}
async deleteThreadsByPrefix(threadIdPrefix) {
const observationScopeId = (0, typeorm_1.Like)(`${threadIdPrefix}%`);
await this.threadRepository.manager.transaction(async (trx) => {
await this.dropEpisodicEntriesWithoutSources(trx, observationScopeId);
const observationScope = { agentId: this.agentId, observationScopeId };
await trx.delete(agent_observation_entity_1.AgentObservationEntity, observationScope);
await trx.delete(agent_observation_cursor_entity_1.AgentObservationCursorEntity, observationScope);
await trx.delete(agent_observation_lock_entity_1.AgentObservationLockEntity, observationScope);
await trx.delete(agent_memory_entry_cursor_entity_1.AgentMemoryEntryCursorEntity, observationScope);
await trx.delete(agent_thread_entity_1.AgentThreadEntity, { id: observationScopeId });
});
}
async dropEpisodicEntriesWithoutSources(trx, threadId) {
const sourceRepo = trx.getRepository(agent_memory_entry_source_entity_1.AgentMemoryEntrySourceEntity);
const entryRepo = trx.getRepository(agent_memory_entry_entity_1.AgentMemoryEntryEntity);
const affectedSources = await sourceRepo.find({
select: { memoryEntryId: true },
where: { agentId: this.agentId, threadId },
});
const affectedEntryIds = (0, agents_1.uniqueStrings)(affectedSources.map((source) => source.memoryEntryId));
if (affectedEntryIds.length === 0)
return;
await trx.delete(agent_memory_entry_source_entity_1.AgentMemoryEntrySourceEntity, {
agentId: this.agentId,
threadId,
});
const remainingSources = await sourceRepo.find({
select: { memoryEntryId: true },
where: { agentId: this.agentId, memoryEntryId: (0, typeorm_1.In)(affectedEntryIds) },
});
const entriesWithSources = new Set(remainingSources.map((source) => source.memoryEntryId));
const orphanedEntryIds = affectedEntryIds.filter((id) => !entriesWithSources.has(id));
if (orphanedEntryIds.length === 0)
return;
await entryRepo.update({ agentId: this.agentId, id: (0, typeorm_1.In)(orphanedEntryIds), status: 'active' }, (0, agents_1.droppedLifecycleState)());
}
async getMessages(threadId, opts) {
const where = {
threadId,
...(opts?.before && { createdAt: (0, typeorm_1.LessThan)(opts.before) }),
...(opts?.resourceId !== undefined && { resourceId: opts.resourceId }),
};
const entities = await this.messageRepository.find({
where,
order: { createdAt: opts?.limit !== undefined ? 'DESC' : 'ASC' },
...(opts?.limit !== undefined && { take: opts.limit }),
});
if (opts?.limit !== undefined) {
entities.reverse();
}
return entities.map((e) => this.toAgentDbMessage(e));
}
async saveMessages(args) {
if (args.messages.length === 0)
return;
const now = new Date();
const entities = args.messages.map((dbMsg) => {
const role = 'role' in dbMsg ? dbMsg.role : 'custom';
const type = 'type' in dbMsg ? dbMsg.type : null;
return {
id: dbMsg.id,
threadId: args.threadId,
resourceId: args.resourceId,
role,
type: type ?? null,
content: dbMsg,
createdAt: dbMsg.createdAt,
updatedAt: now,
};
});
await this.messageRepository.upsert(entities, ['id']);
}
async deleteMessages(messageIds) {
if (messageIds.length === 0)
return;
await this.messageRepository.delete(messageIds);
}
async deleteMessagesByThread(threadId, resourceId) {
await this.messageRepository.delete({
threadId,
...(resourceId !== undefined && { resourceId }),
});
}
async appendObservationLogEntries(rows) {
if (rows.length === 0)
return [];
const entities = rows.map((row) => this.observationRepository.create({
agentId: this.agentId,
observationScopeId: row.observationScopeId,
marker: row.marker,
text: row.text,
parentId: row.parentId ?? null,
tokenCount: row.tokenCount ?? estimateObservationTokens(row.text),
...(0, agents_1.activeLifecycleState)(),
createdAt: row.createdAt,
}));
const saved = await this.observationRepository.save(entities);
return saved.map((e) => this.toObservationLogEntry(e));
}
async getActiveObservationLog(scope) {
return await this.getObservationLog({ ...scope, status: 'active' });
}
async getObservationLog(opts) {
const baseWhere = {
agentId: this.agentId,
observationScopeId: opts.observationScopeId,
...(opts.status !== undefined && { status: opts.status }),
...(opts.parentId !== undefined && { parentId: opts.parentId ?? (0, typeorm_1.IsNull)() }),
};
const entities = await this.observationRepository.find({
where: [baseWhere],
order: {
createdAt: opts.order === 'desc' ? 'DESC' : 'ASC',
id: opts.order === 'desc' ? 'DESC' : 'ASC',
},
...(opts.limit !== undefined && { take: opts.limit }),
});
return entities.map((e) => this.toObservationLogEntry(e));
}
async getMessagesForObservationScope(observationScopeId, opts) {
const baseWhere = {
threadId: observationScopeId,
};
const where = opts?.since
? [
{ ...baseWhere, createdAt: (0, typeorm_1.MoreThan)(opts.since.sinceCreatedAt) },
{
...baseWhere,
createdAt: (0, typeorm_1.Equal)(opts.since.sinceCreatedAt),
id: (0, typeorm_1.MoreThan)(opts.since.sinceMessageId),
},
]
: [baseWhere];
const entities = await this.messageRepository.find({
where,
order: { createdAt: 'ASC', id: 'ASC' },
});
return entities.map((e) => this.toAgentDbMessage(e));
}
async dropObservationLogEntries(ids) {
if (ids.length === 0)
return;
await this.observationRepository.update({ id: (0, typeorm_1.In)(ids) }, (0, agents_1.droppedLifecycleState)());
}
async supersedeObservationLogEntries(ids, supersededBy) {
if (ids.length === 0)
return;
await this.observationRepository.update({ id: (0, typeorm_1.In)(ids) }, (0, agents_1.supersededLifecycleState)(supersededBy));
}
async applyObservationLogReflection(scope, reflection) {
return await this.observationRepository.manager.transaction(async (trx) => {
const repo = trx.getRepository(agent_observation_entity_1.AgentObservationEntity);
const activeEntries = await repo.find({
where: {
agentId: this.agentId,
observationScopeId: scope.observationScopeId,
status: 'active',
},
order: { createdAt: 'ASC', id: 'ASC' },
});
const normalized = (0, agents_1.normalizeObservationLogReflection)(activeEntries.map((entry) => this.toObservationLogEntry(entry)), reflection);
const inserted = normalized.merge.length
? await repo.save(normalized.merge.map((entry) => repo.create({
agentId: this.agentId,
observationScopeId: scope.observationScopeId,
marker: entry.marker,
text: entry.text,
parentId: entry.parentId ?? null,
tokenCount: entry.tokenCount ?? estimateObservationTokens(entry.text),
...(0, agents_1.activeLifecycleState)(),
createdAt: entry.createdAt,
})))
: [];
if (normalized.drop.length > 0) {
await repo.update({
agentId: this.agentId,
observationScopeId: scope.observationScopeId,
id: (0, typeorm_1.In)(normalized.drop),
}, (0, agents_1.droppedLifecycleState)());
}
for (const [index, merge] of normalized.merge.entries()) {
const replacement = inserted[index];
if (replacement && merge.supersedes.length > 0) {
await repo.update({
agentId: this.agentId,
observationScopeId: scope.observationScopeId,
id: (0, typeorm_1.In)(merge.supersedes),
}, (0, agents_1.supersededLifecycleState)(replacement.id));
}
}
return {
droppedIds: [...normalized.drop],
supersededIds: normalized.merge.flatMap((entry) => entry.supersedes),
inserted: inserted.map((entry) => this.toObservationLogEntry(entry)),
};
});
}
async getCursor(observationScopeId) {
const entity = await this.observationCursorRepository.findOneBy({
agentId: this.agentId,
observationScopeId,
});
if (!entity)
return null;
return {
observationScopeId: entity.observationScopeId,
lastObservedMessageId: entity.lastObservedMessageId,
lastObservedAt: entity.lastObservedAt,
updatedAt: entity.updatedAt,
};
}
async setCursor(cursor) {
await this.observationCursorRepository.upsert({
agentId: this.agentId,
observationScopeId: cursor.observationScopeId,
lastObservedMessageId: cursor.lastObservedMessageId,
lastObservedAt: cursor.lastObservedAt,
updatedAt: cursor.updatedAt,
}, {
conflictPaths: ['agentId', 'observationScopeId'],
skipUpdateIfNoValuesChanged: false,
});
}
async acquireObservationLogTaskLock(observationScopeId, taskKind, opts) {
const now = new Date();
const heldUntil = new Date(now.getTime() + opts.ttlMs);
const updateResult = await this.observationLockRepository
.createQueryBuilder()
.update(agent_observation_lock_entity_1.AgentObservationLockEntity)
.set({ taskKind, holderId: opts.holderId, heldUntil })
.where('"agentId" = :agentId')
.andWhere('"observationScopeId" = :observationScopeId')
.andWhere('"taskKind" = :taskKind')
.andWhere('("holderId" = :holderId OR "heldUntil" <= :now)')
.setParameters({
agentId: this.agentId,
observationScopeId,
taskKind,
holderId: opts.holderId,
now,
})
.execute();
if ((updateResult.affected ?? 0) > 0) {
return { observationScopeId, taskKind, holderId: opts.holderId, heldUntil };
}
await this.observationLockRepository
.createQueryBuilder()
.insert()
.into(agent_observation_lock_entity_1.AgentObservationLockEntity)
.values({
agentId: this.agentId,
observationScopeId,
taskKind,
holderId: opts.holderId,
heldUntil,
})
.orIgnore()
.execute();
const claimed = await this.observationLockRepository.findOneBy({
agentId: this.agentId,
observationScopeId,
taskKind,
holderId: opts.holderId,
});
if (!claimed)
return null;
return { observationScopeId, taskKind, holderId: opts.holderId, heldUntil };
}
async releaseObservationLogTaskLock(handle) {
await this.releaseScopeLock(handle);
}
async releaseScopeLock(handle) {
await this.observationLockRepository.delete({
agentId: this.agentId,
observationScopeId: handle.observationScopeId,
taskKind: handle.taskKind,
holderId: handle.holderId,
});
}
async acquireEpisodicMemoryTaskLock(resourceId, opts) {
await this.ensureResource(resourceId);
const now = new Date();
const heldUntil = new Date(now.getTime() + opts.ttlMs);
const updateResult = await this.memoryEntryLockRepository
.createQueryBuilder()
.update(agent_memory_entry_lock_entity_1.AgentMemoryEntryLockEntity)
.set({ holderId: opts.holderId, heldUntil })
.where('"agentId" = :agentId')
.andWhere('"resourceId" = :resourceId')
.andWhere('("holderId" = :holderId OR "heldUntil" <= :now)')
.setParameters({
agentId: this.agentId,
resourceId,
holderId: opts.holderId,
now,
})
.execute();
if ((updateResult.affected ?? 0) > 0) {
return { resourceId, holderId: opts.holderId, heldUntil };
}
await this.memoryEntryLockRepository
.createQueryBuilder()
.insert()
.into(agent_memory_entry_lock_entity_1.AgentMemoryEntryLockEntity)
.values({ agentId: this.agentId, resourceId, holderId: opts.holderId, heldUntil })
.orIgnore()
.execute();
const claimed = await this.memoryEntryLockRepository.findOneBy({
agentId: this.agentId,
resourceId,
holderId: opts.holderId,
});
if (!claimed)
return null;
return { resourceId, holderId: opts.holderId, heldUntil };
}
async releaseEpisodicMemoryTaskLock(handle) {
await this.memoryEntryLockRepository.delete({
agentId: this.agentId,
resourceId: handle.resourceId,
holderId: handle.holderId,
});
}
async saveEpisodicMemoryEntryWithSources(entry, sources) {
await this.ensureResource(entry.resourceId);
return await this.memoryEntryRepository.manager.transaction(async (trx) => {
const entryRepo = trx.getRepository(agent_memory_entry_entity_1.AgentMemoryEntryEntity);
const sourceRepo = trx.getRepository(agent_memory_entry_source_entity_1.AgentMemoryEntrySourceEntity);
const contentHash = entry.contentHash ?? (0, agents_1.hashEpisodicMemoryContent)(entry.content);
const now = new Date();
const entity = entryRepo.create({
agentId: this.agentId,
resourceId: entry.resourceId,
content: entry.content,
contentHash,
...(0, agents_1.activeLifecycleState)(),
embeddingModel: entry.embeddingModel ?? null,
embedding: entry.embedding ?? null,
metadata: entry.metadata ?? null,
createdAt: entry.createdAt ?? now,
lastSeenAt: entry.lastSeenAt ?? now,
});
let persisted = null;
try {
const [saved] = await entryRepo.save([entity]);
persisted = saved ?? null;
}
catch (error) {
if (!(error instanceof Error) || !(0, response_helper_1.isUniqueConstraintError)(error))
throw error;
const existing = await entryRepo.findOneBy({
agentId: this.agentId,
resourceId: entry.resourceId,
contentHash,
});
if (!existing)
throw error;
(0, agents_1.markLifecycleActive)(existing);
existing.lastSeenAt = entry.lastSeenAt ?? now;
existing.updatedAt = now;
const [saved] = await entryRepo.save([existing]);
persisted = saved ?? existing;
}
if (!persisted)
return null;
for (const source of sources) {
const evidenceHash = (0, agents_1.hashEpisodicMemoryEvidence)(source.evidenceText);
const sourceEntity = sourceRepo.create({
agentId: this.agentId,
memoryEntryId: persisted.id,
observationId: source.observationId,
threadId: source.threadId,
evidenceHash,
evidenceText: source.evidenceText,
createdAt: source.createdAt,
});
try {
await sourceRepo.save([sourceEntity]);
}
catch (error) {
if (!(error instanceof Error) || !(0, response_helper_1.isUniqueConstraintError)(error))
throw error;
const existing = await sourceRepo.findOneBy({
agentId: this.agentId,
memoryEntryId: persisted.id,
observationId: source.observationId,
evidenceHash,
});
if (!existing)
throw error;
}
}
return this.toEpisodicMemoryEntry(persisted);
});
}
async searchEpisodicMemoryEntries(scope, query, opts) {
const statuses = opts?.includeStatuses ?? ['active'];
const entities = await this.memoryEntryRepository.find({
where: { agentId: this.agentId, resourceId: scope.resourceId, status: (0, typeorm_1.In)(statuses) },
});
return (0, agents_1.rankEpisodicMemoryEntries)(entities.map((entity) => this.toEpisodicMemoryEntry(entity)), query, opts);
}
async getEpisodicMemoryEntrySources(entryIds) {
if (entryIds.length === 0)
return [];
const entities = await this.memoryEntrySourceRepository.find({
where: { agentId: this.agentId, memoryEntryId: (0, typeorm_1.In)(entryIds) },
order: { createdAt: 'ASC', id: 'ASC' },
});
return entities.map((entity) => this.toEpisodicMemoryEntrySource(entity));
}
async applyEpisodicMemoryReflection(scope, reflection) {
return await this.memoryEntryRepository.manager.transaction(async (trx) => {
const entryRepo = trx.getRepository(agent_memory_entry_entity_1.AgentMemoryEntryEntity);
const sourceRepo = trx.getRepository(agent_memory_entry_source_entity_1.AgentMemoryEntrySourceEntity);
const actionIds = (0, agents_1.uniqueStrings)([
...reflection.drop,
...reflection.merge.flatMap((merge) => merge.supersedes),
]);
if (actionIds.length === 0)
return { droppedIds: [], supersededIds: [], inserted: [] };
const activeEntries = await entryRepo.find({
where: {
agentId: this.agentId,
resourceId: scope.resourceId,
id: (0, typeorm_1.In)(actionIds),
status: 'active',
},
});
const activeIds = new Set(activeEntries.map((entry) => entry.id));
const normalized = (0, agents_1.normalizeFlatReflectionActions)({
activeIds,
drop: reflection.drop,
merge: reflection.merge,
normalizeMerge: (entry, supersedes) => ({ ...entry, supersedes }),
});
const now = new Date();
const replacementHashes = (0, agents_1.uniqueStrings)(normalized.merge.map((item) => item.entry.contentHash ?? (0, agents_1.hashEpisodicMemoryContent)(item.entry.content)));
const existingReplacements = replacementHashes.length
? await entryRepo.find({
where: {
agentId: this.agentId,
resourceId: scope.resourceId,
contentHash: (0, typeorm_1.In)(replacementHashes),
},
})
: [];
const existingByHash = new Map(existingReplacements.map((entry) => [entry.contentHash, entry]));
const replacements = [];
for (const item of normalized.merge) {
const contentHash = item.entry.contentHash ?? (0, agents_1.hashEpisodicMemoryContent)(item.entry.content);
const existing = existingByHash.get(contentHash);
const update = {
...(0, agents_1.activeLifecycleState)(),
lastSeenAt: item.entry.lastSeenAt ?? now,
updatedAt: now,
};
if (item.entry.embedding !== undefined)
update.embedding = item.entry.embedding;
if (item.entry.embeddingModel !== undefined) {
update.embeddingModel = item.entry.embeddingModel;
}
if (item.entry.metadata !== undefined)
update.metadata = item.entry.metadata;
if (existing) {
await entryRepo.update({ agentId: this.agentId, resourceId: scope.resourceId, id: existing.id }, update);
replacements.push({
...existing,
...update,
});
continue;
}
const entity = entryRepo.create({
agentId: this.agentId,
resourceId: scope.resourceId,
content: item.entry.content,
contentHash,
...(0, agents_1.activeLifecycleState)(),
embeddingModel: item.entry.embeddingModel ?? null,
embedding: item.entry.embedding ?? null,
metadata: item.entry.metadata ?? null,
createdAt: item.entry.createdAt ?? now,
lastSeenAt: item.entry.lastSeenAt ?? now,
});
try {
const [persisted] = await entryRepo.save([entity]);
if (persisted) {
existingByHash.set(contentHash, persisted);
replacements.push(persisted);
}
}
catch (error) {
if (!(error instanceof Error) || !(0, response_helper_1.isUniqueConstraintError)(error))
throw error;
const persisted = await entryRepo.findOneBy({
agentId: this.agentId,
resourceId: scope.resourceId,
contentHash,
});
if (!persisted)
throw error;
await entryRepo.update({ agentId: this.agentId, resourceId: scope.resourceId, id: persisted.id }, update);
existingByHash.set(contentHash, persisted);
replacements.push({
...persisted,
...update,
});
}
}
const replacementIds = new Set(replacements.map((entry) => entry.id));
const effectiveDrop = normalized.drop.filter((id) => !replacementIds.has(id));
if (effectiveDrop.length > 0) {
await entryRepo.update({
agentId: this.agentId,
resourceId: scope.resourceId,
id: (0, typeorm_1.In)(effectiveDrop),
status: 'active',
}, (0, agents_1.droppedLifecycleState)());
}
const supersededIds = [];
for (const [index, item] of normalized.merge.entries()) {
const replacement = replacements[index];
if (!replacement)
continue;
const sourceRows = await sourceRepo.find({
where: { agentId: this.agentId, memoryEntryId: (0, typeorm_1.In)(item.supersedes) },
order: { createdAt: 'ASC', id: 'ASC' },
});
const existingReplacementSources = await sourceRepo.find({
where: { agentId: this.agentId, memoryEntryId: replacement.id },
});
const existingKeys = new Set(existingReplacementSources.map((source) => `${source.observationId}\n${source.evidenceHash}`));
const copiedSources = sourceRows.flatMap((source) => {
const key = `${source.observationId}\n${source.evidenceHash}`;
if (existingKeys.has(key))
return [];
existingKeys.add(key);
return [
sourceRepo.create({
agentId: this.agentId,
memoryEntryId: replacement.id,
observationId: source.observationId,
threadId: source.threadId,
evidenceHash: source.evidenceHash,
evidenceText: source.evidenceText,
createdAt: now,
}),
];
});
if (copiedSources.length > 0)
await sourceRepo.save(copiedSources);
const itemSupersededIds = item.supersedes.filter((id) => id !== replacement.id);
if (itemSupersededIds.length > 0) {
await entryRepo.update({
agentId: this.agentId,
resourceId: scope.resourceId,
id: (0, typeorm_1.In)(itemSupersededIds),
status: 'active',
}, (0, agents_1.supersededLifecycleState)(replacement.id));
supersededIds.push(...itemSupersededIds);
}
}
return {
droppedIds: effectiveDrop,
supersededIds,
inserted: replacements.map((entry) => this.toEpisodicMemoryEntry(entry)),
};
});
}
async getEpisodicMemoryCursor(scope) {
const entity = await this.memoryEntryCursorRepository.findOneBy({
agentId: this.agentId,
observationScopeId: scope.observationScopeId,
});
if (!entity)
return null;
return {
observationScopeId: entity.observationScopeId,
lastIndexedObservationId: entity.lastIndexedObservationId,
lastIndexedObservationCreatedAt: entity.lastIndexedObservationCreatedAt,
updatedAt: entity.updatedAt,
};
}
async setEpisodicMemoryCursor(cursor) {
const cursorRow = {
agentId: this.agentId,
observationScopeId: cursor.observationScopeId,
lastIndexedObservationId: cursor.lastIndexedObservationId,
lastIndexedObservationCreatedAt: cursor.lastIndexedObservationCreatedAt,
updatedAt: cursor.updatedAt ?? new Date(),
};
await this.memoryEntryCursorRepository
.createQueryBuilder()
.insert()
.into(agent_memory_entry_cursor_entity_1.AgentMemoryEntryCursorEntity)
.values(cursorRow)
.orIgnore()
.execute();
await this.memoryEntryCursorRepository
.createQueryBuilder()
.update(agent_memory_entry_cursor_entity_1.AgentMemoryEntryCursorEntity)
.set(cursorRow)
.where('"agentId" = :agentId')
.andWhere('"observationScopeId" = :observationScopeId')
.andWhere('("lastIndexedObservationCreatedAt" < :lastIndexedObservationCreatedAt OR ("lastIndexedObservationCreatedAt" = :lastIndexedObservationCreatedAt AND "lastIndexedObservationId" < :lastIndexedObservationId))')
.setParameters({
agentId: this.agentId,
observationScopeId: cursor.observationScopeId,
lastIndexedObservationId: cursor.lastIndexedObservationId,
lastIndexedObservationCreatedAt: cursor.lastIndexedObservationCreatedAt,
})
.execute();
}
describe() {
return { name: 'n8n', connectionParams: {}, constructorName: this.constructor.name };
}
toAgentDbMessage(entity) {
const msg = entity.content;
msg.id = entity.id;
msg.createdAt = entity.createdAt;
return msg;
}
toObservationLogEntry(entity) {
return {
id: entity.id,
observationScopeId: entity.observationScopeId,
marker: entity.marker,
text: entity.text,
parentId: entity.parentId,
tokenCount: Number(entity.tokenCount),
status: entity.status,
supersededBy: entity.supersededBy,
createdAt: entity.createdAt,
};
}
toEpisodicMemoryEntry(entity) {
return {
id: entity.id,
resourceId: entity.resourceId,
content: entity.content,
contentHash: entity.contentHash,
status: entity.status,
supersededBy: entity.supersededBy,
...(entity.embedding ? { embedding: entity.embedding } : {}),
...(entity.embeddingModel ? { embeddingModel: entity.embeddingModel } : {}),
metadata: entity.metadata,
createdAt: entity.createdAt,
updatedAt: entity.updatedAt,
lastSeenAt: entity.lastSeenAt,
};
}
toEpisodicMemoryEntrySource(entity) {
return {
id: entity.id,
memoryEntryId: entity.memoryEntryId,
observationId: entity.observationId,
threadId: entity.threadId,
evidenceText: entity.evidenceText,
createdAt: entity.createdAt,
};
}
mergeThreadMetadata(existingMetadata, nextMetadata) {
return JSON.stringify({
...this.parseThreadMetadata(existingMetadata),
...nextMetadata,
});
}
parseThreadMetadata(value) {
if (!value)
return undefined;
try {
return JSON.parse(value);
}
catch {
return undefined;
}
}
toThread(entity) {
return {
id: entity.id,
resourceId: entity.resourceId,
title: entity.title ?? undefined,
metadata: this.parseThreadMetadata(entity.metadata),
createdAt: entity.createdAt,
updatedAt: entity.updatedAt,
};
}
}
exports.N8nMemoryImpl = N8nMemoryImpl;
//# sourceMappingURL=n8n-memory.js.map