From 22f034bd76df031563afb9192247fe9811acf016 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:38:05 +0800 Subject: [PATCH] test(snapshot): retain inbox wait timeout diagnostics --- .../session-snapshot/README.i18n.yaml | 4 +- .../test-support/session-snapshot/README.md | 2 +- .../session-snapshot/README.zh.md | 2 +- .../session-snapshot/src/harness.ts | 37 +++++++++++-------- .../session-snapshot/tests/harness.spec.ts | 2 + 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/packages/test-support/session-snapshot/README.i18n.yaml b/packages/test-support/session-snapshot/README.i18n.yaml index e3c7bafd17..d7de89fd1b 100644 --- a/packages/test-support/session-snapshot/README.i18n.yaml +++ b/packages/test-support/session-snapshot/README.i18n.yaml @@ -30,8 +30,8 @@ en: 64666ea2894acc4a zh: 0e78df32bcfd3633 /deepseek-ai-dsh-session-snapshot/use-this-package/what-can-go-wrong: - en: 9e596b474f37950a - zh: caef58074f332819 + en: 6c5e9a1f97153cd0 + zh: e41bb04558fff2ed /deepseek-ai-dsh-session-snapshot/understand-the-implementation: en: 18ff76b440fe40da zh: 4dbf2eb5f9ee98a2 diff --git a/packages/test-support/session-snapshot/README.md b/packages/test-support/session-snapshot/README.md index 69321737a8..de6ece882c 100644 --- a/packages/test-support/session-snapshot/README.md +++ b/packages/test-support/session-snapshot/README.md @@ -92,7 +92,7 @@ A scenario requiring a non-Windows host declares `posixOnly`, which skips its ru ### What can go wrong -- **A turn-end wait fails** — `waitForTurnEnd` and `waitForSubagentTurnEnd` identify the session or child and deadline even when the first log harvest exceeds that deadline. Child waits also name the requested turn. Both retain the underlying failure as the error cause. +- **A durable wait fails** — `waitForTurnEnd`, `waitForSubagentTurnEnd`, and `waitForInboxMessage` identify the session or child and deadline even when the first log harvest exceeds that deadline. Child waits also name the requested turn. All retain the underlying failure as the error cause. - **A fixture guard rejects the committed files** — orphan scenario dirs, missing files, multiple pins for one header class, duplicate sidecar content, unscrubbed prompt text or tool schemas, a `request/header` with no preceding `system/message`, and malformed pinning headers all fail the suite before comparisons run. - **The session harvest needs raw JSONL mode** — snapshot configs set the JSONL backend's `compression: 'none'`; compressed JSONL has no snapshot-harvest path. - **Built mode needs current artifacts** — run `pnpm run build` before selecting `DSH_EXAMPLE_MODE=lib`; source mode remains the zero-build path. diff --git a/packages/test-support/session-snapshot/README.zh.md b/packages/test-support/session-snapshot/README.zh.md index 635a186e3e..e2edaa53d6 100644 --- a/packages/test-support/session-snapshot/README.zh.md +++ b/packages/test-support/session-snapshot/README.zh.md @@ -92,7 +92,7 @@ Headless/ACP 与 SDK 适配器在规范化之前,将原始目录中的子创 ### 可能出什么问题 -- **轮次结束等待失败**——即使首次日志收集就超过期限,`waitForTurnEnd` 和 `waitForSubagentTurnEnd` 也会指出会话或子会话及等待期限。子会话等待还会指出目标轮次。两者都通过错误的 cause 保留底层失败。 +- **持久记录等待失败**——即使首次日志收集就超过期限,`waitForTurnEnd`、`waitForSubagentTurnEnd` 和 `waitForInboxMessage` 也会指出会话或子会话及等待期限。子会话等待还会指出目标轮次。三者都通过错误的 cause 保留底层失败。 - **fixture 保护拒绝已提交文件**——遗留场景目录、缺失文件、一个 header 类别包含多个 pin、重复的伴随文件内容、未擦除的提示文本或工具 schema、没有前置 `system/message` 的 `request/header`,以及格式错误的 pin header 都会在比较运行前使套件失败。 - **会话收集需要原始 JSONL mode**——快照配置使用 JSONL 后端的 `compression: 'none'`;压缩 JSONL 没有快照收集路径。 - **构建 mode 需要当前产物**——选择 `DSH_EXAMPLE_MODE=lib` 前先运行 `pnpm run build`;源 mode 仍是零构建路径。 diff --git a/packages/test-support/session-snapshot/src/harness.ts b/packages/test-support/session-snapshot/src/harness.ts index fb6b496a01..92ff6b9e98 100644 --- a/packages/test-support/session-snapshot/src/harness.ts +++ b/packages/test-support/session-snapshot/src/harness.ts @@ -65,6 +65,8 @@ const WAIT_POLL_INTERVAL_MS = 10 * selected session's latest complete raw-JSONL turn boundary is `turn/end`. * `waitForGoalPhase` waits for the latest durable goal snapshot to reach one phase. * `waitForInboxMessage` waits for inserted inbox text containing a scenario marker. + * Its failures identify the session and deadline even if the first log read is + * still pending; the underlying failure is retained as cause. * `waitForSubagentTurnEnd` waits until one background child has persisted a * closed model-work turn after its own descriptor; child progress has no ACP * update to wait on. Failures identify the child, turn, and deadline even if @@ -663,22 +665,25 @@ async function waitForPersistedInboxMessage( text: string, timeoutMs = DEFAULT_WAIT_TIMEOUT_MS, ): Promise { - await vi.waitFor(async () => { - const log = (await harvestSessionLogs(root)).find(candidate => candidate.id === sessionId) - const matched = log?.content.split('\n').some((line) => { - if (line.length === 0) return false - const record = JSON.parse(line) as { - type?: unknown - data?: { inserted?: Array<{ content?: Array<{ type?: unknown; text?: unknown }> }> } - } - return record.type === 'agent/inbox/spliced' && record.data?.inserted?.some(message => - message.content?.some(block => block.type === 'text' - && typeof block.text === 'string' && block.text.includes(text))) === true - }) ?? false - if (!matched) { - throw new Error(`snapshot-harness: session "${sessionId}" did not persist expected inbox message within ${timeoutMs}ms`) - } - }, { interval: WAIT_POLL_INTERVAL_MS, timeout: timeoutMs }) + const message = `snapshot-harness: session "${sessionId}" did not persist expected inbox message within ${timeoutMs}ms` + try { + await vi.waitFor(async () => { + const log = (await harvestSessionLogs(root)).find(candidate => candidate.id === sessionId) + const matched = log?.content.split('\n').some((line) => { + if (line.length === 0) return false + const record = JSON.parse(line) as { + type?: unknown + data?: { inserted?: Array<{ content?: Array<{ type?: unknown; text?: unknown }> }> } + } + return record.type === 'agent/inbox/spliced' && record.data?.inserted?.some(message => + message.content?.some(block => block.type === 'text' + && typeof block.text === 'string' && block.text.includes(text))) === true + }) ?? false + if (!matched) throw new Error(message) + }, { interval: WAIT_POLL_INTERVAL_MS, timeout: timeoutMs }) + } catch (cause) { + throw new Error(message, { cause }) + } } /** Whether a child log contains model work after its own descriptor event. */ diff --git a/packages/test-support/session-snapshot/tests/harness.spec.ts b/packages/test-support/session-snapshot/tests/harness.spec.ts index 375ad13dd1..710b07be13 100644 --- a/packages/test-support/session-snapshot/tests/harness.spec.ts +++ b/packages/test-support/session-snapshot/tests/harness.spec.ts @@ -1002,6 +1002,8 @@ describe('runScenario', () => { it.each([ { label: 'session', step: { op: 'waitForTurnEnd', timeoutMs: 20 }, expected: 'did not persist turn/end within 20ms' }, + { label: 'inbox', step: { op: 'waitForInboxMessage', text: 'missing', timeoutMs: 20 }, + expected: 'did not persist expected inbox message within 20ms' }, { label: 'child', step: { op: 'waitForSubagentTurnEnd', child: 2, timeoutMs: 20 }, expected: 'subagent child #2 did not persist closed turn 1 within 20ms' }, ] satisfies { label: string; step: InputStep; expected: string }[])