qwen-code/docs/plans
易良 354ae9292d
feat(core): resolve model limits and modalities from a models.dev catalog (#11959)
* feat(core): resolve model limits and modalities from a models.dev catalog

Bundle a trimmed models.dev snapshot (12 first-party providers, 237
models) and refresh it in the background once a day into
~/.qwen/model-registry.json with ETag revalidation, so a model that
appears on models.dev gets its context window, output limit, and input
modalities without a CLI release. tokenLimit()/knownTokenLimit()/
hasExplicitOutputLimit() consult the catalog before the regex tables;
defaultModalities() merges both so the catalog can only add modalities.

QWEN_CODE_MODELS_DEV=off restores the regex-only behavior (default in the
unit test setups), QWEN_CODE_MODELS_DEV_REFRESH=off disables the network
refresh, QWEN_CODE_MODELS_DEV_URL points at a mirror. `npm run
generate:model-catalog` regenerates the committed snapshot.

Implements PR2 and PR3 of the design in #9851. Refs #8558.

* feat(core): add model.customCatalog for offline model metadata

Offline and proxy-restricted users cannot reach models.dev, so let them
point `model.customCatalog` at a URL or local file whose entries are
merged over the bundled catalog per model and field. Both the trimmed
`{ models: { id: entry } }` shape and models.dev-style payloads are
accepted. A file is re-read synchronously at every start so the first
model resolution already sees it; a URL is cached like models.dev and
is still fetched when QWEN_CODE_MODELS_DEV_REFRESH=off. The cache
records its source, so a changed or removed setting drops the overlay.

Also sort snapshot keys with a plain comparator instead of localeCompare
so regeneration is deterministic across machines.

* docs: add a verification brief for the models.dev catalog

CI covers the mechanism, but every unit test stubs fetch and the test
setups run with the catalog off, so neither the data values nor the real
runtime behavior are verified anywhere. This brief is the handoff for a
machine that can run the CLI against live endpoints: an on/off A/B with
measured reference numbers, the refresh and offline paths, and the two
values that may still be wrong (Anthropic 1M context, qwen3.8-max PDF).

* fix(core): only catalog models the endpoint question has one answer for

Two projection bugs, both found by reviewing the magnitude of the changes
the catalog was making rather than their direction.

The provider whitelist filtered by vendor but never by model type, so the
catalog also carried embedding, text-to-speech, image, video, OCR and
translation models whose limits mean something else entirely:
gemini-embedding-001 reported an output limit of 1 and veo-3.1-generate a
context of 480, and those numbers outranked the family fallbacks that keep
such ids harmless today. Require tool calling and text output, which drops
34 ids and no real chat model.

Conflict resolution picked the vendor's numbers when several providers
served one id, but a context window is a property of the endpoint, not the
weights. DashScope caps GLM-5 output at 16,384 where Z.ai allows 131,072;
both are correct for their own endpoint. Worse, kimi-k2.6 has no matching
output pattern today, so it is treated as unknown and the user's max_tokens
is respected, and the catalog would have clamped it to 262,144. Drop such
ids instead of guessing: 220 of 237 ids have no conflict, and the 11 that
remain keep exactly the answer they get today.

Snapshot goes from 237 models to 192, 29,824 to 24,685 bytes.

* fix(core): validate catalog defaults and reject alias conflicts

* fix(core): narrow catalog defaults to verified behavior

* fix(core): preserve curated output limits over catalog defaults

* fix(core): harden models.dev catalog refresh and keying

- Reject a 200 response that projects to zero models so a gateway error
  body or an upstream field rename cannot shadow the bundled snapshot,
  and treat an already-empty cache as absent so a 304 cannot re-stamp it.
- Omit catalog candidates whose key is not a normalize() fixed point:
  deepseek-v3 was unreachable by its own name while its dated alias
  inherited a 163,840 output limit. Snapshot drops that entry (194).
- Exclude QWEN_CODE_MODELS_DEV_URL from project .env files: the refresh
  writes the global cache every project reads.
- Reject false-valued modalities when parsing catalog caches.
- Document the fixed-point keying rule and the accurate REFRESH=off
  semantics in the settings table.

* fix(core): preserve explicit budgets for catalog-only models

* fix(core): harden catalog cache reads and correct qwen3-coder-plus window

- loadModelCatalog drops cache keys no normalized spelling can reach and
  treats a cache that is empty after validation as absent, so a stale or
  poisoned cache can no longer displace the bundled snapshot.
- Correct qwen3-coder-plus to its vendor-declared 1,000,000-token window
  (models.dev's alibaba buckets approximate it as 1,048,576), applied in
  the loader so a refreshed cache cannot reintroduce the approximation.
- Pin the cross-origin refresh guard with a non-empty cache fixture, the
  normalized-id catalog lookup for modalities, the env-budget clamp
  precondition for glm-4.7, and control keys plus reload/snapshot arms
  for the project-scoped catalog URL exclusion.
- Align the QWEN_CODE_MODELS_DEV_REFRESH docs and JSDoc with the
  newer-than-bundled selection rule.

* fix(core): project catalog modalities across providers and test against the real catalog

Review round on PR #11959:

- trimModelsDevCatalog: token limits stay gated on the first-party
  allowlist with disagreement-drop; input modalities describe the
  weights, so they now union across every provider that serves the
  model. First-party vendors outside the allowlist (issue #8558's
  thinkingmachines/inkling) project image/pdf support again, while
  their endpoint limits stay untrusted.
- modalityDefaults.test.ts: drop the file-scoped catalog mock that let
  three assertions pin the opposite of shipped behavior; run against
  the real bundled snapshot in an isolated QWEN_HOME and correct the
  qwen3.8-max / glm-5v-turbo / glm-5.3-flash expectations. The
  mock-based merge tests move to modalityDefaults.catalog.test.ts.
- vscode-ide-companion: add the missing QWEN_CODE_MODELS_DEV=off test
  default via test-setup.ts so a host's ~/.qwen/model-registry.json
  cache cannot flip its contextLimit assertions.

* revert(vscode-ide-companion): drop out-of-footprint catalog test wiring

Deterministic verification rejected the setupFiles addition to
packages/vscode-ide-companion/vitest.config.ts: per-package vitest
configs are test-config verification machinery, and this PR's
footprint does not include that package. Revert the wiring and remove
the now-orphaned test-setup.ts.

The underlying hazard (R4-2: acpModelInfo contextLimit assertions
resolve against the host's ~/.qwen/model-registry.json cache) is real
and reproduced on this head — a poisoned cache flips exactly the two
witness tests red — but every fix lives outside this PR's footprint,
so it is deferred to the follow-up queue with an open question for
maintainers.

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

* fix: close model catalog environment gaps

* fix(core): validate refreshed model metadata

* fix(core): reject incomplete model catalogs

* fix(core): keep catalog defaults agent-safe

* fix(core): preserve DeepSeek vision boundary

* fix(core): harden the catalog refresh fetch and pin the catalog fixture clock

Six review findings on the refresh/generator path:

- Bound the download: the refresh now reads the 200 body with a
  200KB ceiling (the generator's budget) instead of buffering an
  unbounded payload, rejects a present-but-non-JSON Content-Type one
  step earlier than JSON.parse, and cancels the body on the HTTP-error
  path so the socket is released.
- Clock skew: a cache stamped by a faster clock (negative or unreadable
  age) now refetches instead of serving indefinitely; the re-stamp uses
  the local clock so the skew self-heals.
- The refresh failure log goes through the canonical getErrorMessage,
  so error.cause survives into the debug channel.
- The 'serves it immediately' test pins its clock ahead of the bundled
  snapshot's committed fetchedAt, decoupling it from the runner's wall
  clock (the snapshot is regenerated by whichever host runs the
  generator).
- The generator refuses a non-OK response and an empty projection
  instead of overwriting the committed snapshot with either.

Each new test is red under its corresponding revert (byte cap, content
-type check, skew guard); the clock-pin witness mutates the snapshot's
fetchedAt to 9999 and stays green.

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

* fix(core): drop mirror providers from catalog limits and keep parse fidelity

Seven more review findings on the catalog path:

- MODELS_DEV_PROVIDERS no longer includes modelscope. Measured against
  the live payload: its only contributions were four dated -2507 qwen3
  snapshots recorded under the bare ids plus vetoing z.ai's first-party
  glm-4.6 numbers into a conflict drop. volcengine stays — it is the
  first-party endpoint for the doubao/seed family (dropping it costs 16
  keys). Snapshot regenerated: 203 entries, glm-4.6 restored to the
  first-party figures.
- parseModelCatalog no longer lets one bad modalities member take a
  whole entry down: unknown or mistyped modality keys are filtered and
  the entry keeps its limits; context/output stay strict. The parser
  also skips an own-property __proto__ key instead of invoking the
  prototype setter (the refresh path can legitimately write one).
- A declared text-only modalities list now survives projection as {}
  on entries that ship limits, instead of being indistinguishable from
  "upstream said nothing" — inert under today's union, recorded so a
  future narrowing can tell the two apart. A bare {} never creates an
  entry on its own, so conflict-dropped and limit-less models still
  drop.
- The committed snapshot now has a parse-and-budget gate: every shipped
  entry must survive parseModelCatalog and stay within the 200KB budget
  (corrupting one entry turns the new test red).
- The gpt-4o resolver expectation's comment now says it pins the
  catalog-off regex answer; the catalog-on production value already has
  its own twin test.
- The verification README's evidence pointers no longer reference a
  gitignored .qwen path; they name the committed suites that pin the
  same numbers.

Modality list is now single-sourced as CATALOG_MODALITIES exported from
model-catalog.ts. Each behavior change is red under its revert:
restoring modelscope re-drops glm-4.6, restoring whole-entry modality
rejection or the __proto__ assignment reds the parse test, restoring
the empty-modalities drop reds the projection expectations.

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

* fix(core): narrow catalog modality keys with a type predicate

Object.entries widens the key to string, and includes() on the widened
readonly string[] does not narrow it back, so the indexed write into
InputModalities was an implicit any under noImplicitAny. That failed the
packages/core build with TS7053 and cascaded into every required check.

Extract the membership test into an isCatalogModality predicate so the
write is type-safe. Type-level only, no behavior change: the existing
"drops malformed entries while parsing" coverage still pins that an
unrecognised modality key degrades that one field instead of discarding
the entry.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmunuawz4h7

* fix(core): keep catalog refresh usable and process-consistent

* fix(core): invalidate incompatible model catalog projections

* fix(core): stamp canonical models.dev URL as bundled catalog provenance

The generator passed its input locator straight through as the provenance
stamp, so generating from a local payload copy recorded a machine-specific
path (e.g. /tmp/11959-models-dev.json) in the committed snapshot. Stamp
MODELS_DEV_URL for local-path inputs (the payload is models.dev data
regardless of where the machine stored it), keep URL inputs stamping
themselves, regenerate the snapshot, and pin the artifact's source field
with a test.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmuofqisz0a

* fix(core): share the provider-coverage guard and key DeepSeek narrowing on the family root

Round-9 review findings on 4516bd759d:

- R9-1: the snapshot generator only checked "at least one entry
  projected", while the runtime refresh treats a provider-incomplete
  payload as fatal. A truncated download or a mirror missing one vendor
  would silently replace the committed 203-entry snapshot. Extract the
  coverage check into assertCatalogPayloadComplete() and call it from
  both refreshRemote and scripts/generate-model-catalog.ts.

- R9-2: the #10270 DeepSeek text-only narrowing keyed on the hyphenated
  prefix `deepseek-`, but normalize() folds bare-model records onto the
  family root (`deepseek-v3` -> `deepseek`), which passes
  isModelCatalogKey — so a bare record kept an overstated image claim.
  Key the guard on the family: /^deepseek(?:-|$)/.

Both pins are mutation-verified: reverting either change turns its new
assertion red.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-issue-patrol/jmuolga6k0g

* fix(core): keep four more catalog windows at their declared value

models.dev buckets the vendor-declared window of kimi-k3 and
qwen3-coder-plus up to 1,048,576, and MiniMax-M2.5 / GLM-4.7 up to
204,800, while the curated table in tokenLimits.ts and the provider
presets both declare the real value. The catalog wins for input limits,
so for kimi-k3 the compaction thresholds computed from the inflated
window fired above the window the vendor accepts
(computeThresholds(1048576).hard = 1,025,576 > 1,000,000).

The correction list already had the shape for qwen3-coder-plus; extend
it to the complete set of ids where the catalog contradicts both a
curated row and a preset, and drive the application from one table.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmupe138l16

* fix(cli): freeze all three model-catalog keys against project config

A project `.env` or `settings.json` `env` block could set
QWEN_CODE_MODELS_DEV and QWEN_CODE_MODELS_DEV_REFRESH even though
QWEN_CODE_MODELS_DEV_URL was already excluded. On the `qwen serve` fast
path both keys are applied before runQwenServeImpl snapshots and freezes
process.env into daemonRuntimeBaseEnv, which every workspace session
child inherits and which setRuntimeEnvIfUnset cannot repair per
workspace — so one repository's config decided catalog behaviour for
every other workspace the daemon hosts, and the rejection was silent
because the keys are not loader keys. On the reload path a repository's
`on` force-wrote over an operator's exported `off`, re-enabling the
outbound refresh in an air-gapped environment.

Add both keys beside the URL key in PROJECT_ENV_HARDCODED_EXCLUSIONS
and document the project-file restriction on their settings.md rows.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmupe138l16

---------

Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen Autofix Bot <autofix@qwen-code.dev>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com>
2026-10-02 14:39:11 +00:00
..
2025-06-03-stats-dashboard-redesign.md
2026-03-22-agent-tool-display-design.md
2026-05-14-auto-compaction-threshold-redesign.md
2026-05-15-worktree-phase-c.md
2026-05-18-qwen-runtime-memory-investigation.md
2026-05-26-daemon-logger.md
2026-05-27-daemon-workspace-service.md
2026-05-28-computer-use-built-in.md feat(computer-use): replace built-in tools with bundled skill (#9856) 2026-08-24 11:05:23 +00:00
2026-06-30-channel-loop.md
2026-07-01-channel-lifecycle-status-adapters.md
2026-07-01-channel-lifecycle-status-umbrella.md
2026-07-01-channel-p0-identity-task-lifecycle.md
2026-07-01-extension-runtime-refresh-implementation.md
2026-07-10-pr-6635-remaining-comments.md
2026-07-10-pr-6635-review-followups.md
2026-07-10-workspace-qualified-acp-hardening.md
2026-07-11-chrome-extension-alpha-diagnostics.md feat(browser-ext): add alpha readiness diagnostics (#6739) 2026-08-04 03:23:47 +00:00
2026-07-11-managed-memory-microcompaction.md
2026-07-14-dingtalk-webhook-direct-message.md
2026-07-16-default-background-subagents.md feat(core): improve subagent delegation defaults and guardrails (#7048) 2026-07-18 08:52:48 +00:00
2026-07-16-webshell-git-integration.md feat(web-shell): git status chip, visual working-tree diff, and sidebar git status (#7054) 2026-07-18 10:06:07 +00:00
2026-07-17-observed-channel-delivery-targets.md feat(channels): expose workspace-scoped observed contacts (#7109) 2026-07-18 07:35:45 +00:00
2026-07-18-observed-channel-group-names.md feat(channels): observe group names from inbound messages (#7155) 2026-07-18 09:26:12 +00:00
2026-07-21-channel-delivery-review-fixes.md feat(daemon): add explicit channel delivery (#7388) 2026-07-23 17:44:23 +00:00
2026-07-22-channel-delivery-final-only.md feat(daemon): add explicit channel delivery (#7388) 2026-07-23 17:44:23 +00:00
2026-07-23-session-workflow-approval.md feat: visualize ordinary-session plan execution (#7580) 2026-08-01 10:48:30 +00:00
2026-07-24-webshell-git-status-fast-path.md perf(web-shell): paint the composer git chip before git status completes (#7680) 2026-07-25 07:05:52 +00:00
2026-07-31-daemon-memory-budget-pr1.md feat(serve): resolve and report the daemon memory budget (#8245) 2026-08-02 04:19:31 +00:00
2026-08-05-feishu-ask-user-question-cards.md feat(channels): add Feishu ask-user question cards (#8578) 2026-08-08 04:20:17 +00:00
2026-08-08-selective-session-restore.md docs(design): Define selective session restore (#8743) 2026-08-13 03:11:44 +00:00
2026-08-13-standalone-pr1-runtime-boundary.md feat(daemon): Isolate the Conversations runtime boundary (#9181) 2026-08-16 17:32:45 +00:00
2026-08-14-standalone-pr2-core.md fix(serve): Harden standalone conversation primitives (#9512) 2026-08-20 08:09:44 +00:00
2026-08-18-autofix-handoff-bilingual.md feat(ci): post autofix failure-path handoff comments bilingually (#9386) 2026-08-19 00:17:35 +00:00
2026-08-18-peer-session-collaboration.md feat: agent board — share work across independently started agents (#9402) 2026-09-13 10:21:45 +00:00
2026-08-20-cua-driver-0.20.0-upstream-sync.md feat(cua-driver): add versioned Computer Use SDK and release pipeline (#9587) 2026-08-23 14:20:14 +00:00
2026-08-20-cua-driver-computer-use-sdk.md feat(cua-driver): add versioned Computer Use SDK and release pipeline (#9587) 2026-08-23 14:20:14 +00:00
2026-08-22-serve-open-with-auth.md feat(serve): add --open-with-auth (#9738) 2026-08-24 07:33:05 +00:00
2026-08-25-standalone-pr3-daemon-api.md feat(cli): Add standalone daemon session API (#10179) 2026-08-27 12:55:19 +00:00
2026-08-27-standalone-pr4-typescript-sdk.md feat(sdk): Add standalone session APIs (#10294) 2026-08-28 00:44:18 +00:00
2026-08-27-web-shell-composer-add-menu.md feat(web-shell): add opt-in composer add menu (#10379) 2026-08-28 09:21:54 +00:00
2026-08-28-standalone-pr5-webshell-context.md feat(web-shell): Add explicit daemon session contexts (#10418) 2026-08-29 14:40:11 +00:00
2026-08-29-dingtalk-permission-cards.md feat(dingtalk): present tool permission requests with native interactive cards (#10457) 2026-09-09 05:50:50 +00:00
2026-08-29-standalone-pr6-webshell-ui.md feat(web-shell): Add standalone chats (#10514) 2026-09-01 09:43:35 +00:00
2026-08-31-dingtalk-lifecycle-delivery-convergence.md feat(dingtalk): show dynamic lifecycle tags (#10504) 2026-09-08 08:55:17 +00:00
2026-08-31-dingtalk-permission-card-language.md feat(dingtalk): present tool permission requests with native interactive cards (#10457) 2026-09-09 05:50:50 +00:00
2026-09-01-dingtalk-granular-kind-phases.md feat(dingtalk): show dynamic lifecycle tags (#10504) 2026-09-08 08:55:17 +00:00
2026-09-03-test-suite-barrel-cost.md docs(plans): record why CI test time is bound by module import, not scheduling (#10909) 2026-09-22 15:51:24 +00:00
2026-09-04-background-agent-surfaces.md docs: record where a background agent shows up, and who owns it (#10951) 2026-09-05 11:15:26 +00:00
2026-09-04-session-workflow-dag-design.md feat(web-shell): make Session Workflow dependencies navigable and quiet its chrome (#10938) 2026-09-13 13:32:32 +00:00
2026-09-04-web-shell-global-turn-navigation-phase2.md feat(web-shell): add continuous history and compact turn navigation (#11208) 2026-09-07 15:52:37 +00:00
2026-09-06-conversations-ownership-cutover.md feat(serve): allow concurrent standalone daemons with session fencing (#11207) 2026-09-07 15:52:05 +00:00
2026-09-10-remote-web-shell-daemon.md feat(web-shell): connect to a selected remote daemon (#11548) 2026-09-16 04:37:17 +00:00
2026-09-13-pr9040-review-followups.md fix(cli): prevent dialog clipping in short terminals (#9040) 2026-09-13 07:58:04 +00:00
2026-09-14-remote-computer-use-desktop-relay.md feat(computer-use): let a remote session use your desktop through a node_repl relay (#11799) 2026-09-28 06:43:22 +00:00
2026-09-16-models-dev-catalog-plan.md feat(core): resolve model limits and modalities from a models.dev catalog (#11959) 2026-10-02 14:39:11 +00:00
2026-09-16-non-conversation-context-token-governance.md fix(core): deduplicate system prompt guidance in a second pass (#12546) 2026-09-25 17:48:53 +00:00
dingtalk-richtext-multi-image.md fix(channels): preserve DingTalk rich-text multi-image messages (#9922) 2026-08-26 14:01:43 +00:00
extension-management-v2.md
feishu-observed-contact-label-enrichment.md feat(feishu): enrich observed contact labels (#8569) 2026-08-07 16:41:37 +00:00
final-tool-response-budget-implementation.md fix(core): Enforce final tool response budgets (#7323) 2026-07-21 16:09:46 +00:00
memory-diagnostics-reference-design.md
web-shell-session-sources.md feat(web-shell): add unified session sources (#11262) 2026-09-10 02:34:58 +00:00