* 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>
133 lines
3.4 KiB
TypeScript
133 lines
3.4 KiB
TypeScript
import { describe, expect, it, beforeEach } from 'bun:test'
|
|
import {
|
|
startNewTurn,
|
|
getCurrentTurn,
|
|
addMessageToTurn,
|
|
addToolCallToTurn,
|
|
setTurnState,
|
|
getTurnState,
|
|
getTurnHistory,
|
|
getRecentTurns,
|
|
getMultiTurnStats,
|
|
resetMultiTurnState,
|
|
createMultiTurnTracker,
|
|
} from './multiTurnContext.js'
|
|
|
|
function createMessage(role: string, content: string): any {
|
|
return {
|
|
message: { role, content, id: 'test', type: 'message', created_at: Date.now() },
|
|
sender: role,
|
|
}
|
|
}
|
|
|
|
describe('multiTurnContext', () => {
|
|
beforeEach(() => {
|
|
resetMultiTurnState()
|
|
})
|
|
|
|
describe('startNewTurn', () => {
|
|
it('creates a new turn', () => {
|
|
const turn = startNewTurn()
|
|
expect(turn.turnId).toBeDefined()
|
|
expect(turn.startTime).toBeDefined()
|
|
expect(turn.messages).toEqual([])
|
|
})
|
|
|
|
it('tracks turn count', () => {
|
|
startNewTurn()
|
|
const turn2 = startNewTurn()
|
|
expect(turn2.turnId).toContain('turn_2')
|
|
})
|
|
})
|
|
|
|
describe('addMessageToTurn', () => {
|
|
it('adds message to current turn', () => {
|
|
startNewTurn()
|
|
addMessageToTurn(createMessage('user', 'Hello'))
|
|
expect(getCurrentTurn()?.messages.length).toBe(1)
|
|
})
|
|
|
|
it('creates turn if none exists', () => {
|
|
addMessageToTurn(createMessage('user', 'Hello'))
|
|
expect(getCurrentTurn()).toBeDefined()
|
|
expect(getCurrentTurn()?.messages.length).toBe(1)
|
|
})
|
|
})
|
|
|
|
describe('addToolCallToTurn', () => {
|
|
it('adds tool call to turn', () => {
|
|
startNewTurn()
|
|
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('key', 'value')
|
|
expect(getTurnState('key')).toBe('value')
|
|
})
|
|
|
|
it('returns undefined for unknown keys', () => {
|
|
startNewTurn()
|
|
expect(getTurnState('unknown')).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('getTurnHistory', () => {
|
|
it('returns turn history', () => {
|
|
startNewTurn()
|
|
startNewTurn()
|
|
expect(getTurnHistory().length).toBe(2)
|
|
})
|
|
})
|
|
|
|
describe('getRecentTurns', () => {
|
|
it('returns recent turns', () => {
|
|
startNewTurn()
|
|
startNewTurn()
|
|
startNewTurn()
|
|
expect(getRecentTurns(2).length).toBe(2)
|
|
})
|
|
})
|
|
|
|
describe('getMultiTurnStats', () => {
|
|
it('returns statistics', () => {
|
|
startNewTurn()
|
|
addMessageToTurn(createMessage('user', 'Hello'))
|
|
const stats = getMultiTurnStats()
|
|
expect(stats.totalTurns).toBe(1)
|
|
expect(stats.totalTokens).toBeGreaterThan(0)
|
|
})
|
|
})
|
|
|
|
describe('createMultiTurnTracker', () => {
|
|
it('creates tracker with all methods', () => {
|
|
const tracker = createMultiTurnTracker()
|
|
expect(tracker.startTurn).toBeDefined()
|
|
expect(tracker.addMessage).toBeDefined()
|
|
expect(tracker.getStats).toBeDefined()
|
|
})
|
|
|
|
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')
|
|
})
|
|
})
|
|
})
|