Skip to content

feat: add native advisor tool - #9636

Merged
yiliang114 merged 62 commits into
QwenLM:mainfrom
ZijianZhang989:codex/advisor-native-tool
Sep 25, 2026
Merged

yiliang114 merged 62 commits into
QwenLM:mainfrom
ZijianZhang989:codex/advisor-native-tool

Conversation

@ZijianZhang989

@ZijianZhang989 ZijianZhang989 commented Aug 21, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

This PR adds an opt-in native Advisor tool. When a separate Advisor model is configured, the executor can call the no-argument advisor {} tool for an independent second opinion and continue the same task with the review as a tool result.

Users can open the configured-model picker with /advisor, select directly with /advisor <model>, disable it with /advisor off, or apply a session startup override with --advisor <model|off>. Invalid model selectors are rejected instead of being persisted or sent to a provider. When Advisor is off, the tool is not registered and is absent from subsequent executor requests.

Persisted Advisor selection is restricted to user and system settings. A repository's Workspace setting cannot enable or override Advisor; it is ignored with a warning. The explicit --advisor session override still takes precedence.

The Advisor receives the executor system instruction, tool declarations, and conversation up to the call point. Hidden reasoning and binary payloads are omitted. It uses the selected independent model, has no executable workspace tools, does not use model fallbacks, and returns a validated review with verdict, risks, missing evidence, and recommendation. Failures are non-fatal so the executor can continue; cancellation still cancels the active turn.

The structured-review path also changes the shared forked-agent jsonSchema mechanism used by existing follow-up suggestion, speculation, /btw, and manual Advisor review callers. These requests now use the synthetic respond_in_schema tool, with text JSON parsing retained as a fallback when a provider does not force the tool.

Native results reuse the existing Advisor presentation: the same four-title Markdown is rendered inside the existing cyan /advisor · model bordered view. The executor continuation remains below it throughout streaming and final history. Web Shell receives the same structured review through the normal tool event path.

Why it's needed

Issue #9036 requests an executor-visible Advisor capability comparable to Claude Code: the main model should be able to consult a separately configured model without granting that model workspace execution permissions. The existing implementation only offered a user-triggered review, so its output could not be reinjected into an in-flight executor task.

Reviewer Test Plan

How to verify

Run /advisor in the interactive TUI and confirm the picker contains Off and only configured eligible models. Select a model, ask the executor to handle a non-trivial task, and confirm it can call advisor {}. The completed result should use the existing cyan /advisor · model border and show Verdict, Risks, Missing evidence, and Recommendation; the executor's continuation should remain below it.

Run /advisor off, then send another prompt. Confirm the next executor request does not expose Advisor and no Advisor model request occurs. Separately test --advisor <model> and --advisor off. Unknown, ineligible, inherit, or unresolved selectors should fail configuration instead of silently falling back to the executor.

Place a different advisorModel in Workspace settings and restart. Confirm it is ignored with a warning and cannot override the user selection or enable Advisor by itself.

With an OpenAI-compatible mock provider, confirm the Advisor request uses the configured independent model, contains the conversation through the call point, and has only respond_in_schema, with no executable workspace tools. A malformed response should produce a non-fatal tool error. A response with the four required fields plus extra fields should retain the four-field review.

Evidence (Before & After)

Before: the executor had no native Advisor tool. Advisor was a manual review command whose output was not returned to the executor as part of the active task.

After: the executor can call advisor {}, the independent review is reinjected as a tool result, and the existing presentation is preserved:

╭────────────────────────────────────────╮
│ /advisor · qwen3.7-max                 │
│                                        │
│ Verdict                                │
│ ...                                    │
│ Risks                                  │
│ ...                                    │
│ Missing evidence                       │
│ ...                                    │
│ Recommendation                         │
│ ...                                    │
╰────────────────────────────────────────╯
Executor continuation...

A real tmux run against the bundled CLI verified the picker, independent model call, four-title cyan border, stable continuation ordering, and /advisor off removal. Maintainer verification additionally built and drove the merge result on Linux against a recording mock provider.

Automated verification includes focused Core and CLI Advisor/configuration/display tests, full typecheck, build and bundle, and integration-tests/cli/advisor-tool.test.ts (3/3) against the bundled CLI.

Tested on

OS Status
macOS ✅
Windows ⚠️
Linux ✅

Environment (optional)

macOS and Linux x86_64, Node.js 22+, bundled CLI, isolated runtime/config directories, tmux, and a local OpenAI-compatible fake provider.

Risk & Scope

  • Main risk or tradeoff: enabling Advisor sends the active conversation, system instruction, and tool declarations to the separately configured model/provider and incurs an additional model request. A maintainer Linux E2E observed a roughly 100 KB payload in a small two-turn session, mostly repeated system/tool declarations; this PR does not add a per-prompt Advisor call-count or payload budget.
  • Not validated / out of scope: provider-native Advisor protocols, cross-model prompt-cache guarantees, mandatory checkpoints or call-count policy, Advisor file tools, and manual Windows TUI verification.
  • Breaking changes / migration notes: /advisor now configures the native Advisor model; manual review moves to /advisor review [focus]. Leaving advisorModel empty disables Advisor rather than falling back to the executor model. Workspace-level advisorModel values are ignored because repository settings must not opt a user into sending the active conversation to another model/provider.

