feat(cli): OpenTUI migration live-session and input batch - #10368
feat(cli): OpenTUI migration live-session and input batch#10368chiga0 wants to merge 130 commits into
Conversation
…10124) * refactor(core,cli): rename Gemini residue in memory/spinner/leaf ids PR 1 of QwenLM#4063 item 6 (de-Google naming). Renames three independent families plus the leaf LLM types: - Memory filename: GeminiMd* -> Memory* (project memory file, not an LLM client) - UI spinners: GeminiRespondingSpinner/GeminiSpinner -> RespondingSpinner/Spinner - Leaf types: GeminiCodeRequest/GeminiChatSendOptions/GeminiErrorEventValue/GeminiFinishedEventValue -> Llm* - geminiRequest.ts -> llm-request.ts (and its collocated test) No behavior change. Renamed symbols typecheck clean in core+cli; eslint clean on renamed files. Refs QwenLM#4063 * fix(cli): resolve rename build failure * docs(serve): fix memory filename references * test(cli): pin primary workspace QWEN.md init fallback Assert that the primary daemon workspace service receives the hard-coded 'QWEN.md' context filename when boot settings carry no context.fileName. Previously only the secondary workspace's explicit SECONDARY.md resolution was asserted, so swapping the fallback literal at the createDaemonWorkspaceService call site survived the suite. * refactor(core,cli): finish Gemini residue rename in memoryDiscovery Complete the rename flagged in review: GeminiFileContent -> MemoryFileContent (module-local interface), includeDirectoriesToReadGemini -> includeDirectoriesToReadMemory (parameter only; all call sites are positional, zero cross-package impact), plus test-local variable names and the stale ORIGINAL_GEMINI_MD_FILENAME test title. * docs(design): route loadHierarchicalGeminiMemory to Memory naming Per exception #1 the Llm prefix is reserved for the generic LLM-client surface; the symbol is a memory-file loader (thin wrapper around core's loadServerHierarchicalMemory), so the PR-2 symbol map targets loadHierarchicalMemory instead of loadHierarchicalLlmMemory. Doc-only: the code symbol is not renamed by this PR. * docs(cli): narrow extractContextFilename fallback description The undefined fallback first inherits the primary workspace's configured context.fileName snapshot (contextFilenameForInit) at the secondary startup and dynamically added workspace call sites, before the hard-coded QWEN.md. Describe the actual chain instead of the hard-coded default only. Comment-only: the inheritance behavior predates this PR and is unchanged. * refactor(cli): rename loadHierarchicalGeminiMemory to loadHierarchicalMemory The design doc's symbol map routes the memory loader to loadHierarchicalMemory (memory family, exception #1), but no phasing bullet performed the rename and a prior round left the mixed signature. Complete the rename across the definition (config.ts), the AppContainer call site, and the AppContainer test mocks, and update the design doc's exception #1, symbol map, and PR-1 phasing bullet so the map row is no longer orphaned. * fix(core): preserve Gemini rename compatibility * docs(core): extend Gemini deprecation window * refactor(core,cli): rename Gemini LLM identifiers * fix(core): retain Gemini content generator aliases * docs(core): document legacy content generator paths * ci: trigger checks after retargeting to main * test(cli): update renamed LLM expectations --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Third landing batch of the OpenTUI migration (QwenLM#8662): live-session stream fold and model, message rendering (markdown heal, MCP progressive, client tool runs, text batching), transcript adapter with resume/session-switch, sticky todos, the composer (input-prompt view/key/model), mouse rows and scrollbar, unified-diff rendering, and session-compaction notice. All additive — no reachable ink code path is touched, ink remains the default. Carries the first consumer of the remend dependency deferred from the infra batch, placed in devDependencies per the renderer-deps convention. The stacked-skill completion helpers import from the relocated ui/commands module following the upstream rename.
…wenLM#10337) * fix(test): isolate integration tests from the host global qwen dir The integration suites spawn the real CLI without isolating the global qwen dir, so whatever sits in the host's `~/.qwen` shapes the run. Hosted runners have an empty one and never noticed; the persistent pool does not, and there the saved memories left by earlier jobs made managed auto-memory recall issue its own model request ahead of the agent's first turn. The SDK suites script their fake model server by request index, so that extra request shifted every index: each scripted tool call landed on the recall selector and the turn under test got the trailing text instead, failing 42 cases across permission control and tool control on both Linux legs while macOS and the other shards stayed green. The same reds reproduce on any developer machine that has saved memories. Give each run its own global qwen dir, carrying the host's configuration across but none of the files that dir accumulates. Configuration has to come along whole: the suites that talk to a real model rely on ambient auth, which can live in the credentials block, in the environment block, or in the model routing, and the persistent pool's own credential source is not knowable from here. The accumulated files - saved memories, tool-usage history, extensions, skills, commands - are what a run has no business depending on, and dropping them is what makes the suites deterministic again. * fix(test): keep scratch-home cleanup from failing an all-green run Removing the run's scratch qwen dir threw when a CLI child that outlived its test was still writing under the debug directory: the removal walk reached a directory that refilled between its listing and its rmdir, and the ENOTEMPTY escaped teardown. Every test had passed, so the shard reported no failure and still exited red - the same shape as the memory-file restore before it was made best-effort. Retry the removal so an ordinary race resolves itself, and warn instead of throwing when it still cannot finish, so a host that keeps a directory busy stays diagnosable without failing the run. Sweep scratch homes an earlier run left behind, past an age floor that clears any run in flight on the same host, so the persistent pool does not accumulate what a best-effort cleanup gives up on. The new case drives the failure through a real writer holding the directory busy rather than a permission trick, so it does not depend on the runner's privileges, and it waits for that writer to be producing before tearing down - without the wait it passes against the very bug it pins.
…nLM#10149) * feat(external-context): add configurable Mem0 extension skeleton Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(external-context): harden Mem0 extension boundaries Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(extension): harden Mem0 runtime boundaries Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs: align Mem0 design with PR1 scope Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…10355) (QwenLM#10359) Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
…it on running sessions (QwenLM#10302) * fix(web-shell): keep archive out of the sidebar hover slot and block it on running sessions The archive action was one of the two default inline buttons that appear over a session row's metadata slot on hover, right on top of the row's own click target, so users opening a session could archive it by accident. Archive now stays in the row's dropdown by default (inlineItems still accepts 'archive' for hosts that want it back), and it is disabled on any session with a running turn because the daemon closes the live session when it archives, which would end that turn. * test(web-shell): pin the running-session archive guard and capability gate (QwenLM#10302) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(serve): persist skill toggles without catalog validation * fix(web-shell): reconcile skill toggle status * fix(skills): reconcile settings-only toggles * fix(serve): version skill settings capabilities * fix(serve): clarify skill settings outcomes * docs(serve): clarify skill settings contracts * docs(serve): align skill toggle design semantics * docs(serve): distinguish skill changes from activation * docs(serve): clarify deferred skill refreshes * fix(web-shell): allow model-only skill settings writes
left a comment
There was a problem hiding this comment.
@chiga0 Thanks for the third batch — the write-up of the changes themselves is thorough, but the PR body is missing three required sections of the PR template:
### Tested on— the OS test matrix (CI runs on macOS / Windows / Linux; state which environments you verified locally).## Risk & Scope— main risk or tradeoff, what is not validated, breaking changes / migration notes. For a ~10k-line batch this is exactly the context reviewers need up front.## Linked Issues— the migration plan (#8662) and the foundation batch (#10146) are mentioned in the prose; they belong in this section.
The sibling foundation batch #10146 fills in all of these sections. Once the PR body is updated to match, a maintainer or author re-run (@qwen-code /triage) will pick it up and continue. Not reviewing code until then.
中文说明
@chiga0 感谢提交第三批!改动本身的描述很详尽,但 PR 正文缺少 PR 模板 中的三个必填部分:
### Tested on—— 操作系统测试矩阵(CI 在 macOS / Windows / Linux 上运行,请说明你本地验证过哪些环境)。## Risk & Scope—— 主要风险或权衡、未验证的内容、破坏性变更 / 迁移说明。对于约 1 万行的批量改动,这正是评审者最需要前置看到的信息。## Linked Issues—— 迁移计划(#8662)和基础模块批(#10146)已在正文中提到,但应放在该小节里。
同系列的基础批 #10146 完整填写了以上所有部分。补齐正文后,维护者或作者重新触发(@qwen-code /triage)即可继续流程。在此之前不会进入代码审查。
— Qwen Code · qwen3.8-max
* fix(test): stop shared-runner state leaking into SDK E2E fake servers permission-control and tool-control E2E fail on every main commit since Linux E2E moved to persistent self-hosted runners (QwenLM#10085): the fake server's scripted responses key off request index 0, but host state leaks extra model requests ahead of the turn's main request, which then only receives 'Done.'. - Isolate the spawned CLI from the runner's HOME / global ~/.qwen state via a per-test scratch HOME (HOME + QWEN_HOME in options.env, which the SDK merges over process.env). - Default SDK E2E settings to disable managed auto-memory/dream: recall selection and background extraction issue side model requests against the fake server and were observed consuming scripted responses. - Serve fake-server responses by matching the turn's user prompt instead of the request index, so any remaining non-turn request cannot desync the sequence. Matching reads raw string/text-block content because JSON.stringify escapes quotes in prompts. Test-only change. All 52 cases in the two suites pass on a host that reproduces the polluted-runner state; the suite-wide SDK E2E run shows no new failures. * fix(test): narrow SDK E2E isolation Keep only the targeted tool-control hardening: redirect QWEN_HOME into the suite scratch directory and disable managed auto-memory and auto-dream. This removes the per-test HOME cleanup and widespread fake-server rewrites while preventing host settings and background model requests from desynchronizing request-index handlers. --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
…red (QwenLM#10260) * fix(goal): stamp the wind-down hand-off only when its turn was delivered QwenLM#10132 marked the record's `windDownTurnId` whenever the turn holding the wind-down permit finished -- reading "the permit was used" as "the user got the hand-off". QwenLM#10013 established why that inference is wrong for the objective-updated notice: a system message or a direct user query can claim a queued continuation's permit and send its own text under it, so the turn finishes with the prompt never reaching the model. Hosts therefore mark delivery at the real send site, and only a delivered turn commits what it carried. The hand-off now follows the same rule. `finishTurn` stamps the marker only when the wind-down turn was marked delivered; an undelivered one leaves the record clean, so the next `queueContinuation` grants the hand-off again instead of settling `usage_limited` on a hand-off the user never received (which a resume would not have repaired either, since the marker is cleared only by a re-arm). The in-memory permit marker is released either way; it belongs to the permit, not the outcome. The wind-down tests that finish the hand-off turn now mark it delivered first, so they keep meaning "the model saw the hand-off". Two new cases pin the split: finished-but-undelivered leaves no marker and re-mints the hand-off; finished-and-delivered stamps it and stops. Mutation probe: making the stamp unconditional again fails exactly the undelivered case (145 others green). * docs(goal): align wind-down comments with the delivered-stamp rule (QwenLM#10260) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(channels): recover DWS direct messages * fix(channels): harden DWS direct-message recovery per review (QwenLM#10274) * fix(channels): contain DWS history-dispatch costs and failures per review (QwenLM#10274) * fix(channels): close the DWS in-flight double-spend per review (QwenLM#10274) * test(channels): pin the DWS stale non-direct drop mark per review (QwenLM#10274) --------- Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
commented
Aug 28, 2026
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 28 passed · 0 failed · 28 total Flakiness gate: 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:28 通过 · 0 失败 · 28 总计 抖动门: Verification reportPR #10368 verification — feat(cli): OpenTUI migration live-session and input batchVerdict: 中文摘要
Central claim and A/BCentral claim: this batch is additive-only and unreachable — it adds the live-session/input OpenTUI layer without changing any reachable behavior, and its 280 new tests actually pin the new code. Because the batch is intentionally unreachable, the load-bearing proof is structural (diff = control) plus a mutation A/B (tests must kill mutants of the new code):
Witnesses:
All kills are behavioral expected-vs-received assertions (e.g. m1: Targeted gates (final-gates.sh, 15/15)
Reviewer Test Plan walk-through
FindingsF1 (low, non-blocking) — F2 (low, non-blocking) — Not covered
MethodologyEnvironment: CI verify job (merge-ref checkout, depth 2, pre-built at HEAD, no GitHub token). Harnesses drove the real code: vitest against Flakiness gate logEvidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
commented
Aug 28, 2026
|
Thanks for the third batch — the template gaps from the earlier gate are fixed, so this clears Stage 1. A few notes before code review: Problem: not a bug fix — this is the live-session & input batch of the OpenTUI migration tracked in #8662, where the maintainer direction call (2026-08-26) approved Phase 1 batch landings: additive-only PRs, flag-gated, ink default untouched. This batch matches that shape. Direction: aligned. The batch follows the ground rules set on the tracking issue (each batch lands on its own review merits; acceptance criteria per PR). One flag: the stack base #10146 (foundation modules) is still open with changes requested — this PR can't land ahead of it, and any rework there may ripple into this batch's imports. Size: 5,361 production-logic lines / 4,891 test lines / 9 lines manifest + lockfile (18 production modules, 17 test files, all under Approach: the scope reads as one coherent unit — the streaming fold + model, message rendering, transcript adapter (resume/session-switch), composer, mouse/scrollbar, diff rendering, todos, compaction notice. All additive, nothing wired into the reachable renderer yet, which keeps the blast radius at zero until the activation batch. One early question I'll take into code review: the author discloses that the transcript adapter has no dedicated test file (its conversion logic is exercised through the resume-mapping tests) — I'll check whether that coverage is actually load-bearing. Risk: no elevated risk signals — none of the changed files match the repo's revert-correlated high-risk paths. What this PR does not have: any CI test evidence on the head commit. The only workflow runs on this SHA are bot orchestration jobs (triage/review); the fork PR's build/test CI has not run. The suite results quoted in the PR body are the author's own run (macOS/arm64), not independently reproduced. That matters for the review verdict and I'll say so again in Stage 2. Moving on to code review. 🔍 中文说明感谢提交第三批——之前模板门禁缺的小节已补齐,Stage 1 通过。进入代码审查前的几点: 问题:不是 bug 修复,而是 #8662 OpenTUI 迁移的 live-session & input 批次。维护者已于 2026-08-26 在跟踪 issue 中批准 Phase 1 分批落地:仅增量、旗标门控、ink 默认路径不动。本批符合该形态。 方向:对齐。批次遵守跟踪 issue 定的基本规则(每批独立按自身评审价值合入、各 PR 有验收标准)。一个提醒:栈基 #10146(foundation modules)仍未合入且被要求修改——本 PR 不能在它之前合入,那边的返工也可能波及本批的 import。 规模:生产逻辑 5,361 行 / 测试 4,891 行 / 清单与 lockfile 9 行(18 个生产模块、17 个测试文件,全部位于 方案:范围读起来是一个内聚单元——流式折叠与模型、消息渲染、转录适配(resume/会话切换)、输入框、鼠标/滚动条、diff 渲染、待办、压缩提示。全部增量,尚未接入可达渲染器,在激活批之前爆炸半径为零。带一个初步问题进入代码审查:作者披露转录适配没有专门测试文件(其转换逻辑通过 resume 映射测试覆盖)——我会核实该覆盖是否真正承重。 风险:无升级风险信号——改动文件均未命中本仓库与 revert 相关的高风险路径。本 PR 缺少的是:头提交上没有任何 CI 测试证据。该 SHA 上仅有机器人编排任务(triage/review),fork PR 的构建/测试 CI 未运行。PR 正文引用的测试结果来自作者本机(macOS/arm64),未独立复现。这会影响最终结论,Stage 2 会再次说明。 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
commented
Aug 28, 2026
Code reviewI read all 18 production modules in full (5.4k lines) and mapped the test suite (280 cases across 60 describe blocks). Independent baseline first: for this batch I'd have built exactly this shape — a framework-neutral streaming fold, thin render helpers, a transcript adapter for resume, and a composer split into decision model + view — so the architecture matches what I'd propose. The execution is disciplined: every port cites its ink counterpart, the fold is pure (input never mutated), and the stateful session-switch paths carry explicit core-first swap + rollback. No correctness bugs, security holes, or regressions found. Modules are unreachable from the running CLI, so runtime risk of this batch is zero until the wiring batch lands. Three things worth fixing or tracking, none blocking this additive batch:
Minor: the The live turn loop this batch introduces (the one piece of genuinely new runtime flow, replicating sequenceDiagram
participant P1 as Backend submit
participant P2 as livePromptEvents
participant P3 as GeminiClient stream
participant P4 as CoreToolScheduler
participant P5 as Event queue
P1->>P2: prompt, abort signal
P2->>P3: sendMessageStream
P3-->>P2: stream events, tool_call_request
P2-->>P1: neutral events, folded into history
P2->>P4: schedule pending calls
P4->>P5: output chunks, approval requests
P5-->>P2: drained live as tool-output events
P4-->>P2: all calls complete
P2->>P3: functionResponses, loop again or end
Files changed (30 of 37 shown)
Testing evidenceThis run carries the PR's own CI signal as fetched from the API — and the headline is that there is none on the reviewed commit. The only workflow runs on
So the suite numbers in the PR body — full workspace build clean, typecheck clean, dependency-direction gate passing, The author has write access, so the sandboxed lanes are directly available: the central unverified claim here is the suite itself, not a behavior change (nothing is wired yet — real-scenario rendering is genuinely N/A until the activation batch). Sandboxed verification would settle it: 中文说明代码审查:完整读了全部 18 个生产模块(5.4k 行),并梳理了测试套件(60 个 describe 块、280 个用例)。独立基线先行:这一批我会做成同样的形状——框架无关的流式折叠、薄渲染辅助、resume 用转录适配、决策模型与视图分离的输入框——架构与我的独立方案一致。执行有纪律:每个移植都注明 ink 对应物,折叠是纯函数(不改输入),有状态的会话切换路径带明确的 core 先行交换 + 回滚。未发现正确性 bug、安全漏洞或回归。模块对运行中的 CLI 不可达,在接线批落地前本批运行时风险为零。 三点建议/跟踪项,均不阻塞本增量批:
小问题: 测试证据:本次携带的是通过 API 抓取的 PR 自身 CI 信号——要点是被审提交上没有任何项目 CI。 — Qwen Code · qwen3.8-max Reviewed at |
commented
Aug 28, 2026
|
Confidence: 3/5 — clean, well-executed review of the code itself, but I cannot attest to the test claims and the PR physically cannot merge yet, so this defers rather than approves. Honest read: this is the best kind of migration batch. The architecture is what I'd have proposed independently — pure event fold, thin render helpers, decision model separated from the composer view, transcript replay as an adapter — and the execution has real discipline behind it: every module cites the ink behavior it reproduces, the fold never mutates its input, the stateful So why not approve? Three things I can't talk myself out of:
Housekeeping note: the standing Deferred to the maintainer — see the follow-up comment for the named asks. 中文说明置信度:3/5 —— 代码本身审查干净、执行到位,但我无法为测试结论作证,且 PR 目前物理上无法合入,因此转交维护者而非批准。 坦率地说:这是一个高质量迁移批次的范本。架构与我的独立方案一致——纯事件折叠、薄渲染辅助、决策模型与输入框视图分离、转录回放做成适配器——执行也有真纪律:每个模块都注明复现的 ink 行为,折叠不改输入,有状态的 /resume 与 /branch 路径带明确的 core 先行交换 + 回滚,280 个测试瞄准行为对齐而非行覆盖。六个月后接手我会感谢作者。diff 里没有顺手改动,每个文件都属于本批。 那为什么不批?三点我说服不了自己绕过的:
杂务说明:本 PR 上挂着的 CHANGES_REQUESTED 评审是我早前模板门禁留下的,作者已补齐模板。GitHub 评审不可编辑,故原样留存、以本次审查为准,未重复提交。 已转交维护者——具体请求见后续评论。 — Qwen Code · qwen3.8-max Reviewed at |
commented
Aug 28, 2026
|
⏸️ Deferring to @yiliang114 — batch 3 of the OpenTUI migration reviews clean as code, but needs a human call on evidence and sequencing:
Nothing here is a request-changes on the author — the template gate is satisfied and the code findings are suggestions; this is an evidence + sequencing defer. — Qwen Code · qwen3.8-max Reviewed at |
commented
Aug 28, 2026
|
Triage re-run completed without a new review. The bot already has a review of its own on 机器人在 The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
* fix(test): dispatch ACP cron responses by prompt * test: limit cron dispatch to user prompts
QwenLM#10334) The `Notify silent triage re-run` step in qwen-triage.yml decides solely from the review list (HAS_REVIEW / STANDING). A Stage 1-pre duplicate-close exit terminates a run without leaving any review, so the step cannot tell it apart from a re-run that did nothing: it POSTs the stage=rerun-summary comment ("Triage re-run completed without a new review ... it did not") on the PR the same run just closed, and emits ::warning title=Triage re-run left no bot review, dispatching a human to a correctly-handled run. Read the PR's own state before warning: if closedAt is at or after the trigger comment, the close is this run's terminal action — exit quietly with no summary comment and no warning. A close BEFORE the trigger keeps the existing notify behaviour, and a failed `gh pr view` falls through to the review-list check as before. Only the notify step changes; no other step is touched. Tests: scripts/tests/qwen-triage-workflow.test.js gains an executed test driving the real step body with a stubbed gh in the close-exit shape (no comment, no warning) and the closed-before-trigger boundary (old behaviour preserved); the existing executed pin for open PRs is extended to serve the new pr view call so the pinned no-review notification behaviour is guarded. Fixes QwenLM#10324 Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
The `Check triage response` step of qwen-triage.yml classified any
non-empty response as a successful triage. A model-layer API error
response ("[API Error: Connection error. ...]", 268 chars) is a
non-empty string, so run 33070765162 (triage for QwenLM#10285) reported
success, posted nothing, and no retry or alert fired.
Strip the known rate-limit guidance suffixes, right-trim, and fail the
step when the response ends with the "[API Error: ...]" shape -- the
pattern qwen-code-pr-review.yml already uses for the same CLI output
behavior (the stream-json adapter appends the formatted error last).
This covers both bare error responses and errors appended after partial
output, while a legitimate summary that merely quotes an API error
mid-prose stays green. The existing failure surface then engages: red
run, "ended early" lifecycle comment, re-run path.
Adds executed-step tests in scripts/tests/qwen-triage-workflow.test.js
following the existing check-step harness: the verbatim 268-char
response, an appended-error response, and a quota error with its
guidance suffix must fail; a quoted-error summary, a normal response,
empty, and 'null' keep their existing behavior.
Fixes QwenLM#10314
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…M#10292) * feat(triage): add duplicate / already-fixed gate (Stage 1-pre) A PR opened after its linked issue was already fixed by a merged PR stays open forever: no existing gate looks at the linked issue's state, and triage has no close action at all. Add a deterministic Stage 1-pre check that resolves the linked issue's closer via GraphQL and, only when the PR's production diff is fully subsumed by the merged fix, posts a bilingual terminal comment and closes the PR. Any remaining delta requests changes instead; ambiguity escalates to the maintainer. Register the gate in SKILL.md so it is not treated as a fabricated policy. * fix(triage): correct Stage 1-pre subsumption, base scope, and linkage - Define subsumption over the entire diff (added lines present AND deleted lines absent in the default branch) so deletions-only and tests-only diffs can never reach the close branch. - Run Stage 1-pre only for PRs targeting the default branch; backports to release/* branches legitimately carry changes already on the default branch. - Extract linked issues via GitHub's closingIssuesReferences instead of a keyword grep that missed 6 of 9 closing-keyword forms and matched substrings like "prefixes". - Pin all three invariants in qwen-triage-workflow.test.js. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(triage): align Stage 1-pre exits, closer query, and state mechanics - Name both Stage 1-pre request-changes exits in the terminal gate exception and the footer rule, give each an explicit `gh pr review --request-changes` command like 1a/1b, and note the duplicate-close exit posts its terminal comment and closes instead of submitting a review; extend SKILL.md's rejection exception clause to cover them. - Closer query: window the LAST 20 CLOSED_EVENTs and take only the most recent close's closer (older closes belong to reopen cycles), null-guard the jq filter, and spell out that a failed or empty query means the closer is unresolved (a PR number or missing issue must not hard-error). - Add the per-issue state loop that assigns $N and produces the state/stateReason the branch bullets consume. Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com> * fix(triage): scope Stage 1-pre linkage to same-repo closing references The closingIssuesReferences extraction kept only the bare issue number, so a cross-repo closing reference resolved against this repo's same-numbered unrelated issue and could drive the wrong-issue gate branches. Filter the extraction to references whose repository matches the triaged repo and state that cross-repo closing references are skipped. Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com> * fix(triage): guard the Stage 1-pre duplicate close against human reopens An explicit /triage re-run on a PR a maintainer reopened after a 1-pre duplicate-close re-derives identical inputs and closes again, indefinitely overriding the deliberate reopen. Before the close exit posts and closes, require that no stage=1-pre comment exists yet on the open PR; if one does, escalate to the maintainer instead of re-closing. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(triage): pin Stage 1-pre state dispatch and ambiguity prohibition The per-issue state dispatch (OPEN / CLOSED NOT_PLANNED / CLOSED COMPLETED) and the "never close on ambiguity" bullet had no test witness: deleting or inverting either kept the suite green. Add toContain pins binding each state to its action and one pinning the ambiguity escalation rule. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(triage): drop production qualifier from Stage 1-pre exits in SKILL.md The exit summary qualified both sides with 'production' (remaining production delta -> request-changes; entire production diff fully subsumed -> close), contradicting pr-workflow.md's operational definition: request-changes fires on ANY remaining delta including non-production additions, and a diff with NO production changes is never fully subsumed. A tests-only PR got opposite instructions from the two files. Align SKILL.md with pr-workflow.md and pin the boundary in the suite. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(triage): define fixed precedence for mixed linked-issue states in Stage 1-pre The per-issue loop legend said OPEN -> proceed to 1a while the aggregate bullets said any closed-as-completed issue enters the closer flow, so 'fixes QwenLM#101 and fixes QwenLM#102' with QwenLM#101 OPEN, QwenLM#102 CLOSED-COMPLETED had two contradictory outcomes. Make the loop collect-only and state one fixed precedence (not-planned > completed > all-open), so an OPEN issue never short-circuits a CLOSED one. Pin the precedence. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(triage): make Stage 1-pre linkage prose honest about its verification The doc promised linkage is 'input to verify against the issue's actual state, never as proof by itself', but the issue state check is the only verification, so an accidental prose linkage (e.g. 'resolves QwenLM#123's closer') still drove the branches off an unrelated issue. Reword to state the truth: there is no deterministic intent check, the linkage decides which issues are read, and the blast radius is bounded because close additionally requires the diff to be fully subsumed by the default branch (true only when the change already landed), so an accidental linkage reaches at worst a visible, reversible request-changes or escalation. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
Round-2 review fixes (17 Critical + 10 Suggestion resolved in code):
- dialogs-shared: move number-select flush out of the setState updater
(StrictMode double-fires onSelect); split setActiveIndex (ink
SET_ACTIVE_INDEX, lands on any in-range row) from highlightIndex
(arrow keys skip disabled rows) so wheel navigation never sticks
- event-adapter: chat_compressed notice mirrors ink formatCount ('~'
prefix for estimated counts); vision_bridge_notice renders
summary\nnotice; explicit projections for task_execution /
findings_list / terminal_image keep multi-MB payloads off the
transcript; retry-countdown-clear forwards isContinuation
- slash-dispatch: isSlashCommandInput drops the '?' branch (ink gate
routes ? input to the model); executeSlashCommand races the action
against the abort signal; dialog effects carry the
OpenDialogActionReturn payload; projected added-item text surfaces
alongside non-handled effects (notice); message-shaped items project
to their text; ui.history comes from env; absent sessionStats stamp
now, not epoch; telemetry parity (recordSkillInvocation /
recordAutoSkillCommandUsage / makeSlashCommandEvent)
- item-projection: model stats render per-(model,source) sections with
N/A for unpriced entries; Tool Calls line uses ASCII x like ink;
redactProxy deduplicated via systemInfoFields export
- theme: palette/syntax colors resolve through color-utils toHex before
parseColor (ink CSS names / *bright names no longer degrade to
magenta); unresolvable values stay unset
- key-map: kitty 'kpenter' normalizes to 'return'; resolveCommands
exposes ink's key fan-out (Ctrl+C fires QUIT + CLEAR_INPUT)
- a11y: hardWrap delegates to wrap-ansi (word-boundary parity with
ink's screen-reader path); markdown reducer tracks fence length,
keeps fence-like lines literal inside fences and inner backticks in
multi-backtick spans; stripAnsi delegates to strip-ansi plus a
private-parameter CSI pass (SGR mouse, DEC save/restore)
- clipboard: OSC 52 write gated on a TTY (stderr preferred), tests spy
the stream instead of writing real sequences to the runner's terminal
- exit-guard: independent per-key arm windows like ink
- dialogs-theme: diff preview pane receives syntaxStyle/filetype
* ci: serialize helper test files * test(ci): pin helper test serialization
…review - kitty-negotiation: KITTY_REPLY_RE requires at least one flag digit (\d+), so an echoed bare query \x1b[?u in PTY/CI environments no longer resolves true and locks the renderer into kitty mode on a terminal that never answers queries; the accumulation buffer keeps only a 256-byte tail (bounded memory, bounded rescan under byte floods); the settle-window drain is removed — an EventEmitter data listener cannot consume chunks from other listeners, so late replies flow to the renderer's input parser like any other terminal noise - a11y-screen-reader: ScreenReaderOutputWriter sanitizes written content (stripAnsi + drop bare C0/C1 controls, keep newlines) so the plain-text-only contract is enforced at the writer instead of trusting every future caller — smuggled OSC 52 clipboard writes or title/cursor sequences cannot execute on the main screen
…nLM#9811) * feat(vscode-ide-companion): reuse WebShell transcript UI behind experimental flag Bridge ACP session/update notifications into the shared SDK daemon transcript reducer and render the result with the WebShell transcript component, gated on qwen-code.experimental.webShellTranscript (default off). The WebShell renderer and its heavy transitive dependencies (echarts, mermaid, shiki, codemirror, katex) are lazily loaded via esbuild code splitting, so the default configuration keeps the ~700KB webview bundle unchanged. * fix(vscode-ide-companion): grant wasm-unsafe-eval for shiki WASM when WebShell transcript enabled * feat(vscode-ide-companion): adopt WebShell transcript as default timeline Drop the experimental flag and the legacy MessageList renderer. The companion timeline now always renders through the shared WebShell transcript component, fed by ACP session/update notifications via the SDK daemon transcript reducer (lazy loaded through esbuild code splitting). The flag-gated wiring is removed: the qwen-code.experimental.webShellTranscript setting, the conditional CSP/body attribute in WebViewContent, and the legacy MessageList path in App.tsx (~850 lines). The webview CSP now grants wasm-unsafe-eval unconditionally for Shiki's Oniguruma WASM. * fix(vscode-ide-companion): reset WebShell transcript state on session switch The experimental useAcpTranscript hook only consumed transcriptUpdate messages, so its reducer state survived session boundaries. When the extension switched sessions it kept the webview mounted and replayed the newly-selected session through ACP, causing the previous session's blocks to merge with the new replay (e.g. user text "alpha" from session A leaked into session B as "alphabeta"). Reset both the reducer state and the rendered blocks on the same boundaries the legacy message flow uses: qwenSessionSwitched (sent before the ACP replay of the selected session) and conversationCleared (new session). Adds a regression test that replays two sessions with a switch between them. * fix(vscode-ide-companion): harden WebShell transcript session boundaries - reset the transcript on `conversationLoaded` too, closing the same cross-session leak the previous commit fixed for `qwenSessionSwitched` and `conversationCleared` (agent reconnect posts only this boundary) - track the active session id and drop late `transcriptUpdate` frames whose `sessionId` no longer matches, so a previous session's trailing frames cannot contaminate the next session's timeline - seed the transcript from cached messages carried by `qwenSessionSwitched` so offline restores and load-failure fallbacks render their history instead of a blank timeline - dispatch `assistant.done` on `streamEnd`/`sessionLoadComplete` so the final assistant/thought block of a turn (or history replay) does not stay `streaming: true` forever * fix(vscode-ide-companion): adopt live ACP session id after load-failure fallback * fix(vscode-ide-companion): echo user prompt into WebShell transcript * fix(vscode-ide-companion): keep WebShell transcript expanded and clear of the composer * fix(vscode-ide-companion): surface local error and interrupt notices in the transcript area * fix(vscode-ide-companion): restore file-link opening from the WebShell transcript * fix(vscode-ide-companion): restore contributed copy commands for the WebShell transcript * fix(vscode-ide-companion): add localOnly marker to TextMessage state type * fix(vscode-ide-companion): restore /insight progress card and report link in the transcript UI * fix(vscode-ide-companion): finalize in-flight tools on timeout and pin session-switch seeding guard Map streamEnd reasons timeout/session_expired onto the reducer's error reason so abandoned mid-tool turns no longer spin forever (ceuI). Add qwenSessionSwitched cases with no messages field and an empty cache array; the no-messages case fails when the seeding guard is forced true, pinning its false side (ceuN). * fix(vscode-ide-companion): remove unreachable editMessage backend and dead submit options The user-message edit/rewind UI was dropped in the WebShell-transcript migration, leaving editTargetTurnIndex/onSubmitted options in useMessageSubmit and the full editMessage/rewind flow in SessionMessageHandler unreachable. Remove the dead options, the editMessage dispatch case, the rewind/snapshot flow with its recovery branches, and their tests (R1-8 direction b). * fix(vscode-ide-companion): drop write-only loadingMessage bookkeeping The waiting-message renderer was removed with the WebShell transcript migration and the user prompt is echoed into the timeline at send time (bd09e19), so the loadingMessage string was write-only dead state. Keep the isWaitingForResponse flag (submit gating / cancel) and pin its API surface (R1-19 direction b). * fix(vscode-ide-companion): align waiting-flag pin test with the argument-less setter * fix(vscode-ide-companion): echo attached images into the transcript timeline The prompt carries pasted/attached images as ACP resource_link blocks, which the transcript reducer cannot render (no inline data), so user images vanished from the timeline while the attach path stayed alive. Read each saved prompt image back from disk and echo it alongside the text echo as an inline user_message_chunk image part (the daemon-echo content shape), which the shared reducer folds into the user block and the WebShell renderer already displays. Unreadable images are skipped without breaking the send. * fix(vscode-ide-companion): track live VS Code theme for the transcript webShellTheme was snapshotted once at mount via useMemo with an empty dependency array, so switching the VS Code color theme left the timeline on the stale theme (VS Code updates data-vscode-theme-kind on <body> in place without reloading the webview). Hold the theme in state and refresh it with a MutationObserver on the body theme attributes. * fix(vscode-ide-companion): copy every transcript block kind and map ambiguous row keys - Copy All Messages now includes tool, shell, user_shell, and status blocks via getBlockCopyText, matching the pre-PR copyAllMessages handler which included formatted tool calls (review 5001842059 S-1). - findBlockByRowKey prefers an exact id match and otherwise the longest matching block id, so one block id that dash-prefixes a sibling (e.g. `a` vs `a-1`) can no longer capture the sibling's row key (S-4). * fix(vscode-ide-companion): drop whitespace-only cached transcript rows cachedMessageToNotification rejected empty strings but admitted whitespace-only content, which the reducer turns into an empty block when seeding history from cached rows. Reject content that trims to nothing (review 5001842059 S-2). * fix(vscode-ide-companion): ship missing third-party notices in NOTICES.txt Extend generate-notices.js so the regenerated NOTICES.txt carries the attribution texts it previously only pointed at or dropped: - Append license files from a package's licenses/ directory (echarts' Apache LICENSE references licenses/LICENSE-d3 for its embedded d3-derived files; the BSD-3-Clause text is now shipped). - Append a package's NOTICE file when present (Apache-2.0 §4(d)), covering echarts' Apache Software Foundation attribution. - Accept string-form package.json repository values (full URLs and GitHub shorthand) instead of emitting "(No repository found)". - Fall back to the standard MIT text (copyright holder from package.json metadata) for MIT-declared packages that ship no license file. * fix(vscode-ide-companion): show a recoverable error state when the transcript chunk fails to load * test(vscode-ide-companion): gate the transcript blocks wiring into the WebShell renderer * test(vscode-ide-companion): gate the transcriptUpdate forwarding from agent to webview * docs(vscode): plan complete Web Shell cutover * refactor(web-shell): own daemon React bindings * fix(webui): preserve package entry filenames * refactor(vscode): complete WebShell UI cutover * chore(vscode): refresh third-party notices * fix(vscode): fill embedded chat viewport * test(web-shell): disambiguate workspace visual locator * fix(vscode): match embedded chat layout to host * fix(vscode): compact embedded chat styling * fix(vscode): align embedded chat density with VS Code * fix(vscode): complete embedded composer integration * fix(vscode): restore user message editing after cutover * fix(vscode): complete WebShell feature parity * test(vscode-ide-companion): repair host-wiring tests for the WebShell cutover * refactor(vscode-ide-companion): replace webui build scanner with an ESLint boundary rule The bespoke recursive source scanner reimplemented a dependency-boundary check on every extension build. A scoped no-restricted-imports rule enforces the same boundary on every lint run with less custom code; the manifest dependency entry was already removed by the cutover. * fix(web-shell): keep ChatEditor commands prop referentially stable (QwenLM#9811) The `additionalSlashCommands = []` destructure default allocated a fresh array on every App render, invalidating the `commands` useMemo and breaking ChatEditor memoization on every transcript-only re-render. Default to a module-level constant instead, matching the existing EMPTY_* convention. Also align the /skills completion expectation with the autoSubmit field the completion source intentionally emits for leaf skill items. * fix(vscode): distinguish the VS Code channel and localize its chrome The companion now drives Web Shell against a shared `qwen serve` daemon, so the CLI, the browser Web Shell, and this extension all create sessions in the same workspace catalog. Web Shell recorded `'default'` for every surface, leaving VS Code conversations indistinguishable from terminal and browser ones — the panel's history listed sessions the user never opened here, and nothing attributed a session back to the editor. Give Web Shell a `sessionSourceType` prop (defaulting to today's `'default'`) and have the companion stamp `'vscode'` on the sessions it creates, then scope the history dropdown to that source. The host also supplies a stable daemon `clientId`, which the bootstrap previously declared but never sent. Web Shell localizes its own surface from the `language` signal while the companion's chrome was hardcoded English, so a zh-CN panel rendered a Chinese transcript under an English header, history dropdown, onboarding screen, and account dialog. Route that chrome through a small string table driven by the same signal, including the host-only slash entries. Also fix accessibility defects in the history dropdown: rename and delete were revealed on hover alone and unreachable by keyboard, date headers sat inside `role="listbox"` as invalid non-option children, arrow-key roving stopped at group boundaries, `aria-modal` had no focus trap, and a primed "Delete?" survived both search changes and the pointer leaving the row. Formatting: `FileMessageHandler` and `SessionMessageHandler` were left unformatted earlier in this branch and failed the Prettier gate. * refactor(vscode): drop code orphaned by the WebShell cutover The webview entry now renders EmbeddedApp against the daemon, which left the ACP-era hook layer unreachable: nothing imports acpTranscriptAdapter, useWebViewMessages, useAcpTranscript, useToolCalls, useSessionManagement, useMessageHandling, useFileContext, useImage, or the permissionTypes added by this branch. A reachability walk from webview/index.tsx reaches eight modules; every reference to the rest comes from inside the orphaned set itself, so it deletes as a closed unit. EmbeddedWebShell goes with them. It was the host-driven entry point from the earlier stage of this branch, superseded when EmbeddedApp moved to WebShellWithProviders, and has had no consumer since — only its own DOM test and a barrel export. Also harden the daemon process lifecycle. `start()` returned the cached runtime without comparing the workspace, so in a multi-root window the second folder's chat silently reused a daemon bound to the first and scoped every session, history page, and prompt to the wrong root. Bind the daemon to its workspace and respawn on a change, keep a superseded child's late exit from tearing down its successor, and report a post-startup exit to the webview instead of leaving it fetching against a dead port. * docs(vscode): describe the daemon architecture the cutover actually ships The design doc still recorded the plan this branch started from: keep ACP as the runtime boundary, add no daemon server or loopback port, and treat "replacing ACP with daemon HTTP/SSE" as a non-goal. The final stage did exactly that, so the document argued against the code beneath it. Record the decision and its consequences instead — two processes per workspace, a daemon shared with the CLI and browser Web Shell, the vscode source type that keeps the panel's history its own, workspace rebinding in multi-root windows, and the turn-driven host features that stopped firing. * fix(vscode): repair round-2 review findings on the web-shell cutover (QwenLM#9811) - closeDiff now resolves workspace-relative paths the same way showDiff does, so permission-cycle diffs opened from daemon-relative paths can actually be matched and closed - a superseded or disposed daemon child no longer reports its exit as a crash of the live daemon - authCancelled no longer hides an already-authenticated session behind onboarding; only an unknown auth state settles to unauthenticated - selection-only activeEditorChanged events no longer undo an explicit active-file exclusion - prepareSubmit dedupes mentions in both path spaces and matches typed references on a whole-reference boundary - permission diffs open only from the SDK's authoritative file_diff preview (writes included, model-controlled toolCall mining removed) - the webview HTML carries VS Code's locale so chrome strings localize - discontinued qwen-oauth models are no longer re-applied through the new-session initial-model route * fix(vscode): repair round-3 critical findings on the web-shell cutover (QwenLM#9811) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(web-shell): import daemon-react-sdk from web-shell instead of webui The cutover branch dropped the ./daemon-react-sdk export from @qwen-code/webui, but the TerminalPanel merged in from main still imports it, breaking the web-shell vite build (Missing "./daemon-react-sdk" specifier). Point the import and its test mock at @qwen-code/web-shell/daemon-react-sdk, which re-exports the same useWorkspace hook and matches every other web-shell call site. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(vscode): close WebShell UI regression gaps Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(vscode): initialize WebShell refs explicitly Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(vscode): narrow queued prompt edits Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(release): enumerate actual npm workspaces Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(web-shell): include hasOlderHistory in the render-item callback deps The renderItem useCallback reads hasOlderHistory to gate the edit action but omitted it from its dependency array, failing CI's react-hooks/exhaustive-deps gate. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(web-shell): report each connection error once to stop the inline onError re-render loop (QwenLM#10454) * fix(web-shell): report each connection error once to stop the onError re-render loop While a connection error persists (e.g. the daemon is unreachable), the error-notification effect re-fires whenever the onError callback identity changes. Hosts such as the VS Code embedded app pass an inline onError and update their own state when it fires, so every notification triggers a host re-render that hands the effect a fresh callback identity — re-notifying the same persistent error forever (QwenLM#10406). Track the last reported connection.error value in a ref and notify only when the value changes, resetting the tracker once the connection recovers. This guards every inline-callback consumer, not just memoized hosts. Fixes QwenLM#10406 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(web-shell): only stamp the dedup ref once an onError handler exists Stamping lastReportedConnectionErrorRef before delivery meant a host that attaches onError after a persistent connection error appeared never received it: the no-op delivery already marked the error as reported. Guard on the handler first and add a regression test covering the late-attach case (red when the guard is removed). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(web-shell): document the onError dedup contract and fix comment wording Describe the reported-once-per-distinct-error semantics, the reset on recovery, and that replacing the handler mid-error does not re-deliver. Reword the effect and test comments to describe the host class instead of naming the VS Code embedded app, which passes a useCallback handler. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(vscode): mirror the web-shell value-dedup in the EmbeddedApp mock The WebShellWithProviders mock re-notified on every onError identity change, mirroring the loop App.tsx can no longer produce. Rewrite it to report each distinct error value once (resetting on recovery), keep the loop guard as a regression tripwire, exercise it with a changing callback identity plus a post-delivery effect re-run, and refresh the handleShellError comment that still cited the old loop as the memoization reason. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(vscode): cast the captured onError prop for the mock wrapper CapturedProps is an unknown index signature, so the destructured onError needs the same cast the previous mock applied inline to stay callable under tsc. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(vscode): bail the EmbeddedApp mock before stamping when no onError exists The mirrored dedup effect stamped lastReportedError and counted a notification even when no handler was attached, while App.tsx returns before stamping on that path. Add the same early return so a handler attached mid-error still receives the persistent error, and pin the no-handler no-stamp behavior with a test that fails if the guard is removed. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(web-shell): remove duplicate history dependency Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(vscode): close remaining WebShell cutover regressions Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(vscode): keep permission diff handling host-scoped Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(vscode): remove orphaned completion trigger test Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: yiliang114 <jinjing.zzj@gmail.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
left a comment
There was a problem hiding this comment.
Not reviewed: territory review of all 168 diff chunks — chunk agents were never launched; the review time budget was exhausted before the territory fan-out.
Not reviewed: test coverage matrix — prompt was built but the agent was never launched (review time budget).
Not reviewed: cross-file tracer (1c) — prompt was built but the agent was never launched (review time budget).
Not reviewed: whole-file invariant review of the 4 heavily rewritten files — agents were never launched (review time budget).
Not reviewed: the entire diff, the whole-diff test-coverage check, the cross-file consistency pass, the invariant check (state, timers, collections) on packages/cli/src/ui/opentui/slash-dispatch.ts, the invariant check (counters, return values, error taxonomies) on packages/cli/src/ui/opentui/slash-dispatch.ts, the invariant check (config fields, early returns) on packages/cli/src/ui/opentui/slash-dispatch.ts, the invariant check (state, timers, collections) on packages/core/src/core/geminiChat.ts, the invariant check (counters, return values, error taxonomies) on packages/core/src/core/geminiChat.ts, the invariant check (config fields, early returns) on packages/core/src/core/geminiChat.ts, the invariant check (state, timers, collections) on packages/core/src/core/geminiContentGenerator/geminiContentGenerator.ts, the invariant check (counters, return values, error taxonomies) on packages/core/src/core/geminiContentGenerator/geminiContentGenerator.ts, the invariant check (config fields, early returns) on packages/core/src/core/geminiContentGenerator/geminiContentGenerator.ts, the invariant check (state, timers, collections) on packages/web-shell/client/hooks/useAtMentionMenu.ts, the invariant check (counters, return values, error taxonomies) on packages/web-shell/client/hooks/useAtMentionMenu.ts, the invariant check (config fields, early returns) on packages/web-shell/client/hooks/useAtMentionMenu.ts — its prompt was built, but no agent on record was launched with it.
Not reviewed: reverse audit — no auditor was launched with a prompt this skill builds — the pass that hunts what the rest of the review missed ran, if at all, without the method its brief carries.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/e2e.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-triage.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
未审查:territory review of all 168 diff chunks — chunk agents were never launched; the review time budget was exhausted before the territory fan-out。
未审查:test coverage matrix — prompt was built but the agent was never launched (review time budget)。
未审查:cross-file tracer (1c) — prompt was built but the agent was never launched (review time budget)。
未审查:whole-file invariant review of the 4 heavily rewritten files — agents were never launched (review time budget)。
未审查:整个 diff、全 diff 测试覆盖检查、跨文件一致性检查、不变量检查(状态、定时器、集合)(packages/cli/src/ui/opentui/slash-dispatch.ts)、不变量检查(计数器、返回值、错误分类)(packages/cli/src/ui/opentui/slash-dispatch.ts)、不变量检查(配置字段、提前返回)(packages/cli/src/ui/opentui/slash-dispatch.ts)、不变量检查(状态、定时器、集合)(packages/core/src/core/geminiChat.ts)、不变量检查(计数器、返回值、错误分类)(packages/core/src/core/geminiChat.ts)、不变量检查(配置字段、提前返回)(packages/core/src/core/geminiChat.ts)、不变量检查(状态、定时器、集合)(packages/core/src/core/geminiContentGenerator/geminiContentGenerator.ts)、不变量检查(计数器、返回值、错误分类)(packages/core/src/core/geminiContentGenerator/geminiContentGenerator.ts)、不变量检查(配置字段、提前返回)(packages/core/src/core/geminiContentGenerator/geminiContentGenerator.ts)、不变量检查(状态、定时器、集合)(packages/web-shell/client/hooks/useAtMentionMenu.ts)、不变量检查(计数器、返回值、错误分类)(packages/web-shell/client/hooks/useAtMentionMenu.ts)、不变量检查(配置字段、提前返回)(packages/web-shell/client/hooks/useAtMentionMenu.ts)——它的 prompt 已构建,但没有任何 agent 有记录用它启动过。
未审查:反向审计——没有审计 agent 是用本 skill 构建的 prompt 启动的——负责搜寻评审其余部分遗漏问题的这道工序,即便运行过,也缺失了 brief 承载的方法。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/e2e.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/qwen-triage.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.8-max via Qwen Code /review (v0.22.3)
* fix(cli): align provider update model messaging * test(cli): cover unaffected provider update prompts * docs(cli): align provider update model guidance * test(cli): assert provider diff runtime shape --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…8754) * fix(config): remove dead dynamic command translation setting * fix(review): keep repository context within file limit * test(review): pin repository context headroom * test(config): pin removed setting surfaces * test(review): make context bound probe robust * test(review): avoid changed-path exclusion false positive * test(review): harden manifest policy probes * test(review): cover all top-level skill sources * test(review): share repository file walker * test(review): normalize walked repository paths * chore(review): drop temporary context CI backport --------- Co-authored-by: destire-mio <248462155+destire-mio@users.noreply.github.com>
…wenLM#8725) The `--parallel` flag was a proposal for `npm`, but it wasn't merged in the end. With version 12 unknown flags are now errors instead of warnings, so on that version it blocks running `npm run test` and similar tasks.
* fix(cli): handle unverifiable Windows file identities
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): pin getWorkingDir() assertions to the raw stored cwd
Config resolves targetDir in the constructor but stores cwd verbatim,
so getWorkingDir() returns it unresolved. The rewritten assertions
compared against path.resolve('/tmp'), which holds on POSIX but is
drive-qualified on Windows (C:\tmp), re-breaking both rebinds tests on
the windows-latest lane this PR restores. Restore toBe('/tmp') and
comment the getter-contract asymmetry.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): pin the backfill isolation oracle to the exact guard message
expect.any(String) also matches an empty or unrelated error, so the
closed-generation isolation path could silently stop being exercised.
Pin WorkspaceGenerationClosedError's exact message instead.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): treat symlink-to-directory ancestors as provably absent
isPathProvablyAbsent's ancestor walk used lstatSync(...).isDirectory(),
which is false for a symlink resolving to a directory, so a genuinely
absent path under a symlinked intermediate was reported "not provably
absent" and forced a full re-review every local round. Probe the
ancestor with statSync so resolved directories are traversable; a
symlink to a file still refuses, and a broken link throws ENOENT and
the walk continues. Document the fail-closed contract on the helper
and add real-fs tests for the directory, file-component, and symlink
shapes.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(cli): pin isPathProvablyAbsent's Windows-shaped arms with a spied lstatSync
On POSIX a regular-file intermediate raises ENOTDIR at the leaf, so the
ancestor walk added for Windows never executes there: a future
simplification back to "leaf ENOENT ⇒ absent" would pass every real-fs
test while restoring the R19-3 misclassification. Add platform-
independent arms that spy lstatSync into the Windows shape — ENOENT
leaf with a regular-file ancestor stays unmeasurable (false), ENOENT
leaf under a directory ancestor is genuine absence (true), and a
non-ENOENT leaf error is never absence. Restore the fail-closed
pointers the deleted R19-2/R19-3 call-site comments carried.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): tighten the review same-file inode predicate to safe-positive values
same-file.ts used core's canonical hasVerifiableInode (Number(ino) !== 0),
but Windows surfaces 64-bit NTFS file indices rounded at the JS boundary:
two distinct files whose indices land in one double-rounding bucket
compare equal, so isSameFile equated them and the anti-clobber guards
findings/save-artifact/repo-context spuriously refused non-colliding
paths. Restate Number.isSafeInteger(ino) && ino > 0 locally instead of
tightening core, whose looser predicate also gates
assertVerifiableTranscriptIdentity on bigint transcript inodes. Extend
the volume-pose harness with a rounded-inode shape: distinct files stay
distinct, one file under two spellings stays one.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* refactor(cli): share one inode-verifiability predicate across identity checks
The safe-positive inode semantics this PR establishes (verifiable
predicate, normalize to 0, verifiability-parity compare) were pasted
into standalone-deletion-journal.ts and acpAgent.ts while both already
import conversation-directory-identity.js — a lockstep edit the next
tightening would have to repeat in three places, with one missed copy
enough to let one identity check accept what another rejects. Export
hasVerifiableInode and normalizedInode from the cli-local module and
import them at both call sites; no core barrel import, so the serve
bundle closure is unchanged.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(cli): cover the unverifiable-inode branch of managed-directory identity
managedConversationExpectation builds expectations from real fs stats,
always safe-positive on CI platforms, so the new degradation branch in
assertManagedConversationDirectoryIdentity never executed — a mutant
that unconditionally claims inode verifiability survives every test yet
reintroduces the 'standalone working directory identity is compromised'
rejection on Windows volumes with file IDs beyond MAX_SAFE_INTEGER.
Mirror the conversation-directory-identity simulation: spy fs.lstat to
report ino 0 and Number.MAX_SAFE_INTEGER + 1, and assert sessionCd
resolves with the inode-0 expectation instead of throwing.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(cli): import the Stats type from node:fs in the acp agent test
node:fs/promises re-exports no Stats member, so the degradation test's
cast must use the node:fs type directly to keep tsc --noEmit green.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* perf(cli): share absence-probe ancestor results across one enumeration
isPathProvablyAbsent walked the same missing ancestor chain to the repo
root once per path. In a sparse-checkout repo every out-of-cone tracked
path is absent with unmaterialized ancestors, so the walk multiplied
the metadata calls by depth+1 on each invisibleTrackedPaths enumeration
— the slowest call on Windows, the platform this series restores (R1-6).
Thread an optional per-enumeration memo of actually-probed ancestor
verdicts through the walk and seed one in the enumeration; the leaf
lstat stays per-path, ENOENT-only fail-closed semantics unchanged. Two
spied-statSync tests pin the one-probe-per-ancestor count and the
memoized non-ENOENT refusal.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): share the absence-probe memo across the vanished-path enumeration
`vanishedStillOnDisk` called `isPathProvablyAbsent` without the
per-enumeration `ancestorProbes` memo this PR introduces, while the
sibling enumeration `invisibleTrackedPaths` passes one. A subtree
dropped between rounds (a bulk deletion committed, a branch switch)
sends every vanished path down the same missing ancestor chain:
K×(d+1) synchronous statSync calls where K+O(chain) would suffice —
the multiplied walk this diff introduced at that call site. Pass a
fresh per-invocation memo, mirroring `invisibleTrackedPaths`, and pin
it with an end-to-end capture test counting the ancestor probes (red
without the memo argument).
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* refactor(cli): close the conversation-identity predicate restatements (R1-8)
The module comment claimed the verifiability predicate was the shared
semantics for EVERY conversation-identity check ("import it, do not
restate it"), while four restatements stood in the tree. Consolidate
what can be consolidated without moving a semantic boundary:
- export `isSameDirectoryIdentity` and replace acpAgent.ts's textually
identical `isSameManagedDirectoryIdentity` with it (three call
sites);
- same-file.ts now imports `hasVerifiableInode` from the identity
module instead of keeping a fourth verbatim copy;
- narrow the module comment to the consumers that actually import it
and document the two deliberate local restatements that remain:
`syncStandaloneRoot`'s inline composite around the open handle, and
`hasExpectedManagedDirectoryIdentity`, whose expectation side must
keep deriving verifiability from `inode !== 0` because the wire
payload carries no `inodeVerifiable` field.
Core's looser canonical predicate stays untouched.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Use the shared path containment helper so workspace-local directories such as ..build are not treated as escapes while real parent traversal remains rejected. Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…n_context (QwenLM#9847) * fix(cli): reject symlinked screenshot paths on win32 in capture_screen_context Windows silently ignores O_NOFOLLOW, so the symlink guard in readPrivatePng was a no-op on win32: a Host returning a symbolic link could have its target read as a screenshot, bypassing the private-directory containment check. Probe the path with lstat and reject symlinks (including NTFS junctions) before opening; POSIX keeps O_NOFOLLOW as a TOCTOU backstop. Before: 'rejects a symlink and deletes only the Host-provided link' failed on Windows (tool read through the link). After: rejected with an explicit error; only the link is removed and the target stays intact. Part of QwenLM#9481 (cluster 3). * test(cli): pin the dedicated symlink error message in capture_screen_context The lstat guard added for win32 had no test that fails when it is removed: on Windows dropping the guard makes the tool read through the link, but on POSIX CI O_NOFOLLOW still rejects the link with a generic ELOOP error, so the regression would be invisible there. Assert the exact 'Host returned a symbolic link screenshot path.' message so both removal paths turn the suite red. Addresses review finding R1-1 (Suggestion). * chore: re-trigger automatic review --------- Co-authored-by: zhou2024NAU <zhou2024NAU@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
QwenLM#8702) * docs(users): add evidence-based-conclusions recipe to common workflows Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(users): replace scope table with link to memory.md per review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…enLM#9862) * fix(acp): route-scope the session token-limit cache in Session.ts The ACP Session keeps a private `lastPromptTokenCount` fed from streamed `usageMetadata`, reset only when the chat instance changes (#syncPromptTokenCountWithCurrentChat). ACP model switches (unstable_setSessionModel -> setModel -> config.switchModel) rebuild the content generator but keep the same GeminiChat, so a count recorded on the previous route survived and anchored the session-token-limit gate for the new route: any modelOverride send (compression skipped) or any send whose compression attempt throws reached #getPostCompressionTokenCount(null) with the stale pre-switch count and was wrongly dropped with SessionTokenLimitExceeded / stopReason 'max_tokens'. Attribute the cached count to the route that produced it (Config.getModelRouteIdentity) and invalidate it on a route change, mirroring the route-scoping QwenLM#9506 applied to the GeminiChat counts. Same-route counting and the chat-instance reset are unchanged. Fixes QwenLM#9529 * fix(cli): retain acp token counts per route * fix(cli): retain token counts for request route * test(acp): pin override-route token recording for the QwenLM#9529 gate Add a QwenLM#9529 regression test that drives a route override through the full-turn vision selector (fullTurnModelOverride): the first override send streams usage metadata over the session token limit, and a second same-override send whose compression falls back to the cache must then resolve max_tokens — proving the first count was recorded under the override route key, not the active route's. Reverting the record site to the default route key makes the test fail. Also make the hoisted requestRouteKey initialization in #executePromptInner and #runStopContinuation use optional chaining (this.config.getModelRouteIdentity?.(...) ?? ''), matching the #currentRouteKey convention for partial Config mocks; the unguarded call threw for every prompt in the ~340 Session tests whose mock config does not define getModelRouteIdentity. * fix(cli): bound acp route token cache * fix(cli): cover acp route token eviction * fix(acp): drop dead request route key initializers in the send loops The hoisted requestRouteKey initializer in #executePromptInner and #runStopContinuation computed a route identity that was discarded on every turn: the null-stream paths return before any record site, and every path that reaches a record site first assigns requestRouteKey from the send result. Replace both with a plain empty initializer. * test(acp): factor the QwenLM#9529 over-limit usage stream setup into a helper The ~25-line mock setup that streams a 101-token usage metadata chunk on the first send and an empty stream on the second was pasted verbatim in eight QwenLM#9529 session-token-limit tests. Extract it into createOverLimitUsageSendStream next to the existing stream helpers and migrate all eight copies. * test(acp): cover route count eviction in the session token cache The evict-oldest branch in #setLastPromptTokenCount had no coverage: existing tests exercise at most three route identities, so deleting the eviction block, flipping the size comparison, or evicting the newest entry all survived silently. Drive nine distinct route identities (one past MAX_RETAINED_SESSION_ROUTE_COUNTS) through session.prompt, then assert the evicted oldest route reads back no cached count (its send goes out) while a retained route still trips the gate. * test(acp): pin the stop-continuation token record route scope The existing override-route recording test only exercises the primary prompt record site in #executePromptInner; the only Stop-hook gate test drops the continuation send before streaming and never mocks getModelRouteIdentity, so the #runStopContinuation record site was unpinned. Drive a Stop-hook continuation whose send streams over-limit usage under a \0 exact-route override, then assert a second same-override send trips the gate from the cached count — reverting the continuation record site to the default route key makes the test fail. * test(acp): factor the QwenLM#9529 vision-override mock setup into a helper * fix(acp): invalidate the session token cache on every compression rewrite The route-keyed fallback cache was only cleared on a chat-instance change and re-stamped by the pre-send compression hook, so compressions inside GeminiChat.sendMessageStream (hard-tier rescue, reactive overflow — surfaced as StreamEventType.COMPRESSED, which the session loops ignored) left it holding pre-compression counts sized against destroyed history. A returning route's send could then be false-dropped with 'Session token limit exceeded' when tryCompressChat failed. Handle StreamEventType.COMPRESSED in all four session send loops and clear every retained route count on any COMPRESSED result (pre-send or in-send), re-stamping the fresh count under the request route and the active route when they differ — mirroring GeminiChat clearing its keyed counts in the COMPRESSED branch of tryCompress. Move the pre-send record after request-route resolution so the invalidation keys correctly. Update the zero-newTokenCount COMPRESSED test to pin the corrected semantics: after a successful rewrite the pre-compression count must not gate the send (owner-side parity). * fix(acp): re-check abort after the route-key await (QwenLM#9529) * fix(acp): key cron and background-notification usage records by the request route (QwenLM#9529) The cron/loop-tick and background-notification send loops captured the request route key and threaded it into the COMPRESSED handler, but their post-stream usage record still called #recordPromptTokenCount(usageMetadata), whose default route key is the record-time active route. A model switch landing between request and record stored the outgoing route's API-reported count under the incoming route's key, so the next new-route send whose pre-send compression failed was false-dropped with 'Session token limit exceeded' (and, on the cron path, could permanently disable cron via #stopCronAfterTokenLimit). Pass the captured requestRouteKey into the usage record at both call sites, matching the interactive prompt loops. Add collocated tests pinning that each loop records usage under the request route even when the route switches mid-stream. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(channel): preserve source metadata on resume Ensure channel workers stamp source attribution when resuming routed sessions so legacy channel conversations are grouped under Channels instead of Tasks. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(channel): carry source metadata through restore Propagate channel source attribution through the SDK restore transport and daemon resume route so resumed legacy channel sessions classify correctly. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(channel): persist restore source metadata Ensure restored channel sessions carry source attribution through the SDK, daemon route, and ACP bridge without overwriting existing persisted attribution. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(channel): guard internal restore source metadata Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(channel): cover restore source review cases Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(channel): forward restore source type to daemon bridge Keep channel restore attribution complete when the bridge creates daemon sessions so source ids are not sent without their source type. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(channel): cover restore source metadata regressions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(session): harden restore source attribution Reject restores that lose their channel while source metadata is persisted, and make appended source records discoverable after restart. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(session): keep source metadata discoverable Re-anchor durable source attribution as transcripts grow and remove the unused channel source-type option. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com --------- Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Let DeepSeek reasoning models use their model default temperature so deterministic sampling does not amplify repeated thinking loops. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com>
* fix(core): disambiguate send_message destinations * test(core): cover task miss without active team * test(core): cover teammate hint display * test(core): cover no-hint display paths * fix(core): surface teammate hints in task errors
…le-task end (QwenLM#9638) * fix(cli): deliver teammate messages at tool-round boundaries, not whole-task end Teammate→leader messages were only drained into the leader's session when `streamingState === Idle`. During a long multi-round agentic task `streamingState` never reaches Idle between rounds (tool calls are continuously scheduled/executing or terminal-but-unsubmitted), so queued teammate messages waited for the entire task — minutes — even though the tool description promises delivery when the current turn ends (QwenLM#8172). Drain `teammateQueueRef` at the tool-round boundary in `handleCompletedTools`, appending the envelopes after the tool-response parts of the next `SendMessageType.ToolResult` submission — the same mechanism and ordering already used for steer messages at that exact site (tool_result blocks lead the user message). The existing Idle drain stays as the fallback for turns that end without another tool round. Race/loss safety: the drain is skipped on cancelled boundaries and generation-change-surviving continuations (mirroring steer), and the batch is restored (idempotently) on cancel/preempt/admission/delivery failure so messages are never lost or double-delivered. The `isSubmittingQueryRef` guard (QwenLM#4844) on the Idle path is untouched. * fix(cli): settle boundary-drained teammate envelopes by acceptance, not failure Review round on QwenLM#9638 found two real loss/duplication edges in the tool-round boundary teammate drain, plus test/observability gaps: - A drained envelope was delivered TWICE when the boundary submission failed after being accepted (user cancel mid-stream, terminal API error): GeminiChat pushes the submission's user content before any model attempt, yet onDeliveryFailed unconditionally requeued the envelope and the Idle drain resubmitted it. - A blocking UserPromptSubmit hook permanently LOST the envelope: ToolResult is not in the hook's exclusion list, the blocked stream dispatched onDelivered, and nothing restored the drained queue. - Boundary deliveries skipped chat recording entirely (recordNotification is keyed on Teammate/Notification), so resumed sessions lost both the notification item and the envelope from the reconstructed context. Settle the drained batch by acceptance instead of by failure: snapshot GeminiChat's user-content push counter (the same signal settleSteerInput uses in client.ts) before the submission; on failure, requeue only when the counter did not advance, and when it did, record the delivery via recordNotification instead of redelivering. Count a UserPromptSubmitBlocked stream as a delivery failure so blocked submissions restore their drained payloads. Also extract the drain protocol (splice + display marking + idempotent restore) into one drainTeammateQueue helper shared by the boundary and Idle paths, add debug logs at the drain/record/restore transitions, and shrink the QwenLM#8172 test harness to a wrapper over renderTestHook. New regression tests pin the no-duplicate, hook-block restore, recording, and survivesGenerationChange-skip behaviors (mutation-checked). * fix(cli): isolate teammate delivery settlement Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): settle steer carriers by the actual push, not the hook window The attached steer-input carrier (which the QwenLM#8172 teammate boundary settlement rides) was decided from a push-counter snapshot taken at sendMessageStream generator entry, before the awaited UserPromptSubmit hook. ToolResult submissions are not hook-exempt, so a concurrent submission admitted during the hook await could push its own content into the global counter and supply the observed push for a send that never pushed: a hook-blocked or cancelled round settled as accepted, journaling a delivery that never happened while the drained teammate envelope was never requeued — silent message loss. - Blocked sends and hook failures exit before the settlement try/finally and provably never pushed: restore the carrier unconditionally instead of comparing the counter. - Re-snapshot the counter immediately before `turn.run` (after the hook await) so sends that reach the push compare against the tightest window; exits before `turn.run` restore unconditionally. - Drop the dead push-counter fixture from useGeminiStream.test.tsx (nothing under test reads it at this head), correct the settlement wrapper comment, and pin the Idle teammate drain's goal-claim deferral restore path plus its exactly-once redelivery. * fix(cli): strip restored teammate envelopes from the Ctrl+Y retry payload When a boundary submission fails before the history push (e.g. a UserPromptSubmit hook that throws on the ToolResult prompt), the drained teammate batch is restored to the queue, but the same envelopes stay baked into lastPromptRef. A Ctrl+Y retry then re-sends them while the queue still holds them, so the leader receives the report twice (retry + Idle drain). Strip the restored envelopes from the retry payload so the queue redelivery is the single source. * refactor(core): fold restoreSteerInput into settleSteerInput restoreSteerInput duplicated settleSteerInput's idempotence guard, try/catch, and failure warning — only the decision differed. settleSteerInput now takes an optional pushCountBefore: undefined marks a send that provably never pushed and restores unconditionally. Keeps the 'settle each carrier exactly once' invariant in one closure. * fix(core): settle attached carriers by the push-site snapshot GeminiChat now publishes the user-content push counter on the request array immediately before pushing it into history, and GeminiClient settles the attached steer/teammate carrier against that push-site snapshot instead of a client-side one: a snapshot taken before turn.run still covers the send-lock and tryCompress awaits ahead of the push, where a concurrently admitted send (/btw) can push and supply the counter growth that reads as acceptance for a send that then exits before its own push (silent teammate loss plus a false delivery journal). A send that exits before the publish never pushed and restores unconditionally. Also settle the attached carrier on the Goal turn admission failure path, which rethrows before the settlement try/finally and would otherwise leak the carrier (drained messages neither delivered nor requeued) for future attachers without their own onDeliveryFailed fallback. * test(cli): unify the teammate settlement shim with the real client contract The Ctrl+Y test rebuilt ~40 lines of the renderBusyMultiRoundTask harness inline because the shared shim settled in a finally (a throwing stream accepted) while the real GeminiClient restores on any pre-push exit. Fold the inline harness back into the shared one and align the single shim with the real contract: blocked sends and pre-push throws restore, while a push that landed (first event observed / completed stream) accepts — including mid-stream failures and consumer abandonment after the first event. * test(cli): pin the trailing-match guard on the teammate retry strip The guard that keeps settleDrainedTeammates from stripping the Ctrl+Y retry payload unless its trailing entries match the restored batch is load-bearing but pinned by no test: with an unconditional strip, a goal-preempt restore firing before submitQuery stores the round's own payload truncates the PREVIOUS turn's retry payload. Regression test: goal-owned tool round, teammate queued, goal controller aborted mid-round before the submission; assert the envelope is requeued and a subsequent retryLastPrompt() re-sends the previous payload intact. Verified the test fails with the guard replaced by unconditional strip (observed payload drops [call-r1, call-r1-extra] -> [call-r1]). * fix(cli): strip accepted teammate envelopes from the Ctrl+Y retry payload The accept branch of settleDrainedTeammates journaled the delivery but left the envelope parts in lastPromptRef, so a Ctrl+Y retry after an accepted-but-failed tool round resubmitted the already-delivered envelope to the leader. Apply the same trailing-match strip on accept; journaling semantics unchanged. * fix(cli): preserve accepted teammate envelopes across the Ctrl+Y retry orphan pop Acceptance settles on the push, but an accepted round can still fail terminally before any content (a 503 after exhausted retries). The pushed entry is then the trailing orphan the Retry path pops before re-pushing the stored payload, and the landing push suppresses restoreStrippedRetryEntries — so the envelope stripped from lastPromptRef on accept would silently vanish while the delivery journal claims delivered. Record the stripped parts as retry debt and re-attach them in retryLastPrompt exactly when the orphan pop is about to drop them (trailing-match against the popped region), keeping the payload stripped when the entry is not orphaned so accepted-then-failed-mid-stream retries still do not double-deliver. * fix(cli): harden teammate retry debt against string payloads and identical resends Addresses both review Criticals on the retry-debt re-attach path: 1. `reattachOrphanedRetryEnvelopes` early-returned for non-array retry payloads, discarding the journaled debt unexamined. Idle Teammate/ Notification drains and plain prompts store strings in `lastPromptRef`, so a Ctrl+Y retry of such a payload lost accepted envelopes to the Retry path's orphan pop while the journal claimed delivered. Debt is now evaluated for any payload shape; string queries are wrapped into a text part only when something is re-attached. 2. Debt re-attach matched by envelope text alone, so a byte-identical resend orphaning a younger entry re-attached a debt whose own entry still sat mid-history — duplicate delivery. Each debt record now captures the pushed history entry's parts as an identity fingerprint at accept time (tool-response parts carry unique callIds), and the re-attach requires that fingerprint to be a trailing orphan entry. * fix(cli): harden teammate envelope retry debt across admission, retries, and team swaps Four Criticals from the round-14 review of the boundary envelope retry debt mechanism: 1. TeamManager swap leaked in-flight boundary batches: the swap handler cleared only the queue, while a batch already drained into a tool-round submission survived in the settlement closure and its restore requeued it into the NEW team's session. A queue generation counter now makes the restore drop the batch and the settlement skip journal/debt once a swap moved the generation. 2. Retry debt was one-shot: reattach consumed the debt and nothing recorded debt for the retry's own re-pushed entry, so an envelope surviving one retry could be permanently popped by a later retry of a different payload while the journal claimed delivered. The consumed records now transfer into a settlement carrier on the retry's own submission: accept records debt for the retry's pushed entry, restore re-records the original records (core re-adds popped entries as-is when the push never landed). 3. Debt was consumed during argument evaluation before submitQuery's admission gate ran, so a lease-rejected Ctrl+Y permanently discarded it. retryLastPrompt now bails on isSubmittingQueryRef before evaluating the debt (for Retry the gate rejects exactly when the lease is held, and the path to the gate is synchronous). 4. Debt was recorded only when the accept-time strip matched lastPromptRef, but a concurrent submission admitted during the time-to-first-token window overwrites it and the orphan pop drops the accepted entry regardless. Debt is now recorded unconditionally on accept; the retry-time orphan check still keeps double delivery impossible. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): pin teammate retry-debt admission, retry transfer, and swap guards Regression pins for the four round-14 Criticals: - a boundary batch restored after a TeamManager swap is dropped, never resubmitted into the new team's session - an accepted-after-swap batch is neither journaled nor recorded as retry debt against the new team - an envelope re-attached by one retry stays protected when that retry's own entry is orphaned by a later different payload (debt transfers through the retry's settlement carrier) - a lease-rejected Ctrl+Y does not discard the debt (no history scan, no submission, debt still usable afterwards) - a concurrent /btw overwriting lastPromptRef before the accept settlement no longer suppresses debt recording Adds a non-awaiting startToolRound helper to the multi-round harness so tests can hold the boundary submission in flight. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): strip accepted retry envelopes from the stored payload in the carrier accept The retry carrier's accept re-recorded debt but never stripped the re-attached envelopes back out of lastPromptRef, so each accept-fail-before-content-Ctrl+Y cycle re-attached the envelopes onto a base that still carried them, appending one duplicate copy per cycle. Mirror the boundary settlement and the carrier's own restore: strip the consumed envelope texts before re-recording debt. Regression test pinned and mutation-checked: reverting the strip makes the second Ctrl+Y submit [toolResponses, envelope, envelope]. Co-authored-by: Qwen-Coder <qwen-coder@alibaba-inc.com> * fix(core): re-add popped retry entries when pre-try catches settle the carrier The Goal-admission catch settles the attached carrier by restore after the Retry orphan pop has run but before the only restoreStrippedRetryEntries call site (inside the settlement try/finally below), so the popped entries were permanently dropped from history while the carrier re-recorded debt against entries that no longer exist. Call restoreStrippedRetryEntries before settling in that catch, and symmetrically in the hook-failure catch (a no-op today, since hooks never fire for Retry, the only type that populates the entries). Regression test pinned and mutation-checked: without the re-add the Goal-admission rejection drops the orphaned entry from history. Co-authored-by: Qwen-Coder <qwen-coder@alibaba-inc.com> * fix(core): restore retry entries per send * test(core): publish push snapshot in retry-restore mocks The two retry-restore tests anchored the old global push-counter gate and never published userContentPushSnapshotKey from their mocked turn, so the per-send snapshot gate saw "no snapshot, never pushed" and restored unconditionally. Mirror the sibling tests' GeminiChat contract miniature: publish the counter on the request immediately before the simulated push, after the simulated compression in the shrink test. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Qwen-Coder <qwen-coder@alibaba-inc.com>
* fix(cli): optimize repeated inline image rendering * fix(cli): make inline image guard type-safe --------- Co-authored-by: nothing <nothing@U-DQY4PXFJ-0222.local> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
…ifier (QwenLM#10352) * feat(core): forward bounded MCP tool arguments to the AUTO-mode classifier DiscoveredMCPTool never overrode toAutoClassifierInput, so every MCP call reached the classifier as `Tool: mcp__server__tool / Arguments: {}`. Told to err on the side of blocking, the classifier rejected most of them on the name alone, which made AUTO mode unusable with MCP and pushed users toward blanket `mcp__server` allow rules that skip the classifier. The projection now carries the server name, the server-side tool name, the server's self-reported annotations, and a bounded copy of the arguments: 2,000 chars per string, a 16,000-char shared budget, depth and entry caps, with every cut marked in place and flagged via `arguments_truncated`. The classifier system prompt explains how to read the projection — arguments are the evidence for exfiltration and external-write rules, annotations are unverified, truncation is never a reason to relax. `permissions.autoMode.mcp.forwardArguments: false` restores the name-only projection for deployments whose classifier runs against a different provider than the main model. Claude-Session: https://claude.ai/code/session_01YX3fw1haWj6KD5saknQFb6 * fix(core): make the MCP classifier projection bound hold and keep every key visible Addresses review round 1 on QwenLM#10352: - Build projected objects with a null prototype so an argument key named `__proto__` stays an own, visible property instead of vanishing through the Object.prototype setter (R1-1). - Cap `server` / `tool` names at 200 chars inside the shared budget, strip control characters, and flag cuts with `name_truncated` (R1-2). - Charge the budget at serialized cost (encoded length plus pretty-print line overhead), truncate keys like values, charge every marker, and stop container iteration once the budget is spent, so the pretty-printed payload the classifier receives stays within the budget plus one marker per nesting level (R1-3, R1-4). - Pick a collision-free key for the remainder marker (R1-6). - Unify marker forms to `…[truncated N chars]` / `[omitted: …]` and say so in the classifier prompt and docs (R1-5). - Update the base-class `toAutoClassifierInput` docstring to mention the MCP override (R1-7). - Bound rendered historical actions in the classifier transcript: 4,000 chars each, 40,000 in aggregate, newest kept first, older ones reduced to their tool name plus an omission marker (R1-8). Claude-Session: https://claude.ai/code/session_01YX3fw1haWj6KD5saknQFb6 * fix(core): announce and pin every MCP annotation key the projection forwards `ANNOTATION_KEYS` forwards four keys, but the classifier system prompt and the auto-mode doc enumerated only three: a server could assert `idempotentHint` and the classifier would see a key the prompt never marked as self-reported and unverified, right beside the rule that annotations never justify allowing an action on their own. Nothing pinned that key either — removing `'idempotentHint'` from `ANNOTATION_KEYS` left the whole suite green. The projection test now passes all four keys and asserts all four echo back through the exact-match `toEqual`, and a new sibling test keeps the boolean-only filter live (a non-boolean `idempotentHint` must not reach the prompt), which the widened test would otherwise have stopped covering. Verified by mutation: dropping `'idempotentHint'` from `ANNOTATION_KEYS` reds the projection test, dropping it from the prompt enumeration reds the `MCP guidance` test, and relaxing the boolean filter to a `!== undefined` check reds the new filter test. Claude-Session: https://claude.ai/code/session_018dYE4LwSMeMPFchXk5UBdM * test(core): pin the classifier prompt to the exported annotation key list The four hand-copied `expect(prompt).toMatch(/xHint/)` assertions duplicated `ANNOTATION_KEYS`, so they only caught a key *removed* from the projection. A key added to `ANNOTATION_KEYS` was forwarded by `projectAnnotations` immediately while the prompt never named it, and the suite stayed green -- the classifier would then receive an annotation key the prompt never marked as unverified. This is the drift that had to be repaired by hand for `idempotentHint` one commit ago. Export the list and iterate it so the guard works in both directions. Claude-Session: https://claude.ai/code/session_01Y7nLadH7zFM6B6bJZ2Vfzk * fix(core): sanitize classifier projection separators * fix(core): fail closed when an MCP call's tool has left the registry Two gaps the maintainer verification found in the classifier projection. The `permissions.autoMode.mcp.forwardArguments` opt-out lives on the tool object, so a history entry whose MCP server was removed from settings (or a session resumed without it) had nothing left to express it: `projectFunctionArgs` fell back to the raw arguments and forwarded a prior call's payload — secrets included — into the classifier prompt unbounded. An `mcp__*` name the registry cannot resolve now projects to the same `{}` an opted-out MCP tool produces, as does a resolved MCP tool whose projection threw: for MCP arguments the fallback must not be the unbounded one. Unknown non-MCP tools keep passing their args through. A non-object payload (an array, a bare string) projected to `{}` with no `arguments_truncated` flag, so dropped content read as a call that genuinely had no arguments — the one place the module broke its own "omitted content is never presented as absent" invariant. Absent params stay unflagged; anything else is marked. Claude-Session: https://claude.ai/code/session_01NkW1J2aBKcsKS62dkPcWbT --------- Co-authored-by: qqqys <qys-us2@outlook.com> Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
…wenLM#10042) * fix(serve): prefer a usable issuer over an expired same-subject twin Claude-Session: https://claude.ai/code/session_01238SuDzxRkjb9LLKQcx2H8 * fix(serve): judge issuer preference and path validity at one clock * test(serve): pin the validTo boundary and harden the one-clock witness (QwenLM#10042) * test(serve): fail loudly on a reordered renewed-root fixture (QwenLM#10042) --------- Co-authored-by: qqqys <qys-us2@outlook.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
* ci: isolate agent workflows from CI runners Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): align dedicated agent runner safeguards Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): preserve ownership during session cleanup Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: fix CI failure on PR QwenLM#10300 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10300) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10300) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10300) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10300) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10300) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10300) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10300) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10300) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10300) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10300) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(serve): hot-reload runtime model providers Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): address runtime provider sync review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(daemon): strengthen provider sync coverage Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): harden provider runtime synchronization Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): update worktree ACP provider reload mocks Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10269) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10269) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10269) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): harden provider runtime synchronization Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): synchronize user providers across runtimes Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(web-shell): update daemon SDK mocks after cutover Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10269) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(serve): trust tokenless loopback operator access Allow the tokenless primary loopback listener to use strict operator APIs, explicitly enabled session shell, Local Control pairing material, and Web Shell Channel controls while preserving hardened and LAN authentication boundaries. Closes QwenLM#10401 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10403) Resolve localhost once and pin the listener to the resulting literal before deriving trusted-loopback authority. Verify the actual listener address before publishing startup, and document the embedder-owned socket boundary. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10403) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10403) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10403) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10403) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10403) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10403) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: wenshao <shaojin.wensj@alibaba-inc.com>
…nLM#10394) * ci: gate heavy jobs on a disk floor and persist pressure samples The in-repo slice of QwenLM#10035: fail fast on a saturated self-hosted host before npm ci instead of dying on ENOSPC mid-run, and keep the disk-pressure timeline from a failed run as an artifact so the peak can be correlated with the job and runner after cleanup reclaims the host. * ci: single-quote the failure() condition to satisfy yamllint quoted-strings Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com> * ci: keep TMPDIR routing block byte-identical across test legs The sample-file setup landed inside the routing block that no-ak-integration-ci pins identical across test/test_macos/test_windows, breaking the identity assertion. Move the DISK_SAMPLES definition and DISKCONTEXT header ahead of the routing block; the DFSAMPLE lines still carry the routed tmpdir. * fix(ci): validate disk floor overrides * fix(ci): reject oversized disk floor overrides Keep the disk-floor shell comparisons inside bash's signed integer range by rejecting numeric overrides that cannot be compared safely. Add the existing helper test coverage for the overflow boundary.\n\nCo-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(ci): pin the disk-floor overflow guard boundaries Pin the three behaviours of validate_floor_override that the current suite leaves unpinned, each verified to catch its mutant: - INT64_MAX is a legal floor and must reach the disk comparison (tightening the length check to -gt 18 now goes red). - Zero-padded overrides padded past 19 raw characters are normalized and accepted (removing the leading-zero strip now goes red). - 20-digit values are rejected by the length branch, which the existing 19-digit case never exercises (widening to -gt 20 now goes red). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(ci): simplify disk floor validation and sampling Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): preserve the TMPDIR sampler sentinel Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(ci): clarify disk floor gate placement Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(web-shell): mock the relocated daemon SDK Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* ci: update qwen on third Hong Kong ECS host Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * ci: allowlist Hong Kong updater labels Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(ci): isolate OSS publisher fixture module mode Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(web-shell): mock the current daemon provider in boot test Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(channels): attribute named session output Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10420) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: fix CI failure on PR QwenLM#10420 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10420) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10420) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10420) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10420) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#10420) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>



What this PR does
Third batch of the OpenTUI migration landing plan (#8662), stacked on the foundation-modules batch: the live-session and input layer of the OpenTUI renderer. It adds the live-session stream fold and its model, message rendering (streaming markdown heal, progressive MCP displays, client tool runs, text batching), the transcript adapter with resume and session-switch support, sticky todos, the composer (view, key handling, input model), mouse rows and scrollbar, unified-diff rendering, and the session-compaction notice. Thirty-six files, about 10.3k lines including tests; 280 new tests, all green.
Everything is additive: no reachable ink code path is touched, nothing imports these modules yet, and the default renderer stays ink. The batch is self-contained on top of the foundation modules (theme, event adapter, input history, slash dispatch, mouse hit-testing) and adds no dependency on the dialog or backend batches that follow.
Two landing notes. This batch carries the first consumer of the markdown-heal dependency that was deferred from the infra batch; it lands in devDependencies per the renderer-deps convention used there. One of the carried modules, the transcript adapter, has no dedicated test file on the implementation branch; its conversion logic is exercised through the resume-mapping tests, which replay saved sessions end to end.
Why it's needed
The migration lands batch by batch so each unit is reviewable on its own merits; this is the batch that brings the live conversation — streaming fold, message rendering, and input — into the OpenTUI tree. With the streaming model and foundation services already landed or under review, this batch connects them to actual session rendering and composer behavior, leaving the dialogs, backend composition, and activation batches to complete the path to a flag-reachable renderer.
Risk & Scope
Main risk is bounded by construction: all modules are unreachable from the running CLI until a later batch wires the renderer dispatch, so the blast radius of a defect here is zero for current users; the default (ink) path is byte-for-byte unchanged. Not validated in this PR: rendering behavior on a real terminal (requires the activation batch to make the renderer reachable), and Windows behavior of the mouse-row/scrollbar geometry (CI covers type-level correctness only). No breaking changes, no migration notes — additive only. The markdown-heal dependency is new to the tree (devDependencies, first consumer lands here per the deferred-dependency plan recorded in the infra batch).
Reviewer Test Plan
All new code is unreachable from the running CLI (no wiring yet), so there is no user-visible behavior change to exercise; verification is build, types, and the test suite.
How to verify
Build all workspaces and run the typecheck — both are clean. Run the dependency-direction gate (
npm run check:tui-dep-direction): it passes, confirming the new renderer modules keep the framework-neutral boundary intact. Run the CLI test suite: the full packages/cli suite is green, with the OpenTUI subset at 42 files / 619 tests. To confirm the ink path is untouched, note the diff adds files only under the OpenTUI directory plus the renderer dependency in the CLI package manifest and its lockfile.Tested on
Evidence (Before & After)
N/A — no user-visible change; modules are not yet wired into the renderer dispatch.
Linked Issues
Checklist
中文说明:OpenTUI 迁移第三批(Live-session & input)——实时会话折叠与模型、消息渲染(流式 markdown 修复、MCP 渐进显示、工具运行、文本批处理)、转录适配(含 resume/会话切换)、粘性待办、输入框(视图/按键/模型)、鼠标行与滚动条、diff 渲染、会话压缩提示。全部为增量代码,不触碰 ink 可达路径,默认渲染器仍为 ink。stacked 在基础模块批(#10146)之上;本批首次引入基建批延迟的 markdown 修复依赖(置于 devDependencies,沿用渲染器依赖约定)。构建、类型检查、依赖方向门禁全过,全量 CLI 测试绿,OpenTUI 子集 42 文件 / 619 用例。