fix: preserve WeChat attachment task context

This commit is contained in:
inman committed 2026-08-30 15:41:55 +08:00
1 parent e7aa58a203
commit 2360506607
5 files changed
+219 -17

No files matched your search

+100 -4
View File
@@ -108,6 +108,13 @@ class FakeSocket {
}
}
async function waitFor(predicate: () => boolean, timeoutMs = 1_000): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (!predicate() && Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 5));
}
}
function testConfig() {
return loadConfig({
NODE_ENV: 'test',
@@ -188,6 +195,21 @@ test('WeChat transport envelope exposes only its business text to the global par
const extracted = extractAgentBusBusinessText(wrapped);
assert.equal(extracted, businessText);
assert.equal(resolveBusinessRoute(extracted).routeId, 'team_order_create');
const envelopeConversation = parseAgentBusFrame(JSON.stringify({
id: 'wechat-envelope-conversation',
type: 'event',
from: 'channel:wechat:user-1',
payload: { text: wrapped }
}));
assert.equal(envelopeConversation?.conversation_id, 'conversation-example-1');
const explicitConversation = parseAgentBusFrame(JSON.stringify({
id: 'wechat-explicit-conversation',
type: 'event',
from: 'channel:wechat:user-1',
conversation_id: 'conversation-explicit-1',
payload: { text: wrapped }
}));
assert.equal(explicitConversation?.conversation_id, 'conversation-explicit-1');
const missingConversation = `New WeChat message\nConversation:\nText: ${businessText}`;
const wrongTextLabel = `New WeChat message\nConversation: conversation-example-1\nBody: ${businessText}`;
@@ -510,7 +532,7 @@ test('AgentBus attachment references omit archived visitor XLS and keep only XLS
}]);
});
test('AgentBus listener connects with the documented Authorization header and returns one final result', async () => {
test('AgentBus listener connects with the documented Authorization header and returns one final result', async (t) => {
const config = testConfig();
const socket = new FakeSocket();
let capturedUrl = '';
@@ -608,6 +630,7 @@ test('AgentBus listener connects with the documented Authorization header and re
}
}
});
t.after(() => listener.stop());
listener.start();
assert.match(capturedUrl, /[?&]ready=1/);
@@ -645,11 +668,11 @@ test('AgentBus listener connects with the documented Authorization header and re
payload: { text: wrappedBusinessText, reply_policy: { progress: true } }
}));
await new Promise((resolve) => setTimeout(resolve, 50));
await waitFor(() => socket.sent.length === 2);
assert.equal(received.length, 1);
assert.equal(capturedContext.source, 'agentbus');
assert.equal(received[0].message, wrappedBusinessText.split('\n').slice(2).join('\n').replace(/^Text:\s*/, ''));
assert.equal(received[0].conversationId, 'agentbus:channel:wechat:user-1');
assert.equal(received[0].conversationId, 'conversation-wechat-1');
assert.equal(received[0].idempotencyKey, 'agentbus:channel-event-1');
assert.equal(socket.sent.length, 2);
assert.equal((socket.sent[0].payload as Record<string, unknown>).event, 'task.progress');
@@ -675,7 +698,80 @@ test('AgentBus listener connects with the documented Authorization header and re
assert.ok(logs.some((entry) => entry.metadata.agentbus_event === 'outbound_progress_sent'));
assert.ok(logs.some((entry) => entry.metadata.agentbus_event === 'outbound_result_sent'));
assert.doesNotMatch(JSON.stringify(logs), /test-ws-token/);
listener.stop();
});
test('WeChat attachment placeholder without file metadata fails closed before task ingestion', async (t) => {
const socket = new FakeSocket();
const events = new EventEmitter();
const logs: Array<{ level: string; metadata: Record<string, unknown>; message?: string }> = [];
let ingestCalls = 0;
const tasks: AgentBusTaskGateway = {
events,
async ingestMessage() {
ingestCalls += 1;
return { task: makeTask('parse_queued'), attached: false, created: true };
},
async getTask() {
return makeTask('failed');
}
};
const listener = new AgentBusListener({
config: testConfig(),
tasks,
organizationId: 'org-1',
scheduleParseQueue: async () => {},
socketFactory: () => socket as unknown as AgentBusSocket,
logger: {
info(metadata, message) {
logs.push({ level: 'info', metadata, message });
},
warn(metadata, message) {
logs.push({ level: 'warn', metadata, message });
},
error(metadata, message) {
logs.push({ level: 'error', metadata, message });
}
}
});
t.after(() => listener.stop());
listener.start();
socket.readyState = 1;
socket.emit('open');
socket.emit('message', JSON.stringify({
id: 'ready-attachment-placeholder',
type: 'event',
session_id: 'session-attachment-placeholder',
epoch: 1,
to: 'bot:test:listener',
payload: { event: 'session.ready' }
}));
socket.emit('message', JSON.stringify({
id: 'wechat-attachment-placeholder',
type: 'event',
from: 'channel:wechat:user-1',
payload: {
text: [
'New WeChat message',
'Conversation: thread:conversation-attachment-1',
'Text: [WeChat attachment: synthetic-roster.xlsx]'
].join('\n')
}
}));
await waitFor(() => socket.sent.length === 1);
assert.equal(ingestCalls, 0);
assert.equal(socket.sent.length, 1);
assert.equal(socket.sent[0].conversation_id, 'thread:conversation-attachment-1');
assert.equal((socket.sent[0].payload as Record<string, unknown>).event, 'task.result');
assert.equal((socket.sent[0].payload as Record<string, unknown>).status, 'failed');
assert.equal(
(socket.sent[0].payload as Record<string, unknown>).text,
'附件内容未传到平台,原任务仍在等待附件。请检查微信桥接器的文件转发后重新发送。'
);
assert.ok(logs.some((entry) => entry.metadata.agentbus_event === 'task_processing_failed'
&& entry.metadata.error_code === 'roster_attachment_metadata_missing'));
assert.ok(logs.some((entry) => entry.metadata.agentbus_event === 'outbound_result_sent'));
});
test('listener reload stop does not overwrite the channel status as disabled', () => {