Skip to content

test(web-shell): cover restored-history pagination retries - #7657

Merged
wenshao merged 2 commits into
QwenLM:mainfrom
jay666mnj:test/restored-history-pagination-regressions
Jul 25, 2026
Merged

test(web-shell): cover restored-history pagination retries#7657
wenshao merged 2 commits into
QwenLM:mainfrom
jay666mnj:test/restored-history-pagination-regressions

Conversation

@jay666mnj

@jay666mnj jay666mnj commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Adds regression coverage for restored-session transcript pagination. It verifies that a beforeRecordId boundary is URL-encoded when requesting a transcript page and that retrying a transient initial-page failure preserves that boundary and the session client identifier.

Why it's needed

Restored sessions with truncated replay use their first persisted record as the boundary for loading older transcript history. A regression in query encoding or retry state could request the wrong page, skip history, or make the retry inconsistent. These tests preserve the pagination contract introduced by #7064.

Reviewer Test Plan

How to verify

Run the focused SDK and WebUI transcript-pagination tests. Confirm that record/1 is encoded as record%2F1 in the daemon request URL, then simulate a transient first-page failure and confirm that both the failed request and its retry send beforeRecordId: 'record-2', limit: 25, and the active session client identifier.

Evidence (Before & After)

N/A - this PR adds non-user-visible regression coverage.

Tested on

OS Status
macOS ⚠️ not tested
Windows ✅ tested
Linux ⚠️ not tested

Environment (optional)

Windows 11, Node.js v22.11.0, npm 11.12.0.

Risk & Scope

  • Main risk or tradeoff: Test-only change; no production behavior changes.
  • Not validated / out of scope: Manual TUI verification is not applicable; macOS and Linux were not tested locally.
  • Breaking changes / migration notes: None.

Linked Issues

Refs #7084

中文说明

此 PR 的内容

为已恢复会话的转录分页添加回归覆盖。它验证请求转录页时会对 beforeRecordId 边界进行 URL 编码,并验证首次页面请求发生瞬态失败后的重试会保留该边界和会话客户端标识符。

为什么需要它

回放被截断的已恢复会话会使用其第一条持久化记录作为加载较旧转录历史的边界。查询编码或重试状态的回归可能请求错误页面、跳过历史记录,或使重试不一致。这些测试保护由 #7064 引入的分页契约。

审阅者测试计划

如何验证

运行聚焦的 SDK 和 WebUI 转录分页测试。确认 daemon 请求 URL 中的 record/1 被编码为 record%2F1,然后模拟一次瞬态的首页面失败,并确认失败请求及其重试均发送 beforeRecordId: 'record-2'limit: 25 和活动会话客户端标识符。

证据(前后对比)

不适用 - 此 PR 添加非用户可见的回归覆盖。

测试环境

操作系统 状态
macOS ⚠️ 未测试
Windows ✅ 已测试
Linux ⚠️ 未测试

环境(可选)

Windows 11、Node.js v22.11.0、npm 11.12.0。

风险与范围

  • 主要风险或取舍:仅测试改动;不改变生产行为。
  • 未验证 / 范围外:不适用手动 TUI 验证;未在本地测试 macOS 和 Linux。
  • 破坏性变更 / 迁移说明:无。

关联 Issue

Refs #7084

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments above for the result.

Qwen Triage 已完成 —— 查看运行。结果见上方各阶段评论。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: observed need — issue #7084 explicitly tracks deferred test coverage from the #7064 review rounds, and this PR addresses two of the three items listed there (URL-encoding of the beforeRecordId boundary, and retry parameter preservation). Not theoretical.

Direction: aligned — regression coverage for the pagination contract introduced by #7064, scoped to what the follow-up issue asks for.

Size: not applicable — no core paths touched. Both changed files are test files (packages/sdk-typescript/test/, packages/webui/src/daemon/session/).

Approach: the scope feels right. Two small, focused additions: one new SDK-level test for query encoding, and strengthened assertions on an existing WebUI retry test. No unrelated changes, no scope creep. The third item in #7084 (replay production stamps / edge coverage) is not covered here, but the PR title scopes itself honestly to "pagination retries."

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:已确认的实际需求——issue #7084 明确跟踪了 #7064 审查中推迟的测试覆盖,本 PR 覆盖了其中两项(beforeRecordId 边界的 URL 编码、重试参数保持一致)。非理论性加固。

方向:对齐——为 #7064 引入的分页契约补充回归覆盖,范围与后续 issue 一致。

规模:不适用——未触及核心路径。两个改动文件均为测试文件。

方案:范围合理。两个小而聚焦的新增:一个 SDK 层查询编码测试,以及对现有 WebUI 重试测试的断言加强。无无关改动,无范围蔓延。#7084 中的第三项(replay 生产标记/边界覆盖)未在此覆盖,但 PR 标题诚实地限定为 "pagination retries"。

