Skip to content

feat(web-shell): Share session catalog scheduling - #8891

Merged
doudouOUC merged 4 commits into
QwenLM:mainfrom
doudouOUC:agent/web-shell-session-catalog
Aug 11, 2026
Merged

feat(web-shell): Share session catalog scheduling#8891
doudouOUC merged 4 commits into
QwenLM:mainfrom
doudouOUC:agent/web-shell-session-catalog

Conversation

@doudouOUC

@doudouOUC doudouOUC commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This PR adds a page-scoped Web Shell session catalog that is isolated per daemon client. Identical session-list queries now share cached pages and in-flight requests, while a client-wide scheduler limits total list concurrency to two and background concurrency to one so explicit user work retains a request slot.

It preserves the existing legacy and workspace-qualified route ownership, complete pagination metadata, polling cadence, and public interfaces. Session-list consumers across the sidebar, overview, split picker, dialogs, scoped hooks, and command-style lookups now use the shared catalog, and lifecycle or mutation events invalidate or conservatively patch the owning workspace before authoritative refreshes converge the catalog.

The catalog pauses pending automatic work while the page is hidden, resumes only overdue active queries when visibility returns, retains successful pages across transient errors, applies a 30-second automatic retry floor, and keeps zero-subscriber entries for 30 seconds to absorb StrictMode remounts and short UI transitions.

Why it's needed

Several Web Shell surfaces can request the same session list independently, multiplying polling traffic and allowing stale responses from one consumer to race newer mutation or lifecycle state from another. The shared scheduler deduplicates equivalent reads, bounds multi-workspace pressure, reserves capacity for user actions, and gives every consumer one consistent invalidation model without changing daemon or SDK APIs.

Reviewer Test Plan

How to verify

  • Open the sidebar, session overview, and split-session picker together and confirm identical workspace queries share one request stream instead of multiplying by the number of mounted consumers.
  • Exercise primary, trusted secondary, and read-only secondary workspaces and confirm each request keeps its existing legacy or workspace-qualified route and that unavailable or untrusted runtimes do not fall back to the primary runtime.
  • Create, run, complete, rename, pin, archive, restore, delete, and release sessions and confirm the owning workspace catalog converges after successful, failed, and timed-out mutations without affecting equal session IDs in other workspaces.
  • Keep multiple workspaces active and confirm session-list concurrency never exceeds two, background work never occupies more than one slot, and an explicit refresh or fresh command lookup can use the reserved slot.
  • Hide the page long enough for polls to become due, confirm pending automatic traffic stops, then restore visibility and confirm only one coalesced overdue refresh runs per active query.
  • Automated coverage: the full Web Shell unit suite passed (3,125 tests across 179 files), including store scheduling, hooks, workspace routing, component polling, lifecycle synchronization, mutation reconciliation, hidden in-flight invalidation, and abandoned-render cache cleanup. The focused dialog and Vite proxy regression suites passed (15 tests), and a Playwright smoke test confirmed the Web Shell loads with catalog modules served locally. Web Shell lint, changed-file formatting, typecheck, production/library build, repository-wide build, and repository-wide typecheck also passed.

Evidence (Before & After)

N/A — this changes internal request scheduling and catalog consistency without changing the rendered UI.

Tested on

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

Environment (optional)

macOS local workspace with Node.js 22.22.3 and the repository npm workspace dependencies.

Risk & Scope

  • Main risk or tradeoff: Catalog freshness now depends on shared revision, visibility, retry, and mutation-invalidation paths; focused tests cover stale-response rejection, fresh-read semantics, backoff, visibility, and owner-scoped reconciliation. Transient list failures deliberately retain the last successful page together with the error instead of clearing dialogs, so users keep last-known data until an authoritative refresh succeeds.
  • Not validated / out of scope: Cross-tab sharing, push-based session catalog events, pagination-size policy, session groups, status reports, and Git status remain out of scope. Windows and Linux were not tested locally. The full Web Shell format check reports five unchanged CSS/HTML files; changed-file formatting, Web Shell builds, and repository-wide build/typecheck pass.
  • Breaking changes / migration notes: None. Daemon REST routes, TypeScript SDK APIs, generic WebUI hooks, and public Web Shell props remain unchanged.

Linked Issues

N/A

中文说明

本 PR 做了什么

本 PR 在 Web Shell 中新增按 daemon client 隔离的页面级会话目录。相同的 Session List 查询现在共享缓存页面和在途请求,同时客户端级调度器将列表总并发限制为 2、后台并发限制为 1,从而为显式用户操作保留一个请求槽位。

它保留现有 legacy 与 workspace-qualified 路由归属、完整分页元数据、轮询节奏和公开接口。Sidebar、Session Overview、Split picker、dialogs、scoped hooks 与命令式查询等 Session List 消费者现在统一经过共享目录;会话生命周期或 mutation 事件会按 owner workspace 失效或保守 patch,随后通过权威刷新使目录收敛。

页面隐藏时,目录暂停尚未开始的自动任务;恢复可见时只补刷已到期的活跃查询。瞬时错误会保留最后一次成功页面,自动重试最短间隔为 30 秒;零订阅缓存保留 30 秒,以吸收 StrictMode 重挂载和短暂 UI 切换。

为什么需要

多个 Web Shell 界面可能分别请求同一个 Session List,导致轮询流量随已挂载消费者数量增长,并使某个消费者的旧响应与另一个消费者更新的 mutation 或生命周期状态竞争。共享调度器可以去重等价读取、限制多工作区压力、为用户操作保留容量,并在不修改 daemon 或 SDK API 的前提下为所有消费者提供一致的失效模型。

Reviewer 测试计划

如何验证

  • 同时打开 Sidebar、Session Overview 和 Split session picker,确认相同工作区查询共享一条请求流,不再随挂载消费者数量倍增。
  • 验证主工作区、可信次工作区和只读次工作区,确认每个请求仍使用原有 legacy 或 workspace-qualified 路由,并且 unavailable 或 untrusted runtime 不会回退 primary runtime。
  • 执行会话创建、运行、完成、重命名、置顶、归档、恢复、删除和释放,确认 mutation 成功、失败或超时后 owner workspace 的目录最终收敛,且不会影响其他工作区中相同 session ID 的会话。
  • 保持多个工作区活跃,确认 Session List 总并发不超过 2、后台任务最多占用 1 个槽,并且显式刷新或 fresh 命令式查询可以使用保留槽位。
  • 隐藏页面直至轮询到期,确认待执行自动流量停止;恢复可见后,确认每个活跃查询只执行一次合并后的到期刷新。
  • 自动化覆盖:Web Shell 全量单测通过(179 个文件、3,125 项测试),覆盖 Store 调度、hooks、工作区路由、组件轮询、生命周期同步、mutation 对账、隐藏页在途失效和中止渲染缓存回收。弹窗与 Vite 代理定向回归套件通过(15 项测试),Playwright smoke 测试确认 Web Shell 能够加载并由本地开发服务器正确提供 catalog 模块。Web Shell lint、改动文件格式检查、typecheck、生产/库构建、仓库级 build 和仓库级 typecheck 均已通过。

