Skip to content

Handle missing web-shell sessions without redirecting - #6357

Merged
wenshao merged 15 commits into
QwenLM:mainfrom
chiga0:codex/web-shell-missing-session-empty-state
Jul 6, 2026
Merged

Handle missing web-shell sessions without redirecting#6357
wenshao merged 15 commits into
QwenLM:mainfrom
chiga0:codex/web-shell-missing-session-empty-state

Conversation

@ytahdn

@ytahdn ytahdn commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This PR makes the web shell handle missing or expired session routes explicitly. When the controlled session load fails with a terminal missing-session status, the chat pane shows a minimal empty state with a message that the current session does not exist and a button to start a new session. The URL is not cleared automatically during the failed load; it is only cleared when the user clicks the new-session action.

The session provider now preserves structured HTTP status information on connection errors so the web shell can branch on status codes instead of parsing daemon error text. It also keeps workspace capabilities available after a terminal session load failure so the sidebar can continue rendering workspace session organization such as recent and archived sections.

Why it's needed

Opening a web-shell URL with a stale or nonexistent session id previously fell through to the normal no-session flow and could replace the route with the new-chat page. That made it look like the app silently redirected instead of telling the user that the requested session no longer exists. Parsing the daemon error message was also fragile because the SDK prefixes HTTP errors and daemon text can change.

This change gives users a stable, understandable state for stale links while keeping the existing new-session flow one click away.

Reviewer Test Plan

How to verify

Open the web shell with a session id that the daemon cannot load and confirm that the main chat pane shows only the missing-session message and a new-session button while the sidebar remains visible. Confirm the URL keeps the stale session id after the failed load. Click the new-session button and confirm the route is cleared and the normal create-page state appears. Reviewers can also simulate provider state by returning a 404 or 410 from session load and checking that the same missing-session empty state appears.

Evidence (Before & After)

Before: a missing session load could clear the controlled route and show the normal new-chat page, and the sidebar could lose organization metadata because capabilities were not preserved after the failed load. After: 404 and 410 session load failures render the explicit missing-session page, the route stays intact until the user clicks new session, and workspace capabilities remain available to the sidebar.

Tested on

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

Environment (optional)

Local verification from the repository root on macOS: npm run build, npm run typecheck, cd packages/web-shell && npx vitest run client/App.test.tsx client/index.test.tsx, cd packages/webui && npx vitest run src/daemon/session/DaemonSessionProvider.test.tsx.

Risk & Scope

  • Main risk or tradeoff: The missing-session branch intentionally hides the composer and transcript for terminal 404/410 load failures, so any future non-terminal use of those status codes would need a separate status classification.
  • Not validated / out of scope: Manual browser screenshots on Windows and Linux were not captured.
  • Breaking changes / migration notes: No migration is required; the connection state gains an optional errorStatus field for consumers that need structured error handling.

Linked Issues

image

N/A

中文说明

What this PR does

这个 PR 让 web shell 显式处理缺失或已失效的会话路由。当受控 session load 返回终态的会话不存在状态时,聊天区域会显示一个最小空白状态,提示当前会话不存在,并提供新建会话按钮。失败 load 时不会自动清空 URL;只有用户点击新建会话按钮时才清空路由。

session provider 现在会在连接错误上保留结构化 HTTP 状态,因此 web shell 可以基于状态码判断,而不是解析 daemon 错误文案。同时,在终态 session load 失败后会保留 workspace capabilities,让侧边栏仍然可以正常渲染最近、归档等工作区会话组织信息。

Why it's needed

以前使用过期或不存在的 session id 打开 web-shell URL 时,可能会落入普通无会话流程并把路由替换成新建聊天页。这样用户看到的是页面静默跳走,而不是明确知道请求的会话已经不存在。解析 daemon 错误文案也很脆弱,因为 SDK 会给 HTTP 错误加前缀,daemon 文案也可能变化。

这个改动为失效链接提供稳定、可理解的状态,同时保留一键进入新建会话的现有流程。

Reviewer Test Plan

How to verify

使用 daemon 无法 load 的 session id 打开 web shell,确认主聊天区域只显示会话不存在提示和新建会话按钮,同时侧边栏仍然可见。确认失败 load 后 URL 仍保留旧 session id。点击新建会话按钮后,确认路由被清空并进入正常创建页面。Reviewer 也可以通过让 session load 返回 404 或 410 来模拟 provider 状态,并确认都会显示同一个缺失会话空白页。

Evidence (Before & After)

