diff --git a/src/memory/__tests__/knowledge-graph.test.ts b/src/memory/__tests__/knowledge-graph.test.ts index a65d527b64..bd6706d270 100644 --- a/src/memory/__tests__/knowledge-graph.test.ts +++ b/src/memory/__tests__/knowledge-graph.test.ts @@ -336,16 +336,39 @@ describe('KnowledgeGraphManager', () => { expect(result.entities.map(e => e.name)).toContain('Bob'); }); - it('should include relations between opened nodes', async () => { + it('should include relations connected to opened nodes', async () => { const result = await manager.openNodes(['Alice', 'Bob']); - expect(result.relations).toHaveLength(1); - expect(result.relations[0].from).toBe('Alice'); - expect(result.relations[0].to).toBe('Bob'); + // Returns both Alice->Bob (between opened nodes) and Bob->Charlie (Bob is opened) + expect(result.relations).toHaveLength(2); + expect(result.relations.some(r => r.from === 'Alice' && r.to === 'Bob')).toBe(true); + expect(result.relations.some(r => r.from === 'Bob' && r.to === 'Charlie')).toBe(true); }); - it('should exclude relations to unopened nodes', async () => { + it('should include both incoming and outgoing relations for single node', async () => { + // When opening Bob, should return both incoming (Alice->Bob) and outgoing (Bob->Charlie) relations const result = await manager.openNodes(['Bob']); - expect(result.relations).toHaveLength(0); + expect(result.relations).toHaveLength(2); + expect(result.relations.some(r => r.from === 'Alice' && r.to === 'Bob')).toBe(true); + expect(result.relations.some(r => r.from === 'Bob' && r.to === 'Charlie')).toBe(true); + }); + + it('should return outgoing relations from requested node (issue #3137)', async () => { + // Reproduces issue #3137: open_nodes should return relations where the node is either source or target + await manager.createEntities([ + { name: '2025-12-17', entityType: 'work-day', observations: [] }, + { name: 'incident/example', entityType: 'incident', observations: ['Test incident'] }, + ]); + + await manager.createRelations([ + { from: '2025-12-17', to: 'incident/example', relationType: 'worked-on' }, + ]); + + const result = await manager.openNodes(['2025-12-17']); + expect(result.entities).toHaveLength(1); + expect(result.relations).toHaveLength(1); + expect(result.relations[0].from).toBe('2025-12-17'); + expect(result.relations[0].to).toBe('incident/example'); + expect(result.relations[0].relationType).toBe('worked-on'); }); it('should handle opening non-existent nodes', async () => { diff --git a/src/memory/index.ts b/src/memory/index.ts index c7d781d2c4..52ab71dc7f 100644 --- a/src/memory/index.ts +++ b/src/memory/index.ts @@ -200,23 +200,24 @@ export class KnowledgeGraphManager { async openNodes(names: string[]): Promise { const graph = await this.loadGraph(); - + // Filter entities const filteredEntities = graph.entities.filter(e => names.includes(e.name)); - + // Create a Set of filtered entity names for quick lookup const filteredEntityNames = new Set(filteredEntities.map(e => e.name)); - - // Filter relations to only include those between filtered entities - const filteredRelations = graph.relations.filter(r => - filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to) + + // Filter relations to include those where either endpoint is in the filtered entities + // This returns both outgoing and incoming relations for the requested nodes + const filteredRelations = graph.relations.filter(r => + filteredEntityNames.has(r.from) || filteredEntityNames.has(r.to) ); - + const filteredGraph: KnowledgeGraph = { entities: filteredEntities, relations: filteredRelations, }; - + return filteredGraph; } }