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

@@ -12,19 +12,16 @@ export interface TurnContext {
turnId: string
startTime: number
messages: Message[]
toolCalls: ToolCallInfo[]
toolCalls: Array<{
id: string
name: string
input: Record<string, unknown>
timestamp: number
}>
state: Map<string, unknown>
tokens: number
}
export interface ToolCallInfo {
id: string
name: string
input: Record<string, unknown>
result?: string
timestamp: number
}
export interface MultiTurnOptions {
maxTurns?: number
maxTokensPerTurn?: number
@@ -33,7 +30,7 @@ export interface MultiTurnOptions {
const DEFAULT_OPTIONS: Required<MultiTurnOptions> = {
maxTurns: 10,
maxTokensPerTurn: 5000,
maxTokensPerTurn: 50000,
preserveState: true,
}
@@ -67,58 +64,45 @@ export function getCurrentTurn(): TurnContext | null {
}
export function addMessageToTurn(message: Message): void {
if (!currentTurn) {
currentTurn = startNewTurn()
}
const content = typeof message.message?.content === 'string'
? message.message.content
: JSON.stringify(message.message?.content)
currentTurn.messages.push(message)
currentTurn.tokens += roughTokenCountEstimation(content)
const turn = currentTurn || startNewTurn()
turn.messages.push(message)
// Update token estimate
const content = typeof message.message.content === 'string'
? message.message.content
: JSON.stringify(message.message.content)
turn.tokens += roughTokenCountEstimation(content)
}
export function addToolCallToTurn(toolCall: ToolCallInfo): void {
if (!currentTurn) {
currentTurn = startNewTurn()
}
currentTurn.toolCalls.push(toolCall)
export function addToolCallToTurn(call: TurnContext['toolCalls'][0]): void {
const turn = currentTurn || startNewTurn()
turn.toolCalls.push(call)
}
export function setTurnState(key: string, value: unknown): void {
if (!currentTurn) return
currentTurn.state.set(key, value)
const turn = currentTurn || startNewTurn()
turn.state.set(key, value)
}
export function getTurnState<T>(key: string): T | undefined {
if (!currentTurn) return undefined
return currentTurn.state.get(key) as T | undefined
return currentTurn?.state.get(key) as T
}
export function getTurnHistory(): TurnContext[] {
return turnHistory
}
export function getRecentTurns(count: number): TurnContext[] {
return turnHistory.slice(-count)
}
export function getTurnById(turnId: string): TurnContext | undefined {
return turnHistory.find(t => t.turnId === turnId)
}
export function getCrossTurnContext(key: string): unknown[] {
return turnHistory.map(t => t.state.get(key)).filter(v => v !== undefined)
export function getRecentTurns(n: number): TurnContext[] {
return turnHistory.slice(-n)
}
export function getMultiTurnStats() {
return {
totalTurns: turnHistory.length,
currentTurnActive: currentTurn !== null,
totalTokens: turnHistory.reduce((sum, t) => sum + t.tokens, 0),
totalToolCalls: turnHistory.reduce((sum, t) => sum + t.toolCalls.length, 0),
totalTokens: turnHistory.reduce((acc, t) => acc + t.tokens, 0),
avgTokensPerTurn: turnHistory.length > 0
? Math.round(turnHistory.reduce((acc, t) => acc + t.tokens, 0) / turnHistory.length)
: 0,
}
}
@@ -146,4 +130,4 @@ export function createMultiTurnTracker(options: MultiTurnOptions = {}) {
getStats: getMultiTurnStats,
reset: resetMultiTurnState,
}
}
}