Skip to content

feat(web-shell): add artifact right panel - #6591

Merged
ytahdn merged 33 commits into
QwenLM:mainfrom
chiga0:feat/web-shell-right-panel
Jul 11, 2026
Merged

feat(web-shell): add artifact right panel#6591
ytahdn merged 33 commits into
QwenLM:mainfrom
chiga0:feat/web-shell-right-panel

Conversation

@ytahdn

@ytahdn ytahdn commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Adds a right-side panel experience to web-shell for reviewing turn outputs. Edited files now appear as a dedicated turn card with line statistics, expandable per-file diffs, a draggable review panel, file list/tree navigation, and responsive layout behavior. Artifacts and scheduled tasks are shown as separate turn output cards and can open in right-panel tabs, with HTML artifacts rendered in a sandboxed iframe and scheduled task snapshots shown with action controls.

The web-shell artifact flow now connects to daemon artifact snapshots and artifact change events, so recorded artifacts can be loaded from the daemon, refreshed when the daemon reports changes, and associated with the turn that produced them. The public web-shell API also supports overriding turn-output open behavior and choosing which turn output card types should be rendered.

Why it's needed

Web-shell previously had no integrated surface for reviewing generated files, artifacts, or scheduled-task outputs after a turn. Users had to infer outputs from tool text or external file state, which made multi-file edits, generated HTML reports, and scheduled task creation harder to inspect. This PR gives those outputs a stable, session-scoped review surface while keeping files, artifacts, and scheduled tasks visually distinct.

Reviewer Test Plan

How to verify

Run web-shell and complete a turn that edits files, records an HTML artifact, and creates or updates a scheduled task. Confirm the message area shows separate cards for edited files, artifacts, and scheduled tasks. Click Review and verify the right panel opens with a file list, file tree, resizable panel sections, and CodeMirror diffs with line statistics. Click an artifact and verify it opens as a separate closable tab; HTML content should render in an iframe without a detail wrapper. Click a scheduled task and verify the snapshot/detail view opens with available actions. Switch sessions and confirm right-panel state is scoped per session.

Evidence (Before & After)

Before: web-shell did not expose a right-side review surface for turn artifacts, generated files, or scheduled task snapshots. After: turn outputs render as cards below the relevant message, and the right-side panel can inspect file diffs, artifacts, and scheduled tasks with session-scoped tabs.

Local verification completed:

cd packages/sdk-typescript && npm run build
cd packages/sdk-typescript && npx vitest run test/unit/daemonUi.test.ts
cd packages/webui && npm run build
cd packages/webui && npx vitest run src/daemon/session/DaemonSessionProvider.test.tsx
cd packages/web-shell && npm run build
cd packages/web-shell && npx vitest run client/components/artifacts/turnOutputSelectors.test.ts client/components/ChatPane.test.tsx client/App.test.tsx client/build-artifact.test.ts

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested
🐧 Linux ⚠️ not tested

Environment (optional)

Local Node/npm workspace on macOS; no sandbox-specific behavior was required for the verified unit/build checks.

Risk & Scope

  • Main risk or tradeoff: this adds a sizeable web-shell UI surface and CodeMirror-based diff rendering, so the main review focus should be layout behavior, session switching, artifact association, and iframe safety.
  • Not validated / out of scope: manual cross-browser visual QA, Windows/Linux local runs, and a full end-to-end daemon session recording flow were not run locally.
  • Breaking changes / migration notes: none expected; the new right-panel override and turn-output visibility props are additive.

Linked Issues

N/A

Pictures

代码审核
image

artifact (普通文件)
image

artifact(html)
image

文件和artifact 在消息流中的展示
image

定时任务
image

分屏模式
image

中文说明

What this PR does

为 web-shell 新增右侧面板,用于审查会话输出。已编辑文件现在会以独立的 turn 卡片展示,包含行数统计、单文件 diff 展开、可拖拽的审查面板、文件列表/文件树导航,以及响应式布局。Artifact 和定时任务也会作为独立的 turn output 卡片展示,并能打开到右侧面板页签;HTML artifact 使用 sandbox iframe 渲染,定时任务使用快照详情和操作按钮展示。

web-shell 的 artifact 流程现在接入 daemon artifact 快照和 artifact 变更事件,因此 record_artifact 生成的内容可以从 daemon 加载,并在 daemon 发出变更事件时刷新,同时能关联到产生它们的对话轮次。公开的 web-shell API 也新增了外部接管 turn-output 打开行为、以及控制 message 区域展示哪些 turn output 卡片类型的能力。

Why it's needed

之前 web-shell 没有集成的右侧审查区域来查看生成文件、artifact 或定时任务输出。用户只能从工具文本或外部文件状态中推断结果,这让多文件编辑、生成 HTML 报告和创建定时任务后的检查都比较困难。这个 PR 为这些输出提供了稳定、跟 session 绑定的审查区域,同时让文件、artifact 和定时任务在视觉上保持分离。

Reviewer Test Plan

How to verify

运行 web-shell,并完成一轮会话,让它修改文件、记录一个 HTML artifact,并创建或更新一个定时任务。确认 message 区域分别展示已编辑文件、artifact 和定时任务卡片。点击 Review,确认右侧面板打开后包含文件列表、文件树、可调整大小的面板区域,以及带行数统计的 CodeMirror diff。点击 artifact,确认它以独立可关闭页签打开;HTML 内容应只在 iframe 中渲染,不显示详情外壳。点击定时任务,确认可以打开快照/详情视图并看到可用操作。切换 session,确认右侧面板状态按 session 隔离。

Evidence (Before & After)

Before:web-shell 没有用于 turn artifact、生成文件或定时任务快照的右侧审查区域。After:turn output 会作为卡片展示在对应 message 下方,右侧面板可以通过 session-scoped 页签检查文件 diff、artifact 和定时任务。

本地验证已完成:

cd packages/sdk-typescript && npm run build
cd packages/sdk-typescript && npx vitest run test/unit/daemonUi.test.ts
cd packages/webui && npm run build
cd packages/webui && npx vitest run src/daemon/session/DaemonSessionProvider.test.tsx
cd packages/web-shell && npm run build
cd packages/web-shell && npx vitest run client/components/artifacts/turnOutputSelectors.test.ts client/components/ChatPane.test.tsx client/App.test.tsx client/build-artifact.test.ts

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested
🐧 Linux ⚠️ not tested

Environment (optional)

macOS 本地 Node/npm workspace;已验证的单测和构建不依赖特定 sandbox 行为。

Risk & Scope

  • Main risk or tradeoff:这次增加了较大的 web-shell UI 面板和基于 CodeMirror 的 diff 渲染,审查时应重点关注布局行为、session 切换、artifact 关联和 iframe 安全性。
  • Not validated / out of scope:本地没有做完整的跨浏览器视觉 QA、Windows/Linux 本地运行,也没有跑完整 daemon session 录制式端到端流程。
  • Breaking changes / migration notes:预期无破坏性变更;新的右侧面板打开行为覆盖能力和 turn-output 展示控制参数都是增量能力。

Linked Issues

N/A

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Thanks for sticking with this PR @ytahdn — re-triage after the latest 3 commits (sanitizer hardening, review regressions, split pane snapshot reconciliation).

Template looks good ✓ — all required sections present and filled in.

Problem: feature addition, not a bug fix. Web-shell previously had no integrated surface for reviewing generated files, artifacts, or scheduled-task outputs. Users had to infer outputs from tool text or external file state. The gap is real and well-scoped to web-shell UX.

Direction: aligned — a right-side review panel is a natural extension for a web-based development shell. CodeMirror merge diffs for code review, session-scoped tabs for state isolation, and sandboxed iframe for HTML artifact preview are the right building blocks. No direct CHANGELOG reference, but web-shell artifact review is clearly within the product's web UI mission.

Size: ~5,000 production lines + ~1,250 test lines across 3 packages (sdk-typescript, webui, web-shell). No core modules touched. This is large but coherent — the right-panel UI, daemon event plumbing, session-scoped state management, and turn-output association logic are all part of one feature. The 1,000+ line advisory applies; splitting further would create dangling dependencies between the parts.

Approach: the three-package split is justified — sdk-typescript adds event normalization, webui adds daemon actions/signals, web-shell builds the UI. The latest 3 commits address real issues: normalizePath now properly handles .. traversal (preventing potential path confusion), withArtifactPreviewCsp strips noscript/meta[refresh]/existing CSP meta tags before prepending strict CSP, and the pane artifact snapshot reconciliation ensures split-view panes correctly report their artifacts back to the parent. One non-blocking observation: App.tsx has grown to ~5,850 lines with significant artifact state management — extracting the paneArtifactSnapshots/artifactPanelTabs/artifactPanelExtraArtifacts cluster into a custom hook would improve readability. Not blocking.

Moving on to code review. 🔍

中文说明

感谢 @ytahdn 持续维护这个 PR——在最新 3 个 commit(加固 sanitizer、修复 review 回归、修复分屏 artifact 快照同步)后重新审查。

模板完整 ✓

问题:功能新增,非 bug 修复。web-shell 之前没有集成右侧面板来查看生成文件、artifact 或定时任务输出。用户只能从工具文本或外部文件状态推断结果。这个空白是真实存在的,且明确属于 web-shell UX 范畴。

方向:对齐——右侧审查面板是 web 开发 shell 的自然扩展。CodeMirror merge diff 用于代码审查、session-scoped 页签用于状态隔离、sandbox iframe 用于 HTML artifact 预览都是正确的构建模块。

规模:约 5,000 行生产代码 + ~1,250 行测试代码,跨 3 个包(sdk-typescript、webui、web-shell)。未触及核心模块。规模大但内聚。1,000+ 行建议适用;进一步拆分会产生悬空依赖。

方案:三包拆分合理。最新 3 个 commit 解决了真实问题:normalizePath 正确处理 .. 路径遍历,withArtifactPreviewCsp 在注入严格 CSP 前清除 noscript/meta[refresh]/已有 CSP meta 标签,分屏 artifact 快照同步确保分屏 pane 正确上报 artifact。非阻塞观察:App.tsx 已增长到约 5,850 行,建议将 artifact 状态管理集群提取为自定义 hook。

进入代码审查 🔍

Qwen Code · qwen3.7-max

@ytahdn
ytahdn force-pushed the feat/web-shell-right-panel branch from 454e410 to 7712280 Compare July 9, 2026 11:05
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration.

中文

请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code Review

Read through the full diff (7,225 lines across 34 files). All unit tests pass (531 total) and all three packages build cleanly.

Independent proposal: To add a right-side artifact/file review panel, I'd add: (1) SDK-level event normalization for artifact_changed daemon events, (2) a daemon action to load artifacts on demand, (3) a session-scoped React hook for artifact state with auto-refresh on prompt-idle and artifact-version signals, (4) a selector layer to associate artifacts/files/scheduled-tasks with their originating turns, (5) a panel component with tabbed navigation (review/artifact/scheduled-task), CodeMirror merge view for diffs, sandboxed iframe for HTML preview, and a resizable split layout. That's essentially what this PR does.

Findings:

  • Architecture is clean. Three-layer split (sdk-typescript → webui → web-shell) follows the existing package pattern. The SDK normalizer adds artifact_changed event handling with proper fallback for malformed payloads. The webui layer adds artifactsVersion to workspace event signals and a loadArtifacts action. The web-shell layer handles all UI state with proper session scoping.

  • Security is solid. HTML artifact preview uses <iframe sandbox="" referrerPolicy="no-referrer"> with CSP injected as the first <meta> element: default-src 'none'; base-uri 'none'; style-src 'unsafe-inline'; img-src data: blob:. The withArtifactPreviewCsp function (latest commit) properly strips noscript, meta[http-equiv=refresh], and existing meta[http-equiv=Content-Security-Policy] elements before prepending the strict CSP — preventing an artifact from overriding or weakening the policy. A regex fallback (stripUnsafePreviewMarkup) covers non-browser environments. normalizePath (latest commit) now resolves .. segments correctly, preventing path confusion in isSamePath comparisons.

  • No correctness bugs found. turnOutputSelectors.ts correctly associates artifacts and file changes with turns (20 tests). useSessionArtifacts properly handles race conditions with request IDs, clears stale artifacts on session switch, and refreshes on both prompt-idle and artifact-version signals. attachTurnOutputs handles edge cases including parallel_agents display items (fixed in latest commit).

  • CodeMirror merge view is lazy-loaded via dynamic import('@codemirror/merge'), with side-by-side for wide panels (≥720px) and unified view for narrow panels. Good approach — avoids loading the heavy merge module for users who never open the panel.

  • Split-pane artifact reconciliation (latest commit) properly tracks per-pane artifact snapshots in a Map<string, PaneArtifactSnapshot>, with cleanup on pane unmount via the useEffect cleanup function in ChatPane. The handlePaneArtifactsChange callback uses deep equality checks (id + updatedAt + sizeBytes) to avoid unnecessary re-renders.

  • Non-blocking observations:

    • setArtifactPanelTabs updater always creates a new array via tabs.map() even when changed stays false — should return tabs (the original) when nothing changed to avoid unnecessary React re-renders. Minor optimization.
    • App.tsx has grown to ~5,850 lines. The paneArtifactSnapshots + artifactPanelTabs + artifactPanelExtraArtifacts state cluster is a good candidate for extraction into a useArtifactPanelState hook.
    • The pointer-resize handler is duplicated between App.tsx and ArtifactPanel.tsx (~40 lines each) — a shared usePointerResize hook would reduce duplication.

Testing

Tmux real-scenario testing: N/A. Web-shell is a browser-based React application — it doesn't run in a terminal. Verification is unit tests + production builds.

packages/sdk-typescript:  262 tests passed ✓
packages/webui:           146 tests passed ✓
packages/web-shell:       123 tests passed ✓ (turnOutputSelectors: 20, ChatPane: 34, App: 64, build-artifact: 5)
packages/sdk-typescript:  build ✓
packages/webui:           build ✓
packages/web-shell:       build ✓

