agent-generation-recovery-smoke-test.js 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. 'use strict';
  2. const assert = require('assert/strict');
  3. const fs = require('fs');
  4. const os = require('os');
  5. const path = require('path');
  6. const { AgentWorkbenchDb } = require('../mcp/src/core/agent-workbench-db');
  7. const { AgentWorkbenchService } = require('../mcp/src/core/agent-workbench-service');
  8. const output = () => ({
  9. content: '已收到,我先按当前信息整理下一步。',
  10. confidence: 0.96,
  11. intent: '信息确认',
  12. reason: '恢复测试固定输出。',
  13. requiresHuman: false,
  14. profileUpdates: {},
  15. tasks: [],
  16. alerts: [],
  17. citations: [],
  18. toolTrace: [],
  19. });
  20. async function main() {
  21. const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'qiwei-generation-recovery-'));
  22. const db = new AgentWorkbenchDb(path.join(dir, 'workbench.db'), { globalPaused: false, defaultMode: 'autopilot' });
  23. let calls = 0;
  24. const sent = [];
  25. const service = new AgentWorkbenchService({
  26. db,
  27. agent: {
  28. modelClient: { isConfigured: () => true },
  29. async run() {
  30. calls += 1;
  31. if (calls === 1) throw new Error('fixture fatal generation failure');
  32. return output();
  33. },
  34. },
  35. qiwei: {
  36. isConfigured: () => true,
  37. async sendText(contactId, content) {
  38. sent.push({ contactId, content });
  39. return { isSendSuccess: true, msgServerId: `recovery-out-${sent.length}` };
  40. },
  41. },
  42. config: {
  43. accountKey: 'generation-recovery-fixture',
  44. agent: {
  45. provider: 'fixture', model: 'fixture', apiKey: 'fixture', generationRetryAttempts: 1, generationRetryBaseMs: 0, qualityPassScore: 60,
  46. generationRecovery: { pendingAgeMs: 5000, retryCooldownMs: 5000, maxAgeMs: 3600000 },
  47. },
  48. qiwei: { allowedSenders: ['recovery-contact'] },
  49. memory: { enabled: false },
  50. },
  51. });
  52. try {
  53. const queued = await service.ingestInbound({
  54. externalId: 'generation-recovery-in-1',
  55. contactId: 'recovery-contact',
  56. contactName: '恢复测试',
  57. content: '请介绍一下服务',
  58. }, { awaitGeneration: false, allowAutoSend: true });
  59. assert.equal(queued.status, 'generation_queued');
  60. for (let i = 0; i < 50 && !db.listAudit(100).some(item => item.action === 'agent_failed'); i += 1) {
  61. await new Promise(resolve => setTimeout(resolve, 5));
  62. }
  63. const conversation = db.getConversationByContactId('recovery-contact');
  64. const inbound = db.getLatestInbound(conversation.id);
  65. assert.equal(inbound.status, 'generation_failed', '后台失败必须留下可恢复状态');
  66. assert.equal(sent.length, 0);
  67. const recovered = await service.recoverPendingGenerations('smoke:recovery');
  68. assert.equal(recovered.recovered, 1, '下一轮恢复必须认领并处理失败入站');
  69. assert.equal(sent.length, 1, '恢复成功后必须只发送一次');
  70. assert.equal(db.getMessage(inbound.id).status, 'reply_sent');
  71. assert.equal(db.listAudit(100).some(item => item.action === 'agent_generation_recovery_attempt'), true);
  72. assert.equal(db.listAudit(100).some(item => item.action === 'agent_generation_recovery_completed'), true);
  73. process.stdout.write(JSON.stringify({ status: 'passed', checks: [
  74. '异步生成失败持久化为 generation_failed',
  75. '下一轮恢复扫描重新生成并自动发送',
  76. '恢复链路保持单次出站与审计记录',
  77. ] }, null, 2) + '\n');
  78. } finally {
  79. service.stopBackgroundWorkers();
  80. db.close();
  81. fs.rmSync(dir, { recursive: true, force: true });
  82. }
  83. }
  84. main().catch(error => {
  85. process.stderr.write(JSON.stringify({ status: 'failed', message: error.message, stack: error.stack }, null, 2) + '\n');
  86. process.exitCode = 1;
  87. });