import { renderMail } from './inbox-format.js'; /** * 「等人点头期间新到的邮件」—— 权限恢复时要一并交给模型的那几封。 * * # 为什么需要它 * * 一个 worker 在等人类点「同意/拒绝」时是**停放**的(不占并发额度,见 pool.mjs), * 而这条会话的新邮件会被排队等在它后面。于是人类在等待期间发来的更正(实测: * 22:18:08 发出的「不对,不是让你拉取到agentmail仓库,是让你拉取到program仓库!!」) * 要等整个回合结束才被看到 —— 22:26:30,整整 8 分钟,期间模型一直在错误的目录上打转。 * * 而那一刻 worker 正好卡在等决策,本来就是"要重新给模型喂上下文"的时刻 —— * 决策一到就把这些人发的新邮件一并附上,模型当场就能改道。 * * # 为什么是纯函数 * * 筛选规则容易写错(漏掉会话过滤就会把别人会话的信塞进来;忘了排除权限邮件就会 * 把决策回执自己当成"新更正"),而这些错误都不会抛异常、只会让提示读起来不对 —— * 必须有判据钉住。 */ /** 权限请求/决策是控制面邮件,不是"新到的信"。 */ const PERMISSION_TYPES = new Set(['permission_request', 'permission_decision']); /** * @param {object|Array} inbox `/mail/inbox` 的回包(或它的 mails 数组) * @param {object} opts * @param {string} [opts.sessionID] 只取这条会话里的(空则不筛) * @param {Set} [opts.seen] 已经交付过的 mail_id(不重复给) * @param {number} [opts.limit] 最多几封(默认 3:再多会把工具结果撑爆) * @param {number} [opts.bodyLimit] 每封正文截断长度 * @returns {string[]} 已渲染好的条目,可直接拼进拒绝理由 */ export function selectWaitingMails(inbox, { sessionID = '', seen = new Set(), limit = 3, bodyLimit = 300 } = {}) { const list = Array.isArray(inbox) ? inbox : (inbox?.mails ?? []); const out = []; for (const m of list) { if (!m?.mail_id || seen.has(m.mail_id)) continue; if (PERMISSION_TYPES.has(m.mail_type)) continue; // 会话过滤只在两边都有值时生效:老回包没有 session_id 时不该把邮件全丢掉 // (宁可多给一封,也不能让人类的更正消失)。 if (sessionID && m.session_id && m.session_id !== sessionID) continue; out.push(renderMail(m, bodyLimit, '')); if (out.length >= limit) break; } return out; }