Эх сурвалжийг харах

补齐模型确认浏览器回归与实际服务部署验收

Samien 1 сар өмнө
parent
commit
7abf6a1fec

+ 12 - 1
docs/verification/2026-09-29-model-confirmation.md

@@ -33,4 +33,15 @@ Hermes 的长上下文切换确认默认阈值为 100,000 token,该会话最
 
 截图的 1015.9k 不能直接解释为当前上下文超限:当前 UI 在缺少上下文值时会回退到累计用量。此次未改动该显示逻辑。
 
-此次仅完成源码修复、本地构建与提交,未发布客户端、替换运行服务或重启 Hermes。
+首次排查完成源码修复与提交后,用户继续反馈卡片自动消失、点击允许无效,随后完成以下部署与验收。
+
+## 实际部署与浏览器验收
+
+- 运行服务原来是提交 `6396efc`,尚未包含此次修复。
+- 执行 `npm run desktop:build`,通过 `bin/desktop-service-sync.mjs` 更新现有 15300 服务。同版本构建先后关系无法自动判断,核对目标后使用更新器的 `force: true` 覆盖流程,保留更新前数据备份。
+- 当前服务构建:`af061893a4e6471f06258af10d918b48dd92f722`,资源摘要 `d6bfeae0acc817a99917b09ab5b0845bf6733d969e5de0693a7fa9480687d3c1`。`/healthz`、`/readyz` 均成功。
+- 在新服务的真实认证浏览器页面中打开原会话并重新加载,选择器与服务器均返回 `grok-4.7`。
+- Hermes 日志确认:用户 08:53:26 发送的“测试”已使用 `grok-4.7`,08:53:47 完成回复;此次验收没有额外向该会话发送聊天消息。
+- 新增 `tests/e2e/model-confirmation.spec.ts`:在隔离测试服务中渲染完整聊天页面,模拟供应商模型确认响应,经过两次真实 transcript SSE 重连后卡片仍可见;实际点击“允许”发送 `confirm_expensive_model: true`,随后聊天提交成功。该测试使用假 Gateway,不计入真实供应商调用证据。
+- 同一浏览器测试分别加载旧部署 UI 和新 UI:旧 UI 在重连后丢失卡片、测试失败;新 UI 全流程通过。
+- 远端已打开的页面需要重新加载才能取得新资源;没有直接操作或验收用户另一台电脑的桌面进程。没有重启 Hermes。

+ 46 - 0
tests/e2e/model-confirmation.spec.ts

@@ -0,0 +1,46 @@
+import { test, expect } from '@playwright/test'
+
+test('model confirmation survives transcript reconnects and the Allow button completes the switch', async ({ page }) => {
+  const modelCommands: Record<string, unknown>[] = []
+  let nativeApprovals = 0
+  await page.route('**/api/realtime/channels/*/commands', async route => {
+    const command = route.request().postDataJSON()
+    if (command.method === 'approval.respond') nativeApprovals++
+    if (command.method !== 'config.set' || command.params.key !== 'model') return route.continue()
+    modelCommands.push(command.params)
+    const result = command.params.confirm_expensive_model === true
+      ? { key: 'model', value: 'gpt-5.5', scope: 'session' }
+      : { confirm_required: true, confirm_message: '长上下文切换验收:请确认重新读取缓存。' }
+    await route.fulfill({ json: { state: 'confirmed', response: { result } } })
+  })
+  await page.goto('/chat/session-demo?profile=yaoyao')
+  if (await page.getByRole('heading', { name: '登录夭夭' }).isVisible()) {
+    await page.getByLabel('账号').fill('admin')
+    await page.getByLabel('密码').fill('e2e-password')
+    await page.getByRole('button', { name: '登录', exact: true }).click()
+  }
+  await page.goto('/chat/session-demo?profile=yaoyao')
+  await expect(page.locator('.composer-tool--model')).toContainText('gpt-5.6')
+  await page.locator('.composer-tool--model').click()
+  await page.locator('.model-dialog__item').filter({ hasText: 'gpt-5.5' }).click()
+  // History can be opened without resuming Hermes; sending applies the pending pick.
+  await page.locator('.composer-textarea').fill('等待模型确认')
+  await page.getByRole('button', { name: '发送消息', exact: true }).click()
+  const confirmation = page.locator('.interaction-card--approval')
+  await expect(confirmation).toContainText('长上下文切换验收')
+  for (let i = 0; i < 2; i++) {
+    const resumed = page.waitForResponse(response => response.url().includes('/api/app/chat/sessions/session-demo/events'))
+    await page.evaluate(() => document.dispatchEvent(new Event('visibilitychange')))
+    await resumed
+    await expect(confirmation).toBeVisible()
+  }
+  await confirmation.getByRole('button', { name: '允许', exact: true }).click()
+  await expect(confirmation).toBeHidden()
+  expect(modelCommands).toHaveLength(2)
+  expect(modelCommands[1]).toMatchObject({ confirm_expensive_model: true, value: 'gpt-5.5 --provider openai --session' })
+  expect(nativeApprovals).toBe(0)
+  await page.locator('.composer-textarea').fill('模型确认后发送')
+  await page.getByRole('button', { name: '发送消息', exact: true }).click()
+  await expect(page.locator('.message__content').filter({ hasText: '这是来自假 Gateway 的流式回复。' })).toHaveCount(1)
+  expect(modelCommands).toHaveLength(2)
+})