Skip to content

feat(core): Align GenAI telemetry with ARMS - #7536

Merged
doudouOUC merged 4 commits into
QwenLM:mainfrom
doudouOUC:agent/align-genai-arms-telemetry
Jul 23, 2026
Merged

feat(core): Align GenAI telemetry with ARMS#7536
doudouOUC merged 4 commits into
QwenLM:mainfrom
doudouOUC:agent/align-genai-arms-telemetry

Conversation

@doudouOUC

@doudouOUC doudouOUC commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This PR aligns the first batch of Qwen Code LLM, tool, and agent span attributes with the fields whose names, types, and meanings agree between the version-pinned OpenTelemetry GenAI semantic conventions and Alibaba Cloud ARMS LLM Trace. It adds canonical operation, provider, conversation, response model, output type, finish reason, cache usage, tool-call, and agent-description attributes while preserving the existing span topology and private fields that have no exact standard equivalent.

Provider response models, finish reasons, cache usage, and token counts now retain their source provenance. OpenAI-compatible responses that report only a total token count no longer receive a synthetic input/output split, and Anthropic partial streams retain input/cache usage known at message_start. Provider and operation resolution is URL-boundary-safe, stays consistent with internal DashScope routing, recognizes Azure OpenAI, does not expose credentials or arbitrary proxy hosts, and only emits output type when the effective Gemini or Vertex request carries an unambiguous response format.

Exact-equivalent private aliases and invalid GenAI aliases are removed instead of dual-written. The change also documents the pinned standards baseline, migration mapping, omission rules, deferred scope, and the ARMS resource attribute users must configure for automatic GenAI application recognition.

Why it's needed

Qwen Code previously emitted a mixture of private aliases, incomplete GenAI attributes, and several keys whose names or semantics did not match both ARMS and the OpenTelemetry GenAI conventions. This made the same trace require different queries across backends and could present estimated token splits or request-model fallbacks as provider-reported response data. A single truthful field contract makes traces portable and queryable in ARMS without misrepresenting provenance.

Reviewer Test Plan

How to verify

  • Export telemetry to a file while running an LLM to tool to LLM flow against the fake OpenAI server. Confirm the JSONL spans contain the shared GenAI fields, preserve the provider tool-call ID, retain only private fields without exact standard replacements, and omit the removed aliases.
  • Exercise OpenAI, Azure OpenAI, Anthropic, Gemini, Vertex AI, and Qwen OAuth request paths. Confirm provider and operation resolution, actual response models, multi-candidate finish reasons, explicit zero tokens, omission of input/output usage for total-only responses, cache read/create usage, partial stream failures, cancellation, and idle timeout behavior follow the documented omission rules.
  • Exercise Core and ACP tool scheduling and subagent tracing. Confirm provider call IDs take priority with internal-ID fallback, and agent descriptions are propagated and surrogate-safe when truncated.
  • Local verification for the latest alignment and review updates completed: 766 targeted Core tests and the GenAI telemetry integration test passed, together with Core build/typecheck, targeted ESLint, and bundle generation.

Evidence (Before & After)

N/A — telemetry schema, tracing behavior, tests, and documentation only; there is no TUI change.

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested
🐧 Linux ⚠️ not tested

Environment (optional)

macOS, Node.js v22.22.3, npm 10.9.8, local fake OpenAI server, and the OpenTelemetry file exporter.

Risk & Scope

  • Main risk or tradeoff: OpenTelemetry GenAI semantic conventions are still in Development status, so this implementation is pinned to commit 2e994c6d59a93bb4fc1752c5378eedb9b8e14d6b and future upgrades require a new compatibility review.
  • Not validated / out of scope: Online ARMS staging validation was not performed because credentials were unavailable. Full GenAI span naming, CLIENT span kind, retry topology, sensitive message/tool payloads, embeddings, and fields without a truthful wire-request source remain out of scope. The local full build reaches the CLI package but is blocked because the installed Ink types do not include the selection extensions used by current main; Core build and typecheck, targeted lint and tests, bundle generation, and the telemetry integration test pass.
  • Breaking changes / migration notes: LLM spans replace qwen-code.model with gen_ai.request.model, response_id with gen_ai.response.id, and the bare input_tokens, output_tokens, and cached_input_tokens aliases with their gen_ai.usage.* equivalents. Root tool spans replace tool.name with gen_ai.tool.name. Invalid aliases gen_ai.usage.cached_tokens, gen_ai.server.time_to_first_token, and gen_ai.usage.reasoning_tokens are removed. Interaction spans, logs, hooks, blocked-on-user spans, and private fields without an exact shared standard equivalent retain their existing schemas.

Linked Issues

N/A

中文说明

本 PR 做了什么

本 PR 将 Qwen Code 第一批 LLM、工具和 Agent Span 属性与固定版本的 OpenTelemetry GenAI 语义规范及阿里云 ARMS LLM Trace 中名称、类型和含义一致的字段对齐。它新增规范的 operation、provider、conversation、response model、output type、finish reason、cache usage、tool call 和 agent description 属性,同时保留现有 Span 拓扑以及没有精确标准等价项的私有字段。

Provider 返回的响应模型、结束原因、缓存用量和 token 数现在会保留数据来源。对于只返回 token 总量的 OpenAI 兼容响应,不再合成 input/output 拆分;Anthropic 部分流也会保留 message_start 时已知的 input/cache usage。Provider 与 operation 解析采用安全的 URL hostname 边界匹配,与内部 DashScope 路由保持一致,支持识别 Azure OpenAI,不会暴露凭据或任意代理主机名;仅当实际 Gemini 或 Vertex 请求携带明确且唯一的响应格式时才写入 output type。

与标准字段完全等价的私有别名和无效的 GenAI 别名会直接移除,不进行双写。本次变更还记录了固定的标准版本、迁移映射、省略规则、延期范围,以及用户为 ARMS 自动识别 GenAI 应用所需配置的资源属性。

为什么需要这个 PR

Qwen Code 此前混合上报私有别名、不完整的 GenAI 属性,以及名称或语义无法同时匹配 ARMS 和 OpenTelemetry GenAI 规范的若干字段。这导致同一条 Trace 在不同后端需要不同查询方式,也可能把估算的 token 拆分或请求模型 fallback 表现成 provider 实际返回的数据。统一且真实的字段契约可让 Trace 跨后端使用,并能在 ARMS 中直接查询而不歪曲数据来源。

Reviewer 测试计划

如何验证

  • 使用 fake OpenAI server 跑通 LLM 到工具再到 LLM 的流程并将 telemetry 导出到文件。确认 JSONL Span 包含三方共同的 GenAI 字段、保留 provider tool-call ID、只保留没有精确标准替代项的私有字段,并且不再出现已删除的别名。
  • 覆盖 OpenAI、Azure OpenAI、Anthropic、Gemini、Vertex AI 和 Qwen OAuth 请求路径。确认 provider 与 operation 解析、真实 response model、多 candidate finish reasons、显式零 token、仅有 total 时省略 input/output usage、cache read/create usage、流式部分失败、取消和 idle timeout 均遵循文档中的省略规则。
  • 覆盖 Core 与 ACP 的工具调度和 subagent tracing。确认 provider call ID 优先并在缺失时回退到内部 ID,同时确认 agent description 可正确传递并在截断时不会破坏代理对。
  • 最新字段对齐与 review 修复的本地验证结果:766 个定向 Core 测试和 GenAI telemetry 集成测试通过,同时 Core build/typecheck、定向 ESLint 与 bundle 生成通过。

前后对比证据

N/A——本 PR 仅涉及 telemetry schema、tracing 行为、测试和文档,没有 TUI 变更。

测试平台

OS 状态
🍏 macOS ✅ 已测试
🪟 Windows ⚠️ 未测试
🐧 Linux ⚠️ 未测试

环境(可选)

macOS、Node.js v22.22.3、npm 10.9.8、本地 fake OpenAI server,以及 OpenTelemetry file exporter。

风险与范围

  • 主要风险或取舍:OpenTelemetry GenAI 语义规范仍处于 Development 状态,因此实现固定到 commit 2e994c6d59a93bb4fc1752c5378eedb9b8e14d6b;未来升级需要重新进行兼容性审查。
  • 未验证或不在范围内:由于没有凭据,未进行 ARMS 在线 staging 验证。完整 GenAI Span 命名、CLIENT SpanKind、retry topology、敏感的消息或工具 payload、Embedding,以及缺少真实 wire request 数据源的字段均不在本次范围内。本地全仓 build 可执行到 CLI package,但已安装的 Ink 类型不包含当前 main 使用的 selection 扩展,因此在此处被阻塞;Core build 与 typecheck、定向 lint 与测试、bundle 生成和 telemetry 集成测试均通过。
  • 破坏性变更与迁移说明:LLM Span 使用 gen_ai.request.model 替代 qwen-code.model,使用 gen_ai.response.id 替代 response_id,并使用对应的 gen_ai.usage.* 字段替代裸 input_tokensoutput_tokenscached_input_tokens 别名;根 Tool Span 使用 gen_ai.tool.name 替代 tool.name。无效别名 gen_ai.usage.cached_tokensgen_ai.server.time_to_first_tokengen_ai.usage.reasoning_tokens 已移除。Interaction Span、日志、hook、blocked-on-user Span,以及没有精确共同标准等价项的私有字段继续保留现有 schema。

关联 Issue

N/A

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC
doudouOUC marked this pull request as ready for review July 22, 2026 17:03
@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: This is a feature enhancement aligning telemetry span attributes with the OpenTelemetry GenAI semantic conventions and Alibaba Cloud ARMS LLM Trace fields. The motivation is concrete — existing traces use a mix of private aliases (qwen-code.model, response_id, bare input_tokens) and incomplete GenAI attributes that aren't portable across backends, and estimated token splits can misrepresent provenance. Not a bug fix, so no reproduction is expected.

