Merge remote-tracking branch 'origin/master' into worktree/custom-deepseek-models
# Conflicts: # packages/host/apiproxy/README.i18n.yaml
This commit is contained in:
@@ -6,10 +6,10 @@
|
||||
// layers, per-plugin CSS injection, and a rendered journey reaching chat
|
||||
// content from the keyless FixtureApiClient transport.
|
||||
//
|
||||
// Behavior assertions do NOT belong here: component and wiring behavior is
|
||||
// pinned by the per-package suites (SlotTestRuntime benches over src), which
|
||||
// this smoke's plugin set cannot influence — bundling, module-table
|
||||
// resolution, and boot layering are the only failure modes left to it.
|
||||
// Component behavior remains owned by per-package suites (SlotTestRuntime
|
||||
// benches over src). This smoke additionally pins the resident approval
|
||||
// fixture's cross-plugin projection because only the built connection/runtime/
|
||||
// workspace graph can prove that transport-to-row path end to end.
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { act, cleanup, fireEvent, screen, waitFor, within } from '@testing-library/react'
|
||||
@@ -105,8 +105,17 @@ it('boots the built plugin graph and renders a fixture session end to end', asyn
|
||||
const tree = await screen.findByRole('tree', { name: 'Sessions' }, { timeout: 10_000 })
|
||||
await within(tree).findByText('4 sessions')
|
||||
|
||||
// The resident approval fixture proves the assembled workspace plugin
|
||||
// distinguishes a blocked running session from an ordinarily busy one.
|
||||
const waitingTitle = await within(tree).findByText('Fixture 历史会话')
|
||||
const waitingRow = waitingTitle.closest<HTMLElement>('[role="treeitem"]')
|
||||
if (waitingRow === null) throw new Error('fixture Session title must belong to a tree row')
|
||||
expect(waitingRow.querySelector('[data-state="warning"]')).not.toBeNull()
|
||||
expect(waitingRow.querySelector('[data-state="ongoing"]')).toBeNull()
|
||||
within(waitingRow).getByText('Waiting for approval')
|
||||
|
||||
// Opening a session reaches chat content through the fixture transport.
|
||||
fireEvent.click(await within(tree).findByText('Fixture 历史会话'))
|
||||
fireEvent.click(waitingTitle)
|
||||
await waitFor(() => {
|
||||
expect(document.querySelector('[data-sample="bash"]')).not.toBeNull()
|
||||
}, { timeout: 10_000 })
|
||||
|
||||
205
apps/web/tests/markdown-images.e2e.ts
Normal file
205
apps/web/tests/markdown-images.e2e.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
// Web e2e scenario: absolute HTTP(S) Markdown images. A validated session
|
||||
// assembled through the Session API is seeded cold into the real web
|
||||
// composition, then a separate image origin proves that the browser receives
|
||||
// a real network image while local-path Markdown remains inert alt text.
|
||||
import { createServer, type Server } from 'node:http'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import type { Browser, Page } from 'playwright'
|
||||
import { chromium } from 'playwright'
|
||||
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
|
||||
import { createMessage, createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import {
|
||||
SESSION_FORMAT_VERSION,
|
||||
Session,
|
||||
SessionId,
|
||||
} from '@deepseek-ai/dsh-session'
|
||||
import type {} from '@deepseek-ai/dsh-session-title'
|
||||
import {
|
||||
assertFixtureInventory,
|
||||
captureStableAria,
|
||||
compareOrRefreshGolden,
|
||||
launchWebScaffold,
|
||||
seedSession,
|
||||
watchConsole,
|
||||
webSnapshotMode,
|
||||
type WebScaffold,
|
||||
} from './scaffold.ts'
|
||||
import { newEnglishPage, saveFailureShot } from './support.ts'
|
||||
|
||||
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/markdown-images', import.meta.url))
|
||||
const UI_EXPECTED = fileURLToPath(new URL('./snapshots/markdown-images/ui.expected.md', import.meta.url))
|
||||
const MODE = webSnapshotMode()
|
||||
const SEED_ID = 'markdown-images-web-e2e'
|
||||
const REMOTE_ALT = 'Remote test image'
|
||||
const LOCAL_ALT = 'Local test image'
|
||||
const PNG = Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=',
|
||||
'base64',
|
||||
)
|
||||
|
||||
interface ImageOrigin {
|
||||
server: Server
|
||||
url: string
|
||||
requests: Array<{ path: string | undefined; referer: string | undefined }>
|
||||
}
|
||||
|
||||
/** Start the deterministic remote image origin used by this browser scenario. */
|
||||
async function startImageOrigin(): Promise<ImageOrigin> {
|
||||
const requests: ImageOrigin['requests'] = []
|
||||
const server = createServer((request, response) => {
|
||||
requests.push({ path: request.url, referer: request.headers.referer })
|
||||
response.writeHead(200, {
|
||||
'cache-control': 'no-store',
|
||||
'content-length': PNG.length,
|
||||
'content-type': 'image/png',
|
||||
})
|
||||
response.end(PNG)
|
||||
})
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.once('error', reject)
|
||||
server.listen(0, '127.0.0.1', resolve)
|
||||
})
|
||||
const address = server.address()
|
||||
if (address === null || typeof address === 'string') {
|
||||
throw new Error('image origin did not expose an IP socket')
|
||||
}
|
||||
return {
|
||||
server,
|
||||
url: `http://127.0.0.1:${String(address.port)}/image.png`,
|
||||
requests,
|
||||
}
|
||||
}
|
||||
|
||||
/** Stop one image origin after the browser and host release their requests. */
|
||||
async function stopServer(server: Server): Promise<void> {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.close((error) => {
|
||||
if (error === undefined) resolve()
|
||||
else reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/** Build one closed, invariant-checked session fixture with remote and local image Markdown. */
|
||||
function markdownImageFixture(remoteUrl: string): string {
|
||||
const session = new Session(SessionId('markdown-image-source'))
|
||||
session.append('turn/start', {
|
||||
turn: 1,
|
||||
trigger: { kind: 'message', source: { kind: 'user' } },
|
||||
})
|
||||
const user = session.append('user/message', createUserMessage({
|
||||
content: [{ type: 'text', text: 'Show the Markdown image policy.' }],
|
||||
source: { kind: 'user' },
|
||||
}), { surfaceOp: 'append' })
|
||||
session.append('session/title', {
|
||||
title: 'Markdown image policy',
|
||||
messageSeqs: [user.seq],
|
||||
source: { kind: 'fallback' },
|
||||
})
|
||||
session.append('step/start', { turn: 1, step: 1 })
|
||||
session.append('assistant/message', {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
message: createMessage({
|
||||
role: 'assistant',
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: [
|
||||
'## Markdown images',
|
||||
'',
|
||||
``,
|
||||
'',
|
||||
``,
|
||||
'',
|
||||
'REMOTE_IMAGE_DONE',
|
||||
].join('\n'),
|
||||
}],
|
||||
source: { kind: 'model', provider: 'fixture', model: 'fixture' },
|
||||
}),
|
||||
}, { surfaceOp: 'append' })
|
||||
session.append('step/end', { turn: 1, step: 1 })
|
||||
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
|
||||
|
||||
const header = {
|
||||
type: 'session',
|
||||
version: SESSION_FORMAT_VERSION,
|
||||
id: '{{sessionId}}',
|
||||
createdAt: 0,
|
||||
cwd: '{{cwd}}',
|
||||
}
|
||||
return [
|
||||
JSON.stringify(header),
|
||||
...session.events.map(event => JSON.stringify(event)),
|
||||
'',
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
describe('web e2e: remote Markdown image rendering', () => {
|
||||
let scaffold: WebScaffold
|
||||
let imageOrigin: ImageOrigin
|
||||
let browser: Browser
|
||||
let page: Page
|
||||
let tripwire: ReturnType<typeof watchConsole>
|
||||
|
||||
beforeAll(async () => {
|
||||
imageOrigin = await startImageOrigin()
|
||||
scaffold = await launchWebScaffold({})
|
||||
await seedSession(scaffold, markdownImageFixture(imageOrigin.url), SEED_ID)
|
||||
browser = await chromium.launch()
|
||||
page = await newEnglishPage(browser)
|
||||
tripwire = watchConsole(page)
|
||||
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
||||
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
||||
}, 120_000)
|
||||
|
||||
afterAll(async () => {
|
||||
await browser?.close()
|
||||
await scaffold?.close()
|
||||
await stopServer(imageOrigin.server)
|
||||
})
|
||||
|
||||
it.skipIf(MODE === 'record')('loads only the remote image and matches the conversation golden', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-markdown-images'))
|
||||
const groupRow = page.locator('[role="treeitem"]').first()
|
||||
await groupRow.waitFor({ timeout: 15_000 })
|
||||
await groupRow.click()
|
||||
const sessionRow = page.locator('[role="treeitem"]').nth(1)
|
||||
await sessionRow.waitFor({ timeout: 10_000 })
|
||||
await sessionRow.click()
|
||||
await expect.poll(() => page.getByText('REMOTE_IMAGE_DONE', { exact: true }).count(), {
|
||||
timeout: 15_000,
|
||||
}).toBe(1)
|
||||
|
||||
const image = page.getByRole('img', { name: REMOTE_ALT })
|
||||
await image.waitFor({ timeout: 10_000 })
|
||||
await expect.poll(() => image.evaluate(element => (element as HTMLImageElement).naturalWidth), {
|
||||
timeout: 10_000,
|
||||
}).toBeGreaterThan(0)
|
||||
expect(await image.evaluate((element) => {
|
||||
const computed = getComputedStyle(element)
|
||||
return {
|
||||
borderRadius: computed.borderRadius,
|
||||
decoding: element.getAttribute('decoding'),
|
||||
loading: element.getAttribute('loading'),
|
||||
maxWidth: computed.maxWidth,
|
||||
referrerPolicy: element.getAttribute('referrerpolicy'),
|
||||
}
|
||||
})).toEqual({
|
||||
borderRadius: '8px',
|
||||
decoding: 'async',
|
||||
loading: 'lazy',
|
||||
maxWidth: '100%',
|
||||
referrerPolicy: 'no-referrer',
|
||||
})
|
||||
expect(await page.getByRole('img', { name: LOCAL_ALT }).count()).toBe(0)
|
||||
expect(await page.getByText(LOCAL_ALT, { exact: true }).count()).toBe(1)
|
||||
expect(imageOrigin.requests).toEqual([{ path: '/image.png', referer: undefined }])
|
||||
|
||||
const snapshot = (await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd))
|
||||
.split(SEED_ID).join('{{seededId}}')
|
||||
await compareOrRefreshGolden(UI_EXPECTED, snapshot, MODE)
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
expect(tripwire.warnings).toEqual([])
|
||||
await assertFixtureInventory(SNAPSHOT_DIR, ['ui.expected.md'])
|
||||
}, 60_000)
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
// Web e2e scenario: message IconActions + clocks. Cold-seeds the seeded-history
|
||||
// fixture (zero model calls) and pins the settled conversation aria after the
|
||||
// user/assistant footers are focus-revealed — the surface package jsdom tests
|
||||
// cannot substitute for (docs/testing.md snapshot rule).
|
||||
// Web e2e scenario: message IconActions + clocks. Cold-seeds a deterministic
|
||||
// completed-turn-tail fork case (zero model calls) and pins the settled
|
||||
// conversation aria after the footers are focus-revealed — the surface package
|
||||
// jsdom tests cannot substitute for (docs/testing.md snapshot rule).
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
@@ -25,6 +25,48 @@ const MODE = webSnapshotMode()
|
||||
const SEED_ID = 'message-actions-web-e2e'
|
||||
|
||||
const PROMPT = 'Use the read tool twice in one assistant message: read a.txt and b.txt. Then reply with the single word DONE and stop.'
|
||||
const MID_TURN_TEXT = 'I will read both files before answering.'
|
||||
const SECOND_PROMPT = 'Now give the final answer.'
|
||||
|
||||
/**
|
||||
* Adapt the borrowed recording into response -> tools -> interrupted Think,
|
||||
* followed by one ordinary completed response. The first response keeps
|
||||
* copy/clock but is not a legal branch point; the second is the real turn tail.
|
||||
* @param raw - Recorded seeded-history JSONL.
|
||||
* @returns A contiguous, closed two-turn fixture.
|
||||
*/
|
||||
function completedTailFixture(raw: string): string {
|
||||
const kept: string[] = []
|
||||
for (const line of raw.trimEnd().split('\n')) {
|
||||
const row = JSON.parse(line) as {
|
||||
type: string
|
||||
seq?: number
|
||||
seq0?: number
|
||||
data?: { content?: unknown[] }
|
||||
}
|
||||
const firstSeq = row.seq ?? row.seq0
|
||||
if (firstSeq !== undefined && firstSeq >= 101) break
|
||||
if (row.type === 'assistant/message' && row.seq === 64) {
|
||||
const content = row.data?.content
|
||||
if (!Array.isArray(content)) throw new Error('borrowed step-one assistant message has no content')
|
||||
content.splice(1, 0, { type: 'text', text: MID_TURN_TEXT })
|
||||
kept.push(JSON.stringify(row))
|
||||
} else {
|
||||
kept.push(line)
|
||||
}
|
||||
}
|
||||
const tail = [
|
||||
{ type: 'step/end', seq: 101, time: 1784974102749, data: { turn: 1, step: 2 } },
|
||||
{ type: 'turn/end', seq: 102, time: 1784974102750, data: { turn: 1, reason: { kind: 'aborted' } } },
|
||||
{ type: 'turn/start', seq: 103, time: 1784974103000, data: { turn: 2, trigger: { kind: 'message', source: { kind: 'user', rpcId: '{{rpcId}}' } } } },
|
||||
{ type: 'user/message', seq: 104, time: 1784974103001, data: { content: [{ type: 'text', text: SECOND_PROMPT }], source: { kind: 'user', rpcId: '{{rpcId}}' } }, surfaceOp: 'append' },
|
||||
{ type: 'step/start', seq: 105, time: 1784974103002, data: { turn: 2, step: 1 } },
|
||||
{ type: 'assistant/message', seq: 106, time: 1784974103003, data: { turn: 2, step: 1, content: [{ type: 'text', text: 'DONE' }], provenance: { provider: 'deepseek-official', model: 'deepseek-v4-flash' } }, sourceEventSeqs: [], surfaceOp: 'append' },
|
||||
{ type: 'step/end', seq: 107, time: 1784974103004, data: { turn: 2, step: 1 } },
|
||||
{ type: 'turn/end', seq: 108, time: 1784974103005, data: { turn: 2, reason: { kind: 'completed' } } },
|
||||
]
|
||||
return `${[...kept, ...tail.map(row => JSON.stringify(row))].join('\n')}\n`
|
||||
}
|
||||
|
||||
describe('web e2e: message IconActions and clocks on settled history', () => {
|
||||
let scaffold: WebScaffold
|
||||
@@ -38,8 +80,8 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
|
||||
await mkdir(sessionCwd, { recursive: true })
|
||||
await writeFile(join(sessionCwd, 'a.txt'), 'alpha\n')
|
||||
await writeFile(join(sessionCwd, 'b.txt'), 'beta\n')
|
||||
const raw = await readFile(SEED, 'utf8')
|
||||
expect(fixtureUserPrompts(raw), 'borrowed seed must carry the drive prompt').toEqual([PROMPT])
|
||||
const raw = completedTailFixture(await readFile(SEED, 'utf8'))
|
||||
expect(fixtureUserPrompts(raw), 'adapted seed must carry both prompts').toEqual([PROMPT, SECOND_PROMPT])
|
||||
await seedSession(scaffold, raw, SEED_ID)
|
||||
browser = await chromium.launch()
|
||||
page = await newEnglishPage(browser)
|
||||
@@ -53,7 +95,7 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
|
||||
await scaffold?.close()
|
||||
})
|
||||
|
||||
it.skipIf(MODE === 'record')('lists the seeded session and reveals user/assistant IconActions', async () => {
|
||||
it.skipIf(MODE === 'record')('enables branch only on the completed transcript tail', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-message-actions'))
|
||||
const groupRow = page.locator('[role="treeitem"]').first()
|
||||
await groupRow.waitFor({ timeout: 15_000 })
|
||||
@@ -61,16 +103,24 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
|
||||
const sessionRow = page.locator('[role="treeitem"]').nth(1)
|
||||
await sessionRow.waitFor({ timeout: 10_000 })
|
||||
await sessionRow.click()
|
||||
await expect.poll(() => page.getByText(MID_TURN_TEXT, { exact: true }).count(), { timeout: 15_000 }).toBe(1)
|
||||
await expect.poll(() => page.getByText('DONE', { exact: true }).count(), { timeout: 15_000 }).toBe(1)
|
||||
|
||||
// Focus-reveal the footers (hover:hover keeps them opacity-hidden until
|
||||
// hover/focus-within). User and each turn's last content assistant both
|
||||
// have copy + branch.
|
||||
// hover/focus-within). Every durable message footer keeps branch visible,
|
||||
// but only the final assistant at a completed transcript tail enables it.
|
||||
const copyButtons = page.getByRole('button', { name: 'Copy' })
|
||||
await expect.poll(() => copyButtons.count(), { timeout: 10_000 }).toBeGreaterThanOrEqual(2)
|
||||
await expect.poll(() => copyButtons.count(), { timeout: 10_000 }).toBeGreaterThanOrEqual(4)
|
||||
await copyButtons.first().focus()
|
||||
await expect.poll(() => page.getByRole('button', { name: 'Branch into a new conversation' }).count(), { timeout: 5_000 })
|
||||
.toBeGreaterThanOrEqual(2)
|
||||
const branchButtons = page.getByRole('button', { name: 'Branch into a new conversation' })
|
||||
await expect.poll(() => branchButtons.count(), { timeout: 5_000 }).toBe(4)
|
||||
await expect.poll(
|
||||
() => branchButtons.evaluateAll(buttons => buttons.map(button => button.getAttribute('aria-disabled'))),
|
||||
{ timeout: 5_000 },
|
||||
).toEqual(['true', 'true', 'true', null])
|
||||
await branchButtons.first().focus()
|
||||
await expect.poll(() => page.getByRole('tooltip').textContent(), { timeout: 5_000 })
|
||||
.toBe('Available only on the last message of a completed turn')
|
||||
await expect.poll(() => page.getByRole('button', { name: 'Edit' }).count(), { timeout: 5_000 }).toBe(0)
|
||||
}, 60_000)
|
||||
|
||||
@@ -88,8 +138,7 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
|
||||
|
||||
it.skipIf(MODE === 'record')('forks through the settled-message and session-row actions', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-message-fork'))
|
||||
// Exercise the assistant action specifically; package coverage pins the
|
||||
// user action separately at its own event seq.
|
||||
// The last message action belongs to the completed second-turn assistant.
|
||||
await page.getByRole('button', { name: 'Branch into a new conversation' }).last().click()
|
||||
await expect.poll(
|
||||
() => scaffold.ctx.agents.list().find(agent => agent.session.header.parentSession === SessionId(SEED_ID)),
|
||||
|
||||
@@ -23,15 +23,16 @@ import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './suppor
|
||||
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/question-composer', import.meta.url))
|
||||
const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
|
||||
const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md')
|
||||
// Second golden: the answered transcript — the question resolved into its
|
||||
// tool round trip and the final reply, the state the waiting golden cannot see.
|
||||
const COMPOSED_EXPECTED = join(SNAPSHOT_DIR, 'composed.expected.md')
|
||||
// Final golden: the answered transcript — the question resolved into its tool
|
||||
// round trip and the final reply, the state the composer goldens cannot see.
|
||||
const ANSWERED_EXPECTED = join(SNAPSHOT_DIR, 'answered.expected.md')
|
||||
const MODE = webSnapshotMode()
|
||||
|
||||
// The options carry long descriptions on purpose: the squeeze assertion below
|
||||
// needs option copy that WRAPS, which is the only shape that reproduces a
|
||||
// collapsed row painting its copy outside its own box.
|
||||
const PROMPT = 'Use the ask_user_question tool to ask me exactly one question with id "color", question "Which color do you prefer?", header "Pick one", and two options: label "Blue" with description "A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.", and label "Green" with description "A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions." After I answer, reply with the single word DONE and stop.'
|
||||
const PROMPT = 'Use the ask_user_question tool to ask me exactly one multi-select question with id "color", question "Which color do you prefer?", header "Pick one", and two options: label "Blue" with description "A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.", and label "Green" with description "A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions." Set multi_select to true. After I answer, reply with the single word DONE and stop.'
|
||||
|
||||
describe('web e2e: resident question composer round trip', () => {
|
||||
let scaffold: WebScaffold
|
||||
@@ -124,9 +125,17 @@ describe('web e2e: resident question composer round trip', () => {
|
||||
await page.setViewportSize(original)
|
||||
}
|
||||
|
||||
await composer.getByRole('radio', { name: 'Blue' }).click()
|
||||
// Submit: Enter on the focused option (the composer's documented submit).
|
||||
await composer.getByRole('radio', { name: 'Blue' }).press('Enter')
|
||||
const blue = composer.getByRole('checkbox', { name: 'Blue' })
|
||||
await blue.click()
|
||||
const custom = composer.getByRole('textbox')
|
||||
await custom.fill('Include accessibility notes')
|
||||
expect(await blue.getAttribute('aria-checked')).toBe('true')
|
||||
expect(await custom.inputValue()).toBe('Include accessibility notes')
|
||||
if (MODE !== 'record') {
|
||||
const snapshot = await captureStableAria(page, '[data-question-key]', scaffold.workspaceCwd)
|
||||
await compareOrRefreshGolden(COMPOSED_EXPECTED, snapshot, MODE)
|
||||
}
|
||||
await custom.press('Enter')
|
||||
|
||||
const sessionId = await settled
|
||||
if (MODE === 'record') {
|
||||
@@ -135,7 +144,14 @@ describe('web e2e: resident question composer round trip', () => {
|
||||
}
|
||||
// World state: the tool result carries the chosen answer, and DONE lands.
|
||||
const results = sessionEvents.filter(e => e.type === 'tool/result')
|
||||
expect(JSON.stringify(results.at(-1))).toContain('Blue')
|
||||
const answerText = results.flatMap(event => event.data.message.content.flatMap(block =>
|
||||
block.type === 'tool-result'
|
||||
? block.content.filter(item => item.type === 'text').map(item => item.text)
|
||||
: [],
|
||||
)).at(-1)
|
||||
expect(JSON.parse(answerText ?? '')).toEqual({
|
||||
answers: [{ id: 'color', selected: ['Blue'], custom: 'Include accessibility notes' }],
|
||||
})
|
||||
await expect.poll(() => page.getByText('DONE', { exact: true }).count(), { timeout: 15_000 }).toBeGreaterThanOrEqual(1)
|
||||
// Composer gone; regular input restored.
|
||||
expect(await page.locator('[data-question-key]').count()).toBe(0)
|
||||
@@ -149,6 +165,11 @@ describe('web e2e: resident question composer round trip', () => {
|
||||
}, 200_000)
|
||||
|
||||
it.skipIf(MODE === 'record')('keeps the fixture inventory closed', async () => {
|
||||
await assertFixtureInventory(SNAPSHOT_DIR, ['session.jsonl', 'ui.expected.md', 'answered.expected.md'])
|
||||
await assertFixtureInventory(SNAPSHOT_DIR, [
|
||||
'session.jsonl',
|
||||
'ui.expected.md',
|
||||
'composed.expected.md',
|
||||
'answered.expected.md',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: "Run two shell commands: wait for cancellation, then write skipped.txt. {{date}} {{clock}}"
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: "Using ONE run_code program: run bash `echo CODE_ROUND_OK`, then read the file missing.txt catching its error in the program. Return an object with both outcomes. Then reply DONE and stop. {{clock}}"
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: "Use only Cordis tools. First call cordis_inspect with what \"temporary\". Then call cordis_mount with this exact code: \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\". Read its returned id and call cordis_unmount with that exact id. After all three calls succeed, reply exactly CORDIS_UI_DONE and stop. {{clock}}"
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: "Use the bash tool to run exactly: echo WEB_E2E_OK. Then reply with the single word DONE and stop. {{clock}}"
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: Reply with the single word LIGHTHOUSE and stop. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: Reply with a one-sentence description of event sourcing, then stop. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: Reply with a one-sentence description of event sourcing, then stop. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: Reply with a one-sentence description of event sourcing, then stop. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: Reply with a one-sentence description of event sourcing, then stop. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
|
||||
31
apps/web/tests/snapshots/markdown-images/ui.expected.md
Normal file
31
apps/web/tests/snapshots/markdown-images/ui.expected.md
Normal file
@@ -0,0 +1,31 @@
|
||||
- banner:
|
||||
- navigation "Session hierarchy":
|
||||
- button "Markdown image policy" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
- text: Show the Markdown image policy. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- heading "Markdown images" [level=2]
|
||||
- paragraph:
|
||||
- img "Remote test image"
|
||||
- paragraph: Local test image
|
||||
- paragraph: REMOTE_IMAGE_DONE
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- img
|
||||
- text: {{clock}}
|
||||
- textbox "Message the agent"
|
||||
- button "Commands":
|
||||
- img
|
||||
- 'button "Access mode, current: Workspace Write"': Workspace Write
|
||||
- button "Select model, current deepseek-v4-flash":
|
||||
- text: deepseek-v4-flash
|
||||
- img
|
||||
- button "Send message" [disabled]
|
||||
- text: 1 turns · 1 steps Input 0 tok · Output 0 tok
|
||||
@@ -8,12 +8,19 @@
|
||||
- button "Copy":
|
||||
- img
|
||||
- tooltip "Copy"
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Think The user wants me to read a.txt and b.txt, then reply with \"DONE\". Let me do both reads in parallel.":
|
||||
- img
|
||||
- img
|
||||
- text: Think The user wants me to read a.txt and b.txt, then reply with "DONE". Let me do both reads in parallel.
|
||||
- paragraph: I will read both files before answering.
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn 7/25 {{clock}}
|
||||
- button "Read a.txt":
|
||||
- img
|
||||
- img
|
||||
@@ -28,6 +35,12 @@
|
||||
- img
|
||||
- img
|
||||
- text: Think Both files have been read. a.txt contains "alpha" and b.txt contains "beta". I'll now reply with DONE as instructed.
|
||||
- text: Stopped Now give the final answer. 7/25 {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- paragraph: DONE
|
||||
- button "Copy":
|
||||
- img
|
||||
@@ -42,4 +55,4 @@
|
||||
- text: Select model
|
||||
- img
|
||||
- button "Send message" [disabled]
|
||||
- text: 1 turns · 2 steps Tool call {{duration}} Cache hit 98% Input 15.8K tok · Output 135 tok
|
||||
- text: 2 turns · 3 steps Tool call {{duration}} Cache hit 98% Input 7.8K tok · Output 103 tok
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
- text: "plan Plan mode on. Use /plan off to leave. Plan a small change: add a --greeting flag to a CLI. Do not read or write any files. Call exit_plan_mode with a short plan of at most five bullet points. Once the plan is approved, reply with the single word DONE and stop. {{clock}}"
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
- text: "Use the ask_user_question tool to ask me exactly one question with id \"color\", question \"Which color do you prefer?\", header \"Pick one\", and two options: label \"Blue\" with description \"A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.\", and label \"Green\" with description \"A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.\" After I answer, reply with the single word DONE and stop. {{clock}}"
|
||||
- text: "Use the ask_user_question tool to ask me exactly one multi-select question with id \"color\", question \"Which color do you prefer?\", header \"Pick one\", and two options: label \"Blue\" with description \"A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.\", and label \"Green\" with description \"A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.\" Set multi_select to true. After I answer, reply with the single word DONE and stop. {{clock}}"
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
- region "Which color do you prefer?":
|
||||
- text: Pick one
|
||||
- heading "Which color do you prefer?" [level=2]
|
||||
- button "Dismiss all questions":
|
||||
- img
|
||||
- group:
|
||||
- checkbox "Blue" [checked]: Blue A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.
|
||||
- checkbox "Green": Green A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.
|
||||
- textbox "Type your answer": Include accessibility notes
|
||||
- button "Previous question" [disabled]:
|
||||
- img
|
||||
- text: 1 / 1
|
||||
- button "Next question" [disabled]:
|
||||
- img
|
||||
- status
|
||||
- button "Skip this question"
|
||||
- button "Submit"
|
||||
@@ -1,20 +1,20 @@
|
||||
{"type":"session","version":0,"id":"{{sessionId}}","createdAt":1785150167878,"cwd":"{{cwd}}/workspace"}
|
||||
{"type":"turn/start","seq":0,"time":1785150167924,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user","rpcId":"{{rpcId}}"}}}}
|
||||
{"type":"user/message","seq":1,"time":1785150167925,"data":{"content":[{"type":"text","text":"Use the ask_user_question tool to ask me exactly one question with id \"color\", question \"Which color do you prefer?\", header \"Pick one\", and two options: label \"Blue\" with description \"A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.\", and label \"Green\" with description \"A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.\" After I answer, reply with the single word DONE and stop."}],"source":{"kind":"user","rpcId":"{{rpcId}}"}},"surfaceOp":"append"}
|
||||
{"type":"user/message","seq":1,"time":1785150167925,"data":{"content":[{"type":"text","text":"Use the ask_user_question tool to ask me exactly one multi-select question with id \"color\", question \"Which color do you prefer?\", header \"Pick one\", and two options: label \"Blue\" with description \"A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.\", and label \"Green\" with description \"A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.\" Set multi_select to true. After I answer, reply with the single word DONE and stop."}],"source":{"kind":"user","rpcId":"{{rpcId}}"}},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":2,"time":1785150167927,"data":{"title":"Use the ask_user_question tool to","messageSeqs":[1],"source":{"kind":"fallback"}}}
|
||||
{"type":"step/start","seq":3,"time":1785150167928,"data":{"turn":1,"step":1}}
|
||||
{"type":"request/header","seq":4,"time":1785150167929,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"assistant/chunk","seq":5,"time":1785150168452,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
|
||||
{"type":"reasoning-chunks","seq0":6,"time0":1785150168452,"data":{"turn":1,"step":1,"index":0,"dt":[87,26,1,0,0,0,38,0,0,0,0,1,12,27,0,27,0,0,1,25,0],"texts":["The"," user"," wants"," me"," to"," use"," the"," ask","_user","_","question"," tool"," with"," specific"," parameters","."," Let"," me"," do"," exactly"," that","."]}}
|
||||
{"type":"assistant/chunk","seq":28,"time":1785150168775,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
|
||||
{"type":"tool-call-chunks","seq0":29,"time0":1785150168776,"data":{"turn":1,"step":1,"index":1,"dt":[25,1,0,0,0,25,0,0,0,26,1,0,0,0,0,25,1,0,0,0,0,25,1,0,0,0,0,25,1,0,0,0,1,25,0,0,0,0,1,25,1,0,0,0,0,25,1,0,0,26,0,0,1,0,24,1,0,0,0,1,26,1,0,0,0,0,25,0,1,0,0,0,25,1,0,0,25,0,0,0,0,1,25,0,0,1,0,0,25,0,0,0,1,0,26,1,24],"id":"call_00_Cijldc88LYmVPCXYUsRq1617","name":"ask_user_question","args":["","{","\"","questions","\"",": ","[","{\"","id","\":"," \"","color","\","," \"","question","\":"," \"","Which"," color"," do"," you"," prefer","?\","," \"","header","\":"," \"","Pick"," one","\","," \"","options","\":"," [","{\"","label","\":"," \"","Blue","\","," \"","description","\":"," \"","A"," cool"," recessive"," hue"," that"," reads"," as"," calm"," and"," trustworthy"," in"," long"," reading"," sessions"," and"," dense"," dash","boards",".\"","},"," {\"","label","\":"," \"","Green","\","," \"","description","\":"," \"","A"," rest","ful"," mid","-spect","rum"," hue"," with"," the"," highest"," perceived"," brightness",","," easiest"," on"," the"," eye"," over"," long"," sessions",".\"","}]","}]","}"]}}
|
||||
{"type":"tool-call-chunks","seq0":29,"time0":1785150168776,"data":{"turn":1,"step":1,"index":1,"dt":[25,1,0,0,0,25,0,0,0,26,1,0,0,0,0,25,1,0,0,0,0,25,1,0,0,0,0,25,1,0,0,0,1,25,0,0,0,0,1,25,1,0,0,0,0,25,1,0,0,26,0,0,1,0,24,1,0,0,0,1,26,1,0,0,0,0,25,0,1,0,0,0,25,1,0,0,25,0,0,0,0,1,25,0,0,1,0,0,25,0,0,0,1,0,26,1,24],"id":"call_00_Cijldc88LYmVPCXYUsRq1617","name":"ask_user_question","args":["","{","\"","questions","\"",": ","[","{\"","id","\":"," \"","color","\","," \"","question","\":"," \"","Which"," color"," do"," you"," prefer","?\","," \"","header","\":"," \"","Pick"," one","\", \"multi_select\": true,"," \"","options","\":"," [","{\"","label","\":"," \"","Blue","\","," \"","description","\":"," \"","A"," cool"," recessive"," hue"," that"," reads"," as"," calm"," and"," trustworthy"," in"," long"," reading"," sessions"," and"," dense"," dash","boards",".\"","},"," {\"","label","\":"," \"","Green","\","," \"","description","\":"," \"","A"," rest","ful"," mid","-spect","rum"," hue"," with"," the"," highest"," perceived"," brightness",","," easiest"," on"," the"," eye"," over"," long"," sessions",".\"","}]","}]","}"]}}
|
||||
{"type":"assistant/chunk","seq":127,"time":1785150169308,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to use the ask_user_question tool with specific parameters. Let me do exactly that."}}}}
|
||||
{"type":"assistant/chunk","seq":128,"time":1785150169308,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_Cijldc88LYmVPCXYUsRq1617","name":"ask_user_question","arguments":"{\"questions\": [{\"id\": \"color\", \"question\": \"Which color do you prefer?\", \"header\": \"Pick one\", \"options\": [{\"label\": \"Blue\", \"description\": \"A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.\"}, {\"label\": \"Green\", \"description\": \"A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.\"}]}]}"}}}}
|
||||
{"type":"assistant/chunk","seq":128,"time":1785150169308,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_Cijldc88LYmVPCXYUsRq1617","name":"ask_user_question","arguments":"{\"questions\": [{\"id\": \"color\", \"question\": \"Which color do you prefer?\", \"header\": \"Pick one\", \"multi_select\": true, \"options\": [{\"label\": \"Blue\", \"description\": \"A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.\"}, {\"label\": \"Green\", \"description\": \"A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.\"}]}]}"}}}}
|
||||
{"type":"assistant/chunk","seq":129,"time":1785150169308,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":113,"outputTokens":158,"cacheReadTokens":4096,"reasoningTokens":22}}}}
|
||||
{"type":"assistant/chunk","seq":130,"time":1785150169308,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||
{"type":"assistant/message","seq":131,"time":1785150169311,"data":{"turn":1,"step":1,"content":[{"type":"reasoning","text":"The user wants me to use the ask_user_question tool with specific parameters. Let me do exactly that."},{"type":"tool-call","id":"call_00_Cijldc88LYmVPCXYUsRq1617","name":"ask_user_question","arguments":"{\"questions\": [{\"id\": \"color\", \"question\": \"Which color do you prefer?\", \"header\": \"Pick one\", \"options\": [{\"label\": \"Blue\", \"description\": \"A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.\"}, {\"label\": \"Green\", \"description\": \"A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.\"}]}]}"}],"provenance":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"usage":{"inputTokens":113,"outputTokens":158,"cacheReadTokens":4096,"reasoningTokens":22}},"sourceEventSeqs":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130],"surfaceOp":"append"}
|
||||
{"type":"tool/call","seq":132,"time":1785150169312,"data":{"turn":1,"step":1,"callId":"call_00_Cijldc88LYmVPCXYUsRq1617","name":"ask_user_question","arguments":"{\"questions\": [{\"id\": \"color\", \"question\": \"Which color do you prefer?\", \"header\": \"Pick one\", \"options\": [{\"label\": \"Blue\", \"description\": \"A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.\"}, {\"label\": \"Green\", \"description\": \"A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.\"}]}]}"}}
|
||||
{"type":"tool/result","seq":133,"time":1785150169787,"data":{"turn":1,"step":1,"callId":"call_00_Cijldc88LYmVPCXYUsRq1617","content":[{"type":"text","text":"{\"answers\":[{\"id\":\"color\",\"selected\":[\"Blue\"]}]}"}],"isError":false},"sourceEventSeqs":[132],"surfaceOp":"append"}
|
||||
{"type":"assistant/message","seq":131,"time":1785150169311,"data":{"turn":1,"step":1,"content":[{"type":"reasoning","text":"The user wants me to use the ask_user_question tool with specific parameters. Let me do exactly that."},{"type":"tool-call","id":"call_00_Cijldc88LYmVPCXYUsRq1617","name":"ask_user_question","arguments":"{\"questions\": [{\"id\": \"color\", \"question\": \"Which color do you prefer?\", \"header\": \"Pick one\", \"multi_select\": true, \"options\": [{\"label\": \"Blue\", \"description\": \"A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.\"}, {\"label\": \"Green\", \"description\": \"A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.\"}]}]}"}],"provenance":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"usage":{"inputTokens":113,"outputTokens":158,"cacheReadTokens":4096,"reasoningTokens":22}},"sourceEventSeqs":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130],"surfaceOp":"append"}
|
||||
{"type":"tool/call","seq":132,"time":1785150169312,"data":{"turn":1,"step":1,"callId":"call_00_Cijldc88LYmVPCXYUsRq1617","name":"ask_user_question","arguments":"{\"questions\": [{\"id\": \"color\", \"question\": \"Which color do you prefer?\", \"header\": \"Pick one\", \"multi_select\": true, \"options\": [{\"label\": \"Blue\", \"description\": \"A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.\"}, {\"label\": \"Green\", \"description\": \"A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.\"}]}]}"}}
|
||||
{"type":"tool/result","seq":133,"time":1785150169787,"data":{"turn":1,"step":1,"callId":"call_00_Cijldc88LYmVPCXYUsRq1617","content":[{"type":"text","text":"{\"answers\":[{\"id\":\"color\",\"selected\":[\"Blue\"],\"custom\":\"Include accessibility notes\"}]}"}],"isError":false},"sourceEventSeqs":[132],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":134,"time":1785150169790,"data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","seq":135,"time":1785150169790,"data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/chunk","seq":136,"time":1785150170605,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
- heading "Which color do you prefer?" [level=2]
|
||||
- button "Dismiss all questions":
|
||||
- img
|
||||
- radiogroup:
|
||||
- radio "Blue": 1 Blue A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.
|
||||
- radio "Green": 2 Green A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.
|
||||
- group:
|
||||
- checkbox "Blue": Blue A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.
|
||||
- checkbox "Green": Green A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.
|
||||
- textbox "Type your answer"
|
||||
- button "Previous question" [disabled]:
|
||||
- img
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: Reply with a one-sentence description of event sourcing, then stop. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: Reply with a one-sentence description of event sourcing, then stop. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: Reply with a one-sentence description of event sourcing, then stop. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
@@ -22,8 +23,9 @@
|
||||
- text: {{clock}} Edited queue item {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- paragraph: partial
|
||||
- status: Deep diving...
|
||||
- list:
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: Reply with a one-sentence description of event sourcing, then stop. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: "Use the read tool twice in one assistant message: read a.txt and b.txt. Then reply with the single word DONE and stop. 7/25 {{clock}}"
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Think The user wants me to read a.txt and b.txt, then reply with \"DONE\". Let me do both reads in parallel.":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: "Use the read tool twice in one assistant message: read a.txt and b.txt. Then reply with the single word DONE and stop. 7/25 {{clock}}"
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Think The user wants me to read a.txt and b.txt, then reply with \"DONE\". Let me do both reads in parallel.":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: Use the ask_user_question tool to ask me exactly one question with id "checkpoint", question "Ready to continue?", header "Checkpoint", and options labeled "Yes" and "No". After I answer, reply with one short sentence acknowledging my answer and stop. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: Use the ask_user_question tool to ask me exactly one question with id "checkpoint", question "Ready to continue?", header "Checkpoint", and options labeled "Yes" and "No". After I answer, reply with one short sentence acknowledging my answer and stop. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
@@ -24,8 +25,9 @@
|
||||
- text: "Interjection: include the word BANANA in your final reply. {{clock}}"
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Think The user selected \"Yes\" and wants me to include the word \"BANANA\" in my final reply. Let me acknowledge their answer.":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
- text: Explain event sourcing in one sentence. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
@@ -30,8 +31,9 @@
|
||||
- text: {{clock}} Now give the same explanation to a human reader. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- 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
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
- text: Use web_search to search exactly "DeepSeek Harness snapshot search". Then reply exactly SEARCH_DONE and stop. {{clock}}
|
||||
- button "Copy":
|
||||
- img
|
||||
- button "Branch into a new conversation":
|
||||
- button "Branch into a new conversation" [disabled]:
|
||||
- img
|
||||
- text: Available only on the last message of a completed turn
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
|
||||
@@ -400,6 +400,17 @@ describe('web e2e: persisted subagent conversation and human continuation', () =
|
||||
const tree = page.getByRole('tree', { name: 'Subagent sessions' })
|
||||
const nestedRow = tree.getByRole('treeitem', { name: new RegExp(NESTED_LABEL) })
|
||||
expect(await nestedRow.locator(':scope > *').count()).toBe(1)
|
||||
const clickArea = nestedRow.locator(':scope > *')
|
||||
const [treeBox, clickAreaBox] = await Promise.all([
|
||||
tree.boundingBox(),
|
||||
clickArea.boundingBox(),
|
||||
])
|
||||
expect(treeBox).not.toBeNull()
|
||||
expect(clickAreaBox).not.toBeNull()
|
||||
expect([
|
||||
Math.round(clickAreaBox!.x - treeBox!.x),
|
||||
Math.round(treeBox!.x + treeBox!.width - clickAreaBox!.x - clickAreaBox!.width),
|
||||
]).toEqual([5, 5])
|
||||
await compareOrRefreshGolden(
|
||||
BRANCHLESS_EXPECTED,
|
||||
await captureStableAria(page, '[role="tree"][aria-label="Subagent sessions"]', scaffold.workspaceCwd),
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"tests/cordis-tool-round.e2e.ts",
|
||||
"tests/web-search-round.e2e.ts",
|
||||
"tests/message-actions.e2e.ts",
|
||||
"tests/markdown-images.e2e.ts",
|
||||
"tests/queue-actions.e2e.ts",
|
||||
"tests/skill-invocation-policy.e2e.ts",
|
||||
"tests/permission-policy-context.e2e.ts",
|
||||
|
||||
Reference in New Issue
Block a user