证据(之前与之后)

N/A——该变更调整内部请求调度和目录一致性,不改变渲染 UI。

测试平台

操作系统 状态
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

环境(可选)

macOS 本地工作区,Node.js 22.22.3,使用仓库 npm workspace 依赖。

风险与范围

  • 主要风险或权衡:目录新鲜度现在依赖共享 revision、visibility、retry 和 mutation invalidation 路径;定向测试覆盖旧响应丢弃、fresh 读取语义、退避、可见性和 owner-scoped 对账。瞬时列表失败会有意保留最后一次成功页面及错误信息,而不是清空 dialogs,因此用户会继续看到最后已知数据,直到权威刷新成功。
  • 未验证/范围外:跨标签页共享、推送式 Session Catalog 事件、分页大小策略、session groups、status reports 和 Git status 均不在范围内。Windows 和 Linux 未在本地测试。Web Shell 全目录格式检查仍报告 5 个未修改的 CSS/HTML 文件;改动文件格式检查、Web Shell 构建以及仓库级 build/typecheck 均通过。
  • Breaking changes/迁移说明:无。Daemon REST 路由、TypeScript SDK API、通用 WebUI hooks 和公开 Web Shell props 均保持不变。

关联 Issue

N/A

@doudouOUC

doudouOUC commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator Author

Web Shell session catalog store E2E plan

Baseline

  • Global qwen --version: 0.21.8.
  • Before implementation, Sidebar, Session Overview, Split, workspace sections,
    dialogs, and App helpers each issued their own Session List reads.

Scenarios

  1. Open Sidebar, Session Overview, and Split against the same primary
    workspace. Confirm identical list requests share one in-flight request and
    retain their existing UI contents.
  2. Register several trusted workspaces and expand their sections. Confirm no
    more than two Session List requests run concurrently and background work uses
    only one slot while a user-triggered fresh lookup can occupy the second.
  3. Start and complete a prompt, create a new session whose first prompt fails,
    rename it, pin it, archive/unarchive it, and delete it. Confirm every visible
    catalog converges without duplicate refresh bursts.
  4. Open Resume, Delete, Release, Split picker, and side-task catalog surfaces.
    Confirm they load through the shared scheduler and retain partial results
    when one secondary workspace fails.
  5. Hide the browser tab for longer than each polling interval. Confirm no
    background list calls occur while hidden and only overdue queries refresh on
    visibility restoration.
  6. Exercise primary, trusted secondary, read-only secondary, mismatched, and
    unavailable workspace routes. Confirm existing route-specific trust and
    failure behavior remains unchanged with no primary-runtime fallback.

Result

  • Deterministic Store, hook, component, and App coverage passed: 12 Vitest
    files and 630 tests.
  • The scheduler tests cover shared in-flight reads, total/background concurrency,
    priority upgrades, stale-response rejection, fresh versus reusable loads,
    failure backoff, hidden queue removal/restoration, hidden invalidation during
    an older request, abandoned-render cache cleanup, retained-entry resubscribe,
    and owner-scoped patches.
  • Web Shell lint, TypeScript checking, and both application/library production
    builds passed. Repository-wide build and typecheck also passed.
  • A signed-in browser plus live multi-workspace daemon was not available in this
    worktree run, so request-count inspection against a live daemon remains a
    manual acceptance step for scenarios 1–6.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC
doudouOUC force-pushed the agent/web-shell-session-catalog branch from 0ee5a1c to 968006f Compare August 10, 2026 17:39
@github-actions

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)为单个提交。

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC doudouOUC self-assigned this Aug 11, 2026
@doudouOUC
doudouOUC marked this pull request as ready for review August 11, 2026 02:40
@doudouOUC
doudouOUC enabled auto-merge August 11, 2026 02:40
@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Re-run at the author's request, after 53ba1505 (CI fixes) and 657b8ab (main merge + freshness fix). Gate verdict unchanged from the first pass; numbers updated.

Template: complete ✓ — all sections present, bilingual body, test plan. The risk section now also documents the deliberate last-successful-page error semantics that the first pass asked for.

Problem: real and statically verifiable on main — the same workspace session list is fetched through four or five independent polling paths (sidebar, WorkspaceSection, SessionOverviewPanel, SplitView, dialogs) with no shared cache or request dedup anywhere in the SDK. Still no linked issue or measured request-count before/after, so the size of the practical win remains a claim; the duplication itself is not in doubt.

