Skip to content

feat(web-shell): add native Live Voice - #7859

Merged
LaZzyMan merged 49 commits into
QwenLM:mainfrom
LaZzyMan:lazzy/web-shell-live-voice
Aug 5, 2026
Merged

feat(web-shell): add native Live Voice#7859
LaZzyMan merged 49 commits into
QwenLM:mainfrom
LaZzyMan:lazzy/web-shell-live-voice

Conversation

@LaZzyMan

@LaZzyMan LaZzyMan commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This PR adds experimental Live Voice onboarding and the Codex-parity Live architecture to WebShell on macOS. Live Voice is disabled by default and is not exposed by the CLI/TUI, API-only daemons, non-macOS daemons, or WebShell instances without the native integration surface.

Users configure a dedicated DashScope key for qwen3.5-omni-plus-realtime and a global shortcut in the WebShell Experimental settings. Enabling the feature requires confirmation, validates the realtime credential, automatically downloads and verifies the correct signed Qwen Live Host build, installs it in /Applications, launches it, and guides the user through Microphone, Accessibility, and Screen Recording permissions. The default shortcut is Command+E, and the existing one-shot dictation button remains available beside Live Voice.

Each call attaches a Qwen Omni Realtime conversation to one persistent projectless Qwen Code session. Realtime answers self-contained dialogue directly and hands off requests that need tools or deeper execution to the same ordinary session, which retains its normal tools and permission pipeline while adding Live Appshot and task list/read/wait/send/create tools. Backend agent messages return incrementally to the active realtime conversation, and new speech can interrupt or steer the same turn.

Appshot is a built-in native Qwen Live Host capability. It does not use an external MCP, CUA driver, helper application, or process launcher. A screen request is handed to the backend session, which invokes the Live-only screen-context tool over the authenticated Host channel.

Desktop releases now build arm64 and x64 Qwen Live Host ZIP/DMG assets plus a versioned manifest containing protocol, bundle, size, and SHA-256 data. Published builds require the expected Developer ID team, Gatekeeper acceptance, notarization, and stapling; stable assets are uploaded to the fixed desktop release feed consumed by onboarding.

Why it's needed

WebShell already supports one-shot dictation, but it did not provide an always-available full-duplex coding conversation that can be invoked from any macOS app, remain attached to durable Qwen Code work, inspect the visible app on request, and follow existing or newly created tasks. This change supplies that experience while keeping model authority, tool permissions, installation, credentials, and Appshot ownership explicit and fail-closed.

Reviewer Test Plan

How to verify

  1. Start WebShell on macOS and open Experimental settings. Confirm Live Voice is off, the dedicated realtime key and Command+E shortcut controls are present, and existing dictation remains visible. Confirm the same settings and Live routes are absent from non-macOS, API-only, and no-WebShell daemon surfaces.
  2. Save a valid realtime key while Live is disabled. Confirm saving alone does not open a realtime connection. Enable Live, accept the confirmation, and confirm the key is validated once before the setting changes or installation starts. Invalid or missing keys must leave Live disabled.
  3. Confirm onboarding downloads only the fixed release manifest and architecture-specific asset, verifies size, SHA-256, protocol, bundle identity, Developer ID team, and Gatekeeper acceptance, then installs and launches Qwen Live Host. Confirm the UI reports installation progress and the three required macOS permissions without requesting Input Monitoring, Bluetooth, or Camera permission.
  4. Use Command+E from another application. Confirm the overlay appears immediately, selected audio input works, Command+E closes the call, barge-in interrupts current speech, and a new call does not create an overlapping provider response.
  5. Ask a self-contained question and confirm Realtime answers directly. Then request tool-backed work and confirm one handoff runs in the same projectless Live session, normal permission requests remain in WebShell with a quick-open action, incremental backend messages reach Realtime before final completion, and steering does not duplicate the task.
  6. Ask to inspect the visible app, then list/read/follow/create tasks. Confirm Appshot uses the built-in Host channel, screen content is treated as untrusted data, reading or following an existing task does not create a replacement session, and created session links open the correct stored session.
  7. Build a published desktop release and confirm both Host architectures, ZIP/DMG files, and the manifest are uploaded to the version release and stable desktop feed only after signing and notarization checks pass.

Evidence (Before & After)

Before: WebShell exposed only browser dictation and had no supported global Live onboarding, projectless Live group, realtime-owned dialogue, task follow-up surface, built-in Appshot channel, or release-delivered native Host.

After: The macOS WebShell Experimental panel provides the complete disabled-by-default key, shortcut, confirmation, installation, launch, and permission flow. The Live overlay, realtime conversation, selective same-session handoff, incremental backend return, interruption, task tools, existing permission UI, internal Appshot, projectless session links, and independent dictation control have been exercised in real user scenarios. Static architecture checks confirm no external CUA/MCP dependency or Input Monitoring path remains.

Final validation passed full-repository lint, build, and typecheck; targeted setup, WebShell onboarding, Live lifecycle, SDK, bridge, and Host suites; dual-architecture release-manifest SHA/size verification; workflow lint; and two clean source/packaging audits. The production signed-and-notarized auto-install path remains gated on an actual release run with repository signing and notarization secrets.

Tested on

OS Status
🍏 macOS
🪟 Windows N/A
🐧 Linux N/A

Environment (optional)

macOS, Node.js 22, Qwen Code WebShell, Qwen Live Host arm64/x64 local packages, and the live DashScope qwen3.5-omni-plus-realtime service.

Risk & Scope

  • Main risk or tradeoff: This is a broad cross-package feature spanning realtime audio, ordinary Qwen sessions, native macOS permissions, release signing, and background installation. Every unsupported or unverifiable state fails closed instead of degrading to another architecture.
  • Not validated / out of scope: A production release run using repository signing/notarization secrets and a fresh-user install of those published assets cannot be completed locally; the workflow enforces those checks before publication.
  • Breaking changes / migration notes: None. Live Voice is experimental and disabled by default, and existing dictation is unchanged.

Linked Issues

N/A

中文说明

本 PR 做了什么

本 PR 为 macOS WebShell 增加实验性 Live Voice 上手流程和对齐 Codex 的 Live 架构。Live Voice 默认关闭,并且不会在 CLI/TUI、仅 API daemon、非 macOS daemon 或没有原生集成面的 WebShell 中暴露。

用户在 WebShell 的实验性设置中配置 qwen3.5-omni-plus-realtime 专用 DashScope key 和全局快捷键。开启功能时必须确认,系统会验证 realtime 凭证,自动下载并校验正确架构的已签名 Qwen Live Host,安装到 /Applications、启动应用,并引导完成麦克风、辅助功能和屏幕录制三项授权。默认快捷键为 Command+E,原有的一次性语音输入按钮继续与 Live Voice 并存。

每次通话都会把一段 Qwen Omni Realtime 对话附加到一个持久的无项目 Qwen Code session。Realtime 直接回答自包含对话;需要工具或更深执行时,handoff 到同一个普通 session。该 session 保留正常工具和权限流程,同时增加 Live Appshot 以及任务 list/read/wait/send/create 工具。后台 Agent 消息会增量返回仍处于活动状态的 realtime 对话,新语音可以打断或引导同一个 turn。

Appshot 是 Qwen Live Host 内置的原生能力,不依赖外置 MCP、CUA driver、辅助应用或进程启动器。屏幕请求先 handoff 到后台 session,再通过经过认证的 Host 通道调用 Live 专用屏幕上下文工具。

