mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-06 15:25:34 +00:00
3780 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
90052f25ae
|
fix(cli): prevent silent VP-mode crash by adding uncaughtException handler and error visibility (#8088)
* fix(cli): prevent silent VP-mode crash by adding uncaughtException handler and error visibility VP (alternate-screen) mode swallows all error output: uncaught exceptions write their stack trace to stderr which lands on the alternate screen buffer, then gets discarded when teardown switches back to the primary buffer. The user sees a silent exit with no error message and nothing in the debug log. Root cause: no `uncaughtException` handler existed anywhere in the CLI. PR #7406's ErrorBoundary and unhandledRejection handler only cover React render errors and promise rejections — synchronous exceptions bypass both. Changes: - Add `setupUncaughtExceptionHandler()` with sync debug-log write, alternate-screen exit before stderr output, and clean process.exit(1) - Add `consumeLastRenderError()` to ErrorBoundary for VP main-screen echo after unmount leaves the alternate screen - Remove SIGTERM/SIGINT handlers from kittyProtocolDetector.ts that raced with the main signal handlers (#7779) - Add SIGHUP handler alongside SIGTERM/SIGINT (#7781) - Guard ThinkBody's MarkdownDisplay with per-item ErrorBoundary so partial markdown during thought streaming degrades to plain text Related: #7971 #7972 #7779 #7781 * fix(cli): consolidate uncaughtException handler into a single listener (#8088) The VP-crash handler added a second uncaughtException listener that conflicted with the pre-existing one in runCliEntryPoint: the first listener's process.exit(1) ran before the second, leaving the visibility feature inert for real errors, and the new listener lacked the PTY-race guard, crashing the session on benign teardown errors. Replace the startup handler with one session-aware listener (PTY guard, isTTY-guarded alternate-screen leave, writeStderrLineSafe), sanitize the inline render-error fallback, gate the exit-time render-error echo on onError, and cover SIGHUP exit code. * fix(cli): harden uncaughtException handler and scope render-error echo (#8088) * fix(cli): address review feedback on VP crash handler (#8088) - Close over Config instead of sessionId string so the uncaughtException handler reads the current session ID at crash time - Create debug log directory before appending so the write succeeds on fresh installs where ~/.qwen/debug/ does not yet exist - Sanitize error stack with sanitizeTerminalText before writing to stderr to prevent ANSI injection on the persistent main buffer - Add onError debug logging to the ThinkBody ErrorBoundary - Gate the '(logged to debug file)' hint on isDebugLogFileEnabled() so the message is accurate when debug file logging is disabled * fix(cli): move uncaught-exception helpers to a leaf module (#8088) gemini.tsx statically imported handleUncaughtException and isExpectedPtyRaceError from cli.ts, the esbuild entry point. gemini.tsx is only ever loaded lazily, so that import formed an entry<->lazy cycle; with splitting enabled esbuild hoisted the entry body into a shared chunk and left dist/cli.js as a re-export stub. The bootstrap guard at the bottom of cli.ts then never ran and the bundled CLI exited 0 silently. Move the helpers (and the private getErrnoCode) into a new leaf module, utils/uncaught-exception-handler.ts, imported by both cli.ts and gemini.tsx. cli.ts re-exports them so existing importers (cli.test.ts) are unaffected, and the handler reuses the shared writeStderrLine from stdioHelpers.ts. The uncaughtException behavior itself is unchanged. --------- Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
31a26ea99c
|
fix(cli): add Ctrl+Tab alternative for @ completion tab switching (#8074)
* fix(cli): add Ctrl+Tab alternative for @ completion tab switching (#8069) Ctrl+←/→ is intercepted by most terminals (iTerm2, Windows Terminal, macOS Terminal.app) for word-jump, making @ completion tab switching unusable. Added Ctrl+Tab (next) and Ctrl+Shift+Tab (previous) as alternative keybindings that are less commonly intercepted. Also fixed the hint text: "(Ctrl+←/→ to switch)" was displayed even when availableCategories <= 2 (where switching is disabled). Now the hint only shows when there are >2 categories and mentions Ctrl+Tab as an alternative. * fix(cli): keep Ctrl+Shift+Tab from cycling approval mode (#8069) Ctrl+Shift+Tab was added as a completion-category navigation binding, but the approval-mode cycling handlers matched any Shift+Tab without excluding ctrl. In Kitty-protocol terminals (which preserve the ctrl modifier on Shift+Tab) the navigation keystroke also silently cycled the approval mode. Exclude ctrl from the Shift+Tab match and mention both Ctrl+Tab directions in the switching hint. * test(cli): cover Ctrl+Shift+Tab guard in AgentComposer (#8074) * refactor(cli): drop redundant category-count guard in SuggestionsDisplay (#8074) * test(cli): cover Ctrl+Tab completion tab-switch bindings (#8074) --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
5d4fa392ea
|
feat(anthropic): support extended 1h cache_control TTL (#8048)
* feat(anthropic): support extended 1h cache_control TTL
Adds `generationConfig.cacheRetention` / `cacheRetentionByBlock` settings
that let the Anthropic-compatible content generator request the 1-hour
extended prompt-cache tier (`cache_control: { type: 'ephemeral',
ttl: '1h' }`) instead of always relying on the spec's 5-minute default.
- `cacheRetention` sets the default retention for every cache anchor the
converter writes (system text, last tool definition, trailing user
message); `cacheRetentionByBlock` allows overriding per anchor.
- No model allowlist: live verification against the Anthropic Messages
API confirmed every currently-active model (Haiku 4.5 through Opus 4.8
and Sonnet 5) accepts the 1h tier, so a model that ever doesn't will
simply 400 directly rather than being silently masked by a stale
allowlist.
- The `extended-cache-ttl-2025-04-11` beta header is sent defensively
whenever the assembled request body actually carries `ttl: '1h'`
(mirrors the existing `hasGlobalCacheScopeOnWire` body-scan pattern),
even though live testing found it has no observable effect on at least
one Anthropic-compatible backend -- some backends may still enforce it.
Verification:
- New unit tests: 5 in converter.test.ts (ephemeral default omits ttl,
'1h' sets ttl on system/tool/trailing-user, composes with
scope:'global', per-anchor override wins, staticSystemPrefix suffix
carries ttl) and 2 in anthropicContentGenerator.test.ts (beta header
sent/omitted based on wire content).
- Live end-to-end verification: drove the real AnthropicContentGenerator
through a logging proxy and confirmed the outbound wire body carries
`cache_control: { type: 'ephemeral', ttl: '1h' }` and the real
Anthropic API response reports `ephemeral_1h_input_tokens` (not
`ephemeral_5m_input_tokens`).
Fixes #8047
* fix(anthropic): normalize cacheRetentionByBlock to keep wire order legal
Addresses the blocking review comment on this PR: cacheRetentionByBlock
could emit request bodies that violate Anthropic's documented ordering
rule ("cache entries with longer TTL must appear before shorter TTLs").
Render order is tools -> system -> messages, so the converter's three
cache anchors (tool, system, user.last) sit on the wire in exactly that
order; a per-anchor override that raises a later anchor to '1h' while an
earlier one stays at the 5-minute default inverts the required ordering.
4 of the 8 reachable retention combinations were invalid, including the
PR's own new test case (cacheRetentionByBlock: { system: '1h' }) and the
most natural usage ("cache my system prompt for an hour").
Applies resolution option 1 from the review: normalize instead of
validate. resolveCacheRetention now resolves each anchor by scanning
itself and every anchor later on the wire — if any of them is '1h', the
anchor resolves to '1h' too. This makes every cacheRetentionByBlock
configuration legal (retention is monotonically non-increasing in wire
order) without adding a new error surface or rejecting any input.
Also folds in the review's medium/minor findings while touching this
code:
- settingsSchema.ts: cacheRetention is now `type: 'enum'` with an
ephemeral/1h option list (previously unconstrained `type: 'string'`),
and cacheRetentionByBlock gets a jsonSchemaOverride enumerating the
three anchor keys with additionalProperties: false — a typo'd value
(e.g. '1hr', 'user_last') or key no longer silently degrades to 5m
caching with nothing on the wire to contradict it. Regenerated
settings.schema.json via `npm run generate:settings-schema`.
- cacheRetention's settings description now states the 1h tier's 2x
write-cost premium (vs 1.25x for the 5m default; reads stay 0.1x for
both) so the tradeoff the setting exists to expose is visible where
it's configured.
- Moved resolveCacheRetention above buildSystemWithCacheControl's doc
comment — it had been inserted between that comment and the method it
documents.
- Corrected the stale AnthropicCacheControl comment claiming ttl is
"gated behind" the extended-cache-ttl-2025-04-11 beta; Anthropic's
current docs describe the 1h tier as GA, and the beta is sent
defensively for older Anthropic-compatible backends that may still
gate on it.
Verification:
- Added 2 unit tests covering the two review-flagged case shapes: { tool:
'1h' } alone (no promotion needed, was already legal) and { 'user.last':
'1h' } alone (now promotes both tool and system). Updated the existing
"per-anchor override" test to assert the tool anchor's new promoted
value instead of the old (invalid) unpromoted one.
- Re-ran the reviewer's exact reproduction case live: drove the real
AnthropicContentConverter with cacheRetentionByBlock: { system: '1h' }
and a tool attached, confirmed the assembled body now resolves to
tool=1h/system=1h/user.last=ephemeral (monotonic, legal), and sent it
through the corp llm-proxy to Anthropic — HTTP 200 with tool_use
content and a cache_creation usage block (previously this exact shape
was the reviewer's case C ordering violation).
- `tsc --noEmit -p packages/core/tsconfig.json` and eslint clean for all
touched files.
- `vitest run` on converter.test.ts (79 tests), anthropicContentGenerator.test.ts
(112 tests), and settingsSchema.test.ts (36 tests) — all pass.
---------
Co-authored-by: Palanisamy, Dinesh <Dinesh.Palanisamy@netapp.com>
|
||
|
|
e4b5501a20
|
feat(channels): scope daemon adapter state by workspace (#8178)
Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
038c6c4b9f
|
fix(cli): allow scratch workspace without bearer token (#8204)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
9e164d854c
|
feat(review): check cache identity when reviewing workflow PRs (#8205)
A cache whose producer and consumer never agree on identity is invisible
to every lens the review currently has. `actions/cache` matches an entry
on `(key | restore-key)` AND a `version` that hashes the literal `path`
strings plus the compression method, not the key alone. So two jobs can
share a key, share the `path:` line exactly as written, and never hit
once: `${{ runner.temp }}` expands to a different string on a hosted
runner than in a container job or on a self-hosted runner, and an image
without the `zstd` binary picks gzip where a hosted runner picks zstd.
The workflow path-rule already covered a cache a fork can poison; it said
nothing about one that can never fire. Add that class as a blocker, so
every dimension agent whose territory holds a workflow is asked whether
the side that writes agrees with the side that reads — a question no
assertion about the YAML's shape can answer, because the two sides'
strings match in exactly the case that fails.
Give verify-pr the method to settle it: read the matching key from the
implementation rather than the documentation, compare the two sides'
environment tuples rather than their YAML strings, and treat a hit that
leaves no observable signal as the finding rather than a nit.
Also record that the verify container is a live sample of the lane's own
runtime. When the diff changes what those lanes execute, `command -v
zstd` or `echo "$RUNNER_TEMP"` settles in one shell command what no
amount of YAML reading settles, and needs no GitHub token — which that
environment does not have.
|
||
|
|
01afcb0bba
|
feat(hooks): include session source in lifecycle payloads (#8155)
Co-authored-by: 欢伯 <ri.xur@alibaba-inc.com> |
||
|
|
0d3473cb4d
|
chore(release): v0.21.2 (#8200)
* chore(release): v0.21.2 * docs(changelog): sync for v0.21.2 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
a1c33de18f
|
fix(web-shell): isolate worktree session execution (#8068)
* fix(web-shell): isolate worktree session execution Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(mcp): expect session cwd in effective config Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(acp-bridge): cover shell execution after a failed cwd change (#8068) * fix(acp-bridge): abort direct shell waiting on a hung cwd change (#8068) * refactor(core): centralize effective MCP server resolution (#8068) Extract McpClientManager.getEffectiveMcpServers() as the single source of truth for the populateMcpServerCommand recipe, replacing six duplicated call sites that this PR otherwise had to edit in lockstep. Also cover relocateWorkingDirectory's combined memory + MCP refresh failure path so both errors stay surfaced. * fix(core): align MCP cwd stamping with transport classifier (#8068) * fix(core): move MCP cwd stamping to populateMcpServerCommand (#8068) getMcpServers() injected cwd into the effective config, which changed the hash used for MCP approval binding (hashMcpServerConfig). This caused approved project/workspace-scoped stdio servers to show as pending approval, and re-approving silently gated them off on the next daemon start. Move the cwd stamping down to populateMcpServerCommand — the transport-config seam every spawn path already goes through — so getMcpServers() stays an identity-stable view of configuration and the approval hash contract holds. Also make populateMcpServerCommand non-mutating (spread instead of in-place assignment). Additionally, clean up the abort listener registered during the direct-shell cwd-queue race so it does not accumulate on the long-lived per-session signal (ACP path). --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
c3620bc7a0
|
fix(serve): restore build by routing side-task rollback through the workspace session service (#8144)
The side-task creation route still constructed a session service directly on two rollback paths after the surrounding module moved to the workspace runtime helper, leaving the identifier unbound and breaking the CLI build on main. Route both rollbacks through the workspace runtime helper so they honour the per-workspace runtime base dir like every other session-service use in this module. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3bdaeac046
|
fix(serve): Isolate daemon session maintenance writers (#7975)
* fix(serve): isolate daemon session maintenance writers Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: fix CI failure on PR #7975 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#7975) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: fix CI failure on PR #7975 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): address review feedback on daemon session writer maintenance (#7975) - Extract teardownBoundSession helper to deduplicate bound-session teardown in scheduled-tasks create handler - Extract shared cleanupSession callback in createServeApp to eliminate three verbatim copies of the orphan-deletion wrapper - Fire onError callback on the SessionNotFoundError deletion path in deleteDaemonSessions, matching the normal close-succeeded path - Update stale @priority docstring on Storage.getRuntimeBaseDir() * codex: address PR review feedback (#7975) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): cover orphan-delete paths and draining guards (#7975) --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> |
||
|
|
0a3098a279
|
feat(web-shell): add contextual task panels (#7929)
* feat(web-shell): add contextual task panels * fix(web-shell): harden contextual task panels * fix(web-shell): preserve side task titles * fix(web-shell): address review feedback on context panels PR (#7929) - Add POST /session/:id/side-task to telemetry route catalog (51 routes) - Increase SDK browser bundle size limit to 184KB - Fix duplicated data-testid="chat-pane" → "chat-pane-container" on container - Gate sourceType behind session_source_metadata capability check - Add removeSession cleanup after killSession in !res.writable path - Add i18n key sideTask.renameFailed for error fallback - Add unit tests for selectVisibleHistoryRecords invariant * fix(cli): update telemetry-catalog route drift guard to 51 routes (#7929) * fix(web-shell): address review feedback round 2 on context panels PR (#7929) - Fix /fork sider discarding createSideTask() return value: show toast when side tasks are unavailable - Fix layout feedback loop: availableWidth no longer depends on environmentPanelVisible since the CSS overlay does not change the chat pane DOM width - Remove dead environmentPanelSuppressed state (never set to true) - Restore setArtifactPanelOpen(false) in closeArtifactPanelTab when the last tab is closed - Extract agentDisplayName(task) to a local variable to avoid triple invocation per render * fix(web-shell): dedupe completed background agents in environment panel (#7929) getEnvironmentAgentTasks correlated a transcript tool card with the live /tasks snapshot only on toolUseId, the notification taskId, and a <subagentType>-<callId> derived id. A completed background agent can lose that linkage (its live task carries no usable toolUseId and its daemon id is general-purpose-<internalId>), so the trailing loop appended the live task as a second entry. Add a conservative content fallback (prompt, or description+subagentType) mirroring the daemon's legacy resolver. * feat(web-shell): support side tasks during active turns * fix(web-shell): deduplicate completed subagents and gate sourceType on capability (#7929) * fix(web-shell): restore background agent reconciliation and fix agent dedupe (#7929) Restore the one-shot subagent reconciliation for inline background Agent tool cards. Persisted notification records do not always retain a toolUseId, so the SSE discrete-notification path alone can leave a card stuck in Running; the documented fallback resolves pending cards through the subagent endpoint after catch-up, reconnect, and terminal notifications. Also stop the loose description content fallback in getEnvironmentAgentTasks from claiming a live task that another transcript tool call already links precisely (by toolUseId, message taskId, or derived id). Two agents sharing a description previously collapsed into one: the fallback stole the linked task, its owner re-matched the same task, and the orphan was dropped. * fix(web-shell): address critical review feedback on context panels (#7929) * fix(web-shell): reconcile side-task state across sessions and listings (#7929) * fix(web-shell): preserve contextual panel fallbacks --------- Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> |
||
|
|
36a3fb2fa2
|
feat(review): statement-level mutation probes in test-efficacy (#8020)
* feat(review): statement-level mutation probes in test-efficacy
The revert probe is all-or-nothing: it reverts whole production files, so a
suite that gates six of a diff's behaviours goes red and the probe says
"gated" — even when the seventh behaviour, a one-line safety statement, has no
test at all. Dogfooded on a live PR: deleting a single `reminders.clear()`
inside the not-continued branch left the full 471-test suite green, and that
line carried the PR's headline safety property (an abandoned task's todos must
not bleed into an unrelated new prompt). A human reviewer found it with a
hand-rolled mutation probe; the command could not.
Add the probe kind the human ran, deterministically. Candidates are added
lines from the committed head (never the dirty worktree) whose trimmed
statement calls a safety verb — .clear(), .delete(), .reset(), .abort(),
.removeListener(), .unref() — or reassigns state to empty ([] / new
Map()/Set()), and that are removable as a whole: single complete expression
statements, brace-balanced via a string/comment-aware scanner, previous
significant line ending ;, { or } (which rejects fluent tails, continuations,
and the brace-less-if silent-rebind trap), outside template literals and block
comments. Selection is conservative by design: a false negative costs
nothing, a false positive burns a suite run.
Each mutant (capped at 8, files with collocated new tests first) deletes its
one line in the existing probe worktree and re-runs the affected suites
through the existing vitest-json classifier: red = killed (the line is
guarded), green = SURVIVED — the invariant it enforces ships unprotected —
filed as a finding in the unreachable/inert register so the Agent-7 pipeline
picks it up without any skill change. Compile/load failure = inconclusive,
never a finding. Mutants run only after a cleanly green baseline, inside the
command's existing deadline budget (each run must leave room for the revert
probe); candidates that no longer fit are counted, not silently dropped.
* fix(cli): harden test-efficacy mutant selection text checks (#8020)
Mutant selection ran its end-anchored checks on raw trimmed lines, so a
trailing comment hid a statement's real end (dropping genuine candidates
like `reminders.clear(); // why`) and a safety verb inside a string faked
one (a wasted suite run plus a misleading survivor). Route the
`SAFETY_VERB_RE`, `endsWith(';')`, and predecessor `/[;{}]$/` checks
through a shared `codeOnly()` that strips comments and blanks literal
contents first.
Also guard the template-state escape skip against a backslash-continued
line, mirroring the single/double-quote branch: swallowing that newline
dropped a per-line literal flag and shifted every later line's verdict.
Adds unit coverage for all three selection fixes and an integration test
for the baseline-not-green skip branch.
* fix(cli): gitignore fake vitest in test-efficacy integration fixture (#8020)
The fake vitest bin was committed by `git add -A` and checked out into
the probe worktree as the stale passing copy, so installFailingVitest's
overwrite in the main worktree was never seen by npx in the probe tree.
The baseline read green and the baseline-not-green skip test failed.
* fix(review): make mutation-probe reporting precise (#8020)
Address review feedback on the test-efficacy mutation probe:
- Count candidates the MAX_MUTANTS cap drops in `skippedForCap` instead of
silently losing them, so a capped `survived: 0` cannot read as "every
safety statement is covered" (mirrors the existing `skippedForBudget`).
- Gate the mutant phase per probe file: run each mutant against the files
that are green in the unmutated baseline, so one unrelated quarantined
(all-skip) suite — `inconclusive`, not red — no longer disables the whole
probe.
- Scope the `mutant-survived` finding to the diff's own tests ("confirm an
existing test covers it, or add one") rather than asserting the invariant
ships unprotected, which an untouched pre-existing test may still cover.
* test(review): cover the budget-skip path in mutation-probe integration tests (#8020)
* fix(review): harden mutation-probe selection and diff parsing (#8020)
* fix(review): handle multi-line class headers in mutation-probe selection (#8020)
* fix(review): stop class-body walk at braces before matching class keyword (#8020)
* fix(review): whole-file literal scan for mutant selection; pin the untested guard paths
The per-line scanner pair (codeOnly + lineStartsInsideLiteral) shared a blind
spot: a backtick inside a `${…}` interpolation read as the outer template's
closing backtick. That flipped the literal state for every following line and,
in the single-line skip, exposed nested-template content as code — so a safety
verb inside a string could be selected as a mutant (a false-finding vector) and
a class field below a brace-bearing template could slip the class-body
rejection. Replace the pair with one whole-file pass that tracks interpolation
brace depth: per-line code text with comments stripped and literal contents
blanked, plus the same `${}`-aware skip for delimiter scanning. Differential
audit over every core+cli source file: zero selection differences on real code;
the pathological shapes are pinned by three new tests.
Also pin the remaining untested paths from review: the selection-failure catch
(discloses and still runs the revert probe), the runOneMutant line-mismatch
guard (now exported; inconclusive, file untouched), and the budget-skip stdout
disclosure.
* fix(review): clamp probe deadlines to the whole-command budget (#8020)
* fix(review): harden mutant selection guards and disclosure accounting (#8020)
* fix(review): close silent-zero paths in mutant disclosure and harden diff parsing (#8020)
* fix(review): drop interpolation quote-skip that mis-parsed regex literals (#8020)
* fix(review): track template nesting with a stack; disclose derailed files; harden and pin the remaining probe paths
The nested-template fix that landed as a counter cannot represent a nested
template INSIDE a nested interpolation: at two levels the deep template's text
`}` is charged against the wrong frame, the scan desyncs, and the file either
admits template text as a mutant or derail-drops its real candidates. Replace
the counter with a stack — one frame per open template, `}` closes only the
top interpolation, a backtick closes only the innermost template — and derive
the end state from the stack. The two-level trigger is pinned by a test
written red-first against the counter.
Derailed files are now disclosed, not silently dropped: selectMutants returns
them, and the note composer stacks the derail note with the red-baseline note
instead of clobbering. The hostile-git-config path gets its missing test (repo
diff.srcPrefix/dstPrefix, diff.external, core.quotePath with a non-ASCII
path — fails with the pinned flags removed). The budget test drops its
Date.now call-count coupling for an injected clock threaded through
runTestEfficacy/runProbeSuite. The mutation-phase catch gets an end-to-end
test (ENOBUFS mid-phase → all candidates inconclusive, revert probe still
runs, report still written).
---------
Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
|
||
|
|
c50120985b
|
fix(serve): Prevent repeated workspace skill rescans (#8080)
* fix(serve): prevent repeated workspace skill rescans Make workspace skill status reads use committed snapshots and move refresh work to explicit mutation paths. Add generation-safe daemon caching, conditional HTTP responses, SDK revalidation, and multi-session extension refresh safeguards. Refs #8079 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): narrow the workspace-skills read model and close its regressions Follow-up to the previous commit on this branch, from reviewing it. Subtractions — these were separable from the fix and carried more surface than value, so they move out of this change: - Revert the ETag / If-None-Match layer (CORS allow+expose headers, the SDK conditional JSON cache, the browser bundle budget bump). Express already emits an ETag and answers 304 for these routes, so the only new behavior was the SDK cache. It saves transfer bytes but no daemon work — the ETag is a hash of the already-serialized body — and it shipped without a paired `Cache-Control`, which is what actually keeps an intermediary from serving a stale snapshot of an authenticated, mutable resource. The SDK cache was also unbounded, with no eviction or clear entry point. - Revert moving `extensions_final` ahead of skill initialization in `Config.initialize`. In non-safe, non-bare mode `extensions_initial` is already the same argument-less `refreshCache()`, and it runs `applyStoreActivation`, so `getActiveExtensions()` is fully populated before skills are enumerated either way. The move changed only startup event order (and pushed permissionManager past the extension refresh) for every surface including the interactive CLI. Regression fixes — the read went pure, but two of its inputs lost their only path back to disk: - Extension sources have no watcher, unlike skills. With the per-read `extensionManager.refreshCache()` gone, an extension installed, removed, enabled, or disabled outside the daemon would never reach the snapshot until the child restarted — and because extension-level skills are derived from the extension set, a skill-watcher tick could not recover it either. Adds `ExtensionManager.refreshCacheIfSourcesChanged()`: a stat-based fingerprint over the extension directory entries, each manifest, the enablement file, and the store state, which refreshes only when they moved. A status read pays one readdir plus one stat per entry instead of a directory scan and a full parse, and stays self-healing. The baseline is the pre-load fingerprint, so a change landing during a refresh stays visible to the next check instead of being masked by a post-load stat. The directory and store halves are captured at different points because a refresh writes the store itself but never the manifests. - Revalidation is skipped in safe and bare mode, and the whole of it — including that mode check — sits inside its error boundary. Those modes never populate the extension cache by design, while the snapshot derives extension skills from `getExtensions()`, so revalidating there would have loaded the extensions the mode exists to exclude. Keeping the mode check outside the boundary would also have let a config missing those accessors fail a read. - `initialized: true` with an empty list when the config has no `SkillManager` is now `initialized: false`. The daemon latches any initialized answer into `lastWorkspaceSkillsStatus` and then prefers it over its own local enumeration, so the old value could suppress the fallback permanently. Also: - The retained-snapshot path bumped the freshness timestamp without checking its generation, so a read that started before an invalidation could push out the TTL of a snapshot a later read had committed — letting a post-mutation snapshot go unrevalidated for longer than the window. - `setWorkspaceSkillEnabled` folded `configsFailed` into `sessionsFailed`, but it sends `reason: 'settings'`, which never refreshes a skill cache, so the term was structurally zero. Report `configsFailed` from the `content` path instead, where it can actually be non-zero. - Documents the settings-freshness gap this read model accepts: enablement now comes from the child's in-memory `LoadedSettings`, which `SettingsWatcher` keeps current for the User and Workspace scopes but not for System / SystemDefaults (locked-skill policy) or an untrusted workspace. Tests: adds a real-filesystem guard that drives 50 consecutive cached reads and asserts zero additional readdir/readFile calls — the mocked suites could only prove `refreshCache` was not *called*, which is not the invariant that broke. Adds coverage for the fingerprint gate (steady state, install, removal, in-place manifest edit, concurrent callers, and the mid-refresh race), for the null-manager, moved-sources, and safe/bare-mode read paths, and for the generation guard. The generation-guard and safe/bare-mode tests were each verified to fail with their fix reverted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f3ad4fcffb
|
feat(serve): page large text files by byte cursor (#8002)
* fix(serve): allow bounded reads of large text files
* fix(serve): bound large-text reads by scan cost, not by which knob was set
Follow-up to the bounded large-text read path. Three changes:
Gate on any explicit window argument, not on `limit`. Gating on `limit`
had the cost model backwards in both directions: `{ line: 900_000_000,
limit: 20 }` was admitted despite walking the whole file, while
`{ maxBytes: 4096 }` — satisfiable from the first 4 KiB — was refused. A
read with no window argument at all still fails, since a caller that
believes it holds the whole file may write it back truncated.
Add MAX_TEXT_SCAN_BYTES (8 MiB). MAX_READ_BYTES caps what a read
returns; nothing capped what it cost. Line offsets are resolved by
scanning from byte 0, so a query param could turn into an
uninterruptible multi-second scan of an arbitrarily large file — and on
Windows hold a read handle for that span, blocking renames and deletes.
Past the budget the read is refused with `file_too_large` pointing at
readBytes, which reaches any offset in O(1).
Tolerate appends on streamed windows. Requiring whole-file size/mtime
stability after reading a prefix rejected reads whose returned bytes
were still valid, and the case it rejected — tailing a live log — is the
one this path exists for. Streamed windows now assert inode identity
plus "did not shrink"; truncation and replacement are still rejected.
Also: non-UTF-8 large text now returns `binary_file` rather than
`file_too_large`, so a client retrying on 413 with a smaller window
can't loop forever; and `readFileWithLineAndLimit` throws instead of
silently ignoring a caller-supplied `fileHandle` on the by-path
fallback.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(core): thread the descriptor instead of forking text-read helpers
PR #7947 pinned large-text reads to one inode by threading a caller-owned
FileHandle into readTextRange as an optional field, plus a second field,
forceStreaming, to suppress the buffering fast path. Two optional fields
produced four combinations: one meaningful, one used by a single test, one
unreachable, and — in readFileWithLineAndLimit — one that silently fell
through to a by-path read, defeating the reason the caller opened a handle.
Unify the two encoding detectors. detectFileEncoding now takes a path or a
borrowed handle, so detectFileHandleEncoding is deleted along with the
message discrepancy between them: an encoding iconv-lite cannot load now
raises LargeNonUtf8TextError naming that encoding rather than deferring to
the decoder's generic invalid-utf8 variant. Both still refuse the file, and
the Serve boundary maps both to binary_file.
Split the reader into readTextRange (path) and readTextRangeFromHandle
(always streams, both byte bounds required). The unreachable combination and
its untested readFileHandleBuffer are gone, and with no fileHandle parameter
left for readFileWithLineAndLimit to ignore, the RangeError guarding that
fallthrough is deleted too — the trap can no longer be expressed.
CoreReadTextFileHandleRequest drops its required stats field. Nothing
downstream read it, and because the ACP request type it extends permits
extra properties, TypeScript accepted the dead argument silently.
readFileHandleChunks becomes chunksFromHandle(fh, from) — the one seam
byte-cursor text paging needs.
No observable change at the Serve boundary: its 222 tests pass unmodified.
Two fileSystemService tests were deleted rather than repaired; they asserted
the arguments readFileWithLineAndLimit received, which is nothing once the
handle path stops calling it. Their coverage lives in read-text-range.test.ts
against real files and in workspace-file-system.test.ts at the real boundary.
258 production lines in core, net -71 overall.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(core): make CoreReadTextFileHandleRequest standalone
Self-audit follow-up to f55c867a. Two fields survived the reshape that the
handle path never reads:
- `stats` was documented as required ("must pass the Stats captured from that
handle") and nothing downstream read it. The handle path always streams, so
it never needs a size to choose a strategy, and the encoding probe does its
own fstat.
- `path` became dead once readTextRangeFromHandle replaced the path-plus-handle
call. Errors are labelled with the path by the Serve boundary that owns it.
Neither was caught by the compiler: the ACP ReadTextFileRequest the type
derived from permits extra properties, so the CLI kept passing both silently.
That is the argument for declaring the type standalone rather than Omit-ing
four of six inherited fields and quietly re-admitting the rest.
Also record the second behaviour delta of the detector merge in the design
doc: detectFileEncoding catches I/O errors and falls back to 'utf-8', where
detectFileHandleEncoding let them propagate. The failure is not lost — a handle
that fails the 8 KiB probe fails the streaming read immediately after — but a
different call now reports it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(serve): page large text files by byte cursor
Line offsets address a byte stream, so `readText` resolves them by scanning
from byte 0. Paging a large log that way is O(n^2) across pages, and past
MAX_TEXT_SCAN_BYTES (8 MiB) a deep page is refused outright — agents had no
O(1) path short of dropping to GET /file/bytes and splitting lines themselves,
losing encoding handling, multibyte safety, and the binary_file refusal.
A response that leaves content behind now returns `hasMore`, and where a file
byte offset is derivable, an opaque `nextCursor`. Passing it back as `cursor`
resumes in O(1). Page 1 is an ordinary `limit` read, so clients never compute
byte offsets themselves, and a paging loop does not break when a file happens
to be small.
The cursor is unsigned base64url JSON carrying {off, size, dev, ino}, matching
encodeOrganizedCursor rather than the HMAC-signed transcript codec: the path is
re-resolved through the workspace boundary on every request, so a forged cursor
can only move the offset within a file the caller may already read — what
GET /file/bytes?offset= allows today. What the payload is for is staleness:
a replaced or truncated file yields hash_mismatch instead of bytes from the
wrong place, while an append leaves an outstanding cursor valid — the case the
feature exists for.
Every minted cursor points at the start of a line. When a single line exceeds
maxOutputBytes the reader emits a truncated prefix and skips to the next line
rather than resuming mid-line, because a mid-line cursor makes the following
page snap forward and silently drop the rest of that line at the seam. Windows
cut mid-line by a byte cap therefore report hasMore with no cursor, as do
non-UTF-8 snapshot reads whose decoded text is a UTF-8 re-encoding with no
mapping back to file offsets. That is why hasMore is a field rather than a
restatement of nextCursor.
Cursor reads branch before the size check, not by widening the window gate:
a cursor read of a file under MAX_READ_BYTES would otherwise land on the
snapshot path, which knows only line/limit, and silently return line 0.
Adds the workspace_file_read_cursor capability, per the convention that new
behavior gets a new tag, and retargets the scan-budget hint at cursor paging.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(core): advance UTF-8 cursors after truncation
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(serve): clarify cursor bootstrap limits
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(sdk): raise daemon browser bundle budget
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(serve): cover ACP cursor dispatch and cursor binary_file mapping (#8002)
* fix(core): only set sawCrlf for emitted lines in cursor paging (#8002)
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
|
||
|
|
953c9d8177
|
feat(core): tag UserPromptSubmit hook context and record display provenance (#7956)
* feat(core): tag UserPromptSubmit hook context and record display provenance UserPromptSubmit additionalContext was appended to the request as a bare text part and persisted verbatim, so hook-injected text was indistinguishable from user-authored text in the transcript, polluted resumed sessions, telemetry, and auto-memory recall queries. - Wrap injected context in a reserved <qwen:user-prompt-submit-context> tag (hook output already escapes angle brackets, so the tag cannot be forged from inside). - Record the pre-injection user prompt as systemPayload.displayText plus the injected string as hookContext on the user record; the model-bound message stays verbatim for faithful resume replay. - Use the pre-injection prompt text for telemetry prompt attributes and managed auto-memory recall. - Resume projection prefers displayText, strips a trailing whole-part tagged block when no payload exists, and leaves legacy bare-injected records unchanged. - Apply the same tag wrapping on the ACP session injection path, which already records the pre-injection prompt. Closes #7940 Co-authored-by: Cursor <cursoragent@cursor.com> * docs: note UPS promptText TDZ ordering and sole-part resume guard Document the conflict-resolution constraint that promptText must be declared before the injection assignment, and the sole-part read-path guard that keeps a user-authored whole-tag message intact. Co-authored-by: Cursor <cursoragent@cursor.com> * test(cli): cover at_command resume with tagged UPS context Confirm the at_command branch still prefers payload.userText when a paired user record carries a trailing tagged hook-context part, and falls back to the tag-stripping projection only when userText is absent. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(core): address PR 7956 review findings and Goal recording spy Omit the optional UserPromptRecordPayload third arg when no hook injected, so Goal admission spies expecting two args stay exact and CI client-goal.test.ts passes. Project plain UserPromptSubmit-augmented records through transcript-replay with the same displayText / trailing-tag strip fallback as the TUI, covering ACP/export surfaces. Strengthen the displayText preference fixture so it disagrees with the tag-strip path, and use the named UserPromptRecordPayload type in resume. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(acp-bridge): import UPS tag helper via Node-free package export transcript-replay is inlined into the browser daemon/transcript SDK bundle. Importing isUserPromptSubmitContextPartText from the core package barrel pulled the whole Node-bound core graph into that bundle and failed CI (esbuild Could not resolve "node:*") across Test, web-shell E2E, and Real daemon E2E. Export the pure helper as @qwen-code/qwen-code-core/userPromptSubmitContext and import that path instead. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(test): alias userPromptSubmitContext for Vitest source resolution CLI and acp-bridge Vitest configs already map goalWire/transcriptRecords to TypeScript sources; without the same alias the new package export fails import analysis and breaks dozens of CLI suites. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(acp-bridge): keep images when projecting displayText user records Preferring UserPromptSubmit displayText previously returned early and skipped projectMessageParts, dropping multimodal inlineData. Rebuild parts so displayText replaces text while images keep their order. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(core): drop unused hookContext and cover image-only displayText UserPromptRecordPayload.hookContext had no read sites; keep displayText only and recover injected text from the tagged message part. Also cover the image-only !replaced append path and simplify the recording guard. Co-authored-by: Cursor <cursoragent@cursor.com> * test: cover remaining UserPromptSubmit provenance Suggestions Share stripTrailingUserPromptSubmitContextPart between TUI resume and ACP replay, assert ACP Session tags additionalContext, and lock telemetry to the pre-injection prompt text. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
19761af072
|
fix(cli): stamp QWEN_CODE_CLI at the workspace entry and publish QWEN_CODE_MODEL (#7993)
* fix(cli): stamp QWEN_CODE_CLI at the workspace entry and publish the active model as QWEN_CODE_MODEL
Skill subprocesses shell out through `"${QWEN_CODE_CLI:-qwen}"`. The npm entry
(scripts/cli-entry.js) stamps QWEN_CODE_CLI, but the workspace entry
(packages/cli/dist/index.js) never did — so a dev run, or any direct
`node dist/index.js` launch, leaves the variable unset and every review
subcommand the /review skill issues silently lands in whatever global `qwen`
PATH resolves to. Measured on a live run: a freshly built CLI's review pipeline
executed entirely on a global v0.21.0 — `script-lint` did not exist there, so
the deterministic gate the skill expected was silently absent, and any
behavioral fix to the review CLI is inert in such runs.
Stamp the entry in runCliEntryPoint, first-writer-wins: an outer launcher
(cli-entry.js, the desktop shim) has already stamped in-process and must keep
winning; an empty value counts as unset, matching the consumer's `:-`
semantics. The entry is derived as `../index.js` from the compiled
dist/src/cli.js — the shebang-bearing bin — and skipped entirely for non-file
schemes (vitest) and unbuilt layouts (tsx dev runs keep today's fallback).
tsc emits dist/index.js as 0644 and the spawn-time filter blanks a
non-execable entry, so the stamp grants 0o755 best-effort; a failed chmod
degrades to today's `qwen` fallback.
Separately, subprocesses had no authoritative way to learn the ACTIVE model:
the /review skill's compose step wants a modelId, and the orchestrator resorts
to reading settings files — wrong under QWEN_HOME isolation and after /model
switches (measured: a report stamped with a model that never ran the review).
Publish QWEN_CODE_MODEL exactly the way QWEN_CODE_SESSION_ID is published: the
first Config claims the process-global slot, only the claiming instance
republishes (on refreshAuth and every model-change notification), and
getShellContextEnvVars passes it through, omitted when absent. The daemon
limitation is the session ID's own — later sessions read the first session's
model — and is documented at both the producer and the consumer.
* fix(core): clarify QWEN_CODE_MODEL daemon comment and cover refreshAuth republish (#7993)
* test(cli): cover stampCliEntryEnv wiring in runCliEntryPoint (#7993)
* fix(core): publish QWEN_CODE_MODEL per session and preserve entry mode on stamp (#7993)
Address review feedback:
- Key QWEN_CODE_MODEL on the session (registerSessionModel/getSessionModel),
mirroring the project dir, so daemon-mode subprocesses read their own
session's active model instead of the first session's. The process-global
slot remains as the single-session CLI fallback. This also neutralizes the
order-dependent claim: a throwaway Config's registration is keyed under a
session id no real spawn resolves.
- stampCliEntryEnv now adds exec bits to the existing mode (mode | 0o111)
rather than setting 0o755, so a private 0o600 checkout becomes execable
without becoming world-readable.
- Cross-reference scripts/dev.js and scripts/start.js in the stamp doc comment
and note the bundled `node dist/cli.js` launch is intentionally not stamped.
- Widen the AuthType test mock to include QWEN_OAUTH, pin the stamp-before-run
ordering in the wiring test, and cover the per-session model lookup.
* fix(cli): correct comment on Vite rewrite mechanism in protocol guard (#7993)
* fix(core): re-key per-session model registry on startNewSession (#7993)
startNewSession minted a new session id and re-stamped QWEN_CODE_SESSION_ID
but left the per-session model registry keyed on the outgoing id. After
/clear (or /reset, /new, /resume) a non-owner Config's subprocesses then
resolved the model by the new id, missed, and fell back to another
session's value. Unregister the old entry and republish under the new id.
Also correct the stampCliEntryEnv comments: npm start / npm run dev route
through scripts/start.js and scripts/dev.js, which stamp QWEN_CODE_CLI
themselves, so the only uncovered launcher is a direct node dist/index.js.
---------
Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@alibaba-inc.com>
|
||
|
|
0a9db38221
|
feat(review): add review run — headless review with a machine-readable verdict (#7983)
* feat(review): add `review run` — headless review with a machine-readable verdict The review pipeline already runs non-interactively: `qwen --prompt "/review …"` expands the bundled skill, launches the dimension agents, and honors the approval mode. What that path lacks is a contract. The verdict lives in the model's prose and in files whose names the caller must simply know, the exit code says nothing about the outcome, and piped stdin silently defeats slash-command detection (the runner prepends piped input, so the leading `/` is no longer first). Anyone who wants "run a review, tell me what it decided" ends up scraping a terminal. `qwen review run [target]` is that contract and nothing more. It assembles the /review invocation from typed flags (--effort, --comment), re-enters this build's own CLI in a child process with stdin closed, streams the child's progress to stderr, and then reads the verdict from the artifact compose-review wrote — the same JSON the skill treats as the verdict authority — never from anything the model printed. stdout carries only the result (human lines, or the full JSON with --json). Exit codes make the outcome scriptable without parsing: 0 = the review completed (whatever it decided), 1 = it never reached a verdict (child failure, timeout, or no composed artifact — a clean child exit without one is a run that wandered off, not an approve), 3 = completed AND --fail-on request-changes AND the event is REQUEST_CHANGES, so a CI gate can tell "blocking verdict" from "the tool broke". Artifact discovery is scoped to this run (mtime cutoff with a small slack for coarse filesystem clocks): a stale composed JSON from an earlier review says whatever THAT review decided, which is exactly the wrong thing to republish. * fix(cli): harden review run against EPIPE, target injection, and drift (#7983) - Use writeStderrLineSafe in the timeout and spawn-error handlers and guard the progress stream, so an EPIPE on stderr can no longer skip the child kill, hang the promise, or orphan the review. - Reject a review target carrying whitespace or a leading dash before it is re-tokenized by the child CLI (e.g. `123 --comment` silently authorising posting). - Constrain --approval-mode to the same choices as the top-level CLI. - Capture the child's exit signal and surface it (OOM/SIGKILL vs spawn fail). - Sync the top-level `qwen --help` review description with the command. - Register `run` in the review.test.ts subcommand expectation and add tests for the timeout branch, the readComposed guard, and target rejection. * fix(cli): kill process group on review run timeout, harden edge cases (#7983) The CLI relaunches itself in a child process (for --max-old-space-size), so child.kill() only reached the relaunch wrapper — the real review was reparented to PID 1 and kept burning API calls. Spawn with detached:true and kill the process group (-pid) so the timeout actually terminates the review. Also: clamp negative --timeout-minutes to a 1-minute floor, distinguish a corrupt composed artifact from a missing one in human-readable output, and add test coverage for the default (non-JSON) output path. * fix(cli): use specific MockInstance type for process.kill spy (#7983) * fix(cli): capture review run verdict before cleanup, forward signals (#7983) * fix(cli): reject quoted review targets, pin signal forwarding (#7983) * fix(cli): keep captured review verdict when timeout fires after compose (#7983) --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: qwen-code-bot <qwen-code-bot@users.noreply.github.com> |
||
|
|
2abfa3d54e
|
fix(core): preserve active Todo context across tool turns (#7919)
* fix(core): preserve active Todo context across tool turns * test(cli): update automatic turn prompt expectation * fix(core): preserve Todo ownership across automatic turns * fix(core): preserve Todo ownership at prompt boundaries * test(todo): cover automatic reminder boundaries * fix(core): throttle active Todo reminder re-injection to bound history growth Every injected reminder copy lands permanently in chat history, so per-turn injection grew the live context linearly with tool turns. Tool-turn injection now re-issues the reminder only every third tool turn since the state was last presented; turn-start injections always fire and reset the cadence. The payload becomes a compact status/content line list capped at 800 characters. History stays append-only, so provider prefix caching is unaffected. Also: cover the new-ordinary-prompt-clears-stale-reminders invariant on the real Config, add TUI coverage for the work-chain notification batch split, cover todoWorkChainId continuation forwarding, and document the deliberate enterWith binding in the daemon tool runner. * fix(core): keep todo reminder before drained input --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
58eb07117b
|
fix(serve): Add certified session writer handoff (#7976)
* fix(core): add certified session writer handoff Seal managed writer ownership after a durable recorder drain and allow trusted replacements to take over only when the transcript proof still matches. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): retain writer claim after failed rollback Keep the fixed transition claim unless the exact predecessor primary is restored, and avoid rollback after claim ownership changes or unlink completes with a reported error. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): close session handoff claim races Wait for claim-aware primary candidates to back out of transition gaps, and release losing claims when no primary transition started. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): bound session handoff candidate waits Fail closed when a claim-aware primary candidate is abandoned during a handoff transition, preserving the transition fence for authoritative recovery. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): reconcile session handoff proof edges Reject dangling transcript paths and reconcile ambiguous lock-record link outcomes by exact bytes before continuing a certified handoff. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): preserve inspect error cause and document handoff fail-closed paths (#7976) --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
563e51540c
|
test(web-shell): expand restored-history pagination regression coverage (#7907) | ||
|
|
18cb393e4b
|
feat(core): preload deferred tools within a context-window threshold (#7922)
* feat(core): preload deferred tools within a context-window threshold Adds tools.toolSearch.threshold (default 10, percent of the context window). At session start, when the combined estimated schema footprint of every deferred tool - bundled built-ins and MCP alike - fits within the budget, all are revealed upfront so the declaration list stays stable for the whole session and prefix KV caches survive; otherwise everything stays deferred. Set 0 to always defer. Mirrors Claude Code's ENABLE_TOOL_SEARCH=auto threshold mode, extended to bundled deferred tools because here every reveal rewrites the declaration list and busts the prompt-cache prefix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(core): log deferred-tool preload budget decision Emit debugLogger diagnostics in preloadDeferredToolsWithinBudget covering the computed budget, estimated token footprint, candidate count, and which branch of the all-or-nothing gate was taken (no candidates, over budget, or preloaded). Lets an operator diagnosing session-startup cost tell from debug logs whether the deferred set fit the budget or was left behind ToolSearch, without adding temporary instrumentation. No behavior change. * fix(tools): bound toolSearch.threshold to 0-100% The threshold setting is a percentage of the context window but had no upper bound, so a value like 200 (a typo or misreading of the "(%)" label) made the preload budget exceed the whole window and unconditionally preloaded every deferred tool — the opposite of the prefix-stability the threshold buys. - Add minimum:0/maximum:100 to the setting schema (jsonSchemaOverride, like autoCompactThreshold) and regenerate the VS Code settings schema. - Add a symmetric runtime upper guard next to the existing 'thresholdPercent <= 0' lower guard in client.ts, clamping to 100% so a hand-edited settings file cannot slip a larger budget past validation. Adds a client test asserting a 200% threshold clamps to a full-context budget. * test(core): cover configured preload budget * fix(tool-search): harden preload threshold * test(tool-search): cover preload exclusions --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
97aaa3808d
|
feat(review): disclose a zero-finding Approve on a non-trivial diff as low signal (#7987)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
Every gate compose-review enforces proves the agents READ the diff — coverage proves the transcripts, anchors prove the quotes — but none proves the review could tell good code from bad. Dogfooded: a weak-model run drafted nothing from its entire roster on a diff where stronger same-condition runs found a verified blocking Critical, and compose printed a bare, confident "Verdict: Approve". A reader takes that as evidence of quality when it is only absence of signal — the most dangerous shape a review verdict can have. Disclose it, deterministically. When the composed event is APPROVE and the plan's srcDiffLines — the same field the review topology is chosen from, with tests, docs and generated files excluded by construction — exceeds 100, the verdict line names the shape: Verdict: Approve — low signal: none of the 11 review agents reported a finding on a non-trivial diff (155 source diff lines) The event never moves on it: a cap would punish every genuinely clean diff, and nothing was in fact found. Docs-only and typo-class diffs keep their bare Approve — there, finding nothing is the expected outcome. The floor sits well past the typo-fix class (a tiny edit scattered one line per hunk stays under it) and at a fifth of the smallest diff the topology gate calls big. Both numbers in the line are the run's own: the roster the plan required (all on record at APPROVE, or coverage would have capped) and the plan's source-line count. Co-authored-by: verify <verify@local> |
||
|
|
566d2c1279
|
fix(cli): stop resize repaint from causing scroll storm (#8009)
* fix(cli): stop resize repaint from causing scroll storm (#8004) Remove the useResizeSettleRepaint -> refreshStatic wiring that wrote clearTerminal (destroying scrollback) and remounted <Static> on every settled resize, re-emitting all conversation history in 50-item chunks. Ghostty's panel-toggle animation exceeds the 200ms debounce, triggering multiple settle-repaint cycles per toggle -- visible as continuous scrolling/flickering. Ink's dynamic region already re-renders on width changes via useTerminalSize; modern terminals handle scrollback reflow natively. The full remount is no longer necessary. The now-unused hook and its test are removed (no remaining callers). * test(cli): guard resize no-repaint contract against settle-time regression (#8004) * test(cli): make resize settle regression test non-vacuous (#8004) The previous test used rerender() which remounts the tree via ink's ErrorBoundary (measureElement returns undefined → layout effect throws → tree unmounted), so the settle debounce never fired and the test passed regardless. Rewrite to keep the tree alive (measureElement mock returns a real value) and deliver width changes to the same mounted instance via a listener pattern. Mutation-verified: fails when the removed useResizeSettleRepaint hook is restored. * chore(cli): remove dead useResizeSettleRepaint from eslint legacy filenames (#8004) --------- Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
c97026040e
|
feat(channels): add pairing approval management API (#8045)
* feat(channels): add pairing approval management API * fix(sdk): expose pairing approval types Re-export the new approval and revocation types from the public SDK entry, and pin the qualified workspace DELETE request body in regression coverage. --------- Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
3bcb4d3412
|
fix(channels): disable native cron in daemon sessions (#8034)
* fix(channels): disable native cron in daemon sessions * test(cli): cover Cron restore routing --------- Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
ec9c36ef82
|
feat(channels): add GitLab polling channel adapter (#7862)
* feat(channels): add GitLab polling channel adapter
Poll GitLab todos via @gitbeaker/rest, dispatch notes through the
existing PollingChannelBase pipeline. Key design points:
- action_prompt_template config drives event filtering and metadata
rendering (unconfigured actions are skipped)
- Per-repo cursor (repo[chatId].last_read) as notes window lower bound,
global lastProcessedAt for todo-level dedup
- mark_done after successful processing; failure skips mark_done for
retry on next poll
- Mention gating delegated to base GroupGate (adapter only sets
isMentioned flag)
- First-contact body fallback for todos with no notes (e.g. mention in
issue description)
* fix(channels/gitlab): persist cursor after each successful todo
Call saveCursor() immediately after advancing lastProcessedAt so that
progress is durable even if the process crashes mid-poll. Also removes
the local watermark variable in favor of direct assignment.
* fix(channels/gitlab): persist cursor on every advancement including skips
* fix(channels/gitlab): address review critical issues
- Remove non-functional proxyAgent (gitbeaker doesn't support it)
- Construct repo_url from host + path (API doesn't return web_url)
- Handle directly_addressed action (falls back to mentioned template)
- First-contact fetches target description instead of using todo.body
- Move todo.project dereference inside try block
- Filter confidential notes
- Update channel-registry.test.ts for gitlab entry
* fix(channels/gitlab): address review suggestions
- Warn on connect if action_prompt_template is not configured
- Guard todo.target.iid before use
- Skip paths now mark_done (best-effort) to clean GitLab UI
- Remove postErrorComment (avoids duplicate comments on retry)
- Fetch only first page of notes (desc, maxPages:1, perPage:100)
instead of paginating entire note history
- Extract fetchRecentNotes for single-page windowed enumeration
* refactor(channels/gitlab): simplify to todo.body dispatch, add description mention support
- Remove notes API fetching; dispatch todo.body directly
- Detect description mentions via target_url anchor (#note_ absence)
- Always fetch target description for %description% metadata
- Remove per-repo cursor; dedup via cursor + mark_done only
- Cursor advances regardless of success/failure (no retry)
- Use zod for cursor validation
- Rename template vars to GitLab terminology:
%project% %project_url% %target_type% %iid% %title% %description% %todo_id%
- Support %% escape for literal percent
* docs(channels): add GitLab adapter documentation
- New user guide: docs/users/features/channels/gitlab.md
- Update _meta.ts navigation
- Update developer adapter matrix and SDK list
* fix(channels/gitlab): use correct Issues.show(issueIid, { projectId }) signature
* chore: regenerate NOTICES.txt for new gitlab channel dependencies
* fix(channels/gitlab): address review suggestions
- Add todo.project null guard (item 2)
- Single-pass regex for %% escape + %var% substitution (item 4)
- sendThreadMessage throws directly on undefined threadId (item 5)
- Dedup fetchDescription with per-poll cache (item 6)
- Remove per-todo saveCursor; base class saves after pollOnce (item 7)
- Add undefined threadId test (item 8)
- Expand confidential notes limitation in docs (item 3)
* test(channels/gitlab): add mention tests, directly_addressed coverage, skip assertions, temp cleanup
- New mention.test.ts: 14 cases for testBotMention/stripBotMention/escapeRegex
- Add directly_addressed fallback test
- Skip tests now assert TodoLists.done + cursor advancement
- afterEach cleans up mkdtempSync temp dirs
* fix(channels/gitlab): address review round 4
- Non-mention actions (assigned, etc.) set forceMentioned=true to bypass GroupGate
- Merge dead note-filter tests into single 'skips todo authored by bot'
- Log fetchDescription errors to stderr instead of silent swallow
- Post error comment on issue/MR when handleInbound fails (best-effort)
* fix(channels/gitlab): always force isMentioned=true, remove regex re-derivation
The action_prompt_template config is already the event filter, and
GitLab has already decided the mention when creating the todo.
Re-deriving isMentioned via regex on todo.body causes permanent
message loss when the regex misses (description mention + fetch
failure, group mentions). Always set forceMentioned=true so
GroupGate never drops a todo that passed the template filter.
* fix(channels/gitlab): propagate fetchDescription errors for description mentions
For note mentions, description is metadata-only — fetch failure is
logged and swallowed. For description mentions, description IS the
message — fetch failure now propagates to the outer catch, which
posts the ⚠️ error comment so the user knows to re-mention.
* perf(channels/gitlab): clean up stale todos, skip unnecessary fetchDescription
- Mark stale todos (updated_at <= cursor) as done on each poll to
prevent perpetual re-fetching of pre-existing pending todos
- Skip fetchDescription for note mentions when template does not
contain %description%, saving one API call per todo
- Update docs: stale todo cleanup, error comment on failure
* docs(channels/gitlab): clarify requireMention is bypassed, template is the real filter
* Apply suggestions from code review
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
* fix(channels/gitlab): use todo ID cursor instead of timestamp to eliminate equal-timestamp loss
Timestamp-based cursors (second granularity) could silently destroy
todos sharing the same updated_at as the cursor boundary. Switch to
monotonically increasing todo IDs which are unique and collision-free.
Add initialized flag to preserve first-start drain semantics: pre-existing
pending todos are marked done without dispatch on the first poll cycle.
* fix(channels/gitlab): harden first-poll drain, add ordering tests, fix lockfile
- Replace Math.max(...spread) with reduce to avoid RangeError on large
backlogs (~100k+ todos). Move initialized=true after the drain work so
any throw retries the drain instead of falling through to dispatch.
- Add unit tests: identical-timestamp delivery and id-order-when-updated_at-disagrees
(kills M2 sort mutant).
- Align lockfile: file:../base → ^0.21.0 for channel-base dep.
* fix(channels/gitlab): include dot in mention lookahead for GitLab usernames
GitLab usernames may contain dots (e.g. bot.name). The lookahead
character class inherited from GitHub omitted '.', causing @bot.name
to match as @bot. Add '.' to the negated class.
* docs(channels/gitlab): align docs with ID cursor and drain semantics
- Add first-poll drain as step 2 in How It Works
- Clarify GroupGate always passes (isMentioned forced true)
- Document initialized flag in Known Limitations
* Apply suggestions from code review
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
* fix(channels/gitlab): align package version and channel-base dependency to 0.21.1
Bump version from 0.21.0 to 0.21.1 to match other channel packages after
upstream merge. Pin @qwen-code/channel-base to exact 0.21.1 instead of
^0.21.0, matching the convention used by other published channels.
* fix(channels/gitlab): regenerate lockfile to match package.json versions
Manually add only gitlab-related lockfile entries (workspace, @gitbeaker
packages, transitive deps, channel-gitlab link) without unrelated npm
normalization churn.
* test(channels/gitlab): add regression tests for first-poll drain hardening
Two tests that kill the M1 (Math.max spread RangeError) and M2 (flag
ordering) mutants which survived the original 46-test suite:
- 150k todo drain verifies reduce() handles large backlogs without
RangeError and without dispatching
- Drain throw verifies initialized stays false so the next poll retries
the drain instead of falling through to dispatch
Test file duration: ~40ms → ~170ms.
* docs(channels/gitlab): clarify groupPolicy must be "open" and add runtime warning
The default groupPolicy "disabled" silently drops all mentions — todos are
marked done and cursor advances, but no dispatch occurs. Fix misleading docs
that said "GroupGate always passes" (only true at groupPolicy: "open") and
add a connect()-time warning when groupPolicy is not "open".
* fix(channels/gitlab): correct xcase integrity hash in lockfile
The manually added xcase entry had a typo in the sha512 hash (ys → ks),
causing npm ci EINTEGRITY failures in CI.
* fix(channels/gitlab): correct requester-utils integrity hash in lockfile
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(channels/gitlab): allow groupPolicy "allowlist" in warning and docs
The groupPolicy warning and docs incorrectly stated that groupPolicy
must be "open". In reality "allowlist" with the project listed also
works because isMentioned is forced true and GroupGate only requires
the group to be listed. Also fix the inaccurate "no error is logged"
claim — ChannelBase logs preflight rejected reason=group_disabled.
Fixes R5-🟡3 from PR #7862 review.
---------
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
||
|
|
aac663f28a
|
feat(hooks): add security.allowPrivateNetworkHooks to bypass SSRF range checks for trusted scopes (#7968)
* feat(hooks): add security.allowPrivateNetworkHooks to bypass SSRF range checks for trusted scopes HTTP hooks hard-block all private/link-local address ranges via ssrfGuard, which makes them unusable in platform-managed environments where the hook receiver is a first-party, VPC-internal endpoint (e.g. an internal API gateway resolving to 172.16.0.0/12). Add an opt-in setting, security.allowPrivateNetworkHooks (default false), that skips the SSRF IP-range checks in urlValidator.isBlocked (literal IPs) and validateResolvedHost (literal + post-DNS-resolution paths). Security properties: - Honored only from User/System/SystemDefaults scopes; the value is stripped from Workspace settings during the merge (with a startup warning), so a cloned repository can never self-grant the bypass. - BLOCKED_HOSTS (169.254.169.254, metadata.google.internal, ...) remains blocked even when the flag is on. - Default false keeps every code path byte-for-byte compatible with current behavior; bare/safe mode forces it off. * fix(hooks): enforce metadata endpoint blocklist regardless of allowPrivateNetworkHooks Address review findings on #7968: with the flag on, cloud metadata endpoints were reachable through gaps in the relaxed checks. - ssrfGuard: add METADATA_IPS (169.254.169.254, 100.100.100.200) and isMetadataAddress(), which normalizes IPv4-mapped IPv6 forms (::ffff:a9fe:a9fe, ::ffff:6464:64c8, ...) via the existing extractMappedIPv4/expandIPv6Groups helpers. - urlValidator.isBlocked: BLOCKED_HOSTS matching and the literal-IP isMetadataAddress check now run unconditionally; only the general range check (isBlockedAddress) is relaxed by the flag. - httpHookRunner.validateResolvedHost: no longer returns early with the flag on — DNS resolution still runs and resolved addresses are checked against isMetadataAddress, so a hostname resolving to a metadata endpoint is blocked. DNS failures still defer to fetch, as before. - settings warning text now lists User/System/SystemDefaults, matching the schema and docs. - docs: precise wording — the flag relaxes only range checks; metadata endpoints stay blocked in all serialized forms and after DNS resolution. The flag now opens RFC1918/CGNAT/link-local ranges only; cloud metadata endpoints (169.254.169.254, 100.100.100.200 in any form, plus the BLOCKED_HOSTS hostnames) are unreachable in every configuration. --------- Co-authored-by: 欢伯 <ri.xur@alibaba-inc.com> |
||
|
|
7ce9a4fac7
|
fix(cli): MCP prompt completion no longer blocks Enter for optional params (#7995)
* fix(cli): MCP prompt completion no longer blocks Enter for optional params (#7991) The completion handler treated all unused arguments (required + optional) as completion suggestions with auto-appended `="`, making optional params look mandatory. After selecting a prompt name, pressing Enter would auto-append `--input="` instead of executing the prompt. Two changes: 1. Completion handler now parses named args directly instead of using parseArgs() (which returns Error on missing required args — the normal state during tab completion). 2. Only required unused args are suggested as completions. When all remaining unused args are optional, the completion list is empty so Enter executes the prompt with defaults. * fix(cli): restore optional and positional MCP prompt completion discovery (#7991) * fix(cli): share named-arg regex and cover mixed MCP prompt completion (#7991) * fix(cli): suggest optional args mid-keystroke and test multi-word positionals (#7995) --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-bot <qwen-code-bot@users.noreply.github.com> |
||
|
|
97ed1da3c4
|
fix(core): auto-record artifact file writes (#7914)
* fix(core): auto-record write_file artifacts * test(core): cover all inferWorkspaceArtifactKind kind groups (#7914) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(core): cover .htm extension in artifact kind inference (#7914) * fix(core): clarify artifact registration guidance * fix(core): keep artifact extension kinds in sync * fix(core): type workspace write artifacts * fix(core): guard artifact title safety and fill ipynb mimeType (#7914) * fix(core): align artifact guard with store rules and fix Windows test (#7914) * fix(core): share artifact length limits and pin guard clauses (#7914) --------- Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com> Co-authored-by: qwen-code-bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
bfd64114bf
|
fix(cli): reserve Ctrl+Shift+C for terminal copy, not quit/clear (#8011)
* fix(cli): reserve Ctrl+Shift+C for terminal copy, not quit/clear
Ctrl+Shift+C is the standard terminal copy shortcut. The QUIT and
CLEAR_INPUT bindings matched it because they only constrained ctrl
and left shift unspecified, which the matcher ignores. In Kitty-
protocol terminals the escape-hatch check also caught Ctrl+Shift+C
(ESC[99;6u) since it tested ctrl+name without excluding shift.
Add shift: false to both bindings and !key.shift to the escape-hatch
condition so Ctrl+Shift+C passes through to the terminal for copy.
Plain Ctrl+C (shift undefined or false) is unaffected.
Fixes #8006
* test(cli): guard Ctrl+Shift+C paste escape-hatch against !key.shift mutation
The existing Kitty Ctrl+Shift+C test passed identically with or without
the `!key.shift` condition in the Ctrl+C escape hatch, so a regression
making Ctrl+Shift+C clear a stuck paste would ship undetected. Add a
test that enters a stuck keypress-level paste and asserts Ctrl+Shift+C
is buffered as paste content rather than dispatched, which fails when
`!key.shift` is removed.
* fix(cli): remove dead !key.shift escape-hatch guard (#8011)
The KeypressContext.tsx escape-hatch guard added in
|
||
|
|
2ce9da85bd
|
fix(review): preflight free disk before build-test installs and builds (#7986)
Dogfooded on a live review: with ~2.7G free, `npm ci` on this monorepo ran 33 seconds and died on ENOSPC — and the now-full disk went on to fail every agent scheduled after this command. Verification and reverse-audit agents could not even spawn, so a Critical that nine agents independently reported ended the run capped at Comment because nothing could verify it. A disk a command fills is not a failure that stays contained to that command. build-test already treats "cannot finish in time" as an infrastructure result: the deadline skips the command and discloses it, and the agent brief routes such notes to informational, never a finding. "Cannot fit on the disk" is the same class of result, discoverable before the command runs instead of 33 seconds into it. Add a free-disk preflight at two floors: 3 GiB before `npm ci` (the installed tree is ~1.4G and npm stages cache and temp writes on the same filesystem while materialising it) and 1 GiB before the build phase (dist/ and tsbuildinfo write far less; the floor exists so a compile cannot be the thing that fills the disk). Two floors deliberately: a warm tree at ~2G free skips the install anyway and must still be allowed to build. Below a floor the command is skipped with the same disclosure shape as a deadline skip. Where statfsSync is unavailable the preflight passes — it exists to prevent failures, not to invent them. Co-authored-by: verify <verify@local> |
||
|
|
cc617e6707
|
fix(daemon): distinguish draining channel worker diagnostics (#7932)
Co-authored-by: destire-mio <248462155+destire-mio@users.noreply.github.com> |
||
|
|
9bbd394cb2
|
fix(cli): map Kitty Super (Command) modifier to meta (#7996)
* fix(cli): map Kitty Super (Command) modifier to meta Kitty-protocol terminals forward Cmd+C as a CSI-u sequence whose modifier parameter carries the Super bit (8), which the parser silently dropped. The key then parsed as a bare printable "c" (meta: false) and leaked into the input box even though the terminal performed the copy itself. Fold the Super bit into the emitted meta flag at every Kitty modifier decode site so Super-modified keys are no longer inserted as text. Fixes #7990 * test(cli): cover Super-bit fold on reverse-tab and functional-keys paths (#7996) * test(cli): make functional-key Super-bit case load-bearing Use Home (ESC [ 1 ; 9 H) instead of Up arrow (ESC [ 1 ; 9 A): readline claims modified arrows before the Kitty arrowPrefix decoder, so the arrow case passed even without the Super-bit fold. Home exercises the decoder and fails on unfixed code. --------- Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
4615f84d73
|
fix(serve): allow bounded reads of large text files (#7947)
* fix(serve): allow bounded reads of large text files
* fix(serve): bound large-text reads by scan cost, not by which knob was set
Follow-up to the bounded large-text read path. Three changes:
Gate on any explicit window argument, not on `limit`. Gating on `limit`
had the cost model backwards in both directions: `{ line: 900_000_000,
limit: 20 }` was admitted despite walking the whole file, while
`{ maxBytes: 4096 }` — satisfiable from the first 4 KiB — was refused. A
read with no window argument at all still fails, since a caller that
believes it holds the whole file may write it back truncated.
Add MAX_TEXT_SCAN_BYTES (8 MiB). MAX_READ_BYTES caps what a read
returns; nothing capped what it cost. Line offsets are resolved by
scanning from byte 0, so a query param could turn into an
uninterruptible multi-second scan of an arbitrarily large file — and on
Windows hold a read handle for that span, blocking renames and deletes.
Past the budget the read is refused with `file_too_large` pointing at
readBytes, which reaches any offset in O(1).
Tolerate appends on streamed windows. Requiring whole-file size/mtime
stability after reading a prefix rejected reads whose returned bytes
were still valid, and the case it rejected — tailing a live log — is the
one this path exists for. Streamed windows now assert inode identity
plus "did not shrink"; truncation and replacement are still rejected.
Also: non-UTF-8 large text now returns `binary_file` rather than
`file_too_large`, so a client retrying on 413 with a smaller window
can't loop forever; and `readFileWithLineAndLimit` throws instead of
silently ignoring a caller-supplied `fileHandle` on the by-path
fallback.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(serve): harden large text range snapshots
Treat caller-owned file handles as bounded streaming reads, cap them to the captured file size, and reuse the chunk buffer.
Restore strict Serve snapshot stability and align returned-slice metadata with the full-snapshot path.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(serve): make large text ranges snapshot-safe
* fix(serve): harden large-text ctime tests and document buffer reuse (#7947)
Address review feedback on the large-text range read PR:
- Pause before restoring mtime in the two ctime-dependent mutation tests so the change-time advances past the pre-read snapshot even on coarse-resolution filesystems, removing a latent flake in the same-size-overwrite precondition. The assertions are unchanged.
- Document at the readFileHandleChunks yield site that the 512 KiB buffer is reused across iterations, so yielded views must be decoded or copied before advancing the generator.
* docs(serve): soften same-size rewrite guarantee to coarse-clock best-effort (#7947)
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
|
||
|
|
4c11e21bcd
|
fix(cli): prevent SGR mouse events from being swallowed as paste on Windows (#7988)
On Windows (pasteWorkaround path), shouldFlushRawDataAsPaste misclassifies raw stdin data containing both a carriage return (0x0d) and an SGR mouse sequence as pasted content. The synthetic paste wrapper sets isPaste=true in handleKeypress, which discards SGR mouse data via resetSgrMouse(), breaking wheel scrolling in VP mode. This commonly triggers when Enter (\r) and a subsequent mouse wheel event arrive in the same stdin chunk — a frequent occurrence on Windows Terminal. macOS is unaffected because handleStdinData does not use this heuristic. Skip the paste heuristic when the buffer contains ANSI escape bytes (0x1b) so SGR mouse sequences always reach readline for proper parsing and dispatch to mouse subscribers. Closes #7964 Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
81f1ffb94c
|
fix(cli): correct hardware cursor off-by-one in fullscreen mode (#7998)
* fix(cli): correct hardware cursor off-by-one in fullscreen mode In fullscreen mode Ink omits the trailing newline, so after writing output the terminal cursor sits ON the last line rather than one past it. buildCursorSuffix assumed the latter and emitted one extra cursorUp, placing the hardware cursor (used for IME positioning) one row above the software block cursor — visible as an extra white rectangular segment protruding above the input line. Pass a hasTrailingNewline flag through buildCursorSuffix, buildReturnToBottom, buildCursorOnlySequence, and buildReturnToBottomPrefix in the Ink patch so both the standard and incremental log-update renderers compute the correct cursor movement. Closes #7980 * fix(cli): revert incorrect hasTrailingNewline in buildReturnToBottom Self-review caught that buildReturnToBottom does NOT need the hasTrailingNewline adjustment: its previousLineCount argument comes from str.split('\n').length, which already includes the trailing empty element when the output ends with '\n'. The original formula (previousLineCount - 1 - y) is the exact inverse of the corrected buildCursorSuffix in both fullscreen and non-fullscreen modes. Only buildCursorSuffix needs the flag, because its visibleLineCount argument excludes the trailing empty element. * fix(cli): sync cursor-helpers.d.ts and add fullscreen cursor-only test (#7998) --------- Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> |
||
|
|
5ce832e497
|
feat(core): connect Goal v3 to the turn engine (#7895)
* feat(core): integrate Goal turn engine * test(core): preserve Goal tool isolation in agent overrides * fix(core): pause Goal at Stop hook cap * fix(core): keep Goal recovery from blocking sessions * fix(core): degrade failed Goal migration writes * fix(core): reset loop detector in Goal stop hook continuation (#7895) The goal-runtime stop hook continuation path was missing loopDetector.reset(prompt_id) before recursing, causing tool calls to accumulate across iterations and trip TURN_TOOL_CALL_CAP after a handful of healthy iterations. The non-goal stop hook path already had this reset. Also simplifies the redundant conditional in Turn.run() — the two near-identical sendMessageStream calls are collapsed into one since sendMessageStream already handles an undefined goalContext internally. * fix(core): align turn.test.ts assertion with unified sendMessageStream call (#7895) * fix(core): preserve error cause in GoalPersistenceUnavailableError (#7895) --------- Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-bot <qwen-code-bot@users.noreply.github.com> |
||
|
|
3144046c6b
|
chore(release): v0.21.1 (#7958)
* chore(release): v0.21.1 * docs(changelog): sync for v0.21.1 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
dade3ab334
|
feat(web-shell): add git branch picker, commit dialog, and create PR flow (#7731)
* feat(web-shell): add git branch picker, commit dialog, and create PR flow Add an IntelliJ-style branch picker popover to the web shell git workspace, accessible from the branch chip in both the composer toolbar and sidebar. The picker provides search-filtered branch listing (local, remote, tags, recent), branch checkout, new branch creation, pull, push, and a commit view integrated into the existing GitDialog. The commit view reuses the diff panel (expandable file diffs with syntax highlighting, fullscreen support) and adds a commit message textarea with Commit / Commit and Push buttons. When a session is available (or one is auto-created), the commit message and PR title/body are generated via the model using session side-queries (btwSession), giving the agent full conversation context for accurate generation. The Create PR flow provides an inline form with auto-detected base branch and model-generated title/description, backed by a new daemon route that shells out to gh pr create. New daemon routes: - GET /workspaces/:workspace/git/branches - POST /workspaces/:workspace/git/checkout - POST /workspaces/:workspace/git/branch - POST /workspaces/:workspace/git/push - POST /workspaces/:workspace/git/pull - POST /workspaces/:workspace/git/commit - POST /workspaces/:workspace/github/prs/create - GET /workspaces/:workspace/github/default-branch * fix(web-shell): resolve correct workspace session for AI generation The commit message and PR title/body generation now resolves the most recent session for the target workspace via listWorkspaceSessions, rather than using the globally active connection.sessionId which may belong to a different workspace or session. Falls back to creating a new session only when no sessions exist for the workspace. Also improves the PR body editor with an Edit/Preview toggle using the existing Markdown component, and updates the generation prompt to follow the project PR template structure from AGENTS.md. * fix(web-shell): stabilize session resolver prop to prevent infinite re-generation Pass resolveSessionForWorkspace as a stable useCallback reference instead of an inline arrow function. The inline function created a new reference on every App render, causing the GitDialog useEffect to abort and restart generation in an infinite loop. * fix(web-shell): group remote branches by remote name and add PR target branch dropdown Remote branches in the branch picker are now grouped by remote (origin, upstream, etc.) with sub-headers, making fork workflows clear. The PR create form's base branch field is now a select dropdown populated from the workspace's branch list, grouped by remote with optgroup labels, instead of a free-text input. * fix(web-shell): use ref for session resolver to prevent effect re-run abort When resolveSessionForWorkspace creates a new session, it updates connection.sessionId in the provider, which changes the useCallback reference, which triggers the useEffect to re-run and abort the in-flight btwSession generation. Store the callback in a ref so the effect never depends on its identity. * fix(web-shell): resolve session per workspace, not from global active session The commit/PR generation effects used connection.sessionId directly without checking if it belongs to the target workspace. When opening the commit dialog from a sidebar workspace different from the active session's workspace, the wrong session was used for generation, producing incorrect content. Now always routes through resolveSessionForWorkspace(workspaceCwd) which checks workspace membership before reusing the active session. Also replaces 'PR' with 'Pull Request' / '合并请求' in all UI strings and adds error logging to the generation catch blocks. * fix(web-shell): retry btwSession with fresh session when stale session detected When listWorkspaceSessions returns a session that no longer exists in the daemon's memory (e.g. after daemon restart), btwSession fails with 'No session with id ...'. The generation effects now catch this error, force-create a new session via resolveSessionForWorkspace(cwd, true), and retry the btwSession call once. Both btwWithRetry and resolveSessionForWorkspace are stored in refs to avoid useEffect dependency chain aborts. * fix(web-shell): base PR generation on branch diff, not working tree PR title/body generation now fetches the commit log between the resolved base branch and HEAD (git log <base>..HEAD) plus any uncommitted changes, instead of only the working tree diff. The base branch is resolved inside the effect's promise chain (not from state) to avoid stale values and dependency warnings. Also adds range parameter support to fetchGitLog and workspaceGitLog. * feat(web-shell): replace PR base branch select with searchable popover The native <select> for the PR target branch is replaced with a custom searchable popover (BranchSelect) that shows a search input and a filtered branch list grouped by remote. The default selection is the target repository's main branch (resolved via getDefaultBranch). Supports filtering by typing in the search box. * fix(web-shell): show full remote ref in branch select (origin/main) Branch select now stores and displays full remote refs like origin/main instead of stripped names. getDefaultBranch returns the full ref (origin/main) instead of stripping the prefix. When creating the PR, the remote prefix is stripped for the gh pr create --base flag (origin/main → main). * fix(web-shell): stop pointer propagation in branch picker list to prevent popover dismiss Radix Popover's outside-click detection was incorrectly firing when clicking section headers (Recent/Local/Remote/Tags) inside the popover content, causing the popover to close immediately. Adding onPointerDown stopPropagation on the list container prevents pointer events from reaching Radix's document-level handlers. * fix(web-shell): use onPointerDownOutside guard for branch picker popover The previous stopPropagation approach failed because Radix Popover uses capture-phase document listeners for outside-click detection, which fire before bubble-phase stopPropagation. The correct fix is onPointerDownOutside on PopoverContent: when Radix incorrectly fires the outside handler for a click that is actually inside the content (can happen with portal containers), we check contentRef.contains() and preventDefault to keep the popover open. * fix(web-shell): stop click propagation on branch picker popover content Root cause: the ChatEditor composer container has onClick that calls core.focus(), stealing focus from the popover. React synthetic events bubble through the React tree (not DOM tree), so portaled popover clicks reach the container handler. Radix then detects focus-outside and dismisses the popover. Fix: onClick stopPropagation on PopoverContent, matching the existing pattern in GitModePopover and ToolbarPopover which already have this fix with an explanatory comment. * docs: add PR verification screenshots for branch picker feature * fix(web-shell): mock useWorkspace in tests for BranchPickerPopover BranchPickerPopover calls useWorkspace() which requires DaemonWorkspaceProvider context. The existing WorkspaceSection and ChatEditor tests didn't provide this context, causing 5 test failures. Added vi.mock with importActual to preserve other exports while providing a mock useWorkspace. Also updated the git chip click test to reflect that clicking now opens the branch picker popover instead of directly calling onOpenGitDiff. * fix: harden git write paths against argument injection Address review feedback on the web-shell git surface: - Reject option/pathspec injection in git checkout ref and branch start point (isValidCheckoutRef), and terminate `git checkout` argv with `--`. - Drop `git log` range values that start with `-` and terminate the argv with `--` so a range can never be reinterpreted as `--output=<file>`. - Fix getDefaultBranch always falling back to origin/main: the promisified exec lacked `encoding: 'utf8'`, so stdout was a Buffer and .trim() threw. - Parameterize ghErrorMessage so `gh pr create` timeouts name the right command and duration; sanitize workspace paths in PR-create errors. - GitDialog: guard doCommit against double-submit (button + keyboard), and strip only a known remote prefix from the PR base so local branches with "/" are not mangled; use theme tokens for commit button/success colors. - BranchPickerPopover: guard checkout/new-branch behind busyAction, reset inline-input text on reopen, and hide the commit action when unavailable. Adds regression tests for the checkout/branch validation and the git log range guard. * fix(web-shell): address review feedback on branch picker PR (#7731) - Fix Commit+Push error masking: split try/catch so push failure reports alongside the successful commit SHA - Replace hardcoded screenshot path with captureScreenshot harness - Replace silent if-isVisible skip with explicit assertion in visual test - Add focus-visible style for search input accessibility - Fix CSS specificity for active PR tab hover state - Add viewChanges to actionsVisible search filter - Wrap toggleSection in useCallback to avoid unnecessary re-renders - Add windowsHide: true to getDefaultBranch subprocess - Fix trailing slash handling in mockDaemon git action routing - Add git methods to top-level client mock in tests - Remove dead branchPicker.commitSuccess i18n key - Remove dead .actionShortcut CSS class - Show generation failure feedback in commit message placeholder * fix(web-shell): address review feedback on branch picker PR (#7731) - Add workspace trust checks to bound git branch routes - Use workspace-scoped client in BranchPickerPopover (fixes wrong-workspace mutation) - Add branch name validation and -- terminator to gitCreateBranch - Filter refs/remotes/*/HEAD from branch listings - Force LC_ALL=C for reflog parsing (non-English locale fix) - Narrow 'could not resolve' error regex to avoid DNS false positives - Add range validation to git log (reject path traversal) - Fix commit+push error i18n (dedicated key instead of concatenation) - Add i18n for BranchSelect component strings - Fix commit tab ARIA attributes - Add onBranchChanged callback to handlePush - Add btw to mockDaemon isDaemonPath regex - Add workspace_github_prs to visuals spec capabilities - Add -- terminator regression test - Remove docs/pr-assets/ from repo * fix(web-shell): address review feedback on branch picker PR (#7731) * fix(cli): reject dash-prefixed branch name with 400 in branch route (#7731) * fix(web-shell): address review feedback on git branch picker (#7731) Security: - Clear GIT_DIR/GIT_WORK_TREE/GIT_COMMON_DIR/GIT_INDEX_FILE from git subprocess env to prevent repository redirection - Add strict mutation gate to all POST git branch routes - Add generation guard to qualified write routes - Fail closed on invalid ?cwd= in mutation routes (resolveContainedCwdOrFail) - Reject wrong-typed startPoint, fetchOnly, rebase, and PR options with 400 Correctness: - Filter remote symbolic refs (origin/HEAD) by %(symref) instead of /HEAD name suffix, preserving valid branches like feature/HEAD - Add git rev-parse --git-dir probe so non-git dirs get 404 instead of empty available:true - Push preserves existing upstream; only adds --set-upstream when unset, resolving the remote from branch config or the sole configured remote - git commit -a replaced with git add -A + git commit so untracked files displayed in the UI are included - Always pass --body to gh pr create to prevent interactive prompts - getDefaultBranch returns null instead of fabricating origin/main - Memoize workspaceByCwd client in BranchPickerPopover to fix infinite render loop - Move sessionId to a ref in GitDialog effects to prevent self-abort - Bound commit-message prompt to fit /btw 4096-char limit - Mark all platforms as unverified in PR template (no fabricated ✅) - Guard PR auto-fill effect against wiping user edits on reconnect Accessibility: - Add tabIndex and onKeyDown to commit-mode tab span - Add aria-label to BranchSelect trigger and search input Cleanup: - Remove dead CSS (.prInputSmall, .prSelect) - Remove 9 unused i18n keys - Add ^ to git log range validation regex - Add busyAction guard to handlePush/handlePull - Add unit tests for gitCommit, gitPull, and route input validation * fix(web-shell): address review feedback on git branch picker PR (#7731) - Change commit tab from <span> to <button> for keyboard accessibility - Move setCommitMsg('') to success-only branches so the message is preserved when push fails after a successful commit - Add mutate middleware and generationGuard to PR creation route, matching all other POST mutation routes - Set genFailed when session resolution returns undefined so the user sees the failure indicator instead of a silent empty textarea - Make PR number nullable when URL regex does not match instead of returning a misleading 0 - Add LC_ALL=C and LANG=C to gitEnv() so for-each-ref upstream track parsing is locale-independent - Validate setUpstream and force as booleans in handlePush, matching the existing validation in handlePull - Add missing workspaceCwd and available fields to test mocks - Use stable data-web-shell-git-branch attribute in e2e selector * fix(web-shell): address R5 review feedback on git branch picker PR (#7731) - Classify git errors on stdout+stderr instead of err.message to fix false-positive no_upstream on every push failure and dead nothing_to_commit classifier - Sanitize workspace paths and cap error message length in sendGitError - Fix remote branch checkout to strip remote prefix so git DWIM creates a local tracking branch instead of detaching HEAD - Restore keyboard accessibility on composer branch chip (span → button) - Trim startPoint in handleCreateBranch before forwarding to git - Return bare branch name from getDefaultBranch (strip remote prefix) - Fix i18n shortcut hint to show ⌘/Ctrl+Enter for cross-platform - Update aria-label to reflect git management menu, not just changes - Add available: true to mockDaemon gitDiff default payload - Add regression tests: upstream preservation, sole remote resolution, strengthened fetch-only with divergent remote commit - Add aria-expanded assertion to sidebar picker test Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(web-shell): address R6 review feedback on git branch picker PR (#7731) * fix(web-shell): address R7 review feedback on git branch picker PR (#7731) * fix(web-shell): address R8 review feedback on git branch picker PR (#7731) * fix(web-shell): address R9 review feedback on git branch picker PR (#7731) * fix(web-shell): address R10 review feedback on git branch picker PR (#7731) * fix(web-shell): address R11 review feedback on git branch picker PR (#7731) * fix(web-shell): address R12 review feedback on git branch picker PR (#7731) * fix(web-shell): address R13 review feedback on git branch picker PR (#7731) * fix(web-shell): address R14 review feedback on git branch picker PR (#7731) * fix(web-shell): address R15 review feedback on git branch picker PR (#7731) - Validate localName derived from remote-tracking ref to prevent option injection (e.g. origin/-f → git checkout -f) - Add gitCwd prop to BranchPickerPopover and pass it to all git SDK calls so worktree sessions target the correct directory - Add symlink-escape and non-existent-path tests for resolveContainedCwdOrFail - Pin initial branch name in makeRepo() with git init -b master - Add gitPull merge and rebase integration tests * fix(web-shell): address git branch picker review feedback (#7731) * fix(web-shell): address R6 review feedback on git branch picker PR (#7731) * fix(web-shell): address review feedback on git branch picker PR (#7731) - Strip GIT_CONFIG_GLOBAL/SYSTEM/NOSYSTEM in gitEnv to prevent inherited config redirection (consistent with extension/github.ts) - Pass gitCwd to workspaceGitBranches in GitDialog loadPrBranches so worktree sessions fetch branches from the correct repository - Add aria-expanded to collapsible branch section headers - Add happy-path tests for PR create (201) and default-branch (200) routes, including the null fallback to origin/main * fix(cli): add sendGenerationClosedError to POST routes and cover untested branches (#7731) * fix(web-shell): address review feedback on branch picker and PR creation (#7731) - Refresh branch list after push/pull to avoid stale ahead/behind counts - Add pre-flight check for unpushed branches before PR creation - Fix base branch prefix stripping when branch list is unavailable - Cap PR body file list at MAX_SUMMARY_CHARS to bound model prompt size - Add qualified route tests: trust guard, input validation, cwd containment * fix(web-shell): hoist MAX_SUMMARY_CHARS to module scope for PR body generation (#7731) * fix(web-shell): address review feedback for git branch picker (#7731) - Hoist onOpenCommit to useCallback to fix App.test.tsx prop stability test - Keep commit tab visible after navigating away (startedInCommit ref) - Add onClick handler to commit tab for navigation back to commit view - Fix branch-prefix strip mangling local branch names containing '/' - Update sessionIdRef after force-creating a stale session replacement - Pin core.hooksPath in test makeRepo for reliable rollback tests - Add test asserting --force-with-lease is used for force pushes * fix(web-shell): target the worktree for sidebar commits and harden git actions (#7731) Scope the sidebar commit dialog to the active session's worktree checkout (matching the composer path) so linked-worktree sessions commit to the right checkout, guard PR creation against a double-click race, and surface an error when an invalid branch name is submitted. Adds focused coverage for the branch picker action wiring and the git branch route validation paths. * fix(web-shell): address review feedback for git branch picker (#7731) --------- Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> |
||
|
|
6fb63b73e1
|
fix(review): recover the resolved effort when --effort is not re-threaded (#7855)
* fix(review): recover the resolved effort when --effort is not re-threaded The capture commands (capture-local, plan-diff, fetch-pr) record the review effort as plan.effort, which every downstream consumer — roster, check-coverage, compose-review — reads. They learn it from --effort <level>, and the skill asks the orchestrator to pass the level parse-args resolved. But that is a value the model must copy from one file into a flag, and it does not reliably happen: a `/review --effort medium` local run had the orchestrator omit the flag, so plan.effort was absent and the roster safe-expanded to the FULL set — the user asked for the reduced medium roster and silently got every agent (6a/6b/6c included). Close the gap deterministically. A new resolveEffort() prefers an explicit --effort, and otherwise reads the level parse-args already wrote to its conventional report (.qwen/tmp/qwen-review-parse-args.json). When neither is available it returns undefined and the roster fail-safes to the full set exactly as before, so a missing report never reduces coverage, and a malformed level is ignored rather than trusted. * refactor(review): dedupe effort resolution per review feedback - Share one EFFORT_LEVELS set: export it from parse-args and import it in effort.ts, so a new level cannot be added to one set but not the other. - Collapse the three identical effort-spread IIFEs into planEffortField(). - Isolate CWD in plan-diff.test.ts (matching capture-local.test.ts) so resolveEffort's CWD-relative report read cannot pick up a stale file and fail the "omits effort" case. * test(review): pin PARSE_ARGS_REPORT value and plan-diff effort fallback (#7855) * test(review): dedupe seed helper, cover fetch-pr effort, trace resolution (#7855) * fix(review): spread planEffortField last for consistent effort precedence (#7855) --------- Co-authored-by: verify <verify@local> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com> |
||
|
|
20f9f08ce5
|
feat(channels): expose loop tools in daemon sessions (#7891)
* feat(channels): expose loop tools in daemon sessions * fix(serve): preserve fast-path bundle boundary --------- Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
ad1b5f3de1
|
fix(cli): default to virtualized terminal history (#5738)
* fix(cli): default to virtualized terminal history * fix(cli): remove redundant alternate screen exit handler * fix(cli): keep non-interactive output off VP mode * fix(cli): stabilize VP tests in CI environments * test(cli): resolve SDK daemon source in vitest * fix(cli): normalize CI env checks for VP mode * fix(cli): keep default VP mouse interactions enabled * fix(cli): align VP mouse behavior with runtime state * fix(cli): stabilize virtual viewport runtime state * test(cli): cover virtual viewport fallbacks * docs(cli): clarify virtual viewport requirements --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
0afb48b1ea
|
fix(safe-mode): preserve caller-supplied top-tier MCP servers (#7827)
* fix(safe-mode): preserve caller-supplied top-tier MCP servers Safe mode is meant to distrust LOCAL/ambient state (settings.json, extensions, project .mcp.json) so a user can isolate which local customization is misbehaving. It was also unconditionally dropping topTierMcpServers -- the caller-supplied servers from an ACP session/new's mcpServers field or --mcp-config -- which are an explicit, per-invocation argument, not ambient local state. The real gate turned out to be in packages/core/src/config/config.ts's Config.getMcpServers() (the accessor mcp-client-manager.ts actually reads for discovery), not just the mcpServers assembly in loadCliConfig -- fixed both so the raw field and the accessor agree. Also guarded the hot-reload path (hot-reload.ts) with the same bare/safe-mode check, so a live settings.json edit can't smuggle local servers into an already-running bare/safe-mode session. Fixes #7819 * docs: clarify safe-mode's MCP servers note distinguishes local vs caller-supplied Per CONTRIBUTING.md guideline 5 (docs for user-facing changes). * fix(safe-mode): apply allowedMcpServers to top-tier servers, skip pendingMcpServers I/O under safe mode Address Copilot's review of PR #7827: - getMcpServers() now runs the safe-mode top-tier map through the same allowedMcpServers filter as the non-safe-mode path -- safe mode is not an exemption from a session's own --allowed-mcp-server-names upper bound (High severity finding, confirmed real). - Re-added safeMode to pendingMcpServers' skip condition in loadCliConfig. Functionally a no-op either way (top-tier servers are never gated, #4615), but skips getMcpApprovals' local file read entirely under safe mode instead of doing a no-op read -- safe mode shouldn't touch local/ambient state at all, not even harmlessly (Medium severity finding). * fix(safe-mode): actually run MCP discovery for surviving top-tier servers Config.getMcpServers() reporting a top-tier server as configured isn't enough on its own -- something has to actually connect to it and register its tools with the model. That's a separate gate in initialize(): startMcpDiscoveryInBackground() was skipped outright whenever isSafeMode() was true, written back when getMcpServers() always returned {} under safe mode (so skipping discovery was a harmless no-op). Left unpatched after the earlier fix, a caller-supplied top-tier server survives getMcpServers() but is never actually discovered/connected -- confirmed live against a real ACP session before this commit: the agent reported the tool as "not configured" even though Config.getMcpServers() already returned it. Found by actually running the fix end-to-end against a live ACP session (qwen --acp --safe-mode + a real stdio MCP fixture server) instead of relying on unit tests of Config in isolation. After this commit the same live session correctly discovers and calls the caller-supplied tool. Checks getMcpServers() (not topTierMcpServers directly) so the allowedMcpServers filter still applies -- no discovery is kicked off if the only top-tier server present is filtered out. * fix(safe-mode): also run MCP discovery for surviving bare-mode top-tier servers The discovery-kickoff gate in Config.initialize() special-cased safe mode (skip only when there's nothing to discover) but left bare mode's half of the same guard unconditional (!this.getBareMode()), even though loadCliConfig feeds top-tier MCP servers into bare mode's mcpServers assembly exactly the way it does safe mode's topTierMcpServers field. A bare-mode session with a caller-supplied server (qwen --bare --mcp-config, or ACP session/new under bare mode) had that server reported as configured by getMcpServers() but never actually connected/discovered — the same stranded-server regression already fixed for safe mode in this PR, just the bare-mode twin of it. Found by an automated review pass on PR #7827 after the safe-mode fix had already landed. Added the same three-case regression coverage (present / nothing supplied / filtered out by allowedMcpServers) mirroring the existing safe-mode tests, using mcpServers (not topTierMcpServers) since bare mode's "local sources dropped" guarantee lives entirely in the CLI-layer assembly, not a core-level short-circuit. * refactor(safe-mode): simplify the bare/safe discovery-gate condition (!bare || has) && (!safe || has) reduces by distributivity to !(bare || safe) || has, so factor the has-servers check into a single hasMcpServers computed once, instead of calling getMcpServers() (which allocates and filters) twice. Suggested by an automated review pass on PR #7827, commit 25ddf8b. No behavior change — same 23 discovery-gate tests pass unmodified. * fix(safe-mode): stop reading settings.mcp.allowed/excluded under safe mode allowedMcpServers/excludedMcpServers assembly in loadCliConfig() only guarded the settings-sourced branch with `!bareMode`, missing `!safeMode` — so a local settings.json mcp.allowed/excluded list (LOCAL/ambient state, same category as settings.mcpServers itself, which safe mode already drops) was still read under safe mode. Combined with getMcpServers()'s own allowedMcpServers filter (added earlier in this PR for the --allowed-mcp-server-names case), a settings.json mcp.allowed list narrower than the caller's own top-tier servers would silently filter them back out — defeating the guarantee this PR exists to provide, via the filter's source rather than the mcpServers map directly. The argv.allowedMcpServerNames branch is unaffected: that's an explicit per-invocation argument, not local state, so it still applies under safe mode same as topTierMcpServers itself. Found by an automated review pass (doudouOUC, CHANGES_REQUESTED) on PR #7827. Regression test confirmed red before the fix (session-supplied server silently filtered out) and green after. Full targeted vitest run (packages/cli config/: 1018 passed, same 3 pre-existing Windows-only extension-file-watcher failures as before, unrelated), tsc --noEmit, eslint, prettier --check all clean. * fix(safe-mode): stop reading settings.mcp.allowed/excluded on hot-reload too Same class of bug as the previous commit's loadCliConfig fix, found by an automated review pass on the SAME PR: recomputeMcpGating (hot-reload.ts) reads settings.merged.mcp.allowed/excluded unconditionally, with no bare/safe guard of its own. registerMcpHotReload's existing bare/safe guard only covered the servers map (`next`), not the admission lists computed right after it — so a live settings.json edit narrowing mcp.allowed during an already-running safe/bare session would flow straight into setAllowedMcpServers and silently filter the caller's top-tier server out of getMcpServers() mid-session. Same stranded-server outcome as the boot-time bug, reached through the gating list's SOURCE instead of the mcpServers map. Fix: under bare/safe mode, skip recomputeMcpGating entirely and build the gating directly from only the CLI --allowed-mcp-server-names bound (explicit, per-invocation, not local state — same treatment as topTierMcpServers itself); excluded/pending are irrelevant once nothing but the never-gated top-tier servers can be present. Regression tests (safe mode + bare mode) confirmed red before the fix (setAllowedMcpServers called with the settings-sourced list) and green after (called with the CLI bound, undefined here). Full targeted vitest run (packages/cli config/: 1020 passed, same 3 pre-existing Windows-only extension-file-watcher failures as before this PR touched anything, unrelated), tsc --noEmit, eslint, prettier --check all clean. * fix(safe-mode): stop reading settings.mcp.allowed/excluded on ACP reload too Third instance of the same bug class found by an automated review pass on this PR: reloadWorkspaceMcpDiscovery (packages/cli/src/acp-integration/ acpAgent.ts) — the ACP control-endpoint reload path (workspaceMcpReload), distinct from registerMcpHotReload's settings-file-watcher path fixed in the previous commit — called assembleMcpServers(settings.merged.mcpServers, ...) and recomputeMcpGating(settings, ...) unconditionally, per live Config in liveConfigs, with no bare/safe guard. A workspaceMcpReload request against an already-running safe/bare session would fold local mcpServers/mcp.allowed/excluded back in, silently stranding or filtering the caller's own top-tier server mid-session — same outcome as the two prior fixes, reached through a third independent reload path. Fix: per-config (liveConfigs holds a Set of potentially differently-moded Configs — the base config, active session configs, and the discovery config), skip assembleMcpServers/recomputeMcpGating under bare/safe mode and build servers/gating directly from that config's own getTopTierMcpServers()/getCliAllowedMcpServerNames() — same treatment as the other two fixes. Regression test (packages/cli/src/acp-integration/acpAgent.test.ts) confirmed red before the fix (settings-sourced 'local' server leaked into reinitializeMcpServers alongside the caller's 'probe') and green after. Also added getBareMode/isSafeMode mocks (defaulting false) to the two pre-existing Config-shaped mocks in this describe block that didn't have them — reloadWorkspaceMcpDiscovery now calls these unconditionally per config, which would otherwise throw "not a function" against any mock missing them, even for a normal-mode test. Full targeted vitest run (packages/cli/src/acp-integration/acpAgent.test.ts: 318 passed), tsc --noEmit, eslint, prettier --check all clean. * test(safe-mode): add bare-mode counterpart for the workspaceMcpReload guard Suggested by an automated review pass on PR #7827: the previous commit's regression test for reloadWorkspaceMcpDiscovery only exercised isSafeMode: true, leaving the bare-mode half of config.getBareMode() || config.isSafeMode() unverified at this layer — unlike the hot-reload.ts tests, which already cover both modes for both the servers map and the admission lists. A future change narrowing that guard to isSafeMode() only would go undetected here. Confirmed red before the fix (temporarily reverted acpAgent.ts to the prior commit): settings-sourced 'local' leaked into reinitializeMcpServers alongside the caller's 'probe', same as the safe-mode case. Green with the fix restored. Full targeted vitest run (acp-integration/acpAgent.test.ts: 319 passed), tsc --noEmit, eslint, prettier --check all clean. |
||
|
|
788e5cd3a8
|
feat(core): add ARMS session user ID (#7921)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
f7fd831003
|
fix(cli): remove redundant 'Read file' prefix from @mention tool card (#7902)
* fix(cli): remove redundant "Read file" prefix from @mention tool card
The @mention file-read tool card set its description to
"Read file <name>", which duplicated the display name ("Read")
when rendered as "{displayName} {description}", producing
"Read Read file README.md".
Change the description to "@<name>" — matching the @mention
syntax the user typed, and consistent with how normal read_file
tool calls show just the filename in their description.
* test(cli): assert @mention tool card description format (#7902)
---------
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com>
|
||
|
|
0be42b782d
|
fix(cli): add polling fallback for git branch name display (#7830)
* fix(cli): add polling fallback for git branch name display (#7828) fs.watch on .git/logs/HEAD is unreliable on NFS, FUSE, Docker overlay, and some Linux filesystems — events can be silently dropped with no recovery path, leaving the footer branch name stale indefinitely. Added a 5-second polling fallback in useGitBranchName that calls resolveBranchName and updates the state only when the value changes. The timer is unref'd so it doesn't keep the process alive. The existing fs.watch mechanism is preserved for immediate updates when it works correctly. * test(cli): cover the git branch polling fallback (#7830) * refactor(cli): use idiomatic timer.unref?.() in branch poller (#7830) * fix(cli): order concurrent branch refreshes by generation (#7830) --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
4703cc5432
|
fix(serve): Release managed session writer locks on shutdown (#7812)
Some checks are pending
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
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* fix(serve): release managed writer locks on shutdown Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): address shutdown review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): release writer locks after flush failures Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): harden managed shutdown recovery Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
6f739c7a46
|
fix(cli): hide stale sticky todos from previous turns (#7900)
* fix(cli): hide sticky todo panel when a new turn starts PR #7062 hid the sticky panel when streamingState is Idle, but the panel resurfaces with stale todos from a previous turn as soon as the user sends a new message (state → Responding). This is confusing — the user sees in-progress indicators for work that already finished in an earlier turn. Add a turn-boundary check in getStickyTodos: if a user message exists after the todo snapshot in history, the snapshot belongs to a previous turn and the sticky panel returns null. Fixes #7061 * test(cli): pin sticky-todo turn boundary for local slash commands (#7061) --------- Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> |
||
|
|
9461aa860d
|
fix(core): bridge tool-result images for text-only models (#7484)
* fix(core): bridge tool-result images for text-only models * test(vision-bridge): pin tool-result full-turn guards and surface bridge errors (#7484) * test(cli): cover drain-item model override conflict rejection (#7484) * test(cli): cover stop-hook full-turn model persistence * fix(core): disclose tool image routing * fix(cli): type restored vision notices * test(cli): fix stop-hook vision fixture --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> |