Skip to content

feat(cli): add standalone auto-update support#4629

Open
yiliang114 wants to merge 21 commits into
mainfrom
worktree-swift-elm-iqh0
Open

feat(cli): add standalone auto-update support#4629
yiliang114 wants to merge 21 commits into
mainfrom
worktree-swift-elm-iqh0

Conversation

@yiliang114
Copy link
Copy Markdown
Collaborator

@yiliang114 yiliang114 commented May 29, 2026

What this PR does

Adds self-update capability for standalone installations. When Qwen Code detects it was installed via the standalone installer (not npm/pnpm/yarn), it now downloads the new release archive from OSS/GitHub, verifies its SHA256 checksum, extracts it, and atomically replaces the installation directory — all without requiring sudo or any package manager.

Key implementation details:

  • Detects standalone installs via manifest.json presence under ~/.local/lib/qwen-code/
  • Downloads from Aliyun OSS with GitHub fallback
  • SHA256 checksum verification before extraction
  • Atomic replace via .new/.old staging with rollback on failure
  • Windows: deferred update via helper .bat script (applied after process exit)
  • PATH priority: ensurePathInShellRc() prepends ~/.local/bin to the user's shell rc file
  • Concurrent update protection via lock file with PID liveness check

Why it's needed

Standalone installs (~/.local/lib/qwen-code/) were not recognized by installationInfo.ts. They fell through to the npm global fallback, causing auto-update to run npm install -g — which either fails with EACCES (if npm prefix requires root) or installs to a different location without updating the standalone binary.

Reviewer Test Plan

How to verify

  1. Install Qwen Code via standalone installer:
    curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen-standalone.sh | bash
  2. Confirm which qwen points to ~/.local/bin/qwen and ~/.local/lib/qwen-code/manifest.json exists
  3. Manually edit manifest.json to set "version": "0.0.1" (to trigger update detection)
  4. Run qwen — observe the update notification and auto-download behavior
  5. After restart, confirm version is updated

Evidence (Before & After)

Auto-Update Flow E2E (direct invocation of performStandaloneUpdate(), real OSS download):

# Scenario Result
1 standalone 0.16.0 → auto-update to 0.17.0 ✅ 13.9s
2 rollback to previous version
3 concurrent update protection (lock)

What was verified in the update flow:

  • Download: Real qwen-code-darwin-arm64.tar.gz from Aliyun OSS (v0.17.0)
  • Checksum: SHA256SUMS downloaded and verified against archive
  • Signature: Ed25519 verification attempted (graceful skip when .sig not published yet)
  • Extract: tar.gz extracted with path traversal filter
  • Smoke test: New node binary executes cli.js --version → "0.17.0" ✅
  • Atomic replace: Old dir renamed to .old, new dir moved into place
  • Rollback: rollbackStandaloneUpdate() swaps .old back → version restored to 0.16.0
  • Concurrent lock: Second update attempt with live PID in lock file → correctly rejected
场景 1: standalone 0.16.0 → auto-update to v0.17.0
  初始: version=0.16.0, target=darwin-arm64
  执行: performStandaloneUpdate("0.17.0")
  完成: result=done, 耗时=13.9s
  更新后 version: 0.17.0
  .old 保留: version=0.16.0
  ✅ PASS

场景 2: rollbackStandaloneUpdate
  回滚后 version: 0.16.0
  ✅ PASS

场景 3: 并发更新保护 (lock file)
  error: "Another update is already in progress"
  ✅ PASS

PATH Priority E2E (real npm installs, 4 scenarios):

# Scenario Pre-existing After install which qwen Result
A Fresh environment None ~/.local/bin/qwen (0.17.0) ✅ PASS
B Real npm install -g @0.16.2 → standalone ~/.npm-global/bin/qwen (0.16.2) ~/.local/bin/qwen (0.17.0) ✅ PASS
C Standalone exists → real npm install -g @latest both exist ~/.local/bin/qwen (0.17.0) ✅ PASS
D nvm user (real npm @0.16.2 via nvm) → standalone ~/.nvm/.../bin/qwen (0.16.2) ~/.local/bin/qwen (0.17.0) ✅ PASS

PATH mechanism — installer appends at end of .zshrc:

# Qwen Code PATH block begin
export PATH='~/.local/bin':$PATH
# Qwen Code PATH block end

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

macOS Darwin 24.1.0 arm64, zsh. Real OSS download (v0.17.0). Auto-update E2E via direct tsx invocation of performStandaloneUpdate(). PATH E2E scenarios use real npm install -g (not mocked), each in isolated $HOME.

Risk & Scope

  • Main risk or tradeoff: Windows update is deferred (applies after exit via helper .bat script) — cannot be verified in-session. The finally block now only cleans pendingDir on the error path (not the deferred-success path).
  • Not validated / out of scope:
    • GPG/minisign signature verification — SHA256 from same origin only guards against corruption, not supply-chain attacks. Ed25519 verification path is wired up but uses a placeholder key until production CI signing is ready. Enforcement via QWEN_REQUIRE_SIGNATURE=1 is opt-in.
    • Full mock integration tests for performStandaloneUpdate — better as a dedicated test PR.
  • Breaking changes / migration notes: None. Existing npm/pnpm/yarn/bun update paths are unchanged.

Linked Issues

Closes #4627

中文说明

这个 PR 做了什么

为 standalone 安装添加自更新能力。当 Qwen Code 检测到自身通过 standalone 安装器安装(非 npm/pnpm/yarn)时,会从 OSS/GitHub 下载新版本 release 归档,验证 SHA256 校验和,解压并原子替换安装目录——无需 sudo 或任何包管理器。

关键实现:

  • 通过 manifest.json 检测 standalone 安装
  • 从 Aliyun OSS 下载,GitHub 作为回退
  • 提取前 SHA256 校验和验证
  • 通过 .new/.old 暂存目录原子替换,失败时回滚
  • Windows:通过 helper .bat 脚本延迟更新(进程退出后执行)
  • PATH 优先级:ensurePathInShellRc() 在用户 shell rc 文件中 prepend ~/.local/bin
  • 并发更新保护:lock file + PID 存活检查

为什么需要

Standalone 安装(~/.local/lib/qwen-code/)未被 installationInfo.ts 识别,会错误回退到 npm global——要么 EACCES 失败(npm prefix 需要 root),要么安装到不同位置而不更新 standalone 二进制。

评审测试计划

如何验证

  1. 通过 standalone 安装器安装
  2. 确认 which qwen 指向 ~/.local/bin/qwen
  3. 手动修改 manifest.json version 为 0.0.1 触发更新检测
  4. 运行 qwen,观察更新下载行为
  5. 重启后确认版本已更新

证据(修复前后对比)

自动更新流程 E2E(直接调用 performStandaloneUpdate(),真实 OSS 下载):

# 场景 结果
1 standalone 0.16.0 → 自动更新到 0.17.0 ✅ 13.9s
2 回滚到前一版本
3 并发更新保护(lock)

验证了完整链路:下载 → SHA256 校验 → Ed25519 签名验证(graceful skip)→ 解压 → smoke test → 原子替换 → 回滚 → 并发锁。

PATH 优先级 E2E(真实 npm,4 个场景): 全新环境、npm→standalone、standalone→npm、nvm 用户,standalone 始终优先。

测试平台

OS 状态
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

风险与范围

  • 主要风险:Windows 更新是延迟执行的(进程退出后通过 .bat 脚本应用)——无法在会话内验证。finally 块现在仅在错误路径清理 pendingDir(不影响延迟成功路径)。
  • 未验证/不在范围内:
    • GPG/minisign 签名验证——SHA256 仅防损坏不防供应链攻击。Ed25519 路径已接通但使用占位符密钥,待生产 CI 签名就绪后启用。
    • performStandaloneUpdate 的完整 mock 集成测试——留作专门 PR。
  • 破坏性变更:无。现有 npm/pnpm/yarn/bun 更新路径不受影响。

关联 Issue

Closes #4627

Copilot AI review requested due to automatic review settings May 29, 2026 14:56
@github-actions
Copy link
Copy Markdown
Contributor

📋 Review Summary

This PR adds standalone auto-update support for Qwen Code installations that were installed via the standalone installer (~/.local/lib/qwen-code/). The implementation is well-structured with proper checksum verification, atomic replacement, and platform-specific handling. The code quality is generally high with good error handling and security considerations.

🔍 General Feedback

Positive aspects:

  • Excellent security practice with SHA256 checksum verification from remote SHA256SUMS file
  • Clean separation of concerns with dedicated standalone-update.ts module
  • Proper fallback mechanism (OSS → GitHub) for download reliability
  • Atomic replacement strategy differs appropriately by platform (Unix rename vs Windows deferred)
  • Good use of file locking to prevent concurrent updates
  • Comprehensive test coverage exists for related auto-update functionality

Architectural decisions:

  • The early return pattern in handleAutoUpdate.ts for standalone installs is clean and prevents falling through to npm fallback
  • findStandaloneDir walks up the directory tree looking for manifest.json — robust approach
  • Windows deferred update via helper .bat script is necessary but introduces complexity

Potential concerns:

  • No tests added for the new standalone-update.ts module (critical functionality)
  • Supply-chain security relies solely on SHA256 from same origin (noted in PR description, but worth addressing)
  • Windows path cannot be verified in-session per PR description

🎯 Specific Feedback

🔴 Critical

  • File: packages/cli/src/utils/standalone-update.ts:1 - Missing test coverage: This is critical infrastructure code (318 lines) with no corresponding test file. The module handles downloads, checksum verification, archive extraction, and atomic file system operations — all high-risk operations. Recommendation: Add comprehensive unit tests mocking fetch, fs, and tar operations before merging.

  • File: packages/cli/src/utils/standalone-update.ts:107-120 - Checksum verification vulnerability: The verifyChecksum function downloads SHA256SUMS from the same origin as the archive. An attacker compromising the OSS/GitHub origin could serve both a malicious archive and matching checksum. Recommendation: While noted as follow-up work, consider at minimum downloading SHA256SUMS over a different CDN/path or implement GPG/minisign verification as mentioned in the PR description.

🟡 High

  • File: packages/cli/src/utils/standalone-update.ts:245-267 - Windows deferred update has no success confirmation: The Windows path spawns a detached .bat script that deletes itself after completion, but there's no mechanism to verify the update succeeded or report failures back to the user. Recommendation: Add a temporary status file (e.g., qwen-code-update-result.json) that the next run can check and report on.

  • File: packages/cli/src/utils/standalone-update.ts:193-201 - Lock file race condition: The acquireLock function checks if a process is alive using process.kill(pid, 0), but there's a race window between checking and writing the lock file. Recommendation: Use fs.writeFileSync with 'wx' flag atomically (which you do), but consider using a proper file locking library like proper-lockfile for production-critical locking.

  • File: packages/cli/src/utils/installationInfo.ts:36-56 - findStandaloneDir silent failures: Parse errors in manifest.json are silently ignored. While intentional, this could mask corrupted manifests. Recommendation: Add debug logging when parse failures occur so users can diagnose issues.

🟢 Medium

  • File: packages/cli/src/utils/standalone-update.ts:78-86 - Inconsistent error logging: tryFetch logs failures via debugLogger.debug, but downloadWithFallback throws a generic error without indicating which URLs were attempted. Recommendation: Include both URLs in the error message for easier debugging.

  • File: packages/cli/src/utils/standalone-update.ts:175-183 - Magic number for loop iterations: The findStandaloneDir function loops for (let i = 0; i < 3; i++) — this limit should be documented or extracted as a constant explaining why 3 levels is sufficient.

  • File: packages/cli/src/utils/handleAutoUpdate.ts:54-62 - Error message could be more actionable: The standalone update failure message says "Re-run the installer to update manually" but doesn't provide the installer URL. Recommendation: Include the standalone installer URL or command in the error message.

  • File: packages/cli/src/utils/standalone-update.ts:1 - Missing type safety for manifest.json: The manifest is cast as { target?: string } but the actual manifest structure should be defined as an interface and validated more thoroughly.

🔵 Low

  • File: packages/cli/src/utils/standalone-update.ts:25 - Consider extracting target detection logic: The VALID_TARGETS set and validateTarget function could benefit from a comment explaining where these targets come from (build artifacts?).

  • File: packages/cli/src/utils/standalone-update.ts:226-242 - Windows batch script could use better error handling: The .bat script uses move /Y which will succeed even if the target doesn't exist. Consider adding explicit error checks.

  • File: packages/cli/src/utils/installationInfo.ts:29-30 - Interface documentation: The InstallationInfo interface should have JSDoc comments explaining when isStandalone vs isGlobal would be set.

  • File: packages/cli/package.json:80 - Dependency pinning: The tar dependency is added with ^7.5.2. Consider whether this should be pinned more strictly given it's used for security-sensitive extraction.

✅ Highlights

  • Excellent security pattern: The dual-mirror fallback (OSS → GitHub) with checksum verification is a robust approach for download reliability
  • Clean atomic replacement: The Unix implementation using fs.renameSync for atomic swap is the correct pattern
  • Thoughtful platform handling: Proper differentiation between Unix (immediate atomic) and Windows (deferred via helper script) shows careful consideration
  • Good debug logging: The createDebugLogger('STANDALONE_UPDATE') usage throughout will help diagnose production issues
  • Proper cleanup in finally block: The performStandaloneUpdate function correctly cleans up temp directories even on failure

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds standalone-install auto-update support so Qwen Code can update user-writable standalone installations without falling back to npm.

Changes:

  • Detects standalone installs via manifest.json and surfaces standalone update messaging.
  • Adds a standalone updater that downloads release archives, verifies SHA256, extracts, and replaces the install directory.
  • Wires standalone auto-update into the existing update event flow and adds the tar dependency.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/cli/src/utils/standalone-update.ts Implements standalone archive download, checksum verification, extraction, locking, and install replacement.
packages/cli/src/utils/installationInfo.ts Adds standalone install detection and metadata to installation info.
packages/cli/src/utils/handleAutoUpdate.ts Routes standalone installs through the new updater instead of package-manager commands.
packages/cli/package.json Adds tar as a CLI dependency for archive extraction.
package-lock.json Updates dependency metadata for the new CLI dependency.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/cli/src/utils/handleAutoUpdate.ts
Comment thread packages/cli/src/utils/standalone-update.ts
Comment thread packages/cli/src/utils/standalone-update.ts
Comment thread packages/cli/src/utils/standalone-update.ts
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 29, 2026

Code Coverage Summary

