| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- const assert = require('assert/strict');
- const fs = require('fs');
- const os = require('os');
- const path = require('path');
- const messagesDir = fs.mkdtempSync(path.join(os.tmpdir(), 'qiwei-generic-intake-messages-'));
- process.env.QIWEI_MESSAGES_DIR = messagesDir;
- const { AgentWorkbenchDb } = require('../mcp/src/core/agent-workbench-db');
- const { AgentWorkbenchService } = require('../mcp/src/core/agent-workbench-service');
- const { evaluatePolledMessage } = require('../mcp/src/core/agent-poller-policy');
- const { __testing } = require('../mcp/src/dashboard/agent-service');
- const results = [];
- async function check(name, fn) { await fn(); results.push({ name, status: 'passed' }); }
- function createContext({ accountKey = 'account-a', mode = 'allowlist_only', welcomeEnabled = false, welcomeSendMode = 'draft', sendSuccess = true } = {}) {
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'qiwei-generic-intake-'));
- const db = new AgentWorkbenchDb(path.join(dir, 'workbench.db'), {
- globalPaused: false,
- defaultMode: 'review',
- autoSendConfidence: 0.88,
- personalIntakeMode: mode,
- welcomeEnabled,
- welcomeText: '您好,已经收到您的消息。',
- welcomeSendMode,
- });
- if (mode === 'auto_enroll_autopilot') {
- db.setIntakePolicy({ mode, welcomeEnabled, welcomeText: '您好,已经收到您的消息。', welcomeSendMode, autopilotConfirmation: 'v1' });
- }
- let currentSendSuccess = sendSuccess;
- const sent = [];
- const config = {
- accountKey,
- intake: { effectiveAutopilotMode: 'autopilot' },
- memory: { enabled: false },
- agent: { apiKey: 'smoke', model: 'stub', provider: 'stub' },
- qiwei: { accountKey, allowedSenders: [], manualAllowedSenders: [], autoEnrolledSenders: [], intakeAutopilotConversationMode: 'autopilot' },
- };
- const qiwei = {
- isConfigured: () => true,
- async sendText(contactId, content) {
- sent.push({ contactId, content });
- return { isSendSuccess: currentSendSuccess };
- },
- };
- const agent = {
- modelClient: { isConfigured: () => true },
- async run() {
- return { content: '普通 Agent 回复草稿', confidence: 0.5, intent: 'general', reason: 'smoke', requiresHuman: true, profileUpdates: {}, citations: [], toolTrace: [] };
- },
- };
- const service = new AgentWorkbenchService({ db, agent, qiwei, config });
- const poller = { status: () => ({ running: false }), stop() {} };
- return {
- dir, db, config, service, sent,
- target: { config: config.qiwei, db, service, qiwei },
- workbenchTarget: { config, db, service, poller },
- setSendSuccess(value) { currentSendSuccess = value; },
- close() {
- service.close?.();
- db.close();
- fs.rmSync(dir, { recursive: true, force: true });
- },
- };
- }
- function inbound(contactId, overrides = {}) {
- return {
- msgType: 0,
- senderId: contactId,
- senderName: `联系人-${contactId}`,
- content: '你好,我想咨询一下',
- timestamp: Math.floor(Date.now() / 1000),
- seq: Math.floor(Math.random() * 1000000),
- msgServerId: `message-${contactId}-${Date.now()}-${Math.random()}`,
- ...overrides,
- };
- }
- async function main() {
- await check('strict mode and group policy create no onboarding records', async () => {
- const ctx = createContext();
- try {
- const strict = await __testing.ingestMessageForWorkbench(ctx.target, inbound('strict-contact'), 'smoke');
- assert.equal(strict.status, 'ignored_not_allowlisted');
- assert.equal(ctx.db.listConversations().length, 0);
- assert.equal(ctx.db.listOnboardings('account-a').length, 0);
- const group = evaluatePolledMessage(inbound('group-member', { fromRoomId: 'room-1', chatType: 'group' }), { intakeMode: 'auto_enroll_review', allowedSenders: [] });
- assert.equal(group.reason, 'group_message');
- } finally { ctx.close(); }
- });
- await check('review auto enrollment creates one welcome draft with zero sends', async () => {
- const ctx = createContext({ mode: 'auto_enroll_review', welcomeEnabled: true });
- try {
- const first = await __testing.ingestMessageForWorkbench(ctx.target, inbound('review-contact'), 'smoke');
- assert.equal(first.status, 'pending_review');
- assert.equal(ctx.db.getConversationByContactId('review-contact').mode, 'review');
- assert.equal(ctx.sent.length, 0);
- const onboarding = ctx.db.getOnboarding('account-a', 'review-contact');
- assert.ok(onboarding.first_message_id);
- assert.ok(onboarding.welcome_draft_id);
- const restarted = { ...ctx.config.qiwei, allowedSenders: ['review-contact'], autoEnrolledSenders: ['review-contact'] };
- await __testing.ingestMessageForWorkbench({ ...ctx.target, config: restarted }, inbound('review-contact', { content: '继续了解办理流程' }), 'restart');
- assert.equal(ctx.db.listDrafts({ conversationId: onboarding.conversation_id }).filter(item => item.intent === 'new_contact_welcome').length, 1);
- assert.equal(ctx.sent.length, 0);
- } finally { ctx.close(); }
- });
- await check('generic automatic intake requires confirmation and maps to full autopilot mode', async () => {
- const ctx = createContext();
- try {
- assert.throws(() => __testing.updateIntakePolicyForWorkbench(ctx, { mode: 'auto_enroll_autopilot', welcomeEnabled: true, welcomeText: '欢迎', welcomeSendMode: 'send' }), /二次确认/);
- const policy = __testing.updateIntakePolicyForWorkbench(ctx, { mode: 'auto_enroll_autopilot', welcomeEnabled: true, welcomeText: '欢迎', welcomeSendMode: 'send', confirmation: 'ENABLE_AUTO_ENROLL_AUTOPILOT' }, 'smoke-admin');
- assert.equal(policy.autopilotConfirmed, true);
- assert.equal(policy.effectiveConversationMode, 'autopilot');
- } finally { ctx.close(); }
- });
- await check('failed welcome can be retried once and then stays sent', async () => {
- const ctx = createContext({ mode: 'auto_enroll_autopilot', welcomeEnabled: true, welcomeSendMode: 'send', sendSuccess: false });
- try {
- const first = await __testing.ingestMessageForWorkbench(ctx.target, inbound('send-contact'), 'smoke');
- assert.equal(first.status, 'welcome_send_failed');
- assert.equal(ctx.db.getConversationByContactId('send-contact').mode, 'autopilot');
- assert.equal(ctx.db.getOnboarding('account-a', 'send-contact').attempt_count, 1);
- ctx.setSendSuccess(true);
- assert.equal((await ctx.service.retryOnboardingWelcome('send-contact', 'smoke-admin')).status, 'welcome_sent');
- assert.equal(ctx.db.getOnboarding('account-a', 'send-contact').attempt_count, 2);
- await assert.rejects(() => ctx.service.retryOnboardingWelcome('send-contact', 'smoke-admin'), /明确发送失败/);
- const next = await __testing.ingestMessageForWorkbench(ctx.target, inbound('send-contact', { content: '继续咨询' }), 'smoke-next');
- assert.equal(next.status, 'autopilot_sent');
- assert.equal(ctx.db.listDrafts({ conversationId: next.conversation.id }).length, 0);
- } finally { ctx.close(); }
- });
- await check('two accounts keep manual, automatic and listener state isolated', async () => {
- const accountA = createContext({ accountKey: 'account-a' });
- const accountB = createContext({ accountKey: 'account-b', mode: 'auto_enroll_review' });
- try {
- __testing.updateAllowlistForWorkbench(accountA.workbenchTarget, { contactIds: ['contact-a'], autoStart: true });
- assert.deepEqual(accountA.config.qiwei.allowedSenders, ['contact-a']);
- assert.deepEqual(accountB.config.qiwei.allowedSenders, []);
- assert.equal(accountA.db.getSetting('listener_enabled', 'false'), 'true');
- assert.equal(accountB.db.getSetting('listener_enabled', 'false'), 'false');
- await __testing.ingestMessageForWorkbench(accountB.target, inbound('contact-b'), 'smoke-b');
- __testing.hydrateAccountAllowlist(accountA.config, accountA.db);
- __testing.hydrateAccountAllowlist(accountB.config, accountB.db);
- assert.deepEqual(accountA.config.qiwei.allowedSenders, ['contact-a']);
- assert.deepEqual(accountB.config.qiwei.allowedSenders, ['contact-b']);
- } finally { accountA.close(); accountB.close(); }
- });
- console.log(JSON.stringify({ status: 'ok', passed: results.length, results }, null, 2));
- }
- main()
- .finally(() => fs.rmSync(messagesDir, { recursive: true, force: true }))
- .catch(error => { console.error(error); process.exitCode = 1; });
|