进入代码审查 🔍

Qwen Code · qwen3.8-max-preview

Reviewed at 90cbcf16ed005c2d011a6e1f856e7abe315dc42e · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

Independent proposal: for the two coverage items this PR tackles from #7084, I would (1) add a SDK-level test calling getSessionTranscriptPage with a beforeRecordId containing / and asserting the URL encodes it as %2F, and (2) strengthen the existing WebUI retry test with toHaveBeenNthCalledWith assertions so both the failed call and the retry are verified to carry the same beforeRecordId, limit, and clientId.

Comparison with the diff: the PR does exactly this. No simpler path missed.

Two additions, both clean:

  • DaemonClient.test.ts — new test encodes a before-record transcript boundary. Uses the existing recordingFetch/jsonResponse helpers, calls getSessionTranscriptPage('with/slash', { beforeRecordId: 'record/1', limit: 2 }), and asserts the full URL is http://daemon/session/with%2Fslash/transcript?beforeRecordId=record%2F1&limit=2. This correctly covers both path-segment encoding (encodeURIComponent) and query-parameter encoding (URLSearchParams). Follows the neighboring test's structure exactly.

  • DaemonSessionProvider.test.tsx — adds toHaveBeenNthCalledWith(1, ...) and toHaveBeenNthCalledWith(2, ...) to the existing keeps transient transcript page failures retryable test. Both assertions verify beforeRecordId: 'record-2', limit: 25, clientId: session.clientId. The existing test only checked toHaveBeenCalledTimes(2) and the final rendered blocks — it never verified what parameters the failed call and its retry actually sent. This closes that gap, which is exactly what test(web-shell): expand restored-history pagination regression coverage #7084 asked for.

No critical blockers. No convention violations. No unrelated changes.

Test Results

SDK: DaemonClient.test.ts — targeted test

 RUN  v1.6.1 /home/github-runner/actions-runner-5/_work/qwen-code/qwen-code/packages/sdk-typescript

 ✓ test/unit/DaemonClient.test.ts  (293 tests | 292 skipped) 5ms

 Test Files  1 passed (1)
      Tests  1 passed | 292 skipped (293)
   Start at  15:15:39
   Duration  430ms (transform 252ms, setup 0ms, collect 286ms, tests 5ms, environment 0ms, prepare 40ms)

SDK: DaemonClient.test.ts — full suite

 RUN  v1.6.1 /home/github-runner/actions-runner-5/_work/qwen-code/qwen-code/packages/sdk-typescript

 ✓ test/unit/DaemonClient.test.ts  (293 tests) 1947ms

 Test Files  1 passed (1)
      Tests  293 passed (293)
   Start at  15:18:14
   Duration  2.37s (transform 277ms, setup 0ms, collect 286ms, tests 1.95s, environment 0ms, prepare 40ms)

WebUI: DaemonSessionProvider.test.tsx — targeted test

 RUN  v3.2.4 /home/github-runner/actions-runner-5/_work/qwen-code/qwen-code/packages/webui

 ✓ src/daemon/session/DaemonSessionProvider.test.tsx (171 tests | 170 skipped) 35ms

 Test Files  1 passed (1)
      Tests  1 passed | 170 skipped (171)
   Start at  15:17:51
   Duration  1.52s (transform 741ms, setup 0ms, collect 920ms, tests 35ms, environment 236ms, prepare 163ms)

WebUI: DaemonSessionProvider.test.tsx — full suite

 ✓ src/daemon/session/DaemonSessionProvider.test.tsx (171 tests) 3697ms

 Test Files  1 passed (1)
      Tests  171 passed (171)
   Start at  15:18:34
   Duration  4.92s (transform 534ms, setup 0ms, collect 672ms, tests 3.70s, environment 226ms, prepare 172ms)

All 464 tests across both files pass (293 SDK + 171 WebUI). The stderr detach failed: TypeError: fetch failed in the WebUI run is pre-existing test-cleanup noise (mock daemon not running), unrelated to this PR.

中文说明

代码审查

独立方案: 针对 #7084 中本 PR 覆盖的两项,我会 (1) 在 SDK 层添加一个测试,用包含 /beforeRecordId 调用 getSessionTranscriptPage,断言 URL 将其编码为 %2F;(2) 在现有 WebUI 重试测试中加 toHaveBeenNthCalledWith 断言,验证失败调用和重试都携带相同的 beforeRecordIdlimitclientId

与 diff 对比: PR 完全这样做了。没有遗漏更简路径。

