diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 8f1a41c..95b4004 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: env: TOKEN: ${{ secrets.GITHUB_TOKEN }} UI_DEPLOY_KEY: ${{ secrets.UI_DEPLOY_KEY }} - UI_SHA: 316891b13acf4c35434a8d7e5e1a5f1d03b081cd + UI_SHA: 67beff6c45f2887b6634677843fae1485acbee4f run: | set -euo pipefail printf 'context server=%s repository=%s sha=%s\n' "${GITHUB_SERVER_URL}" "${GITHUB_REPOSITORY}" "${GITHUB_SHA}" diff --git a/e2e/work-checkpoints.playwright.ts b/e2e/work-checkpoints.playwright.ts index 85dca3e..f3cd632 100644 --- a/e2e/work-checkpoints.playwright.ts +++ b/e2e/work-checkpoints.playwright.ts @@ -33,20 +33,17 @@ async function setup(request:any,scenario:Scenario='normal',awaitTask=true,optio }).toBe(scenario.endsWith('failure')?'failed':'completed') return {project,conv,ids,sendRunId:sendBody.run.id} } -async function waitForFailureNotice(request:any,seed:any){ +async function waitForFailedWorkCard(request:any,seed:any){ await expect.poll(async()=>{ - const response=await request.get(manifest.base+`/api/conversations/${seed.conv.id}`) - if(!response.ok())return false - const current=await response.json() - const children=current.runs.filter((r:any)=>r.kind==='subagent') - const noticeText=current.messages.filter((m:any)=>m.role==='system').flatMap((m:any)=>m.parts??[]) - .filter((p:any)=>p.type==='text').map((p:any)=>p.text??'').join('\n') - const noticeCount=(noticeText.match(/后台子代理「/g)??[]).length - const noticeRuns=current.runs.filter((r:any)=>r.kind==='conversation'&&r.id!==seed.sendRunId) - return children.length>0&¬iceCount>=children.length&¬iceRuns.length>0&& - noticeRuns.every((r:any)=>['completed','failed','interrupted'].includes(r.status)) + const out=await(await request.get(manifest.base+`/api/conversations/${seed.conv.id}/cards`)).json() + const card=out.cards.find((c:any)=>c.task) + return card?.task.executionState==='failed'&&card.status==='failed'&&Boolean(card.work) }).toBe(true) } +async function expectOnlySendRun(request:any,seed:any){ + const current=await(await request.get(manifest.base+`/api/conversations/${seed.conv.id}`)).json() + expect(current.runs.filter((r:any)=>r.kind==='conversation').map((r:any)=>r.id)).toEqual([seed.sendRunId]) +} async function open(page:any,seed:any){ await page.addInitScript(({id}:{id:string})=>{localStorage.setItem('as.conv',id);localStorage.setItem('as.ui',JSON.stringify({theme:'light',rightCollapsed:true,fontSize:14}))},{id:seed.conv.id}) await page.goto('/') @@ -145,25 +142,27 @@ test('two sourced stages appear while the material task is still running',async( for(const scenario of ['zero-failure','partial-failure'] as const){ test(`${scenario} shows the saved scope without a deliverable choice`,async({page,request})=>{ const seed=await setup(request,scenario) - await waitForFailureNotice(request,seed) + await waitForFailedWorkCard(request,seed) await page.addInitScript(({id}:{id:string})=>{localStorage.setItem('as.conv',id);localStorage.setItem('as.ui',JSON.stringify({theme:'light',rightCollapsed:true,fontSize:14}))},{id:seed.conv.id}) await page.goto('/') const work=page.locator('[data-conversation-work]') await expect(work).toContainText('执行失败') if(scenario==='zero-failure')await expect(work).toContainText('暂无阶段结论记录') else await expect(work).toContainText('上线六月') - await expect(page.getByText('后台整理状态已回投工作卡;请按卡片中的结果继续。').last()).toBeVisible() + await expect(page.locator('.work-choice')).toHaveCount(0) await expect(page.locator('.hitl-choice')).toHaveCount(0) + await expectOnlySendRun(request,seed) }) } test('completed sourced work offers only its grounded suggestions',async({page,request})=>{ const seed=await setup(request,'choice') await open(page,seed) - await expect(page.locator('.hitl-choice')).toHaveCount(3) - for(const label of ['会议摘要','验收清单','联调计划'])await expect(page.locator('.hitl-choice').filter({hasText:label})).toHaveCount(1) - await expect(page.locator('.hitl-card')).toContainText('基于整理结果,接下来生成什么?') + const work=page.locator('[data-conversation-work]') + await expect(work.locator('.work-choice')).toHaveCount(3) + for(const label of ['会议摘要','验收清单','联调计划'])await expect(work.locator('.work-choice').filter({hasText:label})).toHaveCount(1) + await expect(work.locator('.work-decision legend')).toContainText('接下来需要做什么?') await page.reload() - await expect(page.locator('.hitl-choice')).toHaveCount(3) + await expect(work.locator('.work-choice')).toHaveCount(3) }) test('failed material registration reports no started task or unsupported finding',async({page,request})=>{ const seed=await setup(request,'normal',false,{startFailure:true})