feat(cli): Ctrl+click hyperlinks and right-click context menu in VP mode - #8439
feat(cli): Ctrl+click hyperlinks and right-click context menu in VP mode#8439chiga0 wants to merge 13 commits into
Conversation
E2E test reportVerified end-to-end via tmux (3.6a) + a mock OpenAI server that always replies with a markdown link, a Baseline (global qwen 0.21.3, pre-fix): identical injections produced zero reaction — Ctrl+click never opened the browser (log never created), right-click left the pane byte-identical (no menu). Wheel scroll worked (VP owns the wheel). Post-fix (this branch): 14/14 checks passed:
Rebuilt-bundle re-verification after review hardening: 6/6 focused checks byte-identical in menu geometry and behavior. Unit coverage: 43 new module tests (URL extraction edge cases incl. BEL/ST/C1-ST terminators, OSC 8 params, tmux doubled-ESC form, wide-char snap; menu state machine; controller gestures incl. the crafted- Tested on macOS; Windows/Linux not run interactively (platform dispatch comes from the existing secure launcher). |
|
Thanks for the PR! Template looks good ✓ Problem: observed regression, not theory. VP mode (default since 0.21.1) enables SGR mouse tracking, so the terminal hands every mouse event to the app — native link clicks and the right-click menu are silently lost for every default-config user. The description carries a before/after baseline (both gestures inert on global qwen 0.21.3), and this is the acknowledged follow-up to #8198 / #8217, which mitigated the same regression behind the Direction: aligned. This completes an initiative already in the CHANGELOG (#8198, #8217) — the escape hatch costs wheel scrolling, hover, and drag-select, so restoring both affordances in-app under default settings is the right next step. No auth / sandbox / public-contract surface is touched. Size: ~794 production-logic lines (+782 test, +439 docs, +4 schema mirror). Only 4 production lines touch a core path ( Approach: the scope feels right. Both affordances map one-to-one to the two lost capabilities and share their infrastructure (frame-cell OSC 8 hit-testing, the selection stack's coordinate path, one gesture controller). Reading the URL from the composited frame cell instead of re-parsing markdown is exactly the path that avoids the prior attempt's failure modes — command injection, untransformed coordinates, and CJK/wrap column drift. Every edit earns its place (wiring, consumer gating, settings text, docs, design doc); no drive-by changes spotted. Risk: no elevated risk signals — none of the changed files match the revert-correlated paths. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:这是已观测到的回归,不是理论问题。VP 模式(0.21.1 起默认开启)启用了 SGR 鼠标跟踪,终端把所有鼠标事件交给应用处理——原生链接点击和右键菜单对每个默认配置的用户都被悄悄丢掉了。PR 描述带有 before/after 基线(全局 qwen 0.21.3 上两个手势均无反应),且这是 #8198 / #8217 已确认的后续工作(那两个 PR 用 方向:对齐。这补全了 CHANGELOG 中已有的工作(#8198、#8217)——逃生开关的代价是失去滚轮滚动、悬停和拖拽选择,因此在默认配置下于应用内恢复这两项能力是正确的下一步。不触及 auth / sandbox / 公共契约面。 规模:约 794 行生产逻辑(另 +782 测试、+439 文档、+4 schema 镜像)。仅 4 行生产代码触及核心路径( 方案:范围合理。两项能力与两个丢失的原生能力一一对应,且共享基础设施(帧单元格 OSC 8 命中测试、选择栈坐标路径、同一个手势控制器)。从合成帧单元格读取 URL 而不是重新解析 markdown,正是避开此前尝试失败原因(命令注入、未变换坐标、CJK/软换行列漂移)的路径。每处改动都服务于目标(接线、消费者门控、设置文案、文档、设计文档),未发现顺手改动。 风险:无升级风险信号——改动文件均未命中与 revert 相关的高风险路径。 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewRead the full diff against my own baseline for this problem (frame-cell hit-test, reuse the existing secure launcher, a context-owned overlay with every other mouse consumer gated). The PR matches that baseline and goes past it in the right places. No correctness or security blockers found. What I verified while reading:
Two non-blocking observations, both already disclosed: Escape-dismissing the menu also clears the active selection (documented in the design doc as a follow-up), and the wheel tick that dismisses an open menu is consumed rather than also scrolling. sequenceDiagram
participant P1 as Terminal (SGR events)
participant P2 as ContentMouseController
participant P3 as ContextMenuContext
participant P4 as ContextMenuOverlay
participant P5 as Gated consumers
P1->>P2: right-click
P2->>P2: viewport hit-test, read frame cell for link or selection
P2->>P3: openMenu with items and clamped position
P3->>P4: render overlay at position
P3-->>P5: go quiet, mouse and nav keys owned by the menu
P1->>P2: hover, click, or scroll
P2->>P3: execute item or close
P3->>P4: unmount
P3-->>P5: resume
Files changed (25 of 25 shown)
Test evidenceThis is an unattended CI run — per triage rules no PR code is built or executed here. The evidence is the PR's own CI, fetched via the API at review time: Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 At review time the ubuntu unit/test job is still running (the suite takes ~30 minutes); macOS/Windows legs are skipped on this event, and nothing has failed so far. The finalize workflow updates the table once CI settles. Not verified here, explicitly: interactive gesture behavior. The PR thread carries an E2E report from the author (tmux + mock model + raw SGR injection, 14/14 checks on macOS) — that is the author's claim, not independently re-run. Sandboxed verification would settle it: 中文说明代码审查在给出我自己的独立方案(帧单元格命中测试、复用既有安全启动器、context 持有的浮层并门控其余鼠标消费者)之后通读了全部 diff。PR 与该基线一致,且在正确的方向上做得更多。未发现正确性或安全性阻塞问题。 核查要点:链接打开复用 core 的 测试证据无人值守 CI 运行——按 triage 规则不构建、不执行 PR 代码。以上证据来自 PR 自身 CI(API 实时获取):ubuntu 单元/测试作业审查时仍在运行(约 30 分钟),macOS/Windows 在本事件下跳过,目前无失败。finalize 工作流会在 CI 结束后更新表格。此处未验证交互手势行为;PR 线程中的 E2E 报告为作者自述(macOS,14/14),未独立复跑。沙箱验证可以定论: — Qwen Code · qwen3.8-max Reviewed at |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
|
Confidence: 3/5 — clean review across every stage, but the Stage 0 core-path size escalation needs a maintainer's sign-off, and CI had not landed green at review time. This is a genuinely good PR, and I want to be clear that the 3/5 is policy, not doubt. Stepping back:
What keeps this from an approval: the gate flags any non-maintainer PR touching a core path with 500+ production lines for maintainer awareness — here the core touch is only a 4-line settings-description change, but the rule is mechanical, so the call belongs to a human. Separately, the unit suite was still running at review time, and the interactive gesture matrix rests on the author's macOS E2E (their claim, not re-run here). ⏸️ Deferring to @wenshao — the review itself found no blockers; this needs your sign-off on the Stage 0 escalation. If you want live-behavior evidence beyond the author's report, 中文说明置信度:3/5 —— 各阶段审查都很干净,但 Stage 0 的核心路径规模升级需要维护者拍板,且审查时 CI 尚未全绿。 这是一个质量很高的 PR,3/5 是规则使然,并非存疑。整体来看:
不予批准的原因:gate 规则要求任何触及核心路径且生产代码 500+ 行的非维护者 PR 提请维护者知悉——本次核心触面仅 4 行设置描述,但规则是机械的,决定权应交还人类。另外,审查时单元测试仍在运行,交互手势矩阵目前依赖作者的 macOS E2E(作者自述,未在此独立复跑)。 ⏸️ 转交 @wenshao —— 审查本身未发现阻塞问题;需要你对 Stage 0 升级拍板。如需独立的行为证据,可触发 — Qwen Code · qwen3.8-max Reviewed at |
|
@qwen-code /takeover |
Visual evidence (before / menu open / after)Screenshots rendered from the real ANSI output via the project's terminal-capture pipeline (node-pty → xterm.js → Playwright), scenario committed as 1. Rendered link (before): the assistant line shows 2. Right-click → context menu: right-press/release on the link label raises the in-app menu at the click cell — 3. Escape → menu dismissed: the overlay is gone, the full link renders, and the composer is back to normal. |
|
🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. Remove the 中文说明🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round: no action requiredThis round's feedback for PR #8439 contains no actionable items:
No code changes were made this round, and nothing was committed. 中文说明Autofix 审查轮次:无需处理本轮针对 PR #8439 的反馈中没有任何可操作的项目:
本轮未修改任何代码,也未提交任何内容。 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
Qwen Code review timed out. Qwen review timed out after 14400 seconds (of the 240-minute budget). This run already used the maximum 240 minute timeout. See workflow logs. |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: 🤖 Reviewed the latest feedback — no changes needed this round. What arrived since the last evaluation (2026-08-03T08:17:28Z):
Why no code changes were made:
No commits were made; the branch remains at 中文说明🤖 已审阅最新反馈 —— 本轮无需修改。 自上次评估(2026-08-03T08:17:28Z)以来收到的反馈:
为何未做任何代码修改:
本轮未提交任何 commit;分支保持在 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
@qwen-code /review --timeout=240 |
| _Qwen Code review request accepted. Review is queued in [workflow run](https://github.com/QwenLM/qwen-code/actions/runs/30835644523)._ |
|
🔀 Base updated: red check(s) [review-pr] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [review-pr] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
|
🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下: Autofix review round: failed check addressedFeedback triage
What failed and whyThe failing step is Root cause (reproduced locally, evidence-backed):
Change made
Commit: Conflict notes
VerificationCommands actually run this round (results):
Not run: integration tests — the change is a workflow-text test under 中文说明Autofix 评审轮次:已处理失败的检查反馈分类
失败原因失败的步骤是 根因(已在本地复现,有证据支撑):
所做更改仅修改 提交: 冲突说明
验证本轮实际执行的命令(结果):
未运行集成测试:本次更改是 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
|
@qwen-code /resolve |
…parity # Conflicts: # scripts/tests/qwen-resolve-workflow.test.js
|
🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/100 轮)。改动内容与我反驳保留之处如下: Autofix address-review round — PR #8439Feedback triageThe prepared feedback file contained no actionable items this round: no reviews, no inline comments, no issue-level comments, no failed checks, and no still-red checks. Nothing to address, decline, or escalate. Conflict resolution (
|
|
Qwen Code did not run conflict resolution for this request. PR #8439 does not currently have merge conflicts with main. |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix address-review round — PR #8439No code changes this round. The only feedback item is the failed Feedback triage
Evidence
Why no code changeThe review agent's time budget and self-hosted runner health are workflow-side configuration/infrastructure. No edit on this branch can influence either. The earlier automatic base-update also cannot help here: Maintainer decision neededThe automated review has now failed three times on this PR, and how to proceed is a maintainer call. Options, with my recommendation first:
No changes were made and nothing was committed this round; the branch remains at 中文说明Autofix address-review 轮次 — PR #8439本轮未做任何代码改动。 本轮唯一的反馈项是失败的 反馈分类
证据
为何不做代码改动评审智能体的时间预算和自托管运行器的健康状况属于工作流侧的配置/基础设施问题,本分支上的任何改动都无法影响二者。此前的自动 base 更新在这里同样无济于事: 需要维护者决策自动化评审在本 PR 上已连续失败三次,接下来如何处理需要维护者决定。选项如下(按我的建议排序):
本轮未做任何改动、未提交任何 commit;分支保持在 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
🔀 Base updated: red check(s) [review-pr] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [review-pr] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix address-review round — PR #8439No code changes this round. The only feedback item is the failed Feedback triage
Evidence
Why no code changeThere is no evidence-backed code-level hypothesis: every runnable surrogate of the failed job passes on this exact tree, the only local failures are proven container artifacts, and the merge delta touches no code path that could explain the red check. Inventing a speculative change would only grow the diff without addressing a demonstrated defect (AGENTS.md, Simplicity First). Transient Test failures of this shape are handled by the repository's 中文说明Autofix address-review 轮次 — PR #8439本轮不做任何代码改动。 唯一的反馈项是失败的 反馈分类
证据
为什么不做代码改动不存在有证据支撑的代码层面假设:该失败任务的每一个可运行替代验证都在这棵完全相同的代码树上通过,仅有的本地失败已被证明是容器产物,合并带入的变更也未触及任何能解释该红色检查的代码路径。臆测性的改动只会增大 diff,却无法解决任何被证实的缺陷(AGENTS.md,Simplicity First 原则)。此类形态的瞬时性 Test 失败由本仓库的 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
|
Qwen Code review timed out. Qwen review timed out after 21600 seconds (of the 360-minute budget). This run already used the maximum 360 minute timeout. See workflow logs. |
|
🔀 Base updated: red check(s) [review-pr] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [review-pr] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round — no code changes requiredThis round's feedback contained no review comments and no inline findings — only a bot comment and two failed checks. After a full evidence-based investigation, no code-level defect was found, so no changes were made. Feedback triage
Investigation of the failed
|
| 项目 | 分类 | 处理 |
|---|---|---|
@qwen-code-ci-bot:“Qwen Code review timed out”(issue 评论) |
工作流层面:自动化 Qwen 审查用满了 360 分钟预算后超时 | PR 代码中无可修改项;审查工作流会独立重跑 |
失败检查 review-pr |
同上述审查超时(审查工作流的 check run) | 无法通过本分支代码解决 |
失败检查 Test (ubuntu-latest Node 22.x) |
已端到端排查(见下文) | 无可复现缺陷;该 CI job 的每个步骤都已在 CI 等效环境下本地复现且全部通过 |
对失败 Test 检查的排查
本 runner 无法获取 CI 日志(没有 GitHub 凭据),因此在 PR head(6761b3f74)上逐步本地复现了整个 Test job,并使用 CI 等效环境(全新 HOME、NO_COLOR=true、空 API key、未设置 SANDBOX/QWEN_HOME/CI):
- Lint/格式/产物新鲜度步骤 — 全部通过:
npm run lint:ci(--max-warnings 0)、全仓库 Prettier、敏感词 lint、npm run check-i18n、settings-schema 重新生成(已提交的 schema 为最新)、VS Code companion notices(最新)、npm run check:lockfile、npm run audit:runtime:critical(0 个严重漏洞)、npm run check:desktop-isolation、npm run check:serve-fast-path-bundle、.github/scripts辅助测试(206/206)。 - 单元测试 — 全部 9 个 workspace 通过:cli 17260 通过、core 19226 通过、sdk-typescript 1444、web-shell 2784、webui 427、vscode-ide-companion 480、acp-bridge 1053、chrome-extension 76、audio-capture 2;另有根目录
test:scripts884 通过。 - 构建/类型检查 —
npm run build与npm run typecheck通过。 - 无 AK 集成门禁(
test:integration:no-ak:sandbox:none)— 128/129 通过;唯一的本地失败已证明是沙箱环境问题(见下文)。
复现过程中遇到的仅限本地的失败,均已证明为环境问题(CI 中均不存在):
Footer快照 +ideCommand提前返回 — 由本沙箱的SANDBOX环境变量导致(CI 不设置该变量)。- 12 个
settings.test.ts失败 — 由本沙箱环境中的QWEN_HOME变量导致(CI 的单元测试步骤中不存在);取消设置后即通过。 AuthDialogTUI 输入测试 — 在 CI 中按设计被跳过(测试文件内的CI=true门控);本地的偶发失败与 CI 无关。- 5 个
install-script.test.js失败 —spawnSync zip ENOENT:本沙箱缺少zip二进制,而 ubuntu-latest runner 自带。 qwen-serve-streaming.test.ts的 Todo Stop Guard 重放 — daemon 会话加载了本沙箱 checkout 中被 git 忽略的.qwen/settings.json,其tools.core白名单不含todo_write,导致该工具不在会话注册表中、guard 从未触发。用干净的工作区目录重跑该场景即证明:预期的 4 个模型请求在 5 秒内全部到达。CI 的 checkout 永远不会包含.qwen/settings.json(它被 git 忽略,且 Test job 在 checkout 前会清理残留的.qwen),因此 CI 中不可能出现此问题。
结论:红色的 Test 检查与自托管 runner 上的瞬时/偶发失败相符,而非本 PR 引入的缺陷;下一步应当重跑 CI。
验证
本轮实际执行的命令(基于 PR head 6761b3f74):
npm run build— 通过npm run typecheck— 通过npm run lint与npm run lint:ci(--max-warnings 0)— 通过- 对改动文件执行
npx prettier --check,随后全仓库node scripts/lint.js --prettier— 通过 node scripts/lint.js --sensitive-keywords— 通过npm run check-i18n— 通过npm run generate:settings-schema+ 对该 schema 的git status— 最新,无漂移npm run generate:notices --workspace=qwen-code-vscode-ide-companion+git status— 最新,无漂移npm run check:lockfile— 通过npm run audit:runtime:critical— 通过(0 个严重)npm run check:desktop-isolation— 通过npm run check:serve-fast-path-bundle— 通过.github/scripts辅助测试(node --test,13 个文件)— 206 通过,0 失败- 定向 Vitest(CI 等效环境):packages/cli — 17260 通过 / 0 失败(13 个仅限本地的失败已证明为环境问题,见上文);packages/core — 19226 通过;sdk-typescript — 1444;web-shell — 2784;webui — 427;vscode-ide-companion — 480;acp-bridge — 1053;chrome-extension — 76;audio-capture — 2;根目录
test:scripts— 884 通过(5 个zip ENOENT为仅限沙箱的失败) - 集成测试(无 AK 门禁等效,全新构建后)— 128/129 通过;唯一失败已证明是本沙箱的
.qwen/settings.json泄漏进测试工作区(干净工作区重跑:guard 流程完成,5 秒内 4/4 请求)
未改动任何代码;工作树在 PR head 上保持干净。
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。
🧠 Handled by Qwen Code · model/模型 qwen3.8-max
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x), review-pr] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x), review-pr] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
|
Qwen Code review timed out. Qwen review timed out after 21600 seconds (of the 360-minute budget). This run already used the maximum 360 minute timeout. See workflow logs. |
|
🤖 AutoFix ran out of time before finishing (timeout (1080000ms)) (attempt 3/100) — it will retry on the next scan. What I found before stopping: See the Qwen Autofix agent step logs for model/tool output. Run log: https://github.com/QwenLM/qwen-code/actions/runs/30996516909 🧠 Handled by Qwen Code · model/模型 |
|
🤖 AutoFix ran out of time before finishing (timeout (1080000ms)) (attempt 4/100) — it will retry on the next scan. What I found before stopping: See the Qwen Autofix agent step logs for model/tool output. Run log: https://github.com/QwenLM/qwen-code/actions/runs/31000060856 🧠 Handled by Qwen Code · model/模型 |
|
🤖 AutoFix stopped: this counting window now contains 3 time-budget exhaustions (pushed rounds in between included; this round itself may have failed differently). That is 3 full agent runs that pushed nothing. A human should split or reduce the PR (or raise the agent time budget AND its step backstop together), then comment What I found before stopping: See the Qwen Autofix agent step logs for model/tool output. Run log: https://github.com/QwenLM/qwen-code/actions/runs/31002906776 🧠 Handled by Qwen Code · model/模型 |
|
⏸️ Takeover paused: this PR reached its round cap (100/100). Comment 中文说明⏸️ 托管已暂停:本 PR 达到轮次上限(100/100)。评论 |
Review: Ctrl+click hyperlinks and right-click context menu in VP modeOverviewRestores the two native terminal affordances that SGR mouse tracking takes away in VP mode:
The architecture is the right one. Reading the URL from the frame instead of re-parsing markdown is a genuinely good call — it makes soft-wrap and wide-char drift impossible by construction. Reusing Two issues below look blocking; the rest are suggestions. 1. The menu box does not paint its own interior — transcript text bleeds through it
Reproduced against the same ink (7.0.3) with the PR's exact markup (border Every menu that mixes label lengths hits this, i.e. the link menu ( Fix is one line — pad to the width const longest = Math.max(...menu.items.map((i) => i.label.length));
...
{` ${item.label.padEnd(longest)} `}The E2E screenshots probably missed it because the cells behind the short row happened to be blank. 2. Esc-to-dismiss still fires the global Escape handler (can cancel an in-flight response)
Dismissing a context menu should have no side effect at all. Note the fix isn't purely local: Same mechanism, smaller blast radius: Suggestions
TestsGood unit coverage for the parts that are easy to get wrong: OSC 8 terminator variants (BEL/ST/C1-ST/tmux doubled-ESC), params section, wide-char snap, the geometry helpers including edge flipping, the provider state machine, controller gestures (ctrl press/release same cell, drag cancel, non-http fallback, viewport rejection, scroll dismiss), overlay keyboard, and the new
VerdictSolid design and a real regression fixed the right way. Issues 1 and 2 should land before merge — the first is visible on the main path, the second can cancel a user's in-flight response. Everything else is follow-up material. |
|
Qwen Code review timed out. Qwen review timed out after 21600 seconds (of the 360-minute budget). This run already used the maximum 360 minute timeout. See workflow logs. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: reverse audit — stopped before round 4 by the review time budget.
中文说明
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未审查:反向审计——评审时间预算不足,未能开始第 4 轮。
— qwen3.8-max via Qwen Code /review (v0.21.6)
|
🔓 Takeover auto-released: the autofix loop paused on this PR 10 day(s) ago (🤖 AutoFix stopped: this counting window now contains 3 time-budget exhaustions (pushed rounds in between included; this ) and no re-arm followed, so the 中文说明🔓 已自动释放接管:autofix 循环在 10 天前暂停于此 PR(🤖 AutoFix stopped: this counting window now contains 3 time-budget exhaustions (pushed rounds in between included; this ),此后无人重新武装,现移除 |
- InputPrompt: fall through after closing the context menu so the dismissing key keeps reaching vim/paste/shell-mode interceptors - use-text-selection: pause gate above the scroll-clear branch (wheel ticks while paused keep the selection) and finish an in-flight drag on a paused left-release (no stale dragging leak) - hyperlink-at: stop OSC 8 URL extraction on DEL and the full C1 range - ContextMenuContext: closeMenu clears the ref mirror synchronously; provider gains onMenuChange for AppContainer's global keypress gate - AppContainer: skip the ESCAPE / btw-dismiss global branches while the context menu is open - ContentMouseController: close the menu on unmount and on terminal resize while open - HistoryItemDisplay: ignore Ctrl-modified mouse events (reserved for the OSC 8 link gesture) - ContextMenuOverlay: pad rows to the longest label so the absolute box paints its own interior



What this PR does
Virtual Viewport mode enables SGR mouse tracking, which makes the terminal hand every mouse event to the app — so two native terminal capabilities were silently lost: clicking a hyperlink to open it, and right-clicking for a context menu. This PR restores both while tracking stays on, so they coexist with wheel scrolling, scrollbar dragging, hover, and drag-to-select.
Ctrl+clicking a rendered hyperlink opens it in the browser. The URL is read from the composited frame cell under the pointer — ink preserves the OSC 8 escape in per-cell styles, so the hit-test is exact by construction: no markdown re-parsing, no column arithmetic, no CJK-width or soft-wrap drift. Opening goes through the existing secure launcher (http/https validation, no shell interpolation); non-http OSC 8 targets (mailto:, ssh:, …) degrade to a clipboard copy with a visible notice. Ctrl is the gesture because Shift/Option remain reserved for the documented terminal-selection bypass, and plain clicks keep their exact current behavior — nothing opens on an accidental click or a drag.
Right-clicking in the history viewport raises a small in-app context menu at the pointer: Open Link and Copy Link Address over a hyperlink, Copy Selection over an active text selection (no menu when neither applies). It is an absolutely-positioned overlay drawn last in the app layout, navigable by mouse hover/click or ↑/↓ + Enter, dismissed by Esc, clicking elsewhere, scrolling, or any dialog opening. While the menu is open it owns the pointer and keyboard: the scroll list, thought toggle, row hover/select, and composer click-to-position go quiet so clicks on the overlay can't also act on the content underneath, and the composer's navigation keys are scoped so they drive the menu instead of the prompt. Text selection is paused but deliberately preserved — opening the menu must not clear the selection the Copy Selection item is about to copy — and the selected text is snapshotted at open time so a frame that keeps streaming can't make the copy stale.
The
ui.mouseTrackingsetting description and the user docs are refreshed to match: with tracking enabled the app now supplies these equivalents itself, and disabling it remains the escape hatch that hands the mouse fully back to the terminal.Why it's needed
Since Virtualized History became the default (0.21.1), every VP-mode user lost two things their terminal used to do: URL clicks and the right-click menu — the events are captured by the app and dropped. PR #8198 added the
ui.mouseTrackingescape hatch, but with default settings both regressions remained, and the escape hatch costs wheel scrolling, hover, and drag-select. A prior in-app attempt at link clicks (removed during #8198 review) failed on Windows command injection, untransformed screen coordinates, and markdown column drift; this implementation eliminates all three by construction (secure launcher, the selection stack's coordinate path, and frame-cell hit-testing instead of text re-parsing).Reviewer Test Plan
How to verify
Unit:
cd packages/cli && npx vitest run src/ui/context-menu/ src/ui/utils/hyperlink-at.test.ts src/ui/selection/(41 new tests covering URL extraction edge cases — BEL/ST/C1-ST terminators, OSC 8 params, tmux doubled-ESC form, wide-char snap; menu state machine; controller gestures; overlay rendering + keyboard).Interactive (any OSC 8-capable terminal, VP mode default):
Automated E2E (tmux + mock model + raw SGR injection) ran the full matrix: baseline on global qwen 0.21.3 confirmed every gesture inert; the same injections on this branch open the link and raise the menu — 14/14 checks passed, plus a 6/6 focused re-verification on the final rebuilt bundle. Plan and results:
.qwen/e2e-tests/vp-native-mouse-parity.md(local artifact).Evidence (Before & After)
Before (global qwen 0.21.3): Ctrl+click / right-click on a rendered link → no reaction (pane byte-identical, browser log never created).
After (this branch): Ctrl+click → URL recorded by the
$BROWSERstub; right-click → rounded menu box at the click cell (capture-paneshows the box and items); clicking Open Link records the URL and the menu disappears.Tested on
Environment
Local bundle (
npm run build && npm run bundle,node dist/cli.js) under tmux 3.6a withFORCE_HYPERLINK=1, plus focused vitest suites.Risk & Scope
ui.mouseTrackingsemantics unchanged; default behavior gains two affordances.Linked Issues
Related to the VP-mode mouse regressions mitigated by #8198 (the
ui.mouseTrackingescape hatch this supersedes for the default configuration).中文说明
这个 PR 做了什么
虚拟视口(VP)模式启用了 SGR 鼠标跟踪,终端会把所有鼠标事件交给应用处理——因此两个原生终端能力被悄悄丢掉了:点击超链接打开它、右键弹出上下文菜单。本 PR 在保持鼠标跟踪开启的同时恢复了这两项能力,使其与滚轮滚动、滚动条拖拽、悬停和拖拽选择共存。
Ctrl+点击已渲染的超链接会在浏览器中打开它。URL 直接从指针下方合成帧单元格里读取——ink 会把 OSC 8 转义保留在每个单元格的样式里,因此命中测试在构造上就是精确的:不重新解析 markdown、不做列号计算、不受 CJK 宽字符和软换行影响。打开动作走既有的安全启动器(http/https 校验、无 shell 插值);非 http 的 OSC 8 目标(mailto:、ssh: 等)降级为复制到剪贴板并给出可见提示。选择 Ctrl 作为手势是因为 Shift/Option 仍保留给已文档化的"终端原生选择"绕过手势,且普通点击保持现有行为完全不变——误触或拖拽不会打开任何东西。
在历史视口中右键会在指针处弹出一个应用内上下文菜单:在超链接上显示 Open Link 和 Copy Link Address,在有文本选区时显示 Copy Selection(两者都不满足则不弹菜单)。它是应用布局最后绘制的绝对定位浮层,可用鼠标悬停/点击或 ↑/↓ + Enter 操作,Esc、点击他处、滚动或打开任何对话框都会关闭它。菜单打开期间它独占指针和键盘:滚动列表、思考折叠、行悬停/选择、输入框点击定位全部静默,避免点在菜单上的同时也作用于下方内容;composer 的导航键也被接管,用于操作菜单而非输入框。文本选择只是暂停而被刻意保留——打开菜单不能清掉 Copy Selection 正要复制的选区——且选中文本在打开时即快照,避免仍在流式输出的帧让复制内容过期。
ui.mouseTracking设置描述和用户文档同步更新:跟踪开启时应用自身提供这些等价能力;关闭它仍是把鼠标完全交还终端的逃生开关。为什么需要
自虚拟视口成为默认(0.21.1)以来,每个 VP 模式用户都失去了终端原本的两项能力:URL 点击和右键菜单——事件被应用捕获后直接丢弃。PR #8198 增加了
ui.mouseTracking逃生开关,但默认配置下这两个回归依然存在,且该开关的代价是失去滚轮滚动、悬停和拖拽选择。此前一次应用内链接点击的尝试(在 #8198 评审中被移除)因 Windows 命令注入、未做坐标变换和 markdown 列号漂移而失败;本实现在构造上消除了全部三个问题(安全启动器、复用选择栈的坐标路径、用帧单元格命中测试替代文本重解析)。评审者测试计划
如何验证
单元测试:
cd packages/cli && npx vitest run src/ui/context-menu/ src/ui/utils/hyperlink-at.test.ts src/ui/selection/(41 个新测试,覆盖 URL 提取边界——BEL/ST/C1-ST 终止符、OSC 8 参数、tmux 双 ESC 形式、宽字符吸附;菜单状态机;控制器手势;浮层渲染与键盘)。交互验证(任意支持 OSC 8 的终端,VP 模式默认开启):
自动化 E2E(tmux + mock 模型 + 原始 SGR 注入)跑完了全部矩阵:在全局 qwen 0.21.3 上确认所有手势无反应的基线;同样的注入在本分支上能打开链接并弹出菜单——14/14 项通过,最终重建的 bundle 又做了 6/6 项聚焦复验。计划与结果见
.qwen/e2e-tests/vp-native-mouse-parity.md(本地工件)。证据(前后对比)
修复前(全局 qwen 0.21.3):Ctrl+点击 / 右键点击已渲染链接 → 无反应(面板字节级一致,浏览器日志从未创建)。
修复后(本分支):Ctrl+点击 →
$BROWSER桩记录到 URL;右键 → 点击单元格处出现圆角菜单框(capture-pane 可见菜单框与菜单项);点击 Open Link 后记录 URL 且菜单消失。测试平台
macOS ✅(已测);Windows / Linux⚠️ (未测,启动器路径共享、平台分发来自既有安全启动器)。
环境
本地 bundle(
npm run build && npm run bundle,node dist/cli.js),tmux 3.6a +FORCE_HYPERLINK=1,另加聚焦 vitest 套件。风险与范围
ui.mouseTracking语义不变;默认配置新增两个交互能力。关联 Issue
与 #8198 缓解的 VP 模式鼠标回归相关(本 PR 在默认配置下取代了该逃生开关的必要性)。