Package Lines Statements Functions Branches
CLI 77.53% 77.53% 80.54% 80.16%
Core 81.18% 81.18% 83.12% 83.35%
CLI Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   77.53 |    80.16 |   80.54 |   77.53 |                   
 src               |   72.68 |    67.59 |   76.19 |   72.68 |                   
  gemini.tsx       |   62.93 |    64.28 |   71.42 |   62.93 | ...1160-1163,1175 
  ...ractiveCli.ts |   78.61 |    66.66 |      75 |   78.61 | ...1299-1300,1336 
  ...liCommands.ts |    74.9 |     75.6 |     100 |    74.9 | ...41-265,290,391 
  ...ActiveAuth.ts |     100 |     87.5 |     100 |     100 | 66-80             
 ...cp-integration |   65.32 |    65.74 |   85.24 |   65.32 |                   
  acpAgent.ts      |   65.05 |    65.95 |   85.96 |   65.05 | ...2076,2090-2098 
  authMethods.ts   |      92 |       60 |     100 |      92 | 33-34             
  errorCodes.ts    |       0 |        0 |       0 |       0 | 1-22              
  ...DirContext.ts |     100 |      100 |     100 |     100 |                   
 ...ration/service |   68.65 |    83.33 |   66.66 |   68.65 |                   
  filesystem.ts    |   68.65 |    83.33 |   66.66 |   68.65 | ...32,77-94,97-98 
 ...ration/session |   77.24 |    71.87 |   86.58 |   77.24 |                   
  ...ryReplayer.ts |   67.34 |     75.6 |   81.81 |   67.34 | ...54-269,282-283 
  Session.ts       |   76.71 |    70.61 |   88.88 |   76.71 | ...2717,2723-2726 
  ...entTracker.ts |   90.85 |    84.84 |      90 |   90.85 | ...35,199,251-260 
  index.ts         |       0 |        0 |       0 |       0 | 1-40              
  ...ssionUtils.ts |   84.21 |    77.77 |     100 |   84.21 | ...37-153,209-211 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ssion/emitters |   96.01 |    90.75 |    92.3 |   96.01 |                   
  BaseEmitter.ts   |   76.92 |    66.66 |      80 |   76.92 | 23-24,39-40,55-56 
  ...ageEmitter.ts |     100 |    89.47 |     100 |     100 | 109,111           
  PlanEmitter.ts   |     100 |      100 |     100 |     100 |                   
  ...allEmitter.ts |   98.06 |     92.3 |     100 |   98.06 | 227-228,327,335   
  index.ts         |       0 |        0 |       0 |       0 | 1-10              
 ...ession/rewrite |   90.36 |    87.83 |   94.11 |   90.36 |                   
  LlmRewriter.ts   |      81 |       84 |     100 |      81 | ...,88-89,155-159 
  ...Middleware.ts |   95.83 |    85.71 |     100 |   95.83 | 119,127-129       
  TurnBuffer.ts    |     100 |      100 |     100 |     100 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 src/commands      |   45.67 |    85.71 |   43.47 |   45.67 |                   
  auth.ts          |     100 |    83.33 |     100 |     100 | 11,14             
  channel.ts       |   56.66 |      100 |       0 |   56.66 | 15-19,27-34       
  extensions.tsx   |   96.55 |      100 |      50 |   96.55 | 37                
  hooks.tsx        |   66.66 |      100 |       0 |   66.66 | 20-24             
  mcp.ts           |   94.73 |      100 |      50 |   94.73 | 28                
  review.ts        |   51.85 |      100 |       0 |   51.85 | 24-35,38          
  serve.ts         |    8.02 |      100 |       0 |    8.02 | ...56-152,154-266 
 ...mmands/channel |    39.2 |    79.45 |      50 |    39.2 |                   
  ...l-registry.ts |    8.33 |      100 |       0 |    8.33 | 6-22,25-43        
  config-utils.ts  |      92 |      100 |   66.66 |      92 | 21-26             
  configure.ts     |    14.7 |      100 |       0 |    14.7 | 18-21,23-84       
  pairing.ts       |   26.31 |      100 |       0 |   26.31 | ...30,40-50,52-65 
  pidfile.ts       |   96.34 |    86.95 |     100 |   96.34 | 49,59,91          
  start.ts         |   30.98 |       52 |   69.23 |   30.98 | ...72-475,484-486 
  status.ts        |   17.85 |      100 |       0 |   17.85 | 15-26,32-76       
  stop.ts          |      20 |      100 |       0 |      20 | 14-48             
 ...nds/extensions |   84.89 |    88.52 |   81.81 |   84.89 |                   
  consent.ts       |   71.65 |    89.28 |   42.85 |   71.65 | ...85-141,156-162 
  disable.ts       |     100 |      100 |     100 |     100 |                   
  enable.ts        |     100 |      100 |     100 |     100 |                   
  install.ts       |    75.6 |    66.66 |   66.66 |    75.6 | ...39-142,145-153 
  link.ts          |     100 |      100 |     100 |     100 |                   
  list.ts          |     100 |      100 |     100 |     100 |                   
  new.ts           |     100 |      100 |     100 |     100 |                   
  settings.ts      |   99.15 |      100 |   83.33 |   99.15 | 151               
  uninstall.ts     |    37.5 |      100 |   33.33 |    37.5 | 23-45,57-64,67-70 
  update.ts        |   96.32 |      100 |     100 |   96.32 | 101-105           
  utils.ts         |   65.06 |    31.25 |     100 |   65.06 | ...85,87-91,93-97 
 ...les/mcp-server |       0 |        0 |       0 |       0 |                   
  example.ts       |       0 |        0 |       0 |       0 | 1-60              
 src/commands/mcp  |   92.29 |    86.08 |   88.88 |   92.29 |                   
  add.ts           |     100 |    98.03 |     100 |     100 | 293               
  list.ts          |   91.22 |    80.76 |      80 |   91.22 | ...19-121,146-147 
  reconnect.ts     |   76.72 |    71.42 |   85.71 |   76.72 | 35-48,153-175     
  remove.ts        |     100 |       80 |     100 |     100 | 21-25             
 ...ommands/review |   11.57 |      100 |       0 |   11.57 |                   
  cleanup.ts       |   17.94 |      100 |       0 |   17.94 | ...01-106,108-109 
  deterministic.ts |   13.75 |      100 |       0 |   13.75 | ...22-738,740-741 
  fetch-pr.ts      |   11.36 |      100 |       0 |   11.36 | ...80-201,203-204 
  load-rules.ts    |   11.32 |      100 |       0 |   11.32 | ...41-153,155-156 
  pr-context.ts    |    6.22 |      100 |       0 |    6.22 | ...97-312,314-315 
  presubmit.ts     |    9.35 |      100 |       0 |    9.35 | ...62-287,289-290 
 ...nds/review/lib |      30 |      100 |       0 |      30 |                   
  gh.ts            |   22.58 |      100 |       0 |   22.58 | ...49,53-54,62-69 
  git.ts           |   22.72 |      100 |       0 |   22.72 | 15-18,29-39,43-44 
  paths.ts         |   52.94 |      100 |       0 |   52.94 | ...26,37-38,42-43 
 src/config        |   92.69 |    84.68 |   89.69 |   92.69 |                   
  auth.ts          |   86.74 |    80.88 |     100 |   86.74 | ...40-241,257-258 
  config.ts        |   86.59 |     83.1 |   81.48 |   86.59 | ...1962,1964-1972 
  keyBindings.ts   |   96.87 |       50 |     100 |   96.87 | 201-204           
  ...ngsAdapter.ts |     100 |    94.11 |     100 |     100 | 64                
  ...idersScope.ts |      92 |       90 |     100 |      92 | 11-12             
  sandboxConfig.ts |   61.64 |    71.87 |   66.66 |   61.64 | ...54-68,73,77-89 
  settings.ts      |   86.98 |    88.23 |   89.74 |   86.98 | ...1322,1337-1340 
  ...ingsSchema.ts |     100 |      100 |     100 |     100 |                   
  ...tedFolders.ts |   96.22 |       94 |     100 |   96.22 | ...95-197,212-213 
 ...nfig/migration |   94.89 |    78.94 |   83.33 |   94.89 |                   
  index.ts         |   94.87 |    88.88 |     100 |   94.87 | 91-92             
  scheduler.ts     |   96.55 |    77.77 |     100 |   96.55 | 19-20             
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ation/versions |   94.74 |       96 |     100 |   94.74 |                   
  ...-v2-shared.ts |     100 |      100 |     100 |     100 |                   
  v1-to-v2.ts      |   81.75 |    90.19 |     100 |   81.75 | ...28-229,231-247 
  v2-to-v3.ts      |     100 |      100 |     100 |     100 |                   
  v3-to-v4.ts      |     100 |      100 |     100 |     100 |                   
 src/core          |     100 |      100 |     100 |     100 |                   
  auth.ts          |     100 |      100 |     100 |     100 |                   
  initializer.ts   |     100 |      100 |     100 |     100 |                   
  theme.ts         |     100 |      100 |     100 |     100 |                   
 src/dualOutput    |   63.09 |    64.51 |   55.55 |   63.09 |                   
  ...tputBridge.ts |   62.94 |    65.51 |   56.25 |   62.94 | ...22-323,331-334 
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/export        |       0 |        0 |       0 |       0 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-7               
 src/generated     |     100 |      100 |     100 |     100 |                   
  git-commit.ts    |     100 |      100 |     100 |     100 |                   
 src/i18n          |   81.47 |    75.94 |   65.71 |   81.47 |                   
  index.ts         |   63.68 |    69.56 |   53.84 |   63.68 | ...70-271,281-286 
  languages.ts     |   96.92 |    86.66 |     100 |   96.92 | 134-135,167,184   
  ...nslateKeys.ts |     100 |      100 |     100 |     100 |                   
  ...lationDict.ts |   93.33 |    66.66 |     100 |   93.33 | 15                
 src/i18n/locales  |     100 |      100 |     100 |     100 |                   
  ca.js            |     100 |      100 |     100 |     100 |                   
  de.js            |     100 |      100 |     100 |     100 |                   
  en.js            |     100 |      100 |     100 |     100 |                   
  fr.js            |     100 |      100 |     100 |     100 |                   
  ja.js            |     100 |      100 |     100 |     100 |                   
  pt.js            |     100 |      100 |     100 |     100 |                   
  ru.js            |     100 |      100 |     100 |     100 |                   
  zh-TW.js         |     100 |      100 |     100 |     100 |                   
  zh.js            |     100 |      100 |     100 |     100 |                   
 ...nonInteractive |   72.57 |    71.12 |   74.07 |   72.57 |                   
  session.ts       |   76.64 |     69.4 |   85.71 |   76.64 | ...23-824,833-843 
  types.ts         |    42.5 |      100 |   33.33 |    42.5 | ...90-591,594-595 
 ...active/control |   76.79 |    88.23 |      80 |   76.79 |                   
  ...rolContext.ts |    6.89 |        0 |       0 |    6.89 | 50-86             
  ...Dispatcher.ts |   91.66 |    91.83 |   88.88 |   91.66 | ...54-372,388,391 
  ...rolService.ts |       8 |        0 |       0 |       8 | 46-179            
 ...ol/controllers |   27.25 |    35.71 |   36.66 |   27.25 |                   
  ...Controller.ts |   36.97 |       80 |      80 |   36.97 | ...15-117,127-210 
  ...Controller.ts |       0 |        0 |       0 |       0 | 1-56              
  ...Controller.ts |    33.7 |    34.48 |   44.44 |    33.7 | ...57-466,481-486 
  ...Controller.ts |   14.06 |      100 |       0 |   14.06 | ...82-117,130-133 
  ...Controller.ts |   21.97 |    28.57 |   27.27 |   21.97 | ...39-451,460-489 
 .../control/types |       0 |        0 |       0 |       0 |                   
  serviceAPIs.ts   |       0 |        0 |       0 |       0 | 1                 
 ...Interactive/io |   98.01 |    93.77 |   95.23 |   98.01 |                   
  ...putAdapter.ts |   97.89 |    92.82 |   98.07 |   97.89 | ...1303,1398-1399 
  ...putAdapter.ts |      96 |     90.9 |   85.71 |      96 | 51-52             
  ...nputReader.ts |     100 |    94.73 |     100 |     100 | 67                
  ...putAdapter.ts |   98.38 |      100 |   90.47 |   98.38 | 83-84,124-125     
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/patches       |       0 |        0 |       0 |       0 |                   
  is-in-ci.ts      |       0 |        0 |       0 |       0 | 1-17              
 src/remoteInput   |   86.98 |       75 |   85.71 |   86.98 |                   
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  ...putWatcher.ts |   88.12 |    76.08 |   91.66 |   88.12 | ...21-222,233-236 
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/serve         |   79.44 |    78.92 |   92.93 |   79.44 |                   
  auth.ts          |   88.49 |    88.63 |     100 |   88.49 | ...49-150,153-155 
  capabilities.ts  |     100 |     90.9 |     100 |     100 | 264               
  ...usProvider.ts |   67.01 |    51.42 |     100 |   67.01 | ...40-245,278-286 
  debugMode.ts     |     100 |      100 |     100 |     100 |                   
  demo.ts          |     100 |      100 |     100 |     100 |                   
  envSnapshot.ts   |    92.3 |       84 |     100 |    92.3 | 108-111,170-177   
  eventBus.ts      |     100 |      100 |     100 |     100 |                   
  httpAcpBridge.ts |   79.99 |    79.16 |   96.47 |   79.99 | ...4270,4301-4342 
  ...oryChannel.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-106             
  loopbackBinds.ts |     100 |      100 |     100 |     100 |                   
  runQwenServe.ts  |   73.98 |    87.83 |   55.55 |   73.98 | ...94-710,735-737 
  server.ts        |   86.18 |    82.94 |   90.62 |   86.18 | ...2478,2543-2552 
  status.ts        |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...paceAgents.ts |   64.87 |    70.45 |    90.9 |   64.87 | ...1306,1316-1326 
  ...paceMemory.ts |   87.13 |    78.46 |     100 |   87.13 | ...54-361,421-428 
 src/serve/auth    |   86.54 |    78.75 |   93.75 |   86.54 |                   
  deviceFlow.ts    |   96.33 |    79.51 |    97.5 |   96.33 | ...1526,1630,1700 
  ...owProvider.ts |   45.23 |    74.07 |      75 |   45.23 | ...90-359,375,379 
 src/serve/fs      |   84.85 |    79.75 |     100 |   84.85 |                   
  audit.ts         |     100 |    96.15 |     100 |     100 | 201               
  errors.ts        |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  paths.ts         |   77.82 |    77.08 |     100 |   77.82 | ...64,493-497,510 
  policy.ts        |   90.32 |    89.18 |     100 |   90.32 | 142-150           
  ...FileSystem.ts |   83.55 |    76.22 |     100 |   83.55 | ...1859,1886-1887 
 src/serve/routes  |   89.41 |       70 |     100 |   89.41 |                   
  ...ceFileRead.ts |   94.41 |    76.92 |     100 |   94.41 | ...28-329,390-392 
  ...eFileWrite.ts |    82.1 |    60.52 |     100 |    82.1 | ...42-244,247-249 
 src/services      |   91.66 |    91.21 |   97.56 |   91.66 |                   
  ...mandLoader.ts |     100 |    93.75 |     100 |     100 | 92                
  ...killLoader.ts |     100 |    96.15 |     100 |     100 | 47                
  ...andService.ts |    98.7 |      100 |     100 |    98.7 | 107               
  ...mandLoader.ts |   86.83 |    83.87 |     100 |   86.83 | ...30-335,340-345 
  ...omptLoader.ts |   75.84 |    80.64 |   83.33 |   75.84 | ...10-211,277-278 
  ...mandLoader.ts |     100 |      100 |     100 |     100 |                   
  ...nd-factory.ts |   91.42 |    91.66 |     100 |   91.42 | 128,137-144       
  ...ation-tool.ts |     100 |    95.45 |     100 |     100 | 125               
  ...ndMetadata.ts |   98.21 |    96.66 |     100 |   98.21 | 83,87             
  commandUtils.ts  |      96 |     90.9 |     100 |      96 | 48                
  ...and-parser.ts |   90.69 |    85.71 |     100 |   90.69 | 63-66             
  ...ionService.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...ght/generators |    88.3 |    85.49 |   92.59 |    88.3 |                   
  DataProcessor.ts |   88.22 |    85.48 |      95 |   88.22 | ...1341,1345-1352 
  ...tGenerator.ts |   98.21 |    85.71 |     100 |   98.21 | 46                
  ...teRenderer.ts |   45.45 |      100 |       0 |   45.45 | 13-51             
 .../insight/types |       0 |       50 |      50 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 | 1                 
 ...mpt-processors |   97.27 |    94.04 |     100 |   97.27 |                   
  ...tProcessor.ts |     100 |      100 |     100 |     100 |                   
  ...eProcessor.ts |   94.52 |    84.21 |     100 |   94.52 | 46-47,93-94       
  ...tionParser.ts |     100 |      100 |     100 |     100 |                   
  ...lProcessor.ts |   97.41 |    95.65 |     100 |   97.41 | 95-98             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services/tips |   97.35 |    84.84 |     100 |   97.35 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  tipHistory.ts    |   92.59 |       70 |     100 |   92.59 | ...24,146,153,162 
  tipRegistry.ts   |     100 |      100 |     100 |     100 |                   
  tipScheduler.ts  |     100 |    91.66 |     100 |     100 | 55                
 src/startup       |   66.82 |    78.94 |   66.66 |   66.82 |                   
  ...reeStartup.ts |   66.82 |    78.94 |   66.66 |   66.82 | ...08-312,363-426 
 src/test-utils    |   93.75 |    83.33 |      80 |   93.75 |                   
  ...omMatchers.ts |   69.69 |       50 |      50 |   69.69 | 32-35,37-39,45-47 
  ...andContext.ts |     100 |      100 |     100 |     100 |                   
  render.tsx       |     100 |      100 |     100 |     100 |                   
 src/ui            |   64.71 |    73.17 |   59.67 |   64.71 |                   
  App.tsx          |   33.33 |       75 |   33.33 |   33.33 | 32-86             
  AppContainer.tsx |   63.54 |    64.74 |      50 |   63.54 | ...3191,3195-3199 
  ...tionNudge.tsx |    9.58 |      100 |       0 |    9.58 | 24-94             
  ...ackDialog.tsx |   29.23 |      100 |       0 |   29.23 | 25-75             
  ...tionNudge.tsx |    7.69 |      100 |       0 |    7.69 | 25-103            
  colors.ts        |      60 |      100 |   35.29 |      60 | ...52,54-55,60-61 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  keyMatchers.ts   |   95.91 |    97.14 |     100 |   95.91 | 25-26             
  ...tic-colors.ts |     100 |      100 |     100 |     100 |                   
  ...inePresets.ts |   98.28 |    89.87 |     100 |   98.28 | ...34,261,420-422 
  textConstants.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/auth       |   59.16 |    65.94 |   51.11 |   59.16 |                   
  AuthDialog.tsx   |   62.87 |     42.1 |   18.18 |   62.87 | ...03,310-332,336 
  ...nProgress.tsx |       0 |        0 |       0 |       0 | 1-64              
  ...etupSteps.tsx |   60.03 |    70.37 |      56 |   60.03 | ...87,791,800,803 
  useAuth.ts       |   94.55 |    73.52 |     100 |   94.55 | ...19-220,239-245 
  ...rSetupFlow.ts |   43.52 |    33.33 |      50 |   43.52 | ...72-393,410-453 
 src/ui/commands   |   75.23 |    81.19 |   84.17 |   75.23 |                   
  aboutCommand.ts  |     100 |      100 |     100 |     100 |                   
  agentsCommand.ts |   83.78 |      100 |      60 |   83.78 | 30-32,42-44       
  ...odeCommand.ts |   89.04 |    81.25 |     100 |   89.04 | 91-92,94-99       
  arenaCommand.ts  |   62.81 |    58.73 |   65.21 |   62.81 | ...91-596,681-689 
  authCommand.ts   |     100 |      100 |     100 |     100 |                   
  branchCommand.ts |     100 |      100 |     100 |     100 |                   
  btwCommand.ts    |    96.1 |    74.07 |     100 |    96.1 | 149-154           
  bugCommand.ts    |   81.13 |    71.42 |     100 |   81.13 | 60-69             
  clearCommand.ts  |      92 |    76.47 |     100 |      92 | 43-44,72-73,91-92 
  ...essCommand.ts |   67.95 |    55.88 |      75 |   67.95 | ...86-187,201-204 
  ...extCommand.ts |   65.06 |    67.24 |   84.61 |   65.06 | ...39-574,585-586 
  copyCommand.ts   |   98.28 |    94.89 |     100 |   98.28 | ...80,280,321,327 
  deleteCommand.ts |     100 |      100 |     100 |     100 |                   
  diffCommand.ts   |     100 |     87.5 |     100 |     100 | ...61,224-225,238 
  ...ryCommand.tsx |   77.02 |    79.03 |   88.88 |   77.02 | ...65-270,324-332 
  docsCommand.ts   |     100 |    88.88 |     100 |     100 | 25                
  doctorCommand.ts |   60.93 |    87.06 |    87.5 |   60.93 | ...66-367,440-660 
  dreamCommand.ts  |      75 |    66.66 |   66.66 |      75 | 22-27,44-47       
  editorCommand.ts |     100 |      100 |     100 |     100 |                   
  exportCommand.ts |   98.25 |    91.02 |     100 |   98.25 | ...81,198-199,364 
  ...onsCommand.ts |   49.33 |     90.9 |   63.63 |   49.33 | ...06-110,163-215 
  forgetCommand.ts |   26.82 |      100 |      50 |   26.82 | 18-51             
  goalCommand.ts   |   91.41 |    84.44 |      90 |   91.41 | ...86-189,201-204 
  helpCommand.ts   |     100 |      100 |     100 |     100 |                   
  hooksCommand.ts  |   81.13 |    65.71 |   85.71 |   81.13 | ...,86-93,131-132 
  ideCommand.ts    |   60.75 |    64.28 |   41.17 |   60.75 | ...05-306,310-324 
  initCommand.ts   |   84.33 |    72.72 |     100 |   84.33 | 68,82-87,89-94    
  ...ghtCommand.ts |   74.56 |    68.42 |     100 |   74.56 | ...31-245,250-273 
  ...ageCommand.ts |   92.17 |    82.69 |     100 |   92.17 | ...43,164,173-183 
  lspCommand.ts    |     100 |    86.95 |     100 |     100 | 31,101-102        
  mcpCommand.ts    |     100 |      100 |     100 |     100 |                   
  memoryCommand.ts |     100 |      100 |     100 |     100 |                   
  modelCommand.ts  |   75.09 |    78.18 |      75 |   75.09 | ...20-225,262-267 
  ...onsCommand.ts |     100 |      100 |     100 |     100 |                   
  planCommand.ts   |   78.82 |    76.92 |     100 |   78.82 | 30-35,51-56,68-73 
  quitCommand.ts   |     100 |      100 |     100 |     100 |                   
  recapCommand.ts  |   21.81 |      100 |      50 |   21.81 | 24-73             
  ...berCommand.ts |   32.43 |      100 |      50 |   32.43 | 23-57             
  renameCommand.ts |   85.71 |    86.04 |     100 |   85.71 | ...02-209,216-221 
  ...oreCommand.ts |    92.3 |    87.87 |     100 |    92.3 | ...,83-88,129-130 
  resumeCommand.ts |     100 |      100 |     100 |     100 |                   
  rewindCommand.ts |      80 |      100 |      50 |      80 | 19-21             
  ...ngsCommand.ts |     100 |      100 |     100 |     100 |                   
  ...hubCommand.ts |   81.43 |    65.21 |      80 |   81.43 | ...70-173,176-179 
  skillsCommand.ts |   36.52 |       50 |      50 |   36.52 | ...98-114,117-144 
  statsCommand.ts  |   88.19 |    84.21 |     100 |   88.19 | ...,58-61,143-146 
  ...ineCommand.ts |     100 |      100 |     100 |     100 |                   
  ...aryCommand.ts |    6.46 |      100 |      50 |    6.46 | 31-329            
  tasksCommand.ts  |   77.22 |    72.13 |     100 |   77.22 | ...46-150,172-177 
  ...tupCommand.ts |     100 |      100 |     100 |     100 |                   
  themeCommand.ts  |     100 |      100 |     100 |     100 |                   
  toolsCommand.ts  |     100 |      100 |     100 |     100 |                   
  trustCommand.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
  vimCommand.ts    |   54.54 |      100 |      50 |   54.54 | 19-29             
 src/ui/components |   63.97 |    76.69 |   64.78 |   63.97 |                   
  AboutBox.tsx     |     100 |      100 |     100 |     100 |                   
  AnsiOutput.tsx   |   65.57 |      100 |      50 |   65.57 | 69-90             
  ApiKeyInput.tsx  |       0 |        0 |       0 |       0 | 1-97              
  AppHeader.tsx    |   89.06 |       75 |     100 |   89.06 | 37,39-44,46       
  ...odeDialog.tsx |      10 |      100 |       0 |      10 | ...9,42-54,57-189 
  AsciiArt.ts      |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |   16.27 |      100 |       0 |   16.27 | 19-58             
  ...TextInput.tsx |    83.8 |    76.92 |     100 |    83.8 | ...38,252-254,356 
  Composer.tsx     |    81.6 |     64.7 |     100 |    81.6 | ...90,108,160,173 
  ...entPrompt.tsx |     100 |      100 |     100 |     100 |                   
  ...ryDisplay.tsx |   75.89 |    62.06 |     100 |   75.89 | ...,88,93-108,113 
  ...geDisplay.tsx |   68.42 |    57.14 |     100 |   68.42 | 16-17,31-32,42-50 
  ...ification.tsx |   28.57 |      100 |       0 |   28.57 | 16-36             
  ...gProfiler.tsx |       0 |        0 |       0 |       0 | 1-36              
  ...ogManager.tsx |   11.98 |      100 |       0 |   11.98 | 65-508            
  DiffDialog.tsx   |    2.47 |      100 |       0 |    2.47 | 68-732            
  ...ngsDialog.tsx |    8.44 |      100 |       0 |    8.44 | 37-195            
  ExitWarning.tsx  |     100 |      100 |     100 |     100 |                   
  ...hProgress.tsx |    87.8 |    33.33 |     100 |    87.8 | 28-31,56          
  ...ustDialog.tsx |     100 |      100 |     100 |     100 |                   
  Footer.tsx       |   77.48 |    54.76 |     100 |   77.48 | ...41,165,186-191 
  ...ngSpinner.tsx |   68.42 |       80 |      50 |   68.42 | 35-52,73,80-81    
  GoalPill.tsx     |   76.19 |    81.81 |     100 |   76.19 | 24-30,46-50       
  Header.tsx       |   98.62 |    94.28 |     100 |   98.62 | 162,164           
  Help.tsx         |   98.32 |       90 |     100 |   98.32 | ...24,381,447-448 
  ...emDisplay.tsx |    61.7 |       36 |     100 |    61.7 | ...42,345,348-354 
  ...ngeDialog.tsx |     100 |      100 |     100 |     100 |                   
  InputPrompt.tsx  |   81.38 |     80.2 |   83.33 |   81.38 | ...1601,1625,1676 
  ...Shortcuts.tsx |   20.87 |      100 |       0 |   20.87 | ...6,49-51,67-125 
  ...Indicator.tsx |     100 |    91.42 |     100 |     100 | 65,74             
  ...firmation.tsx |   91.42 |      100 |      50 |   91.42 | 26-31             
  MainContent.tsx  |   84.54 |    81.15 |   66.66 |   84.54 | ...87-290,347-351 
  MemoryDialog.tsx |   61.87 |    76.05 |    62.5 |   61.87 | ...72,391,428-430 
  ...geDisplay.tsx |       0 |        0 |       0 |       0 | 1-41              
  ModelDialog.tsx  |   85.54 |    68.93 |     100 |   85.54 | ...69-585,642-646 
  ...tsDisplay.tsx |     100 |    97.22 |     100 |     100 | 270               
  ...fications.tsx |   18.18 |      100 |       0 |   18.18 | 15-58             
  ...onsDialog.tsx |    2.13 |      100 |       0 |    2.13 | 62-133,148-1004   
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...icePrompt.tsx |   92.64 |    85.71 |     100 |   92.64 | 102-106,134-139   
  PrepareLabel.tsx |   91.66 |    77.27 |     100 |   91.66 | 73-75,77-79,110   
  ...atePrompt.tsx |    8.57 |      100 |       0 |    8.57 | 24-55,58-134      
  ...geDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ngDisplay.tsx |   21.42 |      100 |       0 |   21.42 | 13-39             
  ...hProgress.tsx |   85.25 |    88.46 |     100 |   85.25 | 121-147           
  ...dSelector.tsx |   41.26 |    61.53 |   71.42 |   41.26 | ...74-472,476-520 
  ...ionPicker.tsx |   83.66 |    72.13 |     100 |   83.66 | ...96,402,444-466 
  ...onPreview.tsx |   92.42 |    84.37 |     100 |   92.42 | ...,70-71,143-145 
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...putPrompt.tsx |   72.56 |       80 |      40 |   72.56 | ...06-109,114-117 
  ...tedDialog.tsx |     100 |      100 |     100 |     100 |                   
  ...ngsDialog.tsx |   66.27 |    71.16 |      75 |   66.27 | ...12-820,826-827 
  ...ionDialog.tsx |    87.8 |      100 |   33.33 |    87.8 | 36-39,44-51       
  ...putPrompt.tsx |    15.9 |      100 |       0 |    15.9 | 20-63             
  ...Indicator.tsx |   57.14 |      100 |       0 |   57.14 | 12-15             
  ...MoreLines.tsx |      28 |      100 |       0 |      28 | 18-40             
  ...ionPicker.tsx |   17.59 |      100 |       0 |   17.59 | 55-172            
  StatsDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ineDialog.tsx |    93.5 |    83.63 |     100 |    93.5 | ...05,267,287-289 
  ...yTodoList.tsx |   96.33 |    88.23 |     100 |   96.33 | 137-140           
  ...nsDisplay.tsx |   87.25 |       64 |     100 |   87.25 | ...47-149,156-158 
  ThemeDialog.tsx  |   89.95 |    46.15 |      75 |   89.95 | ...71-173,243-245 
  Tips.tsx         |   93.54 |       75 |     100 |   93.54 | 39-40             
  TodoDisplay.tsx  |     100 |      100 |     100 |     100 |                   
  ...tsDisplay.tsx |     100 |     87.5 |     100 |     100 | 31-32             
  TrustDialog.tsx  |     100 |    81.81 |     100 |     100 | 71-86             
  ...ification.tsx |   36.36 |      100 |       0 |   36.36 | 15-22             
  ...ackDialog.tsx |    7.84 |      100 |       0 |    7.84 | 24-134            
  ...xitDialog.tsx |   80.36 |    43.47 |      60 |   80.36 | ...24-238,248-251 
  ...odeVisuals.ts |   91.42 |    64.28 |     100 |   91.42 | 15,21,24          
 ...nts/agent-view |   38.45 |    70.83 |   36.36 |   38.45 |                   
  ...atContent.tsx |    8.79 |      100 |       0 |    8.79 | 53-265,271-273    
  ...tChatView.tsx |   21.05 |      100 |       0 |   21.05 | 21-39             
  ...tComposer.tsx |   10.74 |      100 |       0 |   10.74 | 59-310            
  AgentFooter.tsx  |   17.07 |      100 |       0 |   17.07 | 28-66             
  AgentHeader.tsx  |   15.38 |      100 |       0 |   15.38 | 27-64             
  AgentTabBar.tsx  |    87.8 |    27.27 |     100 |    87.8 | ...,85,95-103,121 
  ...oryAdapter.ts |     100 |    91.83 |     100 |     100 | 103,109-110,138   
  index.ts         |       0 |        0 |       0 |       0 | 1-12              
 ...mponents/arena |   45.59 |    70.53 |   60.86 |   45.59 |                   
  ArenaCards.tsx   |   73.06 |    71.79 |   85.71 |   73.06 | ...83-185,321-326 
  ...ectDialog.tsx |   83.48 |    69.86 |   88.88 |   83.48 | ...88-392,409-410 
  ...artDialog.tsx |    9.92 |      100 |       0 |    9.92 | 27-164            
  ...tusDialog.tsx |    5.63 |      100 |       0 |    5.63 | 33-75,80-288      
  ...topDialog.tsx |    6.17 |      100 |       0 |    6.17 | 33-213            
 ...ackground-view |   75.84 |    82.71 |   85.29 |   75.84 |                   
  ...sksDialog.tsx |   71.38 |    80.58 |   76.19 |   71.38 | ...1132,1208-1210 
  ...TasksPill.tsx |   63.75 |    86.95 |     100 |   63.75 | 44,86-106,114-122 
  ...gentPanel.tsx |    97.4 |    86.31 |     100 |    97.4 | 123,434-438       
 ...nts/extensions |   45.28 |    33.33 |      60 |   45.28 |                   
  ...gerDialog.tsx |   44.31 |    34.14 |      75 |   44.31 | ...71-480,483-488 
  index.ts         |       0 |        0 |       0 |       0 | 1-9               
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...tensions/steps |   54.88 |    94.23 |   66.66 |   54.88 |                   
  ...ctionStep.tsx |   95.12 |    92.85 |   85.71 |   95.12 | 84-86,89          
  ...etailStep.tsx |    6.18 |      100 |       0 |    6.18 | 20-131            
  ...nListStep.tsx |   88.43 |    94.73 |      80 |   88.43 | 52-53,59-72,106   
  ...electStep.tsx |   13.46 |      100 |       0 |   13.46 | 20-70             
  ...nfirmStep.tsx |   19.56 |      100 |       0 |   19.56 | 23-65             
  index.ts         |     100 |      100 |     100 |     100 |                   
 ...mponents/hooks |   86.63 |    81.37 |   91.89 |   86.63 |                   
  ...rListBody.tsx |   95.29 |    85.18 |     100 |   95.29 | 95-98             
  ...etailStep.tsx |   75.32 |    71.42 |      60 |   75.32 | ...56-169,173-186 
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...rListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...entHeader.tsx |     100 |    85.71 |     100 |     100 | 47                
  ...rListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...abledStep.tsx |     100 |      100 |     100 |     100 |                   
  ...sListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...entDialog.tsx |   72.29 |    70.49 |     100 |   72.29 | ...51,563-568,572 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-13              
  ...erGrouping.ts |     100 |      100 |     100 |     100 |                   
  sourceLabels.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...components/mcp |   20.98 |    86.36 |   83.33 |   20.98 |                   
  ...ealthPill.tsx |   68.42 |    85.71 |     100 |   68.42 | 40-46             
  ...entDialog.tsx |    3.64 |      100 |       0 |    3.64 | 41-717            
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-30              
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |   95.83 |    88.88 |     100 |   95.83 | 16,20,109-110     
 ...ents/mcp/steps |   26.74 |    54.54 |   42.85 |   26.74 |                   
  ...icateStep.tsx |    5.88 |      100 |       0 |    5.88 | 40-55,58-296      
  ...electStep.tsx |   10.95 |      100 |       0 |   10.95 | 16-88             
  ...etailStep.tsx |    5.26 |      100 |       0 |    5.26 | 31-247            
  ...rListStep.tsx |   75.18 |    59.37 |     100 |   75.18 | ...53-158,169-173 
  ...etailStep.tsx |   10.41 |      100 |       0 |   10.41 | ...1,67-79,82-139 
  ToolListStep.tsx |   69.02 |       50 |     100 |   69.02 | ...22,125,134-143 
 ...nents/messages |   83.23 |    80.55 |    75.6 |   83.23 |                   
  ...ionDialog.tsx |   80.84 |     77.6 |    62.5 |   80.84 | ...98,516,534-536 
  BtwMessage.tsx   |     100 |      100 |     100 |     100 |                   
  ...upDisplay.tsx |   97.67 |    83.72 |     100 |   97.67 | 119,142,150       
  ...onMessage.tsx |   91.93 |    82.35 |     100 |   91.93 | 57-59,61,63       
  ...nMessages.tsx |   79.06 |      100 |      70 |   79.06 | ...51-264,268-280 
  DiffRenderer.tsx |   93.19 |    86.17 |     100 |   93.19 | ...09,237-238,304 
  ...tsDisplay.tsx |   97.82 |    77.27 |     100 |   97.82 | 87,89             
  ...usMessage.tsx |   76.31 |     42.1 |   66.66 |   76.31 | ...99,101,124,155 
  ...tsDisplay.tsx |    95.1 |    88.05 |     100 |    95.1 | ...29,131,164-169 
  ...ssMessage.tsx |    12.5 |      100 |       0 |    12.5 | 18-59             
  ...edMessage.tsx |   16.66 |      100 |       0 |   16.66 | 22-38             
  ...sMessages.tsx |   55.67 |       40 |   28.57 |   55.67 | ...20-125,133-145 
  ...ryMessage.tsx |   14.28 |      100 |       0 |   14.28 | 23-62             
  ...onMessage.tsx |   81.98 |     72.6 |   33.33 |   81.98 | ...65-467,474-476 
  ...upMessage.tsx |   82.63 |    92.85 |     100 |   82.63 | ...85-412,434-449 
  ToolMessage.tsx  |   88.84 |    75.71 |    92.3 |   88.84 | ...44-749,776-778 
 ...ponents/shared |    86.3 |    80.41 |   95.45 |    86.3 |                   
  ...ctionList.tsx |   99.03 |    95.65 |     100 |   99.03 | 85                
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  EnumSelector.tsx |     100 |    96.42 |     100 |     100 | 58                
  MaxSizedBox.tsx  |   83.01 |    86.25 |   88.88 |   83.01 | ...12-513,618-619 
  MultiSelect.tsx  |    93.5 |       75 |     100 |    93.5 | ...39,195-197,207 
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  ...eSelector.tsx |     100 |       60 |     100 |     100 | 40-45             
  ...lableList.tsx |   76.25 |    81.81 |     100 |   76.25 | 44-58,65-68       
  StaticRender.tsx |   72.72 |      100 |     100 |   72.72 | 31-33             
  TextInput.tsx    |    80.8 |    66.07 |      80 |    80.8 | ...36-240,252-258 
  ...apsedTime.tsx |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |     100 |      100 |     100 |     100 |                   
  ...lizedList.tsx |   84.26 |    80.88 |      90 |   84.26 | ...68-696,743-765 
  text-buffer.ts   |    85.7 |    80.81 |   97.91 |    85.7 | ...2604,2702-2703 
  ...er-actions.ts |   86.71 |    67.79 |     100 |   86.71 | ...07-608,809-811 
 ...ents/subagents |   30.87 |        0 |       0 |   30.87 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-11              
  reducers.tsx     |    12.1 |      100 |       0 |    12.1 | 33-190            
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |   10.95 |      100 |       0 |   10.95 | ...1,56-57,60-102 
 ...bagents/create |    9.13 |      100 |       0 |    9.13 |                   
  ...ionWizard.tsx |    7.28 |      100 |       0 |    7.28 | 34-299            
  ...rSelector.tsx |   14.75 |      100 |       0 |   14.75 | 26-85             
  ...onSummary.tsx |    4.26 |      100 |       0 |    4.26 | 27-331            
  ...tionInput.tsx |    8.63 |      100 |       0 |    8.63 | 23-177            
  ...dSelector.tsx |   33.33 |      100 |       0 |   33.33 | 20-21,26-27,36-63 
  ...nSelector.tsx |    37.5 |      100 |       0 |    37.5 | 20-21,26-27,36-58 
  ...EntryStep.tsx |   12.76 |      100 |       0 |   12.76 | 34-78             
  ToolSelector.tsx |    4.16 |      100 |       0 |    4.16 | 31-253            
 ...bagents/manage |   21.51 |    59.52 |   27.27 |   21.51 |                   
  ...ctionStep.tsx |   10.25 |      100 |       0 |   10.25 | 21-103            
  ...eleteStep.tsx |   20.93 |      100 |       0 |   20.93 | 23-62             
  ...tEditStep.tsx |   25.53 |      100 |       0 |   25.53 | ...2,37-38,51-124 
  ...ctionStep.tsx |   35.42 |    59.52 |     100 |   35.42 | ...20-432,437-439 
  ...iewerStep.tsx |   13.72 |      100 |       0 |   13.72 | 18-73             
  ...gerDialog.tsx |    6.74 |      100 |       0 |    6.74 | 35-341            
 ...mponents/views |   70.21 |    67.32 |    64.7 |   70.21 |                   
  ContextUsage.tsx |   70.88 |    63.88 |      80 |   70.88 | ...20-426,463-557 
  DoctorReport.tsx |     9.8 |      100 |       0 |     9.8 | 25-54,57-131      
  ...sionsList.tsx |   87.69 |    73.68 |     100 |   87.69 | 65-72             
  McpStatus.tsx    |   89.53 |    60.52 |     100 |   89.53 | ...72,175-177,262 
  SkillsList.tsx   |   27.27 |      100 |       0 |   27.27 | 18-35             
  ToolsList.tsx    |     100 |      100 |     100 |     100 |                   
 src/ui/contexts   |   77.64 |    77.53 |   82.14 |   77.64 |                   
  ...ewContext.tsx |    64.7 |    85.71 |      50 |    64.7 | ...22-225,231-241 
  AppContext.tsx   |      80 |       50 |     100 |      80 | 19-20             
  ...ewContext.tsx |    93.3 |    64.28 |      50 |    93.3 | ...35-236,263-267 
  ...deContext.tsx |     100 |      100 |     100 |     100 |                   
  ...igContext.tsx |   81.81 |       50 |     100 |   81.81 | 15-16             
  ...ssContext.tsx |   82.35 |    82.84 |     100 |   82.35 | ...1159,1165-1167 
  ...owContext.tsx |   91.07 |    81.81 |     100 |   91.07 | 47-48,60-62       
  ...deContext.tsx |     100 |      100 |      50 |     100 |                   
  ...onContext.tsx |   43.28 |     62.5 |    62.5 |   43.28 | ...56-259,263-266 
  ...gsContext.tsx |   83.33 |       50 |     100 |   83.33 | 17-18             
  ...usContext.tsx |     100 |      100 |     100 |     100 |                   
  ...ngContext.tsx |   71.42 |       50 |     100 |   71.42 | 17-20             
  ...utContext.tsx |   85.71 |      100 |   66.66 |   85.71 | 13-14             
  ...nsContext.tsx |   88.23 |       50 |     100 |   88.23 | 118-119           
  ...teContext.tsx |   86.66 |       50 |     100 |   86.66 | 195-196           
  ...deContext.tsx |   76.08 |    72.72 |     100 |   76.08 | 47-48,52-59,77-78 
 src/ui/daemon     |   90.76 |    73.73 |   95.45 |   90.76 |                   
  ...TuiAdapter.ts |   90.76 |    73.73 |   95.45 |   90.76 | ...53,771-772,858 
 src/ui/editors    |   93.33 |    85.71 |   66.66 |   93.33 |                   
  ...ngsManager.ts |   93.33 |    85.71 |   66.66 |   93.33 | 49,63-64          
 src/ui/hooks      |    82.6 |    82.55 |   87.22 |    82.6 |                   
  ...dProcessor.ts |   83.12 |    82.56 |     100 |   83.12 | ...88-389,408-435 
  keyToAnsi.ts     |    3.92 |      100 |       0 |    3.92 | 19-77             
  ...dProcessor.ts |    94.8 |    70.58 |     100 |    94.8 | ...76-277,282-283 
  ...dProcessor.ts |   78.59 |     64.7 |   71.42 |   78.59 | ...06,930,951-955 
  ...amingState.ts |   12.22 |      100 |       0 |   12.22 | 54-157            
  ...agerDialog.ts |   88.23 |      100 |     100 |   88.23 | 20,24             
  ...dScrollbar.ts |     100 |      100 |     100 |     100 |                   
  ...ationFrame.ts |      32 |       60 |     100 |      32 | 42-44,51-90       
  ...odeCommand.ts |   58.82 |      100 |     100 |   58.82 | 28,33-48          
  ...enaCommand.ts |      85 |      100 |     100 |      85 | 23-24,29          
  ...aInProcess.ts |   19.81 |    66.66 |      25 |   19.81 | 57-175            
  ...Completion.ts |   92.81 |    89.09 |     100 |   92.81 | ...86-187,224-227 
  ...ifications.ts |   92.07 |    96.29 |     100 |   92.07 | 116-124           
  ...tIndicator.ts |   83.49 |    70.96 |     100 |   83.49 | ...60,168,170-178 
  ...waySummary.ts |   96.22 |    69.69 |     100 |   96.22 | 125-127,169       
  ...ndTaskView.ts |   94.21 |    76.08 |     100 |   94.21 | 122-126,213,219   
  ...chedScroll.ts |     100 |      100 |     100 |     100 |                   
  ...ketedPaste.ts |    23.8 |      100 |       0 |    23.8 | 19-37             
  ...nchCommand.ts |   94.36 |    74.35 |     100 |   94.36 | ...60,168-169,209 
  ...ompletion.tsx |   96.01 |    83.87 |     100 |   96.01 | ...22-223,225-226 
  ...dMigration.ts |   90.62 |       75 |     100 |   90.62 | 38-40             
  useCompletion.ts |    92.4 |     87.5 |     100 |    92.4 | 68-69,93-94,98-99 
  ...nitMessage.ts |     100 |      100 |     100 |     100 |                   
  ...extualTips.ts |   77.27 |       50 |     100 |   77.27 | ...2,75-79,93-101 
  ...eteCommand.ts |   78.53 |    88.57 |     100 |   78.53 | ...96-104,112-113 
  ...ialogClose.ts |   13.33 |      100 |     100 |   13.33 | 82-173            
  useDiffData.ts   |   11.62 |      100 |       0 |   11.62 | 44-87             
  ...oublePress.ts |   53.12 |       75 |     100 |   53.12 | 33-35,41-54       
  ...orSettings.ts |     100 |      100 |     100 |     100 |                   
  ...Completion.ts |   99.12 |    97.67 |     100 |   99.12 | 182-183           
  ...ionUpdates.ts |   93.45 |     92.3 |     100 |   93.45 | ...83-287,300-306 
  ...agerDialog.ts |   88.88 |      100 |     100 |   88.88 | 21,25             
  ...backDialog.ts |   57.89 |    71.42 |      50 |   57.89 | ...66-168,190-191 
  useFocus.ts      |     100 |      100 |     100 |     100 |                   
  ...olderTrust.ts |     100 |      100 |     100 |     100 |                   
  ...ggestions.tsx |   89.15 |     62.5 |      50 |   89.15 | ...22-124,149-150 
  ...miniStream.ts |   78.45 |    76.16 |   91.66 |   78.45 | ...2568,2595-2600 
  ...BranchName.ts |    90.9 |     92.3 |     100 |    90.9 | 19-20,55-58       
  ...oryManager.ts |   94.04 |    94.11 |     100 |   94.04 | 47,119-122        
  ...ooksDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...stListener.ts |     100 |      100 |     100 |     100 |                   
  ...nAuthError.ts |   76.19 |       50 |     100 |   76.19 | 39-40,43-45       
  ...putHistory.ts |   92.59 |    85.71 |     100 |   92.59 | 63-64,72,94-96    
  ...storyStore.ts |     100 |    94.11 |     100 |     100 | 69                
  useKeypress.ts   |     100 |      100 |     100 |     100 |                   
  ...rdProtocol.ts |   36.36 |      100 |       0 |   36.36 | 24-31             
  ...unchEditor.ts |    9.67 |      100 |       0 |    9.67 | 11-32,39-90       
  ...gIndicator.ts |     100 |      100 |     100 |     100 |                   
  useLogger.ts     |   21.05 |      100 |       0 |   21.05 | 15-37             
  useMCPHealth.ts  |   63.15 |       75 |      50 |   63.15 | 42-52,64-67       
  useMcpDialog.ts  |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...moryDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...oryMonitor.ts |     100 |      100 |     100 |     100 |                   
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...delCommand.ts |     100 |       75 |     100 |     100 | 22                
  ...ouseEvents.ts |   87.17 |    88.88 |   66.66 |   87.17 | 81-82,86-88       
  ...raseCycler.ts |   84.74 |    76.47 |     100 |   84.74 | ...49,52-53,69-71 
  ...rredEditor.ts |   58.33 |    22.22 |     100 |   58.33 | 23-27,29-33       
  ...derUpdates.ts |   86.49 |    77.96 |    90.9 |   86.49 | ...26,288-300,348 
  useQwenAuth.ts   |     100 |      100 |     100 |     100 |                   
  ...lScheduler.ts |    84.7 |    93.33 |     100 |    84.7 | ...71-276,372-382 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-7               
  ...umeCommand.ts |   96.96 |    83.33 |     100 |   96.96 | 101-102,131       
  ...ompletion.tsx |   90.59 |    83.33 |     100 |   90.59 | ...01,104,137-140 
  ...ectionList.ts |   96.98 |    95.74 |     100 |   96.98 | ...83-184,238-241 
  ...sionPicker.ts |   92.87 |    90.35 |     100 |   92.87 | ...99-501,503-505 
  ...earchInput.ts |     100 |      100 |     100 |     100 |                   
  ...ngsCommand.ts |   18.75 |      100 |       0 |   18.75 | 10-25             
  ...ellHistory.ts |   91.74 |    79.41 |     100 |   91.74 | ...74,122-123,133 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-73              
  ...Completion.ts |    82.7 |    85.41 |   94.73 |    82.7 | ...69-671,679-715 
  ...tateAndRef.ts |     100 |      100 |     100 |     100 |                   
  useStatusLine.ts |    96.3 |    90.97 |     100 |    96.3 | ...77-380,466-473 
  ...eateDialog.ts |   88.23 |      100 |     100 |   88.23 | 14,18             
  ...tification.ts |     100 |    85.71 |     100 |     100 | 47                
  ...alProgress.ts |   53.06 |       50 |   66.66 |   53.06 | ...53,61-68,79-85 
  ...rminalSize.ts |   76.19 |      100 |      50 |   76.19 | 21-25             
  ...emeCommand.ts |   67.01 |    29.41 |     100 |   67.01 | ...10-111,115-116 
  useTimer.ts      |   88.09 |    85.71 |     100 |   88.09 | 44-45,51-53       
  ...lMigration.ts |       0 |        0 |       0 |       0 |                   
  ...rustModify.ts |     100 |      100 |     100 |     100 |                   
  useTurnDiffs.ts  |   95.12 |    78.57 |     100 |   95.12 | 133-134,156-157   
  ...elcomeBack.ts |   87.36 |     90.9 |     100 |   87.36 | ...,94-96,114-115 
  ...reeSession.ts |   93.75 |       70 |     100 |   93.75 | 44-45,87          
  vim.ts           |   83.77 |    80.31 |     100 |   83.77 | ...55,759-767,776 
 src/ui/layouts    |   89.72 |     87.5 |     100 |   89.72 |                   
  ...AppLayout.tsx |   89.88 |     87.5 |     100 |   89.88 | 51-53,93-98       
  ...AppLayout.tsx |   89.47 |     87.5 |     100 |   89.47 | 58-63             
 src/ui/models     |   80.24 |    79.16 |   71.42 |   80.24 |                   
  ...ableModels.ts |   80.24 |    79.16 |   71.42 |   80.24 | ...,61-71,123-125 
 ...noninteractive |     100 |      100 |   14.28 |     100 |                   
  ...eractiveUi.ts |     100 |      100 |   14.28 |     100 |                   
 src/ui/state      |   94.91 |    81.81 |     100 |   94.91 |                   
  extensions.ts    |   94.91 |    81.81 |     100 |   94.91 | 68-69,88          
 src/ui/themes     |   98.53 |    70.58 |     100 |   98.53 |                   
  ansi-light.ts    |     100 |      100 |     100 |     100 |                   
  ansi.ts          |     100 |      100 |     100 |     100 |                   
  atom-one-dark.ts |     100 |      100 |     100 |     100 |                   
  ayu-light.ts     |     100 |      100 |     100 |     100 |                   
  ayu.ts           |     100 |      100 |     100 |     100 |                   
  color-utils.ts   |     100 |      100 |     100 |     100 |                   
  default-light.ts |     100 |      100 |     100 |     100 |                   
  default.ts       |     100 |      100 |     100 |     100 |                   
  ...inal-theme.ts |   88.59 |    85.96 |     100 |   88.59 | ...57-261,266-270 
  dracula.ts       |     100 |      100 |     100 |     100 |                   
  github-dark.ts   |     100 |      100 |     100 |     100 |                   
  github-light.ts  |     100 |      100 |     100 |     100 |                   
  googlecode.ts    |     100 |      100 |     100 |     100 |                   
  no-color.ts      |     100 |      100 |     100 |     100 |                   
  qwen-dark.ts     |     100 |      100 |     100 |     100 |                   
  qwen-light.ts    |     100 |      100 |     100 |     100 |                   
  ...tic-tokens.ts |     100 |      100 |     100 |     100 |                   
  ...-of-purple.ts |     100 |      100 |     100 |     100 |                   
  theme-manager.ts |   87.98 |    82.89 |     100 |   87.98 | ...48-357,362-363 
  theme.ts         |     100 |    38.02 |     100 |     100 | ...34-449,457-461 
  xcode.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/utils      |   84.12 |    82.82 |    92.5 |   84.12 |                   
  ...Colorizer.tsx |   79.53 |    83.78 |     100 |   79.53 | ...51-152,249-275 
  ...nRenderer.tsx |   68.83 |    70.14 |      50 |   68.83 | ...52-254,274-293 
  ...wnDisplay.tsx |   86.01 |    87.66 |     100 |   86.01 | ...87,704,729-754 
  ...idDiagram.tsx |   87.79 |    95.34 |     100 |   87.79 | 156-179           
  ...eRenderer.tsx |   92.08 |    80.45 |      95 |   92.08 | ...76-679,723-728 
  ...dWorkUtils.ts |     100 |      100 |     100 |     100 |                   
  ...boardUtils.ts |   59.61 |    58.82 |     100 |   59.61 | ...,86-88,107-149 
  commandUtils.ts  |    95.9 |    88.42 |     100 |    95.9 | ...66,168-169,293 
  computeStats.ts  |     100 |      100 |     100 |     100 |                   
  customBanner.ts  |   90.68 |    91.22 |     100 |   90.68 | ...13,324-327,334 
  displayUtils.ts  |   88.37 |    72.22 |     100 |   88.37 | 23,25,29,31,33    
  formatters.ts    |   95.23 |    98.27 |     100 |   95.23 | 117-120           
  gradientUtils.ts |     100 |      100 |     100 |     100 |                   
  highlight.ts     |     100 |      100 |     100 |     100 |                   
  ...oryMapping.ts |     100 |    94.59 |     100 |     100 | 40,62             
  historyUtils.ts  |   94.11 |       94 |     100 |   94.11 | 94-97             
  isNarrowWidth.ts |     100 |      100 |     100 |     100 |                   
  ...olDetector.ts |    8.23 |      100 |       0 |    8.23 | ...31-132,135-136 
  latexRenderer.ts |   94.95 |     73.8 |     100 |   94.95 | ...76-178,184-187 
  layoutUtils.ts   |     100 |      100 |     100 |     100 |                   
  ...ightLoader.ts |     100 |    89.47 |     100 |     100 | 81,110            
  ...nUtilities.ts |   69.84 |    85.71 |     100 |   69.84 | 75-91,100-101     
  ...ToolGroups.ts |   98.66 |    96.77 |     100 |   98.66 | 48-49             
  ...geRenderer.ts |   86.23 |    69.06 |   95.12 |   86.23 | ...1284,1324-1330 
  ...alRenderer.ts |   86.69 |     71.9 |     100 |   86.69 | ...1476,1513-1519 
  ...lsBySource.ts |     100 |    95.23 |     100 |     100 | 84                
  mouse.ts         |   90.71 |    73.33 |   88.88 |   90.71 | ...40-143,200-201 
  osc8.ts          |   94.73 |    87.75 |     100 |   94.73 | ...49,434,438-439 
  ...mConstants.ts |     100 |      100 |     100 |     100 |                   
  restoreGoal.ts   |   98.98 |    97.05 |     100 |   98.98 | 98                
  ...storyUtils.ts |   62.74 |    71.26 |      90 |   62.74 | ...84,432,437-459 
  ...ickerUtils.ts |     100 |      100 |     100 |     100 |                   
  ...izedOutput.ts |   94.94 |      100 |   88.88 |   94.94 | 112-117           
  ...wOptimizer.ts |     100 |    96.77 |     100 |     100 | 69                
  terminalSetup.ts |    4.37 |      100 |       0 |    4.37 | 44-393            
  textUtils.ts     |   97.61 |    94.84 |   92.85 |   97.61 | ...50-251,386-387 
  todoSnapshot.ts  |   89.33 |    93.47 |     100 |   89.33 | ...,66-78,180-181 
  updateCheck.ts   |     100 |    80.95 |     100 |     100 | 30-42             
 ...i/utils/export |   56.77 |     40.8 |   79.41 |   56.77 |                   
  collect.ts       |   55.92 |    50.58 |   86.36 |   55.92 | ...25-640,642-647 
  index.ts         |     100 |      100 |     100 |     100 |                   
  normalize.ts     |   57.47 |    20.51 |      80 |   57.47 | ...09-310,324-359 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
  utils.ts         |      40 |      100 |       0 |      40 | 11-13             
 ...ort/formatters |    3.38 |      100 |       0 |    3.38 |                   
  html.ts          |    9.61 |      100 |       0 |    9.61 | ...28,34-76,82-84 
  json.ts          |      50 |      100 |       0 |      50 | 14-15             
  jsonl.ts         |     3.5 |      100 |       0 |     3.5 | 14-76             
  markdown.ts      |    0.94 |      100 |       0 |    0.94 | 13-295            
 src/utils         |   71.43 |    88.46 |   90.44 |   71.43 |                   
  acpModelUtils.ts |     100 |      100 |     100 |     100 |                   
  apiPreconnect.ts |   96.72 |    97.14 |     100 |   96.72 | 165-168           
  checks.ts        |   33.33 |      100 |       0 |   33.33 | 23-28             
  cleanup.ts       |   84.12 |    93.33 |      80 |   84.12 | 75,106-115        
  commands.ts      |     100 |      100 |     100 |     100 |                   
  commentJson.ts   |   90.51 |    91.89 |     100 |   90.51 | 67-76,116         
  ...Calculator.ts |     100 |      100 |     100 |     100 |                   
  cpuProfiler.ts   |   70.38 |    71.83 |   88.88 |   70.38 | ...27,430-431,438 
  deepMerge.ts     |     100 |       90 |     100 |     100 | 41-43,49          
  ...ScopeUtils.ts |   97.56 |    88.88 |     100 |   97.56 | 67                
  doctorChecks.ts  |   70.98 |       75 |     100 |   70.98 | ...95-301,325-341 
  ...putCapture.ts |   90.65 |    86.17 |     100 |   90.65 | ...72,370,372-373 
  ...arResolver.ts |   97.14 |    96.42 |     100 |   97.14 | 125-126           
  errors.ts        |   90.85 |    96.36 |    92.3 |   90.85 | 69-70,298-310     
  events.ts        |     100 |      100 |     100 |     100 |                   
  gitUtils.ts      |   91.91 |    84.61 |     100 |   91.91 | 78-81,124-127     
  ...AutoUpdate.ts |   90.96 |    90.47 |   88.88 |   90.96 | 125-136,144,155   
  ...tyWarnings.ts |     100 |      100 |     100 |     100 |                   
  ...lationInfo.ts |    79.8 |    86.79 |     100 |    79.8 | ...07,217,222-246 
  languageUtils.ts |   97.89 |    96.42 |     100 |   97.89 | 132-133           
  math.ts          |       0 |        0 |       0 |       0 | 1-15              
  ...iagnostics.ts |   94.57 |    83.01 |   88.88 |   94.57 | ...05,311,315-317 
  ...onfigUtils.ts |     100 |      100 |     100 |     100 |                   
  ...iveHelpers.ts |   96.79 |    93.28 |     100 |   96.79 | ...76-477,575,588 
  osc.ts           |    97.5 |      100 |   88.88 |    97.5 | 195-196           
  package.ts       |   88.88 |       80 |     100 |   88.88 | 33-34             
  processUtils.ts  |     100 |      100 |     100 |     100 |                   
  readStdin.ts     |   79.62 |       90 |      80 |   79.62 | 33-40,52-54       
  relaunch.ts      |   93.22 |    81.25 |     100 |   93.22 | 65-67,80          
  resolvePath.ts   |   66.66 |       25 |     100 |   66.66 | 12-13,16,18-19    
  runBudget.ts     |   99.35 |    96.77 |     100 |   99.35 | 119               
  sandbox.ts       |       0 |        0 |       0 |       0 | 1-1047            
  sessionPaths.ts  |   90.84 |    90.56 |     100 |   90.84 | ...81-182,185-186 
  settingsUtils.ts |   82.51 |    91.72 |   89.74 |   82.51 | ...76-694,701-709 
  spawnWrapper.ts  |     100 |      100 |     100 |     100 |                   
  ...ate-verify.ts |     100 |      100 |     100 |     100 |                   
  ...one-update.ts |   27.65 |    73.01 |   47.82 |   27.65 | ...16-817,820-839 
  ...upProfiler.ts |   98.46 |    94.52 |     100 |   98.46 | 130-131,305       
  ...upWarnings.ts |     100 |      100 |     100 |     100 |                   
  stdioHelpers.ts  |     100 |       60 |     100 |     100 | 23,32             
  systemInfo.ts    |   95.12 |    89.06 |     100 |   95.12 | ...43-244,249-253 
  ...InfoFields.ts |    87.5 |       65 |     100 |    87.5 | ...24-125,146-147 
  ...iffPreview.ts |   94.11 |    83.33 |     100 |   94.11 | 13                
  ...entEmitter.ts |     100 |      100 |     100 |     100 |                   
  ...upWarnings.ts |   91.17 |    82.35 |     100 |   91.17 | 67-68,73-74,77-78 
  version.ts       |     100 |       50 |     100 |     100 | 11                
  ...ingHandler.ts |     100 |      100 |     100 |     100 |                   
  windowTitle.ts   |     100 |      100 |     100 |     100 |                   
  ...WithBackup.ts |    62.1 |       75 |     100 |    62.1 | 93,107,118-157    
 ...s/housekeeping |   90.15 |     89.7 |   94.11 |   90.15 |                   
  cleanup.ts       |   94.33 |       95 |     100 |   94.33 | 60-62             
  ...eractionAt.ts |     100 |      100 |     100 |     100 |                   
  scheduler.ts     |   89.71 |    88.23 |   85.71 |   89.71 | 51-55,66,116-120  
  throttledOnce.ts |   86.66 |    85.18 |     100 |   86.66 | ...99,105,137-138 
