mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-09 17:19:02 +00:00
2897 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
53243de0c0
|
feat(daemon): persist session artifacts across restarts (#6557)
* feat(daemon): persist session artifact metadata * fix(daemon): address artifact restore review findings * fix(daemon): harden artifact persistence restore * fix(daemon): align artifact persistence review decisions * fix(daemon): address artifact persistence review gaps * fix(daemon): harden artifact persistence recovery * fix(daemon): align artifact ownership capability * fix(daemon): preserve marker identity during fork * fix(daemon): roll back durable replacement removals * fix(daemon): surface artifact rollback warnings * fix(daemon): surface restore warning details * fix(daemon): preserve artifact marker metadata safely * fix(daemon): sanitize fork marker metadata * fix(daemon): harden artifact restore boundaries * fix(daemon): omit orphaned sticky snapshot markers * fix(daemon): preserve artifact tombstone and rewind warnings * fix(daemon): address artifact fork review blockers --------- Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com> Co-authored-by: 易良 <1204183885@qq.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
41c405b3bf
|
feat(review): post Suggestion findings as inline comments (#6593)
Suggestion-level findings were routed to a single updatable issue comment (the "suggestion summary") while only Critical findings became inline review comments. That split traded away two things that turned out to matter more than the convergence it bought: - An issue comment has no lifecycle. GitHub folds an inline review thread away as Outdated once the author edits the line it is anchored to, so an addressed finding removes itself from the page. The summary comment just sits in the PR conversation forever; PATCHing it to "all addressed" replaces its content but not the comment. The mechanism meant to prevent clutter was the clutter. - A Markdown table cannot carry a one-click fix. GitHub renders a ```suggestion fence as an applicable change only inside a review comment on a diff line. Suggestion findings are exactly the mechanical, localized cleanups that benefit most from one-click apply, so the split withheld the feature from the findings that needed it most. Both severities now post as inline comments, distinguished by a **[Critical]** or **[Suggestion]** body prefix. The `qwen review post-suggestions` subcommand and its plumbing are removed. Follow-on changes required by the reroute: - pr-context: the "Previous suggestion summary" section is gone. Legacy summary comments are still recognised so they stay out of "Already discussed", but the exclusion is now marker-only rather than author-gated. The author check missed summaries posted by the *other* identity: /review runs as a maintainer locally and as qwen-code-ci-bot in CI, and roughly half of the last 60 PRs carry a bot-authored summary. Those leaked into "Already discussed" and told the review agents not to re-report the findings listed there. The check originally guarded promotion into a trusted rendering section; that section no longer exists, so it only gated exclusion, where a third party embedding the marker merely hides their own comment. - qwen-autofix: the workflow filters "suggestion summaries" out of the autofix bot's actionable queue, but only on the issue-comment channel. With Suggestions now inline, they entered the unfiltered inline channel and the bot would apply non-blocking recommendations and spend a review round on them. The inline channel now applies the same gate, keyed on the **[Suggestion]** prefix plus the /review footer so a human quoting the prefix stays actionable. - Step 7 gains a 422 fallback. Create Review is all-or-nothing, so one Suggestion anchored outside the diff would take the Critical findings down with it — a risk that did not exist when Suggestions travelled on a line-agnostic issue comment. GitHub's 422 does not name the offending entry, so the model rechecks anchors against the diff, relocates failing Criticals into the body, discards failing Suggestions, and degrades to an all-prose review rather than posting nothing. COMMENT reviews now always carry a one-line body: an empty body is only known to be accepted alongside inline comments on REQUEST_CHANGES, and a Suggestion- only review is the common case for a clean PR. |
||
|
|
ac2f371c44
|
feat(scheduled-tasks): add isolated run mode via create_sub_session tool (#6535)
* feat(scheduled-tasks): add isolated run mode via create_sub_session tool
Introduce a new `create_sub_session` tool (daemon-only) that spawns a
fresh top-level sub-session with its own clean context and transcript.
Wire it into the cron scheduler as an `isolated` run mode so each
scheduled fire dispatches its prompt into a fresh sub-session instead
of accumulating in one shared transcript.
- Add `create_sub_session` tool with `first-turn` and `sent` completion modes
- Add `SubSessionLauncher` in cli/serve with concurrency cap, timeout, and truncation
- Extend ACP bridge `extMethod` dispatch for child→daemon sub-session requests
- Add `runMode` field (`shared`|`isolated`) to DurableCronTask, CronJob, and API types
- Add run-mode radio picker to ScheduledTasksDialog UI
- Fix AuthMessage hardcoded placeholder to use i18n key
* fix(scheduled-tasks): dispatch isolated fires daemon-side, not via the model
An `isolated` fire was relayed through the model: the fired prompt was
wrapped with an instruction to call `create_sub_session`. That tool's
default permission is `'ask'`, so under `ApprovalMode.DEFAULT` an
unattended fire reached `client.requestPermission`, found no SSE
subscriber, and was cancelled by the daemon's 5-minute permission
timeout. The task never ran, and the cancel was booked as a successful
run — the headline use case of a scheduled task was broken.
Route isolated fires straight to the daemon instead: the cron `onFire`
handler in `Session` calls the sub-session spawner directly, with no
model relay and no tool-permission gate. The prompt was already approved
when the task was created; laundering it back through the model only
re-opened that gate. `create_sub_session` keeps `'ask'` for
model-initiated calls, and the attended "Run now" button keeps its relay
(a user is present to answer the prompt).
Also fix orphan-session cleanup in the launcher. `closeSession` was
guarded only by `.catch()`, which covers an async rejection but not a
synchronous throw; because the call sits inside the launcher's own
`catch (err)` block, a sync throw escaped and replaced the real launch
error. Guard both shapes.
Tests:
- Cover isolated routing: dispatch, in-session fallback with no spawner,
missed one-shot, dispatch failure (dropped, never run inline), and
shared mode.
- Cover the orphan close, including a `closeSession` that throws.
- Replace the sent-mode concurrency test, which only asserted the slot
was eventually released (moving the release to the drain's *start*
kept it green) with one that asserts the slot is HELD while the drain
runs, plus one that asserts it is released at `turn_complete`.
* fix(scheduled-tasks): honor the caller's AbortSignal and harden the spawn boundary
Four findings from review, all in the model-initiated `create_sub_session`
path (the scheduled `isolated` dispatch reaches none of them).
`execute()` took no parameters, so it silently dropped the parent turn's
`AbortSignal`. `Session.ts` awaits `invocation.execute(signal)` without
racing the abort itself, so cancelling a turn with a `first-turn`
sub-session in flight pinned the caller's tool loop until the daemon's
5-minute ceiling. Accept the signal and return as soon as it fires.
The sub-session is deliberately NOT cancelled and deliberately KEEPS its
concurrency slot: `sendPrompt` has no abort seam, so the sub-session runs
on. Releasing its slot on cancel — as the review suggested — would let the
caller over-admit against sub-sessions that are still consuming a bridge
session and model quota.
`handleCreateSubSession` trusted the child-supplied `callerSessionId`
verbatim, and that id keys the launcher's per-caller concurrency bucket: a
fabricated id starts a fresh bucket at zero (cap evasion) and a victim's id
burns their slots (DoS). Validate it with the connection's existing
`ownsSession` seam.
Every daemon session wires a spawner, sub-sessions included, and each gets
its own cap-sized bucket — so one prompt could fan out 5ⁿ sub-sessions until
`maxSessions` ran dry. Gate nesting at one level: the launcher remembers the
sessions it spawned and refuses to spawn from them. With `callerSessionId`
now authenticated, the gate cannot be sidestepped.
Cap the prompt at 100,000 chars (matching the scheduled-task REST route) and
the display name at 200, both at the bridge trust boundary and, for the
prompt, in the tool's own validation so the model gets an actionable error.
Not changed: `create_sub_session` stays in `PermissionManager.CORE_TOOLS`.
Membership there SUBJECTS a tool to the `coreTools` allowlist; it does not
exempt it. Removing it — as the review suggested — is what would let the
tool bypass a user's allowlist, the way `agent` and `send_message` do today.
* fix(core): do not spawn a sub-session for an already-cancelled turn
`raceCancellation(spawner({…}), signal)` evaluated the spawner as an
argument, so the spawn started before the abort was ever checked. A turn
cancelled before `execute()` ran still created a sub-session on the daemon
— and it kept a concurrency slot — while the tool reported itself
cancelled.
Take a thunk instead, so the pre-abort check happens before any daemon work
is started. Track whether the spawn actually began, and say so: "cancelled
before it started, no sub-session was created" is a different fact from
"a sub-session may already have been created and is not cancelled".
Regression test asserts the spawner is never called for a pre-aborted
signal; it fails against the eager-argument form.
* fix(serve): require callerSessionId and stop misreporting an early stream close
Two findings from review.
`awaitFirstTurn`'s `'incomplete'` stopReason was unreachable. The cleanup
`finally` calls `ac.abort()` unconditionally to tear the subscription down,
so by the time the stopReason ternary read `ac.signal.aborted` it was always
true. An event stream that closed before the turn finished (bridge teardown,
WS drop) was reported as a 5-minute wall-clock `'timeout'` — indistinguishable
from a real one. Track the timer firing in its own flag.
`callerSessionId` was validated only when present. Omitting it handed the
launcher `undefined`, which minted an `anon:<uuid>` bucket — a fresh
concurrency bucket per call, so no cap — and skipped the depth-1 nesting gate
(`info.callerSessionId !== undefined && …`). Authenticating the id closed
forgery but not omission. It is now required at the bridge boundary, and
required in `CreateSubSessionInfo`, so the launcher's anonymous fallback and
the gate's presence check are both gone. Every real caller has a session id —
the tool only ever runs inside a session's turn.
* fix(serve): surface dropped fires and drain timeouts; bound sub-sessions per workspace
Three findings from review.
A dropped `isolated` scheduled fire left no trace. `debugLogger.warn` writes
nothing unless a debug log session is active, and the scheduler persists the
fire as a run before dispatch — so a nightly task could fail forever while its
history claimed it ran. It now also writes to stderr, which the daemon forwards
from the child.
A sent-mode drain that hit its 30-minute ceiling was equally silent: the catch
saw `drainAc.signal.aborted` and skipped logging, the `finally` freed the
concurrency slot, and the sub-session — which the abort does not cancel — kept
burning a bridge session and model quota. The timer now records its own firing
(the controller cannot: `finally` aborts it on every exit path) and the timeout
is written to stderr. The drain ceiling is injectable for tests, mirroring
`firstTurnTimeoutMs`.
The per-caller concurrency cap trusts `callerSessionId`, and the bridge can only
authenticate that id as "a session on this channel". Every session of a
workspace shares one child process, so nothing at the transport can prove which
of them issued the call — and a per-session secret would be readable by the
whole process anyway. Rather than pretend otherwise, add a workspace-wide
ceiling on concurrent sub-sessions that holds no matter which bucket a launch is
charged to.
|
||
|
|
637d00cebc
|
feat(core): render PDF pages to images when text extraction overflows or fails (#6585)
* feat(core): render PDF pages to images when text overflows or fails Replace the PDF text-only 12000-token dead-end with a bounded page->image fallback for vision-capable models, mirroring claude-code's approach. - pdf.ts: add renderPDFPagesToImages / isPdftoppmAvailable (pdftoppm -jpeg -scale-to), with timeout, error mapping, and a total-bytes cap. - fileUtils.ts: text-first, then four ordered fallbacks on overflow/failure: (1) render to the vision main model (explicit read, <=20 pages), (2) render <=4 pages to the vision bridge (text-only model, scanned @-PDF), (3) reference (no-pages @-attach), (4) text too-large guidance / error. Widen ProcessedFileReadResult.llmContent to PartListUnion. - read-file.ts / readManyFiles.ts / pathReader.ts: handle the Part[] payload; pathReader now references large @-PDFs, aligning the two @ paths. WIP: tests not yet migrated. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * test(core): cover PDF page rendering and vision-bridge fallbacks - pdf.test.ts: renderPDFPagesToImages (success/sort, page range, Infinity last page, unavailable, password, corrupt, empty output, byte-cap). - fileUtils.test.ts: mock renderPDFPagesToImages and assert vision-model page/whole-doc/scanned rendering, >20-page guidance, truncation notes, renderer-unavailable fallback, and text-only bridge rendering (scanned @ PDF -> <=4 pages, page-count note, text-first reference, no-flag error). 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * fix(core): never drop PDF pages silently when page count is unknown Audit follow-up. Both image-render fallbacks could omit pages without a note when pdfinfo is unavailable (page count null): - vision no-pages render: when the size heuristic underestimates and the render fills the per-read page ceiling, add a note (previously only the byte-cap path was flagged). - vision-bridge render: when the page count is unknown and the render hits the image cap, flag it (previously required a known count). Soften the note wording to not over-promise a later-range read on the @ path. Adds regression tests for both unknown-page-count paths. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) |
||
|
|
25423b1526
|
fix(cli): align memory dialog with managed memory (#6434)
* fix(cli): align memory dialog with managed memory * test(cli): stabilize memory dialog path rendering * fix(cli): make memory target switch exhaustive * fix(cli): tighten memory dialog target handling * fix(cli): handle headless managed memory dialog * test(cli): cover desktop managed memory dialog branches * fix(cli): open memory folders asynchronously * test(cli): assert managed memory folder setup * fix(cli): simplify memory folder opener * fix(cli): clarify memory folder opener behavior |
||
|
|
4d5015389d
|
chore(core): remove stale refreshStartupContextReminder mocks from tool-search tests (#6423)
Two inline mock clients still stubbed refreshStartupContextReminder after it was removed from tool-search.ts. makeConfigWithRegistry() was cleaned up in the original PR but these two were missed. Co-authored-by: Aleks-0 <aleks-0@users.noreply.github.com> |
||
|
|
3b20a12436
|
fix(extension): clean tempDir before fallback git clone on Windows (#6545)
downloadFromGitHubRelease can write a partial archive (or extracted files) into tempDir before failing - e.g. a repo whose latest GitHub release is a source tarball that isn't a valid extension archive. The catch block then reused that dirty tempDir for cloneFromGit, where `git clone <url> ./` fails with "destination path '.' already exists and is not an empty directory". On Windows this surfaces as a hard install failure for any extension repo that has a GitHub release but isn't structured as a release-asset extension (reproduced with https://github.com/Imbad0202/academic-research-skills). Recreate a clean tempDir (rm + mkdir) before the fallback clone so the git clone always runs on an empty directory. Fixes #6334 |
||
|
|
e3a247f99e
|
perf(core): add pure-ASCII fast path to text token estimation (#6551)
estimateTextTokens scanned every string char-by-char via charCodeAt to classify ASCII vs non-ASCII code units. For pure-ASCII text (code, English prose - the common case) a single regex scan using V8's optimized string search replaces the JS loop, and the mixed-text path now counts only non-ASCII units, deriving the ASCII count from the length. The token formula is unchanged, so results are byte-identical for every input; verified exhaustively over all 65536 single code units plus 20k randomized mixed strings against the previous implementation. Median of 6 solo benchmark runs over a deterministic mixed fixture set: 51.9ms -> 32.2ms (-38%, 1.61x). |
||
|
|
fbdaa52c52
|
Gate browser automation MCP on external adapter (#6472)
* feat(cli): gate browser automation adapter * fix(cli): close browser automation review gaps * test(cli): cover browser automation gates * fix(cli): close browser automation review gaps * fix(cli): close browser automation review gaps |
||
|
|
10fa9effbb
|
fix(shell): avoid self-kill from pgrep selectors (#6544)
* fix(shell): avoid self-kill from pgrep selectors Fixes #6246 * fix(shell): handle pgrep review cases |
||
|
|
0a54652e07
|
fix(core): configurable vision bridge timeout + retry with fresh budget (#6541)
* fix(core): configurable vision bridge timeout + retry with fresh budget The vision bridge capped image transcription at a hardcoded 30s. On a slow or proxied vision endpoint one latency spike permanently lost the image: the retry inside the side query shared the same abort signal, so a second attempt inherited whatever seconds were left of the first attempt's budget. Add a visionBridgeTimeoutMs setting (per attempt; unset keeps 30s, non-positive values are ignored) and retry a timed-out attempt once at the bridge level with a freshly created timeout signal. Non-timeout failures still fail immediately, and user cancellation is still reported as skipped. Fixes #6524 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(core): harden visionBridgeTimeoutMs against invalid timer values Maintainer E2E review found that fractional or out-of-range values such as 30000.5 and 4294967296 could pass the old number-typed config path and Config's Number.isFinite && > 0 guard. Node rejects fractional AbortSignal.timeout values with RangeError and can degrade oversized timer values to a 1ms timeout, which made image turns fail before any model request. Tighten the Config guard to positive integers within the supported 32-bit timer ceiling, make visionBridgeTimeoutMs a bounded integer setting so /config and the generated JSON schema reject bad values up front, and move AbortSignal.timeout/any creation inside the bridge try block so any future bad value becomes a safe failure result instead of an escaped rejection. Also mark the setting requiresRestart because it is read once in the Config constructor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6dafb330f2
|
docs: fix model-provider config shape and refresh feature/setting drift (#6552)
Audit findings against the current codebase:
- model-providers.md, auth.md: the documented modelProviders shape used
the reverted `{ protocol, models }` wrapper. The canonical shape is a
bare `ModelConfig[]` array per provider id (a wrapped entry in a
migrated settings file is silently skipped). Update all examples and
prose, document the separate top-level `providerProtocol` map for
custom provider ids, and correct the unknown-key behavior.
- settings.md: correct the default for
`model.chatCompression.screenshotTriggerThreshold` (20, not 50).
- commands.md: add the missing `/reload-plugins` command and note that
`/dream` and `/forget` are registered only when managed auto-memory
is available.
- Add a Computer Use feature page (on-by-default desktop automation via
the cua-driver native driver) and wire it into the features nav and
the qc-helper doc index.
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
87cad6f1ae
|
feat(memory): make background memory agent timeouts configurable (#6459)
* feat(memory): make background memory agent timeouts configurable Adds a memory.agentTimeoutMinutes setting that overrides the hardcoded max runtime of the four background memory agents (extraction, dream, remember, skill review). Unset keeps each agent's built-in default (2-5 minutes); 0 disables the time limit entirely. Local LLM setups load large extraction prompts far slower than hosted models, so the fixed 2-minute extractor budget times out before the context even finishes loading — and each retry carries a longer conversation, making the next timeout more likely. Fixes #6308 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(memory): address review — wire agentTimeoutMinutes to skill review, clamp negatives, add tests The auto-skill scheduling path always passed an explicit timeoutMs, so the new setting never reached the skill review agent; drop the redundant pass-through so the planner's config fallback applies. Clamp negative settings values at the Config constructor (schema validation only runs on interactive edit paths). Add positive override tests for the dream, remember, and skill review planners, and reduce the settings.md diff to the single new table row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(memory): cover negative-clamp and remember default-timeout paths Review follow-up: assert the Config constructor treats a negative memory.agentTimeoutMinutes as unset, and that the remember planner keeps its built-in 5-minute default when nothing is configured. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
65c0d36be3
|
fix(session): detect and mark broken history chains instead of silently truncating (#6502)
* fix(session): bridge broken parentUuid chains instead of truncating history reconstructHistory walked parentUuid from the newest leaf and stopped at the first missing ancestor, silently dropping every earlier record. A session file with a broken chain (a partial write, or a lost middle segment) therefore lost all history before the break on resume — in both the terminal /resume and the web-shell/ACP replay, which both go through sessionService.loadSession. Add a shared hardened chain walk (buildOrderedUuidChain) that, on a missing parent, bridges onto the newest still-present earlier connected component (union-find; position-based). It treats /rewind gap children as a barrier and matches the tail's sidechain-ness, so it never resurrects abandoned rewind branches or crosses the main/subagent boundary. sessionService and background-agent-resume now share it. loadSession returns historyGaps metadata; the terminal /resume and ACP replay render a localized (i18n) visible divider so the recovered halves are not read as contiguous. The bridged child's parentUuid is rewritten (on the aggregated copy) to the bridged record so rebuildTurnBoundaries/rewind re-root correctly. Read-side only; write-side durability is a separate follow-up. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * qwen: fix CI failure on PR #6502 * fix(session): use non-recovered copy for history gaps with no bridged island When a missing-parent gap has no earlier island to bridge onto (bridgedToUuid is null), the divider is the first visible item — the previous copy still said "recovered earlier history is shown above" with nothing above it. Emit a distinct notice for the null-bridge case (both terminal /resume and ACP replay go through formatHistoryGapNotice). 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * fix(i18n): add zh-TW translation for the non-recovered history-gap notice zh-TW is a strict-parity locale, so the new null-bridge notice key must be translated there too (pre-empts a CI strict-parity failure). 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * fix(session): address history-gap review nits (accuracy, lazy maps, docs, tests) - conversation-chain: gap duration now uses the target island's last-occurrence timestamp; a uuid can span several streamed records, and the first occurrence overstated the gap. - conversation-chain: build posByUuid/lastByUuid lazily on the first gap (healthy sessions skip them); early-return for a caller-supplied leafUuid not backed by any record. - resumeHistoryUtils: reorder createHistoryGapItem so the convertToHistoryItems JSDoc documents its own function again. - HistoryReplayer: add tests covering the gap-notice replay path (notice emitted before the gap child; none when there are no gaps). 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * fix(acp): thread historyGaps through the qwen/session/loadUpdates replay path collectHistoryReplayUpdates now accepts gaps from both callers; the loadUpdates ACP surface passed only records, so a bridged (recovered) history was rendered contiguous there with no gap divider. Adds a loadUpdates test asserting the gaps reach the replayer. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * fix(session): detect-and-mark broken history chains instead of stitching Read-side, a record whose parentUuid is physically missing is indistinguishable from a lost /rewind marker — where the "earlier" turns are ones the user deliberately discarded. Speculatively stitching the nearest earlier island back on (the previous approach) could therefore resurrect deleted content (wenshao's [Critical]). This mirrors claude-code, which never guesses: it only reconnects across a gap when durable metadata (snip removedUuids, compact re-root) proves it safe, and otherwise truncates. Replace the connected-component bridging with detect-only: on a missing parent the walk stops (as it always did) and records a HistoryGap, so the terminal /resume and ACP replay surfaces show a visible "earlier history was lost and could not be recovered" marker instead of silently truncating. No earlier records are reconstructed. Renames the option bridgeGaps -> detectGaps, drops the bridgedToUuid/approxLostMs fields and the now-unused "recovered above" i18n copy, and adds a regression test where the rewind marker is missing and the discarded branch must not be restored. True recovery (keeping the earlier island) requires durable write-side metadata and is left as a follow-up. * refactor(session): correct stale gap comments and dedup gap indexing The detect-only rewrite (13c613c9) left doc comments that still described the removed stitching path — claiming the earlier history was "bridged" or "stitched back on". Reword them to match the actual behavior: the break is detected and marked, the lost segment is not recovered. Also extract the duplicated gap-by-child map construction (identical in both HistoryReplayer.replay and resumeHistoryUtils.convertToHistoryItems) into a shared indexGapsByChild helper alongside formatHistoryGapNotice — the one still-applicable item from the review suggestion summary. Comments + one small refactor only; no behavior change. * fix(session): reset pending @-command state at a history-gap divider Belt-and-braces for the resume renderer: when convertToHistoryItems emits a history-gap divider it already flushes the pending tool group; also clear pendingAtCommands so an unconsumed pre-gap at_command can never be shift()- paired with the post-gap user turn (which would attach @file reads to a turn the user never wrote them on). In the current detect-only design reconstructHistory truncates to the tail island — the gap child is always the first replayed record, so the buffer is already empty at the divider and this cannot trigger. The reset keeps the invariant if that ever changes. Adds a regression test at the convertToHistoryItems boundary. * fix(session): don't detect history gaps on the background-agent resume path Addresses wenshao's review: this non-interactive transcript recovery has no surface to render a gap marker on (unlike interactive /resume via sessionService and the ACP replay via HistoryReplayer), so passing detectGaps: true only to emit a debugLogger.warn and then drop the gaps was an inconsistent half-measure ("half-detection is worse than no detection"). Turn detection off here — the walk truncates at a broken parent link either way, matching this path's historical behavior. Gap surfacing stays exactly on the two paths that have a UI for it. --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
016d624021
|
fix(core): detect subagent tool call loops (#6543) | ||
|
|
b330ec884f
|
chore(release): v0.19.8 (#6549)
* chore(release): v0.19.8 * docs(changelog): sync for v0.19.8 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
5f41b166e6
|
fix(cli): unblock /clear after task cancellation and surface the blocked reason (#5949) (#6499)
/new (alias of /clear) silently did nothing when typed right after cancelling a request, for two stacked reasons: - hasBlockingBackgroundWork() gated on the registry's hasUnfinalizedTasks(), which counts cancelled-but-not-yet-finalized entries. That clause exists for the headless holdback loop (every task_started must pair with a task_notification), but /clear and session resume abort-and-reset the registry right after the gate — suppressing that very notification — so blocking on it only made the switch fail in the window between cancel and finalizeCancelled(). The gate now keys off a new BackgroundTaskRegistry.hasRunningTasks(), which counts only entries still actually executing; the headless holdback keeps using hasUnfinalizedTasks() unchanged. - When genuinely blocked, interactive mode showed only a transient debug line while non-interactive returned a proper error. Interactive now returns the same visible error message, so a blocked /clear no longer looks like a no-op. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
082b3bb3d9
|
fix(memory): give each linked git worktree its own auto-memory root (#6462)
getAutoMemoryRoot() resolved linked worktrees back to the canonical repository root, so every worktree of a repo shared one project memory. Focused worktree sessions polluted the shared MEMORY.md index with unrelated entries, and chats/, workflows/, and team memory were already per-worktree — project memory was the only shared exception. Anchor project memory at the nearest git root (same resolution team memory already uses) so each worktree gets its own memory directory. The main checkout resolves to the same path as before, so existing memory is unaffected. Fixes #6449 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7281f0de8c
|
feat(core): add working_dir to the Agent tool for pinning subagents to an existing worktree (#6456)
* feat(core): add working_dir to the Agent tool for pinning subagents to an existing worktree
Add an opt-in working_dir parameter to the Agent (sub-agent) tool that pins a sub-agent's entire working-directory context to an existing, caller-owned git worktree of the current repository. Unlike isolation: 'worktree', the harness neither provisions nor removes the directory — the caller owns its lifecycle — so a sub-agent can be aimed at a worktree that some earlier step already prepared.
Every "where am I?" surface on the sub-agent's config (target dir, cwd, project root, file discovery, workspace context) is rebound to the worktree, reusing the existing worktree-isolation rebind, so the sub-agent's file and shell tools operate inside that directory and cannot leak into the parent project tree. The path is validated as a worktree registered against the current repository before use, and working_dir is mutually exclusive with isolation.
Wire the /review skill to pass working_dir to every review agent for same-repo PR reviews, so the PR worktree isolation is enforced deterministically at the agent boundary instead of by prompt convention. This makes reviewing multiple PRs concurrently safe.
* fix(core): harden working_dir validation from review feedback
- Reject a working_dir that resolves to the repository's own main working tree. getRegisteredWorktreeBranch accepts the primary checkout too, so `working_dir: "."` or the repo root would otherwise pin a sub-agent to the user's main tree and silently defeat the isolation. A linked worktree has a `.git` file; the main tree has a `.git` directory — require the former.
- Reject working_dir combined with run_in_background. The caller owns the worktree lifecycle and could remove it while a detached agent is still running in it (ENOENT on its own cwd); the isolation: 'worktree' path does not have this problem because the tool owns and reaps the worktree.
- Add an isGitRepository() preflight so a non-repo parent reports the real cause instead of a misleading "not a registered git worktree" error.
- Add tests: repo-relative working_dir resolution (the /review production form), main-working-tree rejection, and the run_in_background guard.
* fix(core): make main-worktree detection robust and tighten working_dir validation
Address a second review round on the Agent tool working_dir parameter.
- Replace the ".git is a file ⟹ linked worktree" heuristic with a reliable check: a linked worktree's per-worktree git dir (--absolute-git-dir) differs from the common git dir (--git-common-dir), while a main working tree's are identical. The heuristic was wrong for a main tree whose .git is a file (git clone --separate-git-dir, submodules), which would have let the main checkout pass the isolation guard. Extracted as GitWorktreeService.isLinkedWorktree(); the fs.stat probe (and its all-errors catch) is gone.
- Reject whitespace-only working_dir (trim before the empty check), matching the worktree-name validation.
- Tests: assert the sub-agent's getCwd()/getWorkingDir() are rebound (not just project root / target dir); cover the monorepo-subdirectory re-anchor branch (getTargetDir() below the repo root); and add real-git coverage for isLinkedWorktree, including the separate-git-dir main tree.
* fix(core): reject working_dir for named teammates
A named teammate spawns via TeamManager with cwd = getCwd() and returns before the working_dir rebind is reached, so the pin was silently ignored and the teammate ran in the parent working tree — a false sense of isolation. Reject working_dir at the point a teammate would actually spawn (name + active TeamManager + top-level session), leaving the name-without-active-team fallback (which spawns a normal sub-agent, where working_dir does apply) untouched. Add a test.
* docs(core): describe working_dir as a cwd pin, not a sandbox
The working_dir parameter description, the interface doc, and the /review skill said the sub-agent "cannot touch"/"cannot leak into" the parent tree. That overstates it: file and shell tools still accept absolute paths, so the isolation is a working-directory pin (cwd-relative operations and search tools stay inside the worktree), not a filesystem sandbox — the same limitation as isolation: 'worktree'. Reword all three to state this accurately.
* test(core): cover working_dir non-git and fail-closed paths; clarify main-tree error
- Add an execute-level test for the working_dir isGitRepository() preflight (non-git parent directory -> "not a git repository").
- Add a test for isLinkedWorktree's fail-closed catch (a non-git path returns false).
- Reword the main-tree rejection error to name both possible causes ("is the main working tree, or its git metadata could not be read"), since isLinkedWorktree also fails closed on a git error rather than only on the main tree.
* fix(core): canonicalize path in isLinkedWorktree; test relative working_dir in a monorepo
- isLinkedWorktree now realpaths the worktree path before comparing --absolute-git-dir (which git returns canonicalized) against --git-common-dir. Without this, a symlinked input (macOS /var → /private/var, os.tmpdir()) made the two diverge for the main working tree and misreported it as linked, letting the main checkout pass the isolation gate.
- Add a test proving a repo-relative working_dir resolves against the subdirectory cwd where fetch-pr actually creates the worktree (git resolves relative worktree paths against cwd), not the repo root — the production monorepo form.
* fix(core): close working_dir gaps from review — classifier, background config, review-workflow coverage
- Include working_dir in toAutoClassifierInput so AUTO-mode permission classification can see the child is rebound to another worktree; a launch could otherwise look benign from subagent_type + prompt alone.
- Reject working_dir when the effective background decision is true — not only for the explicit run_in_background param (already rejected in validateToolParams) but also for a subagent config with background: true, which validateToolParams cannot see. Guard on the resolved shouldRunInBackground so a nested call that downgrades to the foreground is not over-rejected.
- /review skill: extend the working_dir mandate to the Step 4 verification agent and Step 5 reverse-audit agents, which also read files and re-check the diff and would otherwise run against the user's main checkout.
- Add tests for all three.
* test(core): symlink + preserved-suffix coverage; soften working_dir grep/glob wording
- isLinkedWorktree: add a test that passes a symlinked path (the macOS /var → /private/var case, reproduced with an explicit symlink) so the realpath canonicalization is actually exercised.
- Assert a caller-owned worktree run does not emit a spurious "[worktree preserved]" suffix, guarding the externallyManaged cleanup skip against regression.
- Soften the working_dir description: grep/glob default to the worktree as their root but can still be pointed outside via an explicit path, so drop the "enforce it as the workspace boundary" claim.
* fix(core): validate working_dir against the authoritative git worktree registry
- Replace the --git-dir vs --git-common-dir "is it a linked worktree" heuristic with an authoritative check against `git worktree list`. The heuristic could be fooled by a hand-crafted directory carrying a .git file copied from a real linked worktree (structurally a linked worktree, yet not registered); `git worktree list` reads .git/worktrees/<name>/gitdir and excludes such a fake. Renamed isLinkedWorktree -> isRegisteredLinkedWorktree.
- Tests: reject a fake worktree (copied .git file); match a registered worktree through a symlinked input path; and a nested-context test confirming a background: true subagent that downgrades to foreground is NOT rejected by the working_dir background guard (guards the shouldRunInBackground keying against regression).
* fix(core): fix flaky nested working_dir test; harden worktree-registry parsing
- The nested background:true test relied on the default '/test/project' cwd, which does not exist on CI, so simple-git threw at construction instead of the test reaching the isGitRepository() path (this is the red CI on the prior commit). Point it at a real, non-git temp directory instead.
- isRegisteredLinkedWorktree: parse `git worktree list --porcelain -z` (NUL-terminated) so a worktree path that itself contains a newline cannot inject a fake `worktree <path>` entry into a newline-split parse; and reject the primary working tree by canonical path so a linked record whose on-disk path is a symlink onto the main checkout cannot smuggle it through.
* fix(core): fall back when `worktree list -z` is unsupported; add newline-injection regression test
- `git worktree list -z` requires Git >= 2.36. On older git the call threw and the surrounding catch rejected every valid caller-owned worktree with a generic "not a registered linked worktree" error. Fall back to the newline-separated porcelain form instead of hard-failing: that parse is only vulnerable to a worktree path that itself contains a newline, which is a far narrower exposure than rejecting every worktree.
- Add a real-git test that creates a worktree whose path embeds "\nworktree <other>" and asserts the injected path is not accepted while the real (awkwardly named) worktree still validates. Confirmed the test fails when the parser reverts to newline splitting, so it genuinely guards the -z parse.
* fix(core): accept detached-HEAD worktrees for working_dir; cover the failure-path cleanup guard
- getRegisteredWorktreeBranch returns null for a detached-HEAD worktree (its branch reads as 'HEAD'), so a legitimate `git worktree add --detach` target was rejected with a factually wrong "not a registered git worktree" error and the authoritative registry check never ran. Make isRegisteredLinkedWorktree the sole gate and read the branch best-effort: branch is unused for caller-owned worktrees anyway, since cleanup short-circuits on externallyManaged.
- Add a test asserting a detached worktree is accepted and the sub-agent is rebound onto it.
- Add a failure-path test: when the sub-agent throws, the finally / outer-catch path runs cleanupWorktreeIsolation() and the externallyManaged guard must leave the caller's worktree intact. Confirmed both new tests fail without their respective fixes, so they genuinely guard the behaviour.
* fix(core): verify a worktree's registry pointer instead of parsing `git worktree list`
Parsing the registry list forced a bad trade: `--porcelain` alone is newline-delimited, so a worktree path containing a newline can inject a bogus entry, while `-z` needs Git >= 2.36 and would reject every worktree on older git. Verify the single path in question instead:
1. `--git-dir` equal to `--git-common-dir` means the primary working tree, which is rejected.
2. The common dir must be this repository's, or the path belongs to another repo.
3. `<gitDir>/gitdir` records the worktree that registry entry belongs to; it must resolve back to the probed path.
Step 3 is what makes it authoritative: a directory holding a `.git` file copied from a real linked worktree does report a per-worktree git dir, but that entry's pointer names the real worktree, not the copy. No list is parsed, so the newline-injection class of bug is gone on every git version and no `-z` (Git >= 2.36) is required. Detached worktrees, symlinked inputs, separate-git-dir main trees, and other repositories' worktrees are all still handled correctly.
Also bump the real-git integration suite's vitest timeouts to 30s, matching the sibling hooks/symlinks suites, to reduce CI flakiness.
* fix(core): contain working_dir to the repo, tailor the pinned-worktree notice, keep the newline test off Windows
- Containment: a registered worktree can live anywhere on disk, and pinning rebinds the child's WorkspaceContext wholesale, so a model-supplied path could silently move the file tools' boundary outside the repository. Require the resolved path to sit inside the repo (canonical comparison, so a symlink cannot straddle the boundary) and say so in the parameter description. isolation: 'worktree' already had this implicitly.
- Prompt: a caller-owned worktree is the code the agent was asked to work on, not a provisioned copy of the parent's tree. Give it a narrow notice instead of the isolation notice, whose "translate the parent's paths" and "re-read what the parent changed" guidance contradicts the caller's own instructions (/review tells its agents not to cd or prefix absolute paths).
- Skip the newline-injection test on Windows: a path component containing a newline is not representable there (the embedded drive letter makes it invalid), so `git worktree add` errors and the windows test job would fail. The injection cannot occur on Win32 either.
- Add a regression test for a stale registry record whose directory was recreated as a plain directory: `git worktree list` keeps such records for months, and `git rev-parse --show-toplevel` inside one resolves to the MAIN checkout. Probing the path itself rejects it.
* docs(core): correct the working_dir helper's doc comment
The comment still credited getRegisteredWorktreeBranch with enforcing "must be a registered worktree". That gate is now isRegisteredLinkedWorktree (plus the in-repository containment check); getRegisteredWorktreeBranch is consulted only for a best-effort branch label and is deliberately not a gate, since it returns null for a legitimate detached-HEAD worktree.
* fix(core): read the worktree registry from the repo, not the candidate directory
isRegisteredLinkedWorktree derived everything from files inside the directory being validated: `<target>/.git` names a git dir, whose `commondir` and `gitdir` were then trusted. All three are candidate-controlled, so a *fabricated* chain (not merely a `.git` copied from a real worktree) could name itself and be accepted even though git's registry holds no entry for it. The docstring's claim that it consulted "git's own registry entry" was therefore wrong.
Read the registry from the repository instead: some `<commonDir>/worktrees/<name>/gitdir` must name the path. Keep a liveness probe inside the path as well: its own `--git-dir` must be that same entry. The two are complementary. The registry answers "is this path registered?" and so rejects a fabricated chain, a copied `.git` file (the entry names the original), another repository's worktree, and the primary working tree, which has no entry of its own. Only the probe answers "is it a worktree right now?" and so rejects a stale `prunable` record whose directory was deleted and recreated as an ordinary directory.
Add a fabricated-chain regression test, and confirm each of the two tests fails when its corresponding half of the check is removed.
|
||
|
|
e7f94a5a3c
|
fix(core): detect non-SSE HTTP 200 responses in OpenAI streaming pipeline (#6466)
* Initial plan * fix(core): detect non-SSE HTTP 200 responses in OpenAI streaming pipeline When a gateway/proxy intercepts a streaming request and returns HTTP 200 with a non-SSE content-type (e.g. text/html), the OpenAI SDK silently produces zero chunks, resulting in a confusing "Model stream ended without a finish reason" error and an empty interaction log (response: null, error: null). Add NonSSEResponseError that is thrown early when the response content-type is incompatible with SSE (not text/event-stream, application/json, etc.). The error carries diagnostic metadata: HTTP status, content-type, bounded body prefix, and request-id header — allowing users and maintainers to distinguish "empty model stream" from "gateway returned non-SSE page". Uses the OpenAI SDK's withResponse() API to access the raw HTTP response headers before consuming the stream iterator. Falls back gracefully when withResponse() is unavailable (e.g. in mocked environments). Closes QwenLM/qwen-code#6465 * fix(core): replace non-null assertion with nullish coalescing in content-type check * fix(core): address non-sse review feedback * fix(core): expose non-sse request id alias --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: yiliang114 <effortyiliang@gmail.com> |
||
|
|
151d269413
|
feat: extension file reload — watch for plugin changes and hot-reload runtime (#6347)
* feat: extension file reload — watch for plugin changes and hot-reload runtime - Extract refreshExtensionRuntime to centralize MCP, skills, subagents, hooks, and memory refresh - Add ExtensionFileWatcher (chokidar) for auto-detecting extension file changes - Add ExtensionRefreshState with per-session scoped instance and mutation suppression - Replace monkey-patching with ExtensionManager native mutation listeners - Add /reload-plugins slash command with i18n-aware summary across all 9 locales - Add auto-refresh of extension content (commands/skills/agents) on file change - Add HookRegistry.reloadConfiguredHooks() with correct error recovery - Fix async mutation pairing via id-based Map instead of LIFO stack - Fix bootstrap watcher close() UB with queueMicrotask deferral - Fix concurrent refresh with runningRef/pendingRef guard - Fix error propagation from refreshExtensionContentRuntime to UI - Fix isIgnored cross-platform path splitting (path.sep → regex) - Fix wrong ExtensionMutationEvent type via import from core - Fix addItem on unmounted component with mountedRef guard - Set followSymlinks: false on chokidar watchers * fix: address extension reload review feedback * docs: expand extension file reload design * fix: harden extension reload watcher state * fix(core): tag extension refresh legs * fix(cli): harden extension reload state handling * fix(cli): clarify extension reload failure state * fix(cli): tighten extension reload boundaries * chore: resolve main conflicts for extension reload * chore: drop unrelated merge formatting changes * fix(core): harden extension refresh edge cases --------- Co-authored-by: 俊良 <zzj542558@alibaba-inc.com> Co-authored-by: 易良 <1204183885@qq.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> |
||
|
|
c516ee8c2f
|
fix(core): omit deprecated temperature param for Claude 4.8+ (#6520)
* fix(core): omit deprecated temperature param for Claude 4.8+ Claude Opus 4.8 deprecated the `temperature` sampling parameter — the server returns 400 with "temperature is deprecated for this model" when it is sent. Add `modelRejectsTemperature()` version gate (mirroring the existing `modelRejectsManualThinking()` pattern) and conditionally omit `temperature` from the request body for models with major > 4 or 4.minor >= 8. Older models and unknown/unversioned ids retain the previous behavior (default temperature of 1). Fixes #6519 * fix(core): address review — ternary form, debug warn, 5.x + unknown tests - Switch from boolean-spread to ternary form for idiom consistency with the thinking/output_config spreads at L697-698. - Add a once-per-generator debugLogger.warn when a user-configured temperature is silently dropped on 4.8+ (latched like effortClampWarned). - Add test coverage for claude-sonnet-5 (5.x family omits temperature) and unknown/unversioned model id (keeps temperature) to pin the major > 4 branch and prevent future narrowing. --------- Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
a07fdc6042
|
fix(memory): allow forget to remove user managed memory (#6432)
* fix(memory): allow forget to remove user managed memory * fix(memory): harden forget index rebuilds * test(cli): stabilize session archive race assertion * test(memory): cover deny precedence with ask bypass |
||
|
|
a6ad81f23c
|
feat(hooks): inject background tasks and cron jobs status into Stop/SubagentStop hook payloads (#6531)
Extend StopInput and SubagentStopInput with background_tasks and crons fields, and wire existing BackgroundTaskRegistry and CronScheduler snapshots into fireStopEvent/fireSubagentStopEvent. This enables hook scripts to observe async work state at agent stop time for status reports, cleanup logic, and monitoring. Fields are always present (empty arrays when no active tasks/crons). Data collection is non-blocking via try/catch to not delay stop hook response. Closes #6529 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
e83d548cd9
|
fix(core): reject Windows-style workspace artifact paths (#6483)
Reject Windows-style absolute and traversal paths in record_artifact workspacePath validation by checking portable slash-normalized path semantics. This keeps artifact metadata aligned with the workspace-relative locator contract while preserving valid relative paths. Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com> |
||
|
|
29cefd7fb1
|
fix(web-shell): count daemon sessions in Daemon Status usage dashboard (#6493)
* fix(web-shell): count daemon sessions in Daemon Status usage dashboard
The Web Shell usage dashboard read usage_record.jsonl exclusively, but only
the TUI /clear path ever writes that file — so daemon / Web Shell sessions and
any un-cleared session (whose usage lives only in the per-session transcripts)
were never counted. Real-world "today" totals could undercount ~20x.
Add core loadUsageHistoryWithLive(): the durable persisted history unioned with
a bounded replay of recent transcripts, deduped by sessionId (persisted wins,
as the authoritative final snapshot). rebuildFromSessionJsonl gains an mtime
window and a skip-set (read from each transcript's first line) so the merge is
incremental and cheap. The daemon /usage/dashboard route and loadUsageDashboard
now use it.
The trailing window (35d) keeps the summary + daily charts exact while bounding
load latency (~1.7s vs ~13s for a full-year replay on a heavy history); older
heatmap cells fall back to persisted data. With no persisted base (fresh
machine / pure Web Shell user) it replays the full history, so the heatmap is
never silently truncated. Read-only: serving the dashboard never writes ~/.qwen.
* fix(web-shell): address review — skip transcripts by filename, add coverage
Skip already-persisted transcripts by their filename (`{sessionId}.jsonl`,
guaranteed by chatRecordingService) instead of opening each file to read the
sessionId from its first line — zero I/O per skipped session on a cache miss.
Add tests: the loadUsageDashboard wiring counts a transcript-only (daemon)
session, the rebuilt-empty (all-persisted) common case, and a corrupt
usage_record.jsonl falling back to a full transcript replay.
|
||
|
|
14f02c132a
|
fix(core): Match hook display-name matchers to tool ids (#6373)
* fix(core): match hook tool display names * fix(core): tighten hook matcher aliases * fix(core): align hook matcher aliases |
||
|
|
faf7c434fc
|
fix(core): reject fractional LSP limit inputs (#6455)
* fix(lsp): validate limit as positive integer Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com> * fix(lsp): declare limit minimum in schema Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com> --------- Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com> |
||
|
|
560e6103a9
|
feat(cli): review auto-generated skills with an inline preview, editor handoff, and an in-dialog off switch (#6393)
* feat(cli): skill review dialog — inline preview, open-in-editor, turn-off option The auto-skill review dialog now shows the staged SKILL.md inline (sanitized, bounded reads, wrap-aware height cap), opens it in the configured editor without advancing (with watcher-based preview refresh so non-blocking GUI editors work), and offers a visible last option to turn the feature off — effective immediately in-session, persisted at workspace scope, non-destructive to the pending batch. Bulk options render only while at least two skills remain. Re-enabling auto-skill from /memory can resurface a batch put aside by turn-off. * test(integration): render harness + capture scenarios for the skill review dialog Browser-free harness that renders the production dialog from source via an ESM loader hook; its before mode renders the globally installed qwen (no local fixture — the baseline is what actually shipped, or a loud failure). Terminal-capture scenarios produce the PR's before/after screenshots. * fix(cli): address review findings on the skill review dialog - Sanitize the model-generated name and description in the dialog header, same as the preview body — an escape sequence in the frontmatter must not reach the terminal through the header fields. - Clamp the preview width to the dialog container cap (min(columns-4, 100), the same clamp DiffDialog uses) instead of the raw terminal width, which broke the wrapped-row accounting on terminals wider than ~106 columns. - Catch settings persistence failures in the turn-off option: surface the error in the dialog and leave the feature untouched instead of letting the throw escape the keypress handler. - Extract the auto-open gate into shouldAutoOpenSkillReview and cover it with a truth table (turn-off, /memory overlap, re-enable, Esc-dismiss). - Cover the MemoryDialog auto-skill ON->OFF toggle direction. - Release the capture harness temp dir with try/finally. * fix(cli): guard the preview watcher against async errors and event bursts An FSWatcher 'error' event after attach had no listener, so Node raised it as an uncaught exception and the global handler exited the CLI. Consume it and drop the watcher; the blocking-editor reload still works. Also debounce the watch callback (300ms, same as SettingsWatcher): a single editor save fires several raw events, and each one re-read the file and re-attached the watcher. * test(cli): drop white-box watcher tests, keep the end-to-end refresh test The prototype-spy scaffolding tested implementation details (listener registration, synthetic event bursts) and leaned on vite-node interop quirks. The existing on-disk refresh test already exercises the watcher path, debounce included. * fix(cli): sanitize action errors, log preview read failures, cover key guards - Render actionError through sanitizeMultilineForDisplay: error messages can embed the staged path, whose basename derives from the model-generated skill name. - Log the underlying cause when the preview read fails; all failures render the same 'Preview unavailable' otherwise. - Cover Ctrl+O/Cmd+O inertness and Esc dismissal with tests. - Document that getAutoSkillEnabled() also gates on bare/safe mode. --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
86ae16a6d6
|
chore(release): v0.19.7 (#6484)
* chore(release): v0.19.7 * docs(changelog): sync for v0.19.7 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
65c82bed66
|
feat(web-shell): unify scheduled task sessions — bind chat-created tasks + clock icon (#6453)
* fix(web-shell): rename scheduled tasks "查看历史" to "查看对话" * feat(serve): bind cron_create durable tasks to dedicated sessions via keepalive The cron_create tool (core layer) writes durable tasks to disk without a sessionId because it has no access to the session bridge. The keepalive loop runs in the daemon process where the bridge IS available, so it retroactively binds unbound tasks to dedicated sessions — the same flow POST /scheduled-tasks uses for UI-created tasks. Each unbound task gets: spawnOrAttach(sessionScope:'thread'), named ⏰ prompt, sessionId written back to disk. This makes chat-created tasks show "查看对话" with a clock icon in the session list, matching the UI's "新建定时任务". * feat(serve): watch tasks file for immediate binding of new cron_create tasks The keepalive interval is 2-5 minutes, so a chat-created task could wait that long before being bound to a dedicated session — showing no "查看对话" link until the next tick. Adding a file watcher (same directory-watch + debounce pattern the scheduler uses) triggers an immediate tick when cron_create writes to disk, so the task is bound within ~500ms. * feat(serve): bind cron_create tasks to current session + ⏰ rename via keepalive Switch from creating a separate dedicated session to binding the task to the current chat session (so the first message is already in the transcript). The keepalive then renames that session to ⏰ prompt — the core layer can't rename sessions (no bridge access), but the daemon process can. A Set tracks renamed sessions to avoid repeated updateMetadata calls. Unbound tasks (legacy/CLI) still get new sessions via the existing bind path. * fix(core): keep createDurable() tasks unbound by default Reverts the auto-binding of durable tasks to the current session in createDurable(). Binding to a specific session means only that session can fire the task (#shouldFireDurable), but non-daemon paths (TUI, ACP, headless) have no keepalive to rehydrate the session after exit — making tool-created durable tasks go dormant. The daemon keepalive (bindAndNameSessions) already handles binding unbound tasks to dedicated sessions with ⏰ naming, so daemon-mode tasks get the same UX without the regression. * fix(serve): roll back orphan sessions in keepalive binding + add tests When bindAndNameSessions spawns a dedicated session for an unbound task but the subsequent updateCronTasks write fails (or the task was deleted between read and write), the spawned session was left behind with no owning task — the next tick would see the task still unbound (or spawn more orphans). Add rollback: closeSession + removeSession on failure, matching the POST /scheduled-tasks rollback pattern. Also add positive test coverage for the new binding paths: - unbound task → spawn + name + write sessionId to disk - bound task without ⏰ prefix → named exactly once (renamed Set dedup) - task vanishes before write → spawned session is rolled back * fix(serve): add timeout to spawnOrAttach in keepalive binding + test hardening BZ-D: spawnOrAttach in bindAndNameSessions had no timeout boundary — a hung spawn would keep running=true and stall all subsequent ticks, stopping heartbeats/revives for every scheduled-task session. Wrap with withTimeout (configurable via spawnTimeoutMs, default 30s) and attach a background handler to clean up late-resolved orphans. Also generalized withTimeout error messages to include the operation name, and made spawn timeout configurable for tests. Test improvements (GPT-5 review suggestions): - Assert spawnOrAttach payload (workspaceCwd + sessionScope: thread) - Verify SessionService.removeSession called during rollback - Regression test: createDurable stays unbound after enableDurable - Hung-spawn test: tick completes despite non-abortable spawn hang * fix(serve): keepalive hardening + i18n sync (review suggestions) - i18n: sync English 'View history' → 'View conversation' to match Chinese '查看对话' - Prune renamed Set alongside reviveState when tasks are removed - fs.watch: clarify null filename handling for Linux (treat as match) - updateCronTasks: skip .map() when task not found (no-op optimization) - Add tests: disabled unbound exclusion, naming failure resilience |
||
|
|
3d1122d284
|
perf(cli): defer startup prefetch tasks (#6303)
Some checks failed
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
SDK Python / Classify PR (push) Has been cancelled
SDK Python / SDK Python (3.10) (push) Has been cancelled
SDK Python / SDK Python (3.11) (push) Has been cancelled
SDK Python / SDK Python (3.12) (push) Has been cancelled
* perf(cli): defer startup prefetch tasks * fix(cli): await IDE for prompt-interactive startup * perf(cli): defer interactive telemetry startup * test(cli): add missing assertions and Zed/ACP path coverage for startup prefetch Address three test coverage gaps identified during code review: - Assert mockStartEarlyStartupPrefetches in both kitty protocol tests (C1: API preconnect call was wired but never verified) - Add Zed/ACP integration test verifying deferIdeConnection is false when getExperimentalZedIntegration returns true (C2: Zed path was entirely untested) - Assert mockStartBackgroundHousekeeping in startup-prefetch test (C3: unconditional housekeeping dispatch was never verified) * docs: move startup prefetch design doc to performance subdirectory * docs: translate startup prefetch design doc to English * fix(cli): address startup prefetch review comments Tighten the startup prefetch follow-up fixes from review while keeping prompt-interactive telemetry on the fast interactive startup path. - Preserve Error objects when deferred startup tasks fail - Remove the unbalanced api_preconnect profiler lifecycle event - Guard background housekeeping so it only runs for interactive configs - Document and test prompt-interactive telemetry deferral semantics * fix(cli): initialize telemetry for prompt-interactive prompts Ensure sessions launched with an initial interactive prompt have telemetry ready before the auto-submitted first request runs. - Exclude prompt-interactive startup from telemetry deferral - Pass a post-render telemetry option through interactive UI startup - Skip duplicate post-render telemetry startup for initial prompts - Update tests to cover the first-prompt telemetry guarantee Note: Plain interactive TUI startup still defers telemetry post-render. * fix(cli): preserve startup first-request guarantees Keep deferred startup work from weakening first-request behavior in interactive sessions that submit prompts automatically or remotely. - Store telemetry deferral on Config and reuse that decision at render time - Keep IDE startup awaited for prompt-interactive and input-file sessions - Add a timeout for deferred IDE connection failures - Cover ordinary interactive telemetry deferral and IDE startup edge cases * fix(cli): make post-render IDE connection opt-in Default startInteractiveUI to the already-connected IDE path so future callers do not accidentally connect twice when initializeApp used its eager default. - Change the post-render IDE connection default to false - Update startInteractiveUI tests to assert the safer default * perf(cli): surface deferred IDE connection status Make ordinary interactive IDE startup visible while preserving the post-render prefetch path and first-paint performance tradeoff. - Emit deferred IDE connection lifecycle events for connecting, success, and failure states - Surface IDE startup status in the TUI footer without blocking input - Log late underlying IDE failures after timeout for better diagnostics - Document telemetry deferral tradeoffs and add startup lifecycle tests --------- Co-authored-by: heyang.why <heyang.why@alibaba-inc.com> |
||
|
|
d1d53d7122
|
fix(monitor): preserve inherited git pager (#6429) | ||
|
|
9bb68b323c
|
fix(core): strip system-reminder blocks from session title and recap side-query prompts (#6435)
* fix(core): strip system-reminder pollution from session title and recap prompts The `filterToDialog` function in sessionTitle.ts and sessionRecap.ts was including system-reminder blocks (skills list, CLAUDE.md, MCP announcements) in the prompt sent to the title/recap model. When the user's first message was short, `flattenToTail` would reach back into these injected entries, causing titles like "resolve-cr-comments" instead of reflecting the actual conversation. - Skip startup prelude entries via `getStartupContextLength` - Strip `<system-reminder>` blocks from text parts - Add shared `stripSystemReminderBlocks` helper in environmentContext.ts - Add regression tests for both sessionTitle and sessionRecap Closes #6419 * fix(core): preserve mixed reminder prompt turns |
||
|
|
6fdd0fc710
|
fix(core): Support large text range reads (#6404)
* fix(core): support large text range reads Allow text reads to stream bounded line ranges for files larger than the previous 10MB guard, while preserving media size limits and forwarding cancellation through read_file/read_many_files/ACP paths. Refs #6403 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): address large text review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): propagate abort signals in text reads Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): validate streamed utf8 reads Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): handle disabled line truncation for large reads Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): use kebab-case for text range reader Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): preserve artifact size errors for large sources Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): address large text review follow-up Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): allow default large text reads Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): honor text read byte caps Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): clarify invalid utf8 range read errors Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): prevent truncated full large reads Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): preserve unbounded line-zero reads Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): forward artifact read cancellation Pass artifact execution abort signals into source file reads and preserve cancellation semantics when the read is aborted. Add regression coverage for unbounded large UTF-8 range reads and offsets beyond EOF. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): address file read review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): preserve large text mutation reads Allow default unbounded readTextFile calls to keep reading full large text files so mutation tools can prepare complete snapshots after a prior ranged read. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
cfb1febfe3
|
Avoid refreshing session activity on load (#6439)
* fix(core): avoid refreshing session activity on load * test(core): align resumed title source expectations * docs(core): clarify resumed title reanchor |
||
|
|
077a2471f9
|
fix(core): prevent re-invoking loaded skill from appending duplicate content (#6430)
When a skill is invoked multiple times in a session, each invocation previously appended the full SKILL.md body content to the conversation history as a new tool result, wasting context tokens. Add an isSkillLoaded callback to SkillToolInvocation that checks loadedSkillNames before building the full content. On re-invocation, return a short confirmation message instead of the full body. The check runs after successful skill load (so disabled/not-found paths are unaffected) but before content construction, hooks registration, and allowedTools application (which are idempotent and already applied on first load). Fixes #6427 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
736b710ed0
|
feat(core): add tools.visible config for selective deferred-tool visibility at startup (#6372)
* feat(core): add tools.visible config for selective deferred-tool visibility * fix(cli): wire tools.visible from settings.json into Config Add settingsSchema entry for tools.visible and plumb it through loadCliConfig into ConfigParameters.visibleTools. Without this the core-level visibleTools support was unreachable from settings.json. Adds 3 CLI-level tests: visibleTools passthrough, empty default, safe-mode suppression. Fixes #6368 * fix(core): exclude visibleTools from tool_search candidates and reveal path Add visibleTools gate to collectCandidates() and loadAndReturnSchemas in tool-search.ts to prevent KV-cache invalidation when tool_search is invoked for a visible-deferred tool. Without this, a tool listed in tools.visible would still appear as a keyword-search candidate and select: would still trigger revealDeferredTool + setTools, defeating the purpose of promoting it to first-class visibility. 3 tests: keyword-search exclusion, select: no-reveal/no-setTools, and select: still works for non-visible deferred tools. * fix(cli): include visibleTools in /context per-tool token breakdown Add config.getVisibleTools().has(tool.name) gate to the deferred-tool skip condition in collectContextData. Without this, visible-deferred tools appear in the headline total (via getFunctionDeclarations()) but are excluded from the per-tool breakdown, causing the sum to mismatch. 1 test: visibleTools included in breakdown despite deferred+unrevealed. * fix: address all 7 review suggestions for tools.visible 1. settingsSchema: user-facing description instead of internal jargon 2. config.ts: use normalizeToolNameList (generic name) for both disabled and visible 3. config.test.ts: add bare-mode exclusion test 4. tool-registry.test.ts: disabledTools > visibleTools priority test 5. tool-registry.ts: update JSDoc for getDeferredToolSummary 6. tool-search.test.ts: mixed select: visible+non-visible test 7. tool-registry.test.ts: visible survives clearRevealedDeferredTools * chore: regenerate settings.schema.json after description update CI check detected that settings.schema.json was out of sync with settingsSchema.ts after the description was changed in commit 73e879882. * fix: address wenshao review — extract isDeferredAndHidden, clean up abstractions 1. Remove normalizeToolNameList (empty wrapper, violates AGENTS.md no-abstraction rule) 2. Extract ToolRegistry.isDeferredAndHidden() — 5 call sites reduced to 1 predicate source 3. Add dirty-input test for tools.visible (whitespace, duplicates, empty strings) 4. Add MergeStrategy.UNION test for tools.visible across user + workspace scopes --------- Co-authored-by: Aleks-0 <aleks-0@users.noreply.github.com> |
||
|
|
5d2bfbd21b
|
feat(core): add Tool(param:value) permission syntax for parameter-level access control (#6106)
* feat(core): add Tool(param:value) permission syntax for parameter-level access control Introduces key:value parameter matching in permission rules, allowing users to grant or deny tool access based on specific input parameters. - Parse key:value pairs from specifiers for literal-kind rules - Support wildcard patterns (*), multiple params, and mixed syntax - Thread toolParams through PermissionCheckContext and matchesRule - Add 11 unit tests covering parsing, matching, wildcards, and edge cases - Maintain backward compatibility with existing specifier kinds Example rules: Agent(model:opus) # deny agents using Opus model Agent(coder,model:*) # deny coder-type agents with any model Bash(git:*) # still works (legacy :* → git *) Closes #6100 * fix(permissions): resolve PR #6106 review comments for tool param permission syntax - buildPermissionRules now propagates toolParamMatchers for 'Always Allow' flow - MCP tool rules now check param matchers after name matching - Added Object.hasOwn check to prevent prototype chain lookup vulnerability - Replaced matchesCommandPattern with matchesParamValuePattern for param value matching - Added diagnostic logging for param matching failures - Added warnings for empty valuePattern, invalid keys, and non-literal key:value syntax - Added type checking for non-primitive param values * fix(core): address critical review feedback on Tool(param:value) permission syntax - Add 's' flag to RegExp in matchesParamValuePattern for multiline support - Filter buildPermissionRules to stable param keys only (model, subagent_type, skill, server_name) to prevent sensitive data leakage - Reject MCP rules with unsupported specifiers instead of silently ignoring - Fix :* wildcard conversion to use global replace for backward compatibility - Extract shared evaluateParamMatchers helper to deduplicate MCP and standard branches * fix(permissions): address remaining review feedback for Tool(param:value) syntax - Remove unused @ts-expect-error in gitWorktreeService.ts (CI blocker) - Fix ReDoS in matchesParamValuePattern: replace regex with linear-time glob matcher using indexOf, avoiding catastrophic backtracking on multi-wildcard patterns like *a*a*a*a*b - Fix MCP backward compatibility: exclude MCP tools from key:value parsing in parseRule and buildPermissionRules to preserve existing MCP deny rule semantics - Add tests for MCP + param matcher, partial wildcards, ReDoS prevention, number coercion, and buildPermissionRules with toolParams (stable params, volatile params, sensitive data, round-trip) * fix(permissions): address PR #6106 review comments and fix useStatusLine test timeout - Make matchesParamValuePattern case-insensitive to match matchesDomainPattern convention - Remove duplicate JSDoc block before matchesParamValuePattern - Remove dead server_name from stableParamKeys in buildPermissionRules - Add PermissionManager integration tests with toolParams (evaluate, findMatchingDenyRule, hasRelevantRules, hasMatchingAskRule) - Add type guard tests for evaluateParamMatchers (null, undefined, boolean, object) - Fix useStatusLine.test.ts timeout by stubbing cron-task exports in core mock --------- Co-authored-by: 易良 <1204183885@qq.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
001d20ff26
|
feat(scheduled-tasks): run each task in its own dedicated, named session (#6389)
* feat(scheduled-tasks): run each task in its own dedicated, named session Scheduled tasks created through the Web Shell management page were never firing in the daemon-only case: the durable-cron tick runs inside an active agent session, and the Web Shell creates a session only lazily on the first prompt, so a task created on the management page (with no chat open) had nothing ticking it. This binds every management-page task to a dedicated session, minted at create time and named "⏰ <task>". The task fires ONLY inside that session — its transcript is the task's run history — instead of via the shared per-project durable owner. A daemon-side keepalive heartbeats those sessions so the idle reaper doesn't stop them, and a boot-time rehydration reloads them after a restart. Archiving, deleting, or unarchiving the session disables, removes, or re-enables the bound task (covered on both the REST and ACP surfaces). Also adds task editing, a live next-run countdown, run history, a one-per-row card layout, and a "run now" that executes in the task's bound session and updates the last-run time. All resident-session management is opt-in and enabled only by the real daemon (runQwenServe), so createServeApp embeds/tests are unaffected. * fix(scheduled-tasks): address code review on per-session task feature Review fixes for #6389: - Distinguish archive-disabled from user-disabled tasks: disableTasksForSessions now marks disabledByArchive; enableTasksForSessions only re-enables tasks carrying that flag, so a task the user deliberately disabled stays disabled across an archive/unarchive cycle. [Critical] - Rehydrate task sessions concurrently with a per-session 30s timeout so one hung loadSession can't stall the boot sweep or leave healthy tasks dormant. [Critical] - Await runScheduledTask + reload before executing the prompt in handleRunNow, so a record failure surfaces and the card's "last run" reflects the trigger. [Critical] - Log keepalive/rehydrate read + heartbeat failures at debug instead of swallowing them silently, so a persistently-failing keepalive is diagnosable. [Critical] - Add integration tests: deleteDaemonSessions -> removeTasksForSessions and unarchiveDaemonSessions -> enableTasksForSessions (guard the coupling). [Critical] - DELETE route: single atomic updateCronTasks that captures the bound session and removes the task in one cycle, closing the read-then-remove TOCTOU. - Stop the keepalive timer during shutdown (matters for embedders that don't process.exit) so it can't fire against a disposed bridge. - Deduplicate DEFAULT_BUILDER: export it once from scheduledTasksSchedule and drop the dialog's copy so the create form and cron-reversal can't drift. - Reject empty-string sessionId in isValidTask: a bound task with "" would silently run unbound under the scheduler's truthy guard. * fix(scheduled-tasks): isolate task sessions, fix catch-up/jitter/revive Second review round (#6389): - Force `sessionScope: 'thread'` when minting a task's session. The daemon's default scope is 'single', which attaches to (reuses) the shared workspace session — so a second task, or a task alongside an open chat, would bind to the same session, rename it, land runs in the wrong transcript, and close it on delete. Thread scope guarantees each task an isolated session. [Critical] - Re-seat a recurring task's schedule anchor to now when a PATCH changes its cron (or flips one-shot→recurring), not just on re-enable. A bound task's catch-up runs on every file-watch reload, so a bare cron edit to an expression with an already-past slot would fire immediately on save. [Critical] - Revive a non-resident bound session from the keepalive when its heartbeat fails (reaper let it go while disabled/archived, now re-enabled). Covers the unarchive and PATCH false→true paths uniformly and retries each interval, so a re-enabled task actually resumes instead of showing a live countdown that never fires. Best-effort, timeout-bounded, non-blocking. [Critical] - Report `nextRunAt` using the scheduler's jittered fire time (`nextDurableFireMs`) instead of the bare cron boundary, so the UI countdown lines up with the real fire (the tick offsets each fire by up to the jitter window) rather than expiring early and advancing prematurely. All four are mutation-verified. The cross-daemon double-fire on bound tasks (same session live in two schedulers) is a separate, architecturally-invasive fix (claim-then-fire on the durable file) tracked as a follow-up. * fix(scheduled-tasks): sync bound session name on task rename Create names a task's session after the task (`⏰ <name>`), but a later PATCH that renamed the task (or edited the prompt of an unnamed task) left the session's display name stale. The PATCH route now re-applies `updateSessionMetadata` with the task's effective label whenever that label actually changes — a bare cron/enabled edit does not touch the session. Best-effort: a metadata failure doesn't fail the committed schedule change. Mutation-verified. * fix(scheduled-tasks): mirror run sessionId on client type; clarify server wiring Review follow-up (#6389, qqqys): - [Medium] `DaemonScheduledTaskRun` now mirrors the daemon's `CronTaskRun` `sessionId?: string`, so run-attribution the wire already sends isn't silently dropped by the client type (not surfaced in the UI yet; passthrough cast means no mapping change needed). - [Nit] Comment the `app.locals.stopScheduledTaskKeepalive` set site, noting it follows the same convention as `fsFactory`/`boundWorkspace`/`acpHandle` and is read by the run-qwen-serve shutdown path (kept the convention rather than diverge to a one-off return value / declaration merge). - [Nit] Comment the outer `.catch(() => {})` on rehydrate as intentional defense-in-depth (the function already handles read + per-session failures). * fix(scheduled-tasks): couple archive/enable + record manual run only on enqueue Two [Critical] review items (#6389, gpt-5-codex): - PATCH re-enable coupling: reject `enabled: true` on a task disabled BY archiving its session (`disabledByArchive`) with 409 `task_session_archived`. Re-enabling it here would show an enabled task with a countdown while its bound session stays archived and can never fire — the caller must unarchive the session (which clears the marker and reloads it). A user-disabled task (no marker) and non-enable edits are unaffected. - Manual "run now" ordering: record the run only AFTER the prompt is enqueued, not before. `runTaskManually` now returns a promise that resolves on enqueue and rejects if the bound session can't be opened (archived/deleted), is superseded, or times out; the dialog awaits it before writing /scheduled-tasks/:id/run, so a failed session switch no longer leaves a phantom run in history. Runs are serialized (one pending at a time, button disabled) so two quick clicks can't drop a prompt on the single bound-run latch. Added coverage for failed session load and double-click; all new tests mutation-verified. * fix(scheduled-tasks): close dormancy/orphan/overflow gaps from review Five items from GPT-5 /review (#6389): - [Critical] Bind tasks to sessions only when resident management is on: createServeApp now passes the bridge to the scheduled-task routes only when `manageScheduledTaskSessions` is set. Embedders that leave it off get UNBOUND tasks (shared-owner firing) instead of bound tasks nothing keeps resident or reloads (which would silently go dormant). - [Critical] Keep the keepalive/revive loop running whenever task sessions are managed, not only when a reaper is active — archiving closes a task session, so a re-enabled one still needs reviving with the reaper disabled. Size the interval under the reaper window (≤ half of it) so a small idle timeout can't let a session be reaped before its first heartbeat. - [Critical] Record a manual run only after the prompt is admitted: the bound run latch now resolves only if `sendPrompt` admitted the prompt and rejects on cancellation (e.g. onSubmitBefore) / failure, so a cancelled Run now no longer advances lastFiredAt or appends history. - [Critical] Clamp the dialog's reload timer to the 32-bit setTimeout ceiling (~24.8 days) so a months-away schedule can't overflow and spin a reload loop. - [Suggestion] Pre-check the task cap before spawning a session, so an over-cap create never mints an orphan task session it must roll back. New tests (route unbound-when-no-bridge, cap-no-spawn, computeKeepaliveIntervalMs bounds, far-future timer clamp) mutation-verified; full server suite green. * fix(scheduled-tasks): guard catch-up double-fire, run-now hang, /run + cron edits Four items from GPT-5 /review (#6389): - [Critical] Bound-task catch-up could double-fire: detection ran on every file-watch reload and read the stale on-disk lastFiredAt, so a reload racing the async catch-up persist (a foreign write to the tasks file) re-detected and re-fired the same overdue slot. Track ids whose catch-up was DELIVERED but not yet persisted (`deliveredCatchUp`) and skip re-detecting them until the write lands; a merely-buffered-then-dropped catch-up isn't tracked, so it still re-detects from disk (recovery preserved). - [Critical] "Run now" hung the full 30s switch timeout when the bound session was ALREADY the current, loaded one (no dep change → the consuming effect never re-ran). Fire the enqueue directly after loadSidebarSession resolves as well as from the effect; whoever runs first nulls the latch, so it runs once. - [Critical] POST /run recorded a run with no enabled/disabledByArchive guard, unlike PATCH — a direct API caller could write a phantom "ran" record onto a paused/archived task. Return 409 task_disabled for a disabled task. - [Suggestion] Anchor re-seat on cron edit compared the raw string, so a cosmetic change (`0 9 * * *` → `00 9 * * *`) dropped a pending catch-up. Compare the canonical (parsed) schedule instead. (The setTimeout-overflow and keepalive-floor reports were already fixed in 2a12cba.) New tests for the first three + the cosmetic-cron case are mutation-verified; full core scheduler + route suites green. * fix(scheduled-tasks): block disabled-task run in UI; record manual run at admission Two [Critical] review follow-ups (#6389): - A disabled task could still EXECUTE from the Web Shell: the Run button was only gated on `runningTaskId`, so clicking it enqueued the prompt and the server's `/run` `task_disabled` guard merely refused the later history write — a real, unrecorded run. Gate `handleRunNow` and disable the button on `!task.enabled` too, so a disabled task's prompt is never enqueued. - Manual run recorded only after the whole turn: the bound-run latch resolved via sendPrompt, which completes through waitForAcceptedPromptCompletion, so a long/permission-blocked run or a closed tab could execute without ever being recorded. Add an `onAdmitted` callback to sendPrompt (fired when the daemon accepts the prompt, before the turn) and resolve the manual-run latch at admission instead — cancellation before admission still rejects. New dialog test (disabled task → no enqueue) mutation-verified; webui/web-shell typecheck + existing session-action tests green. * fix(scheduled-tasks): guard tick double-fire, cap rehydration, harden lifecycle writes Review follow-ups (#6389): - Extend the fire-persist re-detection guard to ON-TIME tick fires, not just catch-ups (renamed deliveredCatchUp → firePersistPending): a bound task fired by the tick advances lastFiredAt asynchronously, so a reload racing that write (bound detection runs every reload) could re-detect the slot and double-fire. The tick persist now adds its ids to the guard and clears them when the write lands, symmetric to the catch-up persist. - Bound boot-rehydration concurrency (batches of 4): each loadSession forks a child, so loading up to 50 at once spiked the host and risked spawn failures that strand tasks. The keepalive revive path was already sequential. - Archive disable failure is now logged (was fully swallowed) so a broken archive→pause coupling — where the keepalive would revive the just-archived session — is diagnosable. - Unarchive re-enable failure is surfaced in the result `errors` and logged, and enableTasksForSessions also runs for already-active sessions — so a task left stranded ({enabled:false, disabledByArchive:true}) by a prior failed enable is recoverable by re-unarchiving, instead of being permanently stuck. - Create rollback now removes the persisted session (close + removeSession), so the loser of a concurrent create at the cap boundary (passes the pre-check, loses the authoritative write) doesn't leave an orphan named session. New tests (tick-fire guard, bounded rehydration, already-active recovery) mutation-verified; full core scheduler + serve suites green. * fix(scheduled-tasks): one-shot run/edit correctness; tick persist non-regression Review follow-ups (#6389, ci-bot): - [Critical] Manual /run on a ONE-SHOT task now removes it from the store. Its slot is still in the future, so stamping lastFiredAt=now didn't stop the scheduler firing it again at its original time — a double run. A one-shot's manual run IS its single fire, so the task is spent. - [Critical] PATCH recurring:false now re-seats the one-shot's createdAt anchor. The old (long-past) anchor made the scheduler read it as a MISSED one-shot and fire + permanently delete it. Re-seating createdAt points its next fire at the upcoming occurrence. Also covers a cron edit on an existing one-shot. - [Suggestion] The tick persist no longer regresses lastFiredAt: it skips the write when the on-disk stamp is already >= the tick slot (a concurrent manual /run or catch-up may have stamped newer), mirroring the catch-up persist guard. - [Suggestion] Added the missing create-rollback test: a post-spawn commit failure closes AND removes the minted session (no orphan). New tests (one-shot run removal, recurring→one-shot re-seat, rollback teardown) mutation-verified; core scheduler + route suites green. * fix(scheduled-tasks): ref-count fire guard, real rehydration cap, authoritative run check Four [Critical] review follow-ups (#6389): - Ref-count firePersistPending (was a boolean Set): the same task can have two lastFiredAt persists in flight (fired again before the first write landed); clearing on the first settle dropped the guard while the second was still pending, re-opening the double-fire window. The count holds it until the last persist settles. - Rehydration concurrency is now enforced on the REAL loads: loadSession isn't abortable, so a timed-out load kept forking in the background while the next batch started. A bounded worker pool holds each slot until the underlying load actually settles, so in-flight child spawns never exceed the cap. - Unarchive recovery reports failures for the full resume set: it enables both unarchived AND already-active sessions but only logged/returned errors for unarchived, so a failed already-active recovery surfaced errors:[] and left a task stranded. Deduped one list used for the call, log, and errors. - Manual "run now" re-checks server-authoritative state before enqueuing: the dialog snapshot can be stale (another tab/API disabled/deleted the task), so it would execute the prompt and only the /run record would 409. It now refreshes, bails if gone/disabled, and enqueues the FRESH prompt/session. New tests (ref-count, slot-held-past-timeout, stale-disabled re-check) mutation-verified; core scheduler + serve + dialog suites green. * fix(scheduled-tasks): catch-up non-regression, disabled-edit re-seat, run/timer/keepalive hardening Review follow-ups (#6389): - [Critical] Catch-up persist no longer regresses lastFiredAt: use `>=` like the tick persist, so a newer stamp (a cross-process manual /run) landing while the catch-up write is in flight isn't overwritten back to the older minute. - [Critical] The PATCH anchor re-seat now runs for schedule edits even while the task is disabled — editing a disabled one-shot's cron then re-enabling it (two separate requests) no longer leaves a stale anchor that fires + deletes it. - [High] Manual "run now" of a bound ONE-SHOT consumes it server-side (/run, which deletes) BEFORE enqueuing, so a record failure leaves a recoverable "recorded but never ran" instead of a silent double execution at its slot. - [Medium] The dialog reload timer backs off a stuck past-due nextRunAt (fast reloads to catch a just-fired advance, then a slow lane) instead of spinning a 1 Hz GET loop. - [Medium] The manual-run latch bounds the admission phase with a timeout, so a send that wedges before admission degrades to a visible "run failed" instead of freezing the run controls. - [Suggestion] Keepalive: an in-flight guard skips a tick while the previous pass runs (no duplicate concurrent loadSession spawns), and per-session exponential backoff stops retrying a permanently-gone session every interval. New tests mutation-verified. Two deeper items (a task session winning the durable lock and firing unbound tasks; tearing down a consumed one-shot's session) are left open as tracked follow-ups — both need new daemon↔child infrastructure. * fix(scheduled-tasks): one-shot anchor on unarchive, memoize next-fire, sanitize + log Review follow-ups (#6389): - [Critical] enableTasksForSessions now re-seats a ONE-SHOT's createdAt anchor (not just recurring's lastFiredAt) on unarchive — otherwise unarchiving a task that was converted to recurring:false while disabled fires it as a missed one-shot and permanently deletes it. - [Critical] Log the DELETE-path removeTasksForSessions failure (was fully swallowed) like the archive/unarchive paths — the session is already gone, so a silent write failure leaves the still-enabled bound task a permanent ghost. - [Medium] Memoize nextDurableFireMs (deterministic per id/cron/recurring/anchor) — a sparse cron costs hundreds of ms per scan and the route recomputed it per task on every request, stalling the event loop for 50 yearly tasks. - [Nit] The consumed one-shot /run response now nulls nextRunAt (it was advertising a future fire on an entity the next GET omits). - [Suggestion] scheduledTaskSessionName strips terminal control sequences (the bridge title guard rejects them → silently drops the rename) and truncates on a code-point boundary (no lone surrogate broadcast as U+FFFD). - [Critical/doc] Document that firePersistPending is instance-scoped — the narrow cross-instance restart window is an accepted edge. - Added the missing test for editing an enabled one-shot's cron. New tests mutation-adjacent; suites green. Two deeper items (session deleted outside the daemon orphaning a bound task; surfacing bound tasks in cron_list) are left open as tracked follow-ups. * fix(scheduled-tasks): re-seat one-shot anchor on re-enable; guard duplicate revive Two [Critical] review follow-ups (#6389): - Re-enabling a one-shot now re-seats its createdAt anchor (added justReEnabled to the one-shot branch). A one-shot disabled past its slot then re-enabled was otherwise read as a missed one-shot on the next reload — fired immediately and permanently deleted. Updated the prior "leaves anchor untouched" test to the safe behavior (fires at next occurrence). - Keepalive revive no longer spawns a duplicate child: loadSession isn't abortable, so a timed-out revive keeps running; a later tick (past its backoff) would start a SECOND load for the same session. An in-flight `reviving` set (cleared on the load's TRUE settlement, not the timeout) blocks that — without holding the sequential tick, so other sessions' heartbeats aren't delayed. Added a configurable reviveTimeoutMs for the test. Both mutation-verified. (The one-shot /run session teardown raised again is the same item as the open deferral — a synchronous close there would break the run, which executes after /run; it's tracked for the keepalive orphan-sweep.) * fix(scheduled-tasks): strip bidi override/isolate chars from session name The bridge's title guard (hasControlCharacter) only rejects C0/DEL, so Unicode bidi override/embedding/isolate controls (U+202A–202E, U+2066–2069) slip past it and can visually reorder a scheduled-task session name in the session list — a Trojan-Source-style attack (CVE-2021-42574). Strip them alongside the existing terminal-control-sequence pass, matching core's stripDisplayControlChars canonical set. Adds a test built from code points so the test file itself carries no reordering controls. * fix(scheduled-tasks): close review findings — rehydrate deadlock, manual-run recording, shared helpers, tests Addresses the review findings on the per-task-session work: - keepalive rehydrate no longer awaits a non-abortable loadSession after its timeout. A genuinely hung load would pin its worker and, with enough hangs, wedge the whole boot sweep (Promise.all never settles) so later task sessions never rehydrated. The worker now records the timeout as failed and pulls the next queued session; the background load is left to settle. Rewrote the test that pinned the old "hold the slot" behavior into a no-wedge regression guard. - web-shell manual run drops its pre-admission timeout. sendPrompt isn't abortable, so rejecting on the timer while the send was still in flight let a LATE admission execute an UNRECORDED run the user could retry into a duplicate. The run is now tied to admission (accepted prompts are always recorded); the "session never becomes active" phase stays bounded by the switch timeout in runTaskManually. - extract collectBoundSessionIds() shared by the heartbeat + rehydrate passes (was duplicated) and isBoundTask() in the lifecycle module (was the lone `sessionId !== undefined` check vs. the strict one used everywhere else). - spell the nextDurableFireMs cache-key separator as `\x00` rather than a literal NUL byte, so cronScheduler.ts no longer reads as binary to ripgrep. - add App.test coverage for the manual-run orchestration (admission-resolve, cancel/error reject, immediate fire, supersede, switch timeout) and a keepalive test that a disabled task gets no heartbeat and no revive. * fix(web-shell): "create via chat" opens a fresh session in scheduled tasks The scheduled-tasks "Create via chat" button switched to the chat view but stayed on the CURRENT session, piling the task-creation conversation onto whatever the user was already doing. It now starts a new session first (createNewSession) and jumps to it before priming the composer, so task creation gets its own chat. Covered by a new App.test case asserting clearSession() is called. * fix(scheduled-tasks): address follow-up review findings - keepalive rehydrate: guard the onError callback with try/catch. If it threw (e.g. stderr EPIPE during log rotation) the rejection escaped loadOne, failed its worker, and short-circuited Promise.all — stranding every other queued session. - cronScheduler catch-up: use the strict `typeof sessionId === 'string' && length > 0` bound-check instead of `!== undefined`, matching every other "is bound?" site. - server rehydration: log the outer defense-in-depth catch instead of swallowing it, so an unexpected throw isn't a silent "tasks never fire". - session-name sanitizer: also strip the standalone Bidi_Control marks U+061C / U+200E / U+200F, not just the override/isolate ranges. - scheduled-tasks dialog: when a consumed one-shot then fails to deliver, show a specific "deleted but never ran — recreate it" error instead of the generic "run failed" that hid the deletion. Kept the deliberate consume-first ordering. * fix(web-shell): don't prime the composer when "create via chat" can't start a new session onCreateViaChat's deferred composer-priming ran unconditionally: if createNewSession() failed, the task-starter text was dropped into the CURRENT session (only onSessionIdChange was gated on success). Gate all post-create side effects on `created`, matching handleMissingSessionNewSession. Adds an App.test failure-path case (new session fails → composer not primed). --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
9ee8546a60
|
fix(shell): avoid Unix pager default on Windows (#6390)
* fix(shell): avoid Unix pager default on Windows * fix(shell): clear inherited pager env on Windows * docs(shell): clarify platform-specific pager default * fix(shell): normalize pager env handling * fix(shell): preserve git pager fallback behavior * test(shell): stabilize pager env coverage |
||
|
|
067cfbba62
|
docs: consolidate design docs and plans under docs/ (#6417)
Design docs and implementation plans were scattered across .qwen/design, .qwen/plans, and docs/superpowers. The .qwen/ locations are git-ignored, so docs written there never got tracked, while docs/design already held the richer, version-controlled set. Consolidate everything under docs/design and docs/plans, relocate two stray root docs into docs/design, and repoint the references left dangling by the move (moved-doc cross-links and a few source comments). Also update AGENTS.md and the feat-dev skill so the documented workflow writes new design docs and plans to the tracked docs/ locations. Co-authored-by: DragonnZhang <dragonzhang1024@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
4c884e47bd
|
fix(core): prevent KV-cache invalidation on tool_search by reordering reminderParts (#6420)
Reorder reminderParts in getInitialChatHistory() so stable parts (MCP instructions, skills snapshot, startup context) come first and volatile deferred-tools reminder is last — prefix-caching servers retain the KV-cache for the shared prefix, only the tail recomputes. Co-authored-by: Aleks-0 <aleks-0@users.noreply.github.com> |
||
|
|
46bbe76835
|
fix(core): align monitor limit parameter schemas (#6413)
* fix(core): align monitor limit parameter schemas Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com> * test(core): cover monitor fractional limit validation Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com> --------- Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com> |
||
|
|
ce00e932ae
|
fix(core): allow rewind after compressed history (#6358)
* fix(core): allow rewind after compressed history * fix(core): separate compressed prefix rewind handling * fix(core): handle rewind after restored startup context * test(core): cover compressed rewind sentinel mismatch * fix(cli): align rewind mapping after compression |
||
|
|
132801b739
|
feat(core): add maxSubAgents setting to limit parallel sub-agent count (#6354)
* feat(core): add maxSubAgents setting to limit parallel sub-agent count Adds a `maxSubAgents` configuration option that limits the number of sub-agents running in parallel. Excess agents are queued without timeout countdown until a slot becomes available. Closes #5176 * fix(core): apply sub-agent concurrency cap to foreground runs * fix(core): narrow sub-agent concurrency scope * fix(core): clarify invalidated slot reservations * fix(core): correct sub-agent slot accounting * fix(core): drain silent cancellation waiters * test(core): cover background slot reservation paths |
||
|
|
ff317d61cf
|
perf(core): Add session start profiler (#6349)
* perf(core): Add session start profiler Add an opt-in internal profiler for GeminiClient.startChat so session initialization can be broken down by bounded stages before choosing the next #6312 optimization. The profiler writes best-effort JSONL records only when QWEN_CODE_PROFILE_SESSION_START=1 and avoids sensitive values such as prompts, paths, session IDs, hook output, model responses, and tool names. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): Keep session profiler finish best-effort Wrap session-start profiler finish metadata collection in the same best-effort boundary as record writes, and cover repeat finish plus sync failure handling in tests. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(core): Cover profiler review suggestions Deduplicate startChat profile finalization attributes and add coverage for repeated stage duration accumulation. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(core): Cover profiler failure paths Strengthen session-start profiler tests for first-failure tracking and startChat sync-stage failure finalization. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): Harden session profiler output Restrict session-start profiler JSONL output permissions and add review-requested tests for optional fields and first-stage warm failures. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(core): Reuse session profiler env constant Use the profiler env constant in the JSONL test so the test cannot drift from the runtime gate. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(core): Cover profiler timing edge cases Add coverage for fractional session profiler rounding and the absence of session context application timing when SessionStart returns no additional context. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(core): Cover non-zero profiler counts Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6349) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6349) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6349) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6349) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6349) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6349) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6349) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(core): cover disabled session profile env values Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
06cd7ce13f
|
feat(cli): add --project and --global flags to /model for per-project model persistence (#6060)
* feat(cli): add --project and --global flags to /model for per-project model persistence Add scope control to the /model command so users can persist model selections to either project-level or user-level settings independently. - /model --project: persist to workspace .qwen/settings.json - /model --global: persist to user ~/.qwen/settings.json - /model (no flag): unchanged behavior (backward compatible) - Model dialog title shows scope: 'Select Model (this project)' / 'Select Model (global)' - Completion and argumentHint updated with new flags - Full i18n support for zh/en Closes #6052 Signed-off-by: Alex <alex.tech.lab@outlook.com> * fix(cli): add missing zh-TW translations for /model scope flags Signed-off-by: Alex <alex.tech.lab@outlook.com> * fix(cli): address PR review — scope flags, subcommand persistScope, titles, tests - parseScopeFlags: use (?:^|\s) instead of \b for --flag matching (\b fails because - is not a word character) - Completion: strip all flags to isolate model prefix, supports any order - Subcommand dialogs (fast/voice/vision) now propagate persistScope - slashCommandProcessor forwards persistScope for all subcommand cases - ModelDialog title combines subcommand mode + scope label e.g. 'Select Fast Model (this project)' - Subcommand confirmations show scope suffix (project/global) - Extract persistScopeSpread() helper to reduce duplication - Add 9 tests covering scope flags, dialog returns, confirmations - Add i18n keys for scope suffix labels in zh/en/zh-TW Signed-off-by: Alex <alex.tech.lab@outlook.com> * fix(cli): use Partial<Config> & {[key:string]:unknown} to fix index signature TS error Replace Record<string,unknown> with Partial<Config> & {[key:string]:unknown} to satisfy TS4111 index signature access rule in the CI build. Signed-off-by: Alex <alex.tech.lab@outlook.com> * fix(cli): add scope suffix to ModelDialog history items Address review comment: historyManager.addItem for voice/fast/vision/main model selections now shows scope indicator like ' (this project)' or ' (global)', consistent with CLI direct-set confirmations. Affected: handleModelSwitchSuccess (main), handleSelect (voice/fast/vision) Signed-off-by: Alex <alex.tech.lab@outlook.com> * fix(cli): wrap scopeSuffix in t() and unify wording with ModelDialog - scopeSuffix in modelCommand.ts now uses t(' (this project)') / t(' (global)') instead of hardcoded English strings, matching ModelDialog.tsx wording - Main model confirmation uses shared scopeSuffix instead of separate i18n keys, eliminating 'Model: {{model}} (project)' duplication - Remove unused i18n keys from en/zh/zh-TW locales - Update tests to expect '(this project)' wording Signed-off-by: Alex <alex.tech.lab@outlook.com> * fix(cli): address code review feedback — scope validation, i18n, tests - Reject inline prompt + scope flag combination with clear error (#1) - Add mutual exclusivity check for --project and --global (#5) - Verify setValue scope parameter in tests + add --global test (#2) - Extract scopeSuffix to shared variable, remove duplication (#3) - Remove dead i18n keys 'Select Model (this project)' / '(global)' (#4) - Fix scopeSuffix placement on model line not API key line (#8) - Add fr.js / ja.js translations for scope keys (#10) - Remove unused export ModelDialogPersistScope (#6) - Wrap non-interactive help text in t() with new flags (#7) - Fix argumentHint grouping to show mode vs scope flags (#11) Signed-off-by: Alex <alex.tech.lab@outlook.com> * fix(cli): reject --project when workspace is untrusted Reject --project scope flag before direct persistence or opening ModelDialog when settings.isTrusted is false. Workspace settings are ignored on merge in that state, so the save would silently not take effect. Also mirrors the guard in ModelDialog.tsx resolvePersistScope() to fall back to user scope when the dialog is opened with --project on an untrusted folder. Default mock settings now includes isTrusted: true. Signed-off-by: Alex <alex.tech.lab@outlook.com> --------- Signed-off-by: Alex <alex.tech.lab@outlook.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
1ee9780223
|
fix(core): Gate large PDF text extraction (#6409)
* fix(core): Gate large PDF text extraction Prevent text-only PDF fallback from injecting full large-document extraction results into the prompt. Large attachment reads now become short references, direct no-pages reads return a short file-too-large error, and page-range extraction is token guarded. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(core): Stabilize large PDF reference test Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): Address PDF budget review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(core): Stabilize PDF read-file test Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): Allow page-range reads for huge PDFs Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): Clarify PDF text truncation contract Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): Address PDF review follow-ups Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(core): Cover multi-page PDF guidance Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): Harden paged PDF extraction guards Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): Restore authoritative PDF page-count gate Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): Keep large PDF references independent of pdftotext Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): Narrow dense PDF retry guidance Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
80340fb73f
|
fix(review): remove qwen-code-specific core-infra gate from bundled /review (#6412)
The bundled /review skill is a general command that runs against arbitrary
repositories (and cross-repo PRs), but a previous change baked qwen-code's own
"core infrastructure is maintainer-only" governance into the shipped prompt:
hardcoded packages/core and packages/*/src/{auth,providers,models,config,tools,services}
paths, a 500+ line hard block, and an authorAssociation-based maintainer check.
Those path names are generic — src/auth, src/config, src/tools, src/services are
common across monorepos — so an external contributor's large PR to an unrelated
repo would be hard-blocked as "must be maintainer-initiated" under a policy that
repo never adopted.
Remove the gate and its escalate-flag plumbing (Steps 1, 6, and 7) from the
bundled skill, along with the matching DESIGN.md rationale and the user-doc
section. qwen-code's maintainer-only policy stays documented in AGENTS.md for
this repo. The Issue Fidelity / root-cause ownership agent (Agent 0) is a
universal review principle and is left unchanged.
Co-authored-by: dragon <dragon@U-2Q53JQG9-0233.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|