Desktop release 现在会构建 arm64 和 x64 两种 Qwen Live Host ZIP/DMG 产物,并生成包含协议、bundle、大小和 SHA-256 的版本化 manifest。正式发布必须通过指定 Developer ID team、Gatekeeper、公证和 stapling 校验;稳定产物会上传到 onboarding 使用的固定 desktop release feed。

为什么需要

WebShell 已经支持一次性语音输入,但此前无法提供可从任意 macOS 应用唤起、保持全双工语音交互、附着于持久 Qwen Code 工作、按需查看当前应用并跟进已有或新建任务的能力。本改动补齐该体验,同时让模型权威、工具权限、安装、凭证和 Appshot 归属保持明确并 fail-closed。

Reviewer 测试计划

如何验证

  1. 在 macOS 启动 WebShell 并打开实验性设置。确认 Live Voice 默认关闭,存在 realtime 专用 key 和 Command+E 快捷键配置,原有语音输入仍可见;确认非 macOS、仅 API 和无 WebShell daemon 不暴露这些设置及 Live 路由。
  2. 在关闭 Live 时保存有效 realtime key。确认单独保存不会创建 realtime 连接。接受确认后开启 Live,确认只验证一次 key,并且必须先验证成功才会更新开关或开始安装;无效或缺失 key 必须保持关闭。
  3. 确认 onboarding 只下载固定 release manifest 和当前架构产物,校验大小、SHA-256、协议、bundle、Developer ID team 和 Gatekeeper,然后安装并启动 Qwen Live Host。确认 UI 展示安装进度和三项必需 macOS 权限,不请求输入监控、蓝牙或摄像头权限。
  4. 在其他应用中使用 Command+E。确认悬浮 UI 立即出现、选择的音频输入可用、再次按 Command+E 可关闭通话、插话会打断当前语音,并且新通话不会触发重叠的 provider response。
  5. 提出自包含问题并确认由 Realtime 直接回答;再请求需要工具的任务,确认 handoff 在同一个无项目 Live session 中执行,正常权限请求仍由 WebShell 处理并提供快速跳转,后台增量消息会在最终完成前到达 Realtime,语音引导不会重复创建任务。
  6. 请求查看当前可见应用,再测试任务读取、查看、跟进和创建。确认 Appshot 使用内置 Host 通道、屏幕内容被视为不可信数据、读取或跟进已有任务不会创建替代 session,并且新建 session 链接能打开正确的持久化 session。
  7. 构建正式 desktop release,确认两种架构的 Host、ZIP/DMG 和 manifest 只有在签名与公证校验通过后才会同时上传到版本 release 和稳定 desktop feed。

证据(Before & After)

Before:WebShell 仅暴露浏览器语音输入,没有受支持的全局 Live onboarding、无项目 Live 分组、Realtime 对话权威、任务跟进面、内置 Appshot 通道或随 release 交付的原生 Host。

After:macOS WebShell 实验性面板提供完整的默认关闭、key、快捷键、确认、安装、启动和权限引导流程。Live 悬浮 UI、Realtime 对话、选择性同 session handoff、后台增量返回、打断、任务工具、现有权限 UI、内部 Appshot、无项目 session 链接和独立语音输入控件均已在真实用户场景中验证;静态架构审计确认不再存在外部 CUA/MCP 依赖或输入监控路径。

最终验证通过全仓 lint、build 和 typecheck,setup、WebShell onboarding、Live 生命周期、SDK、bridge 和 Host 的针对性测试,双架构 release manifest 的 SHA/大小校验,workflow lint,以及两轮干净的源码和打包架构审计。正式签名及公证后的自动安装仍需在具备仓库签名和公证 secrets 的真实 release 中完成。

测试平台

OS 状态
🍏 macOS
🪟 Windows N/A
🐧 Linux N/A

环境(可选)

macOS、Node.js 22、Qwen Code WebShell、Qwen Live Host arm64/x64 本地产物,以及线上 DashScope qwen3.5-omni-plus-realtime 服务。

风险与范围

  • 主要风险或权衡:这是一个覆盖 realtime 音频、普通 Qwen session、macOS 原生权限、release 签名和后台安装的跨 package 功能。所有不支持或无法校验的状态都会 fail-closed,不会降级到其他架构。
  • 未验证或范围外:本地无法完成使用仓库签名/公证 secrets 的正式 release,以及该正式产物的全新用户安装;workflow 会在发布前强制执行这些校验。
  • 破坏性改动或迁移说明:无。Live Voice 是默认关闭的实验性功能,原有语音输入不变。

关联 Issue

N/A

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 27, 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 4c0811d. 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/ChatEditor.tsx
  • packages/web-shell/client/components/messages/SettingsMessage.tsx
  • packages/web-shell/client/components/sidebar/WebShellSidebar.module.css
  • packages/web-shell/client/components/sidebar/WebShellSidebar.tsx
  • packages/web-shell/client/i18n.tsx
  • packages/web-shell/client/live/HotkeySetter.tsx
  • packages/web-shell/client/live/LiveVoiceButton.module.css
  • …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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

🩺 serve daemon A/B

Built the PR base vs this PR head 4c0811d, drove a fixed endpoint set against each, and diffed the JSON responses. Only fields that changed are shown.

No response changes against the PR base across 4 scenario(s).

Qwen Code · serve A/B

@wenshao
wenshao marked this pull request as ready for review July 27, 2026 17:37
@wenshao

wenshao commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /takeover

@qwen-code-dev-bot qwen-code-dev-bot added the autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) label Jul 27, 2026
@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Qwen Triage finished — CI landed green on 4c0811d and the deferred approval was posted. finalize run

Qwen Triage 已完成 —— 4c0811d 的 CI 全绿,延迟审批已提交。查看 finalize 运行

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

@LaZzyMan thanks for putting this together — the design doc, test plan, and fail-closed posture are all well thought out, and the PR description is one of the more thorough ones we've seen.

That said, this is a 21,400-line, 111-file feature spanning five packages (core, cli, desktop, sdk-typescript, acp-bridge), a new Electron + Swift native host, a realtime voice provider, and system-level macOS permissions (Accessibility, Screen Recording, Input Monitoring). It also reuses DashScope credentials for the realtime provider and introduces new computer-use trust boundaries (Appshot gating, hostile on-screen instruction handling). Those are exactly the areas — auth, credential flow, and computer-use trust — where we need a maintainer to weigh in on direction before anyone dives into a code review of this scale.

A few things worth discussing before this moves forward:

  • Scope. Could this land as a sequence of smaller PRs? The native host app, the realtime session + coordinator, the SDK/routes, and the Web Shell controls each seem like they could be reviewed independently. A 21K-line PR is very hard to review carefully, and splitting would make each piece easier to reason about and revert.
  • CI. The Test (ubuntu-latest, Node 22.x) check is failing on yamllint — packages/desktop/apps/live-host/electron-builder.yml has unquoted strings. Straightforward fix, but it needs to go green.
  • Direction question. Live Voice is a significant new product surface. Is this something the team wants to take on as a first-party feature, or would it be better suited as an extension / community integration? That's a maintainer call, not something the gate can decide.

Escalating to the maintainer for a direction review. No judgement on the code quality yet — this is about whether the scope and direction align with where the project wants to go before investing in a full review pass.

中文说明

@LaZzyMan 感谢提交——设计文档、测试计划和 fail-closed 策略都考虑得很周全,PR 描述也非常详尽。