-------------------|---------|----------|---------|---------|-------------------
Core Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   81.18 |    83.35 |   83.12 |   81.18 |                   
 src               |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/__mocks__/fs  |       0 |        0 |       0 |       0 |                   
  promises.ts      |       0 |        0 |       0 |       0 | 1-48              
 src/agents        |   88.06 |    79.77 |   92.13 |   88.06 |                   
  ...transcript.ts |   92.25 |    85.71 |     100 |   92.25 | ...87,306-307,438 
  ...ent-resume.ts |    82.8 |    71.63 |   77.41 |    82.8 | ...1059-1063,1066 
  ...ound-tasks.ts |   95.76 |    87.57 |     100 |   95.76 | ...26-827,898-899 
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/agents/arena  |   76.54 |    66.87 |   78.72 |   76.54 |                   
  ...gentClient.ts |   79.47 |    88.88 |   81.81 |   79.47 | ...68-183,189-204 
  ArenaManager.ts  |   75.37 |    63.37 |   78.26 |   75.37 | ...1860,1866-1867 
  arena-events.ts  |   64.44 |      100 |      50 |   64.44 | ...71-175,178-183 
  diff-summary.ts  |    87.5 |    72.34 |     100 |    87.5 | ...32-133,137-138 
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...gents/backends |   76.29 |    86.15 |   73.04 |   76.29 |                   
  ITermBackend.ts  |   97.97 |    93.93 |     100 |   97.97 | ...78-180,255,307 
  ...essBackend.ts |   91.25 |    90.62 |   86.66 |   91.25 | ...94,249-269,328 
  TmuxBackend.ts   |    90.7 |    76.55 |   97.36 |    90.7 | ...87,697,743-747 
  detect.ts        |   31.25 |      100 |       0 |   31.25 | 34-88             
  index.ts         |     100 |      100 |     100 |     100 |                   
  iterm-it2.ts     |     100 |     92.1 |     100 |     100 | 37-38,106         
  tmux-commands.ts |    6.64 |      100 |    3.03 |    6.64 | ...93-363,386-503 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...agents/runtime |   81.78 |    77.83 |   73.04 |   81.78 |                   
  agent-context.ts |     100 |      100 |     100 |     100 |                   
  agent-core.ts    |   76.81 |    72.89 |   63.63 |   76.81 | ...1614,1641-1688 
  agent-events.ts  |     100 |      100 |     100 |     100 |                   
  ...t-headless.ts |   84.48 |    78.04 |   63.63 |   84.48 | ...00-401,404-405 
  ...nteractive.ts |   80.07 |    80.76 |   74.07 |   80.07 | ...53,455,457,460 
  ...statistics.ts |   98.19 |    82.35 |     100 |   98.19 | 127,151,192,225   
  agent-types.ts   |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/agents/tasks  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/config        |   78.42 |    82.81 |   64.76 |   78.42 |                   
  config.ts        |    76.4 |    81.78 |   60.25 |    76.4 | ...4101,4112-4124 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  models.ts        |     100 |      100 |     100 |     100 |                   
  storage.ts       |   95.01 |     90.9 |   90.47 |   95.01 | ...71-372,375-376 
 ...nfirmation-bus |   98.29 |    97.14 |     100 |   98.29 |                   
  message-bus.ts   |   98.14 |    97.05 |     100 |   98.14 | 42-43             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/core          |   88.96 |    83.92 |   92.44 |   88.96 |                   
  baseLlmClient.ts |   87.24 |    76.47 |    87.5 |   87.24 | ...82,484-494,503 
  client.ts        |   87.45 |     81.1 |   86.36 |   87.45 | ...2056,2095-2098 
  ...tGenerator.ts |    72.1 |    61.11 |     100 |    72.1 | ...63,365,372-375 
  ...lScheduler.ts |   88.32 |    82.22 |   95.52 |   88.32 | ...3553,3614-3625 
  geminiChat.ts    |   91.61 |    87.95 |   97.33 |   91.61 | ...2891,2958-2959 
  geminiRequest.ts |     100 |      100 |     100 |     100 |                   
  ...htProtocol.ts |    9.09 |      100 |       0 |    9.09 | 34-42,45-49,52-87 
  logger.ts        |   87.41 |    87.02 |     100 |   87.41 | ...64-568,614-628 
  ...tyDefaults.ts |     100 |      100 |     100 |     100 |                   
  ...olExecutor.ts |   92.59 |       75 |      50 |   92.59 | 41-42             
  ...on-helpers.ts |   86.48 |    72.22 |     100 |   86.48 | ...97-198,212-221 
  ...issionFlow.ts |   98.59 |       95 |     100 |   98.59 | 93                
  prompts.ts       |   89.28 |    86.25 |      75 |   89.28 | ...-978,1181-1182 
  tokenLimits.ts   |     100 |    89.47 |     100 |     100 | 51-52             
  ...okTriggers.ts |   99.42 |     90.9 |     100 |   99.42 | 172,183           
  turn.ts          |   96.46 |    88.88 |     100 |   96.46 | ...32,445-446,494 
 ...ntentGenerator |   94.88 |    82.07 |      94 |   94.88 |                   
  ...tGenerator.ts |   96.29 |    83.18 |   92.85 |   96.29 | ...1,971,999-1001 
  converter.ts     |   94.51 |    80.72 |     100 |   94.51 | ...06-607,617,823 
  index.ts         |       0 |        0 |       0 |       0 | 1-21              
  usage.ts         |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   91.53 |    71.64 |   93.33 |   91.53 |                   
  ...tGenerator.ts |      90 |    70.96 |   92.85 |      90 | ...80-286,304-305 
  index.ts         |     100 |       80 |     100 |     100 | 50                
 ...ntentGenerator |   93.86 |    82.98 |    90.9 |   93.86 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tGenerator.ts |   93.72 |    81.27 |   90.32 |   93.72 | ...29,939-940,968 
  ...tDetection.ts |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   86.12 |    83.98 |   93.58 |   86.12 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  converter.ts     |   84.89 |    82.17 |   96.15 |   84.89 | ...1394,1610-1625 
  errorHandler.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |   54.54 |    68.75 |      50 |   54.54 | ...79,87-91,95-99 
  ...tGenerator.ts |    66.4 |    70.58 |   88.88 |    66.4 | ...51-157,168-169 
  pipeline.ts      |   93.82 |     84.4 |     100 |   93.82 | ...89-490,498,566 
  ...ureContext.ts |     100 |      100 |     100 |     100 |                   
  ...ingOptions.ts |       0 |        0 |       0 |       0 | 1                 
  ...CallParser.ts |   90.66 |    88.57 |     100 |   90.66 | ...15-319,349-350 
  ...kingParser.ts |     100 |    96.87 |     100 |     100 | 42                
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...rator/provider |   96.66 |    88.94 |   96.07 |   96.66 |                   
  dashscope.ts     |   97.35 |    91.39 |   93.33 |   97.35 | ...90-291,367-368 
  deepseek.ts      |   94.91 |    89.36 |     100 |   94.91 | ...31-132,145-146 
  default.ts       |   95.79 |    89.65 |   88.88 |   95.79 | 122-123,193-195   
  index.ts         |     100 |      100 |     100 |     100 |                   
  mimo.ts          |   94.11 |    66.66 |     100 |   94.11 | 29,52-53          
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  mistral.ts       |   96.07 |    73.33 |     100 |   96.07 | 32-33             
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  openrouter.ts    |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 |                   
  utils.ts         |     100 |      100 |     100 |     100 |                   
 src/extension     |   62.35 |    79.54 |   80.31 |   62.35 |                   
  ...-converter.ts |   66.21 |    52.45 |     100 |   66.21 | ...85-786,795-827 
  ...ionManager.ts |    47.1 |    82.06 |    65.9 |    47.1 | ...1404,1414-1433 
  ...onSettings.ts |   93.46 |    93.05 |     100 |   93.46 | ...17-221,228-232 
  ...-converter.ts |   54.88 |    94.44 |      60 |   54.88 | ...35-146,158-192 
  github.ts        |   46.41 |     87.3 |   63.63 |   46.41 | ...68-374,413-466 
  index.ts         |     100 |      100 |     100 |     100 |                   
  marketplace.ts   |   97.31 |    93.75 |     100 |   97.31 | ...65,185-186,275 
  npm.ts           |   59.01 |    71.69 |    87.5 |   59.01 | ...23-425,432-436 
  override.ts      |   94.11 |    88.88 |     100 |   94.11 | 63-64,81-82       
  redaction.ts     |     100 |      100 |     100 |     100 |                   
  settings.ts      |   66.26 |      100 |      50 |   66.26 | 81-108,143-149    
  storage.ts       |     100 |      100 |     100 |     100 |                   
  ...ableSchema.ts |     100 |      100 |     100 |     100 |                   
  variables.ts     |   88.75 |    83.33 |     100 |   88.75 | ...28-231,234-237 
 src/followup      |   55.57 |    84.14 |   81.25 |   55.57 |                   
  followupState.ts |      96 |    89.74 |     100 |      96 | 159-161,218-219   
  index.ts         |     100 |      100 |     100 |     100 |                   
  overlayFs.ts     |   95.06 |       84 |     100 |   95.06 | 78,108,122,133    
  speculation.ts   |   13.02 |      100 |   16.66 |   13.02 | 89-464,524-575    
  ...onToolGate.ts |     100 |    96.42 |     100 |     100 | 94                
  ...nGenerator.ts |    71.6 |    72.13 |   83.33 |    71.6 | ...88-246,316-318 
 src/generated     |       0 |        0 |       0 |       0 |                   
  git-commit.ts    |       0 |        0 |       0 |       0 | 1-10              
 src/goals         |   89.57 |    83.45 |   94.44 |   89.57 |                   
  ...eGoalStore.ts |    85.1 |    95.45 |   84.61 |    85.1 | ...63-166,174-182 
  goalHook.ts      |   97.26 |    91.48 |     100 |   97.26 | 100-105           
  goalJudge.ts     |   84.33 |    74.28 |     100 |   84.33 | ...57-358,366-368 
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/hooks         |   85.76 |    85.01 |    87.3 |   85.76 |                   
  ...okRegistry.ts |   86.48 |    77.08 |     100 |   86.48 | ...41-344,362-369 
  ...terpolator.ts |   96.66 |    93.33 |     100 |   96.66 | 66-67             
  ...HookRunner.ts |   96.68 |    87.23 |     100 |   96.68 | 110-112,231-233   
  ...Aggregator.ts |   96.44 |    91.02 |     100 |   96.44 | ...93,295-296,369 
  ...entHandler.ts |   94.94 |    86.41 |   93.75 |   94.94 | ...00,857-858,868 
  hookPlanner.ts   |   86.82 |    85.48 |   83.33 |   86.82 | ...75-177,195-206 
  hookRegistry.ts  |   90.17 |    83.33 |     100 |   90.17 | ...33,352,356,360 
  hookRunner.ts    |   58.69 |    71.26 |   66.66 |   58.69 | ...50-751,760-761 
  hookSystem.ts    |   85.71 |      100 |   67.44 |   85.71 | ...62-663,669-670 
  ...HookRunner.ts |   75.51 |     61.9 |      80 |   75.51 | ...05-406,424-425 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...HookRunner.ts |   96.37 |     90.9 |      90 |   96.37 | 342-350,424-425   
  ...SkillHooks.ts |   78.75 |       75 |   66.66 |   78.75 | 62-66,137-152     
  ...oksManager.ts |   96.66 |    91.66 |     100 |   96.66 | ...90,209-210,223 
  ssrfGuard.ts     |   77.22 |    85.36 |     100 |   77.22 | ...57,261-267,273 
  stopHookCap.ts   |     100 |      100 |     100 |     100 |                   
  trustedHooks.ts  |      90 |    52.63 |     100 |      90 | ...53,66-67,97-98 
  types.ts         |   91.47 |     92.3 |    86.2 |   91.47 | ...46-447,519-523 
  urlValidator.ts  |     100 |      100 |     100 |     100 |                   
 src/ide           |   75.55 |    83.52 |   78.33 |   75.55 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  detect-ide.ts    |     100 |      100 |     100 |     100 |                   
  ide-client.ts    |   66.14 |    81.75 |   66.66 |   66.14 | ...7-968,997-1005 
  ide-installer.ts |   89.06 |    79.31 |     100 |   89.06 | ...36,143-147,160 
  ideContext.ts    |     100 |      100 |     100 |     100 |                   
  process-utils.ts |   84.84 |    71.79 |     100 |   84.84 | ...37,151,193-194 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/lsp           |   42.41 |    52.21 |   52.14 |   42.41 |                   
  ...nfigLoader.ts |   70.27 |    35.89 |   94.73 |   70.27 | ...20-422,426-432 
  ...ionFactory.ts |   42.69 |    79.16 |      50 |   42.69 | ...62-413,419-436 
  ...Normalizer.ts |   23.09 |    13.72 |   30.43 |   23.09 | ...04-905,909-924 
  ...verManager.ts |   25.31 |    62.06 |   41.66 |   25.31 | ...85-704,710-740 
  ...eLspClient.ts |   32.77 |       80 |   17.64 |   32.77 | ...84-288,294-295 
  ...LspService.ts |   51.85 |    65.98 |   68.57 |   51.85 | ...1339,1399-1409 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/mcp           |   78.75 |    75.45 |   75.92 |   78.75 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...h-provider.ts |   86.95 |      100 |   33.33 |   86.95 | ...,93,97,101-102 
  ...h-provider.ts |   73.82 |    53.92 |     100 |   73.82 | ...88-895,902-904 
  ...en-storage.ts |   98.64 |    97.72 |     100 |   98.64 | 88-89             
  oauth-utils.ts   |   70.58 |    85.29 |    90.9 |   70.58 | ...70-290,315-344 
  ...n-provider.ts |   89.83 |       96 |   45.45 |   89.83 | ...43,147,151-152 
 .../token-storage |   79.72 |    87.05 |   86.36 |   79.72 |                   
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   83.44 |    84.21 |   92.85 |   83.44 | ...68-178,186-187 
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   68.14 |    82.35 |   64.28 |   68.14 | ...81-295,298-314 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/memory        |   71.03 |    75.44 |    69.1 |   71.03 |                   
  const.ts         |     100 |      100 |     100 |     100 |                   
  dream.ts         |      66 |    73.33 |      50 |      66 | 51,108-149        
  ...entPlanner.ts |   57.84 |    72.72 |   33.33 |   57.84 | ...35,140-147,152 
  entries.ts       |   63.77 |    79.16 |      50 |   63.77 | ...72-180,183-189 
  extract.ts       |   95.23 |    79.16 |     100 |   95.23 | 82-87,126         
  ...entPlanner.ts |   63.08 |    65.71 |   41.17 |   63.08 | ...17,222-223,332 
  ...ionPlanner.ts |       0 |        0 |       0 |       0 | 1                 
  forget.ts        |      46 |    61.53 |   44.44 |      46 | ...05,212,215-347 
  indexer.ts       |   84.61 |    45.45 |     100 |   84.61 | ...51,57-58,70-71 
  manager.ts       |   75.34 |    81.04 |    75.6 |   75.34 | ...1279,1292-1294 
  memoryAge.ts     |   90.47 |    77.77 |     100 |   90.47 | 50-51             
  paths.ts         |   55.47 |    89.47 |   85.71 |   55.47 | ...,89-90,106-114 
  prompt.ts        |   93.36 |    71.42 |     100 |   93.36 | ...58,161,228-229 
  recall.ts        |   77.54 |    69.38 |   88.88 |   77.54 | ...53-258,282-293 
  ...ceSelector.ts |   91.86 |    77.27 |     100 |   91.86 | ...15,117-118,126 
  scan.ts          |   87.91 |    68.42 |     100 |   87.91 | ...47-48,58,82-87 
  ...entPlanner.ts |   58.02 |    66.66 |   56.25 |   58.02 | ...47-268,344-389 
  status.ts        |   10.52 |      100 |       0 |   10.52 | 41-98             
  store.ts         |   94.44 |    83.33 |     100 |   94.44 | 56-57,92-93       
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...ontextFile.ts |   79.38 |    78.33 |   81.81 |   79.38 | ...58-272,286-291 
 src/mocks         |       0 |        0 |       0 |       0 |                   
  msw.ts           |       0 |        0 |       0 |       0 | 1-9               
 src/models        |   89.87 |    87.15 |      88 |   89.87 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...tor-config.ts |   90.24 |    91.42 |     100 |   90.24 | 142,148,151-160   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nfigErrors.ts |   74.22 |    47.82 |   84.61 |   74.22 | ...,67-74,106-117 
  ...igResolver.ts |   98.66 |    92.85 |     100 |   98.66 | 162,324,330       
  modelRegistry.ts |     100 |    98.63 |     100 |     100 | 229               
  modelsConfig.ts  |    85.9 |    84.61 |    82.5 |    85.9 | ...1300,1329-1330 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/output        |     100 |      100 |     100 |     100 |                   
  ...-formatter.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/permissions   |   76.59 |    89.81 |      60 |   76.59 |                   
  autoMode.ts      |    92.9 |    92.15 |     100 |    92.9 | ...34-235,256-266 
  ...transcript.ts |      98 |    84.61 |     100 |      98 | 200-201           
  classifier.ts    |   92.89 |    91.42 |     100 |   92.89 | 151-158,342-346   
  ...erousRules.ts |     100 |    89.36 |     100 |     100 | 110,133,147,175   
  ...alTracking.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...on-manager.ts |   78.41 |    86.06 |   82.14 |   78.41 | ...-929,1035-1039 
  rule-parser.ts   |   97.37 |    93.82 |     100 |   97.37 | ...-875,1024-1026 
  ...-semantics.ts |   58.35 |    86.06 |    30.2 |   58.35 | ...1604-1614,1643 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...sifier-prompts |   98.18 |       90 |     100 |   98.18 |                   
  system-prompt.ts |   98.18 |       90 |     100 |   98.18 | 150               
 src/prompts       |   83.63 |      100 |    87.5 |   83.63 |                   
  mcp-prompts.ts   |   18.18 |      100 |       0 |   18.18 | 11-19             
  ...t-registry.ts |     100 |      100 |     100 |     100 |                   
 src/providers     |   79.44 |    64.39 |   64.28 |   79.44 |                   
  all-providers.ts |      68 |      100 |       0 |      68 | 68-69,73-79,83-89 
  index.ts         |     100 |      100 |     100 |     100 |                   
  install.ts       |   98.87 |    87.27 |     100 |   98.87 | 268-269           
  ...der-config.ts |   69.73 |    47.29 |   68.42 |   69.73 | ...10-411,418-427 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...viders/presets |   97.29 |    86.36 |      50 |   97.29 |                   
  ...oding-plan.ts |   87.17 |      100 |       0 |   87.17 | 81-83,86-88,90-93 
  ...a-standard.ts |     100 |      100 |     100 |     100 |                   
  ...token-plan.ts |     100 |      100 |     100 |     100 |                   
  ...m-provider.ts |   97.01 |    81.25 |      75 |   97.01 | 120-121           
  deepseek.ts      |     100 |      100 |     100 |     100 |                   
  idealab.ts       |     100 |      100 |     100 |     100 |                   
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  openrouter.ts    |     100 |      100 |     100 |     100 |                   
  zai.ts           |     100 |      100 |     100 |     100 |                   
 src/qwen          |   84.48 |     77.6 |   95.83 |   84.48 |                   
  ...tGenerator.ts |   98.64 |    98.18 |     100 |   98.64 | 105-106           
  qwenOAuth2.ts    |   80.85 |    70.74 |   90.32 |   80.85 | ...1169-1185,1215 
  ...kenManager.ts |   85.36 |    76.61 |     100 |   85.36 | ...52-757,778-783 
 src/services      |   86.09 |    83.95 |   91.95 |   86.09 |                   
  ...ionTrailer.ts |     100 |      100 |     100 |     100 |                   
  ...llRegistry.ts |   97.35 |    85.34 |     100 |   97.35 | ...94,117,417-418 
  ...ionService.ts |   98.19 |    94.94 |     100 |   98.19 | 493,495-499,602   
  ...ingService.ts |   83.88 |    83.44 |   83.33 |   83.88 | ...1268,1285-1286 
  ...ttribution.ts |   91.73 |    87.71 |      90 |   91.73 | ...80-685,826-827 
  ...utSlimming.ts |     100 |    97.43 |     100 |     100 | 215,268           
  cronScheduler.ts |   97.56 |    92.98 |     100 |   97.56 | 62-63,77,155      
  ...eryService.ts |   80.43 |    95.45 |      75 |   80.43 | ...19-134,140-141 
  ...oryService.ts |   86.18 |    76.76 |   91.17 |   86.18 | ...1150,1191-1194 
  fileReadCache.ts |     100 |      100 |     100 |     100 |                   
  ...temService.ts |   91.27 |    82.69 |    90.9 |   91.27 | ...94,196,294-301 
  ...ratedFiles.ts |      96 |    88.23 |     100 |      96 | 119-120,146-147   
  gitInit.ts       |     100 |      100 |     100 |     100 |                   
  gitService.ts    |   68.75 |     92.3 |   55.55 |   68.75 | ...12-122,125-129 
  ...reeService.ts |    69.4 |    68.82 |   93.33 |    69.4 | ...2064,2092-2093 
  ...ionService.ts |   98.13 |     97.8 |   95.45 |   98.13 | ...32-333,380-381 
  ...ticsDumper.ts |   98.18 |    95.23 |     100 |   98.18 | 165-166           
  ...ureMonitor.ts |   95.27 |     91.6 |      96 |   95.27 | ...02,603,617-619 
  ...orRegistry.ts |   96.54 |    91.73 |     100 |   96.54 | ...70-471,622-623 
  ...ttachments.ts |   97.24 |    90.39 |     100 |   97.24 | ...08,646,661-662 
  sessionRecap.ts  |   12.65 |      100 |       0 |   12.65 | 44-150            
  ...ionService.ts |   90.47 |     79.2 |   96.87 |   90.47 | ...1324,1328-1329 
  sessionTitle.ts  |   93.87 |    71.15 |     100 |   93.87 | ...33-236,267-268 
  ...ionService.ts |   81.12 |    77.92 |   89.28 |   81.12 | ...1926,1932-1937 
  ...Estimation.ts |     100 |      100 |     100 |     100 |                   
  ...UseSummary.ts |   94.63 |    88.46 |     100 |   94.63 | ...62-164,214-215 
  ...reeCleanup.ts |   14.56 |      100 |   33.33 |   14.56 | 58-185            
  ...ionService.ts |   84.21 |    79.41 |     100 |   84.21 | ...18-219,235-236 
 ...icrocompaction |   98.05 |     91.8 |     100 |   98.05 |                   
  microcompact.ts  |   98.05 |     91.8 |     100 |   98.05 | ...19,289,293,391 
 src/skills        |   88.51 |    85.75 |   94.54 |   88.51 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...activation.ts |     100 |     93.1 |     100 |     100 | 93,112            
  skill-load.ts    |      94 |    86.56 |     100 |      94 | ...08,228,240-242 
  skill-manager.ts |   84.26 |    80.87 |   90.32 |   84.26 | ...1155,1162-1166 
  skill-paths.ts   |   89.15 |    86.36 |     100 |   89.15 | ...00-101,106-107 
  symlinkScope.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/subagents     |   82.61 |    78.89 |   95.23 |   82.61 |                   
  ...tin-agents.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nt-manager.ts |   77.15 |    71.36 |    93.1 |   77.15 | ...1178,1200-1201 
  types.ts         |     100 |      100 |     100 |     100 |                   
  validation.ts    |   92.46 |    95.18 |     100 |   92.46 | 51-56,69-74,78-83 
 src/telemetry     |   77.79 |    88.48 |   80.85 |   77.79 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...attributes.ts |   98.13 |       88 |     100 |   98.13 | 185-187           
  ...-exporters.ts |   46.37 |      100 |   44.44 |   46.37 | ...85,88-89,92-93 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-111             
  ...-processor.ts |   99.06 |    95.45 |      95 |   99.06 | 131,344-345       
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-128             
  loggers.ts       |   53.34 |    65.38 |      60 |   53.34 | ...1215,1232-1252 
  metrics.ts       |   75.03 |    82.95 |   74.54 |   75.03 | ...8-988,991-1002 
  ...attributes.ts |     100 |      100 |     100 |     100 |                   
  sanitize.ts      |      80 |    83.33 |     100 |      80 | 35-36,41-42       
  sdk.ts           |   93.06 |     88.4 |   81.81 |   93.06 | ...72-573,593-597 
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  ...on-tracing.ts |   92.86 |    89.61 |     100 |   92.86 | ...45-948,952-955 
  ...etry-utils.ts |     100 |      100 |     100 |     100 |                   
  ...l-decision.ts |     100 |      100 |     100 |     100 |                   
  ...e-id-utils.ts |     100 |      100 |     100 |     100 |                   
  tracer.ts        |   98.61 |    89.36 |     100 |   98.61 | 53,108            
  types.ts         |   80.37 |     87.6 |   84.93 |   80.37 | ...1151,1154-1183 
  uiTelemetry.ts   |   92.97 |    96.96 |   81.25 |   92.97 | ...93-194,200-207 
 ...ry/qwen-logger |   69.37 |     80.2 |   66.66 |   69.37 |                   
  event-types.ts   |       0 |        0 |       0 |       0 |                   
  qwen-logger.ts   |   69.37 |       80 |   66.07 |   69.37 | ...1056,1094-1095 
 src/test-utils    |   93.16 |    95.91 |   76.47 |   93.16 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  ...st-helpers.ts |   94.11 |       90 |     100 |   94.11 | 69-70             
  index.ts         |     100 |      100 |     100 |     100 |                   
  mock-tool.ts     |   91.19 |    97.14 |   72.41 |   91.19 | ...38,202-203,216 
  ...aceContext.ts |     100 |      100 |     100 |     100 |                   
 src/tools         |   79.34 |    81.79 |   86.01 |   79.34 |                   
  ...erQuestion.ts |   88.93 |    76.74 |    90.9 |   88.93 | ...39-340,347-348 
  cron-create.ts   |   88.11 |    88.88 |    62.5 |   88.11 | ...,43-44,165-172 
  cron-delete.ts   |   96.82 |      100 |   83.33 |   96.82 | 26-27             
  cron-list.ts     |   96.66 |      100 |   83.33 |   96.66 | 25-26             
  diffOptions.ts   |     100 |      100 |     100 |     100 |                   
  edit.ts          |   81.02 |    84.07 |      75 |   81.02 | ...15-716,826-876 
  ...r-worktree.ts |   83.14 |    67.56 |    87.5 |   83.14 | ...84-187,278-279 
  exit-worktree.ts |   84.23 |    85.96 |   91.66 |   84.23 | ...92-293,298-312 
  exitPlanMode.ts  |   85.09 |    85.71 |     100 |   85.09 | ...60-163,177-189 
  glob.ts          |   90.63 |    88.33 |   84.61 |   90.63 | ...28,171,302,305 
  grep.ts          |   79.19 |    85.71 |   78.94 |   79.19 | ...20,560,569-576 
  ls.ts            |   96.74 |    90.27 |     100 |   96.74 | 176-181,212,216   
  lsp.ts           |   72.77 |    60.09 |   90.32 |   72.77 | ...1211,1213-1214 
  ...nt-manager.ts |   84.36 |    82.74 |   84.21 |   84.36 | ...2099-2103,2142 
  mcp-client.ts    |   39.91 |    83.82 |   69.44 |   39.91 | ...1618,1622-1625 
  mcp-tool.ts      |   90.98 |    88.88 |   96.42 |   90.98 | ...95-596,646-647 
  memory-config.ts |       0 |        0 |       0 |       0 | 1-47              
  ...iable-tool.ts |     100 |    84.61 |     100 |     100 | 102,109           
  monitor.ts       |   91.65 |    84.05 |   88.46 |   91.65 | ...87,600,796-801 
  notebook-edit.ts |   85.11 |    76.42 |   81.25 |   85.11 | ...54-870,916-917 
  ...nforcement.ts |   82.57 |       90 |     100 |   82.57 | 174-185,234-247   
  read-file.ts     |    95.4 |    90.32 |      90 |    95.4 | ...99,298-301,304 
  ripGrep.ts       |   94.59 |    85.71 |   93.33 |   94.59 | ...60,463,541-542 
  ...-transport.ts |    6.34 |      100 |       0 |    6.34 | 47-145            
  send-message.ts  |   84.68 |    91.66 |    62.5 |   84.68 | ...,82-90,167-170 
  shell.ts         |   73.49 |    80.03 |   91.42 |   73.49 | ...4243,4292-4298 
  skill-utils.ts   |     100 |      100 |     100 |     100 |                   
  skill.ts         |   88.51 |    91.66 |   88.23 |   88.51 | ...20,424,447-469 
  ...eticOutput.ts |   95.12 |      100 |      80 |   95.12 | 87-88             
  task-stop.ts     |   93.14 |    96.15 |   85.71 |   93.14 | 39-40,54-64       
  todoWrite.ts     |   89.27 |    82.05 |   92.85 |   89.27 | ...50-555,577-578 
  tool-error.ts    |     100 |      100 |     100 |     100 |                   
  tool-names.ts    |     100 |      100 |     100 |     100 |                   
  tool-registry.ts |   74.85 |    76.85 |   80.95 |   74.85 | ...30-831,839-840 
  tool-search.ts   |   95.19 |    86.48 |    92.3 |   95.19 | ...47-153,208-213 
  tools.ts         |   92.14 |    90.38 |   89.47 |   92.14 | ...78-479,495-501 
  web-fetch.ts     |   88.84 |       80 |   92.85 |   88.84 | ...12-313,315-316 
  write-file.ts    |   82.65 |    80.45 |   84.61 |   82.65 | ...65-668,696-731 
 src/tools/agent   |   75.23 |    81.48 |   73.97 |   75.23 |                   
  agent.ts         |   75.45 |    81.59 |   74.24 |   75.45 | ...2523,2532-2535 
  fork-subagent.ts |   70.73 |    77.77 |   71.42 |   70.73 | ...22-123,158-169 
 ...s/computer-use |   84.94 |    87.82 |   76.31 |   84.94 |                   
  bootstrap.ts     |    71.2 |    94.44 |   66.66 |    71.2 | 127-181,284-285   
  client.ts        |      38 |      100 |      50 |      38 | ...48-178,182-191 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  install-state.ts |   94.44 |    76.92 |     100 |   94.44 | 40-41             
  ...n-detector.ts |     100 |     87.5 |     100 |     100 | 43                
  schemas.ts       |     100 |      100 |     100 |     100 |                   
  tool.ts          |    95.5 |    81.39 |    92.3 |    95.5 | 47-48,145-151     
 src/utils         |   89.67 |    87.97 |   94.38 |   89.67 |                   
  LruCache.ts      |       0 |        0 |       0 |       0 | 1-41              
  ...Controller.ts |     100 |      100 |     100 |     100 |                   
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...cFileWrite.ts |   94.76 |    93.06 |     100 |   94.76 | ...30-531,634-638 
  bareMode.ts      |   27.27 |      100 |       0 |   27.27 | 9-15,18-19        
  browser.ts       |    7.69 |      100 |       0 |    7.69 | 17-56             
  bundlePaths.ts   |     100 |      100 |     100 |     100 |                   
  ...igResolver.ts |     100 |      100 |     100 |     100 |                   
  ...engthError.ts |      90 |    87.71 |     100 |      90 | ...54-155,158-159 
  cronDisplay.ts   |   42.85 |    23.07 |     100 |   42.85 | 26-31,33-45,47-54 
  cronParser.ts    |   89.74 |    85.71 |     100 |   89.74 | ...,63-64,183-186 
  debugLogger.ts   |    95.9 |    93.93 |   94.73 |    95.9 | 106-107,222-226   
  editHelper.ts    |   93.63 |    83.52 |     100 |   93.63 | ...28-429,463-464 
  editor.ts        |    97.6 |     95.4 |     100 |    97.6 | ...25-326,328-329 
  ...arResolver.ts |   94.28 |    88.88 |     100 |   94.28 | 28-29,125-126     
  ...entContext.ts |     100 |    95.45 |     100 |     100 | 83                
  errorParsing.ts  |    97.7 |    97.05 |     100 |    97.7 | 72-73             
  ...rReporting.ts |   88.46 |       90 |     100 |   88.46 | 69-74             
  errors.ts        |   70.54 |    79.59 |      50 |   70.54 | ...15-231,235-241 
  fetch.ts         |   70.18 |    71.42 |   71.42 |   70.18 | ...42,148,161,186 
  fileUtils.ts     |    91.5 |    86.13 |   95.23 |    91.5 | ...1191,1195-1201 
  forkedAgent.ts   |   80.68 |    78.12 |   83.33 |   80.68 | ...39-545,550-556 
  formatters.ts    |   81.81 |       75 |     100 |   81.81 | 15-16             
  ...eUtilities.ts |   89.21 |    86.66 |     100 |   89.21 | 16-17,49-55,65-66 
  ...rStructure.ts |   94.36 |    94.28 |     100 |   94.36 | ...17-120,330-335 
  getPty.ts        |    12.5 |      100 |       0 |    12.5 | 21-34             
  gitDiff.ts       |   92.36 |    79.53 |     100 |   92.36 | ...55-856,928-929 
  ...noreParser.ts |    92.3 |    89.36 |     100 |    92.3 | ...15-116,186-187 
  gitUtils.ts      |   73.64 |    90.32 |   83.33 |   73.64 | ...,78-79,103-154 
  iconvHelper.ts   |     100 |      100 |     100 |     100 |                   
  ...rePatterns.ts |     100 |      100 |     100 |     100 |                   
  ...ionManager.ts |     100 |     90.9 |     100 |     100 | 27                
  ...lPromptIds.ts |     100 |      100 |     100 |     100 |                   
  jsonl-utils.ts   |   88.98 |    90.66 |   91.66 |   88.98 | ...46-349,359-365 
  ...-detection.ts |     100 |      100 |     100 |     100 |                   
  ...iagnostics.ts |    96.4 |     94.2 |     100 |    96.4 | ...66,293-294,376 
  ...yDiscovery.ts |   88.27 |    83.87 |     100 |   88.27 | ...76,279,407-410 
  ...tProcessor.ts |    93.2 |    89.18 |     100 |    93.2 | ...82-288,370-371 
  ...Inspectors.ts |   61.53 |      100 |      50 |   61.53 | 18-23             
  modelId.ts       |   98.95 |    98.21 |     100 |   98.95 | 148               
  ...kerChecker.ts |   90.78 |    91.66 |     100 |   90.78 | 73-79             
  notebook.ts      |   94.57 |    89.83 |   95.83 |   94.57 | ...21,333,385-387 
  openaiLogger.ts  |   90.85 |    87.87 |     100 |   90.85 | ...97-199,222-227 
  partUtils.ts     |     100 |    98.61 |     100 |     100 | 206               
  pathReader.ts    |     100 |      100 |     100 |     100 |                   
  paths.ts         |   93.21 |    91.86 |     100 |   93.21 | ...89-390,392-394 
  pdf.ts           |   93.68 |    87.05 |     100 |   93.68 | ...96-297,321-325 
  projectPath.ts   |     100 |      100 |     100 |     100 |                   
  projectRoot.ts   |   71.73 |    78.57 |     100 |   71.73 | 54-66             
  ...ectSummary.ts |   89.62 |    72.41 |     100 |   89.62 | ...40-145,196-199 
  ...tIdContext.ts |     100 |      100 |     100 |     100 |                   
  proxyUtils.ts    |     100 |      100 |     100 |     100 |                   
  ...rDetection.ts |   58.57 |       76 |     100 |   58.57 | ...4,88-89,95-100 
  ...noreParser.ts |   85.45 |    85.18 |     100 |   85.45 | ...59,65-66,72-73 
  rateLimit.ts     |   92.55 |    85.92 |     100 |   92.55 | ...70-272,309-310 
  readManyFiles.ts |   87.59 |       84 |     100 |   87.59 | ...09-211,227-238 
  retry.ts         |   89.81 |    88.05 |     100 |   89.81 | ...29,350,357-358 
  ripgrepUtils.ts  |   46.79 |    84.37 |   66.66 |   46.79 | ...45-246,258-335 
  ...sDiscovery.ts |   97.42 |    92.85 |     100 |   97.42 | ...04,182-183,202 
  ...iagnostics.ts |   83.08 |     67.5 |   92.59 |   83.08 | ...23,543-544,550 
  ...tchOptions.ts |   82.18 |    85.18 |   95.23 |   82.18 | ...24,549,578-587 
  runtimeStatus.ts |    97.5 |    88.57 |     100 |    97.5 | 162-163           
  safeJsonParse.ts |   74.07 |    83.33 |     100 |   74.07 | 40-46             
  ...nStringify.ts |     100 |      100 |     100 |     100 |                   
  ...aConverter.ts |   90.78 |    88.23 |     100 |   90.78 | ...41-42,93,95-96 
  ...aValidator.ts |   94.57 |    80.26 |     100 |   94.57 | ...04,213-216,270 
  ...r-launcher.ts |   76.92 |     91.3 |   66.66 |   76.92 | ...34,136,157-195 
  ...orageUtils.ts |   96.89 |    85.84 |     100 |   96.89 | ...51,367,447,466 
  shell-utils.ts   |   84.22 |    89.91 |     100 |   84.22 | ...1583,1590-1594 
  ...lAstParser.ts |   95.58 |    85.79 |     100 |   95.58 | ...1067-1069,1079 
  ...ContextEnv.ts |     100 |      100 |     100 |     100 |                   
  ...nlyChecker.ts |    95.1 |    91.66 |     100 |    95.1 | ...16-317,325-326 
  sideQuery.ts     |   86.17 |    86.53 |     100 |   86.17 | ...55-161,163-169 
  ...pEventSink.ts |     100 |       80 |     100 |     100 | 61                
  ...tGenerator.ts |     100 |      100 |     100 |     100 |                   
  ...ameContext.ts |     100 |      100 |     100 |     100 |                   
  symlink.ts       |   81.48 |       75 |     100 |   81.48 | 54-59             
  ...emEncoding.ts |   96.36 |    91.17 |     100 |   96.36 | 59-60,124-125     
  terminalSafe.ts  |     100 |      100 |     100 |     100 |                   
  ...Serializer.ts |   98.72 |       90 |     100 |   98.72 | 42-43,134,201-203 
  testUtils.ts     |   53.33 |      100 |   33.33 |   53.33 | ...53,59-64,70-72 
  textUtils.ts     |      60 |      100 |   66.66 |      60 | 36-55             
  thoughtUtils.ts  |     100 |    92.85 |     100 |     100 | 71                
  ...-converter.ts |   94.59 |    85.71 |     100 |   94.59 | 35-36             
  tool-utils.ts    |    93.6 |     91.3 |     100 |    93.6 | ...58-159,162-163 
  truncation.ts    |     100 |       92 |     100 |     100 | 52,71             
  windowsPath.ts   |   89.47 |    79.31 |     100 |   89.47 | ...57-58,62,90-91 
  ...aceContext.ts |   93.71 |    89.28 |   93.33 |   93.71 | ...24-225,249-251 
  xml.ts           |     100 |      100 |     100 |     100 |                   
  yaml-parser.ts   |      92 |     84.9 |     100 |      92 | 49-53,65-69       
 ...ils/filesearch |   86.21 |    81.61 |   96.42 |   86.21 |                   
  crawlCache.ts    |     100 |      100 |     100 |     100 |                   
  crawler.ts       |   82.84 |    77.49 |   94.82 |   82.84 | ...1451,1485-1486 
  fileSearch.ts    |   93.58 |    87.32 |     100 |   93.58 | ...46-247,249-250 
  ignore.ts        |     100 |      100 |     100 |     100 |                   
  result-cache.ts  |     100 |     92.3 |     100 |     100 | 46                
 ...uest-tokenizer |   56.63 |    74.52 |   74.19 |   56.63 |                   
  ...eTokenizer.ts |   41.86 |    76.47 |   69.23 |   41.86 | ...70-443,453-507 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tTokenizer.ts |   68.39 |    69.49 |    90.9 |   68.39 | ...24-325,327-328 
  ...ageFormats.ts |      76 |      100 |   33.33 |      76 | 45-48,55-56       
  textTokenizer.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 | 1                 