Direction: in scope ✓ — Web Shell is a first-party surface and consolidating its data fetching is legitimate infrastructure work. No auth/sandbox/public-contract concerns. (Reminder: sibling PR #8892 applies the same catalog idea to the CLI — worth evaluating as a pair.)

Size: 2,743 production-logic lines + 2,510 test lines + 41 design-doc lines across 32 files, all in packages/web-shell — no core paths, so Stage 0's two-tier gate does not engage. Still past the 1,000-line large-PR advisory: noted, informational only.

Approach: the scope question from the first pass stands on record — an in-flight dedup keyed by query params would have captured most of the traffic win at a fraction of this surface area. A maintainer has since deep-reviewed the substance and accepted the full design, so this stays a discussion note, not a gate. Both CI blockers from the first pass are fixed on head, and the reviewer's P2 (dialog staleness under the 30s zero-subscriber retention) was addressed with a maxAgeMs subscription option exactly as suggested.

Risk: no high-risk-path matches from the revert-history analysis. The architectural freshness risk is now explicitly documented in the PR body.

Moving on to code review. 🔍

中文说明

应用户请求重跑,此前已推送 53ba1505(CI 修复)与 657b8ab(合并 main + 新鲜度修复)。门禁结论与首轮一致,数字更新。

模板: 完整 ✓ —— 各部分齐全、中英双语、含测试计划;风险部分也补上了首轮要求的"保留最后成功页"错误语义说明。

问题: 真实存在且可在 main 上静态验证——同一 workspace 的 session list 经由四五条独立轮询路径获取(sidebar、WorkspaceSectionSessionOverviewPanelSplitView、dialogs),SDK 层无任何共享缓存或请求去重。仍无关联 issue 或请求数 before/after 实测,实际收益大小仍是声明;但重复请求本身没有疑问。

方向: 在范围内 ✓ —— Web Shell 是一方界面,整合数据获取是合理的基础设施工作。无 auth/sandbox/公开契约问题。(提醒:姊妹 PR #8892 将同样的 catalog 思路用于 CLI,建议成对评估。)

规模: 2,743 行生产逻辑 + 2,510 行测试 + 41 行设计文档,共 32 个文件,全部位于 packages/web-shell——未触及核心路径,Stage 0 双层门禁不适用。仍超过 1,000 行大 PR 建议线:仅记录,不阻塞。

方案: 首轮的范围疑问保留在案——按查询参数做在途去重本可以用小得多的面积拿到大部分流量收益。维护者已深入审查实质并接受了完整设计,因此此项作为讨论备注保留,不作为门禁。首轮的两个 CI 阻塞项已在 head 上修复;reviewer 提出的 P2(30s 零订阅保留期内弹窗可能展示陈旧列表)已按建议以 maxAgeMs 订阅选项解决。

风险: revert 历史分析未命中高风险路径。架构性新鲜度风险已在 PR 正文中明确记录。

进入代码审查。🔍

Qwen Code · qwen3.8-max

Reviewed at 657b8ab56f22399cc051b45de55b133405771b16 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Code review

My independent baseline is unchanged from the first pass: an in-flight request map keyed by (client, workspaceCwd, list options) sharing one promise across hook instances (~100–200 lines) would capture most of the traffic win. The PR remains much larger than that — the scope question stays on record as a discussion note, not a blocker. This pass reviews the delta since 7f9510fd, two commits, and both hold up:

53ba1505 fixes exactly the two CI blockers from the first pass.

  • The three dialog test files (DeleteSessionDialog, ReleaseSessionDialog, ResumeDialog) now mock ../../hooks/useScopedSessions — the boundary the rewired hook actually calls — instead of the old SDK useSessions surface that left the catalog fetch unresolved.
  • vite.config.ts extends the existing /extensions/ proxy bypass to /session-catalog/* source modules (GET + source-file extension), which fixes the E2E smoke pattern where vite proxied the browser's requests for the new catalog modules at the daemon and died on ECONNREFUSED 127.0.0.1:4170. A focused config test pins the bypass behavior.

657b8ab is a clean main merge plus the reviewer's P2 fix.

  • The only conflict was App.test.tsx; the resolution keeps both sides — main's subagent-panel tests (fix(web-shell): improve subagent panel responsiveness #8915) and this PR's catalog mocks. I checked attribution: the subagent-panel hunks in App.tsx are main's via the merge, not PR-owned.
  • The PR-owned change is maxAgeMs: all three dialogs now subscribe with { autoLoad: true, maxAgeMs: 1_000 }, plumbed through useScopedSessions → catalog hooks → store.subscribe, where a retained page older than maxAgeMs schedules a non-blocking background refresh on subscribe while the retained page is served immediately. It is correctly kept out of the query key (staleness policy, not wire-affecting), reuses requestBackground with its existing dedup/priority guards so re-subscribes can't storm the scheduler, and the new store test pins both halves: no refetch within maxAge, refetch after expiry. This closes the repeated open/close staleness hole the maintainer flagged.

The base machinery I verified on the first pass — route ownership preserved on both routeKinds, owner-scoped invalidation everywhere, revision-based stale-response rejection — is untouched by the delta and still stands. @yiliang114's substantive review independently confirmed the deeper invariants (query-key coverage of every wire-affecting option, numerically unchanged polling cadence, scheduler slot protection, bounded teardown) and approved this head.

sequenceDiagram
    participant P1 as UI consumer (sidebar, overview, dialogs)
    participant P2 as catalog hooks (useSyncExternalStore)
    participant P3 as SessionCatalogStore
    participant P4 as scheduler queue (2 slots, 1 background)
    participant P5 as DaemonClient
    P1->>P2: subscribe or loadOnce(query)
    P2->>P3: subscribe / refresh
    P3->>P4: ensureScheduled(priority, background)
    P4->>P5: fetchPage via legacy or qualified route
    P5-->>P3: DaemonSessionListPage
    P3->>P3: accept only if revision matches desired
    P3-->>P2: snapshot update, resolve waiters
    P1->>P3: mutation event via controller.invalidateWorkspace(cwd)
    P3->>P4: bump revision, mark stale, reschedule owner workspace
Loading
Files changed (30 of 32 shown)
File What changed
docs/design/web-shell/web-shell-session-catalog-store.md New design doc for the catalog store
packages/web-shell/client/session-catalog/session-catalog-store.ts New store: entries, 2-slot scheduler, revisions, polling, visibility, retention; now with maxAgeMs-triggered refresh
packages/web-shell/client/session-catalog/session-catalog-store.test.ts Store tests incl. expired-retained-page refresh
packages/web-shell/client/session-catalog/session-catalog-hooks.ts React bridge hooks; maxAgeMs plumbed into subscribe options
packages/web-shell/client/session-catalog/session-catalog-hooks.test.tsx Hook-layer tests
packages/web-shell/client/hooks/useScopedSessions.ts Rewired to the catalog; gains maxAgeMs option
packages/web-shell/client/hooks/useScopedSessions.test.tsx Tests updated for the new path
packages/web-shell/client/hooks/useOtherWorkspaceSessions.ts Per-workspace catalog queries replace the allSettled fan-out
packages/web-shell/client/hooks/useOtherWorkspaceSessions.test.tsx Tests updated
packages/web-shell/client/App.tsx Lifecycle/mutation events feed the controller; command lookups use loadOnce
packages/web-shell/client/App.test.tsx Rewired flows; merge resolution keeps main's subagent tests alongside catalog mocks
packages/web-shell/client/components/sidebar/WebShellSidebar.tsx Three useSessions instances replaced by catalog queries
packages/web-shell/client/components/sidebar/WorkspaceSection.tsx Per-workspace 10s poll becomes a catalog subscription
packages/web-shell/client/components/sidebar/WorkspaceSection.test.tsx Test updates
packages/web-shell/client/components/SessionOverviewPanel.tsx Own 3s poll becomes pollIntervalMs on shared queries
packages/web-shell/client/components/SessionOverviewPanel.test.tsx Test updates
packages/web-shell/client/components/SplitView.tsx Reload-token effect removed; picker uses shared queries
packages/web-shell/client/components/SplitView.test.tsx Mocks moved to listWorkspaceSessionsPage
packages/web-shell/client/components/ChatPane.tsx Reports prompt admission and turn completion to the controller
packages/web-shell/client/components/ChatPane.test.tsx Test updates
packages/web-shell/client/components/artifacts/SideTaskPanel.tsx Side-task rename/admission feed the controller
packages/web-shell/client/components/artifacts/SideTaskPanel.test.tsx Test updates
packages/web-shell/client/components/dialogs/DeleteSessionDialog.tsx Subscribes with autoLoad plus maxAgeMs 1000
packages/web-shell/client/components/dialogs/DeleteSessionDialog.test.tsx Mocks the migrated hook boundary; asserts the subscribe options
packages/web-shell/client/components/dialogs/ReleaseSessionDialog.tsx Subscribes with autoLoad plus maxAgeMs 1000
packages/web-shell/client/components/dialogs/ReleaseSessionDialog.test.tsx Mocks the migrated hook boundary; asserts the subscribe options
packages/web-shell/client/components/dialogs/ResumeDialog.tsx Subscribes with autoLoad plus maxAgeMs 1000
packages/web-shell/client/components/dialogs/ResumeDialog.test.tsx Mocks the migrated hook boundary; asserts the subscribe options
packages/web-shell/vite.config.ts Dev proxy bypass extended to session-catalog source modules
packages/web-shell/client/vite-config.test.ts Pins the new proxy bypass

… and 2 more files (sidebar collapse-persist and workspace-removal test updates).

Testing

Unattended CI run — no PR code was built or executed here; the evidence below is the PR's own CI on head 657b8ab, read through the API.

Both checks that failed the first pass are now green, and they failed for reasons traced to concrete fixes rather than a flake re-run: the dialog tests were migrated to the new hook boundary, and the vite proxy bypass stops the dev server from proxying catalog source modules at the daemon. The rest of the in-scope lanes are green with them — unit suite on ubuntu, E2E smoke, visuals capture (no screenshot changed vs base, expected for a scheduling-only change), and Desktop Shell on both OSes.

One correction to my first-pass comment: the macOS/Windows unit legs and the CLI integration lane being skipped is by design, not a gap — those jobs are merge-queue-only since #8792 (if: github.event_name == 'merge_group' in ci.yml, "Keep this Linux-only PR gate explicit"). They never ran on any PR in this window.

Not verified here: live request coalescing, slot reservation, and visibility-resume behavior under real load — unit tests pin the semantics and the smoke suite loads the real modules, but no measured traffic reduction exists. Sandboxed verification is already in flight for exactly this claim (triggered alongside this re-run); its report will post separately, and the coalescing/slot claims are what it should settle.

Check Conclusion
Test (ubuntu-latest, Node 22.x) ✅ success (was ❌ 12 web-shell failures)
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success (was ❌ 29/32 failing)
Capture web-shell visuals (ubuntu-latest, Node 22.x) ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
precheck-pr / precheck ✅ success
Classify PR ✅ success
Test (macos-latest, Node 22.x) ⚪ skipped — merge-queue-only by design (#8792)
Test (windows-latest, Node 22.x) ⚪ skipped — merge-queue-only by design (#8792)
Integration Tests (CLI, No Sandbox) ⚪ skipped — merge-queue-only by design (#8792)
review-pr ⏳ bot job, in flight
中文说明

代码审查: 我的独立基线与首轮一致——按 (client, workspaceCwd, list options) 共享在途请求约 100–200 行即可拿到大部分收益;本 PR 依旧大得多,规模疑问作为讨论备注保留、不作阻塞。本轮审查 7f9510fd 之后的增量(两个提交),都站得住:

  • 53ba1505 恰好修复了首轮的两个 CI 阻塞项。 三个弹窗测试改为 mock 改造后 hook 实际调用的 useScopedSessions 边界;vite.config.ts 把现有 /extensions/ 代理 bypass 扩展到 /session-catalog/* 源模块,修掉 E2E smoke 中 vite 把新 catalog 模块请求转发给 daemon 导致 ECONNREFUSED 的问题,并有定向配置测试钉住该行为。
  • 657b8ab 是一次干净的 main 合并加 reviewer 的 P2 修复。 唯一冲突在 App.test.tsx,解决方案同时保留了 main 的 subagent 面板测试(fix(web-shell): improve subagent panel responsiveness #8915)与本 PR 的 catalog mock——我核对了归属:App.tsx 中 subagent 面板相关改动来自 main 合并,非本 PR 所有。PR 自有改动是 maxAgeMs:三个弹窗以 { autoLoad: true, maxAgeMs: 1_000 } 订阅,经 useScopedSessions → catalog hooks → store.subscribe 贯通;超过 maxAge 的保留页在订阅时触发一次非阻塞后台刷新,同时立即展示保留页。它正确地不进入查询键(属新鲜度策略而非影响请求参数),复用 requestBackground 现有的去重与优先级保护,不会造成重复订阅风暴;新增 store 测试同时钉住两侧语义(maxAge 内不重取、过期后重取)。这关闭了维护者指出的"反复开关弹窗可能看到陈旧列表"的洞。

首轮验证过的基础机制——两种 routeKind 路由归属保留、失效全部按 owner workspace 作用域、基于 revision 的过期响应丢弃——未被增量触及,仍然成立。@yiliang114 的实质审查独立确认了更深的不变量(查询键覆盖所有影响请求的选项、轮询节奏数值不变、调度器槽位保护、有界清理),并已在该 head 上批准。

测试: 无人值守 CI 运行——未构建或执行任何 PR 代码,证据为 head 657b8ab 上 PR 自身 CI(经 API 读取)。首轮失败的两个检查现已转绿,且失败原因可追溯到具体修复而非重跑碰运气:弹窗测试迁移到新 hook 边界、vite bypass 阻止 dev server 把 catalog 源模块代理给 daemon。其余在范围车道全绿——ubuntu 单测、E2E smoke、视觉采集(与 base 无截图差异,对纯调度变更符合预期)、两平台 Desktop Shell。

更正首轮评论的一处表述:macOS/Windows 单测与 CLI 集成车道被跳过是设计使然而非缺口——自 #8792 起这些任务仅在 merge queue 运行(ci.yml 中 if: github.event_name == 'merge_group',并注明"保持 Linux-only PR 门禁")。本时间窗内任何 PR 都不会运行它们。

此处未验证:真实负载下的请求合并、槽位保留与可见性恢复行为——单测钉住了语义、smoke 套件加载了真实模块,但尚无流量下降的实测数据。沙箱验证已随本次重跑并行触发,报告会单独发布;合并/槽位声明正是它应当收口的内容。

Qwen Code · qwen3.8-max

Reviewed at 657b8ab56f22399cc051b45de55b133405771b16 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — both first-pass blockers fixed with traceable causes, the staleness P2 closed exactly as suggested with tests, CI green on every in-scope lane, and a maintainer deep-reviewed and approved this exact head. Remaining notes are non-blocking.

Stepping back, here's where I land.

I came in skeptical about scope, and I'm not dropping that note: an in-flight dedup keyed by query params remains the simpler design I would have reached for, and the PR still carries no measured before/after request counts — the traffic-reduction headline is substantiated by test semantics, not by numbers. But a gate also has to recognize when its questions have been answered. Every red check from the first pass is green for a traced reason (dialog tests migrated to the hook boundary; vite bypass for catalog source modules), not a flake re-run. The one semantic hole found in review — dialogs showing a minutes-old retained list under the 30s zero-subscriber retention — is closed by a minimal, tested maxAgeMs option rather than by weakening the retention design. And the invariants I could only check statically (route ownership, owner-scoped invalidation, polling cadence, teardown bounds) were independently verified by a maintainer who then approved this head. If I had to maintain this in six months, the machinery is documented, tested at 2,500+ lines, and its freshness semantics are now explicit — I'd curse at the surface area once, then be fine.

Approving, pinned to the reviewed commit. Non-blocking follow-ups:

  • Sibling PR perf(cli): Cache persisted session catalogs #8892 applies the same catalog idea to the CLI — evaluate the pair together; a shared design doc between them would help future readers.
  • A request-count before/after (even a local-devtools capture) would turn the traffic-reduction claim from promise into evidence.
  • The sandboxed verification run is in flight and will post its report separately; if it surfaces anything on the coalescing/slot claims, that should be read before merge.
中文说明

置信度:4/5 —— 首轮两个阻塞项均以可追溯的原因修复,新鲜度 P2 按建议以带测试的最小方案关闭,所有在范围 CI 车道转绿,维护者已深入审查并批准当前 head。其余备注均不阻塞。

退一步看:我对规模的怀疑仍在案——按查询参数做在途去重依然是我会选择的更简设计,PR 也仍缺少请求数 before/after 实测,流量下降的标题目前由测试语义而非数字支撑。但门禁同样要承认问题已被回答:首轮每一个红色检查都因具体修复而转绿(弹窗测试迁移到 hook 边界、catalog 源模块的 vite bypass),不是重跑碰运气;review 中找到的唯一语义漏洞——30s 零订阅保留期内弹窗可能展示数分钟前的列表——以最小且带测试的 maxAgeMs 选项关闭,而不是削弱保留设计;我只能静态核对的不变量(路由归属、owner 作用域失效、轮询节奏、有界清理)已由维护者独立验证并在该 head 上批准。若六个月后由我维护:机制有设计文档、2,500+ 行测试、新鲜度语义现已显式化——只会在接手时嫌一次面积大,之后没问题。

予以批准,锚定在被审查的提交。非阻塞后续:

  • 姊妹 PR perf(cli): Cache persisted session catalogs #8892 将同样的 catalog 思路用于 CLI——请成对评估;两者之间若有共享设计文档会更利于后续读者。
  • 一次请求数 before/after(哪怕是本地 devtools 抓包)能把流量下降从承诺变成证据。
  • 沙箱验证正在运行,报告会单独发布;若在合并/槽位声明上发现问题,应在合并前阅读。

Qwen Code · qwen3.8-max

Reviewed at 657b8ab56f22399cc051b45de55b133405771b16 · re-run with @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.

Requesting changes — the PR's own CI is red on the package it touches, and the failures are PR-caused (same checks green on #8892/#8893/#8882):

  1. Test (ubuntu-latest): web-shell suite fails 12 tests — the dialog tests (DeleteSessionDialog, ReleaseSessionDialog, ResumeDialog) still mock listWorkspaceSessions while the rewired useScopedSessions now calls workspaceByCwd(cwd).listWorkspaceSessionsPage, so the dialogs render zero sessions. Those three test files need updating as part of this migration.
  2. web-shell E2E Smoke (ubuntu-latest): 29 of 32 tests fail (repeated ECONNREFUSED 127.0.0.1:4170 while the browser requests the new session-catalog/* modules) — needs investigation before this can move.

The architecture itself is sound (route ownership and owner-scoped invalidation both verified); full notes in my review comments above. Happy to re-review once CI is green. 🙏

Qwen Code · qwen3.8-max

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

Copy link
Copy Markdown
Collaborator Author

Addressed both CI blockers from the review in commit 53ba1505ba:

  • Dialog tests now mock the migrated useScopedSessions boundary; the full Web Shell unit suite passes (3,125 tests across 179 files).
  • Vite now bypasses daemon proxying for /session-catalog/* source modules; the focused proxy test and a Playwright Web Shell smoke test pass locally.

I also updated the PR body with the current verification evidence and the deliberate last-successful-page error semantics. The new CI run is in progress.

已在提交 53ba1505ba 中处理评审指出的两个 CI 阻塞项:弹窗测试已适配迁移后的 useScopedSessions 边界,Web Shell 全量 3,125 项单测通过;Vite 已避免将 /session-catalog/* 源模块转发到 daemon,本地代理回归测试和 Playwright smoke 均通过。PR 正文也已同步最新验证证据和保留最后成功页面的错误语义;新一轮 CI 正在运行。

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

🖼️ web-shell visual preview

Rendered against a mock daemon (no real backend): the PR base vs this PR head 657b8ab. Only screenshots that changed are shown (flows below, if any, are head-only) — refreshes on every push.

Screenshots · before / after

ℹ️ No screenshot changed against the PR base — but this PR edits 10 render-shaping files:

  • packages/web-shell/client/App.tsx
  • packages/web-shell/client/components/ChatPane.tsx
  • packages/web-shell/client/components/SessionOverviewPanel.tsx
  • packages/web-shell/client/components/SplitView.tsx
  • packages/web-shell/client/components/artifacts/SideTaskPanel.tsx
  • packages/web-shell/client/components/dialogs/DeleteSessionDialog.tsx
  • packages/web-shell/client/components/dialogs/ReleaseSessionDialog.tsx
  • packages/web-shell/client/components/dialogs/ResumeDialog.tsx
  • …and 2 more.

Either the change has no visual effect (logic, plumbing, a state the scenarios never reach), or no scenario renders this UI — in which case the preview cannot see it, and an empty result is a coverage gap rather than a clean bill of health. To make it visible, add a scenario to packages/web-shell/client/e2e/visuals/screenshots.spec.ts that seeds whatever state the UI is gated on; it then appears here as a head-only (NEW) capture.

Full-resolution recordings (.webm) are attached to the workflow run.

Qwen Code · web-shell visuals

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

[codex] Thanks — won’t take this one. This PR intentionally changes session-list scheduling and catalog state without changing rendered UI; the unchanged screenshots are expected, while the affected non-visual paths are covered by catalog/component tests and the Web Shell smoke test.

yiliang114
yiliang114 previously approved these changes Aug 11, 2026

@yiliang114 yiliang114 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 on the substance. The catalog is verified sound: the query key covers every wire-affecting option (differing pageSize/cursor correctly do not share), route ownership is exactly preserved (legacy GET /workspace//sessions vs the workspace handle's paged list), mutation/lifecycle invalidations are owner-workspace-scoped with double-session guards, polling cadence per surface is numerically unchanged, the 2-total/1-background scheduler protects explicit work, hidden-pause drops queued background jobs without amplification on rapid tab switches, and teardown on client disconnect is bounded (WeakMap keying, timers self-extinguish). Both prior blockers are fixed on head (dialog tests mock useScopedSessions at the hook boundary — web-shell suite 3125/3125; vite proxy bypass extended to /session-catalog/* — E2E smoke green).

One P2 to fix or explicitly accept: dialogs (Delete/Resume/ReleaseSessionDialog) subscribe with autoLoad but no pageSize/pollIntervalMs, so their query key matches no polling surface and nothing refreshes a retained entry except local-mutation invalidation; combined with the 30s zero-subscriber retention, repeated open/close cycles under 30s apart never refetch — the dialog can show a minutes-old list (missing sessions created via CLI/another tab, or offering sessions deleted elsewhere), a freshness regression vs the old fetch-on-open. Suggested fix: treat a retained page as stale for a NEW autoLoad subscriber when updatedAt exceeds a small max-age, or have the dialogs call reload() on open.

CI note (process, not code): the ubuntu leg is red on 8 CLI-package tests this PR does not touch (update/serve/workspace-registration-store) with flake signatures (15s timeouts, ENOTEMPTY rmdir races, severe runner overload — a clipboard suite took ~170s, collect 9507s) and the identical server-default-bridge-wiring file failed with identical signatures in an unrelated sibling PR in the same window; the PR's own surface is green. Needs a green re-run, not a code change. P3s: cross-workspace initial loads are now serialized by design (1 background slot) — acknowledge in the doc; dispose() is tests-only, acceptable. Ship it after the staleness decision and the re-run.

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

Copy link
Copy Markdown
Collaborator Author

Resolved the merge conflict and pushed the updated branch in 657b8ab56f22399cc051b45de55b133405771b16.

Item Decision Result
Conflict with current main Fixed Merged main without rebasing/force-pushing. The App.test.tsx resolution preserves both this PR's side-task/session-catalog mocks and main's subagent-session mock.
Reopening Delete/Resume/Release dialogs could reuse a retained no-poll catalog page Fixed Added subscription maxAgeMs support and set these dialogs to refresh retained catalog data once it is at least 1 second old, while retaining request coalescing, visibility handling, and backoff behavior.
Cross-workspace initial loads share the single background slot Not changed This is the intended client-level scheduler invariant that reserves the second slot for interactive work; changing it would expand scheduler semantics beyond this conflict/review fix.

Verification:

  • Web Shell focused catalog/hooks/dialog tests: 50 passed.
  • App.test.tsx plus dialog tests: 381 passed.
  • Web Shell typecheck and build: passed.
  • Web Shell full coverage suite after rebuilding artifacts: 179 files / 3129 tests passed.
  • Targeted ESLint, Prettier, and git diff --check: passed.
  • The package-wide Prettier check still reports five formatting issues inherited from current main; none are in this PR's diff.

中文说明:冲突已通过合并当前 main 解决,未 rebase 或 force-push;同时采纳并修复了弹窗重复打开时可能复用旧目录数据的问题。跨工作区后台加载继续遵守“后台最多占一个槽”的既定调度约束。当前没有 unresolved review thread。

@doudouOUC
doudouOUC requested a review from yiliang114 August 11, 2026 07:39

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

Re-approving at head 657b8ab: the only PR-owned delta since my approval is the P2 fix, implemented exactly as suggested — a maxAgeMs subscription option plumbed end-to-end with all three dialogs passing { autoLoad: true, maxAgeMs: 1000 }, triggering a non-blocking background refresh of retained pages older than 1s on subscribe, with tests covering no-refetch within maxAge, refetch after 1s, and snapshot update. maxAgeMs is correctly kept out of the query key (staleness policy, not wire-affecting), the trigger reuses requestBackground with its existing dedup/priority guards so no re-subscribe storm, and the rest of the new commit is a clean main merge (first-parent diff purely additive, no PR-owned lines lost). CI green on every in-scope lane; the previously flaky CLI integration lane is now merge-queue-only per main's #8792 change, so it no longer gates this PR. Nothing blocks merge.

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ❌ not passed — findings reported (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 49 passed · 1 failed · 50 total

中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:49 通过 · 1 失败 · 50 总计

Verification report

PR 8891 deep verification — feat(web-shell): Share session catalog scheduling

Verdict: findings — scripted assertions: 49 pass / 1 fail / 50 total. Verified head: 657b8ab56f22399cc051b45de55b133405771b16 (git rev-parse HEAD^2), A/B base: 1a2c5026b2a5e3f37a6e0fec845c179b32568691 (HEAD^1, merge-ref checkout). Every behavioral cell passed; the single fail is a mutation-survivor finding (test-coverage gap on a load-bearing guard), not a behavioral defect — see Finding F1.

中文摘要
  • 结论: findings(49 通过 / 1 失败 / 共 50 项脚本化断言)。所有行为学单元全部通过;唯一的失败项是变异矩阵中的一个测试覆盖缺口(F1,Suggestion 级),不是行为缺陷。
  • A/B 结论: 中心主张成立。3 个消费者订阅同一 workspace 查询、轮询 6 个周期:HEAD 仅发出 6 条 wire 请求(共享一条请求流),BASE 模式(各消费者独立轮询)发出 18 条,恰好 3.0×;在途请求合并、并发上限(总 2 / 后台 1)、隐藏页暂停与恢复后"恰好一次"合并补刷、owner-workspace 作用域 patch、过期响应丢弃、30s 重试下限、30s 零订阅保留、legacy/qualified 路由归属,均经真实 DaemonClient + loopback HTTP 假 daemon 验证(见下表各 harness)。
  • Findings: F1 —— patchSession 的跨 workspace 保护由两层冗余守卫承担,但现有测试夹具用不同 sessionId 构造两个 workspace,任何一层被单独删除测试都不变红;已提供可固定该性质的最小夹具(HEAD 下绿、双层同时删除后按预期红)。另有两处非阻塞观察项(见 Observations)。
  • 未覆盖范围: 逐 commit 归因(depth-2 浅克隆仅可达 1/4 个 commit,验证的是聚合 diff);React 组件级渲染 A/B(以 store+SDK 层替代);Playwright e2e smoke;Windows/Linux 手测;仓库级 gate(仅跑 web-shell workspace)。

Scope

  • Central claim: identical session-list queries share one cached/in-flight request stream instead of multiplying by mounted consumer count.
  • Secondary claims: (1) client-wide scheduler caps list concurrency at 2 total / 1 background, reserving a slot for interactive work; (2) visibility pause/resume, transient-error page retention with a 30 s retry floor, 30 s zero-subscriber retention, owner-scoped mutation reconciliation — all without changing route ownership or public APIs.
  • Out of scope (declared up front): cross-tab sharing, push-based events, pagination policy, group/status/Git polling, daemon/SDK changes (the diff touches none — verified).

Central claim — A/B load-bearing proof

Real DaemonClient (from @qwen-code/sdk, unchanged by this PR) over a loopback fake daemon that records every request; HEAD arm drives the real SessionCatalogStore; BASE arm reproduces the pre-PR consumer pattern extracted from HEAD^1 sources (each surface ran its own fetch loop: WorkspaceSection 10 s setInterval, SessionOverviewPanel LIST_POLL_MS timer, SplitView token-driven reloads — no shared layer existed). Witness: 01-ab-dedup-head-6-reqs-vs-base-18.png.

cell environment oracle result
A — HEAD, 3 subscribers, same legacy query, 300 ms poll × 1800 ms store + real SDK + HTTP server-side count of /workspace/<cwd>/sessions 6 requests (≈1 per tick)
B — BASE pattern, 3 independent loops, same window real SDK + HTTP same 18 requests (3× ticks)
C — HEAD, initial load in flight + 3 concurrent loadOnce 250 ms server latency wire calls; page identity 1 wire call, 3 waiters, identical page object
D — BASE, 3 concurrent direct calls 250 ms server latency wire calls 3 wire calls
E — HEAD, qualified query for /sec ondary/w2 URL capture exact path /workspaces/%2Fsec%20ondary%2Fw2/sessions, never the legacy/primary route

Ratio B/A = 3.00×, exactly the consumer count. Route ownership held on both arms: legacy queries hit /workspace/<cwd>/sessions, qualified queries hit /workspaces/<cwd>/sessions with the requested (secondary) cwd encoded — the store performs no fallback rewriting (the deeper "untrusted runtime never falls back to primary" property lives in the daemon-side selector, which this diff does not touch). Harness: harness-01-ab-dedup.ts, log logs/01-ab-dedup.log (10/10 assertions).

Secondary claims — wire-oracle cells

All cells drive the real store + real SDK against the recording fake daemon.

area harness (log) key measured results
Concurrency budget harness-02-concurrency.ts (logs/02-concurrency.log, 9/9) 5 simultaneous background loads serialize: watermark 1; bg poll + interactive refresh overlap: watermark 2, cap never exceeded; interactive fresh read completed in 124 ms while five bg loads queued (full serialization would be ≥600 ms); BASE fan-out watermark 5+ (unbounded). Witness 02-concurrency-caps-bg1-total2.png
Visibility harness-03-visibility.ts (logs/03-visibility.log, 6/6) visible baseline polls normally; zero automatic requests across 700 ms hidden (≥3 due ticks) incl. two hidden background invalidations; on restore exactly one coalesced catch-up request, then rhythm resumes; unsubscribe→immediate remount serves the cached page with no fetch (StrictMode-retention path). Witness 03-visibility-pause-and-one-coalesced-resume.png
Mutation & reconciliation harness-04-mutation.ts (logs/04-mutation.log, 10/10) rename patch applied in owning workspace /wA while an equal session id in /wB stayed untouched; stale in-flight payload (rev 1) never surfaced after invalidation — observed snapshot sequence [, , v2], converged on v2; trailing workspace refresh fired exactly once after its delay; injected 500 retained the last successful page with the error attached; automatic retries honored the 30 s floor; explicit refresh() bypassed backoff and cleared the error. Witness 04-mutation-owner-scope-stale-reject-retry-floor.png
Policy constants same SESSION_CATALOG_ERROR_RETRY_MS=30000, SESSION_CATALOG_TRAILING_REFRESH_MS=2000 as documented

Vacuity / mutation matrix on the new tests

Baseline control green (34/34, both new test files). Each mutant is a single-point change to session-catalog-store.ts, run via run-mutants.mjs (restores byte-identical after each round; tree verified clean). Witness 05-mutant-matrix-6-killed-1-survivor.png. Positive control M0 landed first: deleting routeKind from the query key turned exactly one test red, proving the harness can make the suite fail.

mutant guard under test result pinned by
M0 drop routeKind from key (positive control) KILLED (1 red) keys every wire-affecting option and route kind
M1 disable in-flight reuse in loadOnce request sharing KILLED (1 red) shares an in-flight request between non-fresh command loads
M2a remove total cap (<2) scheduler KILLED (1 red) caps total concurrency at two…
M2b remove background cap (<1) scheduler KILLED (5 red) reserves one request slot… + visibility/queue tests
M3 accept stale in-flight response revision guard KILLED (6 red) reconciliation + hidden-invalidation tests
M4 remove session-level workspaceCwd clause in patchSession owner scoping SURVIVED (34 green) — see F1
M5 retention 30 s → 0 entry retention KILLED (1 red) retains an unused snapshot for thirty seconds…

Targeted gates: full Web Shell unit suite 3164/3164 passed, exit 0 at the merge head (witness 06-full-web-shell-suite-3164-green.png, log logs/05-full-suite.log, JUnit archived) — the PR body cites 3,125 tests / 179 files; the higher count here is the merged-base tip contributing additional tests (per-commit attribution unavailable at depth 2, see Not covered). Web Shell typecheck clean, proven live (a planted type error was reported; removed; clean again). Vitest liveness proven by the matrix itself (six mutants went red).

Findings

F1 — Suggestion: cross-workspace patchSession scoping is doubly guarded but pinned by no test

Severity: coverage gap (behavior verified correct). patchSession filters at two layers — the entry loop skips entries whose query.workspaceCwd differs, and the session map additionally skips sessions whose own workspaceCwd differs. Removing either layer alone survives the whole shipped suite (mutants M4 and M4b both left 34/34 green), because the one test named for this property, patches only the owning workspace without changing page metadata, builds its two workspaces with distinct session ids ('/a' vs '/b'): the cross-workspace assertion then holds by session-id inequality alone, and the name buys coverage nothing paid for. Only removing both layers (M4c) breaks the property.

The property itself is load-bearing and correct at HEAD: the wire-oracle harness drove two real catalog entries whose pages contain the same session id under /wA and /wB; the rename patch stayed in /wA (harness-04-mutation.ts). Measured candidate fixture (applied as a scratch probe test, then deleted): two entries sharing session id dup-id, patch one, assert the other keeps its name — green on HEAD, and under M4c it fails the intended assertion exactly (expected 'RENAMED' to be '/b-original'). The shipped suite stays green with and without the fixture's axis under single-layer mutations, so the fixture should ship together with the acceptance of either guard layout. Reproduce:

cd packages/web-shell && npx vitest run session-catalog   # 34/34 green at HEAD and under M4/M4b

(survivor classification: coverage gap, not dead code — the guard demonstrably decides production behavior when both layers are considered, and sessions with foreign/absent workspaceCwd are exactly the shapes fetchPage normalizes for.)

Observations (non-blocking)

  • WorkspaceSection fires one interactive refresh() when a section transitions to active with a fresh cached page — deliberate-looking freshness choice, bounded to one request per activation transition; it qualifies the "one request stream" picture only outside steady-state polling (which is what the A/B measures).
  • Every list call carrying sourceType preflights an uncached GET /capabilities (SDK behavior, pre-existing, equal on both arms). Real daemon traffic is therefore ~2× the list-request counts above; the dedup ratio is unaffected since the catalog dedups the list leg that all consumers share.

Not covered

  • Per-commit attribution: metadata lists 4 commits; the depth-2 merge-ref checkout reaches only HEAD^2 (git rev-list HEAD^1..HEAD^2 = 1, shallow boundary). Verified the aggregate HEAD^1..HEAD diff.
  • Component-rendering A/B: the A/B drives the real store through the real SDK over HTTP rather than rendering React component trees on both arms; consumer wiring (sidebar/overview/split/dialogs/hooks) is exercised by the full unit suite (3164 green), which includes the PR's component-level tests.
  • Playwright e2e smoke claimed in the PR body — not run (no browser in this lane's budget); the dev-proxy half of that claim is covered by vite-config.test.ts (in-suite, green), and I verified no daemon route owns /session-catalog/ so the new proxy bypass cannot shadow an API path.
  • Windows / Linux manual passes (author marked ⚠️), cross-tab behavior, push events, pagination policy — out of PR scope, untouched.
  • Repo-wide gates: only the web-shell workspace suite/typecheck ran; the diff touches no other workspace's production code (docs aside).
  • Base arm models the pre-PR consumers as N independent SDK poll loops, extracted from HEAD^1 sources (archived in base-files/); base components were not rendered.
  • The 30 s retention window and 30 s retry floor were verified via the suite's fake timers plus short-window live probes, not by waiting out full 30 s intervals live.

Methodology

Environment: CI verify container (node:22-bookworm), working tree at refs/pull/8891/merge (depth 2); npm ci + npm run build pre-run. Harnesses (harness-0*.ts, fake-daemon.ts, run-mutants.mjs, this dir) ran under tsx against compiled-free TS source with a real DaemonClient talking real loopback HTTP to a recording fake daemon (request log, in-flight concurrency watermarks, delay/fault injection); the store's visibility logic was driven through a stubbed document exposing only hidden + visibilitychange, the two members it uses. Control purity: the PR leaves package.json/lockfile untouched and modifies no workspace other than web-shell; internal links resolve into the head tree (readlink -f node_modules/@qwen-code/sdkpackages/sdk-typescript, likewise webui, core), so both arms loaded identical SDK code. Mutation rounds edited the live store file and restored it byte-identically each time (RESTORED=true; git status clean at the end). Raw logs in logs/, JUnit in logs/web-shell-junit.xml, evidence PNGs in evidence/ produced by scripts/verify-capture.mjs.

Evidence images

01-ab-dedup-head-6-reqs-vs-base-18

02-concurrency-caps-bg1-total2

03-visibility-pause-and-one-coalesced-resume

04-mutation-owner-scope-stale-reject-retry-floor

05-mutant-matrix-6-killed-1-survivor

06-full-web-shell-suite-3164-green

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@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. ✅ Both first-pass CI blockers fixed with traceable causes, the staleness P2 closed as suggested, and CI green on every in-scope lane.

@doudouOUC
doudouOUC added this pull request to the merge queue Aug 11, 2026
Merged via the queue into QwenLM:main with commit 096bb0e Aug 11, 2026
54 of 55 checks passed
@doudouOUC
doudouOUC deleted the agent/web-shell-session-catalog branch August 11, 2026 09:13
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.21.10.

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.

3 participants