Before:缺失会话 load 可能清空受控路由并显示普通新建聊天页,侧边栏也可能因为失败 load 后没有保留 capabilities 而丢失组织信息。After:404 和 410 的 session load 失败会渲染明确的缺失会话页,路由会保留到用户点击新建会话为止,并且 workspace capabilities 会继续提供给侧边栏。

Tested on

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

Environment (optional)

在 macOS 的仓库根目录本地验证:npm run build、npm run typecheck、cd packages/web-shell && npx vitest run client/App.test.tsx client/index.test.tsx、cd packages/webui && npx vitest run src/daemon/session/DaemonSessionProvider.test.tsx。

Risk & Scope

  • Main risk or tradeoff:缺失会话分支会在终态 404/410 load 失败时有意隐藏 composer 和 transcript;如果未来这些状态码被用于非终态场景,需要单独调整状态分类。
  • Not validated / out of scope:没有在 Windows 和 Linux 上手动截图验证。
  • Breaking changes / migration notes:不需要迁移;connection state 增加了可选 errorStatus 字段,供需要结构化错误处理的消费者使用。

Linked Issues

N/A

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Thanks for the PR! (Re-run after extensive maintainer review cycles and multiple author pushes.)

Template looks good ✓

Problem: Real UX issue — opening a web-shell URL with a stale or nonexistent session id silently redirected to the new-chat page instead of telling the user the session no longer exists. Parsing daemon error text was fragile. This is an observed problem with clear before/after described and a screenshot showing the expected behavior.

Direction: Aligned. Explicit missing-session state is the right pattern. Propagating structured HTTP status codes via errorStatus instead of parsing error strings is a clean improvement. The missingSession boolean flag on DaemonConnectionState gives the UI a clear signal without requiring consumers to know about HTTP status codes. Web-shell session handling is squarely within scope.

Size: packages/webui/src/daemon/session/ touches core paths (session module). Breakdown:

  • Production logic: ~760 lines (additions + deletions across 11 non-test files)
  • Test files: ~473 lines (5 test files including the new status.test.ts)
  • Other: 1 line (.qwen/skills/triage/references/pr-workflow.md — stray blank line)
  • Total: 1234 lines

Title is not refactor-type — no hard block. 760 production lines exceeds the 500-line maintainer-awareness threshold; flagging for maintainer awareness (not blocking).

Approach: Scope is focused on the stated goal. The two new fields (errorStatus, missingSession) on DaemonConnectionState are minimal additions. The status.ts helper module (isMissingSessionHttpStatus, resolveConnectionErrorStatus) is well-scoped and exported through the daemon-react-sdk barrel for reuse. App.tsx's diff is dominated by indentation churn from the missingSession conditional, but the actual new rendering code is small. The heartbeat error handling has been hardened through multiple rounds of maintainer review — auth failures (401/403), missing sessions (404/410), active prompt cleanup, diagnostic logging, and sticky error-status preservation across transport-only retries. The newSession() action correctly clears missingSession and error state.

Moving on to code review and verification. 🔍

中文说明

感谢贡献!(Re-run — 在 maintainer 多轮 review 和作者多次推送后重新审查。)

模板完整 ✓

问题: 真实的 UX 问题——使用过期或不存在的 session id 打开 web-shell URL 时,会静默跳转到新建聊天页面,而不是告知用户会话已不存在。解析 daemon 错误文案也很脆弱。这是一个已观测到的问题,有清晰的 before/after 描述和截图。

方向: 对齐。明确的缺失会话状态是正确的模式。通过 errorStatusmissingSession 传播结构化状态信息,而不是解析错误字符串,是一个干净的改进。

规模: packages/webui/src/daemon/session/ 触及核心路径(session 模块)。生产逻辑:~760 行(11 个非测试文件),测试文件:~473 行(5 个测试文件),其他:1 行。标题不是 refactor 类型——无硬性阻止。760 行生产代码超过 500 行维护者关注阈值(不阻塞)。

方案: 范围聚焦。status.ts helper 模块范围合理,通过 daemon-react-sdk barrel 正确共享。App.tsx 的 diff 主要是 missingSession 条件包装导致的缩进变化,实际新渲染代码很少。Heartbeat 错误处理经过 maintainer 多轮 review 已很全面。

进入代码审查和验证 🔍

Qwen Code · qwen3.7-max

@ytahdn

ytahdn commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the triage note by adding an inline comment explaining why the session-id-change effect intentionally preserves the missing-session route until the user clicks new chat. Verified with: cd packages/web-shell && npx vitest run client/App.test.tsx client/index.test.tsx and npx prettier --check packages/web-shell/client/App.tsx.

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code Review (Re-run after maintainer review cycles)

