From 35f71d514432324497c15d73f547215da3930462 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 13 Sep 2026 12:06:40 +0800 Subject: [PATCH] =?UTF-8?q?test(webui):=20=E5=85=A8=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E6=BC=94=E7=BB=83=E7=9A=84=E4=B8=A4=E5=A4=84=E5=88=A4=E6=8D=AE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=88=E5=90=8C=E4=B8=BB=E9=A2=98=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E7=9A=84=E5=AE=9A=E4=BD=8D=E3=80=81=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E5=8C=BA=E7=9B=AE=E5=BD=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 第三次跑通 **37/37(0 失败 0 无法判定)**,含真 Agent 闭环:写信带附件 → pi 请求 bash 授权 → **在界面点同意** → 决策落库 → pi 回信把附件原样带回 (sha256 一致)→ 回信出现在收件箱。 两处都是**判据/前置条件**的问题,不是产品缺陷,但都会伪装成产品故障: 1. 授权页上同一 Agent 的请求主题**一模一样**("是否允许执行 bash?")。原先按主题 `.first()` 选行,于是点到了**别的会话那条旧请求**上 —— 网关回 `expired` 警告, 界面看起来"点了但没生效"。现在按**本轮会话别名**定位分组容器 (`header.locator('xpath=..')`,DOM 探针确认头与请求行同容器),且**只在折叠时** 才点头按钮(已展开时再点会把它折起来,那正是第二次又落到别人行上的原因)。 2. **工作区寻址里的 path 必须是已存在的目录**,否则桥按设计回退到自己的兜底目录 (`~/.pi/mail-sessions/<会话>`),产物就落在别处而不是我指定的目录。脚本现在 先 mkdir —— zcode 那次也栽在同一条规则上。 顺带记录一个**产品层面值得决策**的现象:pi 的 worker 池上限是 3,而**"等人点头"的 worker 占着池子**。我上一轮的误点让 3 个 worker 全卡在等决策上 → 池满 → 新邮件排队 (设计如此:满载排队不丢信),于是我 300s 等不到回信。清掉卡住的请求后队列**立即 排空**、排队那封信被取出并起了一轮。也就是说:人在开会时,同一个 Agent 接不了新活。 是否把等待授权的 worker 挪出池子(或单独给额度),需要你定。 --- .../electron/test/manual/webui-full-flow.mjs | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/client/electron/test/manual/webui-full-flow.mjs b/client/electron/test/manual/webui-full-flow.mjs index 6016a1f..588d869 100644 --- a/client/electron/test/manual/webui-full-flow.mjs +++ b/client/electron/test/manual/webui-full-flow.mjs @@ -213,6 +213,10 @@ try { if (composeOpened && composeText.includes('新建邮件')) { pass('写信页打开', '看到「新建邮件」'); const sessionPath = `/root/gotmp/webui-full-flow/${MARK}.new`; + // ★ 先建目录:工作区寻址里的 path **必须是已存在的目录**,否则桥按设计回退到 + // 自己的兜底目录(`~/.pi/mail-sessions/<会话>`),产物就落在别处 —— + // 实测撞过两次(zcode 一次、pi 一次),脚本这边必须先建。 + mkdirSync(`/root/gotmp/webui-full-flow/${MARK}`, { recursive: true }); await page.locator('input[placeholder*="@"]').first().fill(`${AGENT}@${sessionPath}`); await page.locator('input[placeholder*="特性"], input[placeholder*="分支"]').first().fill(`全流程演练 ${MARK}`).catch(() => {}); await page.locator('textarea').first().fill(`这是全流程演练 ${MARK}。请回信并**原样附回**我发的附件。`); @@ -248,6 +252,13 @@ try { console.log('\n— E. Agent 闭环(权限请求 → 批准 → 回传附件,最多 300s)—'); const mine = (sent.body?.mails || []).find(m => String(m.subject || '').includes(MARK)); const sid = mine?.session_id; + // 本轮会话别名:授权页上用它把"点哪条请求"限定到这一轮(同主题的旧请求太多) + let sessionAlias = ''; + if (sid) { + const sess = await api(`/api/v1/sessions/${sid}`, {}, adminKey).catch(() => ({ body: {} })); + sessionAlias = String(sess.body?.session?.session_alias || sess.body?.session_alias || '').trim(); + if (sessionAlias) console.log(` · 本轮会话别名:${sessionAlias}`); + } if (!sid) { undec('Agent 闭环', '发件箱里没找到刚发出的那封(拿不到会话 id)'); } else { @@ -280,11 +291,33 @@ try { await page.locator('text=授权').first().click().catch(() => {}); }); await page.waitForTimeout(2000); + // ★ 必须**限定到本轮会话**再点。 + // 授权页上同一 Agent 的请求主题常常一模一样("是否允许执行 bash?"), + // 点开本轮那条请求。 + // 授权页上同一 Agent 的请求主题一模一样("是否允许执行 bash?"),所以必须 + // **限定到本轮会话的容器里**再点:按主题 .first() 会点到别的会话那条(多半早已 + // 过期)——症状是"点了、但库里没有这条的决策结果"(网关回的是 expired 警告)。 const reqText = String(perm.subject || '').replace(/^权限请求:\s*/, '').slice(0, 12); - const rowById = page.locator(`text=${String(perm.subject || '').slice(0, 18)}`).first(); - const rowByText = page.locator(`text=${reqText}`).first(); - if ((await rowById.count()) > 0) await rowById.click().catch(() => {}); - else await rowByText.click().catch(() => {}); + const header = page.locator(`button:has-text("${sessionAlias}")`).first(); + let clickedRow = false; + if (sessionAlias && (await header.count()) > 0) { + // 分组容器 = 头按钮的父节点(DOM 里头与请求行同容器,探针确认过) + const group = header.locator('xpath=..'); + const rows = group.locator('button').filter({ hasText: '权限请求' }); + const visible = (await rows.count()) > 0 && (await rows.first().isVisible().catch(() => false)); + if (!visible) { + // 只有折叠状态才点开;已经展开时再点会把它折起来(我踩过:于是又落到别人的行上) + await header.click().catch(() => {}); + await page.waitForTimeout(700); + } + if ((await rows.count()) > 0) { + clickedRow = await rows.first().click().then(() => true).catch(() => false); + } + } + if (!clickedRow) { + const byText = page.locator(`text=${reqText}`).first(); + if ((await byText.count()) > 0) await byText.click().catch(() => {}); + } await page.waitForTimeout(2000); await shot('44-permission-detail'); // ★ 决策按钮必须用**精确文本**匹配。