Merge remote-tracking branch 'origin/master' into worktree/web-theme-settings-integration-fde706

This commit is contained in:
Yichen Jiang
2026-08-10 16:37:14 +08:00
48 changed files with 482 additions and 92 deletions

View File

@@ -3,12 +3,11 @@
// else covers: sidebar cold listing, the implicit resume/attach inside the
// history RPC, history-page tool views, and the client's log-ordered transcript
// events — with ZERO model calls in replay (no replay fixture; a stray stream
// fails loud on the open llm seam). The cold session also carries the one
// keyless command-row surfaces: the seeded manual `/compact` lifecycle folds
// into its checkpoint, while an Access-chip pick later runs `/permission` on
// the host. The seed is a recorded
// fixture under the
// same record discipline as every other: DSH_SNAPSHOT=record drives the turn
// fails loud on the open llm seam). The cold session also carries keyless
// command-row surfaces: the seeded manual `/compact` lifecycle folds into its
// checkpoint, an Access-chip pick later runs `/permission` on the host, and
// `/feedback` pins its expandable correlation ids. The seed is a recorded
// fixture under the same record discipline as every other: DSH_SNAPSHOT=record drives the turn
// live through the composer (real read tool against seeded workspace files)
// and harvests seed.jsonl; replay/refresh seed it cold and only render.
import { readFile, writeFile, mkdir } from 'node:fs/promises'
@@ -32,9 +31,9 @@ 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))
const UI_EXPECTED = fileURLToPath(new URL('./snapshots/seeded-history/ui.expected.md', import.meta.url))
// The command-row golden: the same conversation after one /permission switch,
// which is the only surface that shows a settled command row's copy.
// Command-row goldens over the same conversation after direct host commands.
const COMMAND_ROW_EXPECTED = fileURLToPath(new URL('./snapshots/seeded-history/command-row.expected.md', import.meta.url))
const FEEDBACK_ROW_EXPECTED = fileURLToPath(new URL('./snapshots/seeded-history/feedback-row.expected.md', import.meta.url))
const MODE = webSnapshotMode()
const SEED_ID = 'seeded-history-web-e2e'
@@ -446,6 +445,44 @@ describe('web e2e: seeded history renders through cold resume', () => {
await compareOrRefreshGolden(COMMAND_ROW_EXPECTED, snapshot, MODE)
}, 60_000)
it.skipIf(MODE === 'record')('reports full feedback correlation ids in an expandable two-line row', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-seeded-feedback-row'))
const previousDshHome = process.env.DSH_HOME
process.env.DSH_HOME = scaffold.harnessHome
try {
const input = page.locator('textarea').first()
await input.fill('/feedback the diff view is unreadable')
await input.press('Enter')
const row = page.locator('[data-variant="others"]').filter({
hasText: `Feedback recorded for session ${SEED_ID}`,
})
await row.waitFor({ timeout: 10_000 })
const disclosure = row.locator('[data-expandable]')
expect(await disclosure.getAttribute('aria-expanded')).toBe('false')
await disclosure.click()
await expect.poll(() => disclosure.getAttribute('aria-expanded')).toBe('true')
const agent = scaffold.ctx.agents.get(SessionId(SEED_ID))
if (agent === undefined) throw new Error('seeded session did not attach an agent')
const done = agent.session.events.filter(event => event.type === 'command/done').at(-1)
if (done?.type !== 'command/done') throw new Error('feedback command did not settle')
const [sessionLine, userLine, extraLine] = done.data.text?.split('\n') ?? []
expect(sessionLine).toBe(`Feedback recorded for session ${SEED_ID}`)
expect(userLine).toMatch(/^User: [0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i)
expect(extraLine).toBeUndefined()
const userId = userLine?.slice('User: '.length)
if (userId === undefined) throw new Error('feedback command omitted the user id')
const snapshot = (await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd))
.split(SEED_ID).join('{{seededId}}')
.split(userId).join('{{userId}}')
await compareOrRefreshGolden(FEEDBACK_ROW_EXPECTED, snapshot, MODE)
} finally {
if (previousDshHome === undefined) delete process.env.DSH_HOME
else process.env.DSH_HOME = previousDshHome
}
}, 60_000)
it.skipIf(MODE === 'record')('fits short logged context without a scrollport', async () => {
const agent = scaffold.ctx.agents.get(SessionId(SEED_ID))
if (agent === undefined) throw new Error('seeded session did not attach an agent')
@@ -473,6 +510,6 @@ describe('web e2e: seeded history renders through cold resume', () => {
// stream would have failed the turn loudly. Cleanliness pins the wire.
expect(tripwire.pageErrors).toEqual([])
expect(tripwire.warnings).toEqual([])
await assertFixtureInventory(SNAPSHOT_DIR, ['command-row.expected.md', 'seed.jsonl', 'ui.expected.md'])
await assertFixtureInventory(SNAPSHOT_DIR, ['command-row.expected.md', 'feedback-row.expected.md', 'seed.jsonl', 'ui.expected.md'])
})
})

View File

@@ -0,0 +1,53 @@
- banner:
- navigation "Session hierarchy":
- button "Use the read tool twice" [disabled]
- tablist:
- tab "Chat" [selected]
- tab "Trajectory"
- 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 "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.
- button "Read a.txt":
- img
- img
- text: Read
- button "a.txt"
- button "Read b.txt":
- img
- img
- text: Read
- button "b.txt"
- button "Think Both files have been read. a.txt contains \"alpha\" and b.txt contains \"beta\". I'll now reply with DONE as instructed.":
- 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.
- paragraph: DONE
- button "Copy":
- img
- button "Branch into a new conversation":
- img
- text: 7/25 {{clock}} Ran for {{duration}} TTFT {{duration}} {{throughput}} tok/s
- button "compact Compacted 5 history items (~{{tokens}} tokens)"
- button "Context injection AGENTS.md":
- img
- img
- text: Context injection AGENTS.md
- img
- text: permission preset read-only
- 'button "feedback Feedback recorded for session {{seededId}} User: {{uuid}}" [expanded]':
- img
- text: "feedback Feedback recorded for session {{seededId}} User: {{uuid}}"
- text: "Feedback recorded for session {{seededId}} User: {{uuid}}"
- textbox "Message the agent"
- button "Commands":
- img
- 'button "Access mode, current: Read Only"': Read Only
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "Send message" [disabled]
- text: 1 turns · 2 steps LLM {{duration}} · Tool call {{duration}} TTFT avg {{duration}} · {{throughput}} tok/s Cache hit 98% Input 15.8K tok · Output 135 tok