Independent proposal: I'd add an optional errorStatus field and a missingSession boolean to DaemonConnectionState, thread them through the provider's error paths (SSE connect catch, heartbeat catch, session load catch), add a minimal "missing session" rendering branch in App.tsx that replaces the chat view with a message + new-session button when missingSession is true, and add i18n strings, a sticky-preservation helper for error status, heartbeat-specific handling for auth/missing-session failures, and comprehensive tests.

Comparison with the diff: The PR's approach matches this closely. Key implementation decisions:

  • Two-field design (errorStatus for raw HTTP status, missingSession for UI-ready boolean) — clean separation. Consumers don't need to know about HTTP codes.
  • Sticky preservation via resolveConnectionErrorStatus — a later transport-only retry (no HTTP status) cannot erase a prior 404/410 signal. Subtle edge case, handled correctly.
  • lastHttpError tracking in heartbeat — pairs the most recent HTTP status with a later transport-only failure so the threshold-crossing check uses the right status. Well-designed.
  • Heartbeat branching — 401/403 → 'error' status (terminal auth failure), 404/410 → 'disconnected' with active prompt abort and session cleanup. Both correct.
  • loadingRequestedSession flag — scopes missingSession: true to initial session load failures only, preventing false positives from later SSE reconnect errors. Important distinction, correctly implemented.
  • newSession() clearing — clears missingSession, error, and errorStatus before incrementing the nonce. Prevents stale missing-session state from persisting after the user clicks "New session."

Findings:

  • No blockers. The error-status threading through DaemonSessionProvider is correct. All state transitions properly handle the new fields. The getConnectionAfterSessionClear and createDaemonSessionActions functions consistently clear errorStatus and missingSession.
  • App.tsx rendering is straightforward: missingSession flag drives a clean conditional — the missing-session state div replaces the chat view. The "New session" button uses a ref-backed guard against concurrent clicks while React state handles the disabled/loading UI. The session-id-change effect correctly preserves the missing-session route until the user clicks new chat.
  • Status helpers (isMissingSessionHttpStatus, resolveConnectionErrorStatus) are clean, well-tested, and exported through the daemon-react-sdk barrel.
  • Reuse check: isMissingSessionHttpStatus is imported from @qwen-code/webui/daemon-react-sdk in App.tsx — shared properly through the SDK barrel rather than duplicated.

Verification

Unit tests (reported by author, confirmed by CI):

  • status.test.ts: 4 tests (status detection, sticky preservation, next-status precedence)
  • App.test.tsx: 29+ tests (missing-session rendering for 404/410, non-404/410 negative cases, new-session action, concurrent-click guard, connecting-state hiding)
  • DaemonSessionProvider.test.tsx: 140+ tests (heartbeat terminal states, auth failures, sticky error preservation, active prompt cleanup, session load vs reconnect distinction)
  • actions.test.ts: 4 tests (clear/create/load clearing errorStatus and missingSession)

CI: Test (ubuntu-latest, Node 22.x) — ✅ pass. Build and typecheck — ✅ pass.

Tmux real-scenario testing: Not applicable — this is a browser-based web shell feature that renders in a Chromium/Firefox tab. The behavior (rendering a missing-session empty state when the daemon returns 404/410) cannot be exercised via tmux in a terminal. Unit tests comprehensively cover the rendering logic and provider state transitions.

Maintainer real-browser verification: @wenshao performed a full real-browser E2E run on Linux (Chromium + Vite dev server + real qwen serve daemon returning genuine 404), confirming all Reviewer Test Plan assertions pass with real DOM/URL inspection. RED/GREEN analysis with surgical one-expression reverts confirmed the new tests have teeth (each revert flips exactly the expected tests).

中文说明

代码审查(Re-run — maintainer 多轮 review 后)

独立方案:DaemonConnectionStateerrorStatusmissingSession 两个可选字段,在 provider 的错误路径中传播,在 App.tsx 加最小化的缺失会话渲染分支,加上 i18n、sticky 保留 helper、heartbeat 特定处理和测试。

对比: PR 的实现与这个方案高度一致。关键设计决策:

  • 双字段设计(errorStatus 保留原始 HTTP 状态,missingSession 提供 UI 就绪的布尔值)——干净的分离
  • resolveConnectionErrorStatus 的 sticky 保留——后续的纯 transport 重试不会覆盖之前的 404/410 信号
  • Heartbeat 的 lastHttpError 跟踪——确保阈值检查使用正确的状态码
  • loadingRequestedSession 标志——将 missingSession: true 限定在初始 session load 失败
  • newSession() 正确清除所有缺失会话状态

发现: 无阻塞问题。所有状态转换正确处理新字段。App.tsx 渲染简洁明了。Status helper 通过 SDK barrel 正确共享。