不过,这是一个 21,400 行、111 个文件的大型功能,横跨五个 package(coreclidesktopsdk-typescriptacp-bridge),包含一个全新的 Electron + Swift 原生 Host、实时语音 provider,以及系统级 macOS 权限(辅助功能、屏幕录制、输入监控)。它还复用了 DashScope 凭证来访问 realtime 模型,并引入了新的 Computer Use 信任边界(Appshot 门禁、屏幕恶意指令处理)。这些恰好是——认证、凭证流转和 Computer Use 信任——需要维护者先对方向表态,然后才能进行如此大规模的代码审查。

几个值得在推进前讨论的问题:

  • 范围。 能否拆成一系列更小的 PR?原生 Host 应用、实时会话 + Coordinator、SDK/路由、Web Shell 控件,看起来都可以独立审查。21K 行的 PR 很难仔细审查,拆分后每个部分都更容易推理和回滚。
  • CI。 Test (ubuntu-latest, Node 22.x) 检查因 yamllint 失败——packages/desktop/apps/live-host/electron-builder.yml 中的字符串未使用单引号。修复很简单,但需要先变绿。
  • 方向问题。 实时语音是一个重要的新产品面。这是团队希望作为第一方功能承接的,还是更适合作为扩展/社区集成?这是维护者的决定,不是 gate 能判断的。

转交维护者进行方向审查。目前尚未对代码质量做评判——这是关于范围和方向是否与项目目标一致,然后再投入完整的审查。

Qwen Code · qwen3.8-max-preview

@wenshao

wenshao commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Review: feat(web-shell): add native Live Voice

Reviewed 9faa806...c9d8712 (111 files, +21439/−192). Ran the new daemon-side suites locally: 113/115 passed; the 2 failures in run-qwen-serve-live.test.ts were my scratch worktree failing to resolve @qwen-code/channel-github, not PR defects. create-sub-session, routes/live, workspace-management, workspace-trust, workspace-trust-reconciler are all green (149/149).

Overview

Adds an opt-in macOS Live Voice stack: a private ~/Documents/Qwen Code/Conversations runtime with live-conversation provenance, a LiveHostCoordinator (host lease over /live/host WS, nonce + heartbeat + epoch), a LiveSessionCoordinator bridging the DashScope realtime model to an authoritative Qwen Code coordinator session, /live/* REST routes, a discovery file for daemon location, SDK + Web Shell controls, and a packaged Electron menu-bar/overlay host.

What's good

  • The trust plumbing is careful. managedRelocation: 'live-conversation' is gated on privateParentState === 'trusted', requires exactly one absolute allowed root, and re-validates owner-only dir + dev/ino before and after realpath, plus direct-child containment — the folder-trust bypass is scoped strictly to that path.
  • modelPrompt is stripped from _meta before echo, re-validated in the child, and rejected without a trusted invocation context.
  • Fresh per-turn nonce for start_new_live_conversation with exact-match comparison — a replayed or injected stale nonce can't fire it.
  • Worker discovery only trusts a completed create_sub_session tool_call_update and re-validates runtime ownership at open time (onOpenSession in server.ts).
  • apiKey non-enumerable + sanitizeErrorText redaction; DashScope host allowlist and credential-in-query rejection on both baseUrl and the realtime endpoint.
  • Electron hardening: contextIsolation/sandbox, no nodeIntegration, per-window ephemeral partition, all permissions denied, Authorization attached only on same-daemon-origin requests, navigation confined to the daemon origin.
  • ~5,900 lines of tests for the new serve/live modules — genuinely thorough, including adversarial cases like the forged </task-notification> escape test.

High

H1 — /live/status and every state broadcast do a synchronous, uncached loadSettings(), including once per output-audio delta.

LiveHostCoordinator.getStatus()readProviderReadiness()server.ts:getProviderReadinessresolveLiveCredential()loadSettings(), which is fully synchronous (fs.realpathSync ×2, existsSync/readFileSync + JSON parse + migration for system / system-defaults / user settings). providerOverride is cleared to undefined on a healthy connect, so nothing short-circuits it during a call.

broadcastState() calls getStatus(), and setCallState()/setTranscript() broadcast unconditionally with no change detection. onOutputAudioDelta calls setCallState(epoch, 'speaking') for every audio chunk from the provider, and onInputTranscriptDeltasetTranscript likewise — so a live call performs one blocking settings load (and one host WS state frame) per audio/transcript delta.

Independently, useLiveVoice polls GET /live/status every 1000 ms per visible Web Shell tab whenever the realtime_voice feature is advertised — which is whenever ACP-HTTP is enabled, i.e. for essentially every Web Shell user, even with general.liveVoice.enabled: false and no host installed. Each poll is another synchronous settings load on the daemon event loop.

Suggested: memoize provider readiness (settings mtime / short TTL / invalidate from the existing settings watcher); make setCallState/setTranscript no-op when the value is unchanged; and have useLiveVoice back off hard (or stop) once available === false with requirements.host === 'missing'.

H2 — create_sub_session with completion: 'sent' now notifies the parent for every caller, not just Live.

In the sent-mode drain, deliverSentCompletion(...) is called unconditionally; isolatedWorkspace only adds the Live-specific cwd handling. Previously 'sent' was fire-and-forget (it only held the concurrency slot). Now every sent-mode sub-session injects <task-notification>… into its parent, persists it via recordNotificationStrict, and triggers a normal automatic follow-up turn — extra model calls and new visible output for existing Web Shell / channel / scheduled-task users. create-sub-session.test.ts:585 covers this for a launcher with no isolatedWorkspace, so it's clearly intentional, but:

  • the PR description doesn't mention it (it reads as Live-scoped);
  • the module's own docstring for 'sent' still says "dispatch the prompt and return { sessionId } immediately; the sub-session keeps running and is idle-reaped later" with no mention of the completion return.

Please either gate it on the Live path, or call it out explicitly in the PR body and update the header comment.

H3 — The entire live-host test suite never runs in CI.

~1,300 lines of new tests under packages/desktop/apps/live-host/src/main/__tests__/ (run via bun test) plus CommandTapRecognizerTests.swift via scripts/test-native-helper.mjs. But packages/desktop is excluded from root workspaces ("!packages/desktop"), ci.yml only runs check:desktop-isolation, and desktop-release.yml only builds apps/electron. Nothing invokes live-host:test or live-host:typecheck. This is the security-critical half of the feature (nonce handling, Authorization scoping, navigation guards, CUA bundle validation) — it should get at least a macOS CI job, or a Linux job for the pure-logic tests.


Medium

M1 — The daemon bearer token is written to disk even when Live Voice is disabled.

publishLiveDiscovery early-returns only on !resolveAcpHttpEnabled(). targetBaseDirs adds runtimeBaseDir whenever it differs from ~/.qwen, regardless of liveVoiceEnabledAtBoot, and the record includes token. So any daemon with a custom QWEN_RUNTIME_DIR writes its bearer token to <runtimeBaseDir>/live/daemon.json for a feature that is off — and nothing reads that path unless QWEN_LIVE_DISCOVERY_FILE points at it. run-qwen-serve-live.test.ts's first test confirms this: it enables nothing and still asserts the token is on disk. The 0600 + owner check is right, but this is a new credential-at-rest surface; please gate the whole publish on liveVoiceEnabledAtBoot.

M2 — The wire protocol and the CUA driver pin are duplicated across the desktop isolation boundary with no drift check.

packages/cli/src/serve/live/types.ts vs packages/desktop/apps/live-host/src/shared/protocol.ts (LIVE_HOST_PROTOCOL_VERSION/LIVE_PROTOCOL_VERSION = 2, bundle id, 8-byte epoch, frame caps), and core/tools/computer-use/constants.ts:CUA_DRIVER_VERSION vs live-host/src/shared/cua-driver-version.ts:PINNED_CUA_DRIVER_VERSION. They agree today, but only comments keep them in sync. A one-sided bump silently produces a permanent host_version blocker or a permanent appshot blocker with no test failure. A small assertion in scripts/check-desktop-isolation.js would close this.

M3 — awaitSentCompletionAcceptance retries at a fixed 100 ms for up to 30 minutes, no backoff.

If enqueueBackgroundNotification keeps returning accepted: false — e.g. Session.#persistDaemonBackgroundNotification returns false because the session is closing/disposed or has no recording service — this issues on the order of 18,000 ACP ext-method round-trips against the child. Exponential backoff, and treating a persistent accepted: false (as opposed to a transient throw) as terminal after a few attempts, would be safer.

M4 — CommandMonitor.requestAccess() can leave two helper processes feeding one stdout buffer.

stop() does this.child?.kill('SIGTERM'); this.child = undefined; and start() immediately spawns a replacement. The stdout listener is registered as child.stdout.on('data', (chunk) => this.consumeStdout(chunk)) with no child !== this.child guard — unlike the close handler, which has one. Until SIGTERM lands, the dying helper's output is appended to the same this.buffer as the new helper's, which can split a JSON line (→ failHelper('helper_protocol')) or deliver a spurious toggle → an unintended call start. Add the identity guard and reset this.buffer per child.


Low / nits

  • GET /live/status mutates state: getStatus()buildStatus(true) can call stopForReadinessLoss() and end an active call. A GET with that side effect is surprising; consider a timer-driven readiness sweep or an explicit POST.
  • Tray menu strings in live-host/src/main/index.ts are hardcoded Chinese ('打开 Qwen Live 状态', '开始对话', …) while the Web Shell side ships proper EN/ZH i18n.
  • app.setLoginItemSettings({ openAtLogin: true, openAsHidden: true }) runs unconditionally when packaged — the app registers itself for launch-at-login with no UI to opt out.
  • if (!app.requestSingleInstanceLock()) { app.quit(); } falls through; the losing instance still registers all whenReady work.
  • ipcRenderer.send('live:audio:capture-error', { code }) sends a payload the main handler ignores, and helper stderr is read only to check a single chunk's size and otherwise discarded — both lose diagnostics.
  • readDiscoveryFile accepts value.protocol / value.nonce aliases the daemon never writes — dead compatibility shim.
  • LiveConversationWorkspace.recycleConversationDirectory moves deleted conversations into <root>/.trash/ and never reaps them — unbounded growth under ~/Documents/Qwen Code/Conversations/.trash.
  • resolveLiveProviderCredential validates endpoint/baseUrl tightly but passes model and voice through unvalidated from settings straight into session.update.
  • After any readiness change the host does a full WS teardown + reconnect (scheduleReadinessReconnectreconnectNow). Because attachHost rejects a second host while the old lease still reads OPEN (close 4009), this can race and cost a backoff cycle. handleHello already accepts a repeat hello on the same lease — re-sending host.hello would avoid the churn.
  • No server-side maxPayload on /live/host; the 64 KB checks run after the frame is fully buffered.

中文版

审查范围 9faa806...c9d8712(111 文件,+21439/−192)。本地跑了新增的 daemon 侧用例:113/115 通过run-qwen-serve-live.test.ts 的 2 个失败是我的临时 worktree 无法解析 @qwen-code/channel-github 导致的环境问题,不是 PR 缺陷。create-sub-sessionroutes/liveworkspace-managementworkspace-trustworkspace-trust-reconciler 全绿(149/149)。

做得好的地方managedRelocation: 'live-conversation' 的信任门禁很扎实(trusted 私有父进程 + 唯一绝对 allowedRoot + realpath 前后 owner-only 与 dev/ino 双重校验 + 直接子目录约束),folder-trust 绕过范围严格受限;modelPrompt 在回显前从 _meta 剥离并在子进程重新校验;每轮新鲜 nonce + 全等比较使得重放/注入的旧 nonce 无法触发新建对话;worker 只认 completedcreate_sub_session 工具结果并在打开时重新校验归属;apiKey 非枚举 + 错误文本脱敏;DashScope 域名白名单;Electron 侧隔离分区、全权限拒绝、Authorization 仅同源附加、导航限制在 daemon origin。

High

  1. /live/status 与每次状态广播都会同步调用未缓存的 loadSettings(),包括每个输出音频分片一次。 getStatus()readProviderReadiness()resolveLiveCredential()loadSettings()(同步 realpathSync×2 + 读多个 settings 文件 + 迁移)。setCallState/setTranscript 无变化检测且必定 broadcastState(),而 onOutputAudioDelta 每个音频分片都会 setCallState('speaking')。同时 useLiveVoice 每 1 秒轮询一次 /live/status,只要 ACP-HTTP 打开就生效——即使 liveVoice.enabled: false 且未安装 Host。建议:缓存 provider readiness、状态无变化时不广播、不可用时前端退避。
  2. create_sub_sessionsent 模式现在对所有调用方都会回写父会话通知,不只 Live。deliverSentCompletion 无条件调用,会向父会话注入 <task-notification> 并触发一次自动跟进 turn——对现有 Web Shell / channel / 定时任务用户属于新增的模型调用与可见输出。测试确认是有意为之,但 PR 描述未提及,且文件头对 'sent' 的注释仍是旧行为。
  3. live-host 的全部测试从不在 CI 运行。 packages/desktop 被根 workspaces 排除,ci.yml 只跑 check:desktop-isolationdesktop-release.yml 只构建 apps/electron,没有任何地方调用 live-host:test / live-host:typecheck——而这正是安全关键的一半。

Medium

  1. Live Voice 关闭时仍会把 daemon bearer token 落盘。 publishLiveDiscovery 只在 !resolveAcpHttpEnabled() 时提前返回;只要 runtimeBaseDir !== ~/.qwen 就会写含 token 的记录,与 liveVoiceEnabledAtBoot 无关。建议整体以 liveVoiceEnabledAtBoot 为门禁。
  2. 协议常量与 CUA 版本在 desktop 隔离边界两侧各存一份,无漂移检查。 单侧升级会静默造成永久 host_versionappshot 阻塞。建议在 scripts/check-desktop-isolation.js 加断言。
  3. awaitSentCompletionAcceptance 固定 100ms 重试、最长 30 分钟、无退避,持续 accepted:false 时约 1.8 万次 RPC。
  4. CommandMonitor.requestAccess() 可能出现两个 helper 进程共用一个 stdout 缓冲。 stdout 监听缺少 close 处理器里那样的 child !== this.child 身份判断,旧进程的输出会混入新缓冲,可能截断 JSON 行或触发误 toggle(误启动通话)。

LowGET /live/status 有副作用(可能停止通话);托盘菜单硬编码中文而 Web Shell 侧有完整 EN/ZH i18n;打包后无条件设置开机自启且无退出入口;单实例锁失败后未 return;capture-error payload 被忽略、helper stderr 被丢弃;readDiscoveryFileprotocol/nonce 别名是死代码;.trash 无回收;model/voice 未校验直传 provider;readiness 变化触发整连重连会与单 Host 租约(4009)竞争;/live/host 缺少服务端 maxPayload

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully. Run review failed. See workflow logs for details. A transient error is retried automatically; if you are seeing this, retry with @qwen-code /review. See workflow logs.

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. Remove the autofix/takeover label (or comment @qwen-code /takeover stop) to release.

中文说明

🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 autofix/takeover 标签(或评论 @qwen-code /takeover stop)即可释放。

@qwen-code-dev-bot

qwen-code-dev-bot commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

AutoFix round 5 finishedview run. See this round's report below.

中文说明

AutoFix 第 5 轮已完成 —— 查看运行。本轮报告见下方。

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed. Suggestions are inline. 2 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here. Not reviewed: chunks 18-20 — briefs not reached (test-only: session-coordinator.test.ts). Not reviewed: chunks 23-24 — briefs not reached (test-only: worker-workspace, provider-credentials tests). Not reviewed: chunks 29-31 — briefs not reached (test-only: run-serve-live, multi-workspace tests). Not reviewed: chunks 35-37 — briefs not reached (test-only: server.test.ts). Not reviewed: chunks 43-45 — briefs not reached (test-only: live-host tests). Not reviewed: 55 of the diff's 61 sections — its prompt was built, but no agent on record was launched with it. Not reviewed: verification and reverse audit — neither the verifier nor the reverse auditor was launched with a prompt this skill builds — the posted findings were ruled on, and the misses the rest of the review left were hunted, if at all, without the briefs this skill certifies against. [Critical] H1 — /live/status and every state broadcast do a synchronous, uncached loadSettings(), including once per output-audio delta. Still stands — not addressed by this diff. [Critical] H2 — create_sub_session with completion: 'sent' now notifies the parent for every caller, not just Live. deliverSentCompletion is called unconditionally. Still stands — not addressed by this diff. [Critical] H3 — The entire live-host test suite never runs in CI. Still stands — not addressed by this diff. [Critical] M1 — The daemon bearer token is written to disk even when Live Voice is disabled. Still stands — not addressed by this diff. [Critical] M2 — The wire protocol and CUA driver pin are duplicated across the desktop isolation boundary with no drift check. Still stands — not addressed by this diff. [Critical] M3 — awaitSentCompletionAcceptance retries at a fixed 100 ms for up to 30 minutes, no backoff. Still stands — not addressed by this diff.

中文说明

已审查。 建议见行内评论。 2 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。 未审查:chunks 18-20 — briefs not reached (test-only: session-coordinator.test.ts)。 未审查:chunks 23-24 — briefs not reached (test-only: worker-workspace, provider-credentials tests)。 未审查:chunks 29-31 — briefs not reached (test-only: run-serve-live, multi-workspace tests)。 未审查:chunks 35-37 — briefs not reached (test-only: server.test.ts)。 未审查:chunks 43-45 — briefs not reached (test-only: live-host tests)。 未审查:diff 61 个片段中的 55 个——它的 prompt 已构建,但没有任何 agent 有记录用它启动过。 未审查:验证与反向审计——验证 agent 与反向审计 agent 都没有用本 skill 构建的 prompt 启动——发布的发现即便被裁定过、评审其余部分遗漏的问题即便被搜寻过,也都缺失了本 skill 用以认证的 brief。 [Critical] H1 — /live/status and every state broadcast do a synchronous, uncached loadSettings(), including once per output-audio delta. Still stands — not addressed by this diff. [Critical] H2 — create_sub_session with completion: 'sent' now notifies the parent for every caller, not just Live. deliverSentCompletion is called unconditionally. Still stands — not addressed by this diff. [Critical] H3 — The entire live-host test suite never runs in CI. Still stands — not addressed by this diff. [Critical] M1 — The daemon bearer token is written to disk even when Live Voice is disabled. Still stands — not addressed by this diff. [Critical] M2 — The wire protocol and CUA driver pin are duplicated across the desktop isolation boundary with no drift check. Still stands — not addressed by this diff. [Critical] M3 — awaitSentCompletionAcceptance retries at a fixed 100 ms for up to 30 minutes, no backoff. Still stands — not addressed by this diff.

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/serve/capabilities.ts Outdated
Comment thread packages/cli/src/serve/live/provider-credentials.ts Outdated
Comment thread packages/desktop/apps/live-host/src/main/index.ts Outdated
Comment thread packages/cli/src/serve/live/conversation-workspace.ts Outdated
Comment thread packages/cli/src/serve/live/live-host-coordinator.ts
Comment thread packages/cli/src/serve/live/qwen-realtime-session.ts Outdated
Comment thread packages/desktop/apps/live-host/src/preload/index.ts
Comment thread packages/desktop/apps/live-host/src/renderer/main.ts Outdated
Qwen Code Autofix added 2 commits July 27, 2026 19:16
- Quote all strings in electron-builder.yml to fix yamllint CI failure
- Gate discovery publish on liveVoiceEnabledAtBoot to avoid writing
  bearer token to disk when Live Voice is disabled (M1)
- Add child identity guard to CommandMonitor stdout/stderr handlers
  to prevent stale helper output from corrupting the new buffer (M4)
- Add exponential backoff to sent-completion delivery retry (M3)
- Skip broadcastState when setCallState/setTranscript value is
  unchanged to reduce per-audio-delta overhead (H1)
- Document sent-mode completion notification in module docstring (H2)
- Remove dead protocol/nonce aliases from readDiscoveryFile
- Fix single instance lock fall-through with process.exit(0)
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下:

Review feedback addressed

Same-run verification repair

The previous commit (e0837791) was rejected by deterministic verification due to two test failures in packages/cli. This follow-up commit fixes both:

Failure Root cause Fix
capabilities-docs-contract.test.ts realtime_voice was added to CONDITIONAL_SERVE_FEATURES but not documented in the conditional features table in docs/developers/qwen-serve-protocol.md Added the realtime_voice row to the docs table
process-env-guard.test.ts packages/cli/src/serve/live/provider-credentials.ts accesses process.env as a whole-object fallback (options.env ?? process.env) but was not in the allowedProcessEnvAccesses allowlist Added an allowlist entry with reason "Embedded callers may omit the daemon-level environment argument"

Automated reviewer (CHANGES_REQUESTED)

Finding Decision Action
CI yamllint failure on electron-builder.yml Fixed (previous round) Quoted all string values with single quotes per .yamllint.yml rules
Scope: split into smaller PRs Escalated to maintainer Product/scope tradeoff requiring maintainer direction
Direction: first-party vs extension Escalated to maintainer Product direction call, not an autofix decision

@wenshao's review

High

Finding Decision Action
H1 — loadSettings() per audio delta Partially fixed (previous round) Added no-op guards to setCallState/setTranscript. Full memoization and frontend polling backoff deferred.
H2 — create_sub_session sent-mode notifies all callers Fixed (previous round) Updated module docstring to document the behavior applies to all callers.
H3 — live-host test suite never runs in CI Deferred CI infrastructure changes out of scope.

Medium

Finding Decision Action
M1 — Bearer token written when Live Voice disabled Fixed (previous round) Added early return gated on liveVoiceEnabledAtBoot.
M2 — Protocol drift check across isolation boundary Deferred Moderate scope, recorded for follow-up.
M3 — Fixed 100ms retry for 30 minutes, no backoff Fixed (previous round) Added exponential backoff capped at 30s.
M4 — Two helper processes feeding one stdout buffer Fixed (previous round) Added child !== this.child identity guard to stdout/stderr handlers.

Low / nits

Finding Decision Action
GET /live/status mutates state Deferred Route redesign out of scope.
Hardcoded Chinese tray strings Deferred i18n for Electron host, moderate scope.
Unconditional openAtLogin Escalated to maintainer Product decision on launch-at-login without opt-out UI.
Single instance lock fall-through Fixed (previous round) Added process.exit(0) after app.quit().
ipcRenderer.send payload ignored / stderr discarded Deferred Low-priority diagnostics.
readDiscoveryFile dead aliases Fixed (previous round) Removed dead protocol/nonce fallbacks.
.trash unbounded growth Deferred Requires reaping strategy.
model/voice unvalidated Deferred Moderate scope.
Readiness reconnect race Deferred Moderate scope.
No server-side maxPayload Deferred Moderate scope.

Verification

  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • vitest packages/cli src/serve/capabilities-docs-contract.test.ts — 1 passed
  • vitest packages/cli src/serve/process-env-guard.test.ts — 3 passed
中文说明

已处理的审查反馈

同轮验证修复

上一个提交(e0837791)因 packages/cli 中两个测试失败而被确定性验证拒绝。本次跟进提交修复了两者:

失败 根因 修复
capabilities-docs-contract.test.ts realtime_voice 已添加到 CONDITIONAL_SERVE_FEATURES,但未在 docs/developers/qwen-serve-protocol.md 的条件功能表中记录 在文档表中添加了 realtime_voice
process-env-guard.test.ts packages/cli/src/serve/live/provider-credentials.ts 以整体对象回退方式访问 process.envoptions.env ?? process.env),但未在 allowedProcessEnvAccesses 白名单中 添加了白名单条目,原因为"嵌入式调用方可能省略 daemon 级环境参数"

自动审查器(CHANGES_REQUESTED)

发现 决定 操作
CI yamllint 在 electron-builder.yml 上失败 已修复(上一轮) 按照 .yamllint.yml 规则,将所有字符串值用单引号括起
范围:拆分为更小的 PR 转交维护者 需要维护者方向决策的产品/范围权衡
方向:第一方 vs 扩展 转交维护者 产品方向决策,非 autofix 能判断

@wenshao 的审查

High

发现 决定 操作
H1 — 每个音频分片都调用 loadSettings() 部分修复(上一轮) setCallState/setTranscript 中添加了无变化跳过广播的判断。完整缓存和前端轮询退避延后。
H2 — create_sub_session sent 模式通知所有调用方 已修复(上一轮) 更新了模块文档字符串,明确说明该行为适用于所有调用方。
H3 — live-host 测试套件从不在 CI 运行 延后 CI 基础设施变更超出范围。

Medium

发现 决定 操作
M1 — Live Voice 关闭时仍写入 bearer token 已修复(上一轮) 添加了以 liveVoiceEnabledAtBoot 为门禁的提前返回。
M2 — 隔离边界两侧的协议漂移检查 延后 范围中等,已记录待跟进。
M3 — 固定 100ms 重试 30 分钟,无退避 已修复(上一轮) 添加了上限 30s 的指数退避。
M4 — 两个 helper 进程共用一个 stdout 缓冲 已修复(上一轮) 在 stdout/stderr 处理器中添加了 child !== this.child 身份判断。

Low / nits

发现 决定 操作
GET /live/status 有副作用 延后 路由重设计超出范围。
托盘菜单硬编码中文 延后 Electron host 的 i18n,范围中等。
无条件 openAtLogin 转交维护者 关于无退出 UI 的开机自启是产品决策。
单实例锁失败后未 return 已修复(上一轮) app.quit() 后添加 process.exit(0)
ipcRenderer.send payload 被忽略 / stderr 被丢弃 延后 低优先级诊断改进。
readDiscoveryFile 死的别名 已修复(上一轮) 移除了死的 protocol/nonce 回退。
.trash 无界增长 延后 需要回收策略。
model/voice 未校验 延后 范围中等。
Readiness 重连竞争 延后 范围中等。
/live/host 缺少服务端 maxPayload 延后 范围中等。

验证

  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过
  • vitest packages/cli src/serve/capabilities-docs-contract.test.ts — 1 通过
  • vitest packages/cli src/serve/process-env-guard.test.ts — 3 通过

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/模型 qwen3.8-max-preview

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

⚠️ This run could not certify that any of this diff was reviewed. Suggestions are inline. Not reviewed: chunks 1, 11, 12, 13, 14, 15, 18, 19, 20, 23, 24, 26, 29, 30, 31, 37, 40, 41, 42, 43, 44, 45 — test-only or docs chunks not covered by review agents. Not reviewed: the entire diff, the linked-issue fidelity pass, the whole-diff test-coverage check, the removed-behavior audit, the cross-file consistency pass, the build-and-test check, the invariant check (state, timers, collections) on packages/cli/src/serve/create-sub-session.ts, the invariant check (counters, return values, error taxonomies) on packages/cli/src/serve/create-sub-session.ts, the invariant check (config fields, early returns) on packages/cli/src/serve/create-sub-session.ts — its prompt was built, but no agent on record was launched with it. Not reviewed: verification and reverse audit — neither the verifier nor the reverse auditor was launched with a prompt this skill builds — the posted findings were ruled on, and the misses the rest of the review left were hunted, if at all, without the briefs this skill certifies against.

中文说明

⚠️ 本次运行无法证明这个 diff 的任何部分经过了审查。 建议见行内评论。 未审查:chunks 1, 11, 12, 13, 14, 15, 18, 19, 20, 23, 24, 26, 29, 30, 31, 37, 40, 41, 42, 43, 44, 45 — test-only or docs chunks not covered by review agents。 未审查:整个 diff、关联 issue 一致性检查、全 diff 测试覆盖检查、删除行为审计、跨文件一致性检查、构建与测试验证、不变量检查(状态、定时器、集合)(packages/cli/src/serve/create-sub-session.ts)、不变量检查(计数器、返回值、错误分类)(packages/cli/src/serve/create-sub-session.ts)、不变量检查(配置字段、提前返回)(packages/cli/src/serve/create-sub-session.ts)——它的 prompt 已构建,但没有任何 agent 有记录用它启动过。 未审查:验证与反向审计——验证 agent 与反向审计 agent 都没有用本 skill 构建的 prompt 启动——发布的发现即便被裁定过、评审其余部分遗漏的问题即便被搜寻过,也都缺失了本 skill 用以认证的 brief。

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/serve/live/live-session-coordinator.ts
Comment thread packages/cli/src/serve/live/provider-credentials.ts
Comment thread packages/cli/src/serve/live/live-session-coordinator.ts Outdated
Comment thread packages/cli/src/serve/routes/workspace-management.ts Outdated
Comment thread packages/cli/src/serve/run-qwen-serve.ts Outdated
Comment thread packages/desktop/apps/live-host/src/main/command-monitor.ts Outdated
Comment thread packages/cli/src/acp-integration/session/Session.ts

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

⚠️ This run could not certify that any of this diff was reviewed. Unresolved, please confirm: [Critical] packages/desktop/apps/live-host/src/main/index.ts:705 — microphone permission re-grant race with selfChecks.audioInput (from prior review comment 3660056652); could not fully trace the renderer self-check timing from the diff alone [Critical] packages/cli/src/serve/live/live-session-coordinator.ts:980 — coordinatorPromise cached via ??= but never cleared on rejection (from autofix review comment 3660833936); not re-checked in this pass Not reviewed: chunk agents launched as batches rather than individual roster blocks — briefs reached the batch agents but individual chunk/role prompts were not matched by check-coverage. Not reviewed: the entire diff, the linked-issue fidelity pass, the whole-diff test-coverage check, the removed-behavior audit, the cross-file consistency pass, the build-and-test check, the invariant check (state, timers, collections) on packages/cli/src/serve/create-sub-session.ts, the invariant check (counters, return values, error taxonomies) on packages/cli/src/serve/create-sub-session.ts, the invariant check (config fields, early returns) on packages/cli/src/serve/create-sub-session.ts — its prompt was built, but no agent on record was launched with it. Not reviewed: reverse audit — no auditor was launched with a prompt this skill builds — the pass that hunts what the rest of the review missed ran, if at all, without the method its brief carries. Not reviewed: verification — a verifier ran and opened its brief, but no agent was launched with the prompt the CLI built — the launch was written by hand, and the posted findings cannot be counted as verified against it.

中文说明

⚠️ 本次运行无法证明这个 diff 的任何部分经过了审查。 未决,请确认:[Critical] packages/desktop/apps/live-host/src/main/index.ts:705 — microphone permission re-grant race with selfChecks.audioInput (from prior review comment 3660056652); could not fully trace the renderer self-check timing from the diff alone [Critical] packages/cli/src/serve/live/live-session-coordinator.ts:980 — coordinatorPromise cached via ??= but never cleared on rejection (from autofix review comment 3660833936); not re-checked in this pass 未审查:chunk agents launched as batches rather than individual roster blocks — briefs reached the batch agents but individual chunk/role prompts were not matched by check-coverage。 未审查:整个 diff、关联 issue 一致性检查、全 diff 测试覆盖检查、删除行为审计、跨文件一致性检查、构建与测试验证、不变量检查(状态、定时器、集合)(packages/cli/src/serve/create-sub-session.ts)、不变量检查(计数器、返回值、错误分类)(packages/cli/src/serve/create-sub-session.ts)、不变量检查(配置字段、提前返回)(packages/cli/src/serve/create-sub-session.ts)——它的 prompt 已构建,但没有任何 agent 有记录用它启动过。 未审查:反向审计——没有审计 agent 是用本 skill 构建的 prompt 启动的——负责搜寻评审其余部分遗漏问题的这道工序,即便运行过,也缺失了 brief 承载的方法。 未审查:验证——有验证 agent 运行并打开了自己的 brief,但没有 agent 是用 CLI 构建的 prompt 启动的——启动 prompt 是手写的,发布的发现不能算作经它验证。

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/serve/create-sub-session.ts Outdated
Comment thread packages/cli/src/serve/create-sub-session.ts
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/100 轮)。改动内容与我反驳保留之处如下:

Review Feedback — Round Summary

Critical findings

ID Finding Decision
rc:3660056634 realtime_voice missing from protocol docs Resolved — already addressed in a prior commit on this branch; verified the doc row exists between the conditional-serve-features:start/end markers
rc:3660056643 process.env access unregistered in allowlist Resolved — already addressed in a prior commit; verified the entry in allowedProcessEnvAccesses
rc:3660056652 Microphone re-grant doesn't reset selfChecks.audioInput Resolved — added selfChecks.audioInput = true in the re-grant branch so publishState() and scheduleReadinessReconnect() broadcast the correct capability
rc:3660833936 coordinatorPromise cached on rejection Resolved — wrapped createOrResumeCoordinator in .catch() that resets context.coordinatorPromise = undefined before re-throwing, so subsequent delegations retry

Suggestions

ID Finding Decision
rc:3660056657 validateRootStats error messages say "root" for child dirs Resolved — added a label parameter (default 'root'); validateConversationDirectory passes 'directory'
rc:3660056661 failCall doesn't invoke onStop Resolved — added ++this.nextEpoch and the same onStop invocation pattern used by finishCall and stopForReadinessLoss
rc:3660056666 Dead if (activeResponseId === undefined) guard Resolved — removed the always-true guard; the three field clears are now unconditional, matching the non-cancelled branch
rc:3660056691 Unthrottled mousemove IPC Resolved — throttled with requestAnimationFrame and skip IPC when the computed boolean hasn't changed; mouseleave also resets the cached value
rc:3660056694 Missing CSS for disconnected/connecting/incompatible orb states Resolved.orb.disconnected and .orb.incompatible use red (like .orb.error); .orb.connecting uses blue pulse (like .orb.starting)
rc:3660833962 Bare maas.aliyuncs.com missing from host allowlist Resolved — added host === 'maas.aliyuncs.com' before the .endsWith check
rc:3660833968 Redundant .closed.then() close handler Resolved — removed; the onClose callback alone dispatches handleRealtimeClose
rc:3660833969 Off-by-one > vs >= in workspace count limit Resolved — changed to >= for consistency with all other call sites
rc:3660833976 cleanupLiveDiscovery missing try/catch on dynamic import Resolved — wrapped loadLiveDiscoveryRuntime() in try/catch; logs a warning and returns early if the import fails
rc:3660833983 CommandMonitor has no tests Declined — adding tests requires exporting a private method or mocking Electron IPC + child_process; out of scope for this review round, no defect identified
rc:3660833986 Trusted modelPrompt not guarded against /-prefix Declined — currently unreachable (Live delegation prompts start with <realtime_delegation>); adding a guard would be defense for an impossible scenario per AGENTS.md Simplicity First

Review-body Critical findings (H1–H3, M1–M3)

These six findings were posted in the review body (not as inline comments) and are marked "Still stands — not addressed by this diff." They describe pre-existing architectural concerns in the PR (uncached loadSettings(), unconditional deliverSentCompletion, live-host CI gap, bearer-token-on-disk, protocol drift, retry backoff). They are not regressions introduced by the latest diff and require broader design decisions; deferring to maintainer triage.

Verification

  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • vitest run (packages/cli): conversation-workspace.test.ts, live-host-coordinator.test.ts, live-session-coordinator.test.ts, provider-credentials.test.ts, qwen-realtime-session.test.ts — 96 passed
  • vitest run (packages/cli): workspace-management.test.ts, process-env-guard.test.ts — 107 passed
  • vitest run (packages/cli): server.test.ts — 850 passed
中文说明

审查反馈 — 本轮总结

Critical 发现

ID 发现 决定
rc:3660056634 realtime_voice 未添加到协议文档 已解决 — 已在本分支的先前提交中修复;已验证文档行存在于 conditional-serve-features:start/end 标记之间
rc:3660056643 process.env 访问未注册到白名单 已解决 — 已在先前提交中修复;已验证 allowedProcessEnvAccesses 中的条目
rc:3660056652 麦克风重新授权未重置 selfChecks.audioInput 已解决 — 在重新授权分支中添加了 selfChecks.audioInput = true,使 publishState()scheduleReadinessReconnect() 广播正确的能力状态
rc:3660833936 coordinatorPromise 在 reject 后被缓存 已解决 — 用 .catch() 包装 createOrResumeCoordinator,在重新抛出前将 context.coordinatorPromise 重置为 undefined,使后续委派可以重试

建议

ID 发现 决定
rc:3660056657 validateRootStats 错误消息对子目录也说"root" 已解决 — 添加了 label 参数(默认 'root');validateConversationDirectory 传入 'directory'
rc:3660056661 failCall 不调用 onStop 已解决 — 添加了 ++this.nextEpoch 和与 finishCallstopForReadinessLoss 相同的 onStop 调用模式
rc:3660056666 死代码 if (activeResponseId === undefined) 守卫 已解决 — 移除了永真的守卫;三个字段清除现在是无条件的,与非取消分支一致
rc:3660056691 未节流的 mousemove IPC 已解决 — 使用 requestAnimationFrame 节流,并在计算出的布尔值未变化时跳过 IPC;mouseleave 也重置缓存值
rc:3660056694 disconnected/connecting/incompatible orb 状态缺少 CSS 已解决.orb.disconnected.orb.incompatible 使用红色(同 .orb.error);.orb.connecting 使用蓝色脉冲(同 .orb.starting
rc:3660833962 裸域名 maas.aliyuncs.com 未在主机白名单中 已解决 — 在 .endsWith 检查前添加了 host === 'maas.aliyuncs.com'
rc:3660833968 冗余的 .closed.then() 关闭处理器 已解决 — 已移除;onClose 回调单独调度 handleRealtimeClose
rc:3660833969 工作区计数限制 >>= 的 off-by-one 已解决 — 改为 >=,与所有其他调用点一致
rc:3660833976 cleanupLiveDiscovery 动态导入缺少 try/catch 已解决 — 用 try/catch 包装 loadLiveDiscoveryRuntime();导入失败时记录警告并提前返回
rc:3660833983 CommandMonitor 没有测试 拒绝 — 添加测试需要导出私有方法或模拟 Electron IPC + child_process;本轮审查范围之外,未发现缺陷
rc:3660833986 可信 modelPrompt 未防护 / 前缀 拒绝 — 当前不可达(Live 委派提示以 <realtime_delegation> 开头);按照 AGENTS.md 简洁优先原则,添加防护属于为不可能的场景做防御

审查正文中的 Critical 发现(H1–H3、M1–M3)

这六项发现发布在审查正文中(非行内评论),标记为"仍然存在——本次 diff 未解决"。它们描述了 PR 中预先存在的架构问题(未缓存的 loadSettings()、无条件的 deliverSentCompletion、live-host CI 缺失、磁盘上的 bearer token、协议漂移、重试退避)。它们不是最新 diff 引入的回归,需要更广泛的设计决策;推迟到维护者分类处理。

验证

  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过
  • vitest run(packages/cli):conversation-workspace.test.ts、live-host-coordinator.test.ts、live-session-coordinator.test.ts、provider-credentials.test.ts、qwen-realtime-session.test.ts — 96 通过
  • vitest run(packages/cli):workspace-management.test.ts、process-env-guard.test.ts — 107 通过
  • vitest run(packages/cli):server.test.ts — 850 通过

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/模型 qwen3.8-max-preview

@LaZzyMan

LaZzyMan commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the confirmed round-5 findings in e84799c and synced the branch with current main in 4e7761c.

  • Restore now carries source metadata only through the load/resume request. The ACP child enables Live screen context from that metadata, so a second sessionSource control call can no longer fail an otherwise successful restore.
  • GET /live/setup performs only a read-only installed-host refresh; installation remains on explicit update/retry paths.
  • User workspace registration and promotion may contain the hidden Live runtime, while paths inside the Live runtime remain blocked.
  • Packaged Live Host builds now disable RunAsNode, NODE_OPTIONS, and CLI inspect fuses and enable embedded ASAR integrity plus only-load-from-ASAR.
  • Release verification now checks only the two top-level packaged apps and no longer requires DMG stapling that the workflow never performs.
  • Replacement microphone streams now receive the same ended monitoring as the initial stream and advance the capture generation so stale tracks cannot tear down a replacement.

Validation: ACP bridge 479 tests, affected CLI suites 508 tests, Live Host 67 tests, and all three affected typechecks pass. Local arm64 and x64 packages were inspected with electron-fuses and show the intended fuse states; the release find expression matches 2 top-level apps instead of 10 paths including helpers. No install, app launch, published release, or takeover automation was run.

@LaZzyMan

LaZzyMan commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ⚠️ incomplete — results unavailable - workflow run

The verification ran, but its result artifact could not be retrieved for publishing, so there is nothing to report here. The run log still has the agent output; re-run @qwen-code /verify for a fresh report.

中文 — 判定:⚠️ 未完成 · 结果不可用

验证已执行,但结果产物未能取回用于发布,因此此处没有可报告的内容。运行日志中仍有 agent 输出;如需完整报告请重新运行 @qwen-code /verify

Qwen Code · sandboxed verification

@LaZzyMan

LaZzyMan commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

本地合并前自查已完成并收敛:

  • 分别复核了架构/权限/进程边界、Realtime 与 backend 生命周期、首次安装与平台门禁、独立发布链路。
  • 发现并修复长时间 Live 通话中已完成输入转录与 handoff admission 记录持续累积的问题;回归测试先红后绿。
  • 验证通过:daemon Live 162/162、WebShell 59/59、Live Host 67/67、release workflow 4/4;CLI、WebShell、SDK typecheck,CLI build、ESLint、Prettier 均通过。
  • 再次确认 Live 运行时不含 cua-driver、mobile-mcp、Input Monitoring 或外置 MCP/进程捕获路径。
  • 已合并最新 main,当前 PR 无冲突。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

⚠️ The bot has neither a verdict nor a deferral on 4c0811d5ff21b8a5a69448c31cca8e685a07e63e — no APPROVED, CHANGES_REQUESTED, or COMMENTED review of its own. A DISMISSED one does not count: dismiss_stale_reviews voids the bot's approval on every push, which is exactly when a fresh one is needed. If this re-run was meant to review or approve, it did not, and an approval left by another account is a separate vote that does not count as the bot's own.

⚠️ 机器人在 4c0811d5ff21b8a5a69448c31cca8e685a07e63e既没有裁决也没有 defer —— 没有属于它自己的 APPROVEDCHANGES_REQUESTEDCOMMENTED 评审。DISMISSED 不算:dismiss_stale_reviews 会在每次推送时作废机器人的批准,而那恰恰是需要一次新批准的时刻。如果这次重跑本应评审或批准,那么它没有做到;而其他账号留下的批准是另一张票,不能算作机器人自己的。

The stage comments above were updated with the latest result. View workflow run.

上方各阶段评论已更新为最新结果。查看工作流运行

@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 — CI landed green after the review. ✅

@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, no blockers — unusually hardening-conscious for a feature this size: release workflow permissions/injection clean (publish gated to workflow_dispatch+main+repo check, SemVer-validated, SHA-pinned actions, installer trust ends at Developer ID+notarization not just GitHub), host-daemon auth nonce+timing-safe on loopback, audio bounded with backpressure, live-session isolation unforgeable, raw audio never persisted by default. Non-blocking P2s to address or document before/after merge: (1) experimental.liveVoice.apiKey stored plaintext in settings.json — move to OS keychain or warn loudly in docs; (2) Live tools (incl. capture_screen_context) bypass the permission pipeline with finalPermission allow — users can't deny screen capture per-call inside a Live session; add an on-screen capture indicator and document that permission rules don't apply; (3) create_thread/send_message_to_thread queue agent work in any registered workspace from voice alone — release-note callout. P3s: add stapler validate in inspectApp for CI parity; document QWEN_LIVE_DIAGNOSTICS PCM dumps.

@LaZzyMan
LaZzyMan added this pull request to the merge queue Aug 5, 2026
Merged via the queue into QwenLM:main with commit a5c637b Aug 5, 2026
60 of 61 checks passed
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.21.6.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autofix/skip Not eligible for the scheduled autofix agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants