fix(audit): resolve lint global, compaction metrics, and nudge id

This commit is contained in:
William Valentin
2026-02-15 21:54:12 -08:00
parent 50dcff5ea6
commit 948d589ac3
7 changed files with 106 additions and 11 deletions
+2 -3
View File
@@ -201,9 +201,8 @@ describe('NativeAgent tool loop', () => {
callCount++;
// After nudge message, model should respond with text
const lastMsg = req.messages[req.messages.length - 1];
const hasNudge = typeof lastMsg?.content !== 'string' &&
Array.isArray(lastMsg?.content) &&
lastMsg.content.some((b: any) => b.content?.includes('do NOT call it again'));
const hasNudge = typeof lastMsg?.content === 'string'
&& lastMsg.content.includes('do NOT call it again');
if (hasNudge) {
return {
content: 'Here is what I found from my searches.',
+5 -6
View File
@@ -339,18 +339,17 @@ export class NativeAgent {
// If the same tool has been called too many times, append a nudge
// telling the model to use what it has. This combats local models
// that endlessly retry searches with slight query variations.
let nudgeMessage: string | null = null;
if (sameToolStreak >= maxSameToolStreak && !nudged) {
nudged = true;
toolResultBlocks.push({
type: 'tool_result',
tool_use_id: '__system',
content: `You have called this tool ${sameToolStreak} times in a row. You have enough information — do NOT call it again. Summarize what you have found and respond to the user now.`,
is_error: false,
});
nudgeMessage = `You have called this tool ${sameToolStreak} times in a row. You have enough information — do NOT call it again. Summarize what you have found and respond to the user now.`;
}
// Add tool results as a user message
loopMessages.push({ role: 'user', content: toolResultBlocks });
if (nudgeMessage) {
loopMessages.push({ role: 'user', content: nudgeMessage });
}
// Break out if the model is stuck in a repeated tool call loop
if (consecutiveRepeats >= maxConsecutiveRepeats) {
+78
View File
@@ -8,6 +8,8 @@ import { MemoryStore } from '../../memory/store.js';
import { mkdtempSync, rmSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { auditLogger, initAuditLogger } from '../../audit/index.js';
import type { AuditLogger } from '../../audit/index.js';
describe('AgentOrchestrator', () => {
let mockDefaultClient: ModelClient;
@@ -450,6 +452,82 @@ describe('AgentOrchestrator', () => {
});
});
describe('compact()', () => {
it('emits compaction audit event with message counts (not token counts)', async () => {
const compactClient: ModelClient = {
chat: vi.fn().mockResolvedValue({
content: 'summary',
stopReason: 'end_turn',
usage: { inputTokens: 8, outputTokens: 4 },
}),
};
const compactRouter = new ModelRouter({
default: compactClient,
fast: compactClient,
complex: compactClient,
fallbackChain: [],
});
const history: any[] = [
{ role: 'user', content: 'u1' },
{ role: 'assistant', content: 'a1' },
{ role: 'user', content: 'u2' },
{ role: 'assistant', content: 'a2' },
{ role: 'user', content: 'u3' },
{ role: 'assistant', content: 'a3' },
];
const session = {
id: 'session-compact-audit',
addMessage: vi.fn((m: any) => { history.push(m); }),
getHistory: vi.fn(() => [...history]),
clear: vi.fn(() => { history.length = 0; }),
replaceHistory: vi.fn((msgs: any[]) => {
history.length = 0;
history.push(...msgs);
}),
getConfig: vi.fn(() => undefined),
setConfig: vi.fn(),
deleteConfig: vi.fn(),
} as any;
const sessionCompact = vi.fn();
const previousAuditLogger = auditLogger;
initAuditLogger({ sessionCompact } as unknown as AuditLogger);
try {
const orchestrator = new AgentOrchestrator({
modelRouter: compactRouter,
systemPrompt: 'You are helpful.',
session,
primaryTier: 'default',
delegation: {
compaction: 'fast',
memory_extraction: 'default',
classification: 'complex',
tool_summarisation: 'default',
complex_reasoning: 'complex',
},
maxDelegationDepth: 10,
compaction: {
thresholdPct: 80,
keepTurns: 1,
summaryMaxTokens: 128,
importanceThreshold: 1,
},
});
const result = await orchestrator.compact();
expect(result).not.toBeNull();
expect(sessionCompact).toHaveBeenCalledWith(expect.objectContaining({
session_id: 'session-compact-audit',
messages_before: 6,
messages_after: 3,
}));
} finally {
initAuditLogger(previousAuditLogger as unknown as AuditLogger);
}
});
});
describe('reset()', () => {
it('clears primary agent conversation history', async () => {
const orchestrator = new AgentOrchestrator({
+2 -2
View File
@@ -310,8 +310,8 @@ export class AgentOrchestrator {
if (this._session) {
auditLogger?.sessionCompact({
session_id: this._session.id,
messages_before: result.tokensBefore,
messages_after: result.tokensAfter,
messages_before: messages.length,
messages_after: result.messages.length,
tokens_before: result.tokensBefore,
tokens_after: result.tokensAfter,
});