feat(web-shell): paginate restored session history - #7064
Conversation
E2E / verification reportTested locally on macOS with Node.js 22 after rebasing onto the current
Expected stderr from mocked detach failures and the intentional Split View error-boundary case appeared during tests; all corresponding tests passed. |
|
Re-run at Template good ✓ — all required sections present with bilingual content. Problem: Real, observed performance issue. Loading all persisted records before rendering makes long Web Shell sessions slow and produces oversized replay responses. The before/after is clear — restored sessions now render the bounded recent page first. Direction: Aligned with Web Shell performance and UX goals. Paginated session restore with capability negotiation is a well-established pattern. The Size: 1661 production logic lines (additions + deletions) across 30 production files, 1214 test lines across 9 test files, 155 design-doc lines. This PR touches core paths ( Approach: The scope matches the feature — pagination threads through core reader, serve routes, ACP bridge, SDK types, and Web Shell UI. Each layer does its part and no more. The latest commit ( Moving on to code review. 🔍 中文说明Re-run at 模板完整 ✓ 问题:已观测到的性能问题。恢复时全量加载持久化记录导致长会话变慢。 方向:与 Web Shell 性能目标一致。capability 协商确保向后兼容。 规模:1661 行生产逻辑代码(30 个生产文件),1214 行测试代码(9 个测试文件),155 行设计文档。触及核心路径( 方案:范围与功能匹配——分页贯穿核心 reader、serve 路由、ACP bridge、SDK 类型和 Web Shell UI。最新提交为 malformed transcript page event 添加了防御性 try/catch。未发现更简路径。 进入代码审查 🔍 — Qwen Code · qwen3.7-max Reviewed at |
chiga0
left a comment
There was a problem hiding this comment.
Code Review Overview (AI Generated)
PR: #7064 — feat(web-shell): paginate restored session history
Type: New Feature
Change size: +1764/-56 across 34 files
HEAD: c5fe3bc5
Findings Summary
- Critical/Major: 0
- Minor: 4
- Nit: 3
Review
Well-architected paginated history feature with strong layering, careful turn-boundary handling, and clean backward compatibility via capability negotiation. The design correctly separates concerns: core reader handles page selection with turn alignment, compaction engine respects record boundaries, ACP agent forwards pagination parameters, and Web Shell manages scroll-to-top UX with proper anchor preservation.
Key Strengths Verified
- Turn boundary preservation:
selectBackwardPageUuidsuses a multi-pass algorithm (limit-based start → forward turn scan → backward turn scan → byte limiting → re-alignment) to ensure no page begins with only the tail of a multi-update turn. If the aligned turn exceedsmaxBytes, it throwsSessionTranscriptPageTooLargeErrorrather than returning a partial turn. - Capability negotiation:
session_transcript_paginationis correctly gated. When absent, the client sends no page size, receives nohasMore, and never callsgetSessionTranscriptPage. Degradation is clean. - Scroll anchor preservation: Standard "capture height/position, adjust after prepend" pattern with
requestAnimationFrame.followPausedByUserRefprevents auto-follow from fighting the anchor. - Race condition prevention: Three-layer protection — component ref guard, provider ref guard, and session change guard. Dual guards make duplicate triggers impossible even under rapid scroll events.
- Capacity limit: Correctly handled for both initial page (oversized replay kept intact with
capacityReached: true) and subsequent pages (prepend rejected when total blocks exceedmaxBlocks). - Test coverage: 30+ new/updated tests across 8 files covering backward pagination, turn alignment, long-turn completeness, maxBytes rejection, capability gating, scroll-to-top trigger, underfill auto-load, resize auto-load, and capacity indicator.
Minor Findings
-
Turn-start predicate duplicated:
isHistoryTurnStartinacpAgent.tsandisReplayTurnStartinsession-transcript-reader.tsencode the same logic (type === 'user' && subtype !== 'mid_turn_user_message') independently. Drift risk if the definition changes. Consider extracting a shared predicate in core. -
No DOM test validating scroll position math: The scroll anchor preservation logic (
scrollTop = previousTop + (newScrollHeight - previousHeight)) is not explicitly tested. Existing DOM tests verify thatloadMoreis called but don't assert the resultingscrollTopvalue. -
Error path permanently stops paging: On error,
hasMoreis set tofalseand a warning notice is emitted. The user cannot retry without switching sessions. Consider exposing a retry action or allowingloadMoreto be retried after a cooldown (UX follow-up). -
Missing edge-case tests: No test for empty-session pagination,
beforeRecordIdpointing to the first record, or the stale-session guard during an in-flight older-page load.
Nits
selectBackwardPageUuidsperforms five passes — more inline comments explaining each pass's purpose would aid future maintainers.ResizeObserverStubassignment changed from??=to=in test setup — intentional but could mask cross-test pollution if isolation is weak.getPersistedReplayRecordIdwrapsisRecordchecks in try/catch — the type guards should be sufficient; the catch is belt-and-suspenders.
Final Verdict
LGTM. The design is sound, turn boundaries are correctly preserved, capability negotiation degrades cleanly, and test coverage is broad. All findings are Minor/Nit and non-blocking.
This review was generated by QoderWork AI
|
CI failure follow-up: The workspace-sidebar visual test still expected the I updated the visual assertion to wait for both workspace headings and the loaded primary session row, then verified the focused dark/light Playwright captures (2 passed) and the sidebar workspace-removal unit suite (20 passed). Commit: fe42aaf. |
Code ReviewRe-run at New in this pass: The Core reader ( ACP bridge + CLI routes: Compaction engine: WebUI provider ( Web Shell scroll anchoring ( No blockers found. Implementation is thorough, well-tested, and follows project conventions. CIAll checks green:
中文说明代码审查在 核心 reader、ACP bridge、CLI 路由、compaction engine、WebUI provider 和 Web Shell scroll anchoring 的实现均正确。无阻塞问题。 CI 全部通过。 — Qwen Code · qwen3.7-max Reviewed at |
|
Confidence: 5/5 — Clean across every stage. Real performance problem, well-architected paginated solution with capability negotiation, thorough implementation, all CI green, maintainer actively engaged through multiple review rounds. This PR solves a genuine problem — restoring long sessions was slow and could produce oversized replay responses. The solution is well-layered: backward paging in the core reader with turn-boundary alignment, capability negotiation through The implementation has been through several review cycles with The PR is large (1661 production lines across 30 files) but the scope is driven by the feature's requirements — pagination threads through core, CLI, bridge, SDK, and web-shell. No part of the diff feels gratuitous. LGTM, ready to ship. ✅ 中文说明信心:5/5 — 各阶段均通过。真实的性能问题,带有 capability 协商的分页方案架构良好,实现完整,CI 全绿,维护者在多轮审查中积极参与。 PR 较大(30 个文件中 1661 行生产代码),但范围由功能需求驱动——分页贯穿 core、CLI、bridge、SDK 和 web-shell。diff 中没有多余的改动。 可以合入。✅ — Qwen Code · qwen3.7-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head 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 |
🩺 serve daemon A/BBuilt the PR base vs this PR head
|
| field | PR base (before) | this PR (after) |
|---|---|---|
features[] |
— | "session_transcript_pagination" |
— Qwen Code · serve A/B
Review — head
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: chunk 3 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 4 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 2 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 7 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 9 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 6 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 8 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 5 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 1 — launched with a prompt that is not the one the CLI built. Not reviewed: reverse audit — its prompt was built, but no agent was launched with it that opened its brief, so the reverse-audit pass did not run.
— qwen3.7-max via Qwen Code /review
|
Addressed the completed review and CI feedback together in
Verification after the final changes:
The focused affected suites were also independently reproduced and verified. |
|
Addressed the completed review and the Ubuntu CI failure together in
Verification:
The findings and CI failure were independently reproduced before the fix. |
|
Follow-up on the
The fixes are in |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. 2 Suggestion-level finding(s) could not be anchored to the diff; see the terminal output. Not reviewed: PR #7064 adds paginated history loading for restored Web ... — pointed at diff lines it never opened: it made tool calls, but none of them read the diff. Not reviewed: chunk 1 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 3 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 2 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 8 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 10 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 6 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 4 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 2 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 1 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 4 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 5 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 3 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 3 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 8 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 6 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 1 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 9 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 10 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 10 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 5 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 9 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 7 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 4 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 2 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 5 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 7 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 8 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 6 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 7 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 9 — launched with a prompt that is not the one the CLI built. Not reviewed: reverse audit — its prompt was built, but no agent was launched with it that opened its brief, so the reverse-audit pass did not run. Not reviewed: verification — its prompt was built, but no agent was launched with it that opened its brief, so the posted findings were not verified.
— qwen3.7-max via Qwen Code /review
Re-verification — head
|
| Previous finding | Status at fa98b2ed4 |
|---|---|
[medium] bound GET /session/:id/transcript → ACP readPage had no byte cap (one long turn ⇒ unbounded page) |
Fixed. SESSION_TRANSCRIPT_MAX_PAGE_BYTES (4 MiB) is now shared by both routes; ACP maps SessionTranscriptPageTooLargeError → -32012 → HTTP 413 transcript_page_too_large (verified live, see A/B below) |
[low] dead branch in readPage (backward && beforeRecordId === undefined) |
Removed |
| [low] rAF-based scroll-anchor restore could race the commit | Replaced with a useLayoutEffect state-anchored restore (synchronous with the prepend commit) |
[low] un-namespaced _meta.recordId |
Now qwen.session.recordId end-to-end (replay page, compaction engine, provider) |
The new commits also add: retryability classification in the provider (5xx/408/429 retryable; 4xx/partial-replay terminal), error-blocked older-history retry only on an explicit user gesture (wheel-up/touch/ArrowUp/PageUp/Home at top), and an underfill auto-load guard that stops empty-page loops — each covered by new unit tests.
What I ran (macOS, Node 22, npm ci builds)
1. Scoped suites at head — all green (1884 tests)
| Suite | Result |
|---|---|
core session-transcript-reader.test.ts |
44 ✅ |
acp-bridge bridge + compactionEngine |
458 ✅ |
cli acpAgent + history-replay-page |
261 ✅ |
cli serve/server.test.ts |
752 ✅ |
webui DaemonSessionProvider |
160 ✅ |
web-shell MessageList.dom + App + ChatPane |
209 ✅ |
2. Live daemon API E2E — 15/15. Built dist qwen serve over a seeded workspace: a 180-record / 60-turn session and a session whose first turn contains a single >4 MiB record. Verified: pagination capability advertised; backward pages always start at a complete user turn; contiguous tiling [0,150) with exact prefix reconstruction in 4 pages; bound (bridge→ACP) backward page turn-aligned with qwen.session.recordId metas; 413 on both routes for the oversized turn (pageBytes=4718936 maxBytes=4194304); cursor+beforeRecordId → 400.
3. Baseline A/B at merge-base 30984a2f5 — identical seeds, identical invocation. Baseline does not advertise the capability, replay events carry no record-boundary metas, beforeRecordId is silently ignored, and the bound route returns the >4 MiB record unbounded in one 4.7 MB response — exactly the failure mode the PR fixes; the PR daemon returns 413 for the same call.
4. Real-browser E2E against the PR daemon's Web Shell — 9/9 (Playwright/Chromium, real HTTP, no mocks).
Restore paints only the bounded recent page: turns 28–60 (99 records ≤ 100-record target), pinned to bottom; turn 1 is not in the DOM:
One scroll-to-top fires exactly one request — GET /session/…/transcript?beforeRecordId=u-028&limit=100 → 200 — and turns 1–27 are prepended with the scroll anchor preserved (viewport stays on turn 28, no jump):
Scrolling on up reaches turn 1; at history start no further transcript requests fire (no loop):
The >4 MiB-turn session fails safe: initial replay is byte-bounded and says so in-transcript ("History truncated: retained 4, dropped 2 (window 4194304 bytes)"), the older-page request gets 413 and the client stops with a visible notice — the huge text never enters the DOM and there is no retry loop:
Notes (non-blocking)
fa98b2ed4is a merge commit that also carries the empty-loop fix in its first-parent diff; the PR's net tree vs merge-base is clean (only the 40 pagination files), and a squash-merge makes the point moot.- An oversized complete turn is terminal for older history in the UI (
hasMore=false+ notice). That matches the PR description's documented trade-off, and the bounded initial page keeps the session itself usable.
Verdict: LGTM. All previous findings are fixed and re-verified end-to-end; CI is green at this head.
中文版本(Chinese version)
复验报告 — head fa98b2ed4(本地构建 + 真实 E2E)
继上次在 fe42aaf48 的评审(https://github.com/QwenLM/qwen-code/pull/7064#issuecomment-4998410110)之后,分支新增了 8c1c3114(加固)、bd6540c0(分页可重试)、fa98b2ed(停止空页自动加载循环,并合并了 main)。我在隔离 worktree 中分别构建了 PR head 和 merge-base 30984a2f5,并对两个真实 daemon 做了验证。
上次评审的 4 个发现全部已修复
| 上次发现 | 在 fa98b2ed4 的状态 |
|---|---|
[中] bound GET /session/:id/transcript → ACP readPage 无字节上限(一个超长 turn ⇒ 单页无界) |
已修复。 两条路由现共享 SESSION_TRANSCRIPT_MAX_PAGE_BYTES(4 MiB);ACP 将 SessionTranscriptPageTooLargeError 映射为 -32012 → HTTP 413 transcript_page_too_large(已实测,见 A/B) |
[低] readPage 中的死分支 |
已删除 |
| [低] rAF 滚动锚点恢复存在时序竞争 | 改为 useLayoutEffect 状态锚定(与前插提交同步) |
[低] _meta.recordId 未加命名空间 |
全链路改为 qwen.session.recordId |
新提交还增加了:provider 的可重试分类(5xx/408/429 可重试;4xx/部分回放为终态)、失败后仅在用户显式手势(顶部滚轮上滑/触摸/ArrowUp/PageUp/Home)时重试、以及防止空页自动加载死循环的守卫——均有新单测覆盖。
验证内容(macOS,Node 22,npm ci 构建)
1. head 上范围化测试套件——全绿(共 1884 个):core reader 44 ✅、acp-bridge 458 ✅、cli acpAgent+replay-page 261 ✅、serve server.test 752 ✅、webui provider 160 ✅、web-shell 客户端 209 ✅。
2. 真实 daemon API E2E——15/15。 用构建产物 qwen serve 加载播种工作区:一个 180 条记录/60 轮的会话,以及一个首轮含单条 >4 MiB 记录的会话。验证了:capability 已声明;向后分页每页都从完整用户轮开始;[0,150) 连续平铺、4 页精确重建前缀;bound(bridge→ACP)向后分页轮对齐且带 qwen.session.recordId;超限轮在两条路由均返回 413(pageBytes=4718936 maxBytes=4194304);cursor+beforeRecordId → 400。
3. merge-base 30984a2f5 基线 A/B——相同种子、相同启动参数。 基线不声明该 capability、回放事件无记录边界 meta、beforeRecordId 被静默忽略,且 bound 路由把 >4 MiB 记录以 4.7 MB 单页无界返回——正是本 PR 修复的问题;PR daemon 对同一调用返回 413。(见上方 API/A-B 证据图)
4. 真实浏览器 E2E(Playwright/Chromium,真实 HTTP,无 mock)——9/9。
- 恢复只渲染有界最近页:turn 28–60(99 条 ≤ 100 目标),钉在底部,turn 1 不在 DOM(截图 1);
- 滚动到顶只发出一次请求
?beforeRecordId=u-028&limit=100→ 200,turn 1–27 前插且滚动锚点保持(视口停留在 turn 28,不跳动)(截图 2); - 继续上滚可见 turn 1;到达历史起点后不再发请求(无循环)(截图 3);
-
4 MiB 轮会话失败安全:初始回放按字节预算截断并在记录内明示("History truncated: retained 4, dropped 2 (window 4194304 bytes)"),旧页请求得到 413,客户端停止并显示提示——巨型文本从未进入 DOM,也没有重试循环(截图 4)。
备注(不阻塞)
fa98b2ed4是一个在 first-parent diff 中携带修复内容的 merge commit;PR 相对 merge-base 的净 diff 干净(仅 40 个分页相关文件),squash 合并后无影响。- 超限完整轮在 UI 中对更早历史是终态(
hasMore=false+ 提示),与 PR 描述中声明的取舍一致;初始页有界,会话本身仍可用。
结论:LGTM。 上次全部发现已修复并经端到端复验;该 head 的 CI 全绿。
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
|
@qwen-code /resolve |
|
Qwen Code resolved the merge conflicts and pushed the branch update. Merge Conflict Resolution SummaryPR: #7064 — feat(web-shell): paginate restored session history Conflicts and ResolutionsThree files had content conflicts between the PR branch (history pagination feature) and 1.
|
9959ec0
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not reviewed: reverse audit — its prompt was built, but no agent was launched with it that opened its brief, so the reverse-audit pass did not run.
— qwen3.7-max via Qwen Code /review
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Not reviewed: chunk 9 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 9 — launched with a prompt that is not the one the CLI built.
— qwen3.7-max via Qwen Code /review
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. 2 Suggestion-level finding(s) could not be anchored to the diff; see the terminal output. Not reviewed: chunk 7 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 6 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 5 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 8 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 11 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 2 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 9 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 3 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 1 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 10 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 4 — launched with a prompt that is not the one the CLI built. Not reviewed: reverse audit — its prompt was built, but no agent was launched with it that opened its brief, so the reverse-audit pass did not run.
— qwen3.7-max via Qwen Code /review
| const retryable = | ||
| !terminalFailure && | ||
| (!(error instanceof DaemonHttpError) || | ||
| error.status >= 500 || | ||
| error.status === 408 || | ||
| error.status === 429); |
There was a problem hiding this comment.
[Suggestion] Missing test for non-retryable DaemonHttpError in error classification — Failure scenario: the daemon returns 404 because the session was deleted between the load and the transcript page request. The provider should set hasMore = false and stop paging. If someone changes error.status >= 500 to error.status >= 400, a 404 would be treated as retryable, causing the UI to repeatedly attempt loading history from a nonexistent session. Existing tests cover transient errors (generic Error) and terminal failures (partial page), but no test passes a DaemonHttpError with a 4xx status (e.g., 404).
Suggested fix: add a test where getSessionTranscriptPage rejects with a DaemonHttpError (status 404) and assert that history.hasMore becomes false and no further calls are made.
— qwen3.7-max via Qwen Code /review
| if (history.blocks.length + current.blocks.length > maxBlocks) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
[Suggestion] Capacity check uses original maxBlocks prop instead of the store's possibly-expanded capacity — Failure scenario: a restored session produces exactly 200 replay blocks with maxBlocks = 200. The replayExceededCapacity guard at line ~1040 does NOT fire (since 200 > 200 is false). The user sees "Load more" available. Clicking it loads a history page; prependTranscriptHistory checks 200 + N > 200 which is always true for N >= 1, and returns false. The UI flips to capacityReached = true after the first failed click — a button that appears available but always fails on first use.
| if (history.blocks.length + current.blocks.length > maxBlocks) { | |
| return false; | |
| } | |
| if (history.blocks.length + current.blocks.length > current.maxBlocks) { | |
| return false; | |
| } |
— qwen3.7-max via Qwen Code /review





What this PR does
This PR changes restored Web Shell sessions to paint a bounded recent history page first and fetch complete older user turns as the transcript scrolls to the top. Backward pages use stable persisted-record boundaries and opaque cursors, preserve tool execution sequences within a turn, maintain the scroll anchor while prepending content, and stop cleanly at the browser transcript capacity limit.
The daemon advertises the new contract through the
session_transcript_paginationcapability. Web Shell opts into pagination only when that feature is present, so older servers continue using their existing full-history load behavior.Why it's needed
Loading every persisted record before rendering makes long sessions slow and can produce oversized replay responses and browser transcript state. A bounded initial page improves restore latency and memory use while automatic top-loading keeps older history accessible without adding a manual control.
Capability negotiation is required because a newer Web Shell may connect to an older daemon that does not understand the pagination request or backward transcript contract.
Reviewer Test Plan
How to verify
session_transcript_paginationand confirm session load omits the page-size option and no older-page request is made.transcript_page_too_largeinstead of a partial turn.Evidence (Before & After)
Before: restored sessions replayed all persisted history before rendering, and Web Shell had no automatic path to retrieve history omitted by replay compaction.
After: restored sessions render the recent bounded page first, automatically prepend complete older turns at the top, and retain the legacy full-load path when the server does not advertise pagination.
Tested on
Environment (optional)
Node.js 22, local daemon bundle and Web Shell build.
Risk & Scope
Linked Issues
N/A
Pictures
中文说明
此 PR 的改动
此 PR 将 Web Shell 恢复会话改为优先渲染有上限的最近历史页,并在记录滚动到顶部时按完整用户轮次获取更早内容。向后分页使用稳定的持久化记录边界和不透明游标,保证同一轮中的工具执行序列不会被拆开,在前插内容时保持滚动锚点,并在达到浏览器记录容量上限时停止加载。
daemon 通过
session_transcript_paginationcapability 声明新协议。只有该 feature 存在时 Web Shell 才启用分页,因此旧 server 会继续使用原来的全量历史 load 行为。为什么需要
恢复时先加载全部持久化记录,会让长会话变慢,并可能产生过大的 replay 响应和浏览器 transcript 状态。限制初始页可以改善恢复延迟和内存占用,同时顶部自动加载让用户无需手动操作仍可访问更早历史。
能力协商是必要的,因为新版 Web Shell 可能连接到不理解分页请求或向后 transcript 协议的旧 daemon。
Reviewer 测试计划
如何验证
session_transcript_pagination的 capabilities,确认 session load 不发送页长参数,也不会请求更早页面。transcript_page_too_large,而不是返回部分轮次。前后对比证据
改动前:恢复会话会在渲染前 replay 全部持久化历史,Web Shell 也没有自动获取被 replay 压缩丢弃历史的路径。
改动后:恢复会话先渲染有上限的最近页,在顶部自动前插完整的更早轮次;当 server 未声明分页能力时,仍保留旧的全量 load 路径。
测试平台
环境(可选)
Node.js 22,本地 daemon bundle 和 Web Shell build。
风险与范围
关联 Issue
N/A