Merge remote-tracking branch 'origin/master' into codex/status-bar-token-metrics

Master independently refreshed the same stale web aria goldens this branch had
absorbed in 08aa0ca54, so every conflict was golden-vs-golden with no source
overlap. Goldens are recorded rather than merged: master's recordings win here,
and the following commit re-records to reapply this branch's one added
occupancy segment on top of them.
This commit is contained in:
Hypatia May
2026-07-30 14:49:20 +08:00
56 changed files with 317 additions and 169 deletions

View File

@@ -18,7 +18,7 @@ import {
captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
const FIXTURE = fileURLToPath(new URL('./snapshots/code-mode-round/session.jsonl', import.meta.url))
const UI_EXPECTED = fileURLToPath(new URL('./snapshots/code-mode-round/ui.expected.md', import.meta.url))
@@ -44,7 +44,7 @@ describe('web e2e: Code Mode round renders nested sub-calls', () => {
})
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
browser = await chromium.launch()
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
page = await newEnglishPage(browser)
tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })

View File

@@ -12,7 +12,7 @@ import {
captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
const FIXTURE = fileURLToPath(new URL('./snapshots/cordis-tool-round/session.jsonl', import.meta.url))
const UI_EXPECTED = fileURLToPath(new URL('./snapshots/cordis-tool-round/ui.expected.md', import.meta.url))
@@ -62,7 +62,7 @@ describe('web e2e: Cordis tools use the generic row variants', () => {
})
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
browser = await chromium.launch()
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
page = await newEnglishPage(browser)
tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })

View File

@@ -8,7 +8,7 @@ import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import {
fixtureUserPrompts, launchWebScaffold, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
const FIXTURE = fileURLToPath(new URL('./snapshots/lifecycle-chrome/session.jsonl', import.meta.url))
const SEED_FIXTURE = fileURLToPath(new URL('./snapshots/seeded-history/seed.jsonl', import.meta.url))
@@ -40,7 +40,7 @@ describe.skipIf(MODE === 'record')('web e2e: details panel follows the current S
scaffold = await launchWebScaffold({ replayFixture: FIXTURE, paceMs: 5 })
await seedSession(scaffold, await readFile(SEED_FIXTURE, 'utf8'), 'details-session-lifecycle-seed')
browser = await chromium.launch()
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
page = await newEnglishPage(browser)
tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await appFrame(page).waitFor({ timeout: 30_000 })

View File

@@ -20,7 +20,7 @@ import {
acknowledgeReloadConnectionLoss, assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/lifecycle-chrome', import.meta.url))
const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
@@ -43,7 +43,7 @@ describe('web e2e: lifecycle & chrome (workspace flow / reload / dark mode)', ()
scaffold = await launchWebScaffold(MODE === 'record' ? {} : { replayFixture: FIXTURE, paceMs: 15 })
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
browser = await chromium.launch()
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
page = await newEnglishPage(browser)
tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })

View File

@@ -23,7 +23,7 @@ import {
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/live-interactions', import.meta.url))
const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
@@ -90,7 +90,7 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => {
})
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
browser = await chromium.launch()
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
page = await newEnglishPage(browser)
tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })

View File

@@ -12,7 +12,7 @@ import {
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
launchWebScaffold, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { saveFailureShot } from './support.ts'
import { newEnglishPage, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/message-actions', import.meta.url))
// Borrowed read-only: this scenario needs any settled user+assistant pair, not
@@ -40,7 +40,7 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
expect(fixtureUserPrompts(raw), 'borrowed seed must carry the drive prompt').toEqual([PROMPT])
await seedSession(scaffold, raw, SEED_ID)
browser = await chromium.launch()
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
page = await newEnglishPage(browser)
tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
@@ -75,7 +75,7 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
it.skipIf(MODE === 'record')('matches the conversation aria golden with IconActions and clocks', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-message-actions-aria'))
await page.getByRole('button', {
name: '选择模型,当前 deepseek-v4-flash',
name: 'Select model, current deepseek-v4-flash',
}).waitFor({ timeout: 10_000 })
// Keep a footer focused so opacity-hidden actions stay in the a11y tree
// as an active/focused control during the capture.

View File

@@ -18,7 +18,7 @@ import {
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
launchWebScaffold, recordFixture, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { saveFailureShot } from './support.ts'
import { newEnglishPage, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/navigation-panes', import.meta.url))
const SEED = join(SNAPSHOT_DIR, 'seed.jsonl')
@@ -56,7 +56,7 @@ describe('web e2e: navigation & panes over a rich seeded session', () => {
await seedSession(scaffold, raw, SEED_ID)
}
browser = await chromium.launch()
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
page = await newEnglishPage(browser)
tripwire = watchConsole(page)
slotErrors = []
page.on('console', (message) => {

View File

@@ -18,7 +18,7 @@ import {
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/question-composer', import.meta.url))
const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
@@ -44,7 +44,7 @@ describe('web e2e: resident question composer round trip', () => {
scaffold = await launchWebScaffold(MODE === 'record' ? {} : { replayFixture: FIXTURE, paceMs: 15 })
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
browser = await chromium.launch()
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
page = await newEnglishPage(browser)
tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })

View File

@@ -16,7 +16,7 @@ import {
assertFixtureInventory, captureStableAria, compareOrRefreshGolden,
launchWebScaffold, watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/queue-actions', import.meta.url))
const FIXTURE = fileURLToPath(new URL('./snapshots/live-interactions/session.jsonl', import.meta.url))
@@ -63,7 +63,7 @@ describe('web e2e: queue row actions', () => {
scaffold = await launchWebScaffold({ replayFixture: FIXTURE, replayOverride: overridePath })
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
browser = await chromium.launch()
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
page = await newEnglishPage(browser)
const tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })

View File

@@ -18,7 +18,7 @@ import {
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/fresh-round-trip', import.meta.url))
const FIXTURE = fileURLToPath(new URL('./snapshots/fresh-round-trip/session.jsonl', import.meta.url))
@@ -43,7 +43,7 @@ describe('web e2e: fresh round trip through the real assembly', () => {
})
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
browser = await chromium.launch()
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
page = await newEnglishPage(browser)
tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
@@ -106,7 +106,7 @@ describe('web e2e: fresh round trip through the real assembly', () => {
await expect(page.getByRole('textbox').first().isVisible()).resolves.toBe(true)
expect(await page.getByText('WEB_E2E_OK', { exact: false }).count()).toBeGreaterThanOrEqual(1)
await page.getByRole('button', {
name: '选择模型,当前 DeepSeek-V4-Flash',
name: 'Select model, current DeepSeek-V4-Flash',
}).waitFor({ timeout: 10_000 })
const snapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(UI_EXPECTED, snapshot, MODE)

View File

@@ -17,7 +17,7 @@ import {
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
launchWebScaffold, recordFixture, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { saveFailureShot } from './support.ts'
import { newEnglishPage, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/seeded-history', import.meta.url))
const SEED = fileURLToPath(new URL('./snapshots/seeded-history/seed.jsonl', import.meta.url))
@@ -49,7 +49,7 @@ describe('web e2e: seeded history renders through cold resume', () => {
await seedSession(scaffold, raw, SEED_ID)
}
browser = await chromium.launch()
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
page = await newEnglishPage(browser)
tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
@@ -125,7 +125,7 @@ describe('web e2e: seeded history renders through cold resume', () => {
// This scenario deliberately leaves the LLM seam open to prove zero
// model calls. History still restores the selected id, but no catalog
// adapter exists to provide its presentation name.
name: '选择模型,当前 deepseek-v4-flash',
name: 'Select model, current deepseek-v4-flash',
}).waitFor({ timeout: 10_000 })
const snapshot = (await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd))
.split(SEED_ID).join('{{seededId}}')

View File

@@ -67,7 +67,7 @@ import {
assertFixtureInventory, compareOrRefreshGolden, launchWebScaffold, seedSession, watchConsole,
webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { saveFailureShot } from './support.ts'
import { newEnglishPage, saveFailureShot } from './support.ts'
const SEED = fileURLToPath(new URL('./snapshots/seeded-history/seed.jsonl', import.meta.url))
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/sidebar-scrollbar', import.meta.url))
@@ -275,7 +275,7 @@ describe('web e2e: sidebar session list scrollbar (reserved gutter / themed thum
browser = await chromium.launch()
// Shorter than the other scenarios' 1000px so SEED_COUNT rows overflow
// the list with room to spare.
page = await browser.newPage({ viewport: { width: 1680, height: 800 } })
page = await newEnglishPage(browser, 800)
tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })

View File

@@ -17,7 +17,7 @@ import {
webSnapshotMode,
type WebScaffold,
} from './scaffold.ts'
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/skill-invocation-policy', import.meta.url))
const MENU_EXPECTED = join(SNAPSHOT_DIR, 'menu.expected.md')
@@ -80,7 +80,7 @@ describe('web e2e: skill invocation policy through the real host', () => {
scaffold = await launchWebScaffold({})
await seedSkills(scaffold.workspaceCwd)
browser = await chromium.launch()
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
page = await newEnglishPage(browser)
tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })

View File

@@ -24,7 +24,7 @@ import { pathToFileURL } from 'node:url'
import type { Browser, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import { REPO_ROOT, connectFreshWorkspace, probeFreePort, requireDist, saveFailureShot } from './support.ts'
import { REPO_ROOT, connectFreshWorkspace, newEnglishPage, probeFreePort, requireDist, saveFailureShot } from './support.ts'
function waitForReadyLine(child: ChildProcess): Promise<string> {
return new Promise((resolveReady, reject) => {
@@ -376,7 +376,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY || notReady.length > 0)('web smoke
)
baseUrl = (await waitForReadyLine(child)).replace('0.0.0.0', '127.0.0.1')
browser = await chromium.launch()
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
page = await newEnglishPage(browser)
page.on('pageerror', e => pageErrors.push(String(e)))
await page.goto(baseUrl, { waitUntil: 'load' })
}, 120_000)

View File

@@ -11,7 +11,6 @@
- img
- button "编辑":
- img
- button "▸ 上下文注入"
- 'button "Think The user wants me to write a single `run_code` program that:"':
- img
- img
@@ -33,12 +32,12 @@
- button "在新对话中分支":
- img
- text: {{clock}}
- textbox "给智能体发消息"
- textbox "Message the agent"
- button "Add attachment":
- img
- 'button "Access mode, current: Danger Full Access"': Danger Full Access
- button "选择模型,当前 DeepSeek-V4-Flash":
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "Send message" [disabled]
- text: 1 turns · 2 steps Tool call {{duration}} Context 7% of 128K Cache hit 52% Input 17.2K tok · Output 252 tok
- text: 1 turns · 2 steps Tool call {{duration}} Cache hit 52% Input 17.2K tok · Output 252 tok

View File

@@ -11,7 +11,6 @@
- img
- button "编辑":
- img
- button "▸ 上下文注入"
- button "Think The user wants me to:":
- img
- img
@@ -47,12 +46,12 @@
- button "在新对话中分支":
- img
- text: {{clock}}
- textbox "给智能体发消息"
- textbox "Message the agent"
- button "Add attachment":
- img
- 'button "Access mode, current: Danger Full Access"': Danger Full Access
- button "选择模型,当前 DeepSeek-V4-Flash":
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "Send message" [disabled]
- text: 1 turns · 4 steps Tool call {{duration}} Context 13% of 128K Cache hit 77% Input 66.5K tok · Output 312 tok
- text: 1 turns · 4 steps Tool call {{duration}} Cache hit 77% Input 66.5K tok · Output 312 tok

View File

@@ -11,7 +11,6 @@
- img
- button "编辑":
- img
- button "▸ 上下文注入"
- button "Think The user wants me to run a simple bash command and reply with \"DONE\".":
- img
- img
@@ -30,12 +29,12 @@
- button "在新对话中分支":
- img
- text: {{clock}}
- textbox "给智能体发消息"
- textbox "Message the agent"
- button "Add attachment":
- img
- 'button "Access mode, current: Danger Full Access"': Danger Full Access
- button "选择模型,当前 DeepSeek-V4-Flash":
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "Send message" [disabled]
- text: 1 turns · 2 steps Tool call {{duration}} Context 6% of 128K Cache hit 99% Input 15.7K tok · Output 111 tok
- text: 1 turns · 2 steps Tool call {{duration}} Cache hit 99% Input 15.7K tok · Output 111 tok

View File

@@ -1,9 +1,9 @@
- button "新建会话"
- button "收起侧边栏":
- button "New session"
- button "Collapse sidebar":
- img
- button "新建会话":
- button "New session":
- img
- text: 新会话
- text: New Session
- text: Workspaces
- button "Group by":
- img
@@ -17,9 +17,9 @@
- img
- text: workspace 1 session
- treeitem "New Session now" [selected]
- button "设置":
- button "Settings":
- img
- text: 设置
- text: Settings
- text: Let's start building
- button "Choose workspace":
- img
@@ -29,7 +29,7 @@
- button "Add attachment":
- img
- 'button "Access mode, current: Danger Full Access"': Danger Full Access
- button "选择模型,当前 DeepSeek-V4-Flash":
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "Send message" [disabled]

View File

@@ -11,7 +11,6 @@
- img
- button "编辑":
- img
- button "▸ 上下文注入"
- button "Think The user wants me to reply with a single word. Let me comply.":
- img
- img
@@ -22,12 +21,12 @@
- button "在新对话中分支":
- img
- text: {{clock}}
- textbox "给智能体发消息"
- textbox "Message the agent"
- button "Add attachment":
- img
- 'button "Access mode, current: Danger Full Access"': Danger Full Access
- button "选择模型,当前 DeepSeek-V4-Flash":
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "Send message" [disabled]
- text: 1 turns · 1 steps Context 6% of 128K Cache hit 99% Input 7.8K tok · Output 21 tok
- text: 1 turns · 1 steps Cache hit 99% Input 7.8K tok · Output 21 tok

View File

@@ -11,7 +11,6 @@
- img
- button "编辑":
- img
- button "▸ 上下文注入"
- paragraph: partial
- text: 已停止
- button "复制":
@@ -19,12 +18,12 @@
- button "在新对话中分支":
- img
- text: {{clock}}
- textbox "给智能体发消息"
- textbox "Message the agent"
- button "Add attachment":
- img
- 'button "Access mode, current: Danger Full Access"': Danger Full Access
- button "选择模型,当前 DeepSeek-V4-Flash":
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "Send message" [disabled]
- text: 1 turns · 1 steps Context 0% of 128K Input 0 tok · Output 0 tok
- text: 1 turns · 1 steps Input 0 tok · Output 0 tok

View File

@@ -11,12 +11,11 @@
- img
- button "编辑":
- img
- button "▸ 上下文注入"
- textbox "给智能体发消息"
- textbox "Message the agent"
- button "Add attachment":
- img
- 'button "Access mode, current: Danger Full Access"': Danger Full Access
- button "选择模型,当前 DeepSeek-V4-Flash":
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "Send message" [disabled]

View File

@@ -11,7 +11,6 @@
- img
- button "编辑":
- img
- button "▸ 上下文注入"
- button "Think The user is asking for a one-sentence description of event sourcing. This is a straightforward knowledge question that doesn't require any skill loading or tool calls.":
- img
- img
@@ -22,12 +21,12 @@
- button "在新对话中分支":
- img
- text: {{clock}}
- textbox "给智能体发消息"
- textbox "Message the agent"
- button "Add attachment":
- img
- 'button "Access mode, current: Danger Full Access"': Danger Full Access
- button "选择模型,当前 DeepSeek-V4-Flash":
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "Send message" [disabled]
- text: 1 turns · 1 steps Context 6% of 128K Cache hit 99% Input 7.8K tok · Output 79 tok
- text: 1 turns · 1 steps Cache hit 99% Input 7.8K tok · Output 79 tok

View File

@@ -32,11 +32,11 @@
- button "在新对话中分支":
- img
- text: {{clock}}
- textbox "给智能体发消息"
- textbox "Message the agent"
- button "Add attachment":
- img
- 'button "Access mode, current: Danger Full Access"': Danger Full Access
- button "选择模型,当前 deepseek-v4-flash":
- button "Select model, current deepseek-v4-flash":
- text: deepseek-v4-flash
- img
- button "Send message" [disabled]

View File

@@ -11,7 +11,6 @@
- img
- button "编辑":
- img
- button "▸ 上下文注入"
- button "Think The user wants me to use the ask_user_question tool with specific parameters. Let me do exactly that.":
- img
- img
@@ -30,12 +29,12 @@
- button "在新对话中分支":
- img
- text: {{clock}}
- textbox "给智能体发消息"
- textbox "Message the agent"
- button "Add attachment":
- img
- 'button "Access mode, current: Danger Full Access"': Danger Full Access
- button "选择模型,当前 DeepSeek-V4-Flash":
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "Send message" [disabled]
- text: 1 turns · 2 steps Tool call {{duration}} Context 3% of 128K Cache hit 95% Input 8.6K tok · Output 180 tok
- text: 1 turns · 2 steps Tool call {{duration}} Cache hit 95% Input 8.6K tok · Output 180 tok

View File

@@ -2,11 +2,11 @@
- text: Pick one
- heading "Which color do you prefer?" [level=2]
- text: 1 / 1
- button "上一题" [disabled]:
- button "Previous question" [disabled]:
- img
- button "下一题" [disabled]:
- button "Next question" [disabled]:
- img
- button "放弃整组问题":
- button "Dismiss all questions":
- img
- radiogroup:
- radio "Blue":
@@ -15,9 +15,9 @@
- radio "Green":
- text: 2 Green A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.
- img
- button "其他,请填写自定义答案":
- button "Other — enter a custom answer":
- img
- text: 其他,请填写自定义答案
- text: Other — enter a custom answer
- status
- button "跳过本题"
- button "提交" [disabled]
- button "Skip this question"
- button "Submit" [disabled]

View File

@@ -26,11 +26,11 @@
- img
- button "取消编辑":
- img
- textbox "给智能体发消息"
- textbox "Message the agent"
- button "Add attachment":
- img
- 'button "Access mode, current: Danger Full Access"': Danger Full Access
- button "选择模型,当前 DeepSeek-V4-Flash":
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "Stop generating"

View File

@@ -20,11 +20,11 @@
- img
- button "删除排队消息":
- img
- textbox "给智能体发消息"
- textbox "Message the agent"
- button "Add attachment":
- img
- 'button "Access mode, current: Danger Full Access"': Danger Full Access
- button "选择模型,当前 DeepSeek-V4-Flash":
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "Stop generating"

View File

@@ -31,11 +31,11 @@
- button "在新对话中分支":
- img
- text: {{clock}}
- textbox "给智能体发消息"
- textbox "Message the agent"
- button "Add attachment":
- img
- 'button "Access mode, current: Danger Full Access"': Danger Full Access
- button "选择模型,当前 deepseek-v4-flash":
- button "Select model, current deepseek-v4-flash":
- text: deepseek-v4-flash
- img
- button "Send message" [disabled]

View File

@@ -1,3 +1,3 @@
- listbox "Trigger suggestions":
- text: 技能
- text: Skills
- option "policy-shared Available to both model and user invocation" [selected]

View File

@@ -11,7 +11,6 @@
- img
- button "编辑":
- img
- button "▸ 上下文注入"
- button "Think The user wants me to use the ask_user_question tool to ask them a specific question with the given parameters. Let me do exactly that.":
- img
- img
@@ -24,11 +23,11 @@
- text: Checkpoint
- heading "Ready to continue?" [level=2]
- text: 1 / 1
- button "上一题" [disabled]:
- button "Previous question" [disabled]:
- img
- button "下一题" [disabled]:
- button "Next question" [disabled]:
- img
- button "放弃整组问题":
- button "Dismiss all questions":
- img
- radiogroup:
- radio "Yes":
@@ -37,9 +36,9 @@
- radio "No":
- text: 2 No
- img
- button "其他,请填写自定义答案":
- button "Other — enter a custom answer":
- img
- text: 其他,请填写自定义答案
- text: Other — enter a custom answer
- status
- button "跳过本题"
- button "提交" [disabled]
- button "Skip this question"
- button "Submit" [disabled]

View File

@@ -11,7 +11,6 @@
- img
- button "编辑":
- img
- button "▸ 上下文注入"
- button "Think The user wants me to use the ask_user_question tool to ask them a specific question with the given parameters. Let me do exactly that.":
- img
- img
@@ -30,12 +29,12 @@
- button "在新对话中分支":
- img
- text: {{clock}}
- textbox "给智能体发消息"
- textbox "Message the agent"
- button "Add attachment":
- img
- 'button "Access mode, current: Danger Full Access"': Danger Full Access
- button "选择模型,当前 DeepSeek-V4-Flash":
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "Send message" [disabled]
- text: 1 turns · 2 steps Tool call {{duration}} Context 6% of 128K Cache hit 98% Input 15.8K tok · Output 156 tok
- text: 1 turns · 2 steps Tool call {{duration}} Cache hit 98% Input 15.8K tok · Output 156 tok

View File

@@ -1,10 +1,10 @@
- dialog "选择工作区目录":
- heading "选择工作区目录" [level=2]
- dialog "Select Workspace Directory":
- heading "Select Workspace Directory" [level=2]
- navigation:
- button "主目录"
- button "Home"
- img
- button "browse-golden"
- button "编辑路径"
- button "Edit path"
- list:
- listitem:
- button "alpha":
@@ -16,8 +16,8 @@
- img
- text: beta
- img
- button "新建文件夹":
- button "New folder":
- img
- text: 新建文件夹
- button "取消"
- button "打开"
- text: New folder
- button "Cancel"
- button "Open"

View File

@@ -23,7 +23,7 @@ import {
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/steering', import.meta.url))
const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
@@ -67,7 +67,7 @@ describe('web e2e: mid-turn steering lands durably and visibly', () => {
sessionEvents.push(event)
})
browser = await chromium.launch()
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
page = await newEnglishPage(browser)
tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })

View File

@@ -2,13 +2,28 @@
import { existsSync, mkdirSync } from 'node:fs'
import { createServer } from 'node:net'
import { fileURLToPath } from 'node:url'
import type { Page } from 'playwright'
import type { Browser, Page } from 'playwright'
/** The built page under test; `pnpm run test:web` rebuilds it before running. */
export const DIST_INDEX = fileURLToPath(new URL('../dist/index.html', import.meta.url))
export const REPO_ROOT = fileURLToPath(new URL('../../..', import.meta.url))
/**
* Open the standard browser-test page with English selected before client
* boot. This keeps role locators and goldens deterministic across localized
* component migrations; the settings locale scenario deliberately bypasses
* this helper to cover the product's default Chinese state.
* @param browser - Playwright browser owning the page.
* @param height - Viewport height; width is fixed to the lane baseline.
* @returns the initialized page.
*/
export async function newEnglishPage(browser: Browser, height = 1000): Promise<Page> {
const page = await browser.newPage({ viewport: { width: 1680, height } })
await page.addInitScript(() => { localStorage.setItem('dsh.locale', 'en') })
return page
}
/** Fail loud on a stale checkout instead of testing yesterday's bundle. */
export function requireDist(): void {
if (!existsSync(DIST_INDEX)) {

View File

@@ -16,7 +16,7 @@ import {
acknowledgeReloadConnectionLoss, assertFixtureInventory, captureStableAria, compareOrRefreshGolden,
launchWebScaffold, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { saveFailureShot } from './support.ts'
import { newEnglishPage, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/workspace-management', import.meta.url))
// The seed is another scenario's committed fixture, reused read-only: this
@@ -42,12 +42,12 @@ describe('web e2e: workspace management (create / rename / flat view / hover car
const agentsBefore = scaffold.ctx.agents.list().length
await page.getByRole('button', { name: 'Create workspace' }).click()
await page.getByRole('menuitem', { name: 'Open local folder…' }).click()
const dialog = page.getByRole('dialog', { name: '选择工作区目录' })
const dialog = page.getByRole('dialog', { name: 'Select Workspace Directory' })
await dialog.waitFor({ timeout: 10_000 })
await dialog.getByRole('button', { name: '编辑路径' }).click()
await dialog.getByLabel('编辑路径').fill(path)
await dialog.getByLabel('编辑路径').press('Enter')
await dialog.getByRole('button', { name: '打开' }).click()
await dialog.getByRole('button', { name: 'Edit path' }).click()
await dialog.getByLabel('Edit path').fill(path)
await dialog.getByLabel('Edit path').press('Enter')
await dialog.getByRole('button', { name: 'Open' }).click()
await dialog.waitFor({ state: 'hidden', timeout: 10_000 })
await expect.poll(
() => scaffold.ctx.workspace.resolveByPath(path),
@@ -72,7 +72,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover car
await writeFile(join(sessionCwd, 'b.txt'), 'beta\n')
await seedSession(scaffold, await readFile(SEED, 'utf8'), SEED_ID)
browser = await chromium.launch()
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
page = await newEnglishPage(browser)
tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
@@ -363,15 +363,15 @@ describe('web e2e: workspace management (create / rename / flat view / hover car
try {
await page.getByRole('button', { name: 'Create workspace' }).click()
await page.getByRole('menuitem', { name: 'Open local folder…' }).click()
const dialog = page.getByRole('dialog', { name: '选择工作区目录' })
const dialog = page.getByRole('dialog', { name: 'Select Workspace Directory' })
await dialog.waitFor({ timeout: 10_000 })
await dialog.getByRole('button', { name: '编辑路径' }).click()
await dialog.getByLabel('编辑路径').fill(staged)
await dialog.getByLabel('编辑路径').press('Enter')
await dialog.getByRole('button', { name: 'Edit path' }).click()
await dialog.getByLabel('Edit path').fill(staged)
await dialog.getByLabel('Edit path').press('Enter')
await expect.poll(() => dialog.getByText('alpha', { exact: true }).count(), { timeout: 10_000 }).toBe(1)
const snapshot = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(BROWSER_EXPECTED, snapshot, MODE)
await dialog.getByRole('button', { name: '取消' }).click()
await dialog.getByRole('button', { name: 'Cancel' }).click()
await dialog.waitFor({ state: 'hidden', timeout: 10_000 })
} finally {
if (realHome === undefined) delete process.env.HOME
@@ -406,7 +406,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover car
// card; no aria role — text anchors are the stable selector).
await expect.poll(() => page.getByText('Idle', { exact: true }).count(), { timeout: 5_000 }).toBeGreaterThanOrEqual(1)
// Leaving the anchor closes it with no delay.
await page.getByRole('button', { name: '设置' }).hover()
await page.getByRole('button', { name: 'Settings' }).hover()
await expect.poll(() => page.getByText('Idle', { exact: true }).count(), { timeout: 5_000 }).toBe(0)
expect(tripwire.pageErrors).toEqual([])
}, 60_000)