/** * E2E regression: desktop resume must hide agent-only transcript rows. * * Compaction handoffs are active user rows because the model needs them for * context continuity. They are not authored chat content, so the desktop * transcript must never display them after a real compressor-generated resume. */ import * as fs from 'node:fs' import * as path from 'node:path' import { buildAppEnv, createSandbox, launchDesktop, type MockBackendFixture, waitForAppReady, writeEnvFile, writeMockProviderConfig, } from './fixtures' import { MOCK_REPLY, startMockServer, VERIFICATION_STOP_TEXT, VERIFICATION_STOP_TRIGGER, } from './mock-server' import { RealSessionBuilder } from './real-session-builder' import { expect, test } from './test' const SESSION_TITLE = 'E2E Hidden History Messages' const VISIBLE_USER_TEXT = 'E2E_VISIBLE_USER_HISTORY' const VISIBLE_POST_COMPACTION_TEXT = 'E2E_VISIBLE_POST_COMPACTION_HISTORY' const COMPACTION_TRIGGER_PADDING = ' force real context compression'.repeat(600) async function setupSeededMockBackend(): Promise { const mock = await startMockServer() const sandbox = createSandbox('hidden-history') writeMockProviderConfig(sandbox.hermesHome, mock.url) fs.appendFileSync( path.join(sandbox.hermesHome, 'config.yaml'), '\ncompression:\n threshold_tokens: 1\n', 'utf8', ) writeEnvFile(sandbox.hermesHome) const builder = await RealSessionBuilder.start(sandbox.hermesHome) try { await builder.createSession({ title: SESSION_TITLE, turns: [ `${VISIBLE_USER_TEXT}${COMPACTION_TRIGGER_PADDING}`, VISIBLE_POST_COMPACTION_TEXT, ], }) } finally { await builder.close() } const { app, page } = await launchDesktop(buildAppEnv(sandbox)) return { app, page, mock, mockUrl: mock.url, sandbox, cleanup: async () => { await app.close().catch(() => undefined) await mock.close() sandbox.cleanup() }, } } test('resume hides real context-compaction handoffs', async ({}, testInfo) => { const fixture = await setupSeededMockBackend() try { const { page } = fixture await waitForAppReady(fixture, 120_000) const sessionRow = page .locator('[data-slot="sidebar"] button') .filter({ hasText: SESSION_TITLE }) .first() await sessionRow.click() const transcript = page.locator('[data-slot="aui_thread-viewport"]') await expect(transcript).toContainText(VISIBLE_USER_TEXT) await expect(transcript).toContainText(VISIBLE_POST_COMPACTION_TEXT) await expect(transcript).toContainText(MOCK_REPLY) await expect(transcript).not.toContainText('[CONTEXT COMPACTION — REFERENCE ONLY]') await page.screenshot({ path: testInfo.outputPath('hidden-history-resume.png') }) } finally { await fixture.cleanup() } }) test('live verify-on-stop continuations stay out of the transcript', async ({}, testInfo) => { const sandbox = createSandbox('live-verification-nudge') const projectRoot = path.join(sandbox.root, 'project') const changedFile = path.join(projectRoot, 'e2e-verification-target.py') fs.mkdirSync(projectRoot) fs.writeFileSync( path.join(projectRoot, 'pyproject.toml'), '[project]\nname = "e2e-verification-project"\nversion = "0.0.0"\n', 'utf8', ) const mock = await startMockServer({ verificationWritePath: changedFile }) writeMockProviderConfig(sandbox.hermesHome, mock.url) fs.appendFileSync(path.join(sandbox.hermesHome, 'config.yaml'), '\nagent:\n verify_on_stop: true\n', 'utf8') // Auto session titling (feat f726090d48) fires an auxiliary title_generation // LLM call whose user snippet CONTAINS the trigger keyword, so the mock's // isVerificationStopTrigger matches it and the title call steals a scripted // verify-on-stop turn (the transcript then ends on 'The code edit is // complete.' instead of the exhausted-verifier final). Disable the // model-backed title upgrade so script indices track real chat turns. fs.appendFileSync( path.join(sandbox.hermesHome, 'config.yaml'), '\nauxiliary:\n title_generation:\n enabled: false\n', 'utf8', ) writeEnvFile(sandbox.hermesHome) const { app, page } = await launchDesktop(buildAppEnv(sandbox)) const fixture: MockBackendFixture = { app, page, mock, mockUrl: mock.url, sandbox, cleanup: async () => { await app.close().catch(() => undefined) await mock.close() sandbox.cleanup() }, } try { await waitForAppReady(fixture, 120_000) const composer = page.locator('[contenteditable="true"]').first() await composer.click() await composer.type(VERIFICATION_STOP_TRIGGER) await page.keyboard.press('Enter') const transcript = page.locator('[data-slot="aui_thread-viewport"]') await expect(transcript).toContainText(VERIFICATION_STOP_TEXT, { timeout: 60_000 }) await expect.poll( () => mock.receivedPrompts.some(prompt => prompt.includes('[System: You edited code in this turn')), { timeout: 30_000 }, ).toBe(true) expect(fs.existsSync(changedFile), 'The scripted write_file call should edit only the sandbox project').toBe(true) await expect(transcript).not.toContainText('[System: You edited code in this turn') await page.screenshot({ path: testInfo.outputPath('live-verification-nudge.png') }) } finally { await fixture.cleanup() } })