feat(web-shell): expose channel sessions in sidebar and settings - #8457
Conversation
E2E Test ReportEnvironment: macOS, local Web Shell development server, Playwright Chromium, mock daemon. Command: Verified behaviors:
Result: 2 passed. Not covered: live DingTalk, Feishu, or WeCom provider round trips. |
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head Screenshots · before / afterFull-resolution recordings (.webm) are attached to the workflow run. — Qwen Code · web-shell visuals |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
E2E Test Report — Channel session scopeEnvironment: macOS, local Web Shell development server, Playwright Chromium, mock daemon. Command: Verified behaviors:
Result: 2 passed. Not covered: live DingTalk, Feishu, WeCom, GitHub, or GitLab provider round trips. |
🩺 serve daemon A/BBuilt the PR base vs this PR head ✅ No response changes against the PR base across 4 scenario(s). — Qwen Code · serve A/B |
Follow-up E2E report: Channel refresh and transcript projection
补充 E2E 报告:频道刷新与对话显示文本
|
|
Re-run on Template looks good ✓ What changed since the last pass: exactly one commit — a merge of Problem: unchanged and still real — the sidebar hard-coded Direction: aligned, unchanged. The PR exposes capabilities the daemon and channel runtime already have rather than adding a new subsystem, and closes the display leak the new visibility would have introduced. Web Shell channel management is an active roadmap area here. Size: at this head the diff is ~1,285 production lines vs ~3,576 test lines and ~108 lines of design docs across 66 files — slightly smaller than the last pass now that Goal v3 and the admission hardening live on the main side of the diff. The author holds write access (same-repo branch), so the two-tier core gate's maintainer exemption applies and the 1,000+ production-line count remains an advisory, not an escalation. My honest answer on splitting is unchanged: mostly no — the growth is tests and review-mandated hardening of one feature. The disclosed coordination overlap with #8848 stands (whichever lands second drops its copy). Approach: unchanged — scope matches the problem. Both controls are capability-gated (older daemons keep today's behavior), every sidebar query path takes the selected source, and the display projection extends the recording service's existing pattern instead of inventing a parallel one. Nothing in the merge alters this. Risk: Moving on to code review. 🔍 中文说明由 模板完整 ✓ **上次评估以来的变化:**仅一个提交——合并 问题:不变且真实存在——daemon 声明 方向:对齐,不变。PR 暴露 daemon 与 channel 运行时已有能力而非新增子系统,并修补新可见性本身会引入的展示泄漏。Web Shell channel 管理是本仓库正在推进的方向。 规模:本 head 下 diff 约为 1,285 行生产代码、3,576 行测试、108 行设计文档,共 66 个文件——由于 Goal v3 与准入加固已位于 main 一侧,比上次略小。作者有 write 权限(同仓分支),适用核心模块两级门禁的 maintainer 豁免,1,000+ 生产行数仍为提示而非升级。关于拆分的诚实答案不变:基本不能——增量是测试与评审要求的加固。与 #8848 已披露的协调重叠仍然有效(后合入的一方删除重复部分)。 方案:不变——范围与问题匹配。两个控件均按能力开关,侧边栏所有查询路径都带上所选来源,display 投影复用录制服务已有模式。合并不改变这一点。 风险: 进入代码审查。🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code review (re-run at head
|
| File | What changed |
|---|---|
packages/channels/base/src/ChannelBase.ts |
Display projection computed and sanitized at the inbound boundary, threaded through buffering and coalescing |
packages/channels/base/src/sanitize.ts |
New sanitizeDisplayText for human-facing sinks |
packages/channels/base/src/DaemonChannelBridge.ts |
Carries displayText and worker authorization in prompt meta, deleteSessionData hook |
packages/channels/base/src/AcpBridge.ts |
Private-parent capability handshake, displayText forwarding for standalone bridges |
packages/channels/base/src/ChannelWebhookTask.ts |
Webhook display projection mirroring the model-prompt caps |
packages/channels/github/src/GithubAdapter.ts |
Sanitized displayText for review/issue/comment-aggregate envelopes |
packages/channels/qqbot/src/QQChannel.ts |
Display text keeps non-bot mentions, slash detection restored |
packages/acp-bridge/src/bridge.ts |
Single-source display gate, projected echo and pending-entry text, re-arm strip |
packages/cli/src/serve/routes/session.ts |
Strips display and auth meta, forwards only workspace-authorized display text |
packages/cli/src/serve/channel-worker-prompt-authorization.ts |
Process-local worker-token registry |
packages/cli/src/serve/channel-worker-supervisor.ts |
Registers and revokes the worker token across the spawn lifecycle |
packages/cli/src/acp-integration/acpAgent.ts |
Honors display meta only for trusted parents |
packages/cli/src/acp-integration/session/Session.ts |
Records displayText alongside the model prompt (the one merge conflict, resolved on top of Goal v3) |
packages/cli/src/commands/channel/channel-registry.ts |
sessionScope descriptor injection and validation |
packages/cli/src/commands/channel/daemon-worker.ts |
Wires promptAuthorization and permanent session deletion |
packages/cli/src/serve/channel-settings-store.ts |
Accepts chat_thread scope |
packages/cli/src/ui/commands/renameCommand.ts |
Auto-title uses projection, filters rename and tag commands |
packages/core/src/services/chatRecordingService.ts |
Tracks bounded per-turn display texts, trims on rewind |
packages/core/src/services/sessionService.ts |
Session-list previews prefer displayText, code-point truncation |
packages/core/src/services/sessionTitle.ts |
Forward projection mode for title generation |
packages/web-shell/client/components/sidebar/WebShellSidebar.tsx |
Tasks/Channels switch, per-source state, grouping render, poll cadence |
packages/web-shell/client/components/sidebar/WorkspaceSection.tsx |
Per-workspace channel grouping and catalog polling |
packages/web-shell/client/components/sidebar/channelSessionGroups.ts |
Platform grouping helper with fallback ordering |
packages/web-shell/client/components/channels/ChannelEditorDialog.tsx |
Session scope section with i18n and plugin defaults |
…and 42 more files (tests, wire-key pins, design docs, i18n, e2e spec and fixtures)
Testing
Unattended CI run — I do not build or execute PR code here; the evidence below is the PR's own CI at the reviewed head, fetched through the API, plus the static review above.
| Check | Conclusion |
|---|---|
| Test (ubuntu-latest, Node 22.x) | ✅ success |
| web-shell E2E Smoke (ubuntu-latest, Node 22.x) | ✅ success |
| Serve A/B (ubuntu-latest, Node 22.x) | ✅ success |
| Desktop Shell (ubuntu-22.04) | ✅ success |
| Desktop Shell (windows-2022) | ✅ success |
| Capture web-shell visuals (ubuntu-latest, Node 22.x) | ✅ success |
| Post Coverage Comment (ubuntu-latest, 22.x) | ✅ success |
| SDK Java / Real daemon E2E (Java 11–21) | ✅ success |
| Integration Tests (CLI, No Sandbox) | ⏭️ skipped (merge-queue-only by design) |
| Test (macos/windows-latest, Node 22.x) | ⏭️ skipped (merge-queue-only by design) |
| review-pr | 🔄 in progress (bot orchestration, not a PR gate) |
All four pull_request-event workflow runs at this head completed green — the merge of Goal v3 and the admission hardening did not break anything the PR depends on. The three skipped legs are repo policy, not a gap in this PR: ci.yml runs the macOS/Windows unit legs and the CLI integration leg only in the merge queue ("ubuntu is the fast PR signal"), so they report skipped on every PR event now. (Earlier runs in this PR's history predate that policy, which is why prior tables showed them executing.)
Independent of CI: maintainer wenshao ran a three-arm local verification on an earlier head (PR head / merge-base "before" / capability-removed) with real qwen serve daemons, real channel workers against fake GitHub/GitLab APIs, a recording model server, and Playwright — all eight Reviewer Test Plan steps passed — and has since approved the current head e961edd. Attributed as the maintainer's own run; the only delta since is the main merge reviewed above. Not verified by anyone: live DingTalk/Feishu/WeCom provider round trips (no credentials), as the PR itself discloses.
Sandboxed verification would settle what CI cannot: @qwen-code /verify — the display-projection and worker-authorization boundary is pinned by unit tests, but the browser-level channels flow is not exercised by PR CI because the new spec is not @smoke-tagged; an A/B run would prove the wire behavior load-bearing on CI infrastructure, and tagging the spec (or widening the smoke job) is the repo-side fix for the browser leg.
中文说明
代码审查(re-run,head e961edd)
本轮覆盖上次审查以来的增量:一个提交,即合并 origin/main(ACP 会话采纳 Goal v3 #8732、prompt 准入所有权加固 #8955,以及 desktop/web-shell 工作)。该合并只有一处冲突——Session.ts——即 PR 的 display-text 记录与 Goal v3 重构后的 prompt 记录路径交汇处。我对照两个父提交审查了解决方案,而不是只看合并结果。
冲突解决——核实正确:
- 合并后的
Session.ts是 main 的 Goal v3 版本加一个最小补丁:以typeof === 'string'守卫从promptMetadata提取promptDisplayText,然后作为载荷元数据记录——recordUserMessage(promptText, goalTurn?.permit, { displayText, hookContext: '' })——优先级为 displayText 分支 → goalTurn 分支 → 普通分支,存在时透传 Goal permit。调用与 Goal v3 前置工作落在录制服务中的既有recordUserMessage(message, goalContext?, promptPayload?)签名完全匹配;解决方案没有丢弃或替换模型提示词(promptText仍是被记录内容,displayText仅为展示元数据)。 - 本 head 新增的 Session 测试恰好固定了这种分离:对
'internal channel instructions\n\nhello'提示词携带_meta: { 'qwen.daemon.promptDisplayText': 'hello' }时,完整文本被记录为消息,{ displayText: 'hello', hookContext: '' }被记录为载荷。 - 早前各轮建立的信任链与 fix(web-shell): Harden prompt admission ownership #8955 的加固干净地组合:serve 路由剥离转发
_meta中的授权键与 display 键,仅当 worker 令牌已在该工作区注册时重新注入 display 文本;bridge 剥离调用方元数据并像既有channelDelivery/modelPrompt模式一样重新装填受信值;acpAgent仅在privateParentState === 'trusted'时接受 display 元数据——正是 fix(web-shell): Harden prompt admission ownership #8955 加固的准入所有权状态。合并没有削弱任何一层。
上次审查的其余结论在本 head 依然成立——我复查了关键承重点而不是照抄上一轮记录:ChannelBase.processInbound 的 sanitizeDisplayText 边界(8,000 码点上限、bidi/零宽/C0 中和)、QQ 斜杠检测基于剥离全部 mention 的输入、回显遍历上限替换、预览/恢复/标题各展示面读取记录的投影、按来源的侧边栏锁与未读跟踪、sessionScope 目录/编辑器路径。其余重叠面(MessageEmitter、acpAgent、web-shell App.tsx、DaemonSessionProvider、transcript-replay)为自动合并;本 head 下 PR 对它们的 diff 与此前审查的逻辑完全一致。
**未发现:**合并解决引入的正确性或安全回归,或超出已披露 #8848 重叠之外的范围蔓延。
长期、非阻塞备注(与此前各轮相同,均不影响主路径):若干防护的测试效力缺口、channel-worker 授权 wire key 以两份字符串字面量重复而非固定为跨包常量、部分适配器 display 投影仍用 sanitizePromptText 而 sanitizeDisplayText 语义更合适、channels e2e 用例仍未标记 @smoke 因此 PR CI 浏览器环节跳过它(定时全量 e2e 会执行)。
测试
无人值守 CI 运行——此处不构建、不执行 PR 代码;以下证据是通过 API 获取的本 head 下 PR 自身 CI 结果,加上上面的静态审查。
本 head 下所有四个 pull_request 事件的工作流运行全部绿——Goal v3 与准入加固的合并没有破坏 PR 依赖的任何东西。三个跳过的环节是仓库策略而非本 PR 的缺口:ci.yml 现在只在合并队列中运行 macOS/Windows 单测环节与 CLI 集成环节("ubuntu 是 PR 快速信号"),因此每个 PR 事件下它们报告 skipped。(本 PR 历史上更早的运行早于该策略,所以此前的表格里它们还在执行。)
CI 之外:维护者 wenshao 曾在较早 head 上完成三臂本地验证(PR head / merge-base「变更前」/ 删除门控能力),使用真实 qwen serve daemon、指向伪 GitHub/GitLab API 的真实 channel worker、录制式模型服务器与 Playwright——Reviewer 测试计划全部 8 步通过——此后已批准当前 head e961edd。归属为维护者本人的运行;其后唯一增量是上面审查的 main 合并。任何人都未验证:真实钉钉/飞书/企微提供方往返(无凭据),PR 本身也已披露。
沙箱验证可以补足 CI 无法覆盖的部分:@qwen-code /verify——display 投影与 worker 授权边界已有单测固定,但浏览器级 channels 流程因新用例未标记 @smoke 而不在 PR CI 中执行;A/B 运行可在 CI 基础设施上证明 wire 行为确实 load-bearing,浏览器环节的仓库侧修复是给用例加 @smoke 标记(或放宽 smoke job 范围)。
— Qwen Code · qwen3.8-max
Reviewed at e961edd309538ca00030817b82fa306fe2925bab · re-run with @qwen-code /triage
|
Confidence: 4/5 — the only thing that happened since the last clean pass is a main merge, and its single conflict (the one file I would have worried about) resolves correctly on top of Goal v3 with a test pinning the separation; CI is green at the new head and a maintainer already approved it. The remaining residue is the same suggestion-level follow-ups as before, not doubt about the code. Stepping back: this re-run was narrow by design — one merge commit since the reviewed head, no new author feature code. The merge pulled in Goal v3 (#8732) and the prompt-admission hardening (#8955), both touching the same surface as this PR, and produced exactly one conflict, in What keeps this at 4 rather than 5 — all non-blocking, unchanged from the prior pass:
Verdict: approve. All four PR CI workflow runs at the reviewed head completed green (no pending legs — the skipped macOS/Windows/integration jobs are merge-queue-only by repo policy), maintainer @wenshao has already approved this exact head, and the prior bot approval was dismissed by the merge push, so a fresh approval pinned to the reviewed commit follows this comment. 中文说明信心:4/5 —— 自上次干净评估以来唯一发生的事是一次 main 合并,而它仅有的冲突(恰是我会担心的那个文件)在 Goal v3 之上解决正确,并有测试固定分离行为;新 head 下 CI 全绿,维护者已批准。剩余问题与此前相同,都是 Suggestion 级跟进项,而不是对代码的疑虑。 整体来看:本次 re-run 范围本就收敛——被审查 head 之后只有一个合并提交,没有作者的新 feature 代码。合并带入了 Goal v3(#8732)与 prompt 准入加固(#8955),两者都触及与本 PR 相同的面,并产生了唯一一处冲突,位于 扣 1 分的原因——均非阻塞,与上次相同:
结论:批准。本 head 下所有四个 PR CI 工作流运行全部绿(无待决环节——跳过的 macOS/Windows/集成任务按仓库策略仅在合并队列运行),维护者 @wenshao 已批准同一 head,而此前的 bot 批准已被合并推送 dismiss,因此本条评论之后将提交固定到被审查提交的新批准。 — Qwen Code · qwen3.8-max Reviewed at |
E2E test report: Channel platform groupingVerified locally on macOS with the Web Shell mock daemon and Chromium.
Validation: 75/75 targeted sidebar tests, 303/303 Web Shell app regression tests, 2/2 Chromium Channel E2E tests, full repository build and typecheck, targeted ESLint, and |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
中文说明
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
— qwen3.8-max via Qwen Code /review (v0.21.3)
…l-sessions # Conflicts: # packages/acp-bridge/src/bridge.test.ts
|
🤖 Addressed the latest review feedback (round 7/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 7/100 轮)。改动内容与我反驳保留之处如下: Autofix review round summary — PR #8457Feedback triageCritical-only mode is active (after five change-producing rounds). This round's actionable sections were empty: no reviews, no inline comments, no issue-level comments, no failed checks, and no still-red checks. The There was nothing to decline, defer, or escalate: no findings of any kind arrived this round. Base-conflict resolution (
|
|
🤖 Addressed the latest review feedback (round 8/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 8/100 轮)。改动内容与我反驳保留之处如下: Thank you for the exceptionally thorough end-to-end verification — three independent daemon stacks, real channel workers, a merge-base "before" arm, and a capability-removed arm. Glad all eight test-plan steps reproduce and the display-projection trust boundary holds against forgery. Feedback dispositionsVerification report (all 8 Reviewer Test Plan steps pass; authorization boundary is load-bearing) — no change needed. Treated as confirmation that the shipped behavior matches the description. Nothing to act on. Observation 2 — Observation 1 — Channels tab costs three requests per tick (
So the per-tick Also noted, and deliberately left alone as pre-existing (not introduced here): a legacy channel config that predates the access-policy field cannot be saved from the editor without also choosing an access policy. That predates this PR; it only surfaces now because editing a legacy config is the normal way to set session scope. Worth its own small fix. Conflict notes
VerificationAll commands actually run on this tree (head
Not applicable: integration tests (change is a pure sidebar grouping function, not exercised through the bundled CLI) and 中文说明非常感谢这次极其彻底的端到端验证——三套互相独立的 daemon 栈、真实的 Channel worker、merge-base「变更前」对照,以及一个删掉门控能力的环境。很高兴看到测试计划的全部 8 个步骤都能复现通过,且显示投影的信任边界能够抵抗伪造。 反馈处理结果验证报告(Reviewer 测试计划 8 步全部通过;授权边界确实在起作用)—— 无需改动。 视为对「实际行为与描述一致」的确认,没有需要处理的内容。 观察 2 ——「其他频道」按首次出现的会话排序、而不是排在最后 —— 已实现。 这是一个真实的观感缺陷:由于分组按会话遍历顺序产出,先于任何平台会话出现的孤儿会话会把兜底区块夹在两个真实平台之间(即你举的 观察 1 ——「频道」页签每个 tick 是三次请求(每次目录刷新都重新解析
因此,每个 tick 的 另外有一点记录在案、但因属于既有问题(并非本 PR 引入)而刻意不处理:早于「准入策略」字段的旧频道配置,在编辑器里必须同时选一个准入策略才能保存。这在本 PR 之前就存在,只是因为现在「编辑旧配置」成了设置会话作用域的常规操作才浮现出来。值得单独做一个小修复。 冲突说明
验证以下命令均在当前代码树(head
不适用:集成测试(改动是一个纯粹的侧边栏分组函数,不经过打包后的 CLI 执行)以及 Deferred non-Critical feedbackCritical-only mode is active after 5 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. ( 中文说明完成 5 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (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: 57 passed · 0 failed · 57 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:57 通过 · 0 失败 · 57 总计 Verification reportVerification report — PR #8457 (feat(web-shell): expose channel sessions in sidebar and settings)Verdict: 中文摘要
Scope selectionCentral claim — a channel prompt's user-authored text travels as a separate display projection across Channel → daemon → ACP → persistence → UI boundaries, under an authorization gate: every user-visible surface (session-bus echo, pending entries, recorded transcript, sidebar preview, auto-title input) shows only the projection while the model keeps the full prompt; callers that are not an authenticated channel worker cannot inject one. Secondary claims — (1) the Channels sidebar catalog groups sessions into collapsible platform sections with the unresolved "Other channels" section pinned after every named platform; (2) every manageable channel advertises a Everything else (live provider round trips, Chromium e2e, per-commit attribution) is listed under Not covered. Central claim: A/B at the persistence/preview seamThe seam is
Witness: The projection is honored end-to-end on head, not just at this seam: Authorization gate and wire keysThe display-text meta key can only enter the system through two gated writers:
Harness 2 ( Secondary claim 1: sidebar grouping matrixHarness 3 ( Secondary claim 2: session scope
Display-text sanitizer boundariesHarness 4 ( Mutation matrix (vacuity of the PR's own tests)
No survivors. Both files were restored byte-identical to head afterwards ( Targeted gates (head, restored tree)
The two mutation kills double as the liveness proof for the core and web-shell suites (planted violation reported, then clean run green). Reviewer Test Plan walkthrough
FindingsF-1 (Suggestion, non-blocking): webhook display projection folds newlines, channel path preserves them. F-2 (Suggestion, non-blocking): echo drops text blocks after the first when a projection is present. In Not covered
MethodologyEnvironment: Evidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅ Every Critical from the review rounds is verifiably fixed at this head, CI is green (including the previously red required unit leg), and the display-projection trust boundary holds up under reading. Non-blocking follow-ups are named in the Stage 3 comment. Approved by the triage gate on behalf of the maintainers.
|
🤖 Could not produce a passing fix for this feedback (round 9/100). This item now needs a human; the loop stays engaged and still picks up new feedback and base conflicts, but will not retry this item on its own. What I found before stopping: See the Qwen Autofix agent step logs for model/tool output. Run log: https://github.com/QwenLM/qwen-code/actions/runs/31610505107 🧠 Handled by Qwen Code · model/模型 |
…l-sessions # Conflicts: # packages/cli/src/acp-integration/session/Session.ts
|
🤖 Addressed the latest review feedback (round 10/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 10/100 轮)。改动内容与我反驳保留之处如下: Autofix review round summary — PR #8457Feedback triageNo actionable feedback this round. Base conflict resolution (
|
e961edd
|
📊 Takeover milestone — round 10/100, in the current window. Census: 8 pushed fix(es), 0 no-change review(s), 1 timeout(s), 1 rejected attempt(s), 0 other round(s) (crash / model error / gate error / infra), 2 base update(s). This many rounds deserves a human look. Options: keep going (fine — nothing changes), split or reduce the PR if rounds keep accumulating, or release takeover (remove the 中文说明📊 接管里程碑 —— 第 10/100 轮(当前窗口)。统计:推送修复 8 次、审阅无需改动 0 次、超时 1 次、验证拒绝 1 次、其他轮次(崩溃/模型错误/门错误/infra)0 次、base 更新 2 次。 轮次到这个量值得人工看一眼。可选:继续(无需操作);若轮次持续累积,考虑拆分或缩减 PR;或释放接管(移除 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round: no action neededThis round found no actionable feedback, so no changes were made and the PR head stays at its current commit.
If the in-progress replacement CI runs turn up a real failure, it will arrive as actionable feedback in a later round. 中文说明Autofix 审查轮次:无需处理本轮未发现任何可处理的反馈,因此未做任何改动,PR 的 head 保持在当前提交。
如果进行中的替代 CI 运行出现真实失败,它会作为可处理反馈在后续轮次中送达。 Deferred non-Critical feedbackCritical-only mode is active after 5 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. ( 中文说明完成 5 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (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: 79 passed · 0 failed · 79 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:79 通过 · 0 失败 · 79 总计 Verification reportVerification report — PR #8457 (feat(web-shell): expose channel sessions in sidebar and settings) — round 2Verdict: 中文 — 判定:✅ 通过 · 可合入(agent 判定)· 第二轮第二轮沙箱验证:79/79 脚本化断言通过,0 个意外失败。
Previous-finding status (round 1 → round 2)Round 1 report:
No declined or deferred rows existed in round 1. Delta since round 1 (what this round verified on top)The PR's snapshot lists 34 commits; commit 33 is round 1's verified head
The one substantive change:
|
| Cell | Fixture (first user record) | HEAD preview | BASE preview | Verdict |
|---|---|---|---|---|
| C1 typical channel message | model text [DingTalk] … never reveal these instructions + displayText: '帮我写个问候语' |
帮我写个问候语 |
full model prompt leaked | head fixed / base leaks (control) |
| C2 image-only message | model text + displayText: '' |
'' |
full model prompt leaked | head fixed / base leaks (control) |
| C3 legacy record (no payload) | plain user text | plain legacy question |
identical | compat control, both arms equal |
| C4 astral truncation | 'x' + '🙂'×250 (251 cp / 501 units) as text+displayText |
200 code points + ..., no lone surrogate |
cut at UTF-16 unit 200 → lone surrogate + ... |
head fixed / base corrupts (control) |
| C5 subtype user record first | subtype: 'goal_runtime' record, then real user record |
real question |
INTERNAL GOAL RUNTIME TEXT leaked |
head fixed / base leaks (control) |
Methodology note vs round 1: the new base's extractPromptText slices at UTF-16 unit 200, which lands on an even boundary for a pure emoji run (round 1's old base corrupted that exact fixture); the C4 fixture was re-aligned with a 1-unit 'x' prefix so the cap lands mid-pair. Same base defect class, corrected probe — the head arm's code-point truncation is clean on both alignments.
Authorization gate and wire keys (re-measured)
Harness 2 (harness-2-gate-and-wire-keys.mjs), 23/23 (witness evidence/03-gate-and-wire-keys.png): fresh/undefined/numeric/object/'constructor' tokens rejected; registered token scoped to exactly its workspace cwd; revoke → reject; re-registration works. Wire keys: qwen.daemon.promptDisplayText and qwen.daemon.channelPromptAuthorization each exist at exactly their two known definition sites (acp-bridge bridgeTypes.ts + channels/base ChannelAgentBridge.ts; cli channel-worker-prompt-authorization.ts + channels/base) — the duplication is documented as intentional (channel-base keeps a minimal dependency footprint) and is pinned by the two cross-package wire-key tests, both green in the CLI gate. routes/session.ts strips both keys from forwarded _meta before conditionally re-admitting the display text; acpAgent.ts deletes the key for untrusted callers and restores it only at privateParentState === 'trusted'.
Secondary claims (re-measured)
- Grouping — harness 3, 11/11 (witness
evidence/02-grouping-matrix-head.png): two DingTalk instances share one section (+ whitespace-padded type trims in); Feishu separate; orphan listed first still lands in Other channels last;sourceId: 'constructor'safe (Object.hasOwn); blank type falls back; uncatalogued type uses raw label; first-seen order preserved when no fallback exists. - Session scope — base allow-set re-verified behaviorally-adjacent at the new base:
tmp/base-treechannel-settings-store.ts:147=['user','thread','single'], head =['user','thread','chat_thread','single']; pinned by the green registry/builtins/settings-store tests in the CLI gate.
Sanitizer boundaries (re-measured)
Harness 4 (harness-4-sanitize-display.mjs), 18/18: NEL/bidi/zero-width/line-separator/variation-selector stripped; newline preserved (multi-line keeps all lines); TAB/CR/DEL folded; brackets preserved; code-point cap clean at every boundary; production cap 8000 (ChannelBase.ts:335); benign CJK/emoji unchanged; plus the F-1 contrast pair.
Mutation matrix (re-run at new head)
| Mutation | Suite | Result |
|---|---|---|
MUT-1: extractFirstPromptFromRecords reverted to base logic |
core sessionService.test.ts |
killed — 4 failed | 134 passed; intended assertions, e.g. expected 'hello session a' to be 'later prompt' (witness evidence/04-mut1-kills-displaytext-tests.png) |
MUT-2: Other-channels pin removed (return ordered) |
web-shell channelSessionGroups.test.ts |
killed — 2 failed | 2 passed (witness evidence/05-mut2-kills-pinning-tests.png) |
MUT-3a: goalTurn?.permit → undefined in the displayText branch (Session.ts) |
cli Session.test.ts, both targeted tests |
survived — both green; classified as a coverage gap (F-3), behavior verified correct by reading + gates (witness evidence/06-mut3-permit-drop-survives.png) |
MUT-3b (positive control): if (false) on the same branch |
cli Session.test.ts |
killed — spy-argument mismatch names expected vs received |
No other survivors. Both round-1 kill counts reproduced exactly. All mutated files restored byte-identical (git diff empty) before the green gate runs.
Targeted gates (head tree, all re-run this round)
| Suite | Result |
|---|---|
| core: sessionService + sessionTitle + chatRecordingService + autoTitle | 258 passed (258) |
| acp-bridge: bridge.test.ts | 575 passed (575) |
| channels/base: full workspace | 1028 passed (1028) |
| channels/github GithubAdapter.test.ts | 190 passed (190) |
| channels/qqbot events.test.ts | 88 passed (88) |
| cli: wire-keys, registry, builtins, daemon-worker, memory-intent, settings-store, worker-supervisor, rename, resumeHistory (10 files) | 397 passed (397) |
| cli: Session.test.ts + acpAgent.test.ts + server.test.ts | 1912 passed (1912) — round 1 had 1892; +20 are main-side tests absorbed by the merge |
| web-shell: grouping + WorkspaceSection + sidebar collapse/removal + editor (6 files) | 144 passed (144) |
tsc --noEmit on packages/cli (the merge-affected package) |
clean, exit 0 |
Liveness of the core/web-shell/cli suites is proven by the mutation kills and positive controls above. Note: the first CLI gate run accidentally overlapped the MUT-1 window (cli vitest aliases core to ../core/index.ts source); it was re-run on the restored tree — the 1912 figure is from the clean re-run.
Reviewer Test Plan walkthrough (status at new head)
- Tasks default / channel session absent — sidebar suites green; capability gate unchanged (not browser-driven here).
- Platform sections, shared DingTalk, pinned stays, Other channels — harness 3, re-measured.
- New conversation appears under Channels on the active interval — code path + polling suites green; browser not available.
- User message/title show only user-visible content; image-only shows nothing — harness 1 C1/C2/C4/C5 re-measured; sessionTitle dialog switch green in core gate.
- Session scope section with effective default — registry/builtins green, re-run.
- Select Per thread, save, reopen — settings-store + editor green, re-run.
- Active/pinned/archived/search/secondary scoped to source — sidebar suites green, re-run.
- Old daemon: switch hidden, behavior unchanged — capability gate in code + collapse-persist suite green.
Findings
F-1 (Suggestion, carried over, stands): webhook display projection folds newlines, channel path preserves them. buildChannelWebhookDisplayText (ChannelWebhookTask.ts:118) runs title/summary through sanitizePromptText (folds CR/LF/TAB), while the live channel boundary uses newline-preserving sanitizeDisplayText. The comment states this mirrors the model-prompt treatment intentionally; no leak either way. Consider whether webhook summaries should get the display-side sanitizer for consistency.
F-2 (Suggestion, carried over, stands): echo drops text blocks after the first when a projection is present. echoPromptToSessionBus (bridge.ts:1386-1405): with displayText !== undefined, text blocks after the first are skipped. Current channel constructors emit exactly one text block (DaemonChannelBridge.ts:411-419), so nothing is dropped today; a future multi-text-block adapter would silently lose the extras from the session-bus echo. Latent, unchanged.
F-3 (Suggestion, new): the displayText × goalTurn combination is unpinned. See the Session.ts section above — MUT-3a survives both targeted tests; the reachable combination (channel message during an active Goal) records correctly today (both goalContext and the projection), but nothing asserts it. Completeness gap, not a defect; suggested fixture included above.
No blocking findings.
Not covered
- Chromium e2e (
web-shell.channels.spec.tsetc.): no browser in this container. Unit-level counterparts green. - Live provider round trips (DingTalk/Feishu/WeCom/GitHub/GitLab) — same scope statement as the PR.
- Per-commit attribution of commits 1–33: shallow checkout; round 1 verified the aggregate at the old head, this round verified the aggregate at the new head plus the exact inter-round delta (only commit 34 is new, and its content effect is bounded in the Delta section). Individual older commits were not re-exercised.
- Full-daemon HTTP wire run of a channel webhook end-to-end — each seam verified in isolation (harnesses 1–4) plus the 575-test bridge and 1912-test CLI suites; harnesses reproduce the persisted wire shape, not a live daemon trigger.
- Trial merge into current main / repo-wide build+typecheck: no network in the sandbox, so
origin/mainbeyond the snapshot'sbaseRefOidis unreachable; the PR head itself is a fresh merge oforigin/mainwith conflict resolutions, which is the closest available proxy. The PR's own CI covers the repo-wide build/typecheck; this round ran a targetedtsc --noEmiton the merge-affected cli package only. - Behavioral A/B of the
chat_threadsettings-store acceptance — base allow-set cited from theHEAD^1source; head acceptance pinned by its green test.
Methodology
Environment: node:22-bookworm CI container, repo at merge 04d354df03 (merge-ref checkout: HEAD^1 = base tip 50097c15, HEAD^2 = PR head e961edd3, matching the snapshot's baseRefOid/headRefOid); npm ci + npm run build pre-ran at head. Delta analysis used the locally reachable round-1 head 06336a25 (fetched object) to bound inter-round changes per file and per hunk. Arms: head = repo tree; base = git worktree add tmp/base-tree HEAD^1, each compiled with the repo's esbuild (--bundle --format=esm --platform=node --packages=external; verified zero @qwen-code/* runtime imports in every bundle, so no workspace-symlink confound, and identical external dependency sets across arms). Bundle placement: the two sessionService bundles live at packages/core/.harness/ inside the head tree because their external ajv/dist/2020.js import must resolve to the nested packages/core/node_modules/ajv v8 (the root node_modules/ajv is v6 and lacks it — a relocation that ignored this produced ERR_MODULE_NOT_FOUND and was reverted); the four self-contained bundles (authz, wirekeys, groups, sanitize — zero external imports) ship in this artifact's bundles/ so harnesses 2–4 rerun anywhere. Rebuild the sessionService bundles with ./node_modules/esbuild/bin/esbuild packages/core/src/services/sessionService.ts --bundle --format=esm --platform=node --packages=external --outfile=packages/core/.harness/sessionService.head.mjs (and the base-worktree equivalent) before re-running harness 1. Gates ran via per-package npx vitest run <files>; mutations were applied with exact-match text replacement and restored byte-identical (git-diff-verified) before green runs. Evidence PNGs via scripts/verify-capture.mjs. Assertion counts: H1 11 + H2 23 + H3 11 + H4 18 + mutation adjudications 4 + gates 9 + delta analysis 3 = 79, all passing; fail counts only unexpected outcomes (base-arm red cells are encoded as expected-failure controls and count as passes). One harness-design iteration occurred on H1 C4 (the pure-emoji fixture aligned evenly on the new base's 200-unit slice; re-aligned with an 'x' prefix) — the final run is the measurement.
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅ Re-run verified the main merge (Goal v3 + admission hardening) and its single Session.ts conflict resolution on top of it; CI green at this head. Approved pinned to the reviewed commit.
|
Released in v0.21.11. |






































What this PR does
Adds a Tasks / Channels source switch above the Web Shell project session catalog. Tasks remains the default view, while Channels shows sessions started through configured integrations such as DingTalk, Feishu, and WeCom.
Keeps the selected Channels catalog on the active refresh interval so conversations created externally appear without switching tabs. The model still receives the complete Channel context, while live echo, restored transcripts, and sidebar fallback titles use a separate user-facing message projection that excludes internal Channel instructions.
Groups Channel conversations into collapsible platform sections such as DingTalk and Feishu. Multiple configured instances of the same platform share one section, pinned conversations remain in their platform section, and sessions whose configuration is no longer available remain discoverable under Other channels.
Adds a Session scope setting to every manageable Channel. Users can choose whether incoming messages create sessions per user and chat, per thread, per chat and thread, or as one channel-wide shared session. Existing plugin defaults remain effective, including the chat-and-thread default used by GitHub and GitLab.
Applies the selected sidebar source consistently to active, pinned, archived, primary-workspace, and secondary-workspace session lists. Controls are capability- or catalog-driven so older daemons retain their existing behavior.
Why it's needed
Daemon-managed Channels already persist conversations as workspace sessions tagged with the channel source, but the Web Shell sidebar only requested regular task sessions. A valid Channel conversation therefore could not be reopened from the sidebar even though it belonged to the selected workspace. After source filtering was added, the idle refresh interval also left the selected Channels view stale until the user switched tabs.
As the catalog grows, a flat list also makes it difficult to tell which integration owns each conversation. Grouping by the current workspace's Channel configuration keeps related conversations together without changing how sessions are persisted.
Channel requests combine user input with transport instructions and contextual metadata for the model. Reusing that model-facing text for transcript echo and fallback titles exposed internal instructions as if the user had typed them. The persisted display projection now keeps those concerns separate without reducing model context.
The Channel runtime also already supports configurable session routing, but the Web management catalog exposed only platform-specific credentials and access policy. Users could not inspect or change which conversations shared agent context without editing configuration manually.
Reviewer Test Plan
How to verify
Evidence (Before & After)
Local validation passed: the full repository build and typecheck, targeted lint, 617 Channel base/daemon bridge tests, 471 ACP bridge tests, 507 CLI Session tests, 123 SessionService tests, 75 current Web Shell sidebar tests, 303 Web Shell app regression tests, 18 transcript replay tests, 43 TUI resume-history tests, and 2 Chromium Channel E2E tests. Earlier scope and catalog coverage also passed 39 Channel settings tests and 82 Web Shell sidebar/editor tests.
Tested on
Environment (optional)
Local Web Shell development build with the mock daemon and Chromium.
Risk & Scope
Linked Issues
N/A
中文说明
本 PR 做了什么
在 Web Shell 项目会话目录上方新增“任务 / 频道”来源切换。“任务”仍是默认视图,“频道”用于展示由钉钉、飞书和企业微信等已配置集成发起的会话。
“频道”保持选中时使用活跃刷新间隔,因此从外部新建的对话无需切换页签即可出现。模型仍会收到完整的 Channel 上下文,而实时消息回显、恢复后的对话记录和侧边栏兜底标题使用独立的用户可见消息,不包含内部 Channel 指令。
Channel 对话会按钉钉、飞书等平台归入可折叠区块。同一平台的多个配置实例会合并到同一区块,置顶对话仍保留在对应平台下,配置已不存在的会话则继续显示在“其他频道”中。
为每个可管理 Channel 新增“会话作用域”设置。用户可以选择按用户和对话、按话题、按对话和话题,或整个频道共享一个会话。现有插件默认值保持有效,包括 GitHub 和 GitLab 使用的“按对话和话题”默认值。
所选侧边栏来源会一致应用于活跃、置顶、已归档、主工作区及次级工作区的会话列表。控件均由能力或管理目录驱动,因此旧版 daemon 会保持现有行为。
为什么需要它
daemon 管理的 Channel 已经会将对话持久化为工作区会话,并标记频道来源,但 Web Shell 侧边栏此前只请求普通任务会话。因此,即使 Channel 会话属于所选工作区,也无法从侧边栏重新打开。加入来源过滤后,空闲刷新间隔仍会导致已选中的“频道”视图保持旧数据,直到用户切换页签。
随着会话目录增长,平铺列表也很难看出每条对话属于哪个集成。按当前工作区的 Channel 配置分组,可以在不改变会话持久化方式的前提下将相关对话归在一起。
Channel 请求会将用户输入、传输指令和上下文元数据组合后发送给模型。此前复用这段模型侧文本进行对话回显和兜底标题生成,会把内部指令显示成用户输入。现在持久化独立的显示文本,在不减少模型上下文的前提下分离两种用途。
Channel 运行时也已经支持可配置的会话路由,但 Web 管理目录只暴露平台凭据和准入策略。用户无法在不手动编辑配置的情况下查看或修改哪些对话共享 Agent 上下文。
Reviewer 测试计划
如何验证
证据(变更前后)
本地验证已通过:全仓构建与类型检查、针对性 lint、617 个 Channel base/daemon bridge 测试、471 个 ACP bridge 测试、507 个 CLI Session 测试、123 个 SessionService 测试、当前 75 个 Web Shell 侧边栏测试、303 个 Web Shell App 回归测试、18 个对话重放测试、43 个 TUI 恢复历史测试,以及 2 个 Chromium Channel E2E 测试。此前的作用域和目录覆盖还通过了 39 个 Channel 设置测试和 82 个 Web Shell 侧边栏/编辑器测试。
测试平台
环境(可选)
本地 Web Shell 开发构建、模拟 daemon 和 Chromium。
风险与范围
关联 Issue
无