验证

单元测试: 180+ 个测试覆盖所有新行为。CI ✅。Build + typecheck ✅。

Tmux 真实场景测试: 不适用——这是浏览器 web shell 功能。

Maintainer 真实浏览器验证: @wenshao 在 Linux 上用真实 Chromium + Vite dev + 真实 daemon 做了完整 E2E 验证,所有 Reviewer Test Plan 断言通过。RED/GREEN 回退分析确认新测试有效。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Re-run after extensive maintainer review cycles (8+ rounds of feedback from @wenshao, all addressed by the author) and real-browser verification on Linux.

This PR has been through an unusually thorough gauntlet for a community contribution. Looking at it fresh against the current state of the diff:

Does the implementation match my independent proposal? Yes — and exceeds it in several areas. The lastHttpError tracking across consecutive heartbeat failures, the loadingRequestedSession flag scoping missingSession to initial loads only, and the ref-backed concurrent-click guard are all details I would have added in follow-up passes. The author got them right through iterative review.

Does this solve something users actually care about? Yes. Stale web-shell links silently redirecting is a real frustration — the screenshot in the PR body shows exactly what users see today. The fix gives a clear, actionable state.

Is the code straightforward? Yes. The two-field design (errorStatus + missingSession) keeps the UI logic simple while preserving raw status for future consumers. The heartbeat handler's branching (auth vs missing vs generic) is readable despite handling three distinct failure modes. The status.ts module is 37 lines — no over-abstraction.

Is every change necessary? The PR grew through iterative review rather than being designed end-to-end upfront, which shows in a few places (e.g., the heartbeat catch block now handles auth, missing-session, and generic disconnect in one place with branching). But the resulting code is correct, well-tested, and the iterative approach is how most real-world features ship. No drive-by refactors or scope creep — every commit addresses specific review feedback.

Does the evidence support the claims? Yes. CI passes on Linux. The maintainer's real-browser run on Linux with a genuine 404 daemon response confirms all assertions. RED/GREEN analysis with surgical reverts proves the tests have teeth. 180+ unit tests cover the new behavior comprehensively.

Am I approving because it's genuinely good? Yes. This is a well-scoped fix for a real UX problem, with clean implementation, comprehensive tests, and real-world verification. The author has been responsive to every round of feedback.

No concerns. Approving. ✅

中文说明

Re-run — 经过 maintainer 8+ 轮 review(@wenshao 的反馈,作者全部处理完毕)和 Linux 真实浏览器验证。

这个 PR 经过了异常严格的打磨。对照当前 diff 重新审视:

与独立方案对比: 一致,并在多个方面超出预期——lastHttpError 跨连续 heartbeat 失败的跟踪、loadingRequestedSessionmissingSession 限定在初始加载、ref-backed 并发点击防护。

是否解决真实用户问题: 是。过期 web-shell 链接静默跳转是真实的痛点。修复提供了清晰、可操作的状态。

代码是否简洁: 是。双字段设计让 UI 逻辑简单同时保留原始状态码。Heartbeat 分支可读性好。status.ts 只有 37 行——没有过度抽象。

每个改动都必要吗: PR 通过迭代 review 增长,而非预先端到端设计。但最终代码正确、测试全面。无顺手重构或范围蔓延。

证据是否支持声明: 是。CI 通过。Maintainer 真实浏览器验证通过。RED/GREEN 回退分析确认测试有效。180+ 单元测试全面覆盖。

是否真心认为好而批准: 是。这是一个范围合理的真实 UX 问题修复,实现干净,测试全面,验证充分。作者对每轮反馈都积极响应。

没有顾虑,批准。 ✅

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. ✅

@ytahdn
ytahdn requested a review from qqqys July 6, 2026 03:23

@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.

No blockers found. CI is currently failing (Post Coverage Comment, review-pr, delay-automatic-review, Test (ubuntu-latest, Node 22.x)) — recommend checking CI before merge. Suggestion-level recommendations are in the Suggestion summary comment below.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Suggestions — commit 993329c98bb912dbed132f93c33b1070f758b7ab

File Issue Suggested fix
packages/webui/src/daemon/session/DaemonSessionProvider.tsx:~1435 Heartbeat .catch sets errorStatus for 404/410 but preserves sessionId, so the missing-session UI in App.tsx (which requires !connection.sessionId) never triggers from heartbeat-detected terminal errors. The main SSE handler correctly clears sessionId, but there's a transient gap. When extractHttpStatus(error) returns a terminal status in the heartbeat handler, mirror the SSE handler: clear sessionId: undefined and preserve capabilities.
packages/web-shell/client/App.tsx:~2284 handleMissingSessionNewSession uses .then()/.catch() but createNewSession has an internal try/catch that always returns boolean, so the .catch() is unreachable for createNewSession errors and can only fire if onSessionIdChange throws. Convert to async/await and drop the unreachable .catch(): const success = await createNewSession(); if (success) onSessionIdChange?.(undefined);

— qwen3.7-max via Qwen Code /review

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

ytahdn commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the latest review suggestions and conflict with main: updated the missing-session new-session path to avoid duplicate session-id notifications, mirrored the SSE missing-session behavior for heartbeat-detected 404/410 errors, added regression coverage, and merged current upstream main. Verified with: cd packages/web-shell && npx vitest run client/App.test.tsx client/index.test.tsx, cd packages/webui && npx vitest run src/daemon/session/DaemonSessionProvider.test.tsx, npm run build, and npm run typecheck.

wenshao
wenshao previously approved these changes Jul 6, 2026
@ytahdn

ytahdn commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@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. ✅

Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx Outdated
Comment thread packages/web-shell/client/App.tsx Outdated
Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx Outdated
Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx Outdated
@wenshao

wenshao commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

… inline panels

Merge origin/main into the PR branch. The two conflicted areas were
independent features touching the same JSX/CSS regions in App.tsx and
App.module.css:

- PR branch: added a missing-session empty state (inline message + new
  session button) that replaces the chat view when a session is not found.
- Main branch: refactored Settings/Daemon Status from modal dialogs into
  inline panels with activePanel state, and added chatViewWrap wrapping.

Resolution: both features coexist — the missing-session ternary gates the
entire chat area; when absent, the main layout (inline panels, scheduled
tasks page, chat view) renders as usual. Approval overlay and StatusBar
handler take main's versions (ref-based overlay, openPanel for settings).
@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 #6357

Branches

  • PR branch: codex/web-shell-missing-session-empty-state (checked out as qwen-resolve/pr-6357)
  • Base branch: main (origin/main)

Conflicted Files

1. packages/web-shell/client/App.module.css

2 conflict blocks (lines ~403–531)

Side What it added
PR .missingSessionState, .missingSessionMessage, .missingSessionButton — styles for the empty-state UI shown when a session is not found
Main .panelHost, .panelHeader, .panelBack, .panelTitle, .panelBody, .chatViewWrap, .chatViewHidden, .appChatEmpty .chatViewWrap — styles for the inline panel system that replaced modal dialogs

Resolution: Combined both sets of CSS rules. They are independent — the missing-session styles style the empty state, the panel styles style the inline settings/daemon-status panels. Also removed a stray } left over from a conflict marker boundary.

2. packages/web-shell/client/App.tsx

8 conflict regions across ~4700 lines

Conflict 1 — createNewSession dependency array (line ~2416)

  • PR: Added handleMissingSessionNewSession callback after createNewSession, which depends on [createNewSession, onSessionIdChange]
  • Main: Changed createNewSession deps from [reportError, sessionActions] to [closeMobileDrawer, closePanel, reportError, sessionActions]
  • Resolution: Took main's expanded dep array for createNewSession AND kept the PR's handleMissingSessionNewSession callback immediately after it. The callback calls createNewSession() (which now also closes panels/mobile drawer) then resets the session ID.

Conflicts 2 + 3 + 8 — Main JSX rendering structure (lines ~4330, ~4466, ~4735)

  • PR: Wrapped the chat content in a {missingSession ? <emptyState> : <originalContent>} ternary
  • Main: Restructured the chat area: added {activePanel && <section>...</section>}, wrapped chat i

@wenshao

wenshao commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

🔴 Build Failure Analysis

Failing check: Test (ubuntu-latest, Node 22.x)
Error: Unterminated regular expression at packages/web-shell/client/App.tsx:4459

Root Cause

The PR wraps the main content area in a {missingSession ? (...) : (<> ... </>)} ternary with a fragment. However, two stale closing </div> tags from the original structure were left behind during the refactor:

  1. Line 4459 — orphan </div> between the scheduledTasks section close and the )}:

                    </div>    // ← closes inner div of scheduledTasks page
                  </div>      // ← closes fullPage div
                      </div>  //   ORPHAN  no matching opening tag
                    )}
  2. Line 4709 — orphan </div> after WebShellCustomizationProvider closes:

                  </WebShellCustomizationProvider>
                      </div>  // ← ❌ ORPHAN — no matching opening tag
                    </>
                  )}

The JSX parser (esbuild) gets confused by the mismatched nesting and misinterprets a later expression as an unterminated regular expression.

Fix

Remove the two orphan </div> tags at lines 4459 and 4709. The fragment <> only needs closing for:

  • {activePanel && (<section>...</section>)}
  • {mainView === 'scheduledTasks' && (<div>...</div>)}
  • <div className={chatViewWrap}>...</div>

No extra wrapper div was added, so no extra </div> closings are needed.


🇨🇳 中文分析

🔴 构建失败分析

失败的检查项: Test (ubuntu-latest, Node 22.x)
错误信息: packages/web-shell/client/App.tsx:4459Unterminated regular expression

根因

PR 将主内容区域包裹在 {missingSession ? (...) : (<> ... </>)} 三元表达式 + Fragment 中。但在重构过程中,两个原结构中的 </div> 闭合标签 被遗留了下来:

  1. 第 4459 行 — 在 scheduledTasks 区域关闭和 )} 之间的孤立 </div>

                    </div>    // ← 关闭 scheduledTasks 页面的内部 div
                  </div>      // ← 关闭 fullPage div
                      </div>  //   孤立标签  没有对应的开标签
                    )}
  2. 第 4709 行 — 在 WebShellCustomizationProvider 关闭后的孤立 </div>

                  </WebShellCustomizationProvider>
                      </div>  // ← ❌ 孤立标签 — 没有对应的开标签
                    </>
                  )}

JSX 解析器 (esbuild) 因嵌套结构不匹配而混乱,将后续的表达式误解析为未终止的正则表达式。

修复方法

删除第 4459 行和第 4709 行的两个孤立 </div> 标签。Fragment <> 只需要为以下元素闭合:

  • {activePanel && (<section>...</section>)}
  • {mainView === 'scheduledTasks' && (<div>...</div>)}
  • <div className={chatViewWrap}>...</div>

没有新增额外的包裹 div,因此不需要额外的 </div> 闭合标签。

@ytahdn

ytahdn commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the latest review feedback:

  • Preserve the missing-session 404/410 signal when later non-HTTP SSE errors update connection state.
  • Clear the stale session ref and abort/delete any active prompt when heartbeat detects a missing session.
  • Keep auth-failure connection updates functional so existing capabilities are preserved while still clearing the dead session id.
  • Restore the scheduled-tasks page as an overlay sibling of the chat subtree and fix the malformed JSX nesting.

Verification:

  • cd packages/web-shell && npx vitest run client/App.test.tsx client/index.test.tsx
  • cd packages/webui && npx vitest run src/daemon/session/DaemonSessionProvider.test.tsx
  • npm run build
  • npm run typecheck

Comment thread packages/web-shell/client/App.test.tsx Outdated
Comment thread packages/web-shell/client/App.tsx
Comment thread packages/web-shell/client/App.tsx Outdated
Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx Outdated
@wenshao

wenshao commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx
Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx Outdated
Comment thread packages/web-shell/client/App.test.tsx
@ytahdn

ytahdn commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the latest review threads in 9f022e3:

  • Added diagnostics for terminal 404/410 SSE session errors.
  • Preserved the most recent HTTP heartbeat status across consecutive heartbeat failures so a 404/410 is not lost if the threshold-crossing failure is transport-only.
  • Added negative App coverage to ensure non-404/410 errors do not render the missing-session empty state.

Verification:

  • cd packages/web-shell && npx vitest run client/App.test.tsx client/index.test.tsx
  • cd packages/webui && npx vitest run src/daemon/session/DaemonSessionProvider.test.tsx src/daemon/session/status.test.ts
  • npm run build && npm run typecheck

@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.

⚠️ Downgraded from Approve to Comment: CI still running.

— qwen3.7-max via Qwen Code /review

Comment thread packages/web-shell/client/App.tsx
Comment thread packages/web-shell/client/App.tsx
Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx Outdated
Comment thread packages/webui/src/daemon/session/status.ts
Comment thread packages/web-shell/client/App.tsx
@wenshao

wenshao commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@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. ✅

@ytahdn

ytahdn commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up fixes pushed in 919f737ed:

  • Use a ref-backed guard for the missing-session New session action while keeping state only for the disabled/loading UI.
  • Gate the missing-session empty state on a provider-owned missingSession flag and hide it while connecting, so SSE/heartbeat/other API failures do not trigger the blank missing-session page.
  • Clear missingSession when clearing, creating, switching, or starting a new session so clicking New session does not fall back to the stale error page.
  • Pair heartbeat HTTP status with its original message and share the missing-session HTTP status source of truth.
  • Added tests for same-session active goal preservation, connecting-state hiding, failed New session, load-only missing-session marking, SSE/heartbeat non-empty-state behavior, and clear-session state cleanup.

Verified locally:

  • cd packages/web-shell && npx vitest run client/App.test.tsx client/index.test.tsx
  • cd packages/webui && npx vitest run src/daemon/session/DaemonSessionProvider.test.tsx src/daemon/session/status.test.ts src/daemon/session/actions.test.ts
  • npm run build
  • npm run typecheck

@wenshao

wenshao commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

✅ Local verification (real browser + real daemon, Linux)

I built and drove this branch end-to-end on Linux — the row your test matrix left as ⚠️ — with a real Chromium, the PR's frontend, and a real qwen serve daemon returning a genuine 404. Everything in the Reviewer Test Plan holds.

1. Real end-to-end (Chromium + Vite dev of this branch + real daemon)

Setup: the PR frontend runs through the web-shell Vite dev server built from this branch's source (so web-shell/client/App.tsx + webui/.../DaemonSessionProvider + the new status.ts are the code actually executing), proxying to a real qwen serve backend. I opened a stale/nonexistent session route:

GET /session/stale-session-2f9c1a-DOES-NOT-EXIST
→ daemon: 404  "POST /session/:id/load: No session with id "stale-session-2f9c1a-DOES-NOT-EXIST""

The browser console emitted this branch's own new breadcrumb, confirming the running code is this PR (neither the log line nor the copy exists on main):

[DaemonSessionProvider] terminal session error (sessionId=…, status=404, message=…)

Missing-session route — the explicit empty state renders instead of a silent redirect:

Missing session empty state: 'Current session does not exist' + New session button, sidebar intact

After clicking “New session” — route clears to /, normal composer returns:

After New session: route cleared, Welcome to Qwen Code composer restored

Asserted directly in the driver against the live DOM/URL:

Check Result
“Current session does not exist” message shown
Composer + submit hidden ([data-testid="submit"] count) 0
“New session” button present 1
Sidebar stays visible (capabilities preserved)
URL keeps the stale session id after the failed load /session/stale-…
“New session” → clearSession() only, no create/attach
“New session” → route cleared to /, composer count 1

2. Unit suites (real vitest, run from this branch)

Package File(s) Tests
webui status.test.ts + actions.test.ts + DaemonSessionProvider.test.tsx 157 ✅
web-shell client/App.test.tsx + client/index.test.tsx 36 ✅
cli acp-integration/acpAgent.test.ts 191 ✅

384/384 passed. (CI Test (ubuntu-latest) is also green on the full suite.)

3. RED/GREEN — the new tests actually have teeth

Three surgical one-expression reverts, each isolating a different layer; each flips exactly the expected tests, and restoring returns all to green:

Revert Suite Effect
resolveConnectionErrorStatusreturn nextStatus status.test.ts 2 fail (404/410 preservation across status-less retries)
App.tsx missingSession = false App.test.tsx 3 fail (404 & 410 empty-state render)
provider missingLoadedSession = false DaemonSessionProvider.test.tsx 2 fail (- missingSession: true / + false for 404 & 410)

4. Scope / honest notes

  • The real-browser run exercised the 404 path (the daemon returns 404 for unknown sessions). The 410 path is covered by the unit tests through the identical isMissingSessionHttpStatus gate (MISSING_SESSION_HTTP_STATUSES = [404, 410]) — I couldn't easily make a real daemon emit a 410.
  • The daemon backend is unchanged by this PR; I used the built main daemon purely as the real 404 source. Only the frontend rendering of that 404 is what this PR changes, and that's what the browser run verifies.

Verdict: behavior matches the Reviewer Test Plan, and this fills the Linux gap. LGTM from a verification standpoint. 👍

中文说明

✅ 本地验证(真实浏览器 + 真实 daemon,Linux)

我在 Linux(你们测试矩阵里标 ⚠️ 的那一行)上用真实 Chromium、本分支的前端和一个会返回真实 404qwen serve daemon,做了完整的端到端验证。Reviewer Test Plan 里的每一条都成立。

1. 真实端到端(Chromium + 本分支 Vite dev + 真实 daemon)

环境:web-shell 的 Vite dev server 直接用本分支源码运行(也就是说真正执行的是本分支的 web-shell/client/App.tsx + webui/.../DaemonSessionProvider + 新增的 status.ts),并代理到真实的 qwen serve 后端。我打开了一个过期/不存在的会话路由:

GET /session/stale-session-2f9c1a-DOES-NOT-EXIST
→ daemon: 404  "POST /session/:id/load: No session with id "stale-session-2f9c1a-DOES-NOT-EXIST""

浏览器控制台打印出了本分支新增的日志,可以确认正在运行的就是本 PR 的代码(这行日志和这句文案在 main 上都不存在):

