qwen-code/docs/design/telemetry-runtime-client-attribution-design.md
易良 4ec0371e61
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
feat(telemetry): attribute daemon-spawned sessions via channel (daemon/desktop) (#8670)
* feat(telemetry): add runtime and client attribution to usage statistics

Daemon-spawned sessions (TS/Python/Java SDKs, Web Shell, Tauri desktop
shell) all report properties.channel=ACP because the daemon spawns plain
`qwen --acp` children with no channel argument.

Add two stable dimensions to the default usage-statistics payload:

- properties.runtime (cli|acp|daemon): the daemon marks every child it
  spawns with QWEN_CODE_SERVE=1 (ACP session children and channel
  workers), which takes precedence over the channel-based acp heuristic.
- properties.client (vscode|desktop|desktop-shell, omitted when unknown):
  derived from the --channel value (VSCode/desktop) and the
  QWEN_CODE_DESKTOP env marker set by the Tauri desktop shell.

properties.channel and app.channel are unchanged; the new keys are purely
additive. See docs/design/telemetry-runtime-client-attribution-design.md.

Issue: #8660

* chore(telemetry): use Qwen Team copyright header on new attribution files

New files use "Copyright 2026 Qwen Team" per repository convention
(see e.g. packages/core/src/utils/file-identity.ts), not the legacy
Google LLC header inherited from the gemini-cli fork.

* refactor(telemetry): report daemon attribution via channel, no new payload keys

Per maintainer feedback: extend properties.channel instead of adding
properties.runtime/client. getChannel() has no behavioral consumers
(telemetry is its only reader), so channel is a pure reporting dimension.

- resolve the ACP channel fallback from the daemon env markers:
  QWEN_CODE_DESKTOP -> desktop-shell, QWEN_CODE_SERVE -> daemon, else ACP
- drop the runtime/client payload keys and the runtime-attribution module
- keep the QWEN_CODE_SERVE spawn marker at both daemon spawn sites

* refactor(cli): report Tauri desktop-shell sessions as channel=desktop

Maintainer decision: the Tauri desktop shell shares the desktop client
identity with the Electron app instead of getting a separate
desktop-shell value. Also document the SDK split: SDK query() spawns
the CLI directly with --channel=SDK (never daemon, unchanged), while
the SDK daemon-client entrypoints ride the daemon bridge and report
channel=daemon.

* fix(cli): preserve daemon channel attribution

* fix(cli): keep attribution markers out of home env bootstrap
2026-08-07 18:13:03 +00:00

5.5 KiB
Raw Blame History

Telemetry: Daemon 会话的 channel 归因

配套 issue: #8660 基于 2026-08-07 对 qwen-code main 分支的代码复核

1. 背景

默认 usage-statisticsqwen-logger RUM载荷中只有一个入口维度 properties.channel,它来自 --channel 标志:

  • VS Code 伴生插件直接启动 qwen --acp --channel=VSCode
  • Electron 桌面端直接启动 qwen --acp --channel=desktop
  • TS/Python/Java SDK 的主入口(query())直接 spawn CLIstream-json 模式),自带 --channel=SDK
  • --acp 未显式指定 channel 时回退为 ACPpackages/cli/src/config/config.ts 的 ACP fallback

qwen servedaemon的 spawn 工厂启动的是不带 channel 的 qwen --acp 子进程(packages/acp-bridge/src/spawnChannel.ts),因此经 daemon 承载的会话——SDK 的 daemon 客户端入口(如 TS SDK 的 DaemonClient/DaemonSessionClient、Web Shell、Tauri 桌面 shell——全部上报为 ACP无法区分。Tauri shell 启动 daemon 时设置了 QWEN_CODE_DESKTOP=1packages/desktop-shell/src-tauri/src/runtime.rs),但 telemetry 从未读取该变量。

app.channel 是来自 ~/.qwen/source.json安装来源,与入口归因是不同概念,不应被重载。

2. 方案

复用现有 properties.channel 维度,不新增 payload 键。getChannel() 经复核没有任何行为消费方(只有 telemetry 读取channel 是纯上报维度,扩展其取值无副作用。

daemon 在每个子进程环境中设置 QWEN_CODE_SERVE=1 标记,覆盖两个 spawn 点:

  • packages/acp-bridge/src/spawnChannel.tsACP 会话子进程)
  • packages/cli/src/serve/channel-worker-supervisor.tschannel workerworker 内 channels/base/AcpBridge.ts 通过 {...process.env} 继续继承该标记)

CLI 的 ACP channel 回退(packages/cli/src/config/acp-channel-fallback.ts)按标记解析:

条件 channel 取值
显式 --channel=X X(不变,显式参数优先)
QWEN_CODE_DESKTOP=1 desktopTauri shell 会话,与 Electron 桌面端同一客户端身份)
QWEN_CODE_SERVE=1 daemondaemon 承载的会话)
其余 ACP(直接三方 ACP 启动,不变)

归因矩阵:

场景 properties.channel
交互/无头 CLI (无)
三方直接 ACP ACP
VS Code 伴生 VSCode
Electron 桌面端(直连,不走 daemon desktop
SDK query() 直连TS/Python/Java不走 daemon SDKSDK 自带,不变)
daemon 会话SDK daemon 客户端、Web Shell 等) daemon
daemon 会话Tauri desktop shell desktop
docker/podman sandbox 内的 daemon 会话 继承外层 daemon/desktop
daemon channel worker worker 名(如 feishu不变

3. 为什么 daemon 会话不能像 VS Code 那样直接传 --channel

VS Code 伴生插件自己拥有 spawn:一个客户端 = 一个专属子进程,所以能传 --channel=VSCode

daemon 的桥接模型不同(packages/acp-bridge/src/bridge.ts一个 bridge一个 workspace至多一个 qwen --acp 子进程,所有客户端的会话经 connection.newSession() 多路复用到同一个进程上,共享进程/OAuth/FileReadCache。因此

  • spawn 时不知道哪个客户端会连进来(子进程可能预热);
  • 同一进程内同时跑着不同客户端的会话,进程级参数无法表达会话级身份;
  • qwen-logger payload 是进程级构造,进程级 channel 无法按会话区分客户端。

环境标记 + 回退解析因此是当前模型下唯一的进程级归因手段。

4. Schema 影响与兼容性

  • 零新增键。properties.channel 仅新增一个可能取值:daemondesktop 是既有取值Tauri shell 会话并入其中)。
  • app.channel 语义与取值不变;显式 --channel 的既有取值(VSCode/desktop/SDK/worker 名)不变。
  • QWEN_CODE_SERVE 为信息性标记,不含敏感信息;不进入 SCRUBBED_CHILD_ENV_KEYSdenylist 语义不受影响)。
  • docker/podman sandbox 会显式透传 QWEN_CODE_SERVE / QWEN_CODE_DESKTOP,避免 sandbox 内子进程退回 ACP;项目 .env/settings.env 不能设置这两个标记,避免 workspace 伪造客户端归因。

5. 后续工作(不在本次范围)

按 SDK/Web Shell 细分客户端需要会话级归因:各客户端在创建会话时经由 daemon 自我声明,现有 qwen.session.source meta → config.setSessionSource(sourceType, sourceId) 管道(#8155 为生命周期钩子引入)是自然的扩展点。当前 Web Shell 主会话仅使用 sourceType: 'default'。由于默认 payload 是进程级的,会话级身份落到 telemetry 还需要会话维度的支持,届时再决定是否引入独立的 client 键。