feat: add leader AgentBus task summaries

This commit is contained in:
inman committed 2026-09-07 13:01:01 +08:00
1 parent f4664997a8
commit 1a3ab63700
18 files changed
+2158 -19

No files matched your search

@@ -0,0 +1,160 @@
import { businessRouteById } from './business-routes.js';
export type LeaderTaskSummaryMilestone = 'final' | 'needs_review' | 'resolved';
export type LeaderTaskSummaryStatus = 'completed' | 'failed' | 'cancelled' | 'needs_review';
export interface LeaderTaskSummaryInput {
taskId: string;
status: string;
businessRouteId: string | null;
assigneeUsername: string;
createdAt: string | Date;
successReceipt?: Record<string, unknown> | null;
hadNeedsReview?: boolean;
}
export interface LeaderTaskSummaryProjection {
milestone: LeaderTaskSummaryMilestone;
deliveryStatus: LeaderTaskSummaryStatus;
statusLabel: '已完成' | '未完成' | '已取消' | '需人工核验';
businessLabel: string;
resultText: string;
messageText: string;
}
const COMPLETED_STATUSES = new Set(['completed', 'dry_run']);
const FAILED_STATUSES = new Set([
'failed',
'parse_failed',
'parse_blocked',
'agent_parse_blocked',
'blocked',
'operation_blocked'
]);
const NEEDS_REVIEW_STATUSES = new Set([
'reconciliation_pending',
'saved_unverified',
'execution_uncertain',
'uncertain'
]);
function singleLine(value: unknown, maxLength: number): string {
return String(value ?? '')
.normalize('NFKC')
.replace(/[\u0000-\u001f\u007f]+/gu, ' ')
.replace(/\s+/gu, ' ')
.trim()
.slice(0, maxLength);
}
function identifierValues(...values: unknown[]): string[] {
const output: string[] = [];
const visit = (value: unknown): void => {
if (Array.isArray(value)) {
value.forEach(visit);
return;
}
if (typeof value !== 'string' && typeof value !== 'number') return;
const candidate = singleLine(value, 80);
if (!candidate || !/^(?=.*\d)[A-Za-z0-9._/()-]{4,80}$/u.test(candidate)) return;
if (/^1[3-9]\d{9}$/u.test(candidate) || /^\d{15,18}[xX]?$/u.test(candidate)) return;
if (!output.includes(candidate)) output.push(candidate);
};
values.forEach(visit);
return output.slice(0, 10);
}
function completedResult(status: string, receipt: Record<string, unknown>): string {
if (status === 'dry_run') return '业务方案已生成,尚未正式提交。';
const groupNumbers = identifierValues(receipt.group_numbers, receipt.group_number);
const orderNumbers = identifierValues(receipt.order_numbers, receipt.order_number);
const details = [
groupNumbers.length ? `团号:${groupNumbers.join('、')}` : '',
orderNumbers.length ? `订单号:${orderNumbers.join('、')}` : ''
].filter(Boolean);
return details.length
? `业务处理已完成。${details.join(';')}`.slice(0, 300)
: '业务处理已完成,结果已记录。';
}
export function formatShanghaiTimestamp(value: string | Date): string {
const date = value instanceof Date ? value : new Date(value);
if (Number.isNaN(date.getTime())) return '时间未记录';
const parts = new Intl.DateTimeFormat('en-CA', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hourCycle: 'h23'
}).formatToParts(date);
const part = (type: Intl.DateTimeFormatPartTypes) => (
parts.find((item) => item.type === type)?.value || ''
);
return `${part('year')}-${part('month')}-${part('day')} ${part('hour')}:${part('minute')}:${part('second')}`;
}
export function buildLeaderTaskSummary(
input: LeaderTaskSummaryInput
): LeaderTaskSummaryProjection | null {
const status = singleLine(input.status, 80).toLowerCase();
let deliveryStatus: LeaderTaskSummaryStatus;
let statusLabel: LeaderTaskSummaryProjection['statusLabel'];
let resultText: string;
if (COMPLETED_STATUSES.has(status)) {
deliveryStatus = 'completed';
statusLabel = '已完成';
resultText = completedResult(status, input.successReceipt || {});
} else if (FAILED_STATUSES.has(status)) {
deliveryStatus = 'failed';
statusLabel = '未完成';
resultText = '本次工作未完成,请在平台查看业务结果。';
} else if (status === 'cancelled') {
deliveryStatus = 'cancelled';
statusLabel = '已取消';
resultText = '这项工作已取消。';
} else if (NEEDS_REVIEW_STATUSES.has(status)) {
deliveryStatus = 'needs_review';
statusLabel = '需人工核验';
resultText = '当前结果需要人工核验,请勿重复提交。';
} else {
return null;
}
const milestone: LeaderTaskSummaryMilestone = deliveryStatus === 'needs_review'
? 'needs_review'
: input.hadNeedsReview
? 'resolved'
: 'final';
const route = businessRouteById(input.businessRouteId);
const businessLabel = singleLine(route?.directive || '其他任务', 120) || '其他任务';
const assignee = singleLine(input.assigneeUsername, 160) || '未知员工';
const taskId = singleLine(input.taskId, 200) || '任务编号未记录';
const header = milestone === 'resolved' ? '【员工任务摘要·结果更新】' : '【员工任务摘要】';
return {
milestone,
deliveryStatus,
statusLabel,
businessLabel,
resultText: resultText.slice(0, 300),
messageText: [
header,
`员工:${assignee}`,
`业务:${businessLabel}`,
`状态:${statusLabel}`,
`任务:${taskId}`,
`提交:${formatShanghaiTimestamp(input.createdAt)}`,
`结果:${resultText.slice(0, 300)}`
].join('\n')
};
}
export function isLeaderTaskSummaryStatus(value: unknown): boolean {
const status = singleLine(value, 80).toLowerCase();
return COMPLETED_STATUSES.has(status)
|| FAILED_STATUSES.has(status)
|| NEEDS_REVIEW_STATUSES.has(status)
|| status === 'cancelled';
}