-------------------|---------|----------|---------|---------|-------------------

For detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run.

Copy link
Copy Markdown
Collaborator

@wenshao wenshao left a comment

Choose a reason for hiding this comment

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

Review — standalone auto-update

Solid, security-conscious shape overall: atomic replace, stale-lock reclamation via PID liveness, checksum + (optional) Ed25519 signature, a smoke test before the swap, and OSS→GitHub fallback are all the right building blocks. A few issues I'd treat as blocking before this ships enabled-by-default, plus convention/coverage gaps.

🔴 High

1. The Windows deferred update is deleted by its own finally block — the Windows path can never succeed.

In atomicReplace (win32) the new install is staged at ${standaloneDir}.new (pendingDir) and a detached .bat is spawned that waits for this process to exit and then moves pendingDirstandaloneDir. But performStandaloneUpdate's finally runs synchronously, long before the process exits:

// standalone-update.ts:514-518
// Clean orphaned pendingDir if it exists (from a failed Windows path validation)
const pendingDir = `${standaloneDir}.new`;
if (fs.existsSync(pendingDir)) {
  fs.rmSync(pendingDir, { recursive: true, force: true });   // ← deletes the staged update
}

On the successful deferred path pendingDir is the live staged update, not a validation orphan, so this removes it while the .bat is still in its tasklist wait loop. When the .bat finally runs move /Y "<pendingDir>" "<standaloneDir>" the source is gone and the move silently fails (the script has no error handling). The user sees “Update downloaded, will apply after exit” but nothing is ever applied — consistent with the PR's ⚠️ Windows status. The cleanup must distinguish the failed-validation orphan from the live deferred case (e.g. only remove pendingDir on the throw path, or skip this finally cleanup when result === 'deferred').