两个新增,都很干净:

  • DaemonClient.test.ts——新测试 encodes a before-record transcript boundary,使用现有的 recordingFetch/jsonResponse 辅助函数,验证路径段编码(encodeURIComponent)和查询参数编码(URLSearchParams)。结构完全遵循相邻测试。

  • DaemonSessionProvider.test.tsx——在现有的 keeps transient transcript page failures retryable 测试中添加 toHaveBeenNthCalledWith(1, ...)toHaveBeenNthCalledWith(2, ...) 断言。原测试只检查了调用次数和最终渲染结果,从未验证失败调用和重试实际发送了什么参数。这正好补上了 test(web-shell): expand restored-history pagination regression coverage #7084 要求的缺口。

无关键阻塞。无规范违反。无无关改动。

测试结果

两个文件共 464 个测试全部通过(SDK 293 + WebUI 171)。WebUI 运行中的 stderr detach failed 是已有的测试清理噪音(mock daemon 未运行),与本 PR 无关。

Qwen Code · qwen3.8-max-preview

Reviewed at 90cbcf16ed005c2d011a6e1f856e7abe315dc42e · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 5/5 — clean test-only PR that does exactly what #7084 asked for, nothing more.

This is the kind of follow-up that keeps a pagination contract honest. The existing retry test proved the UI recovered from a transient failure, but never checked whether the retry actually sent the same beforeRecordId — a regression there would silently request the wrong page. The new toHaveBeenNthCalledWith assertions close that gap, and the SDK encoding test guards the %2F boundary that a careless decodeURIComponent or string concatenation could break.

Both additions use the existing test helpers and follow the neighboring tests' structure. 39 lines, two files, zero production changes. All 464 tests in the touched files pass. Nothing to maintain, nothing to curse in six months.

中文说明

置信度:5/5——干净的纯测试 PR,完全按照 #7084 的要求补充覆盖,没有多余内容。

这类后续补充让分页契约保持可靠。现有的重试测试证明了 UI 能从瞬态故障中恢复,但从未检查重试是否实际发送了相同的 beforeRecordId——如果这里发生回归,会静默请求错误的页面。新增的 toHaveBeenNthCalledWith 断言补上了这个缺口,SDK 编码测试则守护了 %2F 边界。

两个新增都使用现有测试辅助函数,遵循相邻测试的结构。39 行,两个文件,零生产改动。触及文件中的全部 464 个测试通过。

Qwen Code · qwen3.8-max-preview

Reviewed at 90cbcf16ed005c2d011a6e1f856e7abe315dc42e · re-run with @qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found. LGTM! ✅

— qwen3.7-max via Qwen Code /review

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

🖼️ web-shell visual preview

Rendered against a mock daemon (no real backend): the PR base vs this PR head a1cb62d. Only screenshots that changed are shown (flows below, if any, are head-only) — refreshes on every push.

Screenshots · before / after

No screenshot changes against the PR base.

Full-resolution recordings (.webm) are attached to the workflow run.

Qwen Code · web-shell visuals

@wenshao

wenshao commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Maintainer local verification — real test runs on macOS

I built and ran this PR locally in isolated worktrees. For a test-only PR the interesting question isn't "does it pass" (CI already answered that) — it's do these assertions actually catch anything? So I ran a mutation matrix: break the production code in eight specific ways and see which side goes red.

Verdict: ✅ merge-ready. Both new assertions are load-bearing, each guarding a distinct regression that nothing else in the repo currently catches. One non-blocking note on the Fixes #7084 keyword below.

This also covers the ⚠️ not tested macOS row in the PR description.


1. Baseline, merge simulation, and repo gates

Check Result
DaemonClient.test.ts on PR head a1cb62d1 ✅ 293/293
DaemonSessionProvider.test.tsx on PR head ✅ 171/171
PR patch re-applied onto current main (a4d88455) ✅ applies clean, no conflict
Full @qwen-code/sdk suite, mainmain+PR ✅ 1434 → 1435 passed (+1 = the new test)
Full @qwen-code/webui suite on merged state ✅ 26 files / 368 tests
prettier --check / eslint / tsc --noEmit ✅ all clean

baseline

Full disclosure: one unrelated file (approval-mode-drift.test.ts) fails identically on both main and main+PR in my sandbox — a stale ajv@6 in the borrowed node_modules, not a PR defect. It is green in CI.


2. Mutation matrix — are the new assertions load-bearing?

Each row breaks production code one way, then runs the affected suite twice: BASE = main without this PR's 39 lines, PR = main + this PR. Every mutation is anchor-checked, so an edit that silently failed to apply can never be misread as "no test caught it".

