agent-chat-history-context.test.js 2.38 KB
/**
 * Guard: 同一 convId 恢复上下文时(buildPrompt 场景 C)按模型上下文 token 预算动态注入历史,
 * 不再是固定的 slice(-20) + 单条 500 字截断 —— 预算内整段塞进去,超预算才从最早的开始丢。
 * 否则 ACP 进程重启/空闲超时后,早于第 20 条的历史会静默丢失,agent 会"忘了"之前聊过什么。
 */
const fs = require('fs');
const path = require('path');

const { buildPrompt, appendHistory, truncateHistoryToBudget, estimateTokens } = require('../../routes/agent-chat');

const agentId = 'master';
const agentDir = path.resolve(__dirname, `../../../system/agents/${agentId}`);

describe('agent-chat buildPrompt 场景 C:同 convId 恢复上下文', () => {
  it('注入完整历史,不按 slice(-20) 或单条 500 字截断', () => {
    const convId = `test-history-guard-${Date.now()}`;
    const convDir = path.resolve(__dirname, `../../../system/agents/${agentId}/_global/conversations/${convId}`);
    try {
      const longFirstMessage = 'X'.repeat(600); // 超过原来单条 500 字截断阈值
      appendHistory(agentId, convId, { role: 'user', content: longFirstMessage });
      for (let i = 0; i < 25; i++) {
        appendHistory(agentId, convId, { role: 'assistant', content: `filler-${i}` });
      }

      const prompt = buildPrompt(agentDir, agentId, convId, '新消息', true);

      expect(prompt).toContain(longFirstMessage);
      expect(prompt).not.toContain('...(截断)');
    } finally {
      fs.rmSync(convDir, { recursive: true, force: true });
    }
  });
});

describe('truncateHistoryToBudget:预算超支时按 token 从最早的开始丢', () => {
  it('预算装不下全部历史时,丢最老的,留最近的', () => {
    const history = [
      { role: 'user', content: 'oldest' },
      { role: 'assistant', content: 'middle' },
      { role: 'user', content: 'newest' },
    ];
    const budget = estimateTokens('middle') + estimateTokens('newest'); // 刚好够后两条,装不下 oldest

    const kept = truncateHistoryToBudget(history, budget);

    expect(kept.map(m => m.content)).toEqual(['middle', 'newest']);
  });

  it('单条消息本身就超预算时,仍然至少保留最近一条,不会返回空', () => {
    const history = [{ role: 'user', content: 'X'.repeat(10000) }];

    const kept = truncateHistoryToBudget(history, 1);

    expect(kept).toHaveLength(1);
  });
});