import { Message } from './api/models'; /** * 组员超时检测 —— 组长派活后组员一直不回。 * * ## 为什么需要 * * 组长制下组长会「限时轮询」等组员。但实测量到: * * - 组员忙的时候消息在排队(可能排 20 分钟) * - 组员可能压根没收到(曾出现同一消息别的容器收到、它没收到) * * 用户看到的是「组长一直在等」,但不知道是组员慢还是死了。 * * ## 这个模块只做判断,不做动作 * * | 状态 | 含义 | App 该做什么 | * |---|---|---| * | `pending` | 刚派活,正常等待 | 什么都不做 | * | `slow` | 等得有点久了 | 轻提示「组员正在处理」| * | `stuck` | 等太久了 | 提示「可能卡住了,建议问问组长」| * * 🔴 **不自动重发** —— 重发可能让组员做两遍,产生重复产出。 */ /** 多久算「有点久」 */ export const SLOW_MS = 5 * 60_000; /** 多久算「卡住了」 */ export const STUCK_MS = 15 * 60_000; export type MemberTaskState = 'idle' | 'pending' | 'slow' | 'stuck'; export interface MemberTaskStatus { state: MemberTaskState; /** 已经等了多久(毫秒),没有在等时为 0 */ waitedMs: number; /** 给用户看的提示 */ hint: string; /** 卡住的组员名字(多组员时用户要知道找谁) */ memberName?: string; } /** * 判断「组长派给组员的活」等多久了。 * * @param messages 群消息(时间升序) * @param leaderMxId 组长的 Matrix ID(用于识别「组长派的活」) * @param targetMxId 组员的 Matrix ID(用于识别「组员回了没」) * @param now 当前时刻 */ export function memberTaskStatus( messages: Message[], leaderMxId: string, targetMxId: string, now = Date.now(), memberName?: string, ): MemberTaskStatus { const idle: MemberTaskStatus = { state: 'idle', waitedMs: 0, hint: '' }; if (!leaderMxId || !targetMxId) return idle; // 从后往前找:最近一次「组长 @ 该组员」 let assignedAt = 0; let assignedIdx = -1; for (let i = messages.length - 1; i >= 0; i--) { const m = messages[i]; if (m.senderId !== leaderMxId) continue; if (!m.mentions?.includes(targetMxId)) continue; assignedAt = Date.parse(m.ts); assignedIdx = i; break; } if (assignedIdx < 0 || !Number.isFinite(assignedAt)) return idle; // 派活之后,该组员有没有说过话 const replied = messages .slice(assignedIdx + 1) .some((m) => m.senderId === targetMxId && !!m.text?.trim()); if (replied) return idle; const waitedMs = Math.max(0, now - assignedAt); // 多组员时带上名字,否则用户不知道该找谁 const who = memberName ? `${memberName} ` : ''; if (waitedMs >= STUCK_MS) { return { state: 'stuck', waitedMs, memberName, hint: `${who}已经等了 ${Math.round(waitedMs / 60_000)} 分钟还没动静,可能卡住了,建议问问组长`, }; } if (waitedMs >= SLOW_MS) { return { state: 'slow', waitedMs, memberName, hint: `${who}还在处理组长派的活(已 ${Math.round(waitedMs / 60_000)} 分钟)`, }; } return { state: 'pending', waitedMs, hint: '' }; }