Linked Issues

References #9036.

中文说明

这个 PR 做了什么

这个 PR 增加可选的原生 Advisor 工具。配置独立 Advisor 模型后,executor 可以调用无参数的 advisor {} 获取第二意见,并将评审作为工具结果回注,在同一个任务中继续执行。

用户可以通过 /advisor 打开模型选择器,通过 /advisor <model> 直接选择,通过 /advisor off 禁用,或用 --advisor <model|off> 覆盖当前 session。无效模型不会被持久化或发送给 provider;关闭后工具不会注册。

持久化选择只接受 User 和 System 设置。Workspace 设置不能启用或覆盖 Advisor;该值会被忽略并给出警告。

Advisor 会收到 executor 的 system instruction、工具声明和调用点之前的会话。隐藏推理和二进制载荷会被移除。Advisor 使用独立模型,不具备 workspace 可执行工具,不使用模型 fallback,并返回 verdict、risks、missing evidence 和 recommendation。失败是非致命的,取消仍会取消当前 turn。

结构化评审路径还会改变既有 follow-up suggestion、speculation、/btw 和手动 Advisor review 共同使用的 forked-agent jsonSchema 机制:这些请求现在使用合成的 respond_in_schema 工具;当 provider 未强制该工具时,保留文本 JSON 解析兜底。

原生结果复用既有四标题青色 /advisor · model 边框,executor 后续输出稳定保持在其下方。Web Shell 通过普通工具事件接收同一份结构化评审。

如何验证

在 TUI 中运行 /advisor,确认 picker 包含 Off 且只列出符合要求的模型。选择模型后让 executor 处理非简单任务,确认它可以调用 advisor {},并显示四标题边框。运行 /advisor off 后,后续请求不应再暴露 Advisor。

在 Workspace 设置中写入 advisorModel,确认它被忽略且不能覆盖 User 选择。使用 mock provider 确认 Advisor 请求使用独立模型、只携带 respond_in_schema,且格式错误时 executor 可以继续。

macOS 与 Linux 已验证;focused Core/CLI 测试、全仓 typecheck、build、bundle,以及 bundled CLI 的 Advisor E2E 3/3 均通过。

风险与范围

  • 启用 Advisor 会把活动会话、system instruction 和工具声明发送给另一个模型/provider,并增加模型请求。维护者在 Linux E2E 的一个小型双轮会话中观察到约 100 KB payload,其中大部分是重复的 system/tool 声明;本 PR 不增加每 prompt 调用次数或 payload 预算。
  • 范围外:provider 原生 Advisor 协议、跨模型 prompt cache 保证、强制 checkpoint 或调用次数策略、Advisor 文件工具,以及 Windows 人工 TUI 验证。
  • /advisor 现在配置原生 Advisor;手动评审入口为 /advisor review [focus]。空 advisorModel 表示关闭,不再回退到 executor。Workspace advisorModel 会被忽略。

关联 Issue

References #9036.

@ZijianZhang989

ZijianZhang989 commented Aug 21, 2026 •

Copy link
Copy Markdown
Collaborator Author

E2E Verification Report — Native Advisor Tool

Branch: codex/advisor-native-tool · Build: ✅ npm run build && npm run bundle · Unit Tests: 336/336 ✅

Verified Scenarios

1. /advisor Model Selection Dialog ✅

Typed /advisor → model selection dialog rendered correctly:

  > /advisor
  ╭──────────────────────────────────────────────────────────────────────────╮
  │ Select Advisor Model                                                     │
  │                                                                          │
  │    1. Off                                                                │
  │       Disable the native Advisor tool                                    │
  │    2. [openai] [IdeaLab] bigmodel/GLM-5.1 (bigmodel/GLM-5.1)            │
  │ ›  3. [openai] [IdeaLab] qwen3.7-max (qwen3.7-max)                      │
  │    4. [openai] [IdeaLab] qwen3.6-plus (qwen3.6-plus)                    │
  │    ...                                                                   │
  │                                                                          │
  │ ⚠ Advisor sends the active conversation evidence to this provider.       │
  │ Modality:       text-only                                                │
  │ Context Window: 1,000,000 tokens                                         │
  │                                                                          │
  │ Enter to select, ↑↓ to navigate, Esc to close                            │
  ╰──────────────────────────────────────────────────────────────────────────╯

📸 Screenshot: /advisor dialog
01-advisor-dialog

2. Model Selection Confirmation ✅

Selected qwen3.7-max → confirmed:

  ✓ Advisor Model: openai:qwen3.7-max

📸 Screenshot: model selected

02-model-selected

3. /advisor status ✅

  > /advisor status
  ●︎ Current Advisor model: openai:qwen3.7-max
    Use "/advisor <model-id>" to enable Advisor or "/advisor off" to disable it.

4. Native Advisor Tool Call + Feedback Card Rendering ✅

