feat(vscode-ide-companion): adopt WebShell transcript as the default timeline - #9719
Conversation
…imental flag Bridge ACP session/update notifications into the shared SDK daemon transcript reducer and render the result with the WebShell transcript component, gated on qwen-code.experimental.webShellTranscript (default off). The WebShell renderer and its heavy transitive dependencies (echarts, mermaid, shiki, codemirror, katex) are lazily loaded via esbuild code splitting, so the default configuration keeps the ~700KB webview bundle unchanged.
|
Re-run at the current head, which has moved well past the flag-gated version the last pass reviewed. Template looks good ✓ Problem: Real and documented. This is the deliberately-narrow first stage of #9187 (open, triaged with an "accept for exploration" direction verdict), part of the WebShell convergence roadmap (#5883 — Desktop already converged in #8092). The companion maintaining a second transcript renderer that drifts from the maintained one is an observed, ongoing cost, not a hypothetical. Direction: Aligned. With Desktop shipped as a thin shell over WebShell, the VS Code webview was the last heterogeneous live host. Since the last triage pass the PR has taken the follow-up step the issue's roadmap points at: the Size: 17.0k lines changed sounds alarming but decomposes cleanly: ~10.9k is Approach: Still matches what the issue asked for — reuse the shared SDK daemon transcript reducer and Risk: No high-risk-path matches from the revert-history signal. The one unconditional change from the earlier pass (IIFE → ESM) remains unconditional — now by design, since the timeline is always WebShell. Moving on to code review. 🔍 中文说明按当前 head 重新运行——相比上次审查时的 flag 版本,本分支已大幅推进。 模板完整 ✓ 问题:真实且有据。这是 #9187(开放中,已按"接受探索"方向结论完成 triage)刻意收窄的第一阶段,属于 WebShell 收敛路线图(#5883,Desktop 已在 #8092 完成收敛)。companion 维护第二套与主线渐行渐远的 transcript 渲染器是持续发生的实际成本,不是假设性问题。 方向:对齐。Desktop 已作为 WebShell 的薄壳发布,VS Code webview 是最后一个自渲染的 live host。自上次 triage 以来,PR 已迈出 issue 路线图指向的下一步:移除 规模:17.0k 行改动听起来吓人,但拆解清晰:约 10.9k 是 方案:仍然符合 issue 的要求——薄适配器复用共享 SDK daemon transcript reducer 和 风险:回滚历史信号无高风险路径命中。上一轮的唯一无条件改动(IIFE → ESM)仍是无条件的——现在是设计使然,因为时间线永远是 WebShell。 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewRe-reviewed against an independent proposal (raw ACP notifications forwarded verbatim → shared SDK reducer → lazily rendered
The previous pass's one real gap is closed, thoroughly. Session switching no longer accumulates transcripts: No critical blockers found. The non-blocking observations, all disclosed in the PR body rather than discovered here: edit/rewind is deliberately gone (backend removed with it); sequenceDiagram
participant P1 as ACP connection
participant P2 as QwenAgentManager
participant P3 as SessionMessageHandler
participant P4 as WebViewProvider
participant P5 as useAcpTranscript hook
participant P6 as SDK transcript reducer
participant P7 as WebShellTranscript
P1->>P2: session update notification
P2->>P4: onTranscriptUpdate (verbatim)
P3->>P4: user prompt echo (text and images)
P4->>P5: transcriptUpdate message
P5->>P5: session guard - drop foreign session frames
P5->>P6: reduce via normalizeDaemonEvent
P6-->>P7: blocks render lazily
P3->>P5: boundary reset (switch, clear, load)
TestingCI evidence for the reviewed commit (fetched via API; PR code never executed here): everything that ran is green — What CI cannot settle here is the central claim — that the timeline actually renders inside a real VS Code webview. On that: @wenshao ran a deep local verification against exactly this head (34/34 scripted assertions, verdict non-blocking findings) and a host-level pass, and approved; the web-shell visuals job rendered this head against a mock daemon; and a sandboxed
中文说明代码审查重新对照独立方案审查(原始 ACP 通知原样转发 → 共享 SDK reducer → 懒加载的
上一轮的唯一真实缺口已被彻底关闭:会话切换不再累积 transcript——三个边界全部重置、按边界发布的会话 id 钉住守卫(加载失败回退时 未发现阻断性问题。非阻断观察点均已在 PR 正文披露:编辑/回退有意移除(后端一并删除); 测试审查提交的 CI 证据(经 API 获取,本地未执行任何 PR 代码):所有运行的检查全绿,零失败;78 个按路径过滤的矩阵任务跳过。CI 无法定论的是"时间线在真实 VS Code webview 中正确渲染"这一核心主张——对此:@wenshao 已在同一 head 上完成深度本地验证(34/34 断言通过,结论为非阻断发现)并在主机层验证后批准;web-shell visuals 任务已渲染该 head;沙箱 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — both blockers from the last pass are closed with evidence, review found no criticals, and a maintainer verified this exact head locally; approving with two disclosed parity follow-ups. Stepping back: the two things that held this at 3/5 last time are both settled. The session-switch transcript leak is not just patched but built out properly — three boundary resets, a session-id guard that survives load-failure fallbacks, stale My independent proposal and the implementation remain the same shape, and the load-bearing integration points hold up when checked against the SDK and web-shell sources rather than trusted from the diff. The code reads like someone thinking about blast radius: everything that could fail lazily has a recovery path (chunk-load error boundary with reload, image echo degrading to text-only, cached seeding only where no replay will follow), and the deletions — legacy renderer, edit/rewind backend — remove exactly the code the swap makes unreachable, nothing else. In six months this is a thank-them change: one maintained timeline instead of two drifting ones. Why not 5/5, stated plainly so the follow-ups don't get lost:
Approving, pinned to the reviewed commit below. 中文说明置信度:4/5 —— 上一轮的两个阻断点均已带证据关闭,审查未发现阻断性问题,且维护者已在本地验证该 head;批准,附两个已披露的对等性后续项。 整体来看:上次让它停在 3/5 的两件事都已解决。会话切换的 transcript 泄漏不只是打了补丁,而是被正确建设——三个边界重置、在加载失败回退下仍有效的会话 id 守卫、过期 我的独立方案与实现同形,关键集成点对照 SDK 与 web-shell 源码核验成立。代码能看出作者在考虑影响半径:所有可能惰性失败的地方都有恢复路径(chunk 加载错误边界 + 重载、图片回显降级为纯文本、仅在无重放跟随的路径回填缓存),删除的部分——旧渲染器、编辑/回退后端——恰好是本次替换使其不可达的代码,没有多余删除。六个月后这是值得感谢的改动:一条被维护的时间线,而不是两条渐行渐远的。 为什么不是 5/5,直说以免后续项被遗忘:
批准,锚定在下方被审查的提交。 — Qwen Code · qwen3.8-max Reviewed at |
Visual verification (browser harness)Verified the feature flag behaves as intended by loading the built Flag ON ( Flag OFF (default) — the legacy timeline is unchanged ( Note: the composer's border/background resolve from VS Code design tokens ( |
jifeng
left a comment
There was a problem hiding this comment.
Independent local validation — request changes
Validated exact head 1e9e2e449c72eb9628600889c42453e153699c60 in an isolated worktree on macOS arm64.
Positive evidence
- Adapter and WebView content tests: 9/9 passed.
- VS Code Companion typecheck: passed after building the shared SDK, WebUI and Web Shell packages.
- Production extension build and VSIX packaging: passed; the packaged artifact was 45.02 MB and contained the split WebShell chunks.
- A real isolated VS Code Extension Development Host activated the PR extension successfully (
qwenlm.qwen-code-vscode-ide-companion), registered the readonly file-system provider and started its IDE server. - GitHub Ubuntu test, Web Shell E2E smoke, desktop and security checks are green.
Merge blocker
The experimental transcript state never resets on qwenSessionSwitched (or another session boundary). The existing VS Code flow explicitly keeps the webview mounted, clears the legacy messages, then replays the selected session through ACP. useAcpTranscript ignores every message except transcriptUpdate, so the previous session's reducer state remains live.
I reproduced this on the unmodified exact head with a focused React/jsdom probe: emit user text alpha for session A, emit qwenSessionSwitched to session B, then emit beta for B. Expected output contained only beta; actual output was one user block with text alphabeta. This can expose one session's content inside another session and is therefore merge-blocking.
Recommendation: reset both stateRef and rendered blocks on the same qwenSessionSwitched/new-session boundary used by the legacy path, add a regression test that switches between two replayed sessions, and rerun the packaged Extension Development Host scenario.
The root all-package build initially failed because npm ci --ignore-scripts had not applied the repository's Ink patch; after applying the documented postinstall and building the PR's required shared packages, all PR-scoped checks above passed.
中文验证报告
独立本地验证——当前不建议合并
已在 macOS arm64 的隔离 worktree 中验证精确 head 1e9e2e449c72eb9628600889c42453e153699c60。
正向证据
- Adapter 与 WebView Content 测试:9/9 通过。
- 构建共享 SDK、WebUI、Web Shell 后,VS Code Companion 类型检查:通过。
- 生产扩展构建与 VSIX 打包:通过;打包产物 45.02 MB,并包含拆分后的 WebShell Chunks。
- 真实隔离的 VS Code Extension Development Host 成功激活本 PR 扩展(
qwenlm.qwen-code-vscode-ide-companion),注册只读文件系统 Provider 并启动 IDE Server。 - GitHub 的 Ubuntu 测试、Web Shell E2E Smoke、桌面端及安全检查均为绿色。
合并阻塞项
实验 Transcript 状态不会在 qwenSessionSwitched 或其他 Session 边界上重置。现有 VS Code 流程会保持 Webview 挂载,清空旧版消息,然后通过 ACP Replay 新选择的 Session;但 useAcpTranscript 会忽略 transcriptUpdate 之外的所有消息,因此前一个 Session 的 Reducer 状态仍然存在。
我在未修改的精确 head 上通过定向 React/jsdom Probe 复现:先为 Session A 发送用户文本 alpha,再发送切换到 Session B 的 qwenSessionSwitched,随后为 B 发送 beta。期望结果只包含 beta;实际结果是一个文本为 alphabeta 的用户 Block。这会把一个 Session 的内容显示在另一个 Session 中,属于合并阻塞问题。
建议在旧版路径使用的同一个 qwenSessionSwitched/新 Session 边界上同时重置 stateRef 和渲染 Blocks,增加两个 Replay Session 之间切换的回归测试,并重新运行打包后的 Extension Development Host 场景。
根目录全包构建最初因为 npm ci --ignore-scripts 未应用仓库 Ink Patch 而失败;执行项目约定的 postinstall 并构建本 PR 所需共享包后,上述 PR 范围验证全部通过。
Test report — before / afterThis PR is a flag switch ( Before (flag OFF, default) vs After (flag ON)
No ACP protocol change: CSP fix before / after (
|
| Before the fix | After the fix | |
|---|---|---|
| flag-on code highlight | Shiki WASM blocked by CSP → degrades to plain text (try/catch fallback, no white-screen) | wasm-unsafe-eval granted → WASM loads → code blocks highlight |
| flag-off CSP | script-src ${cspSource}; |
script-src ${cspSource}; — byte-for-byte identical, no widened permission for legacy users |
Evidence
acpTranscriptAdapter.test.ts— 2 tests:user_message_chunkwraps→reduces to a user text block; consecutive chunks merge into one block.WebViewContent.test.ts— 4 flag tests: attribute omitted when off;data-web-shell-transcript="true"when on;script-srcwithoutwasm-unsafe-evalwhen off; with it when on.- Full companion suite
npx vitest run— 55 files / 506 passed / 1 skipped (was 504 before the CSP fix). npm run check-typesandnpm run lint— pass.npm run generate:notices --workspace=qwen-code-vscode-ide-companion— NOTICES.txt updated (642 deps; web-shell's transitive heavy deps echarts/mermaid/shiki/katex/codemirror/react-markdown recorded).- Production bundle audit — entry 504KB;
dist/chunks/= 426 files / ~19MB, all lazy-reachable. - Browser harness — before (flag-off) renders the legacy empty state unchanged; after (flag-on) renders the WebShell transcript (screenshots in the previous comment).
Remaining gaps (out of scope, tracked separately)
- Artifact blob preview (
blob:in CSP) — N/A today:WebShellTranscriptis read-only, no artifact panel → VS Code companion WebShell transcript: artifact blob CSP #9727. - Local-control fetch (
connect-src) — N/A until the companion grows a settings surface. - Stable block identity / real
promptId— deferred v1 (ordinal identity) → VS Code companion WebShell transcript: stable block identity and promptId #9726. - Real VS Code runtime acceptance (scrolling, highlight, permission, session switch) → VS Code companion WebShell transcript: real VS Code runtime acceptance #9725.
Manual regression (real VS Code — the part a browser harness cannot cover)
- flag-off regression — full session with the flag off; legacy timeline must behave exactly as the "before" column (zero change).
- flag-on end-to-end — multi-step task + tool calls + thinking + a code block; no dropped/duplicated blocks, streaming appends incrementally (the "after" column).
- Composer → transcript — submit from the composer; a new user message appears in the transcript.
- Code-block highlight — emit a ```ts block; confirm it highlights with no CSP violation in the console (verifies the CSP fix).
- Artifact preview — trigger a PDF/image artifact; expected blocked (tracked in VS Code companion WebShell transcript: artifact blob CSP #9727).
- Permission request — trigger an edit permission under the flag; the legacy dialog still opens and accepts/rejects.
- Session switch — create/switch sessions under the flag; the transcript resets correctly.
- Empty-state branding — flag on + empty session: middle area blank (legacy
EmptyStatereplaced by the blank WebShell empty list). Known UX gap.
… WebShell transcript enabled
CSP gap #1 resolved (Shiki WASM)Fixed in
Full suite after the fix: Gaps #2 (artifact blob) and #3 (local-control fetch) remain out of scope — blob tracked in #9727; local-control is N/A until the companion grows a settings surface. |
…line Drop the experimental flag and the legacy MessageList renderer. The companion timeline now always renders through the shared WebShell transcript component, fed by ACP session/update notifications via the SDK daemon transcript reducer (lazy loaded through esbuild code splitting). The flag-gated wiring is removed: the qwen-code.experimental.webShellTranscript setting, the conditional CSP/body attribute in WebViewContent, and the legacy MessageList path in App.tsx (~850 lines). The webview CSP now grants wasm-unsafe-eval unconditionally for Shiki's Oniguruma WASM.
… switch The experimental useAcpTranscript hook only consumed transcriptUpdate messages, so its reducer state survived session boundaries. When the extension switched sessions it kept the webview mounted and replayed the newly-selected session through ACP, causing the previous session's blocks to merge with the new replay (e.g. user text "alpha" from session A leaked into session B as "alphabeta"). Reset both the reducer state and the rendered blocks on the same boundaries the legacy message flow uses: qwenSessionSwitched (sent before the ACP replay of the selected session) and conversationCleared (new session). Adds a regression test that replays two sessions with a switch between them.
doudouOUC
left a comment
There was a problem hiding this comment.
Review of PR #9719 — adopt WebShell transcript as the default timeline
Summary
This PR replaces the legacy companion timeline with the shared WebShell transcript renderer, bridged through ACP session/update notifications via the SDK daemon transcript reducer. The WebShell renderer and its heavy transitive dependencies are loaded lazily through esbuild code splitting.
Prior review: jifeng reviewed the initial commit and found a Critical session-switch state leak, which was fixed in commit 7fc35c1. The fix is verified by the useAcpTranscript.test.ts regression tests.
Verification: All 12 PR-scoped tests pass (3 test files, 12 tests). The session boundary reset, adapter wrapping, and CSP changes are correctly tested.
Findings
No Criticals or Suggestions. The changes are clean, well-structured, and the prior round's blocker has been addressed. The following observations are for awareness:
-
CSP hardening —
'wasm-unsafe-eval'is now unconditional in the webview CSP. This is required for Shiki's Oniguruma WASM engine used by the WebShell transcript. The trade-off is documented in the PR description. -
onTranscriptUpdateordering — The callback fires before the rehydration branch inqwenAgentManager.ts. This is intentional (documented in code) and harmless since the hook is idempotent to duplicate notifications. -
Test coverage — The old
getLastUserTurnIndexandMessageListtests were removed with the legacy component. The new tests cover the adapter (2 tests), the hook with session boundary reset (3 tests), and the CSP changes (2 tests). This is proportionate to the change scope.
Test Results
Test Files 3 passed (3)
Tests 12 passed (12)
Reviewed at commit 7fc35c16f286dc90311a898524841ebaf18cf2f5.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: reverse audit — an auditor ran and opened its brief, but no agent was launched with the prompt the CLI built — the launch was written by hand, and what the agent was actually asked is not what this skill certifies.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:反向审计——有审计 agent 运行并打开了自己的 brief,但没有 agent 是用 CLI 构建的 prompt 启动的——启动 prompt 是手写的,agent 实际被要求做的并不是本 skill 所认证的内容。
— qwen3.8-max via Qwen Code /review (v0.22.0)
- reset the transcript on `conversationLoaded` too, closing the same cross-session leak the previous commit fixed for `qwenSessionSwitched` and `conversationCleared` (agent reconnect posts only this boundary) - track the active session id and drop late `transcriptUpdate` frames whose `sessionId` no longer matches, so a previous session's trailing frames cannot contaminate the next session's timeline - seed the transcript from cached messages carried by `qwenSessionSwitched` so offline restores and load-failure fallbacks render their history instead of a blank timeline - dispatch `assistant.done` on `streamEnd`/`sessionLoadComplete` so the final assistant/thought block of a turn (or history replay) does not stay `streaming: true` forever
|
Closeout — round cap-4 of the R1 bot review, plus the R1-1 body fix, pushed to the fork as Body fix (orchestrator, no code):
Fixed in
Verification: Remaining open (21): R1-4/R1-5/R1-6/R1-7/R1-8/R1-10/R1-12 Criticals and R1-13..R1-22 Suggestions — next cap-4 rounds. Note: R1-2/R1-11 are rendering-visible; validated via the jsdom harness + mutation checks (no live VS Code webview harness on this box — bot headless probe remains the pre-merge spot check). |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- R2-9 (Onboarding/empty-state centering broken by the block-level replacement container at App.tsx:835) — dropped as an overlap: that line already carries round-1 comments (R1-5 comment 3836781122, R1-10 comment 3836781132); the finding rema…
[Critical] R1-4 (carried from round 1, still stands): Locally generated messages never render. Connection/auth errors, generic error messages, and the local "Interrupted" cancel marker are added via messageHandling.addMessage (making hasContent true), but the only rendered surface is now fed exclusively by ACP transcriptUpdate notifications, so these messages appear nowhere; a failed "New Conversation" is completely invisible.
[Critical] R1-5 (carried from round 1, still stands): The three contributed copy commands (qwen-code.copyMessage/copyAllMessages/copyLastReply) are now dead — the diff deleted the data-vscode-context attribute, the copyCommand handler, and the contextMenuTriggered emitter, while package.json still contributes the commands under webviewSection == 'chat-messages' and sendCopyCommand still posts into the void.
[Critical] R1-6 (carried from round 1, still stands): File-link clicks can no longer open files in the editor — handleFileClick (which posted openFile) and every onFileClick consumer were removed, and WebShellTranscript exposes no file-open prop, so FileMessageHandler.handleOpenFile is unreachable from transcript content.
[Critical] R1-7 (carried from round 1, still stands): The /insight feedback path is orphaned — WebViewProvider still parses insight notifications and posts insightProgress/insightReportReady, returning before the stream-text path, but the diff removed the setters so the handlers are optional-chained no-ops, and the openInsightReport handler is unreachable dead code.
[Critical] R1-8 (carried from round 1, still stands): The user-message edit/rewind feature is removed from the UI with no replacement, leaving dead switches — useMessageSubmit still declares and branches on editTargetTurnIndex/onSubmitted and SessionMessageHandler still implements the full rewind flow, but no code path can send editMessage.
[Critical] R1-10 (carried from round 1, still stands): The composer occludes the transcript tail — the deleted scroll container reserved pb-[140px] for the floating InputForm, but the replacement scroll area's bottom padding is only calc(8px + var(--web-shell-bottom-panel-inset, 0px)) and nothing in this package sets --web-shell-bottom-panel-inset.
[Critical] R1-12 (carried from round 1, still stands): WebShellTranscript hardcodes isResponding={false} (and pendingApproval={null}) into web-shell's MessageList, and no prop lets the companion report that a turn is in flight, so the in-progress turn is treated as completed and auto-collapses mid-response.
中文说明
本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
[Critical] R1-4 (carried from round 1, still stands): Locally generated messages never render. Connection/auth errors, generic error messages, and the local "Interrupted" cancel marker are added via messageHandling.addMessage (making hasContent true), but the only rendered surface is now fed exclusively by ACP transcriptUpdate notifications, so these messages appear nowhere; a failed "New Conversation" is completely invisible.
[Critical] R1-5 (carried from round 1, still stands): The three contributed copy commands (qwen-code.copyMessage/copyAllMessages/copyLastReply) are now dead — the diff deleted the data-vscode-context attribute, the copyCommand handler, and the contextMenuTriggered emitter, while package.json still contributes the commands under webviewSection == 'chat-messages' and sendCopyCommand still posts into the void.
[Critical] R1-6 (carried from round 1, still stands): File-link clicks can no longer open files in the editor — handleFileClick (which posted openFile) and every onFileClick consumer were removed, and WebShellTranscript exposes no file-open prop, so FileMessageHandler.handleOpenFile is unreachable from transcript content.
[Critical] R1-7 (carried from round 1, still stands): The /insight feedback path is orphaned — WebViewProvider still parses insight notifications and posts insightProgress/insightReportReady, returning before the stream-text path, but the diff removed the setters so the handlers are optional-chained no-ops, and the openInsightReport handler is unreachable dead code.
[Critical] R1-8 (carried from round 1, still stands): The user-message edit/rewind feature is removed from the UI with no replacement, leaving dead switches — useMessageSubmit still declares and branches on editTargetTurnIndex/onSubmitted and SessionMessageHandler still implements the full rewind flow, but no code path can send editMessage.
[Critical] R1-10 (carried from round 1, still stands): The composer occludes the transcript tail — the deleted scroll container reserved pb-[140px] for the floating InputForm, but the replacement scroll area's bottom padding is only calc(8px + var(--web-shell-bottom-panel-inset, 0px)) and nothing in this package sets --web-shell-bottom-panel-inset.
[Critical] R1-12 (carried from round 1, still stands): WebShellTranscript hardcodes isResponding={false} (and pendingApproval={null}) into web-shell's MessageList, and no prop lets the companion report that a turn is in flight, so the in-progress turn is treated as completed and auto-collapses mid-response.
— qwen3.8-max via Qwen Code /review (v0.22.0)
|
Closeout — cap-4 round (4 of 9 Criticals fixed, push
Verification: 64/64 across the 5 targeted suites (App, SessionMessageHandler, useAcpTranscript, neighboring useWebViewMessages), Disclosures: R1-12 + R1-10 share one commit (same JSX element); the producer-side payload assertion for the first fix landed with the second commit's test file due to the ≤3-file-per-finding ceiling (consumer half fully covered in its own commit). Process note: the first reply pass posted the R1-12/R1-10 evidence into the R1-6/R1-5 threads (same file+line collision); the strays were deleted and the evidence re-posted into the correct threads before this comment — the resolved states now carry the right replies. Remaining 5 Criticals (R1-4 local messages, R1-5 copy commands, R1-6 file links, R1-7 /insight, R1-8 edit/rewind) stay unresolved for the next round. |
chiga0
left a comment
There was a problem hiding this comment.
No new blockers found at this head.
Scope: vscode-ide-companion source only (NOTICES.txt excluded). Read SessionMessageHandler.ts, useAcpTranscript.ts, acpTranscriptAdapter.ts, App.tsx (key sections), WebViewContent.ts, WebViewProvider.ts, copyTranscript.ts, fileLinks.ts, chatTypes.ts, qwenAgentManager.ts (transcript dispatch path). Static cross-file trace; no execution (no local toolchain).
CI at time of review: Desktop Shell (ubuntu + windows) ✅ · Test (ubuntu-latest, Node 22.x) ⏳ in-progress · Test (macos/windows) and Integration Tests ⏸ SKIPPED. The Test job was still running; macOS and Windows test jobs are skipped by branch/path filter. I am reviewing at in-progress test state.
What I checked and found clean
Session boundary guards (useAcpTranscript.ts)
- All three boundary message types (
qwenSessionSwitched,conversationCleared,conversationLoaded) triggerresetTranscript(). activeSessionIdRefis pinned on the boundary; late frames from the abandoned session are dropped.liveSessionIdwins oversessionIdfor load-failure fallback paths (so the fresh ACP session's live frames are not dropped).
Turn finalization
finishTurncalled onstreamEnd(withrequestIdcorrelation viaactiveRequestIdRef) and onsessionLoadComplete.- Foreign
streamEndevents (mismatchedrequestId) are dropped; backgroundsource='background_notification'events are also correctly gated.
User message echo
transcriptEchoSessionIdguard sends the user's own turn astranscriptUpdateso it renders in the WebShell timeline (the companion's stdio ACP channel does not receive auser_message_chunkfor interactive prompts).- Image attachments are echoed via per-image
transcriptUpdateframes.
Local-only message rendering
- All
type:'message'sends inSessionMessageHandler.tscarrylocalOnly: true. App.tsxfilters them intolocalNoticesrendered above the composer; they never go toWebShellTranscript.
File-link clicks
handleTranscriptClickat the container level intercepts anchor clicks and callsresolveFileLinkFromAnchor.normalizeExplicitFileLinkhandlesfile://URIs specially (percent-decode then strip scheme; no fragment split on#since that would be a literal path character). Non-file://paths split on#correctly.
Chunk-load recovery
TranscriptErrorBoundarycatches the rejectedimport()that Suspense re-throws (stale content-hashed chunk after an extension auto-update) and offers a reload button. This is the right failure mode.
CSP widening
wasm-unsafe-evalinWebViewContent.tsis required for Shiki/Oniguruma WASM and is documented in the PR description. Acceptable tradeoff.
Copy-all and copy-last-reply
formatBlocksForCopyAllandfindLastAssistantTextincopyTranscript.tshandle the block kinds the WebShell reducer produces.
MIT_FALLBACK_TEXT
- Previously flagged typo ("THE USE OF OTHER DEALINGS") confirmed fixed — reads "THE USE OR OTHER DEALINGS IN THE SOFTWARE" at this head.
Outstanding items (not new findings — pre-existing open threads)
R1 round outstanding: jifeng has a CHANGES_REQUESTED review still open (inline comment on useAcpTranscript.ts:173 — body referenced a local temp file I could not read). Worth confirming that concern was addressed before merging.
Cross-check — cannot confirm or refute: A prior reviewer (round 2) claimed web-shell's MessageList keys tool-group rows as msg:tg-<blockId>, which would make findBlockByRowKey return null for every "Copy Message" on a tool row. findBlockByRowKey currently matches on msg:<blockId> and msg:<blockId>-<suffix> but not msg:tg-<blockId>. I cannot verify web-shell's internal key format here; whoever has access to @qwen-code/web-shell source should confirm the key shape used for tool blocks.
Reviewed with AI assistance.
…ecoding normalizeExplicitFileLink decoded the whole value before splitting on #, so an encoded %23 in a filename was treated as a fragment delimiter and truncated the path. Split on the raw # first and decode the path and fragment parts separately; the file:// branch decodes only the path component. resolveFileLinkFromAnchor also no longer runs URL decoding/fragment logic over the anchor-text fallback: the text is a literal path, which keeps the /export 'export (#1).html' links (whose file: href the sanitizer strips) clickable.
…script messages Cached-history seeding emitted each row as a bare *_message_chunk with no promptId/sourceRecordIds/_meta, so the shared reducer merged runs of consecutive same-role cached rows (Tool Result / telemetry / Plan rows per turn) into one plain-concatenated block, and a dropped whitespace-only user row let different turns fuse. Stamp every synthesized cached row with the reducer's existing anti-merge marker (_meta.qwenDiscreteMessage) so offline restores render the same discrete blocks as live replays.
… content parts Copy Message silently failed on every tool row: web-shell keys tool_group rows as msg:tg-<block id>, which findBlockByRowKey never matched. Strip the tg- prefix before the existing exact/longest-prefix matching; merged groups share the first block's key, so a group row resolves to the group's first tool block (documented). The tool case of getBlockCopyText also serialized only title + details (the input summary), dropping the output text and diffs the timeline renders; walk block.content and append text parts and ---/+++ diff renderings like the pre-PR formatToolCallForCopy did, restoring Copy Message / Copy All parity for tool rows.
|
Cap-4 round on the Aug-23 18:25Z Criticals, all verified real at head
Verification at pushed head: copyTranscript 19, App 24, fileLinks 16, acpTranscriptAdapter 8 (67/67); Untouched this round: R1-20 (escalated KaTeX-fonts design call) + the remaining Suggestions (queued for a future cap round). |
|
Closeout update for Fixed in the final two additive commits:
Local verification:
There are no unresolved Critical threads at this head. The remaining Suggestions are intentionally deferred after the review-round cap: notices-generator wording/test probes; local-notice lifecycle/copy behavior and dead rewind/waiting-contract cleanup (better handled by the full VS Code WebShell cutover / WebUI retirement follow-ups); and test-only mutation cases with no demonstrated production defect. Final GitHub CI, security, visual capture, and automatic review are running on this head. |
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head Screenshots · before / afterFull-resolution recordings (.webm) are attached to the workflow run. — Qwen Code · web-shell visuals |
|
Visual workflow attribution for run 32689856799: the missing preview is caused by the same pre-existing harness failure on both PR head and merge-base. In dark and light |
Local verification report for PR #9719I checked out What passed
Build artifacts
Note on
|
| Before (legacy MessageList) | After (WebShell transcript) |
|---|---|
![]() |
![]() |
Limitations
VS Code desktop is not installed in this verification environment, so I could not activate the packaged .vsix in a real Extension Development Host. The runtime evidence above is limited to:
- successful build/packaging,
- passing unit tests,
- clean bundle parse, and
- the shared Web Shell renderer loading without errors in a headless browser.
A maintainer who has VS Code locally should still do a final smoke test by installing the .vsix and starting a conversation to confirm the timeline renders and session switching does not bleed content.
Conclusion
From a build, type, lint, and unit-test perspective the PR is green. The bundle shape matches the PR description (small initial webview bundle + large lazy WebShell chunk), and the latest review at dedb0b87b8df found no new blockers. I would be comfortable merging once a VS Code runtime smoke test confirms the visual behavior shown in the screenshots above.
📝 中文版验证报告(点击展开)
PR #9719 本地验证报告
我在 macOS arm64(Node v22.22.2)上检出 feat/9187-vscode-webshell-transcript 分支的 82912c5095 提交,完整构建了工作区以及 VS Code companion 扩展。结果如下。
通过的验证项
| 步骤 | 命令 | 结果 |
|---|---|---|
| 工作区安装与构建 | npm install(会触发 prepare → 完整 npm run build) |
✅ 通过 |
| VS Code companion 生产构建 | cd packages/vscode-ide-companion && npm run build:prod |
✅ 通过 |
| VS Code companion 打包 | npm run package(生成 .vsix) |
✅ 通过 |
| VS Code companion 类型检查 | npm run check-types |
✅ 通过(见下方说明) |
| VS Code companion 代码检查 | npm run lint |
✅ 通过 |
| VS Code companion 单元测试 | npm test |
✅ 606 通过,1 跳过 |
| Web Shell 单元测试 | cd packages/web-shell && npm test |
✅ 4191 通过 |
| Webview 包语法检查 | node --check dist/webview.js |
✅ 通过 |
| 无头浏览器冒烟测试 | Playwright 加载构建后的 Web Shell 应用,无页面错误 | ✅ 通过 |
构建产物
packages/vscode-ide-companion/dist/webview.js— 500 KB(初始 webview bundle,与 PR 前基线一致)packages/vscode-ide-companion/dist/chunks/— 61 MB 懒加载 chunk(WebShell 渲染器、Shiki、Mermaid、KaTeX、echarts 等)packages/vscode-ide-companion/qwen-code-vscode-ide-companion-0.22.0.vsix— 61.32 MBdist/webview.js包含React.lazy和WebShell引用,确认渲染器仍为代码分割懒加载。- webview CSP 已按预期包含
'wasm-unsafe-eval',以支持 Shiki 的 Oniguruma WASM。
关于 check-types 的说明
npm run check-types 仅在 npm run package 将 bundled CLI 复制到 dist/qwen-cli/ 后失败。复制的 CLI 产物包含在扩展包内部无法解析的相对导入。删除 dist/qwen-cli/ 后 check-types 恢复通过。这是预先存在的打包产物问题,不是 PR 源码改动导致的。
截图
Web Shell 应用从生产构建正常加载(无头 Chromium,未连接 daemon):
PR 作者提供的 VS Code companion 时间线 Before & After:
| Before(旧 MessageList) | After(WebShell transcript) |
|---|---|
![]() |
![]() |
局限性
本次验证环境未安装 VS Code 桌面版,因此无法在真实 Extension Development Host 中激活打包后的 .vsix。运行时证据限于:
- 成功构建/打包,
- 单元测试通过,
- bundle 语法检查通过,
- 共享 Web Shell 渲染器在无头浏览器中无错误加载。
建议本地有 VS Code 的维护者再做一个最终冒烟测试:安装 .vsix 并开始一段对话,确认时间线正常渲染且切换会话不会串内容。
结论
从构建、类型、代码规范和单元测试角度看,该 PR 为绿色。bundle 形态与 PR 描述一致(较小的初始 webview bundle + 较大的懒加载 WebShell chunk),且最新一次对 dedb0b87b8df 的 review 未发现新的阻塞项。建议在完成 VS Code 运行时冒烟测试、确认上图视觉行为后即可合并。
Deep verification report — local maintainer round (2026-08-24)Verdict: Central claim — A/BThe companion timeline renders through the WebShell transcript fed by raw ACP
Mutation matrix on the author's own test file: boundary-reset removal kills 6/20, strict seeding-guard forcing kills 1/20 (the author's ceuN pin holds), timeout-mapping removal kills exactly the 2 timeout tests (positive control). The graceful guard-true variant survives and is adjudicated behavior-preserving (empty body never runs), not a coverage gap. Findings (both non-blocking)
No Critical or blocking finding. Session-boundary isolation — the bug class this PR's hardest commits closed — was re-proven live in this round. GatesHead companion suite 606 passed / 0 failed (base 501/0 — no pre-existing failures; delta +4 files / +105 tests, all green) · EvidenceNot coveredReal VS Code window rendering (the WebShell component itself is prop-asserted, not pixel-rendered, here), live ACP agent traffic (frames scripted per the daemon reducer's notification shape), Windows/Linux, 中文摘要结论: A/B 结论:mock-free harness 驱动真实 发现(均非阻塞):① 懒加载负载实测 49.4MB / 852 个 chunk(WebShell 核心 chunk 约 14.2MB,时间线挂载时拉取),PR 描述写 "+ ~4MB"、 门禁:head 套件 606/0(base 501/0,无预存失败)、tsc 0、eslint 0(活性已证明)、web-shell build-artifact 13/13、NOTICES.txt 再生成字节级一致。 未覆盖:真实 VS Code 窗口渲染、真实 ACP 流量、Windows/Linux、.vsix 打包、冷挂载延迟。测试为 props 级证明,非像素级渲染证明。 |
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 4872 passed · 0 failed · 4872 total Flakiness gate: ✅ 16 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:4872 通过 · 0 失败 · 4872 总计 抖动门:✅ 16 changed test file(s) x 5 identical rounds, no divergence Verification reportPR 9719 — Deep VerificationVerdict: 中文摘要
Scope
Per-commit attribution was out of reach (shallow checkout: 37 commits in the snapshot, 1 reachable locally); the aggregate A/B: session-leak behavior (central claim)Oracle: the PR's own regression scenario — session A user text "alpha",
The base arm has no hook at all (feature absent), so the meaningful control for this claim is the hunk revert at head; the base arm is exercised in the bundle A/B below. A/B: bundle / CSP (secondary 1)Both arms built with
Load-bearing property verified: the entry did not grow; the heavy payload is behind a dynamic Bridge harness (mock-free, real reducer)
Mutation matrix (vacuity of the new tests)Baseline green (20/20 hook tests) before mutation. Each mutant = one point-revert, applied in place, target test file(s) run, restored; witness
Single-test kills (M3, M6) are the positive controls proving harness resolution; no survivors, so no combination rows were needed. Targeted gatesWitness
Corrections (description vs measured)The PR body's Evidence table and one code comment describe the bundle change with numbers that do not reproduce at this merge commit; the mechanism itself is verified, only the figures are off:
Findings (non-blocking)
Not covered
MethodologyEnvironment: Flakiness gate logEvidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
chiga0
left a comment
There was a problem hiding this comment.
Re-review at head 82912c509521 — 5 new commits since dedb0b87b8df.
Prior ledger
| R1 item | Status at this head |
|---|---|
Outstanding: findBlockByRowKey did not strip tg- prefix from web-shell tool-group row keys |
Fixed — commit 19263295f2d5 strips tg- before matching |
New commits reviewed
d1ad62bd36ca— fix: split file links on raw#before percent-decodingc830bc6c7a38— fix: stamp cached history rows as discrete transcript messages19263295f2d5— fix: resolve tool-group copy rows and copy tool content parts01cc92ea8117— fix: close remaining transcript regressions82912c509521— fix: hide internal image references
What I checked and found clean
fileLinks.ts — # splitting fix: normalizeExplicitFileLink now splits on the raw (unencoded) # before calling safeDecodePath, so %23 in a filename is never mistaken for a fragment delimiter. resolveFileLinkFromAnchor now bypasses URL decoding for anchor text (which is a literal filesystem path, not a URL). Both halves of the fix are correct.
copyTranscript.ts — tool-group copy: findBlockByRowKey strips the tg- prefix that web-shell stamps on tool-group row keys. getToolContentCopyParts walks block.content entries to copy tool output text and ---/+++ diffs, matching pre-PR behaviour. error and debug block kinds are included in formatBlocksForCopyAll. All three address Round 3 [Critical] and [Suggestion] findings.
acpTranscriptAdapter.ts — discrete-message marker: Cached history rows now carry _meta: { qwenDiscreteMessage: true }, preventing the shared reducer from folding consecutive same-role rows into one concatenated block. Empty-after-strip user messages (pure @/path image references) are dropped via the new text.trim().length === 0 guard; safe because the existing !message.content guard above ensures message.content is truthy on entry.
imageSupport.ts / useImage.ts — function move: splitMessageContentForImages and its helpers relocated from useImage.ts to imageSupport.ts. Implementations are byte-for-byte equivalent. Both new callers (acpTranscriptAdapter.ts, SessionMessageHandler.ts) import from the canonical location.
SessionMessageHandler.ts — oversized image guard: toBase64 now calls fsp.stat before fsp.readFile and returns null for images exceeding MAX_IMAGE_SIZE, matching the existing pasted-attachment cap. Test added and pinned to the skip path.
WebViewContent.ts / vite.lib.config.ts / WebShellTranscript.tsx: font-src data: added to the webview CSP for KaTeX inline fonts. katex/dist/katex.min.css removed from the external list so it is bundled into the web-shell artifact. Build artifact test verifies @font-face contains data:font/woff2;base64, inline.
Cross-check against prior reviews
| Finding | Reviewer | Status |
|---|---|---|
Session state not reset on qwenSessionSwitched |
jifeng — CHANGES_REQUESTED (id 5000171614) | Fixed in 7fc35c16f286; confirmed clean at R1. Review not dismissed but blocker no longer present at this head. |
cachedMessageToNotification emits bare chunks with no qwenDiscreteMessage |
qwen-code-ci-bot R3 [Critical] | Fixed in c830bc6c7a38 (this round) |
Stored @/path image refs visible in cached transcript |
qwen-code-ci-bot R3 [Suggestion] | Fixed in 82912c509521 (this round) |
Copy Message silently fails on every tool-group row (msg:tg-<blockId>) |
qwen-code-ci-bot R3 [Critical] | Fixed in 19263295f2d5 (this round) |
Copy All drops error/debug blocks |
qwen-code-ci-bot R3 [Suggestion] | Fixed in 19263295f2d5 (this round) |
fileLinks.ts decodes before # split |
qwen-code-ci-bot R3 [Critical] | Fixed in d1ad62bd36ca (this round) |
stale streamEnd finalizes wrong session / conversationCleared blind-adopt |
qwen-code-ci-bot R3 [Critical] | Fixed in 5eea55b75da2/9e261235c704 (before R1); confirmed clean at R1 |
| qwen-code-ci-bot latest verdict | qwen-code-ci-bot | APPROVED at 2026-08-24T07:22Z (id 5005390576) |
CI at time of review
| Check | Result |
|---|---|
| Test (ubuntu-latest, Node 22.x) | ✅ PASS (25m 30s) |
| Desktop Shell (ubuntu / windows) | ✅ PASS |
| web-shell E2E Smoke | ✅ PASS |
| Test (macos / windows) | ⏸ SKIPPED — branch/path filter (same as R1) |
| Integration Tests | ⏸ SKIPPED — branch/path filter (same as R1) |
Scope: vscode-ide-companion and web-shell source only. NOTICES.txt excluded. Static read + cross-file trace. No local toolchain available — execution rungs 1–3 not run.
Reviewed with AI assistance.
Runtime validation report — PR #9719Verified head Verdict: the timeline swap works and its guards are real. Three parity regressions against the legacy renderer are worth resolving before merge; two are on the cached-history restore path, which the Reviewer Test Plan exercises (step 3) but the current test suite locks in as intended. Findings
1 — Restored history loses images, and image-only turns disappear (High)Same cached
The legacy path is intact and still reachable: the webview still posts
Live sends are not affected — an attached image echoes correctly as an inline 2 — Consecutive cached assistant rows are concatenated with no separator (Medium)The That concatenation is correct for the web-shell's own streaming deltas; it is wrong for the discrete rows the companion now seeds. Since the companion persists tool-result / telemetry / plan rows as separate 3 — First-paint payload and package size (Medium)Measured from the actual network log of a real turn, plus the built artifacts:
Of that, the 4 — Local notices are excluded from Copy all messages (Low)An
Related: the new 5 — Dead code the PR body says was removed (Nit)
What was verified workingBehaviour matrix — 15 scenarios, both arms, real Chromium (click to expand)
Mutation teethEach PR guard was reverted in source, the webview rebuilt, and the same scenario re-driven:
Build & tests
Harness detailBoth arms served over HTTPS from an origin that plays the role of One methodology note worth recording: an early run showed the whole webview crashing with 中文版报告PR #9719 运行时验证报告在真实浏览器中用真实生产构建产物验证了 head 结论:时间线替换本身可用,其防护逻辑经得起变异测试。 有三处相对旧渲染器的行为回退建议合并前处理,其中两处位于缓存历史恢复路径上——正是 Reviewer Test Plan 第 3 步覆盖的场景,但当前测试用例把它固化成了“预期行为”。 问题清单
问题 1(高):把同一份缓存 旧链路仍然完好可用:webview 在恢复时依然会发出
实时发送不受影响:附带的图片会以内联 问题 2(中): 问题 3(中):真实网络日志与构建产物实测——入口 问题 4(低): 问题 5(提示): 已验证正常的行为时间线渲染(6 个离散行、32 个 Shiki 高亮 token、markdown 表格、2 个代码块、0 控制台错误、0 失败请求); 变异测试逐个把 PR 的防护改回,重新构建 webview 并重跑同一场景:移除 构建与测试
方法学备注:早期有一次跑出“任何含图片引用的缓存历史都会让 webview 整体崩溃( |
…o main Resolves conflicts between PR QwenLM#9641 (water-in-stone: WebShell transcript identity, VS Code adapter, and HTML export) and main (QwenLM#9719 already merged). Strategy: main (QwenLM#9719) is authoritative for the adapter plumbing; preserve QwenLM#9641's identity projection and HTML export (renderMode/documentMode) while dropping the duplicate adapter implementation. Also fixes merge remnants: orphaned data-web-shell-transcript flag, duplicate onTranscriptUpdate declaration, stale useAcpTranscript.test.tsx rename, and duplicate @qwen-code/web-shell dependency.
|
Released in v0.22.2. |























What this PR does
Adopts the shared WebShell transcript renderer as the conversation timeline in the VS Code companion. Raw ACP session/update notifications are bridged through the existing shared SDK daemon transcript reducer and rendered by the WebShell transcript component, so the companion timeline gains the same block model (text, tool calls, plans, permissions) the WebShell uses.
Follow-up commits in this branch drop the original
qwen-code.experimental.webShellTranscriptopt-in setting and remove the legacy companion MessageList renderer: the WebShell transcript is now the default and only timeline.The WebShell renderer and its heavy transitive dependencies (charting, diagramming, syntax highlighting, math, markdown) are still loaded lazily via code splitting (
React.lazy), so the WebShell chunk is fetched when the timeline mounts rather than with the initial webview bundle.Why it's needed
The companion previously maintained its own transcript timeline, separate from the WebShell's more complete renderer. This closes that gap (#9187) by reusing the existing shared renderer and reducer rather than duplicating the timeline logic again.
Reviewer Test Plan
How to verify
Evidence (Before & After)
script-src ${cspSource};(no wasm-unsafe-eval)script-src ${cspSource} 'wasm-unsafe-eval';(WebShell WASM)Screenshots: WebShell transcript timeline · legacy timeline (pre-PR)
Unit tests cover the notification-to-blocks reduction (including consecutive-chunk merging) and session-boundary state resets; the companion suite, typecheck, lint, and production bundle build pass.
Tested on
Environment (optional)
node esbuild.js --production,npm run check-types,npm run lint,npx vitest run.Risk & Scope
wasm-unsafe-eval.editMessage) was not carried over to the WebShell transcript timeline; with no edit entry point in the timeline, its now-unreachable backend (the handler rewind flow, theeditTargetTurnIndex/onSubmittedsubmit options, and their tests) is removed in this PR instead of being kept as dead code.Linked Issues
Reuse WebShell transcript UI in the VS Code companion — #9187.
Note: PR #9641 independently contains an adapter file that overlaps in intent with this change. This PR is a thin, standalone implementation scoped to #9187 and does not include the other changes bundled there.
中文说明
本 PR 做了什么
在 VS Code companion 中采用共享的 WebShell transcript 渲染器作为对话时间线。原始 ACP session/update 通知被桥接进现有的共享 SDK daemon transcript reducer,并由 WebShell transcript 组件渲染,使 companion 时间线获得与 WebShell 相同的块模型(文本、工具调用、计划、权限)。
本分支的后续提交移除了最初的
qwen-code.experimental.webShellTranscript实验性开关,并删除了旧的 companion MessageList 渲染器:WebShell transcript 现在是默认且唯一的时间线。WebShell 渲染器及其重型传递依赖(图表、流程图、语法高亮、数学、markdown)仍通过代码分割(
React.lazy)懒加载——WebShell chunk 在时间线挂载时才拉取,而不是随初始 webview bundle 一起加载。为什么需要
companion 此前维护着一条独立的 transcript 时间线,与 WebShell 更完整的渲染器相互分离。本改动通过复用现有的共享渲染器和 reducer 来弥合这一差距(#9187),而不是再次重复实现时间线逻辑。
Reviewer 测试计划
如何验证
证据(Before & After)
script-src ${cspSource};(无 wasm-unsafe-eval)script-src ${cspSource} 'wasm-unsafe-eval';(WebShell WASM)截图:WebShell transcript 时间线 · 旧时间线(PR 前)
单元测试覆盖了通知到块的归约(包括连续 chunk 合并)与会话边界状态重置;companion 测试套件、typecheck、lint 和生产 bundle 构建均通过。
测试环境
环境(可选)
node esbuild.js --production、npm run check-types、npm run lint、npx vitest run。风险与范围
wasm-unsafe-eval。editMessage)未随 WebShell transcript 时间线保留;时间线中没有编辑入口,其已不可达的后端(handler 中的 rewind 流程、editTargetTurnIndex/onSubmitted提交选项及其测试)在本 PR 中删除,而不是作为死代码保留。关联 Issue
在 VS Code companion 中复用 WebShell transcript UI —— #9187。
说明:PR #9641 独立地包含了一个与本改动意图重叠的 adapter 文件。本 PR 是一个仅针对 #9187 的薄、独立实现,不包含该 PR 中捆绑的其他改动。