Direction: Telemetry standardization is a natural evolution of the existing tracing infrastructure. The broader ecosystem is actively adding structured OTel attributes (Claude Code's CHANGELOG shows ongoing OTel attribute work — message.uuid, tool_source, workflow.run_id, etc.). Aligning with the GenAI semantic conventions and ARMS makes traces queryable in Alibaba Cloud monitoring without backend-specific queries. That said, this touches telemetry schema — escalating to the maintainer for direction confirmation since telemetry is a sensitive area.

Size: This PR touches core paths (packages/core/src/**). Breakdown: ~665 production logic lines, ~1244 test lines, ~154 docs lines across 30 files. Production lines exceed 500 — flagging for maintainer awareness per the core module policy. Total production lines also exceed the 1000-line large PR advisory threshold when including docs. This is a feat-type PR, so no hard block applies.

Approach: The scope feels focused on one concern (GenAI attribute alignment). The new gen-ai-provider.ts module (180 lines) centralizes provider/operation resolution with proper hostname boundary checking — worth a close look in code review. The WeakMap-based usage provenance tracking in gen-ai-usage.ts is an elegant solution that avoids changing the public response type. The design doc documenting the pinned standards baseline and migration mapping is a good addition. No unrelated changes or drive-by refactors visible. The two follow-up commits (removing estimated token splits, addressing review feedback) are well-scoped refinements. One question from the prior review remains relevant: the PR removes existing aliases (qwen-code.model, response_id, tool.name) rather than dual-writing — has the downstream impact of this breaking change been considered (e.g., existing dashboards or queries relying on the old field names)?

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:这是一个功能增强 PR,将 telemetry span 属性与 OpenTelemetry GenAI 语义规范及阿里云 ARMS LLM Trace 字段对齐。动机具体——现有 trace 混合使用私有别名(qwen-code.modelresponse_id、裸 input_tokens)和不完整的 GenAI 属性,无法跨后端移植,且估算的 token 拆分可能歪曲数据来源。不是 bug 修复,因此不需要复现。

方向:Telemetry 标准化是现有 tracing 基础设施的自然演进。更广泛的生态系统正在积极添加结构化 OTel 属性(Claude Code 的 CHANGELOG 显示了持续的 OTel 属性工作——message.uuidtool_sourceworkflow.run_id 等)。与 GenAI 语义规范和 ARMS 对齐可使 trace 在阿里云监控中直接查询,无需后端特定查询。不过,这涉及 telemetry schema——由于 telemetry 是敏感领域,转交 maintainer 确认方向。

规模:本 PR 触及核心路径(packages/core/src/**)。明细:约 665 行生产逻辑、约 1244 行测试、约 154 行文档,共 30 个文件。生产行数超过 500——按核心模块政策标记供 maintainer 知悉。这是 feat 类型 PR,不适用硬性阻止。

方案:范围聚焦于一个关注点(GenAI 属性对齐)。新的 gen-ai-provider.ts 模块(180 行)集中了 provider/operation 解析,具有正确的主机名边界检查。gen-ai-usage.ts 中基于 WeakMap 的 usage 来源追踪是优雅的解决方案,避免了更改公共响应类型。记录固定标准版本和迁移映射的设计文档是好的补充。未见无关改动或顺手重构。两个后续提交(移除估算 token 拆分、处理 review 反馈)范围合理。

进入代码审查 🔍

Qwen Code · qwen3.8-max-preview

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code Review

Independent proposal: For aligning GenAI telemetry with ARMS/OTel GenAI conventions, I would: (1) define target attribute names from the pinned spec commit, (2) create a pure provider/operation resolution function with safe hostname matching, (3) update each content generator to emit standard attributes with provenance tracking (distinguishing provider-reported vs estimated values), (4) update session-tracing to use standard names and remove invalid aliases, (5) add comprehensive tests, (6) document the migration mapping and deferred scope.

Comparison: The PR matches this approach closely and executes it well. Two follow-up commits since the last review refined the implementation: one removed the synthetic input/output token split for total-only OpenAI responses, and the other addressed review feedback (Anthropic partial-stream usage retention, cache-creation token tracking, Azure OpenAI recognition).

The implementation is clean and well-structured:

  • gen-ai-provider.ts (180 lines): Pure function for provider/operation resolution with proper hostname boundary checking (isHostOrSubdomain prevents suffix attacks like api.deepseek.com.attacker.example). Resolution priority is clear: Qwen OAuth → DashScope proxy match → hostname → env key hint → protocol fallback. Azure OpenAI is now recognized via openai.azure.com and services.ai.azure.com. No credentials or arbitrary proxy hosts are exposed. The normalizedEndpoint helper strips trailing slashes for reliable proxy comparison.

  • gen-ai-usage.ts (30 lines): WeakMap-based provenance tracking attached to usage objects without changing their public shape. Records cachedInputTokensReported and cacheCreationInputTokens (Anthropic-specific). This lets the telemetry layer distinguish estimated vs. provider-reported values while allowing GC to follow the usage object. Elegant.

  • session-tracing.ts: Removes old aliases (qwen-code.model, response_id, tool.name on LLM/tool spans) and invalid GenAI aliases. Adds standard attributes with proper guards — token counts only emitted as standard GenAI fields when they're non-negative safe integers. Cache read tokens are gated on cachedInputTokensReported so estimated cache values aren't presented as provider-reported. Surrogate-safe truncation for agent descriptions via the renamed truncateSpanText.

  • OpenAI converter: modelVersion now comes from openaiResponse.model / chunk.model (not request-model fallback). The hasTokenBreakdown guard prevents synthesizing an input/output split when only total_tokens is reported — this was the key fix in the second commit. Usage provenance is attached via setGenAiUsageProvenance.

  • Anthropic generator: message_start input and cache usage is now attached to the first subsequent yielded chunk, so partial stream failures retain provider-reported usage. Cache-creation tokens are tracked with a cacheCreationTokensReported flag. The buildAnthropicUsageMetadata discriminator logic (cache_creation reported → Anthropic semantics, sum all three; else OpenAI-style) is well-documented with a comment explaining the earlier false-positive bug.

  • Agent tool: agentDescription is now propagated from subagentConfig.description through the invocation spec to the subagent span, where it's emitted as gen_ai.agent.description with surrogate-safe truncation.

No critical blockers. No AGENTS.md violations.

Unit tests: All changed test files pass — 192 telemetry + 1072 core (Anthropic, OpenAI, logging, scheduler, agent) + 391 CLI Session = 1655 tests total, all green.

Real-Scenario Testing

Ran the built bundle against a local fake OpenAI server with QWEN_TELEMETRY_OUTFILE file export. The LLM span output confirms the PR's claims:

=== qwen-code.llm_request ===
  finish_reason: STOP
  gen_ai.conversation.id: af42155e-db7a-4681-9c0f-42978771328d
  gen_ai.operation.name: chat
  gen_ai.provider.name: openai
  gen_ai.request.model: request-model-test
  gen_ai.response.finish_reasons: ['STOP']
  gen_ai.response.id: chatcmpl-test-7536
  gen_ai.response.model: provider-model-actual
  gen_ai.usage.cache_read.input_tokens: 5
  gen_ai.usage.input_tokens: 15
  gen_ai.usage.output_tokens: 8
  Removed aliases still present: NONE (correct)

Key observations:

  • gen_ai.response.model is provider-model-actual (from the server response), NOT request-model-test (the request model) — provenance is correct
  • gen_ai.usage.cache_read.input_tokens: 5 — cache read properly reported from prompt_tokens_details.cached_tokens
  • All removed aliases (qwen-code.model, response_id, gen_ai.usage.cached_tokens, gen_ai.server.time_to_first_token, gen_ai.usage.reasoning_tokens, bare input_tokens/output_tokens/cached_input_tokens) are confirmed absent
  • gen_ai.conversation.id matches the session ID
  • gen_ai.response.finish_reasons is an array ordered by candidate index
中文说明

代码审查

独立方案: 要将 GenAI telemetry 与 ARMS/OTel GenAI 规范对齐,我会:(1) 从固定规范提交定义目标属性名,(2) 创建带安全主机名匹配的纯 provider/operation 解析函数,(3) 更新每个 content generator 以发出带来源追踪的标准属性,(4) 更新 session-tracing 使用标准名称并移除无效别名,(5) 添加全面测试,(6) 记录迁移映射和延期范围。

对比: PR 与此方案高度一致且执行良好。上次审查以来的两个后续提交改进了实现:一个移除了仅有 total 的 OpenAI 响应的合成 input/output 拆分,另一个处理了 review 反馈(Anthropic 部分流 usage 保留、cache-creation token 追踪、Azure OpenAI 识别)。

实现干净且结构合理:

  • gen-ai-provider.ts(180 行):纯函数实现 provider/operation 解析,具有正确的主机名边界检查。解析优先级清晰。Azure OpenAI 现在通过 openai.azure.comservices.ai.azure.com 识别。不暴露凭据或任意代理主机名。

  • gen-ai-usage.ts(30 行):基于 WeakMap 的来源追踪,记录 cachedInputTokensReportedcacheCreationInputTokens(Anthropic 特有)。设计优雅。

  • session-tracing.ts:移除旧别名和无效 GenAI 别名。Cache read token 以 cachedInputTokensReported 为门控,确保估算值不会被表现为 provider 实际报告值。

  • OpenAI 转换器hasTokenBreakdown 守卫防止在仅有 total_tokens 时合成 input/output 拆分——这是第二个提交的关键修复。

  • Anthropic 生成器message_start 的 input 和 cache usage 现在附加到第一个后续产出的 chunk,部分流失败时保留 provider 报告的 usage。

无关键阻塞项。无 AGENTS.md 违规。

单元测试: 所有变更的测试文件通过——共 1655 个测试,全部绿色。

真实场景测试

使用构建后的 bundle 对本地 fake OpenAI server 运行,启用 QWEN_TELEMETRY_OUTFILE 文件导出。LLM span 输出确认了 PR 的声明:provider 响应模型正确使用、cache read 正确报告、所有已移除别名确认不存在、gen_ai.conversation.id 匹配 session ID。

Qwen Code · qwen3.8-max-preview

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Confidence: 3/5 — clean review across every stage, but the core-module size escalation (665 production lines) and telemetry-scope sensitivity need a maintainer's sign-off.

This is a well-executed telemetry alignment PR that has improved meaningfully since the last review. The two follow-up commits addressed the right things: removing the synthetic token split for total-only OpenAI responses was a real provenance fix (not just hygiene), and the Anthropic partial-stream usage retention closes a genuine data-loss gap on aborted streams.

Going back to my independent proposal — the PR matches it closely and exceeds it in a few places. The WeakMap approach for usage provenance is better than what I would have proposed (adding fields to the type or threading provenance through parameters). The hasTokenBreakdown guard is a clean way to avoid the estimated-split problem without special-casing every provider. The buildAnthropicUsageMetadata discriminator logic — using cache_creation_input_tokens presence as the primary signal for Anthropic vs OpenAI-on-Anthropic semantics — is well-reasoned and the comment explaining the earlier false-positive bug is exactly the kind of "why" documentation that prevents regressions.

The real-scenario test confirms every claim: response model provenance is correct (provider-model-actual, not the request model), cache read is properly gated on provider-reported values, all removed aliases are confirmed absent, and the conversation ID ties spans to the session.

The breaking change (removing qwen-code.model, response_id, tool.name without dual-write) is documented and scoped — interaction spans, logs, hooks, and blocked-on-user spans retain their existing schemas. The design doc's migration table makes the mapping explicit. The PR is honest about what it doesn't cover (span naming, CLIENT span kind, retry topology, sensitive payloads, embeddings).

Why 3/5 and not higher: policy, not code quality. This touches core telemetry schema at 665 production lines — the core-module policy requires maintainer awareness at 500+, and telemetry is a sensitive area where direction confirmation is appropriate. The code itself would be a 4/5 (solid, well-tested, no blockers).

中文说明

置信度:3/5 — 每个阶段审查均干净,但核心模块规模升级(665 行生产代码)和 telemetry 领域敏感性需要 maintainer 签核。

这是一个执行良好的 telemetry 对齐 PR,自上次审查以来有实质性改进。两个后续提交解决了正确的问题:移除仅有 total 的 OpenAI 响应的合成 token 拆分是真正的来源修复(而非仅仅是卫生问题),Anthropic 部分流 usage 保留关闭了中止流上的真实数据丢失缺口。

回到我的独立方案——PR 与之高度一致且在若干方面超越。WeakMap 方法用于 usage 来源比我提议的更好。hasTokenBreakdown 守卫是避免估算拆分问题的干净方式。buildAnthropicUsageMetadata 判别逻辑——使用 cache_creation_input_tokens 存在作为 Anthropic 与 OpenAI-on-Anthropic 语义的主要信号——推理合理,解释早期误报 bug 的注释正是防止回归的那种"为什么"文档。

真实场景测试确认了每一项声明。

为什么是 3/5 而不是更高:政策原因,非代码质量。代码本身是 4/5。

Qwen Code · qwen3.8-max-preview

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

⏸️ Deferring to @yiliang114 — this PR touches core telemetry schema (665 production lines across packages/core/src/telemetry/, content generators, and tool scheduler) and introduces a breaking change to span attribute names. The code review is clean at the updated head (c1123192) and the real-scenario test confirms correct behavior, but two policy triggers require a maintainer's call:

  1. Core module size: 665 production logic lines in core paths (up from 549 at the prior review — the two follow-up commits added Anthropic partial-stream usage retention and cache-creation tracking) → maintainer awareness per the two-tier gate.
  2. Telemetry scope: Schema changes to tracing attributes affect downstream dashboards and queries — direction confirmation is appropriate.

The PR removes qwen-code.model, response_id, and tool.name from spans without a dual-write period. If existing ARMS dashboards or alerting rules depend on those field names, this is a breaking change that needs coordination. The code quality is high (would be 4/5 on merit alone) — this is purely a policy escalation. Needs a human call on this one.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

⚠️ Downgraded from Approve to Comment: CI failing: Test (ubuntu-latest, Node 22.x). Reviewed.

— qwen3.7-max via Qwen Code /review

@doudouOUC doudouOUC self-assigned this Jul 22, 2026

@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. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

— qwen3.7-max via Qwen Code /review

Comment thread packages/core/src/telemetry/session-tracing.ts Outdated
@gwinthis

Copy link
Copy Markdown
Collaborator

Review & Local Verification Report

代码审查

设计评价:系统性的遥测对齐工程。 本 PR(30 文件,+1697/-119)将 GenAI 遥测字段与 ARMS(Application Real-Time Monitoring Service)对齐。

变更范围:

  • 3 个设计文档(field alignment、LLM request timing、subagent spans)
  • 遥测核心:gen-ai-provider.tsgen-ai-usage.tssession-tracing.ts
  • 内容生成器:OpenAI pipeline、Anthropic generator、logging generator
  • 转换器:OpenAI/Anthropic converter 的 usage 提取
  • 工具调度:coreToolScheduler.tsagent.ts 的 span 追踪
  • 集成测试:gen-ai-telemetry.test.tsfake-openai-server.ts
  • CLI 层:ACP Session 的遥测集成

设计亮点:

  • 设计文档先行,明确字段映射和 span 层级
  • 跨 provider 统一 usage 提取(OpenAI/Anthropic/Gemini)
  • subagent span 追踪支持多 agent 工作流的可观测性
  • 集成测试用 fake OpenAI server 验证端到端遥测

结论

LGTM。 大规模但系统化的遥测对齐,设计文档完整,跨 provider 统一,测试覆盖充分。

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

Review Summary

High-quality telemetry alignment PR. The OTel GenAI semantic convention mapping is precise, the WeakMap usage-provenance design is elegant, and provider hostname parsing has proper boundary checks. Test coverage is comprehensive.

One concern needs author confirmation before merge:

(1) Breaking attribute removal without dual-write (session-tracing.ts): qwen-code.model, response_id, tool.name, gen_ai.usage.cached_tokens, gen_ai.server.time_to_first_token, gen_ai.usage.reasoning_tokens, gen_ai.agent.id, and subagent gen_ai.provider.name are all removed in one shot. The design doc says "removed without a dual-write period." If any existing ARMS/Grafana dashboards depend on these keys, they break immediately. Suggest at minimum a prominent breaking-change note in the release, or one release of dual-emit.

(2) cacheCreationInputTokens guard order (session-tracing.ts ~L680): Number.isSafeInteger(x) && x !== undefined — the !== undefined is redundant since isSafeInteger(undefined) is already false. Consider swapping order to match the cachedInputTokens guard style above.

(3) gen_ai.response.model omission when provider doesn't return it (converter.ts:354): correct per spec, but some OpenAI-compatible proxies never return a model field, leaving the ARMS column permanently empty. Worth a doc note.

(4) finishReasons Map uses last-wins semantics for same candidate index (loggingContentGenerator.ts ~L638): reasonable but worth a comment clarifying the override behavior for multi-chunk scenarios.

(5) gen_ai.output.type only maps Gemini/Vertex (gen-ai-provider.ts:145): OpenAI response_format: json_object is not mapped. Design doc covers this, noting for future extensibility.

Minor: truncateSpanError is now a single-line wrapper of truncateSpanText — consider exporting the latter directly. Hostname list in gen-ai-provider.ts is hardcoded (12+ domains) — could be registry-driven later.

— qwen3.8-max-preview via Qwen Code /review

@doudouOUC
doudouOUC enabled auto-merge July 23, 2026 03:54

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

The main alignment direction looks right, but the provider mismatch above affects supported configurations and should be fixed before merge. I also left one non-blocking partial-stream telemetry issue inline.

Comment thread packages/core/src/telemetry/gen-ai-provider.ts Outdated
@wenshao

wenshao commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Local Build & Real-Flow Verification (maintainer merge reference)

Independent local verification of head 726618f — real build, changed-file test suites, a full end-to-end telemetry run through the built CLI, and a base-vs-PR A/B on the actual exported telemetry.log. This complements the existing code reviews (which did not include local run evidence). Environment: macOS, Node 22.23.1, isolated git worktree at the PR head with a separate base bundle built at the merge-base 32c491fc6.

TL;DR — ✅ verified, merge-ready

Build clean, all exercised suites green, and the emitted spans match the documented field contract exactly — every added gen_ai.* key present, every removed alias gone, private compat fields retained. The one pre-merge item is the release-note breaking-change callout already raised by @yiliang114; the A/B below makes those removed keys concrete.

1. Build & tests

build and test verification

Stage Result
npm run build:packages (tsc, all workspaces) + npm run bundle ✅ exit 0 (PR & base bundles)
Unit — changed files (vitest, packages/core) 10 files, 1257 tests
Unit — packages/cli ACP telemetry (Session.test.ts) 391 tests
Integration — cli/gen-ai-telemetry.test.ts (real E2E: built dist/cli.js + fake OpenAI server) 1 test
Security — URL-boundary-safe provider resolution ✅ spoof / look-alike rejected

Upstream CI Test (ubuntu-latest, Node 22.x) is also green at this head (the ci-bot's earlier "downgrade" reflected a since-resolved transient failure).

2. Real-flow A/B — the field contract on live telemetry

I ran an identical LLM → tool → LLM turn through the base bundle (PR not applied) and the PR bundle, then read the spans back out of the exported telemetry.log. The server returns request-model request-model, a distinct response-model provider-model-tool, a provider tool-call id provider-call-123, and cached_tokens: 3 — so every migrated field is observable.

telemetry A/B before vs after

Confirmed on the wire, exactly as the design doc specifies:

  • Added (OTel GenAI / ARMS aligned): gen_ai.operation.name, gen_ai.provider.name, gen_ai.conversation.id, gen_ai.response.model, gen_ai.usage.cache_read.input_tokens on the LLM span; gen_ai.operation.name=execute_tool, gen_ai.tool.name, gen_ai.tool.type=function, gen_ai.tool.call.id on the tool span.
  • Removed (private aliases / invalid GenAI keys), no dual-write: qwen-code.model, response_id, gen_ai.server.time_to_first_token, gen_ai.usage.cached_tokens, gen_ai.usage.reasoning_tokens, tool.name.
  • Provenance honored: request/response model split is real (request-model vs provider-model-tool); tool-call id prefers the provider id; private compat keys (input_tokens, cached_input_tokens, ttft_ms, thoughts_token_count, tool.call_id) are retained.

3. Provider resolution is spoof-proof

Exercising the built resolveGenAiProviderName directly (bottom panel of image #1): a real subdomain x.dashscope.aliyuncs.comdashscope, but the spoof dashscope.aliyuncs.com.attacker.com and the look-alike xdashscope.aliyuncs.com both correctly fall through to the default rather than being mis-attributed. Unknown hosts fall back to the protocol provider with no host/credential leak; qwen-oauth and apiKeyEnvKey paths resolve as documented.

Notes for author / reviewers

  1. @yiliang114 point (2) is cosmetic-only, not a bug. In session-tracing.ts the cacheCreationInputTokens guard Number.isSafeInteger(x) && x !== undefined has a redundant !== undefined (Number.isSafeInteger(undefined) === false), so it is harmless — no functional impact. Fine to tidy or leave.
  2. Breaking-change note (@yiliang114 point (1)): the removed keys above are the concrete blast radius. Recommend a prominent release-note callout so any ARMS/Grafana dashboard keyed on qwen-code.model / response_id / tool.name / the three invalid gen_ai.usage.* keys is migrated. Intentional per design doc — flagging for visibility, non-blocking.
  3. Minor: the PR body's "9 GenAI telemetry integration tests" — the new integration file gen-ai-telemetry.test.ts contains 1 end-to-end test; the broader GenAI coverage lives in the unit suites (gen-ai-provider 57, session-tracing 131, etc.). Wording only.

Verdict: build + unit + integration + real-flow A/B all pass; emitted telemetry matches the field contract. LGTM from a verification standpoint.

中文版本(点击展开)

本地构建与真实流程验证(维护者合并参考)

对 head 726618f 的独立本地验证:真实构建、改动文件的测试套件、通过构建后 CLI 的完整端到端遥测运行,以及基线 vs PR 在真实导出 telemetry.log 上的 A/B 对比。这补齐了现有代码评审所缺少的本地运行证据。环境:macOS、Node 22.23.1,在 PR head 处使用隔离 git worktree,并在 merge-base 32c491fc6 单独构建基线 bundle。

结论 — ✅ 已验证,可合并

构建干净,所执行的套件全绿,导出的 span 完全符合文档定义的字段契约——新增的每个 gen_ai.* 字段都在、被移除的每个别名都已消失、私有兼容字段全部保留。唯一的合并前事项是 @yiliang114 已提出的 release note 破坏性变更说明;下方 A/B 将这些被移除的字段具体呈现。

1. 构建与测试

阶段 结果
npm run build:packages(tsc 全 workspace)+ npm run bundle ✅ exit 0(PR 与基线两套 bundle)
单元测试——改动文件(vitest, packages/core 10 文件, 1257 个测试
单元测试——packages/cli ACP 遥测(Session.test.ts 391 个测试
集成测试——cli/gen-ai-telemetry.test.ts(真实 E2E:构建后的 dist/cli.js + fake OpenAI server) 1 个测试
安全——URL 边界安全的 provider 解析 ✅ 伪造/近似域名均被拒绝

该 head 上游 CI Test (ubuntu-latest, Node 22.x) 同样为绿(ci-bot 之前的"降级"源自一次已恢复的临时失败)。

2. 真实流程 A/B——活体遥测上的字段契约

我以完全相同LLM → tool → LLM 回合分别跑基线 bundle(未应用 PR)与 PR bundle,再从导出的 telemetry.log 读回 span。server 返回请求模型 request-model、不同的响应模型 provider-model-tool、provider tool-call id provider-call-123、以及 cached_tokens: 3——因此每个迁移字段都可观测。

线上确认,与设计文档完全一致:

  • 新增(OTel GenAI / ARMS 对齐): LLM span 上的 gen_ai.operation.namegen_ai.provider.namegen_ai.conversation.idgen_ai.response.modelgen_ai.usage.cache_read.input_tokens;tool span 上的 gen_ai.operation.name=execute_toolgen_ai.tool.namegen_ai.tool.type=functiongen_ai.tool.call.id
  • 移除(私有别名 / 无效 GenAI 键),无双写: qwen-code.modelresponse_idgen_ai.server.time_to_first_tokengen_ai.usage.cached_tokensgen_ai.usage.reasoning_tokenstool.name
  • 来源保真: 请求/响应模型拆分真实(request-model vs provider-model-tool);tool-call id 优先 provider id;私有兼容键(input_tokenscached_input_tokensttft_msthoughts_token_counttool.call_id)均保留。

3. Provider 解析可防伪

直接调用构建后的 resolveGenAiProviderName(图 1 底部面板):真实子域 x.dashscope.aliyuncs.comdashscope,而伪造的 dashscope.aliyuncs.com.attacker.com 与近似的 xdashscope.aliyuncs.com 都正确地回落到默认值,不会被误判。未知 host 回落到协议 provider,且不泄漏 host/凭据;qwen-oauthapiKeyEnvKey 路径按文档解析。

给作者 / 评审的备注

  1. @yiliang114 第 (2) 点仅为书写冗余,非 bug。 session-tracing.tscacheCreationInputTokens 的判断 Number.isSafeInteger(x) && x !== undefined!== undefined 冗余(Number.isSafeInteger(undefined) === false),无功能影响,可清理也可保留。
  2. 破坏性变更说明(@yiliang114 第 (1) 点): 上述被移除的键即具体影响面。建议在 release note 显著提示,以便迁移任何依赖 qwen-code.model / response_id / tool.name / 三个无效 gen_ai.usage.* 键的 ARMS/Grafana 看板。按设计文档为有意为之,此处仅作提示,不阻塞。
  3. 小项: PR 描述中"9 个 GenAI telemetry 集成测试"——新的集成文件 gen-ai-telemetry.test.ts 实际含 1 个端到端测试;更广的 GenAI 覆盖在单元套件里(gen-ai-provider 57、session-tracing 131 等)。仅措辞问题。

结论: 构建 + 单元 + 集成 + 真实流程 A/B 全部通过;导出遥测符合字段契约。从验证角度 LGTM。

Verified locally on an isolated worktree at 726618f with a base bundle at merge-base 32c491fc6; screenshots are real terminal output rendered from the actual test runs and telemetry export.

doudouOUC and others added 2 commits July 23, 2026 14:09
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Addressed all three currently unresolved review threads in c112319: standardized the safe-integer guard order, aligned DashScope internal and Azure OpenAI provider resolution with regression coverage, and preserved provider-reported Anthropic message_start usage on partial stream failure without synthesizing output. Also removed the 70/30 total-only token split and the remaining private LLM token aliases in 4752b91. Verification: 766 targeted Core tests, Core build/typecheck, targeted ESLint, bundle generation, and the GenAI telemetry JSONL integration test all pass. The local full-repository build remains blocked only by the installed Ink types lacking current main selection extensions, as documented in the PR body.

@wenshao

wenshao commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

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.

Reviewed — no blockers. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here.

— qwen3.7-max via Qwen Code /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 — the provider-resolution and partial-stream follow-ups are addressed, and the current CI checks are green.

@doudouOUC
doudouOUC added this pull request to the merge queue Jul 23, 2026
Merged via the queue into QwenLM:main with commit 3617397 Jul 23, 2026
112 of 114 checks passed
@doudouOUC
doudouOUC deleted the agent/align-genai-arms-telemetry branch July 23, 2026 07:45
chiga0 pushed a commit that referenced this pull request Jul 23, 2026
* feat(core): align GenAI telemetry with ARMS

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

* fix(core): remove estimated token usage splits

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

* fix(core): address GenAI telemetry review feedback

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
yiliang114 added a commit to he-yufeng/qwen-code that referenced this pull request Jul 23, 2026
)

* fix(cli): correct queued message display style and ordering

Mid-turn steer messages (user input queued while the model is
responding) had two display bugs:

1. They rendered with notification styling (● icon) instead of
   user-input styling (> prefix) because accept() added them to
   UI history as MessageType.NOTIFICATION.

2. They appeared below the model's reply because accept() was
   only called in the finally block after the entire response
   stream completed, appending the user message after all model
   response items.

Fix: use MessageType.USER with sentToModel: true for steer
messages, and settle the steer input on the first stream event
(after the user-content push lands but before model-response
events are committed to UI history). Pass steer inputs through
to recursive sendMessageStream calls so all takeSteerInput paths
benefit from early settlement. Add a WeakSet guard to
settleSteerInput for idempotency across recursive invocations.

* test(core): add ordering test for early steer settlement

Verify that accept() is called after the first stream event is
pulled but before subsequent events reach the consumer, pinning
the settle-before-content timing that ensures queued user
messages render above the model's reply.

* fix(cli): use sentToModel: false for steer messages, address review

- Use sentToModel: false instead of true: steer messages are injected
  into an existing tool-result turn, not standalone user turns.
  sentToModel: true would make isRealUserTurn() count them as real
  turns, inflating the rewind turn index.
- Remove unnecessary as HistoryItemWithoutId cast.
- Add post-cleanup assertion in ordering test to verify the WeakSet
  guard prevents double-settlement.

* fix(cli): align resumed mid-turn steer display with live session (#7381)

Resume path now renders mid_turn_user_message as MessageType.USER with
sentToModel: false, matching the live-session styling. Add a comment
documenting the intentional sentToModel: false choice.

* fix(cli): exclude steer messages from user-turn filters (#7381)

Steer messages (sentToModel: false) were counted as real user turns by
five downstream consumers that filter on type === 'user' without checking
sentToModel, breaking cancel auto-restore, telemetry turn count, prompt
recall, away-recap thresholds, and resume collapse boundaries.

Add sentToModel !== false guards at each site.

* test(cli): add coverage for sentToModel !== false guards (#7381)

* test(cli): add coverage for sentToModel !== false guard in input-history filter (#7381)

* test(cli): add coverage for sentToModel !== false guard in YOLO turn-count telemetry (#7381)

* fix(cli): restore corrupted docs and classify steer items as synthetic (#7381)

* fix(docs): restore corrupted autogenerated input names in GitHub Action docs (#7381)

* fix(cli): deduplicate findLastUserItemIndex and add steerInput forwarding test (#7381)

* fix(cli): keep code-block copy numbering continuous across steer items (#7381)

* test(core): add Hook continuation steerInput forwarding test

Verify that steerInput is forwarded through the Stop-hook
continuation path and settled early on the first content event
of the continuation turn, matching the existing Steer
continuation coverage.

* fix(cli): sync selection test fixtures with ink FrameCell/ReadonlyFrame types (#7381)

* fix(core): align cron day wildcard semantics (#7464)

Co-authored-by: destire-mio <248462155+destire-mio@users.noreply.github.com>

* feat(core): keep completed background agents resident (#7426)

* feat(core): keep background agents resident

* fix(core): harden background continuation boundaries

* docs(core): move per-spawn cleanup comment to subagentDispose

The comment describing the per-spawn cleanup (which stays undefined on
the fork-resume path) had drifted above the launchModel declaration,
where it no longer applied and could mislead readers. Relocate it to the
subagentDispose assignment in the non-fork branch it actually documents.

* fix(core): close finishing window and release resident on error in background GOAL path

- Non-worktree GOAL completion drained the message queue but never called
  registry.beginFinishing(), unlike the worktree path. A send_message racing
  the terminal transition could be accepted (status still running,
  finishingAgents empty) and then orphaned by complete(). Call beginFinishing()
  after the empty drain to reject the racing message instead.
- The completion catch block never reset keepResident, so a throw from
  patchAgentMeta/registry.complete left the runtime resident but finalized as
  failed — a zombie that cleanupRuntime never reclaimed. Reset keepResident in
  the catch so the finally block disposes it.

---------

Co-authored-by: Claude <noreply@anthropic.com>

* ci(autofix): continue environment-specific fixes (#7444)

* ci(autofix): continue environment-specific fixes

* docs(autofix): align verification wording

* docs(autofix): require bundle before integration tests

* docs(autofix): scope surrogate verification rules

* docs(autofix): require focused tests before integration checks

* docs(autofix): clarify review verification guidance

* fix(acp-bridge): close prompt-terminal follow-ups from the PR #7400 self-review (#7453)

* fix(acp-bridge): close prompt-terminal follow-ups from PR #7400 self-review

Keep a removed RUNNING prompt visible to the teardown flush via a removed flag so its terminal still publishes when the session closes before the agent cooperates; gate broadcastTurnError's session turn-state mutation to running prompts; propagate the typed PromptDeadlineExceededError from the pre-dispatch abort check; document the deadline FIFO-release overlap trade-off, the trailing prompt_cancelled after flush, and the result.then/finally ordering invariant; route the dedup log to the debug channel; drop the prompt-deadline re-export that pulled the bridge into a leaf module.

Fixes #7451

* test(acp-bridge): cover promote-then-remove-then-settle duplicate completed guard (#7453)

---------

Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>

* fix(core): strip Qwen-internal daemon secrets from agent-spawned child env (#7256)

* fix(core): strip Qwen-internal daemon secrets from agent-spawned child env

Shell subprocesses (and the monitor tool and stdio MCP servers) inherited
the full daemon process.env, including QWEN_SERVER_TOKEN (the serve-daemon
bearer credential), so an agent-run command like printenv QWEN_SERVER_TOKEN
could read an internal secret. Add a shared sanitizeChildEnv() that removes
Qwen-internal daemon/server tokens (QWEN_SERVER_TOKEN, QWEN_DAEMON_TOKEN)
before spawning, and apply it at the shell child_process + PTY paths,
monitor.ts, and the mcp-client stdio transport.

The denylist is deliberately narrow: it does NOT strip third-party
credentials (GH_TOKEN, AWS_*, NPM_TOKEN, ...) that real shell workflows
legitimately inherit -- only Qwen-internal secrets. Exported from the
package root so the desktop denylists can consolidate onto it later.

Fixes #6601.

* test(core): cover daemon-secret stripping on monitor and mcp-client spawn sites

* test(core): replace process.env instead of mutating in shell sanitization tests

The file restores process.env by reference in afterEach, so in-place key
mutations leaked into later tests. Use the replacement pattern already used
by setupConflictingPathEnv.

* docs(core): align JSDoc @param names with actual function signatures (#7492)

Fix 6 instances where JSDoc @param tags had drifted from their
corresponding function signatures — parameters were renamed, removed,
or undocumented over time but the doc blocks were not updated.

Closes #7446

* feat(serve): support forced MCP reconnects (#7488)

* feat(serve): support forced MCP reconnects

* test(serve): cover forced MCP reconnect options

---------

Co-authored-by: 克竟 <dingbingzhi.dbz@alibaba-inc.com>

* fix(cli): insert newline on Shift+Enter and stop streaming thinking-block flicker (#7397)

* fix(cli): re-push Kitty keyboard flags onto the alternate screen in VP mode

In VP mode the app renders on the alternate screen (`alternateScreen: true`),
but the Kitty keyboard progressive-enhancement flags were pushed only once at
startup on the main screen. The Kitty spec tracks these flags per screen
buffer, so the alternate screen's stack stays empty and the terminal never
reports modifiers: Shift+Enter arrives as a bare Enter (submit) or, when the
terminal emits an ESC-prefixed variant, as an orphaned Escape that trips the
empty-buffer double-Esc rewind prompt — so Shift+Enter can never insert a
newline in VP mode even on Kitty-capable terminals (e.g. cmux).

Re-push the flags onto the alternate screen right after Ink enters it (Ink
writes the enter-alt-screen sequence synchronously inside render(), so the
push is correctly ordered). Ink discards the alternate screen and its flag
stack on unmount, leaving the startup main-screen push balanced by the
existing disableKittyProtocol() on cleanup.

Generated with AI

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

* fix(cli): stabilize streaming thinking block height to stop flicker

The pending "Thinking…" block renders the tail of the reasoning stream in a
content-sized box. As the model emits paragraph separators, a blank line
enters and leaves the tail window (and `trimEnd` drops trailing blanks), so the
visible line count oscillates and the block flickers 2→3→5 rows during
streaming.

Track the tallest height the block has reached for the current thought and
never render fewer rows than that (capped at the streaming window size),
padding at the top so the newest line stays pinned to the bottom. The tracker
resets when streaming ends or when the buffer shrinks (a new thought replaced
it), so height is monotonic within a thought without leaking across thoughts.

Generated with AI

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

* fix(cli): decode xterm modifyOtherKeys Shift/Ctrl/Alt+Enter so it inserts a newline

Terminals such as Ghostty report Shift+Enter as the xterm modifyOtherKeys
sequence `ESC [ 27 ; <mods> ; <key> ~` (e.g. `ESC [ 27 ; 2 ; 13 ~`) when the
Kitty keyboard protocol is not negotiated — which is the default, since Kitty
detection does not always succeed. Two bugs kept this from inserting a newline:

1. The CSI-u parser read the leading `27` marker as the key code (matching the
   Escape key code 27) instead of the real key code in the third parameter, so
   with Kitty enabled Shift+Enter was mistaken for Escape and tripped the
   double-Esc rewind prompt.
2. The reassembly path that stitches readline's shredded CSI fragments back
   together was gated behind `kittyProtocolEnabled`, so with Kitty disabled the
   `ESC [ 27 ; 2 ;` head plus the stray `13~` tail leaked into the composer as
   literal text and no newline was inserted.

Decode the third parameter as the real key code for the `27;…~` form, and route
those sequences through the reassembly buffer even when Kitty is off (only the
`ESC [ 27` marker opts in, so keys readline already parses cleanly are
untouched). Shift/Ctrl/Alt+Enter now insert a newline in both VP and non-VP
mode regardless of Kitty negotiation.

Generated with AI

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

* fix(cli): anchor VP viewport to the top until a conversation turn exists

On a fresh VP-mode session the virtualized list holds the banner plus startup
notices (tips / MOTD / info), so it is longer than one item. Keying the initial
scroll anchor off list length alone selected scroll-to-end, which pinned the
banner to the bottom of the full-height viewport and left the top half of the
screen blank.

Anchor to the top until there is an actual conversation turn (a user/user_shell
history item or a pending response), then resume scroll-to-end so the latest
output stays in view. Startup notices no longer count as content that forces
bottom alignment.

Generated with AI

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

* fix(cli): stabilize streaming thinking window against availableTerminalHeight drift

The grow-only streaming thinking window still flickered because its line cap was
derived from availableTerminalHeight. While a thought streams the terminal keeps
constrainHeight on, so availableTerminalHeight (and the derived maxLines) drifts
up and down as sibling pending content grows, and the grow-only clamp
`min(maxLines, …)` shrank the block whenever it dipped.

Use a constant window height (MAX_STREAMING_THINKING_VISUAL_LINES) for the
pending window instead. The window is only a few lines, so a fixed cap cannot
meaningfully overflow (VP scrolls anyway), and the height stays stable while
still growing monotonically within a thought.

Generated with AI

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

* Revert "fix(cli): anchor VP viewport to the top until a conversation turn exists"

This reverts commit fbe86a9e159b75ea1f5b689cc327599c9dc91090.

* fix(cli): guard modifyOtherKeys detection against keypresses without a sequence

The modifyOtherKeys prefix check ran on every keypress, but some synthetic
keypresses (and the useKeypress test harness) emit a key with no `sequence`,
so `key.sequence.startsWith(...)` threw an unhandled rejection. Use optional
chaining so a missing sequence is simply not a modifyOtherKeys start.

Generated with AI

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

* test(cli): mock pushKittyProtocolFlags in gemini.test.tsx kitty mock

The kittyProtocolDetector mock omitted the newly added pushKittyProtocolFlags
export. Add it so the mock stays in sync with the real module and a VP-mode
startup path exercised through this suite cannot hit an undefined call.

Generated with AI

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

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(web-shell): open singleton subagent details (#7495)

Co-authored-by: ytahdn <ytahdn@gmail.com>

* fix(web-shell): avoid redundant git status requests (#7496)

Co-authored-by: ytahdn <ytahdn@gmail.com>

* fix(agent): ignore empty working_dir placeholders (#7343)

* fix(agent): ignore empty working_dir placeholders

* test(agent): align empty working_dir expectations

* feat(prompts): allow overriding core identity via QWEN_SYSTEM_IDENTITY_MD (#7478)

* feat(prompts): update prompts.ts for QWEN_SYSTEM_IDENTITY_MD

* feat(prompts): update prompts.test.ts for QWEN_SYSTEM_IDENTITY_MD

* fix(prompts): address CR on QWEN_SYSTEM_IDENTITY_MD

Keep getDefaultCoreIdentitySentence private, fail loud on path
resolution errors, use trimEnd, and resolve identity only on the
default-prompt branch.

* test(prompts): align identity override tests with CR feedback

Sample default identity from live prompt, cover trimEnd trailing
whitespace, and assert homedir resolution failures throw.

---------

Co-authored-by: 易良 <1204183885@qq.com>

* fix(cli): yield to single-slot background agents (#7258)

Co-authored-by: hogeheer <267467744+hogeheer499-commits@users.noreply.github.com>

* docs(autofix): require evidenced pre-commit verification, not a bare "verified" (#7486)

* docs(autofix): require evidenced pre-commit verification, not a bare "verified"

The skill already said to run build/typecheck/lint/Vitest before
committing, but softly — and #7408 committed a fix with a TS error the
gate then rejected while its summary claimed "verified all 3 commits".
A self-assessment the gate contradicts wastes a whole round.

Strengthens the address-review contract from "run the checks" to:
- actually run them, do not assert them from reading the diff;
- if typecheck or a touched-package test fails, do NOT commit — treat
  the feedback as unresolved (failure.md);
- end address-summary.md with a `## Verification` section listing each
  command run and its result; a bare "verified" is not acceptable.

The framing is structural, not etiquette: the deterministic gate re-runs
the same commands and discards the round on any failure, so skipping them
only moves the rejection later. Pinned by a test so it cannot soften back.

This is the checkable half of "audit before committing" — the
undirected/reverse-audit-until-clean practice does not transfer to an
unsupervised agent (no verifiable stopping condition, and it worsens the
timeouts seen on large PRs), but "run the gate's own checks first and
show the evidence" does.

* fix(autofix): clarify Verification section precedes collapsed Chinese translation (#7486)

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>

* feat(autofix): stop a PR that fails to push for N rounds in a row (#7482)

* feat(autofix): stop a PR that fails to push for N rounds in a row

Under takeover the round cap is 100, which is right for a PR that needs
many PRODUCTIVE rounds. It is wrong for one that fails every round: #6723
ran 7 consecutive failed rounds (3 agent timeouts at 50 min, 4 gate
rejections whose fix broke tests) over 8 hours, heading for round 100,
because it is a 5700-line, 47-file, 5-day-old PR racing a fast-moving
main — every round re-resolves a conflict it cannot finish or that fails
the gate. Retrying at the same per-round budget will not converge; a
human has to rebase or split it.

Adds CONSECUTIVE_FAILURE_CAP (5), distinct from the total round cap. The
handoff step already runs only when a round did NOT push, so it counts
the unbroken run of prior failure markers — stopping at the first push
("Addressed the latest review feedback") or legitimate no-op ("no
changes needed"), either of which proves progress and resets the streak.
At the cap it forces the terminal round even under takeover, with a
handoff that names the real fix (rebase/split, then /retry). Cause-
agnostic: a timeout and a gate rejection both count.

* fix(autofix): address review feedback on consecutive-failure circuit breaker (#7482)

- Fix misleading comment: the walk is oldest-first (API order) with
  reset-on-success, not newest-first with early stop
- Prefer the already-fetched ic.json over a redundant gh api call,
  falling back to the API only when the file is missing
- Filter eval markers by re-arm window (win=) so pre-re-arm failures
  do not immediately re-terminate a re-armed PR
- Add test coverage for the MARK_ROUND == MAX_ROUNDS guard and for
  window-scoped streak counting

* fix(autofix): exempt transient model errors from consecutive-failure breaker (#7482)

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>

* feat(core): restore background agent roster (#7459)

* feat(core): restore background agent roster

* fix(web-shell): add list_agents to TOOL_DISPLAY_NAMES

The new list_agents core wire tool was added to core's ToolNames but not
to the web-shell TOOL_DISPLAY_NAMES map, causing toolFormatting.drift.test.ts
to fail (expected ['list_agents'] to deeply equal []). Add the missing
'ListAgents' display-name entry so the browser panel shows a friendly name
instead of the raw wire name and the drift guard passes.

* fix(cli): reload old-session background agents on failed resume rollback

When /resume fails after core has swapped but before the UI swap, the catch
block rolls core back to the old session via startNewSession(oldSessionId).
However the forward path already called resetBackgroundStateForSessionSwitch,
which cleared the old session's in-memory background agents. The rollback did
not reload them, so list_agents returned empty for the old session (whose
sidecars are still on disk) until the next process start or successful resume.

Reload the old session's paused background agents after rolling core back, so
the restored roster matches on-disk state. Placed after startNewSession so the
loadPausedBackgroundAgents current-session guard is satisfied; best-effort via
.catch so it never blocks the rollback path.

* fix(web-shell): add zh translation for list_agents tool name

The toolFormatting test 'has a zh translation for every tool in the
display-name map' failed with expected ['list_agents'] to deeply equal []
because list_agents was added to TOOL_DISPLAY_NAMES without a matching
toolName.list_agents zh-CN entry. Add the translation to restore parity.

* fix(cli): resolve CI failures for background-agent roster restore

- Add toolDisplayName.ListAgents translations (en, zh, zh-TW, ca) so the
  new list_agents tool has a zh entry; fixes i18n/index.test.ts.
- Add loadPausedBackgroundAgents and consumePendingRecoveredAgentsNotice
  to the acpAgent worktree test config mock, which loadSession now calls
  via #restoreBackgroundAgentsOnResume; fixes acpAgent.worktree.test.ts.

* refactor(core): extract incompatible-isolation blocked reason to a const

Move the incompatible-isolation blocked-reason string out of an inline
literal into a module-level INCOMPATIBLE_ISOLATION_BLOCKED_REASON const,
matching its four sibling reasons so the text is discoverable by
constant-name grep and edited alongside the others.

* fix(core): preserve retained activity state on failed agent revive

Address review feedback on the background-agent roster restore:

- On a failed completed-agent revive, restore UI state with a non-empty
  guard instead of `??`. Because `restorePausedEntry` resets the paused
  entry's `recentActivities` to `[]`, the previous `failedEntry?.field ??
  completedEntry.field` kept that empty array and dropped the pre-revive
  snapshot (the UI Progress section rendered empty). Applied consistently
  to pendingMessages, recentActivities, and pendingApprovals.

Add regression coverage for previously untested paths:

- failed revive preserves pre-revive recentActivities
- terminal-agent cap admits only the newest MAX_RETAINED_TERMINAL_AGENTS
  completed sidecars on restore
- /resume rollback reloads the old session's background agents
- headless resume prepends the recovered-agents notice to the prompt

* test(cli): cover interrupted-turn continuation not consuming recovered-agents notice

Add ACP and headless regression tests asserting an interrupted-turn
continuation does not consume the one-shot recovered-agents notice
(the !isContinue / !continueInterrupted guards), so it is delivered on
the user's next ordinary prompt. Mirrors the existing slash-command
coverage.

---------

Co-authored-by: Claude <noreply@anthropic.com>

* feat(cli): support custom skill directories via settings (#7395)

* feat(cli): support custom skill directories via settings (#7394)

Add skills.directories setting that accepts an array of additional
directory paths to scan for skills (SKILL.md files). Paths support
~ expansion. Directories are scanned recursively at user level,
after the default ~/.qwen/skills/ directory.

Example settings.json:
{
  "skills": {
    "directories": ["~/.agent/skills", "~/.claude/skills"]
  }
}

Changes:
- settingsSchema.ts: add skills.directories array setting
- core Config: add customSkillDirs param and getCustomSkillDirs()
- SkillManager: append custom dirs to user-level skill base dirs
- CLI config: read skills.directories and pass to core Config

* fix(cli): regenerate settings schema for skills.directories (#7394)

* fix(core): address review feedback for custom skill directories (#7395)

- Use optional chaining for getCustomSkillDirs() to prevent TypeError
  on partial Config mocks (workspace-skill-management, workspace-skills-status)
- Reuse expandHomeDir utility instead of inline tilde expansion
- Fix inaccurate 'scanned recursively' wording to 'one level deep'
- Correct JSDoc: paths are raw, expansion happens in SkillManager
- Trim whitespace from custom dir entries in CLI layer
- Add tests for custom dir expansion, dedup, and partial config safety

* fix(core): address review feedback for custom skill directories (#7395)

* fix(core): address review feedback for custom skill directories (#7395)

* test(core): add relative path resolution test for custom skill dirs (#7395)

* fix(cli): add Array.isArray guard for skills.directories and safe mode test (#7395)

* fix(skills): address review feedback on custom skill directories (#7395)

- Add bare mode test for skills.directories guard
- Include resolved absolute path in relative directory warning
- Clarify that dedup applies to default user dirs, not bundled skills
- Regenerate settings schema

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>

* fix(core): add image modality support for qwen3.8-max and kimi-k3 models (#7491)

* fix(core): add image modality support for qwen3.8-max models

qwen3.8-max-preview supports image input but was falling through to the
catch-all text-only rule because no pattern matched it. This caused the
vision bridge to unnecessarily transcribe images via a secondary model
instead of sending them directly to the primary model.

* fix(core): also add image modality for kimi-k3

Kimi K3 officially supports image + video input but was falling through
to the catch-all text-only rule, same issue as qwen3.8-max.

* fix(dingtalk): preserve non-bot mention context (#7473)

* fix(dingtalk): preserve non-bot mention context

* test(dingtalk): cover plural mentions, staffId fallback, and edge cases (#7473)

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>

* fix(core): harden the usage salvage around session deletion (#7425)

Post-merge review follow-ups on #7391 (three findings):

- Salvage the archived transcript in the active-branch deletion too:
  when both copies co-exist (an interrupted archive) and the fresh
  active transcript carries no telemetry, the archived copy holds the
  session's usage history and was deleted unsalvaged. The dedup guard
  makes the extra call a no-op whenever the active copy already wrote.
- Enforce the "never blocks deletion" contract at the call site: a
  salvageUsageBestEffort wrapper catches and warns, so the guarantee is
  structural rather than an implementation detail of
  persistUsageBeforeTranscriptDeletion. The new failure-tolerance test
  (salvage rejects -> deletion still succeeds) fails without the
  wrapper — the bare await let the rejection escape through
  removeSessionFiles' rethrowing catch.
- Clear the salvage module mock in beforeEach so the wiring test's
  invocationCallOrder assertions can never read stale calls.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(core): make fork subagents discoverable (#7460)

* test(core): cover Shell truncation without an artifact (#7470)

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

* fix(ci): autofix route checks existing labels on non-trigger label events (#7481)

* fix(ci): autofix route checks existing labels on non-trigger label events

When triage adds multiple labels in sequence, per-issue concurrency
cancels earlier runs. If the last label is not a trigger label
(e.g. scope/build-system), the surviving run skips the issue phase
even though the issue already has autofix/approved +
status/ready-for-agent.

Before ignoring a non-trigger label event, check ISSUE_LABELS_JSON
for both required labels. If present and the issue is open, proceed
with the issue phase. Trust was already established when the trigger
labels were applied (both require triage+ permission).

* fix(ci): require trusted sender for label fallback

* feat(cli): preserve semantic text when copying VP selections (#7286)

* docs(cli): define semantic copy fidelity scope

* docs(cli): address semantic frame review gaps

* docs(cli): preserve soft-wrap source separators

* feat(cli): preserve semantic selection copy

* fix(cli): address semantic copy review findings

* fix(cli): preserve clipped semantic boundaries

* fix(cli): limit separator carrier joiner to visible width in wrap metadata

The greedy /\s+/ match in wrapTextWithMetadata could capture more
source whitespace than the separator carrier row actually consumed
(e.g. a tab following a space), causing duplicated whitespace in
semantic copy. Limit the match to visibleLine.length characters and
add a mixed space/tab regression test.

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>

* test(core): stub the registry methods agent.ts actually calls (#7538)

The shared stubRegistry in agent.test.ts was missing six methods that
agent.ts reaches: bridgeApprovalEvents, getQueuedCount,
registerResidentAgent, restartCompletedAgent, unregisterResidentAgent and
waitForMessages.

That is not a benign omission. The background body wraps its work in a
try/catch that routes any throw into registry.fail(), so a missing method
never surfaces as 'not a function' — it silently converts a successful
run into a failed one. On the GOAL completion path
unregisterResidentAgent is called immediately before complete(), so the
TypeError replaced the completion entirely:

  registry.fail('fork-...', 'registry2.unregisterResidentAgent is not a
  function', ...)

That is what broke 'runs a non-interactive fork through the background
registry' on main. #7460 added the registry.complete assertion, which
exposed the incomplete stub — before it, nothing checked whether the
background body finished successfully and the TypeError was swallowed.

Stub all six with their real return shapes (unregisterResidentAgent
returns boolean, bridgeApprovalEvents returns the unsubscribe callback
agent.ts later invokes, waitForMessages resolves to a list) and assert
registry.fail was not called before asserting completion, so a future
gap reports the actual error instead of 'complete: 0 calls'.

* perf(startup): lazy-load Google GenAI SDK on first use (#7512)

* perf(startup): lazy-load Google GenAI SDK on first use

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

* codex: address PR review feedback (#7512)

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

* codex: address PR review feedback (#7512)

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

---------

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

* fix(vscode): use file picker image paths for vision input (#7493)

* fix(vscode): use image paths from file picker

* fix(vscode): keep image picker paths raw

* fix(vscode): resolve image picker paths on submit

* fix(vscode): send picked images as vision context

* fix(vscode): encode prompt image file URIs

* fix(vscode): address image path review comments

* test(vscode): cover image file reference edge cases

* fix(cli): open the actual serve fallback port (#7501)

* fix(cli): open actual serve fallback port

* test(cli): match serve URL to fallback listener

* docs(cli): clarify serve listen error handling

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* fix(ci): don't let one failing scenario sink the whole visual preview (#7511)

The web-shell visuals render runs every screenshot and flow in a single
`test:e2e:visuals`, and that step had no `continue-on-error`, while the compose
and upload steps had no `if: always()`. So one failing or timing-out scenario
failed the job, the artifact was never uploaded, and the publish workflow had
nothing to post — the entire preview vanished even when every other scenario
passed and its PNG was already on disk. A flow (a long multi-click sequence) is
the most fragile scenario kind, so the fragile one silently takes down the
deterministic screenshots. PR #7498 hit exactly this: 29 scenarios passed, one
new channel-management flow timed out, and the PR got no preview and no comment
at all.

Make the after-capture step `continue-on-error` so the passing captures survive
and the later steps still compose and upload them. The publish job only runs on
a `success` conclusion, so the job must stay green — but a masked failure must
not read as a clean preview. Ship the step's real `.outcome` (which
continue-on-error does NOT mask, unlike `.conclusion`) to the publisher as
`render-status.txt`, and have the comment builder use it: an empty preview whose
render failed says "one or more scenarios failed to render" and is explicitly
NOT the reassuring green check or the coverage-gap prompt (both imply the render
ran); a partial preview is labelled partial above the shots that did render. A
missing status file (older run) defaults to complete, so this only ever adds a
warning, never suppresses a real preview.

The failing scenario still needs fixing — it's now surfaced in the comment
rather than by silently deleting everyone else's preview.

Co-authored-by: wenshao <wenshao@example.com>

* feat(web-shell): add selective shadow DOM isolation (#7551)

Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>

* feat(web-shell): add renderChatHeader slot for custom session header (#7553)

* fix(cli): say review coverage gaps in the author's units, not chunk ids (#7550)

The posted review body rendered coverage disclosures with the run's own
bookkeeping as subjects: bare chunk ids, unsorted, one per subject. On a
run that certified nothing (PR #7268) the body enumerated all 49 chunk ids
across two sentences while opening with "Reviewed. Suggestions are
inline." — the opener certified the exact thing every following sentence
took back, and nothing on the PR page maps a chunk id to code.

Three changes, all render-time — the structural entries, the caps, the
caller-echo dedup and the stderr remediation still key on chunk ids, which
is where the id is the selector a reader can act on:

- Coverage now returns the plan's chunk→files table (DiffChunk.files was
  already in the plan JSON; the coverage type slice dropped it).
- compose-review renders chunk gaps through describeChunkGap: every
  planned chunk collapses to "the entire diff", a narrow gap with known
  files names the files, and anything wider is counted against the plan's
  total. Applied to the receipt sentence, the uncoverable sentence (bare
  CLI entries only — caller-authored entries render verbatim) and the
  grouped per-cause sentences.
- The COMMENT opener may no longer say "Reviewed." over a disclosure set
  that denies it: when no chunk is both covered and undisclosed — or no
  chunk universe could be read at all — it opens with a zero-certified
  warning instead. A rewritten launch demonstrably read its chunk, so
  coverage alone is not the test; certified is covered with no disclosure
  against it.

Co-authored-by: verify <verify@local>

* fix(autofix): retry a skipped-Prepare instead of stranding the PR terminal (#7490)

* fix(autofix): retry a skipped-Prepare instead of stranding the PR terminal

A base/infra failure BEFORE the agent runs was misread as an agent crash
and terminated the PR forever. When an early step fails — installing or
building the trusted base, checkout, node setup — the `Prepare branch and
feedback` step is skipped, so NEWEST is empty, and the report step's
"crashed before reading feedback" branch fired: MARK_ROUND=MAX_ROUNDS,
terminal, scan skips it on every future tick.

Observed: a web-shell TypeScript break on `main` failed `Install
dependencies and build` (which builds the trusted base) across a whole
scan batch, and SIX healthy PRs were stranded terminal at round=100 in
one run — including ones at round 9 and 11 that had nothing to do with
the break. `round=100` there is a terminal sentinel, not 100 attempts.

NEWEST-empty now splits on steps.prepare.outcome:
- 'skipped' (an earlier step failed, the agent never ran) is infra/base
  and transient: retry with a sentinel ts so the feedback stays live,
  incrementing the round so a PERSISTENTLY broken base is still bounded
  and stops at the cap (recoverable with /retry).
- 'success'/'failure' (Prepare ran, no feedback produced) is a genuine
  pre-read agent crash: unchanged terminal behaviour.

This is the reverse of the asymmetry #7482 addresses: that bounds a
crash AFTER reading that retried forever; this stops a transient failure
BEFORE reading from going terminal after one.

* docs(autofix): note a pre-Prepare cancel also retries intentionally (#7490)

* fix(autofix): also retry a cancelled/empty prepare outcome, not just skipped

A previous review comment on this PR noted that a job cancelled before
Prepare should retry too. It was right about the intent but the code did
not do it: `steps.prepare.outcome` is 'cancelled' for a cancel and '' for
a job that stopped before Prepare entered the step context — both DISTINCT
from 'skipped', so `== 'skipped'` sent them to the terminal branch, the
same over-termination this PR exists to fix.

Match on "not a real Prepare run" (`!= 'success' && != 'failure'`)
instead, so skipped, cancelled, and empty all retry; only a Prepare that
actually ran to a verdict (success/failure) with no feedback stays
terminal — the genuine pre-read agent crash. Test extended to drive the
cancelled and empty cases (retry) and both real-run outcomes (terminal);
mutation-verified that reverting to `== 'skipped'` reddens the cancelled
case.

* test(autofix): update the pre-read-crash case for the broadened retry

The prior commit broadened NEWEST-empty retry to skipped/cancelled/empty
but left the older 'replays the handoff decision' test asserting the old
terminal behaviour for an unset PREPARE_OUTCOME (which now retries). That
test's terminal cases now set PREPARE_OUTCOME=success/failure explicitly —
the only outcomes that still terminate — so it exercises the genuine
pre-read agent crash rather than the infra/cancel path.

* test(autofix): anchor the skipped-Prepare extraction past the CONSEC block

CI reddened `retries a skipped-Prepare` after main's consecutive-failure
cap (#7482) merged into this branch: that block was inserted between this
decision block and the report `{`, and it calls `gh api`. The test's
`{`-anchored regex over-captured through it, so the extracted script ran
the unstubbed `gh api` and failed. Anchor the end on the same
`# Consecutive-failure` comment the sibling gate-crash test already uses,
so the extraction stops at this decision block's own closing `fi`.

* fix(autofix): exempt skipped-Prepare from the consecutive-failure breaker

A broken base build skips Prepare, producing no API error file — so the
consecutive-failure breaker ran on the new retry path and, after 5
scans, re-introduced the exact mass-stranding this PR exists to prevent.
Exempt pre-agent infra failures (skipped/cancelled/empty outcome) from
the breaker, mirroring the transient 429/5xx exemption: same failure
class (not the PR's fault, self-heals, hits the whole batch). The round
cap + sentinel-ts /retry recovery already bounds a persistently broken
base.

Also trim "checkout" from the retry headlines (checkout failures do not
land in this branch) and hoist the duplicated MARK_TS assignment.

* fix(autofix): reset the consecutive-failure streak on prior infra-failure markers

The streak walker counted prior infra-failure headlines ("AutoFix could
not start —…") as failures, inflating the consecutive-failure count on
subsequent rounds.  A PR with 3 real agent failures, then 3 rounds of
base-build infra failures, then 1 more real failure would trip the
cap-5 breaker even though only 4 rounds were the PR's fault.

Add the two infra-failure headline patterns as reset strings in the
streak walker, alongside the existing push and no-op resets.  The
genuine agent-crash headline ("AutoFix could not start evaluation —…")
is deliberately excluded — it is a real failure and must still count.

* fix(autofix): clarify infra-failure headlines and else-branch comment (#7490)

Address review nits: the retry headline now mentions cancelled runs,
the cap headline says 'reached the round cap' instead of overstating
'could not start for N rounds', the else-branch comment says 'prepare
itself crashed' instead of 'agent crash', and the streak-reset pattern
is simplified now that both infra headlines share the same prefix.

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>

* fix(cli): keep role codenames and brief paths out of the posted review body (#7560)

The posted body still carried two operator registers #7550 left in place:
roster role subjects rendered their internal codenames ("Agent 1c:
Cross-file tracer", "Test coverage matrix (whole-diff)"), and an unread
brief's disclosure interpolated its filesystem path. And when verify and
the reverse audit failed the same way, the body said it twice, in two
near-identical sentences.

- Every Brief now carries a publicLabel — the dimension said as what it
  checks ("the cross-file consistency pass") — and coverage's structural
  disclosures carry it as publicSubject beside the internal subject, plus
  a path-free publicReason for unread briefs. The internal label and the
  path stay on stderr, where they are the selector an operator acts on;
  every dedup and certification check still keys on the internal subject.
- compose-review renders the public fields and groups by the reason the
  body PRINTS, so two unread briefs share one path-free sentence instead
  of repeating it per role.
- verificationGaps merges verify and reverse-audit failures of the same
  delivery shape into one sentence with both subjects and both
  consequences; mixed shapes keep their precise per-role texts, and the
  per-role rebuild commands stay on stderr either way.

Co-authored-by: verify <verify@local>

* fix(autofix): retry an agent timeout instead of advancing past its feedback (#7563)

A timeout evaluated NOTHING — the agent ran out of budget before finishing,
so nothing was committed and the feedback is unaddressed. It was treated as
an evaluated verdict (real ts, watermark advances), which strands that
feedback: the next scan sees "nothing new" and never retries. Observed on
#7471 (round 13/100), a heavily-reviewed 1871-line PR: rounds 11 and 13
timed out, but round 12 pushed — so a timeout is transient far more often
than not, and advancing past it left the round-13 feedback unhandled.

run-agent.mjs now drops an `agent-timeout` signal on result.timedOut, and
the handoff routes it like a pre-verdict crash: sentinel ts (feedback stays
live) and a retry, with a headline that names the real fix at the cap
(split the PR or raise the budget). A PR that PERSISTENTLY times out is
bounded by the round cap and the consecutive-failure cap, so this cannot
loop forever — it just stops treating a one-off budget blip as a verdict.

The loop guard stays terminal (a tool-call loop is a real defect, not a
budget blip). An API error still routes to its own model-key handoff; the
timeout signal is written only when NOT an API error.

Co-authored-by: wenshao <wenshao@example.com>

* feat(serve): add workspace-level generation (#7552)

* feat(serve): add workspace-level generation

* docs(serve): document workspace generation capability

* fix(serve): align workspace generation contracts

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>

* ci: matrix ECS runner update + sudo install + repository_dispatch trigger (#7513)

* ci: matrix ECS runner update with sudo install

- Use matrix strategy (ecs-update-sg, ecs-update-64c) to update both
  physical ECS hosts in parallel (fail-fast: false).
- Always use sudo npm install -g so the package lands in /usr/local
  (system-wide PATH) instead of the runner user's home directory.
- Move concurrency to job level (matrix context not available at
  workflow level per actionlint).
- Add repository_dispatch trigger for release-driven updates.
- Register new runner labels in actionlint.yaml.

* fix(ci): use dispatch version for runner update

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* fix(web-shell): include managed id in artifact open requests (#7570)

Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>

* feat(serve): persist workspace channel configuration (#7514)

* feat(serve): persist workspace channel configuration

* fix(serve): harden channel settings snapshots

* fix(serve): validate startup channel names

* fix(serve): reserve all channel name

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* fix(sdk-python): require canonical form in validate_session_id (#7532)

uuid.UUID() accepts several non-canonical spellings — braced
{...}, urn:uuid:..., and dash-less hex — so validate_session_id let them
through after the RFC 4122 variant check. The value is then forwarded to
the CLI verbatim as --session-id/--resume, producing a malformed session
id downstream rather than a clear error at the SDK boundary.

Reject anything whose canonical form differs from the input. Case is
deliberately not part of the comparison: UUID() lowercases, and an
all-uppercase spelling is still valid canonical input.

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* fix(web-shell): sync background agent status (#7561)

* fix(web-shell): sync background agent status

* fix(web-shell): harden background agent reconciliation

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>

* feat(core): propagate trusted daemon invocation context (#7279)

* feat(core): propagate trusted daemon invocation context

* test(cli): update ACP startup expectation

* refactor(core): centralize ACP capability env key

* test(cli): update worktree ACP core mock

* test(integration): run daemon context smoke on PRs

* test(ci): update no-AK smoke expectation

* test(core): cover invocation context isolation

* fix(cli): compare ACP capability safely

* fix(docs): restore GitHub action input names

* fix(core): sanitize private ACP capability from child env

* fix(core): reuse private ACP capability env constant

* test(cli): cover malformed trusted invocation context

* test(acp-bridge): assert exact child environment

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: 易良 <1204183885@qq.com>

* fix(feishu): await stream cancels in media download teardown (#7465)

* fix(feishu): await stream cancels in media download teardown

downloadMedia left two reject paths' stream teardown unawaited:

- the oversize-stream path called reader.cancel() without awaiting, so a
  cancel error during teardown became an unhandled rejection (fatal under
  Node's default --unhandled-rejections=throw);
- the Content-Length reject path returned without cancelling resp.body,
  leaving the connection pinned until GC.

Both were already fixed for the sibling DingTalk downloader in #7361 (which
was itself modelled on this Feishu code), so this brings Feishu to parity.
Adds a regression test that pins the reader.cancel() await via a rejecting
cancel, plus an assertion that the Content-Length path releases the body.

* test(feishu): cover a rejecting body.cancel() on the Content-Length path

Mirrors the existing reader.cancel() teardown test for the other reject
path, per review feedback. Removing the await on resp.body?.cancel()
flips execution onto the 'rejected: size ... exceeds' branch and the
test fails.

* fix(autofix): make the review-address report wrapper lines bilingual (#7569)

The agent's address-summary.md / no-action.md already ends with a
collapsed Chinese translation, but the workflow-appended wrapper lines
around it — the "Addressed/Reviewed the latest feedback" lead-in, the
"Base-conflict check" line, and the "Re-review when you have a moment"
footer — were English-only and sat outside that block. So the posted
comment was only half translated, unlike the takeover-ack comments
(full collapsed Chinese block) and the "model/模型" sign-off in this
same report (already inline-bilingual).

Give each wrapper line an inline Chinese translation, matching the
model/模型 idiom. The English halves are preserved verbatim — the
streak-reset detector globs on "Addressed the latest review feedback"
and "no changes needed", and a test extracts these lines — so behaviour
is unchanged and old English-only comments still match. A new test pins
each English-Chinese pair so a future reword that drops the Chinese
fails. The terminal handoff/failure comment is left English-only for
now (SKILL.md keeps it so by design); that is a separate change.

Co-authored-by: wenshao <wenshao@example.com>

* feat(cli): post the review body bilingually when the PR description is Chinese (#7564)

When the PR author writes Chinese, the posted /review body was
English-only. fetch-pr now records whether the PR description contains
Han characters (prDescriptionHasHan, detected from the same gh pr view
call and stamped into the plan report), and compose-review renders the
body bilingually off that flag: the English body leads, the complete
Chinese version rides collapsed in a <details><summary>中文说明</summary>
block, and the model footer stays outside the fold. The signal is the
CLI's own — the caller cannot toggle the register of a certified body —
and a local plan has no field, so nothing changes for terminal-only
reviews.

Every deterministic body fragment carries an en/zh pair end to end:
compose-review's clause templates and describeChunkGap phrases, the
coverage disclosures (reasons, publicLabel role subjects via a new
publicLabelZh, the path-free unread-brief reason) and the Step 4/5 gap
texts including the combined same-shape sentence. Fragments with no
deterministic translation — model-written findings, caller echoes,
interpolated errors — ride verbatim in both halves. verificationGaps now
returns structural {subject, reason, subjectZh, reasonZh} entries, which
also removes compose-review's last recover-the-boundary-from-prose parse.

SKILL.md instructs the same format for the model-authored inline
comments: English finding first (marker and suggestion block stay in the
English half — tooling filters on them), full Chinese translation
collapsed beneath, footer last.

Co-authored-by: verify <verify@local>

* feat(autofix): auto-rerun a check that died on infrastructure, once (#7562)

* feat(autofix): auto-rerun a check that died on infrastructure, once

A failed check can be red because the machine died, not the code — a
self-hosted runner losing the server, the disk filling. #7490's E2E
failed with "runner lost communication with the server" and went green
on a rerun. The scan now reruns such a check's failed jobs automatically.

Detection is a conservative annotation whitelist (INFRA_FAILURE_SIGNATURES)
— only unambiguous machine failures, never a test-level timeout, which
could be a real regression. The one-shot guard is run_attempt, not a
marker: a run already retried to attempt 2 and still infra-failing is
persistent, so it is left for a human; after a rerun the attempt
increments, so the next scan will not rerun it. Every step is fail-safe
(any API error → no rerun), it runs only when the PR actually has a
failed check, and the gate carries the same review-address carve-out as
the other check selectors so the loop never reruns its own runs.

This is the transient-infra sibling of #7554 (stale-base): that merges
current main when a check is base-inherited; this reruns when a check
died on the runner. Neither touches a check that is a genuine failure.

Note: rerun-failed-jobs needs the PAT to hold `actions: write`.

* fix(autofix): use POSIX ERE groups in infra-failure regex, cover all signatures in tests (#7562)

* fix(autofix): also treat a git fetch/clone transport death as infra

#6506's checkout died mid-transfer — "fetch-pack: invalid index-pack
output" and "RPC failed; curl 92 ... CANCEL" — which then hung the job
into the 20m limit. That is infra, not the PR (it only touches a doc),
and a re-run made it green. But the infra-signature whitelist did not
cover it, so the auto-rerun did not fire and it waited on a human.

Add `invalid index-pack output` and `RPC failed` — the two canonical
git-transport-death phrases — to INFRA_FAILURE_SIGNATURES. A co-present
job-timeout line does not block the match (one matching line classifies
the run), and a BARE timeout with no transport signature is still left
alone, since it can be a real regression. Both new signatures are pinned
in the test's per-signature loop, plus a case on #6506's real composite
annotation and a bare-timeout-is-not-rerun guard.

* fix(autofix): paginate annotations and filter Autofix runs in infra-rerun loop (#7562)

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>

* fix(serve): detect stale SSE cursors across daemon restarts via epoch token; preserve turn attribution and surface compaction failures in replay (#7458)

* fix(daemon): epoch-token restart detection, compaction attribution, and degraded-snapshot signaling (DAEMON-001/007/008)

* fix(acp-bridge): field-level turn attribution merge and replayDegraded bridge test (#7458)

* fix(serve): skip bus epoch lookup for virtual subagent SSE streams (#7458)

The REST SSE route looked up the bus epoch for every session id, but
virtual subagent sessions ride their own bus and their compound ids are
not in the bridge's byId map, so the lookup threw and aborted the
subscription — breaking subagent event streams. Skip the lookup for the
virtual path and degrade a torn-down real session to a headerless stream
(mirrors the /acp route). Also bumps the daemon browser SDK bundle budget
(167KB -> 168KB) for the epoch fields and declares eventEpoch on
DaemonSession so the create/attach path drops its inline type cast.

* fix(serve): stamp eventEpoch on accepted continuations and surface replayDegraded in the SDK (#7458)

Address three review suggestions:
- POST /session/:id/continue now returns eventEpoch alongside lastEventId,
  mirroring the prompt 202 envelope so continuation-seeded SSE cursors
  detect daemon restarts (DAEMON-001)
- DaemonSessionClient exposes replayDegraded from the load response so SDK
  consumers can prefer the full transcript over a degraded snapshot
- add /acp dispatch-level regression test for the degraded-snapshot stderr
  breadcrumb (fires only when snapshot.degraded is set)

* test(cli): fix load-reply race in the degraded-breadcrumb transport test

Await each session/load reply frame before opening the session stream so
the GET cannot race conn.ownSession() into a 403; addresses the review
Critical on the deg-0 arm.

* fix(serve): allow and expose X-Qwen-Event-Epoch in CORS headers

Cross-origin SSE clients must send the epoch header through preflight and
read it from the response, or stale-cursor detection (DAEMON-001) is
silently disabled for every CORS client.

---------

Co-authored-by: qwen-code-bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen Autofix <qwen-autofix[bot]@users.noreply.github.com>

* feat(core): Align GenAI telemetry with ARMS (#7536)

* feat(core): align GenAI telemetry with ARMS

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

* fix(core): remove estimated token usage splits

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

* fix(core): address GenAI telemetry review feedback

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* fix(serve): avoid TOCTOU race dropping live sessions from list response (#7556)

* Initial plan

* fix(serve): avoid TOCTOU race dropping live sessions from list response

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: 易良 <1204183885@qq.com>

* fix(cli): prevent monitor turns after task_stop (#7573)

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix[bot]@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: destire-mio <qppque@gmail.com>
Co-authored-by: destire-mio <248462155+destire-mio@users.noreply.github.com>
Co-authored-by: Dragon <52599892+DragonnZhang@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: 易良 <1204183885@qq.com>
Co-authored-by: jinye <djy1989418@126.com>
Co-authored-by: chinesepowered <nlai@rediffmail.com>
Co-authored-by: ovochouovo <18212194+ovochouovo@users.noreply.github.com>
Co-authored-by: Edenman <67549719+BZ-D@users.noreply.github.com>
Co-authored-by: 克竟 <dingbingzhi.dbz@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: ytahdn <1294726970@qq.com>
Co-authored-by: ytahdn <ytahdn@gmail.com>
Co-authored-by: Truraly <94105924+Truraly@users.noreply.github.com>
Co-authored-by: zjgzx1988 <zjgzx1988@hotmail.com>
Co-authored-by: hogeheer499-commits <hogeheer499@gmail.com>
Co-authored-by: hogeheer <267467744+hogeheer499-commits@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Nothing Chan <chenliu.cl@alibaba-inc.com>
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: yuanyuanAli <135116774+yuanyuanAli@users.noreply.github.com>
Co-authored-by: verify <verify@local>
Co-authored-by: qqqys <qys177@gmail.com>
Co-authored-by: callmeYe <512217680@qq.com>
Co-authored-by: Qwen Autofix <qwen-autofix[bot]@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants