mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-31 10:16:57 +00:00
5966 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a8a6ad2d06
|
feat(core)!: redesign auto-compaction thresholds with three-tier ladder (#4345)
* feat(core)!: redesign auto-compaction thresholds with three-tier ladder
Replaces the single 70% proportional threshold with a three-tier ladder
(warn/auto/hard) that combines proportional fallback with absolute
reservation. Large-window models (>=128K) now reserve ~33K instead of
30% of the window, freeing tens of thousands of context tokens that the
old formula wasted.
Other improvements bundled in the same redesign:
- Compression sideQuery now disables thinking and caps maxOutputTokens
at 20K, matching claude-code so the buffer math is predictable across
providers (Anthropic/OpenAI/Gemini handle thinking budgets
inconsistently)
- Failure handling upgraded from one-shot permanent lock to a 3-strike
circuit breaker; reactive overflow still latches immediately
- New estimatePromptTokens helper closes the lag-by-one-turn and
first-send-is-0 gaps in lastPromptTokenCount
- Hard-tier rescue pulls reactive overflow recovery forward to before
the API call, saving an oversized round-trip
- /context command displays the three-tier ladder + current tier
- tipRegistry's context-* tips track the new thresholds instead of
fixed 50/80/95 percentages
BREAKING CHANGE: chatCompression.contextPercentageThreshold setting is
removed. Settings files containing the field log a one-line deprecation
warning at startup and the value is ignored; behaviour is now controlled
by built-in thresholds via the new computeThresholds() function.
Design: docs/design/auto-compaction-threshold-redesign.md
Plan: docs/plans/2026-05-14-auto-compaction-threshold-redesign.md
* test(core): fix leftover hasFailedCompressionAttempt option in compress test
A pre-existing test case at chatCompressionService.test.ts:678 still
passed `hasFailedCompressionAttempt: false` in the CompressOptions
shape; rebasing onto current main surfaced this as a typecheck error
because the field was renamed to `consecutiveFailures` (Task 7 of the
three-tier ladder migration). Update to `consecutiveFailures: 0` —
semantically equivalent, the test asserts the side-query is called
when `force: true`, no other behaviour change.
* fix(core): drop compaction summary when output hits maxOutputTokens cap
Adds a defensive guard in ChatCompressionService.compress() that detects
when the side-query summary hit COMPACT_MAX_OUTPUT_TOKENS (20K). In that
case the summary is likely truncated mid-content, so we drop it and
return NOOP rather than persist a half-summary. The next send re-tries;
reactive overflow still catches the catastrophic case where the API
rejects the next request as too large.
Documented in the design doc as risk #2; the bot reviewer on PR #4168
correctly pushed for it to land alongside the threshold redesign rather
than as a follow-up since the new 20K cap is what makes truncation
likely in the first place.
* fix(cli): render three-tier thresholds in /context TUI view
The Task 11 redesign updated the non-interactive text formatter
(formatContextUsageText) but left ContextUsage.tsx — the interactive
React component that real /context users see — unchanged. As a result
the TUI still showed the old single "Autocompact buffer" line and none
of the new warn/auto/hard ladder.
Adds a "Compaction thresholds" section after the per-category breakdown:
- Effective window
- Warn / Auto / Hard threshold rows with a ▶ marker on the row the
current usage has crossed
- Current tier label coloured by severity (safe→green, warn/auto→
yellow, hard→red)
The existing progress bar legend (Used / Free / Autocompact buffer)
is preserved because it's tied to the three-segment progress bar
visualisation; the new section adds the absolute numbers + tier badge
on top of that.
Caught by the tmux e2e test (PR #4168 ci-monitor follow-up). Pre-fix
the assertion 'Compaction thresholds' missed completely from the TUI;
post-fix the new section renders correctly for fresh and live sessions
on 1M / 200K / 128K windows.
* fix(core,cli): address PR #4168 review batch 4
Behavior fixes:
- MAX_TOKENS truncation guard now returns COMPRESSION_FAILED_EMPTY_SUMMARY
instead of NOOP so the consecutive-failure breaker actually trips after
repeated max-length summaries (R1.1).
- Reactive overflow failure increments consecutiveFailures by 1 instead
of latching to MAX in one shot, so a transient network blip doesn't
permanently disable auto-compaction. The hard-tier rescue resets the
counter, which remains the designated recovery path (R1.2).
- /context current-tier classification uses rawOverhead (system + tools +
memory + skills) as the tier input when API data is not yet available,
rather than 0 — large inherited contexts no longer silently show 'safe'
(R2.2).
Performance:
- sendMessageStream computes effectiveTokens ONCE and passes it through
TryCompressOptions.precomputedEffectiveTokens, so the cheap-gate inside
service.compress doesn't redo the estimation. Also fixes the
imageTokenEstimate inconsistency between the rescue and cheap-gate
paths (R1.3 + R1.4).
- Steady-state path (lastPromptTokenCount > 0) skips the costly
getHistory(true) clone — estimatePromptTokens only needs the user
message in that branch.
Code hygiene:
- BYTES_PER_TOKEN → CHARS_PER_TOKEN (inputs are char counts, not byte
counts; CJK text would mislead under the old name) (R3.1).
- Drop dead getContextUsagePercent helper + index re-export — no callers
in source after the threshold rewire (R1.5).
- Add a comment on estimatePromptTokens' first-send fallback documenting
the ~15-20K under-estimate (system prompt + tools + skills) and that
reactive overflow is the safety net (R3.3).
Tests:
- New CLI ContextUsage.test.tsx exercises the React renderer for the
three-tier section: section presence, ▶ marker placement per tier,
current-tier label coloring (R1.6).
- New chatCompressionService.test.ts case pins that a stale
contextPercentageThreshold: 0 value in user settings no longer
short-circuits compaction (R2.1).
- New tokenEstimation.test.ts case covers functionResponse (distinct
nested-parts branch from functionCall) (R3.5).
- New geminiChat.test.ts integration test exercises the real
ChatCompressionService — not a mock — for the first-send-after-
inherited-history scenario where lastPromptTokenCount=0 and only the
full-history estimate can cross the auto threshold (R3.4).
Declined: R3.2 (change `>=` to `>` on the MAX_TOKENS guard). The current
operator catches the at-cap case as suspicious, which is intentional —
landing exactly at the output cap is far more likely truncation than
clean stop given p99.99 ≈ 17K. With R1.1 in place, persistent truncations
trip the breaker after MAX_CONSECUTIVE_FAILURES so the worst case is
bounded.
* fix(core,cli): address PR #4168 review batch 5
- R5.1: tighten /context tier comment + TODO. The rawOverhead-based fix
doesn't cover `--continue` restores with many history messages (since
rawOverhead excludes messagesTokens). UI may still show 'safe' for one
render until the first send. Documented inline and added a TODO to plumb
chat history into collectContextData for same-source-of-truth as the
cheap-gate.
- R5.2a: add TODO(finish_reason) at the truncation guard. The `>= cap`
heuristic false-positives on legitimate at-cap summaries; the proper
signal is finish_reason which runSideQuery doesn't surface today.
- R5.2b: split telemetry — new CompressionStatus.COMPRESSION_FAILED_OUTPUT_TRUNCATED
enum value. Distinct from EMPTY_SUMMARY so logs/telemetry can tell
prompt-quality failures (tune prompt / splitter) from capacity failures
(raise cap / shrink splitter input). isCompressionFailureStatus()
treats both as failures so the breaker behavior is unchanged.
- R5.3: expand consecutiveFailures JSDoc to clarify it tracks
"non-force, non-hard-rescue consecutive failures" — hard-rescue resets
the counter and force=true skips increments, so the counter is the
"regular path" health signal only; reactive overflow is the real
safety net for the force-only paths.
- R5.4: document the CompressOptions field rename
(hasFailedCompressionAttempt: boolean → consecutiveFailures: number)
as an SDK breaking change in the design doc with migration guide.
* fix(core): disambiguate hard-rescue from manual /compress orphan-strip
Self-review (dual reviewer / pr-triage round 1) caught a correctness
regression in the hard-rescue path:
`sendMessageStream` calls `tryCompress(force=true)` from inside the
pre-push window when `effectiveTokens >= hard`. The service's
orphan-strip predicate at `chatCompressionService.ts:426-429` gated on
`force` alone, which conflated two distinct call shapes:
- manual `/compress` (force=true, trigger='manual'): user-initiated
between turns; trailing model funcCall IS orphaned because no
funcResponse is coming
- hard-rescue (force=true, trigger='auto'): automatic mid-turn;
trailing model funcCall is ACTIVE because its matching funcResponse
is sitting in the pending `userContent` waiting to be pushed
The strip fired for both, so a hard-rescue triggered mid tool-use loop
would drop the active funcCall. After compression returned and
`userContent` (the funcResponse) was pushed, the next API request
carried tool_result with no matching tool_use → provider validation
error.
The in-code comment at L422-424 already documented this exact
constraint for the auto-compress case (`force=false`), but reusing
`force=true` for hard-rescue silently violated the same constraint.
Fix:
- Gate `hasOrphanedFuncCall` on `compactTrigger === 'manual'` instead
of `force`. The trigger field already disambiguates intent.
- `sendMessageStream` hard-rescue now passes `trigger: 'auto'`
explicitly (without it, `force=true` defaults to `trigger='manual'`
via the `?? (force ? 'manual' : 'auto')` resolver).
Sibling audit for "force=true non-manual callsites":
- `GeminiClient.tryCompressChat` (manual /compress): correct — manual
- `sendMessageStream` hard-rescue: fixed in this commit
- `sendMessageStream` reactive overflow catch: already passes
trigger='auto'; runs AFTER API call (userContent in history), so if
it observes a trailing funcCall it IS orphaned but findCompressSplitPoint
handles the case without needing the strip
RED-first regression test added:
`preserves trailing model+funcCall under hard-rescue (force=true + trigger=auto)`
in `chatCompressionService.test.ts`. Failed against pre-fix code (the
strip dropped the funcCall); passes against the fix.
Adjacent fixes from the same triage round:
- `docs/users/configuration/settings.md`: the
`chatCompression.contextPercentageThreshold` row still said "use 0
to disable compression entirely" — code has ignored the value since
the removal commit. Marked the row REMOVED with migration guidance
pointing at the design doc.
- `packages/core/src/config/config.ts`: the deprecation warning now
tells users how to silence it (remove the key) and where to read
current behavior, instead of just announcing the removal.
- `docs/design/auto-compaction-threshold-redesign.md`: closed Open
Question 2 (small-window hard/auto collapse) — decision is to NOT
annotate `/context`, with rationale on file.
Tests: 2395 core tests passing, typecheck clean.
* docs(core): fix tier-collapse direction in auto-compaction design doc
Self-review on the
|
||
|
|
56522bd89c
|
fix(core): enable cache control for Token Plan (#4495) | ||
|
|
5493888c15
|
ci: split Aliyun OSS sync into a separate post-release workflow (#4492)
* ci: split Aliyun OSS sync into a separate post-release workflow The OSS upload and verification steps were adding significant time to the release workflow's critical path. Move them into a new `sync-release-to-oss.yml` workflow that triggers on `release: published`, running asynchronously after the release completes. Key changes: - Extract all OSS steps (ossutil install, credential config, asset upload, verification, hosted installation sync, latest VERSION pointer) into `sync-release-to-oss.yml` - Switch `gh release create` to use CI_BOT_PAT so the release event can trigger the new downstream workflow (GITHUB_TOKEN events don't trigger other workflows) - Add `workflow_dispatch` input for manual re-runs on failure - New workflow downloads release assets from GitHub Release instead of rebuilding them This decouples publishing from CDN distribution: the release finishes as soon as npm publish + GitHub Release are done, and China CDN sync happens in parallel without blocking. * fix(test): update install-script test to check sync-release-to-oss.yml The test asserts OSS sync steps exist in the workflow. Now that these steps live in sync-release-to-oss.yml instead of release.yml, update the test to read from the correct file and add assertions that release.yml no longer contains OSS logic. * fix(ci): address review feedback for OSS sync split - Add 'Verify Standalone Archives' step before gh release create in release.yml as a pre-publish safety gate (wenshao) - Add concurrency group to sync-release-to-oss.yml to prevent race conditions when multiple releases publish close together (wenshao) - Update test to assert verify step exists in release.yml * chore: add comment explaining CI_BOT_PAT requirement [skip ci] |
||
|
|
7cb017d4b0
|
docs(agents,pr-template): add Working Principles and restructure PR template (#4496)
* docs(agents): add Working Principles and file/comment conventions Add a "Working Principles" section at the top of AGENTS.md, with Simplicity First (adapted from Andrej Karpathy's CLAUDE.md) as the lead principle. Extend Code Conventions with two new entries: - File naming: PascalCase for React components, kebab-case preferred for new non-component files, existing camelCase stays as-is. - Comments: default to none; explain why, not what. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(agents): link Karpathy's CLAUDE.md in attribution Per review feedback, make the source attribution clickable so reviewers can reach the original document in one hop. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(agents): align comments guidance — "default to none" Raise the bar for code comments from "add sparingly" to "default to none" in the runtime prompt, matching the AGENTS.md convention. Add a preservation clause to AGENTS.md so agents do not strip existing high-value comments during cleanup passes. Update snapshots. Co-Authored-By: Qwen Code <noreply@alibaba-inc.com> * docs(pr-template): restructure for reviewer test plan clarity - Reorganize PR template around a Reviewer Test Plan section with How to verify, Before/After, and Tested on - Add collapsible Chinese description section for bilingual PRs - Simplify create-pr command guidance to match the new template - Tighten AGENTS.md file naming and comments conventions; align PR submission guide with the new template This makes PRs easier to review by focusing contributors on the evidence reviewers need most. * docs(pr-template): merge Before/After into Evidence and require full Chinese translation - Consolidate Before and After sections into a single Evidence (Before & After) section - Update Chinese summary comment to require full paragraph-by-paragraph translation instead of abbreviated bullets This reduces template redundancy for non-UI changes and ensures the Chinese block is a proper translation, not a summary. --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Qwen Code <noreply@alibaba-inc.com> |
||
|
|
35e6963285
|
docs(tools): document monitor tool (#4356) | ||
|
|
05458d59ec
|
fix(core): strip additional dangerous interpreter rules (#4371)
* fix(core): strip additional dangerous interpreter rules * test(core): clarify dangerous interpreter coverage * chore(core): group bash.exe with windows shells * fix(core): normalize dangerous interpreter tokens * test(core): normalize dangerous exe interpreter rules * fix(core): detect windows interpreter path allows * fix(core): detect windows interpreter path allows |
||
|
|
632865c0df
|
feat(core): limit background agent concurrency (#4324)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / CodeQL (push) Blocked by required conditions
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* feat(core): limit background agent concurrency * fix(core): handle background agent cap on resume |
||
|
|
45a51185eb
|
fix(extension): redact credentialed source diagnostics (#4426)
* fix(extension): redact credentialed source diagnostics * fix(extension): avoid leaking redacted URL causes * fix(extension): close credential redaction gaps |
||
|
|
9363879ea1
|
fix(core): preserve duplicate object references in safeJsonStringify (#4407)
* fix(core): preserve duplicate object references in safeJsonStringify The replacer kept a WeakSet of every object it had ever seen. JSON.stringify calls the replacer for every key in a DFS walk, siblings included, and the set was never trimmed when the walk unwound. So the second sibling that pointed at the same object got replaced with [Circular]. Not a cycle, just a duplicate reference. Same false positive for repeated array elements and for any shared leaf that appears on more than one branch. Track the current ancestor path instead. The replacer's `this` is the parent of `value`, so on each call pop the stack back to wherever the walk currently is, then check the remaining ancestors for membership. Only real cycles get flagged. Existing cycle tests still pass. Added five regression tests covering shared siblings, repeated array elements, shared subtree leaves, indirect cycles, and a mix of duplicate ref + real cycle in the same graph. * test(core): cover deep unwinding and toJSON paths in safeJsonStringify Four regression tests covering corners the initial five missed: - Shared leaf reached through five levels of nesting plus a sibling branch. Exercises the unwind loop popping multiple frames between the deep arm and the sibling arm of the walk. - Real cycle (root referenced back from depth 5). Same depth as above but the deep arm closes the loop, so the ancestor check must still fire. - Shared object returned by toJSON from two sibling positions. The replacer sees the post-toJSON value, so duplicate-ref handling has to recognize these as duplicates even though the carriers are different objects. - Cycle through a toJSON that returns an ancestor. Confirms the ancestor check fires on the toJSON return value, not the toJSON-bearing carrier. Per review feedback on #4407. |
||
|
|
24ebfbc13e
|
feat(memory): load .qwen/QWEN.local.md as project-local context (#4091) (#4394)
* feat(memory): load .qwen/QWEN.local.md as project-local context (#4091) Adds a per-developer, project-scoped context file slot at `<projectRoot>/.qwen/QWEN.local.md`. Loaded after all hierarchical QWEN.md / AGENTS.md files so local instructions can supplement or override shared ones. Use case: project-specific but personal instructions (local cluster IDs, container registry namespaces, accounts) that shouldn't live in the shared root `QWEN.md` (exposes them to the team) or in the global `~/.qwen/QWEN.md` (applies to every project). Mirrors Claude Code's `.claude/CLAUDE.local.md` convention. The slot is single and fixed (project root only — not searched in CWD subdirectories or via upward traversal), gated by the same trust and explicit-only checks as the rest of project-level discovery, and counted in `fileCount` so the `/memory` panel surfaces it. Users must gitignore the file themselves; `.qwen/` is not auto-ignored and `.qwen/settings.json` is commonly committed. * fix(memory): support .git-file repos when locating QWEN.local.md slot `findProjectRoot()` only accepted `.git` as a directory, so in git worktrees and submodules (where `.git` is a file containing a `gitdir:` pointer) it returned `null`. The new `.qwen/QWEN.local.md` slot then fell back to `<cwd>/.qwen/QWEN.local.md`, silently breaking the documented "single fixed slot at project root" behavior for users inside worktrees — including the developer of this feature. Two changes: 1. `findProjectRoot()` now accepts `.git` as either a directory or a regular file. This also incidentally repairs pre-existing breakage in `rulesDiscovery` / hierarchical-search stop boundary, both of which consume the same helper. 2. The local-context-file slot now requires a real `foundRoot` (the `null` case is no longer covered by the `effectiveRoot` fallback). Without this guard: - a deep cwd in a non-git workspace turned the slot into a per-cwd file, opposite the design; - `cwd === homedir` resolved the slot to `~/.qwen/QWEN.local.md`, colliding with the global Qwen directory. Three regression tests pin the new behavior: `.git`-as-file is recognized, no-`.git`-ancestor skips the slot, `cwd === homedir` without `.git` does not promote a global file to project-local. * refactor(memory): extract findProjectRoot to shared utility (#4091) Two duplicate `findProjectRoot` helpers existed in `packages/core/src/utils/`: one in `memoryDiscovery.ts` (returns `Promise<string | null>`) and one in `memoryImportProcessor.ts` (returns `Promise<string>`, falls back to startDir). The previous fix in 97c6fb41f only updated the first copy for `.git`-file support, so `@import` resolution under git worktrees and submodules was still silently broken — the QWEN.local.md file would load, but its imports would resolve against the wrong root. Extract the helper into `utils/projectRoot.ts`, with the unified nullable return type. Rewire both call sites; `memoryImportProcessor` preserves its previous fallback semantics at the call site (`?? path.resolve(basePath)`). Adds 5 unit tests for the utility (directory / file / null / deep / symlink) and 1 test for the previously-unverified dedup guard in `memoryDiscovery.ts` (exercised via `extensionContextFilePaths`). Addresses inline + cross-file findings from wenshao on PR #4394. |
||
|
|
94da486e19
|
fix(weixin): send decryptable image payloads (#4464) | ||
|
|
8ef73599db
|
fix(weixin): allow Windows image paths inside workspace (#4465) | ||
|
|
ab26a5ab72
|
fix(cli): resolve stale closure race in text buffer submit handler (#4470)
Replace useReducer with useRef + useState + synchronous dispatch so that event handlers always read the latest buffer state. Previously, rapid input via tmux send-keys could deliver characters and Enter in the same event loop tick; the Enter handler read buffer.text from a stale render closure (empty string) because useReducer's dispatch only enqueues actions for the next render pass. The fix runs the reducer synchronously at dispatch time, stores results in a useRef for immediate reads, and calls setState to trigger re-renders. The returned TextBuffer object exposes text, lines, and cursor as getters reading from stateRef.current, so all consumers (BaseTextInput, InputPrompt, vim hook) automatically get fresh values without code changes. |
||
|
|
84f408017a
|
feat(skills): add memory-leak-debug skill for heap snapshot diagnosis (#4468)
Some checks failed
Qwen Code CI / Classify PR (push) Has been cancelled
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Has been cancelled
E2E Tests / E2E Test (Linux) - sandbox:none (push) Has been cancelled
E2E Tests / E2E Test - macOS (push) Has been cancelled
Qwen Code CI / Lint (push) Has been cancelled
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Has been cancelled
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Has been cancelled
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Has been cancelled
Qwen Code CI / Post Coverage Comment (push) Has been cancelled
Qwen Code CI / CodeQL (push) Has been cancelled
Provides a step-by-step workflow for diagnosing memory leaks in the CLI
using Node.js heap snapshots and the chrome-devtools CLI memory tools.
Includes a helper script for tmux PID discovery and a worked example
from the react-reconciler PerformanceMeasure leak (
|
||
|
|
4dc98484fd
|
feat(cli): do not append trailing space for directory completions (#4092) (#4288)
* feat(cli): do not append trailing space for directory completions (#4092) ## What 在 @路径补全和 /dir add 命令的目录补全中不再追加尾部空格。这样可以允许用户在补全目录后直接按 Tab 继续深入下一级子目录,无需先删除空格。 ## Examples - Input: `@src/com` + Tab → Output: `@src/components/` (no trailing space) - Input: `/dir add ./pac` + Tab → Output: `/dir add ./packages/` (no trailing space) - File completions still append a space (e.g., `@src/file.txt `) ## Changes - Added `isDirectory` flag to `Suggestion` and `CommandCompletionItem` interfaces - Updated `handleAutocomplete` to skip trailing space when `isDirectory === true` - Modified `getDirPathCompletions` to return `CommandCompletionItem[]` with `isDirectory: true` - Added test case for directory completion behavior * fix(cli): append trailing / to directory completions for deeper navigation * fix(cli): propagate isDirectory and fix JSDoc comment ## Comment 2: Fix JSDoc in SuggestionsDisplay Removed "(ends with /)" from isDirectory description since it was factually incorrect. ## Comment 3: Add test for isDirectory propagation - Added test suite in useSlashCompletion.test.ts to verify directory command structure - Real filesystem testing is done in directoryCommand.test.tsx * fix(cli): add comprehensive isDirectory propagation tests Added getDirPathCompletions unit tests that verify: - Directory suggestions include isDirectory: true - Directory values end with / for continued navigation - Prefix filtering preserves isDirectory flag - Comma-separated path completion works correctly - Deeply nested directories maintain isDirectory flag This closes the testing gap identified in review comment 3. * fix(cli): address wenshao feedback - lint rules, real test, cross-platform Fixes 4 new review comments from wenshao: - [Critical] Empty catch {} blocks: guarded with if (tempTestDir) + void err - [Critical] useSlashCompletion.no-op test: replaced with real integration test that verifies isDirectory propagation through toSuggestion pass-through - [Suggestion] Windows path separator: using path.sep instead of hardcoded / in both directoryCommand.tsx and related test assertions * fix(cli): remove unused import and fix Windows path separator in tests - Remove unused directoryCommand import in useSlashCompletion.test.ts (TS6133) - Replace hardcoded / regex with path.sep-aware assertions in directoryCommand.test.tsx to fix Windows CI failures Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * Apply suggestion from @wenshao Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> * Update packages/cli/src/ui/commands/directoryCommand.test.tsx Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> * Update packages/cli/src/ui/commands/directoryCommand.tsx Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> * Update packages/cli/src/ui/commands/directoryCommand.tsx Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> * fix(cli): normalize isDirectory to explicit boolean in toSuggestion Normalize isDirectory from three-state (true/false/undefined) to explicit boolean (true/false) to prevent latent bugs in future code that might distinguish between false and undefined. Fixes review comment: isDirectory normalization is inconsistent across completion paths. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * Update packages/cli/src/ui/hooks/useSlashCompletion.ts Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> * chore: remove accidentally committed pr_body.md Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * chore: add pr_body.md to .gitignore Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): remove duplicate .slice and orphaned test code from directoryCommand.tsx Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): only suppress trailing space for dir completions at end-of-line When isDirectory is true, the trailing space was suppressed unconditionally, even when the cursor is mid-line. This caused directory completions to merge directly with following text (e.g. '@src/components/something'). Now only suppress the space when the cursor is at end-of-line, allowing continued Tab navigation into subdirectories. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(cli): document crawler path separator dependency for isDirectory check The isDirectory detection uses p.endsWith('/') which depends on the crawler in @qwen-code/qwen-code-core normalizing paths with posix '/' (fdir.withPathSeparator('/') in crawler.ts). Add a comment to make this implicit coupling explicit. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): add mid-line directory completion test Verify that directory completions append a trailing space when the cursor is mid-line, preventing the completed path from merging with following text. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * Update packages/cli/src/ui/hooks/useCommandCompletion.test.ts Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> --------- Co-authored-by: 方磊 <fanglei@192.168.1.11> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
394e2a3fa8
|
chore(release): v0.16.1 [skip ci]
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
94982c6a91
|
fix(build): clean stale outputs before tsc --build to prevent TS5055 (#4453)
* fix(build): clean stale outputs before tsc --build to prevent TS5055 Run `tsc --build --clean` before `tsc --build` in build_package.js so a stale tsconfig.tsbuildinfo (e.g. after a version bump, branch switch, or a prior `npm ci` prepare) cannot collide with composite project references emitting back into packages/core/dist. Closes #4447 * fix(build): scope clean step to current package only Replace `tsc --build --clean` with direct `rmSync` of `dist` and `tsconfig.tsbuildinfo`. `tsc -b --clean` walks project references, so when scripts/build.js builds packages in dependency order, cleaning from a downstream package (e.g. cli) would also wipe upstream outputs (core, acp-bridge, channels) that were just built — a major perf regression. Spotted by Copilot review on #4453. |
||
|
|
a41afc465d
|
fix(release): move constants above entry point to avoid TDZ error (#4398)
MAX_UPLOAD_ATTEMPTS and INITIAL_BACKOFF_MS were declared after the isMainModule() guard that calls main(). In ES modules, const bindings are not initialized until the declaration is reached, so the runtime threw "Cannot access 'MAX_UPLOAD_ATTEMPTS' before initialization" during the Release workflow. |
||
|
|
e43852f769
|
fix(cli): gate mintty OSC 8 detection on TERM_PROGRAM_VERSION ≥ 3.3 (#4420) (#4451)
* fix(cli): gate mintty OSC 8 detection on TERM_PROGRAM_VERSION ≥ 3.3 (#4420) mintty added OSC 8 in 3.1 and hardened it in 3.3. Older builds — still bundled with some Git-for-Windows distros and developer environments like Laragon — print the raw `\x1b]8;;url\x07` bytes as visible garbage instead of silently ignoring them. The previous unconditional `case 'mintty': return true` deviated from the upstream `supports-hyperlinks` library (which rejects all of win32 outside WT_SESSION) and let those old mintty users see escape bytes in their UI. Gate on TERM_PROGRAM_VERSION (set by mintty since 2.7 in 2017 — a missing value implies an ancient build, so we refuse rather than guess). Users on mintty 3.1–3.2.x who know their build works can still opt in with FORCE_HYPERLINK=1. This fixes the OSC 8 component of #4420 (the "garbled UI on Windows + Git Bash" report). The Ink 7 render interaction and terminalRedrawOptimizer angles flagged in the same triage need separate Windows-environment testing; `QWEN_CODE_LEGACY_ERASE_LINES=1` remains the documented escape hatch for those. * test(cli): assert FORCE_HYPERLINK=1 escape hatch works on gated mintty Mirrors the Warp/Hyper pattern: after asserting auto-detection rejects an older mintty build, set FORCE_HYPERLINK=1 and verify it opts back in. The PR description for #4451 documents this contract for users on mintty 3.1–3.2 who know their build's OSC 8 implementation works; pinning it as a test guards against a future refactor reordering the early-exit checks. Addresses review feedback on #4451. |
||
|
|
b602a72e86
|
fix(cli): stabilize flaky sticky-todo remeasure test (#4416)
* fix(cli): stabilize flaky sticky-todo remeasure test (#4415) Replace absolute mock.calls.length assertion with mockClear() + not.toHaveBeenCalled() in the sticky todo status-only update test. The previous assertion captured the total measureElement call count after initial render, rerendered, and checked the count was unchanged. This was flaky on CI (macOS runner) because React 19's Ink test renderer can invoke useLayoutEffect a variable number of times during mount (StrictMode double-invoke, multiple reconciliation passes), making the absolute count unreliable across environments. The new approach resets the mock after initial render and asserts no new calls occur during rerender — clearly expressing the test intent and eliminating environment-dependent flakiness. * fix(cli): stabilize flaky sticky-todo remeasure test Replace fragile measureElement call-count assertion with a behavioral assertion on availableTerminalHeight stability, wrapped in act() to flush useLayoutEffect timing. The original test asserted that measureElement was not called after rerender when only todo status changed (pending -> in_progress). This was flaky because: 1. The absolute mock.calls.length count was environment-dependent (React 19 StrictMode double-invoke, variable reconciliation passes) 2. Even with mockClear(), the useLayoutEffect fires for legitimate reasons (buffer ref, btwItem) unrelated to sticky todo status, especially on Windows CI runners 3. The controlsHeight state (useState(0)) races with useLayoutEffect's first measurement — mainControlsRef.current may be null on initial render, causing controlsHeight to settle at different times The fix: - Assert on availableTerminalHeight (the behavioral outcome exposed via UIState context) rather than measureElement call count - Wrap render + rerender in act() to ensure useLayoutEffect and setControlsHeight fully settle before capturing the baseline - Consolidate duplicate react imports * test(cli): address review feedback on sticky-todo remeasure test - Narrow the `mockConfig.initialize` stub from `beforeEach` (which flipped `isConfigInitialized` for ~75 tests in the block) back to the single test that needs it. Other tests now exercise the real init gate as before. - Strengthen the behavioral assertion: switch `measureElement`'s mocked return value between the settle phase and the status-only rerender, so any re-measurement triggered by the status change would change `controlsHeight` and break the equality assertion. Without this, the production same-value short-circuit on `setControlsHeight` made the assertion pass even when the optimization regressed. The core layout-key contract (status-only changes return the same key) is already directly covered by `todoSnapshot.test.ts` — this integration test provides layered protection on top. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
15247f4bce
|
chore(deps): update express from 4.21.2 to 5.2.1 (#4458)
express@4.21.2 was a stale peer dependency residual at the top-level node_modules. It was originally pulled in to satisfy express-rate-limit's peerDependency "express >= 4.11" when express@4 was still the latest tag. Since express@5 is now latest and equally satisfies ">= 4.11", updating removes ~1200 lines of unused express@4 dependency tree from the lockfile. Closes #4457 |
||
|
|
61d91ad716
|
fix(build): tree-shake React reconciler dev build to prevent PerformanceMeasure leak (#4462)
The ink 6→7 upgrade (v0.15.11) pulled in react-reconciler 0.33, whose development build calls performance.measure() on every component render. Since NODE_ENV was never set to "production" in the esbuild define map, the bundle shipped both dev and prod builds and selected dev at runtime, causing an unbounded measureEntryBuffer leak (~45% of heap after moderate use, confirmed via heap snapshots). Set process.env.NODE_ENV to "production" at build time so esbuild statically resolves the conditional require and tree-shakes the entire 15k-line dev build. Bundle shrinks by ~700 KB / 15,800 lines. |
||
|
|
0cb9ff0a23
|
fix: renormalize CRLF storage for install-qwen-standalone.bat (#4427) [skip ci]
The blob in HEAD stored raw CRLF bytes while .gitattributes declared 'text eol=crlf', which expects LF in the object database and CRLF on checkout. The mismatch caused git status to permanently report the file as modified on every working tree, with neither reset --hard nor checkout fixing it. |
||
|
|
fd75f77e19
|
feat(telemetry): Phase 4a — TTFT capture + GenAI semconv dual-emit (#3731) (#4417)
Some checks failed
Qwen Code CI / Classify PR (push) Has been cancelled
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Has been cancelled
E2E Tests / E2E Test (Linux) - sandbox:none (push) Has been cancelled
E2E Tests / E2E Test - macOS (push) Has been cancelled
Qwen Code CI / Lint (push) Has been cancelled
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Has been cancelled
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Has been cancelled
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Has been cancelled
Qwen Code CI / Post Coverage Comment (push) Has been cancelled
Qwen Code CI / CodeQL (push) Has been cancelled
|
||
|
|
48b0a8bfce
|
fix(core): preserve tab-indented notebook formatting (#4373)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* fix(core): preserve tab-indented notebook formatting * test(core): cover mixed notebook indentation |
||
|
|
94f7cb5785
|
fix(vscode): skip redundant tsc build in prepackage to prevent TS5055 (#4401)
The prepackage script called `npm run build` (full tsc compilation of all workspace packages) before bundling. This second tsc pass fails with TS5055 in CI because: 1. `npm ci` already built everything via the `prepare` lifecycle 2. `release:version` bumps package versions, making tsbuildinfo stale 3. The redundant `npm run build` triggers tsc --build which attempts a full rebuild, but composite project references cause dist/*.d.ts to be resolved as input files → TS5055 The fix removes the redundant `npm run build` call. The CLI bundle is produced by esbuild directly from TypeScript source files (entry: packages/cli/index.ts), so compiled dist/ artifacts are not needed. Root cause introduced by #4295 (composite project references). |
||
|
|
6b0e75816a
|
fix(core,cli): close tool_use↔tool_result invariant across all failure paths (#4176)
* fix(core): persist partial assistant turn when stream errors mid tool_use
Weak-network failures during an Anthropic-compatible stream (DeepSeek,
api.anthropic.com, etc.) can drop the SSE between a tool_use
`content_block_stop` and the terminal `message_stop`. The functionCall
chunk is already yielded at content_block_stop, so:
- Turn.run records a ToolCallRequest event.
- useGeminiStream's for-await exits and schedules the tool.
- handleCompletedTools eventually fires submitQuery(..., ToolResult)
and pushes a user[functionResponse] into history.
- But processStreamResponse's history.push for the model turn never
ran (the for-await threw first), so the matching tool_use is gone.
The next request body has `user → user[tool_result]` with no tool_use
in between, and the server rejects with HTTP 400:
"tool_use_id ... must have a corresponding tool_use block in the
previous message". Ctrl+Y can't recover because
stripOrphanedUserEntriesFromHistory only strips trailing user
entries — the lost tool_use is unrecoverable, and the session is
wedged.
Wrap the for-await loop in processStreamResponse with try/catch.
When the stream throws AND any functionCall chunk was already
yielded (hasToolCall=true), persist the partial assistant turn to
history before re-throwing. The eventual tool_result submission
then has a matching tool_use and the session can continue.
Plain-text partial turns (no functionCall yielded) are intentionally
NOT persisted: the Retry path pops the trailing user prompt and
re-issues it, so a stale partial-text model turn between them would
either bias the retry or surface as duplicate output.
* test(core): cover thinking+tool_use mid-stream throw in partial-history fix
Adds a third case to the partial-history persistence test suite for
reasoning-mode providers (DeepSeek thinking, Claude 4.6+ adaptive): when
the assistant turn streams a thinking block AND a tool_use before the
SSE drops, the partial push must keep the thinking part before the
functionCall so DeepSeek's `injectThinkingOnToolUseTurns` converter
pass sees an existing block on the replayed turn and does not pre-pend
a synthetic empty one (which would discard the model's original
reasoning text).
* fix(core,cli): close tool_use↔tool_result invariant at failure points
Extends the partial-history fix in
|
||
|
|
fc15b3312d
|
chore(release): v0.16.0 (#4404)
* chore: bump version to 0.16.0 and normalize bat line endings * revert: restore install-qwen-standalone.bat to original CRLF encoding The previous bump commit inadvertently normalized line endings from CRLF to LF. Windows batch files must retain CRLF in the repository to work correctly with cmd.exe. * revert: remove spurious NOTICES.txt change from version bump |
||
|
|
ce82d65aa1
|
Revert "fix(core): set x-api-key alongside Authorization on Anthropic outbound (#4323) (#4342)" (#4385)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
This reverts commit
|
||
|
|
262a15e22e
|
fix(ci): resolve TS5055 release build failure since May 19 (#4383)
The nightly/preview release workflow has been failing for 3 days with `TS5055: Cannot write file ... because it would overwrite input file` in packages/core during the version bump step. Root cause: `npm install --package-lock-only` in version.js triggers the root `prepare` lifecycle, which re-runs `tsc --build` while packages/core/dist/ already exists from the initial `npm ci`. The unbuilt acp-bridge reference (added in #4295 but missing from build.js) corrupts TypeScript's incremental project graph resolution. Fixes: 1. Add --ignore-scripts to the lock-file-only install in version.js 2. Add packages/acp-bridge to the build order in build.js Closes #4368, closes #4339, closes #4307 |
||
|
|
d2ece83726
|
feat(skills): support priority field in SKILL.md for sorting skill display order (#4155)
* feat(skills): support priority field in SKILL.md for sorting skill display order
Closes #4136
* fix(skills): make /skills respect priority and treat unset as 0
- /skills was re-sorting alphabetically after listSkills(), masking the
new priority order. Drop the redundant sort and reuse the manager's
output directly.
- Treat missing priority as 0 instead of -Infinity so an explicit
negative priority (e.g. -1) sorts below unset skills, which matches
user intent.
* fix(skills): harden priority parsing and ordering
* fix(skills): warn when extension supplies invalid priority
Extension-provided skills bypass parseSkillContent / validateConfig, so a
non-number `priority` was silently normalized to 0 in the sort with zero
diagnostic. Match the SKILL.md author signal: warn at load time so the
extension author can see and fix the typo.
Addresses PR #4155 review (the extension-bypass-validation point).
* test(skills): direct unit tests for parsePriorityField and normalizeSkillPriority
Both helpers are exported but previously had no direct tests — coverage
came only via parseSkillContent and listSkills. Adds inputs the
integration paths can't surface cleanly: -0 / NaN / Infinity, numeric
strings, objects, arrays, and the boolean coercion regression that
motivated the strict typecheck.
Also adds a NOTE on parsePriorityField warning future contributors that
SKILL.md frontmatter parsing lives in two places (parseSkillContent here
and SkillManager.parseSkillContent), so any new field must be wired into
both — the same regression that previously hit whenToUse,
disable-model-invocation, paths, and priority. Full dedup of the two
parseSkillContent bodies is left as a follow-up refactor.
Addresses the remaining two [Suggestion] items from PR #4155 review.
* fix(skills): scope priority to /skills listing only
Earlier in this PR, `skill.priority` was mapped into `SlashCommand.completionPriority`
on both bundled and non-bundled skill loaders, so a high-priority skill
also bubbled up in the slash-completion menu and the `/help` custom-commands
tab. That was broader than intended — the design goal is for `priority:`
to control the `/skills` listing only, with everything else (typing `/`,
mid-input completion, `/help`) staying purely alphabetical so a skill
can't reorder built-in commands.
Changes:
- BundledSkillLoader / SkillCommandLoader: drop the
`completionPriority: skill.priority` mapping. Skill commands now have
no `completionPriority`, falling back to alphabetical+recency in the
shared completion comparator.
- Help.tsx: revert the per-group sort to `localeCompare` and remove the
`compareCommandsForHelp` helper. `/help` is again purely alphabetical
within each group.
- Tests:
- Both loader tests assert `completionPriority` is `undefined` when
a skill has a `priority` set, locking the non-leakage in.
- Help.test.tsx's "orders by completionPriority" case is replaced
with "orders alphabetically regardless of completionPriority", so a
future change that re-introduces the leak fails the test.
- Extension-skill validation also normalizes `skill.priority` to 0 (in
addition to the existing sort-time normalization) so downstream
consumers see a clean value matching the emitted warning.
Validation:
- 177/177 unit tests pass across the 5 affected test files
- core typecheck clean
- bundled CLI built (`npm run bundle`) and exercised via tmux E2E:
E1 /skills sorted by priority, E2 / completion menu unaffected,
E3 mid-input alphabetical, E4 invalid priority warns + skill loads,
E5 order stable across restart — all 5 pass.
* fix(skills): tag priority warning with calling module's namespace
`parsePriorityField` previously hardcoded `debugLogger.warn` from
skill-load, so a warning emitted from `SkillManager.parseSkillContent`
(project / user / bundled skills) was tagged `[SKILL_LOAD]` instead of
`[SKILL_MANAGER]`. Annoying for log filtering and slightly misleading
about which parse path actually surfaced the bad priority.
Added an optional `warn` callback parameter; the existing extension
call site keeps the default skill-load logger, while skill-manager
passes its own. Behavior is otherwise unchanged.
* docs(skills): correct priority scope description
Earlier doc said priority sorts "in /skills, slash-command completion,
and the /help custom commands view." After the scope-narrowing in
|
||
|
|
80895d540e
|
fix(core): deduplicate geminiChat recovery continuation text (#3966)
* fix(core): deduplicate geminiChat recovery continuation text
When a provider hits MAX_TOKENS and the model resumes via the recovery
loop, the continuation stream sometimes re-sends characters from the end
of the previous response as a context anchor. Without deduplication this
causes repeated Markdown tables/prose in the final history even if the
live UI suppresses them.
Add getRecoveryContinuationSuffix / findContainedRecoveryPrefixReplayLength
to strip the replayed prefix before appending the continuation parts.
Also include the last 1200 chars of the previous response in the recovery
prompt so the model can see where it left off.
Two new tests cover:
- exact suffix overlap (shared recovery suffix and continuation)
- contained tail anchor replay (Markdown table prefix replayed mid-text)
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): tighten contained-prefix recovery dedup to avoid prose loss
Address review feedback on PR #3966: the contained-prefix fallback in
geminiChat recovery dedup was too permissive — a 6-byte minimum plus a
4000-char lookahead window allowed common opener phrases ("In summary,",
"In conclusion,", "Here is the …") to silently strip legitimate
continuation text whenever they happened to coincide with any substring
in the previous turn. Silent loss is a worse failure mode than the
duplication we were fixing.
Constrain the fallback to its real intended use case — replayed
Markdown blocks that providers re-emit at the start of a recovery
continuation (table headers, headings, fenced code, lists, blockquotes):
- Require the continuation to *open* with a Markdown structural anchor
before considering any contained-prefix replay; plain prose openers
fall through with no dedup attempted.
- Restrict the substring search to the immediate truncation tail
(last 400 chars) so a coincidental match far above the truncation
point cannot win.
- Raise the contained-prefix byte floor (12 bytes) above the suffix-
overlap floor.
Also add coverage for the previously-untested guard branches
(empty input, full-overlap drop, empty previous-text path that skips
the <previous_response_suffix> block) and regression tests for the
prose-loss scenarios called out in review.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): handle leading whitespace in structural anchor + cover tail truncation
Address wenshao review on PR #3966:
- `startsWithMarkdownStructuralAnchor` now strips all leading whitespace
(`/^\s+/`) instead of only newlines (`/^\n+/`). Some providers re-emit
a recovered Markdown block with leading spaces or tabs, not just
newlines; the old regex caused the structural-anchor gate to fail and
the contained-prefix dedup path was silently skipped.
- Add a regression test for `buildOutputRecoveryMessage` that exercises
the `previousText.slice(-OUTPUT_RECOVERY_TAIL_CHARS)` truncation
branch with a 1300-char previous response, asserting the
<previous_response_suffix> block contains exactly the trailing 1200
chars and that the dropped head does not leak.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): unify plain-text predicate and harden recovery delimiter
Address two review concerns on geminiChat output-recovery:
- `isPlainTextPart` was a near-duplicate of `isValidNonThoughtTextPart`
with subtly weaker guards (missing thoughtSignature/inlineData/fileData
and using `!== true` vs `!part.thought`). Delegate to the shared
predicate so the recovery-merge and consolidated-history paths agree
on what counts as plain text.
- `buildOutputRecoveryMessage` embedded the previous response inside a
`<previous_response_suffix>` pseudo-XML block without sanitization. If
the model's own truncated output contained the literal closing tag
(e.g. while generating XML/HTML examples), the recovery prompt's
structure would break. Neutralize literal opening/closing delimiters
inside the tail with a zero-width space so the prompt always has
exactly one well-formed block; add a regression test that asserts the
delimiter pair count stays at 1/1 even when the tail contains a raw
`</previous_response_suffix>`.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(core): cover opening-tag branch of sanitizeRecoverySuffixTail
The existing prompt-recovery-delimiter-collision test only exercises
the closing-tag (`</previous_response_suffix>`) neutralization path.
Add a sibling test that emits a literal opening tag in the previous
model turn so the opening-tag replace branch is also covered. Asserts
exactly one opening/closing delimiter pair in the recovery message and
that the neutralized variant (with zero-width space) appears in the
embedded tail.
* docs(core): document recovery-dedup constants and tighten contained-prefix anchor
Address PR #3966 review polish items from wenshao:
- Add JSDoc rationale to each magic constant (OUTPUT_RECOVERY_TAIL_CHARS,
RECOVERY_OVERLAP_MAX_SCAN_CHARS, RECOVERY_OVERLAP_MIN_BYTES,
RECOVERY_STRUCTURAL_OVERLAP_MIN_BYTES) so future tuning is grounded.
- Make the contained-prefix scan symmetric: require the match inside
previousTail to begin at index 0 or immediately after a newline, mirroring
the structural-anchor check on the continuation side. All occurrences are
walked so a benign mid-paragraph hit doesn't shadow a real line-anchored
match later in the 400-char tail window.
- Document the suffix-anchored overlap loop's O(n^2) bound and the bounded
scan cap so the perf characteristic is explicit rather than reverse-
engineered.
- Explain why appendRecoveryContinuationParts always shifts the first
continuation text part even when the dedup suffix is empty (empty suffix
means a pure replay that must be discarded).
All 68 tests in geminiChat.test.ts still pass; typecheck and lint clean.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): scan recovery parts for plain-text + CJK-safe overlap floor
`appendRecoveryContinuationParts` previously only inspected the boundary
parts (last of previous, first of continuation). `processStreamResponse`
orders parts as `[thoughtPart?, ...consolidatedHistoryParts]`, so for
thinking models the first continuation part is the recovery turn's
thought — the plain-text predicate failed on it and the entire dedup
block was skipped, leaking the replayed overlap into durable history.
Now scan both sides for the plain-text anchor and splice the matched
text part rather than shifting the head. Allocate a fresh merged part
instead of mutating `mergedParts[i].text` in place so callers caching
part references never observe a half-merged turn.
Two additional hardening fixes on the overlap path:
- `isSignificantRecoveryOverlap` adds a 4-code-point floor on top of
the 6-byte floor for prose. CJK characters are 3 UTF-8 bytes each,
so the byte-only floor admitted 2-character coincidences like
"我们" / "但是" that recur constantly across unrelated Chinese
turns. The structural-anchor branch is exempted (those collisions
are far rarer and the structural floor already governs them).
- `findContainedRecoveryPrefixReplayLength` now strips leading
whitespace from the continuation before matching. The structural-
anchor check already tolerated leading spaces/tabs (some providers
re-emit replayed blocks with extra indentation), but the substring
scan still used the un-trimmed prefix and silently failed to match
the corresponding `previousTail` occurrence.
Adds three regression tests covering: a thinking-model recovery
continuation whose first part is a thought, a 2-CJK-character
coincidence that must NOT be dedup'd, and a leading-whitespace
structural replay that must be dedup'd.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(core): cover recovery-dedup line-boundary + normalization branches
Add JSDoc to getRecoveryContinuationSuffix calling out that its empty-input
guard is defensive-only (the production caller already filters both sides),
and document appendRecoveryContinuationParts' implicit coupling with
processStreamResponse's text-part consolidation plus its return-shape
convention that coalesceRecoveryPairs relies on for multi-iteration recovery.
Add two regression tests:
- mid-paragraph match rejection: a structural anchor that appears in the
previous tail but is not preceded by a newline must NOT trigger the
contained-prefix strip, so legitimate continuation survives verbatim.
- newline-normalization branch: when the replayed prefix ends with \n but
the previous tail does not and the suffix does not start with \n, the
helper must insert a separator so the coalesced text keeps its block
boundary.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): tighten table-row anchor + document structural-class scope
Tightens `startsWithMarkdownStructuralAnchor`'s table-row alternation so a
bare `|expression|` (2 pipes) in technical prose no longer qualifies as a
Markdown block anchor — real GFM table rows have ≥3 pipes (≥2 cells) or a
separator row like `|---|`. Without this, prose continuation starting with
a 2-pipe expression that re-appears at a line boundary mid-tail of the
previous response would be silently stripped by the contained-prefix path,
contradicting the JSDoc's stated invariant that "incidental `|` characters
in prose do not count."
Also adds an inline comment to `isSignificantRecoveryOverlap` documenting
why the structural-class detection (`[#|`\n]`) is intentionally loose —
the 2-byte gap between the 4-byte structural floor and the 6-byte prose
floor only matters for 4–5 byte fragments that coincide on both sides of
a truncation boundary, which is far rarer than the structural-replay
scenarios the lower floor exists to catch.
Adds a regression test asserting that a continuation opening with
`|expression| ...` is left intact even when it matches at a line boundary
in the previous tail.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(core): pin recovery thought-before-text ordering
Adds a regression test for @tanzhenxin's review comment: the existing
`prompt-recovery-thinking-continuation` test only asserts joined non-
thought text, so a regression where the recovery turn's leading thought
ends up *after* the merged text part slips through. The new test
explicitly asserts `thoughtIdx < mergedTextIdx` in the final history
entry.
Thinking-model providers (Gemini 2.5+, Anthropic, OpenAI o-series)
validate thought-signature provenance and expect a thought to precede
the content it generated; without an ordering assertion the dedup path
could silently violate that invariant.
The new test fails on the current implementation
(`appendRecoveryContinuationParts` appends the leftover leading thought
at the end of the part list). Fix follows in a separate commit so the
red → green transition is reviewable.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): keep recovery thought before merged text part
The recovery dedup path in `appendRecoveryContinuationParts` previously
spliced only the matched continuation text part out of `nextParts` and
appended the leftover parts (including any leading thought) after the
merged text. For thinking-model providers (Gemini 2.5+, Anthropic,
OpenAI o-series) that validate thought-signature provenance, this
violated the invariant that a thought precedes the content it
generated: durable history ended up as `[..., previousText + suffix,
recoveryThought]`, with the recovery turn's thought trailing its own
text.
Hoist any non-text parts that preceded the matched text on the
continuation side (typically the recovery turn's thought) into
`mergedParts` directly before the merged text part. Trailing non-text
parts (tool calls etc.) keep their position via the final concat.
Existing `prompt-recovery-thinking-continuation` test still passes
because it only asserts joined non-thought text; the new
`...-order` test now passes as well.
Reported by @tanzhenxin in PR review on commit
|
||
|
|
64401e1d17
|
feat(telemetry): support custom resource attributes and add metric cardinality controls (#4367)
* feat(telemetry): support custom resource attributes and add metric cardinality controls Resolves #4365. Adds two coupled OpenTelemetry capabilities to make qwen-code's telemetry production-ready in multi-team / multi-tenant deployments: 1. Custom resource attributes via standard `OTEL_RESOURCE_ATTRIBUTES` and `OTEL_SERVICE_NAME` env vars and a new `telemetry.resourceAttributes` setting. Operators can now tag every span / log / metric with `team`, `env`, `cost_center`, or anything else their backend needs. 2. Metric cardinality controls. `session.id` is moved off the OpenTelemetry Resource (where it auto-attached to every metric data point and caused unbounded time-series fan-out on Prometheus / ARMS Metric / etc.) and gated behind a new opt-in `telemetry.metrics.includeSessionId` toggle. Spans and logs still carry `session.id` for trace and log correlation. Reserved keys (`service.version`, `session.id`) are stripped from both env and settings sources with a `diag.warn`. `OTEL_SERVICE_NAME` follows the OTel spec precedence (highest priority for `service.name`). Settings JSON values are runtime-coerced to strings as defense against hand-edited non-conforming JSON. Breaking change: metrics no longer carry `session.id` by default. Operators who need it can restore the previous behavior with `QWEN_TELEMETRY_METRICS_INCLUDE_SESSION_ID=true` or `telemetry.metrics.includeSessionId: true` in settings.json; recommended only for short-term debugging since it re-introduces the cardinality problem. For long-term session-level analysis, prefer trace and log backends which handle per-event data without cardinality pressure. Design doc: docs/design/telemetry-resource-attributes-design.md 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * docs(telemetry): align reserved-key descriptions with implementation Round 1 review fixes (#4367). After session.id was added to RESERVED_RESOURCE_ATTRIBUTE_KEYS in Codex review, four user-facing descriptions still claimed only service.version was reserved: - packages/core/src/telemetry/config.ts (merge comment) - packages/core/src/config/config.ts (TelemetrySettings JSDoc) - packages/cli/src/config/settingsSchema.ts (schema description) - packages/vscode-ide-companion/schemas/settings.schema.json (regenerated) Also corrects scope claim: resource attributes apply to every signal the SDK exports (OTLP and file outfile share the same Resource), not just OTLP. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * docs(telemetry): clarify warning destination and surface percent-encoding hint Round 2 self-review fixes (#4367). Two small but real UX gaps: 1. Reserved-key / malformed-pair / coerce warnings route to the debug log (per #3986), not the console — so a user who types `OTEL_RESOURCE_ATTRIBUTES=service.version=2.0` sees no feedback that the value was silently dropped. Adds a "Troubleshooting" section in telemetry.md telling users where to look, and a note in the parser docstring documenting where warns go. 2. A literal (unencoded) comma in an env var value is a common foot-gun: the parser splits on it, producing a malformed second half that is silently dropped. Updates the warn text to include a "hint: percent-encode literal commas as %2C" callout, and adds the same guidance to the docs. Deferred to a follow-up: startup-time stderr summary of dropped attributes. Stderr during TUI render could break Ink rendering, so the right surface needs separate design. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * test(telemetry): cover first-`=` split contract in OTEL_RESOURCE_ATTRIBUTES parser Per review feedback on #4367. The parser uses `indexOf('=')` so the first `=` separates key and value while subsequent `=` stay in the value. The behavior was correct but untested; a future refactor to `split('=')` would silently break base64-padded, JWT, or connection-string values. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * feat(telemetry): tighten resource-attribute input validation + startup summary Adopts review feedback from #4367 (wenshao via Qwen Code /review). Five accepted suggestions, bundled because they all touch the same parse/coerce/strip pipeline: 1. Key percent-decoding (CRITICAL). `parseOtelResourceAttributes` now percent-decodes both keys and values per the OTel / W3C Baggage spec. Without this, `OTEL_RESOURCE_ATTRIBUTES=service%2Eversion=99` lands on Resource as the literal key `service%2Eversion`, bypassing the reserved-key filter; a collector that decodes keys downstream could then resurrect `service.version` and spoof the version label. 2. Startup summary of dropped attributes. Every `diag.warn` in resource-attributes.ts routes only to the OTel debug log (per #3986), giving operators zero feedback when their attributes are silently dropped. Helpers now optionally accumulate diagnostics into a `ResourceAttributeWarnings` array; the resolver collects them and the SDK emits a one-time console summary at init (before Ink renders, so no TUI conflict). 3. `||` instead of `??` for service.name fallback. Settings can put an empty string through `??`, producing a blank `service.name` that some backends reject. `||` falls through to the default. 4. `coerceStringResourceAttributes` now trims keys and skips empty/whitespace-only keys, matching `parseOtelResourceAttributes`. Previously `{" ": "x"}` or `{"team ": "y"}` from settings.json would land as malformed Resource attributes. 5. `OTEL_SERVICE_NAME` is trimmed before the truthy check, so values like `' '` or `'\t'` are treated as unset rather than producing a whitespace-only service name on Resource. One suggestion declined (in-thread reply on PR): - "Redundant `?? {}` in sdk.ts:160" — intentional defense-in-depth for `vi.mock('../config/config.js')` callers in `telemetry.test.ts` where auto-stub returns undefined. The reviewer is right that production code paths never hit it, but tests do. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * fix(telemetry): trim whitespace-only service.name + add invalid-key-encoding test Adopts two review suggestions on #4367 (wenshao via Qwen Code /review): 1. `service.name` fallback uses `.trim() || SERVICE_NAME` instead of plain `||`. Plain `||` lets whitespace-only values (`" "`, `"\t"`) through as truthy, producing a blank service name on Resource that some backends reject. Both settings (no value trimming) and env (`%20` decodes to `" "`) can deliver such values. Test added. 2. Adds `key%ZZ=val` to the parameterized parser test to cover the invalid-percent-encoding-on-key catch branch. Previously only the value-side catch was tested. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) |
||
|
|
59d5c1f16f
|
fix(core): handle MiMo tool-result media (#4281) | ||
|
|
d59c9e7b77
|
feat(installer): add standalone hosted install and uninstall flow (#3828)
* feat(installer): add standalone archive installation * fix(installer): harden standalone archive installs * fix(installer): address standalone review findings * chore(installer): clarify review followups * fix(installer): stabilize standalone script checks * chore(installer): remove internal planning docs * chore(installer): simplify standalone release review fixes * test(installer): add Windows batch install smoke * test(installer): fix Windows batch smoke quoting * test(installer): preserve Windows cmd quotes * fix(installer): use robust Windows checksum hashing * ci: narrow installer debug matrix * fix(installer): address standalone review hardening * fix(installer): avoid Windows validation parse errors * fix(installer): simplify Windows option validation * fix(installer): harden standalone review fixes * feat(installer): publish release installer assets * fix(installer): address release asset review feedback * fix(installer): avoid prerelease installer asset links * test(installer): isolate standalone dist fixture * feat(installer): add hosted install release alias * chore: no changes - code review requested Agent-Logs-Url: https://github.com/QwenLM/qwen-code/sessions/38467aec-15b9-4b76-9139-0b2cfe40477a * fix(installer): pin versioned installer assets * fix: parallelize Node.js binary downloads in standalone release build Use Promise.all instead of sequential for...of+await for the 5 independent Node.js runtime downloads, reducing CI release build time by ~4-5x. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(installer): address release asset review followups * refactor(installer): share release CLI parsing * fix(installer): address release asset review followups - sh: reject CR/LF in archive entry names before the literal `..` glob so a `..\r` entry cannot bypass path validation. - bat: prefer Tls12+Tls13 in PowerShell helpers, fall back to Tls12 alone on older .NET Framework where the Tls13 enum is missing. - bat: document the implicit `:ValidateOptions` dependency next to the qwen.cmd wrapper writer so loosening the validator stays a conscious choice. - build-standalone-release: surface the `xz-utils` host requirement for Linux Node downloads in `--help`. - release-script-utils: support `--key=value` form in `parseCliArgs`. - tests: cover the new CRLF message, TLS string, and `--key=value` parsing; register process-level signal/exit handlers in `ensureMinimalDist` so a crashed test still restores `dist/`. * fix(installer): unblock Windows CI for standalone install path Three CI failures and a few review followups in one pass. - ensureMinimalDist places its dist/ backup beside dist/ instead of under os.tmpdir(). On Windows GitHub runners the workspace lives on D: while os.tmpdir() is on C:, so renameSync raised EXDEV for every test that needed to swap dist/ in. - create-standalone-package.js and the matching test fixture build win-x64 zips with [IO.Compression.ZipFile]::CreateFromDirectory. Compress-Archive emits backslash entry names that the .bat installer's path-traversal guard then rejected, so every freshly built archive failed the standalone install path on Windows. - :ValidateArchiveContents normalizes entry separators to '/' before checking for '..', absolute paths, and drive prefixes - archives from any Windows zip tool still install while real traversal entries remain rejected. - createWindowsTraversalStandaloneArchive runs PowerShell via -File instead of a single -Command line; the joined-with-'; ' form had a function definition the runner's PowerShell refused to parse. Drive-by review followups: - replaceRequired uses replaceAll so a future duplicate placeholder cannot silently keep the trailing copy as 'latest'. - :ValidateOptions runs the unsafe-character check on SOURCE alongside the other variables. - build-installation-assets.js drops a dead INSTALLATION_ASSETS re-export; consumers already import from release-asset-config.js. - .gitignore covers the new sibling .qwen-dist-backup-* directory. * fix(installer): address release asset review findings * fix(installer): keep installer entrypoint hosted * fix(installer): reject stale hosted assets * fix(installer): refine hosted asset staging * fix(installer): tighten hosted default-version check, flag legacy URL - Replace the loose `latest` fragment check with per-format regex patterns in HOSTED_INSTALLER_DEFAULT_VERSION_PATTERNS so an unrelated occurrence of `latest` (comment, help text) cannot satisfy the staging guard. The patterns still tolerate whitespace variation, only the default-version assignment itself must be intact. - Add a "Hosted endpoint status" callout in INSTALLATION_GUIDE.md before the curl examples. The documented `--version` flow does not work against the OSS URL today because it currently serves the legacy NVM-based installer; the callout points users at a local checkout until the next release sync. - Tests: drop `latest` from the fragments equality assertion, add positive and negative regex coverage, add a failure-path case for sources whose default version is not `latest`, and pin the new guide markers so the callout cannot silently disappear. * feat(installer): verify installation release assets Adds `npm run verify:installation-release` and wires it into the release workflow after `Build Standalone Archives`, so a broken release directory fails CI before publishing. Local mode (`--dir PATH`) checks: - All five `qwen-code-{platform}.{ext}` standalone archives exist. - `SHA256SUMS` covers exactly those five — missing or unexpected entries fail. - Each archive's actual SHA256 matches its `SHA256SUMS` entry. Remote mode (`--base-url URL`) checks: - `SHA256SUMS` is downloadable, parseable, and contains exactly the expected archive entries. - Each archive URL is reachable via HEAD, with a 1-byte ranged GET fallback for hosts that disable HEAD. Hosted installer scripts (`install-qwen.sh` / `install-qwen.bat`) are intentionally out of scope here — they are served from the hosted endpoint prepared by `package:hosted-installation` (PR #3853), not from the GitHub Release surface this verifier targets. * fix(installer): tighten verifier base-url + clarify test helper Three small refinements from the second review pass: - normalizeHttpsBaseUrl rejects everything except https, since real release URLs are always HTTPS. Accepting http previously would let an operator silently target a stale or attacker-controlled mirror. - Drop EXPECTED_RELEASE_ASSET_NAMES from the public exports; it was only used internally for the verification log line. - Rename the test helper standaloneChecksumContent to placeholderChecksumContent and document that the hashes in its output are placeholders — the remote verifier does not download archives or compare hashes, it only validates that SHA256SUMS lists the expected names and that each archive URL is reachable. The non-https rejection test now also covers `http://` in addition to the existing `file://` case. * fix(installer): address standalone review follow-ups * fix(installer): repair Windows installer tests * fix(release): tighten standalone asset checks * fix(installer): stabilize Windows managed install checks * test(installer): relax Windows installer timeout * fix(test): escape release asset regex * test(cli): avoid POSIX node path in relaunch test * fix(installer): align npm fallback node gate with engines * test(installer): allow Windows archive validation more time * fix(installer): remove stale node 20 installer references * docs(installer): clarify hosted endpoint sync requirement * refactor(installer): reuse standaloneArchiveName in release verifier The verify-installation-release script was duplicating the archive name derivation logic with a hardcoded ternary instead of reusing the standaloneArchiveName helper from build-standalone-release. Export the helper and import it so the extension mapping lives in one place. * fix(scripts): address release verifier review feedback * feat(installer): add standalone archive installer with multi-platform release workflow - Add standalone archive installer (bat/sh) that downloads platform binaries from GitHub/Aliyun without requiring Node.js or npm on the target machine - Add fork-friendly release-test workflow for manual GitHub Release creation covering all 5 platforms (darwin-arm64/x64, linux-arm64/x64, win-x64) - Add OSS upload/mirror tools for staging and release distribution - Update .gitignore to exclude generated build artifacts (release-staging/, hosted-staging/) - Fix Windows PowerShell test command in copy-release-to-latest tool * feat(installer): support QWEN_INSTALL_GITHUB_REPO env var for custom repo * chore(installer): exclude local-only staging tools from PR The tools/ directory contained personal staging-OSS upload helpers (upload-staging, upload-release-mirror, copy-release-to-latest, test-upload-one) that should not ship in the public PR. They reference a personal staging bucket and only exist to validate the installer end-to-end before production release. Removes them from git tracking via `git rm --cached` (files stay on disk for the author's local use) and adds /tools/ to root .gitignore so they cannot be re-added accidentally. No runtime / installer code change. Production CI on ubuntu-latest is unaffected. * fix(installer): enforce CRLF line endings for .bat files via gitattributes cmd.exe requires CRLF in batch scripts; the global eol=lf was causing every line to be misparsed on Windows, producing errors like 'QWEN_VALIDATE_METHOD=detect is not recognized as a command'. * fix(installer): store .bat files with CRLF in git blob for raw GitHub downloads GitHub raw file serving bypasses gitattributes eol conversion and serves blob bytes directly, so eol=crlf alone was not enough. Use -text to disable normalization and commit with actual CRLF so raw downloads work on Windows. * fix(installer): follow HTTP redirects in UrlExists and RaceMirrorHead probes GitHub release asset URLs return HTTP 302 to objects.githubusercontent.com. [Net.WebRequest] with HEAD does not auto-redirect by default, so the existence check and mirror-race probe both incorrectly reported the file as missing. Set AllowAutoRedirect=true on HttpWebRequest instances. * fix(installer): surface download errors and add MaximumRedirection 10 * feat(installer): add hosted install-qwen.ps1 shim for irm|iex one-liner The previous Windows quick-install one-liner used `Invoke-WebRequest -OutFile (Join-Path $env:TEMP 'install-qwen.bat'); & (Join-Path …)`. When pasted into a narrow terminal, line wrap could land on `-OutFile`, orphaning the parameter from its value and producing the "missing argument for OutFile" failure followed by a "file not found" when the second `&` ran. PowerShell's line continuation rules cannot resolve this for parameter-name-at-EOL. Add `install-qwen.ps1` as a thin hosted entrypoint that downloads `install-qwen.bat` into TEMP, runs it, and cleans up. Documented one-liner becomes the standard pattern used by bun, uv, scoop, deno, pnpm: powershell -ExecutionPolicy Bypass -c "irm <url>/install-qwen.ps1 | iex" The `.bat` remains the source of truth for installer behavior; `.ps1` is just the modern hosted entrypoint. Version pinning via `$env:QWEN_INSTALL_VERSION` flows through unchanged. Stored with `*.ps1 -text` so CRLF survives both GitHub raw and OSS uploads, matching the existing `.bat` handling. * fix(installer): stage direct hosted install scripts * chore(installer): trim hosted release diff scope * chore(installer): narrow hosted release diff * feat(installer): restore hosted PowerShell entrypoint * chore(installer): stage standalone hosted entrypoints * fix(installer): address hosted installer review followups * fix(installer): stabilize Windows installer tests * fix(installer): make Windows option validation readable * feat(installer): wire Aliyun OSS sync, address review followups - Add Aliyun OSS sync steps to release workflow: package hosted assets, install pinned ossutil, configure credentials, upload versioned and latest paths, and verify upload via verify:installation-release plus curl probes against the hosted installer endpoint. - Document required production-release environment secrets and bucket variables in INSTALLATION_GUIDE.md. - Restructure hosted endpoint guidance to lead with the pre-sync warning, splitting "Run today" (local checkout) from "After the OSS sync" (hosted one-liners) so users no longer copy a one-liner that silently installs latest. - Distinguish mirror auto-selection timeout from successful selection in install-qwen-standalone.sh and install-qwen-standalone.bat: emit a "timed out; defaulting to github" log instead of pretending the HEAD probe picked github. - Support QWEN_INSTALLER_BAT_URL override (https only) in the PowerShell shim so staging mirrors can be exercised without forking the file. - Strip a leading UTF-8 BOM in verify-installation-release.js parseSha256Sums so BOM-prefixed SHA256SUMS reports a useful "Missing checksum entry" error instead of "Malformed SHA256SUMS line 1". - Add tests for verifier HEAD→Range fallback, partial-failure formatting, all-failure wording, and BOM tolerance. * ci(installer): add temporary OSS smoke test * fix(installer): make OSS release assets public-readable * chore(installer): remove temporary OSS smoke workflow * fix(installer): address hosted installer review gaps * feat(installer): refactor argument parsing and utility functions for release scripts * fix(installer): harden hosted release script checks * fix(installer): suppress PowerShell progress bar in hosted entrypoint shim Add $ProgressPreference = 'SilentlyContinue' to the .ps1 wrapper so Invoke-WebRequest downloads don't render a progress bar when invoked via the irm | iex one-liner. * fix(installer): suppress PowerShell progress bar in bat installer downloads Add $ProgressPreference = 'SilentlyContinue' to DownloadFile so the full-screen progress UI does not appear during archive downloads in interactive PowerShell sessions, consistent with the .ps1 shim. * fix(installer): use curl.exe -# progress bar in Windows downloads Prefer curl.exe with -# (hash-mark progress bar) for archive and installer downloads on Windows 10+. Falls back to Invoke-WebRequest (which shows its own progress bar) when curl.exe is unavailable. Matches the approach used by code-server (curl -#fL) and bun.sh (curl.exe -#SfLo). * fix(installer): suppress progress bars for small downloads and Expand-Archive - .ps1: replace curl.exe -# with silent mode, suppress Invoke-WebRequest progress bar; save/restore $global:ProgressPreference - .bat: add $ProgressPreference = 'SilentlyContinue' before Expand-Archive to prevent full-screen extraction progress UI - .sh: remove --progress-bar / --show-progress from download_file, always use silent curl/wget * fix(installer): auto-backup non-qwen directories and simplify output - ensure_managed_install_dir / :EnsureManagedInstallDir now back up non-qwen directories instead of refusing to install, so users upgrading from npm or old installers don't hit a hard error - Simplify header/footer output: remove banner bars, verbose INFO lines, and redundant "Installation completed!" message - Match bun.sh / code-server style: minimal, to the point * fix(installer): revert Expand-Archive progress suppression in bat The inline $ProgressPreference = 'SilentlyContinue' caused a cmd.exe parsing error ("此时不应有 >") on Chinese Windows. Revert to the original Expand-Archive invocation. * fix(installer): fix cmd.exe parsing error in backup fallback code The %s in the for /f fallback command string was interpreted as a variable reference by cmd.exe, causing "此时不应有 >" on Chinese Windows. Replace with a safe fallback and re-enable Expand-Archive progress suppression. * fix(installer): always persist install bin to user PATH Previously MaybeUpdateUserPath was only called when shadow qwen executables were detected. When no shadow was found, the PATH update was skipped entirely, leaving the user without qwen on PATH after restarting their terminal. Now always persist the bin directory to PATH (unless --no-modify-path is set), regardless of whether other qwen installations exist. * fix(installer): persist PATH to current terminal session on Windows Use the `endlocal & set` trick (same as bun/Rust installers) to export the install bin directory from the setlocal scope to the current cmd session. qwen is now usable immediately without restarting the terminal. * docs(installer): document cmd.exe one-liner for immediate PATH availability Add curl-based one-liner for cmd.exe users. Running the .bat directly in the current cmd session makes `qwen` available immediately via the `endlocal & set` trick. The `powershell -c "irm | iex"` path creates a child process so PATH changes cannot propagate to the parent. * feat(installer): make qwen usable immediately from PowerShell after install - .ps1: detect parent process, update current session PATH, and for cmd.exe parents emit a `set PATH=...` command - .bat: skip final instructions when called from PowerShell to avoid duplicate "Run: qwen" output * fix(installer): remove non-functional doskey approach for cmd parent doskey /exename from a child PowerShell process cannot modify the parent cmd.exe session. Replace with a simple set PATH=... command that the user can copy-paste. * fix(installer): make Windows standalone shim available in cmd * feat(installer): add standalone uninstall scripts * fix(uninstall): match shell-quoted paths when removing the wrapper The installer's write_unix_wrapper shell-quotes the binary path, so paths containing single quotes (or other shell metacharacters) appear as shell-quoted strings in the generated wrapper file. The uninstall script's literal grep -qF missed these, leaving the wrapper orphaned. Add shell_quote to the uninstall script and match against both the raw and shell-quoted forms before removing the wrapper. * fix(installer): update download commands to use progress indicators for curl and wget * fix(installer): resolve Aliyun latest via version pointer * fix(installer): cleanup mirror probe temp dirs * fix(installer): harden standalone release fallback * fix(installer): address standalone review feedback * style(installer): align standalone install output * fix(installer): print standalone uninstall commands * fix(installer): address release review follow-ups * fix(installer): harden Windows target detection * test(installer): stabilize Windows fake tool path * fix(installer): allow explicit Windows curl path * test(installer): use cmd fake curl on Windows * test(installer): cover Windows fake curl helper * test(installer): inject Windows arch overrides in cmd * test(cli): wait for prompt suggestion render * test(cli): revert prompt suggestion wait tweak * fix(installer): harden hosted release publishing * fix(installer): harden Windows latest pointer parsing * fix(installer): bound Windows download timeouts * fix(installer): bound hosted installer probes * fix(release): make ossutil download configurable * fix(installer): address hosted release review feedback * test(installer): keep dist backup on same filesystem * fix(installer): address remaining review feedback on PR #3828 - Remove REQUIRE_CHECKSUM dead code, always hard-fail on checksum issues - Add JSDoc to HOSTED_INSTALLER_BEHAVIOR_PATTERNS explaining its purpose - Add credential cleanup trap for ossutilconfig in release workflow - Add 3-attempt retry with exponential backoff for OSS uploads - Tighten findstr SOURCE regex to require leading letter * fix(release): correct OSS credentials lifetime and mirror probe fallback - release.yml: remove `trap EXIT` inside the Configure step; it deleted ${RUNNER_TEMP}/.ossutilconfig as soon as the configure shell exited, so every subsequent step (publish/sync/verify) lost the credentials. Move credential cleanup to a final `if: always()` step at the job tail. - install-qwen-standalone.sh: drop the predictable PID-based mktemp -d fallback in race_mirror_head; if mktemp fails, return "github" instead of using /tmp/qwen-mirror.$$ which a local attacker could pre-create to bias mirror selection. * fix(installer): address review feedback round 2 Workflow: - Move 'Publish Aliyun OSS Latest VERSION' to run after the hosted installer assets are uploaded and verified, so the latest/VERSION pointer only flips once every release artifact is in place. Previously a hosted-sync failure could leave the pointer ahead of the actual installer scripts. upload-aliyun-oss-assets.js: - Replace `spawnSync('sleep', ...)` retry backoff with an Atomics.wait-based cross-platform sleep so retries also work on Windows runners. install-qwen-standalone.bat: - :DetectTarget no longer emits TARGET=win-arm64 because RELEASE_TARGETS has no win-arm64 archive; ARM64 hosts now fall through to the unsupported-arch branch and (in detect mode) get the npm fallback instead of a 404. - Add QWEN_INSTALL_CURL_EXE to :ValidateRawEnvironmentOptions so this curl override is checked for shell metacharacters like every other knob. - Replace `call echo %%i>>...` with plain `echo %%i>>...` when capturing pre-install qwen.cmd paths; `call` triggered an extra parse pass that could interpret &/|/<,>/etc. inside a directory name as command separators. - Add `--retry 2` to curl.exe downloads (`:DownloadFile` / `:DownloadFileQuiet`) to match the shell installer. - Include expected vs actual hash in the checksum-mismatch error message. install-qwen-standalone.ps1: - Stage the downloaded installer at a cryptographically random temp path (`qwen-installer-<random>.bat`) so a same-user attacker cannot pre-stage a malicious .bat at a predictable path and race the verify/execute window. - Atomically install the current-session cmd shim by writing to a sibling `.new` temp file then renaming, so a partial write cannot leave a half-written shim on PATH. - Add `--retry 2` to the curl.exe download path. - Include expected vs actual hash in the checksum-mismatch error message. install-qwen-standalone.sh: - Include expected vs actual hash in the checksum-mismatch error message. uninstall-qwen-standalone.ps1: - Accept `-Purge` and `-Help` parameters; previously every CLI flag was silently dropped, so users running with `-Purge` got no purge and no error. `-Purge` maps to `QWEN_UNINSTALL_PURGE=1`. uninstall-qwen-standalone.sh: - `remove_install_wrapper` additionally requires the wrapper file to start with a `#!` shebang before it deletes it; a user-authored script that just happens to mention the install path now stays untouched. verify-installation-release.js, build-hosted-installation-assets.js: - Include expected vs actual hash in the checksum-mismatch error messages. scripts/tests/install-script.test.js: - Update assertions for the new error wording, the curl `--retry 2` flag, the dropped ARM64 detection, and the new release-step ordering. * fix(installer): address review feedback round 3 Workflow: - Configure Aliyun OSS Credentials: write the ossutil config file directly with restricted umask instead of invoking `ossutil config -k <secret>`. Passing the access-key secret via argv made it visible in /proc/<pid>/cmdline for the lifetime of that step; writing the INI file in-process keeps the secret out of the process table. upload-aliyun-oss-assets.js: - Upload assets in parallel with `Promise.all` + async `spawn` instead of a sequential `spawnSync` loop. Each asset keeps its own retry budget; failures are aggregated so one flaky upload does not mask a separate failure. - Replace the bespoke `Atomics.wait` retry sleep with `timers/promises#setTimeout` now that the loop is async. INSTALLATION_GUIDE.md: - Drop the misleading "instead of overwriting the global installation/ entrypoint objects" sentence; the workflow has always also refreshed the global versionless objects so curl|bash links keep resolving without a version segment. Document the rollback story instead. * test(installer): add parseUploadArgs unit tests and align verify derivation - scripts/tests/upload-aliyun-oss-assets.test.js: cover --help short-circuit, required-option validation (--bucket/--config/--prefix/empty assets), unknown options, missing option values, and trailing-slash prefix normalization. - scripts/verify-installation-release.js: switch the win-only zip branch from `startsWith('win-')` to the strict `=== 'win-x64'` check used by build-standalone-release.js, and add a comment recording that the two derivations must stay aligned. Without this the helpers would diverge the moment a non-x64 win target gets added. * test(installer): add uploadAssets integration tests with fake ossutil Add two integration tests that route a temp-directory ossutil shim onto PATH so uploadAssets actually spawns the real binary with the real cp argv: - happy-path test asserts the destination URI, `-c <config>`, `--acl public-read`, and per-asset cp invocations land for both inputs. - failure-path test asserts non-zero ossutil exits surface as an aggregate `asset uploads failed` error after the retry budget runs out. * revert(installer): drop over-engineered ossutil/upload changes Roll back two changes from a1ef8697b/0a5d308c9 that were not justified by the actual threat model or release-pipeline needs: - .github/workflows/release.yml: restore the supported `ossutil config -k` invocation. The earlier switch to writing the .ossutilconfig INI file in-process was meant to keep the access-key out of /proc/<pid>/cmdline, but GitHub-hosted runners are single-tenant ephemeral VMs where no other user can read that namespace. The benefit was theoretical; the cost was taking on a brittle dependency on ossutil's undocumented config format. - scripts/upload-aliyun-oss-assets.js: revert the uploadAssets parallel rewrite (Promise.all + spawn + setTimeout) back to the original sync spawnSync loop with retry. Release-time uploads of ~6 small files do not need parallelism, and the async refactor changed the public contract (sync→async) for no real wall-clock win. Kept from those commits: - The cleanup `if: always()` step that removes RUNNER_TEMP/.ossutilconfig at the end of the publish job. - The cross-platform sleepSync(ms) helper, since `spawnSync('sleep', ...)` still does not work on Windows runners. - The INSTALLATION_GUIDE.md doc fix. - All other round-2 fixes. Test assertions updated for the restored sync uploadAssets contract. * test(installer): cover Windows release script regressions * test(release): avoid Windows shim lookup in oss upload tests * test(installer): use stable fake Aliyun version on Windows * fix(installer): parse Aliyun latest version in batch * fix(installer): validate Aliyun latest version without findstr * fix(installer): normalize Aliyun latest version via PowerShell * fix(installer): avoid captured PowerShell output in batch latest parsing * fix(installer): normalize Aliyun latest pointer from file * test(installer): fix fake Windows curl output parsing * fix(installer): print checksum path on miss, gate hardcoded version pin in ps1 [skip ci] Address two narrow follow-ups from PR #3828 review: - build-hosted-installation-assets.js: add a HOSTED_INSTALLER_FORBIDDEN_PATTERNS guard for install-qwen-standalone.ps1. The ps1 shim has no VERSION variable of its own (it forwards @args to the .bat), so the existing default-version positive-match patterns don't apply. The new guard fails the build if a $env:QWEN_INSTALL_VERSION assignment or a --version flag prepended to the forwarded argument list ever lands in the shim. Patterns are line-anchored with /m so the documented usage examples in the header docstring stay valid. Two vitest cases cover the reject and allow paths. - install-qwen-standalone.sh / .bat: include the searched checksum-file path in the "SHA256SUMS not found" error. Operators triaging --archive failures could not tell from the prior message whether the fallback path (next to the archive) or the remote URL was being looked up. Existing test assertions updated to match the new wording. Local validation: npm run test:scripts -> 160 passed | 9 skipped (was 158 | 9). * fix: stamp release version in hosted installers and add Zip Slip protection [skip ci] 1. The hosted installation asset build now accepts --version and stamps it into the copied .sh/.bat installers so they default to the tagged release version instead of 'latest'. The release workflow passes the version. 2. install-qwen-with-source.bat now validates archive entries before calling Expand-Archive, rejecting paths with '..', leading '/', drive-rooted paths, empty names, or control characters — matching the protection already present in install-qwen-standalone.bat and the .sh installer. * fix(installer): add SOURCE to PowerShell unsafe-character validation [skip ci] The SOURCE variable is user-provided and used in path operations but was not included in the :ValidateOptions unsafe-character check. Add it alongside the other validated variables. * fix: correct copyright year 2025 -> 2026 in new files [skip ci] --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: yiliang114 <effortyiliang@gmail.com> |
||
|
|
b58fe19c3a
|
feat(telemetry): Phase 2 — tool.blocked_on_user + hook spans (#3731) (#4321)
* feat(telemetry): Phase 2 — tool.blocked_on_user + hook spans Adds two OTel span types under the existing hierarchical session-tracing infrastructure (#3731 Phase 2; depends on Phase 1 #4126 and Phase 1.5 #4302): 1. `qwen-code.tool.blocked_on_user` — brackets the time a tool spends in awaiting_approval waiting for the user. Child of the tool span. Records decision (proceed_once / proceed_always / cancel / aborted / auto_approved) and source (cli / ide / hook / auto / system). Status stays UNSET — waiting is neither OK nor ERROR. 2. `qwen-code.hook` — wraps each pre/post-hook fire site so a slow hook can be told from a slow tool. Records hook_event (PreToolUse / PostToolUse / PostToolUseFailure), tool_name, shouldProceed, shouldStop, blockType, hasAdditionalContext. Status stays UNSET on intentional blocking decisions; ERROR only when the hook itself throws. To make blocked_on_user a child of the tool span, the tool span lifecycle moved from `executeSingleToolCall` to `_schedule`'s validating-loop — covering validating → awaiting_approval → executing in one span. Two new private Maps on CoreToolScheduler hold span refs across method boundaries (callId-keyed). Centralized cleanup via `finalizeToolSpan` / `finalizeBlockedSpan` private helpers ensures every terminal status path also ends the corresponding span. Eight terminal sites now finalize the tool span: signal.aborted at loop entry, hard deny, plan-mode block, non-interactive deny, permission-hook deny, background-agent deny, _schedule catch, executeSingleToolCall finally. Five blocked_on_user end sites: handleConfirmationResponse cancel and proceed branches, autoApproveCompatiblePendingTools, _schedule catch under signal.aborted, and the global-error catch. ModifyWithEditor stays inside one blocked_on_user span until the final proceed/cancel — the duration_ms reflects total user think-time including editor side trips. Six hook fire sites are wrapped: firePreToolUseHook, firePostToolUseHook, and four safelyFirePostToolUseFailureHook variants (success-path interrupt, toolResult.error path, catch-path interrupt, catch-path real exception). fireNotificationHook is intentionally NOT wrapped — it's fire-and-forget and the duration is meaningless. Mirrors claude-code's session-tracing pattern but deliberately diverges on one point: every end-helper takes the span object explicitly via `getSpanId(span)` lookup instead of `findLast`-by-type. Under concurrent tool calls, claude-code's findLast can end the wrong blocked span; passing the ref directly is concurrency-safe. Tests: - session-tracing.test.ts: 11 new tests covering parent resolution (explicit parent for blocked_on_user, ALS-based for hook), idempotent end, NOOP behavior, error-status mapping, and a concurrency regression test (two parallel blocked spans ended in reverse order). - coreToolScheduler.test.ts: mock extended with the four new helpers and two new metadata fields. New tests cover the tool span outliving a pre-hook deny path, blocked_on_user ending with cancel via the awaiting_approval flow, hook span recording shouldProceed=false / blockType='denied' on pre-hook block and shouldStop=true / blockType='stop' on post-hook stop, and a leak guard that asserts every recorded lifecycle span is ended after a successful tool call. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * fix(telemetry): address #4321 review — Copilot inline + code-reviewer + silent-failure-hunter Eight discrete fixes plus two new tests, all surfaced in the Phase 2 review rounds. Grouped here because they touch the same handful of code paths. Copilot inline (#4321 PR): 1. startToolSpan attrs naming: drop redundant `tool_name` (helper already sets `'tool.name'` from the first arg) and rename `call_id` to the namespaced `'tool.call_id'`. Two sites: `_schedule` validating-loop start, and the defensive fallback in executeSingleToolCall. Without this, traces emit non-namespaced `tool_name` / `call_id` attributes that consumers grepping for `tool.call_id` miss. 2. PreToolUse hook span: propagate the actual `preHookResult.blockType` ('denied' / 'ask' / 'stop') instead of collapsing every block to 'denied'. Also record `hasAdditionalContext` for parity with the PostToolUse / failure-hook spans. 3. blocked_on_user `source` detection: use `config.getIdeMode()` (best- effort) so IDE-driven decisions don't all show up as `'cli'`. Centralized in a new `getBlockedSource()` helper. silent-failure-hunter / code-reviewer: 4. Hook span error-tracking is dead code. firePreToolUseHook / firePostToolUseHook / safelyFirePostToolUseFailureHook all swallow throws internally — every `catch (e) { endMeta = { error, ... }; throw e }` block in the scheduler was unreachable. Simplify all 6 sites to `try { ... } finally { endHookSpan(...) }`. The default `endMeta = { success: false }` keeps the span sensible if a future hook impl decides to throw. 5. handleConfirmationResponse had no error handling. modifyWithEditor / _applyInlineModify / attemptExecutionOfScheduledCalls can throw and would otherwise leak both the tool span and the blocked_on_user span until the 30-min TTL fires. Wrap the body in a try/catch that finalizes both spans on rethrow. Extracted the body to `_handleConfirmationResponseInner` for clarity. 6. Add `'error'` to the `ToolBlockedDecision` union for system-error closes, so dashboards counting `decision: 'cancel'` don't get polluted by thrown exceptions. 7. _schedule's outer catch was labelling its non-aborted close as `'cancel'`. Switch to `'error'` (uses #6). 8. signal.aborted vs explicit user Cancel: when both are true, the old code reported `'aborted'/'system'` even though the user actually clicked Cancel. Reverse the precedence so `outcome === Cancel` wins, with `getBlockedSource()` for the source. Tests: - T1: extend the existing ProceedAlways auto-approve test to assert the two siblings' blocked spans end with `decision: 'auto_approved'`, `source: 'auto'`, while the first tool ends as `'proceed_always'`/cli. - T2: existing cancel-during-confirmation test now also asserts exactly one blocked span is recorded for the lifecycle — the same invariant ModifyWithEditor's intentional preservation across editor side trips must not break. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * fix(telemetry): close autoApprove blocked-span leak + cover three new behaviors Two follow-ups from the post-#6767469b2 review pass on PR #4321: 1. autoApproveCompatiblePendingTools error path was logging-only and leaving the sibling tool's blocked_on_user span open until the 30-min TTL fires. Symmetric with the success branch's finalizeBlockedSpan('auto_approved', 'auto'), the catch now finalizes with ('error', 'system') so the trace deterministically explains why the sibling didn't auto-approve. 2. Three behaviors introduced by |
||
|
|
60d8ffae27
|
feat(cli): respect /editor preference in Ctrl+X external editor (#4310)
* feat(cli): respect /editor preference in Ctrl+X external editor The Ctrl+X external editor prompt previously ignored the general.preferredEditor setting, always falling back to $VISUAL/$EDITOR env vars. Now it consults the preferred editor first, using the correct --wait flags for GUI editors, and falls back to env vars only when no preference is set or the preferred editor is unavailable. Closes #4165 * fix(cli): address review feedback on external editor feature - Fix command injection risk: quote args when needsShell is true - Move writeFileSync inside try/finally with mode 0o600 - Change temp file extension from .md to .txt - Extend needsShell check to cover .bat extension - Fix import formatting in AgentComposer.tsx - Extract usePreferredEditor hook to deduplicate validation - Add 12 tests for openInExternalEditor covering all branches * test(cli): add missing vi.mock for usePreferredEditor and useWorktreeSession AppContainer.test.tsx mocks every hook that AppContainer.tsx imports, but the two new hooks (usePreferredEditor from this PR, useWorktreeSession from main's #4174) were not mocked — causing the real hooks to execute during tests, crash on missing context, and fail all 47 downstream assertions. * fix(cli): address review feedback on env-var fallback and spawnSync timeout - Detect .cmd/.bat in env-var fallback path on Windows and enable shell mode with quoted args, matching the preferred-editor path behavior - Add 30-minute timeout to spawnSync to prevent terminal freeze when a GUI editor hangs - Add test cases for both changes * fix(cli): propagate preferredEditor to TextInput component TextInput creates its own useTextBuffer but was not passing preferredEditor, so Ctrl+X in secondary inputs (dialogs, settings prompts, etc.) silently ignored the /editor preference. * fix(cli): document why simple double-quoting is safe for shell args The args passed to cmd.exe are program-controlled (tmpdir path + fixed flags), never arbitrary user input. cmd.exe does not expand $() or backticks inside double quotes. This matches Claude Code's approach. * fix(cli): handle signal-killed editor and defer undo snapshot - Check spawnSync signal field to avoid reading stale temp file when editor is killed by SIGTERM/SIGKILL - Move undo snapshot creation after successful file read to prevent phantom no-op undo entries on editor failure * fix(cli): restore private tmpdir, skip undo on unchanged content - Restore mkdtempSync isolation directory (was flattened to os.tmpdir) - Skip undo snapshot when editor content is unchanged - Update JSDoc to reflect deferred-snapshot behavior - Remove unused crypto import - Add tests: unchanged content skip, tmpDir cleanup, undo precision * fix(cli): use path.join in external editor tests for Windows compat Tests hardcoded forward-slash paths which fail on Windows where path.join produces backslashes. Use pathMod.join for the expected temp file path so assertions pass on all platforms. * fix(cli): quote editorCmd in shell mode, wrap setRawMode, improve logging - Quote editorCmd along with args when shell: true, so Windows paths with spaces (e.g. C:\Program Files\...\code.cmd) survive cmd.exe. - Wrap setRawMode restore in try/catch so a destroyed stdin doesn't skip temp file cleanup. - Include command, shell mode, and resolution source in error log. - Add tests: CRLF normalization, readFileSync failure, editorCmd quoting. * refactor(core): remove unused isTerminal from ExternalEditorCommand The field was never consumed by any caller — only command, args, and needsShell are destructured. The standalone isTerminalEditor() function already serves the same purpose for openDiff. * docs(cli): update stale JSDoc on openInExternalEditor Reflect the new editor resolution order (/editor → $VISUAL → $EDITOR → vi) and the moved undo-snapshot timing (after editor exit, not before). * fix(cli): address review round 3 — temp dir leak, mkdtemp safety, TextInput stdin - Split unlinkSync/rmdirSync into separate try/catch blocks to prevent temp directory leak when unlinkSync throws (regression from main) - Move mkdtempSync inside try block with early return on failure - Pass stdin/setRawMode from TextInput to useTextBuffer so terminal editors (vim/neovim/emacs) correctly toggle raw mode via Ctrl+X * test(cli): add undo-after-successful-edit test for external editor * fix(cli): opts.editor priority, filePath in error log, warn on invalid editor * fix(cli): address sandbox gap and Windows env-var safety in external editor - usePreferredEditor now checks allowEditorTypeInSandbox() and returns undefined for GUI editors when SANDBOX env is set - env/default editor fallback rejects commands containing " or | before enabling shell mode on Windows * fix(cli): address wenshao review — unsafe-char guard, debug logs, test coverage - Add unsafe-character rejection for opts.editor .cmd paths on Windows - Change env-var unsafe-char handling from throw to graceful return + cleanup - Add debug logging before spawnSync and in setRawMode catch block - Add tests for opts.editor path, .cmd shell mode, and unsafe-char rejection * fix(cli): expand unsafe-char guard, remove stale comment, add tests - Expand Windows unsafe-character regex to include % and ! (cmd.exe variable expansion and delayed expansion) - Remove stale "no hooks needed" comment in TextInput.tsx - Add setRawMode lifecycle test (disable before editor, restore after) - Add default fallback tests for vi (linux) and notepad (win32) * fix(cli): remove explicit type annotation on mock.calls.findIndex callback The `[boolean]` tuple annotation conflicts with vitest's `any[][]` mock.calls type, causing TS2345 in CI. * fix(cli): replace unlinkSync+rmdirSync with recursive rmSync for temp cleanup Leftover swap files from vim/neovim would cause rmdirSync to silently fail on non-empty directories, leaking temp dirs. Use rmSync with recursive+force to handle this. Also fix stale JSDoc fallback comment. * test(cli): add % and ! unsafe-char coverage and error-path raw mode test - Expand opts.editor and env-var unsafe-char tests to cover %, !, and " independently via it.each, preventing silent regex regressions - Add error-path test verifying setRawMode restore when editor exits with non-zero status |
||
|
|
c4421acd53
|
Expose active goal in stream JSON (#4314)
* feat(cli): expose active goal in stream json * fix(cli): support goal clear messages in acp * docs(cli): explain active goal stream events |
||
|
|
b588f74f64
|
fix(core): align session hook matcher targets (#4354)
* fix(core): align session hook matcher targets * fix(core): share hook matcher target mapping * fix(core): satisfy hook matcher exhaustiveness lint |
||
|
|
a3037889a6
|
fix(core): replace structuredClone with shallow copy to prevent OOM in long sessions (#4286)
* docs: add OOM investigation reports and auto-compaction redesign proposal
- Runtime memory investigation plan
- Non-interactive memory benchmark report
- OOM reproduction report with 2GiB/4GiB synthetic tests
- Runtime diagnostics benchmark report
- Auto-compaction threshold redesign proposal
* fix(core): replace structuredClone with shallow copy to prevent OOM
Replace `structuredClone(this.history)` (called up to 4x per turn on the
send path) with a lightweight shallow copy via `copyContentContainer()`.
This eliminates the OOM root cause in long tool-heavy sessions where the
full deep clone exceeded remaining V8 heap headroom.
Key changes:
- Add `copyContentContainer()` helper ({...content, parts: [...parts]})
- Add `getRequestHistory()` private method for the send path
- Add `getHistoryShallow()`, `getHistoryTailShallow()`,
`peekLastHistoryEntry()`, `getLastModelMessageText()`,
`getHistoryLength()` for read-only callers
- Remove HEAP_PRESSURE_COMPRESSION_RATIO safety net (no longer needed
now that the underlying OOM cause is fixed)
- Update chatCompressionService to use getHistoryShallow(true)
- Update nextSpeakerChecker to send only lastMessage (not full history)
- Update memoryDiagnostics with process-tree RSS measurement
* feat(core): add runtimeDiagnostics utility for heap/memory instrumentation
Required by content generators (anthropic, openai, logging) which import
runtimeDiagnostics for optional heap-pressure telemetry during streaming.
Gated by QWEN_CODE_PROFILE_RUNTIME=1 environment variable.
* fix(cli): update doctorCommand test mocks for new MemoryDiagnostics interface
Add missing maxRSSRaw, maxRSSUnit, and processTree fields to test fixtures
to match the updated MemoryResourceUsage and MemoryDiagnostics interfaces.
* fix(vscode-ide-companion): use public core imports
* fix: address review comments — type guards, dead fallbacks, and doc accuracy
Code:
- Fix unsound type guard: `'text' in part` → `typeof part.text === 'string'`
in geminiChat.ts and client.ts (Copilot + wenshao feedback)
- Remove unnecessary optional chaining and dead fallback chains in client.ts
(getHistoryShallow, peekLastHistoryEntry, getHistoryLength, etc. now call
GeminiChat methods directly)
- Add 5s timeout to `execFileAsync('ps', ...)` in memoryDiagnostics.ts
Docs:
- Fix GiB conversion accuracy and add single-run caveat to summary
- Add Node.js version to test environment table
- Fix auto-compaction attempt count (5→4) in OOM report
- Soften root-cause attribution certainty
- Add MCP child process context to investigation plan
- Clarify "Codex" reference (→ OpenAI Codex)
- Fix truncated MCP server name (chrome → chrome-devtools)
- Remove duplicate verification commands in benchmark table
- Clarify thread exhaustion vs V8 heap OOM distinction
- Add workload confound caveat to before/after comparison
- Fix SUMMARY_RESERVE "hard relationship" vs thinking budget contradiction
* fix(core): restore fallback chains in client.ts for mock compatibility
The previous commit removed optional chaining from client.ts wrapper
methods, but client.test.ts mocks getChat() with partial objects that
lack the new shallow methods. Restore ?. fallback chains so both
production (GeminiChat) and test (mock) paths work correctly.
* docs: clarify memory review follow-ups
* docs: fix runtime benchmark unit conversion
* docs: add default-heap OOM stress report
* fix: update copyright year to 2026 in new files [skip ci]
New files added in this PR had 2025 copyright headers. Updated to 2026
to reflect the current year.
|
||
|
|
7c4b7f582a
|
fix(cli): remove QWEN_OAUTH gate from feedback dialog (#4316)
The feedback dialog (point-up/point-down) was only shown to users authenticated via QWEN_OAUTH. With the QWEN_OAUTH free tier closed on 2026-04-15 (#3203), the active user pool that can produce feedback events has effectively drained, leaving the user_feedback telemetry signal blind. The reported payload only contains session_id, rating, model, approval_mode, and prompt_id — no prompt content or other PII — so there is no privacy reason to scope it to a specific auth provider. Keep the existing usageStatisticsEnabled and enableUserFeedback opt-ins, which already gate all telemetry. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4b25f9c05c
|
fix(core): set x-api-key alongside Authorization on Anthropic outbound (#4323) (#4342)
Some checks are pending
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* fix(core): set x-api-key alongside Authorization on Anthropic outbound (#4323) On the IdeaLab-style proxy branch, the Anthropic SDK is constructed with `authToken: <key>, apiKey: null` so it emits `Authorization: Bearer <key>` and suppresses the ANTHROPIC_API_KEY env back-fill (the #4020 leak fix). That covers IdeaLab and CherryStudio-style proxies, but standards- compliant Anthropic-compatible servers (OpenCode-Go, Claude proxy products) authenticate only on the canonical `x-api-key` header and reject the request with "Missing API key" even though the bearer token is present. Inject `x-api-key: <key>` into `defaultHeaders` on the proxy branch (post-`buildHeaders`, so customHeaders cannot override it). The value is the user's already-configured `apiKey` — never an env-resolved one — so the #4020 env-leak vector stays closed. The Anthropic-native branch is untouched: the SDK's apiKey path already emits the header, and duplicating it via defaultHeaders would risk stale-value drift. Verified: - new unit test pins `x-api-key: <key>` on every proxy-branch case (config-baseUrl, malformed baseUrl, DeepSeek anthropic-compat, ANTHROPIC_BASE_URL env-pointed-at-proxy); a negative test pins that the native branch does NOT add the header. - E2E: spun up a local `http.createServer`, pointed the SDK at it the same way `AnthropicContentGenerator` does, and dumped the captured wire headers — `Authorization: Bearer` and `x-api-key` both arrive alongside the existing X-Stainless-* / x-app / claude-cli UA trio. Fixes #4323 * fix(core): clarify x-api-key comment + cover guard branch & customHeaders ordering (#4323) Address review feedback on #4342: - Source comment claimed the apiKey value was "never an env-resolved one"; that's wrong — `resolveCredentialField` in content-generator-config.ts:178 falls through to env vars when the explicit and inherited values are unset. The security reasoning doesn't actually depend on that claim (the same value already ships as `Authorization: Bearer` via `authToken` on the same request), so re-anchor the comment on that fact and drop the misleading "never env-resolved" framing. - Add test pinning the `&& contentGeneratorConfig.apiKey` guard: a falsy apiKey on the proxy branch must NOT inject `x-api-key:` (empty string would otherwise ship a meaningless header). The TypeScript signature `apiKey?: string` keeps the guard needed at the type level, but a future loosen-the-type refactor would silently re-enable the empty ship; the test catches that. - Add test pinning the post-buildHeaders ordering: a user-supplied `customHeaders: { 'x-api-key': … }` must NOT win against the canonical key. The source comment promises this invariant but no test pinned it; a refactor that moved the injection above the customHeaders merge would silently let user config swap the auth header, defeating the dual-auth contract. Declined two suggestions: - Bot suggested extracting the 3-line injection into a `buildApiKeyHeader()` helper for consistency. Declined: adds indirection without abstraction win, and the inline form keeps the post-buildHeaders ordering visible at the call site (the ordering IS the invariant the comment promises). - Bot suggested asserting `Authorization` is absent from `defaultHeaders` on the native path. Declined: the constructor-options pins (`apiKey: 'test-key'`, `authToken: null`) already document the SDK-driven auth mode; asserting on the absence of a header we never set in defaultHeaders is redundant given the existing assertions. 68 tests pass (66 + 2 new). tsc + eslint clean. |
||
|
|
ed14a33064
|
feat(core): add NotebookEdit tool for Jupyter notebooks
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / CodeQL (push) Blocked by required conditions
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
Adds NotebookEdit as the structured write counterpart to existing notebook read support. Summary: - Add `notebook_edit` for safe cell-level `.ipynb` replace/insert/delete operations. - Integrate notebook editing with tool registration, permissions, Claude conversion, prior-read enforcement, IDE/inline modify flow, commit attribution, docs, and SDK permission docs. - Harden notebook read/edit behavior for truncated notebook renders, ambiguous fallback cell IDs, internal modify metadata, compact JSON, UTF-8 BOM notebooks, and cache behavior after structural edits. - Add unit and integration coverage for notebook read/edit behavior. Follow-up work remains for tab-indented notebook formatting preservation, a few low-risk unit-test additions, and non-blocking hardening suggestions from review. |
||
|
|
a552df8998
|
refactor(auth): unify provider config in core, simplify /auth as "Connect a Provider" (#4287)
* refactor(providers): unify provider config into core, remove CLI re-exports Move all ProviderConfig definitions, registry (ALL_PROVIDERS), and utility functions (buildInstallPlan, resolveBaseUrl, etc.) from packages/cli/src/auth/ into packages/core/src/providers/ so both CLI and VSCode can share the same provider system. - Add core providers module with types, presets, install logic - Rewrite VSCode AuthMessageHandler to dynamically generate provider choices from ALL_PROVIDERS instead of hardcoding 3 providers - Add applyProviderInstallPlanToFile in VSCode settingsWriter using the ProviderSettingsAdapter abstraction - Delete 11 CLI re-export wrapper files, update ~20 import sites - Keep CLI-specific applyProviderInstallPlan (uses LoadedSettings) and openrouterOAuth.ts (CLI-only OAuth runtime) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(cli): drop OpenRouter OAuth + /manage-models, simplify /auth OpenRouter now uses the standard API-key flow under "Third-party Providers" (issue #4108). The whole OpenRouter OAuth implementation (PKCE, callback server, model auto-install) and the /manage-models command (only OpenRouter was wired in; /auth Step 2 already covers model selection) are removed. /auth is renamed around the "Connect a Provider" mental model: - Dialog title is now "Connect a Provider"; the OAuth main entry is gone - handleAuthSelect (mixed close + auth trigger) is split into a single-purpose closeAuthDialog; legacy wrappers (handleSubscriptionPlanSubmit, handleApiKeyProviderSubmit, handleCustomApiKeySubmit, ...) are dropped in favor of the unified handleProviderSubmit Core: openRouterProvider switches to authMethod='input', uiGroup='third-party', ships with two recommended free models, and is reordered to the end of the third-party list to keep DeepSeek as the default highlight. Net diff: 34 files, +124 / -3835. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(auth): unify applyProviderInstallPlan in core, drop cli/auth CLI and vscode now share core's applyProviderInstallPlan instead of keeping two parallel implementations. The CLI-only env rollback (snapshot process.env, restore on error) is folded into the core version so vscode also benefits from it. CLI ships a LoadedSettingsAdapter that maps LoadedSettings to core's ProviderSettingsAdapter contract. Backup/restore is layered: write a .orig file, structuredClone settings + originalSettings, then recomputeMerged() on restore — same guarantees as before, just routed through the adapter. Tests for the install logic are migrated to core and rewritten against the adapter mock (more focused than the previous LoadedSettings/Config mocks). packages/cli/src/auth/ is gone entirely. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(providers): drop unused authMethod field from ProviderConfig Every preset has had authMethod='input' since OpenRouter switched to the standard API-key flow, making the field a dead dimension. Removing it cleans up three never-taken branches and aligns the type with reality: connecting a provider always means entering an API key. - core: remove ProviderConfig.authMethod; shouldShowStep('apiKey') is now unconditionally true; drop authMethod from 9 presets - vscode AuthMessageHandler: drop the OAuth branch in handleAuthInteractive - vscode WebViewProvider: simplify the apiKey-required guard - tests: update provider-config.test and custom-provider.test If a future provider needs a browser-based flow, the field can be re-introduced; for now the smaller surface is worth more. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(providers): prefix Alibaba plan presets with alibaba- Rename coding-plan.{ts,test.ts} → alibaba-coding-plan.{ts,test.ts} and token-plan.{ts,test.ts} → alibaba-token-plan.{ts,test.ts} so the file names line up with the existing alibaba-standard preset and make it obvious at a glance which presets belong to Alibaba ModelStudio. Export names (codingPlanProvider, tokenPlanProvider, TOKEN_PLAN_*, CODING_PLAN_*) are unchanged — only the file paths and the two imports in all-providers.ts / index.ts move. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(vscode): guard ProviderSettingsAdapter against prototype pollution The dotted-key writer in createFileSettingsAdapter walked through any segment, including __proto__/constructor/prototype, which would let a malicious or malformed ProviderInstallPlan reach Object.prototype. Refuse to write paths containing reserved segments and use hasOwnProperty when traversing intermediate objects so that inherited properties cannot redirect the walk. Addresses CodeQL alert #226 surfaced on PR #4287. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): default Audio modality to off in provider advanced config In the /auth Custom Provider advanced-config step, "Enable modality" should default to Image + Video only. Audio was on by default, which implied the model accepts audio input even though most providers people configure here don't. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): show base URL default as placeholder, not prefilled value In Custom Provider Step 2/6 (and on protocol switch), the base URL input started with the protocol's default URL pre-filled. Users who wanted a non-default endpoint had to manually clear the field first. Switch to placeholder semantics: the input starts empty, the default URL is shown as a hint, and submitting blank falls back to that default (then writes it back to baseUrl so downstream steps see a real value). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(cli): rename /auth description to "Connect an LLM provider" The old description ("Configure authentication information for login") implied a Qwen-account login. After the /auth refactor it's really about picking an LLM provider and entering credentials, so the menu entry should say that. Also add 'connect' as an alt-name alongside the existing 'login' so users can type /connect when 'auth' feels wrong. Keep 'login' for muscle-memory compatibility. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * i18n(cli): translate "Connect an LLM provider" in all locales Strict-parity locales (zh, zh-TW) require every built-in command description to be translated; the renamed /auth description was falling back to English and breaking the must-translate test. Add translations for zh / zh-TW (required) and refresh the other seven locales (en, ru, de, ja, fr, ca, pt) so the old "Configure authentication information for login" key is removed everywhere rather than left as a dangling dictionary entry. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(vscode): await applyProviderInstallPlanToFile and grow test coverage Critical: applyProviderInstallPlanToFile fired the install plan with `void`, so any rejection (EACCES from persist(), prototype-pollution guard throw, etc.) was silently swallowed and WebViewProvider proceeded to disconnect/reconnect the agent as if the write had succeeded. Make the wrapper `async` and `await` it in the only caller. Tests added: - core/install.test: isSameModelIdentity fallback path (prepend-and-remove-owned with no ownsModel) — verifies models are matched on id+baseUrl, not just id. - vscode/AuthMessageHandler.test: happy-path with a fixed-baseUrl third-party provider, validateApiKey error branch, and BaseUrlOption picker presentation. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): address PR #4287 review (critical + suggestion) vscode AuthMessageHandler (Critical): - Add the missing protocol-selection step so custom-provider users can pick Anthropic/Gemini instead of being silently locked to OpenAI. - Validate free-form base URL with the same /^https?:\/\// check the CLI uses; reject file:/javascript: schemes. vscode AuthMessageHandler (Suggestion): - Stop filtering separator entries from the provider QuickPick so groups (Alibaba Cloud / Third Party / Custom) actually show as headers instead of a flat list. - Treat a null authInteractiveHandler as an error: surface an authError + cancellation notification instead of silently dropping the user's input. - Call notifyAuthCancelled when validateApiKey rejects so the webview state resets and the user can retry. core/providers/presets/openrouter.ts (Critical): - Replace the substring includes() in ownsModel with a URL-hostname match so paths like https://api.example.com/openrouter.ai/v1 stop being misidentified as OpenRouter models (and getting removed on re-install). vscode/services/settingsWriter.ts (Critical): - stripTrailingCommas() so JSONC files with trailing commas (VSCode's default style) parse instead of silently returning {} and then overwriting the entire settings file. - readSettings() distinguishes ENOENT (return {}) from parse errors (log + rethrow) so a malformed file never gets clobbered. - writeSettings() writes through a temp file + fs.renameSync atomic rename, eliminating the half-written file window on EACCES / disk-full / crash. - setValue() refuses to overwrite a scalar at an intermediate path segment (would have silently destroyed e.g. {"env": "legacy-string"}). core/providers/install.ts (Suggestion): - Move settings.backup?.() inside the try block so a backup failure still triggers the env-rollback path in catch. cli/config/loadedSettingsAdapter.ts (Suggestion): - Add the same UNSAFE_KEY_PARTS guard the vscode adapter has, so __proto__/constructor/prototype segments are rejected before reaching the underlying setNestedPropertySafe walker. Defense in depth: not exploitable today but the utility has no built-in guard. vscode/webview/providers/WebViewProvider.ts (Suggestion): - Hoist buildInstallPlan / applyProviderInstallPlanToFile to static imports (both modules already top-level imported); drops two per-call await import() round-trips. cli/utils/doctorChecks.ts (Suggestion): - Whitespace nit before the comma in the qwen-code-core import. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): second round of PR #4287 review fixes Critical: - settingsWriter: stripTrailingCommas now uses a char-by-char scanner so literal ",]" inside a string value is preserved (the previous regex silently corrupted it). - install.ts: wrap settings.restore() in try/catch so a restore failure doesn't mask the original error or skip the env-rollback loop. - install.ts: snapshot the runtime ModelProvidersConfig before applying patches and reload it in the catch path, so an in-flight refreshAuth() failure doesn't leave the live session holding providers that were never successfully installed. - AuthMessageHandler: custom-provider Base URL is now a placeholder instead of a pre-filled value, with the default selected by the user's chosen protocol (openai/anthropic/gemini). Empty input falls back to the protocol-appropriate URL, preventing the pick-Anthropic-but-keep-OpenAI-URL footgun. Suggestion: - AuthDialog: replace the isCurrentlyCodingPlan misnomer with a uiGroup check — resolveMetadataKey returns config.id for *any* provider with a static models[], so the old guard made DeepSeek/MiniMax/OpenRouter users land on the Alibaba tab instead of Third-party Providers. - AuthMessageHandler: guard against modelIds being [] after splitting comma input (matches the CLI's "Model IDs cannot be empty."). - WebViewProvider: restore the explanatory comment for the authState === true success-toast guard that the previous diff accidentally dropped. Tests: - settingsWriter.test: new applyProviderInstallPlanToFile suite covering happy path, prototype-pollution guard (built via Object.defineProperty to bypass __proto__ literal semantics), intermediate-scalar rejection, malformed-file no-clobber, JSONC-with-trailing-commas parsing (including a string containing ",]"), and the atomic-write tmp-file cleanup. - loadedSettingsAdapter.test: new file — forwarding, UNSAFE_KEY_PARTS rejection, getValue against merged settings, backup/restore round-trip, cleanupBackup semantics. - provider-config.test: added findProviderByCredentials and getAllProviderBaseUrls coverage (preset hits, unknown-key misses, BaseUrlOption[] preset expansion). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): satisfy strict tsc --build in loadedSettingsAdapter.test CI's `tsc --build` (with emit) enforced two strict checks that `tsc --noEmit` had been letting through: - `noPropertyAccessFromIndexSignature` flagged `file.settings['env']` reads against `Record<string, unknown>`. Switched the test fixture shape to a named `SettingsShape` interface with explicit `env` and `modelProviders` keys (plus an index signature for setValue's arbitrary writes), so dot access on the known keys is no longer "through" the index signature. - Calling optional methods via `adapter.backup?.()` produced TS2722 (`Cannot invoke an object which is possibly 'undefined'`) under the build flags. createLoadedSettingsAdapter always installs backup/restore/cleanupBackup, so the tests now assert `toBeTypeOf('function')` first and then call via non-null assertion, which both documents the invariant and makes the call typesafe. - Dropped the `({} as Record<string, unknown>)['polluted']` sanity check; `expect(setValue).not.toHaveBeenCalled()` already proves the guard short-circuits before any write reaches LoadedSettings. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): guard mock setValue against prototype pollution in adapter test CodeQL flagged the mock setValue's recursive property assignment as a prototype-pollution sink. Add UNSAFE_KEY_PARTS check at the top of the mock to align with the real setNestedPropertySafe contract. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): use literal === guards for CodeQL prototype-pollution sanitiser CodeQL re-flagged the mock setValue write even after the Set.has guard added in |
||
|
|
aedbf37b9d
|
feat(core): inject git status into system prompt and refine Explore/git-log guidance (#4110)
* add system prompt for codebase task * update prompt snapshot * fix test * resolve comment |
||
|
|
1a59f207bf
|
chore: add .github/release.yml to support skip-changelog label (#4327)
* chore: add .github/release.yml to support skip-changelog label * chore: add comments explaining release.yml purpose * fix(lint): quote string value in release.yml for yamllint |
||
|
|
96fa0616c6
|
fix(review): harden SKILL.md against weak-model rule skipping (#4340)
* fix(review): harden SKILL.md against weak-model rule skipping Weak models often skip parts of the long /review prompt and fall back to familiar defaults — `gh pr checkout` instead of the worktree flow, or running the autofix prompt even when the user passed `--comment` (which means "only post inline comments, don't mutate code"). Three reinforcements, all in SKILL.md (no CLI changes): - Promote the two most commonly violated rules to the top of the "Critical rules" list: worktree is mandatory for PR reviews, and `--comment` skips Step 8 entirely. - Add an inline blockquote at the top of the Step 1 PR branch that names the specific forbidden commands (`gh pr checkout`, `git checkout`, `git switch`, `git pull`, `git reset --hard`). - Add an explicit skip block at the top of Step 8 listing the three conditions that bypass autofix — `--comment`, cross-repo lightweight mode, or no fixable findings — so a weak model doesn't have to infer them from scattered earlier text. * fix(review): address /review comments on rule scope + Step 8 dedup Follow-up to the initial harden pass, addressing the inline review comments on PR #4340. Rule #1 (worktree mandatory): - Scope it to **same-repo PR reviews** so cross-repo PRs running in lightweight mode (no matching local remote, no worktree) don't read as a contradiction. - Replace "Your very first action" with "After argument parsing and remote detection, the first command that touches code state" — the literal "very first" was wrong since `--comment` parsing and URL/remote disambiguation legitimately run before `fetch-pr`. - Align the forbidden-command list with the Step 1 blockquote (add `git pull` and `git reset --hard`) so a weak model that only reads the Critical rules section sees the same five commands as a model that reaches the blockquote at the point of use. - Add an explicit "cross-repo PRs use lightweight mode" parenthetical so the same model knows where to look for the alternative path. Step 8 skip block: - Drop the redundant third bullet ("no Critical or Suggestion findings with concrete, applicable fixes") — it was both logically equivalent to the "Otherwise" clause below and used a different qualifier ("concrete, applicable" vs "clear, unambiguous"), risking a weak model treating them as two distinct thresholds. - "ANY of the following" → "EITHER" since only two bullets remain. - Fold the no-findings case into the Otherwise clause as a no-op note. |
||
|
|
d97b85f2cf
|
Pin fetch to bundled undici for undici higher versions compatibility (#4238)
* fix: pin fetch to bundled undici for Node.js 26 (undici 8.x) compat Node.js 26 bundles undici 8.x, which differs from the project's undici 6.x. Using Node's built-in fetch mixed with ProxyAgent/Client from the bundled undici causes handler-interface mismatches (e.g. 'invalid onError method'). * fix(core): export undici fetch alongside proxy dispatcher to avoid version mismatch for review of #4238 When a custom dispatcher (ProxyAgent) is passed, pin fetch to the bundled undici's implementation so both share the same undici version. Without this, Node's built-in fetch (e.g. undici v8) rejects a ProxyAgent from the bundled undici (e.g. v6) with "invalid onError method". * fix: move pinning fetch alongside with dispatcher in runtimeOptions, change back default.ts * docs(core): update code comment reference in runtimeFetchOptions test |
||
|
|
16f0fde19a
|
fix(test): raise timeout for Windows installer end-to-end tests (#4352)
* fix(test): raise timeout for Windows installer end-to-end tests The Windows-only end-to-end installer tests spawn cmd.exe to run the .bat installer and then qwen.cmd --version, which boots a Node process. On GitHub's windows-latest runners that chain regularly takes >5s, so the default 5s vitest timeout makes them flaky (recently observed at 5804ms on CI). Bump the describe-block timeout to 30s, which leaves headroom without masking real regressions. * fix(test): raise timeout for Linux/macOS installer end-to-end tests Match the timeout already applied to the Windows e2e block: the Linux/macOS installer tests also spawn child processes via execFileSync, so they share the same flake risk near the default 5s vitest timeout. 15s leaves ample headroom without Windows' cmd.exe overhead. Addresses review feedback on #4352. |
||
|
|
dc6a5ad50a
|
feat(cli): add session path status command (#4124)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* feat(cli): add session path status command * fix(cli): add status paths translations * fix(core): use secure subagent id suffix * fix(cli): harden status paths log lookup * fix(cli): use secure prompt id randomness * test(cli): cover status paths formatting |