Files
qwen-code/packages
易良andQwen-Coder 32a25efa6e fix(cli): resolve ACP core settings against the active target dir (#11647)
* fix(cli): resolve ACP core settings against the active target dir

The `qwen/settings/getCore` and `qwen/settings/setCoreValue` handlers in
`extMethodInternal` fell back to `process.cwd()` when no explicit `cwd`
was passed. In a worktree-backed ACP session the daemon runs from the
project root, so workspace-scoped settings were written to
`<projectRoot>/.qwen/settings.json` instead of the worktree's own
`.qwen/settings.json`.

Fall back to `config.getTargetDir()` instead, which
`relocateWorkingDirectory()` keeps pinned to the active session
workspace (including worktrees). An explicit `cwd` still wins.

Fixes #8138

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-issue-patrol/jmtwqd66dxl

* fix(cli): resolve ACP settings from the requesting session's target dir

The `qwen/settings/*` + `qwen/permissions/*` handlers resolved their
workspace root from `process.cwd()` or the daemon's bootstrap
`this.config.getTargetDir()`, both of which stay pinned to the boot
workspace. A worktree session relocates *its own* Config, so
workspace-scoped writes landed in `<projectRoot>/.qwen/settings.json`
instead of the worktree's own `.qwen/settings.json` (#8138).

Resolve the workspace root from `session.getConfig().getTargetDir()`
(falling back to the bootstrap Config when the client sends no
`sessionId`) and apply it uniformly across the settings/permissions
surface. An explicit `cwd` still wins.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtwvd7nnxt

* fix(cli): consolidate duplicate @qwen-code/qwen-code-core imports

Merge ExtensionManager and the type-only Config/GoalSnapshotV2 into the
single existing value import so import/no-duplicates passes under
--max-warnings 0.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-conflict/jmtwy83e5xx

* fix(cli): resolve getMemoryPaths against the active session target dir

Align qwen/settings/getMemoryPaths with its getMemory/setMemory siblings by resolving through settingsCwdFor, so the project memory file points at the requesting session's worktree rather than the daemon boot dir. Correct the settingsCwdFor doc comment, which overstated the no-sessionId fallback as preserving process.cwd() behaviour.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtxadc55yg

* fix(acp): fail loud on unresolvable sessionId, drop per-request env injection

settingsCwdFor previously treated an unresolvable sessionId as absent,
silently retargeting every qwen/settings/* and qwen/permissions/* read and
write to the daemon's bootstrap workspace (a workspace the caller never
selected, with its own trust decision). Reject an unresolvable id with
errorKind session_not_found instead, keeping the no-sessionId fallback that
the workspace-service setRules path depends on.

The twelve rewired handlers also called loadSettings without options, so a
session-scoped read injected the foreign workspace's .env into the daemon's
process-wide process.env. Route them through a shared loadRequestSettings
that sets skipLoadEnvironment (and states consumeCorruptionEnvVars
explicitly, since an options object replaces loadSettings' = true default).

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtxixz7myu

* fix(cli): scope ACP settings adoption and permission sync to the session workspace

Resolve the requesting session's stable workspace root (its bound storage)
instead of the live cwd, so session/cd no longer repoints the twelve settings
handlers at a subdirectory holding no .qwen/settings.json (R2-2). Only publish
a freshly loaded LoadedSettings into the process-wide this.settings cache when
it was loaded for the daemon's own workspace, so a bare sessionId read cannot
repoint the daemon-global consumers (R2-3). Scope syncLivePermissionManagers
fan-out to the workspace a workspace-scoped setRules write landed in, instead
of broadcasting the delta to every live session (R2-5).

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtxn8asoz1

* fix(cli): honor explicit cwd and reject malformed sessionId in ACP settings resolver

Reorder `settingsCwdFor` so an explicit request `cwd` wins before the
`sessionId` lookup, fixing the R2-1 regression where a request that names
its workspace is rejected over a field the resolver never uses. Distinguish
a present-but-malformed `sessionId` (number/object/empty string) from an
absent one so it fails with `invalidParams` instead of silently retargeting
the bootstrap workspace (R3-3). Restore the lost fallback-branch pin and add
the both-fields and malformed-sessionId cases the review asked for (R3-4).

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtxrimarz8

* fix(cli): name and pin the session workspace root, and cover the permission fan-out filter

Extract sessionWorkspaceRoot(config) to give the admission-bound storage
root (storage.getProjectRoot()) a name distinct from the live getTargetDir()
and Config.getProjectRoot(), and route settingsCwdFor and the workspace-scoped
permission fan-out through it. This is a pure refactor of the two inline reads.

Add two regression cases that pin the fan-out filter against the two mutants
that previously survived green: a user-scoped setRules delta must still reach
every live session across workspaces (the Workspace guard must stay
conditional on scope), and the requesting session must survive its own filter
after a session/cd moved its live cwd into a subdirectory (the filter must key
on the stable root, not getTargetDir()).

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmty039h3zl

* fix(cli): keep MCP/hook/extension writes on the workspace-global coordinate

The MCP status/apply routes (buildManagedWorkspaceMcpStatus,
reloadWorkspaceMcpDiscovery) and the hooks/extensions status routes resolve
only the bootstrap workspace. Resolving sessionId on the write side made a
handler answer "saved" for a server no route would ever list or apply, so
setMcpServer, removeMcpServer, setHook, removeHook and setExtensionSetting
now resolve only an explicit cwd (R3-2, option b). Session-aware resolution
stays where write and read-back share the coordinate (core settings, memory,
permissions).

Co-authored-by: Qwen-Coder <[email protected]>

* fix(cli): canonicalize workspace path spellings in permission fan-out

syncLivePermissionManagers and adoptRequestSettings compared workspace
coordinates with path.resolve only, so a grant revoked under a different
spelling of the same directory (symlinked /tmp vs /private/tmp, or a
different case) never reached the live session's permission manager while
the caller was told it saved — fail-open. Canonicalize both sides with
realpathSync.native (path.resolve fallback) plus a darwin/win32 case-fold.

Also treat a present-but-empty cwd as absent in the five workspace-global
settings handlers (|| instead of ??), matching the session-aware handlers.

Co-authored-by: Qwen-Coder <[email protected]>

* style(cli): apply prettier formatting to the ACP agent files

The Prettier lane flags both files touched by this PR (a JSDoc continuation
line and an over-width path.join call), which fails Lint & Static before any
later step runs.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-conflict/jmu0kyvtn3f

* fix(cli): rely on realpath casing instead of folding case in workspace comparison

realpathSync.native() already returns the on-disk casing on macOS and
Windows, so the toLowerCase() on top was redundant on case-insensitive
volumes and actively wrong on case-sensitive ones: proj and PROJ are
genuinely different workspaces there, and folding authorised one under a
rule written for the other. Also pin the removal direction of the
cross-spelling fan-out and clean up the test symlink.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-09-14 03:51:58 +00:00
..