[DaemonSessionProvider] terminal session error (sessionId=…, status=404, message=…)

缺失会话路由 —— 显示的是明确的空状态,而不是静默跳转(见上方第一张截图)。

点击「New session」后 —— 路由清空回 /,正常输入框恢复(见上方第二张截图)。

driver 直接对实时 DOM/URL 的断言:

检查项 结果
显示「Current session does not exist」
输入框 + 发送按钮被隐藏([data-testid="submit"] 计数) 0
「New session」按钮存在 1
侧边栏保持可见(capabilities 被保留)
失败 load 后 URL 仍保留旧 session id /session/stale-…
「New session」→ 只调用 clearSession(),不 create/attach
「New session」→ 路由清空回 /,输入框计数 1

2. 单元测试(真实 vitest,在本分支运行)

文件 测试数
webui status.test.ts + actions.test.ts + DaemonSessionProvider.test.tsx 157 ✅
web-shell client/App.test.tsx + client/index.test.tsx 36 ✅
cli acp-integration/acpAgent.test.ts 191 ✅

384/384 通过。(CI 的 Test (ubuntu-latest) 全量套件也是绿的。)

3. RED/GREEN —— 新增测试确实“抓得住”

三处针对不同层次、各改一行表达式的回退,每一处都精准让预期的测试变红,恢复后全部转绿:

回退 套件 效果
resolveConnectionErrorStatusreturn nextStatus status.test.ts 2 失败(404/410 在无状态重试时的保留)
App.tsx missingSession = false App.test.tsx 3 失败(404 & 410 空状态渲染)
provider missingLoadedSession = false DaemonSessionProvider.test.tsx 2 失败(404 & 410:- missingSession: true / + false

4. 范围 / 如实说明

  • 真实浏览器这一路跑的是 404 路径(daemon 对未知会话返回 404)。410 路径由单元测试通过完全相同的 isMissingSessionHttpStatus 判定(MISSING_SESSION_HTTP_STATUSES = [404, 410])覆盖 —— 真实 daemon 不太好构造 410。
  • daemon 后端本 PR 未改动;我只是用 main 构建出来的 daemon 作为真实 404 的来源。本 PR 改的是对这个 404 的前端渲染,而这正是浏览器这一路所验证的。

结论:行为与 Reviewer Test Plan 一致,并补上了 Linux 这一环。从验证角度 LGTM。👍

🤖 Verified locally with Claude Code · model: Opus 4.8 (1M context)

Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx Outdated
Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx
Comment thread packages/webui/src/daemon/session/status.ts Outdated
Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx Outdated
wenshao
wenshao previously approved these changes Jul 6, 2026
@wenshao

wenshao commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@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. ✅

@ytahdn
ytahdn dismissed stale reviews from qwen-code-ci-bot and wenshao via 5213b44 July 6, 2026 16:42
Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx
Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx Outdated
Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx Outdated
Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx
Comment thread packages/web-shell/client/App.tsx Outdated
Comment thread packages/webui/src/daemon/session/actions.ts
Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx Outdated
Comment thread packages/webui/src/daemon/session/types.ts
Comment thread packages/web-shell/client/App.tsx
Comment thread packages/web-shell/client/App.module.css
Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.test.tsx

@doudouOUC doudouOUC 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.

Qwen Code Review — PR #6357

Status: Review failed / timed out

The automated Qwen Code review was started with /review 6357 --comment but did not complete within the 60-minute timeout. The process produced partial stream-json output (~289 KB) while actively reading the diff and searching the codebase, but no final result or assistant summary was emitted before the timeout.

  • PR title: Handle missing web-shell sessions without redirecting
  • Head SHA: 9f7385f48fe4ed019fa7a12fc54ad88a2bae7304
  • Outcome: ERROR — qwen CLI review timed out
  • Inline comments: not posted (review did not finish)

Please re-run the review or inspect the PR manually.

status: 'connected',
error: undefined,
errorStatus: undefined,
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The heartbeat .then() recovery path clears errorStatus: undefined and restores status: 'connected' after consecutive failures, but no test verifies this recovery transition. The test suite covers heartbeat failures setting errorStatus (410, 401, 403) but has zero tests for recovery from error back to connected. If this clearing logic were removed or broken, users would remain stuck in a disconnected state after transient heartbeat failures resolve.

Suggested test: heartbeat fails with 410 (threshold=1, state goes disconnected with errorStatus: 410), then heartbeat succeeds, and assert connection.status === 'connected' and connection.errorStatus === undefined.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@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 added this pull request to the merge queue Jul 6, 2026
Merged via the queue into QwenLM:main with commit be7e874 Jul 6, 2026
49 checks passed
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