Commit graph

6511 commits

Author SHA1 Message Date
Rayan Salhab
b6988325e9
fix(cli): render full resume preview history (#5565)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Waiting to run
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
Co-authored-by: cyphercodes <cyphercodes@users.noreply.github.com>
2026-06-22 02:18:23 +08:00
易良
e88356c6b7
ci(release): auto-publish VSCode companion after releases (#5572) 2026-06-22 02:08:44 +08:00
qwen-code-ci-bot
8f8ed0d7c1
chore(release): v0.18.5 [skip ci]
* chore(release): v0.18.5

* docs(changelog): sync for v0.18.5

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-21 21:13:19 +08:00
Shaojin Wen
e5c01aa353
feat(mcp): support MCP resources and reliably surface prompts (#5544)
* feat(mcp): support MCP resources and reliably surface prompts

Prompts were silently hidden for MCP servers that implement `prompts/list`
but under-declare the `prompts` capability in their initialize response.
Drop the capability gate in `listMcpPrompts` (and apply the same leniency to
resources): always attempt the list call and swallow `Method not found`, so
those prompts now appear as `/` slash commands like in other clients.

Add first-class MCP resource support:

- core: `listMcpResources` / `discoverResources`, a `DiscoveredMCPResource`
  type, a `ResourceRegistry`, and `Config.getResourceRegistry()`. Discovery
  is wired into the standalone `McpClient.discover()` path and the
  connection-pool path (snapshot -> `SessionMcpView.applyResources` -> the
  session's registry, with a `resourcesChanged` event on reconnect). A
  resource-only server now counts as a successful discovery.
- cli: the `/mcp` dialog shows per-server Resources (and Prompts) counts.
- cli: `@server:uri` reads an MCP resource and injects its contents into the
  message (text inline, blobs as inlineData); `@server:` autocompletes the
  server's resource URIs. The `server` prefix must match a configured MCP
  server, so existing `@path` file references are unaffected.

Docs updated; unit tests added across core and cli.

* fix(mcp): reload commands on discovery + harden resource ref parsing

Address review feedback and complete the prompt UX:

- Slash commands now rebuild when an MCP server finishes connecting.
  Discovery is progressive (runs after the UI is interactive), so prompts
  a server exposes via prompts/list were registered too late to appear in
  the `/` menu — the `/mcp` dialog showed the count while the slash menu
  stayed empty. A debounced MCP-status listener now reloads the command
  tree on connect. Verified end-to-end in a real TUI against a mock server
  that under-declares the prompts capability: `/greet` now lists.
- `isMethodNotFound` keys off the JSON-RPC `-32601` code rather than the
  server-supplied message text (which may be localized or worded
  differently); applied to both `listMcpPrompts` and `listMcpResources`.
- `useAtCompletion` uses `Object.hasOwn` so `@__proto__:` / `@constructor:`
  and other inherited keys are not mistaken for configured servers.

* fix(mcp): lenient resource read to match discovery + review polish

- `McpClient.readResource` no longer prechecks `getServerCapabilities()
  ?.resources`. This PR is the first caller to make the read path
  reachable, and the strict precheck meant a server that answers
  `resources/read` but under-declares the `resources` capability — exactly
  the servers the lenient `listMcpResources` discovery targets — would get
  its resources discovered, listed in `/mcp`, and autocompleted, yet fail
  every `@server:uri` with a misleading "does not support resources". The
  read now matches discovery; a server that truly lacks resources answers
  `-32601`, surfaced as the existing error card. Added a regression test.
- `@server:uri` success cards now report what was injected ("Injected N
  chars" / "N attachments") or "(no readable content)" when a read yields
  no text/blob parts, so a partially-empty multi-ref read isn't hidden.
- `useAtCompletion` resource filter reduced to a single `includes` match
  (`startsWith` was subsumed; empty partial matches all via `includes('')`);
  the overstated "prefers prefix" comment is corrected.
- Tests: mixed `@file` + `@server:uri` injection (both parts + both cards),
  empty-content card, and the pool restart fan-out now asserts
  `applyResources` alongside `applyTools`.

* perf+security(mcp): parallelize discovery/reads, cap & frame resources

Review round 3 fold-ins:

- Discovery now runs `listMcpPrompts` / `listMcpResources` / `discoverTools`
  concurrently in `discoverAndReturn` (independent reads; the SDK client
  multiplexes by JSON-RPC id), saving per-server round-trips at startup.
- `@server:uri` resource reads run in parallel (`Promise.allSettled`) instead
  of serially, matching how the file path batches via `readManyFiles`; order
  is preserved so cards/labels line up with refs.
- Resource injection is now capped and framed: text is bounded by
  `MAX_MCP_RESOURCE_TEXT_CHARS` (100k) and oversized blobs are skipped, so a
  misbehaving/hostile server can't overflow the context window or OOM; the
  content is fenced with `--- Content from MCP resource <label> ---` /
  `--- End ---` delimiters so the model can separate untrusted server output
  from the user's prompt. The success card reports truncation.
- File-read error path now merges `resourceLabels` into `filesRead` /
  recording, so a resource read that succeeded before a file read failed is
  not dropped from the audit trail.
- `isMethodNotFound` (JSON-RPC -32601) now also covers `discoverTools` and
  `invokeMcpPrompt`, replacing the remaining message-substring checks.
- `PoolEntry.markActive` `initialResources` is now required (no `= []`
  default), removing a footgun where an omitted arg would wipe a server's
  resources via `applyResources([])`.
- `useAtCompletion` resource suggestions rank prefix matches above mid-string
  matches.
- `'Resources:'` added to the remaining 6 locales (ca, de, fr, ja, pt, ru)
  for parity with `'Prompts:'`.

Tests: attribution framing, text truncation, completion prefix ranking.

* test(mcp): cover resource registration in discover() and resource-only discovery

Closes the two coverage gaps flagged in review: assert discover() registers
discovered resources into the Config ResourceRegistry, and that a resource-only
server (no tools/prompts) is a successful discovery rather than throwing.

* fix(mcp): idempotent resource re-discovery + cumulative blob cap

Review round 4 (all Suggestions):
- discover() now clears a server's resources (removeResourcesByServer) before
  re-registering, so reconnect / incremental re-discovery is idempotent and a
  resource the server dropped doesn't linger in the registry (matches the
  pool path's SessionMcpView.applyResources).
- @server:uri injection now caps CUMULATIVE blob size per resource, not just
  each blob, so many sub-limit blobs in one response can't inject unbounded
  data. Added a test for the oversized-blob skip + card.
- Documented that file/resource content parts are grouped by type (model
  correlates by delimiter labels, not position).

* test(mcp): cover the MCP-status command reload (prompts surfacing in /)

Adds the missing coverage for the discovery-driven reload: a CONNECTED status
fires the listener and rebuilds the command tree (so progressively-discovered
MCP prompts appear as / commands), and a non-CONNECTED status does not.

* fix(mcp): don't wipe resources when resources/list transiently fails

- discover() only clears + replaces a server's resources when listMcpResources
  returns a non-empty set. Because that helper swallows all errors (including
  transient network failures) and returns [], an unconditional clear-then-
  register would silently purge a server's resources on a transient list
  failure while tools/prompts succeed. Guarding on length>0 keeps the existing
  set on failure; a real partial drop still re-registers the fresh set.
- Resource success card now shows '(truncated)' for capped/skipped blobs too,
  not just text. Added a cumulative-blob-cap test (two sub-limit blobs whose
  sum exceeds the cap).

* fix(mcp): guard pool applyResources against transient-failure wipe too

The non-pool discover() guard (resources.length > 0) left the pool path
exposed: on a restart, doRestart -> discoverAndReturn swallows a transient
resources/list failure to [], and applyResources([]) then wiped the session's
resources. applyResources is now a no-op on an empty snapshot (mirrors the
discover() guard; applyTools/applyPrompts keep their pre-existing clear-on-empty
behavior, out of scope). Added tests: applyResources([]) does not clear, and
discover() with an empty resource list does not call removeResourcesByServer.

* fix(mcp): preserve pool resource snapshot on transient restart failure

The applyResources([]) no-op only protected already-attached sessions; doRestart
still overwrote the pool entry's resourcesSnapshot with [] when the restart's
resources/list transiently failed, so any session attaching AFTER the restart
got zero resources. doRestart now only updates resourcesSnapshot when the
re-read is non-empty, preserving it for new and existing subscribers alike.
Tests: applyResources([]) preserves a pre-populated set; a restart whose
resources/list comes back empty still serves the prior resource to a new
session.

* fix(mcp): trust-gate resource completion, colon server names, narrower method-not-found

- useAtCompletion no longer surfaces resource URIs in an untrusted folder
  (the read path is already blocked there); avoids leaking resource existence.
- parseMcpResourceRef / getMcpResourceSuggestions match the LONGEST configured
  server name as a '<name>:' prefix instead of splitting on the first colon,
  so a server whose name contains ':' (a valid settings.json key) resolves.
- isMethodNotFound's message fallback is back to the case-sensitive exact
  'Method not found' substring (the -32601 code is the primary check), not a
  broad /method not found/i that would swallow unrelated errors.
Tests: @my:server:uri resolution, untrusted-folder completion.

* refactor(mcp): extract shared longest-prefix server matcher + doc/test fixes

- Extract matchMcpServerPrefix (new mcpResourceRef.ts) and use it from both
  parseMcpResourceRef (injection) and getMcpResourceSuggestions (completion),
  removing the duplicated longest-prefix logic and its drift risk.
- Update parseMcpResourceRef JSDoc to describe longest-prefix matching.
- Tests: shared-helper unit tests; the @my:server colon test now configures
  both 'my' and 'my:server' to exercise disambiguation; a colon completion
  test; isMethodNotFound message-casing tests (exact 'Method not found'
  swallowed, 'method not found handler' not swallowed).
2026-06-21 19:04:52 +08:00
interconnectedMe
b4dea10a62
Use VS Code theme tokens for companion scrollbar (#5488)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Waiting to run
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
2026-06-21 16:24:18 +08:00
tt-a1i
f1a5b708fe
fix(extensions): accept uppercase marketplace source schemes (#5435) 2026-06-21 14:33:27 +08:00
tt-a1i
cad6c1bc09
fix(cli): validate ACP file read windows (#5482) 2026-06-21 13:59:00 +08:00
易良
1060741886
docs(triage): add reuse-before-new-code check (#5547) 2026-06-21 13:07:46 +08:00
tt-a1i
0880e34461
fix(desktop): reject fractional transfer sizes (#5527) 2026-06-21 13:05:52 +08:00
tt-a1i
71fd0294e9
fix(desktop): consolidate path boundary checks (#5545) 2026-06-21 13:05:36 +08:00
易良
aa625765c7
ci(release): trigger CI from release branch pushes (#5543)
* ci(release): trigger CI from release branch pushes

* ci(release): use PAT for publish checkout
2026-06-21 12:59:43 +08:00
tt-a1i
948f78279a
fix(desktop): handle Windows file mentions (#5523) 2026-06-21 12:49:43 +08:00
tt-a1i
0eb4395c53
fix(desktop): separate transform data output lines (#5525) 2026-06-21 12:42:57 +08:00
tt-a1i
7a4f080230
fix(cli): allow double dots in update archives (#5521) 2026-06-21 12:35:12 +08:00
Shaojin Wen
665674c5d8
fix(cli): allow dotfile paths in Web Shell sendFile (#5541)
* fix(cli): allow dotfile paths in Web Shell sendFile

res.sendFile() uses the send library which defaults to dotfiles:'ignore',
returning 404 for any path containing a segment starting with '.'. Users
who installed qwen via nvm/volta/asdf have the package under
~/.nvm/.../web-shell/index.html, causing 'Failed to load Web Shell' in
the browser. Pass dotfiles:'allow' to fix this.

* test(cli): add dotfile-path regression test for Web Shell sendFile

Per review suggestion on PR #5541: existing tests use temp dirs without
dot-prefixed segments, so the send library's dotfiles:'ignore' default
was never exercised. Add a test that nests webShellDir inside a
.fake-nvm parent to guard against future regression.
2026-06-21 12:23:56 +08:00
tt-a1i
19573f78ca
test(desktop): enable feedback flag in permission tests (#5533) 2026-06-21 12:21:30 +08:00
tt-a1i
aeb0e5810a
fix(desktop): keep sibling paths absolute (#5517) 2026-06-21 12:21:07 +08:00
tt-a1i
b117c56b46
test(desktop): align interceptor packaging contract (#5531) 2026-06-21 12:16:15 +08:00
易良
d0ba8534fa
feat(ci): on-demand tmux real-user testing for PRs (#5203)
* feat(ci): add on-demand tmux real-user testing to triage workflow

Add a tmux-testing stage to qwen-triage.yml: a write-permission user runs
`@qwen-code /tmux` on a PR (or dispatches with tmux_pr=N) to launch the
changed app in a real tmux TUI session and exercise the affected flow,
catching interactive regressions that diff-based review can't.

- Gated on the PR AUTHOR having write permission (whose code runs), reusing
  the authorize job; no separate fork check.
- Executes untrusted PR code with minimal blast radius: contents:read, no
  GitHub token in the agent env, persist-credentials:false.
- Proxy bypass for the model call only.
- A separate publish-tmux job (clean runner, write PAT, no PR-code checkout)
  posts the verdict back to the PR — keeping the write credential isolated
  from the code execution.

* fix(ci): address tmux-testing review findings

- Gate the workflow_dispatch tmux_pr path on the PR author's write
  permission via the authorize job, so dispatching never runs an
  unauthorized contributor's code on the self-hosted runner.
- Make workflow_dispatch triage vs tmux mutually exclusive (tmux_pr
  set skips the triage job), matching the documented input contract.
- Pin GITHUB_TOKEN/GH_TOKEN empty in the untrusted-code step so no
  inherited repo-scoped credential reaches the agent.
- Serialize concurrent tmux runs per PR; skip PRs whose merge ref is
  unavailable (CONFLICTING); clean the runner workspace on exit.
- publish-tmux now also reports infrastructure failures instead of
  staying silent, and the comment fence widens past any backtick run
  in untrusted output so it cannot break out and render markdown.
- Correct the proxy-bypass comment to describe actual behavior.

* refactor(ci): collapse tmux gating into one decision output

- Fold should_test + has_tui into a single tri-state `decision`
  (skip | na | run); the five repeated step guards become
  `decision == 'run'`, and the standalone `Mark not applicable`
  step is gone (the n/a verdict is set inline in the resolve step).
- Resolve PR state + file list in one `gh pr view` call instead of
  two.

No behavior change: skip stays silent, na posts n/a, run drives the app.

* fix(ci): wait out UNKNOWN mergeability before deciding

Right after a /tmux comment GitHub may not have computed PR
mergeability yet (mergeable=UNKNOWN), and refs/pull/N/merge is only
current once it has. Retry the resolve up to 5x (3s apart) until it
settles, so a freshly-pushed PR isn't checked out from a stale or
missing merge ref.

* fix(ci): harden tmux publish comment and mergeability/exit-code handling

- publish-tmux: render artifacts in HTML-escaped <pre> instead of a
  backtick fence; removes the grep crash on backtick-free content under
  set -euo pipefail and closes the </details>/fence-breakout injection
- tmux-testing: skip when mergeability stays UNKNOWN after retries
- classify exit 137/139 (OOM/segfault) as infra-error, not test fail
- upload-artifact: continue-on-error so a pre-write crash isn't masked

* ci: harden tmux testing workflow

* fix(ci): address tmux testing review follow-ups

* fix(ci): report tmux prepare failures

* fix(ci): use latest qwen CLI for tmux testing
2026-06-21 11:56:29 +08:00
qwen-code-ci-bot
c5fb75b5c2
chore(release): v0.18.4 [skip ci]
* chore(release): v0.18.4

* docs(changelog): sync for v0.18.4

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-21 03:36:38 +00:00
tt-a1i
7c06e62348
fix(vscode): keep UNC paths absolute (#5542) 2026-06-21 11:05:14 +08:00
Shaojin Wen
6b2f800abd
perf(core): read current git branch directly from .git instead of spawning git (#5432)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Waiting to run
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
* perf(core): read current git branch directly from .git instead of spawning git

Reading the current branch for the CLI status line previously shelled out to `git rev-parse --abbrev-ref HEAD` on the render path. Replace it with a direct read of .git/HEAD.

- Add shared module packages/core/src/utils/gitDirect.ts: resolveBranchName, readGitHead, a reference-counted reflog watcher (watchRepoBranch), ref-name validation (isValidRefName), and a gitDir resolution cache. gitDir resolution reuses the existing gitDiff.resolveGitDir (ancestor walk + worktree gitdir pointer); only HEAD is re-read on reflog changes.
- Rewrite useGitBranchName as a thin wrapper over the shared module; the (cwd) => string | undefined hook signature is unchanged.

* fix(core): handle fs.FSWatcher 'error' and don't cache a watcher-less entry

Addresses review feedback on the reflog watcher:

- Add an 'error' handler to the reflog fs.FSWatcher. fs.FSWatcher is an EventEmitter, so an unhandled 'error' (reflog removed by `git gc`/`reflog expire`, worktree removal, inode change, or a platform watch limit) would crash the process. The watch is now torn down instead, and subscribers simply stop auto-refreshing.
- When there is no reflog yet (unborn repo), return a no-op disposer without caching a watcher-less entry, so a later caller can establish the watch once the reflog appears (e.g. after the first commit).
- Tests: FSWatcher 'error' teardown, reflog-appears-later re-watch, concurrent-caller dedup (post-await re-check), and the hook's [cwd]-change re-subscribe path.

* fix(core): harden gitDirect against out-of-repo reads and fs.watch throws

Addresses security/robustness review:

- Containment guard: resolveTrustedGitDir rejects a .git-FILE `gitdir:` pointer that escapes the repo. After realpath, the resolved gitDir must be the repo's own `<root>/.git`, or live under some `.git/worktrees/` (linked worktree) or `.git/modules/` (submodule). This stops a crafted project from making the status line read/watch an arbitrary out-of-repo path (the old `git rev-parse` path refused such repos with exit 128).
- Refuse a symlinked HEAD (readGitHead) and a symlinked reflog (watchRepoBranch) via lstat, so neither follows a link out of the repo.
- Wrap fs.watch in try/catch: a TOCTOU vanish (git gc / reflog expire / worktree removal) or a platform watch limit makes fs.watch throw synchronously; return a no-op disposer rather than rejecting (which the hook's bare `void init()` would surface as an unhandled rejection). The existing 'error' listener only covers async emitter errors.
- Hook: guard `void init()` with .catch() as belt-and-suspenders.
- Tests: containment (decoy rejected, submodule accepted), symlink HEAD/reflog refused, fs.watch sync-throw no-op, and the hook still rendering when watch setup rejects.

* fix(core): validate the git object store instead of path-shape containment

Replaces the path-segment containment guard — which qqqys showed a crafted `.git/worktrees/x` path could spoof — with git's own validity check: a trusted gitDir must have an object store. A standalone repo has `objects/` + `refs/` directly; a linked worktree / submodule gitdir instead carries a `commondir` file pointing at the main gitdir that does. Incomplete forgeries (a lone HEAD, or a path-shaped `.git/worktrees/x` containing only a HEAD) have neither and are rejected — exactly what `git rev-parse` rejects with 'not a git repository' (exit 128). Verified the criterion against real git across standalone / unborn / worktree / fake structures.

- Addresses qqqys (path-shape `.git/worktrees/fake` with only HEAD) and yiliang114 (fake `.git` dir with only HEAD+logs) — both now return undefined.
- Tests: both PoCs rejected; worktree (via commondir) and submodule (own object store) accepted; makeRepo now creates objects/ + refs/ so fixtures are real git dirs.

* fix(core): harden ref-name gate (C1/length) and stop caching non-repo misses

- isValidRefName: also reject C1 controls (0x80-0x9f) and U+2028/U+2029, and cap length at 255. With git no longer vetting the value, a hand-written HEAD could otherwise carry terminal escape bytes (CSI/OSC) or layout-desyncing line separators into the status line, and string-width undercounts C1.
- getCachedGitDir: cache only successful resolutions. A null (non-repo) result was cached permanently, so a directory that became a repo mid-session (git init / clone) never showed a branch until restart — a regression from the old git rev-parse path which always hit the filesystem.

* fix(core): isolate subscriber callbacks in the shared reflog watcher

In the shared-watcher fan-out (one fs.watch per gitDir, many subscribers), a subscriber whose onChange throws synchronously would halt iteration — later subscribers never fire — and the exception would escape to the event loop as uncaughtException, poisoning every component watching that repo. Wrap each callback in try/catch. The sole current caller (useGitBranchName) can't throw, but watchRepoBranch is exported public API.

* fix(core): close readGitHead symlink TOCTOU with O_NOFOLLOW; per-component ref rules

Addresses review:
- readGitHead: open HEAD with O_NOFOLLOW instead of lstat-then-readFile, so a symlinked HEAD is refused atomically (ELOOP) and can't be swapped in during the check->read gap. Mirrors the existing O_NOFOLLOW use in gitDiff.ts; falls back to plain O_RDONLY where the flag is absent (Windows).
- isValidRefName: also reject names where any slash-separated component starts with a dot or ends with .lock (git's check-ref-format applies per component, not just to the whole name).
- hasGitStore: run the two isDir probes in parallel.
- Reword the module doc (drop the cross-product reference) and note the residual, bounded lstat->watch TOCTOU on logs/HEAD (the watch only ever fires readGitHead, which opens HEAD with O_NOFOLLOW, and never reads logs/HEAD content).

* fix(core): clear watchers in clearGitDirCache; debug-log watcher failures; guard refresh

- clearGitDirCache now also closes the shared reflog watchers. Both maps are gitDir-keyed, so clearing only the resolution cache would leak the watchers' fds.
- Add a debug logger and warn on the unexpected paths (fs.watch synchronous throw, FSWatcher 'error'). The common silent fallbacks (not a repo, no HEAD) stay quiet so a status-line read can't log-spam.
- useGitBranchName: guard the watcher-triggered void refresh() with .catch() — the synchronous try/catch inside watchRepoBranch can't observe an async rejection.

* fix(core): bound the HEAD read, cap ref length per-component, guard fs.constants

- readGitHead: read a bounded 4 KB prefix and parse only the first line instead of loading the whole file — a pathologically large HEAD can no longer be read into memory.
- isValidRefName: the length cap is now per slash-separated component (git's actual filesystem limit), not the whole ref — a valid deeply-nested ref longer than 255 total is no longer wrongly rejected.
- Access fs.constants via optional chaining (O_RDONLY/O_NOFOLLOW/F_OK), matching gitDiff.ts, so a mock or platform without `constants` can't throw.

* fix(core): bound/O_NOFOLLOW the commondir read; block bidi/zero-width + more ref rules

- Share a readFirstLineNoFollow helper between HEAD and commondir, so the commondir read is now also bounded (4 KB) + O_NOFOLLOW — a crafted oversized or symlinked commondir can no longer OOM the status-line path or redirect the validity check out of the repo.
- isValidRefName also rejects: bidi-override (U+202A-202E, U+2066-2069) and zero-width (U+200B-200D, U+FEFF) characters (display spoofing); a slash-separated component ending in a dot (git check-ref-format); and the literal name 'HEAD' (ambiguous with a detached HEAD, which git rejects as a branch).

* fix(core): O_NONBLOCK against FIFO hangs, swallow close errors, test commondir symlink

- readFirstLineNoFollow: open with O_NONBLOCK so a crafted FIFO .git/HEAD or commondir can't block indefinitely and pin a libuv thread-pool slot (the old git rev-parse path had subprocess timeouts; the direct read had none). And `await fh.close().catch(() => {})` so a close error (EIO / stale NFS handle) can't escape — the helper promises null on any failure — matching gitDiff.ts / fileHistoryService.ts.
- Test: a symlinked commondir is refused via O_NOFOLLOW, closing the coverage gap alongside the existing symlinked HEAD and reflog tests.
2026-06-21 07:09:19 +08:00
tt-a1i
8553013ee8
test(core): wait for cron lock probe takeover (#5535) 2026-06-21 06:40:42 +08:00
tt-a1i
8be8ef3e27
fix(cli): handle truncated remote input files (#5473)
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-06-21 06:39:03 +08:00
Thibault Jaigu
a6e206c887
feat(core): add Requesty provider (#5478)
* feat(core): add Requesty provider

Requesty (https://requesty.ai) is an OpenAI-compatible model gateway that uses
the same provider/model identifier format as OpenRouter, so it is added by
mirroring the existing OpenRouter provider.

- RequestyOpenAICompatibleProvider + isRequestyProvider detection, mirroring
  the OpenRouter provider (base https://router.requesty.ai/v1, attribution headers)
- register in the openaiContentGenerator dispatch + provider preset registry
- auth migration entry + docs (auth, model-providers)

Signed-off-by: Thibault Jaigu <thibault.jaigu@gmail.com>

* fix(core): harden Requesty provider hostname detection

Address review feedback on isRequestyProvider:

- Replace substring matching (baseURL.includes) with URL-parsed hostname
  detection (host === 'router.requesty.ai' || host.endsWith('.requesty.ai')),
  matching the ownsModel gate in presets/requesty.ts and the MiMo/MiniMax/
  Mistral providers. Rejects crafted URLs like router.requesty.ai.evil.com.
- Add hostile-hostname rejection tests and determineProvider dispatch tests,
  matching the coverage in the other provider suites.

---------

Signed-off-by: Thibault Jaigu <thibault.jaigu@gmail.com>
2026-06-21 06:37:24 +08:00
tt-a1i
ca7638e85c
fix(desktop): allow double dots in bundle filenames (#5515) 2026-06-21 06:16:50 +08:00
Yufeng He
8b6ac721fa
fix(core): don't treat an empty-parts message as a function call/response (#5494)
isFunctionResponse / isFunctionCall use parts.every(...), which is vacuously
true for an empty parts array — so a user (or model) message with parts: []
was reported as an all-function-responses (or all-function-calls) turn. In
checkNextSpeaker that makes an empty user turn look like a function-response
turn and hands the next turn to the model with the wrong reasoning. Require at
least one part before the every() check so an empty message is neither.
2026-06-21 06:05:33 +08:00
tt-a1i
83f51469cf
fix(desktop): validate generic oauth token responses (#5511) 2026-06-21 06:02:20 +08:00
tt-a1i
2a7753658c
fix(desktop): parse server ports strictly (#5509) 2026-06-21 06:02:00 +08:00
Yufeng He
234777c44b
fix(extension): accept uppercase URL schemes in Claude plugin sources (#5461)
* fix(extension): accept uppercase URL schemes in Claude plugin sources

resolvePluginSource compared a string plugin source against 'http://' and
'https://' case-sensitively, so a marketplace.json source such as
'HTTPS://github.com/owner/repo' fell through to local-path handling and
failed with "Plugin source not found". Lowercase the source before the
scheme check, matching #5426 / #5429 / #5439.

* test: return a GitHubDownloadResult from the download mock

The mockImplementation returned void, which tsc --build rejected (TS2345)
even though vitest passed. Return a GitHubDownloadResult so the build is clean.

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-06-21 06:01:09 +08:00
tt-a1i
e4fdd24217
fix(desktop): restore locale parity (#5537) 2026-06-21 06:00:52 +08:00
tt-a1i
0c65dbcaa1
test(desktop): update blocked scheme open-url assertion (#5529) 2026-06-21 05:59:43 +08:00
tt-a1i
cfe55e9147
fix(cli): reject partial cpu profile durations (#5486) 2026-06-21 03:03:29 +08:00
tt-a1i
7e66b1710e
fix(cli): wire ACP model-invocable commands (#5504) 2026-06-21 02:27:27 +08:00
胡玮文
0dc76bc7d4
fix(core): add missing Token Plan models (qwen3.7-plus, glm-5.2, kimi-k2.7-code) (#5505)
The Token Plan preset was missing three models that are available on the
ModelStudio Token Plan endpoint: qwen3.7-plus, glm-5.2, and kimi-k2.7-code.
Users who had these models installed would see a "Built-in Provider Update"
prompt offering to remove them.

- Add qwen3.7-plus (1M context, thinking, image+video) — already existed in
  the Coding Plan preset but was missing from Token Plan
- Add glm-5.2 (1M context, thinking) — already existed in the Z.AI preset
  but was missing from both Alibaba plan presets
- Add kimi-k2.7-code (256K context, always-on thinking, image+video) —
  a new Kimi coding model not present in any preset

Also widen the modalityDefaults pattern for Kimi from /^kimi-k2\.5/ to
/^kimi-k2\./ since k2.5, k2.6, and k2.7 all support image+video input
per the Kimi documentation.
2026-06-21 02:24:02 +08:00
tt-a1i
45c15db56f
fix(extensions): handle uppercase npm registry schemes (#5437) 2026-06-21 02:20:02 +08:00
tt-a1i
009c9919c0
fix(serve): validate session reaper timeouts (#5484) 2026-06-21 02:18:33 +08:00
tt-a1i
8368e7fb06
fix(desktop): parse NO_PROXY ports strictly (#5498) 2026-06-21 02:16:59 +08:00
tt-a1i
172288bc03
fix(desktop): preserve uppercase favicon URLs (#5463) 2026-06-21 02:14:21 +08:00
tt-a1i
64cc9f64ac
fix(cli): enforce temp path boundaries for at-file (#5446) 2026-06-21 02:13:09 +08:00
tt-a1i
d3d4992b20
fix(core): match provider base URL slash variants (#5448) 2026-06-21 02:10:56 +08:00
tt-a1i
3d98a6e010
fix(core): reject fractional computer-use integers (#5500) 2026-06-21 02:10:00 +08:00
tt-a1i
26c13091d4
fix: accept uppercase endpoint URL schemes (#5443)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Waiting to run
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
2026-06-21 02:09:29 +08:00
tt-a1i
4d2724005b
fix(cli): respect installation path boundaries (#5441) 2026-06-21 02:07:40 +08:00
tt-a1i
295860db24
fix(telegram): clear typing intervals on disconnect (#5477) 2026-06-21 01:52:57 +08:00
tt-a1i
cac178aa49
fix(cli): reject partial session size values (#5475) 2026-06-21 01:52:41 +08:00
tt-a1i
cf3e57fa50
fix(desktop): accept uppercase icon URL schemes (#5470) 2026-06-21 01:52:01 +08:00
tt-a1i
fc15ae6cb7
fix(dingtalk): skip uppercase webhook reaction targets (#5466) 2026-06-21 01:51:48 +08:00
tt-a1i
95ff853d61
fix(cli): enforce custom theme home boundary (#5456) 2026-06-21 01:50:42 +08:00
tt-a1i
e28cfb4741
fix(core): parse tool concurrency env strictly (#5496) 2026-06-21 01:49:14 +08:00