feat: add leader AgentBus task summaries
This commit is contained in:
1 parent
f4664997a8
commit
1a3ab63700
18 files changed
+2158
-19
No files matched your search
@@ -6,6 +6,8 @@ import {
|
||||
AgentBusListener,
|
||||
type AgentBusSocket,
|
||||
type AgentBusTaskGateway,
|
||||
type LeaderNotificationGateway,
|
||||
createLeaderTaskSummaryFrame,
|
||||
createTaskResultFrame,
|
||||
createTaskProgressFrame,
|
||||
extractAgentBusBusinessText,
|
||||
@@ -31,6 +33,7 @@ import {
|
||||
} from '../src/agentbus-delivery.js';
|
||||
import { resolveBusinessRoute } from '../src/business-routes.js';
|
||||
import type { AgentBusDelivery, PublicTask } from '../src/task-service.js';
|
||||
import type { LeaderTaskSummaryDelivery } from '../src/leader-notification-service.js';
|
||||
|
||||
function makeTask(status: string, overrides: Partial<PublicTask> = {}): PublicTask {
|
||||
return {
|
||||
@@ -410,6 +413,142 @@ test('AgentBus protocol helpers preserve reply routing fields', () => {
|
||||
assert.equal(fallbackProgress.conversation_id, 'agentbus:channel:wechat:user-2');
|
||||
});
|
||||
|
||||
test('leader summary frame uses explicit proactive routing and cannot become an inbound task', () => {
|
||||
const delivery: LeaderTaskSummaryDelivery = {
|
||||
id: '11111111-1111-4111-8111-111111111111',
|
||||
channel_id: '22222222-2222-4222-8222-222222222222',
|
||||
task_id: 'TASK-20260907-001',
|
||||
recipient_address: 'channel:wechat:leader-a',
|
||||
recipient_fingerprint: 'abc123def456',
|
||||
conversation_id: 'wechat-conversation-a',
|
||||
conversation_fingerprint: 'def456abc123',
|
||||
payload: {
|
||||
event: 'task.summary',
|
||||
status: 'completed',
|
||||
task_id: 'TASK-20260907-001',
|
||||
text: '【员工任务摘要】\n员工:employee-a'
|
||||
},
|
||||
attempt_count: 1
|
||||
};
|
||||
const frame = createLeaderTaskSummaryFrame(delivery, {
|
||||
id: 'session-leader-1',
|
||||
epoch: 9,
|
||||
address: 'bot:leader-a:listener'
|
||||
});
|
||||
assert.equal(frame.id, `leader-summary-${delivery.id}`);
|
||||
assert.equal(frame.from, 'bot:leader-a:listener');
|
||||
assert.equal(frame.to, delivery.recipient_address);
|
||||
assert.equal(frame.conversation_id, delivery.conversation_id);
|
||||
assert.equal(Object.hasOwn(frame, 'reply_to'), false);
|
||||
assert.deepEqual(frame.payload, delivery.payload);
|
||||
assert.equal(isInboundAgentBusTask(frame), false);
|
||||
});
|
||||
|
||||
test('listener sends employee outbox first, then a redacted-log leader summary batch', async (t) => {
|
||||
const socket = new FakeSocket();
|
||||
const order: string[] = [];
|
||||
const delivered: string[] = [];
|
||||
const released: string[] = [];
|
||||
const logs: Array<Record<string, unknown>> = [];
|
||||
const delivery: LeaderTaskSummaryDelivery = {
|
||||
id: '33333333-3333-4333-8333-333333333333',
|
||||
channel_id: 'channel-leader-a',
|
||||
task_id: 'TASK-PRIVATE-1',
|
||||
recipient_address: 'channel:wechat:private-leader-address',
|
||||
recipient_fingerprint: 'a1b2c3d4e5f6',
|
||||
conversation_id: 'private-wechat-conversation',
|
||||
conversation_fingerprint: 'f6e5d4c3b2a1',
|
||||
payload: {
|
||||
event: 'task.summary',
|
||||
status: 'completed',
|
||||
task_id: 'TASK-PRIVATE-1',
|
||||
text: '【员工任务摘要】\n员工:private-employee'
|
||||
},
|
||||
attempt_count: 1
|
||||
};
|
||||
let claimed = false;
|
||||
const tasks: AgentBusTaskGateway = {
|
||||
events: new EventEmitter(),
|
||||
async ingestMessage() {
|
||||
return { task: makeTask('failed'), attached: false, created: true };
|
||||
},
|
||||
async getTask() {
|
||||
return makeTask('failed');
|
||||
},
|
||||
async listAgentBusFinalizationCandidates() {
|
||||
return [];
|
||||
},
|
||||
async enqueueAgentBusResult() {},
|
||||
async claimAgentBusDeliveries() {
|
||||
order.push('employee');
|
||||
return [];
|
||||
},
|
||||
async markAgentBusDeliveryDelivered() {},
|
||||
async markAgentBusDeliveryFailed() {},
|
||||
async releaseAgentBusDeliveries() {}
|
||||
};
|
||||
const leaderNotifications: LeaderNotificationGateway = {
|
||||
async claimDeliveries() {
|
||||
order.push('leader');
|
||||
if (claimed) return [];
|
||||
claimed = true;
|
||||
return [delivery];
|
||||
},
|
||||
async markDeliveryDelivered(deliveryId) {
|
||||
delivered.push(deliveryId);
|
||||
},
|
||||
async markDeliveryFailed() {},
|
||||
async releaseDeliveries(channelId) {
|
||||
released.push(channelId);
|
||||
}
|
||||
};
|
||||
const listener = new AgentBusListener({
|
||||
config: testConfig(),
|
||||
tasks,
|
||||
leaderNotifications,
|
||||
organizationId: 'org-1',
|
||||
scheduleParseQueue: async () => {},
|
||||
socketFactory: () => socket as unknown as AgentBusSocket,
|
||||
channel: {
|
||||
id: 'channel-leader-a',
|
||||
displayName: '组长 A',
|
||||
wsUrl: 'wss://mesh.nianxx.cn/ws',
|
||||
wsToken: 'leader-channel-token',
|
||||
botAddress: 'bot:leader-a:listener',
|
||||
ownerUserId: 'leader-a',
|
||||
ownerRole: 'team_lead'
|
||||
},
|
||||
logger: {
|
||||
info(metadata) { logs.push(metadata); },
|
||||
warn(metadata) { logs.push(metadata); },
|
||||
error(metadata) { logs.push(metadata); }
|
||||
}
|
||||
});
|
||||
t.after(() => listener.stop());
|
||||
listener.start();
|
||||
socket.readyState = 1;
|
||||
socket.emit('open');
|
||||
socket.emit('message', JSON.stringify({
|
||||
id: 'ready-leader-summary',
|
||||
type: 'event',
|
||||
session_id: 'session-leader-summary',
|
||||
epoch: 1,
|
||||
to: 'bot:leader-a:listener',
|
||||
payload: { event: 'session.ready' }
|
||||
}));
|
||||
|
||||
await waitFor(() => socket.sent.length === 1 && delivered.length === 1);
|
||||
assert.deepEqual(order.slice(0, 2), ['employee', 'leader']);
|
||||
assert.equal(delivered[0], delivery.id);
|
||||
assert.equal(socket.sent[0].reply_to, undefined);
|
||||
assert.equal((socket.sent[0].payload as Record<string, unknown>).event, 'task.summary');
|
||||
const logText = JSON.stringify(logs);
|
||||
assert.doesNotMatch(logText, /private-leader-address|private-wechat-conversation|private-employee/);
|
||||
assert.match(logText, /a1b2c3d4e5f6/);
|
||||
listener.stop();
|
||||
await waitFor(() => released.includes('channel-leader-a'));
|
||||
});
|
||||
|
||||
test('AgentBus result text uses the unified important message and preserves the confirmation gate', () => {
|
||||
const needsInput = makeTask('awaiting_user_input', {
|
||||
input_request: {
|
||||
|
||||
Reference in new issue
Block a user