ID Production regression simulated BASE PR Verdict
none (control, SDK) 🟢 297 🟢 298 control
none (control, WebUI) 🟢 171 🟢 171 control
M1b only beforeRecordId loses percent-encoding 🟢 297 🔴 1 UNIQUE
M3 failure path clears history.beforeRecordId → retry loses the boundary 🟢 171 🔴 1 UNIQUE
M1 whole query hand-concatenated (no URLSearchParams) 🔴 2 🔴 3 already covered
M2 sessionId path segment not urlEncode()'d 🔴 1 🔴 2 already covered
M4 request omits clientId 🔴 3 🔴 4 already covered
M5 configured historyPageSize ignored 🔴 3 🔴 4 already covered
M6 boundary sent under the wrong key (cursor) 🔴 2 🔴 3 already covered

Two of eight mutations flip green → red only once this PR is applied — one per new assertion. In both cases the rest of the suite stays green, so each failure points precisely at the contract it guards.

matrix


3. The two regressions only this PR catches

M1b — I deliberately kept cursor, limit, and path-segment encoding intact and broke only the beforeRecordId parameter. main sails straight through with 297 passing; the new SDK test is the sole tripwire:

Expected: "…/transcript?beforeRecordId=record%2F1&limit=2"
Received: "…/transcript?beforeRecordId=record/1&limit=2"

M3 — I added history.beforeRecordId = undefined; to the catch block of loadMoreTranscript(), a very plausible "clean up on error" slip. main passes 171/171 while the retry silently refetches the newest page; the new nth(2) assertion catches it and names the exact lost field:

-   "beforeRecordId": "record-2",     ← boundary lost on the retry
    "clientId": "client-1",
    "limit": 25,

detail


4. Notes (none blocking)

a. Fixes #7084 over-claims — please switch to Refs #7084. Issue #7084 lists three coverage items; this PR delivers the third. I checked the other two against current main and they are still uncovered:

  • "replay production stamps persisted updates with qwen.session.recordId"packages/cli/src/acp-integration/session/history-replay-page.test.ts has 5 tests, none assert the stamping.
  • "edge coverage for recent-history selection and replay page-size parsing"selectRecentHistoryRecords, isHistoryTurnStart, and getLoadReplayPageSize (acpAgent.ts:623-660) are module-private with no direct tests.

Merging with Fixes auto-closes #7084 and silently drops two thirds of the tracked work.

b. The SDK test's unique value is narrower than it looks — that's fine. The path-segment half (with%2Fslash) was already covered by GETs a paged transcript over direct REST, which is why M1/M2 are red on BASE too. The genuinely new coverage is the beforeRecordId query-parameter encoding (M1b). Still worth having.

c. toHaveBeenNthCalledWith(1, …) is redundant, and worth keeping anyway. No mutation in my matrix is caught by nth(1) alone — the sibling test prepends an older transcript page from the first replay record already pins that request shape. It costs nothing and makes the before/after retry contract explicit at the assertion site.

Environment: macOS 15 (Darwin 24.6.0), Node v22.23.1, isolated git worktrees, PR head a1cb62d1 vs main a4d88455.

中文说明

维护者本地验证 — macOS 上的真实测试运行

我在隔离的 git worktree 中本地构建并运行了这个 PR。对于一个纯测试 PR,真正值得问的不是"能不能通过"(CI 已经回答了),而是:这些断言到底能不能抓住东西? 所以我做了一个变异矩阵:用八种具体方式破坏生产代码,看哪一侧会变红。

结论:✅ 可以合并。 两条新断言都是"承重"的,各自守护一个当前仓库中没有任何其他测试能捕获的回归。关于 Fixes #7084 关键字有一条非阻塞性说明,见下文。

这同时也补上了 PR 描述中 ⚠️ 未测试 的 macOS 一行。

1. 基线、合并模拟与仓库门禁

检查项 结果
PR head a1cb62d1 上的 DaemonClient.test.ts ✅ 293/293
PR head 上的 DaemonSessionProvider.test.tsx ✅ 171/171
将 PR 补丁重新应用到当前 maina4d88455 ✅ 干净应用,无冲突
@qwen-code/sdk 完整套件,mainmain+PR ✅ 1434 → 1435 通过(+1 即新增测试)
合并态下 @qwen-code/webui 完整套件 ✅ 26 个文件 / 368 个测试
prettier --check / eslint / tsc --noEmit ✅ 全部干净

如实说明:有一个无关文件(approval-mode-drift.test.ts)在我的沙箱里对 mainmain+PR 两侧同样失败 —— 原因是借用的 node_modulesajv@6 版本过旧,并非本 PR 的缺陷;它在 CI 中是绿的。

2. 变异矩阵 —— 新断言是否承重?

每一行以一种方式破坏生产代码,然后把受影响的套件跑两遍:BASE = 不含本 PR 这 39 行的 mainPR = main + 本 PR。每个变异都做了锚点校验,因此"悄悄没改上"的编辑绝不会被误读成"没有测试抓到"。