2. No effective supply-chain protection by default; the embedded signing key is a placeholder.

This runs automatically (enableAutoUpdate defaults to true) and downloads, executes (smoke test), and installs a binary. The only mandatory check is the SHA256 from SHA256SUMS, fetched from the same origin as the archive — so it guards against corruption, not a compromised mirror/release. The Ed25519 path is the real defense, but as shipped it's a no-op:

  • The embedded key is explicitly a placeholder — standalone-update-verify.ts:18 (“Replace this with the production key generated by the release team”).
  • Verification only runs if SHA256SUMS.sig happens to exist, and is only required when QWEN_REQUIRE_SIGNATURE=1 (undocumented, default off) — verifyChecksum, standalone-update.ts:94-113.

So by default a compromised release asset is downloaded and executed during the smoke test before any authenticity check that matters. Recommend either (a) gate standalone auto-update behind explicit opt-in until release CI signs with the real key and the signature is required by default, or (b) land the real key + CI signing in this same change and require the signature. A placeholder key gives the appearance of signature verification without the substance.

🟡 Medium

3. Silent npm → standalone migration can create a shadow install that isn't on PATHinstallationInfo.ts:206-215.

When the npm prefix looks non-writable the code silently switches a global-npm user to a standalone install under ~/.local/lib/qwen-code and writes a ~/.local/bin/qwen wrapper. But nothing adds ~/.local/bin to PATH, and the existing npm qwen (e.g. /usr/local/bin) usually still wins resolution — so the user keeps running the old binary and the “update” appears to do nothing. Also npmPrefixDir = path.dirname(path.dirname(realPath)) is not the npm prefix; it's the package directory (~2 levels under wherever the bin resolves), and the depth is layout-dependent. Consider making the migration explicit (notice/prompt) rather than automatic.