All new code has test coverage. The artifactUtils.test.ts suite (added in latest commits) covers normalizePath with .. traversal, withArtifactPreviewCsp CSP injection, and stripUnsafePreviewMarkup regex fallback. The DaemonSessionProvider.test.tsx covers artifact_changed event wiring. The web-shell tests cover selectors, hooks, components, and integration scenarios.

CI checks all pass: Classify PR ✓, Test (ubuntu) ✓, web-shell E2E Smoke ✓, precheck-pr ✓.

中文说明

代码审查

通读了完整 diff(34 个文件,7,225 行)。所有单元测试通过(531 个),三个包均构建成功。

架构清晰:三层拆分(sdk-typescript → webui → web-shell)遵循现有包模式。安全性良好:HTML artifact 使用 sandbox="" + referrerPolicy="no-referrer" + 严格 CSP 注入(作为第一个 <meta> 元素)。最新 commit 正确地在注入 CSP 前清除 noscriptmeta[refresh] 和已有 CSP meta 标签。normalizePath 正确处理 .. 路径遍历。未发现正确性问题。CodeMirror merge view 通过动态 import 懒加载。分屏 artifact 快照同步使用深度相等检查避免不必要的重渲染。

非阻塞观察:setArtifactPanelTabs 的 updater 即使没有变化也会创建新数组(小优化点);App.tsx 已增长到约 5,850 行,建议提取 artifact 面板状态为自定义 hook;指针拖拽逻辑在两处重复。

测试

Tmux 真实场景测试:不适用(web-shell 是浏览器端 React 应用)。单元测试 + 生产构建均通过。所有新代码有测试覆盖。CI 检查全部通过。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Stepping back after the full re-triage: this PR has been through an extraordinary number of review cycles, and the author has been remarkably persistent — addressing every piece of feedback, resolving multiple merge conflicts, stabilizing flaky tests, and hardening the security posture along the way. The latest 3 commits (sanitizer hardening, review regressions, split pane snapshot reconciliation) address genuine issues found in prior review rounds. That kind of follow-through speaks well for the code's quality.

The implementation is solid. The right-panel review surface fills a real gap in web-shell — users previously had no way to inspect file diffs, artifacts, or scheduled-task outputs after a turn. The architecture is clean and follows existing package boundaries. Security is handled correctly: sandboxed iframes with maximally restrictive CSP (no scripts, no external resources, no base URI), noscript/meta-refresh/existing-CSP stripped before injection, and path normalization hardened against .. traversal. Tests are comprehensive (531 across 3 packages) and all pass, including the CI web-shell E2E smoke test.

My independent proposal matched what the PR does almost exactly, which gives me confidence the approach is sound. The only things I'd do differently are cosmetic: extract the pointer-resize handler into a shared hook, and pull the artifact panel state cluster out of the 5,850-line App.tsx. Neither blocks the merge.

Approval guardrail check:

isCrossRepository: true
title: "feat(web-shell): add artifact right panel"
title type: feat (not refactor)
Guard result: ok

This is a fork PR, but the title is feat-type, not refactor. The approval guardrail does not block.

However, given the scope (~5,000 production lines across 3 packages, 34 files) and the fork origin, I'm escalating to the maintainer for final approval rather than auto-approving. The code quality is high and the author has demonstrated deep understanding through many review iterations — but a feature of this size deserves a human eye before merge.

中文说明

经过完整的重新审查:这个 PR 经历了非常多的审查轮次,作者一直坚持处理每一条反馈、解决多次合并冲突、稳定不稳定测试,并加固了安全性。最新 3 个 commit 解决了之前审查中发现的真实问题。这种坚持说明代码质量是好的。

实现本身很扎实。右侧审查面板填补了 web-shell 的真实空白。架构清晰,遵循现有包边界。安全性正确:sandbox iframe + 最大限制 CSP(无脚本、无外部资源、无 base URI),注入前清除 noscript/meta-refresh/已有 CSP,路径规范化处理 .. 遍历。测试全面(3 个包共 531 个测试)且全部通过,包括 CI web-shell E2E 冒烟测试。

我的独立方案与 PR 的做法几乎一致。唯一的区别是我会把指针拖拽逻辑提取为共享 hook,以及把 artifact 面板状态集群从 5,850 行的 App.tsx 中拉出来。两者都不阻塞合并。

审批护栏检查:fork PR 但标题是 feat 类型(非 refactor),护栏不阻塞。

鉴于规模(约 5,000 行生产代码,3 个包,34 个文件)和 fork 来源,我选择转交 maintainer 做最终审批,而不是自动批准。代码质量高,作者已通过多轮审查展示了深入理解——但这个规模的功能值得人工确认后再合并。

Qwen Code · qwen3.7-max

@ytahdn
ytahdn force-pushed the feat/web-shell-right-panel branch from 7712280 to 6747e77 Compare July 9, 2026 11:15
@ytahdn
ytahdn force-pushed the feat/web-shell-right-panel branch from 6747e77 to 70d6965 Compare July 9, 2026 11:16
Comment thread packages/web-shell/client/App.tsx
Comment thread packages/web-shell/client/hooks/useSessionArtifacts.ts
@wenshao

wenshao commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Suggestions — commit 771228021b6a50dd3e384fa9ec0ed87e06e064fd

File Issue Suggested fix
packages/web-shell/client/components/artifacts/TurnOutputs.tsx:481, ArtifactPanel.tsx:1439 Duplicate normalizePath with divergent trailing-slash handling Extract one canonical version into artifactUtils.ts (with trailing-slash stripping)
packages/web-shell/client/components/artifacts/TurnOutputs.tsx:485, turnOutputSelectors.ts:422 Duplicate isSamePath/isSameWorkspacePath — identical logic, different names Extract into artifactUtils.ts and import in both files
packages/web-shell/client/components/artifacts/ArtifactPanel.tsx:1228, TurnOutputs.tsx:427 Duplicate LineStats component Define once in shared module
packages/web-shell/client/App.tsx:259, ChatPane.tsx:55 Duplicate TURN_OUTPUT_KINDS constant Export from TurnOutputs.tsx and import in both
packages/web-shell/client/components/artifacts/ArtifactPanel.tsx:1053 getDisplayDiff concatenates partial diffs producing garbled output for multi-edit files Use first diff's oldText + last diff's newText to show net change
packages/web-shell/client/components/artifacts/turnOutputSelectors.ts:390 upsertFileChange sums additions/deletions across edits, inflating line stats Compute stats from the final merged diff instead of summing
packages/web-shell/client/App.tsx:1168 artifactPanelStateBySessionRef Map grows unboundedly — entries never deleted Evict entries for closed sessions or cap Map size
packages/web-shell/client/components/artifacts/turnOutputSelectors.ts:422 isSameWorkspacePath suffix matching can match /other/project/src/config.ts against src/config.ts Resolve both paths to absolute using workspace CWD before comparing
packages/web-shell/client/hooks/useSessionArtifacts.ts:97 isSessionDisconnectedError uses exact string equality — any message change silently breaks the guard Use an error code or custom error class
packages/web-shell/client/components/artifacts/turnOutputSelectors.ts:356 getCronTaskId regex has empty alternative (?:recurring job|one-shot task|) — trailing | matches any word after "Scheduled" Remove the empty alternative
packages/web-shell/client/components/artifacts/ArtifactPanel.tsx:1026 buildFileTree called in render without useMemo Wrap in useMemo keyed on [changes, workspaceCwd]
packages/web-shell/client/components/artifacts/turnOutputSelectors.ts:42 getArtifactsByTurn O(artifacts × messages) — scans messages per artifact Build a single record_artifact map in one pass
packages/web-shell/client/components/artifacts/ArtifactPanel.tsx:1071 CodeMirrorDiff creates MergeView then destroys on narrow panels (isWide init true) Defer view creation until initial width measurement
packages/web-shell/client/components/artifacts/ArtifactPanel.tsx:1615 HTML artifact iframe sandbox="" silently blocks all JavaScript in interactive HTML artifacts Consider sandbox="allow-scripts" or show a "JS disabled" notice
packages/sdk-typescript/src/daemon/ui/normalizer.ts:1138 normalizeArtifactChanged casts via as unknown as with only isRecord check — no field validation Validate action and artifactId are strings before casting
packages/web-shell/client/components/artifacts/ArtifactPanel.tsx No test file for the largest new file (1700 lines) — CRUD paths for scheduled tasks highest priority Add ArtifactPanel.test.tsx covering tab switching, task load/edit/save/error, delete flow
packages/web-shell/client/hooks/useSessionArtifacts.ts No test file — concurrency guard (requestIdRef) and error suppression untested Add tests for stale response discard and disconnect error matching
packages/core/src/utils/pdf.ts:540 renderPDFPagesToImages timeout path untested (equivalent path in extractPDFText is tested) Add timeout simulation test

— qwen3.7-max via Qwen Code /review

Comment thread packages/web-shell/client/components/artifacts/turnOutputSelectors.ts Outdated
Comment thread packages/web-shell/client/components/artifacts/turnOutputSelectors.ts Outdated
Comment thread packages/web-shell/client/App.tsx
Comment thread packages/sdk-typescript/src/daemon/ui/normalizer.ts
Comment thread packages/web-shell/client/components/artifacts/ArtifactPanel.tsx
@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Suggestions — commit 45932eee

  • packages/web-shell/client/components/artifacts/artifactUtils.ts:97-104stripUnsafePreviewMarkup fallback creates nested HTML documents: When DOMParser is unavailable and html is a full HTML document, the fallback wraps it inside <body>...</body>, creating nested <html> tags. Detect full-document HTML and inject CSP into the existing <head> instead: if (/<html\b/i.test(html)) { return html.replace(/<head\b[^>]*>/i, \$&`); }`

— qwen3.7-max via Qwen Code /review

@ytahdn

ytahdn commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the latest review feedback in commit b0f7108.

What changed:

  • Shared path normalization/comparison for artifact/file matching to avoid divergent suffix behavior.
  • Fixed repeated same-file edits to render as the final diff: first originalContent -> last newContent.
  • Recomputed +/- line stats from the final full-content diff when available, so repeated add/remove cycles show the final net change instead of cumulative intermediate edits.
  • Kept partial-hunk fallback as separate diff blocks instead of concatenating unrelated hunks.
  • Added tree guide lines in the review file tree for clearer nested folder ownership.
  • Added cleanup for resize pointer listeners on unmount.
  • Lazy-load CodeMirror merge support when diff preview is opened.
  • Added CSP injection for HTML artifact iframe srcDoc while keeping the restrictive sandbox.
  • Switched loadArtifacts to the shared requireSessionForAction path.
  • Added write_file and repeated-edit selector test coverage.

Verified locally:

  • npx eslint packages/web-shell/client/components/artifacts/ArtifactPanel.tsx --ext .tsx --max-warnings 0
  • npx eslint packages/web-shell/client/components/artifacts/turnOutputSelectors.ts packages/web-shell/client/components/artifacts/turnOutputSelectors.test.ts --ext .ts --max-warnings 0
  • cd packages/web-shell && npx vitest run client/components/artifacts/turnOutputSelectors.test.ts
  • cd packages/web-shell && npx vitest run client/components/artifacts/turnOutputSelectors.test.ts client/components/ChatPane.test.tsx client/App.test.tsx client/build-artifact.test.ts
  • cd packages/webui && npm run build && npx vitest run src/daemon/session/DaemonSessionProvider.test.tsx
  • cd packages/web-shell && npm run build

Comment thread packages/web-shell/client/components/artifacts/ArtifactPanel.tsx Outdated
Comment thread packages/web-shell/client/App.tsx
Comment thread packages/web-shell/client/components/artifacts/ArtifactPanel.tsx
@ytahdn

ytahdn commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Followed up on the inline review comments in commit 942e70a.

Fixed:

  • Scoped artifact panel extra artifacts to the active session and restored them with the rest of the per-session panel state.
  • Capped saved artifact panel session state entries to avoid unbounded growth when switching many sessions.
  • Gated artifact loading on the existing session_artifacts daemon capability so older daemons do not show repeated load failure notices.
  • Added required-field validation for artifact_changed UI normalization before emitting session.artifact.changed.
  • Added a key to scheduled task detail tabs so edit/delete UI state does not leak between task tabs.
  • Wrapped CodeMirror diff initialization in fallback handling so a diff initialization failure does not unmount the whole panel.

Already covered by the prior commit and rechecked: shared path normalization, cron regex, final diff rendering/stats, resize listener cleanup, CSP injection, and dynamic CodeMirror merge loading.

Verified locally:

  • npx eslint packages/web-shell/client/App.tsx packages/web-shell/client/components/artifacts/ArtifactPanel.tsx packages/web-shell/client/hooks/useSessionArtifacts.ts packages/sdk-typescript/src/daemon/ui/normalizer.ts packages/sdk-typescript/test/unit/daemonUi.test.ts --ext .ts,.tsx --max-warnings 0
  • cd packages/web-shell && npx vitest run client/components/artifacts/turnOutputSelectors.test.ts client/components/ChatPane.test.tsx client/App.test.tsx client/build-artifact.test.ts
  • cd packages/sdk-typescript && npx vitest run test/unit/daemonUi.test.ts
  • cd packages/sdk-typescript && npm run build
  • cd packages/web-shell && npm run build

@ytahdn

ytahdn commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Correction for the previous comment: the shell interpreted Markdown backticks while sending it, so here is the clean summary.

Follow-up commit 73817fa tightens scheduled task id parsing from review feedback.

Fixed:

  • Only parse task ids from the daemon's explicit scheduled-task messages: Scheduled recurring job ... and Scheduled one-shot task ....
  • Added coverage so unrelated Scheduled ... output falls back to the tool call id instead of treating an arbitrary word as a task id.

Verified locally:

  • npx eslint packages/web-shell/client/components/artifacts/turnOutputSelectors.ts packages/web-shell/client/components/artifacts/turnOutputSelectors.test.ts --ext .ts --max-warnings 0
  • cd packages/web-shell && npx vitest run client/components/artifacts/turnOutputSelectors.test.ts
  • cd packages/web-shell && npm run build

@ytahdn

ytahdn commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up commit 57aadab addresses the latest review feedback.

Fixed:

  • Replaced regex-based artifact preview CSP injection with parsed HTML injection so the CSP meta tag is inserted as the first head child.
  • Look up artifact titles from the full artifact panel list, including extra artifacts opened from turn output cards.
  • Avoid stale active-tab state when closing artifact panel tabs quickly.
  • Shared TURN_OUTPUT_KINDS and LineStats to remove duplicated UI constants/components.
  • Memoized TurnOutputs.
  • Built a single-pass record_artifact reference index for artifact-to-turn matching and avoided repeated normalization for file-change upserts.
  • Included the file line-stat cleanup discussed in-thread: line stats are shown only when full before/after content is available, with repeated edits computed from the first original content to the final new content.

Verified locally:

  • npx eslint packages/web-shell/client/App.tsx packages/web-shell/client/components/ChatPane.tsx packages/web-shell/client/components/artifacts/ArtifactPanel.tsx packages/web-shell/client/components/artifacts/TurnOutputs.tsx packages/web-shell/client/components/artifacts/LineStats.tsx packages/web-shell/client/components/artifacts/turnOutputSelectors.ts packages/web-shell/client/components/artifacts/turnOutputSelectors.test.ts --ext .ts,.tsx --max-warnings 0
  • cd packages/web-shell && npx vitest run client/components/artifacts/turnOutputSelectors.test.ts client/components/ChatPane.test.tsx client/App.test.tsx client/build-artifact.test.ts
  • cd packages/web-shell && npm run build

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Disregard this review — an accidental anchor probe from a tooling test. Its inline comment has been removed. The real review is the one below.

@ytahdn

ytahdn commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

Merged upstream/main to resolve the PR conflict. The only manual conflict was in packages/webui/src/daemon/session/DaemonSessionProvider.test.tsx; I kept the upstream deferred heartbeat-failure test flow and preserved the artifact event signal coverage.\n\nVerified locally:\n- cd packages/webui && npx vitest run src/daemon/session/DaemonSessionProvider.test.tsx\n- cd packages/web-shell && npx vitest run client/App.test.tsx client/components/ChatPane.test.tsx client/components/SplitView.test.tsx client/components/artifacts/turnOutputSelectors.test.ts\n- cd packages/web-shell && npm run build

pull Bot pushed a commit to mcx/qwen-code that referenced this pull request Jul 10, 2026
…QwenLM#6612)

* feat(review): give every line of a large diff an accountable reviewer

Review agents were handed the diff *command* and left to run it themselves.
Shell tool output is capped at 30 000 characters and split head-1/5 / tail-4/5,
so on a large changeset every agent received a few hundred lines off the top of
the first file, the tail of the last file, and a truncation marker in place of
everything between. Measured on a 211 000-character diff: 14.4% of the
changeset, the same 14.4% for all ten agents. Nineteen of the twenty defects
maintainers eventually confirmed on that PR lay in the hidden 85.6%. The
ten-way dimension fan-out multiplied redundant reads of the visible sliver
rather than adding coverage, and each review round sampled a different subset
of the bugs depending on which files an agent happened to open on its own.

The diff is now captured to a file and partitioned. `read_file` still caps a
single read at ~25 000 characters, so writing the diff out is necessary but not
sufficient — a whole-file read of that diff returns its first 611 lines. Chunks
are therefore bounded by both a line budget (attention) and a character budget
(what one un-truncated read returns), split on hunk boundaries, and never
through the middle of a function. They tile the diff exactly, which is what
makes the new coverage receipts checkable: past 500 diff lines each chunk gets
one agent that owns it and must account for it, and a chunk with no receipt is
re-reviewed before the run proceeds. "No blockers" can no longer be reported
over code nobody read.

Coverage alone did not close the gap. Chunk agents held every state-machine
defect in that PR inside their assigned territory and reported none of them:
the bugs were not inside any hunk but between new lines sitting two thousand
lines apart, and what the agents lacked was not the lines but the question. A
heavily rewritten file now also gets three whole-file agents that walk a fixed
invariant checklist — mutable fields cleared on every exit path, timers
cancelled on every close without discarding captured data, map inserts matched
by deletes, retry counters incremented at every entry, status returns actually
checked, error codes classified permanent versus transient, config honoured on
every path, early returns that skip a required side effect. The checklist is
split three ways deliberately: one agent asked to run all eight checks over a
2 400-line file runs one of them properly.

Verification is sharded at eight findings per agent, because one verifier
re-reading code for sixty findings degrades on the tail of its list. A verifier
may now downgrade a Critical but never delete one — a rejected Critical is
invisible to every later stage, a downgraded one still reaches a human. The
reverse audit fans out per chunk instead of asking a single context-starved
agent to re-read the whole diff, no longer skips verification, and stops after
two consecutive dry rounds rather than one: on the PR that motivated this, the
review reported "no blockers" twice and the next round surfaced five Criticals,
three of them in code present since the first commit.

* fix(review): keep small-diff reads inside the read_file cap

Step 3A told every agent to read the whole diff in one call. `read_file`
truncates a single call at ~25 000 characters, so a 500-line diff of long lines
would come back short — the same blind spot the chunk plan removes, reintroduced
at a smaller scale. Across the last 39 merged PRs that take the Step 3A path the
largest diff is 23 570 characters, so this never fired in practice, but the
margin is six percent. Step 3A now walks the chunk ranges, which are sized to
fit one un-truncated read: one or two calls at this size.

Derive a file's pre-change line count from the diff instead of measuring it with
a second `git show` per file. `git show <base>:<newpath>` returns nothing for a
renamed file, reporting zero pre-change lines and classifying a wholesale
rewrite as light. The identity holds exactly for creations, deletions, renames
and ordinary edits, and halves the process spawns.

* fix(review): choose the topology from source lines, not diff lines

Diff size is a bad proxy for review risk because test code dominates it. Across
this repo's last 40 merged PRs the median diff is 41% test code and 14 of the 40
are more than half tests; PR QwenLM#6457, which motivated the territory fan-out, is
itself 58% tests. Gating on raw diff lines therefore carved small production
changes into territories: a change of 173 source lines shipping 489 lines of new
tests went to the chunked topology, where its production code ended up owned by
a single agent, when the dimension fan-out would have read it through eight
lenses. Territory fan-out is worth it when there is a lot of risky code to
divide, not a lot of lines.

The gate is now `srcDiffLines > 500`, with `diffLines > 2400` as a second clause
— a delivery bound rather than a risk one, since past that point chunking uses
fewer agents than the ten-lens topology anyway and reading a diff that large
dilutes all ten. On the 40-PR sample six PRs move back to the dimension fan-out,
for about 5% more agents in total across the sample.

Paths are classified as source, test, or generated, and the per-kind line counts
ship in the fetch report. Chunking is unchanged: the plan still tiles every
line, tests and generated files included. What the gate decides is how many
reviewers there are and what each is asked to do. Heaviness is likewise
restricted to source files — the invariant checklist asks about fields, timers,
collections, and error taxonomies, and a rewritten test file has none of those.

* fix(review): decode C-quoted diff paths as bytes

`git diff` C-quotes any path with a control character or a non-ASCII byte, so a
file named `sub/中文文件.ts` arrives as `"b/sub/\344\270\255..."`. The chunk
planner stripped the backslashes, turning it into `sub/344270255...ts` — a name
that exists nowhere. Every downstream use of the path then failed silently: the
line count came back zero, the file could never be classified as heavy, and the
chunk agent was told it was reviewing a file that does not exist. Reuse core's
`unquoteCStylePath`, which reassembles the octal escapes as UTF-8 bytes, rather
than keeping a second, wrong decoder here.

Coverage was never affected — line ranges stayed correct — but this repo has
non-ASCII paths, so the mislabelling was reachable.

Also correct two places that claimed hunks are never split. They are: a hunk
larger than the chunk target is split at a top-level declaration, because a
brand-new file arrives as one enormous hunk and treating it as atomic would hand
a single agent a 50 000-character territory.

* fix(review): make diff capture and header parsing robust to git config

Four defects, all found in review of this branch.

Diff capture obeyed whatever the user's git config said. With `color.diff=always`
every `diff --git` line arrives wrapped in ANSI escapes, the parser recognises
none of them, and the plan comes back with zero files and zero chunks — the
coverage guarantee silently evaluates to nothing. `diff.mnemonicPrefix` renames
the `a/`/`b/` prefixes to `i/`/`w/` and every path is then wrong; `diff.external`
and textconv filters emit output that is not a unified diff at all. Capture now
pins `--no-ext-diff --no-textconv --no-color --unified=3` and the two prefixes.

The `diff --git` header was split with a greedy regex. Git separates the two
paths with a space and does not quote a path merely for containing one, so
`a/img with space.png b/img with space.png` split into `space.png`. Usually the
`---`/`+++` headers disambiguate, but a binary or mode-only section has neither.
For a non-rename both paths are the same string, so the split point is
arithmetic; a rename states its new path outright in `rename to`.

A chunk boundary could land on a `-` line. Those exist only on the old side, so
the "starts at a top-level declaration" guarantee did not hold for the
post-change file an invariant agent later reads. Split points are now restricted
to lines present on the new side.

An `oversized` chunk — one hunk with no safe interior boundary — can exceed what
a single `read_file` returns. Chunks now carry their character count, and a
chunk agent is told to page when a read reports truncation. A `Covered:` receipt
for a range the agent only half read is worse than no receipt at all.

* fix(review): split past a distant boundary, and stop probing GitHub for anchors

Both defects surfaced running the new review against PR QwenLM#6591.

A 1431-line React component was emitted as a single 45 675-character chunk —
nearly twice what one `read_file` returns — because the splitter looked for a
safe boundary only inside the 400-line budget window, found none, and gave up on
the entire remainder. Twenty-seven boundaries existed further along; the first
sat 460 lines in. It now reaches past the window for the next one, so a single
distant boundary can no longer collapse a whole file into one chunk. That PR
goes from 15 chunks with one over the read cap to 18 with none.

Step 7 validated comment anchors by trial. GitHub rejects an entire review with
a 422 if any comment's line falls outside every hunk of its file, and the skill
offered no cheap way to check, so a run against a real PR submitted five
throwaway reviews carrying the bodies `Test`, `Test`, `t`, `t`, `t` to discover
which anchors would stick. Those are permanent, public reviews on someone else's
pull request. The fetch report now carries each file's hunks as new-side line
ranges, which turns the check into a lookup, and the skill states plainly that
a review is never submitted to test an anchor.

* fix(review): stop reading hunk payload as metadata, and harden the plan

Eleven defects from review of this branch. The worst two were silent.

A unified diff emits a removed line whose content starts with `-- ` as
`--- ...`, and an added line whose content starts with `++ ` as `+++ ...`. SQL,
Lua and Haskell comments start with `-- `. The parser read those payload lines
as file headers: the path was overwritten by the line's text, and the line
vanished from the add/remove counts. A two-file diff — one SQL file losing a
comment, one text file gaining a `++ ` line — came back with the second file
named `plus line`. Metadata is now only recognised before a file's first hunk.

The tiling invariant — every diff line belongs to exactly one chunk, which is
what makes a missing coverage receipt mean something — was asserted only in
tests. `buildDiffPlan` now checks it and refuses to return a plan with a hole.

The rest: a split point could take a *deleted* blank line as evidence of the
blank line before a declaration, though that blank exists only in the old file;
whole-file invariant agents were pointed at `chunks[].files[]`, which merges
hunks at lines 10 and 900 into one `10-902` span and would have had them report
pre-existing defects as new; pure-deletion hunks were exported as the inclusive
range `[N, N]`, so a right-side comment could be anchored where GitHub has no
line and the 422 would sink the whole review; a deleted file could be marked
heavy and send three agents to read a post-image that does not exist; a chunk
holding a single line longer than one `read_file` can never be fully read by
paging, and must now report itself uncoverable rather than receipt a lie;
capture did not pin rename detection or `--no-relative`; `gitRaw` had no
timeout, so a credential prompt on headless CI would hang forever; a failed
base fetch was swallowed, leaving a stale merge-base and a structurally
complete report describing the wrong diff; and local reviews still captured
with a bare `git diff`, which `color.diff=always` alone renders unparseable.

Adds an integration test that drives the real capture against a real repository
under hostile git config, covering the paths synthetic fixtures cannot: renames
and binaries and mode-only changes with spaces in their names, C-quoted
non-ASCII names, and payload lines that impersonate headers.

* fix(review): pin submodule output, and separate written lines from hunk spans

Four defects from review of this branch.

Diff capture left submodules to user config. `diff.ignoreSubmodules=all` hides a
changed gitlink completely — a silent coverage hole in the file that is now the
review's source of truth — and `diff.submodule=log` replaces the whole
`diff --git` section with prose no parser can read. Both are pinned now, and the
integration test asserts a bumped gitlink survives them.

Whole-file invariant agents were handed `files[].hunks[]` as "the changed
lines". A hunk spans the three context lines git prints either side of every
change: on PR QwenLM#6457's `QQChannel.ts` those spans cover 1 962 new-side lines of
which only 1 403 were written. The agent would have reported defects in 559
lines that predate the PR. The report now also carries `addedRanges[]` — the
exact lines the change wrote — and the skill gates invariant agents on those,
keeping `hunks[]` for the one thing it is right for, GitHub anchor validation.

`Uncoverable:` was introduced as a chunk agent's answer for a chunk holding a
line longer than one read, but the receipt accounting still demanded a
`Covered:` line from every chunk and relaunched any chunk lacking one — so an
uncoverable chunk would have been retried forever. It is now a first-class
terminal status: accepted by the accounting, carried into Step 6 under "Not
reviewed", and it blocks an Approve verdict. Step 3A, which also walks the
chunk plan, is covered by the same rule.

The integration test built its fixture repository inside the developer's git
environment, so a global `core.hooksPath` or `commit.gpgsign` ran during the
test and `~/.gitconfig` decided what the "clean" baseline was. It now disables
system and global config, hooks and signing, and sets the executable bit through
the index rather than shelling out to `chmod`, which does nothing on Windows.

* feat(review): plan any captured diff, and stop the report outgrowing one read

Seven items from review of this branch. None blocking; two of them were the
skill promising a topology it could not deliver.

Step 3B's chunk agents are "one per entry in `chunks[]`", and only `fetch-pr`
produced a chunk plan. A local-diff review, and a cross-repo review in
lightweight mode, therefore routed into the territory fan-out with no chunk
list, no receipts and no tiling guarantee. `qwen review plan-diff <diff-file>`
now emits the same plan from any captured diff; redirecting `git diff` or
`gh pr diff` to a file already sidesteps the shell's character cap, so all four
review paths share one mechanism. A bare diff has no tree to read a post-image
from, so it gets chunk agents but no invariant agents, and says so by omission.

The fetch report is read with the same `read_file` that truncates at 25 000
characters — and for a seven-file PR it was already 28 056. The tail of
`chunks[]` was being silently lost: the coverage hole this design closes,
reappearing one level up. `addedRanges[]` now ships only on `heavy` files, its
only consumer, which brings that report to 24 992; the skill says to page the
read; and the command prints a note when the report exceeds one read. It stays
pretty-printed on purpose — a compact one-line JSON cannot be paged by line.

The tiling assertion threw inside `fetch-pr` after the worktree existed and
before any report was written, so an unforeseen diff shape killed the review
outright. It now degrades to the documented diff-less report with a loud
warning, keeping both the loudness and the review.

`gitOpt` and `git` had no timeout, and `resolveMergeBase` uses `gitOpt` for a
network fetch — the exact path whose credential prompt the `gitRaw` timeout was
added to survive. All three wrappers now share a deadline and
`GIT_TERMINAL_PROMPT=0`.

Markdown under `docs/` or at the repository root classifies as `docs` and stays
out of `srcDiffLines`, so a translation PR does not trip the territory gate.
Markdown inside a source tree stays `source` — the bundled skill prompts are
behaviour, not prose.

Also: the user docs stated the gate without its `diffLines > 2400` clause, and
`READ_FILE_CHAR_CAP` was exported but never used. It now backs the report-size
warning.

* test(review): unit-test the merge-base and plan-report seams

The last open review thread asked for `resolveMergeBase`, `fileMetrics` and
`gitRaw` to be testable with git mocked out. Three of the four functions it
named have since moved: `classifyHeavy` is a pure function with unit tests,
`fileMetrics` became `buildPlanReport`, which already takes an injected
post-image resolver, and `gitRaw`'s output path is exercised by the real-git
integration test. `resolveMergeBase` was still private and untested.

It now lives behind a three-method `GitProbe` — fetch, refExists, mergeBase —
that `fetch-pr` fills from the real wrappers. Seven tests cover the branches
that matter and that no end-to-end run reaches: the tracking ref preferred over
the local branch, the fall-through when the tracking ref shares no history, and
above all the dangerous one — a failed fetch that still resolves a merge-base
from a stale local ref, which produces a structurally complete report describing
a diff nobody wrote.

`buildPlanReport` gains seven of its own: the injected resolver is asked once
per file and never for a binary, a null resolver means "no tree, decide nothing"
rather than a guess, `addedRanges` ship only where an invariant agent will read
them, and a pure-deletion hunk never reaches the anchorable ranges.

* fix(review): see deletions, survive suppressBlankEmpty, and stop approving unread code

Seven findings from review of the merged head. Three of them were the design
contradicting itself.

`diff.suppressBlankEmpty` prints a blank context line as a physically empty
record rather than a lone space, and there is no command-line flag to override
it — only `-c`. The parser advanced its new-side cursor for space-prefixed
context alone, so every `addedRanges` entry after the first blank line shifted
up by one, and the split-point heuristic stopped recognising blank lines. The
capture now pins the config, and the parser treats an empty hunk-body record as
context regardless, because a diff from `gh pr diff` or a hand-captured file
never passes through that pin.

A whole-file invariant agent was given the post-change file and the ranges the
PR wrote. A deletion appears in neither. Removing a `clearTimeout()`, a
`Map.delete()`, or a retry-counter increment is exactly what the checklist
hunts, and the text it was handed cannot show a line that is no longer there —
telling it to "cite the surrounding hunk" pointed at data it never received.
Heavy files now carry a `diffRange` into the report, and the agent reads its own
slice of the diff, where the `-` lines are.

The receipt accounting demanded exactly one per chunk and said it applied to
Step 3A, where nine dimension agents each walk every chunk: literal execution
yields nine receipts or none. Territory ownership is a Step 3B idea. What both
paths share is the uncoverable rule, and that needs no agent — a chunk is
uncoverable iff its `maxLineChars` exceeds the read cap, which the orchestrator
reads out of the plan before launching anything.

That rule was also never threaded into Step 7, so a green PR with an unread
chunk could receive a public LGTM. Any uncoverable chunk now downgrades APPROVE
to COMMENT and must be named in the body.

Also: the capture recipes redirected into `.qwen/tmp` before anything created
it; a file-path review of an unchanged file produced an empty plan that no agent
could read, and the skill now branches to a full-file read instead; and the docs
classifier called `website/src/App.tsx` prose while calling
`packages/cua-driver/docs/*.md` source — it now matches prose extensions under a
documentation directory at any depth.

* fix(review): tell agents what a severity means before asking for one

The severity definitions lived once, in Step 6 — after every severity had
already been assigned. Step 3's finding format asked each agent for
`Severity: Critical | Suggestion | Nice to have` and never said what the words
meant. The agents that fill that field are separate subagents with separate
priors and no shared definition between them, so each fell back on its own, and
the priors disagree.

Observed on a live review of PR QwenLM#6635 — a run of the skill as it stands on main,
whose Step 3 and Step 6 text this branch inherits unchanged. One review,
CHANGES_REQUESTED, ten inline comments. Six were Critical, and four of those six
were coverage gaps: "zero test coverage", "no references to `workers`", "no test
exercises this". Two Suggestions in the same review were the identical class.
The verdict is computed from Criticals alone, so that PR was blocked partly on
the strength of findings its own reviewer had, elsewhere, called suggestions.
The two genuine Criticals — a fail-fast that no longer fires before the daemon
reports healthy, and a startup failure path that never closes the HTTP server —
would have blocked it on their own.

The definitions now sit in the finding format that every agent is handed, they
are listed among the things every agent prompt must carry, and Step 6 points
back at them rather than restating them. A missing test is a Suggestion: "this
file has zero references to X" is a coverage statistic, not a defect. Two shapes
stay Critical because something is genuinely wrong — a test asserting the
opposite of the intended behaviour, and a test weakened or deleted in the diff
so new behaviour passes. If a missing test would let a specific incorrect
behaviour ship, report that behaviour and cite the gap as evidence.

* fix(review): walk cross-file edges in both directions

Cross-file impact analysis only ever asked "will the existing callers break?"
Every bullet was about signature compatibility, and the budget rule told agents
in so many words to "skip unchanged-signature modifications". A field added to
an interface changes no signature and breaks no caller, so the analysis was
blind to it by construction.

The failure that exposed this, on PR QwenLM#6621: the diff added `deviceFlowRegistry?`
to WorkspaceRuntime and passed it into the dispatcher for every secondary ACP
mount, and nothing anywhere assigned it. The reviewing agent saw the
declaration, found no writer, wrote "intentionally deferred to a later
milestone", and filed a Suggestion to fix the JSDoc. The reader was AcpDispatcher
— a file the diff never touched — where `if (!this.deviceFlowRegistry)` turned
`auth/device_flow/start` into an INTERNAL_ERROR and `auth/status` into an empty
list on every non-primary workspace. Workspace-qualified ACP shipped its
authentication dead, and the review called it a documentation nit. A second
reviewer filed the same observation as Critical; the author fixed it with code
and dropped the field.

Reading cannot find this. The declaration, the pass-through, and the read sit in
three different places, and the read is outside the diff, so no agent reaches it
by paging through hunks. Only a grep for the read sites does.

So: for every field, option, or optional parameter the diff adds, grep its read
sites, including outside the diff, and ask what happens when it arrives
undefined. Severity is decided at the read site, not the declaration. And an
agent must not explain an unpopulated field with author intent it cannot
observe — "reserved for future use" is a claim about a person, not about code,
and reaching for one means filling a hole in your own field of view.

* fix(review): pin the diff base, and make the review body checkable

Three defects, all found by reading what live reviews actually posted.

The diff base. Agents were handed a diff command and left to choose a base.
`main..HEAD` and `main...HEAD` differ by one character and by the entire meaning
of the review: a two-dot diff against a main that has moved shows main's later
commits reversed, so main's fixes read as the branch's regressions. A review of
PR QwenLM#6626 approved the four files the PR actually changed, then warned the author
publicly that their branch carried "typo regressions" in a file the PR never
touched and should be rebased. main had corrected `compatability` to
`compatibility` after the fork point. The branch had done nothing. Capture now
resolves the base once and hands agents a file; they never see a ref name, and a
finding in a file outside the report's `files[]` is not a finding about this PR.

The review body. "A Suggestion never goes in body" is stated twice and was
violated anyway, because a model holding a finding it cannot anchor would rather
say it somewhere than drop it. On PR QwenLM#6631 an unanchorable Suggestion about
`session.ts:2048` — a line in no hunk — became a second paragraph of the public
review body. So the rule stops being prose: for COMMENT the body is exactly one
of three sentences plus the footer and nothing else, and you read what you are
about to send and confirm it. A Suggestion that will not anchor is deleted; it is
already in the terminal output and the Step 8 report.

The downgrade sentence. On PR QwenLM#6489 a review with three Suggestions and no
Critical announced it had been "downgraded from Approve" — telling the author the
PR would otherwise have been approved, which was false: a Suggestion-only review
is COMMENT on its own. Decide the event from the findings first, apply the
downgrade flag second, and write the sentence only if it changed the answer.

* fix(review): decide the event by counting, not by weighing

A review of PR QwenLM#6584 filed three inline Suggestions and submitted APPROVE with
an empty body. GitHub recorded it as an approval. The rule it broke has been in
Step 7 all along --- APPROVE means no Critical *and* no Suggestion --- and so has
the one about the body, which is empty only for REQUEST_CHANGES. Both were
stated twice. Both were ignored.

They are ignored because at submit time the model is reasoning about what it
wants to say, and "these are only suggestions, the PR is fine" is a sentence it
can talk itself into. Nothing in that sentence is a count.

So the event and the body become arithmetic. Count the Criticals, count the
Suggestions, read the row off a three-row table, and only then apply the
downgrade flags --- which can turn APPROVE or REQUEST_CHANGES into COMMENT and
nothing else. Then read back what you are about to send and confirm it matches
the row. A body holding text the table does not authorise is a finding that
failed to anchor; if it is a Suggestion, it gets deleted, not relocated into
public prose that no line of code answers to.

This subsumes the body-only invariant added in the previous commit, which the
same submit-time reasoning had already defeated once, on PR QwenLM#6631.

* fix(review): stop the plan report outgrowing the read it must fit in

The report tells an agent how to page everything else, so it has to be readable
in one `read_file` — about 25 000 characters. Running the real `fetch-pr`
against PR QwenLM#6457 produced 25 070.

Two constraints pull against each other. Compact JSON is a single enormous line,
and `read_file` pages at line boundaries, so a report too big for one call could
never be read at all. Indented JSON pages fine but spends four lines on
`{ "start": 812, "end": 815 }`, and a heavily rewritten file contributes hundreds
of them: `QQChannel.ts` alone carries 140 added ranges and 49 hunks.

So indent the structure and inline the leaves. Same JSON, same keys, one range
per line, still pageable — and 28% smaller. The QwenLM#6457 report goes from 25 070
bytes to 18 042, and the "page it" warning that used to fire on a seven-file PR
now stays quiet.

The earlier attempt at this trimmed `addedRanges` to heavy files only and landed
at 24 992 bytes on the same PR. Eight bytes of headroom was not a fix.

Tests pin the three properties that matter: the collapsed text parses back to an
identical object, no range spans two lines, and a path that literally spells a
range is not mistaken for one — JSON escapes the quotes inside a string value,
and the collapse patterns require unescaped ones.

* fix(review): prune the worktree registration a deleted directory leaves behind

`cleanStale` and `cleanup` both guarded `git worktree remove` behind
`existsSync(path)`, and neither ever pruned. Delete the directory by hand — which
is exactly what reclaiming disk with `rm -rf .qwen/tmp` does — and git keeps the
worktree registered but missing. From then on `/review` on that PR cannot run:

    $ git worktree add .qwen/tmp/review-pr-6457 qwen-review/pr-6457
    fatal: '...' is a missing but already registered worktree;
    use 'add -f' to override, or 'prune' or 'remove' to clear

and the branch delete that `cleanStale` does next fails too, because the phantom
worktree still has that branch checked out. Nothing in the review command surface
ran `git worktree prune`, so nothing ever cleared it.

This surfaced running the real skill: the orchestrator's first `fetch-pr` failed,
it fell back to `qwen review cleanup`, and retried. The leak is not rare — three
abandoned worktrees from May and June were still registered in this checkout,
one per review that died before Step 9.

`releaseWorktree` now does both halves in the order they depend on: remove the
directory if it is there, prune the registration unconditionally (a no-op when
nothing is stale), and only then let the caller delete the branch. Both callers
share it.

The tests drive real git. Deleting a worktree directory by hand and re-adding it
throws "missing but already registered" without the prune, and `branch -D` throws
"used by worktree" — both assertions fail if the prune is removed, which is the
point of writing them.

* fix(review): put the open comments where a truncated read will find them

`read_file` returns the first `truncateToolOutputThreshold` characters — 25 000
by default — sets `isTruncated`, and pages by line. `pr-context` wrote
"## Open inline comments (no replies yet — may still need attention)" last, so
on a PR with a long history it was the first thing lost, and nothing read the
flag that said so.

On PR QwenLM#5738 that section began at character 27 125 of a 31 220-character file.
The review submitted "Reviewed — no blockers." Five Critical threads were
unresolved; four had in fact been addressed, but the fifth — `clearCiEnv()`
clearing only `CI*` while `writeTerminalTitle` branches on `TMUX`/`STY`/
`ZELLIJ`/`DVTM` — was live, in the diff, and never seen.

Regenerating the context for ten PRs: four lost part or all of the section, and
all four were the PRs with the most review rounds. Small PRs never trip it.

- Emit the open threads before the already-discussed ones. The findings a round
  must answer outrank the ones already settled.
- `pr-context` warns when the file exceeds the threshold, naming any headings
  past the cut, and says so plainly when the loss is inside the last section's
  body instead.
- Step 2 of SKILL.md now tells the agent to read `isTruncated` and page the
  remainder before Step 3.

Reordering buys headroom; it does not create it. A 40 000-character context still
loses its tail, which is what the warning is for.

* fix(review): load this repo's review rules, and re-check open Criticals before approving

Two gaps the dogfood on live PRs surfaced, both invisible from reading the skill.

`load-rules` looks for a `## Code Review` heading in AGENTS.md and QWEN.md.
Neither had one, so it wrote an empty file on every run: every `/review` in this
repo reviewed with zero project rules. Add the section, distilled from the
conventions already scattered through AGENTS.md (ESM, no cross-package relative
imports, kebab-case/PascalCase naming, collocated tests, comments-only-when-why),
plus the two hard lessons below. The section loads from the base branch by design
— a PR cannot inject its own review rules — so it takes effect once merged.

The skill treated a zero-Critical outcome as a fallback rather than a claim. On
one PR it published two Criticals citing code not present at the reviewed commit
(a fabricated blocker on an already-approved PR); on another it submitted C=0
while a live, twice-filed Critical still stood (a dropped blocker). Add a step
before the verdict: for each unresolved Critical on the PR, read the code at the
reviewed commit and record still-stands / fixed-by-this-diff / cannot-tell. The
event follows from the code, not from the finding count or the thread flags —
`isResolved`/`isOutdated` track the anchored line, not whether the bug was fixed.

- AGENTS.md: new `## Code Review` section.
- load-rules.ts: export `extractCodeReviewSection`; load-rules.test.ts covers the
  boundary scan and asserts AGENTS.md's own section extracts non-empty, so
  deleting the heading fails the build.
- SKILL.md: re-verification step ahead of the Verdict.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no new blockers beyond existing inline comments. Suggestion-level recommendations are in the Suggestion summary comment below.

@ytahdn

ytahdn commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed two small newly reported right-panel state/display issues in commit e92bb16:\n\n- Clear pane artifact snapshots when closing the last right-panel tab individually, matching the full panel close path.\n- When a durable scheduled task has already been deleted, show the deleted snapshot notice without stale schedule/prompt fields.\n\nVerified locally:\n- npx prettier --check packages/web-shell/client/App.tsx packages/web-shell/client/components/artifacts/ArtifactPanel.tsx\n- npx eslint packages/web-shell/client/App.tsx packages/web-shell/client/components/artifacts/ArtifactPanel.tsx --ext .ts,.tsx --max-warnings 0\n- cd packages/web-shell && npx vitest run client/App.test.tsx client/components/ChatPane.test.tsx client/components/SplitView.test.tsx\n- cd packages/web-shell && npm run build\n\nI left the larger suggestions (component extraction, additional rendering test suite, structured daemon fields, and broader refactors) untouched to keep this PR focused.

@wenshao

wenshao commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code resolved the merge conflicts and pushed the branch update.

Merge Conflict Resolution Summary

PR #6591: feat(web-shell): add artifact right panel

Conflicted file

  • packages/web-shell/client/i18n.tsx

What conflicted

Two conflict regions appeared, both in the i18n translation dictionaries (EN and ZH-CN sections), at the same logical insertion point — immediately after scheduledTasks.dur.s and before sidebar.label.

  • HEAD (PR branch) had added a block of new i18n keys for:

    • scheduledTasks.runMode.* (run mode: shared vs. isolated sessions)
    • scheduledTasks.condition.* (precondition text and hints for scheduled tasks)
    • turnOutputs.* (file-edit count, view changes, review, collapse/expand file tree — related to the artifact right panel feature)
  • origin/main had no additions in these regions; the conflict arose because both sides touched adjacent lines (near the sidebar.* keys), not because main added competing content.

How it was resolved

Kept all HEAD (PR branch) additions verbatim in both the EN and ZH-CN sections, since origin/main contributed nothing in those regions. The resolution is a pure keep-ours for the new keys — no semantic merging of competing changes was required.

@ytahdn

ytahdn commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

Resolved the latest merge conflict by merging upstream/main into the branch and reconciling the web-shell i18n changes. I also fixed the scheduled-task hint string that became invalid after the remote conflict-resolution commit was merged locally.\n\nVerified locally:\n- cd packages/webui && npm run build\n- cd packages/web-shell && npm run build\n\nThe new PR checks are still pending after push.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no new blockers beyond existing inline comments. Suggestion-level recommendations are in the Suggestion summary comment below.

@ytahdn

ytahdn commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for this PR, @ytahdn — adding a right-side review panel for turn outputs is a natural fit for web-shell.

Template: complete ✓

Problem: This is a feature addition, not a bug fix. Web-shell currently has no integrated surface for reviewing file diffs, artifacts, or scheduled task outputs produced during a turn. Users must infer outputs from tool text — this is a real UX gap that other IDE-like surfaces (Claude Code's desktop app, Cursor) already address. The problem is valid and well-motivated.

Direction: Aligned. A review panel for agent-generated outputs is squarely within web-shell's mission as the browser-based interface for Qwen Code. No direct CHANGELOG reference, but the area (web-shell UI, artifact system integration) is clearly relevant.

Size: No core module paths (packages/core/src/**, auth, providers, etc.) are touched. Changes span 3 packages (sdk-typescript, web-shell, webui) but all additions are additive — extending the daemon event normalizer with artifact_changed, adding a loadArtifacts action, and bumping an artifactsVersion signal. Breakdown:

  • Production logic: ~5,538 lines (mostly ArtifactPanel.tsx at 1,814 lines and App.tsx at +595 lines)
  • Test: ~1,811 lines
  • CSS: ~1,030 lines
  • i18n: ~62 lines

This exceeds the 1,000-line large PR advisory — worth noting for the maintainer, but non-blocking since it's feat type and the scope is focused on a single feature.

Approach: The scope is large but coherent — it's one feature (right panel) with sub-components (file diff review, artifact preview, scheduled task detail). The ArtifactPanel.tsx file at 1,814 lines is the largest single component; extracting ScheduledTaskDetail, ReviewChanges, HtmlArtifactPreview, and FileArtifactPreview into separate files would help future maintainability, but the author has intentionally deferred that to avoid widening this PR. That's a reasonable call for now. The @codemirror/merge dependency is a natural choice since CodeMirror is already used in web-shell.

Moving on to code review. 🔍

中文说明

感谢 @ytahdn 的贡献!为 web-shell 添加右侧审查面板是自然的功能扩展。

模板: 完整 ✓

问题: 这是功能新增,不是 bug 修复。web-shell 目前没有集成的界面来审查对话轮次中产生的文件 diff、artifact 和定时任务输出。用户只能从工具文本中推断结果——这是一个真实的 UX 缺口。问题有效且有充分动机。

方向: 对齐。agent 输出的审查面板属于 web-shell 作为 Qwen Code 浏览器端界面的核心使命。CHANGELOG 中无直接引用,但该领域(web-shell UI、artifact 系统集成)明显相关。

规模: 未触及核心模块路径。改动跨 3 个包(sdk-typescript、web-shell、webui),但全部为增量改动。生产逻辑约 5,538 行(其中 ArtifactPanel.tsx 1,814 行,App.tsx +595 行),测试约 1,811 行,CSS 约 1,030 行。超过 1,000 行大 PR 提醒——需维护者关注,但不阻塞。

方案: 范围大但聚焦——一个功能(右侧面板)含子组件(文件 diff 审查、artifact 预览、定时任务详情)。ArtifactPanel.tsx 1,814 行是最大单文件;将来可拆分为独立文件以提升可维护性,但作者有意推迟以避免扩大本 PR 范围。这是合理的权衡。

进入代码审查 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

Independent proposal (before reading the diff): To add a review panel for turn outputs, I'd add a right-side resizable panel to the main chat layout, create turn-output selector functions that extract file changes/artifacts/scheduled tasks from the transcript, connect to the daemon artifact system for live artifact snapshots, render file diffs with CodeMirror's merge extension (already a natural fit), sandbox HTML artifact previews in an iframe with strict CSP, and scope all panel state per session. Tests would cover selectors, hook lifecycle, and component rendering.

Comparison with the diff: The PR's approach matches and exceeds this proposal. The implementation is thorough and well-structured:

  • Selector layer (turnOutputSelectors.ts): Pure functions that extract file changes, artifacts, and scheduled tasks from transcript blocks. Well-tested with 20+ test cases covering edge cases (partial diffs, full-content diffs, mixed diffs, path normalization, workspace cwd threading).
  • Artifact lifecycle (useSessionArtifacts.ts): Clean hook with request ID guard for stale responses, session-switch cleanup, and artifactsVersion signal-based refresh. The two tests cover session switching and same-session refresh correctly.
  • Security (artifactUtils.ts): withArtifactPreviewCsp() strips <meta http-equiv="refresh">, <noscript>, and existing CSP meta tags before injecting a strict sandbox CSP. HTML previews use sandbox="" + referrerPolicy="no-referrer". 3 focused tests. The iframe sandbox="" attribute (empty string = all restrictions) is the correct most-restrictive setting.
  • Panel component (ArtifactPanel.tsx): Large but well-organized — review view with file list/tree/diff, artifact detail/preview tabs, scheduled task CRUD with cron builder integration. CodeMirror diff rendering is standard usage.
  • App integration (App.tsx): Tab management, right-panel state, pane-scoped artifact snapshots for split-pane mode, session-switch cleanup. 65 App-level tests.
  • Daemon integration: artifact_changed event normalization in sdk-typescript, artifactsVersion signal bumping in webui's DaemonSessionProvider, loadArtifacts() action. All additive.

Reuse check: The PR reuses existing infrastructure well — CodeMirror (already a dependency), isSafeHref from Markdown component, describeCron/buildCron from scheduled tasks dialog, DialogShell for delete confirmation, existing workspace actions API. The new artifactUtils.ts utility centralizes path normalization, CSP injection, and artifact metadata formatting — no duplication.

Findings: No critical blockers found. The code has been through many review iterations (15+ author comments addressing feedback) and the current state is clean. A few observations that don't block:

  • ArtifactPanel.tsx at 1,814 lines is the largest single component. Future extraction of ScheduledTaskDetail, ReviewChanges sub-components into separate files would help, but the author has acknowledged this as future work.
  • The countLongestCommonSubsequence function in turnOutputSelectors.ts is O(n×m) which could be slow for very large diffs, but this is standard LCS and acceptable for typical file diffs.
  • The measureCanvas module-level singleton for text measurement is a common and correct pattern.

Testing

Unit tests (worktree — PR branch checked out):

web-shell:  225 tests passed (8 test files)
sdk-typescript: 262 tests passed (1 test file)
webui: 146 tests passed (1 test file)
Total: 651 tests, all passing

Builds:

web-shell: ✓ built in 8.42s + 1.48s (client + SSR)
webui: ✓ built in 8.07s

Typecheck: npm run typecheck — all packages pass.

tmux real-scenario testing: N/A — this is a browser UI feature (web-shell). The changes affect React components rendered in a browser, not CLI/TUI behavior. The PR includes screenshots in the description showing the before/after state of the right panel with file review, artifact preview (HTML and file), and scheduled task views.

中文说明

代码审查

独立方案(阅读 diff 之前): 添加右侧面板需要:可调整大小的面板布局、从 transcript 提取文件变更/artifact/定时任务的选择器、接入 daemon artifact 系统、用 CodeMirror merge 扩展渲染 diff、用严格 CSP 沙箱 iframe 渲染 HTML artifact、按 session 隔离面板状态。

与 diff 的比较: PR 的方案匹配并超越了独立方案。实现结构清晰、测试充分:

  • 选择器层: 纯函数提取文件变更、artifact、定时任务,20+ 测试覆盖边界情况
  • Artifact 生命周期: Hook 含请求 ID 防过期、session 切换清理、版本信号刷新
  • 安全性: iframe sandbox="" + CSP 注入,strip refresh/noscript/旧 CSP meta
  • 面板组件: 审查视图 + artifact 预览 + 定时任务 CRUD
  • Daemon 集成: 全部增量改动

发现: 无 Critical 阻塞问题。代码经过 15+ 轮迭代审查,当前状态干净。ArtifactPanel.tsx 1,814 行较大,未来可拆分,但作者已确认为后续工作。

测试

  • 单元测试:651 个测试全部通过
  • 构建:web-shell 和 webui 均成功
  • 类型检查:全部通过
  • tmux 真实场景测试:不适用——这是浏览器 UI 功能

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Reflection

Stepping back: this PR adds a right-side review panel that web-shell has been missing. The motivation is genuine — users need a way to inspect file diffs, artifacts, and scheduled tasks without leaving the chat interface. My independent proposal matched the PR's approach, and the implementation exceeds it in thoroughness.

The code quality is high after many review iterations. The selector layer is well-tested pure functions, the artifact lifecycle hook handles stale responses and session switching correctly, the security model (sandboxed iframes + strict CSP + referrer policy) is sound, and the 651 passing tests give solid coverage. The build and typecheck are clean.

The PR is large at ~5,500 production lines, but the scope is focused — it's one feature with sub-components. The ArtifactPanel.tsx file at 1,814 lines is the main maintainability concern, but the author has acknowledged this and deferred component extraction to future work, which is a reasonable call to avoid PR sprawl.

Every change in the diff serves the stated goal. The sdk-typescript and webui changes are minimal additions that wire the artifact event system through to web-shell. No drive-by refactors or scope creep.

After 15+ rounds of review feedback addressed, the code has converged to a clean state. The author has been responsive and thorough — each round of feedback is addressed with focused fixes and verification commands.

Verdict: This ships the feature cleanly, the tests prove it works, the security model is correct, and the code is maintainable. LGTM. ✅

中文说明

总结

这个 PR 为 web-shell 补充了缺失的右侧审查面板。动机真实——用户需要在不离开聊天界面的情况下检查文件 diff、artifact 和定时任务。实现质量高,经过多轮审查迭代后代码已收敛到干净状态。651 个测试全部通过,构建和类型检查无错误。安全模型(沙箱 iframe + 严格 CSP)正确。

规模较大(~5,500 生产行),但范围聚焦于单一功能。ArtifactPanel.tsx 1,814 行是主要的可维护性关注点,但作者已确认后续拆分为未来工作。

结论: 功能实现完整,测试验证通过,安全模型正确,代码可维护。LGTM. ✅

Qwen Code · qwen3.7-max

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship. ✅

@wenshao

wenshao commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

✅ Maintainer local verification — build + tests + real-browser E2E

I built this PR from a clean worktree (refs/pull/6591/head @ 45932eee9) and ran the full build/test/quality matrix plus a real Chromium E2E pass that exercises the new turn-output cards, review panel, artifacts, and scheduled tasks. Everything passes.

Environment: macOS (Darwin 24.6), Node v22.23.1, npm 10.9.8, fresh npm install in an isolated git worktree.

Build · test · quality matrix

Package Build Unit tests Typecheck Lint Prettier
sdk-typescript 262 ✅ (tsc) ✅¹
webui 146
web-shell 1383 (87 files)

Total: 1791 unit tests green. Prettier checked across all 66 PR-changed source files — All matched files use Prettier code style!.

¹ sdk-typescript's own npm run lint script aborts in the worktree due to a nested eslint 8.57.1 vs root eslint 9.29.0 version clash (Cannot read properties of undefined (reading 'allowShortCircuit')) — a local install artifact, not PR code. Re-running the PR's changed SDK files through the root ESLint 9.29.0 exits clean (0 findings).

Automated E2E (real Chromium via Playwright)

The existing smoke suite passes end-to-end against the fake daemon:

6 passed (8.7s)
✓ loads replayed transcript and connects to fake daemon
✓ submits a prompt and renders a streamed assistant response
✓ keeps later SSE connections alive when an earlier one is cancelled
✓ clears fake SSE connection records when streams close or error
✓ submits permission decisions through the fake daemon
✓ opens slash menu, resume dialog, model dialog, and theme dialog

Functional verification of the new surface (real browser)

I drove a scripted session through the mock-daemon harness (3 turns: multi-file edit → HTML/file artifacts → scheduled task) and confirmed each new behavior live in Chromium:

  1. Turn-output cards in the message stream — an "Edited N files" card with per-file line stats (+/-), separate artifact cards (HTML + plain file with size), and a scheduled-task card, all rendered distinctly below their originating message.
  2. Review right panel — opens from the card with a file list (per-file +/- stats), a collapsible file tree, resizable width, and per-file CodeMirror diffs. Created files show all-additions; the modified file (auth.ts) shows a correct unified diff with red deletions + green additions and accurate +5 -3 stats.
  3. HTML artifact — opens as its own closable right-panel tab and renders inside a sandboxed iframe (no detail wrapper); the report HTML displays correctly.
  4. Scheduled task — opens as a separate tab showing the snapshot detail (Name, Task ID, Schedule, Cron, Type, Prompt) with Edit / Enable / Delete actions.
  5. Per-session scoping & multi-tab — Review / artifact / scheduled-task tabs coexist in the right panel and are scoped to the session.

Note: in the mock-daemon run, the artifact/scheduled-task panels show a small red GET /file: Unauthorized / GET /scheduled-tasks: Unauthorized line. That is only because the test harness doesn't implement those live daemon endpoints — the panel correctly falls back to the session-scoped snapshot and still renders all content, which is exactly the documented behavior.

Review focus / notes for merge

  • Size: ~6.35k additions, mostly new isolated files under web-shell/client/components/artifacts/*. Per the repo's two-tier rule this is a feat (not a core refactor), so no hard-block; it touches sdk-typescript/src/daemon/ui, webui/src/daemon/session, and web-shell — none in the maintainer-only core paths. New public API (onRightPanelOpen, messageTurnOutputs, TurnOutputKind/TurnOutputOpenRequest) is additive.
  • Highest-value review areas (matching the PR's own Risk & Scope): iframe/HTML sanitizer safety (withArtifactPreviewCsp), artifact→turn association (turnOutputSelectors), session switching / stale-state clearing, and the line-stat LCS guard on large diffs.
  • No regressions observed in build, typecheck, lint, format, unit tests, or the smoke E2E.

Recommendation: ✅ Verified locally and safe to merge from a build/test/behavior standpoint. Remaining judgment for a human reviewer is scope/architecture of the new UI surface, not correctness of the wiring.

🇨🇳 中文版本(点击展开)

✅ 维护者本地验证 —— 构建 + 测试 + 真实浏览器 E2E

我在一个干净的 worktree 中基于 refs/pull/6591/head45932eee9)构建了本 PR,跑通了完整的构建/测试/质量矩阵,并用真实 Chromium 做了 E2E,覆盖了新增的 turn-output 卡片、审查面板、artifact 和定时任务。全部通过。

环境: macOS(Darwin 24.6),Node v22.23.1,npm 10.9.8,在隔离 worktree 中全新 npm install

构建 · 测试 · 质量矩阵

构建 单测 类型检查 Lint Prettier
sdk-typescript 262 ✅ (tsc) ✅¹
webui 146
web-shell 1383(87 个文件)

合计 1791 个单测全绿。 对 PR 改动的全部 66 个源文件跑 Prettier 检查:All matched files use Prettier code style!

¹ sdk-typescript 自带的 npm run lint 脚本在 worktree 中因嵌套 eslint 8.57.1 与根 eslint 9.29.0 版本冲突而中断(allowShortCircuit 报错),这是本地安装产物,与 PR 代码无关。用根 ESLint 9.29.0 重新检查 PR 改动的 SDK 文件,0 问题、干净通过

自动化 E2E(Playwright 真实 Chromium)

现有 smoke 套件端到端通过:6 passed (8.7s)(回放 transcript、提交 prompt 流式响应、SSE 连接生命周期、权限决策、slash/resume/model/theme 弹窗)。

新功能真实浏览器验证

我通过 mock-daemon 驱动了一个脚本化会话(3 轮:多文件编辑 → HTML/普通文件 artifact → 定时任务),在 Chromium 中逐一确认:

  1. 消息流中的 turn-output 卡片 —— “已编辑 N 个文件”卡片带每文件行数统计(+/-)、独立的 artifact 卡片(HTML + 普通文件含大小)、定时任务卡片,均在对应消息下方独立展示。
  2. 右侧审查面板 —— 从卡片打开,包含文件列表(每文件 +/-)、可折叠文件树、可调宽度、每文件 CodeMirror diff。新建文件为全新增;被修改的 auth.ts 展示正确的 unified diff(红删/绿增)与准确的 +5 -3 统计。
  3. HTML artifact —— 以独立可关闭页签打开,在 sandbox iframe 中渲染(无详情外壳),报告 HTML 正确显示。
  4. 定时任务 —— 以独立页签打开快照详情(名称、任务 ID、计划、Cron、类型、Prompt)并带 Edit / Enable / Delete 操作。
  5. 按 session 隔离 & 多页签 —— 审查/artifact/定时任务页签在右侧面板共存并按 session 隔离。

说明:mock-daemon 环境下面板会出现红色 GET /file: Unauthorized / GET /scheduled-tasks: Unauthorized —— 仅因测试桩未实现这些实时 daemon 接口;面板正确回退到 session 快照并完整渲染内容,与设计一致。

合并关注点

  • 体量: 约 6.35k 新增,主要是 web-shell/client/components/artifacts/* 下的独立新文件。按仓库两级规则,这是 feat(非核心 refactor),不硬性阻断;改动落在 sdk-typescript/src/daemon/uiwebui/src/daemon/sessionweb-shell,均不在仅限维护者的核心路径。新增公开 API(onRightPanelOpenmessageTurnOutputs 等)均为增量。
  • 建议重点审查(与 PR 自述的 Risk & Scope 一致):iframe/HTML 消毒安全(withArtifactPreviewCsp)、artifact↔turn 关联(turnOutputSelectors)、session 切换/陈旧状态清理、大 diff 行数统计的 LCS 上限保护。
  • 构建、类型检查、lint、format、单测、smoke E2E 均无回归

结论: ✅ 本地验证通过,从构建/测试/行为角度可安全合并。剩余需人工判断的是这块新 UI 面板的范围/架构取舍,而非接线正确性。


🔧 Verified locally by the maintainer: clean-worktree build, 1791 unit tests, Prettier on all changed files, root-ESLint on changed SDK files, Playwright smoke (real Chromium), and a scripted real-browser walkthrough of the new turn-output / review-panel / artifact / scheduled-task surface.

@ytahdn
ytahdn added this pull request to the merge queue Jul 11, 2026
Merged via the queue into QwenLM:main with commit 0ef3a76 Jul 11, 2026
51 checks passed
@wenshao

wenshao commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

🔧 Pre-merge checklist for PR #6591

CI is green and there are two recent APPROVED reviews (ci-bot @ 07-10 21:12, wenshao @ 07-11 00:09), but all 55 review threads remain unresolved — 32 Critical + 20 Suggestion + 3 Nit across security, memory, correctness, and test-coverage. Before merging, please work through these systematically.

Grouped by severity. Every thread number below references an unresolved thread on this PR.


🔴 P0 — Security & hard-correctness (must fix before merge, 7 Critical)

# File Issue
28 ArtifactPanel.tsx iframe sandbox="" does not block <meta http-equiv="refresh"> — crafted HTML artifacts can navigate the iframe to attacker URLs. Add CSP default-src 'self' 'unsafe-inline' or strip the meta tag in the sanitizer.
7 ArtifactPanel.tsx CodeMirrorDiff instantiates MergeView/EditorView with no try/catch and no React ErrorBoundary. Initialization failure (large docs, weird Unicode, malformed diff) crashes the whole app.
4 turnOutputSelectors.ts left.endsWith('/' + right) gives false-positive path matches (/foo/bar/baz.ts matches ar/baz.ts). Require a full path-segment boundary check.
9 App.tsx handleArtifactPanelResizeStart attaches pointermove/pointerup/pointercancel to window but only cleans up in handlePointerUp. Component unmount mid-drag ⇒ listeners leak on window forever.
10 ArtifactPanel.tsx Same pointer-listener leak in ReviewChanges's resize handler.
29 App.tsx ArtifactPanel rendered outside per-pane DaemonSessionProvider — in split view, artifact children resolve the wrong daemon context.
51 ChatPane.tsx Split pane only forwards a one-time artifact snapshot; later artifact_changed events don't refresh artifactPanelExtraArtifacts / tab snapshot — stale previews in split view.

🟠 P1 — Memory, performance, and data-flow correctness (25 Critical + 3 Suggestion, grouped)

# Sev File Issue
5 C App.tsx artifactPanelStateBySessionRef is a Map that accumulates entries and never evicts. Each entry holds full diff oldText/newText + HTML previews — long sessions OOM. Add LRU eviction (cap ~20).
12 C turnOutputSelectors.ts countLongestCommonSubsequence runs O(n×m) synchronously on the main thread with no size guard. 5000-line file ⇒ 25M comparisons freezing the UI.
15 S turnOutputSelectors.ts Same LCS — additionally called from useMemo during React render. 2000-line file ⇒ 4M iterations per re-render. Add a size guard before invoking.
19 C App.tsx Stale opened artifact in artifactPanelExtraArtifacts overrides freshly-loaded daemon artifact when IDs match after refresh.
24 C ArtifactPanel.tsx Open artifact preview does not refresh when the same artifact is updated — previewContent stays forever stale.
55 C App.tsx handlePaneArtifactsChange reconstructs tab objects without preserving previewContent — strips content on every pane refresh.
27 C App.tsx Tab ID format mismatch: artifact:${artifactId} vs artifact:${turnId}:${artifact.id} — same artifact opens two tabs.
20 C useSessionArtifacts.ts Early-return paths clear state but don't invalidate in-flight loadArtifacts() — stale promise resolves and overwrites fresh state.
25 C useSessionArtifacts.ts refresh() happy path calls setLoading(true) but doesn't clear artifacts before await actions.loadArtifacts() — old session's artifacts remain visible during the fetch.
26 C useSessionArtifacts.ts Opposite flicker: setArtifacts([]) before await actions.loadArtifacts() blanks the panel on every refresh — visible flicker during the network round-trip.
22 C ArtifactPanel.tsx Scheduled-task detail only looks in durable list; cron_create defaults to session-only ⇒ valid session tasks show as missing.
11 C turnOutputSelectors.ts mergeFileDiffs silently discards partial edits that follow a fullContent diff — review panel shows file as after write, hides later edits.
21 C ArtifactPanel.tsx getDisplayDiffs() walks backward and returns only the earlier fullContent entry — drops later partials from the diff surface.
41 S turnOutputSelectors.ts getFinalFullContentDiff only inspects diffs.at(-1)[fullContent, partial] sequence silently drops the +N/-M stat badge.
47 C turnOutputSelectors.ts Walking backward to any earlier full-content diff makes the badge stale when later partial edits exist — '' → 'one' then 'one' → 'one\ntwo' reports one addition instead of two.
23 C turnOutputSelectors.ts getStringField() trims and rejects whitespace-only — drops valid empty/whitespace file contents before the diff is built.
35 S turnOutputSelectors.ts getStringField is also used by getToolFilePath — a file literally named readme.md would be silently dropped. Use the non-trimming getStringContentField.
46 C turnOutputSelectors.ts ACP path stores toolResult.returnDisplay as a string; collectText only reads object-valued rawOutput ⇒ cron task ID falls back to tool-call ID and durable-task lookup fails.
39 C turnOutputSelectors.ts collectText recursively concatenates all string values from rawOutput including newContent/originalContent — substring check matches phrases in the user's file content, not just daemon status.
38 C MessageList.tsx attachTurnOutputs silently drops outputs when currentTurnId is still null (before the first isTurnStartMessage).
52 C MessageList.tsx Fallback loses outputs when a resumed transcript starts with two or more agent tool groups — parallel_agents path doesn't set currentTurnId.
53 C App.tsx handlePaneArtifactsChange treats each pane's artifact list as an append delta when it's actually a full snapshot — removed artifacts stay cached, open tabs keep rendering.
1 C App.tsx artifactPanelExtraArtifacts not included in session-switch save/restore cycle.
2 C useSessionArtifacts.ts loadArtifacts() called without checking daemon capabilities — 404s against older daemons.
3 C turnOutputSelectors.ts Regex empty alternative `(?:recurring job
6 C sdk-typescript/.../normalizer.ts change cast to DaemonSessionArtifactChange without verifying action / artifactId — downstream null-guard failures.
8 C ArtifactPanel.tsx ScheduledTaskDetail has no key on task identity — switching tabs reuses the component and leaks UI-state (showForm, busy, etc).
30 C App.tsx closeArtifactPanelTab doesn't clear reviewChanges / selectedReviewPath / artifactPanelExtraArtifacts when closing the last tab (unlike closeArtifactPanel).

🟡 P2 — Test coverage gaps (6 Suggestion)

AGENTS.md: "changed behavior needs tests". The new code is security-sensitive (iframe CSP, CodeMirror, cron CRUD) and stateful (session save/restore with LRU, split-pane reconciliation) — the zero-test surface is a real gap.

# File Gap
31, 40 ArtifactPanel.tsx 1829-line component — iframe CSP sanitizer, CodeMirror diff, cron CRUD, file tree, scheduled task detail — has zero component tests.
32 App.tsx ~400 new lines of artifact panel state management (session save/restore with LRU, RAF-throttled drag, resize clamping) — zero tests.
48 ChatPane.tsx New split-pane routing boundary is untested — no test exercises the MessageList turn-output callback reaching onRightPanelOpen.
49 turnOutputSelectors.ts New leading-transcript fallback covered only for file changes; artifact and scheduled-task selector loops have no tool-group-first regression cases.
54 ChatPane.test.tsx Existing test only proves initial non-empty snapshot; doesn't exercise the App-level reconciliation (update, clear cache, pane-scoped workspaceActions, removal/empty).

ℹ️ Suggestions / Nits (defer OK but please acknowledge, 11 Suggestion + 3 Nit)

# Sev File Summary
13 S turnOutputSelectors.ts normalizedPath !== undefined is a tautological guard — normalizePath() returns string.
14 S App.tsx useSessionArtifacts() called in App.tsx AND independently in each ChatPane — N+1 parallel loadArtifacts() calls per trigger in split view.
16 S ArtifactPanel.tsx error state captures the actual message but render output discards it — debugging detail silently lost.
17 S useSessionArtifacts.ts isSessionDisconnectedError uses === against a hardcoded SDK message — brittle. Use .includes() on a stable substring.
18 S TurnOutputs.tsx Not wrapped in React.memo — parent re-renders cause all visible instances to re-render in the virtualizer.
33 S ArtifactPanel.tsx HtmlArtifactPreview unconditionally calls readWorkspaceFile(workspacePath) even when previewContent is already provided — redundant round-trip.
34 S ArtifactPanel.tsx handleReviewSplitResizeStart has [reviewListWidth] in deps — callback recreates at 60fps during drag, causing children to re-render.
36 S useSessionArtifacts.ts Three useEffect hooks list refresh in deps; when isConnected flips, all three re-run simultaneously — triple API call.
37 S turnOutputSelectors.ts getFinalFullContentDiff only checks diffs.at(-1) instead of searching backward.
42 S App.tsx workspaceCwd is always App-level (main) session, but in split view the active Review tab's diffs may come from another pane — path labels normalize against the wrong prefix.
50 S ArtifactPanel.tsx Returning every retained partial diff makes DiffPreview eagerly mount a CodeMirror MergeView per edit — panel-open jank on repeatedly-edited files. Lazily render off-screen editors.
43 N App.tsx App calls useSessionArtifacts() but each ChatPane also calls it independently — N+1 parallel calls; requestIdRef guards data but traffic is redundant.
44 N App.tsx artifactPanelExtraArtifacts entries added on open are never pruned once the same id appears in the live daemon list — minor retention.
45 N turnOutputSelectors.ts collectText still recursively concatenates the entire rawOutput tree for cron-id extraction — a prompt containing "Scheduled recurring job " could yield a false positive.

Audit of this checklist itself

The thread numbers above were verified against the live review-thread API in three passes before posting:

  1. Pass 1 — enumeration: fetched all 55 threads, classified each by **[Critical]** / **[Suggestion]** / **[Nit]** prefix. Total: 32 Critical + 20 Suggestion + 3 Nit.
  2. Pass 2 — severity alignment: re-validated that every thread I labeled "Critical" in P0/P1 is actually tagged Critical in the source, and every Suggestion in P1_sugg / P2 / SUGG_NIT is actually tagged Suggestion or Nit. Initial draft had 3 mismatches (Related to #5: Fixed bounds checking in RadioButtonSelect component to ensure active… #12, Add OpenRouter authentication support and enhance configuration manag… #47 are Critical not Suggestion; type: fix  #39 is Critical not Suggestion) — corrected.
  3. Pass 3 — coverage: confirmed 55 unique items, no duplicates, sorted range [1..55]. All 32 Critical threads accounted in P0+P1; none missing, none over-included. Files referenced match the thread paths.

Suggested workflow

  1. Author: go through P0 first (security + hard correctness, 7 threads). For each, either push a fix commit and resolve the thread, or reply with a reason and have the reviewer resolve.
  2. Author: then P1 (25 Critical + 3 related Suggestion, grouped by issue — some threads are overlapping observations of the same bug, e.g. 11/21/41/47 are all about the partial-after-fullContent drop).
  3. Author: address P2 test gaps — at minimum ArtifactPanel.tsx CSP sanitizer + App.tsx session-save/restore + the split-pane callback path.
  4. Author: triage Suggestions/Nits — apply or explicitly defer, then resolve.
  5. Reviewer (wenshao / doudouOUC): re-scan after fixes, confirm threads are genuinely closed (not just the "resolved" checkbox — please re-read the current code against each finding).
  6. Merge only after all 55 threads are resolved AND the latest review is post-fix.

🇨🇳 中文版本(点击展开)

🔧 PR #6591 合入前 checklist

CI 全绿、近期有两条 APPROVED review(ci-bot @ 07-10 21:12、wenshao @ 07-11 00:09),但 55 个 review thread 全部未 resolve —— 32 条 Critical + 20 条 Suggestion + 3 条 Nit,分布在安全、内存、正确性、测试覆盖多个维度。合入前请按严重度分级系统处理。

下表每个 thread 编号都对应本 PR 上未 resolve 的 thread。

🔴 P0 — 安全与硬正确性(必须修,7 Critical)

28 iframe sandbox 可被 <meta http-equiv="refresh"> 绕过;7 CodeMirror 无 ErrorBoundary 炸 App;4 路径后缀误匹配(ar/baz.ts 命中 /foo/bar/baz.ts);9/10 pointer listener 泄漏;29 split-view 下 ArtifactPanel 用错 daemon context;51 split pane 只传一次性快照,后续 artifact_changed 不刷新。

🟠 P1 — 内存 / 性能 / 数据流正确性(25 Critical + 3 Suggestion)

5 session Map 无淘汰;12/15 O(n×m) LCS 阻塞主线程(C+S);19/24/55 stale artifact 覆盖 fresh;27 tab id 不一致;20 in-flight 请求未作废;25/26 refresh 时 artifacts 闪空;22 session-only cron 查不到;11/21/41/47 fullContent 后 partial edit 被丢弃(C+C+S+C);23/35 空白文件内容被过滤(C+S);46 ACP string returnDisplay 解析漏;39 collectText 把用户文件内容也纳入 substring 匹配;38/52 resumed transcript 开头丢 turn outputs;53 pane artifacts 当 delta 处理但实际是全量快照;1 session switch 未保存 extraArtifacts;2 无 capabilities 检查;3 regex 空分支;6 SDK change 字段未校验;8 ScheduledTaskDetail 无 key 泄漏 UI 状态;30 closeArtifactPanelTab 漏清 state。

🟡 P2 — 测试覆盖缺口(6 Suggestion)

AGENTS.md 明确要求"changed behavior needs tests"。31/40 ArtifactPanel.tsx 1829 行零测试;32 App.tsx 新增 400 行零测试;48 split-pane 新边界零测试;49 artifact/scheduled-task selector 无 tool-group-first 回归用例;54 ChatPane.test.tsx 仅覆盖初始快照。

ℹ️ Suggestion / Nit(可 defer 但请逐条回应,11 Suggestion + 3 Nit)

13/14/16/17/18/33/34/36/37/42/50 为 Suggestion;43/44/45 为 Nit。每条要么 apply,要么回复 defer 理由,然后 resolve。

本 checklist 自身的审计

投递前对 thread 编号做了三轮审计:(1) 枚举 55 条并按前缀分类,确认 32C + 20S + 3N;(2) 反向校验我标的 Critical/Suggestion 与源数据一致,修正了 #12/#47/#39 三处 severity 误判;(3) 覆盖率校验 55 条唯一、无重复、区间 [1..55],32 Critical 全部归入 P0/P1,无遗漏无多算。

流程

  1. 作者:先 P0(7 条安全/硬正确性),每条推 fix + resolve 或回复理由让 reviewer resolve。
  2. 作者:再 P1(25C + 3S,已按问题分组——有些 thread 是同一 bug 的多角度观察,如 11/21/41/47 都指 fullContent 后 partial 丢失)。
  3. 作者:补 P2 测试(至少 ArtifactPanel CSP sanitizer + App.tsx session save/restore + split-pane 回调路径)。
  4. 作者:Suggestion/Nit 逐条 apply 或显式 defer,然后 resolve。
  5. Reviewer(wenshao / doudouOUC):fix 后重扫,确认 thread 真关闭(不是只点 resolve —— 请对照当前代码重读每条 finding)。
  6. 合入:55 个 thread 全 resolve 且最新 review 在 fix 之后,才可 merge。

Qwen Code / CLI

yuanyuanAli pushed a commit to yuanyuanAli/qwen-code that referenced this pull request Jul 11, 2026
* feat(web-shell): add artifact right panel

* fix(web-shell): address artifact panel review feedback

* fix(web-shell): handle artifact panel review edge cases

* fix(web-shell): tighten scheduled task parsing

* fix(web-shell): address artifact panel review followups

* fix(web-shell): guard large file diff stats

* fix(web-shell): address review panel suggestions

* test(webui): stabilize heartbeat prompt cleanup test

* fix(web-shell): address artifact review refresh issues

* test(web-shell): stabilize ChatPane artifact hook mock

* fix(web-shell): clear stale session artifacts while loading

* fix(web-shell): preserve artifact tabs during refresh

* fix(web-shell): address artifact review followups

* fix(web-shell): respect workspace cwd for artifact outputs

* fix(web-shell): scope artifact panel actions to pane

* fix(web-shell): resolve split pane merge conflict

* fix(web-shell): clear stale artifact panel state

* fix(web-shell): preserve leading turn outputs

* fix(web-shell): tighten turn output selectors

* fix(web-shell): harden artifact preview sanitizer

* fix(web-shell): address artifact panel review regressions

* fix(web-shell): reconcile split pane artifact snapshots

* fix(web-shell): clear pane artifacts on session switch

* fix(web-shell): clear stale right panel snapshots

* fix(web-shell): repair scheduled task hint string

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
JadeCong pushed a commit to CloudEngineHub/qwen-code that referenced this pull request Jul 11, 2026
* feat(web-shell): add mobile welcome composer slots

* refactor(web-shell): deduplicate MessageList JSX and remove dead CSS reference

- Extract ~80 lines of duplicated MessageList rendering into shared variables with conditional props and wrapper
- Remove dead chatPaneWithWelcomeMiddle className reference (CSS class never defined)
- Document mobileWelcomeFooterMiddle dependency on renderWelcomeFooter in JSDoc

* fix(web-shell): stabilize MessageList tree position and conditional customFooter wrapper

- Use stable outer wrapper div for IIFE to prevent MessageList unmount/remount when showMobileWelcomeFooterMiddle toggles
- Only wrap CustomFooter in styles.customFooter div when hasMobileComposerBottom is true, avoiding DOM depth change for non-mobile consumers

* fix(release): raise package size budget to 85 MiB (QwenLM#6688)

* fix(interactive): configure Docker sandbox networking for protocol tag retry test (QwenLM#6684) (QwenLM#6689)

The protocol-tags-interactive.test.ts started the fake OpenAI server
on 127.0.0.1 without Docker-aware host options, making it unreachable
from inside the Docker sandbox container. The CLI running in the
container tried to connect to 127.0.0.1 which resolved to the
container's own loopback, not the host where the test server listens.

Bind the fake server to 0.0.0.0 and advertise host.docker.internal
as the base URL host when QWEN_SANDBOX is docker or podman, matching
the established pattern in tool-control.test.ts. Also set NO_PROXY to
include host.docker.internal so the CLI does not route sandbox model
requests through an HTTP proxy.

Co-authored-by: qwen-autofix[bot] <qwen-autofix[bot]@users.noreply.github.com>

* fix(core): keep YOLO mode when the model calls enter_plan_mode (QwenLM#6630)

* fix(core): keep YOLO mode when the model calls enter_plan_mode

A model-initiated enter_plan_mode call from YOLO silently switched the
session into the read-only Plan mode, surprising users who explicitly
chose YOLO for low-friction execution and then blocking the reads/writes
they expected to proceed. Genuine user-driven plan-mode entries
(Shift+Tab, /plan) call setApprovalMode directly and never route through
this tool, so guarding the tool only affects the model deciding to plan
on its own. From YOLO the tool now keeps the current mode and returns a
message telling the model to continue planning without switching.

Fixes QwenLM#5970

* fix(core): gate the YOLO plan-mode guard on an explicit user request

Addresses review feedback on QwenLM#6630.

The previous guard suppressed every enter_plan_mode invocation while the
session was in YOLO mode. That fixes the unsolicited switch reported in
QwenLM#5970, but it also blocks the legitimate path: the tool description tells
the model to call this tool only after the user explicitly asks, and
/plan is interactive-only (supportedModes: ['interactive']) with no
Shift+Tab equivalent. In a headless or ACP YOLO session the tool is the
only door into plan mode, so a blanket guard made an explicit user
request unreachable.

Add an optional userRequested flag to the tool schema and only no-op when
the entry is NOT user-requested. A user-requested entry still goes through
setApprovalMode(PLAN, { enteredByModel: true }) so the Plan Approval Gate
on exit continues to run for AUTO/YOLO sessions (QwenLM#5574).

* fix(core): address review suggestions on the YOLO plan-mode guard

- Log via debugLogger.info when the guard suppresses a model-initiated
  entry, so a "I asked for plan mode and nothing happened" report is
  diagnosable by grepping ENTER_PLAN_MODE (the other early-return paths
  already log).
- Strengthen the userRequested:false test to assert on the returned
  llmContent/returnDisplay, matching the unsolicited-entry sibling test.
- Add a defensive test pinning that userRequested is inert outside
  YOLO: DEFAULT with the flag set enters plan mode normally.

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* feat(cli): forward ask_user_question answers from SDK can_use_tool (QwenLM#6655)

* feat(cli): forward ask_user_question answers from SDK can_use_tool

SDK-hosted agents could receive ask_user_question calls through the
can_use_tool callback and approve them, but the user's answers never
reached the tool: the CLI called onConfirm(ProceedOnce) with no payload,
so the tool read an empty answers map and the model never got the
decisions.

Route updatedInput.answers from the SDK's allow response into the tool
confirmation payload so the collected answers reach the tool. Reuses the
existing updatedInput channel — no new SDK API or types. Document the
pattern in the TypeScript and Python SDK READMEs.

* fix(cli): forward ask_user_question answers on teammate approval path

Address review feedback on QwenLM#6655:

- handleTeammateApproval now mirrors the leader path and promotes the
  user's answers from updatedInput into the confirmation payload, so
  ask_user_question calls approved through a teammate no longer drop the
  user's choices (wenshao).
- Extract a shared buildAllowConfirmationPayload helper used by both the
  leader and teammate paths, and only promote `answers` for
  ask_user_question so a same-named field on any other tool's input can't
  leak into the payload.
- Add tests for the teammate path and the defensive guards (array
  updatedInput, array/null/empty answers, foreign answers field).

* test(web-shell): stub Range client-rect methods to fix flaky CI

CodeMirror's async measure pass (scheduled via requestAnimationFrame)
calls getClientRects()/getBoundingClientRect() on a text Range. jsdom
implements these on Element but not on Range, so the call throws
"textRange(...).getClientRects is not a function" from a rAF callback
after the test completed. Vitest surfaces it as an unhandled error and
fails the whole run with exit code 1 even though every assertion passed
(seen intermittently in useComposerCore.dom.test.tsx).

Polyfill both methods on Range.prototype in the shared test setup,
mirroring the existing ResizeObserver/scrollIntoView stubs.

* refactor(cli): use ToolNames constant and broaden permission tests

Address review suggestions on QwenLM#6655:

- buildAllowConfirmationPayload now gates answers-promotion on the
  ToolNames.ASK_USER_QUESTION constant instead of a bare string literal,
  so a future rename of the tool name is a compile-time break rather than
  a silent regression.
- Add an it.each case for a non-object primitive updatedInput (string) to
  cover the `typeof updatedInput !== 'object'` guard branch.
- Assert the leader path overrides toolCall.request.args with the host's
  sanitized updatedInput before confirming.
- Add a teammate-path test for an allow response with no updatedInput,
  asserting respond is called with (ProceedOnce, undefined).

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>

* fix(cli): localize approval mode UI labels (QwenLM#6592)

* fix(cli): localize approval mode UI labels

* fix(cli): address approval mode i18n review

* fix(cli): stabilize approval mode i18n key

* test(cli): cover approval mode i18n follow-up

* test(cli): cover localized auto indicator

* test(cli): address approval i18n suggestions

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* feat(dingtalk): mention response senders (QwenLM#6679)

* docs: design DingTalk at-sender replies

* docs: plan DingTalk at-sender replies

* feat(channels): preserve session for response delivery

* feat(dingtalk): optionally mention response sender

* docs(dingtalk): explain response mentions

* fix(dingtalk): retain queued mention targets

* fix(dingtalk): bound mention target lifecycle

* fix(dingtalk): clear synthetic command mention target

* fix(dingtalk): clear buffered targets on session death

* debug(dingtalk): log mention delivery result

* fix(dingtalk): render response mentions

* fix(dingtalk): send visible response mentions

* feat(dingtalk): use text replies for mentions

* fix(dingtalk): preserve mentioned text replies

* feat(web-shell): add artifact right panel (QwenLM#6591)

* feat(web-shell): add artifact right panel

* fix(web-shell): address artifact panel review feedback

* fix(web-shell): handle artifact panel review edge cases

* fix(web-shell): tighten scheduled task parsing

* fix(web-shell): address artifact panel review followups

* fix(web-shell): guard large file diff stats

* fix(web-shell): address review panel suggestions

* test(webui): stabilize heartbeat prompt cleanup test

* fix(web-shell): address artifact review refresh issues

* test(web-shell): stabilize ChatPane artifact hook mock

* fix(web-shell): clear stale session artifacts while loading

* fix(web-shell): preserve artifact tabs during refresh

* fix(web-shell): address artifact review followups

* fix(web-shell): respect workspace cwd for artifact outputs

* fix(web-shell): scope artifact panel actions to pane

* fix(web-shell): resolve split pane merge conflict

* fix(web-shell): clear stale artifact panel state

* fix(web-shell): preserve leading turn outputs

* fix(web-shell): tighten turn output selectors

* fix(web-shell): harden artifact preview sanitizer

* fix(web-shell): address artifact panel review regressions

* fix(web-shell): reconcile split pane artifact snapshots

* fix(web-shell): clear pane artifacts on session switch

* fix(web-shell): clear stale right panel snapshots

* fix(web-shell): repair scheduled task hint string

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>

* feat(cli): workspace-qualified ACP transport (daemon multi-workspace phase 4) (QwenLM#6621)

* docs(design): add daemon multi-workspace phase 4 (workspace-qualified ACP) design

* feat(cli): add workspace-qualified ACP transport (issue QwenLM#6378 phase 4)

Per-runtime ACP dispatcher at /workspaces/:workspace/acp (HTTP + WS) dispatched by URL path from the single upgrade listener; per-runtime device-flow + reverse client-MCP; owner-index via bridge lifecycle; untrusted/unknown rejected; legacy /acp unchanged; advertise workspace_qualified_acp for multi-workspace.

* fix(cli): keep per-runtime device-flow registry out of serve fast-path bundle

Phase 4 secondary-runtime device-flow statically imported createDeviceFlowRegistry into run-qwen-serve, pulling glob/@iarna/toml into the serve fast-path bundle and failing the closure check. Import it dynamically at the creation site; the check now passes and behavior is unchanged.

* refactor(cli): drop per-runtime device-flow for secondary workspaces

Follow-up to the fast-path fix: instead of dynamically importing createDeviceFlowRegistry for secondary runtimes, drop the per-runtime device-flow wiring entirely. Secondary ACP device-flow falls back to the dispatcher default, keeping the serve fast-path bundle closure clean without the dynamic-import indirection. WorkspaceRuntime.deviceFlowRegistry stays optional for a future per-runtime hook.

* fix(cli): share daemon-global device-flow across ACP mounts; harden WS path parsing

Secondary ACP mounts share the daemon-global device-flow registry (single instance per daemon) instead of a per-runtime one; the event sink fans out to every trusted runtime bridge so secondary ACP clients receive their own flow events, fixing the reviewer QwenLM#6621 Critical and the CI test failure. Drops WorkspaceRuntime.deviceFlowRegistry. WS upgrade path is parsed from the raw request-target instead of new URL().pathname, rejecting %2e%2e / backslash / dot-segment traversal.

* refactor(cli): gate CDP claim on primary mount; return plural ACP POST promise

Add a primary flag to RuntimeAcpMount so a secondary workspace's ACP connection cannot claim the CDP tunnel -- the claim is gated on activeMount.primary, matching the primary-only chrome-devtools MCP wiring. The plural /workspaces/:workspace/acp POST handler returns the dispatch promise instead of voiding it.

* refactor(cli): centralize ACP-HTTP enablement in resolveAcpHttpEnabled

Add resolveAcpHttpEnabled() as the single interpretation of the QWEN_SERVE_ACP_HTTP opt-out, replacing four independent env checks across mount, voice-WS advertisement, and CDP-MCP gating. Advertise workspace_qualified_acp only when the ACP HTTP surface is enabled AND multi-workspace sessions are active, so it is not announced when ACP HTTP is disabled.

* feat(cli): ACP dispose 503 gate + aggregate connection snapshot across mounts

After dispose() the shared ACP HTTP handlers (legacy /acp + workspace-qualified) return 503 server_disposed instead of racing torn-down registries during the shutdown drain. Add AcpHttpHandle.getSnapshot() aggregating connection and wsStream counts across the primary mount and every trusted secondary runtime, and switch the metrics sampler to it so daemon metrics report all workspaces' ACP connections rather than only the primary's.

* test(cli): cover ACP dispose 503, aggregate snapshot, and raw dot-segment WS reject

* docs(design): record Phase 4 ACP systematic rework (8-axis hardening)

Correct the Summary (the device-flow registry stays daemon-global and shared, not per-runtime) and add a section documenting the final architecture: runtime mount factory, routing/trust isolation, raw request-target WS parsing, daemon-global device-flow with event-sink fan-out, primary-only CDP, disposed 503 gate, aggregate getSnapshot, and resolveAcpHttpEnabled-gated capability advertisement.

* fix(cli): align /daemon/status ACP counts with the aggregate mount snapshot

Code review found a drift: the metrics sampler switched to the aggregate AcpHttpHandle.getSnapshot() (all mounts) while /daemon/status still read the primary-only registry snapshot, so the two observability surfaces diverged under multi-workspace. Extend AcpHttpSnapshot to aggregate all transport counters (connection/session/sse/ws streams + pending client requests) and feed the /daemon/status transport summary from it; per-connection diagnostics and the connection cap stay primary-scoped. Also refresh the device-flow-registry doc comment to the daemon-global shared model.

* test(cli): regression-test device-flow on a trusted secondary workspace

Locks in the reviewer Critical fix: a trusted secondary workspace's ACP now shares the daemon-global device-flow registry, so device_flow/start reaches provider resolution (an unsupported-provider error here) instead of erroring 'Device flow not configured'. Wires a shared DeviceFlowRegistry into the test harness and drives initialize + device_flow/start over the secondary WebSocket.

* docs(design): mark the superseded per-runtime device-flow section

Address PR QwenLM#6621 review: the pre-rework 'Per-runtime device-flow registry' section contradicted Systematic rework axis 4 (daemon-global shared registry + fan-out). Flag it as superseded design-history so readers don't build the wrong mental model.

* refactor(cli): mount ACP only for trusted secondary workspaces

Address PR QwenLM#6621 review suggestions: (1) skip creating a dispatcher/registry/remember-lane for untrusted non-primary workspaces (they are 403-rejected before any mount lookup), so they no longer appear as always-zero entries in the aggregate getSnapshot(); (2) test that a secondary workspace cannot claim the process-wide CDP tunnel (primary-only guard); (3) test that a WS upgrade to an unknown selector is rejected 400.

* test(cli): cover device-flow event fan-out across bridges

Address PR QwenLM#6621 review: the resolveEventBridges fan-out (the reviewer Critical fix's core delivery path) had zero test coverage. Add unit tests that a device-flow event reaches every resolved bridge, that one bridge throwing does not block the others (best-effort), and that it falls back to the single bridge when no resolver is provided.

* fix(cli): report ACP connection pressure across all mounts

Address PR QwenLM#6621 review: the connection_capacity_high warning read the primary mount's snapshot only, so a saturated secondary workspace was invisible. Compute the busiest mount from the aggregate snapshot (per-mount cap is uniform, opts.maxConnections) so any mount nearing capacity triggers the warning.

* test(cli): allow acp-http-enabled.ts in the serve process.env guard

Fix CI failure on PR QwenLM#6621: the serve process.env guard flagged the new acp-http-enabled.ts as a direct process.env reader. It is the QWEN_SERVE_ACP_HTTP interpreter extracted from index.ts and serve-features.ts (both already allow-listed); QWEN_SERVE_ACP_HTTP is a daemon-level process-global toggle, so the file inherits their allow-list entry.

* docs: harden workspace-qualified ACP design

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs: plan workspace-qualified ACP hardening

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): align workspace-qualified ACP routing

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): harden qualified ACP request errors

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(cli): cover unmarked URIError fallback

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): make ACP disposal terminal

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): aggregate ACP connection diagnostics

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* chore: remove review process artifact

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): address workspace ACP review feedback

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): finish ACP review follow-ups

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qqqys <qys177@gmail.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-autofix[bot] <qwen-autofix[bot]@users.noreply.github.com>
Co-authored-by: nas <156536069+Nas01010101@users.noreply.github.com>
Co-authored-by: Tianyuan <2720711917@qq.com>
Co-authored-by: han <2992336417@qq.com>
Co-authored-by: ytahdn <1294726970@qq.com>
Co-authored-by: ytahdn <ytahdn@gmail.com>
Co-authored-by: jinye <djy1989418@126.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants