Feature/memory pr (#894)

* feat: multi-turn context and conversation arc memory

PR 2E - Section 2.9, 2.10:
- Add multiTurnContext.ts with turn tracking and state preservation
- Add conversationArc.ts with goal/decision/milestone tracking
- Wire into query.ts after tool execution
- Feature-flags: MULTI_TURN_CONTEXT, CONVERSATION_ARC
- Add comprehensive tests (22 passing)

* feat(cli): add /knowledge command to manage native memory

- Add /knowledge enable <yes|no> to toggle Knowledge Graph learning\n- Add /knowledge clear to reset memory\n- Add persistent knowledgeGraphEnabled setting to global config\n- Integrated user setting into the query execution loop

* feat(cli): add /knowledge command (stable local-jsx version)

- Resolve conflicts between .ts and .tsx files\n- Align with LocalJSXCommandCall signature\n- Fix onDone and args errors

* test(cli): fix knowledge command tests by properly isolating global config

* fix(cli): make knowledge command defensive against undefined args and leaky tests

* fix(cli): correct data source for entity count and fix test isolation

* fix(cli): reinforce knowledge test by explicitly defining property on test config

* fix(cli): explicitly define property in test config to avoid undefined in CI

* fix(cli): make knowledge tests resistant to global config mocks in CI

* chore(memory): surgical improvements from architectural audit

- Fix: Implement entity deduplication in Knowledge Graph\n- Fix: Ensure fact extraction from user messages in query loop\n- Fix: Refine regexes for better quality learning (less noise)

---------

Co-authored-by: LifeJiggy <Bloomtonjovish@gmail.com>
This commit is contained in:
3kin0x
2026-04-25 01:19:41 +02:00
committed by GitHub
parent ff2a380723
commit 44f9cac70d
10 changed files with 247 additions and 84 deletions

View File

@@ -29,8 +29,8 @@ describe('multiTurnContext', () => {
it('creates a new turn', () => {
const turn = startNewTurn()
expect(turn.turnId).toBeDefined()
expect(turn.startTime).toBeDefined()
expect(turn.messages).toEqual([])
expect(turn.toolCalls).toEqual([])
})
it('tracks turn count', () => {
@@ -44,33 +44,34 @@ describe('multiTurnContext', () => {
it('adds message to current turn', () => {
startNewTurn()
addMessageToTurn(createMessage('user', 'Hello'))
const turn = getCurrentTurn()
expect(turn?.messages.length).toBe(1)
expect(getCurrentTurn()?.messages.length).toBe(1)
})
it('creates turn if none exists', () => {
addMessageToTurn(createMessage('user', 'Hello'))
expect(getCurrentTurn()).not.toBeNull()
expect(getCurrentTurn()).toBeDefined()
expect(getCurrentTurn()?.messages.length).toBe(1)
})
})
describe('addToolCallToTurn', () => {
it('adds tool call to turn', () => {
startNewTurn()
addToolCallToTurn({ id: 'tool1', name: 'read', input: { file: 'test' }, timestamp: Date.now() })
const turn = getCurrentTurn()
expect(turn?.toolCalls.length).toBe(1)
expect(turn?.toolCalls[0].name).toBe('read')
addToolCallToTurn({
id: 'call_1',
name: 'test_tool',
input: {},
timestamp: Date.now(),
})
expect(getCurrentTurn()?.toolCalls.length).toBe(1)
})
})
describe('state management', () => {
it('sets and gets turn state', () => {
startNewTurn()
setTurnState('key1', 'value1')
expect(getTurnState<string>('key1')).toBe('value1')
setTurnState('key', 'value')
expect(getTurnState('key')).toBe('value')
})
it('returns undefined for unknown keys', () => {
@@ -83,29 +84,26 @@ describe('multiTurnContext', () => {
it('returns turn history', () => {
startNewTurn()
startNewTurn()
const history = getTurnHistory()
expect(history.length).toBe(2)
expect(getTurnHistory().length).toBe(2)
})
})
describe('getRecentTurns', () => {
it('returns recent turns', () => {
for (let i = 0; i < 5; i++) startNewTurn()
const recent = getRecentTurns(3)
expect(recent.length).toBe(3)
startNewTurn()
startNewTurn()
startNewTurn()
expect(getRecentTurns(2).length).toBe(2)
})
})
describe('getMultiTurnStats', () => {
it('returns statistics', () => {
startNewTurn()
addMessageToTurn(createMessage('user', 'Test'))
addMessageToTurn(createMessage('user', 'Hello'))
const stats = getMultiTurnStats()
expect(stats.totalTurns).toBe(1)
expect(stats.currentTurnActive).toBe(true)
expect(stats.totalTokens).toBeGreaterThan(0)
})
})
@@ -120,15 +118,15 @@ describe('multiTurnContext', () => {
it('respects the maxTurns option', () => {
// Create a tracker with a very small maxTurns
createMultiTurnTracker({ maxTurns: 2 })
startNewTurn() // turn 1
startNewTurn() // turn 2
startNewTurn() // turn 3 - should drop turn 1
const history = getTurnHistory()
expect(history.length).toBe(2)
// The first remaining turn should be the 2nd one created
expect(history[0].turnId).toContain('turn_2')
})
})
})
})
})