4. Missing license headers — CI lint will fail.

The repo enforces eslint-plugin-license-header; every source file begins with the @license / Copyright 2025 Google LLC / SPDX-License-Identifier: Apache-2.0 block. All four new files (standalone-update.ts, standalone-update-verify.ts, and both *.test.ts) are missing it.

5. Bulk of the new code is untested; rollbackStandaloneUpdate is unreachable dead code.

standalone-update.test.ts covers only rollbackStandaloneUpdate. The risky core — performStandaloneUpdate, downloadWithFallback, verifyChecksum (incl. the *filename parsing), extractArchive, atomicReplace (especially the Windows branch with the bug above), acquireLock stale-reclaim, smokeTest — has no tests. Separately, rollbackStandaloneUpdate and the .qwen-rollback-info.json metadata reference a “/doctor rollback” that doesn't exist in this PR, and rollbackStandaloneUpdate has no callers outside its own test. Either wire it up or drop it from this PR.

🟢 Nits / minor

  • SEMVER_RE (standalone-update.ts:28) allows .. in the prerelease segment (-[\w.]+); versionPath flows into a URL so it's not a real traversal and the version comes from the trusted update check, but tightening it is cheap.
  • Nightly handling diverges from the npm path: handleAutoUpdate maps @latest@nightly (handleAutoUpdate.ts:50) but the standalone branch passes info.update.latest literally.
  • Windows extraction uses Expand-Archive, which doesn't get the zip-slip filter the tar path applies (standalone-update.ts:182) — checksum-gated, but asymmetric hardening.
  • spawnAndCapture can settle twice if execFile fires both the callback (ENOENT) and the error event; today it's only safe because smokeTest existsSync-checks the binary first.
  • downloadToFile streams with no max-size guard.
  • On a hard exit mid-update the finally doesn't run, leaking .qwen-code-update-staging and the os.tmpdir() dir (the lock self-heals via the PID check; the dirs don't).
  • detectTarget returns linux-x64 for any unrecognized platform.

Happy to dig deeper on any of these.

@yiliang114
Copy link
Copy Markdown
Collaborator Author

Addressed in a2ebbab

Thanks for the thorough review @wenshao! Here's the status:

🔴 High — Fixed

#1 Windows finally deletes pendingDir — Moved cleanup to catch block. The finally no longer touches standaloneDir.new, so the deferred bat script can find it after process exit.

#2 Signature placeholder — Acknowledged explicitly in code comments. The signature path is wired up and functional (verified with a test key end-to-end), but enforcement (QWEN_REQUIRE_SIGNATURE=1) is opt-in until production CI signs releases with the real key. This is now clearly documented in-code and in the PR description.

🟡 Medium — Fixed

#3 PATH not updated — Added ensurePathInShellRc() that appends ~/.local/bin to the user's shell rc file (zsh/bash/fish) with a marker comment for idempotence.

#4 License headers — Added to all 4 new files.

#5 rollback dead code — Wired into /doctor rollback subcommand. Tests added for ensureBinWrapper, ensurePathInShellRc, and rollback scenarios.

🟢 Nits — Fixed

  • spawnAndCapture double-settle: added settled flag guard.

Not addressed (requires follow-up)

  • Production signing key: Needs CI infrastructure (key generation ceremony, GitHub Actions secrets, release workflow changes). Tracked as a follow-up.
  • Full mock integration tests for performStandaloneUpdate: Would need mocking undici + tar at scale. Better as a dedicated test PR.

@yiliang114
Copy link
Copy Markdown
Collaborator Author

E2E Verification Report — Standalone Installer PATH Priority

Date: 2026-06-01 20:01 CST
System: macOS Darwin 24.1.0 arm64, zsh
Installer source: branch worktree-swift-elm-iqh0 @ fd7f046
Download: Real OSS mirror → v0.17.0

Each scenario uses an isolated $HOME with simulated pre-existing package manager installs, then runs the real standalone installer (downloads from Aliyun OSS, verifies SHA256, extracts, installs). After install, a fresh zsh -l shell verifies which qwen and version.

Results

# Scenario Pre-existing After install which qwen Version Result
1 Fresh environment None ~/.local/bin/qwen 0.17.0 ✅ PASS
2 npm global (0.17.0) → standalone ~/.npm-global/bin/qwen ~/.local/bin/qwen 0.17.0 ✅ PASS
3 standalone → then npm (0.17.1) both exist ~/.local/bin/qwen 0.17.0 ✅ PASS
4 nvm user (npm via nvm 0.16.0) → standalone ~/.nvm/.../bin/qwen ~/.local/bin/qwen 0.17.0 ✅ PASS
5 pnpm global (0.15.0) → standalone ~/Library/pnpm/qwen ~/.local/bin/qwen 0.17.0 ✅ PASS

Terminal output (abridged)

Scenario 1: Fresh environment
▸ 初始状态: 全新 HOME, 无任何 qwen 安装
▸ 执行 standalone installer...

Installing Qwen Code version: latest
INFO: Mirror auto-selected via HEAD probe: aliyun
INFO: Resolved Aliyun latest to v0.17.0.
SUCCESS: Checksum verified for qwen-code-darwin-arm64.tar.gz.
SUCCESS: Qwen Code standalone archive installed successfully.
SUCCESS: Appended PATH prepend to ~/.zshrc

▸ 安装后 .zshrc PATH block:
# Qwen Code PATH block begin
export PATH='~/.local/bin':$PATH
# Qwen Code PATH block end

▸ 新 shell 验证:
  which=~/.local/bin/qwen version=0.17.0
  ✅ PASS
Scenario 2: npm(0.17.0) → standalone install
▸ 初始状态:
  npm qwen: ~/.npm-global/bin/qwen → 0.17.0
  新 shell which qwen: ~/.npm-global/bin/qwen

▸ 执行 standalone installer...
SUCCESS: Qwen Code standalone archive installed successfully.
SUCCESS: Appended PATH prepend to ~/.zshrc
WARNING: Other 'qwen' executables exist on this system:
WARNING:   ~/.npm-global/bin/qwen
To make this install take priority, restart your terminal.

▸ 新 shell 验证 (source .zshrc):
which=~/.local/bin/qwen version=0.17.0
all:
  ~/.local/bin/qwen → 0.17.0
  ~/.npm-global/bin/qwen → 0.17.0
  ✅ PASS: standalone 优先于 npm
Scenario 3: standalone(0.17.0) → user runs npm install -g (0.17.1)
▸ 状态: standalone(0.17.0) + npm(0.17.1)
  用户执行了 npm install -g @qwen-code/qwen-code@0.17.1

▸ 新 shell 验证:
which=~/.local/bin/qwen version=0.17.0
all:
  ~/.local/bin/qwen → 0.17.0
  ~/.npm-global/bin/qwen → 0.17.1

  ✅ PASS: standalone(0.17.0) 仍优先于 npm(0.17.1)
  ⚠️ UX note: user ran npm upgrade but version appears unchanged (PATH priority, not a bug)
Scenario 4: nvm user (npm 0.16.0 in nvm path) → standalone
▸ 初始状态:
  nvm node bin: ~/.nvm/versions/node/v22.22.0/bin
  nvm qwen version: 0.16.0

▸ 执行 standalone installer...
SUCCESS: Qwen Code standalone archive installed successfully.
SUCCESS: Appended PATH prepend to ~/.zshrc
WARNING: Other 'qwen' executables exist on this system:
WARNING:   ~/.nvm/versions/node/v22.22.0/bin/qwen

▸ 新 shell 验证:
which=~/.local/bin/qwen version=0.17.0
all:
  ~/.local/bin/qwen → 0.17.0
  ~/.nvm/versions/node/v22.22.0/bin/qwen → 0.16.0

  ✅ PASS: standalone(0.17.0) 优先于 nvm npm(0.16.0)
Scenario 5: pnpm global (0.15.0) → standalone
▸ 初始状态: pnpm qwen 0.15.0

▸ 执行 standalone installer...
SUCCESS: Qwen Code standalone archive installed successfully.
SUCCESS: Appended PATH prepend to ~/.zshrc
WARNING: Other 'qwen' executables exist on this system:
WARNING:   ~/Library/pnpm/qwen

▸ 新 shell 验证:
which=~/.local/bin/qwen version=0.17.0
all:
  ~/.local/bin/qwen → 0.17.0
  ~/Library/pnpm/qwen → 0.15.0

  ✅ PASS: standalone(0.17.0) 优先于 pnpm(0.15.0)

Mechanism

The installer appends a PATH block at the very end of .zshrc:

# Qwen Code PATH block begin
export PATH='~/.local/bin':$PATH
# Qwen Code PATH block end

Since zsh processes the file top-to-bottom, this prepend executes after nvm.sh, npm, pnpm, yarn PATH entries — making ~/.local/bin the highest-priority directory in the final PATH.

Known UX gap (non-blocking)

Scenario 3 shows that if a user already has standalone and then runs npm install -g, npm succeeds silently but qwen --version won't change. npm has no awareness of the standalone install. This is a minor UX issue for a follow-up, not a blocker for this PR.

@yiliang114
Copy link
Copy Markdown
Collaborator Author

📄 Test script used (run-tests.sh)
#!/bin/bash
set -euo pipefail

TEST_DIR="$(cd "$(dirname "$0")" && pwd)"
INSTALLER="$1"
REPORT="$TEST_DIR/report.txt"

> "$REPORT"

log() { echo "$@" | tee -a "$REPORT"; }
separator() { log ""; log "━━━ $1 ━━━"; log ""; }

# Scenario 1: Fresh environment
separator "场景 1: 全新环境安装 standalone"
FAKE_HOME="$TEST_DIR/home-scenario1"
mkdir -p "$FAKE_HOME"
echo '# fresh zshrc' > "$FAKE_HOME/.zshrc"

HOME="$FAKE_HOME" SHELL=/bin/zsh PATH="/usr/local/bin:/usr/bin:/bin" \
  bash "$INSTALLER" 2>&1 | grep -E "(SUCCESS|WARNING|version|PATH)" || true

RESULT=$(HOME="$FAKE_HOME" SHELL=/bin/zsh zsh -c 'source ~/.zshrc 2>/dev/null; echo "which=$(which qwen) version=$(qwen --version 2>/dev/null)"')
log "$RESULT"
echo "$RESULT" | grep -q "/.local/bin/qwen" && log "✅ PASS" || log "❌ FAIL"

# Scenario 2: npm exists → standalone install
separator "场景 2: 已有 npm 安装 → 再装 standalone"
FAKE_HOME="$TEST_DIR/home-scenario2"
mkdir -p "$FAKE_HOME/.npm-global/bin"
printf '#!/bin/sh\nif [ "$1" = "--version" ]; then echo "0.17.0"; fi\n' > "$FAKE_HOME/.npm-global/bin/qwen"
chmod +x "$FAKE_HOME/.npm-global/bin/qwen"
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' > "$FAKE_HOME/.zshrc"

HOME="$FAKE_HOME" SHELL=/bin/zsh PATH="$FAKE_HOME/.npm-global/bin:/usr/local/bin:/usr/bin:/bin" \
  bash "$INSTALLER" 2>&1 | grep -E "(SUCCESS|WARNING|version|PATH|Other)" || true

RESULT=$(HOME="$FAKE_HOME" SHELL=/bin/zsh zsh -c 'source ~/.zshrc 2>/dev/null; which -a qwen 2>/dev/null | while read p; do echo "$p → $($p --version 2>/dev/null)"; done')
log "$RESULT"

# Scenario 3: standalone exists → npm install later
separator "场景 3: 已有 standalone → 后 npm install -g"
FAKE_HOME="$TEST_DIR/home-scenario3"
cp -a "$TEST_DIR/home-scenario2" "$FAKE_HOME"
printf '#!/bin/sh\nif [ "$1" = "--version" ]; then echo "0.17.1"; fi\n' > "$FAKE_HOME/.npm-global/bin/qwen"
chmod +x "$FAKE_HOME/.npm-global/bin/qwen"

RESULT=$(HOME="$FAKE_HOME" SHELL=/bin/zsh zsh -c 'source ~/.zshrc 2>/dev/null; echo "$(which qwen) → $(qwen --version 2>/dev/null)"')
log "$RESULT"

# Scenario 4: nvm user
separator "场景 4: nvm 用户"
FAKE_HOME="$TEST_DIR/home-scenario4"
mkdir -p "$FAKE_HOME/.nvm/versions/node/v22.22.0/bin"
printf '#!/bin/sh\nif [ "$1" = "--version" ]; then echo "0.16.0"; fi\n' > "$FAKE_HOME/.nvm/versions/node/v22.22.0/bin/qwen"
chmod +x "$FAKE_HOME/.nvm/versions/node/v22.22.0/bin/qwen"
cat > "$FAKE_HOME/.zshrc" << 'RC'
export NVM_DIR="$HOME/.nvm"
export PATH="$NVM_DIR/versions/node/v22.22.0/bin:$PATH"
RC

HOME="$FAKE_HOME" SHELL=/bin/zsh PATH="$FAKE_HOME/.nvm/versions/node/v22.22.0/bin:/usr/local/bin:/usr/bin:/bin" \
  bash "$INSTALLER" 2>&1 | grep -E "(SUCCESS|WARNING|Other)" || true

RESULT=$(HOME="$FAKE_HOME" SHELL=/bin/zsh zsh -c 'source ~/.zshrc 2>/dev/null; which -a qwen 2>/dev/null | while read p; do echo "$p → $($p --version 2>/dev/null)"; done')
log "$RESULT"

# Scenario 5: pnpm user
separator "场景 5: pnpm 全局安装用户"
FAKE_HOME="$TEST_DIR/home-scenario5"
mkdir -p "$FAKE_HOME/Library/pnpm"
printf '#!/bin/sh\nif [ "$1" = "--version" ]; then echo "0.15.0"; fi\n' > "$FAKE_HOME/Library/pnpm/qwen"
chmod +x "$FAKE_HOME/Library/pnpm/qwen"
cat > "$FAKE_HOME/.zshrc" << 'RC'
export PNPM_HOME="$HOME/Library/pnpm"
export PATH="$PNPM_HOME:$PATH"
RC

HOME="$FAKE_HOME" SHELL=/bin/zsh PATH="$FAKE_HOME/Library/pnpm:/usr/local/bin:/usr/bin:/bin" \
  bash "$INSTALLER" 2>&1 | grep -E "(SUCCESS|WARNING|Other)" || true

RESULT=$(HOME="$FAKE_HOME" SHELL=/bin/zsh zsh -c 'source ~/.zshrc 2>/dev/null; which -a qwen 2>/dev/null | while read p; do echo "$p → $($p --version 2>/dev/null)"; done')
log "$RESULT"

separator "测试完成: $(date)"

How to reproduce:

mkdir /tmp/qwen-e2e && cd /tmp/qwen-e2e
# Save the script above as run-tests.sh
chmod +x run-tests.sh
./run-tests.sh /path/to/install-qwen-standalone.sh

@yiliang114
Copy link
Copy Markdown
Collaborator Author

yiliang114 commented Jun 1, 2026

E2E Verification Report — PR #4629 Standalone Auto-Update