Sent prompt requesting advisor consultation. Executor autonomously called advisor {}:

  ✓ Advisor Consult Advisor
    ╭────────────────────────────────────────────────────────────────────────╮
    │ Advisor feedback                                                       │
    │                                                                        │
    │ Verdict                                                                │
    │ The Explore agent produced a thorough structural map (18 packages,     │
    │ dependency graph, build pipeline, testing infra). The approach of      │
    │ delegating broad exploration to a specialized agent was correct.       │
    │                                                                        │
    │ Risks                                                                  │
    │  1. Information dump risk: ~15k+ chars of raw structural data...       │
    │  2. Stale/incomplete claims: point-in-time observations...             │
    │  3. No architectural opinion formed...                                 │
    │  4. Missing critical analysis of coupling...                           │
    │                                                                        │
    │ Missing evidence                                                       │
    │  1. Hot-path performance characteristics...                            │
    │  2. Actual test coverage or test health...                             │
    │  3. Extension/plugin boundary clarity...                               │
    │  4. Recent architectural changes...                                    │
    │                                                                        │
    │ Recommendation                                                         │
    │ Synthesize the exploration data into a concise architectural review    │
    │ with 3-4 sections...                                                   │
    ╰────────────────────────────────────────────────────────────────────────╯

  ◆︎ Good feedback. Let me verify a few specific claims before presenting...

  ✓ Shell wc -l .../config.ts → 9010
  ✓ Shell wc -l .../index.ts → 721
  ✓ Shell ls .../tools/*.ts | wc -l → 142

Key observations:

  • Advisor feedback card renders with cyan rounded border and structured sections (Verdict / Risks / Missing evidence / Recommendation)
  • Executor continuation appears directly below the card — ordering preserved throughout streaming
  • Executor acted on advisor's recommendation by running verification commands

📸 Screenshot: advisor feedback card + executor continuation

03-advisor-feedback-card

5. /advisor review focus on error handling (Manual Review) ✅

Produced independent structured review focused on error handling gaps without interfering with executor's task. Rendered as separate bordered /advisor · qwen3.7-max card.

6. /advisor off + Status Verification ✅

  > /advisor off
  ●︎ Advisor disabled

  > /advisor status
  ●︎ Current Advisor model: not set
    Use "/advisor <model-id>" to enable Advisor or "/advisor off" to disable it.

📸 Screenshot: advisor disabled confirmation

04-advisor-off 05-advisor-disabled

Unit Test Results

Test Suite Tests Status
packages/core/src/tools/advisor.test.ts 15/15 ✅
packages/cli/src/ui/commands/advisor-command.test.ts 18/18 ✅
packages/cli/src/ui/components/messages/ToolMessage.test.tsx 74/74 ✅
packages/cli/src/ui/hooks/useGeminiStream.test.tsx 229/229 ✅

Environment

  • macOS, Node.js 26, bundled CLI
  • tmux session (200×50), approval-mode yolo
  • Advisor model: openai:qwen3.7-max via IdeaLab provider

Verdict: ✅ PASS

All 6 scenarios verified. Native advisor tool integrates correctly with executor workflow, feedback card renders properly, ordering is preserved, and enable/disable lifecycle works as expected.

@ZijianZhang989
ZijianZhang989 force-pushed the codex/advisor-native-tool branch from d986128 to a23d43f Compare August 21, 2026 08:19
@github-actions

Copy link
Copy Markdown
Contributor

Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration.

中文

请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。

@ZijianZhang989

Copy link
Copy Markdown
Collaborator Author

@qwen-code /review

@github-actions

Copy link
Copy Markdown
Contributor

Qwen Code review request accepted. Review is queued in workflow run.

@ZijianZhang989
ZijianZhang989 force-pushed the codex/advisor-native-tool branch from 948df71 to 8c8153e Compare August 24, 2026 11:05
@ZijianZhang989
ZijianZhang989 marked this pull request as ready for review August 24, 2026 11:05
@ZijianZhang989
ZijianZhang989 requested a review from qqqys as a code owner August 25, 2026 09:54
chiga0
chiga0 previously approved these changes Sep 25, 2026

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Round 2 — Follow-up at updated head

Head changed from round-1 (8824c5c7) to current (f10c2d94). This pass re-verifies the approval blocker from round 1 and checks whether the three Minors were addressed.


R13-1 / approval blocker from round 1 — RESOLVED

syncAdvisorToolRegistration now opens with if (this.getDisabledTools().has(ToolNames.ADVISOR)) return; at line 11247, consulted before the factory loop. setAdvisorModel additionally vetoes registration when disabledTools contains ADVISOR (line 6124), so the "unregister-then-re-register while disabled" path no longer exists. Independently confirmed by doudouOUC at an earlier head; verified still present at f10c2d94.

No approval blockers remain from round 1.


Round-1 Minors — status at current head

Finding Status
F1 — parseJsonObjectText duplicated with divergent behaviour Open — still two implementations; Minor, no runtime impact
F2 — inherit guard is case-sensitive (=== 'inherit') while off uses .toLowerCase() Open — L90 of advisor-model.ts unchanged; Minor
F3 — /advisor review matched case-sensitively; /advisor off is lower-cased Open — L269 of advisor-command.ts unchanged; Minor

All three are Minor. None blocks merge.


Coverage carried from round 1

  • ModelDialog.tsx production file (378 lines changed): not read. Test diff shows all advisor-mode scenarios (off-only, endpoint selection, fast-only, disabled-tool veto, stale persisted model) are covered; no coverage gap identified.
  • integration-tests/cli/advisor-tool.test.ts: not executed — outside all npm workspaces and CI has not produced a green run on this file on any head of this PR.
  • R13-2 (unresolvable selector): boot-time validateCliAdvisorModel throws FatalConfigError for unresolvable selectors; runtime /advisor <model> calls checkAdvisorModelAvailability and surfaces an error before config.setAdvisorModel. Not a silent fallback. Concern resolved.

Verdict: APPROVE

No confirmed blocker. All three open findings are Minor. All 128 prior threads are resolved; round-15 bot reported zero findings at 3d276bd2; PR is MERGEABLE at f10c2d94.

Reviewed with AI assistance.

Retiring `setting:advisor-model` from the Web Shell presentation map dropped
its key mapping, so `isSettingVisible('advisorModel', …)` fell through to the
"no alias" branch: an exclusion no longer hid the item and an inclusion no
longer showed it. The App-level close-on-exclusion effect for a
settings-launched Advisor picker therefore stopped closing the dialog, which
is what App.test.tsx (from QwenLM#11975) covers.

`advisorModel` is hidden from the settings rows by `showInDialog: false`, not
by dropping the published host id. Keep the alias (and its acceptance in
`WEB_SHELL_SETTING_ITEM_IDS`) exactly as on main.

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Follow-up at 13ffa6e5 (continuing my review from f10c2d94):

The new commit is the right fix — keeping setting:advisor-model in the published alias map while hiding the row via showInDialog: false restores the exclusion/inclusion semantics that retiring the alias had broken, and the test update pins the parity contract again.

My two pre-merge asks are now closed:

  1. Text-fallback coverage: advisor.test.ts has both accepts structured Advisor JSON returned as text and recovers an array-wrapped review from the text fallback — this matters because the text fallback is the production path on the default provider (DashScope drops tool_choice when thinking is on, and response_format only exists on api.openai.com).
  2. Egress disclosure: the advisorModel settings description now states that enabling it sends the active conversation to that model even across providers.

Fixed since my last pass: jsonResult ??= asJsonObject(...) (monotonic per-chunk assignment) and the dead Array.isArray(result.jsonResult) branch is gone.

Still open, non-blocking: the sanitize() guard shape — unknown keys like fileData/executableCode still pass through verbatim (denylist, not allowlist). Not a live leak today (no producer puts those into executor history), but worth a follow-up.

Remaining gate is CI: Test (ubuntu) + Lint + web-shell visuals are pending on this head. The CHANGES_REQUESTED on the PR is the stale bot review from d536a687 (August), several heads behind — needs a maintainer dismissal, not more code.

@qwen-code-review-bot qwen-code-review-bot left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving at 13ffa6e5.

I scoped this to the delta nobody has reviewed yet: everything after the round-13 pass at d536a687 (2026-08-29). Of the seven commits since then, cda083bf / 1910bd49 / 9ce81f92 are the contributor's and 14d7c5ba is a main sync, so the unreviewed production surface is 08fc82e2, f10c2d94 and 13ffa6e5. No blocking findings.

The deferred Critical R13-36 is fixed, and I verified it in the code

checkAdvisorModelAvailability now computes uncapturedRuntimeModelId, which is exactly this finding's shape: the selector resolves to context.currentModel and context.currentAuthType, and is absent from configuredModels. In that case the id is added to availableModelIds and available becomes true, so an advisor identical to the CLI runtime model passed as an auth-qualified selector is accepted rather than rejected at startup. resolvesToSameModel compares authType as well as modelId, so the fast-model equivalence check is auth-aware too. Pinned by advisor-model.test.ts:80 (allows the active runtime model).

R13-3 is fixed as a side effect, and the ensureTool call does not undo it

registerAdvisorTool dropped its own binary permissionManager.isToolEnabled check in favour of registerLazyTool, which resolves getToolRegistrationStatus — three-state, covering the coreTools allowlist, deny rules and tools.eager, and warning-and-skipping on error exactly as the deleted block did. So an eager allowlist that omits Advisor now demotes it to deferred instead of registering it eagerly.

The line worth checking was the await registry.ensureTool(ToolNames.ADVISOR) immediately after, since registerPermissionDeferredFactory writes into the same factories map and isToolAvailable only special-cases IMAGE_GEN — so ensureTool does consume the factory and materialize the instance. It still does not defeat the deferral: isEffectivelyDeferred (tool-registry.ts:422) and the declaration-hiding skip (:1059) both read the permissionDeferred set, which ensureTool never clears. The instance loads early; the schema stays out of the eager model request.

The removed Array.isArray guard was dead code

advisor.ts now passes result.jsonResult ?? parseJsonObjectText(result.text) straight to parseReview, dropping the old Array.isArray(result.jsonResult) ? undefined : .... That guard could not fire: asJsonObject (forkedAgent.ts:373) returns undefined for arrays, and jsonResult's only two assignment sites (:618, :649) both go through it. parseReview throws on any missing or non-string field, so malformed structured output still fails loudly rather than degrading silently.

One non-blocking residue

08fc82e2 retired the web-shell presentation alias and 13ffa6e5 restored it, so packages/web-shell/client/settings.ts nets to zero. But 08fc82e2 also moved the SettingsDialog "String Settings Editing" case off advisorModel onto general.outputLanguage, and the revert did not touch that file. The assertion is indexOf(...) >= 0, so nothing fails — the CLI dialog test just no longer exercises an advisor setting. Cosmetic; not worth a round.

Gate state, stated plainly

100 review threads, none unresolved. At the moment of this approval: 18 checks passed, 26 skipped, none failing, and Test (ubuntu-latest, Node 22.x) was still in progress — so I am approving on the review evidence, not on a completed run of the lane that executes these suites. That check still gates the merge independently, so a red result there blocks landing regardless of this vote. The previous head's lanes were cancelled by the 13ffa6e5 push, which is why this head has no earlier signal.

I have not dismissed the standing CHANGES_REQUESTED from the round-13 pass at d536a687; that is a separate action and not mine to take here. Note that its own body records every listed item as deferred and non-blocking under the convergence posture.


Correction, added after this approval was submitted

The Test (ubuntu-latest, Node 22.x) lane that was in progress when I approved has since failed, and the failure is caused by this PR, not by runner infrastructure. I am recording it here so this approval is not read as a clean bill of health.

  • Job: https://github.com/QwenLM/qwen-code/actions/runs/36112125194/job/107997921347
  • Suite result: Test Files 1 failed | 1112 passed (1113), Tests 1 failed | 34046 passed | 92 skipped
  • The single failure: src/serve/routes/workspace-settings.test.ts > web-shell settings alias drift > aliases every rendered row and serves every published alias, at workspace-settings.test.ts:1082 — AssertionError: expected [ 'advisorModel' ] to deeply equal []
  • No corepack/runner signature anywhere in the log (the Cannot find module .../corepack/v1/pnpm/... breakage seen on other PRs today appears 0 times here), so this is not the infra failure mode.

Line :1082 is expect(aliased.filter((key) => !rendered.includes(key))).toEqual([]); the reverse check on :1081 passes. So advisorModel is aliased but not rendered. Per the test's own derivation, aliased is parsed from SETTING_KEYS in packages/web-shell/client/settings.ts, and rendered is GET /workspace/settings' served keys minus HIDDEN_SETTING_KEYS and LIVE_SETTING_KEYS from SettingsMessage.tsx. advisorModel is in neither exclusion set, so the gap is on the served side: the daemon does not publish an advisorModel key while the web-shell alias table claims one.

This is a half-revert between the last two commits. 08fc82e2 ("retire hidden presentation alias") removed 'setting:advisor-model': 'advisorModel' from SETTING_KEYS and introduced RETIRED_IDS — which is what made this drift guard pass. 13ffa6e5 ("keep the Advisor presentation alias") restored the alias and dropped RETIRED_IDS, but nothing on the served side was restored with it.

I also want to correct my own earlier grading of this: I reviewed that flip-flop, concluded settings.ts nets to zero, and filed the leftover as a cosmetic P3 because the SettingsDialog.test.tsx assertion is only indexOf(...) >= 0. That was too narrow — I checked the test the commit touched and missed that a separate drift guard in another package cross-references the same alias against the served schema. The residue was real and it is the one red test.

Two ways to close it, author's call: revert 13ffa6e5 and keep the alias retired as 08fc82e2 intended, or keep the alias and make /workspace/settings serve advisorModel.

The rest of this review stands — R13-36 and R13-3 verified fixed, the removed Array.isArray guard verified dead, no other findings. Withdrawing the recommendation to merge until this lane is green.

@qwen-code-review-bot qwen-code-review-bot left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Duplicate — please ignore. The substantive approval is the first one at this head (review id 5315518532, 08:37:47Z).

Posted by an automated retry loop whose verification read used per_page=100 without --paginate; this PR has 123 reviews, so the read-back could not see that each write had already landed and kept retrying. No content difference between the copies.

@qwen-code-review-bot qwen-code-review-bot left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Duplicate — please ignore. The substantive approval is the first one at this head (review id 5315518532, 08:37:47Z).

Posted by an automated retry loop whose verification read used per_page=100 without --paginate; this PR has 123 reviews, so the read-back could not see that each write had already landed and kept retrying. No content difference between the copies.

@qwen-code-review-bot qwen-code-review-bot left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Duplicate — please ignore. The substantive approval is the first one at this head (review id 5315518532, 08:37:47Z).

Posted by an automated retry loop whose verification read used per_page=100 without --paginate; this PR has 123 reviews, so the read-back could not see that each write had already landed and kept retrying. No content difference between the copies.

@qwen-code-review-bot qwen-code-review-bot left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Duplicate — please ignore. The substantive approval is the first one at this head (review id 5315518532, 08:37:47Z).

Posted by an automated retry loop whose verification read used per_page=100 without --paginate; this PR has 123 reviews, so the read-back could not see that each write had already landed and kept retrying. No content difference between the copies.

@qwen-code-review-bot qwen-code-review-bot left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Duplicate — please ignore. The substantive approval is the first one at this head (review id 5315518532, 08:37:47Z).

Posted by an automated retry loop whose verification read used per_page=100 without --paginate; this PR has 123 reviews, so the read-back could not see that each write had already landed and kept retrying. No content difference between the copies.

Hiding `advisorModel` from the TUI dialog by flipping `showInDialog` to false
also removed it from the daemon `/workspace/settings` response, so the Web
Shell settings panel lost the only surface that configures the Advisor model
and `workspace-settings.test.ts` reported the now-unmatched
`setting:advisor-model` alias as drift.

`voiceModel` and `imageModel` are `showInDialog: false` and are re-served
through `WEB_SHELL_SETTINGS` for exactly this reason; do the same for
`advisorModel`. The TUI dialog still hides the free-text row, and the alias
stays mapped so a host exclusion closes the settings-launched picker.
Re-serving `advisorModel` to the Web Shell makes a workspace-scope write
reachable again: the settings panel's model sub-dialogs persist to the active
tab's scope, which defaults to Workspace. The merge now strips Workspace
`advisorModel`, so that write would persist a committable dead entry, answer
200 + requiresRestart, and leave the picker showing a model that never applies.

`rejectWorkspaceRestrictedWrite` already refuses this for the sectioned
restriction list; `advisorModel` is the root-level half of that list, so the
guard now tests both. The list itself moves next to its sectioned sibling so
the strip, the warning, and this refusal read one source.

@qqqys qqqys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-review at 5c96f29f — approving

Head moved from 08fc82e2 (my previous pass, a COMMENT) to 5c96f29f; base 64c04538. All three gates I named last time are now closed, and I found no Critical on the current head.

The delta nobody had reviewed yet

Two commits landed after the review bot's approval at 13ffa6e5, both by yiliang114, touching four files. I read all of them at head.

packages/cli/src/serve/routes/workspace-settings.ts adds advisorModel to WEB_SHELL_SETTINGS so the Web Shell model panel can read and persist it, and widens the write guard's membership test from WORKSPACE_RESTRICTED_SETTING_KEYS.includes(key) to a Set combining that list with the new WORKSPACE_RESTRICTED_ROOT_SETTINGS. The direction is fail-closed and the placement is right: rejectWorkspaceRestrictedWrite returns early for any scope other than workspace, and at the call site (line 546) it runs after scope validation (514) and the workspace-trust check (522) and before persistence (585). A workspace-scope write therefore 400s with workspace_restricted_setting instead of silently committing a key that stripWorkspaceRestrictedSettings would strip at merge — which was the actual defect. User scope is untouched, so the enablement path still works. Both directions are pinned by the new workspace-settings.test.ts cases (400 + persistSetting not called; 200 + called).

packages/cli/src/config/settings.ts is a pure relocation: the module-local WORKSPACE_RESTRICTED_ROOT_SETTINGS const is deleted and the shared export from settingsUtils.ts is imported instead. The strip loop and the "ignored" warning that consume it are unchanged in this delta, so there is one list now rather than two that could drift. No behavior change.

One bot claim I verified myself rather than accepting

The round-15 approval argues the Array.isArray guard dropped from advisor.ts was unreachable. Confirmed at head: asJsonObject (packages/core/src/agents/forkedAgent.ts:373-377) rejects arrays through !Array.isArray(value), jsonResult is declared Record<string, unknown> | undefined (line 607), and both of its assignment sites (618, 649) route through asJsonObject. An array can never reach parseReview, and parseReview still throws on a missing or non-string field, so malformed structured output fails loudly.

My three prior gates

  1. CI. Now green on this exact head, zero failures: Test (ubuntu-latest, Node 22.x), Lint & Static, Integration Tests (no-AK, No Sandbox), Serve A/B, Desktop Shell (ubuntu-22.04) and (windows-2022), TUI parity snapshots, OpenTUI no-flicker gate, Real daemon E2E / Java 11, and the Java matrix — 22 passing.
  2. The unreviewed packages/web-shell/client/settings.ts edit. No longer in the PR's file list at all.
  3. The nine conflict resolutions. Covered since: chiga0's round-1 pass at 8824c5c7 and round-2 at f10c2d94 (which re-verifies R13-1 in code at config.ts:11247 / :6124), plus the bot's round-15 scope, which explicitly took the post-round-13 surface including 08fc82e2.

Unresolved threads

134 threads total, 3 unresolved. All three are labelled Minor by their author — the duplicated parseJsonObjectText, the case-sensitivity of the inherit guard, and the case-sensitivity of the review subcommand — and each has a written disposition from yiliang114 declining it as out of scope for a bounded bug-fix pass. None is merge-blocking, and I am not re-litigating an adjudicated Minor here.

Non-blocking residue

web-shell E2E Smoke is still in progress on this head (it was cancelled at 13ffa6e5 when the head moved), so the one lane that would exercise the daemon settings route end-to-end has not completed; Serve A/B and the unit lane that runs the new guard tests both pass, which is why this does not block. review-pr is also pending. Separately, reviewDecision: CHANGES_REQUESTED is stale — every review in that state belongs to qwen-code-ci-bot, the newest at d536a687 from 2026-08-29. No human change request stands against this head.

@yiliang114
yiliang114 dismissed a stale review September 25, 2026 09:35

fixed

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

All 3 round-13 blockers addressed at HEAD 5c96f29f (commit cda083bf fix(advisor): enforce independent model availability):

Bot-ledger ID Finding summary Status at HEAD
R13-1 Advisor unregister/re-register not safe when disabledTools contains advisor — disabled tool re-registers after every setAdvisorModel call Fixed — syncAdvisorToolRegistration in packages/core/src/config/config.ts now checks getDisabledTools().has(ToolNames.ADVISOR) before the unregister/re-register cycle; setAdvisorModel also returns false early when the tool is disabled
R13-2 Unresolvable advisor selector silently fell back to executor model Fixed — packages/core/src/tools/advisor.ts execute() now returns advisorErrorResult(new Error('Advisor model is no longer available.')) when resolvedModel is undefined
R13-3 checkAdvisorModelAvailability accepted inherit as a valid selector Fixed — explicit if (modelSelector.trim() === 'inherit') throw new Error('Advisor cannot inherit the executor model.') added in packages/cli/src/config/advisor-model.ts

Earlier CI-bot Criticals from rounds 1–2 also resolved:

  • hot-reload.ts TypeError on non-string advisorModel: advisor sync block removed from hot-reload.ts; no advisor references remain in that file at HEAD.
  • settingsSchema.ts advisorMaxUses validation gap: field removed from schema entirely (+3/-3 in the diff replaces the old advisorModel field metadata — requiresRestart: false→true, description updated, showInDialog: true→false; advisorMaxUses is absent at HEAD).
  • resolveAdvisorModel trim without type guard (R2-5): fixed — packages/cli/src/config/config.ts:1256 now uses typeof raw === 'string' ? raw.trim() : undefined, correctly handling non-string settings values from hand-edited config files.
  • scope-shadowing in /advisor off / Settings Dialog (advisor-command.ts, DialogManager.tsx): persists to SettingScope.User in the current code; carries forward as a non-blocker per round-13 posture — the in-memory setAdvisorModel(undefined) still disables the advisor for the running session.

Deferred non-blockers from round 13 (carried forward as follow-up work, not requested in this round):
R13-36 (Critical [fails-closed, new-surface]: auth-qualified startup rejection of a model identical to the runtime executor) · R13-1–R13-25 + 16 additional per the bot's ledger. None of these items have changed character at the current head.

No new blockers found in the 60-file diff at HEAD 5c96f29f.

Reviewed with AI assistance.

@yiliang114
yiliang114 added this pull request to the merge queue Sep 25, 2026
Merged via the queue into QwenLM:main with commit 90232f0 Sep 25, 2026
70 of 71 checks passed
wenshao added a commit to doudouOUC/qwen-code that referenced this pull request Sep 25, 2026
…s-p0-p8

Brings in main through 9534a52. The Runtime Broker README and QWEN.md
keep the branch's text and add QwenLM#12681's Workspace binding sections.
cli/src/config/config.ts keeps QwenLM#9636's advisor helpers next to the
branch's isDebugMode(argv, environment), and environment.test.ts
imports both sides' helpers.
yiliang114 added a commit that referenced this pull request Sep 27, 2026
The base refresh brought in the native advisor tool (#9636), and `places every built-in tool name` failed with `expected [ 'advisor' ] to deeply equal []`.

AdvisorTool is constructed with shouldDefer=true and alwaysLoad=false (advisor.ts:210-224), so it belongs in DEFERRED: it stays out of the first request until tool_search loads it, and it gets no size ceiling because a deferred declaration is not resident cost. Listed first to keep DEFERRED alphabetical.

Verified: 868 passed / 0 failed across the eight touched packages/core/src/tools suites (the count is the previous 867 plus the new `advisor stays deferred` case), core typecheck clean, prettier clean.

Co-authored-by: Qwen-Coder <[email protected]>
pull Bot pushed a commit to mcx/qwen-code that referenced this pull request Sep 27, 2026
…surface (QwenLM#12532)

* perf(core): trim restated tool guidance and budget the resident tool surface

Every resident tool's description and schema is sent on every request.
Remove text that restates the tool's own schema, repeats the system
prompt, or contradicts itself, and pin a per-turn size budget on every
built-in tool that had none (QwenLM#12054).

Trimmed, in each case without losing a rule the model can act on:

- run_shell_command: "The command argument is required" restated
  `required`; "5-10 words" contradicted the `description` parameter's
  "up to 3 sentences"; the git status/git diff and mkdir/cp examples
  restated the sentence before them; the foreground list recommended
  `ls`, `cat`, `grep`, which the same description forbids, and is
  collapsed to one line.
- read_file: the PDF page cap and the notebook offset/limit/pages rule
  are already stated on the `pages`/`offset`/`limit` parameters.
- edit: the closing replace_all line repeated the opening paragraph and
  the parameter; a stray six-space indent is removed.
- glob, grep_search: drop the parallel-call reminder (the system prompt
  owns it) and the unconditional "use the Agent tool" pointer, which
  names a tool that may not be registered.

The call-boundary clauses shell.test.ts pins stay byte-identical.

tool-surface-budget.test.ts adds a ceiling for 25 resident tools
(measurement plus ~2%) and pins which 20 tools are deferred, since a
tool flipping to resident is the largest per-turn change a one-line
diff can make. agent, run_shell_command and workflow keep their
existing budgets.

Not run locally: vitest, typecheck and eslint are left to CI. Sizes were
measured by instantiating each tool with the test's own minimal config
in a single tsx process; the shell snapshot was compared byte-for-byte
against the rendered non-Windows description.

* test(core): make the tool budget cover the whole built-in surface

Review of QwenLM#12532:

- exit_plan_mode is shouldDefer but alwaysLoad (QwenLM#5210), so it is sent with
  every request. It moves to the resident list with a 2,450 ceiling, and
  the deferred list now also asserts alwaysLoad is false.
- exec's class schema is never what a request carries: code mode declares
  it with every bound tool folded in. The row is removed and the reason
  recorded.
- skill (2,313), team_create (7,619) and artifact (1,754) get ceilings;
  omni_recall_media_memory joins the deferred list.
- A completeness check fails when a ToolNames entry is not placed in the
  resident list, the deferred list, or the documented not-budgeted set.
- The trimmed edit, glob, grep and read_file descriptions get toContain
  assertions for the clauses a future trim must keep.
- shell.test.ts budgets were still the pre-trim ones; each shape is
  re-measured (bash 4,315, Git Bash 4,140, powershell.exe 4,040, pwsh.exe
  3,934, cmd.exe 3,791) and budgeted at measurement plus ~350 as before.

Measured with the test's own minimal config in a tsx process; the new
file was also executed there against a vitest shim (82 cases, all pass).

* fix(core): keep the Agent-tool steering for open-ended searches

The trim removed the "use the Agent tool for open-ended searches" clause from glob, grep and ripGrep. Unlike every other line this PR removes, that clause is not restated anywhere else: git grep -l across main for "open ended search" and "open-ended searches" in packages/core/src and packages/cli/src returns exactly those three files. Removing it from all three deletes the steering from the whole model-facing surface rather than de-duplicating it, which is outside this PR stated scope of trimming *restated* guidance.

Restored byte-identical to main in all three descriptions, and added to the load-bearing-clause oracle so a future trim cannot drop it silently. Re-measured the three declarations (budgets temporarily set to 1 to read the actual sizes off the assertion) and set each ceiling to the next multiple of 50 above the measurement: ripgrep 1204 to 1250, grep fallback 1078 to 1100, glob 886 to 900. The ripgrep row moves up so RESIDENT stays in descending order.

The other removals are unchanged; each was confirmed to be a genuine restatement: edit replace_all line (edit.ts:824), read_file page cap and notebook argument rule (read-file.ts:613, enforced at :669/:695), glob parallel-call line (prompts.ts:471).

Verified: 877 passed / 0 failed across the ten touched packages/core/src/tools suites, core typecheck clean, prettier clean.

Co-authored-by: Qwen-Coder <[email protected]>

* test(core): place the new advisor tool in the surface budget census

The base refresh brought in the native advisor tool (QwenLM#9636), and `places every built-in tool name` failed with `expected [ 'advisor' ] to deeply equal []`.

AdvisorTool is constructed with shouldDefer=true and alwaysLoad=false (advisor.ts:210-224), so it belongs in DEFERRED: it stays out of the first request until tool_search loads it, and it gets no size ceiling because a deferred declaration is not resident cost. Listed first to keep DEFERRED alphabetical.

Verified: 868 passed / 0 failed across the eight touched packages/core/src/tools suites (the count is the previous 867 plus the new `advisor stays deferred` case), core typecheck clean, prettier clean.

Co-authored-by: Qwen-Coder <[email protected]>

* Revert "fix(core): keep the Agent-tool steering for open-ended searches"

This reverts f2456fa. The restoration was my error, not a review-driven fix.

I ordered it on the reasoning that the clause is not restated anywhere on main, so removing it deleted steering rather than de-duplicating it. That missed the reason the trim was made, which the PR description already states: the clause names a tool that may not be declared in this session, which is the same dangling-reference defect QwenLM#12145 fixed for the system prompt. QwenLM#12145 (merged 2026-09-18) made the base prompt describe only the tools a session actually declares and left no "Agent tool" mention in prompts.ts, so glob, grep and ripGrep were the last static references to it. Tool descriptions cannot be line-gated the way the prompt now is, so dropping the reference is the consistent fix and restoring it re-opened the defect QwenLM#12145 closed.

Reverting returns grep.ts and ripGrep.ts to the trimmed shape, glob.ts to trimming both clauses, and the three budgets to their measured values (ripgrep 1200, grep fallback 1050, glob 800). The base refresh and the advisor census placement are kept.

Verified: 878 passed / 0 failed across the ten touched packages/core/src/tools suites, core typecheck clean, prettier clean.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants