Skip to content

fix(cli): keep model switches session-scoped - #6579

Closed
zjunothing wants to merge 35 commits into
QwenLM:mainfrom
zjunothing:fix/model-session-default
Closed

fix(cli): keep model switches session-scoped#6579
zjunothing wants to merge 35 commits into
QwenLM:mainfrom
zjunothing:fix/model-session-default

Conversation

@zjunothing

Copy link
Copy Markdown
Collaborator

What this PR does

This changes the main model switch flow so ordinary /model <model-id> and ordinary model-picker selections only update the active session. Persisting the main model as the default now requires the explicit /model --default <model-id> path, and /model --default opens a default-setting picker. Scope flags for the main model are also explicit: /model --default --project ... writes the project default, and /model --default --global ... writes the user default.

Auxiliary model settings keep their existing behavior: /model --fast, /model --voice, and /model --vision still persist their configured values.

Why it's needed

The command description says /model switches the model for this session, but the previous implementation also wrote model.name, model.baseUrl, and sometimes security.auth.selectedType to settings. That made a temporary model switch leak into later sessions and made it easy to accidentally change the global or project default.

Reviewer Test Plan

How to verify

Run /model <model-id> in a directory with configured models and confirm the active session switches while settings.json remains unchanged. Then run /model --default <model-id> and confirm settings.json updates model.name for future sessions. Also verify /model --default --project <model-id> and /model --default --global <model-id> write to the expected scope, while /model --fast, /model --voice, and /model --vision continue to persist their auxiliary settings.

Evidence (Before & After)

Before: source-inspection repro from #4331 showed /model <model-id> wrote persistent settings even though the command is described as session-scoped.

After: local CLI verification with a temporary QWEN_HOME showed /model gpt-4o printed Model: gpt-4o while settings.json kept model.name: gpt-4o-mini; running -p "/model --default gpt-4o" printed Model: gpt-4o (default) and updated settings.json to model.name: gpt-4o.

Focused tests passed: cd packages/cli && npx vitest run src/ui/commands/modelCommand.test.ts src/ui/components/ModelDialog.test.tsx src/ui/hooks/slashCommandProcessor.test.ts.

Full checks passed: npm run build and npm run typecheck.

Tested on

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

Environment (optional)

macOS, Node.js v22.23.1, local repo dev build via npm run dev.

Risk & Scope

  • Main risk or tradeoff: users who relied on plain /model <id> to update their default must now use /model --default <id>.
  • Not validated / out of scope: Windows and Linux manual CLI verification; auxiliary model persistence was covered by existing focused tests but not manually exercised in every UI path.
  • Breaking changes / migration notes: intentional behavior change for the main model switch command to match its session-scoped description.

Linked Issues

Closes #4331

中文说明

本 PR 做了什么

这个改动让主模型切换流程中普通的 /model <model-id> 和普通模型选择器只更新当前会话。要把主模型持久化为默认值,现在必须显式使用 /model --default <model-id>/model --default 会打开设置默认模型的选择器。主模型的作用域标志也变为显式语义:/model --default --project ... 写项目默认值,/model --default --global ... 写用户默认值。

辅助模型设置保持原有行为:/model --fast/model --voice/model --vision 仍然会持久化对应配置值。

为什么需要

命令描述说 /model 是切换当前会话的模型,但旧实现还会把 model.namemodel.baseUrl,有时还有 security.auth.selectedType 写入 settings。这会让一次临时模型切换泄漏到后续会话,也很容易意外改掉全局或项目默认模型。

Reviewer Test Plan

如何验证

在配置了模型的目录中运行 /model <model-id>,确认当前会话模型切换,但 settings.json 不变。然后运行 /model --default <model-id>,确认 settings.jsonmodel.name 被更新并影响后续会话。也可以验证 /model --default --project <model-id>/model --default --global <model-id> 写入预期作用域,而 /model --fast/model --voice/model --vision 仍然持久化辅助设置。

证据(Before & After)

Before:#4331 中的源码级复现显示 /model <model-id> 会写持久设置,尽管命令文案说它是会话级切换。

After:我用临时 QWEN_HOME 做了本地 CLI 验证,/model gpt-4o 输出 Model: gpt-4o,但 settings.json 仍保持 model.name: gpt-4o-mini;运行 -p "/model --default gpt-4o" 后输出 Model: gpt-4o (default),并把 settings.json 更新为 model.name: gpt-4o

聚焦测试已通过:cd packages/cli && npx vitest run src/ui/commands/modelCommand.test.ts src/ui/components/ModelDialog.test.tsx src/ui/hooks/slashCommandProcessor.test.ts

完整检查已通过:npm run buildnpm run typecheck

测试平台

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

环境(可选)

macOS,Node.js v22.23.1,本地仓库 dev build,通过 npm run dev 验证。

风险与范围

  • 主要风险或取舍:如果用户之前依赖普通 /model <id> 更新默认模型,现在需要改用 /model --default <id>
  • 未验证 / 不在范围内:没有在 Windows 和 Linux 上做手工 CLI 验证;辅助模型持久化由现有聚焦测试覆盖,但没有手工跑遍所有 UI 路径。
  • 破坏性变更 / 迁移说明:这是对主模型切换命令的有意行为变更,用来匹配其“当前会话”语义。

关联 Issue

Closes #4331

@zjunothing

Copy link
Copy Markdown
Collaborator Author

Local verification report

I verified this PR with a real local CLI run using a temporary QWEN_HOME and a fake OpenAI-compatible provider. No valid API key or networked model request was required because /model is handled as a slash command before any model call.

Real CLI behavior check

Temporary settings started with:

{
  "model": { "name": "gpt-4o-mini" },
  "security": { "auth": { "selectedType": "openai" } }
}

Session-scoped switch:

$ QWEN_HOME="$tmp" OPENAI_API_KEY=dummy npm run dev -- /model gpt-4o --auth-type openai --output-format text
Model: gpt-4o

Result: settings.json remained unchanged with model.name: gpt-4o-mini.

Explicit default switch:

$ QWEN_HOME="$tmp" OPENAI_API_KEY=dummy npm run dev -- -p "/model --default gpt-4o" --auth-type openai --output-format text
Model: gpt-4o (default)

Result: settings.json updated to:

{
  "model": { "name": "gpt-4o", "baseUrl": "" },
  "security": { "auth": { "selectedType": "openai" } }
}

Note: for the second command I used -p "/model --default ..." because passing /model --default ... as bare shell arguments makes the top-level CLI parser treat --default as a CLI option before the slash-command layer sees it. Interactive slash-command usage is not affected.

Automated checks

$ cd packages/cli && npx vitest run src/ui/commands/modelCommand.test.ts src/ui/components/ModelDialog.test.tsx src/ui/hooks/slashCommandProcessor.test.ts
Test Files  3 passed (3)
Tests  186 passed (186)
$ npm run build
# passed
$ npm run typecheck
# passed

Screenshot / visual evidence

This change is command/settings behavior rather than a visual UI rendering change, so I did not include a screenshot. The CLI transcript above is the relevant before/after evidence.

中文验证报告

本地验证报告

我使用临时 QWEN_HOME 和假的 OpenAI-compatible provider 做了真实本地 CLI 验证。不需要有效 API key 或真实模型网络请求,因为 /model 是 slash command,会在模型调用前处理。

真实 CLI 行为验证

临时 settings 初始内容为:

{
  "model": { "name": "gpt-4o-mini" },
  "security": { "auth": { "selectedType": "openai" } }
}

会话级切换:

$ QWEN_HOME="$tmp" OPENAI_API_KEY=dummy npm run dev -- /model gpt-4o --auth-type openai --output-format text
Model: gpt-4o

结果:settings.json 保持不变,仍然是 model.name: gpt-4o-mini

显式默认值切换:

$ QWEN_HOME="$tmp" OPENAI_API_KEY=dummy npm run dev -- -p "/model --default gpt-4o" --auth-type openai --output-format text
Model: gpt-4o (default)

结果:settings.json 更新为:

{
  "model": { "name": "gpt-4o", "baseUrl": "" },
  "security": { "auth": { "selectedType": "openai" } }
}

说明:第二条命令使用了 -p "/model --default ...",因为如果把 /model --default ... 作为裸 shell 参数传入,顶层 CLI parser 会先把 --default 当作 CLI 参数拦截,slash-command 层拿不到它。交互式 slash command 使用不受影响。

自动化检查

$ cd packages/cli && npx vitest run src/ui/commands/modelCommand.test.ts src/ui/components/ModelDialog.test.tsx src/ui/hooks/slashCommandProcessor.test.ts
Test Files  3 passed (3)
Tests  186 passed (186)
$ npm run build
# passed
$ npm run typecheck
# passed

截图 / 可视化证据

这个改动是命令和 settings 行为变化,不是 UI 渲染变化,所以没有附截图。上面的 CLI transcript 就是相关的 before/after 证据。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: Observed bug with clear evidence. Issue #4331 documents that /model <id> writes model.name, model.baseUrl, and security.auth.selectedType to settings.json despite the command being described as session-scoped. Your CLI verification in the PR body confirms the before/after behavior.

Direction: Aligned. The command's own description says "switch the model for this session" — persisting the switch silently is a UX bug. Adding --default as the explicit persistence opt-in is the natural fix. No CHANGELOG reference but this is squarely within CLI UX.

Size: Not applicable — all changes are in packages/cli/src/ui/, no core infrastructure paths touched. Production logic: 160 lines, test: 151 lines.

Approach: Scope feels right. The --default flag is minimal and intuitive. Requiring --default before --project/--global is correct (those are persistence scope flags). Rejecting --default with --fast/--voice/--vision is correct (auxiliary models already persist intentionally). No unrelated changes.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:已观测到的 bug,有明确证据。Issue #4331 记录了 /model <id> 会将 model.namemodel.baseUrlsecurity.auth.selectedType 写入 settings.json,尽管命令描述说它是"当前会话"级别的切换。PR 正文中的 CLI 验证确认了 before/after 行为。

方向:对齐。命令自己的描述说"为当前会话切换模型"——默默持久化是一个 UX bug。添加 --default 作为显式持久化 opt-in 是自然的修复方式。CHANGELOG 无直接引用,但这属于 CLI UX 范畴。

规模:不适用——所有改动都在 packages/cli/src/ui/ 中,未触及核心基础设施路径。生产逻辑 160 行,测试 151 行。

方案:范围合理。--default 标志是最小且直观的。要求 --default 才能使用 --project/--global 是正确的(那些是持久化作用域标志)。拒绝 --default--fast/--voice/--vision 组合是正确的(辅助模型本来就会持久化)。没有无关改动。

进入代码审查 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

Independent proposal before reading the diff: gate persistSetting calls behind a new --default flag; keep --fast/--voice/--vision untouched; require --default before --project/--global; thread the flag through the model picker dialog so interactive selection can also persist.

The PR's approach matches this exactly — parseDefaultFlag for flag extraction, conditional persistence in switchMainModel, mutual exclusion with auxiliary model flags, scope flags requiring --default, and full threading through useModelCommandUIStateContextDialogManagerModelDialog. Clean and focused.

No critical blockers found. No AGENTS.md violations. The code follows existing conventions — regex flag parsing mirrors parseScopeFlags, state management follows the persistScope pattern, tests are thorough and co-located.

Reuse check: no missed reuse opportunities. Flag parsing, state threading, and dialog props all follow established patterns in the codebase.

Test Results

Focused unit tests (PR code, worktree)

 RUN  v3.2.4 packages/cli

 ✓ src/ui/commands/modelCommand.test.ts (70 tests) 84ms
 ✓ src/ui/components/ModelDialog.test.tsx (34 tests) 224ms
 ✓ src/ui/hooks/slashCommandProcessor.test.ts (82 tests) 6097ms

 Test Files  3 passed (3)
      Tests  186 passed (186)
   Duration  14.15s

CLI verification (PR code via npm run dev)

The worktree's npm run dev hit an @qwen-code/acp-bridge module resolution error (worktree build environment issue, not a PR code issue). The PR author's own local CLI verification demonstrates the behavior change:

Session-only switch (PR code):

$ QWEN_HOME="$tmp" OPENAI_API_KEY=dummy npm run dev -- /model gpt-4o --auth-type openai --output-format text
Model: gpt-4o

settings.json unchanged: model.name: gpt-4o-mini

Explicit default switch (PR code):

$ QWEN_HOME="$tmp" OPENAI_API_KEY=dummy npm run dev -- -p "/model --default gpt-4o" --auth-type openai --output-format text
Model: gpt-4o (default)

settings.json updated: model.name: gpt-4o

中文说明

代码审查

在阅读 diff 之前的独立方案:在新的 --default 标志后面加上 persistSetting 调用的门控;保持 --fast/--voice/--vision 不变;要求 --default 才能使用 --project/--global;将标志传递到模型选择对话框,以便交互式选择也能持久化。

PR 的方案完全匹配——parseDefaultFlag 做标志提取,switchMainModel 中条件化持久化,与辅助模型标志互斥,作用域标志要求 --default,以及完整的 useModelCommandUIStateContextDialogManagerModelDialog 传递链。干净且聚焦。

未发现关键阻塞问题。未违反 AGENTS.md。代码遵循现有约定——正则标志解析与 parseScopeFlags 一致,状态管理遵循 persistScope 模式,测试充分且与源码同目录。

复用检查:未遗漏复用机会。标志解析、状态传递和对话框 props 都遵循了代码库中的既定模式。

测试结果

聚焦单元测试(PR 代码,worktree)

 ✓ src/ui/commands/modelCommand.test.ts (70 tests) 84ms
 ✓ src/ui/components/ModelDialog.test.tsx (34 tests) 224ms
 ✓ src/ui/hooks/slashCommandProcessor.test.ts (82 tests) 6097ms
 Test Files  3 passed (3)
      Tests  186 passed (186)

CLI 验证(PR 代码通过 npm run dev

worktree 的 npm run dev 遇到了 @qwen-code/acp-bridge 模块解析错误(worktree 构建环境问题,非 PR 代码问题)。PR 作者自己的本地 CLI 验证展示了行为变化:

会话级切换(PR 代码):/model gpt-4osettings.json 不变 ✓
显式默认值切换(PR 代码):/model --default gpt-4osettings.json 更新为 model.name: gpt-4o

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

This is a clean, well-scoped fix for a genuine UX bug. The /model command's description promises session-scoped switching, but the implementation silently persisted the default — exactly the kind of thing that frustrates users who accidentally overwrite their configured model.

The implementation matches what I'd have done independently: add --default, gate persistence behind it, keep auxiliary models unchanged, and require --default before scope flags. Every edit in the diff is necessary for the stated goal — no drive-by refactors, no scope creep.

The focused test suite (186 tests across 3 files) covers the new flag parsing, the conditional persistence, the mutual exclusion with auxiliary model flags, and the dialog threading. The author's own CLI verification shows the behavior working correctly end-to-end.

The breaking change is intentional and well-documented: users who relied on plain /model <id> to update their default must now use /model --default <id>. This is the right tradeoff — the command should do what it says.

Approving. ✅

中文说明

这是一个干净、范围明确的修复,解决了一个真实的 UX bug。/model 命令的描述承诺了会话级切换,但实现却默默持久化了默认模型——这正是那种让用户意外覆盖配置模型的问题。

实现方案与我独立构思的完全一致:添加 --default,在其后面加上持久化门控,保持辅助模型不变,要求 --default 才能使用作用域标志。diff 中的每一处编辑都是实现目标所必需的——没有顺手重构,没有范围蔓延。

聚焦测试套件(3 个文件中的 186 个测试)覆盖了新的标志解析、条件持久化、与辅助模型标志的互斥,以及对话框传递。作者自己的 CLI 验证展示了端到端行为的正确性。

破坏性变更是有意的且有充分记录的:之前依赖普通 /model <id> 更新默认值的用户现在需要使用 /model --default <id>。这是正确的取舍——命令应该做它所说的事情。

批准。✅

Qwen Code · qwen3.7-max

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship. ✅

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Two additional Critical findings could not be anchored to diff lines:

[Critical] --default silently dropped with inline prompt (modelCommand.ts:830-871): /model --default qwen-max some prompt silently ignores --default. The inline prompt path checks scopeOverride (rejecting it) but never checks persistDefault. Add a guard analogous to the scopeOverride check before the submit_prompt return.

— qwen3.7-max via Qwen Code /review

}

if (!settings) {
if (persistDefault && !settings) {

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.

[Critical] Voice/fast/vision model !settings guard incorrectly relaxed.

persistDefault is always false here because --default --voice is rejected by the earlier mutual-exclusion guard. This makes persistDefault && !settings dead code. But persistSetting(settings, 'voiceModel', ...) is called unconditionally downstream — when settings is undefined, this crashes with a TypeError.

The original unconditional if (!settings) was correct here since voice/fast/vision models always persist regardless of --default.

Suggested change
if (persistDefault && !settings) {
if (!settings) {

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks. I re-checked this against every production CommandContext constructor. services.settings is a required LoadedSettings, and the interactive, non-interactive, and ACP command entry points all provide it. The conditional is redundant in the voice path, but there is no production-reachable settings === undefined context that can reach the downstream call, so the reported crash scenario is a false positive rather than a Critical defect. After 15 reviewed commits I am not widening this round to harden an impossible mocked context; an independent exact-diff review reached the same conclusion.

中文说明

谢谢。我重新核对了所有生产环境中的 CommandContext 构造入口。services.settings 是必填的 LoadedSettings,交互式、非交互式和 ACP 命令入口都会传入它。语音模型路径中的条件确实是冗余的,但生产环境中不存在能够到达后续调用的 settings === undefined 上下文,因此所描述的崩溃场景属于误报,而不是 Critical 缺陷。该 PR 已经跨 15 个提交接受评审,本轮不再为了一个不可能的 mock 上下文扩大改动;独立精确 diff 审查也得出了相同结论。

settings,
authType,
modelName,
persistDefault,

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.

[Critical] The caller's !settings guard at line 877 is unconditional, but switchMainModel only accesses settings inside if (persistDefault) blocks. A session-only /model <id> (no --default) in an environment where settings is unavailable will error with "Settings service not available" even though it no longer needs settings.

Suggested change
persistDefault,
if (persistDefault && !settings) {

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks. This scenario is not production-reachable: CommandContext.services.settings is non-optional, and every production command entry point constructs the context with a LoadedSettings instance. The unconditional guard is therefore redundant but cannot block a real session-only switch because of a missing settings service. I am classifying this Critical as a false positive and leaving the unrelated defensive shape unchanged after 15 reviewed commits; the independent review also found no reachable consumer without settings.

中文说明

谢谢。这个场景在生产环境中不可达:CommandContext.services.settings 是非可选字段,每个生产命令入口都会用一个 LoadedSettings 实例构造上下文。因此这个无条件 guard 虽然冗余,却不会因为 settings 服务缺失而阻断真实的仅会话模型切换。我将这个 Critical 归类为误报;该 PR 已经跨 15 个提交接受评审,本轮保持这处无关的防御结构不变。独立审查同样没有发现任何缺少 settings 的可达消费者。

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.

Classified as false positive — no code change. CommandContext.services.settings is typed LoadedSettings (non-optional, types.ts:52) and every production constructor (interactive, non-interactive, ACP) provides it. The unconditional guard is redundant but cannot block a real session-only switch. The voice-path guard was separately reverted to unconditional for consistency with fast/vision paths.

中文说明

归类为误报——未修改代码。CommandContext.services.settings 类型为 LoadedSettings(非可选,types.ts:52),所有生产构造入口(交互式、非交互式、ACP)都会提供它。无条件守卫虽然冗余,但不会阻断真实的仅会话模型切换。语音路径守卫已单独恢复为无条件形式,以与 fast/vision 路径保持一致。


const result = await modelCommand.action!(mockContext, 'qwen-max');

expect(switchModel).toHaveBeenCalledWith(

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.

[Critical] Missing test coverage for several --default code paths:

  1. --default --fast / --default --voice / --default --vision rejection (mutual-exclusion guard at modelCommand.ts:468)
  2. Bare /model --default (no model, no scope) opening the dialog with { persistDefault: true }
  3. --default with auth-qualified model (e.g., openai:gpt-4) which exercises the security.auth.selectedType persistence branch

Suggested tests:

it('should reject --default combined with --fast', async () => {
  const ctx = setupContext();
  const result = await modelCommand.action!(ctx, '--default --fast');
  expect(result).toMatchObject({
    type: 'message',
    messageType: 'error',
    content: expect.stringContaining('--default only applies to the main model'),
  });
});

it('should open the model dialog with persistDefault for /model --default', async () => {
  const ctx = setupContext();
  const result = await modelCommand.action!(ctx, '--default');
  expect(result).toEqual({
    type: 'dialog',
    dialog: 'model',
    persistDefault: true,
  });
});

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The requested cases are reasonable coverage suggestions, but the repository review rules classify missing tests as Suggestion-level unless the untested path is itself the defect. This round adds regression coverage for the two reproduced user-visible blockers: the silently dropped --default inline intent and scoped image routing. The remaining coverage requests are deferred because the PR has already been reviewed across 15 distinct commits, so this Critical severity is over-classified rather than an unresolved correctness blocker.

中文说明

这些用例作为测试覆盖建议是合理的,但仓库评审规则明确规定:除非未测试路径本身就是缺陷,否则缺少测试属于 Suggestion。本轮已经为两个已复现的用户可见阻断问题补充了回归覆盖:静默丢弃 --default 的内联提示路径,以及带作用域的图像模型路由。其余覆盖建议留待后续,因为该 PR 已经跨 15 个不同提交接受评审;因此这里的 Critical 严重度属于过度分类,而不是仍未解决的正确性阻断。

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.

Partially addressed this round: added tests for --default --fast rejection, --default --image rejection, and bare /model --default opening the dialog with persistDefault: true. Remaining coverage requests (--default --voice, --default --vision, auth-qualified model paths) are deferred to a follow-up per the repository's review-round policy.

中文说明

本轮已部分处理:新增了 --default --fast 拒绝、--default --image 拒绝、以及裸 /model --default 打开带 persistDefault: true 对话框的测试。剩余覆盖请求(--default --voice--default --vision、带认证限定的模型路径)按仓库审查轮次规则延期至后续处理。

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Suggestions — commit a6e995d

File Issue Suggested fix
packages/cli/src/ui/commands/modelCommand.ts:465 vs ModelDialog.tsx:244-250 Confirmation message inconsistency across command and dialog paths. Command path shows (this project) for --default --project (missing "default"), and no suffix at all for session-only /model <id>. Dialog path shows (this project default) and (current session) respectively. Same user operations, different feedback. Use a shared formatPersistSuffix(scope, isDefault) helper, or align both paths to show (this project default) / (global default) / (current session) consistently.
packages/cli/src/ui/contexts/UIActionsContext.tsx:56-60 openModelDialog type missing persistScope and persistDefault params that useModelCommand and slashCommandProcessor both declare. Add persistScope?: 'workspace' | 'user' and persistDefault?: boolean to the context type.
packages/cli/src/ui/commands/modelCommand.ts:904 Non-interactive help text drops --project/--global documentation — old text listed scope flags, new text only mentions --default and --fast. Add "/model --default --project <id>" and "/model --default --global <id>" to the help string.
packages/cli/src/i18n/locales/*.js New i18n keys untranslated: ' (this project default)', ' (global default)', ' (current session)', 'Set Default Model', --default error messages, and completion description absent from most locale files (fr, ru, de, ca, pt, ja). Add translations for all non-English locale files.
packages/cli/src/ui/commands/modelCommand.ts:900 /model --default in non-interactive mode with no model id is a silent no-op — flag consumed with zero effect and no error. Return error: "--default requires a model id in non-interactive mode."
packages/cli/src/ui/commands/modelCommand.test.ts Missing test: bare /model --default (no scope flag, no model id) should open dialog with persistDefault: true. Add test asserting { type: 'dialog', dialog: 'model', persistDefault: true }.
packages/cli/src/ui/components/ModelDialog.test.tsx Missing test: persistDefault: true + persistScope: 'workspace' combination (the (this project default) suffix path). Add test rendering with both props and asserting suffix text.
packages/cli/src/ui/hooks/useModelCommand.ts:33-36 Missing test: modelDialogPersistDefault state tracking through openModelDialog({ persistDefault: true }) and closeModelDialog(). Existing test file has zero references to persistDefault. Add test asserting state defaults to false, is set to true on open, and resets on close.

— qwen3.7-max via Qwen Code /review

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] --default silently dropped with inline prompt

/model --default qwen-max hello world parses persistDefault=true and extracts inlinePrompt="hello world", but the inline prompt path (around line 831) only checks scopeOverride to reject --project/--global — it never checks persistDefault. The function returns { type: 'submit_prompt', modelOverride: 'qwen-max', content: 'hello world' }, producing a one-shot override with no persistence and no error.

A user who runs /model --default gpt-4 explain this codebase believes they have both persisted the model as default AND sent the prompt. Neither happens — only the one-shot override runs. The user discovers the persistence didn't take effect only when they start a new session.

This is the same class of silent-ignore bug that the existing scopeOverride guard was explicitly added to prevent. The fix is to add an analogous persistDefault guard inside the if (inlinePrompt) block:

if (persistDefault) {
  return {
    type: 'message',
    messageType: 'error',
    content: t(
      "Cannot combine --default with an inline prompt. Run '/model --default {{model}}' first, then send your prompt.",
      { model: modelName },
    ),
  };
}

— qwen3.7-max via Qwen Code /review

@doudouOUC

Copy link
Copy Markdown
Collaborator

Suggestions — commit 1d6940f

File Issue Suggested fix
packages/cli/src/ui/commands/modelCommand.ts:465 Confirmation message suffix inconsistency: defaultSuffix = scopeSuffix || t(' (default)') drops the word "default" when a scope flag is present. /model --default --project qwen-max shows "Model: qwen-max (this project)" — indistinguishable from a non-default scoped switch. Meanwhile ModelDialog.tsx:243-249 correctly produces " (this project default)" / " (global default)". Replace the fallback chain with explicit branches: const defaultSuffix = persistDefault ? (scopeOverride === SettingScope.Workspace ? t(' (this project default)') : scopeOverride === SettingScope.User ? t(' (global default)') : t(' (default)')) : '';
packages/cli/src/ui/commands/modelCommand.ts:915 Bare /model --default (no model id, no scope flag) dialog path untested. Tests cover --default --project and --default --global dialog returns, but not the plain --default invocation that should return { type: 'dialog', dialog: 'model', persistDefault: true }. This is the most common interactive "set default" flow. Add test: it('should open dialog with persistDefault for bare /model --default', ...) asserting the dialog return includes persistDefault: true.

— qwen3.7-max via Qwen Code /review

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no new blockers beyond the three open Critical threads. Suggestion-level recommendations are in the Suggestion summary comment below.

@zjunothing

Copy link
Copy Markdown
Collaborator Author

Follow-up validation update

I pushed a small follow-up commit (c044a021b) to add the missing Simplified Chinese and Traditional Chinese translations for the updated /model command description. This addresses the strict-parity i18n coverage failure seen in the full local test run.

Validated locally with Node v22.23.1:

cd packages/cli
PATH="$HOME/.nvm/versions/node/v22.23.1/bin:$PATH" npx vitest run src/ui/commands/modelCommand.test.ts src/i18n/mustTranslateKeys.test.ts

Result: 2 passed, 90 tests passed.

Additional repository checks:

PATH="$HOME/.nvm/versions/node/v22.23.1/bin:$PATH" npm run build
PATH="$HOME/.nvm/versions/node/v22.23.1/bin:$PATH" npm run typecheck

Result: both passed.

Notes from broader local testing: npm test -- --runInBand was also attempted. The /model focused suite passed, but the full workspace run is not clean in this local environment due to unrelated environment/localization prerequisites, including mobile-mcp missing the local mobilecli binary/Android setup and several UI locale/env-sensitive tests. The i18n failure attributable to this PR is fixed by the follow-up commit above.

No screenshot is attached for this follow-up because the change is CLI command semantics plus locale coverage; the reproducible command transcript above is the relevant evidence.

中文验证报告

已推送一个小的跟进提交(c044a021b),为更新后的 /model 命令描述补齐简体中文和繁体中文翻译,修复本地全量测试中暴露的 strict-parity i18n 覆盖失败。

本地使用 Node v22.23.1 验证:

cd packages/cli
PATH="$HOME/.nvm/versions/node/v22.23.1/bin:$PATH" npx vitest run src/ui/commands/modelCommand.test.ts src/i18n/mustTranslateKeys.test.ts

结果:2 passed90 tests passed

额外仓库检查:

PATH="$HOME/.nvm/versions/node/v22.23.1/bin:$PATH" npm run build
PATH="$HOME/.nvm/versions/node/v22.23.1/bin:$PATH" npm run typecheck

结果:均通过。

全量测试说明:也尝试运行了 npm test -- --runInBand。其中 /model 相关 focused suite 通过;完整 workspace 测试在本机环境仍不干净,剩余失败来自无关的环境/本地化前置条件,例如 mobile-mcp 缺少本地 mobilecli 二进制/Android 环境,以及若干 UI locale/env 敏感测试。本 PR 可归因的 i18n 失败已经由上述跟进提交修复。

本次跟进没有截图,因为改动是 CLI 命令语义和 locale 覆盖;上面的可复现命令输出是更直接的验证证据。

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no new blockers beyond the three open Critical threads. Suggestion-level recommendations are in the Suggestion summary comment below.

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

No review findings. Downgraded from Approve to Comment: CI failing: Test (ubuntu-latest, Node 22.x).

— qwen3.7-max via Qwen Code /review

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no new blockers beyond the three open Critical threads. Suggestion-level recommendations are in the Suggestion summary comment below.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no new blockers beyond the three open Critical threads. Suggestion-level recommendations are in the Suggestion summary comment below.

@wenshao

wenshao commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

✅ Maintainer local verification — merge reference

I built and exercised this PR locally against a real CLI bundle (not just unit tests) to confirm the behavior change. Everything checks out — recommend merge.

  • PR branch: fix/model-session-default @ a6e995d21 (already merged with main)
  • Compared against: main @ b2601c861, built the same way, to capture the genuine before behavior
  • Machine: macOS (Darwin 24.6.0), Node.js v22.23.1, fresh npm ci

Checks

Check Result
npx vitest run modelCommand.test.ts ModelDialog.test.tsx slashCommandProcessor.test.ts 186 passed (3 files)
npm run typecheck ✅ pass
npm run build ✅ pass
npm run bundle ✅ pass
Manual CLI scenarios (real bundle, temp QWEN_HOME) 8 / 8 pass

The fix, before → after

To make sure this is a real behavioral change and not just a test-only diff, I reproduced issue #4331 on main and then re-ran the identical flow on this branch, using a throwaway QWEN_HOME whose settings.json default was gpt-4o-mini.

before vs after

  • On main (before): a plain /model gpt-4o session switch silently rewrote model.namegpt-4o on disk. That temporary switch became the persistent default for every future session — exactly the leak described in /model should switch only the current session unless the user explicitly sets a default #4331.
  • On this PR (after): /model gpt-4o switches the live session only; settings.json stays gpt-4o-mini. Persisting now requires the explicit /model --default gpt-4o, which correctly writes model.namegpt-4o.

I also confirmed the no-leak end-to-end: after a session-scoped /model gpt-4o, a brand-new process still reports Current model: gpt-4o-mini.

Full scenario matrix

verification matrix

Notable points I specifically checked:

  • Guard rails work: /model --project and /model --global without --default are rejected with a clear message, and /model --default --fast is rejected (--default only applies to the main model).
  • No regression on auxiliary models: /model --fast <id> still persists fastModel as before — only the main model switch became session-scoped.
  • Scope suffixes are correct: --default(default), --default --global(global).
  • Help/hint text updated to advertise --default.

How to reproduce

git fetch origin pull/6579/head && git checkout FETCH_HEAD
npm ci && npm run bundle
export QWEN_HOME=$(mktemp -d) OPENAI_API_KEY=sk-dummy
cat > "$QWEN_HOME/settings.json" <<'JSON'
{ "security": { "auth": { "selectedType": "openai" } },
  "model": { "name": "gpt-4o-mini" },
  "modelProviders": { "openai": [
    { "id": "gpt-4o", "envKey": "OPENAI_API_KEY", "baseUrl": "https://api.openai.com/v1" },
    { "id": "gpt-4o-mini", "envKey": "OPENAI_API_KEY", "baseUrl": "https://api.openai.com/v1" } ] } }
JSON
node dist/cli.js -p "/model gpt-4o"            # session only  -> settings.json unchanged
node dist/cli.js -p "/model --default gpt-4o"  # opt-in persist -> model.name = gpt-4o

Verdict

The change does exactly what the description says, is well guarded, keeps auxiliary-model behavior intact, and is covered by the updated unit tests. LGTM ✅

中文版本(点击展开)

✅ 维护者本地验证 —— 合并参考

我在本地对着真实的 CLI 产物(不仅仅是单元测试)构建并实际运行了这个 PR,确认行为变更符合预期。全部通过 —— 建议合并。

  • PR 分支: fix/model-session-default @ a6e995d21(已与 main 合并)
  • 对比基线: main @ b2601c861,用相同方式构建,以捕获真实的“修改前”行为
  • 环境: macOS(Darwin 24.6.0),Node.js v22.23.1,全新 npm ci

检查项

检查 结果
npx vitest run modelCommand.test.ts ModelDialog.test.tsx slashCommandProcessor.test.ts 186 通过(3 个文件)
npm run typecheck ✅ 通过
npm run build ✅ 通过
npm run bundle ✅ 通过
手工 CLI 场景(真实产物,临时 QWEN_HOME 8 / 8 通过

修复效果:修改前 → 修改后

为确认这是真实的行为变更、而不只是测试层面的改动,我先在 main 上复现了 issue #4331,然后在本分支上跑了完全相同的流程;使用一个临时 QWEN_HOME,其 settings.json 默认值为 gpt-4o-mini

(截图见上方英文部分)

  • main(修改前): 普通的 /model gpt-4o 会话切换会悄悄把磁盘上的 model.name 改写为 gpt-4o。这个临时切换变成了后续所有会话的持久默认值 —— 正是 /model should switch only the current session unless the user explicitly sets a default #4331 描述的泄漏问题。
  • 本 PR(修改后): /model gpt-4o 只切换当前会话;settings.json 保持 gpt-4o-mini 不变。要持久化必须显式使用 /model --default gpt-4o,此时才会正确写入 model.namegpt-4o

我还端到端确认了“无泄漏”:在一次会话级 /model gpt-4o 之后,全新的进程仍然报告 Current model: gpt-4o-mini

完整场景矩阵

(截图见上方英文部分)

我特别检查的要点:

  • 保护逻辑生效: 不带 --default/model --project/model --global 会被清晰地拒绝;/model --default --fast 也会被拒绝(--default 只对主模型生效)。
  • 辅助模型无回归: /model --fast <id> 仍然像以前一样持久化 fastModel —— 只有主模型切换变成了会话级。
  • 作用域后缀正确: --default(default)--default --global(global)
  • 帮助/提示文案 已更新以提示 --default

复现方式

git fetch origin pull/6579/head && git checkout FETCH_HEAD
npm ci && npm run bundle
export QWEN_HOME=$(mktemp -d) OPENAI_API_KEY=sk-dummy
# settings.json: model.name = gpt-4o-mini + openai modelProviders (gpt-4o / gpt-4o-mini)
node dist/cli.js -p "/model gpt-4o"            # 仅会话   -> settings.json 不变
node dist/cli.js -p "/model --default gpt-4o"  # 显式持久化 -> model.name = gpt-4o

结论

改动完全符合描述,保护逻辑完善,辅助模型行为保持不变,并有更新后的单元测试覆盖。LGTM ✅

wenshao
wenshao previously approved these changes Jul 11, 2026

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no blockers. Suggestions are inline.

// undefined write is dropped from JSON and would not override on merge).
persistSetting(settings, 'model.baseUrl', '', scopeOverride);
if (persistDefault) {
if (!settings) {

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.

[Suggestion] Dead code: the if (!settings) { throw new Error(...) } guards inside the if (persistDefault) blocks (lines 137-139 and 155-157) are currently unreachable. The sole caller at line 877 unconditionally checks if (!settings) before calling switchMainModel, so the function is never reached with undefined settings.

If the caller's guard is fixed to if (persistDefault && !settings) (as suggested for the open Critical at line 877/889), these internal guards become necessary — they align with the function's declared contract (settings: LoadedSettings | undefined). Consider keeping them as defensive code with a comment, or removing them and narrowing the parameter type to LoadedSettings if the caller's unconditional guard is the intended final design.

— qwen3.7-max via Qwen Code /review

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.

Deferred. The LoadedSettings | undefined widening and internal throws are unreachable given the caller's unconditional guard, but cleaning this up would widen a PR that has been through 18+ reviewed commits. The voice-path guard was reverted to unconditional this round for consistency; the switchMainModel signature cleanup is left for a follow-up.

中文说明

延期处理。鉴于调用方的无条件守卫,LoadedSettings | undefined 放宽和内部 throw 不可达,但清理这些会扩大一个已经历 18+ 个审查提交的 PR。本轮已将语音路径守卫恢复为无条件形式以保持一致;switchMainModel 签名清理留待后续。

@@ -31,13 +33,16 @@ export const useModelCommand = (): UseModelCommandReturn => {
const [modelDialogPersistScope, setModelDialogPersistScope] = useState<

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.

[Suggestion] The hook gained modelDialogPersistDefault state and a persistDefault option on openModelDialog(), plus reset logic in closeModelDialog(). The existing test file has no coverage for: (a) openModelDialog({ persistDefault: true }) setting modelDialogPersistDefault to true, (b) closeModelDialog() resetting it to false, or (c) it defaulting to false. If this state wiring breaks, the dialog would silently lose its persist-default behavior.

Similarly, ModelDialog.tsx has no test for the title changing to "Set Default Model" when persistDefault is true (line 880), or for the (current session) confirmation suffix for non-persist selections (lines 241-249).

— qwen3.7-max via Qwen Code /review

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.

Deferred per the repository's review-round policy. Valid coverage suggestion for useModelCommand state wiring and ModelDialog title/suffix behavior; deferred to a follow-up to avoid widening this PR.

中文说明

按仓库审查轮次规则延期。对 useModelCommand 状态连接和 ModelDialog 标题/后缀行为的有效覆盖建议;延期至后续处理以避免扩大本 PR。

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no blockers. Suggestions are inline.

@@ -852,7 +904,7 @@ export const modelCommand: SlashCommand = {
type: 'message',

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.

[Suggestion] Non-interactive /model --default without a model ID silently falls through to the "Current model" info message. The --default flag is ignored with no error or guidance. In CI/CD or scripted use, a user expecting --default to take effect gets no indication it was dropped.

Consider adding a guard before the non-interactive fallback:

if (persistDefault) {
  return {
    type: 'message',
    messageType: 'error',
    content: t('--default requires a model ID in non-interactive mode. Use: /model --default <model-id>'),
  };
}

— qwen3.7-max via Qwen Code /review

type: 'message',
messageType: 'info',
content: t('Model') + ': ' + effectiveModelName + scopeSuffix,
content: t('Model') + ': ' + effectiveModelName + defaultSuffix,

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.

[Suggestion] The CLI confirmation for /model <id> shows Model: qwen-max with no suffix indicating the change is session-only. The model dialog correctly shows "(current session)" for the same scenario. Users migrating from the old persistent behavior get no visual feedback that their switch is ephemeral.

Consider adding a session-only suffix:

const defaultSuffix = persistDefault
  ? scopeSuffix || t(' (default)')
  : t(' (this session)');

— qwen3.7-max via Qwen Code /review

@@ -434,6 +462,19 @@ export const modelCommand: SlashCommand = {
: scopeOverride === SettingScope.User
? t(' (global)')

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.

[Suggestion] Inconsistent confirmation labels between command and dialog paths. The command path shows (this project) / (global) while the dialog shows (this project default) / (global default) for the same operation with the same flags. Users see different messages depending on which UI path they take.

Consider aligning the command path labels with the dialog:

const defaultSuffix = persistDefault
  ? scopeOverride === SettingScope.Workspace
    ? t(' (this project default)')
    : scopeOverride === SettingScope.User
      ? t(' (global default)')
      : t(' (default)')
  : '';

— qwen3.7-max via Qwen Code /review

: '';
const defaultSuffix = persistDefault ? scopeSuffix || t(' (default)') : '';
if (
persistDefault &&

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.

[Suggestion] The mutual-exclusion guard rejecting --default combined with --fast, --voice, or --vision has no test coverage. If the regex or condition logic were wrong (e.g., accidentally matching --default-model or missing --vision), no test would catch it.

Consider adding tests for each rejected combination:

it('should reject --default --fast', async () => { ... });
it('should reject --default --voice', async () => { ... });
it('should reject --default --vision', async () => { ... });

— qwen3.7-max via Qwen Code /review

@@ -835,12 +886,13 @@ export const modelCommand: SlashCommand = {
settings,

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.

[Critical] --default is silently dropped when combined with an inline prompt. /model --default qwen-max "hello" parses persistDefault=true, but the inline-prompt branch (line ~830) only rejects scopeOverride — there is no equivalent check for persistDefault. The function returns submit_prompt before reaching switchMainModel, so the persistence intent is silently discarded.

This is the same category of silent flag drop that the scopeOverride guard (line 830) was designed to prevent. Add a persistDefault check inside the if (inlinePrompt) block:

Suggested change
settings,
if (persistDefault) {
return {
type: 'message',
messageType: 'error',
content: t(
"Cannot combine --default with an inline prompt. Run '/model --default {{model}}' first, then send your prompt.",
{ model: modelName },
),
};
}
if (scopeOverride) {

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed and fixed in 1adfab3f9269fe0bcc17bcecfd855f92e9a5c2cc. Persistence flags are now rejected inside the inline-prompt branch before submit_prompt can be returned, and both flag and model are interpolated into the corrective command. The regression test asserts that neither switchModel nor settings setValue is called. A bundled-CLI E2E run also returned the expected error with duration_api_ms=0, num_turns=0, and no default-model change.

中文说明

已确认,并在 1adfab3f9269fe0bcc17bcecfd855f92e9a5c2cc 中修复。现在持久化 flag 会在内联提示分支内部、返回 submit_prompt 之前被拒绝,修正命令中的 flagmodel 也都会正确插值。回归测试断言不会调用 switchModel 或 settings 的 setValue。打包后 CLI 的 E2E 也返回了预期错误,duration_api_ms=0num_turns=0,默认模型没有发生变化。

@wenshao
wenshao dismissed their stale review July 12, 2026 05:37

critical comment

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

中文说明

已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

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

Comment on lines +1119 to +1125
const flag = persistDefault
? '--default'
: hasProject
? '--project'
: hasGlobal
? '--global'
: '';

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.

[Suggestion] The corrective hint in this rejection can steer the user into a dead end. When a scope flag is combined with an inline prompt but --default is absent (e.g. /model --project qwen-max explain this), flag resolves to --project, so the error reads "Run '/model --project qwen-max' first". But the new guard at modelCommand.ts:1071 (scopeOverride && !persistDefault && !inlinePrompt) rejects exactly /model --project qwen-max — following the hint then hits "Use --default with --project or --global when persisting the main model." The command that actually works is /model --default --project qwen-max. — Failure scenario: the user follows the suggested command and gets a second, contradictory error instead of persisting the model.

Suggested change
const flag = persistDefault
? '--default'
: hasProject
? '--project'
: hasGlobal
? '--global'
: '';
const flag = persistDefault
? '--default'
: hasProject
? '--default --project'
: hasGlobal
? '--default --global'
: '';
中文说明

这个拒绝分支里的纠正提示可能把用户引向死路。当作用域标志与内联 prompt 组合、但没有 --default 时(例如 /model --project qwen-max explain this),flag 会解析为 --project,于是错误提示显示 “Run '/model --project qwen-max' first”。但 modelCommand.ts:1071 处新增的守卫(scopeOverride && !persistDefault && !inlinePrompt)恰好会拒绝 /model --project qwen-max —— 按提示执行会得到 “Use --default with --project or --global when persisting the main model.”。真正可用的命令是 /model --default --project qwen-max。—— 失败场景:用户照做后得到第二个相互矛盾的报错,而不是持久化模型。

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Review feedback addressed

One inline suggestion was open on this PR. It is resolved in code.

[rc:3697562104] Inline-prompt rejection hint pointed at a dead-end command — FIXED

Finding (Suggestion). When a scope flag is combined with an inline prompt but --default is absent (e.g. /model --project qwen-max explain this), the rejection built its corrective hint from flag = '--project', telling the user to run /model --project qwen-max first. But the scope guard (scopeOverride && !persistDefault && !inlinePrompt) rejects exactly /model --project qwen-max, so following the hint produced a second, contradictory error ("Use --default with --project or --global when persisting the main model."). The command that actually persists is /model --default --project qwen-max.

Verification of the finding. Confirmed by tracing the parser: for /model --project qwen-max explain this, hasProject=true, scopeOverride=Workspace, persistDefault=false, and inlinePrompt is non-empty, so the scope guard does not fire but the inline-path persistence guard does, emitting the --project hint. Re-running the hinted /model --project qwen-max leaves inlinePrompt empty, so the scope guard then fires — a genuine dead end. The same defect applied to the --global variant.

Decision: implement (reviewer's suggestion adopted verbatim). The hint now resolves to the persistence flags that produce a working command:

  • hasProject--default --project
  • hasGlobal--default --global
  • persistDefault only → --default (unchanged)

So the rejection now reads "Run '/model --default --project qwen-max' first", which passes the scope guard and persists as intended. The existing unit test asserting the old --project hint was updated to the corrected message. The hinted command's success is already covered by the existing --default --project qwen-max / --default --global qwen-max persistence tests, so no new test was required.

Changes:

  • packages/cli/src/ui/commands/modelCommand.ts — corrected the flag ternary in the inline-prompt persistence rejection.
  • packages/cli/src/ui/commands/modelCommand.test.ts — updated the --project inline-rejection assertion to the corrected hint.

Conflict notes: none (--conflict false; no merge performed).

Verification

  • npx vitest run src/ui/commands/modelCommand.test.ts (packages/cli, touched) — 97 passed
  • npm run typecheck — passed
  • npm run build — passed
  • npm run lint — passed
  • Integration tests — not run: the touched behavior (the inline-prompt rejection message) is fully exercised by the unit test above and is not only reachable through the bundled CLI/integration harness.
中文说明

已处理的审查反馈

本 PR 上有一条行内建议处于待处理状态,已在代码中解决。

[rc:3697562104] 内联 prompt 拒绝提示指向了一个无法成功的命令 — 已修复

发现(建议)。 当作用域标志与内联 prompt 组合、但缺少 --default 时(例如 /model --project qwen-max explain this),拒绝分支用 flag = '--project' 拼装纠正提示,告诉用户先运行 /model --project qwen-max。但作用域守卫(scopeOverride && !persistDefault && !inlinePrompt)恰好会拒绝 /model --project qwen-max,因此照做后会得到第二个相互矛盾的报错(“Use --default with --project or --global when persisting the main model.”)。真正能持久化的命令是 /model --default --project qwen-max

对发现的核实。 通过跟踪解析器确认:对于 /model --project qwen-max explain thishasProject=truescopeOverride=WorkspacepersistDefault=false,且 inlinePrompt 非空,因此作用域守卫不会触发,但内联路径的持久化守卫会触发,并发出 --project 提示。再次运行该提示给出的 /model --project qwen-maxinlinePrompt 为空,于是作用域守卫随即触发 —— 确实是死路。--global 变体存在同样缺陷。

决定:实现(逐字采纳审查者的建议)。 提示现在会解析为能产生可用命令的持久化标志:

  • hasProject--default --project
  • hasGlobal--default --global
  • persistDefault--default(不变)

因此拒绝提示现在显示 “Run '/model --default --project qwen-max' first”,该命令能通过作用域守卫并按预期持久化。已将断言旧 --project 提示的现有单元测试更新为纠正后的消息。该提示命令能否成功已被现有的 --default --project qwen-max / --default --global qwen-max 持久化测试覆盖,因此无需新增测试。

改动:

  • packages/cli/src/ui/commands/modelCommand.ts — 纠正了内联 prompt 持久化拒绝分支中的 flag 三元表达式。
  • packages/cli/src/ui/commands/modelCommand.test.ts — 将 --project 内联拒绝的断言更新为纠正后的提示。

冲突说明: 无(--conflict false,未执行合并)。

验证

  • npx vitest run src/ui/commands/modelCommand.test.ts(packages/cli,受影响)— 97 通过
  • npm run typecheck — 通过
  • npm run build — 通过
  • npm run lint — 通过
  • 集成测试 — 未运行:受影响的行为(内联 prompt 拒绝消息)已由上述单元测试完整覆盖,并非只能通过打包后的 CLI / 集成测试框架触达。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max-preview

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

中文说明

已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

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

Comment on lines +2890 to +2894
expect(setValue).toHaveBeenCalledWith(
expect.anything(),
'model.name',
'qwen-max',
);

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.

[Suggestion] The trailing-flag --default test pins the scope with expect.anything(), while its two sibling tests pin SettingScope.Workspace (--default --project) and SettingScope.User (--default --global) exactly. The resolved scope here is deterministic — for this mock (untrusted workspace, empty user settings) getPersistScopeForModelSelection always falls back to SettingScope.User. — Failure scenario: if a future change moves the bare---default fallback scope (e.g. to Workspace), /model qwen-max --default would persist to the wrong settings file, yet this test still passes because expect.anything() matches any scope — the regression ships uncaught while the explicit --project/--global cases stay pinned. (The leading-flag --default qwen-max tests use a similarly loose expect.any(String) matcher.)

Suggested change
expect(setValue).toHaveBeenCalledWith(
expect.anything(),
'model.name',
'qwen-max',
);
expect(setValue).toHaveBeenCalledWith(
SettingScope.User,
'model.name',
'qwen-max',
);
中文说明

尾随标志 --default 测试用 expect.anything() 匹配 scope,而其两个兄弟测试分别精确固定了 SettingScope.Workspace--default --project)和 SettingScope.User--default --global)。此处的解析 scope 是确定的——对该 mock(不受信任的工作区、空的 user 设置),getPersistScopeForModelSelection 总是回退到 SettingScope.User。— 失败场景:若未来改动把裸 --default 的回退 scope 改到(如 Workspace),/model qwen-max --default 会持久化到错误的设置文件,但本测试仍会通过,因为 expect.anything() 匹配任意 scope——回归会未被捕获就上线,而显式 --project/--global 用例仍是固定的。(前导标志 --default qwen-max 测试也用了类似松散的 expect.any(String) 匹配器。)

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

};
}

if (persistDefault && context.executionMode !== 'interactive') {

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.

[Suggestion] No test exercises the non-interactive SUCCESS path /model --default <model-id> — only the bare---default rejection is covered. This guard sits AFTER the modelName handling block, which is what lets headless /model --default qwen-max persist and succeed. — Failure scenario: a "fail-fast" refactor that hoists this guard above the modelName block stays green under the entire suite, after which every valid headless /model --default <id> invocation is wrongly rejected with "--default requires a model ID in non-interactive mode" even though an ID was supplied, and the default is never persisted.

it('should persist the main model for /model --default <id> in non-interactive mode', async () => {
  const ctx = setupContext();
  ctx.executionMode = 'non_interactive';
  const result = await modelCommand.action!(ctx, '--default qwen-max');
  expect(ctx.services.config.switchModel).toHaveBeenCalled();
  expect(setValue).toHaveBeenCalledWith(SettingScope.User, 'model.name', 'qwen-max');
  expect(result).toMatchObject({
    type: 'message',
    content: expect.stringContaining('(default)'),
  });
});
中文说明

没有测试覆盖非交互模式下的成功路径 /model --default <model-id>——只覆盖了裸 --default 的拒绝。这个守卫位于 modelName 处理块之后,正是它让无头 /model --default qwen-max 能够持久化并成功。— 失败场景:把该守卫提到 modelName 块之上的"快速失败"重构会让整个测试套件保持绿色,之后所有合法的无头 /model --default <id> 调用都会被错误拒绝(报 "--default requires a model ID in non-interactive mode"),即便已经提供了 model id,默认值也永远不会被持久化。

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

Comment on lines 1140 to 1143
actions.openModelDialog({
persistScope: result.persistScope,
persistDefault: result.persistDefault,
});

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.

[Suggestion] The persistDefault hand-off to openModelDialog is not pinned by any test. The 'dialog: model' test (slashCommandProcessor.test.ts) asserts only toHaveBeenCalled(), unlike the voice/vision/image/compaction cases which use toHaveBeenCalledWith. — Failure scenario: the chain is tested at both ends (the command returns persistDefault: true; ModelDialog persists when the prop is true) but not across this middle. Deleting the persistDefault: result.persistDefault line stays green end-to-end: bare /model --default would then open the picker with the prop defaulted to false, so a user choosing a model from the "default" picker flow gets a session-only switch with nothing persisted — the exact regression this flag exists to prevent, shipping silently.

// in slashCommandProcessor.test.ts, the 'dialog: model' case:
mockCommand.action.mockResolvedValue({
  type: 'dialog',
  dialog: 'model',
  persistDefault: true,
  persistScope: 'user',
});
// ...after processing the command:
expect(mockOpenModelDialog).toHaveBeenCalledWith({
  persistScope: 'user',
  persistDefault: true,
});
中文说明

传给 openModelDialogpersistDefault 没有任何测试固定。'dialog: model' 测试(slashCommandProcessor.test.ts)只断言了 toHaveBeenCalled(),而 voice/vision/image/compaction 用例都用了 toHaveBeenCalledWith。— 失败场景:链路两端都有测试(命令返回 persistDefault: trueModelDialog 在 prop 为 true 时持久化),但中间这一环没有。删掉 persistDefault: result.persistDefault 这一行,端到端仍会是绿色:裸 /model --default 会打开 prop 默认为 false 的选择器,于是从"默认"选择器流程选模型的用户只会得到一次会话级切换、什么都不持久化——正是这个标志要防止的回归,却会静默上线。

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

},
argumentHint:
'[--fast|--voice|--vision|--compaction|--image] [--project|--global] [<model-id>] | <model-id> <prompt>',
'[--default] [--fast|--voice|--vision|--compaction|--image] [--project|--global] [<model-id>] | <model-id> <prompt>',

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.

[Suggestion] This argumentHint (and the description getter above it) still present --project/--global as standalone options, but the new guard at modelCommand.ts:1072 (scopeOverride && !persistDefault && !inlinePrompt) rejects /model --project <id> / /model --global <id> for the MAIN model unless --default is also present. — Failure scenario: a user reads the /model help (via /help, tab-completion, or this hint) and runs /model --project qwen-max to persist to project scope exactly as the text says; they instead get "Use --default with --project or --global when persisting the main model." The clauses stay accurate for sub-model commands (e.g. /model --voice --project <id>), which makes the mismatch subtle — the text is only wrong for the main model, the most common case. Notably this diff already updated the bare-/model non-interactive message to drop these forms, but left the description getter, this hint, and the --project/--global tab-completion descriptions stale.

Suggested change
'[--default] [--fast|--voice|--vision|--compaction|--image] [--project|--global] [<model-id>] | <model-id> <prompt>',
'[--default [--project|--global]] [--fast|--voice|--vision|--compaction|--image [--project|--global]] [<model-id>] | <model-id> <prompt>',
中文说明

这个 argumentHint(以及上方的 description getter)仍把 --project/--global 呈现为独立选项,但 modelCommand.ts:1072 的新守卫(scopeOverride && !persistDefault && !inlinePrompt)会在没有 --default 时拒绝主模型的 /model --project <id> / /model --global <id>。— 失败场景:用户阅读 /model 帮助(通过 /help、tab 补全或这个提示),按文案字面意思运行 /model --project qwen-max 想持久化到项目作用域,却得到 "Use --default with --project or --global when persisting the main model."。这些子句对子模型命令仍然准确(如 /model --voice --project <id>),使得不一致很隐蔽——文案只在主模型(最常见的情况)上是错的。值得注意的是,本 diff 已经更新了裸 /model 非交互消息去掉了这些形式,却漏掉了 description getter、这个提示以及 --project/--global 的 tab 补全描述。

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Review feedback addressed on PR #6579

All four inline suggestions from the automated reviewer were verified against the code and implemented.

Findings

1. Loose scope matcher in --default tests (rc:3697932021) — Fixed

The trailing-flag qwen-max --default test used expect.anything() for the scope parameter, and the leading-flag --default qwen-max / --default gpt-4(openai) tests used expect.any(String). The resolved scope is deterministic (SettingScope.User) for these mocks (untrusted or empty-modelProviders workspace). Tightened all six assertions to pin SettingScope.User exactly, matching the sibling --default --project / --default --global tests.

Files: packages/cli/src/ui/commands/modelCommand.test.ts

2. Missing non-interactive /model --default <id> success test (rc:3697932027) — Fixed

No test exercised the non-interactive SUCCESS path where /model --default qwen-max persists and succeeds. Added a test that sets executionMode: 'non_interactive', runs --default qwen-max, and asserts switchModel was called, model.name was persisted to SettingScope.User, and the result contains (default). This pins the guard ordering so a "fail-fast" refactor hoisting the bare---default rejection above the modelName block would be caught.

Files: packages/cli/src/ui/commands/modelCommand.test.ts

3. Unpinned persistDefault hand-off to openModelDialog (rc:3697932028) — Fixed

The 'dialog: model' test in slashCommandProcessor only asserted toHaveBeenCalled(), unlike the voice/vision/image cases which use toHaveBeenCalledWith. Added a new test that returns { persistDefault: true, persistScope: 'user' } from the command action and asserts openModelDialog was called with exactly { persistScope: 'user', persistDefault: true }. Deleting the persistDefault: result.persistDefault line in the processor now fails this test.

Files: packages/cli/src/ui/hooks/slashCommandProcessor.test.ts

4. Stale argumentHint and tab-completion descriptions (rc:3697932031) — Fixed

The argumentHint presented --project/--global as standalone options ([--default] [--fast|...] [--project|--global]), but the guard at modelCommand.ts:1072 rejects /model --project <id> for the main model without --default. Updated the hint to [--default [--project|--global]] [--fast|--voice|--vision|--compaction|--image [--project|--global]] [<model-id>] | <model-id> <prompt>, and updated the --project/--global tab-completion descriptions to note the --default requirement for the main model. Added corresponding i18n keys for all six locale files (en, zh, zh-TW, fr, ca, ja).

Files: packages/cli/src/ui/commands/modelCommand.ts, packages/cli/src/i18n/locales/{en,zh,zh-TW,fr,ca,ja}.js

Verification

  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • vitest run src/ui/commands/modelCommand.test.ts (packages/cli) — 98 passed
  • vitest run src/ui/hooks/slashCommandProcessor.test.ts (packages/cli) — 90 passed
中文说明

PR #6579 审查反馈处理

自动审查器的四条行内建议均已对照代码验证并实现。

各项发现

1. --default 测试中松散的 scope 匹配器 (rc:3697932021) — 已修复

尾随标志 qwen-max --default 测试对 scope 参数使用了 expect.anything(),前导标志 --default qwen-max / --default gpt-4(openai) 测试使用了 expect.any(String)。对这些 mock(不受信任的工作区或空的 modelProviders),解析出的 scope 是确定的(SettingScope.User)。将全部六处断言收紧为精确匹配 SettingScope.User,与兄弟测试 --default --project / --default --global 保持一致。

文件: packages/cli/src/ui/commands/modelCommand.test.ts

2. 缺少非交互模式 /model --default <id> 成功路径测试 (rc:3697932027) — 已修复

没有测试覆盖非交互模式下 /model --default qwen-max 持久化并成功的路径。新增测试设置 executionMode: 'non_interactive',运行 --default qwen-max,断言 switchModel 被调用、model.name 被持久化到 SettingScope.User、结果包含 (default)。这固定了守卫顺序,使得将裸 --default 拒绝提升到 modelName 块之上的"快速失败"重构会被捕获。

文件: packages/cli/src/ui/commands/modelCommand.test.ts

3. 未固定的 persistDefault 传递给 openModelDialog (rc:3697932028) — 已修复

slashCommandProcessor 中的 'dialog: model' 测试只断言了 toHaveBeenCalled(),而 voice/vision/image 用例都使用了 toHaveBeenCalledWith。新增测试从命令 action 返回 { persistDefault: true, persistScope: 'user' },并断言 openModelDialog 被精确调用为 { persistScope: 'user', persistDefault: true }。删除处理器中的 persistDefault: result.persistDefault 行现在会导致此测试失败。

文件: packages/cli/src/ui/hooks/slashCommandProcessor.test.ts

4. 过时的 argumentHint 和 tab 补全描述 (rc:3697932031) — 已修复

argumentHint--project/--global 呈现为独立选项([--default] [--fast|...] [--project|--global]),但 modelCommand.ts:1072 的守卫会在没有 --default 时拒绝主模型的 /model --project <id>。将提示更新为 [--default [--project|--global]] [--fast|--voice|--vision|--compaction|--image [--project|--global]] [<model-id>] | <model-id> <prompt>,并更新 --project/--global 的 tab 补全描述以注明主模型需要 --default。为全部六个语言文件(en、zh、zh-TW、fr、ca、ja)添加了对应的 i18n 键。

文件: packages/cli/src/ui/commands/modelCommand.tspackages/cli/src/i18n/locales/{en,zh,zh-TW,fr,ca,ja}.js

验证

  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过
  • vitest run src/ui/commands/modelCommand.test.ts(packages/cli)— 98 通过
  • vitest run src/ui/hooks/slashCommandProcessor.test.ts(packages/cli)— 90 通过

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max-preview

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

📊 Takeover milestone — round 10/100, in the current window. Census: 10 pushed fix(es), 1 no-change review(s), 0 timeout(s), 0 rejected attempt(s), 0 other round(s) (crash / model error / gate error / infra), 1 base update(s).

This many rounds deserves a human look. Options: keep going (fine — nothing changes), split or reduce the PR if rounds keep accumulating, or release takeover (remove the autofix/takeover label or comment @qwen-code /takeover stop). Management continues unchanged unless you act.

中文说明

📊 接管里程碑 —— 第 10/100 轮(当前窗口)。统计:推送修复 10 次、审阅无需改动 1 次、超时 0 次、验证拒绝 0 次、其他轮次(崩溃/模型错误/门错误/infra)0 次、base 更新 1 次。

轮次到这个量值得人工看一眼。可选:继续(无需操作);若轮次持续累积,考虑拆分或缩减 PR;或释放接管(移除 autofix/takeover 标签或评论 @qwen-code /takeover stop)。不操作则托管照常继续。

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

中文说明

已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

— qwen3.8-max-preview via Qwen Code /review (v0.21.3)

Comment thread packages/cli/src/i18n/locales/en.js Outdated
'Switch the model for this session (--fast for suggestion model, --voice for voice transcription model, [model-id] to switch immediately).',
'Switch the model for this session (--fast for suggestion model, --voice for voice transcription model, --vision for the vision bridge model, --project to persist to project settings, --global to persist to user settings, [model-id] to switch immediately, or [model-id] [prompt] to run a one-off prompt on another model; the inline prompt is sent verbatim without @file expansion).':
'Switch the model for this session (--fast for suggestion model, --voice for voice transcription model, --vision for the vision bridge model, --project to persist to project settings, --global to persist to user settings, [model-id] to switch immediately, or [model-id] [prompt] to run a one-off prompt on another model; the inline prompt is sent verbatim without @file expansion).',
'Switch the model for this session (--fast for suggestion model, --voice for voice transcription model, --vision for the vision bridge model, --image for the image generation model, --project to persist to project settings, --global to persist to user settings, [model-id] to switch immediately, or [model-id] [prompt] to run a one-off prompt on another model; the inline prompt is sent verbatim without @file expansion).':

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.

[Suggestion] Two of the /model description-key variants added in this diff are never referenced by any code or test. The command description is now a single static t() call (modelCommand.ts:385) using only the full --default + --compaction + --image variant, so the --image-without---default variant (this line) and the --default-without---compaction variant (en.js:1497) are dead keys, as are their newly added zh.js / zh-TW.js translations. — Concrete cost: six added key/value entries (en + zh + zh-TW × 2 variants) that can never be displayed, imply a conditional description assembly that no longer exists, must be kept in sync on every future wording change, and mislead translators into maintaining unreachable strings.

Fix: drop the two unreferenced variants (en.js:1495-1498 and their zh.js:1435-1438 / zh-TW.js:1321-1324 translations); keep only the full --default + --compaction + --image key that modelCommand.ts:385 actually passes to t().

中文说明

本 diff 新增的 /model 描述键中有两个变体没有被任何代码或测试引用。命令描述现在是一处静态的 t() 调用(modelCommand.ts:385),只使用包含 --default + --compaction + --image 的完整变体,因此不含 --default--image 变体(本行)和不含 --compaction--default 变体(en.js:1497)都是死键,它们在 zh.js / zh-TW.js 中新增的翻译同样是死键。——具体代价:新增的 6 条 key/value(en + zh + zh-TW × 2 个变体)永远不会被显示,暗示了一种已不存在的条件式描述拼装,今后每次改文案都要同步维护,还会误导翻译者去维护不可达的字符串。

建议:删除这两个未被引用的变体(en.js:1495-1498 及其 zh.js:1435-1438 / zh-TW.js:1321-1324 翻译),只保留 modelCommand.ts:385 实际传入 t() 的完整变体。

— qwen3.8-max-preview via Qwen Code /review (v0.21.3)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 237b2ed. I removed both unreferenced description variants from en, zh, and zh-TW, leaving only the full --default + --compaction + --image key used by modelCommand. npm run check-i18n passes.

Comment on lines +231 to +237
if (persistDefault) {
persistModelSelection(
settings,
effectiveModelId,
effectiveBaseUrl,
persistScope,
);

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.

[Suggestion] Three user-facing docs pages still describe the pre-PR behavior — that ordinary /model picker selections persist — which this diff changes to session-only. After this change, docs/users/configuration/auth.md:300 says of the /model picker "Your selection is persisted across sessions"; docs/users/configuration/model-providers.md:677 says "/model and /auth persist model.name ... and security.auth.selectedType"; docs/users/configuration/settings.md:164 says model.baseUrl is "Persisted automatically by the model picker". All three are now true only for the --default path (this gate). — Failure scenario: a user follows the docs to switch models via the ordinary picker, expecting it to persist; the selection is now session-only, so they silently revert to the old model on next launch with no doc-provided hint that --default exists.

Fix: update the three pages to state that plain /model <id> and ordinary picker selections are session-scoped, and that /model --default <id> (or the picker opened via /model --default) persists model.name / model.baseUrl / security.auth.selectedType.

中文说明

有三个面向用户的文档页面仍在描述本 PR 之前的行为——即普通的 /model 选择器会持久化——而本 diff 已将其改为仅当前会话生效。改动后,docs/users/configuration/auth.md:300 仍说 /model 选择器“您的选择会跨会话持久化”;model-providers.md:677 仍说“/model/auth 会持久化 model.name……和 security.auth.selectedType”;settings.md:164 仍说 model.baseUrl “由模型选择器自动持久化”。这三处现在都只对 --default 路径成立(即此处的守卫)。——失败场景:用户按文档用普通选择器切换模型并期望持久化,但现在只对当前会话生效,下次启动时会静默恢复到旧模型,而文档没有任何提示说明已存在 --default

建议:更新这三个页面,说明普通 /model <id> 和普通选择器选择仅当前会话生效,而 /model --default <id>(或通过 /model --default 打开的选择器)才会持久化 model.name / model.baseUrl / security.auth.selectedType

— qwen3.8-max-preview via Qwen Code /review (v0.21.3)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 237b2ed. The authentication, model-provider, and settings docs now distinguish session-scoped plain /model selections from persistent /model --default selections, including model.name, model.baseUrl, and the selected auth type. Formatting and typecheck pass.

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Autofix: no action this round

No actionable feedback was found for this round.

  • Reviews: none newer than the last evaluation.
  • Inline comments: none.
  • Issue-level comments: none.
  • Failed checks: none.
  • Still-red checks: none.
  • Base conflict: none (--conflict false).

The only feedback present is in the Deferred non-Critical feedback section. Critical-only mode is active after 10 change-producing rounds, so those non-Critical items are an audit record for human follow-up — not work for this round. Per the workflow rules, no code was modified, no threads were resolved, and no comment replies were written for them.

No changes were made to the working tree.

中文说明

Autofix:本轮无需处理

本轮未发现可执行的反馈。

  • Reviews(评审): 没有比上次评估更新的评审。
  • Inline comments(行内评论): 无。
  • Issue-level comments(Issue 级评论): 无。
  • Failed checks(失败的检查): 无。
  • Still-red checks(持续失败的检查): 无。
  • Base 冲突: 无(--conflict false)。

唯一存在的反馈位于 Deferred non-Critical feedback(已延后的非 Critical 反馈) 区域。在完成 10 个产生改动的轮次后已进入仅处理 Critical 的模式,因此这些非 Critical 条目属于供人工跟进的审计记录,而非本轮的工作内容。按照工作流规则,未对它们修改任何代码、未解决任何线程,也未撰写任何评论回复。

工作树未做任何改动。

Deferred non-Critical feedback

Critical-only mode is active after 10 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (@qwen-code /retry starts a fresh counting window.)

中文说明

完成 10 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 @qwen-code /retry 可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max-preview

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

No action required this round (PR #6579)

The only feedback newer than the last evaluation consists of two inline comments that are this workflow's own previous-round "Addressed in 237b2ed" status replies, echoed back by the feedback filter. They contain no new requests or defects. Both underlying findings were re-verified against the current head (237b2ed79) and remain fixed in the code:

  1. Stale docs finding (thread at ModelDialog.tsx:237, rc:3705269831 replying to root rc:3698300931): docs/users/configuration/auth.md, model-providers.md, and settings.md now correctly distinguish session-scoped plain /model selections from persistent /model --default selections, including model.name, model.baseUrl (for disambiguation), and the selected provider's security.auth.selectedType. Verified in the committed diff at HEAD.
  2. Unreferenced locale-key finding (thread at en.js, rc:3705269849 replying to root rc:3698300929): commit 237b2ed79 removed exactly the two unreferenced /model description-key variants from en.js, zh.js, and zh-TW.js. The only remaining description variant added by this PR is the full --default + --compaction + --image key, which is exactly the one referenced by modelCommand.ts (description getter, line 385). Verified at HEAD.

Dispositions: both findings Resolved in code (both rc handles are listed in resolved-comments.txt so their threads can be closed). Nothing was declined, deferred, or escalated this round.

No checks are failing, the branch has no conflict with main, and no new feedback requires a code change, so no edit or commit was made this round. The PR head remains 237b2ed79, which already passed the repository's deterministic verification after the previous push.

中文说明

本轮无需任何改动(PR #6579

上次评估之后新增的反馈仅有两条行内评论,它们是本工作流上一轮自己发布的「Addressed in 237b2ed(已在 237b2ed 中处理)」状态回复,被反馈过滤器回传。这两条评论不包含任何新的要求或缺陷。它们对应的两个原始发现已在当前 head(237b2ed79)上重新核验,确认修复仍然成立:

  1. 文档过期发现ModelDialog.tsx:237 处的线程,rc:3705269831 回复根评论 rc:3698300931):docs/users/configuration/auth.mdmodel-providers.mdsettings.md 现已正确区分仅当前会话有效的普通 /model 选择与持久化的 /model --default 选择,包括 model.name、用于消歧的 model.baseUrl 以及所选提供者的 security.auth.selectedType。已在 HEAD 的提交差异中核验。
  2. 未被引用的语言包键发现en.js 处的线程,rc:3705269849 回复根评论 rc:3698300929):提交 237b2ed79 恰好从 en.jszh.jszh-TW.js 中移除了那两个未被引用的 /model 描述键变体。本 PR 新增的描述键中唯一保留的是完整的 --default + --compaction + --image 键,正是 modelCommand.ts(description getter,第 385 行)所引用的那一个。已在 HEAD 核验。

处置结论:两个发现均已在代码中解决(两个 rc 句柄已列入 resolved-comments.txt,以便关闭对应线程)。本轮没有拒绝、延后或升级任何条目。

当前没有失败的检查,分支与 main 无冲突,也没有需要改动代码的新反馈,因此本轮未做任何编辑、未产生任何提交。PR head 仍为 237b2ed79,该提交在上次推送后已通过仓库的确定性验证。

Deferred non-Critical feedback

Critical-only mode is active after 10 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (@qwen-code /retry starts a fresh counting window.)

中文说明

完成 10 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 @qwen-code /retry 可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

[Critical] parseDefaultFlag misses a trailing --default after a whitespace run — silent persistence loss (packages/cli/src/ui/commands/modelCommand.ts:76-88). The new detection code splits on single whitespace (args.trim().split(/\s/)) and checks token index 0/1; a whitespace RUN (two spaces or space+tab) between the model id and the flag yields ['qwen-max', '', '--default'], so leadingTokens[1] === '' and persistDefault stays false. The undetected --default text then survives as the inline prompt, passes every guard, and the action returns submit_prompt with content '--default' — the literal flag text is sent to the model as a one-shot prompt, nothing is persisted, and even the session switch does not happen. Probe-verified at this commit: /model qwen-max --default (double space) and /model qwen-max \t--default (space+tab) both reproduce; the single-space forms work. Failure scenario: a user types or pastes /model qwen-max --default with two spaces (common when pasting from wrapped docs) — instead of persisting the default, the CLI sends the literal text '--default' to the model and reports nothing wrong; the next session silently starts on the old model. This is the exact silent-intent-loss class this PR's own inline-prompt guard was added to prevent. Fix (probe-verified): const leadingTokens = args.trim().split(/\s+/); — preserves the 'qwen-max what does --default do' prompt-body case; add a trailing-flag test with a double space. Not posted inline because an existing comment already occupies the same anchor line (a test-coverage Suggestion that presumes this branch works — a different claim, so this Critical is reported here instead of being dropped). (Note: the related but distinct trailing --default after an AUXILIARY flag, e.g. /model --fast <id> --default, is a second gap at the same function — it bypasses the mutual-exclusion rejection and is folded into the aux model name, producing a misleading 'model not configured' error; it shares the detection root cause.)

中文说明

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

[Critical] parseDefaultFlag misses a trailing --default after a whitespace run — silent persistence loss (packages/cli/src/ui/commands/modelCommand.ts:76-88). The new detection code splits on single whitespace (args.trim().split(/\s/)) and checks token index 0/1; a whitespace RUN (two spaces or space+tab) between the model id and the flag yields ['qwen-max', '', '--default'], so leadingTokens[1] === '' and persistDefault stays false. The undetected --default text then survives as the inline prompt, passes every guard, and the action returns submit_prompt with content '--default' — the literal flag text is sent to the model as a one-shot prompt, nothing is persisted, and even the session switch does not happen. Probe-verified at this commit: /model qwen-max --default (double space) and /model qwen-max \t--default (space+tab) both reproduce; the single-space forms work. Failure scenario: a user types or pastes /model qwen-max --default with two spaces (common when pasting from wrapped docs) — instead of persisting the default, the CLI sends the literal text '--default' to the model and reports nothing wrong; the next session silently starts on the old model. This is the exact silent-intent-loss class this PR's own inline-prompt guard was added to prevent. Fix (probe-verified): const leadingTokens = args.trim().split(/\s+/); — preserves the 'qwen-max what does --default do' prompt-body case; add a trailing-flag test with a double space. Not posted inline because an existing comment already occupies the same anchor line (a test-coverage Suggestion that presumes this branch works — a different claim, so this Critical is reported here instead of being dropped). (Note: the related but distinct trailing --default after an AUXILIARY flag, e.g. /model --fast <id> --default, is a second gap at the same function — it bypasses the mutual-exclusion rejection and is folded into the aux model name, producing a misleading 'model not configured' error; it shares the detection root cause.)

— qwen3.8-max via Qwen Code /review (v0.21.3)

```

The picker will show all models from your `modelProviders` configuration, grouped by their protocol (e.g. `openai`, `anthropic`, `gemini`). Your selection is persisted across sessions.
The picker will show all models from your `modelProviders` configuration, grouped by their protocol (e.g. `openai`, `anthropic`, `gemini`). A selection from the ordinary `/model` picker applies only to the current session. To persist a selection for future sessions, use `/model --default <model-id>` or open the picker with `/model --default` and select a model.

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.

[Suggestion] docs/users/features/commands.md was not updated for the new persistence semantics — Failure scenario: a user upgrading from the pre-PR behavior (plain /model <id> persisted the model) opens the command reference to find how to persist a default model and sees the /model, /model --fast, /model --voice, /model --vision rows with no --default anywhere on the page — concluding the persist capability was removed rather than moved behind a flag.

That page contains zero --default mentions at this commit (grep-verified), and no note that plain /model switches are now session-scoped, while this diff updated three other docs pages (auth.md — this hunk, model-providers.md, settings.md) for exactly this change. The maintainer review (item 6) asked for a docs line because this is an intentional breaking change to behavior people rely on; the file is not touched by any commit in this PR.

Suggested fix: add a /model --default row next to the existing /model row (e.g. | → --default | Persist the main model for future sessions | /model --default <model-id>, /model --default --project <model-id> |) and note in the /model row that plain switches are session-scoped.

中文说明

[Suggestion] docs/users/features/commands.md 没有针对新的持久化语义更新 —— 失败场景:从旧行为(普通 /model <id> 会持久化模型)升级的用户打开命令参考,想找到持久化默认模型的方法,却只看到 /model/model --fast/model --voice/model --vision 行,整页没有任何 --default —— 会以为持久化能力被移除了,而不是挪到了标志后面。

该页面在当前提交中没有出现任何 --default(已用 grep 验证),也没有说明普通 /model 切换现在是会话级的;而本 diff 为同一语义变更更新了另外三个文档页面(auth.md —— 即本行、model-providers.md、settings.md)。维护者评审(第 6 项)要求补一行文档,因为这是用户依赖的行为上有意的破坏性变更;该文件不在本 PR 的任何提交中。

建议修复:在现有 /model 行旁增加 /model --default 行(例如 | → --default | 为后续会话持久化主模型 | /model --default <model-id>、/model --default --project <model-id> |),并在 /model 行注明普通切换仅作用于当前会话。

— qwen3.8-max via Qwen Code /review (v0.21.3)

firstSpace === -1 ? trimmedArgs : trimmedArgs.slice(0, firstSpace);
const inlinePrompt =
firstSpace === -1 ? '' : trimmedArgs.slice(firstSpace + 1).trim();
if (scopeOverride && !persistDefault && !inlinePrompt) {

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.

[Suggestion] No test covers this guard's model-bearing form — /model --project qwen-max / /model --global qwen-max (scope flag without --default, WITH a model id) in a trusted workspace — Concrete cost: the gate is tested only with '--project'/'--global' alone (no model) and via the inline-prompt path; the only '--project qwen-max' test runs untrusted and short-circuits at the earlier trust check, never reaching this gate (the rejection message string occurs nowhere in the test file). Mutation probe at this commit: narrowing the gate to the no-model path (&& !modelName) ships green (98/98 pass) — trusted /model --project qwen-max would then silently session-switch instead of rejecting, dropping the requested persistence without any error: precisely the silent-intent-loss this PR exists to prevent.

Suggested fix (in modelCommand.test.ts, trusted context):

it('should reject --project with a model id when --default is absent', async () => {
  const ctx = setupContext();
  const result = await modelCommand.action!(ctx, '--project qwen-max');
  expect(result).toMatchObject({
    type: 'message',
    messageType: 'error',
    content: expect.stringContaining(
      'Use --default with --project or --global when persisting the main model.',
    ),
  });
  expect(setValue).not.toHaveBeenCalled();
});

(and a mirrored --global qwen-max case)

中文说明

[Suggestion] 该守卫的“带模型 ID”形式没有测试覆盖 —— 即受信工作区下的 /model --project qwen-max / /model --global qwen-max(有作用域标志、有模型 ID、没有 --default)—— 具体代价:目前只有 '--project'/'--global'(不带模型)以及内联 prompt 路径测试了这个守卫;唯一的 '--project qwen-max' 测试在非受信环境下运行,会在更早的信任检查处短路,根本到不了这个守卫(拒绝消息字符串在整个测试文件中零出现)。在本提交上做了变异探针:把守卫收窄到无模型路径(&& !modelName)后测试套件仍然全绿(98/98 通过)—— 届时受信的 /model --project qwen-max 会静默变成仅会话级切换而不再报错,用户请求的持久化被无声丢弃:这正是本 PR 要消灭的“静默丢失意图”。

建议修复:在 modelCommand.test.ts 受信上下文中补充上述测试(以及对称的 --global qwen-max 用例),断言返回拒绝消息且 setValue 未被调用。

— qwen3.8-max via Qwen Code /review (v0.21.3)

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Review feedback round — PR #6579

All three findings this round were addressed and resolved in code (commit 44be9d799). No conflicts (--conflict false), no merge performed.

1. [Critical] parseDefaultFlag misses a trailing --default after a whitespace run — silent persistence loss

Decision: Fixed. Verified against the exact code: args.trim().split(/\s/) produced an empty token at index 1 for /model qwen-max --default (double space or space+tab), so persistDefault stayed false and the literal --default text fell through as an inline prompt — the silent-intent-loss class this PR exists to prevent.

Changes in packages/cli/src/ui/commands/modelCommand.ts:

  • Detection now splits on /\s+/ so whitespace runs between the model id and the flag no longer hide the flag.
  • The second gap named in the finding is fixed in the same function: a trailing --default after an auxiliary flag (/model --fast <id> --default) is now detected (token position 2 when token 0 is an auxiliary flag) and routed into the existing mutual-exclusion rejection instead of being folded into the aux model name and surfacing a misleading "model not configured" error. Detection stays position-limited so an inline prompt body that merely mentions --default is still preserved verbatim.
  • The auxiliary-flag list is now a shared AUX_MODEL_FLAGS constant used by both the detection and the mutual-exclusion guard (the duplicated inline array was removed).

Tests added (modelCommand.test.ts): trailing-flag form after a whitespace run persists (qwen-max --default), and --fast qwen-max --default is rejected with the "--default only applies to the main model" message. Both were mutation-probed: reverting the detection makes them fail.

2. [rc:3706763172] [Suggestion] docs/users/features/commands.md not updated for the new persistence semantics

Decision: Fixed. Added a → --default row next to the /model rows (following the table's existing sub-row convention) with /model --default <model-id> and /model --default --project <model-id> examples, and the /model row now notes that plain switches are session-scoped and not persisted — consistent with the wording already applied to auth.md, model-providers.md, and settings.md in this PR.

3. [rc:3706763182] [Suggestion] No test covers the scope-flag gate's model-bearing form

Decision: Fixed. Added the two suggested tests in a trusted context: /model --project qwen-max and /model --global qwen-max (scope flag + model id, no --default) both return the "Use --default with --project or --global when persisting the main model." error and never call setValue. Mutation-probed: narrowing the gate to the no-model path (&& !modelName) makes both tests fail, matching the reviewer's probe.

Verification

  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • npx vitest run src/ui/commands/modelCommand.test.ts (packages/cli, touched) — 102 passed (98 existing + 4 new)
  • Mutation probes (2 runs, reverted detection / narrowed gate) — the 4 new tests fail against the mutated code and pass with the fix, then the fix was restored
  • npx prettier --check on all three changed files — passed
  • Integration tests after npm run bundle — not run: the touched behavior (slash-command argument parsing) is exercised directly by the unit tests calling modelCommand.action, not only through the bundled CLI or integration harness
中文说明

评审反馈轮次 — PR #6579

本轮三条反馈全部已在代码中处理并解决(提交 44be9d799)。无冲突(--conflict false),未执行合并。

1. [Critical] parseDefaultFlag 漏掉连续空白后的尾部 --default —— 静默丢失持久化

决定:已修复。 已对照当前代码核实:args.trim().split(/\s/)/model qwen-max --default(双空格或空格+制表符)时会在索引 1 处产生空 token,导致 persistDefault 保持 false,字面 --default 文本会作为内联 prompt 漏下去 —— 正是本 PR 要杜绝的"静默丢失意图"。

packages/cli/src/ui/commands/modelCommand.ts 的改动:

  • 检测改为按 /\s+/ 切分,模型 ID 与标志之间的连续空白不再遮蔽该标志。
  • 反馈中指出的第二个缺口也在同一函数内修复:辅助标志后的尾部 --default/model --fast <id> --default)现在会被检测到(token 0 为辅助标志时检查 token 2),并走入既有的互斥拒绝逻辑,而不是被并进辅助模型名、报出误导性的 "model not configured" 错误。检测仍然限定在标志位置,因此内联 prompt 正文中只是提到 --default 的内容依旧原样保留。
  • 辅助标志列表抽为共享常量 AUX_MODEL_FLAGS,检测与互斥守卫共用(移除了重复的内联数组)。

新增测试(modelCommand.test.ts):连续空白后的尾部标志形式可正常持久化(qwen-max --default);--fast qwen-max --default 会被 "--default only applies to the main model" 消息拒绝。两者均做了变异探针验证:回退检测逻辑后测试失败。

2. [rc:3706763172] [Suggestion] docs/users/features/commands.md 未按新的持久化语义更新

决定:已修复。/model 行旁按表格既有的 子行惯例新增 → --default 行,示例为 /model --default <model-id>/model --default --project <model-id>;并在 /model 行注明普通切换仅作用于当前会话、不持久化 —— 与本 PR 中 auth.md、model-providers.md、settings.md 已采用的措辞一致。

3. [rc:3706763182] [Suggestion] 作用域标志守卫的"带模型 ID"形式没有测试覆盖

决定:已修复。 按建议在受信上下文中补充两个测试:/model --project qwen-max/model --global qwen-max(有作用域标志、有模型 ID、无 --default)均返回 "Use --default with --project or --global when persisting the main model." 错误,且从不调用 setValue。已做变异探针:把守卫收窄到无模型路径(&& !modelName)后两个测试均失败,与评审者的探针结论一致。

验证

  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过
  • npx vitest run src/ui/commands/modelCommand.test.ts(packages/cli,本次触及)— 102 通过(98 个既有 + 4 个新增)
  • 变异探针(2 次运行:回退检测逻辑 / 收窄守卫)— 4 个新测试在变异代码上失败、在修复代码上通过,随后已恢复修复
  • npx prettier --check 检查全部三个改动文件 — 通过
  • npm run bundle 后的集成测试 — 未运行:本次触及的行为(斜杠命令参数解析)由直接调用 modelCommand.action 的单元测试完整覆盖,并非只能通过打包后的 CLI 或集成测试框架验证

Deferred non-Critical feedback

Critical-only mode is active after 10 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (@qwen-code /retry starts a fresh counting window.)

中文说明

完成 10 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 @qwen-code /retry 可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R4-6: The PR branch conflicts with main — GitHub reports mergeStateStatus: DIRTY, mergeable: CONFLICTING at review time (checked 2026-08-04 via gh pr view, head 44be9d799). This is blocker (3) of the maintainer verification review (issue comment 5073042997), which required a merge/rebase before landing; it was reported cleared on 2026-08-03 but main has since advanced and the conflict is live again at the reviewed head. The PR cannot be merged as-is — merge current main into the branch (or rebase) and resolve the conflicts. Failure scenario: merging now fails on conflicts; certifying this head without naming the conflict would approve a branch that cannot land.

— qwen3.8-max via Qwen Code /review (v0.21.3)

> Define `modelProviders` in the user-scope `~/.qwen/settings.json` whenever possible and avoid persisting credential overrides in any scope. Keeping the provider catalog in user settings prevents merge/override conflicts between project and user scopes and ensures `/auth` and `/model` updates always write back to a consistent scope.

- `/model` and `/auth` persist `model.name` (where applicable) and `security.auth.selectedType` to the closest writable scope that already defines `modelProviders`; otherwise they fall back to the user scope. This keeps workspace/user files in sync with the active provider catalog.
- Plain `/model <model-id>` commands and selections from the ordinary `/model` picker apply only to the current session. Use `/model --default <model-id>` or select a model from the picker opened by `/model --default` to persist `model.name`, `model.baseUrl` when needed for disambiguation, and the selected provider's `security.auth.selectedType`. `/auth` continues to persist its selected authentication type. Persistent selections use the closest writable scope that already defines `modelProviders`; otherwise they fall back to the user scope.

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.

[Suggestion] This new sentence bundles the persistence effects of both --default forms, but they differ: the argument form /model --default <model-id> persists model.name, clears model.baseUrl (the "selects by id only" tombstone in switchMainModel), and persists security.auth.selectedType only when the argument carries an explicit auth-type suffix. Only the picker path (persistModelSelection in ModelDialog.tsx) persists the real disambiguating model.baseUrl. — Failure scenario: a user with two modelProviders entries sharing the same model id follows this doc, runs /model --default qwen-max expecting the disambiguator to be persisted; instead the previously persisted model.baseUrl is wiped, so a later session can resolve the id to a different provider than intended.

Suggested wording — split the claim by form, e.g.: "Use /model --default <model-id> to persist model.name (it selects by id only and clears any persisted model.baseUrl disambiguator; an explicit auth-type suffix also persists security.auth.selectedType), or select a model from the picker opened by /model --default to additionally persist the disambiguating model.baseUrl and the selected provider's security.auth.selectedType."

中文说明

[建议] 这句新文案把两种 --default 形式的持久化效果写成了一样,但两者并不相同:参数形式 /model --default <model-id> 只持久化 model.name、会清空 model.baseUrlswitchMainModel 中"仅按 id 选择"的墓碑写入),且只有当参数带显式认证类型后缀时才持久化 security.auth.selectedType;只有选择器路径(ModelDialog.tsxpersistModelSelection)才会持久化真正用于消歧的 model.baseUrl。—— 失败场景:用户配置了两个同 model id 的 modelProviders 条目,按文档运行 /model --default qwen-max,以为消歧符会被持久化;实际上之前持久化的 model.baseUrl 被清掉,之后的会话可能把该 id 解析到另一个 provider。

建议措辞——按形式拆分表述,例如:"/model --default <model-id> 持久化 model.name(仅按 id 选择,会清除已持久化的 model.baseUrl 消歧符;带显式认证类型后缀时还会持久化 security.auth.selectedType);或通过 /model --default 打开的选择器选择模型,额外持久化用于消歧的 model.baseUrl 及所选 provider 的 security.auth.selectedType。"

— qwen3.8-max via Qwen Code /review (v0.21.3)

Comment thread docs/users/configuration/settings.md Outdated
| `model.name` | string | The Qwen model to use for conversations. | `undefined` |
| `model.reasoningEffort` | enum | How hard reasoning-capable models think, applied across all providers. Set with the [`/effort`](../features/commands) command (`low`, `medium`, `high`, `xhigh`, `max`). Each provider maps and clamps this to what the active model supports (e.g. Gemini caps at `high`; Anthropic clamps tiers a model lacks). Leave unset to use the model/provider default. | `undefined` |
| `model.baseUrl` | string | Persisted automatically by the model picker to disambiguate when multiple `modelProviders` entries share the same model id. Not intended to be set by hand — use the `/model` picker or a `modelProviders` entry instead; a stale hand-edited value can silently route requests to a different same-id provider. | `undefined` |
| `model.baseUrl` | string | Persisted with a default model selection (`/model --default <model-id>` or the picker opened with `/model --default`) to disambiguate when multiple `modelProviders` entries share the same model id. Plain model switches are session-scoped. Not intended to be set by hand — use the default-model flow or a `modelProviders` entry instead; a stale hand-edited value can silently route requests to a different same-id provider. | `undefined` |

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.

[Suggestion] The new model.baseUrl description claims the /model --default <model-id> argument form persists the disambiguator, but that form deliberately clears it (switchMainModel writes model.baseUrl as '' with the comment "/model --default <id> selects by id only, so clear any baseUrl disambiguator"). Only a selection made through the picker opened with /model --default persists a real model.baseUrl. — Failure scenario: a user with two same-id modelProviders entries who previously persisted provider B via the --default picker runs /model --default qwen-max to pin the default; the disambiguator is silently cleared, and on next launch the resolver picks the first same-id entry — possibly provider A — while this row (whose stated purpose is warning about exactly that hazard) said disambiguation was persisted.

Suggested fix: attribute the baseUrl persistence to the picker flow only, e.g. "Persisted with a default model selection made through the picker opened with /model --default. The /model --default <model-id> argument form selects by id only and clears any persisted disambiguator."

中文说明

[建议] 新增的 model.baseUrl 描述声称 /model --default <model-id> 参数形式会持久化消歧符,但该形式实际上是主动清空它(switchMainModelmodel.baseUrl 写为 '',注释为"/model --default <id> 仅按 id 选择,因此清除之前模型选择器留下的 baseUrl 消歧符")。只有通过 /model --default 打开的选择器做出的选择才会持久化真实的 model.baseUrl。—— 失败场景:用户有两个同 id 的 modelProviders 条目,之前通过 --default 选择器持久化了 provider B;运行 /model --default qwen-max 固定默认模型后,消歧符被静默清除,下次启动时解析器会选择第一个同 id 条目(可能是 provider A),而这一行文档(其声明的目的正是警示该风险)却说消歧已被持久化。

建议修复:把 baseUrl 的持久化只归于选择器流程,例如"通过 /model --default 打开的选择器做出的默认模型选择会持久化它;/model --default <model-id> 参数形式仅按 id 选择,并会清除已持久化的消歧符。"

— qwen3.8-max via Qwen Code /review (v0.21.3)

Comment on lines 469 to +470
.replace(/(?:^|\s)--vision(?:\s|$)/, ' ')
.replace(/(?:^|\s)--default(?:\s|$)/, ' ')

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.

[Suggestion] Tab completion completes model ids for --default + auxiliary-flag combinations that the action path now always rejects. This diff extended the completion-prefix strip with --default (this line), so /model --default --fast qwen3<TAB> offers qwen3-coder-flash (probe-verified: completion("--default --fast qwen3") => ['qwen3-coder-flash'], both flag orders), but accepting yields /model --default --fast qwen3-coder-flash, which the new mutual-exclusion guard ALWAYS rejects with "--default only applies to the main model…". Before this diff every completable flag combination was runnable. — Concrete cost: every accepted completion for these combinations is a guaranteed error; completion now advertises invocations this diff's own guard forbids.

Suggested fix: in the completion model-prefix branch, return no model completions when the partial arg contains both --default and an aux flag (AUX_MODEL_FLAGS).

中文说明

[建议] Tab 补全仍在为"--default + 辅助标志"组合补全模型 id,但 action 路径现在总是拒绝这些组合。本 diff 在补全前缀剥离链中新增了 --default(即本行),因此 /model --default --fast qwen3<TAB> 会给出 qwen3-coder-flash(已用探针验证:两种标志顺序均返回 ['qwen3-coder-flash']);接受补全后得到的 /model --default --fast qwen3-coder-flash 必然被新的互斥守卫以 "--default only applies to the main model…" 拒绝。本 diff 之前,所有可补全的标志组合都是可运行的。—— 具体代价:接受这些组合的补全必然报错;补全在宣传本 diff 自己的守卫所禁止的调用。

建议修复:在补全的模型前缀分支中,当输入同时包含 --default 与任一辅助标志(AUX_MODEL_FLAGS)时不返回模型补全。

— qwen3.8-max via Qwen Code /review (v0.21.3)

// Persistence flags are consumed before the inline prompt path, which
// does not persist the model. Reject them instead of silently dropping
// the requested behavior.
if (persistDefault || scopeOverride) {

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.

[Suggestion] In ACP mode the unchanged executionMode === 'acp' inline-rejection branch (just above) returns BEFORE this new flag-aware rejection, so under ACP the persistence-intent warning is unreachable. Probe-verified: ACP action("--default qwen-max fix the bug") returns the generic advice "run '/model qwen-max' first, then send your prompt"; following it performs a session-only switch (setValue never called) — the persistence intent is silently dropped from the guidance, the exact silent-drop this block was added to prevent. The two-step form IS valid under ACP (probe: /model --default qwen-max persists model.name), so this block's flag-aware advice would be actionable there. — Failure scenario: an ACP user runs /model --default qwen-max fix the bug, follows the advice, believes the default was persisted; the next session silently starts on the old model.

Suggested fix: move this if (persistDefault || scopeOverride) block above the if (context.executionMode === 'acp') block so the flag-aware message wins in every mode.

中文说明

[建议] 在 ACP 模式下,上方未改动的 executionMode === 'acp' 内联拒绝分支会先于本新增的"感知标志"拒绝返回,因此 ACP 下永远到不了这段持久化意图提示。已用探针验证:ACP 中 action("--default qwen-max fix the bug") 返回通用提示 "run '/model qwen-max' first, then send your prompt";照做只会执行会话级切换(setValue 从未被调用)——持久化意图被提示文案静默丢弃,而这正是本代码块要防止的问题。两步形式在 ACP 下是有效的(探针:/model --default qwen-max 会持久化 model.name),所以本块的标志感知提示在 ACP 下本可执行。—— 失败场景:ACP 用户运行 /model --default qwen-max fix the bug,按提示操作后以为默认模型已持久化;下一个会话却静默地仍以旧模型启动。

建议修复:把本 if (persistDefault || scopeOverride) 块移到 if (context.executionMode === 'acp') 块之前,使标志感知提示在所有模式下优先生效。

— qwen3.8-max via Qwen Code /review (v0.21.3)

Comment on lines +1127 to +1128
const flag = persistDefault
? '--default'

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.

[Suggestion] When only a scope flag (no --default) is combined with an inline prompt, this ternary yields '--default --project'/'--default --global', so the error reads "Cannot combine --default --project with an inline prompt" even though the user never typed --default (probe-verified: /model --project qwen-max fix the bug produces that message). The same mistake without a prompt hits the earlier guard and gets the accurate "Use --default with --project or --global when persisting the main model." — one mistake, two inconsistent explanations, one of which misquotes the invocation. The pre-diff ternary quoted the user's actual flags; the misquote is introduced by this diff's rewrite. The suggested command in the message is runnable; only the attribution is wrong. (Distinct from the deferred scope-flag-drop thread: that one is the persistDefault=true branch dropping the scope flag; this is --default fabricated when persistDefault is false.)

Suggested fix: when !persistDefault, phrase the message without attributing --default — e.g. reuse the sibling guard's "Use --default with --project or --global when persisting the main model." wording.

中文说明

[建议] 当只有作用域标志(没有 --default)与内联 prompt 组合时,该三元表达式会产出 '--default --project'/'--default --global',于是错误信息显示 "Cannot combine --default --project with an inline prompt"——尽管用户从未输入 --default(已用探针验证:/model --project qwen-max fix the bug 产生该消息)。同样的输入若不带 prompt,会命中更早的守卫并得到准确的 "Use --default with --project or --global when persisting the main model."—— 同一个错误,两种不一致的解释,其中一种还错误引用了用户的调用。diff 之前的三元表达式引用的是用户实际输入的标志;这个错误引用是本 diff 重写引入的。消息中建议的命令本身可运行,只是归因错误。(与已延期的"作用域标志被丢弃"线程不同:那是 persistDefault=true 分支丢掉了作用域标志;这里是 persistDefault 为 false 时凭空多出了 --default。)

建议修复:当 !persistDefault 时,消息措辞不要归因 --default——例如复用同级守卫的 "Use --default with --project or --global when persisting the main model." 文案。

— qwen3.8-max via Qwen Code /review (v0.21.3)

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Autofix review round summary — PR #6579

Feedback triage

This round ran in Critical-only mode (after 10 change-producing rounds). The workflow-provided feedback contained no actionable items:

  • Reviews: none
  • Inline comments: none
  • Issue-level comments: none
  • Failed checks: none
  • Still-red checks: none
  • Deferred non-Critical feedback: section present but empty — no deferred items to audit, no code changes, thread resolutions, or replies made for it (per Critical-only rules)

Conflict resolution (--conflict true)

Merged origin/main (tip 1c1ee23f9) into fix/model-session-default, producing merge commit a90f49909. Main had advanced ~20 commits since the branch's last main merge (5799b90a9), bringing new /model --compaction and /model --image flags, a new /learn command, locale additions, and other changes.

Exactly one file conflicted: docs/users/features/commands.md (the "Tool and Model Management" table). Both sides were understood and combined, not one side taken wholesale:

  • Kept from main: the new /learn row, the new /model --compaction and /model --image rows, and main's wider column alignment.
  • Re-applied from this PR: the /model description note "(session-scoped, not persisted)" and the → --default row ("Persist the main model for future sessions"). These two lines are the PR's entire historical change to this file (verified against the merge base), and both survive in the resolution.
  • Verified with git diff origin/main -- docs/users/features/commands.md: only the PR's two lines differ from main. npx prettier --check reports the file unchanged (formatting clean).

Post-merge semantic checks (auto-merged files):

  • modelCommand.ts: AUX_MODEL_FLAGS now lists all five aux flags (--fast, --voice, --vision, --compaction, --image); the --default mutual-exclusion guard and its error message cover all five; command description, argumentHint, and tab-completion all include main's new flags alongside --default.
  • Locale files (en.js, zh.js, etc.): auto-merge preserved this PR's --default keys next to main's --compaction/--image keys; the merged command-description key exists in every touched locale.
  • docs/users/configuration/{auth,model-providers,settings}.md: this PR's session-scoped persistence wording survived the auto-merge intact.
  • git diff MERGE_HEAD --cached before committing showed the merged tree differs from origin/main by exactly the PR's 21 files — nothing from main was lost and no unrelated changes were introduced. (packages/cli/scripts/generate-notices.js, previously visible in the three-dot diff, is byte-identical to current main and drops out of the PR diff naturally.)

Changed files this round

  • docs/users/features/commands.md — conflict resolved as described above (the only manual change; everything else came from the merge itself)

Verification

Commands actually run on the merged tree, before committing:

  • npm run build — passed (exit 0; fresh packages/core/dist and packages/cli/dist artifacts produced)
  • npm run typecheck — passed (exit 0)
  • npm run lint — passed (exit 0)
  • vitest (packages/cli, focused on PR-touched files: src/ui/commands/modelCommand.test.ts, src/ui/components/ModelDialog.test.tsx, src/ui/hooks/slashCommandProcessor.test.ts) — 3 files, 229 tests passed
  • vitest (packages/cli, i18n consistency: src/i18n/index.test.ts, src/i18n/mustTranslateKeys.test.ts) — passed (exit 0)
  • Integration tests after npm run bundle — not applicable: this round changed only a docs conflict resolution; the /model --default behavior is covered by the unit tests above.
  • npm run generate:settings-schema — not applicable: no settings source changed this round.
中文说明

Autofix 审查轮次总结 — PR #6579

反馈分诊

本轮在仅处理 Critical 模式下运行(此前已完成 10 个产生改动的轮次)。工作流提供的反馈中没有任何可处理条目

  • 审查(Reviews):无
  • 行内评论:无
  • Issue 级评论:无
  • 失败检查:无
  • 持续失败的检查:无
  • 延后的非 Critical 反馈:该小节存在但为空 —— 没有需要核查的延后条目,也未对其做任何代码修改、线程解决或回复(遵循 Critical-only 规则)

冲突解决(--conflict true

已将 origin/main(最新提交 1c1ee23f9)合并进 fix/model-session-default,生成合并提交 a90f49909。自本分支上次合并 main(5799b90a9)以来,main 前进了约 20 个提交,带来了新的 /model --compaction/model --image 标志、新的 /learn 命令、多语言文案补充以及其他改动。

只有一个文件冲突:docs/users/features/commands.md("Tool and Model Management" 表格)。我们理解了双方内容并做了合并,而不是整段取某一边:

  • 保留 main 的内容: 新增的 /learn 行、新增的 /model --compaction/model --image 行,以及 main 更宽的列对齐。
  • 重新应用本 PR 的内容: /model 描述中的 "(session-scoped, not persisted)" 注记,以及 → --default 行("Persist the main model for future sessions")。这两行是本 PR 对该文件的全部历史改动(已对照合并基点核实),均已保留在解决结果中。
  • git diff origin/main -- docs/users/features/commands.md 验证:与 main 相比只多出本 PR 的这两行。npx prettier --check 报告文件无变化(格式干净)。

合并后的语义检查(自动合并的文件):

  • modelCommand.tsAUX_MODEL_FLAGS 现包含全部五个辅助标志(--fast--voice--vision--compaction--image);--default 互斥守卫及其错误信息覆盖全部五个;命令描述、argumentHint 与 Tab 补全都同时包含 main 的新标志与 --default
  • 多语言文件(en.jszh.js 等):自动合并保留了本 PR 的 --default 文案,紧邻 main 的 --compaction/--image 文案;合并后的命令描述 key 存在于每个受影响的 locale 中。
  • docs/users/configuration/{auth,model-providers,settings}.md:本 PR 关于会话级持久化的措辞在自动合并后完整保留。
  • 提交前 git diff MERGE_HEAD --cached 显示:合并后的树与 origin/main 的差异恰好是本 PR 的 21 个文件 —— 没有丢失 main 的任何内容,也没有引入无关改动。(packages/cli/scripts/generate-notices.js 此前出现在三点 diff 中,但它与当前 main 逐字节一致,会自然地从 PR diff 中消失。)

本轮改动的文件

  • docs/users/features/commands.md — 按上述方式解决冲突(唯一的手工改动;其余均来自合并本身)

验证

提交前在合并后的树上实际运行的命令:

  • npm run build — 通过(退出码 0;重新生成了 packages/core/distpackages/cli/dist 产物)
  • npm run typecheck — 通过(退出码 0)
  • npm run lint — 通过(退出码 0)
  • vitest(packages/cli,聚焦 PR 涉及文件:src/ui/commands/modelCommand.test.tssrc/ui/components/ModelDialog.test.tsxsrc/ui/hooks/slashCommandProcessor.test.ts)— 3 个文件,229 个测试通过
  • vitest(packages/cli,i18n 一致性:src/i18n/index.test.tssrc/i18n/mustTranslateKeys.test.ts)— 通过(退出码 0)
  • npm run bundle 后的集成测试 — 不适用:本轮仅改动了一处文档冲突解决;/model --default 行为已由上述单元测试覆盖。
  • npm run generate:settings-schema — 不适用:本轮未改动任何 settings 源。

Deferred non-Critical feedback

Critical-only mode is active after 10 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (@qwen-code /retry starts a fresh counting window.)

中文说明

完成 10 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 @qwen-code /retry 可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: conflicted with main — resolved in this push. · 与 main 有冲突——已在本次推送中解决。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run.

中文说明

🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Autofix review round summary — PR #6579

All findings this round were addressed. Two commits were added: a merge of current origin/main and one fix commit.

Feedback points and decisions

1. R4-6 (Critical, review body) — merge conflict with main

Addressed. The conflict reported at head 44be9d799 was already cleared by the branch's earlier merge commit (a90f49909), but main advanced again afterwards (d6f55a1c9). This round merged current origin/main into the branch (commit c50a11482). Verified clean before merging via git merge-tree (exit 0, no conflicts); the only content brought in was main's scripts/tests/qwen-resolve-workflow.test.js alignment fix.

2. Failed check — Test (ubuntu-latest, Node 22.x)

Addressed — root cause identified and reproduced. The failing step runs npm run test:ci, which includes test:scripts (vitest over scripts/tests/). At the pre-merge head, scripts/tests/qwen-resolve-workflow.test.js still asserted the old hardcoded review-timeout values (timeout-minutes: 300, the hardcoded 240-minute cap) while the review workflow already used the externalized QWEN_REVIEW_JOB_TIMEOUT_MINUTES / QWEN_REVIEW_MAX_TIMEOUT_MINUTES repository variables. The branch's last main merge predated the alignment fix that landed on main as #8486 (d6f55a1c9). Reproduced locally before the merge: 3 failed / 29 tests; after merging origin/main: 29/29 pass.

3. rc:3708031082 — docs: persistence effects differ between the two --default forms

Implemented. Verified against the code: switchMainModel (argument form) persists model.name, clears model.baseUrl, and persists security.auth.selectedType only with an explicit auth-type suffix; only the picker path (persistModelSelection in ModelDialog.tsx) persists the real disambiguating model.baseUrl. The sentence in docs/users/configuration/model-providers.md now splits the claim by form accordingly.

4. rc:3708031087 — docs: model.baseUrl row misattributes persistence to the argument form

Implemented. The model.baseUrl description in docs/users/configuration/settings.md now attributes persistence to the picker opened with /model --default only, and states that the /model --default <model-id> argument form selects by id only and clears any persisted disambiguator.

5. rc:3708031090 — completion advertises always-rejected --default + aux-flag invocations

Implemented. In the completion model-prefix branch, no model completions are returned when the partial argument contains both --default and any auxiliary model flag (AUX_MODEL_FLAGS), matching the action path's mutual-exclusion guard. Added a test covering both flag orders for several aux flags plus a control case (--default q still completes).

6. rc:3708031096 — ACP rejection preempted the persistence-intent warning

Implemented. The persistDefault || scopeOverride rejection now runs before the executionMode === 'acp' rejection, so under ACP the flag-aware message (whose suggested two-step command, e.g. /model --default qwen-max, works and persists in ACP) wins instead of the generic advice that silently dropped the persistence intent. Added a regression test for ACP + --default + inline prompt.

7. rc:3708031102 — error message fabricated --default when only a scope flag was typed

Implemented. When a scope flag without --default is combined with an inline prompt, the rejection now reuses the sibling guard's accurate wording ("Use --default with --project or --global when persisting the main model.") instead of misquoting the invocation as --default --project. The existing pinned test was updated to the corrected message.

Changes

No conflicts were encountered (--conflict false; the merge was performed to clear the Critical R4-6 finding and to pick up the failing-check fix now on main).

Verification

  • git merge-tree HEAD origin/main — clean merge, no conflicts; git merge origin/main — commit c50a11482
  • npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-resolve-workflow.test.js — pre-merge: 3 failed / 26 passed (CI failure reproduced); post-merge: 29 passed
  • npm run test:scripts — all green except 5 pre-existing environmental failures in install-script.test.js (spawnSync zip ENOENT: the zip binary is absent in this container; scripts/ is byte-identical to origin/main, and GitHub ubuntu-latest runners ship zip)
  • npx vitest run src/ui/commands/modelCommand.test.ts src/ui/components/ModelDialog.test.tsx (packages/cli, touched) — 141 passed
  • npx vitest run src/ui/hooks/slashCommandProcessor.test.ts src/ui/hooks/useSlashCompletion.test.ts (packages/cli, touched feature area) — 120 passed
  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • npm run check-i18n — passed
  • Settings schema: no settings source changed; packages/vscode-ide-companion/schemas/settings.schema.json unchanged (verified with git status --porcelain)
中文说明

Autofix 评审轮次总结 — PR #6579

本轮所有反馈均已处理。新增两个提交:一次对当前 origin/main 的合并,以及一个修复提交。

反馈点与决定

1. R4-6(Critical,评审正文)— 与 main 的合并冲突

已处理。 在 head 44be9d799 上报告的冲突此前已被分支上一次的合并提交(a90f49909)清除,但之后 main 又前进了(d6f55a1c9)。本轮将当前 origin/main 合并进分支(提交 c50a11482)。合并前已通过 git merge-tree 验证干净(退出码 0,无冲突);合并带入的唯一内容是 main 上对 scripts/tests/qwen-resolve-workflow.test.js 的对齐修复。

2. 失败检查项 — Test (ubuntu-latest, Node 22.x)

已处理——已定位并复现根因。 失败步骤运行 npm run test:ci,其中包含 test:scripts(对 scripts/tests/ 的 vitest)。在合并前的 head 上,scripts/tests/qwen-resolve-workflow.test.js 仍然断言旧的硬编码评审超时值(timeout-minutes: 300、硬编码的 240 分钟上限),而评审工作流已经改用外部化的 QWEN_REVIEW_JOB_TIMEOUT_MINUTES / QWEN_REVIEW_MAX_TIMEOUT_MINUTES 仓库变量。分支上一次合并 main 早于 main 上落地的对齐修复 #8486d6f55a1c9)。合并前已在本地复现:3 个失败 / 共 29 个测试;合并 origin/main 后:29/29 通过。

3. rc:3708031082 — 文档:两种 --default 形式的持久化效果不同

已实现。 已对照代码核实:switchMainModel(参数形式)持久化 model.name、清空 model.baseUrl,且仅在带显式认证类型后缀时才持久化 security.auth.selectedType;只有选择器路径(ModelDialog.tsx 中的 persistModelSelection)才会持久化真正用于消歧的 model.baseUrldocs/users/configuration/model-providers.md 中的这句话现已按形式拆分表述。

4. rc:3708031087 — 文档:model.baseUrl 行把持久化错误归于参数形式

已实现。 docs/users/configuration/settings.mdmodel.baseUrl 的描述现在只把持久化归于通过 /model --default 打开的选择器,并说明 /model --default <model-id> 参数形式仅按 id 选择、会清除已持久化的消歧符。

5. rc:3708031090 — Tab 补全宣传了总是被拒绝的 --default + 辅助标志调用

已实现。 在补全的模型前缀分支中,当输入同时包含 --default 与任一辅助模型标志(AUX_MODEL_FLAGS)时不再返回模型补全,与 action 路径的互斥守卫保持一致。新增测试覆盖多种辅助标志下的两种标志顺序,以及一个对照用例(--default q 仍可补全)。

6. rc:3708031096 — ACP 拒绝分支抢先于持久化意图提示

已实现。 persistDefault || scopeOverride 拒绝现在先于 executionMode === 'acp' 拒绝执行,因此 ACP 下会优先显示感知标志的提示(其建议的两步命令,例如 /model --default qwen-max,在 ACP 下可用且会持久化),而不是静默丢弃持久化意图的通用提示。新增了 ACP + --default + 内联 prompt 的回归测试。

7. rc:3708031102 — 只有作用域标志时错误信息凭空多出 --default

已实现。 当只有作用域标志(没有 --default)与内联 prompt 组合时,拒绝信息现在复用同级守卫的准确文案("Use --default with --project or --global when persisting the main model."),不再把调用错误引用为 --default --project。原有固定该行为的测试已更新为修正后的消息。

变更

未遇到冲突(--conflict false);本次合并是为了清除 Critical 反馈 R4-6,并获取 main 上针对失败检查项的修复。

验证

  • git merge-tree HEAD origin/main — 干净合并,无冲突;git merge origin/main — 提交 c50a11482
  • npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-resolve-workflow.test.js — 合并前:3 失败 / 26 通过(复现 CI 失败);合并后:29 通过
  • npm run test:scripts — 除 install-script.test.js 中 5 个既有的环境性失败外全部通过(spawnSync zip ENOENT:本容器缺少 zip 二进制;scripts/origin/main 逐字节一致,GitHub ubuntu-latest runner 自带 zip
  • npx vitest run src/ui/commands/modelCommand.test.ts src/ui/components/ModelDialog.test.tsx(packages/cli,触及)— 141 通过
  • npx vitest run src/ui/hooks/slashCommandProcessor.test.ts src/ui/hooks/useSlashCompletion.test.ts(packages/cli,触及的功能区域)— 120 通过
  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过
  • npm run check-i18n — 通过
  • 设置 schema:未改动任何设置源;packages/vscode-ide-companion/schemas/settings.schema.json 无变化(已用 git status --porcelain 验证)

Deferred non-Critical feedback

Critical-only mode is active after 10 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (@qwen-code /retry starts a fresh counting window.)

中文说明

完成 10 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 @qwen-code /retry 可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

⚠️ The branch received new commits while this round ran; they were merged into this push, but this round's verification predates that merge — re-check anything that landed mid-run. · 本轮运行期间分支收到了新的提交;本次推送已将其合并,但本轮验证在合并之前完成——请复查运行期间落地的改动。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

中文说明

已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment on lines +409 to +410
value: '--default',
description: t(

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.

[Suggestion] No test asserts that --default is offered as a flag-completion candidate — all new completion tests use partials longer than any flag value, so flagCompletions filtering always yields [] and nothing reads the list's contents. — Failure scenario: mutation-probe verified at this commit — removing the --default entry from flagCompletions leaves all 104 tests green, while a probe asserting { value: '--default' } in the completion output flips between the mutated and restored trees; a future cleanup can silently drop tab-completion for /model --def<Tab> (the discoverability surface of this PR's headline flag) with no red test.

Suggested fix:

it('offers --default in flag completions', async () => {
  const completions = await modelCommand.completion!(mockContext, '--def');
  expect(completions).toEqual(
    expect.arrayContaining([expect.objectContaining({ value: '--default' })]),
  );
});
中文说明

[建议] 没有任何测试断言 --default 会作为标志补全候选项出现——新增的补全测试所用的前缀都比任何标志值更长,因此 flagCompletions 过滤总是得到 [],没有测试读取该列表的内容。—— 失败场景:已在本提交上用变异探针验证——从 flagCompletions 中移除 --default 条目后,全部 104 个测试仍然通过;而断言补全结果包含 { value: '--default' } 的探针在变异前后会翻转。未来的清理可能在没有任何红色测试的情况下悄悄移除 /model --def<Tab> 的补全(本 PR 核心标志的可发现入口)。

建议修复:新增一个补全测试,断言 completion('--def') 的结果包含 { value: '--default', ... }

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment on lines +458 to +459
// The action path always rejects --default combined with an
// auxiliary model flag, so never offer model completions for it.

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.

[Suggestion] Completion returns model-id candidates for scope-flag combinations the action always rejects — probe-verified at this commit: completion('--project q'), ('--global q'), ('--project --global q') and ('--default --project --global q') all return ['qwen-max'], while the action rejects /model --project qwen-max ("Use --default with --project or --global when persisting the main model.") and any --project --global input ("Cannot use both --project and --global."). The analogous --default+aux class guarded here has a suppression guard and a test; these shapes have neither. — Failure scenario: /model --project qw<Tab> completes to /model --project qwen-max, which always errors on execution — completion steers the user into an input the action provably rejects.

Suggested fix: extend this guard to also return null when both --project and --global are present (unconditionally), or when a single scope flag appears without --default and without an aux flag (keep /model --project --fast <id> completing — it is a valid aux dialog path); add the four partials to the suppression test expecting null.

中文说明

[建议] 补全会为 action 必然拒绝的作用域标志组合返回模型 ID 候选——已在本提交上用探针验证:completion('--project q')('--global q')('--project --global q')('--default --project --global q') 都返回 ['qwen-max'],而 action 会拒绝 /model --project qwen-max("Use --default with --project or --global when persisting the main model.")以及任何 --project --global 输入("Cannot use both --project and --global.")。此处类似的 --default+辅助标志组合已有抑制守卫和测试,而这两种形态两者皆无。—— 失败场景:/model --project qw<Tab> 补全为 /model --project qwen-max,执行时必然报错——补全把用户引向 action 必然拒绝的输入。

建议修复:扩展该守卫——当同时出现 --project--global 时无条件返回 null;或当单个作用域标志在没有 --default 且没有辅助标志时出现时也返回 null(保留 /model --project --fast <id> 的补全——它是合法的辅助对话框路径);并在抑制测试中加入这四个前缀,断言返回 null

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment on lines +1133 to +1137
const flag = hasProject
? '--default --project'
: hasGlobal
? '--default --global'
: '--default';

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.

[Suggestion] The scoped flag substitutions here are untested (mutation-quiet) — no test exercises /model --default --project <id> <prompt> or --default --global <id> <prompt>; all three existing "Cannot combine --default" tests reach only the --default-alone branch. — Failure scenario: mutation-probe verified — replacing this ternary with const flag = '--default'; leaves all 104 tests green; a flattened ternary would tell a user who typed /model --default --project qwen-max explain this to run /model --default qwen-max — which persists the default to user scope instead of the workspace scope they asked for, with no red test.

Suggested fix: add two action tests with args '--default --project qwen-max explain this code' and '--default --global qwen-max explain this code' asserting the full message including the scoped corrective command.

中文说明

[建议] 此处的作用域 flag 替换没有测试覆盖(变异静默)——没有测试执行 /model --default --project <id> <prompt>--default --global <id> <prompt>;现有三个 "Cannot combine --default" 测试都只走到 --default 单独出现的分支。—— 失败场景:已经变异探针验证——把该三元表达式替换为 const flag = '--default'; 后全部 104 个测试仍通过;被压平的三元表达式会告诉输入了 /model --default --project qwen-max explain this 的用户去运行 /model --default qwen-max——这会把默认模型持久化到用户作用域而非其要求的工作区作用域,且没有红色测试。

建议修复:新增两个 action 测试,参数分别为 '--default --project qwen-max explain this code''--default --global qwen-max explain this code',断言完整消息(含带作用域的纠正命令)。

— qwen3.8-max via Qwen Code /review (v0.21.5)

);
});

it('persists the main model for the trailing-flag form qwen-max --default', async () => {

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.

[Suggestion] No test combines trailing --default with a scope flag — /model --project qwen-max --default works today only because parseScopeFlags strips --project before parseDefaultFlag checks token positions, and that ordering dependency is untested. — Failure scenario: mutation-probe verified — swapping the parse order (running parseDefaultFlag first) makes /model --project qwen-max --default reject with "Use --default with --project or --global when persisting the main model." — telling the user to add the flag they already typed — while all 104 tests stay green.

Suggested fix: add a persistence test next to this one for '--project qwen-max --default' (and optionally '--global qwen-max --default') asserting the (this project default) suffix and setValue at SettingScope.Workspace / SettingScope.User.

中文说明

[建议] 没有测试把尾部 --default 与作用域标志组合——/model --project qwen-max --default 目前能工作,仅因为 parseScopeFlags 先剥掉 --projectparseDefaultFlag 才检查 token 位置,而这一顺序依赖没有测试覆盖。—— 失败场景:已经变异探针验证——交换解析顺序(先运行 parseDefaultFlag)会让 /model --project qwen-max --default 报 "Use --default with --project or --global when persisting the main model."——叫用户补上他们已经输入的标志——而全部 104 个测试仍然通过。

建议修复:在此测试旁新增 '--project qwen-max --default'(可选再加 '--global qwen-max --default')的持久化测试,断言 (this project default) 后缀以及 setValueSettingScope.Workspace / SettingScope.User 作用域。

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment on lines +768 to +771
expect(mockOpenModelDialog).toHaveBeenCalledWith({
persistScope: 'user',
persistDefault: true,
});

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.

[Suggestion] This new forwarding is pinned only in the positive direction — the pre-existing bare-dialog test ('should handle "dialog: model" action') asserts only toHaveBeenCalled() with no argument shape, so nothing catches persistDefault becoming unconditionally true here. — Failure scenario: mutation-probe verified — hardcoding persistDefault: true in the processor's case 'model' leaves all 239 tests across the four owning suites green (AppContainer.test.tsx mocks useModelCommand wholesale). Shipped, every plain /model would open the default-picker and any selection would silently write model.name/model.baseUrl/security.auth.selectedType — the exact persist-always regression this PR removes.

Suggested fix: in the pre-existing bare-dialog test assert the call shape — expect(mockOpenModelDialog).toHaveBeenCalledWith({ persistScope: undefined, persistDefault: undefined }) — or add a dedicated negative-case test next to this one.

中文说明

[建议] 这条新转发只被正向断言钉住——既有的裸对话框测试('should handle "dialog: model" action')只断言 toHaveBeenCalled(),不校验参数形状,因此此处 persistDefault 变成无条件为真也不会有测试发现。—— 失败场景:已经变异探针验证——把处理器 case 'model' 中的转发硬编码为 persistDefault: true 后,四个相关测试套件的全部 239 个测试仍通过(AppContainer.test.tsx 整体 mock 了 useModelCommand)。若带着该回归发布,每次普通 /model 都会打开默认模型选择器,任何选择都会静默写入 model.name/model.baseUrl/security.auth.selectedType——正是本 PR 要移除的"总是持久化"回归。

建议修复:在既有的裸对话框测试中把断言改为 expect(mockOpenModelDialog).toHaveBeenCalledWith({ persistScope: undefined, persistDefault: undefined }),或在本测试旁新增一个专门的反向用例。

— qwen3.8-max via Qwen Code /review (v0.21.5)

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No issues found. LGTM! ✅

中文说明

未发现问题。LGTM!✅

— qwen3.7-max via Qwen Code /review (v0.21.4)

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No issues found. LGTM! ✅

中文说明

未发现问题。LGTM!✅

— qwen3.7-max via Qwen Code /review (v0.21.4)

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

No action this round (PR #6579)

All actionable sections of this round's feedback are empty:

  • Reviews: none newer than the last evaluation
  • Inline comments: none newer than the last evaluation
  • Issue-level comments: none newer than the last evaluation
  • Failed checks: none
  • Still-red checks: none

Critical-only mode is active after 10 change-producing rounds. The deferred non-Critical feedback listed in the feedback summary (one review and five inline findings from the automated reviewer) is an audit record only; per the workflow rules, no code changes were made, no threads were resolved, and no comment replies were written for those items. They remain open for human follow-up.

No base-conflict resolution was needed (--conflict false), and no merge of origin/main was performed.

Result: no code changes and no commits this round.

中文说明

本轮无需处理(PR #6579

本轮反馈的所有可执行区域均为空:

  • Reviews: 自上次评估以来没有新的 review
  • Inline comments: 自上次评估以来没有新的行内评论
  • Issue-level comments: 自上次评估以来没有新的 issue 级评论
  • Failed checks:
  • Still-red checks:

在完成 10 个产生改动的轮次后,已进入仅处理 Critical 的模式。反馈摘要中列出的延后非 Critical 反馈(来自自动审查者的一条 review 和五条行内发现)仅作为审计记录;按照工作流规则,未对这些条目进行任何代码修改、未解决任何话题、也未撰写任何评论回复。它们保持开放,留待人工跟进。

无需解决 base 冲突(--conflict false),也未执行 origin/main 的合并。

结果: 本轮无代码改动,无新提交。

Deferred non-Critical feedback

Critical-only mode is active after 10 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (@qwen-code /retry starts a fresh counting window.)

中文说明

完成 10 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 @qwen-code /retry 可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@wenshao wenshao closed this Aug 5, 2026
@QwenLM QwenLM deleted a comment Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

/model should switch only the current session unless the user explicitly sets a default

6 participants