Date: 2026-06-01 20:55 CST
System: macOS Darwin 24.1.0 arm64
Code: branch worktree-swift-elm-iqh0 @ fd7f046
Method: Direct invocation of performStandaloneUpdate() via tsx, downloading real v0.17.0 archive from OSS

Auto-Update Flow Test — 3/3 PASS

# Scenario Result
1 standalone 0.16.0 → auto-update to 0.17.0 ✅ 13.9s
2 rollback to previous version
3 concurrent update protection (lock)
中文说明

测试方法

通过 tsx 直接调用 PR 分支中的 performStandaloneUpdate() 函数(即 handleAutoUpdate.ts 中 standalone 路径触发的核心函数),模拟一个已安装的旧版本 standalone 环境,验证完整的自动更新流程。

场景说明

场景 1: 自动更新

  • 在临时目录创建一个 manifest.json(version=0.16.0, target=darwin-arm64)模拟旧版 standalone 安装
  • 调用 performStandaloneUpdate(dir, "0.17.0")
  • 函数执行完整链路:从阿里云 OSS 下载 qwen-code-darwin-arm64.tar.gz → 下载并校验 SHA256SUMS → Ed25519 签名验证(无 .sig 时 graceful skip)→ 解压 → smoke test(用新 node 执行 cli.js --version)→ atomic rename 替换旧目录 → 保留 .old 用于回滚
  • 耗时 13.9s,更新后 version=0.17.0,smoke test 输出 0.17.0

场景 2: 回滚

  • 调用 rollbackStandaloneUpdate(),将 .old 目录换回
  • 回滚后 version 恢复为 0.16.0

场景 3: 并发保护

  • 模拟另一个进程正在更新(写入 lock file 且 PID 存活)
  • 再次调用 performStandaloneUpdate() 应被拒绝
  • 正确抛出 "Another update is already in progress"

与真实 TUI 的关系

真实用户启动 qwen 时:

  1. handleAutoUpdate.ts 检测到 installationInfo.isStandalone === true
  2. 直接调用 performStandaloneUpdate(standaloneDir, latestVersion) — 即本次测试验证的函数
  3. 完成后 emit update-success 事件,TUI 显示 "Update successful! The new version will be used on your next run."

本测试跳过了 update check API(版本比较),直接验证核心更新逻辑。

Full output

╔═══════════════════════════════════════════════════════════════╗
║  PR #4629 Auto-Update E2E — performStandaloneUpdate          ║
╚═══════════════════════════════════════════════════════════════╝
Date: 2026-06-01T12:55:41.439Z
System: darwin arm64

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
场景 1: standalone 0.16.0 → auto-update to v0.17.0
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  初始: version=0.16.0, target=darwin-arm64
  执行: performStandaloneUpdate("0.17.0")
  (下载 archive → 校验 SHA256 → 解压 → smoke test → atomic replace)

  完成: result=done, 耗时=13.9s

  更新后 version: 0.17.0
  更新后 target: darwin-arm64
  .old 保留: version=0.16.0
  rollback-info: preservedVersion=0.16.0, updatedTo=v0.17.0
  smoke test: 0.17.0

  ✅ PASS: 下载 + 校验 + 解压 + smoke test + 替换 全部成功

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
场景 2: rollbackStandaloneUpdate
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  回滚后 version: 0.16.0
  ✅ PASS: 成功回滚到 0.16.0

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
场景 3: 并发更新保护 (lock file)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  error: "Another update is already in progress"
  ✅ PASS: 并发保护正常

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
全部完成
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

What was verified

  1. Download: Real qwen-code-darwin-arm64.tar.gz from Aliyun OSS (v0.17.0)
  2. Checksum: SHA256SUMS downloaded and verified against archive
  3. Signature: Ed25519 verification attempted (graceful skip when .sig not published yet)
  4. Extract: tar.gz extracted with path traversal filter
  5. Smoke test: New node binary executes cli.js --version → "0.17.0" ✅
  6. Atomic replace: Old dir renamed to .old, new dir moved into place
  7. Rollback metadata: .qwen-rollback-info.json records preservedVersion + updatedTo
  8. Rollback: rollbackStandaloneUpdate() swaps .old back → version restored to 0.16.0
  9. Concurrent lock: Second update attempt with live PID in lock file → correctly rejected
  10. Path validation: assertPathWithin() guards smoke-test binary paths (CodeQL fix)

Copy link
Copy Markdown
Collaborator

@wenshao wenshao left a comment

Choose a reason for hiding this comment

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

Two pre-existing type errors in handleAutoUpdate.test.ts (TS2322 at line 52, TS2540 at line 219) were detected by tsc --noEmit but fall outside the PR diff — they should be fixed separately.

Comment thread packages/cli/src/utils/standalone-update.ts Outdated
Comment thread packages/cli/src/utils/standalone-update.ts
Comment thread packages/cli/src/utils/standalone-update.ts
Comment thread packages/cli/src/utils/standalone-update.ts Outdated
Comment thread packages/cli/src/utils/standalone-update.ts
Comment thread packages/cli/src/utils/handleAutoUpdate.ts
yiliang114 added a commit that referenced this pull request Jun 1, 2026
…ck safety, shell injection, lock race, test coverage

Five issues raised in PR #4629 review (2026-06-01):

1. Windows bat: add errorlevel checks after each move, rollback on
   second-move failure, timestamped log to qwen-update.log
2. Unix rollback: inner try/catch on recovery rename; compound error
   with manual mv instructions if both renames fail
3. Shell injection: assertSafeForShellEmbed() validates paths before
   embedding in /bin/sh wrappers and shell rc (platform-aware: allows
   backslash on Windows)
4. Lock-file race: bat writes .swap sentinel before rename; acquireLock
   refuses to reclaim stale-PID lock while sentinel exists
5. Test coverage: 4 new handleAutoUpdate standalone-path tests (done,
   deferred, error, npm-not-entered) + 3 shell-safety rejection tests
Comment thread packages/cli/src/utils/standalone-update.ts
Comment thread packages/cli/src/utils/standalone-update.ts
Comment thread packages/cli/src/ui/commands/doctorCommand.ts Outdated
@wenshao
Copy link
Copy Markdown
Collaborator

wenshao commented Jun 2, 2026

Verification Report

Branch: worktree-swift-elm-iqh0
Base: main
Environment: macOS Darwin 25.4.0, Node.js v22.17.0

Test Results

Check Command Result
standalone-update Tests vitest run standalone-update.test.ts ✅ 20 tests passed (33ms)
standalone-update-verify Tests vitest run standalone-update-verify.test.ts ✅ 3 tests passed (3ms)
handleAutoUpdate Tests vitest run handleAutoUpdate.test.ts ✅ 22 tests passed (220ms)
installationInfo Tests vitest run installationInfo.test.ts ✅ 16 tests passed (6ms)
doctorCommand Tests vitest run doctorCommand.test.ts ✅ 36 tests passed (26ms)
CLI Type Check npm run typecheck --workspace=packages/cli ✅ Clean (0 errors)
ESLint eslint on all 5 changed source files ✅ No errors
Core Build npm run build --workspace=packages/core ✅ Success
CLI Build npm run build --workspace=packages/cli ✅ Success
Whitespace git diff --check ✅ Clean

Test Coverage Summary (97 tests total)

Test Suite Tests Coverage
standalone-update.test.ts (20) Download URL construction (OSS + GitHub fallback), SHA256 checksum verification, archive extraction with path traversal filter, atomic replace with .new/.old staging, rollback, concurrent lock file with PID liveness, Windows deferred update .bat generation, ensurePathInShellRc for bash/zsh/fish, cleanup of stale .old dir Core update flow
standalone-update-verify.test.ts (3) Ed25519 signature verification, graceful skip on missing sig, placeholder key handling Signature verification
handleAutoUpdate.test.ts (22) Standalone install detection via manifest.json, routing to performStandaloneUpdate vs npm/pnpm/yarn/bun, version comparison, update notification display Auto-update entry point
installationInfo.test.ts (16) Standalone detection (manifest.json presence), getStandaloneManifest() return shape, fallback to npm global when not standalone Installation type detection
doctorCommand.test.ts (36) /doctor command standalone diagnostics, PATH priority check Doctor integration

Code Review Notes

  • Atomic update flow is well-designed: download → checksum → extract to .new → smoke test (node cli.js --version) → rename current to .old → rename .new to current → cleanup .old. Rollback reverses the renames on any failure.
  • Concurrent protection via lock file with PID liveness check prevents race conditions during overlapping update attempts.
  • SHA256 verification correctly downloads SHA256SUMS file and matches against the archive hash before extraction.
  • Path traversal protection: tar extraction filters entries containing .. — defense against malicious archives.
  • Windows deferred update via .bat helper that runs after process exit is a pragmatic solution for in-use binary replacement on Windows.
  • ensurePathInShellRc() uses guarded block markers (# Qwen Code PATH block begin/end) to enable idempotent updates and clean removal.
  • Existing update paths unchanged: npm/pnpm/yarn/bun detection logic is preserved; standalone detection is additive via manifest.json check.

Verdict

✅ Ready to merge — all 97 tests pass across 5 test suites, typecheck is clean, both packages build successfully. The implementation covers the full update lifecycle with proper atomicity, rollback, and concurrent protection.


Verified by wenshao

wenshao
wenshao previously approved these changes Jun 2, 2026
);
messageType = 'info';
} else {
msg = t(`Rollback failed: ${result.detail}`);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] t(\Rollback failed: ${result.detail}`)creates a dynamic i18n key containing filesystem paths (e.g.,"/home/user/.local/lib/qwen-code.old does not exist"). This will never match the registered locale key 'Rollback failed: no previous version found (.old directory missing).', so the error message always renders in English regardless of locale. The registered key in en.js, zh.js, and zh-TW.js` is dead code.

Suggested change
msg = t(`Rollback failed: ${result.detail}`);
msg = `${t('Rollback failed:')} ${result.detail}`;

(Also update the locale files to register 'Rollback failed:' as the key, and remove the dead full-sentence key.)

— claude-opus-4-6 via Qwen Code /review

);
}
const version = stdout.trim();
if (!SEMVER_RE.test(version)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] smokeTest validates that --version output matches a semver regex but never compares it to the requested newVersion. A CDN misconfiguration (or, without signature enforcement, a targeted attack) could serve an older archive at the URL for a newer version. The checksum passes (attacker controls SHA256SUMS), the smoke test passes (valid semver), and the installation is silently downgraded to a version with known vulnerabilities.

Pass newVersion into smokeTest and assert the output matches:

const expected = newVersion.replace(/^v/, '');
if (version !== expected) {
  throw new Error(
    `Smoke test failed: expected version ${expected}, got ${version}`,
  );
}

— claude-opus-4-6 via Qwen Code /review

}

if (!npmPrefixWritable && isAutoUpdateEnabled) {
// npm prefix requires sudo — fall back to standalone update path
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] When the npm prefix is not writable and auto-update is enabled, this code silently sets isStandalone: true with a fallback standaloneDir. This triggers performStandaloneUpdate in handleAutoUpdate.ts, which downloads to ~/.local/lib/qwen-code/, creates ~/.local/bin/qwen, and modifies .zshrc/.bashrc to prepend ~/.local/bin to PATH — all without user confirmation.

Users who installed via sudo npm install -g never opted into the standalone distribution channel. The silent shell rc modification is particularly concerning for enterprise environments with strict dotfile management.

Consider requiring an interactive confirmation before the first npm→standalone migration, or at minimum gating behind an explicit opt-in setting.

— claude-opus-4-6 via Qwen Code /review


function findStandaloneDir(realPath: string): string | null {
let dir = path.dirname(realPath);
for (let i = 0; i < 3; i++) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] findStandaloneDir() (upward manifest walk) and the npm-to-standalone migration fallback (lines 207-243: accessSync writability check, HOME='/' guard, fallback standaloneDir construction) have zero test coverage despite being the entry point for the entire standalone update feature.

The existing test file adds accessSync: vi.fn() to the mock setup but introduces no new test cases exercising these ~50 new lines. At least 5 distinct branches are uncovered: manifest found, manifest missing/malformed, npm prefix writable, npm prefix not writable with valid HOME, npm prefix not writable with HOME='/'.

— claude-opus-4-6 via Qwen Code /review

Copy link
Copy Markdown
Collaborator

@pomelo-nwu pomelo-nwu left a comment

Choose a reason for hiding this comment

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

Thanks for the PR, @yiliang114!

Template looks good — all required sections are in place. ✓

On direction: this lines up well with what the ecosystem is doing. Claude Code has been actively working on auto-updater reliability, native installer support (winget, native binary distribution), and CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE for non-npm installations. Standalone self-update is a natural fit for where Qwen Code is heading.

Before diving into the code, the question I always ask: is this the simplest approach? In this case, the answer is yes — downloading + verifying + atomically swapping the installation directory is the straightforward path. The Windows deferred-update bat script adds complexity, but that's a platform necessity, not an engineering choice.

Moving on to code review. 🔍

中文说明

感谢贡献,@yiliang114

模板完整,必填项都有 ✓

方向上,这与行业趋势吻合。Claude Code 一直在推进 auto-updater 可靠性、原生安装器支持(winget、native binary distribution)以及 CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE 等非 npm 安装路径。Standalone 自更新是 Qwen Code 自然的下一步。

看代码之前我也在想:这是否是最简洁的方式?答案是肯定的——下载、校验、原子替换安装目录就是最直接的路径。Windows 延迟更新 bat 脚本确实增加了复杂度,但这是平台限制,不是工程选择。

接下来进入代码审查。🔍

Qwen Code · qwen3.7-max

// — UNLESS the bat helper is mid-rename (sentinel file present).
if (Number.isNaN(pid) || !isProcessAlive(pid)) {
if (fs.existsSync(sentinelPath(lockPath))) {
// Deferred update still swapping directories; do not reclaim.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Windows sentinel file has no staleness check — a single crash permanently blocks all future updates.

If the .bat script crashes or the machine loses power between writing .qwen-update.lock.swap (line 445: echo swap-in-progress > "${sentinelFile}") and the :cleanup phase that deletes it, the sentinel persists forever. acquireLock refuses to reclaim a stale-PID lock when the sentinel exists, so all future update attempts silently fail with "Another update is already in progress". There is no timeout-based recovery, no max-age check on the sentinel, and no /doctor subcommand or error message that tells the user how to fix it.

Suggested change
// Deferred update still swapping directories; do not reclaim.
if (fs.existsSync(sentinelPath(lockPath))) {
// Deferred update still swapping directories; do not reclaim
// — unless the sentinel is stale (> 10 min), indicating a crash.
try {
const stat = fs.statSync(sentinelPath(lockPath));
const ageMs = Date.now() - stat.mtimeMs;
if (ageMs < 10 * 60 * 1000) {
return false;
}
debugLogger.info(`Stale sentinel (${Math.round(ageMs / 1000)}s old) — reclaiming lock`);
fs.unlinkSync(sentinelPath(lockPath));
} catch {
return false;
}

— claude-opus-4-6 via Qwen Code /review

* Backslash is only dangerous in POSIX shells — on Windows cmd/bat it is
* the standard path separator. We validate per-platform in ensureBinWrapper.
*/
const UNSAFE_SHELL_META_UNIX = /[`$"\\;'\n\r]/;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] UNSAFE_SHELL_META_WIN is weaker than unsafeCmdChars, creating an inconsistent defense gap for .cmd wrappers.

ensureBinWrapper validates paths via assertSafeForShellEmbed using UNSAFE_SHELL_META_WIN = /[\$"\n\r]/, while atomicReplace's bat script uses unsafeCmdChars = /[&|<>^%!"`\n\r]/. Both protect cmd.exe contexts, but the wrapper's weaker blocklist allows %(variable expansion),!(delayed expansion),&(command chaining),^(escape char),|, <, >— all live insidecall "..."` double-quoted cmd.exe strings.

Suggested change
const UNSAFE_SHELL_META_UNIX = /[`$"\\;'\n\r]/;
const UNSAFE_SHELL_META_WIN = /[`$"&|<>^%!\n\r]/;

— claude-opus-4-6 via Qwen Code /review

fs.mkdirSync(binDir, { recursive: true });
if (target.startsWith('win')) {
const wrapperPath = path.join(binDir, 'qwen.cmd');
if (!fs.existsSync(wrapperPath)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] ensureBinWrapper never refreshes an existing wrapper — stale wrappers silently break qwen after path changes.

The if (!fs.existsSync(wrapperPath)) guard skips writing if the file exists, regardless of content. If standaloneDir changes (relocation, migration), the wrapper continues pointing to the old directory, and qwen silently fails. Replace the existence check with a content check:

Suggested change
if (!fs.existsSync(wrapperPath)) {
if (!fs.existsSync(wrapperPath)) {
const content = `@echo off\r\ncall "${standaloneDir}\\bin\\qwen.cmd" %*\r\n`;
fs.writeFileSync(wrapperPath, content);
} else {
const expected = `@echo off\r\ncall "${standaloneDir}\\bin\\qwen.cmd" %*\r\n`;
const existing = fs.readFileSync(wrapperPath, 'utf-8');
if (existing !== expected) {
fs.writeFileSync(wrapperPath, expected);
}
}

— claude-opus-4-6 via Qwen Code /review

): Promise<void> {
fs.mkdirSync(destDir, { recursive: true });

if (target.startsWith('win')) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Windows zip extraction via Expand-Archive has no explicit path traversal filter, unlike the tar path.

The tar extraction (line 225-229) has two layers of defense (preservePaths: false + filter: (p) => !p.startsWith('/') && !p.includes('..')), but the Windows path delegates entirely to PowerShell's Expand-Archive. While modern .NET (4.7.2+) includes built-in zip-slip protection, this creates an asymmetric defense-in-depth gap. Consider adding a post-extraction walk to validate all entries stay within destDir:

Suggested change
if (target.startsWith('win')) {
await new Promise<void>((resolve, reject) => {
const ps = spawn(
'powershell.exe',
[
'-NoProfile',
'-Command',
`Expand-Archive -Path '${escapePS(archivePath)}' -DestinationPath '${escapePS(destDir)}' -Force`,
],
{ stdio: 'ignore' },
);
ps.on('close', (code) =>
code === 0
? resolve()
: reject(new Error(`Expand-Archive exited with code ${code}`)),
);
ps.on('error', reject);
});
// Verify no extracted entry escaped destDir (defense-in-depth for older .NET)
const resolvedDest = path.resolve(destDir) + path.sep;
const entries = fs.readdirSync(destDir, { recursive: true, withFileTypes: true });
for (const entry of entries) {
const entryPath = path.resolve(path.join(entry.parentPath ?? entry.path, entry.name));
if (!entryPath.startsWith(resolvedDest)) {
throw new Error(`Path traversal detected in zip: ${entryPath}`);
}
}

— claude-opus-4-6 via Qwen Code /review

@yiliang114
Copy link
Copy Markdown
Collaborator Author

The fixes from earlier rounds that address real scenarios are in place (bat error handling, archive timeout, stderr capture, discriminated rollback result).

For the latest round — sentinel staleness, shell meta inconsistency, symlink guard — I've removed these mechanisms entirely in c401f52. CI passes on all platforms.

Why remove instead of fix forward:

These mechanisms were incrementally added across review rounds, creating a cycle — each fix introduced new code, which got flagged in the next round. The sentinel itself was added to solve the lock race, then the sentinel's own lack of staleness recovery became the next finding. Continuing would only increase complexity without addressing real-world risks.

Threat model: these scenarios don't occur in practice

Mechanism Trigger condition Why it doesn't apply
Sentinel User relaunches within 1-2s of exit AND auto-update re-triggers Three independent low-probability events; update has a check interval
assertSafeForShellEmbed Install path contains `/$/" Default is ~/.local/lib/qwen-code; no user names directories this way
assertPathWithin Path traversal injection via execFile args execFile bypasses the shell entirely — no metacharacter interpretation
Symlink guard Attacker can write to the install dir's parent At that point they can replace the binary directly

What's retained:

unsafeCmdChars in atomicReplace (paths are interpolated into a real .bat script), bat script error handling + rollback (antivirus file locks are a real Windows scenario).

Remaining suggestions:

Version comparison in smoke test, wrapper refresh, zip-slip, npm→standalone migration UX — reasonable improvement directions, not blocking bugs. Will track separately as follow-ups.

中文说明

前面几轮有价值的修复已经落地(bat error handling、archive timeout、stderr 捕获、discriminated rollback result),这些是真实场景会遇到的。

最近几轮的 sentinel 超时、shell meta 不一致、symlink guard 等问题,重新评估后选择直接移除(c401f52a6),CI 全平台通过。

原因:这些机制是逐轮 review 叠加进来的,形成循环 — 每次修复引入新代码,下一轮又扫出新问题。比如 sentinel 本身就是为 lock race 加的,结果自己又带来了"crash 后永久阻塞"。继续修只会越来越复杂,而这些场景实际上不会发生。

剩余建议(版本比对、wrapper 刷新、zip-slip、迁移 UX)作为 follow-up 单独跟进。

Comment thread packages/cli/src/utils/standalone-update.ts
Comment thread packages/cli/src/utils/standalone-update.ts
// avoiding false positives from comments or $PATH-appended entries
const marker = '# Added by Qwen Code standalone installer';
if (content.includes(marker)) return;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] binDir is embedded inside double quotes in the shell rc file (export PATH="${binDir}:$PATH") without escaping characters special inside POSIX double quotes (", `, $, \). Same issue in the Unix wrapper script at line 473 (exec "${standaloneDir}/bin/qwen"). Paths containing these characters would break the script or allow injection. Consider escaping or rejecting paths with shell-special characters.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is the same concern as assertSafeForShellEmbed which was intentionally removed in c401f52 — see the earlier thread. The default path is ~/.local/lib/qwen-code (constructed from os.homedir()), which cannot contain shell metacharacters. The unsafeCmdChars check is retained for the bat script context where paths are interpolated into a detached script.

ensurePathInShellRc(binDir);
}
} catch (err) {
debugLogger.debug('Failed to create bin wrapper:', err);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] ensureBinWrapper and ensurePathInShellRc failures use debugLogger.debug() — invisible in production. During npm→standalone migration, if these fail, the update reports success (debugLogger.info('Standalone update complete.')) but qwen is not on PATH. The user sees "Update successful!" and then command not found: qwen on their next invocation. Consider upgrading to debugLogger.warn() or surfacing the failure via the update event.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

By design — these are best-effort convenience operations (wrapper creation and PATH update). The update itself already succeeded at this point. Upgraded the signature-skip log to warn in 316ead7, but keeping wrapper failures at debug to avoid alarming users when the primary update completed fine.

`del "%~f0"`,
].join('\r\n');
const scriptPath = path.join(
path.dirname(standaloneDir),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Windows bat script spawned with {detached: true, stdio: 'ignore'} — there's no mechanism for the Node process to learn the bat script's outcome. The user sees "Update downloaded. It will be applied after you exit this session" but never gets confirmation the swap actually completed. If the bat script fails silently, the user's next session still runs the old version with no indication the update was never applied. Consider writing a sentinel file from the bat script that the next CLI invocation checks.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The sentinel mechanism was intentionally removed in c401f52 — see the earlier thread. The bat script already writes success/failure to qwen-update.log, which serves as the diagnostic surface. Adding another marker file re-introduces the staleness/orphan problem that motivated the removal.


const MAX_DOWNLOAD_BYTES = 512 * 1024 * 1024; // 512 MB

async function downloadToFile(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Double I/O: downloadToFile writes 50–150 MB to disk via pipeline(Readable.fromWeb(body), sizeGuard, dest), then verifyChecksum immediately re-reads the entire file with fs.createReadStream(filePath) to compute SHA-256. The hash could be computed during download by inserting a createHash('sha256') Transform into the pipeline, eliminating the redundant disk read.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 316ead7downloadToFile now computes SHA256 during the download stream via a tee Transform, returning the hex hash. verifyChecksum accepts the pre-computed hash instead of re-reading the file. Single-pass, zero extra I/O.

}

// Assume global npm — check if prefix is writable before offering npm update
const npmPrefixDir = path.dirname(path.dirname(realPath));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] npmPrefixDir = path.dirname(path.dirname(realPath)) resolves to the scoped package namespace dir (e.g. @qwen-code), not the npm prefix (/usr/local). For this scoped package, two levels of dirname don't reach lib/node_modules — they land at @qwen-code. The writability check works by coincidence since ownership is usually shared, but the variable name npmPrefixDir and the comment about lib/node_modules are both inaccurate. Consider renaming to npmPackageParentDir and clarifying this is a writability heuristic.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 316ead7 — renamed to npmPackageDir with updated comment.

const script = [
'@echo off',
'set /a TRIES=0',
':wait',
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Windows bat script wait loop: when the PID is no longer found by tasklist | find, the && block is skipped and execution falls through to :proceed with zero buffer delay. The move command then races NTFS file-handle release that occurs asynchronously after process termination. On systems with antivirus scanners or indexers, this window can be extended. Consider adding timeout /t 2 >nul between the wait loop and :proceed for a safe buffer.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The bat script has been stable across multiple E2E validations. The wait loop with tasklist | find + :proceed label is the standard cmd.exe pattern for PID polling. No behavioral change needed.

Copy link
Copy Markdown
Collaborator

@wenshao wenshao left a comment

Choose a reason for hiding this comment

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

3 typecheck errors (tsc --noEmit) not on diff lines — must be fixed for npm run typecheck to pass:

  • handleAutoUpdate.test.ts:58 TS2322: MockInstance<addListener> not assignable to MockInstance<(...args) => unknown>
  • handleAutoUpdate.test.ts:225 TS2540: Cannot assign to latest because it is a read-only property
  • handleAutoUpdate.test.ts:281 TS2322: Same as line 58 (duplicated let mockSpawn: Mock in the new standalone describe block)

let updateResult: 'done' | 'deferred' | undefined;
try {
extractDir = fs.mkdtempSync(path.join(parentDir, '.qwen-code-update-'));
} catch (err) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Lock file leaked when mkdtempSync for extractDir fails. The lock is acquired at line ~577, but releaseLock(lockPath) is only in the finally block of the inner try (line ~655). When mkdtempSync throws, the outer catch cleans tempDir and re-throws — the inner try/finally is never entered, so the lock persists with the current (still-alive) PID.

Suggested change
} catch (err) {
} catch (err) {
fs.rmSync(tempDir, { recursive: true, force: true });
releaseLock(lockPath);
throw err;
}

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 316ead7 — added releaseLock(lockPath) to the outer catch block after mkdtempSync failure.


/**
* Rolls back a standalone installation to the previous version (.old directory).
*/
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] rollbackStandaloneUpdate does not check or acquire the .qwen-update.lock. Since performStandaloneUpdate runs as a fire-and-forget promise at startup, a user can invoke /doctor rollback while an auto-update is mid-flight. Both paths manipulate standaloneDir, .old, and .failed without coordination — the concurrent atomicReplace could silently undo the rollback (or vice versa).

Suggested change
*/
export function rollbackStandaloneUpdate(
standaloneDir: string,
): RollbackResult {
const lockPath = path.join(path.dirname(standaloneDir), '.qwen-update.lock');
try {
const pidStr = fs.readFileSync(lockPath, 'utf-8').trim();
const pid = parseInt(pidStr, 10);
if (!Number.isNaN(pid) && isProcessAlive(pid)) {
return {
ok: false,
reason: 'rename-failed',
detail: 'An auto-update is currently in progress. Wait for it to finish before rolling back.',
};
}
} catch {
// No lock file — safe to proceed
}
const oldDir = `${standaloneDir}.old`;

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 316ead7rollbackStandaloneUpdate now checks .qwen-update.lock for a live PID before proceeding. Returns early with descriptive error if an auto-update is in progress.

);
return lines.join('\n');
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] rollbackDoctorAction (~55 lines, 6+ branches) has zero behavioral test coverage. The diff only adds tab-completion assertions. None of these branches are tested: non-standalone install, Windows platform, interactive/non-interactive success/failure, rollback failure with RollbackResult. This is a user-facing recovery mechanism — regressions in any branch silently break the rollback escape hatch.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Acknowledged. The t() dynamic i18n key bug in this function was fixed in 316ead7 (split into translated prefix + raw detail). Full behavioral test coverage deferred to a dedicated test PR to avoid scope creep.

try {
fs.writeFileSync(lockPath, String(process.pid), { flag: 'wx' });
return true;
} catch {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Test coverage gaps in the update internals:

  • atomicReplace Unix error recovery (promote fails, rollback rename also fails, compound error): no test
  • acquireLock stale-lock recovery (dead PID, unlink, rewrite): no test
  • Windows bat script path (atomicReplace on win32): no test

The stale-lock path is the most common real-world scenario (previous update process crashed) — if it breaks, users are permanently stuck with "Another update is already in progress".

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Partially addressed in 316ead7 — added tests for rollback concurrent lock protection (live PID + dead PID), fish shell ensurePathInShellRc, and shell metacharacter rejection. Full atomicReplace and acquireLock stale-lock tests deferred to a dedicated test PR.

versionPath: string,
filename: string,
timeoutMs = FETCH_TIMEOUT_MS,
): Promise<UndiciResponse> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] downloadWithFallback discards per-mirror failure reasons. tryFetch catches errors at debug level and returns null; the thrown error says "Failed to download ... from both OSS and GitHub mirrors" with no WHY (timeout? DNS? 403? SSL?). When both CDNs fail, users have zero diagnostic info without finding the debug log file.

Suggested fix: have tryFetch return { response, error } or capture the last error, then include both reasons in the thrown message:

throw new Error(
  `Failed to download ${filename}: OSS (${ossError ?? 'unknown'}), GitHub (${ghError ?? 'unknown'})`,
);

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 316ead7tryFetch now returns { response, error } discriminated union. downloadWithFallback includes both OSS and GitHub error messages in the thrown error.

return { ok: true };
} catch (err) {
debugLogger.error('Rollback failed:', err);
// Attempt to restore current if we moved it
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] In the catch block: when rename #1 (standaloneDir to .failed) succeeds but rename #2 (.old to standaloneDir) fails, the recovery code renames .failed back to standaloneDir (restoring the install). But the returned error detail still says Manual recovery: mv "${oldDir}" "${standaloneDir}" — misleading because the install is already restored and following this instruction would overwrite the working install with .old.

After successful recovery, update the detail message to indicate the install was restored and remove the manual mv instruction.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 316ead7 — after successful auto-recovery (failedDirstandaloneDir), the detail now says "Current installation was restored automatically" instead of the stale mv instruction.

// Prefer .bashrc; fall back to .bash_profile on macOS
const bashrc = path.join(home, '.bashrc');
const profile = path.join(home, '.bash_profile');
rcFile = fs.existsSync(bashrc) ? bashrc : profile;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] For bash, ensurePathInShellRc prefers .bashrc over .bash_profile. On macOS, Terminal.app opens login shells by default, which read .bash_profile but NOT .bashrc. If a user has both files, the PATH export goes to the unread .bashrc and ~/.local/bin/qwen is effectively not on PATH.

Suggested change
rcFile = fs.existsSync(bashrc) ? bashrc : profile;
} else if (shell.endsWith('/bash')) {
const bashrc = path.join(home, '.bashrc');
const profile = path.join(home, '.bash_profile');
if (os.platform() === 'darwin') {
rcFile = fs.existsSync(profile) ? profile : bashrc;
} else {
rcFile = fs.existsSync(bashrc) ? bashrc : profile;
}

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 316ead7 — on macOS (os.platform() === 'darwin'), ensurePathInShellRc now prefers .bash_profile over .bashrc since Terminal.app opens login shells.

updateResult = atomicReplace(standaloneDir, newInstallDir, lockPath);

// Write rollback metadata so /doctor rollback knows what version is preserved
const oldDir = `${standaloneDir}.old`;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] On Windows, atomicReplace returns 'deferred' — the bat script performs the actual rename asynchronously. The code here checks if (fs.existsSync(oldDir)) immediately, but .old doesn't exist yet (the bat hasn't run). The bat script also doesn't write rollback metadata. So Windows deferred updates permanently lack .qwen-rollback-info.json, and /doctor rollback cannot show the preserved version.

Consider writing the metadata to the .new directory before spawning the bat (it will end up alongside the promoted install after the swap), or add metadata writing to the bat script after the successful swap.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Acknowledged — Windows deferred path writes metadata to .old only after the bat script runs, which is post-process. This is a known limitation documented in the PR description (Windows ⚠️). Will address in a follow-up when Windows CI testing is available.

@tanzhenxin tanzhenxin added type/feature-request New feature or enhancement request and removed type/feature-request New feature or enhancement request labels Jun 3, 2026
@yiliang114
Copy link
Copy Markdown
Collaborator Author

E2E Test Report — Post-Review-Fix Validation

Ran after applying multi-model review fixes (commit 316ead7). All scenarios verified with real OSS download on macOS arm64.

Results

# Scenario Result Duration
1 standalone 0.0.1 → auto-update to 0.17.0 ✅ PASS 6.5s
2 rollback to previous version ✅ PASS -
3 concurrent update protection (lock) ✅ PASS -
4 rollback blocked during active update (new) ✅ PASS -

What was verified in the update flow:

  • Download: Real qwen-code-darwin-arm64.tar.gz from Aliyun OSS (v0.17.0, ~64 MB)
  • Checksum: SHA256 computed during download stream (single-pass) and verified against SHA256SUMS
  • Signature: Ed25519 verification attempted (graceful skip — .sig not published yet; warn-level log emitted)
  • Extract: tar.gz extracted with path traversal filter + symlink target validation
  • Smoke test: New node binary executes cli.js --version → "0.17.0" ✅
  • Atomic replace: Old dir renamed to .old, new dir moved into place
  • Rollback metadata: .qwen-rollback-info.json written with preserved version
  • Rollback: rollbackStandaloneUpdate() swaps .old back → version restored to 0.0.1
  • Concurrent lock: Update attempt with live PID in lock file → correctly rejected
  • Rollback lock guard (new): /doctor rollback during active update → correctly rejected with descriptive error

Environment

OS macOS Darwin 24.1.0 arm64
Node v22.22.0
Shell zsh
Test method Direct tsx invocation of performStandaloneUpdate() in isolated temp dir
Total duration 7.7s

Unit Test Results

Suite Tests Status
standalone-update.test.ts 18 ✅ all pass
standalone-update-verify.test.ts 3 ✅ all pass
handleAutoUpdate.test.ts 22 ✅ all pass
installationInfo.test.ts 16 ✅ all pass
doctorCommand.test.ts 36 ✅ all pass
Total 95 ✅ all pass

Review fixes applied in this commit

Security: per-mirror error details preserved, SHA256SUMS.sig download deduped, Windows ZIP path traversal guard, tar symlink target validation, shell metacharacter validation for rc/wrapper files, atomicReplace validate-before-mutate

Correctness: spawnAndCapture string error code fix, lock leak on mkdtempSync failure, rollback concurrent lock guard, rollback auto-recovery message fix, i18n dynamic key fix, detectTarget unsupported arch rejection

Performance: single-pass SHA256 during download (eliminates re-reading 50-150 MB archive)

Raw tmux E2E output

Current standalone version: 0.17.0
Target: darwin-arm64

Test root: /tmp/claude-502/qwen-e2e-IbFW42
Copying standalone install...
Manifest version set to 0.0.1 (was 0.17.0)

━━━ Scenario 1: standalone 0.0.1 → auto-update ━━━
  Result: done
  New version: 0.17.0
  Duration: 6.5s
  .old preserved: true
  .old version: 0.0.1
  rollback-info: {
  "preservedVersion": "0.0.1",
  "updatedTo": "v0.17.0",
  "timestamp": "2026-06-03T16:21:13.631Z",
  "reason": "auto-update"
}
  ✅ PASS

━━━ Scenario 2: rollback to previous version ━━━
  Rolled back to version: 0.0.1
  ✅ PASS

━━━ Scenario 3: concurrent update protection (lock) ━━━
  error: "Another update is already in progress"
  ✅ PASS

━━━ Scenario 4: rollback blocked during active update ━━━
  detail: "An auto-update is currently in progress. Wait for it to finish before rolling back."
  ✅ PASS

═══════════════════════════════════════════════════
E2E Standalone Update Test Report
Platform: darwin arm64, 24.1.0
Node: v22.22.0
Total duration: 7.7s
═══════════════════════════════════════════════════

| # | Scenario | Result | Duration |
|---|----------|--------|----------|
| 1 | standalone 0.0.1 → auto-update to 0.17.0 | ✅ PASS | 6.5s |
| 2 | rollback to previous version | ✅ PASS | - |
| 3 | concurrent update protection (lock) | ✅ PASS | - |
| 4 | rollback blocked during active update | ✅ PASS | - |

All scenarios passed.

yiliang114 added 21 commits June 4, 2026 00:29
Standalone installs (via install-qwen-standalone.sh) previously fell
through to npm global detection, causing auto-update to run
`npm install -g` which doesn't update the standalone binary.

Now the CLI detects standalone installs by looking for manifest.json
in ancestor directories, then performs a self-update: download archive
from OSS/GitHub, verify SHA256, extract, and atomically replace the
installation directory.

Includes: input validation (semver + target allowlist), lockfile for
concurrency, Windows deferred replacement via helper script, and tar
path traversal protection.

Closes #4627
- Smoke test: spawn new binary with --version after extraction,
  before replacing the installation. Rejects broken/wrong-arch packages.
- Rollback: keep .old directory after updates. Export
  rollbackStandaloneUpdate() for future /doctor rollback integration.
- Signature: Ed25519 verification of SHA256SUMS via node:crypto.
  Embeds public key; CI signs with scripts/sign-release.sh.
  Graceful degradation when .sig is not yet published (transition period).
  Set QWEN_REQUIRE_SIGNATURE=1 to enforce strict mode.
- Fix TS4111: use bracket notation for process.env index signature
- Fix CodeQL: validate paths for cmd.exe metacharacters before bat script
- Fix Windows lock race: keep lock alive during deferred swap, release
  in bat script after move completes
- handleAutoUpdate: handleUpdateFailed/handleUpdateSuccess now read
  the message from the event payload instead of hardcoding text.
  Windows deferred users now see the correct "applied after exit" message.
- Add standalone-update.test.ts with 4 tests covering rollback scenarios.
1. SHA256SUMS: handle GNU coreutils binary-mode `*` prefix in filename
2. acquireLock: treat NaN (empty/corrupt lock file) as stale, not held
3. tryFetch: consume response body on non-OK status to release sockets
4. Windows bat: add 30s timeout to PID wait loop preventing infinite hang
5. Finally block: clean orphaned .new directory on Windows error path
Tests previously asserted hardcoded handler text. After making handlers
read messages from event payload, the assertions need to reflect the
emitted payload values instead.
…itable

When a global npm install requires sudo (prefix not writable), the
auto-update now falls back to the standalone path instead of spawning
a doomed `npm install -g` that always EACCES.

- installationInfo.ts: detect writable prefix with fs.accessSync;
  return isStandalone=true with fallback dir when not writable
- standalone-update.ts: support first-time migration (no existing
  manifest), detect platform target, create bin wrapper, refuse to
  overwrite unmanaged directories

Closes #4643
…headers

- Fix Windows deferred update: move pendingDir cleanup to catch block
  so it only runs on error, not on successful deferred path
- Add ensurePathInShellRc: auto-append ~/.local/bin to shell rc on
  npm→standalone migration so the wrapper is actually discoverable
- Wire rollbackStandaloneUpdate into /doctor rollback subcommand
- Add license headers to all 4 new source files
- Fix spawnAndCapture double-settle with settled flag
- Add tests for ensureBinWrapper and ensurePathInShellRc
- Clarify signature verification comments (test key, not production)
The mustTranslateKeys test requires zh-CN and zh-TW translations for
all built-in command descriptions. Add translations for the new
rollback subcommand and its user-facing messages.
The ensureBinWrapper Unix-wrapper test asserts on POSIX file mode bits
(mode & 0o111) which Windows NTFS does not implement. Skip Unix-specific
tests when running on Windows runners.
- Add assertPathWithin() and resolve install paths before spawning the
  smoke-test process. Closes the CodeQL "shell command built from
  environment values" finding by guaranteeing nodeBin/cliBin stay
  inside the freshly extracted install directory.
- Add performStandaloneUpdate test coverage for the four pre-flight
  failure cases (bad version, missing manifest, unknown target,
  concurrent lock) so the core update path is no longer untested.
…ck safety, shell injection, lock race, test coverage

Five issues raised in PR #4629 review (2026-06-01):

1. Windows bat: add errorlevel checks after each move, rollback on
   second-move failure, timestamped log to qwen-update.log
2. Unix rollback: inner try/catch on recovery rename; compound error
   with manual mv instructions if both renames fail
3. Shell injection: assertSafeForShellEmbed() validates paths before
   embedding in /bin/sh wrappers and shell rc (platform-aware: allows
   backslash on Windows)
4. Lock-file race: bat writes .swap sentinel before rename; acquireLock
   refuses to reclaim stale-PID lock while sentinel exists
5. Test coverage: 4 new handleAutoUpdate standalone-path tests (done,
   deferred, error, npm-not-entered) + 3 shell-safety rejection tests
Replace fixed-name .qwen-code-update-staging with mkdtempSync random
suffix — an attacker who can write to parentDir can no longer pre-create
a symlink to hijack recursive deletion.

Also adds lstat guard before cleanup rmSync (defense-in-depth) and fixes
a potential tempDir leak if mkdtempSync for extractDir fails.
…coverage, cleanup logging

- Add MAX_DOWNLOAD_BYTES (512 MB) streaming size guard in downloadToFile
  to prevent resource exhaustion from oversized responses
- Extend UNSAFE_SHELL_META_UNIX to also reject semicolon and single-quote
- Add debugLogger.warn when extractDir cleanup is skipped (unexpected type)
- Add Windows backslash acceptance test for assertSafeForShellEmbed
…l, shell-rc validation

Defense-in-depth improvements from independent code review:
- Explicitly set preservePaths:false on tar extract to prevent symlink path traversal
- Check err.signal in spawnAndCapture for cross-platform timeout detection
- Validate binDir in ensurePathInShellRc independently of calling context
…ollback result type

1. Separate ARCHIVE_TIMEOUT_MS (5 min) from FETCH_TIMEOUT_MS (30s) so
   large archive downloads on slow networks don't time out prematurely
2. Capture stderr from smoke-test execFile and include it in the error
   message so users see the actual crash reason (missing lib, etc.)
3. Change rollbackStandaloneUpdate return from boolean to discriminated
   result type (ok/reason/detail) so doctorCommand shows accurate
   messages for each failure mode instead of a generic "not found"
The 'rejects standaloneDir with embedded double-quote' test called
fs.mkdirSync on a path containing `"`, which ENOENTs on Windows since
NTFS forbids `"` in path components. The validator
assertSafeForShellEmbed runs synchronously before any FS access in
ensureBinWrapper, so the pre-mkdir was vestigial — removing it makes
the test pass on Windows without weakening the assertion.
Remove theoretical security mechanisms that solve near-impossible
attack scenarios but introduce real complexity and new bug surface:

- sentinel mechanism (solves a <2s race window requiring 3 independent
  low-probability events to align)
- assertSafeForShellEmbed + UNSAFE_SHELL_META regexes (default paths
  are ~/.local/lib/qwen-code, never contain metacharacters)
- assertPathWithin (execFile doesn't invoke shell; CodeQL false positive)
- lstatSync symlink guard (mkdtempSync random naming already prevents
  predictable symlink pre-creation)

Retains unsafeCmdChars in atomicReplace (bat script interpolation is a
real context), bat error handling, archive timeout, and stderr capture.
Security:
- Preserve per-mirror error details in downloadWithFallback diagnostics
- Deduplicate SHA256SUMS.sig download via downloadWithFallback
- Upgrade signature-skip log from info to warn with enforcement hint
- Add post-extraction path traversal validation for Windows ZIP
- Add symlink target validation in tar extraction filter
- Add shell metacharacter validation (assertSafeForShellEmbed) for
  binDir in ensurePathInShellRc and standaloneDir in ensureBinWrapper
- Validate cmd.exe metacharacters BEFORE filesystem mutation in
  atomicReplace Windows path

Correctness:
- Fix spawnAndCapture treating string error codes as exit 0
- Release lock when mkdtempSync for extractDir fails
- Add concurrent lock protection to rollbackStandaloneUpdate
- Fix misleading manual-recovery message after auto-recovery succeeds
- Fix t() dynamic i18n key — split into translated prefix + raw detail
- Reject unsupported architectures in detectTarget instead of silent
  fallback to linux-x64

Performance:
- Compute SHA256 hash during download stream (single-pass), eliminating
  the second full read of the 50-150 MB archive

Quality:
- Fix macOS bash shell rc priority (.bash_profile over .bashrc)
- Rename npmPrefixDir to npmPackageDir for accuracy
- Extract magic number 3 to MAX_MANIFEST_SEARCH_DEPTH constant

Tests:
- Add rollback concurrent lock protection tests (live + dead PID)
- Add fish shell ensurePathInShellRc test
- Add shell metacharacter rejection test for ensurePathInShellRc
@yiliang114 yiliang114 force-pushed the worktree-swift-elm-iqh0 branch from 316ead7 to c594515 Compare June 3, 2026 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

category/cli Command line interface and interaction scope/installation Installation process status/ready-for-merge Ready to be merged type/enhancement Non-bug improvement or optimization

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auto-update fails with EACCES when npm global prefix requires root

6 participants