ID 模拟的生产回归 BASE PR 判定
无(对照,SDK) 🟢 297 🟢 298 对照
无(对照,WebUI) 🟢 171 🟢 171 对照
M1b beforeRecordId 丢失百分号编码 🟢 297 🔴 1 独有
M3 失败分支清空 history.beforeRecordId → 重试丢失边界 🟢 171 🔴 1 独有
M1 整个查询串手工拼接(不用 URLSearchParams 🔴 2 🔴 3 已被覆盖
M2 sessionId 路径段未 urlEncode() 🔴 1 🔴 2 已被覆盖
M4 请求遗漏 clientId 🔴 3 🔴 4 已被覆盖
M5 忽略配置的 historyPageSize 🔴 3 🔴 4 已被覆盖
M6 边界用错误的键发送(cursor 🔴 2 🔴 3 已被覆盖

八个变异中有两个是"只有应用本 PR 之后才由绿转红"—— 恰好每条新断言各一个。 两种情况下套件其余部分都保持绿色,因此每次失败都精确指向它所守护的那条契约。

3. 只有本 PR 能抓住的两个回归

M1b —— 我刻意保持 cursorlimit 和路径段编码完好,破坏 beforeRecordId 参数。main 以 297 全通过一路放行;新增的 SDK 测试是唯一的绊线:

Expected: "…/transcript?beforeRecordId=record%2F1&limit=2"
Received: "…/transcript?beforeRecordId=record/1&limit=2"

M3 —— 我在 loadMoreTranscript()catch 块中加了一行 history.beforeRecordId = undefined;,这是一种非常可能发生的"出错时顺手清理"的疏忽。main 依然 171/171 全通过,而重试会悄悄重新拉取最新那一页;新增的 nth(2) 断言捕获了它,并准确点出丢失的字段:

-   "beforeRecordId": "record-2",     ← 重试时边界丢失
    "clientId": "client-1",
    "limit": 25,

4. 说明(均不阻塞合并)

a. Fixes #7084 声明过头 —— 建议改为 Refs #7084 Issue #7084 列了三项覆盖任务,本 PR 完成的是第三项。我对照当前 main 检查了另外两项,它们仍未被覆盖:

  • "断言回放生产端会给持久化更新打上 qwen.session.recordId" —— packages/cli/src/acp-integration/session/history-replay-page.test.ts 有 5 个测试,没有一个断言该标记。
  • "针对最近历史选取与回放页大小解析的边界覆盖" —— selectRecentHistoryRecordsisHistoryTurnStartgetLoadReplayPageSizeacpAgent.ts:623-660)均为模块私有函数,没有直接测试。

Fixes 合并会自动关闭 #7084,从而悄无声息地丢掉三分之二的待办工作。

b. SDK 测试的独有价值比表面看起来要窄 —— 但这没问题。 路径段那一半(with%2Fslash)已被 GETs a paged transcript over direct REST 覆盖,这也是 M1/M2 在 BASE 侧同样变红的原因。真正新增的覆盖是 beforeRecordId 这个查询参数的编码(M1b)。仍然值得保留。

c. toHaveBeenNthCalledWith(1, …) 是冗余的,但仍建议保留。 我的矩阵中没有任何变异是仅由 nth(1) 捕获的 —— 兄弟测试 prepends an older transcript page from the first replay record 已经钉住了那个请求形状。它不增加成本,并让重试前后的契约在断言处显式呈现。

环境: macOS 15(Darwin 24.6.0)、Node v22.23.1、隔离 git worktree、PR head a1cb62d1 对比 main a4d88455

@jay666mnj

Copy link
Copy Markdown
Contributor Author

Maintainer local verification — real test runs on macOS

I built and ran this PR locally in isolated worktrees. For a test-only PR the interesting question isn't "does it pass" (CI already answered that) — it's do these assertions actually catch anything? So I ran a mutation matrix: break the production code in eight specific ways and see which side goes red.

Verdict: ✅ merge-ready. Both new assertions are load-bearing, each guarding a distinct regression that nothing else in the repo currently catches. One non-blocking note on the Fixes #7084 keyword below.

This also covers the ⚠️ not tested macOS row in the PR description.

1. Baseline, merge simulation, and repo gates

Check Result
DaemonClient.test.ts on PR head a1cb62d1 ✅ 293/293
DaemonSessionProvider.test.tsx on PR head ✅ 171/171
PR patch re-applied onto current main (a4d88455) ✅ applies clean, no conflict
Full @qwen-code/sdk suite, mainmain+PR ✅ 1434 → 1435 passed (+1 = the new test)
Full @qwen-code/webui suite on merged state ✅ 26 files / 368 tests
prettier --check / eslint / tsc --noEmit ✅ all clean
baseline

Full disclosure: one unrelated file (approval-mode-drift.test.ts) fails identically on both main and main+PR in my sandbox — a stale ajv@6 in the borrowed node_modules, not a PR defect. It is green in CI.

2. Mutation matrix — are the new assertions load-bearing?

Each row breaks production code one way, then runs the affected suite twice: BASE = main without this PR's 39 lines, PR = main + this PR. Every mutation is anchor-checked, so an edit that silently failed to apply can never be misread as "no test caught it".

ID Production regression simulated BASE PR Verdict
— none (control, SDK) 🟢 297 🟢 298 control
— none (control, WebUI) 🟢 171 🟢 171 control
M1b only beforeRecordId loses percent-encoding 🟢 297 🔴 1 UNIQUE
M3 failure path clears history.beforeRecordId → retry loses the boundary 🟢 171 🔴 1 UNIQUE
M1 whole query hand-concatenated (no URLSearchParams) 🔴 2 🔴 3 already covered
M2 sessionId path segment not urlEncode()'d 🔴 1 🔴 2 already covered
M4 request omits clientId 🔴 3 🔴 4 already covered
M5 configured historyPageSize ignored 🔴 3 🔴 4 already covered
M6 boundary sent under the wrong key (cursor) 🔴 2 🔴 3 already covered
Two of eight mutations flip green → red only once this PR is applied — one per new assertion. In both cases the rest of the suite stays green, so each failure points precisely at the contract it guards.

matrix

3. The two regressions only this PR catches

M1b — I deliberately kept cursor, limit, and path-segment encoding intact and broke only the beforeRecordId parameter. main sails straight through with 297 passing; the new SDK test is the sole tripwire:

Expected: "…/transcript?beforeRecordId=record%2F1&limit=2"
Received: "…/transcript?beforeRecordId=record/1&limit=2"

M3 — I added history.beforeRecordId = undefined; to the catch block of loadMoreTranscript(), a very plausible "clean up on error" slip. main passes 171/171 while the retry silently refetches the newest page; the new nth(2) assertion catches it and names the exact lost field:

-   "beforeRecordId": "record-2",     ← boundary lost on the retry
    "clientId": "client-1",
    "limit": 25,

detail

4. Notes (none blocking)

a. Fixes #7084 over-claims — please switch to Refs #7084. Issue #7084 lists three coverage items; this PR delivers the third. I checked the other two against current main and they are still uncovered:

  • "replay production stamps persisted updates with qwen.session.recordId"packages/cli/src/acp-integration/session/history-replay-page.test.ts has 5 tests, none assert the stamping.
  • "edge coverage for recent-history selection and replay page-size parsing"selectRecentHistoryRecords, isHistoryTurnStart, and getLoadReplayPageSize (acpAgent.ts:623-660) are module-private with no direct tests.

Merging with Fixes auto-closes #7084 and silently drops two thirds of the tracked work.

b. The SDK test's unique value is narrower than it looks — that's fine. The path-segment half (with%2Fslash) was already covered by GETs a paged transcript over direct REST, which is why M1/M2 are red on BASE too. The genuinely new coverage is the beforeRecordId query-parameter encoding (M1b). Still worth having.

c. toHaveBeenNthCalledWith(1, …) is redundant, and worth keeping anyway. No mutation in my matrix is caught by nth(1) alone — the sibling test prepends an older transcript page from the first replay record already pins that request shape. It costs nothing and makes the before/after retry contract explicit at the assertion site.

Environment: macOS 15 (Darwin 24.6.0), Node v22.23.1, isolated git worktrees, PR head a1cb62d1 vs main a4d88455.

中文说明

I have finished the modification,thanks for your reviewing~

@wenshao
wenshao added this pull request to the merge queue Jul 25, 2026
Merged via the queue into QwenLM:main with commit 2049d50 Jul 25, 2026
49 checks passed
wenshao pushed a commit that referenced this pull request Jul 25, 2026
wenshao pushed a commit that referenced this pull request Jul 25, 2026
@wenshao

wenshao commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Post-merge verification — re-ran the real tests on main

Follow-up to my pre-merge report. This landed as 2049d5082 while I was re-running, so this is a post-merge confirmation on main itself rather than a merge gate: the merge is sound, and both new assertions are still load-bearing at the merged tip.

Verdict: ✅ confirmed good on main. No follow-up action needed. One correction to my own earlier report is at the bottom.

Previous finding → status at the merged tip

# Prior finding Status now
(a) Fixes #7084 over-claims — would auto-close an issue with 2 of 3 items still undone Resolved. Body changed to Refs #7084 before merge; #7084 is still OPEN, so the other two items survive. Thanks @jay666mnj.
(b) SDK test's unique value is narrower than it looks (path-segment half already covered) ✅ Still accurate — and now bounded precisely, see M2b below.
(c) toHaveBeenNthCalledWith(1, …) is redundant but worth keeping ✅ Unchanged; still no mutation caught by nth(1) alone.

1. The merge itself

Check Result
Merged commit 2049d5082, 2 files, 39 insertions, 0 deletions
Added lines on main vs added lines on the PR branch byte-identical (39 = 39, diff clean)
Full @qwen-code/sdk suite at merged tip ✅ 31 files / 1436 passed
Full @qwen-code/webui suite at merged tip ✅ 26 files / 368 passed
prettier --check on both merged files ✅ clean
tsc --noEmit (webui) ✅ clean

post-merge

2. Mutation matrix, re-run at the merged tip

Same method as before, now anchored on shipped main: BASE = merged main with this PR's 39 lines reverse-applied, PR = main as shipped. 8 mutations × 2 sides + 4 control runs = 20 real suite runs. Only a BASE-green / PR-red cell proves an assertion adds coverage nothing else provides.

ID Production regression simulated suite BASE PR Verdict
none (control) sdk 🟢 297 🟢 298 control
none (control) webui 🟢 171 🟢 171 control
M1b only beforeRecordId loses percent-encoding sdk 🟢 297 🔴 1 UNIQUE ✅
M3 failure path clears history.beforeRecordId → retry loses boundary webui 🟢 171 🔴 1 UNIQUE ✅
M1 whole query hand-concatenated (no URLSearchParams) sdk 🔴 2 🔴 3 already covered
M2 sessionId path segment not urlEncode()-d (DaemonClient) sdk 🔴 1 🔴 2 already covered
M2b sessionId path segment not urlEncode()-d (workspace twin) sdk 🔴 1 🔴 1 covered elsewhere
M4 request omits clientId webui 🔴 3 🔴 4 already covered
M5 configured historyPageSize ignored webui 🔴 3 🔴 4 already covered
M6 boundary sent under the wrong key (cursor) webui 🔴 2 🔴 3 already covered

matrix

Both UNIQUE cells reproduce exactly as they did pre-merge, and the failing test in each case is the one this PR touched — not an unrelated test tripping over the mutation:

  • M1b → DaemonClient > getSessionTranscriptPage > encodes a before-record transcript boundary
  • M3 → DaemonSessionProvider > keeps transient transcript page failures retryable

detail

3. New this round: M2b, and why the anchor guard earned its keep

Every mutation aborts unless its anchor matches exactly once. That guard fired on M2: the naive anchor matched twice, because `/session/${urlEncode(sessionId)}/transcript${transcriptPageSuffix(opts)}` is built at two sites — DaemonClient.getSessionTranscriptPage (~line 2320) and a workspace-scoped twin (~line 5001) that routes through workspaceJsonRequest. Without the guard I'd have mutated whichever site String.replace hit first and drawn a conclusion about the wrong one.

So I split them and asked the open question: is the workspace twin pinned by anything? Yes — M2b is red on both sides (1 failure each), caught by workspace transcript paging forces direct REST transport. The count stays at 1 on the PR side, i.e. this PR's new test correctly does not fire there. No coverage gap, no follow-up needed — this just bounds finding (b) precisely: the new SDK test's contribution is exactly the beforeRecordId query-parameter encoding on DaemonClient, nothing more and nothing less.

4. Correction to my earlier report

My pre-merge comment disclosed that approval-mode-drift.test.ts failed on both sides in my sandbox, blamed on a stale ajv@6. That was a sandbox artifact, and it does not reproduce here — with a freshly linked dependency tree the full SDK suite is 1436/1436 green. The earlier caveat should be disregarded; there was never anything wrong with that test.

One artifact remains and is genuinely environmental: tsc --noEmit in packages/sdk-typescript reports 4 errors about @qwen-code/qwen-code-core/transcriptRecords and @qwen-code/acp-bridge/transcriptReplay. Those are dist-only subpath exports whose .d.ts files aren't built in my sandbox. I A/B'd it: 4 errors on BASE, 4 errors on PR — identical, in a file this PR never touches. CI is green.

Environment: macOS 15 (Darwin 24.6.0), Node v22.23.1, isolated git worktree at merged main 2049d5082.

中文说明

合并后验证 —— 在 main 上重跑真实测试

这是对我合并前报告的后续。我在重跑期间本 PR 已作为 2049d5082 合入,因此这不再是合并前的把关,而是main 上的合并后确认:合并是干净的,两条新断言在合并后的提交上依然"承重"。

结论:✅ 在 main 上确认良好。 无需后续动作。文末有一条对我自己先前报告的更正。

上轮结论 → 在合并后提交上的状态

# 上轮结论 当前状态
(a) Fixes #7084 过度声明 —— 会自动关闭一个尚有 2/3 项未完成的 issue 已解决。 合并前正文已改为 Refs #7084#7084 仍为 OPEN,另外两项得以保留。感谢 @jay666mnj
(b) SDK 测试的独有价值比表面看起来更窄(路径段部分已被覆盖) ✅ 依然准确 —— 且现在被精确界定,见下文 M2b。
(c) toHaveBeenNthCalledWith(1, …) 冗余但值得保留 ✅ 不变;仍没有任何变异是仅靠 nth(1) 捕获的。

1. 合并本身

检查项 结果
合并提交 2049d5082,2 个文件,39 行新增,0 行删除
main 上的新增行 vs PR 分支上的新增行 逐字节一致(39 = 39,diff 干净)
合并后提交上的 @qwen-code/sdk 完整套件 ✅ 31 个文件 / 1436 通过
合并后提交上的 @qwen-code/webui 完整套件 ✅ 26 个文件 / 368 通过
对两个合并文件执行 prettier --check ✅ 干净
tsc --noEmit(webui) ✅ 干净

2. 在合并后提交上重跑变异矩阵

方法与上轮相同,但基准换成已发布的 mainBASE = 将本 PR 的 39 行反向应用后的合并版 mainPR = 已发布的 main。9 个变异 × 2 侧 = 18 次真实套件运行。只有 BASE 绿 / PR 红 的格子才能证明该断言提供了其他测试所没有的覆盖。

ID 模拟的生产回归 套件 BASE PR 判定
无(对照) sdk 🟢 297 🟢 298 对照
无(对照) webui 🟢 171 🟢 171 对照
M1b beforeRecordId 丢失百分号编码 sdk 🟢 297 🔴 1 独有 ✅
M3 失败分支清空 history.beforeRecordId → 重试丢失边界 webui 🟢 171 🔴 1 独有 ✅
M1 整个查询串手工拼接(不用 URLSearchParams sdk 🔴 2 🔴 3 已被覆盖
M2 sessionId 路径段未 urlEncode()DaemonClient sdk 🔴 1 🔴 2 已被覆盖
M2b sessionId 路径段未 urlEncode()(workspace 孪生实现) sdk 🔴 1 🔴 1 由其他测试覆盖
M4 请求遗漏 clientId webui 🔴 3 🔴 4 已被覆盖
M5 忽略已配置的 historyPageSize webui 🔴 3 🔴 4 已被覆盖
M6 边界以错误的键(cursor)发送 webui 🔴 2 🔴 3 已被覆盖

两个"独有"格子与合并前完全一致复现,且每种情况下失败的都是本 PR 改动的那个测试,而不是被变异误伤的无关测试:

  • M1b → DaemonClient > getSessionTranscriptPage > encodes a before-record transcript boundary
  • M3 → DaemonSessionProvider > keeps transient transcript page failures retryable

3. 本轮新增:M2b,以及锚点校验为何值得

每个变异都要求锚点恰好匹配一次,否则中止。这个保护在 M2 上触发了:朴素锚点匹配了两次,因为 `/session/${urlEncode(sessionId)}/transcript${transcriptPageSuffix(opts)}`两处被构造 —— DaemonClient.getSessionTranscriptPage(约 2320 行)以及一个走 workspaceJsonRequestworkspace 作用域孪生实现(约 5001 行)。若没有该保护,我会改到 String.replace 先命中的那一处,并对错误的目标下结论。

于是我把两者拆开,并提出了那个悬而未决的问题:workspace 孪生实现是否被任何测试固定?是的 —— M2b 在两侧都是红(各 1 个失败),由 workspace transcript paging forces direct REST transport 捕获。PR 侧的计数仍为 1,即本 PR 的新测试正确地不会在那里触发。没有覆盖缺口,无需后续动作 —— 这只是精确界定了结论 (b):新 SDK 测试的贡献恰好是 DaemonClientbeforeRecordId 查询参数的编码,不多也不少。

4. 对我先前报告的更正

我在合并前的评论中披露 approval-mode-drift.test.ts 在两侧同样失败,并归因于过旧的 ajv@6那是沙箱假象,本轮未能复现 —— 在重新链接依赖树后,SDK 完整套件为 1436/1436 全绿。先前那条附注应予忽略,那个测试从来没有问题。

另有一个确属环境性的假象:packages/sdk-typescripttsc --noEmit 报告 4 个关于 @qwen-code/qwen-code-core/transcriptRecords@qwen-code/acp-bridge/transcriptReplay 的错误。它们是仅存在于 dist 的子路径导出,而其 .d.ts 在我的沙箱中未构建。我做了 A/B:BASE 4 个错误,PR 4 个错误 —— 完全相同,且位于本 PR 从未触及的文件中。CI 是绿的。

环境: macOS 15(Darwin 24.6.0)、Node v22.23.1、位于合并后 main 2049d5082 的隔离 git worktree。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.21.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants